Apr 17, 2018

Kubernetes Up & Running


入門 Kubernetesを読みました。本の中で紹介されているyamlはkubernetes-up-and-running/examplesにあるみたいです。

# Docker
docker run -d --name kuard --publish 8081:8080 --memory 200m --memory-swap 1G --cpu-shares 1024 gcr.io/kuar-demo/kuard-amd64:1
docker stop kuard; docker rm kuard

# Kubernetes
kubectl version
kubectl get componentstatuses

controller-manager' クラスタ上の振る舞いを制御する
scheduler 各Podをクラスタ内のそれぞれのノードに配置する
etcd クラスタのすべてのAPIオブジェクトが保存されるストレージ

ノードの表示する
kubectl get nodes
masterノード APIサーバーやスケジューラのコンテナが動く
workerノード ユーザが作成したコンテナが動く

特定ノードの表示する
kubectl describe nodes xxx

プロキシの一覧を表示する
kubectl get daemonSets --namespace=kube-system kube-proxy

DNSの一覧を表示する
kubectl get deployments --namespace=kube-system kube-dns

DNSサーバをロードバランシングするためのKubernetes Serviceを表示する
kubectl get services --namespace=kube-system kube-dns

UIサーバの確認
kubectl get deployments --namespace=kube-system kube-dashboard

ロードバランスのためのServiceを表示
kubectl get services --namespace=kube-system kube-dashboard

UIにアクセスする
kubectl proxy

# よく使うkubectlコマンド
Namespaceはオブジェクトの集まりを入れるフォルダのようなイメージ

実行中のコンテナのログを確認する
kubectl logs podname
kubectl logs -f podname

ファイルのやりとりを行う
kubectl cp podname:/path/to/remote/file /path/to/local/file

実行中のコンテナにログインする
kubectl exec -it podname -- bash

Kubernetesではコンテナではなく、Podが最小のデプロイ単位であり、1つのPodないのコンテナはすべて同じマシン上に配置される
スケールさせる時もPod単位なので、例えばWordPressとMySQLを同じPodにすると片方だけスケールさせることができない
コンテナが違うマシンに配置されても正常に動作できるのであれば、Podを分けるのが正解な可能性が高い

Podの作成、確認、削除
kubectl run kuard --image=gcr.io/kuar-demo/kuard-amd64:1
kubectl get pods
kubectl delete deployments/kuard

Podをyamlで作成する
apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:1
      name: kuard
      ports:
        - containerPort: 8081
          name: http
          protocol: TCP

kubectl apply -f kuard-pod.yaml

Podの詳しい情報の確認
kubectl describe pods kuard

詳細を確認するオプション
kubectl get pods -o wide
kubectl get pods -o json
kubectl get pods -o yaml

Podの削除
kubectl delete pods/kuard
kubectl delete -f kuard-pod.yaml

Podの削除コマンドを実行した時に、まずTerminatingという状態になりデフォルト30秒の猶予期間(grace period)後に削除される。Terminating状態になるとそのPodはリクエストを受け付けない。この猶予期間によって処理中の可能性のあるリクエストをPodを削除する前に終わられることができる。

ポートフォワード(コマンド実行中のみ有効)
kubectl port-forward kuard 8081:8080

ログの確認
kubectl logs kuard
kubectl logs -f kuard
kubectl logs --previous kuard

ヘルスチェック
プロセスヘルスチェック機能によってアプリケーションのメインプロセスが動いているか常に監視し、動いていない場合はKubernetesがプロセスを再起動する。プロセスが生きているものの、リクエストには応答できていない場合にはこれだけでは対応できない。

Liveness probe
apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:1
      name: kuard
      livenessProbe:
        httpGet:
          path: /healthy
          port: 8080
        initialDelaySeconds: 5
        timeoutSeconds: 1
        periodSeconds: 10
        failureThreshold: 3
      ports:
        - containerPort: 8081
          name: http
          protocol: TCP

initialDelaySecondsはPod内の全コンテナが作成されてから5秒経過するまでリクエストを送らない
timeoutSecondsの1秒以内に応答する必要がある
HTTPチェックだけでなくtcpSocketもサポートしている
exec監視も可能で、コンテナ内でスクリプトの返り値が0なら成功、0以外なら失敗のようなこともできる
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/

