全力で怠けたい

怠けるために全力を尽くしたいブログ。

`go build` した Go のバイナリを実行すると「/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found`」を出力して止まるときにやったこと。

go build した Go のバイナリを実行すると次のメッセージを出力して止まるときにやったことのメモ。

/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found`

「/lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.32' not found」を出力して止まる

やったこと

go build した Go のバイナリを Docker イメージに入れて実行すると「/lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.32' not found」を出力して止まってしまうようだった。 Docker のベースイメージは distroless の debian を使っているのだけど Docker イメージに入れてる Go のバイナリを go build したマシン (Arch Linux) で実行するとちゃんと動くので、おそらく go build するときの設定がよろしくない可能性が高そう。

結論

go build した Go のバイナリを実行すると出力するメッセージでググったら一発だった。

right, you ran into this issue because your build machine (arch) is running newer glibc compared to your deployment host ( ubuntu ), usually, if it was otherway around it would have worked without any issues.

go build するマシンの glibc のバージョンが go build した Go バイナリを動かすマシンの glibc のバージョンより新しいとこの事象が発生するようなので go build するマシンの glibc のバージョンを下げるか go build した Go バイナリを動かすマシンの glibc のバージョンを上げるかしたら事象は解消しそう。 ただ glibc のバージョンを変えると影響が非常に大きくなると思うので go build するマシンの glibc のバージョンはおいそれと上げられないし go build した Go バイナリを動かすマシンの glibc のバージョンを上げるのも少し手間がかかりそう。

結論としては go build するときに CGO_ENABLED=0 のように指定して cgo を無効にして Go バイナリをビルドすればいい。

CGO_ENABLED=0 go build みたいな感じでビルドした Go のバイナリを Docker イメージに入れて実行すると前述の /lib/x86_64-linux-gnu/libc.so.6: version 〜 not found のメッセージが出なくなった。よさそう。

参考サイト

url-anchor コマンドが URL を標準入力から読み込めるようになりました。

url-anchor コマンドが v1.1.0 にバージョンアップして URL を標準入力から読み込めるようになりました。

URL を標準入力から読み込めるように

url-anchor コマンドは v1.1.0 より前は標準入力からの読み込みに対応していなくて、複数の URL を処理するときは xargs コマンドとかと組み合わせてこんな感じに書く必要がありました。

$ cat << EOF | xargs -n1 url-anchor
https://www.kantei.go.jp
https://www.whitehouse.gov
EOF
<a href="https://www.kantei.go.jp">首相官邸ホームページ</a>
<a href="https://www.whitehouse.gov">The White House</a>

url-anchor コマンドは v1.1.0 で標準入力からの URL 読み込みに対応しているので、複数の URL を処理するときは普通にパイプで url-anchor コマンドに流し込めるようになっています。

$ cat << EOF | url-anchor -
https://www.kantei.go.jp
https://www.whitehouse.gov
EOF
<a href="https://www.kantei.go.jp">首相官邸ホームページ</a>
<a href="https://www.whitehouse.gov">The White House</a>

CLI ツールの出力を JSON にする jc コマンドの使い方のメモ。

Twitter で見かけた jc コマンドが便利そうなので少し使ってみたメモ。

jc コマンドのバージョン。

$ jc -a | jq -r .version
1.13.4

jc コマンドの使い方

jc コマンドとは

jc JSONifies the output of many CLI tools and file-types for easier parsing in scripts.

jc コマンドはいろいろな CLI ツールとかファイルタイプの出力を JSON 化するので jc コマンドの出力をパイプで jq コマンドに渡して簡単にフィルタリングしたり加工することができる。 なんとなくだけど WindowsPowerShell に近いイメージ。PowerShell はコマンドの出力そのものがオブジェクトだから JSON とはぜんぜん違うけど、フラットなテキストを JSON に構造化するだけでもかなり強力。

jc コマンドの使い方をいくつか書いてみる。

特定のファイルサイズ以上のファイルだけを出力する。

