コンテンツにスキップ

Kubernetesリソース@Kubernetes

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. Kubernetesリソースとオブジェクト

Kubernetesリソース

Kubernetes 上でアプリケーションを稼働させる概念のこと。

kubernetes_workload-resource


Kubernetesオブジェクト

マニフェストによって量産された Kubernetes リソースのインスタンスのこと。


スコープ

所属する Namespace 内のみにリクエストを送信できる Namespaced スコープな Kubernetes リソースと、Cluster 全体にリクエストを送信できる Cluster スコープな Kubernetes リソースがある。

namespaced-scope_vs_cluster-scoped


02. Workload系リソース

Workload系リソースとは

コンテナの実行に関する機能を提供する。


DaemonSet

▼ DaemonSetとは

Node 上の Pod の個数を維持管理する。

Pod の負荷に合わせて Pod の自動水平スケーリングを実行しない (HorizontalPodAutoscaler が必要である) 。

ただし ReplicaSet とは異なり、Node 内で Pod を 1 つだけ維持管理する。

Node で 1 つだけ稼働させる必要のあるプロセス (例:kube-proxy、CNI、FluentBit、datadog エージェント、cAdvisor エージェント、Prometheus の一部の Exporter など) のために使用される。

こういったプロセスが稼働するコンテナは、Node 内のすべてのコンテナからデータを収集し、可観測性のためのデータセットを整備する。

▼ Pod数の固定

DaemonSet は、Node 内で Pod を 1 つだけ維持管理する。

そのため、例えば Cluster ネットワーク内に複数の Node が存在していて、いずれかの Node が停止したとしても、稼働中の Node 内の Pod を増やすことはない。

▼ DaemonSet配下のPodへの通信

  • NodePort Service
  • ClusterIP Service
  • Pod での hostPort
  • など...


Deployment

▼ Deploymentとは

ReplicaSet を操作し、Cluster ネットワーク内の Pod のレプリカ数を維持管理する。

Pod の負荷に合わせて Pod の自動水平スケーリングを実行しない (HorizontalPodAutoscaler が必要である) 。

ただし StatefulSet とは異なり、ストレートレス (例:アプリ) なコンテナを含む Pod を冗長化することに適する。

▼ ReplicaSetの置き換えが起こる条件

Deployment では、以下の設定値の変更で、ReplicaSet の置き換えが起こる。

条件 説明
.spec.replicas キーの変更 レプリカ数 (.spec.replicas キー) の変更の場合は、Deploymentは既存のReplicaSetをそのままにし、Podのレプリカ数のみを変更する。
.spec.template キー配下の任意の変更 PodTemplate (.spec.template キー) を変更した場合、Deploymentは新しいReplicaSetを作成し、これを古いReplicaSetと置き換える。

kubernetes_deployment_replace_replicaset

▼ Podのレプリカ数の維持

Deployment は、Cluster 内の Pod のレプリカ数を指定された数だけ維持する。

そのため、例えば Cluster 内に複数の Node が存在していて、いずれかの Node が停止した場合、稼働中の Node 内でレプリカ数を維持するように Pod 数を増やす。


Job

▼ Jobとは

単発的なバッチ処理を定義したい場合、Job を使用する。

もう一度実行したい場合は、Job を削除する必要がある。

複数の Pod を作成 (SuccessfulCreate) し、指定された数の Pod を正常に削除 (SuccessfulDelete) する。

デフォルトでは、ログの確認のために Pod は削除されず、Job が削除されて初めて Pod も削除される。

.spec.ttlSecondsAfterFinished キーを使用すると、Pod のみを自動削除できるようになる。

定期的に実行する場合、CronJob のテンプレートとして定義する。

▼ DBマイグレーション

Job を使用して、DB にマイグレーションを実行する。

GitOps ツール (例:ArgoCD など) によっては、アノテーションを使用して Apply 前に Job をフックさせられる。

ArgoCD の場合、DBマイグレーション ---> ArgoCD Sync開始 ---> アプリ起動 ---> ArgoCD Sync完了 という流れになる。

apiVersion: batch/v1
kind: Job
metadata:
  namespace: foo
  name: foo-migration-job
spec:
  backoffLimit: 0
  template:
    spec:
      containers:
        - name: foo-app
          image: foo-app:1.0.0
          command: ["<マイグレーションを実行するためのコマンド>"]
          envFrom:
            - secretRef:
                # DBの接続情報 (ホスト、ユーザー名、パスワード) はSecretに設定しておく。
                name: foo-secret
      restartPolicy: Never

ただし、アプリケーションの起動直前に DB マイグレーションを実行してしまってもよい。

その場合、ArgoCD Sync開始 ---> DBマイグレーション ---> アプリ起動 ---> Sync完了 という流れになる。

FROM node:22.11.0-bullseye-slim as base

...

# Nodeアプリケーションの起動直前にDBマイグレーションを実行してしまう
ENTRYPOINT ["npx prisma migrate deploy", "npm run start"]

もし手動でマイグレーションを実行する運用であれば、kubectl exec コマンドで接続した後に、マイグレーションコマンドを実行する。

$ kubectl exec -it <Pod名> -- bash

/usr/local/src/foo/node_modules/.bin/prisma migrate deploy


CronJob

▼ CronJobとは

定期的なバッチ処理を定義したい場合、CronJob を使用する。

CronJob 配下の Job は、決まった時間にならないと実行されない。

任意の時間に実行するためには、CronJob を指定し、これの配下で一時的に Job を作成する。

$ kubectl create job test-job --from=cronjob/foo-cron-job -n foo

ただし、動作確認後は CronJob に Job を作らせたいので、その Job は削除する。

$ kubectl delete job test-job -n foo


Node

▼ Nodeとは

Kubernetes リソースを配置するサーバーのこと。

▼ ライフサイクルフェーズ

kubelet は、Node のライフサイクルフェーズを設定する。

フェーズ名 説明
Ready NodeがPodをスケジューリング可能な状態であることを表す。
NotReady NodeがPodをスケジューリング不可能な状態であることを表す。

▼ イベント

イベント名 説明
NodeHasSufficientMemory
NodeHasSufficientPID
NodeNotReady
NodeReady
Starting
SystemOOM Podの要求するメモリ量が多すぎて、Nodeのメモリ不足が起こったことを表す。Podのメモリで上限 (limits) = 下限 (requests) のように設定する (Guaranteed QoS) と、SystemOOMを避けられる。


Pod

▼ Podとは

コンテナの最小グループ単位のこと。

Pod を単位として、コンテナ起動/停止や水平スケールアウト/スケールインを実行する。

*例*

PHP-FPM コンテナと Nginx コンテナを稼働させる場合、これら同じ Pod 内に配置する。

kubernetes_pod_php-fpm_nginx

▼ 例外的なコントロールプレーンNode上のPod

脆弱性の観点で、デフォルトではコントロールプレーン Node に Pod はスケジューリングされない。

