KubernetesのConfigMapを試してみる

## ConfigMapとは

ConfigMapとはKubernetes1.2で導入された、設定情報を扱うためのリソースです。

- Podに対して複数の設定情報を以下の形で渡すことができる
    - 環境変数
    - コンテナの引数
    - ファイル(Volume)
- Volumeとして使った場合、動的に変更が反映される :tada:
- Volumeの場合ファイルはシンボリックリンクになっている (後述)

似たような機能のSecretは動的に反映はされなかったので、動的な変更の反映が個人的には嬉しいです。(Issueはあがっていて対応はされそうです。[Secret volume should refresh when secrets are updated #18372](https://github.com/kubernetes/kubernetes/issues/18372))

参考:

- 公式ドキュメント [Using ConfigMap](http://kubernetes.io/docs/user-guide/configmap/)
- Designドキュメント [Generic Configuration Object](https://github.com/kubernetes/kubernetes/blob/master/docs/design/configmap.md)

## ConfigMapを作る

ConfigMapには複数の作り方があります。

- ``kubectl create -f config.conf`` 方式
   - 設定ファイル(yaml/json)を直接書く方式
- ``kubectl create configmap``コマンドを使う方法
  - ファイルやディレクトリ(``--from-file``オプション)を指定して、配置したい設定ファイルを指定する
  - 引数に直接設定情報を書く(``--from-literal``オプション)

ここでは設定ファイルを直接書く方式で試してみます。まず以下のような設定ファイルを書きます。

```yaml:example-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: example-config
data:
  # key, value形式で設定を記述する
  example.property.1: hello
  example.property.2: world
```

設定を反映させます。

```bash
$ kubectl create -f example-config.yaml
```

以下のようにConfigMapが作られました。

```bash
$ kubectl get configmap example-config -o yaml
apiVersion: v1
data:
  example.property.1: hello
  example.property.2: world
kind: ConfigMap
metadata:
  creationTimestamp: 2016-03-19T12:33:55Z
  name: example-config
  namespace: default
  resourceVersion: "1522"
  selfLink: /api/v1/namespaces/default/configmaps/example-config
  uid: d82ebdbc-edce-11e5-8c6d-08002701bf15
```

## PodからConfigMapのデータを使う

作ったConfigMapをPodから読んでみます。環境変数に設定する方法と、ファイルとしてマウントする(Volume)方法の2つの方式があります。ここではVolumeを使ってファイルとしてマウントしてみます。

```yaml:pod-using-config.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-using-config
spec:
  containers:
    - name: example-container
      image: busybox
      # あとからexecで操作するためsleepさせておく
      command: ["sleep", "3600"]
      # 定義したConfigMapのvolumeをマウントする
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config
  volumes:
    # ConfigMapのvolume定義
    - name: config-volume
      configMap:
        # 作ったConfigMapのname
        name: example-config
```

上記設定のPodをデプロイします。

```bash
$ kubectl create -f pod-using-config.yaml
```

Podのマウントした`/etc/config`以下にキー名のファイルが作られ値にアクセスすることが確認できます。

```bash
$ kubectl exec pod-using-config cat /etc/config/example.property.1
hello
$ kubectl exec pod-using-config cat /etc/config/example.property.2
world
```

### ConfigMapの変更の反映を確認する

ConfigMapをVolumeとして使った場合、ConfigMapの変更が動的にPodにも反映されます。

先ほど作成した``example-config``を``kubectl edit``を使って変更してみます。

```bash
$ kubectl edit configmap example-config
```

デフォルトではviが立ち上がるので、下記のようにconfigを書き換えます。

```yaml
...
data:
  # 値を書き換える
  example.property.1: hello-updated
  example.property.2: world-updated
```

値の変更を確認します。変更の反映に時間がかかる場合があります。(KubeletのSyncLoopのタイミングで反映されるようです。cf. [kubelet#syncLoopIteration](https://github.com/kubernetes/kubernetes/blob/4be9587b433dbb5c17aa5a1a7451edc0f5a29907/pkg/kubelet/kubelet.go#L2422))

```bash
$ kubectl exec pod-using-config cat /etc/config/example.property.1
hello-updated
$ kubectl exec pod-using-config cat /etc/config/example.property.2
world-updated
```

### Volumeの場合ファイルはシンボリックリンクになっている

VolumeとしてConfigMapを使った場合、設定ファイルはシンボリックリンクになっていました。複数の設定ファイルを一つ一つ新しい物に置き換えると全体でアトミックにならないため、ディレクトリのシンボリックリンクを切り替える方式が使われているようです。シンボリックリンクを見ないプログラムの場合は注意が必要だと思われます。

実際にPodにマウントされたファイルを見てみると以下の様になっています。

```bash
$ kubectl exec pod-using-config -- ls /etc/config -lra
total 8
lrwxrwxrwx    1 root     root            25 Mar 19 05:23 example.property.2 -> ..data/example.property.2
lrwxrwxrwx    1 root     root            25 Mar 19 05:23 example.property.1 -> ..data/example.property.1
lrwxrwxrwx    1 root     root            31 Mar 19 05:33 ..data -> ..3983_19_03_05_33_10.012371420
drwx------    2 root     root            80 Mar 19 05:33 ..3983_19_03_05_33_10.012371420
drwxr-xr-x    1 root     root          4096 Mar 19 05:23 ..
drwxrwxrwt    3 root     root           120 Mar 19 05:33 .
```

実際のアトミックな切り替え処理は[atomic_writer.go](https://github.com/kubernetes/kubernetes/blob/4be9587b433dbb5c17aa5a1a7451edc0f5a29907/pkg/volume/util/atomic_writer.go#L116)でやっており、以下が処理のイメージになります。

1. タイムスタンプを元に``..3983_19_03_05_33_10.012371420``のような名前でディレクトリを作り、各設定ファイルを書く
2. ``..data``というディレクトリ名で上のディレクトリにシンボリックリンクを貼る
3. マウント先のデータは``.data``の各設定ファイルに対してシンボリックリンクを貼る
4. 更新があった場合、タイムスタンプを元に新しいディレクトリに、各設定ファイルを書く
5. ``..data``のシンボリック先を新しいものに切り替えること
6. 設定ファイルすべてアトミックに切り替わる