この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
手作業によるbuild・deployのヒューマンエラー防止として自動化を検討する際に、ぶつかる壁として考えられるものに
- 手順をCI/CDに落とし込めるか
- 処理時間が肥大化しないか
- CI/CD設定作成が困難ではないか
の3点があります。
今回、それらの課題を抑えつつも「既に出来上がっている手順を大きくは崩さない」前提でCircleCIの設定へ落とし込みをしてみました。
前提
本運用にて用いるプラットフォームの選定が完了していないものの、プラットフォームを変える選択肢有りでとりあえず自動化を済ませたいという目論見があります。既にCircleCI上でpytestの実行だけは行っており、その延長としてCI/CDを実施することにしました。
Dockerイメージのbuildと環境へのdeployで時間を要することが判っています。ですが、Dockerイメージ更新発生頻度は少なめで、環境へのdeployタイミングもそこまで多くはありません。そのため、buildとdeployを毎回実施する必要はありません。
追加する手順の整理
大まかには以下の内訳となります。
- Dockerイメージのビルド
- ECRへイメージをアップロード
- CloudFormation経由での環境への反映
更に、特に行っていなかったものの、追加候補となる手続きとしては以下辺りになります。
- Slackへの更新通知
CircleCI環境を整理する
設定を書き始める前に、利用するOrbや環境変数設定を行います。
Contextsを用いた環境変数設定
手順にてTerminal上で環境変数の設定を行っている箇所について、事前にContextsに追加を行っています。今回はブランチ名で本番用job・開発用jobを切り替えつつ、切り替えに応じて利用するContextsも変更する想定です。
暫定で以下の内容を指定しています。
- AWS_ACCESS_KEY_ID
- AWS_DEFAULT_REGION
- AWS_SECRET_ACCESS_KEY
- ROLE_ARN
- SLACK_WEBHOOK
Orbを用いた設定の簡易化
効率よく設定化するため、いくつかOrbを併用しています。
ECRへのアップロードにはaws-ecrのOrbを用いる方法もあるのですが、手順にてawscliを多く使っていたためそちらに合わせています。
その他
docker imageは全てcircleci/python:3.7.3としています。これはプロジェクトのベースがpythonコードによるためです。
config.ymlの作成
実際には一つのファイルにおさめていますが、各要素毎で触れるために区切った形で掲載しています。
version
orbs
jobs
workflows
の4つから成り立っています。各要素については以下の公式ドキュメントを参照してください。
DockerイメージのビルドとECRへのアップロード
.aws/credentials
へ追加を行い、その後buildとECRへのアップロードを行います。${AWS_ACCESS_KEY_ID}
や${AWS_SECRET_ACCESS_KEY}
の指定がありますが、これはworkflowからの呼び出し時に指定されるcontextに沿って決定されます。
jobs:
build:
docker:
- image: circleci/python:3.7.3
parameters:
python-version:
type: string
executor: aws-cli/default
steps:
- checkout
- setup_remote_docker
- run:
name: set aws-cli
command: |
mkdir -p ~/.aws
echo "[default]" >> ~/.aws/credentials
echo "aws_access_key_id = ${AWS_ACCESS_KEY_ID}" >> ~/.aws/credentials
echo "aws_secret_access_key = ${AWS_SECRET_ACCESS_KEY}" >> ~/.aws/credentials
echo "[assume_role]" >> ~/.aws/credentials
echo "source_profile = default" >> ~/.aws/credentials
echo "role_arn = ${ROLE_ARN}" >> ~/.aws/credentials
- aws-cli/setup:
profile-name: assume_role
- run:
name: build docket image
command: |
echo 'export AWS_PROFILE=profile' >> $BASH_ENV
docker build -t project -f docker/Dockerfile .
eval $(aws ecr get-login --no-include-email --profile=profile)
docker tag project:latest XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/project:latest
docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/project:latest
echo 'export AWS_PROFILE=profile' >> $BASH_ENV
となっている箇所は、以下のリンク先に詳細があります。
環境への反映
./deploy.sh dev
にてawscliの呼び出しでCFnを実行しており、profileを設定するため事前にexportを行います。
parametersにpython-version
を渡しているのは、aws-cliのOrbへworkflow経由で3.7と指定することが目的です。
jobs:
deploy:
docker:
- image: circleci/python:3.7.3
parameters:
python-version:
type: string
executor: aws-cli/default
steps:
- checkout
- setup_remote_docker
- run:
name: deploy to environment
command: |
echo 'export AWS_PROFILE=assume_role' >> $BASH_ENV
./deploy.sh dev
Workflowの設定
requiresを指定することで順序建てています。
ジョブの実行を Workflow で制御する - CircleCI
workflows:
version: 2
build_and_push_image:
jobs:
- test:
python-version: "3.6"
context: dev
- slack/approval-notification:
message: ${CIRCLE_BRANCH}をbuildするためにはApproveが必要です。
requires:
- test
context: dev
- approve_build:
type: approval
requires:
- test
context: dev
- build:
requires:
- approve_build
python-version: "3.6"
context: dev
- slack/approval-notification:
message: ${CIRCLE_BRANCH}をdeployするためにはApproveが必要です。
requires:
- build
context: dev
- approve_deploy:
type: approval
requires:
- build
context: dev
- deploy:
requires:
- approve_deploy
python-version: "3.6"
filters:
branches:
ignore:
- master
context: dev
buildとdeployが頻繁なバッチ系ファイル修正によるプッシュに伴うとリソースの浪費に繋がるため、type: approval
をbuildとdeployのjobに指定することで常時実行を防いでリソースの浪費防止を狙っています。
なお、slack/approval-notification
にもapprovalの文字が含まれていますが、実際にはSlackへ実行承認目的でのWorkflowページへのリンクが通知されるだけです。
Workflowページ上では以下のような表示となります。
config.yml全体
とりあえず動かすことを目的としているため、細かい部分で粗を含む可能性があります。
---
version: 2.1
orbs:
aws-cli: circleci/aws-cli@0.1.16
slack: circleci/slack@3.4.0
jobs:
test:
docker:
- image: circleci/python:3.7.3
parameters:
python-version:
type: string
executor: aws-cli/default
steps:
- checkout
- setup_remote_docker
- run: sudo chown -R circleci:circleci /usr/local/bin
- run: sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages
- restore_cache: # このステップは依存関係をインストールする<em>前</em>に実行します
key: deps9-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
- run: echo "export latest_commit_log=$(git log --oneline -1)" >> $BASH_ENV
- run:
command: |
pip install pipenv
pipenv sync --dev
- save_cache:
key: deps9-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
paths:
- ".venv"
- "/usr/local/bin"
- "/usr/local/lib/python3.7/site-packages"
- run:
name: run tests
command: |
export AWS_REGION_NAME='ap-northeast-1'
export AWS_ACCESS_KEY_ID='test'
export AWS_SECRET_ACCESS_KEY='test'
export run_env=dev
pipenv run pytest tests lambda --cov=project --cov-report=term-missing --cov-report=html --benchmark-skip
- store_artifacts:
path: htmlcov
destination: htmlcov
- run:
name: run linting and metrics
command: |
pipenv run flake8 src/ tests/ lambda/
pipenv run cfn-lint -i W3002 -t cfn/*.yml
build:
docker:
- image: circleci/python:3.7.3
parameters:
python-version:
type: string
executor: aws-cli/default
steps:
- checkout
- setup_remote_docker
- run:
name: set aws-cli
command: |
mkdir -p ~/.aws
echo "[default]" >> ~/.aws/credentials
echo "aws_access_key_id = ${AWS_ACCESS_KEY_ID}" >> ~/.aws/credentials
echo "aws_secret_access_key = ${AWS_SECRET_ACCESS_KEY}" >> ~/.aws/credentials
echo "[assume_role]" >> ~/.aws/credentials
echo "source_profile = default" >> ~/.aws/credentials
echo "role_arn = ${ROLE_ARN}" >> ~/.aws/credentials
- aws-cli/setup:
profile-name: assume_role
- run:
name: build docket image
command: |
echo 'export AWS_PROFILE=assume_role' >> $BASH_ENV
docker build -t project -f docker/Dockerfile .
eval $(aws ecr get-login --no-include-email --profile=assume_role)
docker tag project:latest XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/project:latest
docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/project:latest
deploy_dev:
docker:
- image: circleci/python:3.7.3
parameters:
python-version:
type: string
executor: aws-cli/default
steps:
- checkout
- setup_remote_docker
- run:
name: deploy to environment
command: |
echo 'export AWS_PROFILE=assume_role' >> $BASH_ENV
./deploy.sh dev
deploy_prod:
docker:
- image: circleci/python:3.7.3
parameters:
python-version:
type: string
executor: aws-cli/default
steps:
- checkout
- setup_remote_docker
- run:
name: deploy to environment
command: |
AWS_PROFILE=assume_role ./deploy.sh prod
workflows:
version: 2
build_and_push_image:
jobs:
- test:
python-version: "3.6"
context: dev
- slack/approval-notification:
message: ${CIRCLE_BRANCH}をbuildするためにはApproveが必要です。
requires:
- test
context: dev
- approve_build:
type: approval
requires:
- test
context: dev
- build:
requires:
- approve_build
python-version: "3.6"
context: dev
- slack/approval-notification:
message: ${CIRCLE_BRANCH}をdeployするためにはApproveが必要です。
requires:
- build
context: dev
- approve_deploy:
type: approval
requires:
- build
context: dev
- deploy:
requires:
- approve_deploy
python-version: "3.6"
context: dev
あとがき
取り敢えず自動化することを目的としているため、本運用時にはここから大幅に変わる可能性もあります。環境設営の速度を求める場合の参考になれば幸いです。