逆引き Kubernetes オブジェクト

まえおき
----------------------------

Kubernetes を使い始めたのだが、公式のドキュメントが難解すぎたので目的別に整理した。

各ノードでコンテナを起動したい
--------------------------

**Daemon Set を使う。**
以下の例では、ホストの `/var/log/myapp` ディレクトリに出力された `myapp.log` ファイルを `logstash` コンテナで監視する。簡略化のため Logstash はファイルの中身をそのまま出力している。通常は Filter Plugin を併用し Output Plugin で Elasticsearch に送る。

- 図

```text:ホストのディレクトリをマウントするLogstashの例
+-----------------------------------+
| node                              |
|                     +----------+  |
|                     | logstash |  |
|                     |          |  |
|  /var/log/myapp <-mount-*/log  |  |
|                     +----------+  |
+-----------------------------------+
```

```yaml:sample-logstash.yml
kind: DaemonSet
apiVersion: extensions/v1beta1
metadata:
  name: logstash
spec:
  template:
    metadata:
      labels:
        app: daemon
    spec:
      volumes:
      - name: log
        hostPath:
          path: /var/log/myapp
      containers:
      - name: logstash # ホストの /var/log/myapp/myapp.log を出力するだけのコンテナ
        image: logstash
        args:
        - -e
        - |
          input {
            file { path => '/log/myapp.log' }
          }
          output {
            stdout { codec => rubydebug }
          }
        volumeMounts:
        - name: log # ホストの /var/log/myapp ディレクトリに当たる
          mountPath: /log
```


コンテナからコンテナに問い合わせたい
--------------------------

### コンテナからコンテナに問い合わせたい（多対一の場合）

**Service を使う。**
Pod は Service に対応させられた Pod の名前解決ができることを利用する。
以下の例では `logstash` コンテナが `elasticsearch` コンテナに`/log/myapp.log` パスにあるログを送信する。簡略化のため `/log/myapp.log` には 30 秒おきに `hello` と書き込むコンテナを立てている。通常は `/log/myapp.log` パスにログを出力するようなアプリケーションを立てる。
Service を使うことで `logstash` コンテナが `elasticsearch` という名前で `elasticsearch` コンテナに問い合わせられるようになっている。

```text:多コンテナ対1コンテナで問い合わせる例
+---------------+     +---------------+      +---------------+
| [ReplicaSet]  |     | [Service]     |      | [Pod]         |
|  +----------+ |     | elasticsearch |      | elasticsearch |
|  | [Pod]    | |     |               |      |               |
|  | logstash *----+--*name resolution*----->*:9200/tcp      |
|  +----------+ |  |  +---------------+      +---------------+
|  | [Pod]    | |  |
|  | logstash *----+
|  +----------+ |  |
|  | [Pod]    | |  |
|  | logstash *----+
|  +----------+ |
+---------------+
```

```yaml:sample-elasticsearch.yml
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
spec:
  selector:
    app: elasticsearch
  ports:
  - port: 9200
    targetPort: 9200
---
kind: Pod
apiVersion: v1
metadata:
  name: elasticsearch
  labels:
    app: elasticsearch
spec:
  containers:
  - name: elasticsearch
    image: elasticsearch:2.4.1 # 5.0 だと起動に失敗したので前バージョン
    ports:
    - containerPort: 9200
---
kind: ReplicaSet
apiVersion: extensions/v1beta1
metadata:
  name: logstash
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: logstash
    spec:
      volumes:
      - name: log
      containers:
      - name: logstash # /log/myapp.log のログを elasticsearch に送るだけのコンテナ
        image: logstash:2.4.0-1 # Elasticsearch に合わせたバージョン
        args:
        - -e
        - |
          input {
            file { path => '/log/myapp.log' }
          }
          output {
            stdout {
              codec => rubydebug
            }
            elasticsearch {
              hosts => ['elasticsearch']
            }
          }
        volumeMounts:
        - name: log
          mountPath: /log
      - name: hello # /log/myapp.log に hello と30秒毎に書き込むだけのコンテナ
        image: centos
        args:
        - bash
        - -c
        - |
          while true; do
            echo hello >> /log/myapp.log
            sleep 30
          done
        volumeMounts:
        - name: log
          mountPath: /log
 ```

なお、Elasticsearch に Logstash から送信されたログを確認するためには、

```
kubectl run -it --rm centos --image centos
```

として`bash`を起動したコンテナの中で、

```
curl elasticsearch:9200/logstash-*/_search?pretty
```

と実行すればよい。コンテナはそのまま`exit`で`bash`を終了すれば自動的に削除される。

### コンテナからコンテナに問い合わせたい（一対一の場合）

