Google Container Engine で Webサーバ立ててScale、Fail Overさせてみる


※ Wantedly社内のインフラチームで行われた Kubernetes 速習会の資料です。簡単なWebサーバーを立てて、マシンとコンテナをスケールしたり落としてみたりする実験をハンズオンで行いました。
コマンドの実行結果もだいたい載せているのでGKEとかKubernetes触りたいなと思った時に参考にしてみてください。

## 事前情報

https://cloud.google.com/container-engine/

![Screen Shot 2015-11-10 at 10.28.53 PM.png](https://qiita-image-store.s3.amazonaws.com/0/6664/09c60c95-ff0c-0be3-f115-ae9dc0aed707.png "Screen Shot 2015-11-10 at 10.28.53 PM.png")

この中で実は、Container Registryを提供してくれているのがポイント高い。OpenSourceのコンテナレジストリは不安定 / dockerhubやquai.ioは同じデータセンター無いと比べネットワーク遅延が気になるので。

あとは、ハイブリッドネットワークと書いてあるとおり、kubernetesの特徴であるりネットワークがflatでクラスタ内でPrivateアドレスでやり取りできるというのも、やっぱり使いやすかったりする。
cf. http://radar.oreilly.com/2015/10/swarm-v-fleet-v-kubernetes-v-mesos.html

その他、各マシンレベルのモニタリングもベータだができてきている。
cf. https://cloud.google.com/monitoring/get-started?hl=ja

## ゴール
- 実際、Version 1.0 を超えたKubernetesが本当に使えるレベルか試してみる
- 具体的には、コマンドからWebコンテナの増減(`kubectl`を使う)、クラスタ内のノードマシンの増減(`gcloud container`を使う)が出来るようにする

ちなみに、実際に検証してみた感想としては「かなりすごい！」。普通にマシンのスケールとコンテナのスケールが安定して、十分な速度で動くし、Amazonのコンテナエンジンのように命令にラグがあってコントロールを失うみたいなこともほぼない印象。
Dockerでコンテナ化が進んできた今としては、移行がそんなに大変じゃないので、AWSからGKE/GCPに乗り換えすらありえるかも。


## コマンドのインストール

Cloud SDK `gcloud`と、コンテナを管理する`kubectl`をインストールする

### gcloud のインストール

https://cloud.google.com/sdk/#Quick_Start

```
curl https://sdk.cloud.google.com | bash
```

> ここで、インストール場所を選択する。デフォルトで`~`以下。つまり自分は`/Users/awakia/google-cloud-sdk/`が作成された。

シェルのリロード

```
gcloud init
```

### kubectl のインストール

```
$ gcloud components update kubectl
$ which kubectl
/Users/awakia/google-cloud-sdk/bin/kubectl
```


## プロジェクトの作成

https://console.developers.google.com/project

から新しいプロジェクトを作成してください。

できたら、とりあえず作ったProject名を今のシェルの環境変数に入れておきましょう。

```
export PROJECT_ID=kube_test_awakia
```


## 基本設定

デフォルトで使う project と compute/zone をセットしておきます。

```
$ gcloud config set project ${PROJECT_ID}
$ gcloud config set compute/zone asia-east1-a
```

あと、以下のページで`Enable the Container Engine API`をクリックしておきましょう。

https://cloud.google.com/container-engine/docs/before-you-begin


## クラスタの作成

```bash
$ gcloud container clusters create cluster-1 \
    --num-nodes 2 \
    --machine-type g1-small
Creating cluster cluster-1...done.
Created [https://container.googleapis.com/v1/projects/kube-test-awakia/zones/asia-east1-a/clusters/cluster-1].
kubeconfig entry generated for cluster-1.
NAME          ZONE          MASTER_VERSION  MASTER_IP        MACHINE_TYPE  NUM_NODES  STATUS
cluster-1  asia-east1-a  1.0.7           104.199.138.109  g1-small      2          RUNNING
```


ちなみに本番は、`n1-standard-2`とか以上のクラスで立てたほうが良いと思う。


```
$ gcloud compute instances list
NAME                             ZONE         MACHINE_TYPE  PREEMPTIBLE INTERNAL_IP EXTERNAL_IP     STATUS
gke-cluster-1-a24b740a-node-e13o asia-east1-a g1-small                  10.240.0.6  104.155.204.88  RUNNING
gke-cluster-1-a24b740a-node-zyqw asia-east1-a g1-small                  10.240.0.5  104.199.138.219 RUNNING
```

## コンテナの作成

### Goのサーバーコンテナの作成

https://github.com/awakia/go_server をcloneしてきてください

```
git clone git@github.com:awakia/go_server.git
```

### ローカルで動くか確認

```
$ go build
$ ./go_server
[negroni] listening on :8080
```

で http://localhost:8080/ でアクセス。

Markedown Editorが動けば良い。

重要なのはバイナリ自体と`public/`以下のディレクトリ

### Dockerで動くか確認

READMEみて、Docker上で動くか確認する

### GKE上で動かす

#### Dockerイメージの作成

```bash
$ docker build -t gcr.io/${PROJECT_ID}/docker_go .
Sending build context to Docker daemon 17.62 MB
Step 1 : FROM gliderlabs/alpine:3.2
 ---> 87f9f1ccfaf3
Step 2 : ENTRYPOINT /bin/go_server_linux_amd64
 ---> Using cache
 ---> 972b5333e994
Step 3 : COPY bin/go_server_linux_amd64 /bin
 ---> Using cache
 ---> 67716fb04088
Step 4 : COPY public /public
 ---> Using cache
 ---> 0d6af2a3485a
Successfully built 0d6af2a3485a
```

#### Google Container RegistryにPush

Googleのプライベートレジストリに配布。ここに配布しておくことで各ノードからのpullが速くなるメリットがある。

```bash
$ gcloud docker push gcr.io/${PROJECT_ID}/docker_go
The push refers to a repository [gcr.io/kube_test_awakia/docker_go] (len: 1)
0d6af2a3485a: Pushed
67716fb04088: Pushed
972b5333e994: Pushed
87f9f1ccfaf3: Pushed
latest: digest: sha256:727c66eb1304ae533c3d818ed03614b5bfc6c7ef7ac0e8c676b2511ff91544e9 size: 6534
```

#### GKEに配置

`go-node`という名前のノード(Replication Controller)を作る。これはURIになるのでハイフンはOKだけどアンダースコアはダメ

```bash
$ kubectl run go-node --image=gcr.io/${PROJECT_ID}/docker_go --port=8080
CONTROLLER   CONTAINER(S)   IMAGE(S)                            SELECTOR      REPLICAS
go-node      go-node        gcr.io/kube_test_awakia/docker_go   run=go-node   1
```

サービスを作る

```
$ kubectl expose rc go-node --create-external-load-balancer=true
NAME      LABELS        SELECTOR      IP(S)     PORT(S)
go-node   run=go-node   run=go-node             8080/TCP
```

すぐに以下のコマンドを打つと

```
$ kubectl get services go-node
NAME      LABELS        SELECTOR      IP(S)            PORT(S)
go-node   run=go-node   run=go-node   10.123.241.212   8080/TCP
```

Internal IPのみできている。
しばらく待つと、

```bash

$ kubectl get services go-node
NAME      LABELS        SELECTOR      IP(S)             PORT(S)
go-node   run=go-node   run=go-node   10.123.241.212    8080/TCP
                                      104.155.216.106
```

IPが増えていて、この下に出てきた`104.155.216.106`がExternal IP (前のはInternal IP)

http://104.155.216.106:8080 でアクセスできるはず。

### うまくいかなかった時などの、現状確認の方法

```
$ kubectl get pods
NAME            READY     STATUS                                                     RESTARTS   AGE
example-1egud   1/1       Running                                                    0          22m
go-node-gqwz0   0/1       Error: image kube_test_awakia/docker_go:latest not found   0          5m
```

こんな感じでエラーが出て何が原因歌がわかる

#### 削除する場合

`kubectl expose rc go-node`で作られたReplication Controllerの削除

```
$ kubectl delete rc go-node
replicationcontrollers/go-node
```

`--create-external-load-balancer=true`でできたServiceの削除

```
$ kubectl delete services go-node
services/go-node
```

## コンテナ数を変えてみる



```
$ kubectl scale --replicas=3 replicationcontrollers go-node
scaled
```

```
$ kubectl get pods -o wide
NAME            READY     STATUS    RESTARTS   AGE       NODE
go-node-55kvz   1/1       Running   0          11s       gke-cluster-1-2a299914-node-o9gn
go-node-skf1x   1/1       Running   0          11s       gke-cluster-1-2a299914-node-g5bx
go-node-tn1dk   1/1       Running   0          11m       gke-cluster-1-2a299914-node-o9gn
```

## マシン数を変えてみる

https://cloud.google.com/container-engine/docs/resize-cluster

```bash
$ gcloud container clusters describe cluster-1 --format yaml | grep -A 1 instanceGroupUrls
instanceGroupUrls:
- https://www.googleapis.com/replicapool/v1beta2/projects/kube-test-awakia/zones/asia-east1-a/instanceGroupManagers/gke-cluster-1-2a299914-group
```

この`instanceGroupUrls`の最後の部分をコピーして

```bash
$ gcloud compute instance-groups managed resize gke-cluster-1-2a299914-group --size 1
Updated [https://www.googleapis.com/compute/v1/projects/kube-test-awakia/zones/asia-east1-a/instanceGroupManagers/gke-cluster-1-2a299914-group].
---
baseInstanceName: gke-cluster-1-2a299914-node
creationTimestamp: '2015-11-10T04:12:26.683-08:00'
currentActions:
 abandoning: 0
 creating: 0
 deleting: 1
 none: 1
 recreating: 0
 refreshing: 0
 restarting: 0
fingerprint: 42WmSpB8rSM=
id: '3026761127639671237'
instanceGroup: gke-cluster-1-2a299914-group
instanceTemplate: gke-cluster-1-2a299914-1-0-7
kind: compute#instanceGroupManager
name: gke-cluster-1-2a299914-group
selfLink: https://www.googleapis.com/compute/v1/projects/kube-test-awakia/zones/asia-east1-a/instanceGroupManagers/gke-cluster-1-2a299914-group
targetSize: 1
zone: asia-east1-a
```

これで、サイズが 2 -> 1 に

### 自動でコンテナが3つに復活

Before

```bash
$ kubectl get pods -o wide
NAME            READY     STATUS    RESTARTS   AGE       NODE
go-node-55kvz   1/1       Running   0          6m        gke-cluster-1-2a299914-node-o9gn
go-node-skf1x   1/1       Running   0          6m        gke-cluster-1-2a299914-node-g5bx
go-node-tn1dk   1/1       Running   0          18m       gke-cluster-1-2a299914-node-o9gn
```

After

```
$ kubectl get pods -o wide
NAME            READY     STATUS    RESTARTS   AGE       NODE
go-node-55kvz   1/1       Running   0          6m        gke-cluster-1-2a299914-node-o9gn
go-node-k1aym   1/1       Running   0          11s       gke-cluster-1-2a299914-node-o9gn
go-node-tn1dk   1/1       Running   0          18m       gke-cluster-1-2a299914-node-o9gn
```

真ん中のが死んだマシンにいたので復活していることがわかる

## やり残し

- Pod内に複数のコンテナを書いて連携するパターンの検証
- 複数のPodを同時に立ててうまくリソースを配分できるのか検証
- 起動が遅めの大きいアプリケーションの配置の検証

## 特に参考にした文献

- https://cloud.google.com/container-engine/docs/
- https://cloud.google.com/sdk/gcloud/reference/container/

# おまけ: 調査の時に使うコマンドなど

https://cloud.google.com/container-engine/docs/pods/single-container

## Podの作成

```bash:kubectrl_run
$ kubectl run NAME
    --image=image
    [--port=port]
    [--replicas=replicas]
    [--labels=key=value,key=value,...]
```

```bash
$ kubectl run example --image=nginx
CONTROLLER   CONTAINER(S)   IMAGE(S)   SELECTOR      REPLICAS
example      example        nginx      run=example   1
```


## 状態の確認

`rc`はReplication Controller

```bash
$ kubectl get rc
CONTROLLER   CONTAINER(S)   IMAGE(S)   SELECTOR      REPLICAS
example      example        nginx      run=example   1
```

`pod`のlistは以下で見れる


```bash
$ kubectl get pods
NAME            READY     STATUS    RESTARTS   AGE
example-u55u9   1/1       Running   0          5m
```

-o wide をつけると NODE 名までわかる

```bash
$ kubectl get pods -o wide
NAME            READY     STATUS    RESTARTS   AGE       NODE
example-u55u9   1/1       Running   0          6m        gke-cluster-1-27d01a3c-node-a6jr
```

NODE 名がわかると

```bash
$ gcloud compute ssh gke-cluster-1-27d01a3c-node-a6jr
```

という風にsshで入ることが出来る



#### 個別のPod

```bash
$ kubectl get pod example-u55u9
NAME            READY     STATUS    RESTARTS   AGE
example-u55u9   1/1       Running   0          5m
```

詳しくdescribe

```bash
$ kubectl describe pod example-u55u9
Name:       example-u55u9
Namespace:      default
Image(s):     nginx
Node:       gke-cluster-1-27d01a3c-node-a6jr/10.240.0.4
Labels:       run=example
Status:       Running
Reason:
Message:
IP:       10.120.2.3
Replication Controllers:  example (1/1 replicas created)
Containers:
  example:
    Image:  nginx
    Limits:
      cpu:    100m
    State:    Running
      Started:    Tue, 10 Nov 2015 16:58:11 +0900
    Ready:    True
    Restart Count:  0
Conditions:
  Type    Status
  Ready   True
Events:
  FirstSeen       LastSeen      Count From            SubobjectPath       Reason    Message
  Tue, 10 Nov 2015 16:57:59 +0900 Tue, 10 Nov 2015 16:57:59 +0900 1 {scheduler }                    scheduled Successfully assigned example-u55u9 to gke-cluster-1-27d01a3c-node-a6jr
  Tue, 10 Nov 2015 16:57:59 +0900 Tue, 10 Nov 2015 16:57:59 +0900 1 {kubelet gke-cluster-1-27d01a3c-node-a6jr}  implicitly required container POD pulled    Pod container image "gcr.io/google_containers/pause:0.8.0" already present on machine
  Tue, 10 Nov 2015 16:57:59 +0900 Tue, 10 Nov 2015 16:57:59 +0900 1 {kubelet gke-cluster-1-27d01a3c-node-a6jr}  implicitly required container POD created   Created with docker id 534d9732c647
  Tue, 10 Nov 2015 16:57:59 +0900 Tue, 10 Nov 2015 16:57:59 +0900 1 {kubelet gke-cluster-1-27d01a3c-node-a6jr}  implicitly required container POD started   Started with docker id 534d9732c647
  Tue, 10 Nov 2015 16:58:10 +0900 Tue, 10 Nov 2015 16:58:10 +0900 1 {kubelet gke-cluster-1-27d01a3c-node-a6jr}  spec.containers{example}    pulled    Successfully pulled image "nginx"
  Tue, 10 Nov 2015 16:58:10 +0900 Tue, 10 Nov 2015 16:58:10 +0900 1 {kubelet gke-cluster-1-27d01a3c-node-a6jr}  spec.containers{example}    created   Created with docker id 69b4fbb58d20
  Tue, 10 Nov 2015 16:58:11 +0900 Tue, 10 Nov 2015 16:58:11 +0900 1 {kubelet gke-cluster-1-27d01a3c-node-a6jr}  spec.containers{example}    started   Started with docker id 69b4fbb58d20
```