Wed, Oct 11, 2017

Kubernetesのチュートリアルをやる

Kubernetesのチュートリアルをやる

Kubernetes 1.8が出たので、Minikubeを触ってみる」の続き。 Minikubeのセットアップまではできたので、Kubernetes Basicsというチュートリアルをやりながら、Goslingsをデプロイする。

Kubernetes Basics - 概要

Kubernetes Basicsは、公式のチュートリアルで、Kubernetesクラスタのオーケストレーションの基本を学ぶことができるもの。 以下の6つのモジュールからなる。

  1. Kubernetesクラスタを作る
  2. アプリをデプロイする
  3. アプリを調査する
  4. アプリを公開する
  5. アプリをスケールする
  6. アプリをアップデートする

チュートリアルで使うのはMinikubeだけど、自分でセットアップする必要はない。 Katacodaという、ブラウザ上でIT技術を学べるプラットフォームがあり、Kubernetes Basicsはそれを利用して、ブラウザ上のターミナルからホステッドMinikubeを操作できるようにしている。

が、前回の記事で自PC上にMinikubeをセットアップしたので、そちらを使うことにする。


Kubernetes Basics - モジュール 1: Kubernetesクラスタを作る

Minikubeを起動してkubectlでクラスタの状態をみるだけのモジュール。

これは前回の記事でカバーしている。

Kubernetes Basics - モジュール 2: アプリをデプロイする

アプリ(i.e. コンテナ)をデプロイするにはDeploymentオブジェクトを作る。 MasterはDeploymentのspecに従って各ノードにアプリのインスタンスをスケジューリングする。 Deploymentは、アプリが落ちたら再起動してくれる、つまりself-healingも実現する。

Deploymentオブジェクトを作るコマンドはkubectl run <オブジェクト名> --image=<Dockerイメージ名>。 Goslingsをこれでデプロイする。

Goslingsコンテナは3つの引数を受け取り、指定したポートでWebサーバを起動する。 --portオプションでそのポートをexposeするようにして、--の後にコンテナに渡す引数を記述する。

C:\Users\kaitoy>kubectl run goslings --image=kaitoy/goslings:latest --port 8080 -- 8080 /tmp https://github.com/kaitoy/
deployment "goslings" created

C:\Users\kaitoy>kubectl get deployment
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
goslings   1         1         1            1           27s

デプロイできた。 裏でPodも作られていて、アプリが起動されている。

C:\Users\kaitoy>kubectl get pods
NAME                        READY     STATUS              RESTARTS   AGE
goslings-1210510689-6w5tf   0/1       ContainerCreating   0          1m

(kubectl getに指定するのは、省略形のdeployとかpoでもいい。)


Podは隔離されたネットワークで動くので、そのままではPod同士は通信できるけど、外からはアクセスできない。 kubectlでプロキシを作ってやることで、外からアクセスできるようになる。

C:\Users\kaitoy>kubectl proxy
Starting to serve on 127.0.0.1:8001

これで、kube-apiserverへのプロキシがローカルホストで起動した。 この状態でhttp://localhost:8001を開くと、kube-apiserverのAPI一覧が見れる。 例えば、http://localhost:8001/versionにアクセスすると、以下のJSONデータが返ってくる。

{
  "major": "1",
  "minor": "7",
  "gitVersion": "v1.7.0",
  "gitCommit": "d3ada0119e776222f11ec7945e6d860061339aad",
  "gitTreeState": "dirty",
  "buildDate": "2017-10-04T09:25:40Z",
  "goVersion": "go1.8.3",
  "compiler": "gc",
  "platform": "linux/amd64"
}


各Podへも以下のURLでアクセスできる。

http://localhost:8001/api/v1/proxy/namespaces/default/pods/<Pod名>/

Pod名の部分はkubectl getで確認できる。

C:\Users\kaitoy>kubectl get po
NAME                        READY     STATUS    RESTARTS   AGE
goslings-1210510689-6w5tf   1/1       Running   0          24m

実際に、http://localhost:8001/api/v1/proxy/namespaces/default/pods/goslings-1210510689-6w5tf/をブラウザで開いたら、GoslingsのGUIが出た。 ヒュー。