$ ls -l /usr/bin | jc --ls | jq '.[] | select(.size > 50000000)'

{
  "filename": "docker",
  "flags": "-rwxr-xr-x",
  "links": 1,
  "owner": "root",
  "group": "root",
  "size": 68677120,
  "date": "Aug 14 19:41"
}

CSV の出力を JSON 化する。

$ cat << EOF | jc --csv -p
name,age
Tanaka,20
Takahashi,40
EOF
[
  {
    "name": "Tanaka",
    "age": "20"
  },
  {
    "name": "Takahashi",
    "age": "40"
  }
]

ping コマンドの出力を JSON 化する。

$ ping www.google.com -c 2 | jc --ping -p
{
  "destination_ip": "216.58.197.228",
  "data_bytes": 56,
  "pattern": null,
  "destination": "www.google.com",
  "packets_transmitted": 2,
  "packets_received": 2,
  "packet_loss_percent": 0.0,
  "duplicates": 0,
  "round_trip_ms_min": 33.183,
  "round_trip_ms_avg": 38.236,
  "round_trip_ms_max": 43.29,
  "round_trip_ms_stddev": 5.053,
  "responses": [
    {
      "type": "reply",
      "bytes": 64,
      "response_ip": "216.58.197.228",
      "icmp_seq": 0,
      "ttl": 114,
      "time_ms": 33.183,
      "duplicate": false
    },
    {
      "type": "reply",
      "bytes": 64,
      "response_ip": "216.58.197.228",
      "icmp_seq": 1,
      "ttl": 114,
      "time_ms": 43.29,
      "duplicate": false
    }
  ]
}

インストール方法

jc コマンドの README の Installation を見ると apt-get とか pacman とかいろいろなパッケージマネージャーに対応しているっぽい。

自分は Mac を使っているので Homebrew でインストールした。

$ brew install jc

使い方

jc コマンドは標準入力のパイプから受け取った入力を JSON にして標準出力に出力するのでこんな感じに使っていく。

$ COMMAND | jc PARSER [OPTIONS]

jc コマンドは "magic" 構文というのが使えるのだけど、↑のコマンドは "magic" 構文ではこんな感じに使っていく。

$ jc [OPTIONS] COMMAND

通常の構文でも "magic" 構文でも手に馴染むほうを使うでよさそうだけど "magic" 構文はコマンドのエイリアスをサポートしていないのと "magic" 構文は jc コマンドのオプションはコマンドの前に指定する必要があるのは気をつけていく。

"magic" 構文はコマンドのエイリアスをサポートしていないので ll='ls -l' みたいなエイリアスがあるときに ll | jc --ls はちゃんと動くけど jc ll はエラーになる。

あと "magic" 構文は jc コマンドのオプションはコマンドの前に指定する必要があるので jc -p ls はちゃんと動くけど、jc ls -p-p が ls コマンドのオプションとして解釈してしまって意図通りには動かない。

-p オプション: JSON の出力をフォーマットする

-p オプションを指定すると JSON の出力をフォーマットする。

$ ls
a.txt  b.txt

$ ls | jc --ls
[{"filename": "a.txt"}, {"filename": "b.txt"}]

$ ls | jc --ls -p
[
  {
    "filename": "a.txt"
  },
  {
    "filename": "b.txt"
  }
]

-m オプション: JSON をモノクロ出力する

-m オプションを指定すると JSON をモノクロで出力する。

jc コマンドはデフォルトは JSON をカラー化して出力するけど -p オプションを指定すると JSON をカラー化せずに出力する。

-r オプション: JSON を raw 出力

-r オプションを指定すると JSON のすべての値を文字列として出力する。

-r オプションを指定しないとき。

$ ls -l | jc --ls -p
[
  {
    "filename": "a.txt",
    "flags": "-rw-r--r--",
    "links": 1,
    "owner": "ebi",
    "group": "staff",
    "size": 0,
    "date": "9 25 23:30"
  },
  {
    "filename": "b.txt",
    "flags": "-rw-r--r--",
    "links": 1,
    "owner": "ebi",
    "group": "staff",
    "size": 0,
    "date": "9 25 23:30"
  }
]

