個人のTerraformにTFLint入れたら、あまりにもWarning出たのでやる気を失った。
— adachin👾SRE (@adachin0817) July 4, 2022
プライベートで利用しているインフラ環境(DigitalOcean)ですが、Terraformで全てコード化しています。去年以下ブログより開発環境や、CircleCIでCI/CDの環境も出来上がったというところで、TFLintを導入してみました。導入したものの、Warningが40個以上出てやる気を失いましたが、なんとか全部対応したので振り返っていきたいと思います。
[CircleCI][Orbs][path-filtering]特定ディレクトリに差分が出たらterraform applyを適用するように実装してみた
TFLintって何よ?
https://github.com/terraform-linters/tflint
TerraformのLinterです。上記のブログで terraform validateやplan
をCIで実行していますが、検知できないものもあります。また、TFLintは構文やパラメータなどTerraform本家のルールに相違ないかをチェックしてくれるツワモノなので、新規でインフラ構築をするには必須となるツールでしょう。インストール方法はローカルならbrewで、自分のTerraform開発環境はAlpineなのでwgetで解凍して利用しました。
- Dockerfile
1 2 |
# install tflint RUN wget -P /tmp https://github.com/terraform-linters/tflint/releases/download/v0.38.1/tflint_linux_amd64.zip && unzip /tmp/tflint_linux_amd64.zip -d /usr/local/bin |
- version
1 2 |
# tflint --version TFLint version 0.38.1 |
.tflint.hcl(DigitalOcean)
https://github.com/terraform-linters/tflint/blob/master/docs/user-guide/config.md
https://github.com/terraform-linters/tflint/tree/master/docs/rules
- .tflint.hcl
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 |
rule "terraform_comment_syntax" { enabled = true } rule "terraform_deprecated_index" { enabled = true } rule "terraform_deprecated_interpolation" { enabled = true } rule "terraform_documented_outputs" { enabled = true } rule "terraform_documented_variables" { enabled = true } rule "terraform_empty_list_equality" { enabled = true } rule "terraform_module_pinned_source" { enabled = true } rule "terraform_module_version" { enabled = true } rule "terraform_naming_convention" { enabled = true } rule "terraform_required_providers" { enabled = true } rule "terraform_required_version" { enabled = true } rule "terraform_standard_module_structure" { enabled = true } rule "terraform_typed_variables" { enabled = true } rule "terraform_unused_declarations" { enabled = true } rule "terraform_unused_required_providers" { enabled = true } rule "terraform_workspace_remote" { enabled = true } |
.tflint.hclは各クラウドで利用できるプラグインやruleを指定できます。AWSやGCPの場合、専用のプラグインを利用することで、そんなインスタンスタイプないぜ!、applyすることで失敗になる場合も下がる、など検知が可能になりますが、DigitalOceanはプラグインがなさそう!?とのことで今回は一般的に利用されているデフォルトのruleに絞りました。さて、ここからが正念場となります。
Warning/Notice対応
- Warning: List items should be accessed using square
https://github.com/terraform-linters/tflint/blob/v0.38.1/docs/rules/terraform_deprecated_index.md
Warning: List items should be accessed using square brackets(terraform_deprecated_index)
on do_kubernetes.tf line 17:
17: client_certificate = base64decode(digitalocean_kubernetes_cluster.adachin-wiki-cluster.kube_config.0.client_certificate)
listでcountがある場合は大括弧を指定しよう!と言われているので、以下のように数字のところを []
で囲いました。自分で書いててこれは気づかないぞ…!
1 2 3 4 5 6 7 |
- client_certificate = base64decode(digitalocean_kubernetes_cluster.adachin-wiki-cluster.kube_config.0.client_certificate) - client_key = base64decode(digitalocean_kubernetes_cluster.adachin-wiki-cluster.kube_config.0.client_key) - cluster_ca_certificate = base64decode(digitalocean_kubernetes_cluster.adachin-wiki-cluster.kube_config.0.cluster_ca_certificate) + client_certificate = base64decode(digitalocean_kubernetes_cluster.adachin-wiki-cluster.kube_config[0].client_certificate) + client_key = base64decode(digitalocean_kubernetes_cluster.adachin-wiki-cluster.kube_config[0].client_key) + cluster_ca_certificate = base64decode(digitalocean_kubernetes_cluster.adachin-wiki-cluster.kube_config[0].cluster_ca_certificate) |
- Notice:
k8s-size
variable has no description (terraform_documented_variables)
Notice:
k8s-size
variable has no description (terraform_documented_variables)on variables.tf line 29:
29: variable “k8s-size” {
変数名は必ずdescriptionを書いて誰でも分かるようにしましょう!とのことなので追加するだけです。初めて知った…!
1 2 3 4 5 |
variable "k8s_size" { default = "s-1vcpu-2gb" default = "s-1vcpu-2gb" description = "k8s size" } |
- Notice: resource name
adachin-wiki-cluster
must match the following format: snake_case (terraform_naming_convention)
https://github.com/terraform-linters/tflint/blob/v0.38.1/docs/rules/terraform_naming_convention.md
Notice: resource name
adachin-wiki-cluster
must match the following format: snake_case (terraform_naming_convention)on do_kubernetes.tf line 1:
1: resource “digitalocean_kubernetes_cluster” “adachin-wiki-cluster” {
resource名はアンダーバーにしましょう!とのことなので、これはterraform mvコマンドを利用して参照しているものを全て変更しました。これくらいは朝飯前ですな。
1 |
# terraform state mv digitalocean_kubernetes_cluster.adachin-wiki-cluster digitalocean_kubernetes_cluster.adachin_wiki_cluster |
- Warning: Missing version constraint for provider “kubernetes” in “required_providers” (terraform_required_providers)
https://github.com/terraform-linters/tflint/blob/v0.38.1/docs/rules/terraform_required_providers.md
Warning: Missing version constraint for provider “kubernetes” in “required_providers” (terraform_required_providers)
on do_kubernetes.tf line 14:
14: provider “kubernetes” {
provider指定しているならバージョン指定しましょう!とのことで、backend.tfに追加しました。こんなん知らんぞ…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
terraform { backend "remote" { hostname = "app.terraform.io" organization = "adachin-server" workspaces { name = "adachin-server" } } required_version = ">= 1.0.5" required_providers { digitalocean = { source = "digitalocean/digitalocean" version = "2.11.1" } kubernetes = "~> 2.0" #追加 } } |
- Warning: Module should include a main.tf file as the primary entrypoint (terraform_standard_module_structure)
- Warning: Module should include an empty outputs.tf file (terraform_standard_module_structure)
Warning: Module should include a main.tf file as the primary entrypoint(terraform_standard_module_structure)
on main.tf line 1:
(source code not available)Warning: Module should include an empty outputs.tf file (terraform_standard_module_structure)
on outputs.tf line 1:
(source code not available)
最小限のモジュールとしてmain.tf、outputs.tfは空でもいいから作成してね!とのことだったので追加しました。基本各役割ごとにファイルを作成していたので必須なの知らんかった…
- Warning:
k8s_size
variable has no type (terraform_typed_variables)
https://github.com/terraform-linters/tflint/blob/v0.38.1/docs/rules/terraform_typed_variables.md
変数にはちゃんと型を定義してね!とのことなのでstringを追加しました。なかなか細かい…!
1 2 3 4 5 |
variable "k8s_size" { default = "s-1vcpu-2gb" description = "k8s size" type = string } |
これらが実際40個以上あったので対応完了となります。死にそうだった…
CircleCIにTFLintを追加
- .circleci/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 |
- .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 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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
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 install-tflint: #追加 description: "Install TFLint" steps: - run: command: wget -P /tmp https://github.com/terraform-linters/tflint/releases/download/v0.38.1/tflint_linux_amd64.zip && unzip /tmp/tflint_linux_amd64.zip -d /usr/local/bin 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: Prd 👍 color: '#42f486' message: 'No terraform difference OK !! ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} ## Terraform CI prd_terraform_fmt_validate_tflint: executor: name: terraform/default tag: 1.0.5 resource_class: small working_directory: ~/project steps: - apk-pkg-install - install-tflint #追加 - 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: 'Error terraform validate 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: Prd 👍 color: '#42f486' message: 'terraform validate OK ✨ \n :grin: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - run: #追加 name: tflint command : cd terraform/env/prd/ && tflint - slack/status: fail_only: true mentions: 'here' failure_message: 'Error tflint 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: Prd 👍 color: '#42f486' message: 'tflint 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: 'Error terraform plan 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: Prd 👍 color: '#42f486' message: '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: 'Error terraform apply 🚨 \n :innocent: ${CIRCLE_USERNAME} :branch: ${CIRCLE_BRANCH}' webhook: ${SLACK_WEBHOOK} - slack/notify: title: Prd 👍 color: '#42f486' message: '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_tflint - prd_terraform_plan: requires: - prd_terraform_fmt_validate_tflint prd_terraform_apply: when: << pipeline.parameters.prd_terraform_apply >> jobs: - prd_terraform_apply: filters: branches: only: master |
Terraform CircleCIは最近Orbsに移行し、path-filteringで差分があるときに実行できるようにしています。追加したところはTFLintのインストール36行目と、validate後にTFLintを実行したいため、68、87行目に追加しました。実際にWarningを出してみましょう。
- Warning
- 正常時
まとめ
8月までの目標🙌
・TFLint導入
・wikiをCircleCIでコンテナbuildできるように
・k8sのバージョンアップ
・DOのコスト通知をシェルからGoに移行— adachin👾SRE (@adachin0817) July 25, 2022
まずは8月までの目標としてTFLintを導入することができました。Terraformのリファクタリングを前進できたのは良かったですし、新たなる気付きと勉強にもなりました。ただ、初期の状態から導入しておけばよかったと後悔…!次はwikiのWordPressコンテナをCircleCIでbuildできるように作ってみたいと思います。tfsecも面白そうなので時間ある時に試してみたいと思います。
0件のコメント