Kubernetes 速習会


## この速習会の目標

**Developer がさくっと kubernetes を理解して、実際にリリース出来る**

* kubectl が叩ける用になる
* Kubernetes の manifest ファイルの書き方
* Kubernetes への Release / Deploy 方法
* Kubernetes 上にあるApplication の maintenance
* Kubernetes に上げたApplicationのMonitoring

## kubectl を install

kubectl の install してなかった場合は、こちらの実行ファイルを直接ダウンロードして叩けるようにしましょう

The linux kubectl binary can be fetched with a command like:

`curl -O https://storage.googleapis.com/kubernetes-release/release/v1.3.4/bin/linux/amd64/kubectl`

On an OS X workstation, replace linux in the URL above with darwin:

`curl -O https://storage.googleapis.com/kubernetes-release/release/v1.3.4/bin/darwin/amd64/kubectl`

```bash
chmod +x kubectl
mv kubectl /usr/local/bin/kubectl
```

ref. https://coreos.com/kubernetes/docs/latest/configure-kubectl.html


## config ファイルを設定する

期限付きURL から設定ファイルをダウンロード(リンクは削除してあります)してみよう


* 既に k8s を使っている方

既存の config と混ざってしまうと大変のため、kubectl command の引数に `--kubeconfig=` を付けて絶対他のcluster に実行させないようにしましょう

ex:

```bash
kubectl get po --kubeconfig=k8s-sokusyu.yaml
```

* 初めて使う方

いちいち `--kubeconfig=k8s-sokusyu.yaml` 打つのが面倒なので、 `~/.kube/config` にリネームしておきましょう。
kubectl は default で ~/.kube/config の読み込んで実行してくれます。

```bash
mkdir ~/.kube
cp ~/Downloads/k8s-sokusyu.yaml ~/.kube/config
```

## kubectl を実行してみよう

```
kubectl get pod
```

```bash
kubectl get po --kubeconfig=k8s-sokusyu.yaml
```

一覧が見えましたでしょうか？

```
$ kubectl get pod
NAME                          READY     STATUS    RESTARTS   AGE
hello-world-855027487-25n7g   1/1       Running   0          49m
```

このような画面が返ってきたら設定は完了です！

## hello world

