デプロイのフローでリリースタグをつけたときにだけデプロイしたい、という要件があってやってみたところ意外と詰まるポイントがあったのでまとめてみました。
要件
CI/CD環境を整えるにあたって、
- mergeのときではなく任意のタイミングでデプロイしたい
- master mergeのときはstgにあげるだけ(developブランチは存在しない)
- デプロイ後のトラブルのためにロールバックする方法を準備したい
という要件がありました。
そこで、リリースタグを切ってそれをpushしたタイミングでのみデプロイプロセスを走らせ、かつデプロイの直前でapprovalを要求するという設計にしました。
こんな感じのフローを実現するのがゴールです。(試行錯誤の結果v1.0.12まで到達w)
master mergeの前後でtestは走っていて通っているものという前提かつロールバックのときに迅速に対応できるように、ということでデプロイプロセスではテストは並列で走るようにしています。
git tag
特徴
あまり今までちゃんと使ったことがなかったのでこの期にしっかり勉強してみました。git tagは一種の目印のようなもので特定のコミットにつけることができます。特徴として
- リリースしたタイミングなどを管理しやすい
- checkoutしてその状態を即座に再現することができる
- 一つのタグに複数のタグをつけることができる
- コミットログとは時間軸が異なっても問題ない(ロールバックして最新のタグが昔のコミットでも問題ない)
があげられます。
すなわち、revertや新規のPRなしにロールバックしてその変更を管理できるので、今回の要件にはぴったりです。
付け方
何も指定しない場合は現在のブランチの最新のコミットにタグがつきます。
$ git tag <tag名>
ブランチ名やコミットハッシュを指定することで任意のコミットにタグをつけることができます。
$ git tag <tag名> <branch名 or commit hash>
またタグをつけただけでは、リモートのタグは更新されません。タグを公開するには、
$ git push origin <tag名>
とする必要があります。
jobの作成
version: 2
defaults: &defaults
docker:
- image: circleci/node:8-browsers
jobs:
build:
<<: *defaults
steps:
- checkout
- run:
name: build
command: echo build
test:
<<: *defaults
steps:
- checkout
- run:
name: test
command: echo test
test_for_prd:
<<: *defaults
steps:
- checkout
- run:
name: test_for_prd
command: echo test_for_prd
deploy:
<<: *defaults
steps:
- checkout
- run:
name: deploy
command: echo deploy
とりあえずこんな感じで、echoするだけのjobを作ってみました。全てダミーのジョブとしてechoしてるだけです。これらのjobを組み立ててフローにしていきます。
ワークフローの作成
直感的には、、
要件で求められているものを直感的に記述してみました。
workflows:
version: 2
test_and_deploy:
jobs:
- test
- test_for_prd:
filters:
tags:
only: /^v(\.[0-9]){3}.*/
- deploy:
requires:
- test_for_prd
これでpushしてみると、なんとtagがないときにだけデプロイが実行されてしまいます。。!?わけがわからなくてこれで結構時間を食われました。。
tagsは全部のjobに記述しないといけない
らしいです。このときは理由がよくわかってなかったのですが、とりあえず記述。
workflows:
version: 2
test_and_deploy:
jobs:
- test
- test_for_prd:
filters:
tags:
only: /^v(\.[0-9]){3}.*/
- deploy:
requires:
- test_for_prd
filters:
tags:
only: /^v(\.[0-9]){3}.*/
でも、依然ブランチpushでデプロイが走ってしまいます。。ほんまもんのプロダクトでやってたらえらいこっちゃw
CircleCIのデフォルト
いろいろ試したり調べたりしてるうちにわかったのが、
- filterのbranchesとtagsは無関係
- ブランチは特に指定しない限り、全てのフローが実行される
- タグは明示的に宣言しない限り、全てのフローが実行されない
というデフォルトの設定を持っていたのです。すなわち、タグプッシュでのみデプロイしたい場合は、そのフローを「任意のブランチで実行されないように指定し、かつ指定したタグで実行される」ように指定してやる必要があったのです。知らんわそんなもんw
てことで、
workflows:
version: 2
test_and_deploy:
jobs:
- test
- test_for_prd:
filters:
tags:
only: /^v(\.[0-9]){3}.*/
branches:
ignore: /.*/
- deploy:
requires:
- test_for_prd
filters:
tags:
only: /^v(\.[0-9]){3}.*/
branches:
ignore: /.*/
としてやると、思い通りの実装になりました。
理由としては、タグは印にすぎずタグで何かをトリガーしたい!という強い意思でもない限りフローを実行する必要がないので、あえてCircleCIではこのような実装にしてるんじゃないかな〜と思います。逆にCIなので、すべてのブランチに対して処理を実行というのがデフォルトなのも納得の行く気がします。
approvalの実装
ふむふむtype: approvalとするといいらしい。
- deploy:
type: approval
requires:
- approval
filters:
tags:
only: /^v(\d\.){2}\d.*/
branches:
ignore: /.*/
これでpushすると、、
おお!それっぽい!、、と思ったのもつかの間。。
このデプロイプロセスのログとかが全く見れなくなってしまいました。どうやら、approvalは単体でフローに入れるべきみたいでした。
- test_for_prd:
requires:
- build
filters:
tags:
only: /^v(\d\.){2}\d.*/
branches:
ignore: /.*/
- approval:
type: approval
requires:
- build
filters:
tags:
only: /^v(\d\.){2}\d.*/
branches:
ignore: /.*/
- deploy:
requires:
- approval
filters:
tags:
only: /^v(\d\.){2}\d.*/
branches:
ignore: /.*/
これで、思い通りの挙動になりました。
そして、approveすると、、
おお!デプロイできた!!
ま、echo deployしただけですけどねw