この記事について
Kubernetesのベータ版機能としてService Internal Traffic Policyが提供されています。
この機能がOpenShift環境でも使えるかどうか確認してみました。
Service Internal Traffic Policyについて
ドキュメント
どんな機能か
クラスター内のPod間通信をローカルに制御する機能です。
とあるPodからService経由で別のPodにリクエストを投げる際、発信元と同ノードにスケジュールされているPodにのみリクエストを割り振ってくれます。
具体例を挙げると、下記図について、podからappのServiceに対してリクエストを投げると、podのあるノード1上のapp podへアクセスすることになります。
設定方法
上記例で言うと、app podのServiceにspec.internalTrafficPolicy: Local
に設定すればOK。
検証内容と結論
ざっくり検証内容
- アクセスするとホスト名を返すFlaskアプリを作成し、コンテナーイメージ化
- そのコンテナーイメージをOpenShiftにデプロイ
- アプリのServiceに
spec.internalTrafficPolicy: Local
を設定する - 別Podからアプリにアクセスしてみる
結論
同ノードにアプリのPodがあればそのPodにのみリクエストが割り振られる。ない場合はどこにもリクエストは割り振らない。
検証
環境
Red Hat OpenShift on IBM Cloud上で検証。
$ oc version
Client Version: 4.10.0-202208301216.p0.gdd2bcd0.assembly.stream-dd2bcd0
Server Version: 4.11.12
Kubernetes Version: v1.24.6+5157800
ノードは4つで構成。
$ oc get node
NAME STATUS ROLES AGE VERSION
10.245.0.11 Ready master,worker 13d v1.24.6+5157800
10.245.0.12 Ready master,worker 13d v1.24.6+5157800
10.245.64.7 Ready master,worker 13d v1.24.6+5157800
10.245.64.8 Ready master,worker 13d v1.24.6+5157800
検証手順
アプリ作成
$ ls
Dockerfile app.py
from flask import Flask
import socket
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello world from {}</p>".format(socket.gethostname())
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=True)
FROM python:3.6-alpine
RUN pip install flask
COPY app.py /opt/
EXPOSE 8080
WORKDIR /opt
ENTRYPOINT ["python", "app.py"]
上記Dockerfileとアプリケーションでイメージビルドします。
$ podman build -t print-hostname .
IBM Cloud Container Registry(ICR)にイメージを保管して、そこからOpenShiftにデプロイしたいので、まずイメージのタグ付けをします(事前にレジストリーの名前空間は作成済)。
$ podman tag localhost/print-hostname:latest jp.icr.io/mynamespace/print-hostname:latest
IBM Cloudにログインした状態で、ICRのリージョンをセット。
$ ibmcloud cr region-set jp-tok
その状態でICRの認証。
$ ibmcloud cr login --client podman
認証出来たらレジストリーにプッシュ。
$ podman push jp.icr.io/mynamespace/print-hostname
OpenShiftにアプリデプロイ
ICRからイメージプルするため、あらかじめ作成したProjectにイメージプルシークレットを作成します。
$ oc get secret all-icr-io -n default -o yaml | sed 's/default/myproject/g' | oc create -n myproject -f -
Deploymentはこんな感じ。imagePullSecret
は直前で作成したイメージプルシークレットを指定。
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: flask-app
name: flask-app
spec:
replicas: 4
selector:
matchLabels:
app: flask-app
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: flask-app
spec:
containers:
- image: jp.icr.io/mynamespace/print-hostname
name: print-hostname
resources: {}
imagePullSecrets:
- name: all-icr-io
status: {}
Deployment作成。
$ oc apply -f deploy-print-hostname.yaml
各ノードにPodがスケジュールされていることを確認(flask-app
から始まるPod)。
$ oc get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
access-test 1/1 Running 0 6m36s 172.17.29.22 10.245.64.8 <none> <none>
flask-app-574649d9b8-4dhhk 1/1 Running 0 13s 172.17.43.133 10.245.0.12 <none> <none>
flask-app-574649d9b8-4ff9x 1/1 Running 0 13s 172.17.29.36 10.245.64.8 <none> <none>
flask-app-574649d9b8-br769 1/1 Running 0 13s 172.17.57.8 10.245.64.7 <none> <none>
flask-app-574649d9b8-qjjr6 1/1 Running 0 13s 172.17.60.249 10.245.0.11 <none> <none>
全ノードにスケジュールされない場合はDeschedulerとかPod Topology Spread Constraintとかを使う(といいつつレプリカ数増やせば全ノードにスケジュールされそう)。
Deploymentに対してService作成。
$ oc expose deployment flask-app --port 8080
アプリにアクセスするPodの作成
access-test
という名前のPodを作成して、このPodからアプリにアクセスします。
apiVersion: v1
kind: Pod
metadata:
name: access-test
spec:
restartPolicy: OnFailure
containers:
- name: curl
image: curlimages/curl
imagePullPolicy: Always
command: ["/bin/sh", "-c", "tail -f /dev/null"]
$ oc apply -f pod-access-test.yaml
この状態でアプリにアクセスしてみると、各Podにリクエストが投げられたことが分かります。
$ oc rsh access-test
/ $ curl flask-app:8080
<p>Hello world from flask-app-574649d9b8-4ff9x</p>/ $
/ $ curl flask-app:8080
<p>Hello world from flask-app-574649d9b8-br769</p>/ $
/ $ curl flask-app:8080
<p>Hello world from flask-app-574649d9b8-qjjr6</p>/ $
/ $ curl flask-app:8080
<p>Hello world from flask-app-574649d9b8-4ff9x</p>/ $
/ $ curl flask-app:8080
<p>Hello world from flask-app-574649d9b8-4dhhk</p>/ $
internalTrafficPolicy
の設定とアクセス検証
アプリのServiceにspec.internalTrafficPolicy: Local
を設定します。
$ oc edit svc flask-app
access-test
Podからアプリに複数回アクセスしてみると、1つのPod(flask-app-574649d9b8-4ff9x
)のみにリクエストが投げられたことが分かります。。
$ oc rsh access-test
/ $ curl flask-app:8080
<p>Hello world from flask-app-574649d9b8-4ff9x</p>/ $
/ $ curl flask-app:8080
<p>Hello world from flask-app-574649d9b8-4ff9x</p>/ $
/ $ curl flask-app:8080
<p>Hello world from flask-app-574649d9b8-4ff9x</p>/ $
Podのスケジュールされているノードを確認すると、access-test
Podとflask-app-574649d9b8-4ff9x
Podが同ノードにスケジュールされているので、Internal Traffic Policyが動作していることが確認できました。
$ oc get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
access-test 1/1 Running 0 6m36s 172.17.29.22 10.245.64.8 <none> <none>
flask-app-574649d9b8-4dhhk 1/1 Running 0 13s 172.17.43.133 10.245.0.12 <none> <none>
flask-app-574649d9b8-4ff9x 1/1 Running 0 13s 172.17.29.36 10.245.64.8 <none> <none>
flask-app-574649d9b8-br769 1/1 Running 0 13s 172.17.57.8 10.245.64.7 <none> <none>
flask-app-574649d9b8-qjjr6 1/1 Running 0 13s 172.17.60.249 10.245.0.11 <none> <none>
internalTrafficPolicy: Local
に該当するPodがないとき
まず、access-test
Podの乗るノードにtaintを付けます。
$ oc adm taint node 10.245.64.8 key1=value1:NoSchedule
この状態で、taintありのノード上にあるアプリのPodを削除します。
$ oc delete po flask-app-574649d9b8-4ff9x
アプリのPodが別ノードにスケジュールされ、access-test
Podと同ノードにはアプリのPodが存在しない状態になります。
$ oc get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
access-test 1/1 Running 0 21h 172.17.29.22 10.245.64.8 <none> <none>
flask-app-574649d9b8-4dhhk 1/1 Running 0 21h 172.17.43.133 10.245.0.12 <none> <none>
flask-app-574649d9b8-6kqfj 1/1 Running 0 82s 172.17.57.50 10.245.64.7 <none> <none>
flask-app-574649d9b8-br769 1/1 Running 0 21h 172.17.57.8 10.245.64.7 <none> <none>
flask-app-574649d9b8-qjjr6 1/1 Running 0 21h 172.17.60.249 10.245.0.11 <none> <none>
この状態でアプリへのアクセスを試みるもアクセスできません。
$ oc rsh access-test
/ $ curl flask-app:8080
curl: (7) Failed to connect to flask-app port 8080 after 1058 ms: Couldn't connect to server
/ $ curl flask-app:8080
curl: (7) Failed to connect to flask-app port 8080 after 1025 ms: Couldn't connect to server
/ $ curl flask-app:8080
curl: (7) Failed to connect to flask-app port 8080 after 1033 ms: Couldn't connect to server