最近、新しいPCをセットアップしていたけど、PCのセットアップを完了しないと趣味サイトのdeployすらできないことに気付いた。しばらくdeployしていないと、久しぶりにdeployしたときに大抵事故るし、小まめにやることでdeployの心理的障壁を下げていきたい。現状だと例えば、「寝る前にdeployしてコケると睡眠時間が短かく(2~3時間が飛んでいく)なるから、時間がある週末にやるしかない...」と思ってしまうことが時々ある。そのためにも、テスト済み/ビルド済みの成果物を本番環境にしゅっとリリース可能な状態にしておけるように、CI/CDのパイプラインを整備した。目新しいことは特にはないが、自分用のログを残しておく。
CI/CDの各フェイズの整理
フェイズを分割することで責務を分割して、早い段階でバグに気付いて障害に対応できるようにすることが目的。改めて整理する。
- Testフェイズ
- アプリのコードのテストが目的
- すでにCircleCI上でやっている
- Buildフェイズ
- ソースコードや関連するアセットをinputとして、成果物を生成する
- tarに固めてS3に置いたり、コンテナのイメージにしてECRにpushするなど
- これまでは手元でDocker buildして、ECRにpushしていた
- 以下の3つのイメージを準備する必要がある
- JSONを返すAPI(go)のimage
- Nuxt.jsでSSR(サーバーサイドレンダリング)するNodeのimage(参考: 最近の砂場活動その11: Nuxt.jsでサーバーサードレンダリング)
- 静的ファイル(js/css)を配信するNginxのimage
- 大体はSSRで済むのだが、管理者(私)はアノテーションをぽちぽちやりたい
- 認証のためAmplifyを使っている && ここだけはクライアントサイドレンダリングでやりたい*1
- Deployフェイズ
- Buildフェイズの成果物をAWS ECS(Fargate)上で実行する
- これまではkayac/ecspressoを使って、手元からdeployしていた
- Releaseフェイズ
- Deployフェイズで立ち上げた環境にユーザーへのリクエストを通す
DeployフェイズとReleaseフェイズに分割しているが、自分のサービスの場合、ここがごちゃっと混ざっている。ECSのローリングdeployを選んでいるので、deployされると同時にreleaseされて、新旧それぞれのバージョンのタスクが混在して動いている時間帯がある。障害が発生した場合のロールバックなどは多少時間がかかってしまうが、あくまで趣味プロジェクトなので、大きな影響はない。
新旧タスクの混在を避けたい場合、Blue/Greenデプロイを採用することを考えるとよい。ECSとCodeDeployを連携することで、Blue/Greenデプロイに対応することができる(ECS単体だとダメ)。今回の場合、ひとまず手元からのdeployを止めることが目的なので、ここまではやらないが気が向いたら練習としてやるかもしれない。
- ecspresso advent calendar 2020 day 17 - CodeDeployとの連携
- Amazon ECS デプロイタイプ - Amazon Elastic Container Service
Buildフェイズ
GitHub Actions上でbuildして成果物を作って、docker buildしてイメージに含めて、ECRにpushする。
ECRにpushするのに必要なAWS_ECR_ACCESS_KEY_ID
やAWS_ECR_SECRET_ACCESS_KEY
をGitHub ActionsのSecretsとして事前に登録しておく。IAMユーザー(アクセスの種類はプログラムによるアクセスのみ
を設定)を発行し、最小限の権限を付与するために独自にポリシーを作る。
このフェイズを用意したことによって、Pull Requestがマージされる度に最新のコードが反映されたDocker imageがECRに追加されていく。手元からのbuild & image pushが不要になった。
ビルドに必要なものと最終的なイメージに必要なものは異なることが多い。multi-stage buildを使って、最終的なイメージは最小限のものだけ含まれるようにしておく。
FROM node:alpine as builder RUN apk add --no-cache git python make g++ WORKDIR /tmp RUN cd /tmp && git clone https://github.com/syou6162/go-active-learning-web.git RUN cd /tmp/go-active-learning-web && npm install -g node-gyp && npm install && npm run-script build FROM nginx:1.19.8 RUN rm -f /etc/nginx/conf.d/* ADD nginx.conf /etc/nginx/conf.d/app.conf ADD img /www/app/img ADD robots.txt /www/app/robots.txt COPY --from=builder /tmp/go-active-learning-web/.nuxt/dist/client /www/app/_nuxt CMD ["/usr/sbin/nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]
Deployフェイズ(+ Releaseフェイズ)
sentry
どのエラーがどのリリース以降に出たものかを追跡するため、Sentryを使っている。リリース前にSentryに新しいリリースを登録しておく。
name: Create sentry release on: # masterブランチに対してのみ push: branches: - master jobs: sentry-release: runs-on: ubuntu-latest env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} steps: - uses: actions/checkout@v2 - name: Create new Sentry release run: | # Install Sentry CLI curl -sL https://sentry.io/get-cli/ | bash # clone go-active-learning-web git clone https://github.com/syou6162/go-active-learning-web.git && cd go-active-learning-web # Create new Sentry release export SENTRY_RELEASE=$(sentry-cli releases propose-version) sentry-cli releases new -p $SENTRY_PROJECT $SENTRY_RELEASE sentry-cli releases set-commits --auto $SENTRY_RELEASE sentry-cli releases finalize $SENTRY_RELEASE # Create new deploy for this Sentry release sentry-cli releases deploys $SENTRY_RELEASE new -e production
ecspresso
ecspressoでDeployを行なう。専用のGitHub Actionsが用意されているので、これもテンプレを埋めていくと割と簡単。
name: Deploy with ecspresso on: # masterブランチに対してのみ push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: kayac/ecspresso@v0 with: version: v1.1.3 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ECSPRESSO_DEPLOY_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_ECSPRESSO_DEPLOY_SECRET_ACCESS_KEY }} aws-region: us-east-1 - run: | ecspresso deploy --config ecs-config.yaml
パイプラインを組む
「Buildフェイズが通った場合のみ、Deployフェイズを実行」など、パイプラインは依存関係を持っていることが多い。GitHub Actionsではneeds
に事前に必要なものを指定しておくことで、依存関係を持たせたパイプラインを組むことができる。
workflow_dispatch
を指定しておくと、手動Deployをすることもできる。パラメータを指定することもできるので、ロールバックが必要であればtagなどを指定するのもよい。
name: Push nuxt & nginx images to ECR, and deploy on: workflow_dispatch: push: jobs: sentry-release: runs-on: ubuntu-latest env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} ... steps: - uses: actions/checkout@v2 - name: Create new Sentry release ... nuxt: name: nuxt image push runs-on: ubuntu-latest needs: - sentry-release ... nginx: name: nginx image push runs-on: ubuntu-latest needs: - sentry-release ... deploy: runs-on: ubuntu-latest needs: - nuxt - nginx ...
まとめ
元々やりたかった「手元からのDeployをやめる」というのができた。GitHubのPull Requestのマージボタンを押すだけで、buildやdeployまでの自動化ができるようになった。
今回やってないこととしては、以下がある。暇を見て、また砂場活動をやっていこう。
- DeployフェイズとReleaseフェイズの分離
- 上に書いてたやつ
- 環境によるDeployの仕方の変更
- 例: staging環境はdevelopブランチにマージされたら自動的にdeployまで行なうが、production環境はmainブランチにマージされた段階で初めてdeployする
- そもそも今はstaging環境は(予算的な意味で)ない...
- GKE上のDeployの改善
- 推薦エントリを計算するためのバッチ処理をGKEで行なっている
- k8sを利用する場合のCI/CD、GCP上のサービス(BigQueryなど)を使った場合の認証方法の検証
*1:この辺ごちゃついているので、どうにかしたい...