Tutorial Advent Calendar 2020 14日目になります。
この記事は田尻が書いています。
前にGit Feature Flowという運用を始めた話をブログに書きましたが、そもそもGitの運用方法を明確にしたのは自動デプロイをしたかったためです。
今回は私たちがGitOpsでGKEにアプリケーションの自動デプロイを行っている実例を紹介します。
使用ツール
・CloudBuild(GCP)
・シークレットマネージャー(GCP)
・Container Registry(GCP)
・GKE(GCP)
・github
弊社はインフラに主にGCPを使っていますので、Cloudbuildとの相性が非常に良いです。
また、お値段もCloudbuildはリーズナブルだと思います。
https://cloud.google.com/cloud-build/pricing
参考
Cloud Build を使用した GitOps スタイルの継続的デリバリー
構造
ステージング環境とプロダクション環境でKubernatesのマニフェストが若干異なっていたので、deploy-repoリポジトリでトリガーとなるブランチはstaging用とproduction用で分けています。
ビルド構成ファイル
app-devリポジトリ
ビルド用のビルド構成ファイルになります。
cloudbuild.yaml
steps:
# start build
- name: gcr.io/cloud-builders/docker
args: [
'build',
'-t', 'gcr.io/$PROJECT_ID/app-repo:${SHORT_SHA}',
'-f', 'vendor/docker/app/Dockerfile',
'.'
]
id: 'build-app'
# start test step
- name: gcr.io/cloud-builders/docker
args: [
'build',
'-t', 'gcr.io/$PROJECT_ID/app-repo:run-test',
'-f', 'vendor/docker/app/Dockerfile.test',
'.'
]
id: 'build-app-test'
# start push
- name: 'gcr.io/cloud-builders/docker'
args:
- 'push'
- 'gcr.io/$PROJECT_ID/app-repo:${SHORT_SHA}'
id: 'push-app'
# git clone
- name: 'gcr.io/cloud-builders/gcloud'
entrypoint: 'bash'
args:
- '-c'
- |
gcloud secrets versions access latest --secret=secret-name > /root/.ssh/id_github
volumes:
- name: 'ssh'
path: /root/.ssh
waitFor: ['push-app']
id: 'get-secret'
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args:
- '-c'
- |
chmod 600 /root/.ssh/id_github
cat <<EOF >/root/.ssh/config
Hostname github.com
IdentityFile /root/.ssh/id_github
EOF
ssh-keyscan -t rsa github.com > /root/.ssh/known_hosts
volumes:
- name: 'ssh'
path: /root/.ssh
waitFor: ['get-secret']
id: 'set-up-key-domain'
- name: 'gcr.io/cloud-builders/git'
args:
- clone
- --recurse-submodules
- git@github.com:Tutorial-Inc/deploy-repo.git
volumes:
- name: 'ssh'
path: /root/.ssh
waitFor: ['set-up-key-domain']
id: 'clone-deploy-repo'
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args:
- '-c'
- |
cd deploy-repo && \
git checkout candidate_production && \
git config user.email $(gcloud auth list --filter=status:ACTIVE --format='value(account)')
waitFor: ['clone-deploy-repo']
id: 'checkout-deploy-repo'
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args:
- '-c'
- |
cd deploy-repo/prod && \
sed "s/GOOGLE_CLOUD_PROJECT/${PROJECT_ID}/g" deployment.yaml.tpl | \
sed "s/COMMIT_SHA/${SHORT_SHA}/g" > deployment.yaml && \
sed "s/GOOGLE_CLOUD_PROJECT/${PROJECT_ID}/g" job_migrate.yaml.tpl | \
sed "s/COMMIT_SHA/${SHORT_SHA}/g" > job_migrate.yaml
waitFor: ['checkout-deploy-repo']
id: 'generate-manifest'
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args:
- '-c'
- |
set -x && \
cd deploy-repo && \
git add . && \
git commit -m "Deploying image gcr.io/$PROJECT_ID/deploy-repo:${SHORT_SHA}
Built from commit ${COMMIT_SHA} of repository deploy-repo
Author: $(git log --format='%an <%ae>' -n 1 HEAD)" && \
git push origin candidate_production
volumes:
- name: 'ssh'
path: /root/.ssh
waitFor: ['generate-manifest']
id: 'push-manifest'
timeout: 1800s
deploy-devリポジトリ
デプロイ用のビルド構成ファイルになります。
cloudbuild.yaml
steps:
# deploy start
- name: 'gcr.io/cloud-builders/kubectl'
args:
- 'apply'
- '-f'
- 'prod/job_migrate.yaml'
env:
- 'CLOUDSDK_COMPUTE_ZONE=<your zone>'
- 'CLOUDSDK_CONTAINER_CLUSTER=<your cluster name>'
id: 'deploy-job-migrate'
- name: 'gcr.io/cloud-builders/kubectl'
entrypoint: 'bash'
args:
- '-c'
- |
sh check_complete_migration.sh
env:
- 'CLOUDSDK_COMPUTE_ZONE=<your zone>'
- 'CLOUDSDK_CONTAINER_CLUSTER=<your cluster name>'
waitFor: ['deploy-job-migrate']
id: 'check-complete-job-migrate'
- name: 'gcr.io/cloud-builders/kubectl'
args:
- 'delete'
- '-f'
- 'prod/job_migrate.yaml'
env:
- 'CLOUDSDK_COMPUTE_ZONE=<your zone>'
- 'CLOUDSDK_CONTAINER_CLUSTER=<your cluster name>'
waitFor: ['check-complete-job-migrate']
id: 'delete-job-migrate'
- name: 'gcr.io/cloud-builders/kubectl'
args:
- 'apply'
- '-f'
- 'prod/deployment.yaml'
env:
- 'CLOUDSDK_COMPUTE_ZONE=<your zone>'
- 'CLOUDSDK_CONTAINER_CLUSTER=<your cluster name>'
waitFor: ['check-complete-job-migrate']
id: 'deploy-deployment'
# copy start
- name: 'gcr.io/cloud-builders/gcloud'
entrypoint: 'bash'
args:
- '-c'
- |
gcloud secrets versions access latest --secret=secret-name > /root/.ssh/id_github
volumes:
- name: 'ssh'
path: /root/.ssh
waitFor: ['deploy-deployment']
id: 'get-secret'
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args:
- '-c'
- |
chmod 600 /root/.ssh/id_github
cat <<EOF >/root/.ssh/config
Hostname github.com
IdentityFile /root/.ssh/id_github
EOF
ssh-keyscan -t rsa github.com > /root/.ssh/known_hosts
volumes:
- name: 'ssh'
path: /root/.ssh
waitFor: ['get-secret']
id: 'set-up-key-domain'
- name: 'gcr.io/cloud-builders/git'
args:
- clone
- --recurse-submodules
- git@github.com:Tutorial-Inc/deploy-repo.git
volumes:
- name: 'ssh'
path: /root/.ssh
waitFor: ['set-up-key-domain']
id: 'clone-deploy-repo'
- name: 'gcr.io/cloud-builders/git'
entrypoint: 'bash'
args:
- '-c'
- |
cd deploy-repo && \
git config user.email $(gcloud auth list --filter=status:ACTIVE --format='value(account)') && \
git checkout production && \
git checkout $COMMIT_SHA prod/. && \
git commit -am "Manifest from commit $COMMIT_SHA
$(git log --format=%B -n 1 $COMMIT_SHA)" && \
git push origin production
volumes:
- name: 'ssh'
path: /root/.ssh
waitFor: ['clone-deploy-repo']
id: 'copy-to-production-branch'
jobのステータスがCompletedになるまで待っています。
ちゃんとcontextはenvで指定しているクラスタになります。
ハマったポイント
cloudbuildからgithubのprivateリポジトリへのアクセス
これが結構ハマってしまいました。ビルド構成ファイルごとにSSHの情報をconfigに書かないといけないので、若干面倒でした。
GCPのCloud Source Repositoriesだともっとシンプルに書けるようです。
詳しい説明は公式のドキュメントに書いてありますので、貼っておきます。
https://cloud.google.com/cloud-build/docs/access-private-github-repos
マイグレーションの完了を待つ
スクリプトを書く以外の方法がなさそうでした。
スクリプトを書いて確認するようにしたら無事に成功したのでよかったです。
まとめ
想像以上に簡単にできました。公式のドキュメントにも具体的なやり方が書いてあったので、助かりました。
弊社は目標として毎日リリースするというのを目指しています。実際は2日に1回くらいのリリース頻度ですが、自動ビルド・自動デプロイはとても助かっています。
また、手動でやった時のオペレーションミスがなくなるので、最高です。