Open5

『つくって、壊して、直して学ぶKubernetes入門』のメモ

K'K'

書籍に関係ないことでも疑問に思ったことがあれば書いていく。


ServiceとPodの紐づけについて

演習用のマニフェストとして、ServiceとDeploymentがそれぞれ以下2つの独立したファイルで構成されている。そして、DeploymentのマニフェストをapplyしたあとにServiceマニフェストをapplyする流れになっている。

service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-server-service
spec:
  selector:
    app: hello-server
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
deployment-hello-server.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-server
  template:
    metadata:
      labels:
        app: hello-server
    spec:
      containers:
      - name: hello-server
        image: blux2/hello-server:1.0
        ports:
        - containerPort: 8080

ServiceをDeployment(Pod)と紐づける場合、ラベルセレクターを使うことは理解したが、Deploymentマニフェスト内では同じラベル名( app:hello-server )が複数箇所で使われているので、具体的にそれぞれの役割がわからなかった。なので整理する。

①Deploymentのメタデータ

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-server
  labels:
    app: hello-server

上記はDeployment自体のメタデータとしてapp:hello-serverラベルを付けている。
このラベルはDeployment自体を識別するためのものであり、他のリソースとの直接的な紐付けには使用されていない。

②DeploymentのPodセレクター

spec:
  selector:
    matchLabels:
      app: hello-server

Deploymentが管理するPodを識別するためのセレクターを定義している。
このセレクターはapp: hello-serverというラベルを持つPodを指定している。

③DeploymentのPodテンプレート

template:
  metadata:
    labels:
      app: hello-server

DeploymentによってPodが作成される際に、各Podに app: hello-server というラベルが付与される。
このラベルは②の「DeploymentのPodセレクター」と一致するので、ここで定義されたPodはDeploymentによって管理されることを意味する。

④Serviceのセレクター

spec:
  selector:
    app: hello-server

Serviceがトラフィックを転送するPodを識別するためのセレクターを定義している箇所。
このセレクターは ③のapp: hello-server というラベルを持つPodを指定している。

まとめ

  • ②の「DeploymentのPodセレクター」 は、③の「Podテンプレート」 で定義されたラベルと一致している。これはすなわち、Deploymentは app: hello-server というラベルを持つPodを管理している。
  • ④の「ServiceのPodセレクター」 は、③の「DeploymentのPodテンプレート」 で定義されたラベルと一致する。これによってServiceは app: hello-server というラベルを持つPodにトラフィックを転送することになる。

つまり、DeploymentとServiceは、共通のラベル app: hello-server を使用することで、間接的に紐付けられている。
Deploymentはこのラベルを持つPodを作成し、Serviceはこのラベルを持つPodを探してトラフィックを転送する
(Serviceは、指定されたセレクターと一致するラベルを持つPodを自動的に見つけ、見つけたPodにトラフィックをルーティングしてくれる)

K'K'

ラベルセレクターでDeploymentとServiceを疎結合に管理する利点

ってなんだろうと思った。以下があると思う。

  • Deploymentを更新してPodのイメージを変更したとしてもServiceの設定は変更する必要がない。
    • Deploymentをスケールアップまたはダウンしても、Serviceは自動的に新しいPodを検出してトラフィックをルーティングしてくれる。
  • 複数のDeploymentを単一のServiceに紐づけることができる。これによって複数のバージョンのアプリケーションを同時に運用し、トラフィックを制御することができる(カナリアデプロイメントなど)
K'K'

ConfigMapとSecretの違い

ConfigMapを参照できる人が全員秘密情報にアクセスできるのはセキュリティ上望ましくない
Secretというリソースを使用することで、アクセス権をわけられます。

両者は同じkey-value型のデータであり、アクセスに関してはRBACを使用してConfigMapの操作に制限をかければ一緒なのでは?と思った。
書籍の内容だけを見る限り、違いはSecretのデータがBase64でエンコードされるだけ。
ただし、これは暗号の仕組みではないのでおそらく内部的にセキュアに管理されるためのなにかしらの機構があると思う。ので調べたら以下が見つかった。

