サイボウズ新人研修の「Kubernetes を使った開発入門」を触ってみた備忘録
はじめに
サイボウズの新人研修の資料が公開されており、そこに 「Kubernetes を使った開発入門」 なる研修資料があったので、そちらを Kubernetes 初心者が触ってみた備忘録になります。
解説は本家のサイトがとても詳しく解説してあるので、詳しい解説を求める方はそちらを参照ください。
また、kubectl コマンドについてもほとんど解説しませんが こちら の記事がわかりやすかったです。
感想
触ってみた感想ですが、かなり分かりやすかったので Kubernetes を入門するにはオススメだと思います。
その辺の Kubernetes 関連の記事やスライドを読むときの理解度が、こちらの研修を動かした前と後では全く違うので、とてもためになりました。
研修資料について
以下の 3 部構成です。
- Introduction to Kubernetes
- More Introduction to Kubernetes
- Exercise
「Introduction to Kubernetes」が入門編のメインになっており、「More Introduction to Kubernetes」は "本番運用を想定したらこういうことも考える必要ありますよ" というようなやや応用的な内容になります。「Exercise」は演習問題ですがこちらは手をつけてないです(甘え)。
この記事は「Introduction to Kubernetes」については手厚めに記載します。
「More Introduction to Kubernetes」の内容については触りだけ記載しています。
Introduction to Kubernetes
基本編です。
Kubernetes 環境を整える
Minikube を使用します。
Minikube はシングルノードの Kubernetes クラスタを立ち上げることができます。
複数ノードの Kubernetes クラスタは Minikube ではできないようです。
また、Minikube を使用するには Docker の設定で CPUs:2
以上が必要です。
(CPUs:1
では怒られて Minikube を起動しませんでした。)
Kubernetes のダッシュボードを表示する
# Minikube のインストール
$ brew install minikube
# バージョン確認
$ minikube version
minikube version: v1.12.3
commit: 2243b4b97c131e3244c5f014faedca0d846599f5
# ステータス確認
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
# ダッシュボードの起動
$ minikube dashboard
以下のようなダッシュボードが表示されます。
Kubernetes 上にコンテナをデプロイしてみる
Kubernetes 上にデプロイできる最小単位は Pod という単位になります。
Pod はひとつ以上のコンテナをまとめたものです。 同じ Pod に属するコンテナは常に同じノードに配置されます。
概念的には Container < Pod < Node という順でまとまった単位になります。
ちなみに Minikube は Node になります。
Kubernetes 上にデプロイする Pod を設定した YAML ファイルの準備をします。
一旦、以下の内容で記述します。
apiVersion: v1
kind: Pod
metadata:
name: my-first-pod
labels:
component: nginx
spec:
containers:
- name: nginx
image: nginx:latest
kubectl apply -f [applyするYAMLファイル名]
コマンドで Kubernetes 上にデプロイします。
# Kubernetes への apply
$ kubectl apply -f nginx-pod.yaml
pod/my-first-pod created
# クラスタ内の Pod の一覧を表示
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
my-first-pod 1/1 Running 0 103s
# Pod の詳細を表示
$ kubectl describe pod my-first-pod
Name: my-first-pod
Namespace: default
Priority: 0
Node: minikube/172.17.0.3
Start Time: Sat, 15 Aug 2020 04:15:37 +0900
Labels: component=nginx
(省略)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 5m5s default-scheduler Successfully assigned default/my-first-pod to minikube
Normal Pulling 5m4s kubelet, minikube Pulling image "nginx:latest"
Normal Pulled 4m50s kubelet, minikube Successfully pulled image "nginx:latest"
Normal Created 4m50s kubelet, minikube Created container nginx
Normal Started 4m50s kubelet, minikube Started container nginx
# Pod へのログイン
kubectl exec -it my-first-pod -- /bin/bash
# curl コマンドのインストール
root@my-first-pod:/# apt update && apt install -y curl
(省略)
# localhost:80 へのアクセス(疎通確認)
root@my-first-pod:/# curl -i localhost:80
HTTP/1.1 200 OK
(省略)
ダッシュボードには以下のように my-first-pod が表示されます。
他の Pod にアクセスする
Pod - Pod 間の疎通を確認します。
my-first-pod とは別の Pod を用意します。
apiVersion: v1
kind: Pod
metadata:
name: bastion
spec:
containers:
- name: bastion
image: debian:stretch
command: ["sleep", "infinity"]
ちなみに bastion は「踏み台」という意味になります。
bastion -> my-first-pod (nginx) への疎通を確認します。
# Kubernetes への apply
$ kubectl apply -f bastion.yaml
pod/bastion created
# Pod の一覧表示
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
bastion 1/1 Running 0 14s
my-first-pod 1/1 Running 1 10h
# IP アドレスの表示
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
bastion 1/1 Running 0 84s 172.18.0.6 minikube <none> <none>
my-first-pod 1/1 Running 1 10h 172.18.0.2 minikube <none> <none>
# bastion にログインして `my-first-pod` のv IP アドレスの `172.18.0.2` にアクセス
$ kubectl exec -it bastion -- bash
(bastion)# apt update && apt install -y curl
(bastion)# curl -i http://172.18.0.2/
HTTP/1.1 200 OK
(省略)
ダッシュボードでは以下のように bastion が追加されたことが確認できます。
Service を使って他の Pod にアクセスする
さきほど、Pod - Pod 間の疎通を試しましたが、Pod は消えたり増えたりするものなので、IP アドレスが変わってしまい Pod の IP アドレスに対して通信を行うのはよくないです。
そこで特定の Pod ではなく「所望の機能を提供する Pod のどれか」と通信するための安定した(=変化しない)エンドポイントが欲しくなります。
そこで、普通他の Pod との通信するときには Service というオブジェクトを使います。 Service を使うと「ある特定の Pod」ではなく「特定の ラベル を持つ Pod のどれか」にアクセスすることができます。
ラベルというのは、上の nginx の例だと component: nginx
の部分です。 ここでの component や nginx が特別な意味を持っているわけではなく、利用者が好きな文字列を指定できます。
今回は以下のような Service を使用します。
apiVersion: v1
kind: Service
metadata:
name: my-first-service
spec:
selector:
component: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
Service を Kubernetes へ apply します。
apply の方法は Pod と同じになります。
$ kubectl apply -f nginx-service.yaml
service/my-first-service created
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12h
my-first-service ClusterIP 10.101.46.86 <none> 80/TCP 3m7s
ダッシュボードでは以下のように my-first-service が追加されたことが確認できます。
bastion の Pod から my-first-service を通して component: nginx
でラベリングしている my-first-pod の Pod に先ほど同様にアクセスしてみます。
$ kubectl exec -it bastion -- bash
root@bastion:/# curl -i http://my-first-service/
HTTP/1.1 200 OK
(省略)
図で表すと以下のようになります。
同じ Pod をいくつも立てる
前の節では nginx を一台立ち上げました。 実際の運用環境では冗長性やスケーラビリティのために一つのサービスを複数台の Pod で構成することが一般的です。 ここでは Kubernetes の ReplicaSet というオブジェクトを利用して nginx を指定した台数だけ立ち上げてみましょう。
以下が今回使用する ReplicaSet の定義になります。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-replicaset
labels:
component: nginx
spec:
replicas: 3
selector:
matchLabels:
component: nginx
template:
metadata:
labels:
component: nginx
spec:
containers:
- name: nginx
image: nginx:latest
詳しい説明は省略しますが、ReplicaSet は「selector にマッチする Pod の数」が「replicas に指定した数」になるように Pod を自動的にデプロイしたり削除したりするオブジェクトです。
とりあえず apply して pod の数を確認してみます。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
bastion 1/1 Running 0 109m
my-first-pod 1/1 Running 1 12h
$ kubectl apply -f nginx-replicaset.yaml
replicaset.apps/nginx-replicaset created
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
nginx-replicaset 3 3 3 13s
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
bastion 1/1 Running 0 110m
my-first-pod 1/1 Running 1 12h
nginx-replicaset-wqcdk 1/1 Running 0 28s
nginx-replicaset-xbs28 1/1 Running 0 28s
ダッシュボードをみると Pod が 3つあがっているのが確認できます。
ここで試しに Pod を一つ消してみます。
Pod を消してみてもまたすぐに Pod が 3 つになるように立ち上がります。
$ kubectl delete pod my-first-pod
pod "my-first-pod" deleted
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
bastion 1/1 Running 0 123m
nginx-replicaset-dn57j 1/1 Running 0 9s
nginx-replicaset-wqcdk 1/1 Running 0 13m
nginx-replicaset-xbs28 1/1 Running 0 13m
ダッシュボードでも変わらず Pod が 3つあがっているのが確認できます。
つづいて replicas: 3
-> replicas: 4
に変更して apply してみます。
$ kubectl apply -f nginx-replicaset.yaml
replicaset.apps/nginx-replicaset configured
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
bastion 1/1 Running 0 126m
nginx-replicaset-5swkc 1/1 Running 0 7s
nginx-replicaset-dn57j 1/1 Running 0 3m33s
nginx-replicaset-wqcdk 1/1 Running 0 16m
nginx-replicaset-xbs28 1/1 Running 0 16m
nginx の Pod が 4 つになりましたね。
もちろんダッシュボードでも確認できます。
さらにこの状態から ReplicaSet を削除してみます。
$ kubectl delete replicaset nginx-replicaset
replicaset.apps "nginx-replicaset" deleted
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
bastion 1/1 Running 0 163m
nginx-replicaset-5swkc 0/1 Terminating 0 37m
nginx-replicaset-dn57j 0/1 Terminating 0 40m
nginx-replicaset-wqcdk 0/1 Terminating 0 54m
nginx-replicaset-xbs28 0/1 Terminating 0 54m
nginx の Pod がなくなりましたね。
ダッシュボードを確認すると nginx の Pod も ReplicaSet も消えていることが確認できます。
ローリングアップデートする
Kubernetes でローリングアップデートを行うには Deployment というオブジェクトを利用します。 Deployment は ReplicaSet と似たオブジェクトですが、ReplicaSet と違ってアップデートがサポートされています。
ちなみに Deployment を使わずに ReplicaSet でローリングアップデートを行う場合は以下の図のような手順を踏む必要があります。
この複雑な手順が Deployment を使うと Kubernetes にやらせることができます。
今回使用する Deployment の YAML は以下のようになります。ほとんど ReplicaSet と同じです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
component: nginx
spec:
replicas: 3
selector:
matchLabels:
component: nginx
template:
metadata:
labels:
component: nginx
spec:
containers:
- name: nginx
image: nginx:1.15
Deployment を apply してみます。
$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 40s
# 確認(バージョンを確認するために IMAGE を表示するようにしています)
$ kubectl get pod -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image,PHASE:.status.phase'
NAME IMAGE PHASE
bastion debian:stretch Running
nginx-deployment-77bc96745b-9tdr2 nginx:1.15 Running
nginx-deployment-77bc96745b-kg96q nginx:1.15 Running
nginx-deployment-77bc96745b-xn4v6 nginx:1.15 Running
ダッシュボード上ではこのように表示されます。
ここで image: nginx:1.15
-> image: nginx:1.16
に変更して、Deployment を apply してみます。
$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment configured
以降、kubectl get pod
コマンドを実行し続けると Kubernetes がローリングアップデートしている様子を伺うことができます。
$ kubectl get pod -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image,PHASE:.status.phase'
NAME IMAGE PHASE
bastion debian:stretch Running
nginx-deployment-5b4c7f657-pnwkr nginx:1.16 Pending
nginx-deployment-77bc96745b-9tdr2 nginx:1.15 Running
nginx-deployment-77bc96745b-kg96q nginx:1.15 Running
nginx-deployment-77bc96745b-xn4v6 nginx:1.15 Running
$ kubectl get pod -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image,PHASE:.status.phase'
NAME IMAGE PHASE
bastion debian:stretch Running
nginx-deployment-5b4c7f657-dcqwd nginx:1.16 Running
nginx-deployment-5b4c7f657-pnwkr nginx:1.16 Running
nginx-deployment-5b4c7f657-t9ddv nginx:1.16 Running
nginx-deployment-77bc96745b-9tdr2 nginx:1.15 Running
nginx-deployment-77bc96745b-kg96q nginx:1.15 Running
nginx-deployment-77bc96745b-xn4v6 nginx:1.15 Running
SuguruTakahashiMBP sugurutakahashi ~/git/k8s-sample $
$ kubectl get pod -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image,PHASE:.status.phase'
NAME IMAGE PHASE
bastion debian:stretch Running
nginx-deployment-5b4c7f657-dcqwd nginx:1.16 Running
nginx-deployment-5b4c7f657-pnwkr nginx:1.16 Running
nginx-deployment-5b4c7f657-t9ddv nginx:1.16 Running
このようにすべての Pod が image: nginx:1.15
-> image: nginx:1.16
となりましたね。
ダッシュボードでも確認できます。
Kubernetes を実際に利用する場合、Pod や ReplicaSet を直接作成することはほとんどないそうです。
Pod を作成するときは Deployment を使って作成することが大抵のケースでベストプラクティスとのことです。
それが例え replica 数が1の場合でも Pod を作成する場合は Deployment を使って作るべきであり、理由は単に Pod をひとつデプロイした場合、Pod が立っているノードが死ぬと Pod も道連れになってしまうからです。
サービスをクラスタの外部に公開する
Kubernetes 上にデプロイされたサービスを Kubernetes クラスタの外側に公開する方法を説明します。 クラスタ外にサービスを公開する方法はいくつかありますが、ここでは最も簡単な NodePort を使う方法を説明します。
Service を以下のように変更します。
apiVersion: v1
kind: Service
metadata:
name: my-first-service
spec:
selector:
component: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
nodePort: 30000
と type: NodePort
の行が追加された行です。
apply します。
$ kubectl get service my-first-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-first-service ClusterIP 10.101.46.86 <none> 80/TCP 3m7s
$ kubectl apply -f nginx-service.yaml
service/my-first-service configured
# TYPE が変わっていますね
$ kubectl get service my-first-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-first-service NodePort 10.101.46.86 <none> 80:30000/TCP 124m
この作業によって、my-first-service の type が ClusterIP (デフォルト) から NodePort に変更されました。
ダッシュボードでもポート番号 30000 で空いていることが確認できます。
ここからが研修資料と異なるのですが、<minikube ip
で返却されたIPアドレス : 30000> でアクセスしてもうまくいかなかったので、以下のコマンドで IP アドレス+ポート番号が公開されるみたいなのでそちらで実行してみました。
$ minikube service my-first-service --url
🏃 Starting tunnel for service my-first-service.
|-----------|------------------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|------------------|-------------|------------------------|
| default | my-first-service | | http://127.0.0.1:53157 |
|-----------|------------------|-------------|------------------------|
http://127.0.0.1:53157
http://127.0.0.1:53157
にブラウザからアクセスすると以下のように nginx に接続できました。
Pod をデバッグする
多くの場合、デプロイした Pod をデバッグする必要があります。
kubectl exec
でのログインや kubectl describe
は Pod のデバッグに役立ちます。
この節では、この2つに加えてデバッグで便利な2つのコマンド kubectl logs
と kubectl port-forward
について説明します。
コンテナのログを見る
kubectl logs
コマンドでログが確認できます。
# 現在の Pod 一覧
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-5b4c7f657-cbd8r 1/1 Running 0 103m
nginx-deployment-5b4c7f657-fvd88 1/1 Running 0 103m
nginx-deployment-5b4c7f657-lqqkm 1/1 Running 0 103m
# 特定の Pod に対してログを表示する
$ kubectl logs nginx-deployment-5b4c7f657-cbd8r
172.17.0.3 - - [15/Aug/2020:10:32:24 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
172.17.0.3 - - [15/Aug/2020:10:32:24 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://127.0.0.1:52378/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
2020/08/15 10:32:24 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.3, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:52378", referrer: "http://127.0.0.1:52378/"
# ラベルに該当する Pod に対してログを表示する
$ kubectl logs -l component=nginx
172.17.0.3 - - [15/Aug/2020:10:32:24 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
172.17.0.3 - - [15/Aug/2020:10:32:24 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://127.0.0.1:52378/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
2020/08/15 10:32:24 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.3, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:52378", referrer: "http://127.0.0.1:52378/"
172.17.0.3 - - [15/Aug/2020:10:45:24 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
2020/08/15 10:45:25 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.3, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:52724", referrer: "http://127.0.0.1:52724/"
172.17.0.3 - - [15/Aug/2020:10:45:25 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://127.0.0.1:52724/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
172.17.0.3 - - [15/Aug/2020:12:01:44 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
2020/08/15 12:01:44 [error] 6#6: *3 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.3, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:53157", referrer: "http://127.0.0.1:53157/"
172.17.0.3 - - [15/Aug/2020:12:01:44 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://127.0.0.1:53157/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
ポートフォワードする
kubectl port-forward
コマンドを使うと、Pod の特定のポートをローカルホストにポートフォワードすることができます。
これを使えばローカルの curl コマンドやブラウザから直接 Pod にアクセスできます。
例えば、以下のコマンドを実行するとローカルの 8080 をクラスタ内の nginx の 80 にフォワードしてくれます。
$ kubectl port-forward deployment/nginx-deployment 8080:80
localhost:8080
にアクセスすると "Welcome to nginx!" が表示されます。
More Introduction to Kubernetes
さらに本番環境での運用を想定して重要な機能を説明します。
ここではさらっと触れる程度での紹介になります。
ヘルスチェック
Kubernetes のヘルスチェックは以下の2つがあり、用途に応じて使い分けることができます。
- Liveness probe ― Pod が生きているか死んでいるかをチェックします。一定回数 Liveness probe が失敗した場合、Pod は再起動されます。
- Readiness probe ― Pod がリクエストに応答できるかどうかをチェックします。Readiness probe が成功するまでの間、その Pod は起動が完了していないとみなされ、Service のリバプロ先に追加されません。Readiness probe を使うことで Pod が起動が完了する前にリクエストが飛んでくる現象を防ぐことができます。
研修資料のサンプルは動かすことができなかったので、こちらの記事を参考に YAML ファイルを定義しました。
apiVersion: v1
kind: Pod
metadata:
name: sample-healthcheck
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx:1.12
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /index.html
port: 80
scheme: HTTP
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 2
initialDelaySeconds: 5
periodSeconds: 3
readinessProbe:
exec:
command: ["ls", "/usr/share/nginx/html/50x.html"]
timeoutSeconds: 1
successThreshold: 2
failureThreshold: 1
initialDelaySeconds: 5
periodSeconds: 3
apply してみます。
$ kubectl apply -f sample-healthcheck.yaml
pod/sample-healthcheck created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
sample-healthcheck 1/1 Running 0 17s
# describe で Liveness の設定を確認
$ kubectl describe pod sample-healthcheck | grep "Liveness"
Liveness: http-get http://:80/index.html delay=5s timeout=1s period=3s #success=1 #failure=2
# describe で Readiness の設定を確認
$ kubectl describe pod sample-healthcheck | grep "Readiness"
Readiness: exec [ls /usr/share/nginx/html/50x.html] delay=5s timeout=1s period=3s #success=2 #failure=1
実際にわざとヘルスチェックが発動するように実験します。
# 先に別のコンソールで Pod の状態を監視
$ kubectl get pods sample-healthcheck --watch
# DocumentRoot の index.html の削除(Liveness での監視対象のファイルの削除)
$ kubectl exec -it sample-healthcheck rm /usr/share/nginx/html/index.html
# Pod が再起動する(再起動後は index.html が作成されるため再起動は繰り返さない)
$ kubectl get pods sample-healthcheck --watch
NAME READY STATUS RESTARTS AGE
sample-healthcheck 0/1 Running 2 9m12s
sample-healthcheck 1/1 Running 2 9m20s
Liveness probe, Readiness probe は運用上非常に重要なため、常駐する Pod には必ず両方設定しておくとよいとのことです。
Resource Requests/Limits
Pod に対してリソースをあらかじめ設定することができます。
設定は2種類あります。
- Resource Requests
コンテナが要求するメモリやCPUの量を宣言する機能で、Kubernetes のスケジューラは Resource Requests の値を見て Pod をデプロイするノードを決めます。 - Resource Limits
そのコンテナが実際に使用できるメモリやCPUの量の上限を設定する機能です。 コンテナが使用するメモリが設定された上限を超えた場合、そのコンテナは kill されます。 また CPU 使用量が上限に達した場合、このコンテナはスロットリングされます。
YMML ファイルの例です。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
component: nginx
spec:
containers:
- name: nginx
image: nginx:1.16
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
apply して設定を確認します。
$ kubectl apply -f sample-resource.yaml
$ kubectl describe pod nginx
(省略)
Limits:
cpu: 500m
memory: 128Mi
Requests:
cpu: 250m
memory: 128Mi
(省略)
Volume
Pod に複数のコンテナがあるとき、それらの間でローカルディレクトリを共有したいことがあります。
例えば、APサーバーが出力するログファイルをログ転送エージェントで転送するようなユースケースが挙げられます。
ちなみにサンプルでは fluentd を利用しますが、OSS のデータログ収集ツールとしてはかなり有名なものになります。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
component: nginx
spec:
containers:
- name: nginx
image: nginx:1.16
volumeMounts:
- name: nginx-log
mountPath: /var/log/nginx
- name: fluentd
image: fluent/fluentd:v1.11
volumeMounts:
- name: nginx-log
mountPath: /var/log/nginx
readOnly: true
volumes:
- name: nginx-log
emptyDir: {}
applyして確認します。
$ kubectl apply -f sample-volume.yaml
Pod/nginx created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 2/2 Running 0 2m5s
$ kubectl describe pod nginx
(省略)
Containers:
nginx:
Container ID: docker://3ebb817cf4ee20439c722bbeba711943bb5f05a2d75b420d308faaf1edbd0af2
Image: nginx:1.16
Image ID: docker-pullable://nginx@sha256:d20aa6d1cae56fd17cd458f4807e0de462caf2336f0b70b5eeb69fcaaf30dd9c
Port: <none>
Host Port: <none>
State: Running
Started: Sat, 15 Aug 2020 22:26:10 +0900
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/log/nginx from nginx-log (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-q8zvm (ro)
fluentd:
Container ID: docker://f001646ac161052c67e6aba83ec999b97cb55650c4db095c310e60af75a871cc
Image: fluent/fluentd:v1.11
Image ID: docker-pullable://fluent/fluentd@sha256:617fa61a8a811fd49b730c27835983ecb2a8150584138ec825649ff8352a6d44
Port: <none>
Host Port: <none>
State: Running
Started: Sat, 15 Aug 2020 22:28:13 +0900
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/log/nginx from nginx-log (ro)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-q8zvm (ro)
(省略)
概念的には以下のような図になります。
Anti Affinity
Deployment を使って同じ Pod を2つ作っているとします。 何も指定していないとその2つの Pod が同じノードにスケジュールされることがあります。 このような場合、そのノードが障害やメンテナンスなどで死亡すると2つの Pod が同時に消失してしまいます。
Affinity の機能を使えば Pod が異なるノードに配置されるようにスケジューラーに要求することができます。 Kubernetes の Affinity は非常に柔軟で様々なことができますが、ここでは上で挙げたユースケースに絞って説明します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
component: nginx
spec:
replicas: 2
selector:
matchLabels:
component: nginx
template:
metadata:
labels:
component: nginx
spec:
containers:
- name: nginx
image: nginx:1.16
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: component
operator: In
values:
- "nginx"
topologyKey: "kubernetes.io/hostname"
定義では replicas: 2
と 2 つの Pod が立つようにしています。
そこに affinity:
からの記述で Kubernetes がこの Pod をデプロイするときに component: nginx
というラベルを持つ Pod がいないノードにデプロイしようとします。
これにより、ノードが一台死亡したとしても nginx の Pod がひとつ以上生き残ることが保証されます。
Minikube のシングルノード環境で applyしてみます。
$ kubectl apply -f sample-affinity.yaml
deployment.apps/nginx-deployment created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-6b7b554df-9p4b9 0/1 Pending 0 71s
nginx-deployment-6b7b554df-xf5bp 1/1 Running 0 71s
シングルノードのため、1 つ Pod が立つと、もう 1 つの Pod がデプロイされずに Pending になっていることがわかります。
このように条件を満たすノードが存在しないときは、Pod はデプロイされず、条件を満たすノードが出現するまで Pending 状態になります。
長時間 Pending になっている Pod が存在しないかどうかは監視項目に含めておくとよいとのことです。
Pod Disruption Budget
ノードの再起動が必要になる場合があります。 例えば Linux カーネルに脆弱性が発見された場合などです。
Pod を複数立てて冗長化しておけば、単一のノードのシャットダウンには耐えることができます。
しかし、ノードを次々と再起動していく状況では問題が起こることがあります。
例えば、下の図のように3台のノードからなるクラスタの上に2つの Pod がデプロイされている場合について考えます。
この2つの Pod は Deployment によりレプリカ数が2になるように設定されています。
このクラスタに対して、ノードを1台ずつローリングリブートしていきます。
1台目のノードをシャットダウンする過程で、その上で走っていた Pod が Evict されます。 Pod の数が減ったことを検知した Deployment が直ちに新しい Pod をデプロイします。 しかし、この Pod の起動には時間がかかるため、starting の状態で止まっています。 この状況で node 2 をシャットダウンすると、available な Pod が存在しなくなり、サービスが停止します。
これを防ぐには Pod Disruption Budget によってサービスの稼働に必要な Pod の数の最小値を指定しましょう。
PDB を指定すると、Kubernetes のツールがノードをシャットダウンする前にこの制約が満たされるように適切に待ってくれます。
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: nginx-pdb
spec:
minAvailable: 1
selector:
matchLabels:
component: nginx
apply して確認します。
$ kubectl apply -f frontend-pdb.yaml
poddisruptionbudget.policy/nginx-pdb created
$ kubectl get poddisruptionbudget
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
nginx-pdb 1 N/A 0 66s
当然ですが、PDB の制約が守られるのはクラスタ管理者が意図してノードをシャットダウンする場合だけです。 障害でノードが死ぬ場合には PDB は無視されます。 また、Deployment を削除する場合も PDB は適用されません。
Pod が available かどうかの判定は Readiness probe によって行われます。PDB を正しく機能させるためには、Pod に適切な Readiness probe を設定しておく必要があります。
Exercise
研修資料にある Exercise はやっていません。
ごめんなさい。。。
以上になります。
Discussion