これは、コントロールプレーン Node には Taint (node-role.kubernetes.io/master:NoSchedule) が設定されているためである。

一方で、Node にはこれがないため、Pod をスケジューリングさせられる。

# コントロールプレーンNodeの場合
$ kubectl describe node <コントロールプレーンNode名> | grep -i taint

Taints: node-role.kubernetes.io/master:NoSchedule # スケジューリングさせないTaint

# ワーカーNodeの場合
$ kubectl describe node <ワーカーNode名> | grep -i taint

Taints: <none>

ただし、セルフマネージドなコントロールプレーン Node を採用している場合に、すべてのコントロールプレーン Node で Taint を解除すれば、Pod を起動させられる。

コントロールプレーン Node がマネージドではない環境 (オンプレミス環境、ベアメタル環境など) では、コントロールプレーン Node に DaemonSet による Pod をスケジューリングさせることがある。

$ kubectl taint node --all node-role.kubernetes.io/master:NoSchedule-

▼ Podのライフサイクルフェーズ

Pod は、マニフェストの .status.phase キーにライフサイクルのフェーズを持つ。

status:
  phase: Running
フェーズ名 説明 補足
Completed Pod内のすべてのコンテナが正常に終了した。 Job配下のPodでよく見られるフェーズである。
ContainerCreating Pod内にInitContainerがない場合の理由である。コンテナイメージをプルし、コンテナを作成している。
CrashLoopBackOff Podが、一連のフェーズ (Running フェーズ、Waiting フェーズ、Failed フェーズ) を繰り返している。
CreateContainerError Pod内のコンテナの作成に失敗した。
ErrImagePull Pod内のコンテナイメージのプルに失敗した。
Error Pod内のいずれかのコンテナが異常に終了した。 Job配下のPodの場合はErrorになっても、次のPodが作成される。Jobの .spec.ttlSecondsAfterFinished キーを設定していなければ、ErrorのPodがしばらく残り続けるが、もし新しいPodがCompletedになれば問題ない。
Failed Pod内のすべてのコンテナの起動が完了し、その後に異常に停止した。
ImagePullBackOff Pod内のコンテナイメージのプルに失敗した。
OOMKilled Podのメモリの空きサイズが足らず、コンテナが強制的に終了された。
Pending PodがNodeにスケジューリングされたが、Pod内のすべてのコンテナの起動がまだ完了していない。
PodInitializing Pod内にInitContainerがある場合の理由である。コンテナイメージをプルし、コンテナを作成している。
PostStartHookError PodのPostStartフックに失敗した。
Running Pod内のすべてのコンテナの起動が完了し、実行中である。 コンテナの起動が完了すれば Running フェーズになるが、コンテナ内でビルトインサーバーを起動するようなアプリケーション (例:フレームワークのビルトインサーバー機能) の場合は、Running フェーズであっても Ready コンディションではないことに注意する。
Succeed Pod内のすべてのコンテナの起動が完了し、その後に正常に停止した。
Unknown NodeとPodの間の通信に異常があり、NodeがPodから情報を取得できなかった。

▼ Podのコンディション

Pod のライフサイクルのフェーズは、.status.conditions キーにコンディション持つ。

status:
  phase: Running
  conditions:
    - lastProbeTime: null
      lastTransitionTime: "2022-12-01T18:00:06Z"
      status: "True"
      type: Initialized
    - lastProbeTime: null
      lastTransitionTime: "2022-12-01T18:00:49Z"
      status: "True"
      type: Ready
    - lastProbeTime: null
      lastTransitionTime: "2022-12-01T18:00:49Z"
      status: "True"
      type: ContainersReady
    - lastProbeTime: null
      lastTransitionTime: "2022-12-01T18:00:02Z"
      status: "True"
      type: PodScheduled

例えば Running フェーズであっても、Ready コンディションになっていない可能性がある。

そのため、Pod が正常であると見なすためには、『Running フェーズ』かつ『Ready コンディション』である必要がある。

各フェーズのコンディション名 説明
PodScheduled NodeへのPodのスケジューリングが完了した。
ContainersReady すべてのコンテナの起動が完了し、加えてコンテナ内のアプリケーションやミドルウェアの準備が完了している。
Initialized すべての init コンテナの起動が完了した。
Ready Pod全体の準備が完了した。

▼ Podの最後のフェーズの理由

Pod は、.status.reason キーに、最後のフェーズの理由を値として持つ。

status:
  phase: Failed
  reason: Evicted
理由 説明
Completed コンテナが正常に終了した。InitContainerの実行後に見られる。
Evicted Nodeのハードウェアリソース不足のため、Podが退避対象となった。Evicted が理由の場合、Failed フェーズが最後となる。
Unknown 原因が不明である。

▼ CrashLoopBackOffのデバッグ

Pod が CrashLoopBackOff になっている場合、以下を確認するとよい。

  • kubectl logs コマンドで、該当のコンテナのエラーログを確認する。
  • kubectl describe nodes コマンドで、Pod をスケジューリングさせている Node を指定し、該当の Pod が CPU とメモリの要求量に異常がないかを確認する。
  • kubectl describe pods コマンドで、該当の Pod が CrashLoopBackOff になる原因を確認する。Containers の LastState の項目で、メッセージを確認できる。これは、kubectl logs コマンドと同じ内容である。

▼ Podを安全に削除する方法

Pod の終了プロセスが始まると、以下の一連のプロセスも開始する。

  • Workload (例:Deployment、DaemonSet、StatefulSet、Job など) が古い Pod を切り離す。
  • Service と kube-proxy が古い Pod の宛先情報を削除する。
  • コンテナを停止する。

これらのプロセスはそれぞれ独立して実施され、ユーザーは制御できない。

例えば、Service と kube-proxy が Pod の宛先情報を削除する前に Pod が削除してしまうと、Service から Pod への接続を途中で切断することになってしまう。

また、コンテナを停止する前に Pod を終了してしまうと、コンテナを強制的に終了することになり、ログにエラーが出力されてしまう。

そのため、Service と kube-proxy の処理後に Pod を終了できるように、ユーザーが Pod の .spec.containers[*].lifecycle.preStop キーに任意の秒数を設定し、コンテナに待機処理 (例:sleep コマンド) を実行させる必要がある。

また、コンテナの正常な終了後に Pod を終了できるように、.spec.terminationGracePeriodSeconds キーに任意の秒数を設定し、Pod の終了に伴う一連のプロセスの完了を待機する必要がある。

これらの適切な秒数は、ユーザーがそのシステムに応じて調節するしかない。

.spec.terminationGracePeriodSeconds キーを長めに設定し、.spec.containers[*].lifecycle.preStop キーの秒数も含めて、すべてが完了したうえで Pod を終了可能にする。

pod_terminating_process

(1)

クライアントは、kubectl コマンドを使用して、Pod を終了するリクエストを kube-apiserver に送信する。

(2)

Pod のマニフェストに deletionTimestamp キーが追加され、Pod が Terminating フェーズとなり、削除プロセスを開始する。