リソース要求 最低限必要なリソース
リソース制限 最大リソース量
apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:1
      name: kuard
      resources:
        requests:
          cpu: "500m"
          memory: "128Mi"
      ports:
        - containerPort: 8081
          name: http
          protocol: TCP
1CPUの半分および128MBをリソース要求に設定する例
CPUのリソース要求はLinuxカーネルのcpu-shares機能を使って実装されている

apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:1
      name: kuard
      resources:
        requests:
          cpu: "500m"
          memory: "128Mi"
        limits:
          cpu: "1000m"
          memory: "256Mi"         
      ports:
        - containerPort: 8081
          name: http
          protocol: TCP
リソース制限に1CPU、メモリ256MBを設定する例

データの永続化
spec.volumnsセクションもしくはvolumnMountsを使用する
apiVersion: v1
kind: Pod
metadata:
  name: kuard
spec:
  volumes:
    - name: "kuard-data"
      hostPath:
        path: "/var/lib/kuard"
  containers:
    - image: gcr.io/kuar-demo/kuard-amd64:1
      name: kuard
      volumeMounts:
        - mountPath: "/data"
          name: "kuard-data"
      ports:
        - containerPort: 8081
          name: http
          protocol: TCP
kuard-dataというボリュームを定義し、kuardコンテナの/dataにマウントする例
empDirはキャッシュに便利
NFSサーバーを利用することも可能
  volumes:
    - name: "kuard-data"
      nfs:
        server: my.nfs.server.local
        path: "/exports"


# LabelとAnnotation
LabelとAnnotationの使い分けは、好みにもよる。セレクタとして使いたくなったらLabelに昇格させるのが良い

kubectl run alpaca-prod --image=gcr.io/kuar-demo/kuard-amd64:1 --replicas=2 --labels="ver=1,app=alpaca,env=prod"
kubectl run alpaca-test --image=gcr.io/kuar-demo/kuard-amd64:2 --replicas=1 --labels="ver=2,app=alpaca,env=test"
kubectl run bandicoot-prod --image=gcr.io/kuar-demo/kuard-amd64:2 --replicas=2 --labels="ver=2,app=bandicoot,env=prod"
kubectl run bandicoot-staging --image=gcr.io/kuar-demo/kuard-amd64:2 --replicas=1 --labels="ver=2,app=bandicoot,env=staging"

Labelの確認
kubectl get deployments --show-labels
kubectl get deployments -L env
kubectl get pods --selector="ver=2"
kubectl get pods --selector="ver!=2"
kubectl get pods --selector="app=bandicoot,ver=2"
kubectl get pods --selector="app in (alpaca,bandicoot)"
kubectl get pods --selector="app notin (alpaca,bandicoot)"
kubectl get deployments --selector="canary"
kubectl get deployments --selector="!canary"

Labelの追加(ReplicaSetやPodは変更されないので注意)
kubectl label deployments alpaca-test "canary=true"

Deploymentの削除
kubectl delete deployments --selector="canary"
kubectl delete deployments --all

# サービスディスカバリ
kubectl run alpaca-prod --image=gcr.io/kuar-demo/kuard-amd64:1 --replicas=3 --port=8080 --labels="ver=1,app=alpaca,env=prod"
kubectl expose deployment alpaca-prod
kubectl run bandicoot-prod --image=gcr.io/kuar-demo/kuard-amd64:2 --replicas=2 --port=8080 --labels="ver=2,app=bandicoot,env=prod"


kubectl expose deployment bandicoot-prod
kubectl get services -o wide