-r オプションを指定するとき。

$ ls -l | jc --ls -p -r
[
  {
    "filename": "a.txt",
    "flags": "-rw-r--r--",
    "links": "1",
    "owner": "ebi",
    "group": "staff",
    "size": "0",
    "date": "9 25 23:30"
  },
  {
    "filename": "b.txt",
    "flags": "-rw-r--r--",
    "links": "1",
    "owner": "ebi",
    "group": "staff",
    "size": "0",
    "date": "9 25 23:30"
  }
]

-a オプション: jc コマンドの使い方を出力する

-a オプションを指定すると jc コマンドの使い方とか利用できるパーサーとか author とかバージョンを出力する (出力はもちろん JSON)

$ jc -ap | head -n 25
{
  "name": "jc",
  "version": "1.13.4",
  "description": "JSON CLI output utility",
  "author": "Kelly Brazil",
  "author_email": "kellyjonbrazil@gmail.com",
  "parser_count": 57,
  "parsers": [
    {
      "name": "airport",
      "argument": "--airport",
      "version": "1.1",
      "description": "airport -I command parser",
      "author": "Kelly Brazil",
      "author_email": "kellyjonbrazil@gmail.com",
      "compatible": [
        "darwin"
      ],
      "magic_commands": [
        "airport -I"
      ]
    },
    {
      "name": "airport_s",
      "argument": "--airport-s",

互換性

jc コマンドのパーサーはたくさんあって ls, ps, dig みたいなパーサーはどんなプラットフォームのコマンドの出力もパースできるけど、コマンドの出力がプラットフォームによって変わってくるとちゃんとパースできないよう。 このへんは jc -a の出力を jq でフィルタすると対象のプラットフォームで利用可能なパーサーが確認できる。

たとえば Windows で利用可能なパーサーはこんな感じで確認していく。

$ jc -a | jq -r '.parsers[] | select(.compatible | contains(["win32"])) | .name'
csv
env
hosts
ini
kv
pip_list
pip_show
xml
yaml

プラットフォームは Linux が一番多くのパーサーに対応しているっぽい。

$ jc -a | jq -r '.parsers[] | select(.compatible | contains(["linux"])) | .name' | wc -l
      55

$ jc -a | jq -r '.parsers[] | select(.compatible | contains(["darwin"])) | .name' | wc -l
      39

$ jc -a | jq -r '.parsers[] | select(.compatible | contains(["win32"])) | .name' | wc -l
       9

$ jc -a | jq -r '.parsers[] | select(.compatible | contains(["cygwin"])) | .name' | wc -l
      16

その他

jc は 色を変える とか カスタムパーサー を使うこともできるよう。

シェルで小数点以下を四捨五入して整数にするやり方のメモ。

シェルで小数点以下を四捨五入して整数にするやり方のメモ。

シェルで四捨五入するやり方

シェルでたまに小数点以下を四捨五入して整数にするけどたまにすぎて四捨五入のやり方を忘れて毎回調べてる気がするのでやり方をメモしておく。

awk を使って四捨五入する

awk を使ってこんな感じに四捨五入する。

$ echo 1.4 | awk '{printf("%d\n", $1 + 0.5)}'
1

$ echo 1.5 | awk '{printf("%d\n", $1 + 0.5)}'
2

printf 関数を使って四捨五入する

Bashawk を使わないでも組み込みの printf 関数でこんな感じで小数点以下を四捨五入して整数にする。

$ printf '%.0f\n' 1.4
1

$ printf '%.0f\n' 1.5
2

参考サイト

ECS の Fargate で動かしてるコンテナが socket: too many open files で外部のサーバーに接続できないときにやったこと。

ECS の Fargate で動かしてるコンテナが socket: too many open files で外部のサーバーに接続できないときにやったことのメモ。

ECS で動かしてるコンテナが外部のサーバーに接続できなくなる

やったこと

ことの始まりはとある ECS の Fargate で動かしてるコンテナが突然外部のサーバーに接続できなくなるのに気が付いたときだった。

問題のコンテナは DB とかキャッシュで使ってるサーバーに接続していろいろやるのだけど、どうも問題のコンテナから DB サーバーとかキャッシュサーバーに接続できなくなることがあるようだった。 問題のコンテナからは常に DB とかの外部サーバーに接続できないわけではなくてちゃんとそのへんの外部サーバーに接続できている時間帯があるようなのだけど、接続できたり接続できなかったりという感じで時間帯と接続可否の状態には関連性はないっぽい。 問題のコンテナはいろいろと動作検証に使ってるけど外部には公開していないので、おそらく動作検証のなにかが影響して外部のサーバーに接続できなくなっている可能性が高い。

結論

コンテナのログを見たら一発で原因が分かった。

コンテナのログは外部サーバーに接続できない時間帯は socket: too many open files が大量に出力されていて、たぶんこれはコンテナ使うファイル数がファイルディスクリプタの制限に引っかかっていそう。

コンテナで設定する ulimits のリスト。このパラメータは、Docker Remote API の コンテナを作成する セクションの Ulimits と、docker run の --ulimit オプションにマッピングされます。

Fargate タスクは、デフォルトのリソース制限値を使用します。ただし、Fargate がオーバーライドする nofile リソース制限パラメータを除きます。nofile リソース制限は、コンテナが使用できるオープンファイルの数の制限を設定します。デフォルトの nofile ソフト制限は 1024、ハード制限は Fargate タスク用で 4096 です。これらの制限は、タスクでより多くのファイルを処理する必要がある場合は、タスク定義で調整できます。

コンテナは CDK で構築してるので ContainerDefinition#addUlimits()UlimitName.NOFILEsoftLimithardLimit が大きくなるようにこんな感じで設定した。

このあとコンテナを ECS にデプロイしたら socket: too many open files がログに出力することがなくなって問題のコンテナからちゃんと外部のサーバーに接続できるようになった。

container.addUlimits({
  name: UlimitName.NOFILE,
  softLimit: 6144,
  hardLimit: 12288,
});

参考サイト

Lambda を定期実行する CloudWatch イベントルールを作る CDK のスニペット。

Lambda を定期実行する CloudWatch イベントルールを作る CDK のスニペットを書いておく。

Lambda を定期実行する CloudWatch イベントルールを作る CDK のスニペット

Lambda を定期実行する CloudWatch イベントルールを作る CDK のスニペットを書いておく。

CDK のバージョン。

$ cdk --version
1.62.0 (build 8c2d7fc)

CDK のコード

CDK アプリはこんな感じのディレクトリ構成を想定してる。

├── bin
│   └── cdk-event-rule.ts
├── cdk.json
├── cdk.out
├── lib
│   └── cdk-event-rule-stack.ts
│
(省略)

bin/cdk-event-rule.ts はこんな感じに書いていく。

import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { CdkEventRuleStack } from '../lib/cdk-event-rule-stack';

const app = new cdk.App();
new CdkEventRuleStack(app, 'CdkEventRuleStack');

lib/cdk-event-rule-stack.ts はこんな感じに書いていく。

Lambda 関数は1分毎に Hello World! をログに出力するだけのやつ。 あと Lambda 関数の構築で deprecated な lambda.Code.inline() を使ってるけど実際に使うときはこのへんは適宜 deprecated じゃないメソッドを使うようにしていく。

import * as cdk from '@aws-cdk/core';
import * as events from '@aws-cdk/aws-events';
import * as targets from '@aws-cdk/aws-events-targets';
import * as lambda from '@aws-cdk/aws-lambda';

export class CdkEventRuleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const fn = new lambda.Function(this, `my-lambda-function`, {
      functionName: "hello-world",
      code: lambda.Code.inline(`

def lambda_handler(event, context):
    print("Hello World!")
      `),
      handler: "index.lambda_handler",
      runtime: lambda.Runtime.PYTHON_3_7,
    });

    const target = new targets.LambdaFunction(fn);
    new events.Rule(this, `my-event-rule`, {
      ruleName: 'every-minute',      schedule: events.Schedule.cron({minute: '*/1'}),
      targets: [target],
    });
  }
}

構築したリソースと Lambda 関数の実行ログの確認

CDK アプリをデプロイしたら Lambda 関数と CloudWatch イベントルールが作られてるのを確認してく => ちゃんと作られてそう。

# Lambda 関数
$ aws lambda get-function-configuration --function-name='hello-world' | jq -r '.FunctionName'
hello-world

# CloudWatch イベントルール
$ aws events describe-rule --name='every-minute'
{
    "Name": "every-minute",
    "Arn": "arn:aws:events:<region>:<account-id>:rule/every-minute",
    "ScheduleExpression": "cron(*/1 * * * ? *)",
    "State": "ENABLED",
    "EventBusName": "default"
}

Lambda 関数と CloudWatch イベントルールをデプロイして2, 3分くらい待ったら Lambda 関数の実行ログが CloudWatch に出力してるか確認していく。 こんな感じで Hello World! が出力してて Lambda 関数はちゃんと動いてそう。

$ LOG_STREAM=$(aws logs describe-log-streams --log-group-name='/aws/lambda/hello-world' | jq -r '.logStreams[0].logStreamName')
$ aws logs get-log-events --log-group-name='/aws/lambda/hello-world' --log-stream-name="${LOG_STREAM}" | jq -r '.events[].message' | grep 'Hello World!'
Hello World!
Hello World!
# 省略

以上。

シェルの case でよく使うような気がするやつを軽くメモ。

シェルの case でよく使うような気がするやつを軽くメモしておく。

シェルの case でよく使うような気がするやつ

シェルは case を使うと Java とか Go の switch みたいなことができる。

シェルの case でよく使うような気がするやつを軽くメモしておく。

case の基本的な書き方

case の基本的な書き方を書いておく。

case は case で始めて esac で終わる。

case 文字列 in
    パターン1) 文字列がパターン1に合致するときのコマンド ;;
    パターン2) 文字列がパターン2に合致するときのコマンド ;;
    *) 文字列がどのパターンにも合致しないときのコマンド ;;