Service を使ってもよいが **Pod を使う**と単純に書ける。
Pod の中の他のコンテナは `localhost` で問い合わせられる。
以下の例では Elasticsearch と Kibana をセットで立てる。

```yaml:sample-ek.yml
kind: Pod
apiVersion: v1
metadata:
  name: ek
  labels:
    app: ek
spec:
  containers:
  - name: elasticsearch
    image: elasticsearch:2.4.1 # 5.0 だと起動に失敗したので前バージョン
  - name: kibana
    image: kibana:4.6.2 # Elasticsearch に合わせたバージョン
    env:
    - name: ELASTICSEARCH_URL
      value: http://localhost:9200 # elasticsearch コンテナに繋がる
    ports:
    - containerPort: 5601
```

クラスタの外からコンテナにアクセスしたい
---------------------------------

### クラスタのノードが1台しかない場合

**NodePort タイプのServiceを使う。**
NodePort タイプはコンテナが起動したノードの
IPアドレスでアクセスできるようになる。
以下の例では、前節で作った Kibana に
`http://(ノードのIPアドレス):30601` でアクセスできるようになる。

- もちろん、ファイアウォールやセキュリティグループなどが間にあれば
  接続を許可する設定が必要になる。
- ちなみに、ポート番号が30601になっているのは、
  ノードで使用可能なポート番号が
  デフォルトでは30000～32767に設定されているためである。

```yaml:sample-ek-svc.yml
kind: Service
apiVersion: v1
metadata:
  name: ek-svc
spec:
  ports:
  - port: 5601
    targetPort: 5601
    nodePort: 30601
  selector:
    app: ek
  type: NodePort
```

### クラスタのノードが複数台ある場合

Kubernetes の外部にロードバランサを調達する。
Google Container Engine のようなクラウドサービスでは、
**LoadBalancer タイプの Service を使う**ことで
ロードバランサの作成をサポートしている。
以下の例では、前節で作った Kibana に
 `http://{ロードバランサのIPアドレス}:5601` でアクセスできるようになる。

```yaml:sample-ek-lb.yml
kind: Service
apiVersion: v1
metadata:
  name: ek-lb
spec:
  ports:
  - port: 5601
    targetPort: 5601
  selector:
    app: ek
  type: LoadBalancer
```

コンテナの数を増やしたい
--------------------------

**Replica Set や Job を使う。**
Replica Set は継続的に実行されるようなコンテナを使用する。
Job は実行の完了を想定しているようなコンテナを使用する。

### Replica Set

Replica Set は稼働中のコンテナが指定した数になるまで立て続ける。
デーモンのような、処理が終了しないことが想定されている場合に使う。

### Job

Job は `exit 0` で終了したコンテナの数が指定回数を満たすまで
コンテナを新規に立て続ける。
バッチのような、デーモンとは違って、
処理が終了することが想定されている場合に使う。

Dockerイメージをプルするときに認証したい
---------------------------

**Secret を使う。**

[公式のドキュメント](http://kubernetes.io/docs/user-guide/images/#specifying-imagepullsecrets-on-a-pod) にその手順が書かれている通り、
まずは Secret を作成し、
それから Secret を使用してイメージのプルを行うように設定すればよい。

次のコマンドを使って Secret を作成できる。

```
kubectl create secret docker-registry
```

たとえば、

```
kubectl create secret docker-registry my-secret --docker-username=myname --docker-password=mypass --docker-email=myname@example.com
```

とすれば `my-secret` という名前で Secret を作成できる。
Secret の中には Base64 エンコードされた上記の情報が含まれるようになるので、
取り扱いには十分に注意しなければならない。

次に Secret を使用するように設定する。

イメージをプルするときにデフォルトで上記の認証情報を使用したい場合は、
Service Account に Secret を設定する。
Service Account は Namespace ごとに作成されており、
Namespace に属するオブジェクトがデフォルトで使用する Service Account があるので、
そこに設定を追加する。

デフォルトの Namespace の デフォルト Service Account に設定する場合は、

```
kubectl edit sa default
```

とし、(sa は serviceaccount のエイリアス)
エディタが起動するので、

```
imagePullSecrets:
- name: my-secret
```

を末尾に追加し、保存してエディタを終了する。

Pod ごとに使用する Secret を分けたい場合は、
以下の例のように Pod に Secret を設定する。

```
kind: Pod
apiVersion: v1
metadata:
  name: sample-foo
spec:
  containers:
  - name: sample-foo
    image: httpd
  imagePullSecrets:
  - name: my-secret
```

コンテナに番号を付けて管理したい
------------------------------

**Stateful Set を使う。**

コンテナが自身の番号を知るには、ホスト名から切り出す。
ホスト名は `{Stateful Set 名}-{番号}` となっている。