goslings-proxy

Kubernetes Basics - モジュール 3: アプリを調査する

以下のコマンドで、アプリの状態を調査するモジュール。

  • kubectl get: リソースをリスト表示する。
  • kubectl describe: リソースの詳細情報を表示する。
  • kubectl logs: コンテナのログを表示する。docker logs的な。
  • kubectl exec: コンテナ内でコマンドを実行する。docker exec的な。


kubectl getはさんざんやったので飛ばして、kubectl describeしてみる。

C:\Users\kaitoy>kubectl describe po
Name:           goslings-1210510689-6w5tf
Namespace:      default
Node:           minikube/192.168.99.100
Start Time:     Tue, 10 Oct 2017 21:51:48 +0900
Labels:         pod-template-hash=1210510689
                run=goslings
Annotations:    kubernetes.io/created-by={"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ReplicaSet","namespace":"default","name":"goslings-1210510689","uid":"c74b6518-adb9-11e7-88a0-08002798178d...
Status:         Running
IP:             172.17.0.2
Created By:     ReplicaSet/goslings-1210510689
Controlled By:  ReplicaSet/goslings-1210510689
Containers:
  goslings:
    Container ID:       docker://ce90460886c9555f7748bf59e8d9892f05c05020e7841154ee85713d6d9b0c2d
    Image:              kaitoy/goslings:latest
    Image ID:           docker-pullable://kaitoy/[email protected]:a587e3c5f202cdaa6d4d5a9c4f6a01ba6f4782e00277c3a18c77dd034daa0109
    Port:               8080/TCP
    Args:
      8080
      C:/Users/kaitoy/AppData/Local/Temp
    State:              Running
      Started:          Tue, 10 Oct 2017 21:55:54 +0900
    Ready:              True
    Restart Count:      0
    Environment:        <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-cqq59 (ro)
Conditions:
  Type          Status
  Initialized   True
  Ready         True
  PodScheduled  True
Volumes:
  default-token-cqq59:
    Type:       Secret (a volume populated by a Secret)
    SecretName: default-token-cqq59
    Optional:   false
QoS Class:      BestEffort
Node-Selectors: <none>
Tolerations:    <none>
Events:
  FirstSeen     LastSeen        Count   From                    SubObjectPath                   Type            Reason
                Message
  ---------     --------        -----   ----                    -------------                   --------        ------
                -------
  45m           45m             1       default-scheduler                                       Normal          Scheduled               Successfully assigned goslings-1210510689-6w5tf to minikube
  45m           45m             1       kubelet, minikube                                       Normal          SuccessfulMountVolume   MountVolume.SetUp succeeded for volume "default-token-cqq59"
  45m           45m             1       kubelet, minikube       spec.containers{goslings}       Normal          Pulling
                pulling image "kaitoy/goslings:latest"
  41m           41m             1       kubelet, minikube       spec.containers{goslings}       Normal          Pulled
                Successfully pulled image "kaitoy/goslings:latest"
  41m           41m             1       kubelet, minikube       spec.containers{goslings}       Normal          Created
                Created container
  41m           41m             1       kubelet, minikube       spec.containers{goslings}       Normal          Started
                Started container

Podの詳細な情報が出た。 EventsのとこにKubernetesの頑張りが見えて面白い。


次はkubectl logs

C:\Users\kaitoy>kubectl logs goslings-1210510689-6w5tf

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.3.RELEASE)

2017-10-10 12:56:02.498  INFO 6 --- [           main] c.g.kaitoy.goslings.server.Application   : Starting Application on goslings-1210510689-6w5tf with PID 6 (/usr/local/src/goslings/goslings-server/build/libs/goslings-server-0.0.1.jar started by root in /usr/local/src/goslings)
(snip)

GoslingsはSpring Bootでできてるので、そのログが出てる。


次はkubectl execを試す。

