TerraformでKubernetesのリソース(ConfigMapやSecretなど)を管理する


TerraformはAWSなどのIaaS上でインフラを構築、管理するためのツールとしてよく使われてます。
一方、公式プラグインとして[Kubernetesのリソースを管理するためのProvider](https://www.terraform.io/docs/providers/kubernetes/)が提供されており、これを使ってKubernetesのリソースを管理すると結構便利だったので紹介します。

kubectlでリソースを直接いじるのに比べると、

* `terraform plan`でDryRunできる
* その際、もちろん既存のリソースとの差分を見ることができる
* リソース同士の参照(Docker RegistryのSecretをPodから見るとか)をコード上で表現でき、参照が壊れてると実行前にエラーにできる
* 変数や関数、モジュールを使用して定義を共通化できる
* `terraform destroy`で全部なかったことにできる

あたりがメリットでしょうか。

では、以下Mac上にインストールした`minikube`にローカルのterraformからアクセスするというローカルに閉じた環境で、実際にKubernetesのリソースを管理する様をつらつらと書いていきます。

この記事で使用したterraformのプロジェクトは、以下にPushしているので併せてご参照ください。

https://github.com/literalice/terraform-kubernetes-demo

`minikube`のインストールは超絶簡単なので、以下を参考にインストールしてみていただければと思います。

https://github.com/kubernetes/minikube

## Kubernetes Providerの設定
terraformからKubernetesのクラスタを設定するために、本来は以下を参考に、apiserverのURLやユーザー名などの認証情報を設定する必要があります。

https://www.terraform.io/docs/providers/kubernetes/

ただ、`minikube`は`.kube/config`に認証情報を書き込み、また`terraform`のKubernetes Providerはデフォルトで`.kube/config`を見るので、`minikube`環境にローカルからアクセスする場合は単に宣言だけで大丈夫です。

ここでは、`terraform-kubernetes-demo`というディレクトリを作成して、以下のファイルを作成することにします。


```tf
provider "kubernetes" {}
```

`terraform init`を実行することでKubernetes Providerがダウンロードされ、`kubernetes-provider-demo`ディレクトリに設定されます。

```
Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "kubernetes" (1.0.0)...
```

## ConfigMapをTerraformで管理する

まず、ConfigMapを管理してみます。現時点のKubernetes Providerだと、このConfigMapとSecretの管理が一番便利に感じるところです。

tfファイルは、以下のようになります。

```tf
resource "kubernetes_config_map" "infra" {
  metadata {
    name = "infra-env"
  }

  data {
    db_host = "dbhost:5432"
    cache_host = "cache:6379"
  }
}
```

さて、上記ファイルを適当なファイル名で作成し、`terraform plan`でDry Runしてみます。
すると、以下のように作成されようとしているConfigMapが表示されます。

```
------------------------------------------------------------------------

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:

  + kubernetes_config_map.infra
      id:                          <computed>
      data.%:                      "2"
      data.cache_host:             "cache:6379"
      data.db_host:                "dbhost:5432"
      metadata.#:                  "1"
      metadata.0.generation:       <computed>
      metadata.0.name:             "infra-env"
      metadata.0.namespace:        "default"
      metadata.0.resource_version: <computed>
      metadata.0.self_link:        <computed>
      metadata.0.uid:              <computed>


Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

```

実際に作成する前に内容を確認できるのがいいですね。

`terraform apply`とすると、実際にkubernetesクラスタにこのConfigMapが作成されます。

```
terraform apply

# kubernetes_config_map.infra: Creating...
#   data.%:                      "" => "2"
#   data.cache_host:             "" => "cache:6379"
#   data.db_host:                "" => "dbhost:5432"
#   metadata.#:                  "" => "1"
#   metadata.0.generation:       "" => "<computed>"
#   metadata.0.name:             "" => "infra-env"
#   metadata.0.namespace:        "" => "default"
#   metadata.0.resource_version: "" => "<computed>"
#   metadata.0.self_link:        "" => "<computed>"
#   metadata.0.uid:              "" => "<computed>"
# kubernetes_config_map.infra: Creation complete after # 0s (ID: default/infra-env)

# Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

kubectl describe cm infra-env

# Name:         infra-env
# Namespace:    default
# Labels:       <none>
# Annotations:  <none>

# Data
# ====
# cache_host:
# ----
# cache:6379
# db_host:
# ----
# dbhost:5432
# Events:  <none>
```

さらに、以下のようにConfigMapを変更してみます。

```tf
resource "kubernetes_config_map" "infra" {
  metadata {
    name = "infra-env"
  }

  data {
    db_host = "dbhost2:5432" # 変更する
    cache_host = "cache:6379"
  }
}
```

ここで`terraform plan`を行うと、AWS Providerのような他Provider同様、差分が表示されます。

```
------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  ~ kubernetes_config_map.infra
      data.db_host: "dbhost:5432" => "dbhost2:5432"


Plan: 0 to add, 1 to change, 0 to destroy.

------------------------------------------------------------------------
```

このように、実際に変更される前に、どのようにConfigMapが変更されるのか確認できます。
差分OKであれば、 `terraform apply` で差分を適用します。

```
terraform apply

# kubernetes_config_map.infra: Refreshing state... (ID: default/infra-env)
# kubernetes_config_map.infra: Modifying... (ID: default/infra-env)
#   data.db_host: "dbhost:5432" => "dbhost2:5432"
# kubernetes_config_map.infra: Modifications complete # after 0s (ID: default/infra-env)
```

## SecretをTerraformで管理する

ConfigMap同様、Secretもterraformで管理できます。kubectlで作るのに比べてbase64エンコードもしてくれたりで直感的にSecretを作成できます。

まず、tfファイルを作成します。

```tf
resource "kubernetes_secret" "basic_auth" {
  metadata {
    name = "basic-auth"
  }

  data {
    username = "admin"
    password = "Admin"
  }

  type = "kubernetes.io/basic-auth"
}
```

`terraform apply`を実行すると、上記内容でSecretが作られます。base64でエンコードする必要もありません。

```
kubernetes_secret.basic_auth: Creating...
  data.%:                      "" => "2"
  data.password:               "<sensitive>" => "<sensitive>"
  data.username:               "<sensitive>" => "<sensitive>"
  metadata.#:                  "" => "1"
  metadata.0.generation:       "" => "<computed>"
  metadata.0.name:             "" => "basic-auth"
  metadata.0.namespace:        "" => "default"
  metadata.0.resource_version: "" => "<computed>"
  metadata.0.self_link:        "" => "<computed>"
  metadata.0.uid:              "" => "<computed>"
  type:                        "" => "kubernetes.io/basic-auth"
kubernetes_secret.basic_auth: Creation complete after 0s (ID: default/basic-auth)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

では、少しこのtfファイルを変更してみます。ついでに、変数も使ってみます。

```tf
variable "basicPassword" {
}

resource "kubernetes_secret" "basic_auth" {
  metadata {
    name = "basic-auth"
  }

  data {
    username = "admin"
    password = "${var.basicPassword}"
  }

  type = "kubernetes.io/basic-auth"
}
```

```
TF_VAR_basicPassword=aaa terraform plan

# An execution plan has been generated and is shown below.
# Resource actions are indicated with the following symbols:
#   ~ update in-place

# Terraform will perform the following actions:

#   ~ kubernetes_secret.basic_auth
#       data.password: <sensitive> => <sensitive> (attribute changed)


# Plan: 0 to add, 1 to change, 0 to destroy.
```

このように、terraformであれば変数や参照、関数が利用できるので、構成を共有するのが楽そうですね。

Kubernetes ProviderのSecretリソースは、ファイルやテンプレートからSecretを作成することもできるので、Docker Registryの認証情報をKubernetesに設定するときなどに便利です。

https://www.terraform.io/docs/providers/kubernetes/r/secret.html

## 後片付け

`terraform destroy`で、上記で作成したリソースを全て削除できます。

```
terraform destroy


# kubernetes_config_map.infra: Refreshing state... (ID: default/infra-env)
# kubernetes_secret.basic_auth: Refreshing state... (ID: default/basic-auth)

# An execution plan has been generated and is shown below.
# Resource actions are indicated with the following symbols:
#   - destroy

# Terraform will perform the following actions:

#   - kubernetes_config_map.infra

#   - kubernetes_secret.basic_auth


# Plan: 0 to add, 0 to change, 2 to destroy.

# Do you really want to destroy?
#   Terraform will destroy all your managed infrastructure, as shown above.
#   There is no undo. Only 'yes' will be accepted to confirm.

#   Enter a value: yes

# kubernetes_config_map.infra: Destroying... (ID: default/infra-env)
# kubernetes_secret.basic_auth: Destroying... (ID: default/basic-auth)
# kubernetes_config_map.infra: Destruction complete after 0s
# kubernetes_secret.basic_auth: Destruction complete after 0s

# Destroy complete! Resources: 2 destroyed.
```

## まとめ
Kubernetes Providerでは、その他にもServiceやPod、NamespaceやPVCなど、様々なリソースを管理できます(現時点では`Pod`はあっても`Deployment`がないのが痛い)。

https://www.terraform.io/docs/providers/kubernetes/

`terraform`は差分が発生したことをコマンドの[exit codeで知る](https://www.terraform.io/docs/commands/plan.html#detailed-exitcode)ことができたり、べき等性のあるコマンドでリソースを管理できたりとCIに組み込むのにとても便利な作りなので、インフラをJenkinsなどで管理しているのであれば一度Kubernetes Providerを試してみてはいかがでしょうか。