記事一覧
このエントリーをはてなブックマークに追加

Feature Staging 環境を作ったことの振り返り

はじめに

“まともなステージング環境”を考える という記事を読んで、文中の

開発者、非開発者がある機能のレビューをしたい

・本番相当のデータ+各開発者の 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/ にデプロイしました」と返事があるという感じ。

システム的な流れでいうと下記のような感じ。

  1. featureブランチはCIで、DockerImageがビルドされ AWS ECR にプッシュ
  2. Slackから AWS Chatbot を使って、ブランチ名を引数として AWS Lambda を起動
  3. AWS LambdaAWS ECS に、指定されたブランチでコンテナを起動
  4. 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 LambdaAWS ECS に、指定されたブランチでコンテナを起動」という部分はもっと複雑で

  1. 既存でECSタスクが存在してたら削除
  2. ECSタスクを起動
  3. Aレコードを作成
  4. ECSタスクとAレコードを紐付け

という処理をシェルスクリプト( deploy.sh )に書いて

  1. deploy.sh を立ち上げ時に実行する docker-compose.yml を準備する
  2. それを ecs-cli を使って「デプロイ用のECSタスク」としてアップロードする
  3. AWS Chatbot は、その「デプロイ用のECSタスク」を起動するための AWS Lambda を起動
  4. 「デプロイ用のECSタスク」は、起動処理として deploy.sh を実行
  5. deploy.sh の結果「指定したブランチのコンテナ」と「URL」が生成

という、わりかし意味不明なフローになっている。

「いや deploy-ecs.sh なんて作らんと、直接 node でデプロイのコードを書いて、それをLamdaとして上げれば良いんじゃないの?」という話になるけど、実は deploy-ecs.shecs-cli を利用してて、 ecs-cli にSDKが無いという事情がある。

ecs-cli のおかげで、ECSのタスク定義を書かなくて良くて楽だと思ったら、デプロイフローが難しくなって結果トントンというオチだ。

ちなみにLambda の Custome Runtime を上手く使えば deploy.sh をLambdaとして使えるとは思ったけど、正直よく分かんなかった。

デフォルトでサポートして欲しい。

「小回りがきかない」のが面倒くさい

dockerにおいて「ステートレス」というのは重要なキーワードである。

状態を持たないからこそ、スケールアウト/インの柔軟性があったり、ソフトのチューニングといった「差別化に繋がらない重労働」をなくすことができる。

ただそれがかえって「ステートフル」に比べ回り道を強いられることがあって、それが辛みにつながることがある。

例えば「CIでDockerImageをビルドして、それをECRにプッシュして、Slackからそれをデプロイする」という今回の仕様は「ソースコードの変更をリアルタイムに反映したい」という場合に、回り道に感じられる。

壁パスが発生する時点で待ち時間が増えるし、そもそもこのフローを理解するのにも手間がかかる。

じゃあいっそステートフルに倒して「常にコンテナ立ち上げっぱなしで、都度git pull してきて、ビルドすれば良くない?」という話になる。

ここで大事なのは、やりたいことが「ステートレス」か「ステートフル」なのか見極めて適切なツールを選ぶ、ということだ。

コンテナはそういう「ある種面倒くさいものだ」と認識して、それが許容できない場合にはEC2とかを使うというのも一つの手だ。

脇道にそれたけど、この「小回りがきかない」という問題の解決策としては「すみません、以前みたいにローカル環境でお願いします」と言うしか無い気がしてる。

Feature Stagingを作ったことへの個人的な感想

  • このレベルのインフラを弄るのは、ぶっちゃけ「万が一デグレしたら…!」という心理的な覚悟も必要無いし、レゴブロック弄るみたいで楽しかった。
  • 「コンテナによるインフラ」ってどのくらい流行ってるだろ?コンテナが、例えばEC2に比べ明らかな上位互換かと言われたら、そんなこと無いはず。メリデメ考えたら、コンテナ一色って未来は無いんじゃないかな〜。