esac

こんな感じのシェルスクリプトを用意して実際に動かしてみる。

case $1 in
    hoge) echo 'Hello hoge!' ;;
    fuga) echo 'Hello fuga!' ;;
    *) echo 'Hello *' ;;
esac
$ ./my-case.sh hoge
Hello hoge!

$ ./my-case.sh fuga
Hello fuga!

$ ./my-case.sh piyo
Hello *

パターンは大文字と小文字を区別するので HOGE という文字列は hoge というパターンには合致しない。

$ ./my-case.sh HOGE
Hello *

パターンとパターンに合致するときのコマンドの間には改行を入れることができる。

case $1 in
    hoge)
        echo 'Hello hoge!' ;;
    fuga)
        echo 'Hello fuga!' ;;
    *)
        echo 'Hello *' ;;
esac

パターンに合致するときのコマンドは1つだけじゃなくて複数のコマンドが書ける。

case $1 in
    hoge)
        echo 'hello hoge!'
        echo 'hello hoge!'
        ;;
    fuga)
        echo 'hello fuga!'
        echo 'hello fuga!'
        ;;
    *)
        echo 'hello *'
        echo 'Hello *'
        ;;
esac

パターンに合致するときのコマンドが複数のコマンドのときは ; を使うとまとめて1行に書ける。

