Share
Embed
EC2からのECS移行においてIaCとCDをどう変えたか
Reject Day 2023(https://connpass.com/event/282843/) 登壇資料
登壇動画: https://www.youtube.com/live/kMiijJdWi-s?feature=share&t=4500
shonansurvivors
June 24, 2023
Transcript
50 slides
-
自己紹介 株式会社スマートラウンド SRE / コーポレートIT チーム 山原 崇史 (やまはら たかし)
経歴等 ・SIer → 銀行 → Web系ベンチャー数社 → 現職 ・AWS Startup Community Core Member 好きなAWSサービス IAM Identity Center / Security Hub shonansurvivors -
アジェンダ 1. ECS移行前の構成と課題 2. 今回のECS移行でコンセプトとしたこと 3. 実際の取り組み ◦ ビルド ◦
イメージ管理 ◦ CDツール選定 ◦ IaC ◦ デプロイ ◦ CDパイプライン 4. まとめ -
実際に運用してわかったEBの良かった点 良かった点 • デプロイ処理の構築が非常に容易 (CodePipeline上でデプロイ対象として指定するだけ ) • マネージドAMIを使い自動更新を有効化すれば EC2の管理負荷はほぼ無い (脆弱性は自動で解消
) • デプロイ時に任意のスクリプトを実行可能で、カスタマイズ性高く、その作り込みが楽しい -
実際に運用してわかったEBの課題 課題 • Terraformと相性が悪い ◦ applyに10分以上かかることも(EBのAPIを叩く→FatなCFnの更新が走るという流れのためか ) ◦ 設定値にSensitive valueを含むと、大半の属性の
plan差分が非表示になってしまう • 将来Pull Request単位で開発環境を作成可能とする予定だが、 EBはそのボトルネックになりそう ◦ プロビジョニングに時間がかかり、開発者体験が芳しくないと思われる • 社外にEBの運用経験者が少なく、 多くの場合で新規参画者のキャッチアップが必要 -
今回のECS移行(コンテナ移行)におけるコンセプト 1. 各種のベストプラクティスを参考にそれらを取り込んだ設計、実装を心掛ける ◦ The Twelve-Factor App https://12factor.net/ja/ ▪ III.
設定を環境変数に格納する ▪ V. ビルド、リリース、実行の 3つのステージを厳密に分離する ◦ Best practices for writing Dockerfiles https://docs.docker.com/develop/develop-images/dockerfile_best-practices 2. 今回の移行によって 開発チームがインフラや CDから「遠ざかる」ことの無いように する ◦ わかりやすく変えることはあっても、わかりにくく変えることは避ける ◦ 開発チーム自身が対応可能な範囲を増やす -
パッケージ 環境別の設定ファイル (conf)が存在し、環境ごとにそれぞれでビルド、 パッケージングしていた 👉踏襲してしまうと環境ごとに異なるコンテナイメージを作成 することになり、可搬性を損なう パッケージ 環境ごとに異なるパッケージという課題 デプロイ stg環境のEB
ビルド ソース stg用conf 本番用conf stg用conf jar デプロイ 本番環境のEB ビルド stg用環境変数 (シークレット値等) 本番用環境変数 (シークレット値等) 相違 本番用conf jar -
パッケージ コンテナ化の着手前に、まず 設定ファイル(conf)の共通化を図り、環境差異は全て環境変数で吸収 👉同じコンテナイメージを各環境で使うための下地 が出来た(ビルドが別なのは、捨て去る EBにおいては良しとした ) パッケージ 環境差異は全て環境変数で吸収 デプロイ
stg環境のEB ビルド ソース stg用conf 本番用conf 共通conf jar デプロイ 本番環境のEB ビルド stg用環境変数 (シークレット値等 +旧confに存在したもの) 本番用環境変数 (シークレット値等 +旧confに存在したもの) 同一 共通conf jar 共通conf -
コンテナイメージのビルドツールの選定 前提 • 当社はバックエンドに JVM系言語であるKotlinを使用 選定結果 • コンテナイメージのビルドツールに Jibを採用 https://github.com/GoogleContainerTools/jib
◦ Dockerfileを書く必要が無く、既存のJavaビルドツール(Gradle等)の延長線上で利用できる ◦ イメージビルドのベストプラクティスを自動的に適用 し、細かな考慮から解放される 参考:STORES決済におけるEC2からECS Fargateへの移行〜無停止要件も添えて〜 | RyoMa_0923 https://speakerdeck.com/fufuhu/storesjue-ji-niokeruec2karaecs-fargatehefalseyi-xing-wu-ting-zhi-yao-jian-motian-ete -
stg環境と本番環境で同一イメージを使用するために • ECRレプリーションを採用 ◦ stg環境と本番環境のインフラ差異が比較的少なくて済む ▪ stg環境のみに必要:レプリケーション設定 ▪ 本番環境のみに必要:レプリケート受け入れのためのレジストリポリシー ◦
ECR自体や、ECRからイメージをプルする ECSタスク実行ロールのポリシーが シンプルで済む -
ECRレプリケーションを使う場合の注意点 1. ECRのリポジトリ名はレプリケート先も同一 となる ◦ 当社はリソース名のプレフィックスに「 {プロジェクト名}-{環境名}-」を付けていたため、 例えば本番環境に「smartround-stg-webapp」のようなECRが作成されてしまう ◦ ECRのリポジトリ名からは環境名を外すことで対処した
(例:smartround-webapp) 2. レプリケート先でもAmazon InspectorのECRイメージスキャンのコストがかかる ◦ 同一のイメージに二重でコストがかかるので、気持ち的には勿体無く感じてしまう -
ECS移行におけるCDツール選定 検討内容 • GitHub Actionsを一部(ビルドのみ等)または全部(ビルド&デプロイ)で採用することも考えられた 検討結果 • 現状と同じく全てをCodePipelineで実施することとした ◦ 現状のデプロイフローを大幅に変更しなくて済む
◦ 並行稼働期間中における Elastic BeanstalkとECSの同時並行デプロイも組みやすい ◦ GitHub ActionsでもEnterpriseであれば承認を組み込めるが、当社プランは Enterprise未満 新たな課題 • CodePipelineを選択したことで 「stg環境で作成したコンテナイメージを本番環境で使う」ことの実現に工夫を要することに • 詳細は後半(3.6.CDパイプライン)で改めて説明 (参考:ECS用のCDパイプラインに対する考察 | reireias https://zenn.dev/reireias/articles/8e987af2762eaa) -
当社のIaC化状況とリポジトリ管理 IaC化の状況 • 全AWSリソースをTerraformで管理 リポジトリ管理 • 全てのTerraformコードを専用のTerraform用リポジトリで一元管理 • アプリケーションはアプリケーション用のリポジトリ で管理
※一部例外として、 Lambdaとその周辺リソースの定義を、アプリケーションのリポジトリ内で Serverless Frameworkにて管理している -
ECSのIaC化で生じる課題 ECSではデプロイの都度、タスク定義の新 revision作成とECSサービスの更新が発生する。 結果、デプロイ後にterraform planを行うと、これらの更新結果が 差分として検知されてしまう。 イメージ:aaa イメージ:bbb タスク定義 revision:1
ECSサービス revision:2 デプロイ対象:rev1 「メドピア AWS勉強会 ECS編 https://speakerdeck.com/reireias/medpeer-aws-seminar-ecs?slide=25」を元に作成 イメージ:aaa イメージ:bbb タスク定義 revision:1 ECSサービス revision:2 デプロイ対象:rev2 イメージ:aaa イメージ:bbb タスク定義 revision:1 ECSサービス デプロイ対象:rev1 新規イメージ作成 タスク定義に新revision作成 ECSサービス更新 -
ecspresso採用のその他の狙い ECSサービスやタスク定義のファイルを アプリケーションのリポジトリに配置 することで 開発チームがECSをメンテナンスしやすくなることが期待 できる(例:ECSへの環境変数の設定など ) アプリケーションのリポジトリ . ├──
... ├── batch ├── db ├── ... └── web ├── ... └── ecspresso ├── ecspresso.yml # ecspresso全体の設定ファイル ├── ecs-service-def.json # ECSサービスの定義 ├── ecs-task-def.json # タスク定義 └── README.md -
ecspressoをCodeBuildで実行する 当社はCDにCodePipelineを選定したため、CodeBuildでecspressoを実行 version: 0.2 env: shell: bash variables: ECSPRESSO_VERSION: 2.0.1
phases: install: commands: - curl -sL -O https://github.com/kayac/ecspresso/releases/download/ v${ECSPRESSO_VERSION}/ecspresso_${ECSPRESSO_VERSION}_linux_amd64.tar.gz - tar xzvf ecspresso_${ECSPRESSO_VERSION}_linux_amd64.tar.gz - sudo install ecspresso /usr/local/bin/ecspresso - ecspresso version # 略 build: commands: - cd path/to/ecspresso - ecspresso deploy --envfile=${SR_ENV}.env . └── ecspresso ├── buildspec.yml ├── ecspresso.yml ├── ecs-service-def.json ├── ecs-task-def.json └── README.md -
ECSサービスやタスク定義ファイルを環境間でDRYに • 機密性の無い値は環境別の envファイルに記述し、--envfileオプションで読み込ませる ◦ ecspresso deploy --envfile=${SR_ENV}.env ※SRは、smartroundの意 •
機密性のある値はenvファイルに記述せず、パラメータストアから読み込ませる (ECS標準の機能) . └── ecspresso ├── buildspec.yml ├── ecspresso.yml ├── ecs-service-def.json ├── ecs-task-def.json ├── stg.env ├── prd.env └── README.md # stg.envより抜粋 FOO=xxxxx BAR=xxxxx # ecs-task-def.jsonより抜粋 { "containerDefinitions": [ { "environment": [ { "name": "FOO", "value": "{{ must_env `FOO` }}" }, # 略 ], "secrets": [ { "name": "BAZ_API_KEY", "valueFrom": "/smartround/{{ must_env `SR_ENV` }}/baz_api_key" }, # 略 -
関連AWSリソースのARNやIDの取得について ECSサービスの定義には以下の指定が必要 • ターゲットグループの ARN : arn:aws:elasticloadbalancing:region:account_id:targetgroup/name/xxxxxxxxxxxxxxxxxx • サブネットID :
subnet-xxxxxxxxxxxxxxxxx • セキュリティグループ ID: sg-xxxxxxxxxxxxxxxx 👉これらをファイルに値を直接記述すると可読性やメンテナンス性が落ちる懸念がある tfstate読み込み機能について • ecspressoは、Terraformのtfstate(状態管理ファイル)の読み込みをサポート する • そのため、値をファイルに記述すること無く tfstateから前述のARNやIDを取得することが可能 (※) ※補足:tfstate(状態管理ファイル)は、S3やTerraform Cloudなどに保管されるもので、 Terraform用のリポジトリでもGit管理はされるものではない -
tfstate読み込み機能の利用の検討 しかし、以下の観点から tfstateの読み込み機能は「現段階では」利用を見送った • Terraformへの一定の理解も必要で、 当社の開発チームが ECSを保守する敷居が上がると判断 • アプリケーションのリポジトリに、 別リポジトリのTerraformの知識を持ち込むことへのためらい
👉利用することで開発者に Terraformへの関心を持たせる下地となるとの考えもあり、今後継続検討 参考:DevOps実装初期フェーズの組織がTerraformとecspressoで求めるAmazon ECS CI/CDの最適解 | Tocyuki https://speakerdeck.com/tocyuki/aws-ecs-cicd-with-terraform-and-ecspresso?slide=37 -
AWS CLIの利用 前述の値は、CodeBuild上でAWS CLIを実行して取得することとした (AWSリソースのnameプロパティやNameタグの値を元にリソースを特定して取得 ) CodeBuild上でAWS CLIを実行して取得することとし # buildspec.ymlより、ターゲットグループ
ARN取得部分を抜粋 pre_build: commands: # 略 - TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups \ --names "smartround-${SR_ENV}-webapp" \ --query 'TargetGroups[0].TargetGroupArn' --output text) - export TARGET_GROUP_ARN - echo "$TARGET_GROUP_ARN" # ログ出力用 # 略 build: commands: - cd path/to/ecspresso_dir - ecspresso deploy \ --envfile=${SR_ENV}.env # ecs-service-def.jsonより抜粋 { # 略 "loadBalancers": [ { "containerName": "nginx", "containerPort": 80, "targetGroupArn": "{{ must_env `TARGET_GROUP_ARN` }}" } ], # 略 } -
配列を取得する場合の考慮 配列を取得する場合、 json側ではvalueの前後にダブルクォートを付けないようにする # buildspec.ymlより、サブネットID取得部分を抜粋 pre_build: commands: # 略 #
JSONに埋め込み可能な配列形式で取得したいので、 --output textは付けない - SUBNET_IDS=$(aws ec2 describe-subnets \ --filters "Name=tag:Name,Values=smartround-${SR_ENV}-private-*" \ --query 'Subnets[*].SubnetId') - export SUBNET_IDS - echo "SUBNET_IDS" # ログ出力用 # 略 build: commands: - cd path/to/ecspresso_dir - ecspresso deploy \ --envfile=${SR_ENV}.env # ecs-service-def.jsonより抜粋 { # 略 "networkConfiguration": { "awsvpcConfiguration": { "assignPublicIp": "DISABLED", "subnets": {{ must_env `SUBNET_IDS` }}, # 略 } }, # 略 } [ "subnet-xxxxxxxxxxxxxxxxxx", "subnet-xxxxxxxxxxxxxxxxxx" ] "{{ … }}"とはしない。 ファイルとしてはIDEに怒られるが最終的に正しい jsonとしてrenderされる。 -
解決を阻む制約 CodePipelineの制約 • 接続先を特定のブランチに固定して使用する必要がある • そのため、任意のタグをデプロイするようなことができない ◦ 単発のCodeBuildは起動の都度柔軟にブランチやタグを選べるが、 CodePipelineは不可 (2024年2月追記)
上記はCodePipeline v1の制約。 本登壇資料は2023年6月当時の情報を元に作成されているが、 2023年10月に発表されたCodePipeline v2で はタグを起動トリガーとすることが可能になり、以降も起動トリガーまわりで選べる条件が順次アップデートされ ていっている。 -
CodePipelineの制約を乗り越える • CodePipelineの入力ソースをGitHubからS3に変更 ◦ S3変更検知用のCloudTrailとEventBridgeが作成される(※) • GitHub ActionsからS3にアップロード 👉任意のタグでCodePipelineを起動可能となる ※マネコンで変更した場合の標準の構成。
これらを削除して、新規 EventBridgeとS3イベント通知の組み合わせでも 実現可能。 -
デプロイ運用の変化に対する検討 デプロイ開始トリガーが mainブランチへのマージだったのが releaseブランチへのタグ付けに変更 される • 結果としてBranch Deploymentと呼ばれる手法となる ◦ 参考:
https://github.com/github/branch-deploy#branch-deployment-core-concepts- • デプロイ後、対象の環境の安定を確認できたら releaseブランチをmainブランチへマージする運用 -
デプロイ運用の変化に対する検討 一方で、SREとして当社の開発チームの メンタルモデルを考えた • mainブランチへのマージによるデプロイの方が 直感的ではないか?(※1) • releaseブランチへのタグ付け後の、 mainブランチへのマージを忘れる というリスクもある(※2)
👉検討の結果、デプロイ開始のトリガーを 引き続きmainブランチへのマージとする ことにした ※1 ※2 開発チームがこうした意見を言って運用の変更に反対したということではありません -
GitHub Actionsの仕様に関する補足(AWSとは関係無し) なぜS3アップロードのWFのトリガーをタグプッシュではなく、上のタグ付け WFの完了としたのか? 👉各WFでは標準のGITHUB_TOKENを使っており、トリガーが on:pushのWFは連続起動しない。 on:workflow_runなら起動する。 2つのWFを1本化すれば良いのではないか? 👉任意のタグを指定して S3アップロードのみを
手動起動(workflow_dispatch)できるようにも しておきたかったため、 WFを分離している。 失敗時の回復性を高めたい という意図。 main -
まとめ 1. stg環境で作成/検証済みのイメージを本番環境でも使用するために ◦ 設定を環境変数で吸収 ◦ ECRレプリケーションの利用 ◦ GitHub Actions、S3、CodePipelineを活用したCDパイプラインの構築
2. イメージビルドのベストプラクティスに負担無く沿うために ◦ Jibを採用しDockerfileレスでベストプラクティスに追随 3. ECSのIaC化で生じる課題を解消しつつ開発チームでも ECSをメンテしやすくするために ◦ ecspressoの採用 4. 開発チームが運用しやすいように ◦ 各所で現状の知識やメンタルモデル等を考慮した運用設計