2021/09/30

Kubernetesでの運用のTipsまとめ

k8s

弊社はサーバーアプリケーションのほぼ全てをkubernetesを使って運用しています。
主にGKE(GCPのk8sサービス)とEKS(AWSのk8sサービス)を使っていますが、オフィス内では自前で立てたUbuntuサーバーを活用しています。

数十のプロジェクトをk8sで運用しており、以下の内容を記事として残しておきます。

  • 普段からよくやるやり方
  • 運用上たまにある「あれってどうやったっけ?」的なもの

あと、これは私のポリシーなのですが、kubectlをラップしたようなツールはなるべく使わないようにしています。(例えばk9s
helm chartなどもツールはそのまま使わずに、helm chartが出力するk8sの設定ファイルを実際に自分でyamlで書いて使っています。

k8s configの切り替え

プロジェクトの切り替えにかなり便利で、もうこれなしでは運用が成り立たないです。
プロジェクトのフォルダに入ったら、そのプロジェクトが使っているk8s apiサーバーの設定を切り替える方法です。

direnvを使って、KUBECONFIG環境変数を切り替えて利用します。

これをすると、プロジェクトのフォルダにターミナルで入ってdirenv edit .ENVを切り替えると、k8s api serverの向き先がかわります。

以下設定例です。

.envrc

ENV=prd  # dev/stg/prd などの環境を分ける際の識別子
export KUBECONFIG=$(pwd)/k8s/${ENV}/.kube/config

configが切り替わっているか確認

EKSを使っているとawsとしか表示されないのですが、GCPなどではどのクラスターと繋がっているかわかるので便利です。

% kubectl config current-context

podsの更新を監視する

kubectl applyなどを使って、デプロイした時の監視によく利用しています。podsのstatusの変更をwatchしてくれます。(-wオプションを付ければ良いだけ)

% kubectl get pods -n your-namespace -w

特定のpodにログインする

まずは、kubectl get podsでpod名をみます。pod名がわかったら、以下のようにしてログインします。

% kubectl exec -it your-pod-name -n your-namespace -- bash
# bashがcontaner内にない場合(だいたいインストールしていますが、たまにない場合は/bin/shを指定します)
% kubectl exec -it your-pod-name -n your-namespace -- /bin/sh

毎回、pod名を見るのがめんどくさい場合(deploymentなどではpod名が頻繁に変更になるので)は以下のようにしています。
grepに引っかかった最初のpodにログインできます。

kubectl exec -it $(kubectl get pods -n your-namespace -o name | grep filter) -n your-namespace -- bash

サイドカーのpodを動かしている場合は -c オプションをつけてコンテナ名を指定する必要があります。

% kubectl exec -it your-pod-name -n your-namespace -n your-contaner-name -- bash

特定のpodのログを監視する

以下のようにすると、ログを監視することができます。--tailオプションで最後の50行を最初に表示して、-fオプションをつけて監視します。

% kubectl logs your-pod-name -n your-namespace --tail=50 -f

ローカルのportにバインドする(port-forward)

例えば、your-service-nameというserviceをローカル上で使いたい場合は以下のようにします。

% kubectl port-forward svc/your-service-name -n your-namespace 8080:80

例えば、serviceではなく、podに対してもできます。
複数ポートの紐付けもできます。

% kubectl port-forward your-pod-name -n your-namespace 8443:443 8080:80

特定のpodを検索して全て削除

EvictedしてたりShutdownしてしまったpodを一括で削除します。

何らかの原因で、nodeが再起動したり、スケールアウトしたりする際に起きたりします。

以下はyour-namespace1your-namespace2your-namespace3Shutdownしているpodを全て削除しています。
Shutdownという名前が含まれているpodも削除してしまうかもしれませんので注意してください。

namespaces=(your-namespace1 your-namespace2 your-namespace3)
for namespace in ${namespaces[@]}; do
    echo "Process ${namespace}"
    shutdown_pods=`kubectl get pods -n ${namespace} | grep Shutdown | awk '{print $1}'`
    for pod in ${=shutdown_pods}; do
        echo "Delete ${pod}..."
        kubectl delete pods -n ${namespace} --force --grace-period=0 ${pod}
    done
done

podを立ててPVCの中身を見る

PVCのdiskの中身を見たいときに使います。nginxが動くpodにdiskをくっつけて起動します。
podが起動したら、bashでログインして、/root/dataの中身を見て、必要があれば操作します。

apiVersion: v1
kind: Pod
metadata:
  name: check-data
  namespace: your-namespace
spec:
  containers:
    - name: check-data
      image: nginx
      volumeMounts:
        - name: disk-data
          mountPath: /root/data
  volumes:
    - name: disk-data
      persistentVolumeClaim:
        claimName: your-pvc-name

PVCのdiskを拡張した時

例えば、容量が少なくなってしまった(または足りなくなってエラーになってしまった)みたいな時によくやります。
以下はGKE/GCPなどでやる例です。

  1. podを停止して、diskを解放します。
  2. diskの容量を拡張します。(拡張しても、パーティションが拡張されていないので注意です。)
  3. diskをVMインスタンスにくっつけて作成・起動してsshします。
  4. resize2fsコマンドを使って、拡張します。
$ ls /dev/disk/by-id/
$ sudo mkdir -p /mnt/disks/extend
$ sudo mount -o defaults /dev/disk/by-id/google-your-disk-name /mnt/disks/extend
$ sudo df -h
$ sudo resize2fs /dev/sdb
$ sudo df -h

この手順で、拡張したdiskを再びPVCとして利用します。

SecretをBase64ではない生の文字列で定義

結構便利です。stringDataを使うことで、base64エンコーディングしないでyamlファイルに定義できます。

apiVersion: v1
kind: Secret
metadata:
  name: your-secret-name
  namespace: your-namespace
type: Opaque
stringData:
  example.json: |-
    {
      "password": "123456"
    }

特定のnode poolにしかpodがデプロイされないようにする

affinity.requiredDuringSchedulingIgnoredDuringExecutionを使います。

以下はGKEでの例です。

    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: "cloud.google.com/gke-nodepool"
                    operator: In
                    values:
                      - your-node-pool-name

アプリケーションログの出し方

warning以下は標準出力、errorは標準エラーに出力します。

(必要に応じて出力されたものをfluentdとかを使って転送します。)

pod内でDBやQueueなどのミドルウェアの監視

readinessProbeで呼ぶapiのpath(以下では/healthz)で、アプリケーションサーバーが依存するDB/Queue/Redisなどが正常に動いているかチェックします。
問題があれば、SentryAirbrakeなどに飛ばして通知します。

またこれらがNGになると自動的に、アプリケーションサーバーのpodも利用不可になるので、データの整合性の破壊を緩和することができます。

  • DBの場合はselect 1
  • Redisの場合はping
  • Queueの場合はコネクションを張ってみてclose

設定例

          readinessProbe:
            httpGet:
              path: /healthz
              port: 80
            initialDelaySeconds: 10
            periodSeconds: 30

cluster内の独自ドメイン対応(kube-dns編)

いわゆる /etc/resolver/your-domain.comの対応です。

主にGKEではこの方式が利用できて便利です。

% kubectl edit configmap kube-dns -n kube-system
apiVersion: v1
# 以下の行を追加します。
data:
  stubDomains: |
    {
      "your-domain.com": [
        "XXX.XXX.XXX.XXX"
      ]
    }
kind: ConfigMap
metadata:
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
  name: kube-dns
  namespace: kube-system

cluster内の独自ドメイン対応(coredns編)

主にEKSではこの方式が利用できて便利です。

% kubectl -n kube-system edit configmap coredns
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          upstream
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
    your-domain.com:53 {
      errors
      cache 30
      forward . XXX.XXX.XXX.XXX
      reload
    }

以上です。
またいろいろ追加あれば随時更新しようと思います。