Kubernetes で 複数の nginx インスタンスを手元の Mac に立ち上げる

Kubernetes の動作を確認するため、複数の nginx インスタンスを手元の Mac に立ち上げてみます。

## Docker for Mac で Kubernetes を有効にする

* Docker アイコン > Preferences > Kubernetes > Enable Kubernetes > ラジオボタンを Kubernetes にする
* Apply
* Install

これで Kubernetes の CLI である `kubectl` もインストールされます。

Kubernetes の状態を知るには `kubectl get` を使いますが、そもそもどんなリソースがあるか知らないと使えません。ブラウザで情報を閲覧出来る [Kubernetes Dashboard](https://github.com/kubernetes/dashboard) をインストールすると便利です。

```shell
$ kubectl version 
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.3", GitCommit:"2bba0127d85d5a46ab4b778548be28623b32d0b0", GitTreeState:"clean", BuildDate:"2018-05-21T09:17:39Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.3", GitCommit:"2bba0127d85d5a46ab4b778548be28623b32d0b0", GitTreeState:"clean", BuildDate:"2018-05-21T09:05:37Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

$ kubectl get pods --all-namespaces -l k8s-app=kubernetes-dashboard
NAMESPACE     NAME                                    READY     STATUS    RESTARTS   AGE
kube-system   kubernetes-dashboard-7b9c7bc8c9-9nhlp   1/1       Running   0          4m

$ kubectl proxy
```

* ブラウザで http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/ を開く
* Kubernetes Dashboard で Skip を選択
* Kubernetes Dashboard が表示される。

上記コマンドの意味:

* [kubectl apply](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#apply)
  * 指定されたファイルを元にリソースを設定する。
* [kubectl get](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#get)
  * 指定されたリソースの情報を表示する。
  * [pods](https://kubernetes.io/docs/concepts/workloads/pods/pod/) はリソースの種類
  * -l (--selector) でフィルタをかけることが出来る。

## [オートコンプリートの設定](https://kubernetes.io/docs/reference/kubectl/cheatsheet/)

zsh の場合:

```shell
echo "if [ $commands[kubectl] ]; then source <(kubectl completion zsh); fi" >> ~/.zshrc # add autocomplete permanently to your zsh shell
```

## 用語メモ

* Kubernetes とは、Resource を組み合わせるもの。
* Resource とは、Docker container を組み合わせて使うための色々な部品。
    * [Node](https://kubernetes.io/docs/concepts/architecture/nodes/): ホストマシンの事。
    * [Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/): リソースを分けて管理するための名前。
    * [Pod](https://kubernetes.io/docs/concepts/workloads/pods/pod/): 一つ以上コンテナ。デプロイの単位。
    * [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/): 同じコンテナを複数作るのに使う。またバージョン管理も出来る。
    * [Service](https://kubernetes.io/docs/concepts/services-networking/service/): Pod に名前をつけてお互いにアクセス出来るようにする。また外部からアクセス出来るようにする。
        * [Services without Selectors](https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors) を使うことで、クラスタと外部のサービスの連携を行う事も出来る。
* [Selector](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/): Pod などを特定するのに使う。
    * 選ばれる側のリソースは metadata.labels に辞書形式で定義する。
    * 選ぶ側のリソースは selector などで指定する。

## Pod を作る

Pod は一つ以上のコンテナをまとめたリソースです。以下に [Define a Command and Arguments for a Container](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/) の例を示します。

```shell
apiVersion: v1
kind: Pod # Kind でリソースの種類を指定する。
metadata:
  name: command-demo # kubectl からこの名前で Pod を参照します。
  labels:
    purpose: demonstrate-command
spec:
  containers:
  - name: command-demo-container
    image: debian
    command: ["printenv"]
    args: ["HOSTNAME", "KUBERNETES_PORT"]
  restartPolicy: OnFailure
```

このファイルを commands.yaml という名前で保存して `kubectl apply -f commands.yaml` を実行するとこの Pod が作成されます。面白い事に引数に URL を指定できるので代わりに:

    kubectl apply -f https://k8s.io/examples/pods/commands.yaml

でも動きます。この Pod は printenv HOSTNAME KUBERNETES_PORT を実行するだけの物です。動作状態を確認するには以下のようにします。

```shell
$ kubectl get pods
NAME           READY     STATUS      RESTARTS   AGE
command-demo   0/1       Completed   0          7m
```

出力結果は `kubectl logs` で見れます。

```shell
$ kubectl logs command-demo 
command-demo
tcp://10.96.0.1:443
```

Pod を削除するには `kubectl delete` を使います。いくつかの方法があります。

```shell
kubectl delete pod command-demo # 名前を指定
kubectl delete -f commands.yaml # ファイルを指定
kubectl delete pod/command-demo # kubectl get all の出力を使う
```

この例では一つの Pod に一つのコンテナを含みました。Pod 内は一つの IP アドレスを共有するため、複数のコンテナを含めるとお互いに localhost で通信出来るそうです。

### (注意) ローカルのイメージを使いたい場合は repository name と latest 以外の tag を付ける。

例えば上記の例では

    image: debian

のようにしてイメージを指定しているがこの場合リモートの dockerhub のイメージを参照してしまう。自分のマシンにある未公開のイメージを使いたい場合は repository name と latest 以外の tag を付ける必要がある。

リモートを参照してしまう例

    hoge
    hoge:latest

ローカルを参照出来る例

    fuga/hoge:1.0

## Deployment を使う

Deployment とは、コンテナをデプロイする時に使うリソースです。以下に [Run a Stateless Application Using a Deployment](https://kubernetes.io/docs/tasks/run-application/run-stateless-application-deployment/) の例を示します。

```shell
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx # template.metadata.labels に記載したラベルを指定します。
  replicas: 2 # この数だけ pod を立ち上げます。
  template:
    metadata:
      labels:
        app: nginx # 作成する pod に割り振るラベルを指定します。
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
```

例によってURL を指定して `kubectl apply -f https://k8s.io/examples/application/deployment.yaml` でも動きます。動作中は以下のような状態になります。

```shell
$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-75675f5897-bgrph   1/1       Running   0          1m
nginx-deployment-75675f5897-dpv4m   1/1       Running   0          1m
```

ただ、残念ながら開発マシンから直接から Pod に接続する事は出来ません。Pod ごとに IP が割り振られていますが、ホスト側からは見えないのです。Pod の IP を取得してから、そこでコマンド一発でコンテナを動かせる `kubectl run` を使って Kubernetes クラスタの中から curl コマンドで IP にアクセスします。

```shell
$ kubectl describe pod nginx-deployment-75675f5897-bgrph | grep IP 
IP:             10.1.0.67
$ kubectl run curl --image=byrnedo/alpine-curl -i --rm --tty --restart=Never http://10.1.0.67
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
```

* [kubectl run](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#run): イメージから適当なリソースを作って動かす。
    * --image: docker image を指定する。
    * -i: 標準出力を開いたままにする。
    * --rm: コマンド終了時に作成したリソースを消す。
    * --tty: コンテナごとに TTY を割り当てる。
    * --restart:
        * Always: Deployment を作る。常に再起動する
        * OnFailure: Job を作る。失敗時再起動する。
        * Never: Pod だけ作る。再起動はしない。

ちなみに、コンテナでコマンドを実行するには `kubectl exe` を使います。

```shell
$ kubectl exec -it nginx-deployment-75675f5897-bgrph -- cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 7 (wheezy)"
NAME="Debian GNU/Linux"
VERSION_ID="7"
...
```

## Service を使う

さて、このままでは外部からコンテナに接続出来ないため何の役にも立ちません。コンテナと外部を結びつけるには Service の LoadBalancer タイプを使います。

```shell
# service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
    - name: http
      port: 8087 # 公開するポート番号
      targetPort: 80 # Pod のポート番号
```

Service は一連の Pod に名前をつけてお互いに通信出来るようにする物です。例えば上記の yaml を実行すると Kubernetes 内部の DNS に `nginx` という名前が登録され、app: nginx のラベルをつけた Pod に nginx という名前でアクセス出来るようになります。この例では Service の 8087 ポートを Pod の 80 ポートに転送しています。

```shell
kubectl apply -f service.yaml 
kubectl run curl --image=byrnedo/alpine-curl -i --rm --tty --restart=Never http://nginx:8087
```

Service にはいくつか type があります。この例では LoadBalancer を使っているので、ホストから直接 8087 にアクセス出来ます。ちなみに、本には LoadBalancer はローカル環境で使う事は出来ないと書いてあったのですが、何気なくやってみたら使えました。

    curl http://localhost:8087

これでとりあえず複数のインスタンスを立ち上げ外部からアクセスする所までやりました。やり残した事:

* ストレージの設定
* Helm を使う

## [Yaml](http://yaml.org/) 文法

* 先頭が - で始まるとリスト。[x, y, z] の形式もリスト
* 途中 : があると辞書。{x: a, y: b, z: c} の形式もリスト
* # より後ろはコメント

## 参考

* [Docker/Kubernetes 実践コンテナ開発入門](http://amzn.asia/d/fFn8VA6)
* [Kubernetes Tutorial](https://kubernetes.io/docs/tutorials/)