[DigitalOcean]Terraform開発環境構築とterraform applyをCircleCIのAPI経由でリリースするように実装してみた
先日書いたブログで、Terraformの開発環境は特に問題ないのですが、誰でもterraform applyをCircleCIのAPIで叩けてしまうことに対して懸念点がありました。その際inamuuさんにアドバイスをもらったところ、Orbsの path-filtering
を利用すればTerraformのコードに差分が出ればterraform applyを実行できるとのことなので早速実装してみました。
GitHub ActionsでAnsibleとTerraform用にそれぞれファイルをわけてpathsでディレクトリ名を書いてあげれば、Ansibleが変更あった時はTerraformは適用されないとかできるよー。
— Inamuu🏕 (@kzm0211) October 14, 2021
構成
push時には terraform validate,plan
が実行されて、terraformのディレクトリである terraform/env/prd
に差分があれば path-filtering
が検知して、masterマージすると terraform apply
を実行してくれます。それでは以下CircleCIの修正箇所説明したいと思います。
https://discuss.circleci.com/t/setup-workflow/39578
ちなみにこの path-filtering
はsetup workflow(CircleCI 2.1)と呼ばれており、今年の3月にプレビュー版が出ていたようで現在は本リリース済みとなっています。@mfunakiさんが以下のようにまとめてもらっています。
・ダイナミック コンフィグ: 実行時にニーズに合うコンフィグを生成するようなスクリプトを定義できます。
・プレビルド ステップ: プレビルド ステップとして、実行時に何をビルドするのかを分析し、決定可能なステップを先行して実行できます。
セットアップ・ワークフローが有効であることが期待される領域の一つに、モノリポ(モノレポ)を活用いただいているお客様のサポートが挙げられます。Bazelのようなビルドツールを利用して、gitコマンドを実行して、変更のあったファイルを検出し、複雑な依存関係を分析した上で、複数あるコンフィグファイルを組み合わせて実行しているといった、さまざまなモノリポのユースケースにおいて、それぞれ異なるジョブを利用することが可能になります。各機能はOrbsの中に組み込んでおくことで、必要な機能を再利用することが容易になります。CircleCIでは、パス フィルタリングを行うOrbを今回リリースします。このOrbを使うことで、Push時にどのファイルが変更されたのかに応じて、パラメータを設定することが可能になります。例えば、どのワークフローを実行するのかを、引き続き実行されるワークフローに対してパラメータとして渡すことが可能になります。
CircleCI
https://circleci.com/developer/ja/orbs/orb/circleci/path-filtering
- 準備
Project Settings > Advanced Settings
- 元のソースコード
- .circleci/config.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
version: 2.1 setup: true #追加 orbs: path-filtering: circleci/path-filtering@0.0.3 #追加 slack: circleci/slack@3.4.2 ~省略~ workflows: version: 2.1 terraform_ci_cd: jobs: - terraform_fmt_validate - terraform_plan: requires: - terraform_fmt_validate - path-filtering/filter: base-revision: origin/master config-path: .circleci/terraform_apply.yml mapping: | terraform/env/prd/.* terraform_apply true |
まずはOrbsを追加しましょう。~省略~ の部分はjobsを使ってterraform validate,planの宣言と処理を書いている想定とします。workflowsに path-filtering
の処理を追加します。path-filteringを利用する場合は必ず別ファイルでconfigファイルが必要となります。そのファイルにはterraform applyを実行する処理を以下のファイルに移行しました。base-revisionではデフォルトはmainブランチなのでmasterブランチのコードと比較するように指定します。またmappingには検知して欲しいディレクトリ配下を指定して以下に出てくる pipeline.parameters
が true
つまり差分があればapplyするように仕込みます。
- .circleci/workflows.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
version: 2.1 orbs: slack: circleci/slack@3.4.2 parameters: terraform_apply: type: boolean default: false references: default_config: &default_config docker: - image: hashicorp/terraform:1.0.5 working_directory: ~/project environment: TERRAFORM_ENV: ~/project/ repo_cache_key: &repo_cache_key v1-repo-{{ .Branch }}-{{ .Revision }} restore_repo: &restore_repo restore_cache: key: *repo_cache_key save_repo: &save_repo save_cache: key: *repo_cache_key paths: - ~/project terraform_init: &terraform_init run: name: terraform init command: | cd ~/project/terraform/env/prd terraform init -backend-config="token=${TF_API_TOKEN}" jobs: check_terraform_difference: <<: *default_config steps: - run: name: install package command: | apk add bash curl jq - run: echo "No difference ok !!" - slack/status: fail_only: true mentions: 'here' failure_message: 'Error check terraform difference 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: 👍 color: '#42f486' message: 'No terraform difference OK !! ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} terraform_apply: <<: *default_config steps: - *restore_repo - *set_terraform_environment - *terraform_init - run: name: terraform apply command: | apk add bash curl jq cd ~/project/terraform/env/prd terraform apply -auto-approve - slack/status: fail_only: true mentions: 'here' failure_message: 'Error terraform apply 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: 👍 color: '#42f486' message: 'terraform apply OK ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} workflows: version: 2.1 check_terraform_difference: jobs: - check_terraform_difference when: and: - not: << pipeline.parameters.terraform_apply >> terraform_apply: when: << pipeline.parameters.terraform_apply >> jobs: - terraform_apply: filters: branches: only: master |
ここでは新たに terraform_difference
というjobを作成しました。これはCircleCIの仕様で以下差分がないときにmappingに検知されず、全てのパラメータはdefaultのfalseが返ってきてしまうという動作になってしまうため、実行するworkflowがない場合はエラーとなります。なのでわざわざechoでtrueを返すように作りました。ここはわざわざjob作る工程をなくしてほしい…それ以外はworkflowsでpipeline.parametersによるmasterマージ後に発動するように作るだけです。
All Workflows have been filtered from this Pipeline.No Jobs have been run.
Test
差分がないときはpath-filteringとcheck_terraform_differenceが動作してmaster merge時にterraform applyが実行されていません。
そして差分がある場合はmasterマージでterraform applyされていることが分かります。
ちなみに以下 Max number of workflows exceeded." error
というエラーが出る場合は setup: true
が付いていることによって複数のworkflowが無効化になります。その場合はデプロイ系は全て別ymlに移行して、single workflowにすれば可能です。
https://support.circleci.com/hc/en-us/articles/360060934851–Max-number-of-workflows-exceeded-error
まとめ
CircleCIのpath-filtering使って差分があるときだけマージでterraform applyできた。これはくそ便利なのでブログ書こう! pic.twitter.com/JyaSVtZgnm
— adachin👾SRE (@adachin0817) October 16, 2021
これはめちゃくちゃ便利でした!このpath-filteringを利用することでTerraformのリリースもより安全に運用することができました。他にも、例えば一つのリポジトリに複数のコードが存在した場合(モノレポ)、差分があればリリースするような仕組みも簡単に応用できますね。わざわざリポジトリ分割もする必要がありません。(Lambdaとか)あとはTerraform CIはOrbsに移行するかな…
それとハマったのは以下のエラーでworkflowsではなくjobs配下にpath-filteringの設定を書かないととまったく動かないので注意しましょう。
Cannot find a definition for command named path-filtering/filter
ちなみにCircleCI ユーザコミュニティミートアップでLTしてきました!
https://circleci.com/ja/blog/serverless-terraform-release/
※ちなみにTerraform CIはOrbsに移行してリファクタリングしました。planも差分あるときだけでいいね。
- config.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
version: 2.1 setup: true orbs: path-filtering: circleci/path-filtering@0.0.3 workflows: version: 2.1 terraform_ci_cd: jobs: - path-filtering/filter: base-revision: origin/master config-path: .circleci/workflows.yml mapping: | terraform/env/prd/.* prd_terraform_ci true terraform/env/prd/.* prd_terraform_apply true |
- workflows.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
version: 2.1 orbs: git-shallow-clone: guitarrapc/git-shallow-clone@2.4.0 terraform: circleci/terraform@3.1.0 slack: circleci/slack@3.4.2 parameters: prd_terraform_ci: type: boolean default: false prd_terraform_apply: type: boolean default: false references: default_config: &default_config docker: - image: alpine:3.15.0 resource_class: small working_directory: ~/project terraform_init: &terraform_init run: name: terraform init command: | cd ~/project/terraform/env/prd terraform init -backend-config="token=${TFC_API_TOKEN}" commands: apk-pkg-install: description: "Slack used package" steps: - run: command: apk add bash curl jq jobs: check_no_difference: <<: *default_config steps: - apk-pkg-install - run: echo "No terraform difference ok !!" - slack/status: fail_only: true mentions: 'here' failure_message: 'Error check terraform difference 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: 👍 color: '#42f486' message: 'No terraform difference OK !! ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} ## Terraform CI prd_terraform_fmt_validate: executor: name: terraform/default tag: 1.0.5 resource_class: small working_directory: ~/project steps: - apk-pkg-install - git-shallow-clone/checkout: depth: 1 fetch_depth: 1 - *terraform_init - terraform/fmt: path: terraform/env/prd/ - terraform/validate: path: terraform/env/prd/ - slack/status: fail_only: true mentions: 'here' failure_message: '[Prd] Error terraform validate 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: Prd 👍 color: '#42f486' message: '[Prd] terraform validate OK ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} # prd plan prd_terraform_plan: executor: name: terraform/default tag: 1.0.5 resource_class: small working_directory: ~/project steps: - apk-pkg-install - git-shallow-clone/checkout: depth: 1 fetch_depth: 1 - *terraform_init - terraform/plan: path: terraform/env/prd/ - slack/status: fail_only: true mentions: 'here' failure_message: '[Prd] Error terraform plan 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: Prd 👍 color: '#42f486' message: '[Prd] terraform plan OK ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} # prd apply prd_terraform_apply: executor: name: terraform/default tag: 1.0.5 resource_class: small working_directory: ~/project steps: - apk-pkg-install - git-shallow-clone/checkout: depth: 1 fetch_depth: 1 - *terraform_init - terraform/apply: path: terraform/env/prd/ - slack/status: fail_only: true mentions: 'here' failure_message: '[Prd] Error terraform apply 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: Prd 👍 color: '#42f486' message: '[Prd] terraform apply OK ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} workflows: version: 2.1 check_no_difference: jobs: - check_no_difference when: and: - not: << pipeline.parameters.prd_terraform_ci >> - not: << pipeline.parameters.prd_terraform_apply >> ## Terraform CI/CD prd_terraform_ci: when: << pipeline.parameters.prd_terraform_ci >> jobs: - prd_terraform_fmt_validate - prd_terraform_plan: requires: - prd_terraform_fmt_validate prd_terraform_apply: when: << pipeline.parameters.prd_terraform_apply >> jobs: - prd_terraform_apply: filters: branches: only: master |
0件のコメント