ALPACA_POD=$(kubectl get pods -l app=alpaca -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward $ALPACA_POD 48858:8080

Readiness probe
kubectl edit deployment/alpaca-prod

    spec:
      containers:
      - image: gcr.io/kuar-demo/kuard-amd64:1
        imagePullPolicy: IfNotPresent
        name: alpaca-prod
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          periodSeconds: 2
          initialDelaySeconds: 0
          failureThreshold: 3
          successThreshold: 1
        ports:
        - containerPort: 8080
          protocol: TCP

ServiceのEndpointsに対するトラフィックの確認
kubectl get endpoints alpaca-prod --watch

Endpointsオブジェクトの確認
kubectl describe endpoints alpaca-prod

新しいServiceが作られるとkube-proxyは、iptablesのルールをホストのカーネルに設定する

# ReplicaSet
冗長性、スケール、シャーディングのために用意されているのがReplicaSet
ReplicaSetはPodのLabelを使ってクラスタの状態を監視する
不具合のあるPodのLabelだけを変更し、 ReplicaSetからPodを切り離すと、Podは起動したままになるのでデバッグがしやすくなる

ReplicaSetの最小限の定義例(kuard-rs.yaml)
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  name: kuard-rs
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: kuard
        version: "2"
    spec:
      containers:
        - name: kuard
          image: "gcr.io/kuar-demo/kuard-amd64:2"
kubectl apply -f kuard-rs.yaml

PodからReplicaSetの特定
kubectl get pods kuard-rs-4m2nj -o yaml | grep owner -A 

ReplicaSetに対応するPodの集合の特定
kubectl get pods -l app=kuard,version=2

kubectl scaleを使った命令的スケール(もし使用する場合はyamlも書き換えること)
kubectl scale replicasets kuard-rs --replicas=4

宣言的スケール
yamlのreplicasの値を書き換え後にkubectl apply -f kuard-rs.yaml

水平Podオートスケーリング(HPA Horizontal Pod Autoscaling)
heapsterというPodによりメトリクスを追跡し、HPAがスケーリングの判断を行う時に使用するメトリクスを取得するAPIを提供する
クラスタ内にheapsterが存在しない場合はHPAは正常に動作しない
Podのレプリカを追加することを水平スケール、Podに必要なリソースを増やすことを垂直スケールと区別している

CPU使用率を元にしたオートスケール
kubectl autoscale rs kuard-rs --min=2 --max=5 --cpu-percent=80
CPU使用率80%を閾値にして、レプリカ数2〜5の間でスケールするオートスケーラ

確認
kubectl get hpa

ReplicaSetを削除すると管理しているPodも一緒に削除される
kubectl delete rs kuard-rs

ReplicaSetのみ削除したい場合
kubectl delete rs kuard-rs --cascade=false

# Daemon Set
ReplicaSetと違い、ノードセレクタを使わない限り、全ノードにPodを作る

fluentd.yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    app: fluentd
spec:
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      containers:
      - name: fluentd
        image: fluent/fluentd:v0.14.10
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
kubectl apply -f fluentd.yaml

NodeにLabelを追加し、確認
kubectl label nodes minikube ssd=true
kubectl get nodes --show-lables
kubectl get nodes --selector ssd

nginx-fast-storage.yaml(ssd=trueというLabelがノードでのみ動くDaemonSet)
apiVersion: extensions/v1beta1
kind: "DaemonSet"
metadata:
  labels:
    app: nginx
    ssd: "true"
  name: nginx-fast-storage
spec:
  template:
    metadata:
      labels:
        app: nginx
        ssd: "true"
    spec:
      nodeSelector:
        ssd: "true"
      containers:
        - name: nginx
          image: nginx:1.10.0
他のノードにssd=trueというLabelを追加すれば、そのノードにもデプロイされる。逆にLabelを削除するとそのPodはDaemonSetコントローラから削除される

forループを用いたPodの削除
PODS=$(kubectl get pods -o jsonpath -template='{.ites[*].metadata.name}')
for x in $PODS; do
  kubectl delete pods ${x}
  sleep 60
done

ローリングアップデートを使うにはspec.updateStrategy.typeフィールドにRollingUpdateを設定する。ローリングアップデート中のステータスの確認はkubectl rolloutコマンドを使用する
kubectl rollout status daemonSets nginx-fast-storage

DaemonSetの削除
kubectl delete -f fluentd.yaml

# Job
1回限りの処理を行うのに便利なのがJobオブジェクト
kubectl run -i oneshot --image=gcr.io/kuar-demo/kuard-amd64:1 --restart=OnFailure -- --keygen-enable --keygen-exit-on-complete --keygen-num-to-gen 10
-iオプションはコマンドが対話モードであることを表す
--restart=OnFailureはkubectlにJobオブジェクトを作成するよう指示する

完了後のPodも含めて表示する
kubectl get pods -a

yamlでJobを作成する例
kubectl apply -f job-oneshot.yaml

失敗するJobの例
kubectl apply -f job-oneshot-failure1.yaml
kubectl get pods -a -l job-name=oneshot
KubernetesはこのPodがCrashLoopBackOffだと認識している。クラッシュの繰り返しによってノードのリソースが使われ過ぎないように、Podの再起動まで自動で少し待つ。
restartPolicy: Neverに設定するとkubeletはJob失敗時にPodを再起動せず、代わりにPodを失敗と宣言し、代わりのPodを作成する。restartPolicy: OnFailureにしておくことを推奨。

一定数成功するまで並列実行
kubectl apply -f job-parallel.yaml
kubectl get pods -w
--watchフラグによって時間とともにPodの一覧が更新されることを確認できる

並列実行キューを実装する例
1. 1回限りのキューデーモンを管理するために、ReplicaSetを作成する
kubectl apply -f rs-queue.yaml

2. キューに接続するためにポートフォワードを設定する
QUEUE_POD=$(kubectl get pods -l app=work-queue,component=queue -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward $QUEUE_POD 8080:8080
MemQ Serverを開いてキューが動作していれば、キューのプロデューサとコンシューマがDNS経由でキューを使える状態。

3. キューのServiceを作成する
kubectl apply -f service-queue.yaml

4. サブタスクをキューに入れ、状態を確認
for in in work-item-{0..99}; do
  curl -X POST localhost:8080/memq/server/queues/keygen/enqueue -d "$i"
done
curl 127.0.0.1:8080/memq/server/stats

5. コンシューマJobを作成する
kubectl apply -f job-consumers.yaml
kubectl get podsで5つのPodが並列に動作していることがわかる

# ConfigMapとSecret
ConfigMapにはワークロードに応じた設定情報を保存する
SecretにはパスワードやTLS証明書などの機密情報を保存する

ConfigMapの作成
my-config.txt
parameter1 = value1
parameter2 = value2

kubectl create configmap my-config --from-file=my-config.txt --from-literal=extra-param=extra-value --from-literal=another-param=anather-value

kubectl get configmaps my-config -o yaml

ConfigMapの使用
kuard-config.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kuard-config
spec:
  containers:
    - name: test-container
      image: gcr.io/kuar-demo/kuard-amd64:1
      imagePullPolicy: Always
      command:
        - "/kuard"
        - "$(EXTRA_PARAM)"
      env:
        - name: ANOTHER_PARAM
          valueFrom:
            configMapKeyRef:
              name: my-config
              key: another-param
        - name: EXTRA_PARAM
          valueFrom:
            configMapKeyRef:
              name: my-config
              key: extra-param
      volumeMounts:
        - name: config-volume
          mountPath: /config
  volumes:
    - name: config-volume
      configMap:
        name: my-config
  restartPolicy: Never
kubectl apply -f kuard-config.yaml
kubectl port-forward kuard-config 8080

Secretの作成
サンプルのTLSキーと証明書をダウンロードする(この例以外では使用しないこと)
curl -o kuard.crt https://storage.googleapis.com/kuar-demo/kuard.crt
curl -o kuard.key https://storage.googleapis.com/kuar-demo/kuard.key
kubectl create secret generic kuard-tls --from-file=kuard.crt --from-file=kuard.key
kubectl describe secrets kuard-tls

Secretの使用
今回の例ではSecret volumeを/tlsにマウントすると/tls/kuard.crtおよびtls/kuard.keyにアクセス可能になる
kuard-secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kuard-tls
spec:
  containers:
    - name: kuard-tls
      image: gcr.io/kuar-demo/kuard-amd64:1
      imagePullPolicy: Always
      volumeMounts:
      - name: tls-certs
        mountPath: "/tls"
        readOnly: true
  volumes:
    - name: tls-certs
      secret:
        secretName: kuard-tls
kubectl apply -f kuard-secret.yaml
kubectl port-forward kuard-tls 8443:8443

プライベートDockerレジストリ
kubectl create secret docker-registry my-image-pull-secret \
 --docker-username=xxx \
 --docker-password=xxx \
 --docker-email=xxx
yamlのサンプル kuard-secret-ips.yaml

ConfigMapとSecretの管理
kubectl get secrets
kubectl get configmaps
kubectl describe configmap my-config

# Deployment
Deploymentを使うとダウンタイムやエラーを発生させずに新しいバージョンのソフトウェアをシンプルにロールアウトできる。
kubectl scale deployments nginx --replicas=2
Deploymentをスケールすると、その管理下にあるReplicaSetもスケールされる

kubectl get deployments nginx --export -o yaml > nginx-deployment.yaml
kubectl replace -f nginx-deployment.yaml --save-config
kubectl describe deployments nginx
ロールアウト中はOldReplicaSetsフィールドに値が入る

コマンドではなくyamlでスケールさせるには以下の部分を更新する
spec:
  replicas: 3

ロールアウトを中断・再開する
kubectl rollout pause deployments nginx
kubectl rollout resume deployments nginx

ロールアウトの履歴を確認する
kubectl rollout history deployments nginx
kubectl rollout history deployments nginx --revision=2

ロールアウトのロールバック
kubectl rollout undo deployments nginx

履歴の数を制限する方法
spec:
  revisionHistoryLimit: 14

Deployment戦略
Recreate: Deploymentに基づいたPodを全て停止することでReplicaSetに新しいイメージを作成させる。シンプルで高速だが障害を起こす可能性があり、ダウンタイムも避けられない。
Rolling Update: ある一定の期間、新旧バージョンの両方がリクエストを受けトラフィックを処理する。クライアント側が新旧どちらとも連携できることが重要になってくる。

maxUnavailableパラメータはローリングアップデート中に使用不可能になってもいいPodの最大数を指定する。絶対値もしくはパーセンテージで指定する。この値はローリングアップデートを進める速度にも関わってくる。

maxSurgeパラメータはロールアウト時にどのぐらいの追加リソースを作れるかを制御する。maxUnavailableを0、maxSurgeを20%に設定すると、レプリカ数を1.2倍に増やしてからレプリカの割合を旧0.8 新0.2へ更新する。maxSurgeを100%に設定するとブルーグリーンデプロイメントを行える。

minReadySecondsを使用するとPodが起動してから指定した時間後に次のアップデートに移る
spec:
  minReadySeconds: 60

タイムアウトを設定するにはprogressDeadlineSecondsを使用する
spec:
  progressDeadlineSeconds: 600

# ストレージソリューションとKubernetesの統合
セレクタのないService
dns-service.yaml
kind: Service
apiVersion: v1
metadata:
  name: external-database
spec:
  type: ExternalName
  externalName: "database.company.com
ExternalNameというタイプのServiceを作成すると、外部のDNS名を示すAレコードの代わりにCNAMEレコードを作成する。クラスタ内のアプリケーションがexternal-database.default.svc.cluster.localというホスト名をDNS名前解決すると、DNSプロトコルがdatabase.company.comに名前をエイリアスする。

IPアドレスのみ提供されている場合はサービスとエンドポイントを作成する必要がある
external-ip-service.yaml
kind: Service
apiVersion: v1
metadata:
  name: external-ip-database

external-ip-endpoints.yaml
kind: Endpoints
apiVersion: v1
metadata:
  name: external-ip-database
subsets:
  - addresses:
    - ip: 192.168.0.1
    ports:
    - port: 3306

Persistent Volumnの例
nfs-volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: database
  labels:
    volume: my-volume
spec:
  capacity:
    storage: 1Gi
  nfs:
    server: 192.168.0.1
    path: "/exports"

PersistentVolumeClaimオブジェクトを使ってPersistentVolumnをPodから取得する
nfs-volume-claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: database
spec:
  resources:
    requests:
      storage: 1Gi
  selector:
    matchLabels:
      volume: my-volume

StatefulSetはReplicaSetに似た複製されたPodのグループ。削除時やスケールダウン時に各レプリカはインデックスの数字が大きい順から削除される。
kubectl apply -f mongo-simple.yaml

StatefulSet作成後はDNSエントリを管理するヘッドレスなServiceも作る必要がある。
kubectl apply -f mongo-service.yaml
kubectl exec mongo-0 bash ping mongo-1.mongo

# 実用的なアプリケーションのデプロイ
Parse, Ghost, Redisのデプロイ方法のサンプル