case $1 in
    hoge)
        echo 'hello hoge!' ; echo 'hello hoge!'
        ;;
    fuga)
        echo 'hello fuga!' ; echo 'hello fuga!'
        ;;
    *)
        echo 'hello *' ; echo 'Hello *'
        ;;
esac

一応、こんな感じに1行にまとめて書ける。

case $1 in
    hoge) echo 'hello hoge!' ; echo 'hello hoge!' ;;
    fuga) echo 'hello fuga!' ; echo 'hello fuga!' ;;
    *) echo 'hello *' ; echo 'Hello *' ;;
esac

?: 任意の1文字に合致

パターンは ? を使うと任意の1文字に合致する。

case $1 in
    a?) echo "a で始まる2文字" ;;
    *) echo "*" ;;
esac
$ ./my-case.sh aa
a で始まる2文字

$ ./my-case.sh ab
a で始まる2文字

$ ./my-case.sh a
*

$ ./my-case.sh abc
*

*: 任意の複数文字に合致

パターンは * を使うと任意の複数文字に合致する。

case $1 in
    a*a) echo "a で始って a で終わる文字列" ;;
    *) echo "*" ;;
esac
$ ./my-case.sh aba
a で始って a で終わる文字列

$ ./my-case.sh aa
a で始って a で終わる文字列

