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. Reject Day 2023
    EC2からのECS移行において
    IaCとCDをどう変えたか
    株式会社スマートラウンド
    山原 崇史(@shonansurvivors)

    View Slide

  2. 自己紹介
    株式会社スマートラウンド
    SRE / コーポレートIT チーム
    山原 崇史 (やまはら たかし) 
    経歴等
     ・SIer → 銀行 → Web系ベンチャー数社 → 現職
     ・AWS Startup Community Core Member
    好きなAWSサービス
     IAM Identity Center / Security Hub shonansurvivors

    View Slide

  3. 事業およびプロダクト紹介
    ミッション
     スタートアップが可能性を最大限に発揮できる世界をつくる
    smartroundが実現する世界
     統一化・標準化されたデータ管理によって、スタートアップと投資家双方の業務を効率化

    View Slide

  4. 本日のテーマ
    EC2からのECS移行においてIaCとCDをどう変えたか
     ECS移行において単に基盤を入れ替えることだけを目的とするのではなく、
     IaCやCDについても考慮し、運用性や保守性を高めるようにしました。
     その実現に向けた考え方や工夫した点についてお話しします。
    今回話さないこと
    ● EC2からECSへ安全に切り替えるための工夫など
    👉稼働率やレイテンシー等を見ながら段階的に流量変更を行い、無事に無停止で切替完了

    View Slide

  5. アジェンダ
    1. ECS移行前の構成と課題
    2. 今回のECS移行でコンセプトとしたこと
    3. 実際の取り組み
    ○ ビルド
    ○ イメージ管理
    ○ CDツール選定
    ○ IaC
    ○ デプロイ
    ○ CDパイプライン
    4. まとめ

    View Slide

  6. 1. 移行前の構成と課題

    View Slide

  7. 移行前の構成
    Elastic Beanstalk(以降、EB)を中心としたベーシックな Webアプリケーション(+ Lambda関数が数個)

    View Slide

  8. 実際に運用してわかったEBの良かった点
    良かった点
    ● デプロイ処理の構築が非常に容易 (CodePipeline上でデプロイ対象として指定するだけ )
    ● マネージドAMIを使い自動更新を有効化すれば EC2の管理負荷はほぼ無い (脆弱性は自動で解消 )
    ● デプロイ時に任意のスクリプトを実行可能で、カスタマイズ性高く、その作り込みが楽しい

    View Slide

  9. 実際に運用してわかったEBの課題
    課題
    ● Terraformと相性が悪い
    ○ applyに10分以上かかることも(EBのAPIを叩く→FatなCFnの更新が走るという流れのためか )
    ○ 設定値にSensitive valueを含むと、大半の属性の plan差分が非表示になってしまう
    ● 将来Pull Request単位で開発環境を作成可能とする予定だが、 EBはそのボトルネックになりそう
    ○ プロビジョニングに時間がかかり、開発者体験が芳しくないと思われる
    ● 社外にEBの運用経験者が少なく、 多くの場合で新規参画者のキャッチアップが必要

    View Slide

  10. 2. 今回のECS移行でコンセプトとしたこと

    View Slide

  11. 今回の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から「遠ざかる」ことの無いように する
    ○ わかりやすく変えることはあっても、わかりにくく変えることは避ける
    ○ 開発チーム自身が対応可能な範囲を増やす

    View Slide

  12. 3. 実際の取り組み

    View Slide

  13. 3.1. ビルド

    View Slide

  14. パッケージ
    環境別の設定ファイル (conf)が存在し、環境ごとにそれぞれでビルド、 パッケージングしていた
    👉踏襲してしまうと環境ごとに異なるコンテナイメージを作成 することになり、可搬性を損なう
    パッケージ
    環境ごとに異なるパッケージという課題
    デプロイ
    stg環境のEB
    ビルド
    ソース
    stg用conf 本番用conf
    stg用conf jar
    デプロイ
    本番環境のEB
    ビルド
    stg用環境変数
    (シークレット値等)
    本番用環境変数
    (シークレット値等)
    相違 本番用conf jar

    View Slide

  15. パッケージ
    コンテナ化の着手前に、まず 設定ファイル(conf)の共通化を図り、環境差異は全て環境変数で吸収
    👉同じコンテナイメージを各環境で使うための下地 が出来た(ビルドが別なのは、捨て去る
    EBにおいては良しとした
    )
    パッケージ
    環境差異は全て環境変数で吸収
    デプロイ
    stg環境のEB
    ビルド
    ソース
    stg用conf 本番用conf
    共通conf jar
    デプロイ
    本番環境のEB
    ビルド
    stg用環境変数
    (シークレット値等
    +旧confに存在したもの)
    本番用環境変数
    (シークレット値等
    +旧confに存在したもの)
    同一 共通conf jar
    共通conf

    View Slide

  16. コンテナイメージのビルドツールの選定
    前提
    ● 当社はバックエンドに 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

    View Slide

  17. 3.2. イメージ管理

    View Slide

  18. stg環境と本番環境で同一イメージを使用するために
    ● ECRレプリーションを採用
    ○ stg環境と本番環境のインフラ差異が比較的少なくて済む
    ■ stg環境のみに必要:レプリケーション設定
    ■ 本番環境のみに必要:レプリケート受け入れのためのレジストリポリシー
    ○ ECR自体や、ECRからイメージをプルする ECSタスク実行ロールのポリシーが シンプルで済む

    View Slide

  19. ECRレプリケーションを使う場合の注意点
    1. ECRのリポジトリ名はレプリケート先も同一 となる
    ○ 当社はリソース名のプレフィックスに「 {プロジェクト名}-{環境名}-」を付けていたため、
    例えば本番環境に「smartround-stg-webapp」のようなECRが作成されてしまう
    ○ ECRのリポジトリ名からは環境名を外すことで対処した (例:smartround-webapp)
    2. レプリケート先でもAmazon InspectorのECRイメージスキャンのコストがかかる
    ○ 同一のイメージに二重でコストがかかるので、気持ち的には勿体無く感じてしまう

    View Slide

  20. (余談)ECRレプリケーションを使わないとしたら
    CI/CD専用AWSアカウントを用意し、そこで ECRの中央管理も担わせ、
    stg・本番環境それぞれに対して、クロスアカウントでデプロイを行うアプローチなど
    https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/deployments-ou.html

    View Slide

  21. 3.3. CDツール選定

    View Slide

  22. Elastic Beanstalk利用時のCDツール
    ● CodePipelineを全面的に利用
    ● 社内ポリシーで本番リリースに 承認を必要としており、CodePipelineの承認アクションで担保
    ● 今回移行対象外のDBマイグレーションやLambdaデプロイもCodePipeline上で実行していた

    View Slide

  23. 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)

    View Slide

  24. 3.4. IaC

    View Slide

  25. 当社のIaC化状況とリポジトリ管理
    IaC化の状況
    ● 全AWSリソースをTerraformで管理
    リポジトリ管理
    ● 全てのTerraformコードを専用のTerraform用リポジトリで一元管理
    ● アプリケーションはアプリケーション用のリポジトリ で管理
    ※一部例外として、
    Lambdaとその周辺リソースの定義を、アプリケーションのリポジトリ内で
    Serverless Frameworkにて管理している

    View Slide

  26. 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サービス更新

    View Slide

  27. ecspresso採用による課題解消
    ecspressoとは
    ● ECS用のデプロイツール https://github.com/kayac/ecspresso
    ● ECSサービスとタスク定義を管理 しつつ、デプロイを行う
    👉ECSサービスとタスク定義を Terraformではなくecspressoが管理することによって、
     デプロイ後にterraform planで差分が出る問題を解消 できる

    View Slide

  28. ecspresso採用のその他の狙い
    ECSサービスやタスク定義のファイルを アプリケーションのリポジトリに配置 することで
    開発チームがECSをメンテナンスしやすくなることが期待 できる(例:ECSへの環境変数の設定など )
    アプリケーションのリポジトリ
    .
    ├── ...
    ├── batch
    ├── db
    ├── ...
    └── web
    ├── ...
    └── ecspresso
    ├── ecspresso.yml # ecspresso全体の設定ファイル
    ├── ecs-service-def.json # ECSサービスの定義
    ├── ecs-task-def.json # タスク定義
    └── README.md

    View Slide

  29. 3.5. デプロイ

    View Slide

  30. 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

    View Slide

  31. 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"
    },
    # 略

    View Slide

  32. 関連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管理はされるものではない

    View Slide

  33. 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

    View Slide

  34. 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` }}"
    }
    ],
    # 略
    }

    View Slide

  35. 配列を取得する場合の考慮
    配列を取得する場合、 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される。

    View Slide

  36. 3.6. CDパイプライン

    View Slide

  37. 現状のブランチ戦略とデプロイ
    releaseブランチをmainブランチにマージすると、本番デプロイが開始される

    View Slide

  38. 想定するイメージビルドとECSデプロイの流れ
    stg環境で作成したコンテナイメージを本番環境でも使いたい (イメージタグはコミットハッシュとする )
    ※補足:CodePipelineでは他にDBマイグレーションやLambdaへのデプロイも行なっているが、上図含め以降の図では記載を割愛

    View Slide

  39. 生じる課題
    mainのマージコミット(ccc)が本番デプロイされるため、
    stg環境で作成したイメージを使う流れにならない

    View Slide

  40. 解決案
    releaseブランチのHEADにバージョンタグを付け、これをトリガーに本番デプロイする

    View Slide

  41. 解決を阻む制約
    CodePipelineの制約
    ● 接続先を特定のブランチに固定して使用する必要がある
    ● そのため、任意のタグをデプロイするようなことができない
    ○ 単発のCodeBuildは起動の都度柔軟にブランチやタグを選べるが、 CodePipelineは不可

    View Slide

  42. CodePipelineの制約を乗り越える
    ● CodePipelineの入力ソースをGitHubからS3に変更
    ○ S3変更検知用のCloudTrailとEventBridgeが作成される(※)
    ● GitHub ActionsからS3にアップロード
    👉任意のタグでCodePipelineを起動可能となる
    ※マネコンで変更した場合の標準の構成。
     これらを削除して、新規
    EventBridgeとS3イベント通知の組み合わせでも
     実現可能。

    View Slide

  43. デプロイ運用の変化に対する検討
    デプロイ開始トリガーが mainブランチへのマージだったのが releaseブランチへのタグ付けに変更 される
    ● 結果としてBranch Deploymentと呼ばれる手法となる
    ○ 参考: https://github.com/github/branch-deploy#branch-deployment-core-concepts-
    ● デプロイ後、対象の環境の安定を確認できたら releaseブランチをmainブランチへマージする運用

    View Slide

  44. デプロイ運用の変化に対する検討
    一方で、SREとして当社の開発チームの メンタルモデルを考えた
    ● mainブランチへのマージによるデプロイの方が 直感的ではないか?(※1)
    ● releaseブランチへのタグ付け後の、 mainブランチへのマージを忘れる というリスクもある(※2)
    👉検討の結果、デプロイ開始のトリガーを 引き続きmainブランチへのマージとする ことにした
    ※1 ※2 開発チームがこうした意見を言って運用の変更に反対したということではありません

    View Slide

  45. GitHub Actionsの見直し
    マージ後のmainブランチの直前のコミットにタグ付け し、そのタグのソースを S3にアップロード

    View Slide

  46. GitHub Actionsの仕様に関する補足(AWSとは関係無し)
    なぜS3アップロードのWFのトリガーをタグプッシュではなく、上のタグ付け WFの完了としたのか?
    👉各WFでは標準のGITHUB_TOKENを使っており、トリガーが on:pushのWFは連続起動しない。
     on:workflow_runなら起動する。
    2つのWFを1本化すれば良いのではないか?
    👉任意のタグを指定して S3アップロードのみを
     手動起動(workflow_dispatch)できるようにも
     しておきたかったため、 WFを分離している。
     失敗時の回復性を高めたい という意図。
    main

    View Slide

  47. 完成したCDパイプライン
    CodePipelineで運用体験を変えず に、stgで作成したイメージを本番環境で使用するパイプラインを実現
    ※説明を省略したが、上図の通り、
    stgデプロイ時についても
    S3アップロード方式に併せて変更している

    View Slide

  48. 4. まとめ

    View Slide

  49. まとめ
    1. stg環境で作成/検証済みのイメージを本番環境でも使用するために
    ○ 設定を環境変数で吸収
    ○ ECRレプリケーションの利用
    ○ GitHub Actions、S3、CodePipelineを活用したCDパイプラインの構築
    2. イメージビルドのベストプラクティスに負担無く沿うために
    ○ Jibを採用しDockerfileレスでベストプラクティスに追随
    3. ECSのIaC化で生じる課題を解消しつつ開発チームでも ECSをメンテしやすくするために
    ○ ecspressoの採用
    4. 開発チームが運用しやすいように
    ○ 各所で現状の知識やメンタルモデル等を考慮した運用設計

    View Slide

  50. スマートラウンドでは新しいメンバーを募集中です
    私たちと一緒にスタートアップが可能性を最大限に発揮できる世界をつくりませんか?
    jobs.smartround.com

    View Slide