はじめに
今回は初めて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-filteringOrbを使用して、変更されたファイルのパスを検出します。- 環境ごと(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だとよりフォルダごとでのサービス管理を行う機会も多いので、重宝するかと思います。
フロントとバックエンドを同一リポジトリ管理している小規模プロジェクトなどでも良さそうですね。