$ ./my-case.sh aaaaa
a で始って a で終わる文字列

$ ./my-case.sh a
*

[]: [] で囲んでいる任意の文字に合致

[] で文字を囲むと [] で囲んでいる任意の文字に合致する。

case $1 in
    [aA]*[aA]) echo "a か A で始って a か A で終わる文字列" ;;
    *) echo "*" ;;
esac
$ ./my-case.sh aa
a か A で始って a か A で終わる文字列

$ ./my-case.sh Aa
a か A で始って a か A で終わる文字列

$ ./my-case.sh aA
a か A で始って a か A で終わる文字列

$ ./my-case.sh AA
a か A で始って a か A で終わる文字列

[a-c] みたいに書くと a から c までの範囲の任意の文字に合致する。

case $1 in
    [a-c]*[a-c]) echo "a か b か c で始って a か b か c で終わる文字列" ;;
    *) echo "*" ;;
esac
$ ./my-case.sh ab
a か b か c で始って a か b か c で終わる文字列

$ ./my-case.sh bc
a か b か c で始って a か b か c で終わる文字列

$ ./my-case.sh ca
a か b か c で始って a か b か c で終わる文字列

[a-zA-Z] みたいに書くと任意のアルファベットに合致する。

case $1 in
    [a-zA-Z]*[a-zA-Z]) echo "アルファベットで始ってアルファベットで終わる文字列" ;;
    *) echo "*" ;;
esac
$ ./my-case.sh aZ
アルファベットで始ってアルファベットで終わる文字列

[!]: [] で囲んでいない任意の文字に合致

[!] で文字を囲むと [!] で囲んでいない任意の文字に合致する。

case $1 in
    [!a]*) echo "a 以外で始まる文字列" ;;
    *) echo "*" ;;
esac
$ ./my-case.sh b
a 以外で始まる文字列

$ ./my-case.sh ba
a 以外で始まる文字列

$ ./my-case.sh a
*

$ ./my-case.sh ab
*

[!a-c] みたいに書くと a から c までの範囲以外の任意の文字に合致する。

case $1 in
    [!a-c]*) echo "a と b と c 以外で始まる文字列" ;;
    *) echo "*" ;;
esac
$ ./my-case.sh a
*

$ ./my-case.sh b
*

$ ./my-case.sh c
*

$ ./my-case.sh d
a と b と c 以外で始まる文字列

|: 複数のパターンのいずれかに合致

abc|xyz みたいに書くと abcxyz のいずれかに合致する。

case $1 in
    abc|xyz) echo "abc か xyz" ;;
    *) echo "*" ;;
esac
$ ./my-case.sh abc
abc か xyz

$ ./my-case.sh xyz
abc か xyz

$ ./my-case.sh aaa
*

[ab]* みたいなパターンは | を使って a*|b* みたいに書くことができる。

case $1 in
    a*|b* ) echo "a か b で始まる文字列" ;;
    *) echo "*" ;;
esac

$ ./my-case.sh b
a か b で始まる文字列

$ ./my-case.sh ab
a か b で始まる文字列

$ ./my-case.sh bc
a か b で始まる文字列

$ ./my-case.sh ca
*