[hello world](https://github.com/koudaiii/docker-hello-world) を git clone して下さい。
ここでは、シンプルな sinatra で作ったアプリケーションを例に進めて行きます。

### まずは自分のスペースを作ってみよう

Kubernetes では、たくさんのサーバーに対して、一つのクラスタリングを作り、一つの大きなリソース(サーバー)に見せます。
そのため、複数のチームやたくさんの web application を実行する際にそのまま default で上げ続けるとすべてのアプリケーションが同じところに表示されて、すごく使いづらくなります。

#### namespace について

ref. https://github.com/kubernetes/kubernetes/blob/master/docs/design/namespaces.md

> Motivation
> A single cluster should be able to satisfy the needs of multiple user communities.
> Each user community wants to be able to work in isolation from other communities.

複数のコミュニティ毎に分けて利用することで

* すべてのresource (pod rc rs deployment secret etc...)を分けて利用できる
* 他のapplication を誤って操作出来てしまうのを防ぐ
* namespace を default のまま利用した場合、自分の見たいものが探しにくくなる(常にcommandでlabel を指定する必要がある)
* namespace は初期値として、既に `default` と `kube-system` でわけられており、kubernetes の service が誤って操作されないようにわけられている


そこで、速習会では、個々人で namespace を作って自分のスペースで kubernetes に取り組んで行こうと思います。

**名前は自由ですが、githubアカウント名を使うと周りも理解しやすいのでオススメです。**

#### それぞれの namespace を作ってみよう

まずは、ファイルを編集します。

```bash
diff --git a/kubernetes/namespace.yaml b/kubernetes/namespace.yaml
index 5a96da9..57c89f7 100644
--- a/kubernetes/namespace.yaml
+++ b/kubernetes/namespace.yaml
@@ -1,4 +1,4 @@
 apiVersion: v1
 kind: Namespace
 metadata:
-  name: #GitHub Account Name
+  name: koudaiii
```

その後、実行してみましょう

```bash
$ kubectl create -f kubernetes/namespace.yaml 
namespace "koudaiii" created
```

無事作成できました。実際に出来たか確認しみよう！
すべての namespace の一覧を表示させてみます。

```bash
$ kubectl get namespace
NAME          STATUS    AGE
default       Active    86d
koudaiii      Active    5m
kube-system   Active    86d
```


* 他の人の namespace を見てみよう

`--namespace=` で指定すると、その namespace で作られた pod が見れます。
ここでは kubernetes 上で作られる pod の一覧を表示しています。

```
$ kubectl get po --namespace=kube-system
NAME                                                                    READY     STATUS    RESTARTS   AGE
elasticsearch-logging-v1-0nyp4                                          1/1       Running   0          69d
elasticsearch-logging-v1-ixfyy                                          1/1       Running   0          58d
・・・・・・・・・・・・・・・
```


※複数のクラスタがあってめんどくさい人への設定があります。デフォルトでは、 `namespace=default` が指定されて表示されますが、変更することが出来ます。


### 実際に叩いてみよう

* 自分の space で実際に動かしてみよう

`kubectl create -f kubernetes/production.yaml --namespace=koudaiii`


### 大まかな manifest file の理解

* kind
	* Pod
	* Deployment
	* Service

![](http://kubernetes.io/images/hellonode/image_13.png)

* meta data
	* label
	* name

```yml
$ cat kubernetes/pod.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    name: hello-world
    role: web
  name: hello-world
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    name: hello-world
    role: web
  type: LoadBalancer
---
apiVersion: v1
kind: Pod
metadata:
  name: "hello-world"
  labels:
    name: hello-world
    role: web
spec:
  containers:
    - image: koudaiii/hello-world:latest
      name: hello-world
      ports:
        - containerPort: 8080
      env:
        - name: MESSAGE
          value: Hello Wantedly
```

![Kobito.IM03fJ.png](https://qiita-image-store.s3.amazonaws.com/0/18843/2022925c-1de7-3466-9d11-d1855f55a376.png "Kobito.IM03fJ.png")


```yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hello-world
  labels:
    name: hello-world
    role: web
spec:
  minReadySeconds: 30
  strategy:
    type: RollingUpdate
  replicas: 1
  template:
    metadata:
      name: hello-world
      labels:
        name: hello-world
        role: web
    spec:
      containers:
      - image: koudaiii/hello-world:latest
        name: hello-world
        ports:
          - containerPort: 8080
        env:
          - name: MESSAGE
            value: Hello Wantedly
```


### golang を出してみよう

https://github.com/koudaiii/go_server を git clone して実行してこう

golang の application を立てる

```bash
kubectl create -f kubernetes/blue.yaml --namespace=koudaiii
kubectl create -f kubernetes/green.yaml --namespace=koudaiii
kubectl create -f kubernetes/service-go.yaml --namespace=koudaiii
```

front になる nginx を立てる

```bash
kubectl get po -w --namespace=koudaiii
kubectl create -f kubernetes/deployment-nginx.yaml --namespace=koudaiii
kubectl create -f kubernetes/service-nginx.yaml --namespace=koudaiii
```

確認してみよう

```bash
kubectl get svc --namespace=koudaiii
kubectl get deployment --namespace=koudaiii
kubectl describe svc --namespace=koudaiii
```


### blue-green deployent

ref. https://speakerdeck.com/koudaiii/kubernetes-woshi-tutesabisuwojia-su-saseruqu-rizu-mi

![Kobito.nhowIs.png](https://qiita-image-store.s3.amazonaws.com/0/18843/5fe277d4-cbd3-6ee6-149e-8c65b97517f9.png "Kobito.nhowIs.png")

* Color を知ろう！

![Kobito.dki04l.png](https://qiita-image-store.s3.amazonaws.com/0/18843/e99dc8ca-9ce2-8377-c0e2-209a7b57ea9e.png "Kobito.dki04l.png")

```bash
$ kubectl describe svc go --namespace=koudaiii
Name:			go
Namespace:		koudaiii
Labels:			app=go,name=app
Selector:		app=go,color=blue,name=app
Type:			NodePort
IP:			172.16.174.98
Port:			go	8080/TCP
NodePort:		go	32067/TCP
Endpoints:		172.18.2.5:8080
Session Affinity:	None
No events.
```

* Rolling Update してみよう！

![Kobito.LqcmDd.png](https://qiita-image-store.s3.amazonaws.com/0/18843/24869588-71c7-4f7e-447b-3c7e7e13fb7b.png "Kobito.LqcmDd.png")


```
$ kubectl edit deployment green --namespace=koudaiii
 21 spec:
 22   minReadySeconds: 30
 23   replicas: 10  # ここを10にしてみました                                                                                                                                                                                               
 24   selector:
 25     matchLabels:
 26       app: go

deployment "green" edited
```

* Switch してみよう！

![Kobito.iQJLvH.png](https://qiita-image-store.s3.amazonaws.com/0/18843/b07e6792-1538-c167-3eec-cd0e2a7fca94.png "Kobito.iQJLvH.png")

```
$ kubectl edit svc go --namespace=koudaiii

 25   selector:
 26     app: go
 27     color: green       ## blue -> green  へ変更                                                                                                                                                                                       
 28     name: app
 29   sessionAffinity: None
service "go" edited
```

* Endpoint が増えたか確認してみよう

```
$ kubectl describe svc go --namespace=koudaiii
Name:			go
Namespace:		koudaiii
Labels:			app=go,name=app
Selector:		app=go,color=green,name=app
Type:			NodePort
IP:			172.16.174.98
Port:			go	8080/TCP
NodePort:		go	32067/TCP
Endpoints:		172.18.0.5:8080,172.18.1.6:8080,172.18.10.6:8080 + 7 more...
Session Affinity:	None
No events.
```


### もう少し踏み込んだ manifest file の理解

ref. https://speakerdeck.com/koudaiii/kubernetes-woshi-tutesabisuwojia-su-saseruqu-rizu-mi


* healthcheck

http://kubernetes.io/docs/user-guide/production-pods/
http://kubernetes.io/docs/user-guide/pod-states/#container-probes

* readinessProbe

初回起動時に data や config をたくさん読むこむような大きいアプリケーションの場合、readinessProbe を使う。
Success になった後、 実際に traffic を受付ける。

* livenessProbe

コンテナが生きているどうか見るもの。
LivenessProbe が Failure の場合、 RestartPolicy の対象となり kubelet から コンテナを kill する。

#### Process Health Checking

http://kubernetes.io/docs/user-guide/walkthrough/k8s201/#health-checking

kubernetes 上では default でコンテナのプロセスをチェックしており、もし失敗していれば restart するようになっている。

#### Application Health Checking

http://kubernetes.io/docs/user-guide/liveness/

* httpGet を使ったヘルスチェック

```
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            # Path to probe; should be cheap, but representative of typical behavior
            path: /index.html
            port: 80
          initialDelaySeconds: 30
          timeoutSeconds: 1
```

* exec を使ったヘルスチェック

```
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - args:
    - /bin/sh
    - -c
    - echo ok > /tmp/health; sleep 10; rm -rf /tmp/health; sleep 600
    image: gcr.io/google_containers/busybox
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/health
      initialDelaySeconds: 15
      timeoutSeconds: 1
    name: liveness
```

* 確認

Unhealthyで何度も restart されているのがわかる

```
$ kubectl describe pods liveness-exec
・・・・・・・・・・・・・・
Events:
  FirstSeen	LastSeen	Count	From								SubobjectPath			Type		Reason		Message
  ---------	--------	-----	----								-------------			--------	------		-------
  4m		4m		1	{default-scheduler }										Normal		Scheduled	Successfully assigned liveness-exec to ip-172-20-0-171.ap-northeast-1.compute.internal
  4m		4m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Created		Created container with docker id 027dc93671c0
  4m		4m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Started		Started container with docker id 027dc93671c0
  3m		3m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Killing		Killing container with docker id 027dc93671c0: pod "liveness-exec_default(af352909-1d93-11e6-9d4c-06164e6ae803)" container "liveness" is unhealthy, it will be killed and re-created.
  3m		3m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Started		Started container with docker id 6c79b57f8d03
  3m		3m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Created		Created container with docker id 6c79b57f8d03
  2m		2m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Killing		Killing container with docker id 6c79b57f8d03: pod "liveness-exec_default(af352909-1d93-11e6-9d4c-06164e6ae803)" container "liveness" is unhealthy, it will be killed and re-created.
  2m		2m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Created		Created container with docker id 6037f2406959
  2m		2m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Started		Started container with docker id 6037f2406959
  1m		1m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Killing		Killing container with docker id 6037f2406959: pod "liveness-exec_default(af352909-1d93-11e6-9d4c-06164e6ae803)" container "liveness" is unhealthy, it will be killed and re-created.
  1m		1m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Started		Started container with docker id 3eb2af8cfef1
  1m		1m		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Created		Created container with docker id 3eb2af8cfef1
  48s		48s		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Killing		Killing container with docker id 3eb2af8cfef1: pod "liveness-exec_default(af352909-1d93-11e6-9d4c-06164e6ae803)" container "liveness" is unhealthy, it will be killed and re-created.
  4m		48s		5	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Pulling		pulling image "gcr.io/google_containers/busybox"
  46s		46s		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Started		Started container with docker id 862d07529857
  4m		46s		5	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Pulled		Successfully pulled image "gcr.io/google_containers/busybox"
  46s		46s		1	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Normal		Created		Created container with docker id 862d07529857
  4m		28s		7	{kubelet ip-172-20-0-171.ap-northeast-1.compute.internal}	spec.containers{liveness}	Warning		Unhealthy	Liveness probe failed: cat: can't open '/tmp/health': No such file or directory
  ```

### lifecycle

Kubernetes では delete した際に、 SIGTERM が送られる。
terminationGracePeriodSeconds(default 30s)より長い場合は、 SIGKILL が送られる。

```
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        lifecycle:
          preStop:
            exec:
              # SIGTERM triggers a quick exit; gracefully terminate instead
              command: ["/usr/sbin/nginx","-s","quit"]
```

* termination message

deploy や debugger 目的で terminate されたかどうかを Message を設定することで確認できる。
また log に吐き出されるため、 logentries などで確認することも可能である。

```
apiVersion: v1
kind: Pod
metadata:
  name: pod-w-message
spec:
  containers:
  - name: messager
    image: "ubuntu:14.04"
    command: ["/bin/sh","-c"]
    args: ["sleep 60 && /bin/echo Sleep expired > /dev/termination-log"]
```


* メッセージとStatusを確認する

```
$ kubectl create -f examples/lifecycle/termination-message.yaml

$ kubectl get pods/pod-w-message -o go-template="{{range .status.containerStatuses}}{{.lastState.terminated.message}}{{end}}"
Sleep expired
$ kubectl get pods/pod-w-message -o go-template="{{range .status.containerStatuses}}{{.lastState.terminated.exitCode}}{{end}}"
```

### 環境変数

[Kubernetes - kubectl create secret generic](http://kubernetes.io/docs/user-guide/kubectl/kubectl_create_secret_generic/)

```bash
# Create a new secret named my-secret with key1=supersecret and key2=topsecret
$ kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
```


http://kubernetes.io/docs/user-guide/secrets/#using-secrets-as-environment-variables

changelog: https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG.md#changes-since-v111

```
     env:
        - name: SECRET_USERNAME
          valueFrom:
            secretKeyRef:
              name: my-secret
              key: key1
        - name: SECRET_PASSWORD
          valueFrom:
            secretKeyRef:
              name: my-secret
              key: key2
```

### namespace を越えての通信と名前解決について

ref. https://github.com/kubernetes/kubernetes/blob/master/build/kube-dns/README.md

namespace で区切ったとしても kubernetes では同じ cluster にいる場合は、ネットワーク的にはつながっています。
また、 skydns という kubernetes の addon が用意されており、名前解決も出来ます。


* service `my-svc.my-namespace.svc.cluster.local`

```bash
$ kubectl run -i --tty --rm busybox --image=busybox --restart=Never -- sh
/ # nslookup nginx.koudaiii.svc.cluster.local
Server:    100.64.0.10
Address 1: 100.64.0.10

Name:      nginx.koudaiii.svc.cluster.local
Address 1: 100.64.17.97
```

* pod `pod-ip-address.my-namespace.pod.cluster.local`

describe pod で ip を確認して、`-` で繋いで上げると名前解決できます。

```bash
$ kubectl describe po/blue-534540973-9svyj --namespace=koudaiii
Name:		blue-534540973-9svyj
Namespace:	koudaiii
Node:		ip-172-20-0-54.ap-northeast-1.compute.internal/172.20.0.54
Start Time:	Mon, 22 Aug 2016 11:29:56 +0900
Labels:		app=go,color=blue,name=app,pod-template-hash=534540973
Status:		Running
IP:		100.66.0.12 # ip を確認
・・・・・
```

```bash
$ kubectl run -i --tty --rm busybox --image=busybox --restart=Never -- sh
/ # nslookup 100-66-0-12.koudaiii.pod.cluster.local
Server:    100.64.0.10
Address 1: 100.64.0.10

Name:      100-66-0-12.koudaiii.pod.cluster.local
Address 1: 100.66.0.12
```

### Kubernetes の裏側を知る

ref. https://speakerdeck.com/thockin/kubernetes-extensibility
ref. http://kubernetes.io/docs/hellonode/

* Google では10年以上前からコンテナを動かしている


![Kobito.7kNj5k.png](https://qiita-image-store.s3.amazonaws.com/0/18843/47436324-da8c-1df6-7299-a820e3a1e9a9.png "Kobito.7kNj5k.png")


* kubernetes がどうなっているのか

![Kobito.5GkMhC.png](https://qiita-image-store.s3.amazonaws.com/0/18843/1a5f1da6-d6ff-3c39-fe36-9ab8da0727cb.png "Kobito.5GkMhC.png")


* ReplicationController と Deployment

![](http://kubernetes.io/images/hellonode/image_13.png)


### 今回立てたやつを Datadog で見てみる

* Datadog

Monitoring Service です。
Wantedlyでどのように使われてるかは、[こちらの記事](http://qiita.com/koudaiii/items/bc89368e1279649f2498)に書かれています。

* Dashboardを見てみる

![Kobito.eNVlxP.png](https://qiita-image-store.s3.amazonaws.com/0/18843/1dad5ff3-72cb-3ca4-3f3b-df4d703aee36.png "Kobito.eNVlxP.png")


* kubernetes に dd-agent を立てるには？？

git に管理したくない api key をcommandから kubernetes 上に登録させる

```bash
$ kubectl create secret generic dd-agent --from-literal=api-key=your api key
secret "dd-agent" created
```

datadog 公式が用意している manifestfile

```yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: dd-agent
spec:
  template:
    metadata:
      labels:
        app: dd-agent
      name: dd-agent
    spec:
      containers:
        - image: datadog/docker-dd-agent:kubernetes
          imagePullPolicy: Always
          name: dd-agent
          ports:
            - containerPort: 8125
              name: dogstatsdport
          env:
            - name: API_KEY
              valueFrom:
                secretKeyRef:
                  name: dd-agent
                  key: api-key
          volumeMounts:
            - name: dockersocket
              mountPath: /var/run/docker.sock
            - name: procdir
              mountPath: /host/proc
              readOnly: true
            - name: cgroups
              mountPath: /host/sys/fs/cgroup
              readOnly: true
      volumes:
        - hostPath:
            path: /var/run/docker.sock
          name: dockersocket
        - hostPath:
            path: /proc
          name: procdir
        - hostPath:
            path: /sys/fs/cgroup
          name: cgroups
```

ここでの `kind: DaemonSet` と呼ばれるものは、すべての Node にこの pod を配置するという記述になります。
この方法で、log 収集系の pod を合わせて配置すると便利になります。

```bash
$ kubectl create -f kubernetes/dd-agent.yaml 
daemonset "dd-agent" created
```


### (演習課題)綺麗なAPI速習会で使ったgo-api をkubernetes に出してみよう

https://github.com/shimastripe/go-api-sokushukai  から fork して manifest file を書いてkubernetes に出してよう！！

image は以下の方法で取得できます。

`docker pull quay.io/shimastripe/go-api-sokushukai`

元に kubernetes 立ててみよう

manifest を書く時にやっていること

* `kubectl create -f file` で validate check させる
* 他の人を見てみる
* `kubectl run` でネットワークがつながっているか見てみる

```bash
$ kubectl run -i --tty --rm busybox --image=busybox --restart=Never --namespace=koudaiii -- sh
Waiting for pod sync/busybox to be running, status is Pending, pod ready: false


Hit enter for command prompt

/ # nslookup go
Server:    100.64.0.10
Address 1: 100.64.0.10

Name:      go
Address 1: 100.64.166.49
/ # exit
pod "busybox" deleted
```
* `kubectl exec pod name` で実際に中で何が起こっているか見てみる
* `kubectl logs pod name`  でコンテナのログをみる
* `kubectl describe pod name` でEventのログを見てみる


参考回答: https://github.com/dtan4/go-api-sokushukai/tree/master/kubernetes


### (演習課題) blue-green deployment が出来るようにしてみよう！