Minikube ではじめる Kubernetes 入門

# はじめに

* Mac で Minikube を使って Kubernetes を動かしてみる
* Kubernetes の用語も勉強中なのでまとめる
* 状態を持たないウェブサーバーを複数台立ち上げる設定と、状態を保つ Zookeeper クラスタを複数台立ち上げる設定について内容を理解する

# Minikube のインストール

* https://github.com/kubernetes/minikube の通りにインストール
* VirtualBox のインストール
  * https://www.virtualbox.org/wiki/Downloads
* minikube のインストール
  * `brew cask install minikube`

# minikube の実行

```
# 起動
$ minikube start

# ステータス確認
$ minikube status

# ブラウザでステータスを確認する
$ minikube dashboard

# コンテナを起動する
$ kubectl run hello-minikube --image=gcr.io/google_containers/echoserver:1.4 --port=8080

# サービスの公開
$ kubectl expose deployment hello-minikube --type=NodePort

# サービスのURL取得
$ minikube service hello-minikube --url

# サービスの削除
$ kubectl delete service hello-minikube

# deployment の削除
$ kubectl delete deployment hello-minikube

# 停止
$ minikube stop
```


# 用語

## Cluster

* Node の集合

## [Node](https://kubernetes.io/docs/concepts/architecture/nodes/)

* Pod が動作する物理もしくは仮想マシン
* 1 Node で複数の Pod が動作する

## [Pod](https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/)

* docker コンテナの集合. docker コンテナは Pod 単位で Node に配備される
* 依存関係のあるコンテナ同士を同一の Pod に所属させて Pod 単位で取り扱うことで管理が容易になる

## [ReplicaSet](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/) 

* ReplicaSet は Pod の定義や複製数を持ち、指定された数の Pod が常に起動されていることを保証する

## [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)

* ReplicaSet の作成・管理をする
* ReplicaSet のように Pod の定義や複製数を持ち、ReplicaSet を作成することで Pod を起動できる
* 定義を更新して新しい ReplicaSet を作成し、古い ReplicaSet から 1 台づつ更新することができる（ローリングアップデート）
* ReplicaSet の履歴を持ち、古い ReplicaSet に巻き戻すことができる（ロールバック）

## [Service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services)

* クラスタ外部向けのエンドポイントとしてクラスタ内部とポートフォワーディングしたり負荷分散したりする

## [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configmap/)

* Key-Value でデータを定義して Pod に環境変数やファイルとしてデータを提供する
  * 環境変数の場合は Key が変数名で Value が値
  * ファイルの場合は Key がファイル名で Value がファイルの内容、Pod の定義でマウント先の Path を指定する

## [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)

* Pod の定義と複製数を持ち、定義された数だけ Pod を起動する
* 複製数が N なら Pod の名前は {Name}-0 から {Name}-N となり Pod が死んで再作成されても同じ名前になる
* 永続ストレージの定義を持ち Pod 作成時に自動でストレージも作成される
  * ストレージの削除は自動では行われない
* 複製数が N なら 0 番目の Pod から N 番目の Pod まで順番に起動される
* 複製数が N なら N 番目の Pod から 0 番目の Pod まで順番に停止される


# ローカルのイメージでコンテナを起動する

* https://qiita.com/ocadaruma/items/efe720e46ae7ecb9ec25

```
# minikube の docker マシンに接続する
$ eval $(minikube docker-env)
$ docker ps

# ローカルにイメージを作成する
$ echo "FROM nginx" > Dockerfile
$ docker build -t example/nginx:v1.0 .

# ローカルのイメージを使ってコンテナを起動する
$ cat <<EOL > example.yaml
apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: nginx
    image: "example/nginx:v1.0"
    imagePullPolicy: IfNotPresent
EOL
$ kubectl create -f example.yaml

# 動作確認をする
$ kubectl get pods
NAME                             READY     STATUS    RESTARTS   AGE
example                          1/1       Running   0          16s
$ kubectl exec -it example /bin/bash
```


# kubectl のコンテキストを GKE と切り替える

```
# kubectl の実行コンテキストの確認
$ kubectl config current-context

# kubectl を GKE のコンテキストに切り替え
$ gcloud container clusters get-credentials ${CLUSTER_NAME}

# kubectl を minikube のコンテキストに切り替え
kubectl config use-context minikube
```

# Pod の情報を参照して環境変数にセットする

