Kubernetesを安全に運用操作するためのTIPS – kubectl編

2023年04月19日 水曜日


【この記事を書いた人】
李 瀚

締め切りドリブンな生活から脱却したい

「Kubernetesを安全に運用操作するためのTIPS – kubectl編」のイメージ

概要

もうIaCの時代であまりKubernetesでもkubectlなどのコマンドラインを手で叩いて操作することは少なくなっています。そんな中でもどうしても障害対応など運用上で触らないといけないときは来るものです。今記事では手作業で操作するときに安全に期待通りの挙動をさせるために工夫していることをご紹介します。

操作時の一般論

運用上、操作を行う際の一般論として以下の3段階に分けて作業を行います。

  1. 事前確認:操作する前の状態が意図したものであることを確認する
  2. 操作:実際に行いたい作業を行う
  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で書かれたマニフェストファイルで状態を宣言して構成管理をしている場合、以下の手順となります。

  1. kubectl diff -f <filename>
  2. kubectl apply -f <filename>
  3. 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の履歴からも確認することができます。

イメージタグが変更されたことを示すPrometheusのグラフ

ワークロードに関する場合 – 作成編

次に、より用途が限定された操作の場合の安全装置を紹介します。

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において手作業が必要になった際に、ミスを無くすための確認内容や操作に関する工夫の方法について紹介しました。

手作業をしなくて済むようにシステムとして組まれていることがベストであることは間違いありません。一方で運用をする上でどうしても直ちに変更を行い正常化する必要が生じることがあります。その際には基本に立ち戻って、事前確認・操作・事後確認を怠らず、また、操作の内容を制限することで操作ミスや想定外の事象が発生することを防ぐことができます。

 

李 瀚

2023年04月19日 水曜日

締め切りドリブンな生活から脱却したい

Related
関連記事