C:\Users\kaitoy>kubectl exec goslings-1210510689-6w5tf env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=goslings-1210510689-6w5tf
KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.0.0.1:443
LANG=C.UTF-8
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
JAVA_VERSION=8u111
JAVA_DEBIAN_VERSION=8u111-b14-2~bpo8+1
CA_CERTIFICATES_JAVA_VERSION=20140324
HOME=/root

envコマンドを実行し、コンテナ内の環境変数一覧を出せた。 Kubernetes関係の変数が定義されていることが分かる。

docker execと同様に、-itオプションを付ければ、コンテナ内に「入る」こともできる。

C:\Users\kaitoy>kubectl exec -it goslings-1210510689-6w5tf sh
# ls
Dockerfile  _config.yml  build.log     goslings-server  gradle.properties  gradlew.bat
# exit

C:\Users\kaitoy>

Kubernetes Basics - モジュール 4: アプリを公開する

Serviceオブジェクト扱うモジュール。

例えば、以下のような状況にあるとする。

  • PodがあるNodeで動いていたんだけど、そのNodeが死んだので、Kubernetesが別のNodeにPodを起動しなおしてくれた。
  • 同じコンテナイメージを3つのPodで動かして、負荷分散させたい。

こういう場合、KubernetesはPod毎に固有のIPアドレスを割り当てるので、Podにアクセスするユーザはアクセス先が不安定でめんどいことになる。 この問題を解決してくれるのがServiceで、こいつは、Podを抽象化して、安定したIPアドレスを公開してくれる。 しかもそれはクラスタ外からアクセスできる。

PodとServiceの紐づけには、ラベルとセレクタというものが使われる。


Serviceの情報はDeploymentとかと同様にkubectl getで見れる。

C:\Users\kaitoy>kubectl get svc
NAME         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   10.0.0.1     <none>        443/TCP   1d

ここで出ているkubernetesというのは、Minikubeがデフォルトで作るService。


Serviceオブジェクトは、kubectl exposeで作ることができる。

goslingsという名のDeploymentに対し、NodePortのServiceを作り、コンテナの8080ポートを公開するコマンドは以下のようになる。

C:\Users\kaitoy>kubectl expose deploy/goslings --type=NodePort --port 8080
service "goslings" exposed

C:\Users\kaitoy>kubectl get services
NAME         CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
goslings     10.0.0.69    <nodes>       8080:32406/TCP   11s
kubernetes   10.0.0.1     <none>        443/TCP          1d

C:\Users\kaitoy>kubectl describe services/goslings
Name:                   goslings
Namespace:              default
Labels:                 run=goslings
Annotations:            <none>
Selector:               run=goslings
Type:                   NodePort
IP:                     10.0.0.69
Port:                   <unset> 8080/TCP
NodePort:               <unset> 32406/TCP
Endpoints:              172.17.0.2:8080
Session Affinity:       None
Events:                 <none>

goslingsという名前のServiceができた。 上記kubectl describeの出力のNodePortのとこに書いてあるのが外部にさらされたポート。

minikube ipを実行すると、

C:\Users\kaitoy>minikube ip
192.168.99.100

MinikubeのVMのIPアドレスも分かるので、NodePortのポートと合わせて、http://192.168.99.100:32406にブラウザでアクセスしたら、GoslingsのGUI見れた。 ヒュー。

goslings-service


ところで、上記kubectl describeの出力を見ると、特に指定はしなかったが、Podにrun=goslingsというLabelが付いていることが分かる。 Serviceのdescribeを見ると、

C:\Users\kaitoy>kubectl describe svc goslings
Name:                   goslings
Namespace:              default
Labels:                 run=goslings
Annotations:            <none>
Selector:               run=goslings
Type:                   NodePort
IP:                     10.0.0.69
Port:                   <unset> 8080/TCP
NodePort:               <unset> 32406/TCP
Endpoints:              172.17.0.2:8080
Session Affinity:       None
Events:                 <none>

run=goslingsというSelectorがServiceに紐づいている。 つまり、ServiceとPodが、run=goslingsで紐づいているというわけだ。


Labelはクエリ時のフィルタとかにも使える。

C:\Users\kaitoy>kubectl get po -l run=goslings
NAME                        READY     STATUS    RESTARTS   AGE
goslings-1210510689-6w5tf   1/1       Running   0          1h

