はじめに
“まともなステージング環境”を考える という記事を読んで、文中の
開発者、非開発者がある機能のレビューをしたい
・本番相当のデータ+各開発者の feature branch のアプリケーションが動作する環境
・弊社では Staging 環境と呼んでいる…が正確ではなく、Feature Staging などと呼んだ方が良さそうだ
という環境を半年ほど前に準備したことを思い出したので、その振り返りをする。
作った環境について
もともと非開発者が問い合わせ対応だったり、開発者がデバックに使う環境があった。
それに加えて「非開発者が、仕様や開発進捗の確認のために、任意のブランチを、任意のタイミングで、Slack経由でデプロイしたい」という要望があったので、その環境を準備した。
(上記のブログ記事で言う「Feature Staging環境」にあたるはず。たぶん。)
Feature Staging環境の作成前は、ローカル環境で上記の要望は実現していたが、
- 非開発者にとってローカルで動かすのは負担が高い
- 非開発者のPCではスペック的に厳しい
といった理由で、Feature Staging環境の需要があったため、その環境を準備した。
Feature Staging環境の使い方
Slackで @aws lambda invoke --function-name xxx --payload {"branch": "feature/xxx"}
という感じでコマンド実行すると、5分後くらいに「 http://feature_xxx.example.com/
にデプロイしました」と返事があるという感じ。
システム的な流れでいうと下記のような感じ。
- featureブランチはCIで、DockerImageがビルドされ
AWS ECR
にプッシュ - Slackから
AWS Chatbot
を使って、ブランチ名を引数としてAWS Lambda
を起動 AWS Lambda
はAWS ECS
に、指定されたブランチでコンテナを起動AWS Route53
で起動したコンテナと、ブランチ名から作成されたURL (例:http://feature_xxx.example.com
) を紐付ける
技術的な詳細については Qiitaの記事 を参照。
(Slackの部分は無いけど)
工夫したところ
タスク定義を書くの辛い → 書かない
ecs-cli を使って、既存のローカル開発用の docker-compose.yml
を流用。
これを使えば、ローカル開発用の docker-compose.yml
だけで、ECS上にコンテナ乱立!楽勝!となる、はずだった…(後述)
ただ「開発初期はECSタスク定義に触らなくて速度上がる」というのは間違ってないはず。
結局ECSタスク定義は書いてないし、あんまり書きたくなかったので、そのメリットは大きい。はず。
ECSでMySQLコンテナ繋げない → 繋げた
docker-compose では、サービスとして app と mysql があるとして、app から mysql へ繋ぐ際には mysql でつなげるけど、ECSでは 127.0.0.1 とかで繋がないといけない。
(ただ、実際の運用ではあらかじめRDSを立てといて、そこにアクセスするようになったから、あんまり意味なかった)
entrypointのプロセスから生やした子プロセスで、AWS CLIを通したS3データの取得が失敗する → 成功した
aws cli を叩いてるところが、なぜか「アクセス拒否」となり動かない!という問題があったが、答えは下記。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/ecs-iam-task-roles-config-errors/
環境変数 AWSCONTAINERCREDENTIALSRELATIVEURI を使用できるのは、コンテナ内の PID 1 プロセスのみです。コンテナで複数のプロセスまたは init プロセス (ラッパースクリプト、start スクリプト、supervisord など) を実行している場合、この環境変数を非 PID 1 プロセスで使用することはできません。
今回は entrypoint のプロセスから service で php-fpm のプロセスを生やしていたので、そこからクレデンシャルが読めなかった。
コンテナ運用の思想として「プロセスごとにコンテナ分ける」「プロセスが落ちたら、コンテナ再起動するので service 使わない」みたいなことが多分あって、そういう「正しい」使い方をしてると嵌まらないポイントだと思う。
おかげでネットで解決策が見つからなくて大変だった。
お高い → ちょっとお安く
Fargate Spotで、お安くなったはず。
Fargate価格から最大70%割引で提供します。
https://aws.amazon.com/jp/blogs/news/aws-fargate-spot-now-generally-available/
工夫したかったけど、どうにもならなかったところ
デプロイフローのシステムの流れが複雑
実は使い方の 「3. AWS Lambda
は AWS ECS
に、指定されたブランチでコンテナを起動」という部分はもっと複雑で
- 既存でECSタスクが存在してたら削除
- ECSタスクを起動
- Aレコードを作成
- ECSタスクとAレコードを紐付け
という処理をシェルスクリプト( deploy.sh
)に書いて
deploy.sh
を立ち上げ時に実行するdocker-compose.yml
を準備する- それを
ecs-cli
を使って「デプロイ用のECSタスク」としてアップロードする AWS Chatbot
は、その「デプロイ用のECSタスク」を起動するためのAWS Lambda
を起動- 「デプロイ用のECSタスク」は、起動処理として
deploy.sh
を実行 deploy.sh
の結果「指定したブランチのコンテナ」と「URL」が生成
という、わりかし意味不明なフローになっている。
「いや deploy-ecs.sh
なんて作らんと、直接 node でデプロイのコードを書いて、それをLamdaとして上げれば良いんじゃないの?」という話になるけど、実は deploy-ecs.sh
に ecs-cli
を利用してて、 ecs-cli
にSDKが無いという事情がある。
ecs-cli
のおかげで、ECSのタスク定義を書かなくて良くて楽だと思ったら、デプロイフローが難しくなって結果トントンというオチだ。
ちなみにLambda の Custome Runtime を上手く使えば deploy.sh
をLambdaとして使えるとは思ったけど、正直よく分かんなかった。
デフォルトでサポートして欲しい。
「小回りがきかない」のが面倒くさい
dockerにおいて「ステートレス」というのは重要なキーワードである。
状態を持たないからこそ、スケールアウト/インの柔軟性があったり、ソフトのチューニングといった「差別化に繋がらない重労働」をなくすことができる。
ただそれがかえって「ステートフル」に比べ回り道を強いられることがあって、それが辛みにつながることがある。
例えば「CIでDockerImageをビルドして、それをECRにプッシュして、Slackからそれをデプロイする」という今回の仕様は「ソースコードの変更をリアルタイムに反映したい」という場合に、回り道に感じられる。
壁パスが発生する時点で待ち時間が増えるし、そもそもこのフローを理解するのにも手間がかかる。
じゃあいっそステートフルに倒して「常にコンテナ立ち上げっぱなしで、都度git pull してきて、ビルドすれば良くない?」という話になる。
ここで大事なのは、やりたいことが「ステートレス」か「ステートフル」なのか見極めて適切なツールを選ぶ、ということだ。
コンテナはそういう「ある種面倒くさいものだ」と認識して、それが許容できない場合にはEC2とかを使うというのも一つの手だ。
脇道にそれたけど、この「小回りがきかない」という問題の解決策としては「すみません、以前みたいにローカル環境でお願いします」と言うしか無い気がしてる。
Feature Stagingを作ったことへの個人的な感想
- このレベルのインフラを弄るのは、ぶっちゃけ「万が一デグレしたら…!」という心理的な覚悟も必要無いし、レゴブロック弄るみたいで楽しかった。
- 「コンテナによるインフラ」ってどのくらい流行ってるだろ?コンテナが、例えばEC2に比べ明らかな上位互換かと言われたら、そんなこと無いはず。メリデメ考えたら、コンテナ一色って未来は無いんじゃないかな〜。