はじめに
今回は初めてCircleCIを利用することになりました。
一番大きな要件として、フォルダごとが1サービスとなっているため、サービスごとにCI/CDが走る必要がありました。
1フォルダの変更で全てのフォルダにCI/CDが適用されると不要なCI/CDが走ることになるためです。
今回は1リポジトリの中に2つのフォルダがあり、それらを別々のFargateへデプロイすることを想定しています。
実装の概要
この実装では、下記2つの設定ファイルを使用します:
.circleci/config.yml
:メインの設定ファイル.circleci/continue-config.yml
:条件に応じて実行されるワークフロー
CircleCIが用意してくれているOrbであるpath-filtering
を利用します。
メイン設定ファイル(config.yml)
version: 2.1 orbs: aws-cli: circleci/aws-cli@4.1.3 aws-ecr: circleci/aws-ecr@9.1.0 aws-ecs: circleci/aws-ecs@4.0.0 path-filtering: circleci/path-filtering@1.0.0 executors: aws-executor: docker: - image: cimg/python:3.10 setup: true workflows: version: 2 deploy: jobs: - path-filtering/filter: name: check-updated-files-dev mapping: | service_A/.* run-service_A-workflow true service_B/.* run-service_B-workflow true base-revision: << pipeline.git.base_revision >> config-path: .circleci/continue-config.yml filters: branches: only: develop ........(環境設定が違うだけで同じため省略)
このファイルでは以下の重要な点があります:
path-filtering
Orbを使用して、変更されたファイルのパスを検出します。- 環境ごと(dev, stg, prod)に異なるジョブを設定し、それぞれのブランチに対応させています。
mapping
セクションで、特定のフォルダの変更が検出された場合にパラメータを設定します。
継続設定ファイル(continue-config.yml)の解説
version: 2.1 orbs: aws-cli: circleci/aws-cli@4.1.3 aws-ecr: circleci/aws-ecr@9.1.0 aws-ecs: circleci/aws-ecs@4.0.0 executors: aws-executor: docker: - image: cimg/python:3.10 parameters: run-service_A-workflow: type: boolean default: false run-service_B-workflow: type: boolean default: false jobs: build_service_A: executor: aws-executor steps: - checkout - setup_remote_docker - run: name: Set Build Tag command: | export BUILD_TAG=$(TZ=Asia/Tokyo date +"${ENV}_%Y%m%d-%H%M%S") echo "export BUILD_TAG=$BUILD_TAG" >> $BASH_ENV - aws-ecr/build_and_push_image: auth: - aws-cli/setup: role_arn: $ROLE_ARN region: $AWS_REGION repo: $SERVICE_A_ECR_REPOSITORY tag: $BUILD_TAG platform: linux/arm64 extra_build_args: "--provenance=false" path: ./service_A - aws-cli/setup: role_arn: $ROLE_ARN - aws-ecs/update_service: family: $SERVICE_A_ECS_TASK service_name: $SERVICE_A_ECS_SERVICE cluster: $SERVICE_ECS_CLUSTER container_image_name_updates: "container=${SERVICE_A_ECS_CONTAINER},tag=${BUILD_TAG}" build_serivce_B: executor: aws-executor steps: - checkout - setup_remote_docker - run: name: Set Build Tag command: | export BUILD_TAG=$(TZ=Asia/Tokyo date +"${ENV}_%Y%m%d-%H%M%S") echo "export BUILD_TAG=$BUILD_TAG" >> $BASH_ENV - aws-ecr/build_and_push_image: auth: - aws-cli/setup: role_arn: $ROLE_ARN region: $AWS_REGION repo: $SERVICE_B_ECR_REPOSITORY tag: $BUILD_TAG platform: linux/arm64 extra_build_args: "--provenance=false" path: ./service_B - aws-cli/setup: role_arn: $ROLE_ARN - aws-ecs/update_service: family: $SERVICE_B_ECS_TASK service_name: $SERVICE_B_ECS_SERVICE cluster: $SERVICE_B_ECS_CLUSTER container_image_name_updates: "container=${SERVICE_B_ECS_CONTAINER},tag=${BUILD_TAG}" workflows: service-A-updated-dev: when: and: - equal: [ true, << pipeline.parameters.run-service_A-workflow >> ] - equal: [ develop, << pipeline.git.branch >> ] jobs: - build_and_deploy_serivce_A: context: service_context service-B-updated-dev: when: and: - equal: [ true, << pipeline.parameters.run-service_B-workflow >> ] - equal: [ develop, << pipeline.git.branch >> ] jobs: - build_and_deploy_service_B: context: service-context ........(環境設定が違うだけで同じため省略)
このファイルの主なポイントは:
parameters
セクションで、フォルダごとのワークフロー実行フラグを定義しています。jobs
セクションで、実際のビルドとデプロイのステップを定義しています。workflows
セクションで、条件に応じたジョブの実行を設定しています。
フォルダごとのデプロイの仕組み
config.yml
のpath-filtering/filter
ジョブが変更されたファイルのパスを検出します。- 検出結果に基づいて、
run-service_A-workflow
やrun-service_B-workflow
パラメータが設定されます。 continue-config.yml
のワークフローが、これらのパラメータと現在のブランチに基づいて実行されます。- 条件に合致した場合、対応するビルドとデプロイジョブが実行されます。
まとめ
無駄なCI/CDを走らせることなく、時間短縮も可能となり良さそうです。
同プロジェクトのLambdaの実装においてもこちらのCI/CDを適用してます。
Lambdaだとよりフォルダごとでのサービス管理を行う機会も多いので、重宝するかと思います。
フロントとバックエンドを同一リポジトリ管理している小規模プロジェクトなどでも良さそうですね。