2021/09/30
Kubernetesでの運用のTipsまとめ
弊社はサーバーアプリケーションのほぼ全てを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-namespace1、your-namespace2、your-namespace3のShutdownしている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などでやる例です。
- podを停止して、diskを解放します。
 - diskの容量を拡張します。(拡張しても、パーティションが拡張されていないので注意です。)
 - diskをVMインスタンスにくっつけて作成・起動してsshします。
 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などが正常に動いているかチェックします。
問題があれば、SentryやAirbrakeなどに飛ばして通知します。
またこれらが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
    }
以上です。
またいろいろ追加あれば随時更新しようと思います。
関連する記事
[小〜中規模向け]GKEにTiDBをデプロイする
MySQL互換のNewSQLであるTiDBをGKEにデプロイしてみました。
NATS JetStream Controllerを使ってNATSをGKEにデプロイする
helm chartのnackを使って、NATS JetStreamサーバーをデプロイして、Stream/Consumerをk8sリソースとして管理する
GKEにDragonflydbをデプロイする
redis互換のdragonflydbをGKEにデプロイしました
[GKE]Kafka Strimziをアップグレードする
GKEにデプロイしているKafka Strimzi 0.26.0を0.30.0にアップグレードする
