Terraform開発時のDeveloper Experienceを爆上げする
はじめに1
この記事は terraform Advent Calendar 2020 2日目の記事です。
1日目は rakiさん の 2020年の terraform-jp 振り返り
です!
3日目は rakiさん の aws iam policy で s3 の bucket 制限を書く時に便利かもしれない小技 です!
はじめに2
こんにちは。みなさんterraform書いてますか?
最近は日に日にterraformがIaCのデファクトになりつつあるように感じます。
Google Trend
もちろんterraform本体の開発が活発なのも一因ですが、terraformを取り巻くecosystemの隆盛も近年普及してきた要因の一つなのかなと感じます。適切にecosystemを使いこなせば一般的なwebアプリケーションの開発時のライフサイクルと同じようなDeveloper Experienceをterraformの開発時でも受けられるようになってきています。
さて、それではどのようなツールがあるでしょうか?
こちらのリポジトリに関連ツールやドキュメント等一覧がまとまっているので、迷ったらここを見れば問題なさそうです。(全部読んだならこの記事を読む必要はもう全然ない)ですが、いかんせん量が多いので今回は 開発時のエンジニアの体験(DX) を良くするもの/自分が使ってみて実際に良くなったものにフォーカスして紹介させていただこうと思います。
この記事で触れること
- DX系のterraform関連ツール
この記事で触れないこと
- terraformそのもののベストプラクティス
一覧
setup-terraform
tfenv
tflint
tfnotify
tfsec
tfupdate
今回は作成したばかりのこちらのリポジトリを使って、0からの構築まで行っていきます。
tfenv
1.
まずは最初は何はともあれterraformコマンドを使えるようにしましょう!
もちろん brew install terraform
でも問題ありませんが、1つ問題があります。複数のリポジトリやチームでterraformのversionが違った場合その都度brewで入れ直す事になりとても精神が持ちそうにありません。
簡単にterraformのversionを切り替えられるようにweb開発でも多く用いられる バージョンマネージャー を導入しましょう!nodenvとかrbenvとかそういうやつです。
terraformにおけるバージョンマネージャーは tfenv
が一般的です。
installしたらversionを固定するためにプロジェクトルートに .terraform-version
を作成しておくとチーム開発時も困らなくてとてもいいですね?
$ brwe install tfenv
$ cd ~/YOUR_WORKING_DIR
# remoteのversionを調べる
$ tfenv list-remote
0.14.0-rc1
0.14.0-beta2
0.14.0-beta1
0.14.0-alpha20201007
0.14.0-alpha20200923
0.14.0-alpha20200910
0.13.5
0.13.4
0.13.3
...
$ echo 0.13.5 > .terraform-version
$ tfenv install
Installing Terraform v0.13.5
Downloading release tarball from https://releases.hashicorp.com/terraform/0.13.5/terraform_0.13.5_darwin_amd64.zip
######################################################################## 100.0%
Downloading SHA hash file from https://releases.hashicorp.com/terraform/0.13.5/terraform_0.13.5_SHA256SUMS
No keybase install found, skipping OpenPGP signature verification
Archive: tfenv_download.rqwb9E/terraform_0.13.5_darwin_amd64.zip
inflating: /usr/local/Cellar/tfenv/2.0.0/versions/0.13.5/terraform
Installation of terraform v0.13.5 successful. To make this your default version, run 'tfenv use 0.13.5'
$ terraform -v
Terraform v0.13.5
詳しくは作者の方のQiitaの記事があるのでそちらを掲載させていただきます。
ひとまずterraformをinstallしてinitしてplanできるところまで作成できました!
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_vpc.example will be created
+ resource "aws_vpc" "example" {
+ arn = (known after apply)
+ assign_generated_ipv6_cidr_block = false
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_classiclink = (known after apply)
+ enable_classiclink_dns_support = (known after apply)
+ enable_dns_hostnames = (known after apply)
+ enable_dns_support = true
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
この章のまとめ
- versionが複数あると切り替えるのが辛い
- terraformのバージョンマネージャーはtfenv
- 今回のコミット
tflint
2.
先ほど無事にterraformを使えるようになりましたが問題があります。
tabを連打して見たこともないインデントでコミットしていました😭
今回は私のミスでしたが、チーム開発では人によってインデントがずれたりタブの数が違ったりして最悪死人が出るケースがあります。これを防ぐためにビルドインの fmt
コマンドを使うのが一般的かと思われます。
$ terraform fmt -diff
main.tf
--- old/main.tf
+++ new/main.tf
@@ -19,5 +19,5 @@
}
resource "aws_vpc" "example" {
- cidr_block = "10.0.0.0/16"
+ cidr_block = "10.0.0.0/16"
}
無事インデントが修正されました。
ですがこれ以外にも問題があります。fmt
を実施してかつvalidate
も実施したにも関わらず、terraform apply
を実行するとerrorになる場合があります。(EC2の存在しないリソースタイプを指定した時など)これは実際に開発しているとかなり心にくるものがあります。
こんな事にならないためにterraformのLinter tflint
を導入しましょう!
$ brew install tflint
合わせてproject rootに.tflint.hcl
を配備しておくと自動で読み込んでくれます!.tflint.hcl
内で設定できる ルールの一覧はこちらです!
最後にcommit時にfmt
とtflint
がかかるようにしておきましょう!
pre-commit
のファイル作成と設定変更をしました!(ここは本編にあまり関係ないので割愛します こちらの記事 を参考にさせて頂きました)
$ cat .githooks/pre-commit
terraform fmt -diff
tflint
for FILE in `git diff --diff-filter=d --staged --name-only`; do
git add $FILE
done
$ git cm -m "add tflint"
main.tf
--- old/main.tf
+++ new/main.tf
@@ -19,5 +19,5 @@
}
resource "aws_vpc" "example" {
- cidr_block = "10.0.0.0/16"
+ cidr_block = "10.0.0.0/16"
}
[main 39a878f] add tflint
Date: Tue Dec 1 06:15:41 2020 +0900
3 files changed, 10 insertions(+), 1 deletion(-)
create mode 100755 .githooks/pre-commit
create mode 100644 .tflint.hcl
この章のまとめ
- 喧嘩にならないように
fmt
- 心が疲れないように
tflint
- ここまでのコミット
setup-terraform
3.
terraformのplan/applyと開発時のLintチェックまで実施されるようになりました!👏
ここまできたらいよいよterraformをCIに組み込みましょう!
勿論CIは 今使っているもの/お好きなもの で構いませんが、おそらく最も手軽なのはGitHub Actionsとsetup-terraform
を使う方法でしょう。
setup-terraform
はGitHub Actionsを利用する際に簡単に plan/apply を実行できるようmarcketplaceに公開されているHashicorp公式が作っているActionです!
GitHub Actionsの説明は割愛しますが作成したyamlはこんな感じです!
name: pr-plan
on:
push:
branches-ignore:
- main
jobs:
terraform:
name: Terraform
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v2
- name: configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 0.13.5
- name: Terraform Init
id: init
run: terraform init
- name: Terraform Plan
id: plan
run: terraform plan
setup-terraform
を利用したので面倒な環境設定は不要でこれだけ!簡単にPRにPlanを自動で実行されるようになりました!
おまけ
ついでにmain branchにmergeされた時に自動でapplyされるActionも作成しておきました。
terraformのCI戦略は多数あるかと思いますが私はkimさんのこちらの記事がベストと思っています。
この章のまとめ
tfnotify
4.
CIもできてチーム開発もバッチリ!と思いきや実際に こちらのPR をレビューしてみましょう。
レビューするとなるとシンプルな作成だけならまだいいですが更新や削除など複数存在すると流石にコードからのみ判断するのはすごく難しいです。おそらくほとんどの方はPlanの結果を見て「不要なリソースが作られていないか」「更新箇所は合っているか」などレビューをするのではないかと思います。
ではもう一度レビュワーの気持ちになってみましょう。
確かにGitHub Actionsの実行結果を見ればPlanの結果はわかりますが、少し導線が遠いです😥
また、planの結果を見てConversationで議論するなどといったこともできません。😥
もう少しレビュワーフレンドリーにしていきましょう!
もし、PRにPlan結果がコメントされていたらどうでしょう?
PRを開くと同時にPlan結果が見れますし、続けてコメントすることもできます!とても良さそうです!
それを実現するためにtfnotify
を利用します。
tfnotify
はterraformのplanやapply結果をGitHubやSlackに送信するのを手助けしてくれるツールです。
先ほどのActionsの設定にtfnotify
のinstallを追加します
- name: Install tfnotify
id: install
run: |
sudo curl -fL -o tfnotify.tar.gz https://github.com/mercari/tfnotify/releases/download/v0.7.0/tfnotify_linux_amd64.tar.gz
sudo tar -C /usr/bin -xzf ./tfnotify.tar.gz
既存のplanも少し修正します
- name: Terraform Plan
id: plan
run: terraform plan | tfnotify -config .tfnotify/github.yaml plan
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Slack, GitHubへの通知のフォーマットの設定も作成すれば完了です!
無事PRにplan結果がコメントされました!!👏👏👏
Tips
GitHub ActionsではGITHUB_TOKEN
が最初から用意されておりこのTokenを使えばbot userからのコメントが簡単にできるようです!
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
この章まとめ
tfsec
5.
先ほどのPRはマージして無事リソースを作成することができました!👏
このまま作業を続けていきましょう!今度はECRが必要になりました!早速PRを作っておきます!
resource "aws_ecr_repository" "example" {
name = "sample"
}
こちらを追加して
- コミット時のformat, lintもおkで
- PRのcheckもおk
- 差分もおk
作ったものがうまく機能していますね!問題なさそうです!早速マージしましょう!
...と言いたいところですが一つ問題があります🤯
ECRにはpush時にimageをスキャンして脆弱性を検知してくれる仕組みがあります。セキュリティー的にこちらのimage scanも有効にしておきたいです!しかし、image scanの設定 image_scanning_configuration
はOptionalな項目であり、設定しなくても構文エラーがあるわけでもないので今の仕組みでは検知できません🤯
このようにformatやLintではなくセキュリティ的に問題がある場合も自動で検知しておきたいです。
このようなユースケースでは tfsec
を利用しましょう!
tfsec
はterraformのコードからセキュリティ問題を検知してくれるツールです。setup-terraform
と同じように公式からActionがMarketplaceで公開されています!
こちらを利用してもいいのですが、せっかくなので簡単にPRで検知できるようにしていきます reviewdog が tfsec
をラップしてPRで使いやすいようにしてこちらもActionが公開されていますのでこちらを利用していきます!
planとはcheck結果を分けたかったので新しくworkflowを作っておきます。一緒にしたい場合は先ほどのファイルに追記する形でも問題ないと思います。
name: pr-tfsec
on: [pull_request]
jobs:
tfsec:
name: tfsec
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v2
- name: tfsec
uses: reviewdog/action-tfsec@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
fail_on_error: "true"
filter_mode: "nofilter"
各種設定は こちら にありますので、お好みで設定してみてください。
早速Actionが起動してcheckがfailしました!👏
ECRのimage scanの部分にもコメントが来ています!修正箇所や何をすればいいのかすごく分かり易いです!
修正して push しておきます。
今度は無事にcheck通りました!
memo
tfsec
用のActionを作成した後、 reviewdog: this is not PullRequest build.
と表示されcheckが走らない状態になりました。
PRをcloseして再度開いたら無事に起動し、以降再現しませんでした。
この章のまとめ
- セキュリティの観点からもPRをチェックしたい
- tfsecのreviewdogがPRに便利
- PRはこちら
tfupdate
6.
ここまででかなり開発時の体験を向上できました!(できたな?)
最後にterraform利用者なら誰もが悩むポイントに触れていこうと思います!
冒頭でもお話しした通りterraformの開発は非常に活発に行われており、またterraform本体だけでなくAWSも日夜開発が行われておりそれに対応するためaws providerも非常に高い頻度で開発が行われています。そこで発生するのがバージョンアップ問題です😧
webアプリの開発でもライブラリやフレームワークのバージョンなど油断すると置いていかれてしまいバージョンアップに苦労した。なんて経験がある方も多いと思いますが、だいたい同じような問題がterraform開発時にも発生します。webアプリ開発では Dependabot や Scala Steward などが用いられますがterraformの場合は tfupdate
があります。
tfupdate
は作者の方のQiitaの記事から抜粋させていただくと
これをCIとかジョブスケジューラで流せば、毎日最新版をチェックして、差分があれば自動でバージョンアップのPull Requestを生成したりできます。つまり、「ぼくのかんがえたさいきょうのTerraform版Dependabot」ができるのだ。
こちらもGitHub Actionsで動かして毎朝起動して、version upがあればPRを送ってもらうようにしましょう!
使い方に関しては上記記事に詳細に書かれているのでここでは省略しますが、主に terraform
, provider
, module
, release
のversionを更新できます。今回はprovider
等は人によって違うと思うのでterraform
を自動で検知するようにしてみましょう。
name: tfupdate
on:
schedule:
- cron: "00 23 * * 0-4" # 8:00 JST/SUN-THU
workflow_dispatch:
jobs:
tfupdate:
name: tfupdate
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install
run: |
sudo curl -fL -o tfupdate.tar.gz https://github.com/minamijoyo/tfupdate/releases/download/v0.4.2/tfupdate_0.4.2_linux_amd64.tar.gz
sudo tar -C /usr/bin -xzf ./tfupdate.tar.gz
- name: git setting
run: |
git config --local user.email ${{ secrets.GITHUB_EMAIL }}
git config --local user.name ${{ secrets.GITHUB_USERNAME }}
- name: Update terraform to latest
run: |
VERSION=$(tfupdate release latest hashicorp/terraform)
UPDATE_MESSAGE="[tfupdate] Update terraform to v${VERSION}"
if hub pr list -s "open" -f "%t: %U%n" | grep -F "$UPDATE_MESSAGE"; then
echo "A pull request already exists"
elif hub pr list -s "merged" -f "%t: %U%n" | grep -F "$UPDATE_MESSAGE"; then
echo "A pull request is already merged"
else
git checkout -b update-terraform-to-v${VERSION}
tfupdate terraform -v ${VERSION} -r ./
if git add . && git diff --cached --exit-code --quiet; then
echo "No changes"
else
git commit -m "$UPDATE_MESSAGE"
PULL_REQUEST_BODY="For details see: https://github.com/hashicorp/terraform/releases"
git push origin HEAD && hub pull-request -m "$UPDATE_MESSAGE" -m "$PULL_REQUEST_BODY" -b main
fi
fi
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
無事PRが送られてきました👏👏👏
この章のまとめ
- terraformでもDependabotしたい
- コミットはこちら
さいごに
ご覧いただきありがとうございました!
自分の知ってる範囲でDX向上に繋がりそうなものを紹介させていただきました。「こんなのもあるよ!」とか「ここちょっとおかしくない?」等ご意見あれば(優しい言葉で)ドシドシコメントいただけると嬉しいです!
課題感としては今回紹介した構成ですと 一括plan/一括apply が前提の作りになっています。この場合最大の敵が 構成ドリフト になると思います。手動でGHAを書いて検知してもいいですが、何か素敵なツールをご存知の方がいらっしゃいましたらこちらもご教授いただけると嬉しいです!
以上ありがとうございました!
参考
- https://kiririmode.hatenablog.jp/entry/20200509/1588995498
- https://sil.hatenablog.com/entry/terraform-linter-tflint-release
- https://medium.com/@astatsuya/githooksのpre-pushを共有してレポジトリを健全に保つ-7156def39b64
- https://devblog.thebase.in/entry/tfsec-aws
- https://qiita.com/osakiy/items/df80db383ce33b82c797
- https://github.com/minamijoyo/tfupdate-circleci-example/blob/master/.circleci/config.yml
Discussion