(3)

Pod の .spec.terminationGracePeriodSeconds キーに応じて、Pod の終了プロセス完了の待機時間を開始する。

(4)

最初に preStop フックが起動し、.spec.containers[*].lifecycle.preStop キーで設定した待機処理をコンテナが実行する。

(5)

Deployment が Pod を切り離す。また、Service と kube-proxy が Pod の宛先情報を削除する。

(6)

.spec.containers[*].lifecycle.preStop キーによるコンテナの待機処理が終了する。

(7)

待機処理が終了したため、kubelet は、コンテナランタイムを経由して、Pod 内のコンテナに SIGTERM シグナルを送信する。

これにより、コンテナの停止処理が開始する。

(8)

.spec.terminationGracePeriodSeconds キーによる Pod の終了プロセス完了の待機時間が終了する。

この段階でもコンテナが停止していない場合は、コンテナにSIGKILLシグナルが送信され、コンテナを強制的に終了することになる。

(9)

Pod が削除される。この段階で Deployment や、Service と kube-proxy の処理が完了していない場合は、接続を途中で強制的に切断することになる。

▼ ハードウェアリソースの割り当て

その Pod に割り当てられたハードウェアリソース (CPU、メモリ) を、Pod 内のコンテナが分け合って使用する。

単位
m:millicores 1 コア = 1000 ユニット = 1000m
Mi:mebibyte 1Mi = 1.04858MB

▼ クライアントがPod内のログを参照できる仕組み

(1)

Node 上の kubelet とコンテナランタイムは、/var/log/ ディレクトリの log ファイルにログを書き込む。

(2)

クライアント (特に kubectl コマンド実行者) が kubectl logs コマンドを実行する。

(3)

kube-apiserver が、/logs/pods/<ログへのパス> エンドポイントにリクエストを送信する。

(4)

kubelet はリクエストを受信し、Node の /var/log ディレクトリの log ファイルを読み込む。

コンテナランタイムは、コンテナの標準出力または標準エラー出力に出力したログを/var/log/containerディレクトリ配下に保管する。

/var/log/containerディレクトリのログは、Pod全体のログを/var/log/pods/<Namespace名>_<Pod名>_<UID>/<コンテナ名>/<数字>.logファイルのシンボリックになっている。

なお、削除されたPodのログは、引き続き/var/log/podsディレクトリ配下に保管されている。

kubernetes_pod_logging

補足として、DaemonSet として稼働する Fluentd は、Node の /var/log ディレクトリを読み込むことにより、Pod 内のコンテナのログを収集する。

▼ 待ち受けるポート番号の確認

Pod 内のコンテナ内で netstat コマンドを実行することにより、コンテナが待ち受けるポート番号を確認できる。

$ kubectl exec foo-istiod -n istio-system -- netstat -tulpn

Active Internet connections (only servers)

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:9876          0.0.0.0:*               LISTEN      1/pilot-discovery
tcp6       0      0 :::15017                :::*                    LISTEN      1/pilot-discovery
tcp6       0      0 :::8080                 :::*                    LISTEN      1/pilot-discovery
tcp6       0      0 :::15010                :::*                    LISTEN      1/pilot-discovery
tcp6       0      0 :::15012                :::*                    LISTEN      1/pilot-discovery
tcp6       0      0 :::15014                :::*                    LISTEN      1/pilot-discovery

▼ バルーンPod (OverProvisioning Pod)

『OverProvisioning Pod』ともいう。

Node にバルーン Pod をスケジューリングさせ、Node に常に余剰なリソース (例:CPU/メモリの容量) を確保できる。

バルーン Pod の優先度は最低にしておく。

バルーン Pod は優先度が低いため、他の Pod を Node に優先してスケジューリングできる。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-balloon
spec:
  # ゾーンが3つあるため、ゾーン間で移動するようにレプリカ数を3つにする
  replicas: 3
  selector:
    matchLabels:
      app: balloon
  template:
    metadata:
      labels:
        app: balloon
    spec:
      containers:
        - args:
            - infinity
          command:
            - sleep
          image: ubuntu
          name: ubuntu
          resources:
            requests:
              cpu: 1000m
              memory: 2048Mi
      priorityClassName: foo-balloon
      terminationGracePeriodSeconds: 10
      # Podを同じゾーンにスケジューリングさせない
      topologySpreadConstraints:
        - labelSelector:
            matchLabels:
              app: balloon
          maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: foo-balloon
preemptionPolicy: Never
value: -10
description: Priority class for balloon


ReplicaSet

▼ ReplicaSetとは

Node 上の Pod 数を維持管理する。

Pod の負荷に合わせて Pod の自動水平スケーリングを実行しない (HorizontalPodAutoscaler が必要である) 。

DaemonSet とは異なり、Pod を指定した個数に維持管理できる。

ReplicaSet を直接的に操作するのではなく、Deployment 使用してこれを行うことが推奨される。

▼ PodTemplate

Pod の鋳型として動作する。

ReplicaSet は、PodTemplate を用いて Pod のレプリカを作成する。


StatefulSet

▼ StatefulSetとは

ReplicaSet を操作し、Pod の個数を維持管理する。

Pod の負荷に合わせて Pod を自動水平スケーリングを実行しない (HorizontalPodAutoscaler が必要である) 。

Deployment とは異なり、ストレートフルなコンテナ (例:DB コンテナ) を含む Pod を扱える。

Pod が削除されても PersistentVolumeClaims は削除されないため、新しい Pod にも同じ PersistentVolume を継続的にマウントできる。

▼ ライフサイクル

StatefulSet は、Deployment や ReplicaSet とは異なり、同時に Pod を作成しない。

作成中の Pod が Ready 状態になってから、次の Pod を作成し始める。

そのため Deployment や ReplicaSet と比べて、すべての Pod が揃うのに時間がかかる。


DeploymentとStatefulSetとの違い

▼ 設定値

StatefulSet では、一部の設定変更が禁止されている。

The StatefulSet "foo-pod" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', 'updateStrategy' and 'minReadySeconds' are forbidden

▼ PersistentVolume

Deployment 配下の Pod は、すべてが同じ PersistentVolumeClaim を共有する。

そのため、Pod に紐づく PersistentVolume は同じになる。

一方で StatefulSet 配下の Pod は、別々の PersistentVolumeClaim を使用する。

そのため、Pod に紐づく PersistentVolume は別々になる。

Pod が別の Node に再スケジューリングされても、Pod に同じ PersistentVolume をマウントできる。

Pod が削除されても PersistentVolumeClaims は削除されないため、新しい Pod も同じ PersistentVolume をマウントできる。

kubernetes_deployment_persistent-volume


03. ネットワーク系リソース

ネットワーク系リソースとは

Cluster 内のネットワークを制御する。


EndpointSlice

▼ EndpointSliceとは

各 Service 配下に存在する。Service でルーティング先の Pod の宛先情報を分割して管理し、Pod の増減に合わせて、Pod の宛先情報を追加/削除する。

