kubernetesクラスタを手軽に構築して試す

Kubernetesを試す方法はKubernetesリポジトリの[Getting Started Guide](https://github.com/GoogleCloudPlatform/kubernetes/tree/master/docs/getting-started-guides)にGCEやAWSなどのCloudを使った方法やVagrantを使った方法があります。
今回は手元で試したかったので、いくつか試したなかで一番構築が簡単でメンテナンスされている[Multi-node cluster using cloud-config and Vagrant](https://github.com/pires/kubernetes-vagrant-coreos-cluster)を使います。

前提としてVirtualBoxとVagrantがインストールされてある必要があります。

## クラスタの構築

masterとnodeを2台構築します

```
$ git clone git://github.com/pires/kubernetes-vagrant-coreos-cluster.git
$ cd kubernetes-vagrant-coreos-cluster
$ vagrant up master node-01 node-02
```

必須ではないですが、今後の操作が簡単になるので`vagrant ssh-config`を設定しておくと良いでしょう。

```
$ vagrant ssh-config >> ~/.ssh/config
```

kubernetesを操作するためのCLIとして`kubectl`をインストールします。ちなみに以前存在していた`kubecfg`はもう使われていません。

```
$ ./kubLocalSetup install
$ /usr/local/bin/kubectl -h
```

kubectlで操作するmasterの向き先を設定します。etcdとfleetの設定もしてくれます。

```
$ $(./kubLocalSetup shellinit)
$ env | egrep '(KUBE|FLEET|ETCD)'
ETCDCTL_PEERS=http://172.17.8.101:4001
FLEETCTL_ENDPOINT=http://172.17.8.101:4001
KUBERNETES_MASTER=http://172.17.8.101:8080
```

`kubectl version`でクライアントとmaster(apiserver)のバージョンを確認。`get nodes`でnode一覧が確認できれば完了。

```
$ kubectl version
Client Version: version.Info{Major:"0", Minor:"11+", GitVersion:"v0.11.0-dirty", GitCommit:"18ddff0eb6bc069b41832ce47d19b5b89cfb26e3", GitTreeState:"dirty"}
Server Version: version.Info{Major:"0", Minor:"11+", GitVersion:"v0.11.0-dirty", GitCommit:"18ddff0eb6bc069b41832ce47d19b5b89cfb26e3", GitTreeState:"dirty"}

$ kubectl get nodes
NAME                LABELS              STATUS
172.17.8.102        <none>              Ready
172.17.8.103        <none>              Ready
```

## サンプルを試す

[kubernetesリポジトリ](https://github.com/GoogleCloudPlatform/kubernetes)にある[サンプル](https://github.com/GoogleCloudPlatform/kubernetes/tree/master/examples)を試します。一番標準的な[guestbook](https://github.com/GoogleCloudPlatform/kubernetes/tree/master/examples/guestbook)を試しましょう。

基本的にはREADMEに沿ってコマンドを実行していくだけです。READMEに書いてあるコマンドで`cluster/kubectl.sh`と書いてあるのは`kubectl`と読み替えて下さい。`cluster/kubectl.sh`は構築した環境ごとに向き先を切り替えてくれる`kubectl`のラッパースクリプトです。

### 前準備

v0.11.0のタグで試さないとうまく動作しません。
**この例ではHEADで試していたので一部Podの割り当てがおかしいです**

```
$ git clone git://github.com/GoogleCloudPlatform/kubernetes.git
$ cd kubernetes
$ git checkout refs/tags/v0.11.0
```

### step 1: Redis MasterのRCを作成

まずはredis masterを構築。replicaが1のReplicationControllerを作成します。

```
$ kubectl create -f examples/guestbook/redis-master-controller.json
$ kubectl get rc
CONTROLLER                CONTAINER(S)        IMAGE(S)            SELECTOR            REPLICAS
redis-master-controller   redis-master        dockerfile/redis    name=redis-master   1
```

同時にPodも作成されます。STATUSが`Running`になるまで待ちましょう。`Peding`中はdocker pull中です。

`docker ps`すると3つ動作していますが、それぞれredis、infra container, [cadvisor](https://github.com/google/cadvisor)です。
infra containerはPod毎に必ず1つ作られます。Pod内のコンテナはこれを通してネットワークがつながっています。cadvisorはkubeletと一緒に起動する(ようになった)もので、コンテナのmetrics情報を収集しています。

```
$ kubectl get pods
POD                             IP                  CONTAINER(S)        IMAGE(S)            HOST                        LABELS                        STATUS
redis-master-controller-fplln   10.244.45.3         redis-master        dockerfile/redis    172.17.8.102/172.17.8.102   app=redis,name=redis-master   Pending
```

Runningになったらdockerが動いているか確認しましょう。今回はIPが102なので`node-01`になります。

```
$ ssh node-01 'docker images'
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
google/cadvisor     0.10.1              6a46ed29e869        13 hours ago        18.03 MB
google/cadvisor     latest              6a46ed29e869        13 hours ago        18.03 MB
dockerfile/redis    latest              8d898e6eef25        7 days ago          418.9 MB
kubernetes/pause    go                  6c4579af347b        7 months ago        239.8 kB
kubernetes/pause    latest              6c4579af347b        7 months ago        239.8 kB

$ ssh node-01 'docker ps'
CONTAINER ID        IMAGE                     COMMAND                CREATED             STATUS              PORTS                              NAMES
7f74b7245c65        dockerfile/redis:latest   "redis-server /etc/r   2 minutes ago       Up 2 minutes                                           k8s_redis-master.952cf9b6_redis-master-controller-fplln.default.api_f8f28d8a-bf17-11e4-b8b6-08002787f925_264d797a   
5b4878091382        kubernetes/pause:go       "/pause"               6 minutes ago       Up 6 minutes                                           k8s_POD.5efe987_redis-master-controller-fplln.default.api_f8f28d8a-bf17-11e4-b8b6-08002787f925_86f143e5             
ffef368b3ed1        google/cadvisor:0.10.1    "/usr/bin/cadvisor -   24 minutes ago      Up 24 minutes       8080/tcp, 0.0.0.0:4194->4194/tcp   cadvisor         
```

### step 2: Redis MasterのServiceを作成

redis masterのServiceを作ります。Serviceを作るとServiceの配下にあるPodに対してのエンドポイントが提供されます。どのようなエンドポイントが提供されるかは設定によって変わります。
デフォルトではPod内の環境変数としてアクセス先のPodのIPが渡されるようになります(次のstepで確認)。

```
$ kubectl create -f examples/guestbook/redis-master-service.json
$ kubectl get se
NAME                LABELS                                    SELECTOR            IP                  PORT
kubernetes          component=apiserver,provider=kubernetes   <none>              10.100.0.2          443
kubernetes-ro       component=apiserver,provider=kubernetes   <none>              10.100.0.1          80
redis-master        name=redis-master                         name=redis-master   10.100.194.126      6379
```

ここで表示されるIPはPodのIPとなるので、ルーティングの設定をしていない限りKubernetesクラスタの外からはアクセスできません。クラスタ内ではflannelによってルーティングが設定されているので別のノードからPodのIPを指定してもアクセスできます。

### step 3: Redis Slave RCを作成

redis-masterに(Serviceのエンドポイント経由で)接続するredis-slaveをreplica数2で作成します。

```
$ kubectl create -f examples/guestbook/redis-slave-controller.json
$ kubectl get rc
CONTROLLER                CONTAINER(S)        IMAGE(S)                   SELECTOR            REPLICAS
redis-master-controller   redis-master        dockerfile/redis           name=redis-master   1
redis-slave-controller    redis-slave         brendanburns/redis-slave   name=redis-slave    2
````

Podを確認すると2つのPodができています。

```
$ kubectl get pods
POD                             IP                  CONTAINER(S)        IMAGE(S)                   HOST                        LABELS                                         STATUS
redis-master-controller-fplln   10.244.45.3         redis-master        dockerfile/redis           172.17.8.102/172.17.8.102   app=redis,name=redis-master                    Running
redis-slave-controller-gziey    10.244.59.3         redis-slave         brendanburns/redis-slave   172.17.8.103/172.17.8.103   app=redis,name=redis-slave,uses=redis-master   Pending
redis-slave-controller-oh43e    10.244.59.4         redis-slave         brendanburns/redis-slave   172.17.8.103/172.17.8.103   app=redis,name=redis-slave,uses=redis-master   Pending
```

Runningになったらnode-02に割り当てられたredis-slaveを’確認します。なぜか2台ともnode-02に割り当てられていますが。

```
$ ssh node-02 'docker ps' | grep redis-slave
5f2ccf61a68c        brendanburns/redis-slave:latest   "/bin/sh -c /run.sh"   About a minute ago   Up About a minute                                      k8s_redis-slave.725fc7a_redis-slave-controller-oh43e.default.api_780ac5b4-bf1d-11e4-b8b6-08002787f925_afc7fab2   
c9c404e5775c        brendanburns/redis-slave:latest   "/bin/sh -c /run.sh"   About a minute ago   Up About a minute                                      k8s_redis-slave.725fc7a_redis-slave-controller-gziey.default.api_7807bcb8-bf1d-11e4-b8b6-08002787f925_dfb33229   
5ba9a62ab028        kubernetes/pause:go               "/pause"               5 minutes ago        Up 5 minutes                                           k8s_POD.5efe987_redis-slave-controller-oh43e.default.api_780ac5b4-bf1d-11e4-b8b6-08002787f925_e3841b8f           
d4fc52a77b99        kubernetes/pause:go               "/pause"               5 minutes ago        Up 5 minutes                                           k8s_POD.5efe987_redis-slave-controller-gziey.default.api_7807bcb8-bf1d-11e4-b8b6-08002787f925_d1a853f7           
```

`docker inspect`でコンテナ内の環境変数を確認すると`REDIS_MASTER_*`という名前で先ほど設定したredis-masterのServiceのエンドポイントが提供されています。このようにServiceを作るとPodの中に環境変数として渡されます。

```
$ ssh node-02 'docker inspect 5f2ccf61a68c' | jq '.[].Config.Env'
[
  "KUBERNETES_PORT_443_TCP=tcp://10.100.0.2:443",
  "KUBERNETES_PORT_443_TCP_PORT=443",
  "KUBERNETES_PORT_443_TCP_ADDR=10.100.0.2",
  "KUBERNETES_RO_PORT_80_TCP_PROTO=tcp",
  "REDIS_MASTER_SERVICE_PORT=6379",
  "REDIS_MASTER_PORT=tcp://10.100.194.126:6379",
  "REDIS_MASTER_PORT_6379_TCP=tcp://10.100.194.126:6379",
  "REDIS_MASTER_PORT_6379_TCP_PORT=6379",
  "REDIS_MASTER_PORT_6379_TCP_ADDR=10.100.194.126",
  "KUBERNETES_SERVICE_HOST=10.100.0.2",
  "KUBERNETES_RO_SERVICE_HOST=10.100.0.1",
  "KUBERNETES_RO_PORT_80_TCP_PORT=80",
  "KUBERNETES_RO_PORT_80_TCP_ADDR=10.100.0.1",
  "KUBERNETES_SERVICE_PORT=443",
  "KUBERNETES_PORT=tcp://10.100.0.2:443",
  "KUBERNETES_RO_SERVICE_PORT=80",
  "KUBERNETES_RO_PORT=tcp://10.100.0.1:80",
  "KUBERNETES_PORT_443_TCP_PROTO=tcp",
  "KUBERNETES_RO_PORT_80_TCP=tcp://10.100.0.1:80",
  "REDIS_MASTER_SERVICE_HOST=10.100.194.126",
  "REDIS_MASTER_PORT_6379_TCP_PROTO=tcp",
  "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "HOME=/root"
]
```

### step 4: Redis SlaveのServiceを作成

redis slaveも同様Serviceを作ります。

```
$ kubectl create -f examples/guestbook/redis-slave-service.json
$ kubectl get services
NAME                LABELS                                    SELECTOR            IP                  PORT
kubernetes          component=apiserver,provider=kubernetes   <none>              10.100.0.2          443
kubernetes-ro       component=apiserver,provider=kubernetes   <none>              10.100.0.1          80
redis-master        name=redis-master                         name=redis-master   10.100.194.126      6379
redisslave          name=redis-slave                          name=redis-slave    10.100.159.170      6379
```

### step 5: フロントエンドのRCを作成

```
$ kubectl create -f examples/guestbook/frontend-controller.json 
frontend-controller

$ kubectl get rc
CONTROLLER                CONTAINER(S)        IMAGE(S)                                 SELECTOR            REPLICAS
frontend-controller       php-redis           kubernetes/example-guestbook-php-redis   name=frontend       3
redis-master-controller   redis-master        dockerfile/redis                         name=redis-master   1
redis-slave-controller    redis-slave         brendanburns/redis-slave                 name=redis-slave    2

$ kubectl get po --selector='app=frontend'
POD                         IP                  CONTAINER(S)        IMAGE(S)                                 HOST                        LABELS                                                     STATUS
frontend-controller-0133o   10.244.45.4         php-redis           kubernetes/example-guestbook-php-redis   172.17.8.102/172.17.8.102   app=frontend,name=frontend,uses=redis-slave,redis-master   Pending
frontend-controller-hh2gd   10.244.45.5         php-redis           kubernetes/example-guestbook-php-redis   172.17.8.102/172.17.8.102   app=frontend,name=frontend,uses=redis-slave,redis-master   Pending
frontend-controller-ls6k1   10.244.59.5         php-redis           kubernetes/example-guestbook-php-redis   172.17.8.103/172.17.8.103   app=frontend,name=frontend,uses=redis-slave,redis-master   Pending
```

フロントエンドのPodにはredis-masterとredis-slaveの両方のエンドポイントが設定されています。

```
 $ ssh node-01 'docker inspect 0160f3a9ae28' | jq '.[].Config.Env'
[
  "KUBERNETES_PORT_443_TCP_PROTO=tcp",
  "REDIS_MASTER_SERVICE_HOST=10.100.194.126",
  "REDIS_MASTER_PORT_6379_TCP_PROTO=tcp",
  "KUBERNETES_PORT_443_TCP=tcp://10.100.0.2:443",
  "KUBERNETES_PORT_443_TCP_PORT=443",
  "REDIS_MASTER_SERVICE_PORT=6379",
  "REDIS_MASTER_PORT=tcp://10.100.194.126:6379",
  "REDIS_MASTER_PORT_6379_TCP=tcp://10.100.194.126:6379",
  "REDIS_MASTER_PORT_6379_TCP_PORT=6379",
  "REDISSLAVE_PORT_6379_TCP_ADDR=10.100.159.170",
  "KUBERNETES_RO_PORT_80_TCP_PORT=80",
  "KUBERNETES_PORT=tcp://10.100.0.2:443",
  "KUBERNETES_RO_PORT=tcp://10.100.0.1:80",
  "KUBERNETES_RO_PORT_80_TCP=tcp://10.100.0.1:80",
  "REDISSLAVE_SERVICE_HOST=10.100.159.170",
  "REDISSLAVE_PORT_6379_TCP_PROTO=tcp",
  "KUBERNETES_PORT_443_TCP_ADDR=10.100.0.2",
  "KUBERNETES_RO_PORT_80_TCP_PROTO=tcp",
  "REDIS_MASTER_PORT_6379_TCP_ADDR=10.100.194.126",
  "REDISSLAVE_SERVICE_PORT=6379",
  "REDISSLAVE_PORT=tcp://10.100.159.170:6379",
  "REDISSLAVE_PORT_6379_TCP=tcp://10.100.159.170:6379",
  "REDISSLAVE_PORT_6379_TCP_PORT=6379",
  "KUBERNETES_SERVICE_HOST=10.100.0.2",
  "KUBERNETES_RO_SERVICE_HOST=10.100.0.1",
  "KUBERNETES_RO_PORT_80_TCP_ADDR=10.100.0.1",
  "KUBERNETES_SERVICE_PORT=443",
  "KUBERNETES_RO_SERVICE_PORT=80",
  "HOME=/",
  "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
]
```

### step 6: フロントエンドのServiceを作成

このステップは今回の環境では失敗します。このServiceではエンドポイントとしてロードバランサーを作成しようとしています。ロードバランサーはクラウドプロバイダー(GCE, AWS, OpenStackなど)との連携が必要なため今回は作成できません。ロードバランサーを作ることでKubernetesクラスタ外からPodにアクセスするためのエンドポイントを提供することができます。

### 動作確認

ロードバランサー経由でアクセスできないので直接アクセスして動作確認します。
エンドポイントがなくてもPodの設定でPortを外部公開(expose)するように設定していればNodeがそのポートでListenしているので、直接Nodeに対してアクセスすればPodにアクセスできます。

今回は環境ではVirtualBox内のサーバへ直接アクセスできないのでNodeのIPを指定してもそもそもアクセスできません。なのでポートフォワーディングしてアクセスすることにします。
ローカルホストのPort8080にアクセスしたらnode-02のPort80にアクセスするように設定します。

```
$ ssh -L 8080:localhost:80 node-02
```

この状態でブラウザから http://localhost:8080 にアクセスしてページが表示されたら大丈夫です。テキストボックスに文字を入れて送信して文字が表示されたらバックエンド(Redis)との通信に成功しています。

**以下おまけ(v0.11.0ではなくHEADで試した場合）**

HEADでは公開(expose)するPortに少し修正が入ったのでNode(ホストサーバ)でPortのListenが行われてないようです。step 2で同じNodeにredis serverが2つ割り当てられたのはこのせい。

この場合はnode-02(IP:172.17.8.103)のPort80にアクセスしてもフロントエンドにアクセスできませんが、PodのIPに対してPort80でアクセスは可能です。
ポートフォワーディングの設定を`node-02`経由でPodのIP(10.244.59.19)にアクセスするように変更します。この状態から先ほどと同様にブラウザから http://localhost:8080 にアクセスすればPodにアクセスできます。

```
$ ssh -L 8080:10.244.59.19:80 node-02
```