* 取得例
  * https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/

* 環境変数にセットしている例
  * https://kubernetes.io/docs/tutorials/stateful-application/cassandra/

* セットした環境変数を参照する例
  * https://github.com/kubernetes/examples/blob/6afc1fb8595f0d08a3e53a8433366a54358ef418/cassandra/image/files/run.sh

# 状態を持たない単純なサーバーを Kubernetes で複数台立ち上げる

* ウェブサーバーのような状態を持たないサーバーを複数台立ち上げる場合は Deployment を使って立ち上げることで常に設定した台数起動した状態になってくれて便利
* `kubectl apply -f deployment.yml` として起動する

```yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2 # Pod を常に 2 つ起動させる
  template: # 起動する Pod の設定テンプレート
    metadata:
      # kubectl コマンドや Service などから Pod を参照する際にこのラベルを使って絞り込むことができる
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx # 今回は 1 Pod に nginx のコンテナが一つだけ入っているものを立ち上げる
        image: nginx:1.7.9
        ports:
        - containerPort: 80
```

* さらに詳しくは[公式ドキュメント](https://kubernetes.io/docs/tasks/run-application/run-stateless-application-deployment/)を参考に


# 複数台でクラスタを作って動作するミドルウェアを Kubernetes で立ち上げる

* 複数台で相互に通信しあって動作するミドルウェアである Zookeeper のクラスタ群を立ち上げる設定について https://github.com/Yolean/kubernetes-kafka/tree/master/zookeeper を見ながら確認する

## Namespace の設定

* [00namespace.yml](https://github.com/Yolean/kubernetes-kafka/blob/master/zookeeper/00namespace.yml)
* Pod, Service, StatefulSet, ConfigMap などに名前をつけて管理しやすくする

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: kafka
```

## ConfigMap の設定

* [10zookeeper-config.yml](https://github.com/Yolean/kubernetes-kafka/blob/master/zookeeper/10zookeeper-config.yml)
* 設定ファイルを ConfigMap を使って定義しておく
* StatefulSet の Pod のボリューム定義（spec.template.spec.volumes）でボリュームとして読み込まれ、テンプレート定義（spec.template.spec.containers.[n].volumeMounts）でボリューム名とマウント先パスを指定してマウントされる

```yaml
kind: ConfigMap
metadata:
  name: zookeeper-config
  namespace: kafka
apiVersion: v1
data:
  init.sh: |-
    #!/bin/bash
    set -x

    [ -z "$ID_OFFSET" ] && ID_OFFSET=1
    export ZOOKEEPER_SERVER_ID=$((${HOSTNAME##*-} + $ID_OFFSET))
    echo "${ZOOKEEPER_SERVER_ID:-1}" | tee /var/lib/zookeeper/data/myid
    sed -i "s/server\.$ZOOKEEPER_SERVER_ID\=[a-z0-9.-]*/server.$ZOOKEEPER_SERVER_ID=0.0.0.0/" /etc/kafka/zookeeper.properties

  zookeeper.properties: |-
    tickTime=2000
    dataDir=/var/lib/zookeeper/data
    dataLogDir=/var/lib/zookeeper/log
    clientPort=2181
    initLimit=5
    syncLimit=2
    server.1=pzoo-0.pzoo:2888:3888:participant
    server.2=pzoo-1.pzoo:2888:3888:participant
    server.3=pzoo-2.pzoo:2888:3888:participant
    server.4=zoo-0.zoo:2888:3888:participant
    server.5=zoo-1.zoo:2888:3888:participant

  log4j.properties: |-
    log4j.rootLogger=INFO, stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n

    # Suppress connection log messages, three lines per livenessProbe execution
    log4j.logger.org.apache.zookeeper.server.NIOServerCnxnFactory=WARN
    log4j.logger.org.apache.zookeeper.server.NIOServerCnxn=WARN
```

## Service の設定

### Headless Service の設定

* [20pzoo-service.yml](https://github.com/Yolean/kubernetes-kafka/blob/master/zookeeper/20pzoo-service.yml)
* Headless Service とはクラスタ内のコンテナ同士が通信し合うためのインターフェイス
  * 外部向けの IP アドレスを持たないため headless と呼ばれる

```yaml
apiVersion: v1
kind: Service
metadata:
  # サービス名の定義
  # StatefulSet の Pod の割り当てサービス設定（spec.serviceName）でこの名前を指定する
  # これにより `POD_NAME.SERVIE_NAME.NAMESPACE.svc.cluster.local` で名前解決ができるようになるので、この名前で設定などを行う
  name: pzoo
  namespace: kafka
spec:
  # 公開ポートの定義
  ports:
  - port: 2888
    name: peer
  - port: 3888
    name: leader-election
  clusterIP: None
  # 適用される Pod の絞り込み条件の定義
  selector:
    app: zookeeper
    storage: persistent
```

* [21zoo-service.yml](https://github.com/Yolean/kubernetes-kafka/blob/master/zookeeper/21zoo-service.yml) の方は永続化ストレージを持たない Pod 用のサービス設定

### Client Service の設定

* [30service.yml](https://github.com/Yolean/kubernetes-kafka/blob/master/zookeeper/30service.yml)
* 外部に公開するサービスの定義を行う

```yaml
# the headless service is for PetSet DNS, this one is for clients
apiVersion: v1
kind: Service
metadata:
  name: zookeeper
  namespace: kafka
spec:
  ports:
  - port: 2181
    name: client
  selector:
    app: zookeeper
```

## StatefulSet の設定

* [50pzoo.yml](https://github.com/Yolean/kubernetes-kafka/blob/master/zookeeper/50pzoo.yml)
* Pod のテンプレート定義を持ち、状態持った Pod の集合を生成する

```yaml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: pzoo
  namespace: kafka
spec:
  # pzoo サービスを使って名前解決を行えるように
  serviceName: "pzoo"
  # 3 台立ち上げる
  replicas: 3
  template:
    metadata:
      labels:
        app: zookeeper
        storage: persistent
      annotations:
    spec:
	  # グレースフルシャットダウンの間隔
      terminationGracePeriodSeconds: 10
	  # Pod の作成時に 1 度だけ立ち上げられるコンテナのテンプレート定義
	  # ここでは ConfigMap に定義したスクリプトを使って設定ファイルの初期化処理を行なっている
	  # 実際に Zookeeper が起動するコンテナとは違うコンテナとして実行されるが、両コンテナでマウントされている設定ファイルのボリュームに対して変更を行うので変更が引き継がれる
      initContainers:
      - name: init-config
        # イメージの実体は https://github.com/solsson/dockerfiles/tree/master/initutils にある（Docker Hub から参照）
        image: solsson/kafka:0.11.0.0@sha256:b27560de08d30ebf96d12e74f80afcaca503ad4ca3103e63b1fd43a2e4c976ce
        command: ['/bin/bash', '/etc/kafka/init.sh']
        volumeMounts:
        # 設定ファイルのマウント
        - name: config
          mountPath: /etc/kafka
        - name: data
          mountPath: /var/lib/zookeeper/data
	  # 実際に Zookeeper が起動するコンテナの設定
      containers:
      - name: zookeeper
        # イメージの実体は https://github.com/solsson/dockerfiles/tree/master/kafka にある（Docker Hub から参照）
        image: solsson/kafka:0.11.0.0@sha256:b27560de08d30ebf96d12e74f80afcaca503ad4ca3103e63b1fd43a2e4c976ce
        env:
        - name: KAFKA_LOG4J_OPTS
          value: -Dlog4j.configuration=file:/etc/kafka/log4j.properties
        command:
        - ./bin/zookeeper-server-start.sh
        - /etc/kafka/zookeeper.properties
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 2888
          name: peer
        - containerPort: 3888
          name: leader-election
        resources:
          requests:
            cpu: 10m
            memory: 100Mi
        # コンテナのヘルスチェックコマンドの定義
        readinessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - '[ "imok" = "$(echo ruok | nc -w 1 127.0.0.1 2181)" ]'
        volumeMounts:
        # 設定ファイルのマウント
        - name: config
          mountPath: /etc/kafka
        # 永続化ストレージのマウント
        - name: data
          mountPath: /var/lib/zookeeper/data
      volumes:
      - name: config
        configMap:
          name: zookeeper-config
  # 永続化ストレージの作成要求
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi
```

* [51zoo.yml](https://github.com/Yolean/kubernetes-kafka/blob/master/zookeeper/51zoo.yml) の方は永続化ストレージを持たない Pod 用のサービス設定

## クラスタを立ち上げる

* `kubectl apply -f zookeeper/` として立ち上げる