kube-proxy によるサービス検出のために、Pod の宛先情報を提供する。

Kubernetes の v1.6 より前は Endpoints が使用されていた。

しかし、Endpoints では Pod の宛先情報を一括管理しなければならず、これを分割して管理できるように、Endpoints の代わりとして EndpointSlice が導入された。

kubernetes_endpoint-slices


Gateway

▼ Gatewayとは

Gateway は、L4/L7 プロトコルの通信の受信ルールを定義し、また L4/L7 ロードバランサーとして通信をルーティングする。

▼ Ingressとの違い

L7 プロトコルの受信ルールしか定義できない Ingress とは異なり、L4 プロトコルの受信ルールも定義できる。

また、Gateway 自体が L4/L7 ロードバランサーとしても機能する。


GatewayClass

▼ GatewayClassとは

Gateway の実体として使用するツールを指定する。


Ingress

▼ Ingressとは

Ingress は、L7 プロトコルの通信の受信ルールを定義する。

Ingress を使用する場合、宛先の Service は、Cluster IP Service とする。

NodePort Service や LoadBalancer Service と同様に、外部からのリクエストを受信する方法の 1 つである。

kubernetes_ingress

▼ Gatewayとの違い

L7 プロトコルの通信のみを処理できる。

また、Ingress それ自体はルールのみを持ち、Ingress Controller がロードバランサーとして機能する。

▼ パスベースルーティング

パスの値に基づいて、Service にルーティングする。

kubernetes_ingress_path

▼ ホストベースルーティング

Host ヘッダー値に基づいて、Service にルーティングする。

本番環境では、ドメインを指定した各種ダッシュボードにリクエストを送信できる必要がある。

kubernetes_ingress_host


IngressClass

▼ IngressClassとは

Ingress Controller の実体として使用するツールを指定する。


Ingress Controller

▼ Ingress Controllerとは

Ingress Controller は、L7 ロードバランサーとして Pod に通信をルーティングする。

Node 外から通信を受信し、Ingress で定義したルールに応じて、単一/複数の Service へルーティングする。

クラウドプロバイダー (例:AWS) では、Ingress Controller 状況下で Ingress を作成すると、Ingress の設定値に応じた L7 ロードバランサー (例:AWS ALB と AWS ターゲットグループ) を自動的にプロビジョニングする。

ただし、クラウドプロバイダーによっては、Ingress Controller と ClusterIP Service を中継するカスタムリソース (例:AWS TargetGroupBindings など) を提供している場合がある。

この場合、クラウドプロバイダーのリソースと Kubernetes が疎結合になり、責務の境界を明確化できる。


Service

▼ Serviceとは

Service は、L4 ロードバランサーとして Pod に通信をルーティングする。

kube-proxy が更新した iptables を使用し、また負荷分散方式によるルーティング先 Pod の決定に基づいて、Pod に通信をルーティングする。

DaemonSet や Job で使用する例は少ないが、Pod さえあればすべての Workload (例:Deployment、DaemonSet、StatefulSet、Job など) で Service を使用できる。

マイクロサービスアーキテクチャのコンポーネントである『Service』とは区別する。

kubernetes_kube-proxy_service

▼ ClusterIP Service

kubernetes_clusterip-service

L4 ロードバランサーとして、Service に対する通信を、Cluster-IP を経由して Pod にルーティングする。

Cluster-IP は Service の .spec.clusterIP キーを指定しない限りランダムで決まる。また、Pod の /etc/resolv.conf ファイルに記載される。

Pod 内に複数のコンテナがある場合、各コンテナに同じ内容の /etc/resolv.conf ファイルが配置される。

$ kubectl exec -it <Pod名> -c <コンテナ名> -- bash

[root@<Pod名>] $ cat /etc/resolv.conf

nameserver *.*.*.* # ClusterネットワークのIPアドレス
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5 # 名前解決時のローカルドメインの優先度

Cluster-IP は Node 外から宛先として指定できないため、通信に Ingress を必要とする。

パブリックネットワーク
⬇⬆️︎
# L7ロードバランサー
Ingress Controller (例:Nginx Ingress Controller、AWS Load Balancer Controllerなど)
⬇⬆️︎
# L4ロードバランサー
ClusterIP Service
⬇⬆️︎
Pod

Ingress がないと Cluster ネットワーク内からのみしかアクセスできず、安全である。

一方でもし Ingress を使用する場合、LoadBalancer Service と同様にして (レイヤーは異なるが) 、Pod の IP アドレスを宛先とする L7 ロードバランサー (例:AWS ALB と AWS ターゲットグループ) を自動的にプロビジョニングする。

そのため、クラウドプロバイダーのリソースと Kubernetes リソースが密結合になり、責務の境界が曖昧になってしまう。

▼ NodePort Service

kubernetes_nodeport-service

L4 ロードバランサーとして、Service に対する通信を、Node の NIC の宛先情報 (IP アドレス、ポート番号) 、Cluster-IP を経由して Pod にルーティングする。

Node の NIC の宛先情報は、Node 外から宛先 IP アドレスとして指定できるため、通信に Ingress を必要としない。

パブリックネットワーク
⬇⬆️︎
# L4ロードバランサー
NodePort Service
⬇⬆️︎
Pod

パブリックプロバイダーの LB (例:AWS ALB) を別に配置してもよい (この LB は、Ingress Controller 由来ではない) 。

パブリックネットワーク
⬇⬆️︎
Amazon Route 53
⬇⬆️︎
# L7ロードバランサー
AWS ALB
⬇⬆️︎
# L4ロードバランサー
NodePort Service
⬇⬆️︎
Pod

ただし、NodePort Service は内部的に Cluster-IP を使っている。

そのため、Ingress を作成すると NodePort Service の Cluster-IP を経由して Pod にルーティングする。

この場合、Node の IP アドレスと Ingress の両方が Node の通信の入り口となり、入口が無闇に増えるため、やめたほうがよい。

パブリックネットワーク
⬇⬆️︎
# L7ロードバランサー
Ingress Controller (例:Nginx Ingress Controller、AWS Load Balancer Controller)
⬇⬆️︎
# L4ロードバランサー
ClusterIP Service (実体はNodePort Service)
⬇⬆️︎
Pod

Node の NIC の宛先情報は、Node の作成方法 (例:Amazon EC2、Google Cloud GCE、VMWare) に応じて、確認方法が異なる。

Service のポート番号と紐づく Node の NIC のポート番号はデフォルトではランダムであるため、Node の NIC のポート番号を固定する必要がある。

1 個の Node のポート番号につき、1 個の Service としか紐付けられず、Service が増えていってしまうため、実際の運用にやや不向きである。

この場合、クラウドプロバイダーのリソースと Kubernetes が疎結合になり、責務の境界を明確化できる。

例えば、ポート番号は種類に応じて 200 番の間隔で分類する。

