terraform を upgradeしてtflintをいれる

DMMの[yuua](https://qiita.com/yuua0216)です
terraformのupgradeとtflintの導入を行ったのでその際の手順的な備忘録です。

# upgrade
使用していたterraformのversionが `0.14` 系だったのでそこから `1.1.x` までupgradeしました。
0.13以前のversionの場合は0.1系ずつあげていく必要がありますが、0.14以降であれば一部versionをskipしての
upgradeも可能なようです。ただ0.14系から1.0.x系に上げる場合は0.15系への[upgrade guid](https://www.terraform.io/language/upgrade-guides/0-15)を確認してからupgradeしましょう。

私のチームでのterraform環境は `circle ci` 上で terraform を実行しています。
そのため各versionの `docker image` を作成し、ECR上で管理します。

baseとなるdocker imageは [hashicorp/terraform](https://hub.docker.com/r/hashicorp/terraform/tags)を使用します

## 1.0.x化

```Dockerfile
FROM hashicorp/terraform:1.0.x

# aws cli
RUN apk --update --no-cache add python3 py3-pip curl && pip3 install --no-cache-dir --upgrade pip awscli

# tfnotify
ENV TFNOTIFY_VERSION=0.7.0
RUN wget https://github.com/mercari/tfnotify/releases/download/v${TFNOTIFY_VERSION}/tfnotify_linux_amd64.tar.gz -O tfnotify.tar.gz \
    && tar -xf tfnotify.tar.gz -C /usr/local/bin/ \
    && chmod u+x /usr/local/bin/tfnotify \
    && rm tfnotify.tar.gz

# gcloud
ENV GCLOUD_VERSION=337.0.0
RUN curl -o google-cloud-sdk.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-${GCLOUD_VERSION}-linux-x86_64.tar.gz \
    && tar -xf google-cloud-sdk.tar.gz -C $HOME \
    && $HOME/google-cloud-sdk/install.sh --quiet \
    && rm google-cloud-sdk.tar.gz

ENV PATH $PATH:/root/google-cloud-sdk/bin
```

1.0.x系の docker imageを使用し、 `apply` に際し必要なものやその他必要なツール類をimageに含めるように作成します。(ここではgcloud-sdk / tfnotifyをいれています)

main.tfの `required_version` を変更しておく、変更しなくてもplan/apply自体はできますが一応明示しておきましょう。

```main.tf

terraform {
  required_version = ">= instalしたterraformのversion指定"

  required_providers {
       ....
    }
  }
.....
....
...
..
```

`terrform plan` を実行し no change であることが確認できたら `terraform apply` を実行します。
changeやdeprecatedなどがでた際は対応しましょう。


## 1.1.x化

基本的に1.0.x化と同様にimage上のterraformをupgradeするだけです。
今回はtflintもいれるのでlinterを追加しています。

tflintのversionに関してはterraformのversionと互換性があるversionをいれてください
また、tflintにはinstall shellが用意されていますが、 `hashicorp/terraform:1.1.x` のimage上で実行すると `unzip` でエラーが発生するため
install shellは使用していません。

```Dockerfile
FROM hashicorp/terraform:1.1.x

.....
....
...
..

# tflint
ENV TFLINT_VERSION=v0.34.1
ENV OS=linux_amd64
RUN curl --fail --silent -L -o /tmp/tflint.zip "https://github.com/terraform-linters/tflint/releases/download/${TFLINT_VERSION}/tflint_${OS}.zip" \
    && unzip /tmp/tflint.zip -d /tmp/ \
    && install -c -v /tmp/tflint /usr/local/bin/ \
    && rm /tmp/tflint*
```


```main.tf

terraform {
  required_version = ">= instalしたterraformのversion指定"

  required_providers {
       ....
    }
  }
.....
....
...
..
```

こちらも同様に、`terrform plan` を実行し no change であることが確認できたら `terraform apply` を実行します。
changeやdeprecatedなどがでた際は対応しましょう。

# tflintの導入

tflintを導入することでルールを強制することができます。また主要なプロバイダー(aws/gcp/azure)などのルールセットはgithub上にreleaseされているのでそちらを使用します。
(tflintのversionに関してはterraformのversionと互換性があるversionをいれてください)

tflintのdocker imageの追加自体は1.1.x化の際に対応してあります。

https://github.com/terraform-linters/tflint

```Dockerfile
ENV TFLINT_VERSION=v0.34.1
ENV OS=linux_amd64
RUN curl --fail --silent -L -o /tmp/tflint.zip "https://github.com/terraform-linters/tflint/releases/download/${TFLINT_VERSION}/tflint_${OS}.zip" \
    && unzip /tmp/tflint.zip -d /tmp/ \
    && install -c -v /tmp/tflint /usr/local/bin/ \
    && rm /tmp/tflint*
```

## pluginの追加

pluginを事前に `.tflint.d` に配下にインストールしておくことで都度インストールすることを回避できます。
私のチームでは aws/gcp の2つのクラウド環境を利用しているため
`tflint-ruleset-aws` `tflint-ruleset-google` の2つを事前にインストールしています。
docker image上で実行しているため事前にインストールしておかないと `tflint --init` のたびにインストールしてしまいます。

```Dockerfile
# tflint bundle plugin
ENV TFLINT_AWS_VERSION=0.12.0
ENV TFLINT_GOOGLE_VERSION=0.15.0
RUN mkdir -p  ~/.tflint.d/plugins/github.com/terraform-linters/tflint-ruleset-aws/${TFLINT_AWS_VERSION}/ \
    && wget -O /tmp/tflint-ruleset-aws.zip https://github.com/terraform-linters/tflint-ruleset-aws/releases/download/v${TFLINT_AWS_VERSION}/tflint-ruleset-aws_linux_amd64.zip \
    && unzip /tmp/tflint-ruleset-aws.zip -d ~/.tflint.d/plugins/github.com/terraform-linters/tflint-ruleset-aws/${TFLINT_AWS_VERSION}/ \
    && rm /tmp/tflint-ruleset-aws.zip \
    && mkdir -p  ~/.tflint.d/plugins/github.com/terraform-linters/tflint-ruleset-google/${TFLINT_GOOGLE_VERSION}/ \
    && wget -O /tmp/tflint-ruleset-google.zip https://github.com/terraform-linters/tflint-ruleset-google/releases/download/v${TFLINT_GOOGLE_VERSION}/tflint-ruleset-google_linux_amd64.zip \
    && unzip /tmp/tflint-ruleset-google.zip  -d ~/.tflint.d/plugins/github.com/terraform-linters/tflint-ruleset-google/${TFLINT_GOOGLE_VERSION}/ \
    && rm /tmp/tflint-ruleset-google.zip
```

## tflintの設定

`.tflint.chl` で ルールの設定などをします。

+ `.tflint.chl` は $HOME下もしくはカレントディレクトリにおくとoptionを指定する必要がなく読み込まれます。

私のチームでは

```

├── terraform                
│   ├── aws                
│   │   ├── ap-northeast-1    
│   │   │   ├── dev                                                                                                                                                                                                                                                             
│   │   │   │   ├── main.tf
│   │   │   └── prd
│   │   │       ├── main.tf
....
...
│   │   ├── modules
│   │   │   ├── hogehoge
│   └── google
│       ├── modules
│       │   ├── hogehoge
│   │   └── shared
│   │       ├── dev
│       │       ├── main.tf
│   │   │   └── prd
│   │   │       ├── main.tf
....
...
```

このようなディレクトリ構成でterraformを管理しており、複数のクラウド環境へ `apply` を実施しているため
下記のようにクラウドごとの `.tflint.chl` を作成しそれを `--config` で渡して実行するようにしています。

```.aws_tflint.chl
plugin "aws" {
    enabled = true
    version = "0.12.0"
    source  = "github.com/terraform-linters/tflint-ruleset-aws"
    deep_check = true
}


config {
    module = true
}

rule "terraform_required_version" {
  enabled = true
}

rule "terraform_unused_declarations" {
  enabled = true
}

rule "terraform_unused_required_providers" {
  enabled = true
}
```

```gcp_tflint.chl
plugin "google" {
    enabled = true
    version = "0.15.0"
    source  = "github.com/terraform-linters/tflint-ruleset-google"
}

config {
    module = true
}

rule "terraform_required_version" {
  enabled = true
}

rule "terraform_unused_declarations" {
  enabled = true
}

rule "terraform_unused_required_providers" {
  enabled = true
}

```

$HOMEに `.tflint.chl` を設置して、実行でも良いですが、その場合 各クラウドの設定の tflintの `deep_check=true` をした場合
awsのリソースのlinterなのにgcpのクレデンシャルが必要になってしまいます。(deep_checkのフラグをあげた状態で `tflint` を実行すると apiへの接続などを確認する際にクレデンシャルを使用するためだと思われます。ルールの使用をdisableにすれば回避できるかもしれません)

+ deep_checkは外部のAPI(aws/gcpなど)を使用して厳密なチェックを行うようにするフラグ
+ ルールの定義などはDocumentを確認してください。
[defaultルール](https://github.com/terraform-linters/tflint/blob/master/docs/rules/README.md)
[aws pluginのルール](https://github.com/terraform-linters/tflint-ruleset-aws/blob/master/docs/rules/README.md)
[gcp pluginのルール](https://github.com/terraform-linters/tflint-ruleset-google/blob/master/docs/rules/README.md)

## 実行

.tflint.hclの準備ができたら実行します。
それぞれのカレントディレクトリに移動し
`tflint --config .aws_tflint.chl` や `tflint --config .gcp_tflint.chl` を実行します。
circle ci上でも同様です

# まとめ
terraformをupgradeすることで `moved block` などの新機能を使ってIaCを定義することができ
linterを導入することで、ルールを強制しterraformの管理コストなどをある程度軽減できるようになりました。

DMMではビッグデータ基盤関連の運用を行っており、
様々リソースを活用したプロダクト開発も行っています。
中途採用などもおこなっておりますので、興味のある方は、一度弊社HPなどから
カジュアル面談など、是非ご応募ください。