後からラベル付けることもできる。

C:\Users\kaitoy>kubectl label pod goslings-1210510689-6w5tf ver=1.2.3
pod "goslings-1210510689-6w5tf" labeled

Kubernetes Basics - モジュール 5: アプリをスケールする

アプリのスケールアウト・スケールインを学ぶモジュール。

Deploymentの定義でPodのレプリカ数を変えると、その数に合わせてKubernetesがPodを起動したり止めたりしてくれてスケールできる仕組み。 レプリカを作っておくとローリングアップデートできるのも利点。 オートスケール機能もあるけど、それはチュートリアルでは扱われない。

複数のPodで負荷分散するということなので、Serviceでロードバランシングするのが前提。


現在のDeploymentの状態をみる。

C:\Users\kaitoy>kubectl get deploy
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
goslings   1         1         1            1           1h

Podのレプリカ数は、期待してる(DESIRED)のが1で、今(CURRENT)も1。

スケールアウトするには、kubectl scaleコマンドでレプリカ数を増やしてやる。

C:\Users\kaitoy>kubectl scale deploy/goslings --replicas=3
deployment "goslings" scaled

C:\Users\kaitoy>kubectl get deploy
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
goslings   3         3         3            3           1h

C:\Users\kaitoy>kubectl get po -o wide
NAME                       READY     STATUS    RESTARTS   AGE       IP           NODE
goslings-442066424-jn1lw   1/1       Running   0          1h        172.17.0.2   minikube
goslings-442066424-rdw4k   1/1       Running   0          1m        172.17.0.3   minikube
goslings-442066424-rwwjw   1/1       Running   0          1m        172.17.0.4   minikube

レプリカが3個になった。

スケールインするには、kubectl scaleコマンドでレプリカ数を減らす。

C:\Users\kaitoy>kubectl scale deploy/goslings --replicas=2
deployment "goslings" scaled

C:\Users\kaitoy>kubectl get po
NAME                       READY     STATUS        RESTARTS   AGE
goslings-442066424-0mv4x   1/1       Terminating   0          1m
goslings-442066424-34h1f   1/1       Running       0          1m
goslings-442066424-kmn3p   1/1       Running       0          17m

C:\Users\kaitoy>kubectl get po
NAME                       READY     STATUS    RESTARTS   AGE
goslings-442066424-34h1f   1/1       Running   0          1m
goslings-442066424-kmn3p   1/1       Running   0          17m

kubectl scale直後のkubectl get poでは、一つのPodを停止している最中の様子が見えていて、再度のkubectl get poではレプリカが2個になったのが確認できた。

この状態がKubernetes Basicsで作るクラスタの最終形で、図にすると以下の感じ。

objects

Kubernetes Basics - モジュール 6: アプリをアップデートする

デプロイしたアプリのアップデート(i.e. コンテナイメージの変更)を学ぶモジュール。

Deploymentの定義をいじってコンテナイメージを変えてやると、その中のPodを新しいイメージで順次(デフォルトだと一つ一つ)起動しなおしてくれる。

アプリのアップデートはバージョン管理もされて、ロールバックもできる。


コンテナイメージを変更するには、kubectl set imageコマンドを使う。 goslingsという名のDeployment内の、goslingsという名のContainerのイメージをkaitoy/goslings:hogeに変更するコマンドは以下。

C:\Users\kaitoy>kubectl set image deploy/goslings goslings=kaitoy/goslings:hoge
deployment "goslings" image updated

実際にはkaitoy/goslings:hogeというイメージはないので、イメージのPullに失敗したというエラー(ErrImagePull)になる。

C:\Users\kaitoy>kubectl get po
NAME                       READY     STATUS         RESTARTS   AGE
goslings-274047280-jxmmh   0/1       ErrImagePull   0          9s
goslings-274047280-rgg2v   0/1       ErrImagePull   0          8s
goslings-442066424-34h1f   1/1       Terminating    0          1h
goslings-442066424-kmn3p   1/1       Running        0          1h