################################################
# L7ロードバランサーによるヘルスチェックの流入口
################################################
apiVersion: v1
kind: Service
metadata:
  name: health-check
spec:
  type: NodePort
  ports:
    - name: http-health-check
      port: 15021
      targetPort: 15021
      nodePort: 31000
---
################################################
# Fooに関する流入口
# port: 8000~8199
# targetPort: 8000~8199
# nodePort: 30000~30199
################################################
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  type: NodePort
  ports:
    - name: http-foo
      port: 8000
      targetPort: 8000
      nodePort: 30000

---
################################################
# Barに関する流入口
# port: 8200~8399
# targetPort: 8200~8399
# nodePort: 30200~30399
################################################
apiVersion: v1
kind: Service
metadata:
  name: bar
spec:
  type: NodePort
  ports:
    - name: http-bar
      port: 8200
      targetPort: 8200
      nodePort: 30200

▼ LoadBalancer Service

kubernetes_loadbalancer-service

L4 ロードバランサーとして、Service に対する通信を、External-IP、Node の NIC の宛先情報 (IP アドレス、ポート番号)、Cluster-IP を経由して Pod にルーティングする。

External-IP は Node 外から宛先 IP アドレスとして指定できる。

そのため、通信に Ingress を必要としないが、外部のロードバランサーのみが宛先 IP アドレスを指定できる。

パブリックネットワーク
⬇⬆️︎
Amazon Route 53
⬇⬆️︎
# L4ロードバランサー
LoadBalancer ServiceによるAWS NLB
⬇⬆️︎
Pod

クラウドプロバイダー (例:AWS) では、LoadBalancer Service を作成すると、External-IP を宛先とする L4 ロードバランサー (例:AWS NLB と AWS ターゲットグループ) を自動的にプロビジョニングする。

クラウドプロバイダーのリソースと Kubernetes リソースが密結合になり、責務の境界が曖昧になってしまう。

なお、注意点として、Ingress Controller は L7 ロードバランサーを自動的にプロビジョニングする。

▼ ExternalName Service

Cluster 内 DNS 名と Cluster 外 CNAME レコードを対応づけ、Service に対する通信を対象にルーティングする。

例えば、foo-db-service という ExternalName Service を作成したとする。

この場合、foo-db-service.default.svc.cluster.local を指定すると、指定した CNAME レコードに問い合わせる。

マイクロサービスが外部のドメインに直接リクエストを送信できるが、これを Service のドメインに抽象化できる。

kubernetes_externalname-service

▼ Headless Service

kuberentes_headless-service

Headless Service 以外の Service は、負荷分散方式により、配下のいずれかの Pod の IP アドレスを返却する。

その一方で、Headless Service は配下のすべての Pod の IP アドレスを同時に返却する。

DNS サーバーは Headless Service から IP アドレスを取得し、

$ dig <Serviceの完全修飾ドメイン名>

;; QUESTION SECTION:
;<Serviceの完全修飾ドメイン名>. IN   A

;; ANSWER SECTION:
<Serviceの完全修飾ドメイン名>. 30 IN A       10.8.0.30
<Serviceの完全修飾ドメイン名>. 30 IN A       10.8.1.34
<Serviceの完全修飾ドメイン名>. 30 IN A       10.8.2.55

なお、Headless Service から StatefulSet にルーティングする場合は、唯一、Pod で直接的に名前解決できるようになる。

$ dig <Pod名>.<Serviceの完全修飾ドメイン名>

;; QUESTION SECTION:
;<Pod名>.<Serviceの完全修飾ドメイン名>. IN A

;; ANSWER SECTION:
<Pod名>.<Serviceの完全修飾ドメイン名>. 30 IN A 10.8.0.30


03-04. Serviceの仕組み

パケットの処理方法

kube-proxy_iptables

Service は、パケットの L4 に関するヘッダーの持つ情報を見て、Pod に L4 ロードバランシングする。

Service が Pod と紐づいたり、切り離したりした後、kube-procy が iptables を変更する。

受信したリクエストをパケットとして処理していく流れを見ていく。

(1)

ここでは、ClusterIP Service を例に挙げる。

10.0.0.10というIPアドレスを持つClusterIP Serviceがいるとする。

$ kubectl get svc kube-dns -n kube-system

NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.0.0.10    <none>        53/UDP,53/TCP   12d
(2)

kube-proxy で iptables を確認すると、受信したリクエストをパケットとして処理していく流れを確認できる。

KUBE-SERVICESターゲット配下にはKUBE-SVCターゲットがいる。

KUBE-SVCターゲットは、ClusterIP Serviceである。

$ kubectl exec -it kube-proxy-wf7qw -n kube-system -- iptables -nL -t nat --line-numbers

Chain KUBE-SERVICES (2 references)
num  target                     prot opt source               destination

...

5    KUBE-MARK-MASQ             udp  --  !172.16.10.0/24      10.0.0.10            /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
# Serviceにルーティングするための設定
6    KUBE-SVC-TCOU7JCQXEZGVUNU  udp  --  0.0.0.0/0            10.0.0.10            /* kube-system/kube-dns:dns cluster IP */ udp dpt:53

...
(2)

KUBE-SVC ターゲット配下には KUBE-SEP ターゲットがいる。

statistic mode random probabilityに応じて、パケットをKUBE-SEPのターゲットいずれかに振り分ける。

# KUBE-SVC
Chain KUBE-SVC-TCOU7JCQXEZGVUNU (1 references)

num  target                     prot opt source               destination
1    KUBE-SEP-K7EZDDI5TWNJA7RX  all  --  0.0.0.0/0            0.0.0.0/0            /* kube-system/kube-dns:dns */ statistic mode random probability 0.50000000000
2    KUBE-SEP-JTVLMQFBDVPXUWUS  all  --  0.0.0.0/0            0.0.0.0/0            /* kube-system/kube-dns:dns */

(3) KUBE-SEP のターゲットに応じて、異なる DNAT ターゲットを持つ。

 `DNAT`ターゲットは、Podである。

 kube-proxyはDNAT処理を実行し、パケットの宛先IPアドレス (ServiceのIPアドレス) をPodのIPアドレスに変換する。

 ここでは、パケットの宛先IPアドレスを`172.16.10.9`と`172.16.10.42`に変換する。
Chain KUBE-SEP-K7EZDDI5TWNJA7RX (1 references)

num  target          prot  opt  source           destination
1    KUBE-MARK-MASQ  all   --   172.16.10.42     0.0.0.0/0            /* kube-system/kube-dns:dns */
2    DNAT            udp   --   0.0.0.0/0        0.0.0.0/0            /* kube-system/kube-dns:dns */ udp to:172.16.10.42:53

Chain KUBE-SEP-JTVLMQFBDVPXUWUS (1 references)
num  target          prot  opt  source           destination
1    KUBE-MARK-MASQ  all   --   172.16.10.9      0.0.0.0/0            /* kube-system/kube-dns:dns */
2    DNAT            udp   --    0.0.0.0/0       0.0.0.0/0            /* kube-system/kube-dns:dns */ udp to:172.16.10.9:53

