kubernetesで動かすソフトウェアの設定をConfigMapで記述する

## まとめ

nginx.confなどの設定ファイルをkubernetesのConfigMapで記述し、Volumeとしてマウントすることが出来る。  
ドキュメントはこの辺り。

- [Configure a Pod to Use a ConfigMap | Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#add-configmap-data-to-a-volume)
- [Volumes | Kubernetes](https://kubernetes.io/docs/concepts/storage/volumes/#configmap)

実際のyamlファイルは[Gist](https://gist.github.com/petitviolet/d36f33d145d0bbf4b54eb187b79d0244)にある。

## 何が課題か

kubernetesで動かすDockerコンテナ内にどうやって各アプリケーションが使用する設定ファイルを差し込むか、という話。
例えばnginx.confなどの設定ファイルをどうやって管理するか。
いくつか方法がある。

- Dockerイメージの中に入れておく
  - `ADD`した状態で`docker build`しておく
- ファイルにしておいてコンテナにVolumeとしてマウントする
  - デプロイするホストのディレクトリをマウントする
  - 永続ディスクを作成してそこに配置しておいてマウントする
- どこかのストレージに置いておいてダウンロードする

あたりが一般的な気がする(要出典)。

しかし、それぞれ難点がある

- Dockerイメージの中に入れておく
  - 例えばNginxの公式イメージをそのまま使えない
  - `docker build`, `docker push`して使う必要があって面倒
- ファイルにしておいてコンテナにVolumeとしてマウントする
  - ファイルを置くNodeの状態を管理しないといけない
  - 設定を変更するたびにストレージを変更するのは面倒
- どこかのストレージに置いておいてダウンロードする
  - 権限とか考えないといけなくて面倒
  
と、どれを選んでも一長一短という感じ。

## そこでConfigMap

kubernetesにはConfigMapというものがあり、簡単にいうとkey-valueで値を持てるもの。
環境変数にイメージは近いが、上に書いたような設定ファイルも持つことが出来る。
さらに、ConfigMapはvolumeとしてもマウントできるようになっているのがポイント。  
[ドキュメント](https://kubernetes.io/docs/concepts/storage/volumes/#configmap)

### 設定ファイルをConfigMapで定義する

yamlでは改行を含む文字列を定義できる。
[YAMLの基本について - TASK NOTES](http://www.task-notes.com/entry/20150922/1442890800)

pipe(`|`)を使うと改行を含んでいても一つのスカラ値として扱うことが出来る。
こんな感じ。

```yaml
message: |
  hello
  world
```

`|+`とか`|-`とか`>`もあって少しずつ違うが`|`で大体は問題ないはず。

/etc/nginx/nginx.confと/etc/nginx/virtualhost/virtualhost.confをConfigMapで定義してみる。
`data`項目の下に、`nginx.conf`と`virtualhost.conf`としてそれぞれ記述する。

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  nginx.conf: |
    user nginx;
    worker_processes  3;
    error_log  /var/log/nginx/error.log;
    events {
      worker_connections  10240;
    }
    http {
      log_format  main
              'remote_addr:$remote_addr\t'
              'time_local:$time_local\t'
              'method:$request_method\t'
              'uri:$request_uri\t'
              'host:$host\t'
              'status:$status\t'
              'bytes_sent:$body_bytes_sent\t'
              'referer:$http_referer\t'
              'useragent:$http_user_agent\t'
              'forwardedfor:$http_x_forwarded_for\t'
              'request_time:$request_time';

      access_log	/var/log/nginx/access.log main;

      server {
          listen       80;
          server_name  _;

          location / {
              root   html;
              index  index.html index.htm;
          }
      }
      include /etc/nginx/virtualhost/virtualhost.conf;
    }
  virtualhost.conf: |
    upstream app {
      server localhost:8080; # localhost:8080で受け付けるWebアプリがあるとして
      keepalive 1024;
    }

    server {
      listen 80 default_server;
      server_name _;
      root /usr/local/app;

      access_log /var/log/nginx/app.access_log main;
      error_log /var/log/nginx/app.error_log;

      location / {
        proxy_pass http://app/;
        proxy_http_version 1.1;
      }
    }
```

こうやって定義したCofigMapを公式のnginxイメージに適用してみる。

```yaml
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx # /etc/nginxにvolumesのnginx-confをmountする
          readOnly: true
          name: nginx-conf
        - mountPath: /var/log/nginx
          name: log
      volumes:
      - name: nginx-conf # volumeMountsで/etc/nginxにmountするやつ
        configMap: 
          name: nginx-conf # ConfigMapのnginx-confを/etc/nginx以下に配置する
          items:
            - key: nginx.conf # nginx-confのkey
              path: nginx.conf # nginx.confというファイル名
            - key: virtualhost.conf
              path: virtualhost/virtualhost.conf # ディレクトリを掘ることも可能
      - name: log
        emptyDir: {}

---
apiVersion: v1 
kind: Service # nginxにアクセスする用のservice
metadata:
  name: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx
```

### デプロイ,動作確認

実際にkubernetesにデプロイして、ConfigMapがファイルとして存在しているかどうかを確認してみる。

```shell-session
$ kubectl create namespace nginx-sample
namespace "nginx-sample" created

$ kubectl --namespace=nginx-sample apply -f ./nginx-sample.yaml
configmap "nginx-conf" created
deployment "nginx" created
service "nginx" created

$ kubectl --namespace=nginx-sample get po
NAME                    READY     STATUS    RESTARTS   AGE
nginx-d78988cbc-js2fx   1/1       Running   0          10s
```

ここまでで起動できたので、`kubectl exec`でファイルを見てみると正しく配置されていることが分かる。

```shell-session
$ kubectl exec  --namespace=nginx-sample -it nginx-d78988cbc-js2fx -c nginx -- ls -lhR /etc/nginx/
/etc/nginx/:
total 4.0K
lrwxrwxrwx 1 root root   17 Mar 11 01:38 nginx.conf -> ..data/nginx.conf
drwxr-xr-x 2 root root 4.0K Mar 11 01:38 virtualhost

/etc/nginx/virtualhost:
total 0
lrwxrwxrwx 1 root root 38 Mar 11 01:38 virtualhost.conf -> ../..data/virtualhost/virtualhost.conf
```

続けてnginx.confの設定が正しく反映されていることを確認してみる。

```
$ curl $(minikube service --namespace=nginx-sample nginx --url) -I # localhost:8080で動くサービスがないのでエラーになるけど気にしない
HTTP/1.1 502 Bad Gateway
Server: nginx/1.13.9
Date: Sun, 18 Mar 2018 08:07:08 GMT
Content-Type: text/html
Content-Length: 173
Connection: keep-alive

$ kubectl exec --namespace=nginx-sample -it nginx-d78988cbc-js2fx -- cat /var/log/nginx/app.access_log  # logの中見を見る
remote_addr:172.17.0.1  time_local:18/Mar/2018:08:07:08 +0000   method:HEAD     uri:/   host:192.168.99.100     status:502      bytes_sent:0    referer:-       useragent:curl/7.54.0     forwardedfor:-  request_time:0.000
```

`log_format`で指定したようなログになっていることが確認できた。