『つくって、壊して、直して学ぶKubernetes入門』のメモ
書籍に関係ないことでも疑問に思ったことがあれば書いていく。
ServiceとPodの紐づけについて
演習用のマニフェストとして、ServiceとDeploymentがそれぞれ以下2つの独立したファイルで構成されている。そして、DeploymentのマニフェストをapplyしたあとにServiceマニフェストをapplyする流れになっている。
apiVersion: v1
kind: Service
metadata:
name: hello-server-service
spec:
selector:
app: hello-server
ports:
- protocol: TCP
port: 8080
targetPort: 8080
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にトラフィックをルーティングしてくれる)
ラベルセレクターでDeploymentとServiceを疎結合に管理する利点
ってなんだろうと思った。以下があると思う。
- Deploymentを更新してPodのイメージを変更したとしてもServiceの設定は変更する必要がない。
- Deploymentをスケールアップまたはダウンしても、Serviceは自動的に新しいPodを検出してトラフィックをルーティングしてくれる。
- 複数のDeploymentを単一のServiceに紐づけることができる。これによって複数のバージョンのアプリケーションを同時に運用し、トラフィックを制御することができる(カナリアデプロイメントなど)
ConfigMapとSecretの違い
ConfigMapを参照できる人が全員秘密情報にアクセスできるのはセキュリティ上望ましくない
Secretというリソースを使用することで、アクセス権をわけられます。
両者は同じkey-value型のデータであり、アクセスに関してはRBACを使用してConfigMapの操作に制限をかければ一緒なのでは?と思った。
書籍の内容だけを見る限り、違いはSecretのデータがBase64でエンコードされるだけ。
ただし、これは暗号の仕組みではないのでおそらく内部的にセキュアに管理されるためのなにかしらの機構があると思う。ので調べたら以下が見つかった。
なるほど。
ConfigMap/Secretってなんで必要なんだっけ?
普段、Docker Composeを使っているときは環境変数やパスワードをcompse.yaml内のenvironment
やenv_file
、secret
を使って値を渡しているので、それと同じイメージ。
そういえば「そもそもなんで外部から注入するんだっけ?Dockerイメージを作るときにDockerfileで直接入れちゃう方法もあるよね」とDocker触りたてのときに思ったことがあった。
なぜこれがまずいのかは以下。
-
アプリケーションの可搬性がなくなる
外部から設定を入れられるようにしておけば、アプリの設定値をコンテナイメージから分離できることになる。
こうすることで同じイメージを異なる環境(開発、ステージング、本番)で使うときに環境特有の設定を柔軟に変更することができる。
逆にこうしておかないと、環境ごとにイメージを作る羽目になってしまい、コンテナのメリットである可搬性を享受できない。 -
秘匿データが丸見え
Dockerイメージにデータを埋め込んでしまうと、データはイメージ内に平文で保存される。このデータが秘匿情報だった場合、イメージが流出すると情報漏洩するリスクがあって非常にまずい。 -
設定変更時に再ビルドが必要
Dockerイメージに埋め込んだ設定が間違っていたり後から更新したい場合、イメージの再ビルドが必要になる。つまり、設定の更新に時間がかかり、アプリのダウンタイムにつながる可能性がある。
外部から注入できるようにしておけばビルドは不要であり、コンテナの再読み込み/再起動すればOK。
ちなみに、KubernetesのConfigMapやSecretを更新するとPodは自動的に更新された設定値を取得するっぽいのでそもそも再起動すらいらない?
アプリケーションのヘルスチェック
ヘルスチェックに失敗した場合の挙動を指定でき、以下の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の再起動を行う
- 無限に再起動を繰り返してしまうリスクがある
- 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回失敗した場合にコンテナが再起動されるように設定
具体的な流れ
- コンテナが起動すると、
Readiness Probe
が10秒後に開始され、5秒ごとに/healthzエンドポイントをチェックする -
Liveness Probe
は、30秒後に開始され、10秒ごとにルートエンドポイントをチェックする -
Readiness Probe
が失敗した場合、Kubernetesはコンテナをサービスから削除し、トラフィックを他の正常なコンテナにルーティングする -
Liveness Probe
が3回連続して失敗した場合、Kubernetesはコンテナを再起動する