2月にバクラク事業部Platform Engineering部DevOpsチームに入社したid:itkqです。7月はLayerXエンジニアブログを活発にしよう月間 ということで、この記事では、私が入社してから中心となって進めた、ECSサービスのデプロイの改善について書いています。
バクラクのインフラ
私が所属するバクラク事業部では、バクラク請求書をはじめとする、BtoB向けのSaaSを提供しています。SaaSは主にAWS上でホストしており、サービスの大半がECS Fargateにデプロイされています。昨年、プロダクト開発をイネーブルメントするEnablingチームが発足し*1 、今後の事業成長を支えられるようなソフトウェアアーキテクチャと周辺の仕組みが発達してきています。以下の記事で述べられているように、モノレポかつサービスが多数存在します。
DevOpsチームの目線では、ECSサービスが50+存在する、マイクロサービスのCI/CDをいい感じにする必要があります。今のところ、モノレポ上に構成したGitHub Actionsを大いに活用したCIOpsを行っています。今回は、特にECSサービスのデプロイを改善した話をします。
既存のECSデプロイの仕組み
アプリケーションコードとインフラコードともに、自動生成を積極的に取り入れています。独自のサービス定義ファイルがあり、コード生成コマンドを発行すると、Goやprotoのコードだけでなく、TerraformやECSタスク定義のコードが生成されます。ここでは特にインフラコードの自動生成について説明します。
前提
インフラはTerraformで管理されており、コードはgithub.comでホスティングしています。各サービスには専用のTerraform実行基盤があり、GitHub Actions(以降、GHA)とCodePipeline + CodeBuildで構成されています。
ECSサービスをデプロイする
ECSサービスとそのCIを立ち上げるには以下の手順が必要です。なお「サービス」はモノレポにおけるconnectサービスのことを指します。
- サービスを定義する(サービス定義は独自のものです。中身は今回省略します)
- サービス定義を元に自動生成を行い、前述したサービス専用のTerraform実行基盤を作成
- サービス定義を元に自動生成を行い、初回用のダミーECSタスク定義とECSサービス、Dockerイメージを格納するECRリポジトリ、タスク用のIAMロール・Security Group、MySQLユーザなどのリソースを作成
- サービス定義を元に自動生成を行い、ECSタスク定義の生成元となるCUE*2ファイルとデプロイ用のGHAワークフローを作成。
- cueコマンドからタスク定義のJSONを生成し、aws-actions/amazon-ecs-deploy-task-definition でECSサービスをデプロイ。データベースのスキーママイグレーションなどのタスクは yyoshiki41/ecs-run-task-action を使う*3
課題
上述した構成では、特に以下の問題がありました。
- 手元でデプロイを実行できない
- GHA上のテスト・デバッグが面倒でした
- いわゆるdry-runの仕組みがない
- デプロイでECSの設定がどう変わるか知りたい場面は少なくありません
- 秘匿値の設定が面倒
- ECSタスクの秘匿値は、ネイティブサポートされているAWS Secrets Managerで管理しています。ただし、シークレットのARNにはランダムなsuffixが追加され、またECSタスク定義では完全なARNが必要なため、suffixを調べて追記するトイルが発生していました
新しいECSデプロイの仕組み
ECSのデプロイツールである https://github.com/kayac/ecspresso を採用し、これを中心としたECSデプロイ基盤を構成しました。既存の課題は以下のように解決できます。
- Go製のツールであり手元での実行がしやすい (e.g.
ecspresso render
) - 差分検出 (
ecspresso diff
) や dry-run をサポートしている - tfstate plugin *4 によりシークレットARNを参照可能
ECSデプロイの実行は、これまでと変わらずGHAです。kayac/ecspresso を使い、デプロイは ecspresso deploy
、タスク実行は ecspresso run
を行います。
ecspressoの採用理由
ECSデプロイを行うツールはいくつか存在します。比較検討の詳細は省略しますが、ecspressoは既存の課題を解決するだけでなく、特に以下の嬉しいポイントがあると考えています。
- ECSに最適化されているため、ECS本体のアップデートへの追従が速い
- Terraformと共存しやすい
- OSSであり、現在も活発にメンテナンスされている
- (特に国内で)多くの本番運用実績がある
- ドキュメンテーションが充実している*5*6
移行
バクラク事業部では軽量なADR (Architectural Decision Records) とDesign Docsの両方を活用しています。議論の余地が少ないものはADRとして残す場合が多いです。今回のecspresso移行は、事前にEnablingチームとDevOpsチームと話したところ特に議論が無かったため、ADRを最初に記述しました。
既存のECSデプロイの仕組みは素朴なものだったので、特に移行が難しいポイントはありませんでした。ecspressoにはinitというコマンド*7 があり、既存のECSサービスから設定ファイルを生成できるため、初期設定はかなり楽に済みました。
工夫した点
ecspresso diffの活用
Dockerイメージを作成した後に、イメージタグを書き換えるpull requestが作成する仕組みがあり、pull requestのマージでデプロイが発火します。pull requestイベントをトリガーとして、 ecspresso diff
を実行し、コメントするようにしています*8。これにより、変更が意図通りのものかをpull request上で確認できます。データベースマイグレーションも同様にdry-runとコメントします。
ecspresso verifyの活用
ecspressoには、verifyという外部リソースのチェックを行えるコマンド*9 があり、これをデプロイ時に実行しています。ecsspresso verify
を実行することで、例えば初回デプロイの際に、シークレットの箱は存在するものの値が入ってない状況を検出できます。これにより、デプロイ後にECSタスクが起動と終了を繰り返す状況を事前に回避できます。
その他細かい工夫
他の細かい工夫を以下に列挙しました。
- アプリケーションコードの変更がmainブランチにpushされると、開発環境への自動デプロイが走るように設定しています。ただし、サービス数が多いとデプロイ時間がかさみ、GHAの課金額が増加してしまいます。そこで開発環境のみ
—-no-wait
フラグを有効化し、ECSサービスの安定を待たずにGHAの実行を終了する対策を講じています - .ecspresso-versionファイル*10 を使ってkayac/ecspressoの実行バージョンを指定し、開発環境などの部分的なアップデートや全体反映の作業を楽にしています
- ecspressoがJsonnetをネイティブサポートしているので、設定ファイルにはJsonnetを使っています。ネイティブサポートのおかげで、何もせずともコードコメントや抽象化が捗っています。ちなみに私はJsonnetが大好きです
今後の展望
現時点で、近々やりたいことは次の2点があります。
- ECS定義ファイルの生成するCUEファイルをJsonnetに置き換える
- CUEにはファーストクラスのバリデーションや定義ファイルからのスクリプト実行など、良いところがあると理解していますが、学習コストの観点から、Jsonnetと共存させる積極的な理由はないと考えています。すでに一部はJsonnetに移行し始めています
- ユニバーサルバイナリを活用したデプロイメントの集約
- ECSサービスが多いと単純にコストが増加しがちなため、サービスを集約することでECSサービス数を圧縮できるのではないかと目論んでいます
まとめ
ECSのデプロイツールとしてecspressoを採用し、これまでの問題点を解消しつつ、工夫した点を紹介しました。今後も改善を続け、安全に素早く変更をリリースできるCI/CD基盤を整えていきます。ecspresso本体への貢献も積極的に行っていきたいです。
*1:https://type.jp/et/feature/20929/
*3:https://tech.layerx.co.jp/entry/2022/06/08/090000 で解説されています
*4:https://zenn.dev/fujiwara/articles/ecspresso-20201219
*5:一人アドベントカレンダー https://adventar.org/calendars/5916
*6:https://zenn.dev/fujiwara/books/ecspresso-handbook-v2
*7:https://zenn.dev/fujiwara/articles/f2314651691adcae5215
*8:https://github.com/suzuki-shunsuke/github-comment を活用