Kubernetes in Actionの個人的なまとめ

Kubernetes in Action

Kubernetes in Action


追記 2018/07/11

以下にまとめ直したのでこちらをご参照下さい。以下のリンク先のほうが詳しいです。


最近Kubernetesを触っていて、英語の勉強がてらこの「Kubernetes in Action」を読んだ。 (600ページもあって1日1時間読むようにしてたけど2ヶ月半くらいかかった。。) Kubernetesを知るためには非常に良い本で、Kubernetesの基礎からKubernetesの内部、セキュリティ周りまで丁寧に書かれていた。とてもおすすめです。

日本語に翻訳された本が出てほしいですね。

https://www.manning.com/books/kubernetes-in-action から1章と3章が無料でダウンロードできるので興味がある人は読んでみて下さい。

ということで、忘れないように重要な点を書き残しておく。

個人的な記録なので、日本語訳や内容が間違っている可能性があるので、ご注意下さい。

1〜6

基本的なことだったので省略

7. ConfigMaps and Secrets: configuring applications

7.5 Using Secrets to pass sensitive data to containers

  • 環境変数として渡せるし、ファイルとしてmountすることもできる。
  • Secretはメモリ内に読み込まれる、ファイルには書き出されない。(volumeとしてmountするときはtmpfsに書かれる)
  • ただしectdに保存されるときは平文で保存される(https://kubernetes.io/docs/concepts/configuration/secret/)
  • By default, the default-token Secret is mounted into every container,
$ kc exec git-sync-demo-b9cb448f7-972rh ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt
namespace
token
  • Secretがbase64なのは、バイナリデータも含めることができるように。
  • Secretのサイズ上限は1M
  • private dockerレジストリからimageをpullする時の認証をsecretに登録するためには
$ kubectl create secret docker-registry mydockerhubsecret \
  --docker-username=myusername --docker-password=mypassword \
  --docker-email=my.email@provider.com

のようにしてsecret genericではなくsecret docker-registryを指定する。 podには以下のように imagePullSecrets を指定

apiVersion: v1
kind: Pod
metadata:
  name: private-pod
spec:
  imagePullSecrets:
  - name: mydockerhubsecret
  containers:
  - image: username/private:tag
    name: main

8. Accessing pod metadata and other resources from applications

8.2.2 Talking to the API server from within a pod

kc exec -it curl /bin/bash でpodにログインし、

env | grep KUBERNETES_SERVICE
# でkubernetes APIのIPとPORTがわかる。

export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/default/pods
curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/default/services/git-sync-demo

ちなみに、 /var/run/secrets/kubernetes.io/serviceaccount/namespace にnamespaceが書いてある。

Rubykubernetes API クライアントは https://github.com/abonas/kubeclient がある。

10. StatefulSets: deploying replicated stateful application

  • 再作成されてもpod nameやhostnameは同じ名前で生成される。
  • また同じPersistentVolumeClaimsを使おうとする。
  • 作成されるときは、次の使われていないindexを使って作成される。
  • 現在が2で3が使われている時、4が使われる。(3が使われているときってどんな時…)
  • またpodが削除されるときは大きいindexから削除される。
  • 削除されるときは使っていたPersistentVolumeClaimsは削除されない。

11. Understanding Kubernetes internals

11.4.3 Introducing the Container Network Interface

コンテナが簡単にネットワークに接続できるようにContainer Network Interface (CNI)というプロジェクトがスタートした。 KubernetesはいくつかのCNI pluginを使うことができる。

  • Calico
  • Flannel
  • Romana
  • Weave Net
  • And others

DaemonSetとして各Nodeにデプロイされる。

11.5.1 How services are implemente

serviceはiptables rulesを追加する。実態はiptables rule。(340ページの図 参照)

11.5.2 How kube-proxy uses iptables

Endpoint objectは後ろにserviceがある全てのpodのIP/portのペアを保持している。

12. Securing the Kubernetes API serve

12.1.3 Creating ServiceAccounts

  • ServiceAccountsで使われているtokeは、JWT token

12.2.2 Introducing RBAC resources

  • RoleClusterRoleは、どのリソースにどんな操作を許可するかを指定するもの
  • RoleBindingClusterRoleBindingはどのRole/ClusterRoleをどのユーザ/グループ/ServiceAcctountに紐付けるかを定義するもの

RoleとClusterRoleの違い、RoleBindingとClusterRoleBindingの違いは、Role/RoleBindingは特定のnamespaceに属するが、ClusterRole/ClusterRoleBindingはnamespaceに属さない(cluster-lebel resource)

  • 操作とは
    • get, create, update, delete, list, watch, deletecollection がある。
  • リソースとは
    • これはkubernetesのリソース。pod, depoyment, service, secret etc..

例: service-reader.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: foo
  name: service-reader
rules:
- apiGroups: [""]
  verbs: ["get", "list"]
  resources: ["services"]
  • ServiceAccountについて
    • podはデフォルトではdefault ServiceAccountに紐づく。default ServiceAccountはnamespace作成時に作成される。
    • 追加のServiceAccountを手動で作成することができ、podに紐付けることができる

(roleのbind先のUserやGroupって何を指すんだろう。。)

  • adminという名前のclusterRoleは そのnamespece内のどんなリソースの操作もできる。ただしResourceQuotasとそのnamespaceリソースを除いて。
  • clusterRolesのedit と admin の違いは、そのnamespaceの Roles and RoleBindingsを参照/変更できるかどうか。adminはできる。

もしminikube上でRBACを有効にしたい場合は、minikube起動時に、--extra-config=apiserver.Authorization.Mode=RBACを指定する必要がある。

13. Securing cluster nodes and the network

  • podが自身のvirtualネットワークではなく、nodeのネットワークを使いたい場合、 hostNetworkプロパティを設定すればOK。
  • Kubernetes Control Plane components がpodとしてdeployされるとき、hostNetworkがONでデプロイされる。
  • 同じようにnodeのportを使いたい場合は、hostPortを設定すればOK。
    • しかしその場合、そのpodはnodeに1つしか配置できない。なぜなら同じportを複数のpodで使えないから。
  • 同じように hostPID: true、 hostIPC: true もできる
  • podのコンテナのプロセスの実行ユーザを限定する方法として、以下のようにユーザidを指定できる
apiVersion: v1
kind: Pod
metadata:
  name: pod-as-user-guest
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsUser: 405
  • セキュリティをあげるための方法として以下がある
    • podのコンテナのプロセスの実行ユーザを限定する(user idを指定)
    • コンテナのプロセスをrootで動かさない
    • コンテナをprivilegedで動かすと、nodeのカーネルにフルアクセスできてしまうので避ける
    • SELinuxを設定する
    • コンテナがfileシステムに書き込めないようにする=read onlyに設定する
    • readonlyにしつつ、特定のvolumeは書き込めるようにもできる

またlinuxのcapabilitiesをコンテナに指定することができる

PodSecurityPolicyリソース

参考: https://docs.bitnami.com/kubernetes/how-to/secure-kubernetes-cluster-psp/ https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

PodSecurityPolicy とはクラスタ全体のセキュリティ上のポリシーを定義する機能です。ホストに影響を与える可能性がある 特権 (privileged) や HostIPC などの機能を制限し Pod に脆弱性があった場合にクラスタを守ることができます。

参考: Kubernetes: Pod Security Policy によるセキュリティの設定 https://qiita.com/tkusumi/items/6692af743ae03dc0fdcc

またRBACによって、ServiceAccountに紐付けることができる。

できること

  • どのpodがホストのIPC, PID or Network namespaceを使用できるか
  • どのホストポートをpodが使えるか
  • どのユーザでコンテナのプロセスを動かすか
  • privileged containeを持ったpodを作れるかどうか
  • どの kernel capabilitieを許可するか、またデフォルトでどのkernel capabilitieを追加or削除するか
  • どんな SELinuxLabelをコンテナが使うか
  • コンテナがrootファイルシステムを書き込み許可するかどうか
  • コンテナがどのfile system groupを実行できるか
  • podがどのvolumeタイプを使用できるか

13.4 Isolating the pod network

もしnetworking pluginがサポートしていれば、NetworkPolicyリソースを作成することによって、 ネットワークの隔離をすることができる。 要はpod間の通信の制限ができる。 ingress and egress rulesと呼ばれる。Ingressリソースとは関係がない。 ingressはネットワークの流入、egressはネットワークの流出のルールを定義できる。

このpodはこのpodへしかリクエストを送れないor受け取れないとかこのportを使ってしかあのpodへリクエストを送れないor受け取れないとかが設定できる。 13.4.2 参照。 また特定のlabelがついたnamespace内のpodからリクエストを受け取らないということができる。 もしくはcidrを使ってリクエストを制限することもできる

13.5 Summary

  • podは自分のではなくnodeの Linux namespacesを使うことができる
  • コンテナはコンテナイメージで指定されたユーザとは別のユーザで実行するように設定することができる
  • コンテナはprivileged modeで動かすことができる
  • コンテナはプロセスがファイル書き込みをしないようにread onlyで動くように設定することができる(また特定のvolumeのみ書き込みが可能に設定できる)
  • Cluster-levelリソースであるPodSecurityPolicyは、nodeの安全を脅かすpodの生成を防ぐことができる。
  • PodSecurityPolicyはRBACのClusterRoleとClusterRoleBindingを使って特定のuserに紐付けることができる。
  • NetworkPolicyリソースはpodのinbound/outbound trafficを制限することができる。

14. Managing pods’ computational resources

resource requests

podをデプロイする時に必要とするリソース(CPU/メモリ)を指定することができる。 もちろんそれ以上使うこともできる。(リソース使用量の制限はlimitを使う) またその指定したリソースがそのpod用に確保されるわけではない。 あくまでpodをデプロイ時にそのリソース量が空いているかをチェックするためのもの。 そのリソース量が空いていないnodeにはデプロイされない。

 apiVersion: v1
        kind: Pod
        metadata:
          name: requests-pod
        spec:
          containers:
          - image: busybox
            command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
  requests:
    cpu: 200m
    memory: 10Mi

cpu 200m は200 millicoresの略で1000milli coreで1cpu。200 mill coreで 1/5 CPU使うということ。 10Miは 10 mebibytes 。

$ kubectl describe nodes
Name:       minikube
...
Capacity: # nodeの使用可能リソース量
  cpu: 2
  memory: 2048484Ki
pods: 110
Allocatable: #ここがresource requestsで指定した割当量
  cpu:           2
  memory:        1946084Ki
  pods:          110

ポイントとしては、podをデプロイするときに、nodeのリソース使用量は見ないで、resource requestsをみてデプロイが行われる。 なので、nodeのリソースが100%でも、resource requestsに空きがあればデプロイされる。

resource limits

apiVersion: v1
kind: Pod
metadata:
  name: limited-pod
spec:
  containers:
  - image: busybox
    command: ["dd", "if=/dev/zero", "of=/dev/null"]
    name: main
resources:
  limits: # ここで制限を指定
    cpu: 1
    memory: 20Mi

上記のようにresource requestsを指定していなかったら、limit値と同様の値がresource requestsに設定される。 resource requestと違って、resource limitはnodeの使用可能リソース量を超えて指定ができる。 たとえばメモリが1Gのnodeがあって、pod Aのメモリrequestが500MBでlimitが800MB、pod Bのメモリrequestが500MBでlimitが500MBでもOK。 CPUも同様。

ちなみにそのnode上で使用可能なCPUリソース以上をpodたちが使用しようとした場合、 CPU速度が遅くなるだけで、問題が起きるわけではない。 しかしメモリの場合は違う。resource limitで指定した以上のメモリをpodたちが使用しようとした場合、 コンテナはOOM killされる。 もしpodのrestart policyがAlways or OnFailureが設定されていた場合、podのコンテナは自動的に再起動される。なのでkillされたことに気づかないかもしれない。 もし再度メモリオーバーになったら再度再起動されるが、再起動時間はexponential backoffアルゴリズムにしたがって段々遅くなる。 その場合、podのstatusはCrashLoopBackOffになる。 CrashLoopBackOffはkubeletが諦めたことをささない。少しずつ再起動期間は伸びるが最終的に5分間隔で再起動が繰り返されるようになる。 再起動回数はpodのRESTARTSに表示される。

14.3 pod QoSクラス

Kubernetesは3つのQuality of Service (QoS)クラスを提供する。Qosはpodに付与される。

  • BestEffort (the lowest priority)
  • Burstable
  • Guaranteed (the highest)

QoS classはresource requestsとresource limitsの2つから決定される。

BestEffortが設定される条件

  • pod内のどのコンテナにもresource requestsとlimitsが設定されていない

Guaranteed (the highest)が設定される条件

  • CPUとメモリの両方に、resouce requestsとlimitsがセットされていること
  • 上記がpod内のそれぞれのコンテナにセットされていること。
  • resouce requestsとlimitsの値がそれぞれ同じであること。

Burstableが設定される条件

  • BestEffortもGuaranteedも設定されていない場合

pod内に複数のコンテナが存在する場合、どうやってpodのQoSが決まるか。 まず各コンテナに上記のルールに従ってQoSを割り当てる。 全てのコンテナがBestEffortならpodのQoSはBestEffortになる。 全てのコンテナがGuaranteedならpodのQoSはGuaranteedになる。 それ以外の場合はpodのQoSはBestEffortになる。

podのQoSはkubectl describe pod -o yaml|json したときに、status.qosClass fieldに表示される。

nodeのメモリ上限に達した場合、どういった判断でプロセスがkillされるか

QoSに従ってどのコンテナがkillされるか決まる。 最初にkillされるのはBestEffort。つぎに、Burstable。最後にGuaranteed(ただしsystem processがメモリを必要とした場合のみkillされる)。

同じQoSクラスだった場合、どうやってどのコンテナがkillされるかを決めているか

どの実行中プロセスもOutOfMemory (OOM) scoreを持っている。 システムはOutOfMemory (OOM) scoreのよってどのプロセスをkillするかを比較して決めている。

14.4 namespaceごとに、pod作成時にpodにデフォルトのresource requests/limitsを設定する

LimitRangeリソース

LimitRangeリソースを定義すると、そのLimitRangeリソースが属しているnamespace内にpodを作成するときに、 LimitRangeリソースで指定したresource requestsとlimitsを設定することができる。

apiVersion: v1
kind: LimitRange
metadata:
  name: example
spec:
  limits:
  - type: Pod
    min:
      cpu: 50m
      memory: 5Mi
    max: cpu: 1
      memory: 1Gi
  - type: Container
    defaultRequest: # resource requestsを設定していないコンテナはこのデフォルト値が設定される
      cpu: 100m
      memory: 10Mi
    default: # resource limitsが設定されていないコンテナはこのデフォルト値が設定される。
      cpu: 200m
      memory: 100Mi
    min: # コンテナが設定できる最小のrequests/limits値
      cpu: 50m
      memory: 5Mi
    max: # コンテナが設定できる最大のrequests/limits値
      cpu: 1
      memory: 1Gi
    maxLimitRequestRatio: # Maximum ratio between the limit and request for each resource
      cpu: 4
      memory: 10
  - type: PersistentVolumeClaim # PVCに対しても制限がかけられる
    min:
      storage: 1Gi
    max:
      storage: 10Gi

14.5 namespaceに対して使用可能リソース量を指定する

ResourceQuotaリソース

LimitRangeリソースはpodごとに適用される。 ResourceQuotaリソースはnamespaceに対して適用される。 制限できるのは、 CPU, メモリ、PersistentVolumeClaimのディスク使用量, podの数, 他のAPI objects users(?)が作成すること

apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: cpu-and-mem
  spec:
    hard:
     requests.cpu: 400m
      requests.memory: 200Mi
      limits.cpu: 600m
      limits.memory: 500Mi

ResourceQuotaリソースはそのnamespace内のpodのrequests/limitsのトータル値に対して制限がかかる。

kubectl describe quota で詳細がみれる。

14.6 podリソースの使用量のモニタリング

kubeletには内部にcAdvisorというものを持っている。 cAdvisorはnode自体のリソース使用量及びnode上で実行されているコンテナのリソース消費量のデータを収集する役割。 それらのデータをクラスタ全体で集約するために、Heapsterとよばれるコンポーネントがある。 Heapsterはnodeのうち1つのnode上でpodとして動作する。 Heapsterはserviceを通して公開される。

minikubeの場合は以下のコマンドでheapsterを有効にできる。 minikube addons enable heapster

heapsterが実行されていれば、 kubectl top node でその集約データ結果からnodeのリソース使用量がみれる。

$ kubectl top node
NAME       CPU(cores)   CPU%      MEMORY(bytes)   MEMORY%
minikube   170m         8%        556Mi           27%

グラフィカルにリソースをモニタリングしたい場合、 GKEならGoogle Cloud Monitoringが使える。しかしminikubeやオンプレの場合は、 データの保存にInfluxDB, ビジュアリングにGrafanaが使える。

http://github.com/kubernetes/heapster/tree/master/deploy/kube-config/influxdb に従ってKubernetesクラスタにデプロイできる。 minikubeならHeapster add-onを有効にした時点でInfluxDBもGrafanaも有効になっている。 以下のように。

$ kubectl cluster-info
...
monitoring-grafana is running at https://192.168.99.100:8443/api/v1/proxy/namespaces/kube- system/services/monitoring-grafana

14.7 Summary

  • resource requestを指定することはpodをscheduleするときに役に立つ
  • resource limitsを指定することはpodが他のpodのリソースを使い過ぎることを防ぐ
  • 使われていないCPU時間はコンテナのrequestsに基づいて割り当てられる
  • コンテナはCPUを使い過ぎても決してkillされないが、メモリを使い過ぎようとしたらkillされる。
  • QoSと実際のメモリ使用量に基づいて、より重要なpodのために他のpodが消えるされることもある。
  • LimitRangeを定義することによって、それぞれのpodに対して、resource requestsとlimitsの最小値,最大値, デフォルト値を設定することができる。
  • ResourceQuotaを定義することによって、namespace内のpodのresourceの総量に対して最小, 最大を設定することができる。
  • どのくらい高いpod resource requests/limitsを指定しているか知るために、長い間モニタリングをする必要があります。

15 podとnodeのauto scaling

podのスケールアウト(Horizontal pod autoscaling)とはpodの数を増やすこと HorizontalPodAutoscalerリソースを定義することで可能になる。

まずDeployment, ReplicaSet, ReplicationController, or StatefulSetはscaled resource objectと呼ばれる。 これらはauto scalingが可能なリソース。

autoscalingプロセスは以下の3つで行われる。

  • scaled resource objectによって管理されている全てのpodのメトリックを取得する
  • メトリックを定義されたターゲット値に近づけるためにはどのくらいのpodが必要か計算する
  • scaled resourceのreplicas fieldを更新する。

メトリックはHorizontalPodAutoscalerリソースによって取得されるが、HorizontalPodAutoscalerリソースはHeapsterからメトリックを取得する。HeapsterはcAdvisorからメトリックを取得する。 そのためauto scalingにはHeapsterが起動していないといけない。

scaleoutのための計算方法は、 440のFigure 15.2 の図を参照 もしくは、 https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/horizontal-pod-autoscaler.md#autoscaling-algorithm を参照。

※ポイントとしては、auto scalingはtarget valueに近づくようにpod数が調整されるということ。 target valueが80なら 全体(全podの合計)のCPU使用率が80%になるように調整される。たとえば全体のCPU使用率が90%ならpod数は増えるし、50%ならpod数は減らされる。

HorizontalPodAutoscalerリソースには、autoscale条件と、最小/最大pod数の指定ができる。

重要な点は、定期的にメトリックの取得をしているため、すぐにはscalingが行われないということ。少し時間がかかる。 scale up(pod数を増やす)イベントは3分おきに発生する。 scale down(pod数を減らす)イベントは5分おきに発生する。

Memory-based autoscaling was introduced in Kubernetes version 1.8, and is configured exactly like CPU-based autoscaling. とあるが、ほんとか?

15.1.4 Scaling based on other and custom metrics

custom metricsを用いてauto scalingを行うことができる。 HPAには以下の3種類を指定することができる。

  • Resource
    • resource requests/limitsのようなresource metrics
  • Pods
    • custom metricsを含むpodに関係するmetrics。 例えばQueries Per Second (QPS) や message broker’s queueの数。
  • Object
    • podとは直接関係ないmetrics。たとえばIngressのlatency。

15.1.5 どんなmetricsがautoscalingに適しているか判断する

podのメモリ消費量はautoscaling metricsに向かない。 podが増えるほど全体のメモリ消費量は増え、使えるメモリ量が逆に減るからだ。 metricsの一つとして、Queries per Second (QPS)がある。 autoscaling metricsとしてcustom metricsを使う前に、podが増減したらそのmetricsがどのように振る舞うかよく考えること。

15.2 Vertical pod autoscaling

スケールアウトはサーバーの台数を増やすこと、スケールアップはサーバーのスペックを上げることだが、 podをスケールアップはまだサポートされていない。が、今後サポートされるであろう。 podのスケールアップというのは、podのresource requests or limitsを変更するということ。

15.3

Kubernetesは、cloud providerが提供していれば、nodeのauto scalingもでき、Cluster Autoscalerという機能が担っている。 Cluster Autoscalerは、リソースが枯渇して既存のnodeにpodがschedulingできない場合に、nodeを追加する。 またリソース負荷が低いならnodeの削除も行う。 Cluster Autoscalerはnodeを追加する前にそのnodeにpodがscheduleできるかを確認する。じゃないとnodeを追加してもpodがscheduleできない事態になってしまう。

nodeを削除するとき(scale downするとき)system podsが実行されているかチェックしされているならnodeの削除を行わない。ただしsystem podsがDaemonSetの場合を除く。DaemonSetは各nodeで実行されているためnodeからpodを削除しても問題ないため。 また unmanaged pod(replicasetやdeploymentによって管理されていないpod)やlocal strageを使っているpodが存在する場合もnodeを削除しない。 nodeを削除するさいには、nodeにunschedulableを設定する。これによりpodがscheduleされなくなる。 そしてnode上のpodが追い出される。 追い出されたpodはreplicasetやdeploymentの管理下なので他のnodeへ再scheduleされる。

tips

kubectl cordon <node>でnodeにunschedulableを設定できる。 kubectl drain <node>でnodeにunschedulableを設定し、node上のpodを追い出す。 kubectl uncordon <node>を実行すればnodeのunschedulableを解除できる。

15.3.3 cluster scale donw時にサービスの中断を抑える

nodeが突然死んだ場合はどうしようもないが、手動もしくはCluster Autoscalerでnodeの削除が行わえる場合は、サービスを中断させないことができる。 DisruptionBudgetリソースを作成すればよい。 参考: https://qiita.com/tkusumi/items/946b0f31931d21a78058 「PodDisruptionBudget とは Node を計画的に停止したい場合に、Pod の状況を見ながら退去 (evict) させる機能です」

15.4 Summary

  • podの水平スケーリング(scale out)はHorizontalPodAutoscalerリソースを作成し、それをDeployment, ReplicaSetに指定し、対象CPU使用量を指定することで簡単にできる。
  • podのCPU使用量の他に、アプリケーションが提供するcustom metricsやclusterにdeployされている他のリソースのmetricsをもとにauto scalingすることができる。
  • 垂直スケーリング(scale up)はまだ提供されていない
  • (cloud providerから提供されていれば)cluster nodeもautoscalingできる。

16 Advanced scheduling

最初、特定のnodeに特定のpodをscheduleする方法はnode selectorを使う方法があった。 しかし後からもっと柔軟にscheduleするためのメカニズムが導入された。 それがtaintsとtolerations。 これによって特定のtaintsをもったnodeに、特定のtolerationsをもったpodのみをscheduleすることが可能になる。 taints/tolerationsとnode selector / node affinityにはいくらか違いがある。 node selector / node affinityは、podに対して情報を付与することによってpodがどのnodeにscheduleされるかを決めるための仕組み。 taints/tolerationsはそれに加えて、既存のpodに情報を付与せずnodeに情報を付与することによってpodを特定のnodeにscheduleされないようにすることができる仕組み。

例えば、

$ kubectl describe node master.k8s
Name:         master.k8s
Role:
Labels:       beta.kubernetes.io/arch=amd64
              beta.kubernetes.io/os=linux
              kubernetes.io/hostname=master.k8s
              node-role.kubernetes.io/master=
Annotations:  node.alpha.kubernetes.io/ttl=0
              volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:       node-role.kubernetes.io/master:NoSchedule

この場合、node-role.kubernetes.io/master:NoScheduleがTaints。 taintsは=:で構成されていて、 node-role.kubernetes.io/master がkey、 valueはnull、 effectがNoSchedule。 このnodeのtaintsを受け入れられるtolerationをもったpodでないとこのnodeへscheduleすることはできない。

たとえば

$ kubectl describe po kube-proxy-80wqm -n kube-system
...
Tolerations:    node-role.kubernetes.io/master=:NoSchedule
                node.alpha.kubernetes.io/notReady=:Exists:NoExecute
                node.alpha.kubernetes.io/unreachable=:Exists:NoExecute
...

node-role.kubernetes.io/master=:NoScheduleというtolerationをもっているので上記のnodeへscheduleできる。

node.alpha.kubernetes.io/notReady=:Exists:NoExecute のExistsは、値がnullではないことを指す。

effectは以下の4種類ある。

  • NoSchedule
    • taintが許容できなければnodeへscheduleさせない
  • PreferNoSchedule
    • taintが許容できるnodeを探し、なければ許容できないnodeであってもscheduleする
  • NoExecute
    • scheduling時に影響があるNoScheduleやPreferNoScheduleと違って、NoExecuteはnode上で実行中のpodへも影響がある。もしNoExecuteエフェクトをもったtaintをnodeへ追加した場合でかつpodがそのtaintを許容できない場合、そのpodはnode上から追い出される。

このtaints/tolerationsの分かりやすい使用例としては、production nodeに non-productionなpodをscheduleしたいくない場合に使える。

16.1.3 Adding tolerations to pods

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: prod
spec:
  replicas: 5
  template:
spec: ...
      tolerations:
      - key: node-type
        Operator: Equal
        value: production
        effect: NoSchedule

podのtolerationsの設定は上記の用に設定できる。

16.2 Using node affinity to attract pods to certain nodes

taintsは特定のnodeからpodを避けるための仕組み。 node affinityという仕組みは、podを特定のnode集合へscheduleするための仕組み。 node affinityの前身はnode selectorだった。node affinityはnode selectorよりももっとパワフル。 node selectorはいずれ非推奨になる。

node selectorと同様に、各podに自身のaffinity ruleを設定する。

まずnodeのLabelにaffinityを設定する。

$ kubectl describe node gke-kubia-default-pool-db274c5a-mjnf
Name:     gke-kubia-default-pool-db274c5a-mjnf
Role:
Labels: beta.kubernetes.io/arch=amd64
        beta.kubernetes.io/fluentd-ds-ready=true
        beta.kubernetes.io/instance-type=f1-micro
        beta.kubernetes.io/os=linux
        cloud.google.com/gke-nodepool=default-pool
        failure-domain.beta.kubernetes.io/region=europe-west1
        failure-domain.beta.kubernetes.io/zone=europe-west1-d
        kubernetes.io/hostname=gke-kubia-default-pool-db274c5a-mjnf

上記だと下3つがaffinityで、

  • failure-domain.beta.kubernetes.io/region
    • はnodeが位置するregionを指す
  • failure-domain.beta.kubernetes.io/zone
    • はnodeが属するzoneを指す
  • kubernetes.io/hostname
    • はhostnameを指す

podには以下のようにnodeAffinityを設定する。

apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: gpu
            operator: In
            values:
            - "true"

matchExpressionsを使って柔軟に条件を設定することができる。 requiredDuringSchedulingIgnoredDuringExecution は2つに分解できる。

requiredDuringScheduling はnodeをscheduleするさいこのaffinity ruleに合致するnodeにしかscheduleされないことを意味する。 IgnoredDuringExecution はこのaffinity ruleはnodeで実行中のpodには影響を与えないことを意味する。

preferredDuringSchedulingIgnoredDuringExecution というのもある。 preferredDuringSchedulingはnodeをscheduleするさいこのaffinity ruleに合致するnodeに優先的にscheduleするが合致するnodeがなければ他のnodeへscheduleすることを意味する。 またIgnoredDuringExecutionとは対象的なRequiredDuringExecutionもある。これはnod上で実行中のpodに影響を与えることを意味するが、まだKubernetesで実装されていなく、今後実装される予定。

またnode affinityの設定は複数定義することができ、それぞれにweightを設定することができる。

16.3 pod affinity と pod anti-affinityを用いて一緒に配置する

node selectorと node affinityはpodとnodeの影響を与えるものだった。 pod affinityはpod間に影響を与える仕組み。 たとえばfrontend podとbackend podがあった場合それらのpodが近いほうがlatencyが減らせるので、それぞれ近いほうがいい。 pod affinityを使えば同じnode(もしくはラック、もしくはデータセンター)にscheduleすることができる。

以下のような定義。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 5
  template:
... 
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - topologyKey: kubernetes.io/rack
            labelSelector:
              matchLabels:
                app: backend

topologyKey: kubernetes.io/rackだが、こうすると同じrackにあるnodeにpodが配置される。 どういう仕組かというと、nodeにrack=rack_a, rack=rack_bのようにLabelをつける。 つまりrack_aに属するnodeにはrack=rack_aというLabelをつける。 すると、app=backendというLabelが先にrack_aのnodeに配置されている場合は、このpod affinity ruleをもつpodはrack_aのnodeのいずれかに配置される。

16.3.4 Scheduling pods away from each other with pod anti-affinity

pod anti-affinityを使うと、pod affinityと反対のことができる。

16.4 Summry

  • もしnodeにtaintsをセットすると、podがそのtaintsを許容できるtolerationをセットしていない限りそのnodeにそのpodはscheduleされない。
  • taintsは3タイプあり、NoScheduleは完璧にschedulingされない。 PreferNoScheduleはそこまで厳密ではに。NoExecuteはnode上の実行中のpodであっても追い出す。
  • NoExecute taintは、nodeがunreachableやunready状態になった場合podがどのくらいの時間schedulingされるのを待つか指定できる。
  • Node affinityはどのnodeにpodがscheduleされるべきか指定できる。
  • Pod affinityは同じaffinity ruleをもったpodが実行されているnodeにpodをscheduleすることができる
  • Pod anti-affinity はお互いのpodを避けさせることができる

17.2.3 Starting pods in a specific order

https://qiita.com/tkusumi/items/f64fff37a724b86d9819#init-containers-alpha

Pod内で初期化処理をするコンテナを指定をできる機能です。Podの定義にinitContainersという項目で、従来のcontainersで指定したコンテナの前に起動するコンテナを指定できるようになるようです。複数のコンテナを指定することができますが、containersがパラレルに起動されるのに対してinitContainersは指定した順番に1つずつ起動していくようです。

使用用途としては例えば以下のようなものが挙げられます。

  • データを動的にダウンロードしてくる
  • データベーススキーマの反映
  • アプリケーションが何らかの事前条件を満たすまでウェイトする
BEST PRACTICES FOR HANDLING INTER-POD DEPENDENCIES

init containersを使えば事前条件が満たされるまでpodのmainコンテナが起動するのを遅らせることができる。 readiness probesを設定するのを忘れてはいけません。 アプリケーションがまだ準備段階ではない場合にserviceにpodが追加されるのを防ぐことができますし、 Deployment controllerが、bad versionをrolloutするのを防ぐためにも使用されます。

17.2.4 Adding lifecycle hooks

podのlifecycleに以下の2つのhookを定義することができる。これらをlifecycle hooksと呼ぶ。

  • Post-start hooks
  • Pre-stop hooks

pod全体に設定するinit containersと違って、lifecycle hooksはpodのコンテナごとに設定できる。 lifecycle hooksはcontainerがstartするときとstopするときに実行される。

lifecycle hooksは、以下の点でlivenes、 readiness probesと似ている。

  • containerの内部で実行されるコマンド
  • URLに対してHTTP GET Requestとして実行される

USING A POST-START CONTAINER LIFECYCLE HOOK

Post-start hookはコンテナのmain processが実行された後すぐに実行される。 もしコンテナのアプリケーションとは別のアプリケーションを動かしたい場合、あなたがそのアプリケーションの開発者ならアプリケーション内で別のアプリケーションを動かすことができるが、アプリケーションの開発者ではない場合、post-start hookは役に立つ。 Post-start hookはmain processと平行して実行されるが、以下の点でcontainerに影響を与える まずPost-start hookの実行が完了するまで、containerのstateWaitingのままでありreasonContainerCreatingのまま。そのためpodのstatusはRunningではなくPendingのまま。 もしPost-start hookが実行に失敗したりnon-zero exit statusを返した場合、main containerはkillされる。

以下が定義例。

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-poststart-hook
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    lifecycle:
      postStart:
        exec
          command:
          - sh
          - -c
          - "echo 'hook will fail with exit code 15'; sleep 5; exit 15"

hookによって開始されたprocessが標準出力に出力してもそれをみるすべはない。そのため永続的volumeに書き込むこと。

USING A PRE-STOP CONTAINER LIFECYCLE HOOK

https://qiita.com/superbrothers/items/3ac78daba3560ea406b2

17.3 Ensuring all client requests are handled properly

17.3.1 Preventing broken client connections when a pod is starting up

readinessを設定すること

17.3.2 Preventing broken connections during pod shut-down

まず、podが削除されるときの、Kubernetesの動きは以下になる。

1, client(kubectlとか)からpodの削除リクエストがくる 2, A1とB1が並行して同時に実行される A1, kubeletにpodの削除通知が送られる A2, kubeletからpodにコンテナの停止リクエストが送られる A3, コンテナのpre-stop hookが実行され、, SIGTERMが送られ、, 一定時間待った後、それでもまだコンテナのプロセスが動いていたら強制的にkillする B1, EndpointsControllerへpodの削除通知が送られる B2, EndpointsControllerはAPI Serverへendpointからpodを削除するようにリクエストする B3, API Serverはkube-proxyへEndpoints変更通知を送る B4, kube-proxyはnodeのiptablesからpodを削除する

コンテナが停止プロセスを実行しているときに処理中のリクエストはどうなってしまうか。 リクエストの処理に時間がかかる場合であれば、“Connection Refused”が起こるのは避けられない。 なので考えられる選択肢としては以下。 * 数分待ち、新しいリクエストを受け取るのを止める * 全てのkeep-aliveコネクションを閉じる * 全ての処理中のリクエストが処理完了するまで待つ * shut downを完了させる

pre-stop hookに数分待つように仕込めば実現できる。 しかしそれを入れるとpodの削除まで時間がかかってしまうので諸刃の剣。

17.4 Making your apps easy to run and manage in Kubernetes

17.4.1 Making manageable container images

imageのサイズは小さくし、不要なファイルやツールは含めないように。

17.4.2 Properly tagging your images and using imagePullPolicy wisely

docker imageのtagにはlatestが使えるが、podのyamlファイルにはlatestを指定しないようにしよう。 もしimageを更新しても、新しくscheduleするpodにはその新しいimageが使用されるが、既存のpodのコンテナのimageは古いバージョンのまま。 またlatestタグを使うと1つ前のバージョンへrollbackができない。

もしあなたがimageのタグにmutable tag(同じタグを使い続けること)を使うならpodのspecのimagePullPolicyはAlwaysにしなければならない。 しかしそれはproductionでは大きな落とし穴になる。 もしAlwaysを設定したら、新しいpodがscheduleするたびにcontainer runtimeは毎回container registryに問い合わせることになる。 これはpodが起動するのが少し遅くなる。なぜならnodeは毎回imageが更新されているかチェックする必要がでてくるため。またcontainer registryが応答がない場合、podが起動するのを妨げてしまう。

17.4.3 Using multi-dimensional instead of single-dimensional labels

podだけではなく他のリソースにもlabelをつけることを忘れないようにしよう。 各リソースに複数のlabelをつけるようにすれば、リソースのselectが簡単になる。 labelには例えば以下のものがよいだろう。

  • リソースが属しているアプリケーション(もしくはマイクロサービス)の名前
  • Enviroment(development, QA, staging, production 等)
  • バージョン
  • リリースのタイプ(stable, canary, green or blue for green/blue deployments等)
  • テナント(あなたがもしpodをnamespaceを使用する代わりに各テナントで動かしている場合)
  • シャードシステムならそのシャード

17.4.4 Describing each resource through annotations

他の追加情報はannotationに含めましょう。 たとえばそのアプリケーションの責任を持っている人の情報とか。 もしマイクロサービスであれば、そのpodが使用している他のサービスの名前とか。 バージョン情報とか、ツールやGUIツールが使用するmetadataとか。

17.4.5 Providing information on why the process terminated

Kubernetesにはなぜコンテナが終了したのかの情報を含めることができます。 コンテナの /dev/termination-log に終了した理由を書き込んでおけば、 kubectl describe podしたときにContainersのLast StateのMessageに表示される。

17.4.6 Handling application logs

アプリケーションログは標準に出すべき。 そうすればkubectl logsで見れる。 一つ前のpodのlogはkubectl logs --previousでみれる。

特定のログファイルにはいていたら、 $ kubectl exec <pod> cat <logfile> でみれる。 または、pod内にコンテナが1つなら $ kubectl cp foo-pod:/var/log/foo.log foo.log でログをローカルへcpすることができる。 複数のコンテナが実行されていたら -c containerName オプションでコンテナを指定する。

Kubernetesはログ集約システムは提供していない。 しかしそれをログ集約システムをデプロイするのは簡単。 Google Kubernetes EngineならEnable Stackdriver Logging checkbox にチェックを入れればいい。

EFK(ElacsticSeach, Fluentd, kibana)のようなスタックをデプロイすれば使うこともできる。 fluentdのようなログ収集ミドルウェアは複数行ログ(たとえばJavaスタックトレースのような)は1行1行別のログになってしまうので、jsonとして出力するのがよい。 ただjsonログは人間には見にくいので(kubectl logsした場合)、 標準出力にはplain textにして、特定のログファイルにはjsonで出力するのがよい。

17.6 Summary

  • podのような頻繁にrelocateするappと、ほとんど移動しないappの違いについて
  • マイクロサービスのような複数のコンポーネントアプリは特定の順序で起動にすることに頼るべきではないことを理解する
  • init containersの紹介。init containersとはpodの起動に使用されたり、事前条件を満たすまでpodのmain containerの起動を遅らせることができる
  • コンテナのlifecycle hooks の紹介
  • Kubernetesコンポーネントの分散性質の結果と結果整合性モデルの深い理解
  • リクエストを壊すことないアプリケーションの適切なshut downの仕方について
  • imageサイズを小さくすることにや、annotationsや複数のlabelを各リソースに追加することによって管理がどのように楽になるか
  • どのようにKubernetes上での動くアプリケーションの開発をするか、また、multi node clusterにデプロイする前にminikubeやローカルでどのように開発するか

18 Extending Kubernetes

18.1 Defining custom API objects

自分のAPI objectを定義し、それらのobjectのためのcontrollersを実装することができる。

18.2 Introducing CustomResourceDefinitions

新しいリソースを作成するためには、CustomResourceDefinition objectを定義する必要がある。 CustomResourceDefinitionを定義すれば、他のリソースが同様, yamljsonを通してそのinstanceを作成することができるようになる。 以下が定義例。

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: websites.extensions.example.com
spec:
  scope: Namespaced
group: extensions.example.com
version: v1
names:
  kind: Website
  singular: website
  plural: websites

このリソースのinstanceを作成する場合は以下。

apiVersion: extensions.example.com/v1
kind: Website
metadata:
  name: kubia
spec:
  gitRepo: https://github.com/luksa/kubia-website-example.git

そして新しく定義したこのリソースに対するcontrollerを実装する必要がある。

https://github.com/luksa/k8s-website-controller

controllerもpodとして動かすことができる。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: website-controller
spec:
  replicas: 1
  template:
metadata:
  name: website-controller
  labels:
    app: website-controller
spec:
  serviceAccountName: website-controller
  containers:
  - name: main
    image: luksa/website-controller
  - name: proxy
    image: luksa/kubectl-proxy:1.6.2

custom objectのバリデートはKubernetes1.8でアルファ機能として導入された。 API serverで バリデートするためにはCustomResourceValidation機能を有効にする必要がある。

18.1.4 Providing a custom API server for your custom objects

Kubernetesでcustom objectのサポートを提供する良い方法は、自分のAPI serverを実装し、clientがそのAPI serverとやり取りさせること。 Kubernetes 1.7から、KubernetesのデフォルトのmainのAPI Serverと自分で作成したAPI serverを統合することができるようになった。 複数のAPI Serverがあっても1つのAPIServerとして扱うことができる。

custom api serverを登録するには、custom api serverをpodとしてdeployしserverを通して公開すればよい。

18.2 Extending Kubernetes with the Kubernetes Service Catalog

最初の追加API Serverとして、Service Catalog API serverがある。 Service Catalogとは名前の通りserviceのカタログで、Service Catalog機能を使えば、カタログの中からサービス(アプリケーション)を選んで、pod,service,configmap等を直接扱うこと無く簡単にデプロイすることができるようになる。

Service Catalogは以下のリソースを提供している。

  • ClusterServiceBroker。これはサービスを提供する(外部)システムについて定義したもの
  • ClusterServiceClass。これは提供されるサービスについて定義したもの
  • ServiceInstance。これは提供されたサービスのinstance
  • ServiceBinding。これはServiceInstanceとclients(pods)のセットを結びつけるもの

Kubernetesは、提供されるサービスのリストをbrokerへ問い合わせ、ClusterServiceClassリソースを作成する ユーザが提供されるサービスが必要な場合、KubernetesはServiceInstanceを作成し、ServiceInstanceをpodsに結びつける(bindingする) それらのpodsに必要な認証情報や接続に必要な情報をsecretを通して渡す。

18.2.2 Introducing the Service Catalog API server and Controller Manager

Kubernetesと似ていて、Service Catalog以下の3つのコンポーネントからなる分散システムである。

  • Service Catalog API Server
  • etcd as the storage
  • Controller Manager, where all the controllers run

18.2.3 Introducing Service Brokers and the OpenServiceBroker API

クラスタの管理者は1つ以上の外部ServiceBrokerをService Catalogに登録することができる。 各brokerはOpenServiceBroker APIを実装する必要がある。

Service Catalogがサービスの一覧を取得したあと、Service Catalog は各ClusterServiceClassリソースを生成する。各ClusterServiceClassリソースは、サービスの1つのタイプが定義されている(例えばあるClusterServiceClassは“PostgreSQL database”)。 各ClusterServiceClassは、1つ以上のservice planを持っている。ユーザは必要とするそのサービスレベルを選択することができる。たとえばdatabase ClusterServiceClassはHDDのfreeプランや、SSDのPremiumプランを提供したりするだろう。

18.2.4 Provisioning and using a service

実際にserviceが提供されるためには、service instanceを作成する必要がある。 例:

apiVersion: servicecatalog.k8s.io/v1alpha1
kind: ServiceInstance
metadata:
  name: my-postgres-db
spec:
  clusterServiceClassName: postgres-database
  clusterServicePlanName: free
  parameters:
    init-db-args: --data-checksums

重要なのは、このservice(これだとpostgres-database)は、Kubernetesクラスタ外に作成されてもいいということ。なので例えばクラウド上のDBでもよい。 serviceへの接続情報はServiceBindingリソースを使ってsecretを通して与えられる。

例:

apiVersion: servicecatalog.k8s.io/v1alpha1
kind: ServiceBinding
metadata:
  name: my-postgres-db-binding
spec:
  instanceRef:
    name: my-postgres-db
  secretName: postgres-secret #ここに好きなsecret名を指定

将来的にはPodPresetsというKubernetesの機能が導入される予定。

Service CatalogはServiceBindingで指定した名前で新しいSecretを生成し、必要な情報をsecret内に保存する。 あとはpodからそのsecretをmountして使えば、podが接続情報を得ることができる。

18.4 Summary

  • CustomResourceDefinition objectを作成することで、Custom resourcesをAPI Serverに登録することができる
  • API Serverのコードを変更すること無く、CustomObjectのインスタンスを保存、取得、更新、削除することができる
  • custom objectsを実際にKubernetes上で動くようにCustom Controllerを実装することができる
  • API aggregation機能を通し、Custom API Serverを使うことでKubernetesを拡張することができる
  • Kubernetes上に構築されたPasSはコンテナを使ったアプリケーションを構築するのが楽になる
  • パッケージマネージャーのhelmは既存のアプリケーションをresource manifestsを必要とせずにデプロイすることができる

appendix A: Using kubectl with multiple clusters

A.2.2 Understanding the contents of the kubeconfig file

apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/luksa/.minikube/ca.crt
    server: https://192.168.99.100:8443
  name: minikube
contexts:
- context:
    cluster: minikube
    user: minikube
    namespace: default
  name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
  user:
    client-certificate: /home/luksa/.minikube/apiserver.crt
    client-key: /home/luksa/.minikube/apiserver.key

上記のようにKubeconfig fileは以下で構成されている。

  • clustersのリスト
  • usersのリスト
  • contextsのリスト
  • current contextの名前

それぞれの詳細

cluster

  • Kubernetes clusterを表している。API ServerのURL、certificate authority(CA)ファイル、他には必須ではないがAPI serverと通信するためのいくつかのオプション。 CA certificateは分割して保存できそれをkubeconfigファイル内から指定できる。また直接certificate-authority-dataフィールドに含めることも出来る。

user

  • どのuserもAPI Serverと通信する時に使うcredentialsを定義する。credentialsには以下が使える
    • usernameとpassowrd
    • authentication token
    • client key and certificate
  • client key and certificateはkubeconfig fileに含めることができる。もしくはファイルを分割してconfig file内で指定することができる。以下のように
users:
- name: minikube
  user:
    client-certificate: /home/luksa/.minikube/apiserver.crt
    client-key: /home/luksa/.minikube/apiserver.key

context

  • contextはclusterとuserを紐付けるもの。またkubectlで使われるデフォルトのnamespaceを指定できる。複数のcontextで、同じuserや同じclusterを指定することができる。

appendix B: Setting up a multi-node cluster with kubeadm

kubeadmを使えば複数nodeクラスタが簡単に作成できる。

appendix C: Using other container runtimes

C.1 Replacing Docker with rkt

container runtimeとしてdockerのほかにrktがある。 rktはデフォルトでPod(複数の関連するコンテナを実行する)の概念をサポートしている。 rktはDocker-formatted container imageをそのまま実行することができるのでリパッケージは必要がない。 Kubernetesでrktを使うためにはkubeletに--container-runtime=rktオプションを渡してあげればよい。 なおkubeletはContainer Runtimeとやりとりする唯一のコンポーネント

C.2 Using other container runtimes through the CRI

Kubernetes 1.5 から Container Runtime Interface (CRI) が導入された。 CRIはplugin APIで他のcontainer runtimeを統一的に操作することができる。 kubeletはDockerやrktをCRIを通して操作する。

新しいCRI実装として、CRI-Oというものもでてきた。

Kubernetesをコンテナの代わりにVM上でアプリケーションを動かせる新しいCRI実装が開発中。 そのうちの一つはFrakti(The hypervisor-based container runtime for Kubernetes.)というもの。 https://github.com/kubernetes/frakti

ほかには Mirantis VirtletというCRI実装があり、これは、docker imageの代わりに実際にVM imagesを動かすというもの。

appendix D: Cluster Federation

Kubernetesは複数のKubernetesを1つのKubernetesとして扱うための Cluster Federationという機能がある。 これにより、Kubernetes Clusterを複数のdata center、複数のアベイラビリティゾーン、もしくは複数のクラウド冗長化することができる。