Kubernetesを安全に運用操作するためのTIPS – kubectl編
2023年04月19日 水曜日
CONTENTS
概要
もうIaCの時代であまりKubernetesでもkubectlなどのコマンドラインを手で叩いて操作することは少なくなっています。そんな中でもどうしても障害対応など運用上で触らないといけないときは来るものです。今記事では手作業で操作するときに安全に期待通りの挙動をさせるために工夫していることをご紹介します。
操作時の一般論
運用上、操作を行う際の一般論として以下の3段階に分けて作業を行います。
- 事前確認:操作する前の状態が意図したものであることを確認する
- 操作:実際に行いたい作業を行う
- 事後確認:作業を実施したことによって意図した状態になったかどうかを確認する
「操作」自体が行いたい作業であり、これを実施することでシステムは現在の状態から実現したい状態に変わります。一般的には任意のスコープで冪等性のある操作はないため、実行したい操作が現状に対して成立するか「事前確認」が必要となります。また、作業後に意図した状態に至ったかどうか「事後確認」を行います。
ではこの基本に従って、以下具体的なユースケースに対して記します。
汎用的な事前作業
まず最初に大前提として操作するターゲットに相違がないことを必ず確認します。
手元のターミナルでは常に操作するターゲットの情報を表示するようにしています。Kubernetesの場合、操作するクラスタとnamespaceの2つの情報が重要です。
Bashを使う場合はkube-ps1を使うことができます。
wget https://raw.githubusercontent.com/jonmosco/kube-ps1/master/kube-ps1.sh mv kube-ps1.sh ~/.kube-ps1.sh
~/.bashrc
に以下のように設定をします。
source ~/.kube-ps1.sh if [ "$color_prompt" = yes ]; then PS1='$(kube_ps1) ${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\] \$ ' else PS1='$(kube_ps1) ${debian_chroot:+($debian_chroot)}\u@\h:\w \$ ' fi
すると以下のようなプロンプトになるはずです。
(⎈ |koz-c:mod-test) lhan@kaede:~ $
また、ターミナルの評価は表示時であるため、コマンドを実行する直前に再度Enterを入力して確認すると安全です。
最後にクラスタの操作ミスは致命的であることが多いため、ノードの情報を確認してダブルチェックをすると安心です。
マニフェストファイルへの直接操作
YAMLで書かれたマニフェストファイルで状態を宣言して構成管理をしている場合、以下の手順となります。
kubectl diff -f <filename>
kubectl apply -f <filename>
kubectl get
で内容確認
ワークロードに関する場合 – 変更編
例えばコンテナイメージのタグを変更する作業を考えます。
以下のようにnginxのDeploymentが存在する状態からスタートすることを仮定します。
$ kubectl get deploy nginx NAME READY UP-TO-DATE AVAILABLE AGE nginx 1/1 1 1 14s
次のような設定となっています。
$ kubectl get deploy nginx -oyaml | kubectl neat apiVersion: apps/v1 kind: Deployment metadata: annotations: deployment.kubernetes.io/revision: "1" labels: app: nginx name: nginx namespace: mod-test spec: progressDeadlineSeconds: 600 replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: app: nginx strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: creationTimestamp: null labels: app: nginx spec: containers: - image: nginx imagePullPolicy: Always name: nginx terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler terminationGracePeriodSeconds: 30
このDeploymentによって作られるPodの一覧は以下のとおりです。
$ kubectl get po -l app=nginx NAME READY STATUS RESTARTS AGE nginx-76d6c9b8c-zfd7l 1/1 Running 0 85s
ワークロードの場合、Kubernetesでは最終的にはPodという形になって実行されます。Deploymentを操作する場合は確認するものはPodとなります。あまりないことですが、ReplicaSetを直接触っていたり、StatefulSetを使っていることもありえます。しかし、どのようなリソースを利用していたとしても最後には必ずPodという形で動いていることになります。
今回はコンテナイメージのタグの変更を想定しているため、これが一番よく見える形で事前確認を行います。
$ kubectl get po -l app=nginx -ocustom-columns="NAME:.metadata.name,IMAGE:.spec.containers[*].image" NAME IMAGE nginx-76d6c9b8c-zfd7l nginx
ここで、現状タグまで指定していないことを確認できます。ではこれをなにかに固定する変更をしてみましょう。
直接kubectl edit
で変更する自信があるのであれば問題ありませんが、変更箇所が多いとtypo等によるミスや、事前にどこを変更しているのかわからない懸念があるため、一度設定をダンプしてから変更する方法を考えます。(kubectl-neatは変更に不要な余計な情報を落とすためのプラグインです。 itaysk/kubectl-neat: Clean up Kubernetes yaml and json output to make it readable (github.com))
$ kubectl get deploy nginx -oyaml | kubectl neat > mod.yaml
そしてmod.yaml
を編集してからdiffを行います。
$ kubectl diff -f mod.yaml diff -u -N /tmp/LIVE-2325019920/apps.v1.Deployment.mod-test.nginx /tmp/MERGED-2827044604/apps.v1.Deployment.mod-test.nginx --- /tmp/LIVE-2325019920/apps.v1.Deployment.mod-test.nginx 2023-04-10 15:18:12.368235052 +0900 +++ /tmp/MERGED-2827044604/apps.v1.Deployment.mod-test.nginx 2023-04-10 15:18:12.372235100 +0900 @@ -4,7 +4,7 @@ annotations: deployment.kubernetes.io/revision: "1" creationTimestamp: "2023-04-10T06:15:22Z" - generation: 1 + generation: 2 labels: app: nginx managedFields: @@ -35,7 +35,6 @@ f:containers: k:{"name":"nginx"}: .: {} - f:image: {} f:imagePullPolicy: {} f:name: {} f:resources: {} @@ -84,6 +83,18 @@ operation: Update subresource: status time: "2023-04-10T06:15:30Z" + - apiVersion: apps/v1 + fieldsType: FieldsV1 + fieldsV1: + f:spec: + f:template: + f:spec: + f:containers: + k:{"name":"nginx"}: + f:image: {} + manager: kubectl-client-side-apply + operation: Update + time: "2023-04-10T06:18:12Z" name: nginx namespace: mod-test resourceVersion: "818735754" @@ -107,7 +118,7 @@ app: nginx spec: containers: - - image: nginx + - image: nginx:1.23.4 imagePullPolicy: Always name: nginx resources: {}
ここで確認してイメージタグがない状態から1.23.4に変わっていることをチェックすることができます。
上記の変更内容で問題なければ実際の作業を反映します。
$ kubectl apply -f mod.yaml Warning: resource deployments/nginx is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically. deployment.apps/nginx configured
最後に再度確認を行います。
$ kubectl get po -l app=nginx -ocustom-columns="NAME:.metadata.name,IMAGE:.spec.containers[*].image" NAME IMAGE nginx-5f8fb4dbc-shlg6 nginx:1.23.4
他にもPrometheusから集めたmetricsの履歴からも確認することができます。
ワークロードに関する場合 – 作成編
次に、より用途が限定された操作の場合の安全装置を紹介します。
applyは既存のリソースがあると変更を行います。主に.metadata.nameを間違えたり、.metadata.namespaceで操作するnamespaceを誤ったりすると事故が起こりえます。
新規作成であることが確定している場合createを使うほうが安全です。その代わりに冪等な操作ではなくなり、再実行することはできません。
例えば前節でBlue/Greenデプロイをしたくて、1.23.3のものを横にデプロイして、metadata.nameを衝突させてしまったというケースを考えてみます。
ここでdiffを取ると以下のように差分が出ます。
$ kubectl diff -f mod.yaml diff -u -N /tmp/LIVE-4137151539/apps.v1.Deployment.mod-test.nginx /tmp/MERGED-3344678273/apps.v1.Deployment.mod-test.nginx --- /tmp/LIVE-4137151539/apps.v1.Deployment.mod-test.nginx 2023-04-10 15:27:21.903107879 +0900 +++ /tmp/MERGED-3344678273/apps.v1.Deployment.mod-test.nginx 2023-04-10 15:27:21.907107933 +0900 @@ -2,11 +2,11 @@ kind: Deployment metadata: annotations: - deployment.kubernetes.io/revision: "2" + deployment.kubernetes.io/revision: "1" kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"labels":{"app":"nginx"},"name":"nginx","namespace":"mod-test"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"nginx"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"nginx:1.23.4","imagePullPolicy":"Always","name":"nginx","terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","terminationGracePeriodSeconds":30}}}} creationTimestamp: "2023-04-10T06:15:22Z" - generation: 2 + generation: 3 labels: app: nginx managedFields: @@ -54,24 +54,7 @@ fieldsType: FieldsV1 fieldsV1: f:metadata: - f:annotations: - f:kubectl.kubernetes.io/last-applied-configuration: {} - f:spec: - f:template: - f:spec: - f:containers: - k:{"name":"nginx"}: - f:image: {} - manager: kubectl-client-side-apply - operation: Update - time: "2023-04-10T06:19:27Z" - - apiVersion: apps/v1 - fieldsType: FieldsV1 - fieldsV1: - f:metadata: - f:annotations: - .: {} - f:deployment.kubernetes.io/revision: {} + f:annotations: {} f:status: f:availableReplicas: {} f:conditions: @@ -100,6 +83,22 @@ operation: Update subresource: status time: "2023-04-10T06:19:36Z" + - apiVersion: apps/v1 + fieldsType: FieldsV1 + fieldsV1: + f:metadata: + f:annotations: + f:deployment.kubernetes.io/revision: {} + f:kubectl.kubernetes.io/last-applied-configuration: {} + f:spec: + f:template: + f:spec: + f:containers: + k:{"name":"nginx"}: + f:image: {} + manager: kubectl-client-side-apply + operation: Update + time: "2023-04-10T06:27:21Z" name: nginx namespace: mod-test resourceVersion: "818739542" @@ -123,7 +122,7 @@ app: nginx spec: containers: - - image: nginx:1.23.4 + - image: nginx:1.23.3 imagePullPolicy: Always name: nginx resources: {}
しかし、今回の操作では以前あるものを変更するのではなく、新しく作りたいことが意図となっています。その際にcreateを使う手順であれば以下のようになって変更から保護されます。
$ kubectl create -f mod.yaml Error from server (AlreadyExists): error when creating "mod.yaml": deployments.apps "nginx" already exists
手作業をするとなったときに、人間はいくらでもミスをする可能性があります。その際にはできるだけ操作を限定することでミスを起こしても致命的な問題にならないように工夫することができます。
ワークロードの場合 – 削除編
最後に削除する際のことについて考えます。
直接deleteで削除をする見間違えたりして削除すると困ります。最低限必ずgetをして対象を確認してからdeleteをするほうが安全です。
例えばPodを削除することを見てみます。
$ kubectl get po -l app=nginx NAME READY STATUS RESTARTS AGE nginx-5f8fb4dbc-shlg6 1/1 Running 0 12m
ここでgetした内容について、そのままdeleteのverbに変更すると事前に見た内容を削除することができます。
$ kubectl delete po -l app=nginx pod "nginx-5f8fb4dbc-shlg6" deleted
マニフェストファイルがある場合、ファイルを指定することもできます。この場合、予めdry-runをすると意図した対象を削除しているかどうかを確認することができます。
$ kubectl delete -f mod.yaml --dry-run=client deployment.apps "nginx" deleted (dry run)
このように先にdry-runで削除する項目を確認してから実際の操作をすることがミスを防ぐことに役立ちます。
$ kubectl delete -f mod.yaml deployment.apps "nginx" deleted
もちろん、最後にPodを確認するとすべてが消えてます。
$ kubectl delete po -l app=nginx No resources found
まとめ
Kubernetesにおいて手作業が必要になった際に、ミスを無くすための確認内容や操作に関する工夫の方法について紹介しました。
手作業をしなくて済むようにシステムとして組まれていることがベストであることは間違いありません。一方で運用をする上でどうしても直ちに変更を行い正常化する必要が生じることがあります。その際には基本に立ち戻って、事前確認・操作・事後確認を怠らず、また、操作の内容を制限することで操作ミスや想定外の事象が発生することを防ぐことができます。