Kubernetes入門
障害発生時に各コンテナの設定・復旧が大変
Reconcilation Loop によって障害から自動復旧するよう試みる
コンテナの仕様を個々に管理するのが大変
YAML ファイルを利用して各設定を管理できる (IaC)
サーバーが複数台ある時にどのサーバーでコンテナを起動させるべきか大変
Kubernetes の API インフラレイヤが抽象化されていて、サーバー固有の設定をしる必要がない
https://scrapbox.io/files/6737e6d15ceff65bc50b40e4.svg
kubectl は API serverにアクセス
Control Plane Components
"Manage the overall state of the cluster" とのこと
kube-apiserver: The core component server that exposes the Kubernetes HTTP API
etcd: Consistent and highly-available key value store for all API server data
kube-scheduler: Looks for Pods not yet bound to a node, and assigns each Pod to a suitable node.
なんか cron 的なやつかと思ったけど pod を node に割り当てる君なのか
kube-controller-manager: Runs controllers to implement Kubernetes API behavior.
Controllerは監視対象リソースの変更や一定時間経過などのイベントをトリガーとして調整ループ(Reconciliation Loop)を実行
Node Components
kubelet: Ensures that Pods are running, including their containers. Node に1つ?
kube-proxy: (optional) Maintains network rules on nodes to implement Services. なんのこっちゃ???
高レベルコンテナランタイム
KubernetesやDockerと直接話して、低レベルコンテナランタイムに渡してくれる役割をしているんですね。これはDockerやKubernetesからの命令を直接受け付ける関係で、ホストOSのうえにdaemonプロセスとして常駐しています。
Kubernetesとやりとりするインターフェイスは決まっていて、それを「CRI」(Container Runtime Interface)と呼びます。CRI自体はgRPC通信で成り立ち、UNIXのソケット通信でgRPCのProtocol Buffersを渡してあげると、containerdとかdockershimなどとやりとりできるようになります。 また高レベルコンテナランタイムは、コンテナイメージを管理する役割も持っています。例えば nginxのイメージをレジストリからpullしてきてローカルに保存したとすると、そのローカルのイメージ管理もこの高レベルコンテナランタイムで行っています。
高レベルコンテナランタイム自体はコンテナを作成しません。そこは低レベルランタイムの役割で、高レベルコンテナランタイムが「docker run」とかそういう命令を受け取ってコンテナを作ってと依頼されたら、じゃあこのイメージがここにあるからコンテナを作ってね、という命令を低レベルコンテナランタイムにお願いします。
低レベルコンテナランタイム
低レベルコンテナランタイムはdaemonではなくてバイナリなんですね。なので誰かから実行してもらう必要があり、高レベルコンテナランタイムがこれを実行しています。
低レベルコンテナランタイムは、コンテナの標準仕様「OCI」(Open Container Initiative)に基づいた「config.json」にどういうコンテナを生成するかが書いてあるので、それに従ってコンテナを実際にspawnするのが役割です。
このときに先ほど説明したLinuxのnamespacesやcgroupの処理も行っています。
Control plane は Woker Node に直接指示をしない。代わりに Worker Node が Control Plane に問い合わせる方式をとることで、Control Plane が壊れても Worker Node 上に起動するコンテナが破壊されるわけではない。
code:sh
$ kind version
kind v0.25.0 go1.23.3 darwin/arm64
$ kind create cluster --image=kindest/node:v1.29.0
$ kubectl cluster-info --context kind-kind
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
$ kind delete cluster
hr.icon
Manifest, Pod
マニフェストというYAMLファイルを使ってコンテナを起動する
code:yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.25.3
ports:
- containerPort: 80
上のマニフェストはPodという最小構成単位のリソースを作成。
Pod は複数コンテナをまとめてきどう。例えばAというサービスとログ転送のサイドカーコンテナを1つのPodとして起動など
起動・リリースのタイミングを合わせたいとか、ローカルファイルにアクセスしたいときとか
Namespace
Namespaceはtなにつクラスタ内のリソース軍を分離するメカニズム
リソースの名前はNamespace内で一意である必要があるが、Namespace間では重複可能
ただしクラスタワイドに作成できるリソース(Nodeなど)はNamespaceの適用範囲外
kube-system namespace は Control Plane や Worker Node で起動する kubernetes のシステムコンポーネントの Pod が利用する namespace
pod の作成
cluster があることを確認
code:sh
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 4h8m v1.29.0
code:yaml
# myapp.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
app: myapp
spec:
containers:
- name: hello-server
image: blux2/hello-server:1.0
ports:
- containerPort: 8080
code:sh
$ kubectl get pod --namespace default
No resources found in default namespace.
$ kubectl apply --filename myapp.yaml --namespace default
pod/myapp created
$ kubectl get pod --namespace default
NAME READY STATUS RESTARTS AGE
myapp 1/1 Running 0 34s
kubectl run は Pod を一時的に使う時に利用。
hr.icon
kubectl get pod を実行した結果、podはすべてRunningか?
pod がすべて Running
Pod 内のContainer はすべて Ready の場合
kubectl logs <Pod名> の結果ログにエラーが出ていればアプリケーションエラーかも
ログにエラーが出てない場合は kubectl describe service名 <Service> で Endpoint 一覧が見えて無ければ Service の selector が間違えているかも?
kubectl port-forward service/<Service名> 8080:<serive-port> を実行してアプリケーションにアクセスできなければServiceのTargetPortとコンテナのportが間違っているかも?
Pod 内の Container が Ready でない場合
kubectl describe pod <Pod名> Readiness Probe / Liveness probe で成功しているか? 成功していなければ probe を直す
Pod が Running になってない
Pod の status は?
CrashLoopBackOff
コンテナのログを参照してエラーが出ていれば直す。
エラーがない場合は CrashLoopBackOff を繰り返すようなら Liveness Probe の瀬底を見直す
ImagePullBackOff
イメージが間違ってる?
タグ名が間違っている?
プライベートリポジトリならリポジトリの問題かも?
Pending
kubectl describe pod <Pod名> で Events にかかれている内容を見る
Pod の status
Pending
Kubernetes クラスタから Pod の作成は許可されたものの、1つ以上のコンテナが準備中である。Pod起動直後にこのSTATUSが表示されることがあるが、長時間このstatusである場合は異常かも。PodのEventsを参照しよう(なにそれ?)
Running
Podがノードにスケジュールされて、すべてのコンテナが作成された状態。常時起動が想定されるPodなら正常なstatus
Completed
Pod内のすべてのコンテナが完了した状態。再起動はなし
Unknown
何らかの理由でPodの状態を取得できなかった。Podが実行されるべきノードとの通信エラーが原因
ErerImagePull
イメージの取得に失敗したこと。PodのEventsを参照しよう
Error
コンテナが異常終了したことを表す。Podのログを参照しよう
OOMKilled
コンテナがOOMで終了。仕様リソースを増やそう
Terminating
Podが削除中の状態を表す。これを繰り返す場合は異常かも、PodのEventsを参照
参照系コマンド
kubectl でいろんな情報を見る。
kubectl get pod
kubectl get pod <pod名>? --output wide --namespace default IPアドレスやNode情報も追加して表示
--output yaml YAMLファイル形式で取得
--output jsonpath='{.spec.containers[].image}' jq みたいな記法で指定したspecの情報を取得
--v=<log level>
kubectl describe pod <Pod名>
Events の内容は特にトラブルシューティングで役に立つ(ただし一定時間で消えてしまう
kubectl logs
コンテナからの標準出力が表示される。
kubectl logs myapp --namespace default
kubectl logs myapp --container hello-server --namespace default で特定コンテナからのログに絞ることも可能
kubectl logs deploy/<Deployment名>
Deployment というリソースを使用した場合 Pod名がランダムになる。Deployment を使って log 取得できる
kubectl get pod --selector <labelのキー名>=<labelの値>
label を使ってPodの参照
kubectl get pod --selector app=myapp
kubectl logs --selector app=myapp
デバッグ用のサイドカーコンテナを立ち上げる kubectl debug
code:sh
$ kubectl debug --stdin --tty
<debug対象Pod名>
--image=<debug container image>
--target=<デバッグ対象のコンテナ名>`
コンテナはアプリケーションの起動に最低限必要なツールしか道混んていなかったりすることが多い。デバッグ用コンテナを起動することで、多様なデバッグツールを利用して (例えば curl 使ったり) デバッグできる
code:sh
$ kubectl debug --stdin --tty myapp --image=curlimages/curl:8.4.0 --target=hello-server --namespace default -- sh
Targeting container "hello-server". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
Defaulting debug container name to debugger-crql6.
~ $ curl localhost:8080
Hello, world!~ $
Ctrl-D
Session ended, the ephemeral container will not be restarted but may be reattached using 'kubectl attach myapp -c debugger-crql6 -i -t' if it is still running
コンテナを即座に実行する: kubectl run
code:sh
$ kubectl --namespace default run busybox --image=busybox:1.36.1 --rm --stdin --tty --restart=Never --command -- nslookup google.com
Server: 10.96.0.10
Address: 10.96.0.10:53
Non-authoritative answer:
Name: google.com
Address: 172.217.161.238
Non-authoritative answer:
Name: google.com
Address: 2404:6800:400a:805::200e
pod "busybox" deleted
--rm 実行が完了したらPodを削除
--stdin: 標準入力に渡す
--tty: 擬似端末を割り当てる (--stdin と --tty 合わせて -it と書くこともある)
--restart=Never Podの再起動ポリシーをNeverに設定。コンテナが終了しても再起動を行わない。
--command -- -- の後に渡される拡張引数の1つ目が引数ではなくコマンドとして使われる。
busybox という Pod を起動して nslookup を実行したら終了する
コンテナにログインする: kubectl exec
ログイン用のPodを作成
code:sh
# ログイン用のpodを作成
$ kubectl --namespace default run curlpod --image=curlimages/curl:8.4.0 --command -- /bin/sh -c "while true; do sleep initify; done"
# pod が作成できていることを確認
$ kubectl get pod --namespace default
# myapp の IPアドレスを取得
$ kubectl get pod myapp --output wide --namespace default
# ログイン用のpodから curlを叩いて疎通確認
$ kubectl --namespace default exec -it curlpod -- /bin/sh
$$ curl <myapp pod の IP>:8080
アプリケーションがインターネット上からアクセスできなくなった時に、クラスタ内からIPでアクセスできるか確認して、問題を切り分け
port-forward でアプリケーションにアクセス
Pod には kubenetes クライスタ内のIPアドレスが割り当てられる。そのため、何もしないとクラスタ外からのアクセスはできない。(Serviceというリソースを利用してクラスタ外からアクセス可能にできるが、kubectlからお手軽アクセス
code:sh
$ kubectl port-forward myapp 5555:8080 --namespace default
Forwarding from 127.0.0.1:5555 -> 8080
Forwarding from ::1:5555 -> 8080 $ curl localhost:5555
障害を直すためのkubectlコマンド
マニフェストをその場で編集する: kubectl edit
基本は使わないで kubectl apply を使おう。一刻を争うときだけ
code:sh
$ kubectl edit pod myapp --namespace default
...
リソースを削除する: kubectl delete
Pod を再起動するっていうことができてないので、kubectl delete して Deployment というリソースを使ってpodを冗長化してるので、特定のpodがハングしてしまったときは Pod を削除
code:sh
$ kubectl delete pod myapp --namespace default
Deploymentを利用したPodをすべて順番に咲きどうしたい場合は
code:sh
$ kubectl rollout restart
hr.icon
ReplicaSet
ReplicaSetは指定した数のPodを複製するリソース。
code:yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: httpserver
labels:
app: httpserver
spec:
replicas: 3
selector:
matchLabels:
app: httpserver # template の label と一致している必要がある(なにこれ)
template:
metadata:
labels:
app: httpserver
spec:
containers:
- name: nginx
image: nginx:1.25.3
ReplicaSetは同じPodを複製するため、自動でPodにsuffixをつける。httpserver-xxx
kubectl get replicaset --namespace default
Deployment
ReplicasetはPodを複製して冗長性を担保するのが仕事
本番の運用環境ではそれに加えて無停止でリソースを更新することが求められる。
Replicasetを古いのを残しつつ、新しいのを立ち上げたい
code:deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLables:
app: nginx # template の labels と一致している必要がある。
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.24.0
ports:
- containerPort: 80
kubectl get deployment --namespace default
code:sh
$ kubectl describe deployment nginx-deployment
...
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
↑ Deployment にのみ存在するフィールド
StrategyType
Podをどのような戦略で更新するかを指定
Recreate: 全部のPodを同時に更新
RollingUpdate: Podを順番に更新。アプリケーションのアップデート中も運用を継続でき、サービスの停止を最小限に抑えられる。RollingUpdateStrategyを記載できる
RollingUpdateStrategy
maxUnavailable
最大いくつのPodを同時にシャットダウンできるか
maxSurge
最大いくつのPodを新規作成できるか
一度に必要な数の新規Podを同時に作ると、クラスタ環境に2倍のPodが必要になる。
code:sh
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 10
strategy:
type: Recreate
selector:
matchLabels:
app: nginx # template の labels と一致している必要がある。
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.24.0
ports:
- containerPort: 80
lifecycle:
preStop:
exec:
hr.icon
6.3 Pod へのアクセスを助けるService
DeploymentはIPアドレスを持たないため、Deploymentで作ったリソースにあくせすするためにはPod個々にアクセエスする必要がある...Deploymentで作成した複数Podへのアクセスを適切にルーティングしてもらうためにService というリソースを利用する。
code:service.yaml
apiVersion: v1
kind: Service
metadata:
name: hello-server-service
spec:
selector:
ports:
- protocol: TCP
port: 8080
targetPort: 8080
code:deployment-hello-server.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-server
labels:
app: hello-server
spec:
replicas: 3
selector:
matchLabels:
app: hello-server
template:
metadata:
labels:
app: hello-server
spec:
containers:
- name: hello-server
image: blux2/hello-server:1.0
ports:
- containerPort: 8080
それぞれ kubectl apply して
code:sh
❯ kubectl port-forward svc/hello-server-service 8080:8080 --namespace default
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from ::1:8080 -> 8080 Handling connection for 8080
$ curl localhost:8080
Hello, world!
code:sh
❯ kubectl get service hello-server-service --namespace default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-server-service ClusterIP 10.96.47.46 <none> 8080/TCP 96s
ClusterIP: クラスタ内部のIPアドレスでServiceを公開。このTypeで指定されたIPアドレスはクラスタ内部からしか疎通できない。Ingress というリソースを利用することで外部公開が可能になる
NodePort: すべてのNodeのIPアドレスで指定したポート番号(???
LoadBalancer: 外部のロードバランサを用いて外部IPアドレスを公開。ロードバランサは別で用意する必要あり
ExternalName: ServiceをexternalNameフィールドの内容にマッピン具。クラスタのDNSさーばーがその外部干すtの名の値をもつCNAMEレコードを返すように設定される。
hr.icon
ClusterIP
code:sh
❯ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-server-service ClusterIP 10.96.47.46 <none> 8080/TCP 6d21h
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 8d
❯ kubectl run curl --image curlimages/curl --rm --stdin --tty --restart=Never --command -- curl 10.96.47.46:8080
Hello, world!pod "curl" deleted
NodePort
code:sh
apiVersion: v1
kind: Service
metadata:
name: hello-server-external
spec:
type: NodePort
selector:
app: hello-server
ports:
- port: 8080
targetPort: 8080
nodePort: 30599
code:sh
❯ kubectl get service hello-server-external
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-server-external NodePort 10.96.229.240 <none> 8080:30599/TCP 13s
❯ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
kind-nodeport-control-plane Ready control-plane 33m v1.29.0 172.19.0.2 <none> Debian GNU/Linux 11 (bullseye) 6.4.16-linuxkit containerd://1.7.1
# internal IP は応答がなかったんだけど...
❯ curl localhost:30599
Hello, world!%
NodePortはローカル開発環境にはよいけど本番環境ではClusterIPやLoadBalancerが良い(Nodeが故障すると使えなくなるため)
Service を利用したDNS