(4) 宛先の Pod の IP アドレスを確認すると、DNAT 処理の変換後の IP アドレスと一致している。

$ kubectl get po -n kube-system -o wide -l k8s-app=kube-dns

NAME                     READY   STATUS    RESTARTS   AGE    IP             NODE                                NOMINATED NODE   READINESS GATES
coredns-69c47794-6xnlq   1/1     Running   0          18h    172.16.10.9    aks-nodepool1-19344272-vmss000000   <none>           <none>
coredns-69c47794-cgn9k   1/1     Running   0          7d9h   172.16.10.42   aks-nodepool1-19344272-vmss000001   <none>           <none>


Headless Serviceのユースケース

▼ ライターPodとリーダーPodのクラスタリングする場合

StatefulSet で MySQL をクラスタリングした場合、ライターPod とリーダーPod がいる。

アプリケーションはライターPod とリーダーPod の両方の IP アドレスを認識し、いずれに永続化するべきかを判断する必要がある。

この場合、すべての Pod の IP アドレスを返却する Headless Service が適している。

▼ Pod間でデータ同期が必要な場合

StatefulSet で Keycloak をクラスタリングした場合、JGroups は Infinispan クラスターインスタンス間でセッションデータを同期する。

JGroups がすべての Infinispan クラスターインスタンス間でセッションデータを同期できるように、Infinispan を内蔵したすべての Keycloak Pod を認識できるようにする必要がある。


04. Clusterリソース

Clusterリソースとは

Cluster 全体に渡る機能を提供する。


Namespace

▼ Namespaceとは

各 Kubernetes リソースの影響範囲を制御するための領域のこと。

Namespace が異なれば、.metadata.labels キーに同じ値 (例:同じ名前など) を設定できる。

▼ 初期Namespace

名前 説明
default 任意のKubernetesリソースを配置する。
kube-node-lease Kubernetesリソースのうちで、特にLeaseを配置する。
kube-public すべてのkube-apiserverクライアント (kubectl クライアント、Kubernetesリソース) に公開してもよいKubernetesリソースを配置する。
kube-system Kubernetesが自動的に作成したKubernetesリソースを配置する。ユーザーが設定する必要はない。

▼ NamespaceがTerminatingのままになる

以下の方法で対処する。


05. 設定系リソース

設定系リソースとは

コンテナで使用する変数、ファイル、ボリュームに関する機能を提供する。


ConfigMap

▼ ConfigMapとは

コンテナで使用する機密ではない変数やファイルをマップ型で保持できる。

改行することにより、設定ファイルも値に格納できる。

▼ 機密ではない変数の例

変数 何のために使用するのか
DBホスト名 コンテナがDBに接続するときに、DBのホスト名として使用する。
DBポート番号 コンテナがDBに接続するときに、DBのポート番号として使用する。
DBタイムアウト コンテナがDBに接続するときに、タイムアウト時間として使用する。
DB接続リトライ数 コンテナがDBに接続するときに、リトライの回数として使用する。
タイムゾーン コンテナ内のタイムゾーンとして使用する。
... ...


Secret

▼ Secretとは

コンテナで使用する機密な変数やファイルをキーバリュー型で永続化する。

永続化されている間は base64 方式でエンコードされており、デコードしたうえで、変数やファイルとして対象の Pod に出力する。

▼ 機密ではない変数の例

変数 何のために使用するのか
DBユーザー名 コンテナがDBに接続するときに、DBユーザーのユーザー名として使用する。
DBパスワード コンテナがDBに接続するときに、DBユーザーのパスワードとして使用する。
... ...

▼ コンテナイメージプルのパラメーターとして

Pod の起動時に、kubectl コマンドが実行され、コンテナイメージをプルする。

Secret に永続化された値を復号し、kubectl コマンドにパラメーターとして出力できる。

▼ コンテナの環境変数として

永続化された値を復号し、Pod 内のコンテナに環境変数として出力できる。


06. ストレージ系リソース

ストレージ系リソースの種類

Kubernetes で作成できるストレージは、作成場所で種類を分けられる。

kubernetes_storage_resource_types

ストレージの種類 Volume PersistentVolume
Pod内ストレージ EmptyDir なし
Node内ストレージ HostPath HostPath、Local
Node外ストレージ Node外ストレージ Node外ストレージ


PersistentVolume

▼ PersistentVolumeとは

storage_class

Node 上のストレージ上に Volume を作成する。

Node 上の Pod 間で Volume を共有でき、同一 Pod 内のコンテナ間でも Volume を共有できる。

Pod が PersistentVolume を使用するためには、PersistentVolumeClaim に PersistentVolume を要求させておき、Pod でこの PersistentVolumeClaim を指定する必要がある。

アプリケーションのディレクトリ名を変更した場合は、PersistentVolume を再作成しないと、アプリケーション内のディレクトリの読み出しでパスを解決できない場合がある。

Docker の Volume とは独立した機能であることに注意する。

▼ PersistentVolumeの使用率の確認方法 (CrashLoopBackOffでない場合)

Pod 内で df コマンドを実行することにより、PersistentVolume の使用率を確認できる。

出力結果で、ファイルシステム全体の使用率を確認する。

$ kubectl exec -n prometheus foo-pod -- df -hT

ただし、CrashLoopBackOff などが理由で、コンテナがそもそも起動しない場合、この方法で確認できない。

また、Grafana の kubernetes-mixins には、起動中の Pod の PersistentVolume の使用率を可視化できるダッシュボードがある。

▼ PersistentVolumeの使用率の確認方法 (CrashLoopBackOffの場合)

ここでは、Prometheus を例に挙げる。

(1)

Prometheus の Pod に紐づく PersistentVolume は、最大 200Gi を要求していることがわかる。

$ kubectl get pvc foo-prometheus-pvc -n prometheus
NAME                 STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS    AGE
foo-prometheus-pvc   Bound    pvc-*****   200Gi      RWO            gp3-encrypted   181d
(2)

Node 内 (Amazon EKS の EC2 ワーカーNode の場合) で、Pod に紐づく PersistentVolume がマウントされているディレクトリを確認する。

$ ls -la /var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/aws/<リージョン>/vol-*****/prometheus-db/

-rw-r--r--  1 ec2-user 2000         0 Jun 24 17:07 00004931
-rw-r--r--  1 ec2-user 2000         0 Jun 24 17:09 00004932
-rw-r--r--  1 ec2-user 2000         0 Jun 24 17:12 00004933

...

drwxrwsr-x  2 ec2-user 2000      4096 Jun 20 18:00 checkpoint.00002873.tmp
drwxrwsr-x  2 ec2-user 2000      4096 Jun 21 02:00 checkpoint.00002898
drwxrwsr-x  2 ec2-user 2000      4096 Jun 21 04:00 checkpoint.00002911.tmp
(3)

