この記事はウェブクルー Advent Calendar 2023 19日目の記事です。
昨日は@wc-kadowaki さんの「Nuxt3のproxyについて」でした。
概要
AWS EC2で立てているサーバにCircleCIを導入しました。
対象アプリケーションのシステム構成概要↓
サーバ環境:
フロントエンドサーバ: 2台(AWS EC2)
バックエンドサーバ: 2台(AWS EC2)
ソースコード管理:
Github(単一リポジトリ)
各サーバはEC2の内部でDockerコンテナを立てています(ECS on EC2ではない)。
そんな構成なので、CircleCIでCI/CDをトリガーしてその後CodeDeployでサービスをデプロイする流れでCI/CD環境を作成しました。
この構成だとCodeDeployの設定でハマったので、その時に詰まった部分とその対応についての記録。
利用サービス
CircleCI
SaaSのCICDサービスで、GithubなどのVCSと連携可能。pushしただけでビルド~デプロイしてくれるし自動テストもできます。
いろんな環境に対応しやすいように、公式がライブラリ(Orb)を提供してくれています。
CodeDeploy
AWSでもCircleCIと似たようなサービスとしてCodePipelineを提供しています。CodeDeployはそのサービスの一部で、名前の通りAWS環境でデプロイを実施してくれるもので、デプロイ対象はECS、EC2、Lambdaなどです。
https://aws.amazon.com/jp/codepipeline/
CircleCIはAWS環境にデプロイするためにAWSのサービスと連携するOrbをいくつか提供しています。そのうちの一つがCodeDeployを利用するものです。
CircleCIとCodeDeployの詳しい説明についてはすでにたくさん記事があるので割愛。
はまったポイント
1つのリポジトリでフロント・バックエンドのソースコードを管理しており、またそれぞれのサーバが別に管理されている場合、CodeDeployで対応するのが難しかった。
CodeDeployは設定ファイルに動的な分岐を用意できない作りになっており、また環境ごとにファイルを変更したりもできない。
対応方法
結論として、CircleCIのダイナミックコンフィグを使って、採用するappconfig.yml(CodeDeployの設定ファイル)を動的に変更する構成にしました。
CircelCIはjobへ渡すパラメータにstepを指定できるので、その仕様を利用しました。
記述例
構成
- .circleci/build-deploy.yml
- .circleci/config.yml
- appspec.back.yml
- appspec.front.yml
- codedeploy/start_front.sh
- codedeploy/stop_front.sh
- codedeploy/start_back.sh
- codedeploy/start_back.sh
以下CircleCIの設定ファイル記述例です。CodeDeploy用のファイルは各サービスの実装構成によってだいぶ変わるので start_front.sh
, stop_front.sh
, start_back.sh
, start_back.sh
は省略します。
また、 appspec.front.yml
も appspec.back.yml
とほぼ同じ内容なので割愛します。
version: 2.1
setup: true
orbs:
path-filtering: circleci/path-filtering@1.0.0
parameters:
base-revision:
type: string
default: "origin/master"
workflows:
setup:
jobs:
- path-filtering/filter:
base-revision: <<pipeline.parameters.base-revision>>
config-path: ./.circleci/build-deploy.yml
mapping: |
Application/front/.* build-deploy-front true
Application/api/.* build-deploy-back true
ほぼcodedeploy用orbのjobのコピペ↓
version: 2.1
orbs:
aws-code-deploy: circleci/aws-code-deploy@3.0.0
aws-cli: circleci/aws-cli@3.1.4
parameters:
service-name:
type: string
default: service-name
bucket-name:
type: string
default: myS3BucketKey
build-deploy-app:
type: boolean
default: false
build-deploy-web:
type: boolean
default: false
jobs:
deploy: # aws-code-deploy/deployのコピペjob
docker:
- image: cimg/aws:2023.03
parameters:
application-name:
type: string
arguments:
default: ''
type: string
# ここだけ追記
pre-command:
default: []
description: >
実行前に実施してほしいコマンド
type: steps
# 追記ここまで
auth:
description: >
The authentication method used to access your AWS account. Import the
aws-cli orb in your config and
provide the aws-cli/setup command to authenticate with your preferred
method. View examples for more information.
type: steps
bundle-bucket:
type: string
bundle-key:
type: string
bundle-source:
default: .
type: string
bundle-type:
default: zip
type: string
deploy-bundle-arguments:
default: ''
type: string
deployment-config:
default: CodeDeployDefault.OneAtATime
enum:
- CodeDeployDefault.OneAtATime
- CodeDeployDefault.HalfAtATime
- CodeDeployDefault.AllAtOnce
type: enum
deployment-group:
type: string
get-deployment-group-arguments:
default: ''
type: string
profile-name:
default: default
type: string
region:
default: ${AWS_DEFAULT_REGION}
description: >
AWS region of CodeDeploy App. Defaults to environment variable
${AWS_DEFAULT_REGION}.
type: string
service-role-arn:
description: The service role for a deployment group.
type: string
steps:
- checkout
- steps: << parameters.command >>
- steps: << parameters.auth >>
- aws-code-deploy/create-application:
application-name: << parameters.application-name >>
arguments: << parameters.arguments >>
profile-name: << parameters.profile-name >>
region: << parameters.region >>
- aws-code-deploy/create-deployment-group:
application-name: << parameters.application-name >>
arguments: << parameters.arguments >>
deployment-config: << parameters.deployment-config >>
deployment-group: << parameters.deployment-group >>
get-deployment-group-arguments: << parameters.get-deployment-group-arguments >>
profile-name: << parameters.profile-name >>
region: << parameters.region >>
service-role-arn: << parameters.service-role-arn >>
- aws-code-deploy/push-bundle:
application-name: << parameters.application-name >>
arguments: << parameters.arguments >>
bundle-bucket: << parameters.bundle-bucket >>
bundle-key: << parameters.bundle-key >>
bundle-source: << parameters.bundle-source >>
bundle-type: << parameters.bundle-type >>
profile-name: << parameters.profile-name >>
region: << parameters.region >>
- aws-code-deploy/deploy-bundle:
application-name: << parameters.application-name >>
bundle-bucket: << parameters.bundle-bucket >>
bundle-key: << parameters.bundle-key >>
bundle-type: << parameters.bundle-type >>
deploy-bundle-arguments: << parameters.deploy-bundle-arguments >>
deployment-config: << parameters.deployment-config >>
deployment-group: << parameters.deployment-group >>
get-deployment-group-arguments: << parameters.get-deployment-group-arguments >>
profile-name: << parameters.profile-name >>
region: << parameters.region >>
workflows:
deploy_back_application:
when: <<pipeline.parameters.build-deploy-back>>
jobs:
- aws-code-deploy/deploy:
context: aws-context
pre-command:
- run: cp appspec.back.yml appspec.yml
auth:
- aws-cli/setup:
profile-name: CircleCI IDENTITY PROFILE
role-arn: $ASSUME_ROLE_ARN # aws-contextに登録している環境変数
role-session-name: pdm-codedeploy-session
name: <<pipeline.parameters.service-name>>-Back-App
application-name: <<pipeline.parameters.service-name>>-Back-App
bundle-bucket: <<pipeline.parameters.bucket-name>>
service-role-arn: $SERVICE_ROLE_ARN
profile-name: CircleCI IDENTITY PROFILE
bundle-key: <<pipeline.parameters.service-name>>-Back
deployment-group: <<pipeline.parameters.service-name>>>-Back-DepGrp
filters:
branches:
only:
- master
deploy_front_application:
when: <<pipeline.parameters.build-deploy-front>>
jobs:
- deploy:
context: aws-context
pre-command:
- run: cp appspec.front.yml appspec.yml
auth:
- aws-cli/setup:
profile-name: CircleCI IDENTITY PROFILE
role-arn: $ASSUME_ROLE_ARN
role-session-name: pdm-codedeploy-session
name: <<pipeline.parameters.service-name>>-Front-App
application-name: <<pipeline.parameters.service-name>>-Front-App
bundle-bucket: <<pipeline.parameters.bucket-name>>
service-role-arn: $SERVICE_ROLE_ARN
profile-name: CircleCI IDENTITY PROFILE
bundle-key: <<pipeline.parameters.service-name>>-Front
deployment-group: <<pipeline.parameters.service-name>>-Front-DepGrp
requires:
- approval-deploy
filters:
branches:
only:
- master
version: 0.0
os: linux
files:
- source: /Application/api
destination: /home/app/api
file_exists_behavior: OVERWRITE
hooks:
ApplicationStop:
- location: odedeploy/stop_back.sh
timeout: 300
runas: root
ApplicationStart:
- location: codedeploy/start_back.sh
timeout: 300
runas: root
所感
せっかくorbで簡潔にかけるはずが、今回の構成のためにjobを定義することになってしまった点が残念でした。IaaCがデファクトスタンダードになってきている現状では、バックエンドとフロントエンドのリポジトリを分けるべきですね。
参考
https://circleci.com/docs/ja/contexts/
https://circleci.com/developer/ja/orbs/orb/circleci/aws-code-deploy
https://circleci.com/docs/ja/dynamic-config/
https://docs.aws.amazon.com/codedeploy/latest/userguide/tutorials-wordpress.html
https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-example.html#appspec-file-example-server
明日は @wc-fukuda さんの投稿です。