Upgrade to Pro — share decks privately, control downloads, hide ads and more …

EC2からのECS移行においてIaCとCDをどう変えたか

 EC2からのECS移行においてIaCとCDをどう変えたか

shonansurvivors

June 24, 2023
Tweet

More Decks by shonansurvivors

Other Decks in Technology

Transcript

  1. 自己紹介 株式会社スマートラウンド SRE / コーポレートIT チーム 山原 崇史 (やまはら たかし) 

    経歴等  ・SIer → 銀行 → Web系ベンチャー数社 → 現職  ・AWS Startup Community Core Member 好きなAWSサービス  IAM Identity Center / Security Hub shonansurvivors
  2. アジェンダ 1. ECS移行前の構成と課題 2. 今回のECS移行でコンセプトとしたこと 3. 実際の取り組み ◦ ビルド ◦

    イメージ管理 ◦ CDツール選定 ◦ IaC ◦ デプロイ ◦ CDパイプライン 4. まとめ
  3. 実際に運用してわかったEBの課題 課題 • Terraformと相性が悪い ◦ applyに10分以上かかることも(EBのAPIを叩く→FatなCFnの更新が走るという流れのためか ) ◦ 設定値にSensitive valueを含むと、大半の属性の

    plan差分が非表示になってしまう • 将来Pull Request単位で開発環境を作成可能とする予定だが、 EBはそのボトルネックになりそう ◦ プロビジョニングに時間がかかり、開発者体験が芳しくないと思われる • 社外にEBの運用経験者が少なく、 多くの場合で新規参画者のキャッチアップが必要
  4. 今回の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から「遠ざかる」ことの無いように する ◦ わかりやすく変えることはあっても、わかりにくく変えることは避ける ◦ 開発チーム自身が対応可能な範囲を増やす
  5. パッケージ コンテナ化の着手前に、まず 設定ファイル(conf)の共通化を図り、環境差異は全て環境変数で吸収 👉同じコンテナイメージを各環境で使うための下地 が出来た(ビルドが別なのは、捨て去る EBにおいては良しとした ) パッケージ 環境差異は全て環境変数で吸収 デプロイ

    stg環境のEB ビルド ソース stg用conf 本番用conf 共通conf jar デプロイ 本番環境のEB ビルド stg用環境変数 (シークレット値等 +旧confに存在したもの) 本番用環境変数 (シークレット値等 +旧confに存在したもの) 同一 共通conf jar 共通conf
  6. コンテナイメージのビルドツールの選定 前提 • 当社はバックエンドに 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
  7. 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)
  8. 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サービス更新
  9. ecspresso採用のその他の狙い ECSサービスやタスク定義のファイルを アプリケーションのリポジトリに配置 することで 開発チームがECSをメンテナンスしやすくなることが期待 できる(例:ECSへの環境変数の設定など ) アプリケーションのリポジトリ . ├──

    ... ├── batch ├── db ├── ... └── web ├── ... └── ecspresso ├── ecspresso.yml # ecspresso全体の設定ファイル ├── ecs-service-def.json # ECSサービスの定義 ├── ecs-task-def.json # タスク定義 └── README.md
  10. 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
  11. 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" }, # 略
  12. 関連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管理はされるものではない
  13. 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
  14. 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` }}" } ], # 略 }
  15. 配列を取得する場合の考慮 配列を取得する場合、 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される。
  16. 解決を阻む制約 CodePipelineの制約 • 接続先を特定のブランチに固定して使用する必要がある • そのため、任意のタグをデプロイするようなことができない ◦ 単発のCodeBuildは起動の都度柔軟にブランチやタグを選べるが、 CodePipelineは不可 (2024年2月追記)

    上記はCodePipeline v1の制約。 本登壇資料は2023年6月当時の情報を元に作成されているが、 2023年10月に発表されたCodePipeline v2で はタグを起動トリガーとすることが可能になり、以降も起動トリガーまわりで選べる条件が順次アップデートされ ていっている。
  17. デプロイ運用の変化に対する検討 デプロイ開始トリガーが mainブランチへのマージだったのが releaseブランチへのタグ付けに変更 される • 結果としてBranch Deploymentと呼ばれる手法となる ◦ 参考:

    https://github.com/github/branch-deploy#branch-deployment-core-concepts- • デプロイ後、対象の環境の安定を確認できたら releaseブランチをmainブランチへマージする運用
  18. デプロイ運用の変化に対する検討 一方で、SREとして当社の開発チームの メンタルモデルを考えた • mainブランチへのマージによるデプロイの方が 直感的ではないか?(※1) • releaseブランチへのタグ付け後の、 mainブランチへのマージを忘れる というリスクもある(※2)

    👉検討の結果、デプロイ開始のトリガーを 引き続きmainブランチへのマージとする ことにした ※1 ※2 開発チームがこうした意見を言って運用の変更に反対したということではありません
  19. まとめ 1. stg環境で作成/検証済みのイメージを本番環境でも使用するために ◦ 設定を環境変数で吸収 ◦ ECRレプリケーションの利用 ◦ GitHub Actions、S3、CodePipelineを活用したCDパイプラインの構築

    2. イメージビルドのベストプラクティスに負担無く沿うために ◦ Jibを採用しDockerfileレスでベストプラクティスに追随 3. ECSのIaC化で生じる課題を解消しつつ開発チームでも ECSをメンテしやすくするために ◦ ecspressoの採用 4. 開発チームが運用しやすいように ◦ 各所で現状の知識やメンタルモデル等を考慮した運用設計