HAProxy & socat を使ったオンライン切り離しと Aurora Reader Endpoint における一考
前置き
随分前に RDS Read Replicas や Aurora Replica に対する振り分けを HAProxy で振り分ける方法を投稿した。
この後に、 Aurora Reader endpoint が実装され、 RDS for Aurora を使っている場合に限り、 readers への振り分けに HAProxy などのバランサーをかますことは必須ではなくなっている。 ただ、 Aurora Reader Endpoint も万能ではなく HAProxy を介しておくと小回りが利くことが多いので紹介しておく。
注意
ここで紹介している挙動は 2017 年 5 月時点の自プロダクトで見たものである。 環境固有の要因あり、かつ確認した挙動も古かったり見解が間違っているかもしれないので、あくまで参考程度に留めてほしい。
また、この記事を書いているちょうど今 re:Invent 2017 が開催中で、それに乗じて Aurora に関する Updates も多く出ている。 ちょっと前、2017年11月17日に Aurora Replica の Auto Scaling が可能 にもなっており、当時と今とでは結構状況が変わっている可能性が高い点を留意してほしい。
私と RDS for Aurora
今関わっているプロダクトは 2017 年の 5 月頃にバックエンド DB を RDS for MySQL から RDS for Aurora に移行した。
進め方は概ねデファクトだったと思う。
一通りのテストを終えたあとで、事前に移行元の RDS for MySQL Cluster から Aurora Read Replicas を生やしておき、HAProxy の weight で割合制御して本番トラフィックの参照系クエリの一部を流すようにして、まず様子を見た。 徐々に Aurora に散らすクエリの割合を増やしていき、最終的に 旧 Replicas の weight を 0 した時に まず RDS for MySQL Read Replicas を一掃した。 あとは Master を RDS for MySQL から Aurora writer に切り替える移行作業で、これは他のサービスアップデートのためのデプロイと兼ねて、サービスのピーク帯を避けて2時間程度のメンテナンス時間を設定し書き込み停止点を確保して進めた。
事前にステージング環境で入念なシミュレーションを行っていたこともあり、移行はあっけなく終わった。 細かいところを述べると、その後で本番トラフィックの洗礼を受け、幾つかのクエリチューニングが追加で必要となったりもした。Aurora のバッドノウハウである大量レコードのシーケンシャルリードを行うスロークエリが移行後に顕在化したりして、その手のクエリは抹消したりトランザクション分離レベルを変えたりした。
並列性に優れ、コンピューティングリソースを有効活用してくれる Aurora は RDS for MySQL 時代と比べて 最大で 5 倍のスループット が出るということだが、当然ワークロードによって性能向上率は変わる。 うちの場合だと Aurora の性能を存分に活かしきれるほどのクエリチューニングやアプリケーションリファクタをしまくったわけではないので スループットで言うと多少上がった程度で収まっている。 ただ、設計上 Replica Lag が起きにくいアーキテクチャとなっており、今までだと Read Replicas がサチった場合に Replica Lag が膨れるという副次リスクを問題視していたので、この恩恵を存分に受けることができた。これだけでも移行するメリットがあると感じている。
話が逸れるが、 Aurora Storage の Quorum Model についてまとまった解説がなされており、読むととても面白い。
- Amazon Aurora under the hood: クオーラムと障害 | Amazon Web Services ブログ
- Amazon Aurora Under the Hood: クオーラムの読み取りと状態の遷移 | Amazon Web Services ブログ
- Amazon Aurora Under the Hood: クオーラムセットを使ったコスト削減 | Amazon Web Services ブログ
- Amazon Aurora Under the Hood: クオーラムメンバーシップ | Amazon Web Services ブログ
Aurora における HAProxy の使いどころ
Reader Endpoint がなかった頃は結局 すべての Node Endpoints で振り分けざるを得なく、 また Writer に参照クエリを散らさないためには read_only (innodb_read_only)
から識別するしか手がなかった。
以下、参考記事。
- HAProxy の external-check で Read Replica から Master に昇格した Amazon Aurora に接続できないようにする - tkuchikiの日記
- Amazon Aurora MySQL を使用する際のベストプラクティス - Amazon Relational Database Service
- SpeakerDeck: RDS for MySQLからAuroraに移行した話 / RDS for MySQL to Aurora
なので、このもやもや感を取っ払ってくれた Aurora Reader Endpoint はとても響いたリリースだった。
先程も述べたとおり、今は Aurora Reader endpoint があるので、参照クエリは Read Only なエンドポイントに向けてあげれば基本的には活性化している Aurora Replica にクエリが分散される。 各 Node の障害時は切り離されるし、 Writer 障害時に ある Reader が Writer に昇格すれば、その元 Reader には Reader endpoint 経由でクエリが流れてくることはなくなる。
ただ、いざ使ってみると必ずしも万能ではなく 2017 年 5 月当時に挙動を見る限りいくつか痒いところがあった。 なので、うちでは Reader Endpoint に身を委ねるのではなく、 HAProxy を引き続き間に挟むようにした。
背景と対処方法を紹介しよう。
1. Aurora readers の縮退時、しばらく削除した Reader Node にもクエリが振り分けられることがある
Cluster Endpoint や Reader Endpoint は DNS Endpoint で、ランダムに Reader Node IP を返却することで Readers の分散を実現している。 基本的には非活性な Node の IP は返さないし Reader から Writer に Failover した Node も返さない挙動だが、 DNS record となると DNS Cache や TTL が絡んでくるので、クエリを流すその瞬間に活性な Reader であることを保証してくれるものではない。 かつ、 Replica Node 削除時に実行中のクエリの終了を保証してくれるものでもないと思う。
うちの場合だと、Aurora 移行が一段落して性能もある程度恩恵を受けれるようになったので Aurora Readers を縮退 (削減) させるべく縮退時の挙動を当時見たのだが、 Reader を 幾つか Delete すると実際に何発か DB 接続エラーが出てしまった。
イレギュラーイベントな Writer 障害時の Failover であればまぁ仕方ないのだが、定型的なオペレーション時まで接続エラーが出るのは運用上ちょっと気持ちが悪い。 アプリケーションアラートが飛ぶので精神衛生上もよろしくない。
2. Replica Node の再起動をしたい時に困る
あまり頻度は高くないが、 Replica Node を再起動したい時がある。
例えば、DB Parameter Group を別のものに変更するような時。 特に、Aurora Replica の増設を画面 (RDS Console) で行った場合、Amazon Aurora DB クラスターの作成手順 に従って Cluster Indentifier を指定して Replica Node を追加することになるが、ここで DB Parameter Group を指定が出来ないのでデフォルトの DB Parameter Group (default.aurora) があたった形でReplica Node が起動してしまう。その場合、再起動を伴う DB Parameter Group の変更が必要となる。皆が AWS CLI や CloudFormation, Terraform などで操作していればこの点は困らないのだろうが。
また、Rebooting な Replica Node の IP を Reader Endpoint が返さないことを保証してくれるのか、当時ドキュメントを漁っても見あたらず、少なくとも当時軽く確認した限りでは再起動中の Replica の IP が変えるように見えた。 そして、いずれにしても再起動する以上、処理中のプロセスは kill される。
3. 回避策
なので、結局 HAProxy を間に挟んだ。ポイントは HAProxy の重み付けバランシングを利用するようにしつつ以下のように Backend Node を登録したこと。
- 通常は HAProxy 経由で Reader Endpoint に流す
- あわせて weight 0 な全 Node Endpoint を仕込んでおく
- socat を使ってソケット越しで設定変更できるように操作レベルを admin にしておく
例えばHAProxy の設定はこんな感じ。
こうすると、通常は weight が 1 : 0 : 0 : 0 : 0 なので Reader Endpoint 以外には振り分けられない。 縮退や増設などの構成変更を行う場合は、HAProxy の weight をオンラインで変更して HAProxy から各 Node Endpoint に round-rogin させ、 一方で Reader Endpoint 経由で振り分けないようにする。 socat を使った重み変更なので HAProxy プロセスの再起動/再読込は不要、処理中の接続が中断されることもないのでオンラインで作業できる。
具体的にはこのような流れとなる。
- 継続利用する Node Endpoint の weight を 1 にする
- Reader Endpoint 経由でクエリが流れないように Reader Endpoint の weight を 0 にする
- (縮退する場合は)削除予定の Replica Node にクエリが流れてこないことを確認する
- 構成変更作業を進める
- 構成変更作業が終わったら、 Reader Endpoint の weight を 1 に戻す
- さらに、 Node Endpoint の weight を 0 に戻す
HAProxy Node がいくつもあると操作が面倒なので、Ansible のようなオーケストレーションツールを使うと比較的ラクにオペレーションできると思う。 以下、read4 を削除するためのオンライン weight 変更操作を Ansible で実行する例を載せておく。
最後に
この半年ほどこのオペレーションで、Aurora Replica の台数変更を何回か行っており上手く回っている。 時間も経ってしまったのでいい加減記事におこしておこうと思っていたところで、Aurora の Updates が立て続けに出てきた。
- Amazon Aurora で Aurora レプリカの Auto Scaling をサポート
- Amazon Aurora Multi-Master のプレビュー申し込み開始 | Amazon Web Services ブログ
- In The Works – Amazon Aurora Serverless | Amazon Web Services ブログ
特に Aurora Replica の Auto Scaling サポートは大きい。 増強縮退を前提にしているのであればクエリを中断することもなく上手く処理中のタスクを吐き出してくれる気がするので、今回紹介したような HAProxy を挟む必要性は薄れるかもしれない。 まだ動作確認していないのであくまで希望的観測だが。
それにしても、今年も re:Invent 行きたかった。。
- メディア:
- この商品を含むブログを見る
Amazon Web Services完全ソリューションガイド
- 作者: 大澤文孝,川上明久,大嶋和幸
- 出版社/メーカー: 日経BP社
- 発売日: 2016/12/27
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
Lambda functions (Python) の 依存パッケージの保存場所を指定する
Lambda w/ Apex 関連でもう一つ TIPs を備忘録しておく。
Function hooks のおさらい
Apex には Function hooks という機構があり、 Apex で管理する Lambda function のライフサイクルの中で、特定のステップで任意の shell commands を発火させることができる。
以下は Apex.run に記載されている Sample だ。
Shell commands を指定できるので、当然 golang に限らずなんでも実行できる。当然 pip install
も可能。
hooks の種類は3種ある。
- build
- run before a function zip is built (use this to compile binaries or transform source)
- deploy
- run before a function is deployed (useful for testing, linting)
- clean
- run after a function is deployed (useful for cleaning up build artifacts)
なので、 Lamnbda Functions w/ Python の場合は以下のように build 時に pip install
して functions を梱包して deploy する function.json と requirements.txt を用意することが多いと思う。
pip install したパッケージ群のインストール先を変えたい
ただ、上の方法だと function directory に各パッケージがインストールされてしまう。 function 開発時において重要なものは function.json, Lambda の handler となる python code, あとは依存モジュール記す requirements.txt 。 その他のファイル群が同層に展開されるのは美しくないし、 gitignore 依存パッケージを一つ一つ指定する形になり面倒。
$ tree -L 4 . ├── README.mkd ├── functions │ └── helloworld-python │ ├── certifi │ ├── certifi-2017.7.27.1.dist-info │ ├── chardet │ ├── chardet-3.0.4.dist-info │ ├── function.dev.json │ ├── function.prd.json │ ├── function.stg.json │ ├── idna │ ├── idna-2.5.dist-info │ ├── main.py │ ├── requests │ ├── requests-2.18.2.dist-info │ ├── requirements.txt │ ├── urllib3 │ └── urllib3-1.22.dist-info ├── project.dev.json ├── project.prd.json └── project.stg.json
例えばこうしたい。
$ tree -L 4 . ├── README.mkd ├── functions │ └── helloworld-python │ ├── function.dev.json │ ├── function.prd.json │ ├── function.stg.json │ ├── main.py │ ├── requirements.txt │ └── site-packages │ ├── certifi │ ├── certifi-2017.7.27.1.dist-info │ ├── chardet │ ├── chardet-3.0.4.dist-info │ ├── idna │ ├── idna-2.5.dist-info │ ├── requests │ ├── requests-2.18.2.dist-info │ ├── urllib3 │ └── urllib3-1.22.dist-info ├── project.dev.json ├── project.prd.json └── project.stg.json
やりかた
ざっくり以下。
- function hooks を修正
- 環境変数 PYTHONPATH を指定
1. function hooks を修正
言うまでもないが、 pip install コマンドを微修正する。
- pip install -r requirements.txt -t ." + pip install -r requirements.txt -t ./site-packages"
2. 環境変数 PYTHONPATH を指定
site-packages を検索対象パスに追加する。
+ }, + "environment": { + "PYTHONPATH": "/var/runtime:/var/task/site-packages"
両方をまとめるとこうなる。
これで apex build
すれば、依存パッケージは ./site-packages/ 以下に保存されるようになり、かつ 依存パッケージにパスが通るようになる。
肝は
- 依存パッケージの path を /var/task/site-packages とすること
- /var/runtime も指定しておくこと
理由は以下に記す。
Lambda Functions を覗いてみる
pip install 時に指定した ./site-packages 以下のパッケージ群を検索対象するべくパスを通すために、展開先の挙動を把握しておく。
簡単な function を実行してみよう。
環境変数や実行ファイルのパスを除くだけのプログラムである。 この function のログはこんな感じとなる。
[INFO] 2017-08-31T04:29:53.519Z 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 $ENV: environ({'APEX_FUNCTION_NAME': 'python-sample','LAMBDA_FUNCTION_NAME': 'hello-apex_python-sample', 'PATH': '/var/lang/bin:/usr/local/bin:/usr/bin/:/bin', 'LANG': 'en_US.UTF-8', 'TZ': ':UTC', 'LD_LIBRARY_PATH': '/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib', 'LAMBDA_TASK_ROOT': '/var/task', 'LAMBDA_RUNTIME_DIR': '/var/runtime', 'AWS_REGION': 'ap-northeast-1', 'AWS_DEFAULT_REGION': 'ap-northeast-1', 'AWS_LAMBDA_LOG_GROUP_NAME': '/aws/lambda/hello-apex_python-sample', 'AWS_LAMBDA_LOG_STREAM_NAME': '2017/08/31/[12]xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'AWS_LAMBDA_FUNCTION_NAME': 'hello-apex_python-sample', 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE': '128', 'AWS_LAMBDA_FUNCTION_VERSION': '1', '_AWS_XRAY_DAEMON_ADDRESS': '169.254.79.2', '_AWS_XRAY_DAEMON_PORT': '2000', 'AWS_XRAY_DAEMON_ADDRESS': '169.254.79.2:2000', 'AWS_XRAY_CONTEXT_MISSING': 'LOG_ERROR', '_X_AMZN_TRACE_ID': 'Root=1-xxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx;Parent=xxxxxxxxxxxxxxxx;Sampled=0', 'AWS_EXECUTION_ENV': 'AWS_Lambda_python3.6', '_HANDLER': 'main.handle', 'PYTHONPATH': '/var/runtime', 'AWS_ACCESS_KEY_ID': 'ASIAXXXXXXXXXXXXXXXX', 'AWS_SECRET_ACCESS_KEY': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'AWS_SESSION_TOKEN': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==', 'AWS_SECURITY_TOKEN': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=='}) [INFO] 2017-08-31T04:29:53.519Z 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 __file__ : /var/task/main.py [INFO] 2017-08-31T04:29:53.519Z 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 $PYTHONPATH: /var/runtime [INFO] 2017-08-31T04:29:53.519Z 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 sys.path: ['/var/task', '/var/runtime/awslambda', '/var/runtime', '/var/lang/lib/python36.zip', '/var/lang/lib/python3.6', '/var/lang/lib/python3.6/lib-dynload', '/var/lang/lib/python3.6/site-packages'] [INFO] 2017-08-31T04:29:53.519Z 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 list /var/task/: ['site-packages', 'function.dev.json', 'main.py', 'requirements.txt', 'function.stg.json'] [INFO] 2017-08-31T04:29:53.519Z 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 list /var/runtime/: ['s3transfer-0.1.10.dist-info', 'liblambdalog.so', 'dateutil', 'botocore', 's3transfer', 'boto3', 'six-1.10.0.dist-info', '__pycache__', 'botocore-1.5.89.dist-info', 'six.py', 'python_dateutil-2.6.1.dist-info', 'liblambdaio.so', 'liblambdaipc.so', 'boto3-1.4.4.dist-info', 'jmespath', 'awslambda', 'liblambdaruntime.so', 'docutils-0.13.1.dist-info', 'docutils', 'jmespath-0.9.3.dist-info'] [INFO] 2017-08-31T04:29:53.519Z 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 Received event: { "region": "ap-northeast-2" } END RequestId: 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 REPORT RequestId: 08d61df6-8e05-11e7-a88f-4d9fe5cb6d15 Duration: 17.21 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 20 MB null
ここから分かるのは、
- handler となる main.py の path は /var/task/main.py である
- /var/task に Upload した ZIP が展開される
- $PYTHONPATH には /var/runtime が元々セットされている
- /var/runtime には boto3 をはじめとした util 群 が存在する
つまり、 自分で pip install
したパッケージの保存先 ./site-packages
に path を通したければ PYTHONPATH に /var/task/site-packages
を追加すれば良い。
ただ、デフォルトで PYTHONPATH にセットされている /var/runtime
には boto3 をはじめとした util 群が含まれるので、このパスも有効にしておく。
なので、先程紹介した function.json のような形に行き着く。
実践AWS Lambda ~「サーバレス」を実現する新しいアプリケーションのプラットフォーム~
- 作者: 西谷圭介
- 出版社/メーカー: マイナビ出版
- 発売日: 2017/06/09
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
Apex エントリー & 細かい TIPs
今までも AWS Lambda は採用していましたが、アプリケーションエンジニアに必要な Policy を付与して、あとは各自よしなにで久しく運用していましたが、自分も本格的に使うようになり、デプロイや運用の効率を考え、今更ながら Apex を使い始めました。
※ kakakakakku さんの Apex エントリー に触発されたのが大きかった kakakakakku.hatenablog.com
使い始めると痛感するのですが、ホント Apex いいですよね。重すぎずライトな作りで柔軟性も高く、痒いところの大抵の場所に手が届く作りなので、 Serverless Framework を使うまでもないような軽量な functions の開発にはもってこいのフレームだと思います。
ハマるところ
上述の通り、軽量なスクリプティングを AWS Lambda 上で行うような時にハマると思います。 例えば、
- minutely hourly, daily で動かす軽量なスケジューラー
- ちょっとした運用スクリプト
- AMI や EBS snapshot 取得するようなバックアップ
- 開発, 検証環境の up/down
- EC2 や RDS のメンテナンスウィンドウの検知
軽量な処理だから LL で書いて Lambda Function で実行したいけど、Lambda Console でコピペ運用なんてしたくないし、コーディングからデプロイまで通貫して開発したいし、環境分離したいし、というようなニーズに応えてくれます。というか、基本的にこのレールに載っとくと余計なことを気にしたり実装せずにコアロジックに専念して楽に運用まで持っていけると思います。
代表的な特徴をあげると、
- AWS Lambda がサポートしていない言語 (Golang) をサポート
- Build や Build など各アクションに対する command hook をサポート
- Project 単位, Function 単位で変数や設定値の管理, 分離が可能, Override も可能
- Binary や Thirdparty Library の梱包が簡単
- Dry-run 実行や Function の rollback, ログの参照など運用に必要なひととおりの機能あり
Golang サポートもうれしいですが、その他にあげたような痒いとことに手が届く機能は、素の AWS Lambda を使っていると辛みを感じるところなのでとても嬉しい限りです。
Install 〜 Hello World
Apex.run に丁寧に記載されているので、基本的にこれに準じれば特に困ることなく Hello World レベルは始められると思います。Completion (入力補完) も設定しておきましょう。
以下 Mac OS X w/ Homebrew の例です。
$ brew install bash_completion $ vim /usr/local/etc/bash_completion.d/apex $ cat /usr/local/etc/bash_completion.d/apex _apex() { COMPREPLY=() local cur="${COMP_WORDS[COMP_CWORD]}" local opts="$(apex autocomplete -- ${COMP_WORDS[@]:1})" COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 } complete -F _apex apex $ exec $SHELL -l $ apex Apex version 0.15.0 $ apex <TAB> --chdir --iamrole --region delete exec init logs upgrade --dry-run --log-level alias deploy help invoke metrics version --env --profile build docs infra list rollback
AWS CLI 使っている人であれば、大抵は $HOME/.aws/_config & credential
を設定していると思いますので、その PROFILE を使えば apex init
ですぐに始められると思います。 default 入れてない場合は export AWS_PROFILE=${YOUR_PROFILE_NAME}
で環境変数セットするか、 apex
コマンドに --profile (-p)
でプロファイル名渡してあげればいつもどおり実行できます。
あとは apex init
を実行すると対話モードで Project 名など淡々と入力させられ、AWS上に Lambda 用の IAM Role & Policy と、手元にプロジェクトの雛形ディレクトリが出来上がります。
あとは、ドキュメント見てお作法に習いながら開発を進めていけばOKです。
Example
以下、乱雑なサンプルコードです。
指定した Region の AWS の Public CIDRs を取得するfunction 実装例 & 実行例です。
echo '{ "region": "ap-northeast-2"}' | apex invoke helloworld-python | jq . [ "13.124.0.0/16", "13.125.0.0/16", "52.78.0.0/16", "52.79.0.0/16", "52.92.0.0/20", "52.94.6.0/24", "52.94.198.64/28", "52.94.248.176/28", "52.95.111.0/24", "52.95.192.0/20", "52.95.252.0/24", "52.219.56.0/22", "52.219.60.0/23", "54.239.0.192/28", "54.239.116.0/22", "54.239.120.0/21", "52.92.0.0/20", "52.219.56.0/22", "52.219.60.0/23", "13.124.0.0/16", "13.125.0.0/16", "52.78.0.0/16", "52.79.0.0/16", "52.95.252.0/24", "52.78.247.128/26" ]
かなり柔軟ですが、幾つかの決まり事を以下列挙しときます。
Project (project.json)
その名の通りプロジェクトで、この中に Function を包含できます。プロジェクトの設定値は project.json で設定することが出来ます。このプロジェクトで管理される Function に適用される設定値となります。 代表的な設定は以下です。
- name: プロジェクト名
- description: 説明
- runtime: 実行エンジン。AWS Lambda でネイティブサポートされているものも含めて以下を設定可能
- memory: メモリ
- timeout: タイムアウト値
- role: Lambda Function に当てる IAM Role
- profile: apex 実行時に使用する AWS Profile (
$HOME/.aws/
) - defaultEnvironment: このプロジェクトで使用するデフォルトの環境名 (※後述します)
- environment: Function 実行時に渡す環境変数
- nameTemplate: Apex で Lambda Function を作成する場合の Function 命名ルール. デフォルトは
{{.Project.Name}}_{{.Function.Name}}
Function (function.json)
Lambda Function ごとに持たせたい設定値を記載します。project.json の設定値を override できます。 例えば、 Function の処理内容によって memory や timeout 値, 環境変数を変えるような使われ方が多いと思います。
Function 固有の設定値は handle , すなわち invoke (呼び出し ≒ 実行) 時の Event Handler 名です。 Apex で標準で使われる handler name は言語ごとに以下が標準値なので、変えたい場合はここに定義して変えましょう。
- nodejs: index.handle (index.js file with handle exported function)
- python: handle
- java: lambda.Main::handler
知っておくと良いこと
幾つか TIPs を載せておきます。
Switch Role (AssumeRole) して apex 実行する方法
IAM Role を切り替えて運用する人は多いと思います。 特に、幾つも AWS アカウントを運用している人は、入り口の Credential だけ発行して、他のアカウントや Role に Switch して運用するのがデファクトだと思います。 このブログでも AWS CLI で Switch Role する方法を紹介したことがあります。
結論、Apex でも Switch できます。ただ、手法が AWS CLI のそれとは異なるので、以下で紹介しておきます。
AWS CLI の場合は、 $HOME/.aws/config
に role_arn
で切り替え先の IAM Role の ARN を入れた Profile を入れておけば良いのですが、
AWS CLI のprofileを簡単に切り替える (SwitchRole編) - tail my trail
残念ながら Apex は この role_arn
は対応していないようで、 NoCredentialProviders error が出てしまいます。
$ echo '{ "region": "ap-northeast-2"}' | apex invoke helloworld-python | jq . ⨯ Error: function response: NoCredentialProviders: no valid providers in chain. Deprecated. For verbose messaging see aws.Config.CredentialsChainVerboseErrors
どうしたものかとApex のソースを眺めてみると、 AssumeRole や STS のロジックがあるわけですね。
ドキュメント Apex.run #via-iam-role にもサラッと書いてありました。
Via IAM Role
Using an IAM role can be achieved in two ways, via the AWS_ROLE environment variable or via a command line flag –iamrole. As with other Apex credential loading, the command line flag will supersede the environment variable.
Switch 先の IAM Role ARN を –iamrole で渡してあげれば良いです。つまり、これだけ。
$ apex -p uorat --iamrole arn:aws:iam::987654321098:role/lambda-maintener deploy
IAM Role ARN を入力するのが面倒であれば、環境変数 AWS_ROLE
export してしまうだけで良いようです。
環境の分離方法
同じコードベースで 本番, 検証, 開発 と各環境で設定値を切り替えたい場合は、 Apex の environment 機構を使うと良いです。
. ├── README.mkd ├── functions │ ├── function1 │ │ ├── function.dev.json │ │ ├── function.prod.json │ │ ├── function.stage.json │ │ ├── main.py │ │ └── requirements.txt │ └── function2 │ ├── function.dev.json │ ├── function.prod.json │ ├── function.stage.json │ ├── main.py │ └── requirements.txt ├── project.dev.json ├── project.prod.json └── project.stage.json
このようにすれば 環境毎に Lambda Function にあてる IAM Role を切り替えたり出来ます。 AWS アカウントを環境毎に分離していればこれだけで十分ですが、もし同一AWSアカウントで各環境を稼働させている場合は、これだけだと AWS Lambda 上の Function name が被ります。
環境毎に function name を変えたい場合は、 project_${ENV_NAME}.json
の nameTemplate
をカスタマイズしてあげれば良いです。
デフォルトが {{.Project.Name}}_{{.Function.Name}}
なので、これを例えば以下のように変えてあげれば、複数環境の Apex project を同一AWS アカウントで運用できます。
まとめ
Apex が日本で流行りはじめて1年以上経っており、とても今更感のある記事ですが、あらためて Apex 良いなと思ったので、ざっくりエントリー記事にしておきました。 軽量な Serverless scheduler 組もうとしている人はとりあえず Apex 使っておいて損はないと思います。
最後に、これ読んどけば間違いないかと。
実践AWS Lambda ~「サーバレス」を実現する新しいアプリケーションのプラットフォーム~
- 作者: 西谷圭介
- 出版社/メーカー: マイナビ出版
- 発売日: 2017/06/09
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
Amazon ECSのためのコンテナスケジューラー Blox をDocker for Mac で動かしてみる
tl;dr
- 遅ればせながら、昨年の re:Invent 2016 で発表された Amazon ECS のためのOSS Container Scheduler Blox を試してみた。
- AWS 上で動かす事もできるし、手軽に Blox をローカルで動かすこともできる
- 必要なリソースは CloudFormation Template や Docker compose が用意されているのでさくっと試せるよ you try it
Blox の概要と経緯
公式かつ丁寧な紹介があるので、詳しくはそちらを参照してください。
Blox – Amazon EC2 Container Serviceのための新しいオープンソーススケジューラ | Amazon Web Services ブログ
Blox はできることをシンプルに表わすと、以下の4点です。
昨年の AWS のApplication Load Balancer の登場 を機に、Web API サーバのようなコンテナを単にロードバランサー配下に並べてしまえば事足りる用途であれば、 ECS の Service Task が Dynamic Port Mapping まで面倒見てくれるのでずいぶん楽になりました。 また、ECS の Service Task は主要どころのコンテナ配備方法を提供してくれています。(Spread, BinPack, One Task Per Host)
ただ、独自のスケジューリングや、他アプリケーションやコンポーネントへの連携など見越すと、所謂コンテナスケジューラー的なものが欲しくなります。
- 起動/破棄するコンテナインスタンスで 1回だけ実行するタスク
- Data Volume 内のファイルの退避とか
- コンテナそれぞれの Endpoint (コンテナインスタンスのIP + Port Mapping) を把握しておきたいケース
こうしたケースは ECS API (ListContainerInstances や DescribeContainerInstances) を定期的に呼び出して、ECS コンテナインスタンスやその内部に配置されたコンテナの状態を追っかける必要がありました。
そんな中、 2016/11/25 に Amazon ECSイベントストリームで、クラスタの状態を監視 することができるようになりました。CloudWatch Events と連携できるようになり、ECS Eventをトリガーに独自の処理を発火することができるようになりました。 さらにその直後、 2016/12/2 re:Invent 2016 の Keynote Day 2 で ECS イベントストリームを活用した Blox が発表され、この恩恵を手軽に受けることができるようになりました。
Blox - Open Source Tools for Amazon ECS
つまり、上に挙げたユースケースを実現するにあたり必要だったスケジューラーの実装が Blox を使えば楽になるということです。
Blox の仕組み
Blox の構成要素と役割は以下の3つです。
- cluster-state-service (abbr. css)
- etcd
- ECSイベントを格納するDB (Key-Value Store)
- scheduler
- ECSイベントをトリガーに処理を行うスケジューラー
それぞれかいつまんで紹介します。
cluster-state-service (abbr. css)
CloudWatch Events をもとにクラスタインスタンスやコンテナの情報を追跡し、Blox DB (etcd) に保存します。 また、API も提供しており、css を通じて etcd に保存されたECSクラスタの内部ステータスを取得することが出来ます。
以下、README.md より引用。
The cluster-state-service consumes events from a stream of all changes to containers and instances across your Amazon ECS clusters, persists the events in a local data store, and provides APIs (e.g., search, filter, list, etc.) that enable you to query the state of your cluster so you can respond to changes in real-time. The cluster-state-service utilizes etcd as the data store to track your Amazon ECS cluster state locally, and it also manages any drift in state by periodically reconciling state with Amazon ECS.
css 自身は SQS Queue を監視する仕組みとなっています。 以下のようなEvent Pattern を SQS Queue に流す CloudWatch Events Rule を用意しておくことで、css が ECS のイベントを拾うことができるようになります。
{ "detail-type": [ "ECS Task State Change", "ECS Container Instance State Change" ], "source": [ "aws.ecs" ] }
etcd
Blox DB には Docker 使いなら馴染みのある人が多い、Golang 製の分散KVS etcd が採用されています。css が取得したECSイベントはここに格納されます。
coreos/etcd: Distributed reliable key-value store for the most critical data of a distributed system
scheduler (daemon-scheduler)
css/etcd に流れてきたECSイベントをトリガーに、独自の処理を行うスケジューラーです。
Blox としてサポートしているスケジューラーは今は daemon-scheduler のみで、冒頭に挙げたコンテナインスタンスそれぞれに “1つずつ” 稼働するコンテナを稼働させるケースにマッチするスケジューラーです。
以下、README.md より引用。
The daemon-scheduler allows you to run exactly one task per host across all nodes in a cluster. It monitors the cluster state and launches tasks as new nodes join the cluster, and it is ideal for running monitoring agents, log collectors, etc. The daemon-scheduler can be used a reference for how to use the cluster-state-service to build custom scheduling logic.
daemon-scheduler の機能自体も便利ですが、daemon-scheduler の実装がカスタムスケジューラーのリファレンスとしても使えるということです。
Architecture
本家 から図を拝借します。
Blox 自体は Golang 製の3つのプロセスが動けばよいだけなので、極端な話 SQS Queue に通信できる環境であれば ローカルでもAWS上に立てたEC2上でもどこでも動きます。 さらに、Blox 自体の Docker compose や Docker Image が公開されているので、Blox の Docker コンテナをローカルで動かすことも出来ますし、Blox 自身を ECS で動かすことも出来ます。 さすが本家が開発しているOSSなだけあって、必要な CloudFormation テンプレートもバンドルされています。
blox/deploy at blox/blox | Github
Local の Docker for Mac 環境に Blox を 動かしてみる
前置きが大変長くなりましたが、今回の本題。
blox/deploy/README.md に紹介されている Local Installation を試してみます。
手順はざっくり以下の2点です。CloudFormation Template と Docker Compose が用意されているのでとても簡単。
- Blox の Github Repository を clone
- Blox が使用する AWS Resources を作成 (by CloudFormation)
- SQS Queue
- SQS Queue にイベントを流すための CloudWatch Events Rule
- Blox Components をローカルで動かす (by Docker Compose)
1. Blox の Github Repository を clone
uorat-mbp:~ uorat$ cd app/docker/ uorat-mbp:docker uorat$ git clone https://github.com/blox/blox.git Cloning into 'blox'... remote: Counting objects: 4984, done. remote: Compressing objects: 100% (5/5), done. remote: Total 4984 (delta 0), reused 0 (delta 0), pack-reused 4979 Receiving objects: 100% (4984/4984), 3.94 MiB | 734.00 KiB/s, done. Resolving deltas: 100% (2488/2488), done. Checking connectivity... done. uorat-mbp:~ uorat$ cd blox/
2. Blox が使用する AWS Resources を作成 (by CloudFormation)
CloudFormation Template が用意されているので、これを使うだけです。 ${BLOX_HOME}/deploy/docker/conf/cloudformation_template.json にあります。 region や profile を指定して実行すればOK。
uorat-mbp:blox uorat$ aws --region ap-northeast-1 --profile uorat cloudformation create-stack --stack-name BloxLocal --template-body file://./deploy/docker/conf/cloudformation_template.json { "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/BloxLocal/abcdefgh-abcd-abcd-abcd-abcdefghijkl" }
StackStatus が 完了するのを待つ。
uorat-mbp:blox uorat$ aws --region ap-northeast-1 --profile uorat cloudformation describe-stacks --stack-name BloxLocal | jq ".Stacks[] | .StackId, .Description, .StackName, .StackStatus" "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/BloxLocal/abcdefgh-abcd-abcd-abcd-abcdefghijkl" "Template to deploy Blox framework locally" "BloxLocal" "CREATE_COMPLETE"
3. Blox Components をローカルで動かす (by Docker Compose)
docker-compose.yml が用意されているので、これを使えば良いです。
composeファイル内に、 AWS Region と Profile を指定する必要があるので、自分の環境にあわせて変更しておくこと。 docker-compose.yml を見ると分かるとおり、css も daemon-scheduler も Docker ホストの $HOME/.aws を read-only でマウントしています。 AWS_PROFILE で指定したプロファイル名に対応する credential を使用して SQS や ECS と対話する仕掛けになっています。
README.md にも書いてあります。
- Update the
AWS_REGION
value with the region of your ECS and SQS resources.- Update the
AWS_PROFILE
value with your profile name in ~/.aws/credentials. You can skip this step if you are using thedefault
profile.
uorat-mbp:blox uorat$ cd deploy/docker/conf/ uorat-mbp:blox uorat$ vim docker-compose.yml uorat-mbp:conf uorat$ git diff diff --git a/deploy/docker/conf/docker-compose.yml b/deploy/docker/conf/docker-compose.yml index b626ac2..7a16857 100644 --- a/deploy/docker/conf/docker-compose.yml +++ b/deploy/docker/conf/docker-compose.yml @@ -5,8 +5,8 @@ services: ports: - "2000:2000" environment: - AWS_REGION: "<region>" - AWS_PROFILE: "default" + AWS_REGION: "ap-northeast-1" + AWS_PROFILE: "uorat" command: [ "--bind", "0.0.0.0:2000", "--css-endpoint", "css:3000", @@ -25,8 +25,8 @@ services: ports: - "3000:3000" environment: - AWS_REGION: "<region>" - AWS_PROFILE: "default" + AWS_REGION: "ap-northeast-1" + AWS_PROFILE: "uorat" command: [ "--bind", "0.0.0.0:3000", "--etcd-endpoint", "etcd:2379",
あとは docker-compose コマンドでコンテナを立ち上げるだけ。
uorat-mbp:conf uorat$ docker-compose up -d Creating network "conf_default" with the default driver Pulling etcd (quay.io/coreos/etcd:v3.0.15)... v3.0.15: Pulling from coreos/etcd 3690ec4760f9: Pull complete 53b6b297c402: Pull complete 0ee7413b6e7d: Pull complete 0b6d568d289d: Pull complete f79877e4a632: Pull complete Digest: sha256:aed90a29fbe7ad0e6629b2ea5a290f5b6efb9b719cec97c756df13f1db3760bf Status: Downloaded newer image for quay.io/coreos/etcd:v3.0.15 Pulling css (bloxoss/cluster-state-service:0.2.0)... 0.2.0: Pulling from bloxoss/cluster-state-service 2551f207d95c: Pull complete 559c70e440db: Pull complete b778135d24c5: Pull complete 67ed40d0e0b3: Pull complete Digest: sha256:63aebbcf800a64d51acb20d5d7ae2c261fb272fd6aa020c533d4721c22279114 Status: Downloaded newer image for bloxoss/cluster-state-service:0.2.0 Pulling scheduler (bloxoss/daemon-scheduler:0.2.0)... 0.2.0: Pulling from bloxoss/daemon-scheduler a4d560910c77: Pull complete bcfbf1954f9f: Pull complete e199cf3f7828: Pull complete 1bb380f5b15e: Pull complete Digest: sha256:4e3d0314ceb8ab62bc296c6f351d87ee89c21616f28f3a9bd425f7edc8281636 Status: Downloaded newer image for bloxoss/daemon-scheduler:0.2.0 Creating conf_etcd_1 Creating conf_css_1 Creating conf_scheduler_1
docker ps コマンドで確認。 css は 3000/tcp、daemon-scheduler は 2000/tcp で待ち受けます。
uorat-mbp:conf uorat$ docker-compose ps Name Command State Ports -------------------------------------------------------------------------------------------- conf_css_1 /cluster-state-service --b ... Up 0.0.0.0:3000->3000/tcp conf_etcd_1 /usr/local/bin/etcd --data ... Up 0.0.0.0:2379->2379/tcp, 2380/tcp conf_scheduler_1 /daemon-scheduler --bind 0 ... Up 0.0.0.0:2000->2000/tcp
なお、今回は既に ECS Cluster を稼働させているアカウントを使っていたので特にハマることなく3つのコンテナが稼働してくれましたが、 @smiyaguchi さんによると 先にECS で何かしらのクラスタが起動していないと css_1 の起動に失敗するそうです。
Bloxを使ってみました - smiyaguchi’s blog
また、ECSやSQSへのアクセス権限が足りなかったり、profile の指定が間違っていても同様に以下のようなエラーが出て css_1 がコケます。
css_1 | 2017-02-21T08:55:56Z [INFO] Reconciler loading tasks and instances css_1 | 2017-02-21T09:00:58Z [CRITICAL] Error starting event stream handler: NoCredentialProviders: no valid providers in chain. Deprecated. css_1 | For verbose messaging see aws.Config.CredentialsChainVerboseErrors css_1 | Failed to list ECS clusters. css_1 | github.com/blox/blox/cluster-state-service/handler/reconcile/loader.clientWrapper.listClusters css_1 | /go/src/github.com/blox/blox/cluster-state-service/handler/reconcile/loader/ecs_wrapper.go:75 css_1 | github.com/blox/blox/cluster-state-service/handler/reconcile/loader.clientWrapper.ListAllClusters css_1 | /go/src/github.com/blox/blox/cluster-state-service/handler/reconcile/loader/ecs_wrapper.go:54
複数AWSアカウントを運用していると、Switch Role で切り替えられるように .aws を書いたりすると思いますが、
そのノリで profile name を指定しても 該当する credential が取得できずに css が落ちてしまうので気をつけてください。 (※しばらくハマった残念な人です。)
うまく動くとこんな感じで動きます。
uorat-mbp:conf uorat$ docker-compose logs css Attaching to conf_css_1 css_1 | 2017-02-21T09:29:15Z [INFO] Reconciler loading tasks and instances css_1 | 2017-02-21T09:29:15Z [INFO] Bootstrapping completed css_1 | 2017-02-21T09:29:15Z [INFO] Starting to poll for events from SQS css_1 | 2017-02-21T09:29:47Z [INFO] SQS attribute[ApproximateNumberOfMessages] = 0 css_1 | 2017-02-21T09:29:47Z [INFO] SQS attribute[ApproximateNumberOfMessagesDelayed] = 0 css_1 | 2017-02-21T09:29:47Z [INFO] SQS attribute[ApproximateNumberOfMessagesNotVisible] = 0 css_1 | 2017-02-21T09:30:17Z [INFO] SQS attribute[ApproximateNumberOfMessages] = 0 css_1 | 2017-02-21T09:30:17Z [INFO] SQS attribute[ApproximateNumberOfMessagesDelayed] = 0 css_1 | 2017-02-21T09:30:17Z [INFO] SQS attribute[ApproximateNumberOfMessagesNotVisible] = 0 css_1 | 2017-02-21T09:30:47Z [INFO] SQS attribute[ApproximateNumberOfMessagesDelayed] = 0 css_1 | 2017-02-21T09:30:47Z [INFO] SQS attribute[ApproximateNumberOfMessagesNotVisible] = 0 css_1 | 2017-02-21T09:30:47Z [INFO] SQS attribute[ApproximateNumberOfMessages] = 0 uorat-mbp:conf uorat$ docker-compose logs scheduler Attaching to conf_scheduler_1 scheduler_1 | 2017-02-21T09:29:16Z [INFO] Started dispatcher
css 経由でECS Cluster の情報を参照してみる
css で提供されている REST API を使って ECS の情報を参照してみます。 API は css の README.md に書いてあるとおり swagger.json にて一覧が確認できます。
After you launch the cluster-state-service, you can interact with and use the REST API by using the endpoint at port 3000. Identify the cluster-state-service container IP address and connect to port 3000. For more information about the API definitions, see the swagger specification.
例えば コンテナインスタンスの一覧を取得する場合は “http://localhost:3000/v1/instances” です。 以下 swagger.json より引用。
"/instances": { "get": { "description": "Lists all instances, after applying filters if any", "operationId": "ListInstances", "parameters": [ { "name": "status", "in": "query", "description": "Status to filter instances by", "type": "string" }, { "name": "cluster", "in": "query", "description": "Cluster name or ARN to filter instances by", "type": "string" } ], "responses": { "200": { "description": "List instances - success", "schema": { "$ref": "#/definitions/ContainerInstances" } }, "400": { "description": "List instances - bad input", "schema": { "type": "string" } }, "500": { "description": "List instances - unexpected error", "schema": { "type": "string" } } } } },
試しに問い合わせしてみると、ECS API ライクに ECS Instance の情報が json で返ります。以下、jq で整形した例。
uorat-mbp:~ uorat$ curl http://localhost:3000/v1/instances 2>/dev/null | jq '.items[].entity | [.EC2InstanceID, .clusterARN, .containerInstanceARN, .status, .versionInfo.dockerVersion]' [ "i-xxxxxxxxxxxxxxxxx", "arn:aws:ecs:ap-northeast-1:123456789012:cluster/ecs-demo-php-simple-app-cluster", "arn:aws:ecs:ap-northeast-1:123456789012:container-instance/abcdefgh-abcd-abcd-abcd-hogehogehoge", "ACTIVE", "DockerVersion: 1.12.6" ] [ "i-yyyyyyyyyyyyyyyyy", "arn:aws:ecs:ap-northeast-1:123456789012:cluster/openrec-station-ecs-demo-php-simple-app-cluster", "arn:aws:ecs:ap-northeast-1:123456789012:container-instance/abcdefgh-abcd-abcd-abcd-fugafugafuga", "ACTIVE", "DockerVersion: 1.12.6" ]
同じように task もとれました。
daemon-scheduler を試す
長くなったので、次回にします。。
まとめ
Blox は Amazon ECS のためのOSS Container Scheduler です。 今まで ECS では手の届かなかった(ユーザー任せになっていた)独自のスケジューリングが組み込みやすくなり、他アプリケーションやコンポーネント間の連携まで含めた所謂オーケストレーションが可能となるでしょう。
肝心の scheduler は今回は記載しきれなかったのでまたの機会にしますが、Blox の稼働に必要なものは全て Dockerize & Template 化されているので、さくっと試せます。 ローカルでも動くので、とりあえずカジュアルにお試しを。
AWS CLI のprofileを簡単に切り替える (SwitchRole編)
これまた意外と知らない人がちらほらいたので、書き留めておく。
tl;dr
$HOME/.aws/config で Switch Role な 設定をいれるだけ。
背景
Switch Role という機能を使うと、IAM User から特定のRole に切り替えることができる。
Management Console使ってると別アカウントの切り替えのためにログインし直すのは面倒だし、 アプリケーションのために AccessToken を発行しまくるのも管理上不便でリスクも増える(Token 漏洩すると悲惨)ので、自分はもっぱら IAM User は極力作成せず IAM Role に寄せるようにしている。
また、ログイン用の IAM User は ReadOnly にしておいて、操作が必要なときだけ パワフルな権限を有する Role に切り替えるなどしておくなどしておくと保険にもなる。
見た目もわかりやすいし。
で、今回のお題は、このSwitch Role を AWS CLI でどう扱うか。
やりかた
結構前に、AWS CLI の profile 切替方法を書いたが、
この応用でいける。
$HOME/.aws/config に、以下のように Switch 元の Profile と Switch 先のIAM Role の ARN を書いてあげれば良い。
[profile uorat-account-parent] region = ap-northeast-1 [profile uorat-account-child-a] role_arn = arn:aws:iam::123456789012:role/switchRoleReadOnly source_profile = uorat-account-parent region = ap-northeast-1
あとは、いつもどおり、aws コマンドを実行する時に –profile を指定すれば良いだけ。 source_profile で指定した IAM User で Switch Role され、Switch 先の Role での作業が可能となる。シンプル。
$ aws ec2 describe-vpcs --profile=uorat-account-parent { "Vpcs": [ { "VpcId": "vpc-xxxxxxxx", "InstanceTenancy": "default", "State": "available", "DhcpOptionsId": "dopt-xxxxxxxx", "CidrBlock": "172.31.0.0/16", "IsDefault": true } ] } $ aws ec2 describe-vpcs --profile=uorat-account-child-a { "Vpcs": [ { "VpcId": "vpc-yyyyyyyy", "InstanceTenancy": "default", "Tags": [ { "Value": "uorat-child-a", "Key": "Name" } ], "State": "available", "DhcpOptionsId": "dopt-yyyyyyyy", "CidrBlock": "172.17.0.0/16", "IsDefault": false }, { "VpcId": "vpc-zzzzzzzz", "InstanceTenancy": "default", "State": "available", "DhcpOptionsId": "dopt-yyyyyyyy", "CidrBlock": "172.31.0.0/16", "IsDefault": true } ] }
この方法は公式ドキュメントにも書いてあるが、意外と知らない人が多かったので書き留めておきました。
参考: IAM ロールの切り替え(AWS Command Line Interface) - AWS Identity and Access Management
Docker for Mac 使っている場合の入力補完
tl:dr
Docker.app 内に bash_completion がバンドルされているので、それ使ってね。
背景
たいてい、以下のようなコマンドで completion 設定すると思うが、
curl -L https://raw.githubusercontent.com/docker/docker/master/contrib/completion/bash/docker > /etc/bash_completion.d/docker curl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose version --short)/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose files=(docker-machine docker-machine-wrapper docker-machine-prompt) for f in "${files[@]}"; do curl -L https://raw.githubusercontent.com/docker/machine/v$(docker-machine --version | tr -ds ',' ' ' | awk 'NR==1{print $(3)}')/contrib/completion/bash/$f.bash > `brew --prefix`/etc/bash_completion.d/$f done
- 参考: docker bash completion docker/docker - Github
- 参考: Docker Machine Command-line completion - Docker
- 参考: Docker Compose Command-line completion - Docker
Docker for Mac の場合どうなんだろと気になって軽く調べた。
方法
またしてもググラビリティが低かったらしく、なかなかピンポイントな情報に出会えなかった。 結論、Docker for Mac の公式ドキュメントに書いてあるとおり、Docker.app 内にバンドルされているので、それを使おう。
Installing bash completion: Get started with Docker for Mac - Docker
If you are using bash completion, such as homebrew bash-completion on Mac bash completion scripts for the following commands may be found inside Docker.app, in the Contents/Resources/etc/ directory:
- docker
- docker-machine
- docker-compose
homebrew で bash_completion 入れていれば、こんな感じで bash_completion.d/ ディレクトリに symlink 貼ってあげれば良い。
ln -s /Applications/Docker.app/Contents/Resources/etc/docker.bash-completion /usr/local/etc/bash_completion.d/docker ln -s /Applications/Docker.app/Contents/Resources/etc/docker-machine.bash-completion /usr/local/etc/bash_completion.d/docker-machine ln -s /Applications/Docker.app/Contents/Resources/etc/docker-compose.bash-completion /usr/local/etc/bash_completion.d/docker-compose
実行例
コマンド補完の例
$ docker [TAB] attach create export import login pause push run stack system version build deploy help info logout plugin rename save start tag volume commit diff history inspect logs port restart search stats top wait container events image kill network ps rm secret stop unpause cp exec images load node pull rmi service swarm update
パラメーター補完の例
# 以下のコンテナがある状態で $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES dbd529912c30 uorat/ecs-demo-php-simple-app "/usr/sbin/apache2..." About a minute ago Up About a minute 0.0.0.0:80->80/tcp hopeful_kalam # "docker stop" まで打って TAB 押すと $ docker stop [TAB] ↓ $ docker stop hopeful_kalam # もう一つコンテナ立ち上げる $ docker run -d -p81:80 [TAB] alpine uorat/ecs-demo-php-simple-app ubuntu alpine:latest uorat/ecs-demo-php-simple-app:latest ubuntu:12.04 $ docker run -d -p81:80 uorat/ecs-demo-php-simple-app 01a8b01975c07667574042bced6ba14d9056f87b43053b46e1abd6a29800fa6f # 確認 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 01a8b01975c0 uorat/ecs-demo-php-simple-app "/usr/sbin/apache2..." 2 minutes ago Up 2 minutes 0.0.0.0:81->80/tcp elastic_chandrasekhar dbd529912c30 uorat/ecs-demo-php-simple-app "/usr/sbin/apache2..." 5 minutes ago Up 5 minutes 0.0.0.0:80->80/tcp hopeful_kalam # "docker stop" まで打って TAB 押すと $ docker stop [TAB] elastic_chandrasekhar hopeful_kalam # 止める $ docker stop [TAB] elastic_chandrasekhar hopeful_kalam $ docker stop hopeful_kalam hopeful_kalam # 止まってるコンテナを再度立ち上げる $ docker start [TAB] ↓ $ docker start hopeful_kalam ← ちゃんと止まってるコンテナだけフィルタリングされる
ちゃんちゃん。
Docker実践入門――Linuxコンテナ技術の基礎から応用まで (Software Design plus)
- 作者: 中井悦司
- 出版社/メーカー: 技術評論社
- 発売日: 2015/09/26
- メディア: 大型本
- この商品を含むブログ (2件) を見る
プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化
- 作者: 阿佐志保,山田祥寛
- 出版社/メーカー: 翔泳社
- 発売日: 2015/11/20
- メディア: 大型本
- この商品を含むブログ (3件) を見る
Docker for Mac v1.13 でディスクイメージの自動圧縮に対応されたのであげておくべし
tl;dr
「Docker for Mac 使ってる場合のimage群の保存場所と掃除方法」というタイトルでブログに書き殴っておこうと思ったら、実は Docker for Mac v1.13 から自動圧縮に対応していたというオチ。
Docker for Mac 使いは 今すぐ v 1.13 に上げよう。
背景
Docker および Amazon ECS 素振り中の身。 数年前に 軽く触った程度であまり詳しくないけど所用でコンテナを本番運用することになりそうなので、浦島太郎状態から抜け出すべくキャッチアップしつつ、ついでに開発機のMac OS X のDocker環境を Docker Toolbox から Docker for Mac に移行していた。
ゴミが散らかっていたのでコンテナやイメージを掃除していたら、なかなか開放されず困ったちゃんだったので色々調べていた。
Where are Docker images?
通常Linux をDocker ホストに使ってる場合は、 /var/lib/docker にイメージが保存される。 けど、Docker for Mac 入れたMac OS Xに /var/lib/docker が見当たらなかったので、気になってファイル漁ってみた。 そもそも Docker コマンドってどこにあるんだろう。
$ which docker /usr/local/bin/docker $ ll /usr/local/bin/docker lrwxr-xr-x 1 uorat Users 66 1 30 17:01 /usr/local/bin/docker -> /Users/uorat/Library/Group Containers/group.com.docker/bin/docker
$HOME/Library/Group Containers にあるらしい。 $HOME/Library/Containers というディレクトリもあったので、ここを掘り下げて見てみると
$ ll -h ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/ total 1998512 drwxr-xr-x 13 uorat Users 442B 1 30 17:01 . drwxr-xr-x 22 uorat Users 748B 1 30 17:01 .. -rw-r--r-- 1 uorat Users 976M 1 31 14:04 Docker.qcow2 -rw-r--r-- 1 uorat Users 64K 1 30 17:01 console-ring -rw-r--r-- 1 uorat Users 64K 1 30 16:45 console-ring.0 -rw-r--r-- 1 uorat Users 3B 1 30 17:01 hypervisor.pid -rw-r--r-- 1 uorat Users 0B 1 30 16:45 lock drwxr-xr-x 2 uorat Users 68B 1 30 16:45 log -rw-r--r-- 1 uorat Users 17B 1 30 17:01 mac.0 -rw-r--r-- 1 uorat Users 36B 1 30 16:45 nic1.uuid -rw-r--r-- 1 uorat Users 3B 1 30 17:01 pid -rw-r--r-- 1 uorat Users 94B 1 30 17:01 syslog lrwxr-xr-x 1 uorat Users 12B 1 30 17:01 tty -> /dev/ttys000
更新日やファイルサイズ見る限りなんかそれっぽいのがある。 qcow2 という拡張子見る限り、QEMUのイメージファイルらしい。内部的にQEMU使ってるのか。
フォーラム漁ってみる
Dockerのフォーラム見てみると、似たようなスレッドがあってまさにこのファイルがdefault指定されていると言及されていた。
Where are images stored on Mac OS X? - Docker for Mac - Docker Forums
It depends on the driver being used, but the default is: $HOME/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2
Try it
ごりごり docker rmi で不要なイメージ消してみる。 元々1GBくらいに育ってたのですっきり。
$ docker images prune $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE alpine latest 70c557e50ed6 11 months ago 4.8 MB
では、実体のファイル見てみると、
$ ll -h Docker.qcow2 -rw-r--r-- 1 uorat Users 976M 1 31 14:45 Docker.qcow2
減らないうぇーい。
Docker for Mac の仕組み
Mac OS X の xhyve 使って Linux 仮想マシン立ち上げている
QEMUファイルがあることから推察できるとおり、Docker for Mac も所詮は仮想マシン上にContainer立ち上げる仕組みのよう。 昔使ってた Docker Toolbox はVirtualBoxバックグラウンドで使って実現していたわけだけども、Docker for Mac は 内部的には xhyve という Mac OS X の Hypervisor 使って Dockerホスト用のLinux を立ち上げているらしい。
DOCKER FOR MAC An integrated, easy-to-deploy environment for building, assembling, and shipping applications from a Mac, Docker for Mac is a native Mac application architected from scratch, with a native user interface and auto-update capability, deeply integrated with OS X native virtualization, Hypervisor Framework, networking and file system, making it faster and more reliable than previous ways of getting Docker on a Mac.
引用: Docker for Mac and Windows Beta: the simplest way to use Docker on your laptop - Docker Blog
Faster and more reliable: no more VirtualBox! The Docker engine is running in an Alpine Linux distribution on top of an xhyve Virtual Machine on Mac OS X or on a Hyper-V VM on Windows, and that VM is managed by the Docker application. You don’t need docker-machine to run Docker for Mac and Windows.
なので、screen tty するとLinuxに入れる。
$ cd ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/ $ screen tty ... * Running system containerd ... [ ok ] * Running system containers ... binfmt rng-tools [ ok ] * Configuring host settings from database ... [ ok ] * Setting up proxy port service ... [ ok ] * Starting Docker ... [ ok ] * Starting chronyd ... [ ok ] * Checking system state ... ✓ Drive found: sda ✓ Drive mounted: /dev/sda1 on /var type ext4 (rw,relatime,data=ordered) ✓ Network connected: inet addr:192.168.65.2 Bcast:192.168.65.7 Mask:255.255.255.248 ✓ Process transfused running ✓ Process dockerd running: dockerd --pidfile=/run/docker.pid -H unix:///var/run/docker.sock --swarm-default-advertise-addr=eth0 --userland-proxy-path /usr/bin/slirp-proxy --debug --experimental --storage-driver aufs ✓ Process containerd running: docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc --debug ✓ Docker daemon working: 1.13.0 ✓ Diagnostics server running: /usr/bin/diagnostics-server -vsock ✓ System containerd server running: /usr/bin/containerd ✓ System containerd working * Starting Hyper-V daemon: hv_kvp_daemon ... [ ok ] * Starting Hyper-V daemon: hv_vss_daemon ... [ ok ] * Adjusting oom killer settings ... [ ok ] Welcome to Moby ## . ## ## ## == / # hostname moby / # cat /etc/os-release NAME="Alpine Linux" ID=alpine VERSION_ID=3.5.0 PRETTY_NAME="Alpine Linux v3.5" HOME_URL="http://alpinelinux.org" BUG_REPORT_URL="http://bugs.alpinelinux.org" / # (Ctl-a + D)
Mac OS X でQEMUディスクイメージをリサイズする方法 (従来)
ググってみたら、まさに同じようなことを言及している記事を発見。 ここでは qemu で仮想ディスクをリサイズする方法が紹介されている。KVM触ってた頃を思い出す。
Docker for macでDocker.qcow2というファイルが肥大化する件 | GENDOSU@NET
Github Issue でも同じ案内を見つけた。DockerホストとなるLinuxサーバにログインして /var/tmpfile を0埋めした後で、Mac OS X上の Docker.qcow2 ファイルを qemu-img コマンドで convert しろというもの。
Docker.qcow2 never shrinks - disk space usage leak in docker for mac · Issue #371 · docker/for-mac
試してみたらいつの間にか勝手に圧縮されていた
結論、ブログタイトル & この見出しのような挙動をした。 一応途中までやったこと書き記しておく。
従来の圧縮方法の下準備
$ brew install qemu
リサイズする前に、念のため Linux仮想マシン上に無駄なファイルがないか確認。 上の記事に良いこと書いてあるので、ここにも転機させて頂く。
変換をかける前に、考慮する部分があり。通常ディスク上でファイルを削除した場合にはインデックス上で削除したという情報を管理して、実体は消えていない状態ですが、通常の場合だとそこに上書きすることになるので、容量があるという見え方になりますが、仮想ディスクはそのインデックス上では削除されているけれど、実体がある場合も、ディスク容量として消費していることになります。この削除されているけれど実体がある部分を綺麗にしてから変換をすることで、効率よくファイルを小さくする事が出来ます。
で、Docker for Mac の Preferances を変更する。これを機に自動起動 (Start Docker when you log in) を無効にしておいた。使わないのに無駄にデーモン立ち上がってるの嫌いだから使うときだけ起動するようにする。
で、Docker for Mac 再起動。
圧縮しようとしたら…
ここでddコマンドによる0埋めや、qemu-imgコマンドでリサイズかける前に、今一度 ディスクイメージのファイルサイズを測ってみたところ
$ ll -h Docker.qcow2 -rw-r--r-- 1 uorat Users 113M 1 31 15:30 Docker.qcow2
あら、減っとる。。
Docker for Mac v1.13 で qcow2 ファイルの圧縮に対応していた
2017年1月31日時点のstable版 v1.13 で対応したらしい。 上記の Github Issues を読み進めていくと、 compaction に対応する旨が言及されており、無事 v1.13 に組み込まれリリースされていた。
経緯は以下あたりから読み進めてみてほしい。
Docker.qcow2 never shrinks - disk space usage leak in docker for mac · Issue #371 · docker/for-mac
ドキュメントを探してみると、確かに記載あった。ググラビリティが低かったらしく、最初にこれを見つけられなかったのが無念。
How do I reduce the size of Docker.qcow2?: Frequently asked questions (FAQ) - Docker
以下引用する。
In Docker 1.13 there is preliminary support for “TRIM” to non-destructively free space on the host. First free space within the Docker.qcow2 by removing unneeded containers and images with the following commands:
- docker ps -a: list all containers
- docker image ls: list all images
- docker system prune: (new in 1.13): deletes all stopped containers, all volumes not used by at least one container and all images without at least one referring container.
Note the Docker.qcow2 will not shrink in size immediately. In 1.13 a background cron job runs fstrim every 15 minutes. If the space needs to be reclaimed sooner, run this command: docker run –rm -it –privileged –pid=host walkerlee/nsenter -t 1 -m -u -i -n fstrim /var
厳密には即時圧縮というわけではなく、15分毎に走るcron jobで実現しているらしい。 ただ、以前の手法と比べると大きな改善だし、これで十分だと思う。
結論
Docker for Mac v1.13 に上げると幸せになれます。
プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化
- 作者: 阿佐志保,山田祥寛
- 出版社/メーカー: 翔泳社
- 発売日: 2015/11/20
- メディア: 大型本
- この商品を含むブログ (3件) を見る
Docker実践入門――Linuxコンテナ技術の基礎から応用まで (Software Design plus)
- 作者: 中井悦司
- 出版社/メーカー: 技術評論社
- 発売日: 2015/09/26
- メディア: 大型本
- この商品を含むブログ (2件) を見る