df コマンドでストレージの使用率を確認する。Node にマウントされているデータサイズを確認すると、197G となっている。PersistentVolume に対してデータサイズが大きすぎる。

$ df -h /var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/aws/ap-northeast-1a/vol-*****/prometheus-db/

Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme8n1    197G  197G     0 100% /var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/aws/ap-northeast-1a/vol-*****

▼ HostPath (本番環境で非推奨)

Node のストレージ上に Volume を作成し、これをコンテナにバインドマウントする。

機能としては、Volume の一種である Pod による HostPath と同じである。

マルチ Node はサポートしていないため、本番環境では非推奨である。

▼ Local (本番環境で推奨)

Node 上に Volume を作成し、これをコンテナにバインドマウントする。

マルチ Node をサポートしている (明言されているわけではく、HostPath との明確な違いがよくわからない) 。

▼ Node外ストレージツールのVolume

Node 外ストレージツール (例:AWS EBS、NFS、iSCSI、Ceph など) が提供する Volume をコンテナにマウントする。

StorageClass と PersistentVolumeClaim を経由して、PersistentVolume と Node 外ストレージツールを紐付け、Volume としてコンテナにマウントする。

また、Node 外ストレージを使用する場合には、CSI ドライバーも必要である。

storage_class


Volume

▼ Volumeとは

storage_class

Node 外ストレージツール (例:AWS EBS、NFS、iSCSI、Ceph など) をそのまま Kubernetes の Volume として使用する。

Pod の .spec.volumes キーで指定する。

# Podに接続する
$ kubectl exec -it <Pod名> -c <コンテナ名> -- bash

# ストレージを表示する
[root@<Pod名>:/var/www/html] $ df -h

Filesystem      Size  Used Avail Use% Mounted on
overlay          59G   36G   20G  65% /
tmpfs            64M     0   64M   0% /dev
tmpfs           3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/vda1        59G   36G   20G  65% /etc/hosts
shm              64M     0   64M   0% /dev/shm
overlay          59G   36G   20G  65% /var/www/foo # 作成したボリューム
tmpfs           7.8G   12K  7.8G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs           3.9G     0  3.9G   0% /proc/acpi
tmpfs           3.9G     0  3.9G   0% /sys/firmware

Pod が持つ Volume の一覧は、kubectl describe コマンドで確認できる。

$ kubectl describe pod

...

Volumes:
  foo-volume:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:  <unset>
  bar-volume:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:  <unset>
  baz-volume:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      baz-cm
    Optional:  false

▼ DockerのVolumeとの違い

Docker のボリュームとは独立した機能であることに注意する。

▼ HostPath (本番環境で非推奨)

Node 上の既存のストレージ上に Volume を作成し、コンテナにバインドマウントする。

バインドマウントは Node と Pod 内のコンテナ間で実行され、同一 Node 上の Pod 間でこの Volume を共有でき、同一 Pod 内のコンテナ間でも Volume を共有できる。

また、Pod が削除されてもこの Volume は削除されない。

HostPath は非推奨である。

# Node内でdockerコマンドを実行
$ docker inspect <コンテナID>

    {

        ...

        "HostConfig": {
            "Binds": [
                "/data:/var/www/foo",
                "/var/lib/kubelet/pods/<PodのUUID>/volumes/kubernetes.io~projected/kube-api-access-*****:/var/run/secrets/kubernetes.io/serviceaccount:ro",
                "/var/lib/kubelet/pods/<PodのUUID>/etc-hosts:/etc/hosts",
                "/var/lib/kubelet/pods/<PodのUUID>/containers/foo/*****:/dev/termination-log"
            ],

            ...
        },

        ...

        "Mounts": [

            ...

            {
                "Type": "bind", # バインドマウントが使用されている。
                "Source": "/data",
                "Destination": "/var/www/foo",
                "Mode": "",
                "RW": "true",
                "Propagation": "rprivate"
            },

            ...
        ]
    }

▼ EmptyDir

Pod の既存のストレージ上に Volume (/var/lib/kubelet/pods/<PodのUUID>/volumes/kubernetes.io~empty-dir/ ディレクトリ) を作成し、コンテナにボリュームマウントする。

ディレクトリの中身は、Node の Node 外ストレージやメモリ上 (上限は Node のメモリの 50%) に保管できる。

同一 Node 上の Pod 間でこの Volume を共有できず、同一 Pod 内のコンテナ間では Volume を共有できる。

また、Pod が削除されるとこの Volume も同時に削除されてしまう。

保持期間を設定できるツール (例:Prometheus、VictoriaMetrics、Grafana Mimir など) にて、Pod の Volume を EmptyDir としている場合、Pod を保持期間より先に削除すると、保持期間を待たずに Volume を削除することになってしまう。

▼ Node外ストレージツールのVolume

Node 外ストレージツール (例:AWS EBS、NFS、iSCSI、Ceph など) が提供する Volume をコンテナにマウントする。

同一 Node 上の Pod 間でこの Volume を共有でき、同一 Pod 内のコンテナ間でも Volume を共有できる。

また、Pod が削除されてもこの Volume は削除されない。

▼ Volumeの代わりにPersistentVolumeを使用する

Pod の .spec.volumes キーで PersistentVolumeClaim を宣言すれば、Volume の代わりに PersistentVolume を使用できる。


06-02. ストレージ要求系

PersistentVolumeClaim

▼ PersistentVolumeClaimとは

設定された条件に基づいて、Kubernetes で作成済みの PersistentVolume を要求し、指定した Kubernetes リソースに割り当てる。

storage_class

▼ 削除できない

PersistentVolumeClaim を削除しようとすると、.metadata.finalizers キー配下に kubernetes.io/pvc-protection 値が設定され、削除できなくなることがある。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  finalizers:
    - kubernetes.io/pvc-protection
  name: foo-persistent-volume-claim
spec: ...

この場合、kubectl edit コマンドなどで .metadata.finalizers キーを空配列に編集と、削除できるようになる。

$ kubectl edit pvc <PersistentVolumeClaim名>
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  finalizers: []
  name: foo-persistent-volume-claim
spec: ...

▼ node affinity conflict

PersistentVolumeClaim は、annotation キー配下の volume.kubernetes.io/selected-node キーで、紐づく PersistentVolume が配置されている Node 名を指定している。

PersistentVolumeClaim は、条件に応じて PersistentVolume を探す。

しかし、PersistentVolumeClaim が volume.kubernetes.io/selected-node キーで指定する Node と、Pod をスケジューリングさせている Node が異なる AZ であると、以下のエラーになってしまう。

N node(s) had volume node affinity conflict, N node(s) didn't match Pod's node affinity/selector

これが起こる原因はさまざまある (例:Node の再作成時に Pod のある Node の AZ が変わる、AWS のスポットインスタンスで特定の AZ にしか Node が作成されない)。

*解決例*

ここでは、Node の再作成で Pod のある Node の AZ が変わった場合の解決策を記載する。

AWS のスポットインスタンスで特定の AZ にしか Node が作成されない問題では対処できない。