https://teratail.com/questions/306572

なるほど。

K'K'

ConfigMap/Secretってなんで必要なんだっけ?

普段、Docker Composeを使っているときは環境変数やパスワードをcompse.yaml内のenvironmentenv_filesecretを使って値を渡しているので、それと同じイメージ。

そういえば「そもそもなんで外部から注入するんだっけ?Dockerイメージを作るときにDockerfileで直接入れちゃう方法もあるよね」とDocker触りたてのときに思ったことがあった。
なぜこれがまずいのかは以下。

  1. アプリケーションの可搬性がなくなる
    外部から設定を入れられるようにしておけば、アプリの設定値をコンテナイメージから分離できることになる。
    こうすることで同じイメージを異なる環境(開発、ステージング、本番)で使うときに環境特有の設定を柔軟に変更することができる。
    逆にこうしておかないと、環境ごとにイメージを作る羽目になってしまい、コンテナのメリットである可搬性を享受できない。

  2. 秘匿データが丸見え
    Dockerイメージにデータを埋め込んでしまうと、データはイメージ内に平文で保存される。このデータが秘匿情報だった場合、イメージが流出すると情報漏洩するリスクがあって非常にまずい。

  3. 設定変更時に再ビルドが必要
    Dockerイメージに埋め込んだ設定が間違っていたり後から更新したい場合、イメージの再ビルドが必要になる。つまり、設定の更新に時間がかかり、アプリのダウンタイムにつながる可能性がある。
    外部から注入できるようにしておけばビルドは不要であり、コンテナの再読み込み/再起動すればOK。
    ちなみに、KubernetesのConfigMapやSecretを更新するとPodは自動的に更新された設定値を取得するっぽいのでそもそも再起動すらいらない?

K'K'

アプリケーションのヘルスチェック

ヘルスチェックに失敗した場合の挙動を指定でき、以下の3つのprobeがある。

  • Readiness probe
    • Serviceリソースの接続対象から外される
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: httpserver
  name: httpserver-readiness
spec:
  containers:
  - name: httpserver
    image: blux2/delayfailserver:1.1
    readinessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
  • Liveness probe
    • Podの再起動を行う
      • 無限に再起動を繰り返してしまうリスクがある
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: httpserver
  name: httpserver-liveness
spec:
  containers:
  - name: httpserver
    image: blux2/delayfailserver:1.1
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
  • startup probe
    • Podの初回起動時のみに利用する。起動が遅いアプリケーション等に使用することが想定される。
startupProbe:
    httpGet:
        path: /healthz
        port: liveness-port
    failureThreshold: 30
    periodSeconds: 10

Readiness と Livenessの2つを組み合わせる

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: web-app
        image: nginx:1.25.3
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
          failureThreshold: 3

Readiness Probe

  • httpGetを使用して、コンテナの/healthzエンドポイントに対してHTTP GETリクエストを送信する
  • initialDelaySeconds: 10で、コンテナが起動してから最初のプローブが実行されるまでの遅延を10秒に設定
  • periodSeconds: 5で、probeが5秒ごとに実行されるように設定

Liveness Probe

  • httpGet を使用して、コンテナのルート (/) エンドポイントに対してHTTP GETリクエストを送信する
  • initialDelaySeconds: 30で、コンテナが起動してから最初のプローブが実行されるまでの遅延を30秒に設定
  • periodSeconds: 10で、probeが10秒ごとに実行されるように設定
  • failureThreshold: 3で、プローブが連続して3回失敗した場合にコンテナが再起動されるように設定

具体的な流れ

  1. コンテナが起動すると、Readiness Probeが10秒後に開始され、5秒ごとに/healthzエンドポイントをチェックする
  2. Liveness Probeは、30秒後に開始され、10秒ごとにルートエンドポイントをチェックする
  3. Readiness Probeが失敗した場合、Kubernetesはコンテナをサービスから削除し、トラフィックを他の正常なコンテナにルーティングする
  4. Liveness Probe が3回連続して失敗した場合、Kubernetesはコンテナを再起動する