前回の記事から間が空いてしまいました、SREのbutadoraです。
年末に向けた準備で忙しなくしているこの頃です。
今回はとある環境で実装したCI/CDのフローを紹介したいと思います。
今回のサービスアーキテクチャ
今回はPHP製WEBサービスをデプロイする環境が必要ということで、以下の様な設計としました。
- WEBサービス本体 → ALB+ECS+RDS
- 定時バッチサービス → ECS (Task Scheduler)+RDS
- ファイル設置をトリガーにしたバッチサービス → S3+Lambda(コンテナイメージ)+RDS
CI/CD
簡単な構成図はこんな感じです。
大きなポイントとしては、タイトルにある3種の各デプロイツールを組み合わせることで、開発側のリソース管理を切り出しているところです。
弊社ではAWSリソースの管理をTerraformで行っていますが、図にあるようなリソースまで管理対象としてしまうと、 SRE側の運用に伴うデプロイサイクルと開発によるデプロイサイクルで衝突が発生してしまいます。
そこで、各サービスで利用する以下サービスのリソース管理をTerraformでは行わず、開発側のリポジトリでコード管理しています。
- ECS TaskDefinition、Service Definition
- ECS TaskScheduler
- ECR(Docker Image)
- Lambdaリソース
これにより、開発と運用のデプロイサイクルを分離することができました🎉
各デプロイツールのご紹介
ここで今回のデプロイ3種の神器をご紹介します。
ecspresso
- fujiwaraさんによって作成された、ECS周りのデプロイツールです
- 詳しくは公式解説本をどうぞ zenn.dev
- 今回はECS TaskDefinitionとService Definitionの管理をしてもらっています
lambroll
- こちらもfujiwaraさんによって作成された、Lambdaリソースに特化したのデプロイツールです
- 詳しくはfujiwaraさんのブログをどうぞ sfujiwara.hatenablog.com
- 今回はlambda functionの管理をしてもらっています
ecschedule
- こちらはSongmuさんによって作成されたECS Scheduled Taskに特化した管理ツールです
- 詳しくはSongmuさんのブログをどうぞ songmu.jp
- 今回は言うまでもなくECS Scheduled Taskを管理してもらっています
実際のソースコード
ディレクトリ構造
CI/CDを構成するディレクトリは以下のとおりです
Github Actions
デプロイ先環境ごとに以下のようなファイルを設置しています。
name: Build and Deploy on: push: branches: - master paths-ignore: - xxx - yyy env: ENV: <環境名> AWS_REGION: ap-northeast-1 IMAGE_TAG: ${{ github.sha }} TFSTATE_BUCKET: <tfstate bucket名> defaults: run: shell: bash jobs: build: name: Build and Push Docker Image runs-on: ubuntu-latest environment: name: prd strategy: matrix: docker: ["aaa", "bbb", "ccc", "ddd"] steps: - name: Checkout uses: actions/checkout@v2 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.アクセスキー }} aws-secret-access-key: ${{ secrets.シークレットアクセスキー }} aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build, tag, and push image to Amazon ECR uses: docker/build-push-action@v2 env: DOCKER_BUILDKIT: 1 ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: service-name-${{ matrix.docker }} with: context: . file: ./docker/${{ matrix.docker }}/Dockerfile push: true tags: ${{ format('{0}/{1}:{2}', env.ECR_REGISTRY, env.ECR_REPOSITORY, env.IMAGE_TAG) }} target: production build-args: | XXX=xxx YYY=yyy deploy-ecspresso: name: Deploy with ecspresso needs: build runs-on: ubuntu-latest environment: name: prd url: xxxx steps: - name: Checkout uses: actions/checkout@v2 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.アクセスキー }} aws-secret-access-key: ${{ secrets.シークレットアクセスキー }} aws-region: ${{ env.AWS_REGION }} - name: Checkout ecspresso uses: kayac/ecspresso@v1 with: version: v1.7.13 - name: Register task definition run: ecspresso register --config deploy/ecs/ecspresso.yaml - name: Migrate database run: | ecspresso run --config deploy/ecs/ecspresso.yaml --latest-task-definition \ --overrides='{"containerOverrides":[ {"name": "service-${{ env.ENV }}-app", "command": ["php", "hoge"]}, - name: Deployment with ecspresso run: | ecspresso deploy --config deploy/ecs/ecspresso.yaml deploy-ecschedule: name: Deploy with ecschedule needs: build runs-on: ubuntu-latest environment: name: prd steps: - name: Checkout uses: actions/checkout@v2 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.アクセスキー }} aws-secret-access-key: ${{ secrets.シークレットアクセスキー }} aws-region: ${{ env.AWS_REGION }} - name: Checkout ecschedule uses: Songmu/ecschedule@main with: version: v0.4.0 - name: Deployment with ecschedule env: PRIVATE_SUBNET_ID_AZA: "- subnet-XXXXX" PRIVATE_SUBNET_ID_AZC: "- subnet-YYYYYY" PRIVATE_SUBNET_ID_AZD: "- subnet-ZZZZZZ" SECURITY_GROUP_ID: sg-AAAAA ECS_EVENTS_ROLE: "arn:aws:iam::123456789012:role/<ruleに割り当てるrole名>" run: | ecschedule -conf deploy/ecs/ecschedule.yaml apply \ -rule rule-name deploy-lambroll: name: Deploy with lambroll needs: build runs-on: ubuntu-latest environment: name: prd steps: - name: Checkout uses: actions/checkout@v2 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} - name: Checkout lambroll uses: fujiwara/lambroll@v0 with: version: v0.12.7 - name: Deployment with lambroll env: DB_PASSWORD: ${{ secrets.DB_PASSWORD }} LOG_LEVEL: error run: | lambroll deploy --function=deploy/lambda/function.json \ --tfstate="s3://${TFSTATE_BUCKET}/service.tfstate"
デプロイに必要な権限(IAM Policy)
ecspresso
- こちらの記事を参考にさせていただきました!🙏 zenn.dev
- 不要な権限を削った程度ですので、今回は省略します
lambroll
- 過去に別のところで書いたlambroll最小権限から少し追加が発生したので、書いておきます
- 今回の要件としては以下の通り
- Dockerコンテナイメージで実行
- VPCに接続
- function.json内でterraform stateの値を参照(tfstate template function)
{ "Statement": [ { "Action": [ "ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups" ], "Effect": "Allow", "Resource": "*", "Sid": "" }, { "Action": [ "lambda:UpdateFunctionConfiguration", "lambda:UpdateFunctionCode", "lambda:UpdateAlias", "lambda:ListTags", "lambda:GetFunction", "lambda:CreateFunction", "lambda:CreateAlias" ], "Effect": "Allow", "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:<デプロイ対象function名>", "Sid": "" }, { "Action": "iam:PassRole", "Effect": "Allow", "Resource": "arn:aws:iam::123456789012:role/<lambdaに割り当てるrole名>", "Sid": "" }, { "Action": "s3:GetObject", "Effect": "Allow", "Resource": "arn:aws:s3:::<tfstate bucket名>/<terraform state file名>.tfstate", "Sid": "" } ], "Version": "2012-10-17" }
ecschedule
- ECS Scheduled Taskの実態はEventBridgeなので、主にそのあたりの権限
- 具体的なPolicyとしては以下の通り。
{ "Statement": [ { "Action": [ "events:ListRules", "ecs:DescribeTaskDefinition" ], "Effect": "Allow", "Resource": "*", "Sid": "" }, { "Action": [ "events:PutTargets", "events:PutRule", "events:ListTargetsByRule" ], "Effect": "Allow", "Resource": "arn:aws:events:ap-northeast-1:123456789012:rule/<デプロイ対象rule名>", "Sid": "" }, { "Action": "iam:PassRole", "Effect": "Allow", "Resource": "arn:aws:iam::123456789012:role/<ruleに割り当てるrole名>", "Sid": "" } ], "Version": "2012-10-17" }
あとがき
TerraformやCloudFormation等一元的なリソース管理事例が多い中、デプロイサイクルを意識したCI/CDフローの確立を各種サービスに特化したOSSを利用することで解決する良い例になったと感じています。
また、以前にgo runtimeなLambda functionを対象としてlambrollを使ったCI/CDフローを構築しましたが、その直後にDockerコンテナイメージがLambda/lambroll共にサポートされてからようやく試すことができました💪
今年の春頃に構築後、下書きに入れてる間に今回のサービスは世に出ないものとなってしまうようなので供養記事ということで🙏
次はアドベントカレンダーでお会いしましょう🎉
宣伝
香西がCloudNativeDays登壇します!
今回の記事でもとりあげた、ecspressoを活用したCI/CDフローをよりDeepに語ってくれる予定です!
エンジニア募集
弊社ではSREを絶賛募集中です。 興味がある方はぜひ一度お話しましょう!