例えば、もともと a ゾーンにいる Pod が Node の再作成で再スケジューリングされ、c ゾーンになったとする。

しかし、Pod に紐づく PersistentVolumeClaim はもともとの a ゾーンの Node の PersistentVolume を指定したままになっており、c ゾーンの Pod は a ゾーンの PersistentVolume を指定できないため、volume node affinity conflict になる。

以下の手順で、PersistentVolumeClaim とこれを指定する Pod の両方を再作成し、PersistentVolumeClaim は Pod と同じゾーンの PersistentVolume を指定可能にする。

注意点として、何らかの理由 (例:スポットインスタンス) で、特定の AZ に Node を配置できない場合、この手順では解決できない。

(1)

起動できない Pod をいずれの Node でスケジューリングさせようとしているのか確認する。

$ kubectl describe pod <Pod名> -o wide | grep Node:
(2)

Node のあるゾーンを確認する。

$ kubectl describe node <PodのあるNode名> | grep topology.kubernetes.io
(3)

PersistentVolumeClaim の volume.kubernetes.io/selected-node キーで、Pod がいずれの Node の PersistentVolume を指定しているかを確認する。

このNode名をメモしておく。

$ kubectl describe pvc <PVC名> -n prometheus | grep selected-node
(4)

Node のあるゾーンを確認する。

$ kubectl describe node ip-*-*-*-*.ap-northeast-1.compute.internal | grep zone
(5)

(1)と(4)の手順で確認した Node のゾーンが異なるゾーンであることを確認する。

(6)

PersistentVolumeClaim を削除する。

この時PersistentVolumeは削除されないため、保管データは削除されない。

(7)

StatefulSet 自体を再作成する。

(8)

StatefulSet が PersistentVolumeClaim を新しく作成する。

(9)

PersistentVolumeClaim が、Pod と同じゾーンの PersistentVolume を指定できるようになる。

▼ サイズを拡張する

PersitentVolumeClaim の値が変われば、使用する PersistentVolume を変えられる。

--cascade オプションで Pod を残して StatefulSet を

$ kubectl patch prometheus foo --patch '{"spec": {"paused": "true", "storage": {"volumeClaimTemplate": {"spec": {"resources": {"requests": {"storage":"10Gi"}}}}}}}' --type merge

$ kubectl delete statefulset -l operator.prometheus.io/name=foo-operator --cascade=orphan


StorageClass

▼ StorageClassとは

Node 外ストレージツール (例:AWS EBS、Azure Disk など) を要求し、これを Volume として PersistentVolumeClaim に提供する。

そのため、PersistentVolume も合わせて作成する必要がある。

StorageClass を使用する場合は、PersistentVolumeClaim ではなく StorageClass 側で reclaimPolicy キーとして設定する。

storage_class

▼ AWS EBSを要求する場合

reclaimPolicyDelete になっている PersistentVolumeClaim を削除すれば、StorageClass が AWS EBS もよしなに削除してくれる。



07. 認証系リソース

CertificateSigningRequest

▼ CertificateSigningRequestとは

認証局に対するサーバー証明書の要求 (openssl x509 コマンド) を宣言的に設定する。

別途、秘密鍵から証明書署名要求を作成し、これをパラメーターとして設定する必要がある。


ServiceAccount

▼ ServiceAccountとは

kubernetes_authorization

kube-apiserver が、クライアント側を認証可能にする。

kube-apiserver が、Kubernetes リソース (特に Pod) を認証可能にする。

別途、RoleBinding や ClusterRoleBinding を使用して Kubernetes リソースに認可スコープを設定する必要がある。

Pod で ServiceAccount の指定がない場合、service-account-admission-controller は Pod に ServiceAccount を自動的に設定する。

▼ ServiceAccountのユーザー名

特に ServiceAccount には、より正確な定義のユーザー名がある。

ServiceAccount のユーザー名は、system:serviceaccount:<Namespace名>:<ServiceAccount名> で定義されている。

これは、RoleBinding や ClusterBinding の定義時に使用できる。

▼ service-account-controller

各 Namespace 内に default という ServiceAccount を自動的に作成する。

これは、default の Namespace とは無関係である。

▼ token-controller

ServiceAccount 用の Secret の作成をポーリングし、Secret にトークン文字列を追加する。

一方で、Secret の削除をポーリングし、ServiceAccount から Secret の指定を削除する。

また、ServiceAccount の削除をポーリングし、token-controller は Secret のトークン文字列を自動的に削除する。

▼ service-account-admission-controller

AdmissionWebhook の仕組みのなかで、Pod の作成時に Volume 上の /var/run/secrets/kubernetes.io/serviceaccount ディレクトリをコンテナにマウントする。

トークンの文字列は、/var/run/secrets/kubernetes.io/serviceaccount/token ファイルに記載されている。


UserAccount

▼ UserAccountとは

kubernetes_authorization

kube-apiserver が、クライアント側を認証可能にする。

kube-apiserver が、クライアントを認証可能にする。別途、RoleBinding や ClusterRoleBinding を使用して、クライアントに認可スコープを設定する必要がある。

クライアントの認証に必要なクライアント証明書は、kubeconfig ファイルに登録する必要がある。


User/Group

クラウド上のユーザーやグループを Kubernetes 上で使用する場合、User/Group で指定する。


08. 認可系リソース

Role、ClusterRole

▼ Role

Namespaced スコープな Kubernetes リソースやカスタムリソース (Namespace を設定できる Kubernetes リソース) に関する認可スコープを設定する。

kubernetes_authorization

▼ ClusterRoleとは

Cluster スコープな Kubernetes リソースやカスタムリソース (Namespace を設定できない Kubernetes リソース) に関する認可スコープを設定する。

kubernetes_authorization

▼ RBAC:Role-based access control

Role、ClusterRole を使用して認可スコープを制御する仕組みのこと。


RoleBinding

▼ ClusterRoleBinding

ClusterRole を、UserAccount / ServiceAccount / Group に紐付ける。

注意点として、ClusterRole のみの紐付けに使用できる。

▼ RoleBinding

Role や ClusterRole を、UserAccount / ServiceAccount / Group に紐付ける。

注意点として、Role と ClusterRole の両方の紐付けに使用できる。

もし Role を紐づけた場合は、その UserAccount / ServiceAccount / Group は、Namespaced スコープの Kubernetes リソースやカスタムリソースに関する権限を得る。

もし ClusterRole を紐づけた場合は、その UserAccount / ServiceAccount / Group は、Cluster スコープの Kubernetes リソースやカスタムリソースに関する権限を得る。


09. ポリシー系リソース

NetworkPolicy

▼ NetworkPolicyとは

Pod 間通信でのインバウンド通信とアウトバウンド通信の送受信ルールを設定する。

▼ Ingressの場合

他の Pod からの受信する通信のルールを設定する。

Ingress とは関係がないことに注意する。

▼ Egressの場合

他の Pod に送信する通信のルールを設定する。