イメージ変更前に戻すには、kubectl rollout undoする。

C:\Users\kaitoy>kubectl rollout undo deploy/goslings
deployment "goslings" rolled back

C:\Users\kaitoy>kubectl rollout status deploy/goslings
deployment "goslings" successfully rolled out

C:\Users\kaitoy>kubectl get po
NAME                       READY     STATUS    RESTARTS   AGE
goslings-442066424-kmn3p   1/1       Running   0          1h
goslings-442066424-m3873   1/1       Running   0          5s

無事に戻った。

番外編1 - 3つのオブジェクト管理手法

Kubernetesオブジェクトを管理する手法は大きく3つある

管理手法 いじる対象 難易度
命令的コマンド 生のオブジェクト 簡単
命令的オブジェクト設定 個々のファイル 普通
宣言的オブジェクト設定 ディレクトリに入ったファイル群 難しい


Kubernetes Basicsでやってた手法は一番上の命令的コマンド。 これは簡単で分かりやすい。 けど、何度も同じようなデプロイするならコマンドを毎回打つのが面倒だし、作成されるオブジェクトは明示的じゃないし、変更管理もできない。 この手法は主に開発中に使う。


二つ目の手法の命令的オブジェクト設定では、YAML(かJSON)ファイルにオブジェクト定義を書いておいて、kubectlに渡す。 この手法だと、定義ファイルをオブジェクトのテンプレートとして使えるし、Gitとかのリポジトリに入れることでバージョン管理・変更管理できる。 けど、Kubernetesのオブジェクトモデルを理解しないと使えない。 (オブジェクト定義の詳細はAPIリファレンスを参照。)

命令的オブジェクト設定は以下のような形でやる。

$ kubectl create -f nginx.yaml
$ kubectl delete -f nginx.yaml -f redis.yaml
$ kubectl replace -f nginx.yaml


三つ目の手法の宣言的オブジェクト設定では、設定フォルダに定義ファイル群を置く。 ユーザは明示的にcreateとかupdateとか指示する必要が無く、kubectlが勝手に判断してくれる。 生のオブジェクトを直接いじった後、同じオブジェクトの設定を設定ファイルで変更しても、 両者の変更が上手くマージされる。

なんかすごいけど、上手くいかなかったときのデバッグがむずい。

宣言的オブジェクト設定は以下のような形でやる。

$ kubectl apply -R -f configs/

番外編2 - 命令的オブジェクト設定

3つの手法の内、命令的オブジェクト設定でGoslingsをMinikubeにデプロイしてみる。

まず、Kubernetes Basicsで作ったオブジェクトを消すため、MinikubeのVMを作り直す。

C:\Users\kaitoy>minikube stop
Stopping local Kubernetes cluster...
Machine stopped.

C:\Users\kaitoy>minikube delete
Deleting local Kubernetes cluster...
Machine deleted.

C:\Users\kaitoy>minikube start --vm-driver virtualbox --kubernetes-version v1.7.0
Starting local Kubernetes v1.7.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.


次に定義ファイルを書いていく。

APIリファレンスのDeploymentのとこをみると、Kubernetes Basicsの最終形と同じようなDeploymentを作る定義は以下のように書ける。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: goslings-sample
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: goslings
        ver: latest
    spec:
      containers:
        - name: goslings
          image: kaitoy/goslings:latest
          ports:
            - name: http
              containerPort: 8080
          args:
            - '8080'
            - /tmp
            - https://github.com/kaitoy/

同様に、Serviceは、APIリファレンスのServiceのとこみると以下のように書ける。

kind: Service
apiVersion: v1
metadata:
  name: goslings-sample
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app: goslings
  type: NodePort


で、それぞれのYAMLファイルをkubectl createに渡してやると、Goslingsデプロイ完了。

C:\Users\kaitoy\kubeTest>kubectl create -f deploy_goslings.yml
deployment "goslings-sample" created

C:\Users\kaitoy\kubeTest>kubectl create -f service_goslings.yml
service "goslings-sample" created


オブジェクトの種類もパラメータも大量にあるので、使いこなすのは難しそう。