はじめに
IBM Cloudにおいて異なるサブネットにOpenShiftのworkerノードがあり、各workerノードにルーターPodとアプリケーションPodが配置されているとします。OpenShiftクラスター外部のclassicノードからHTTP要求したときの経路は以下となります。
・classicノード → Load Balancer → ルーターPod(subnetX) → アプリケーションPod(subnetX)
この記事では、Load Balancerのセッション維持設定、HTTPクライアントからのcookie送信有無によって、経路がどのように変化するか確認します。
環境やアプリケーションについては下記の記事を参照ください。
■ OpenShift 4.8環境
■ Spring Bootアプリケーション
1. 事前準備
1.1. classicノード設定
classicノードでLoad BalancerのIPアドレスを解決するためにdnsmasqを設定します。設定変更後、アプリケーションのFQDN(spring-liberty.apps.ocp.cloud.vpc)に対して、Load BalancerのIPアドレスが返されることが確認できます。また、HTTPクライアントツールとして、curlとhttpd(付属のab:apache bench)を導入します。
※OpenShiftはDNS Servicesを参照していますが、classicノードから参照できないため個別にDNSを構成しています。Load Balancerインスタンスの増減には対応できませんが、本記事の目的のためには十分です。
vi /etc/hosts
### 追加箇所↓
10.244.0.7 spring-liberty.apps.ocp.cloud.vpc
10.244.64.12 spring-liberty.apps.ocp.cloud.vpc
systemctl restart dnsmasq
dig @127.0.0.1 spring-liberty.apps.ocp.cloud.vpc +short
### 標準出力↓
10.244.64.12
10.244.0.7
yum -y install curl
yum -y install httpd
ルーターPodのアクセスログをシスログとして受信するための設定をします。
vi /etc/rsyslog.conf
### 修正箇所↓
module(load="imudp") # needs to be done just once
input(type="imudp" port="514")
### 追加箇所↓
:fromhost, isequal, "worker-0" -/var/log/router.log
:fromhost, isequal, "worker-1" -/var/log/router.log
:fromhost, isequal, "worker-2" -/var/log/router.log
systemctl restart rsyslog
1.2. OpenShift設定
ルーターPod(Ingress Controller)のレプリカ数を「3」とし、アクセスログをclassicノードにシスログ転送するように設定します。分散配置するための設定はしていませんが、各subnetのworkerノードに分散配置されているので良しとします。
oc patch IngressController default -n openshift-ingress-operator --type=merge \
--patch='{"spec":{"replicas":3,"logging":{"access":{"destination":{"type":"Syslog","syslog":{"address":"10.192.36.132","port":514}}}}}}'
oc get pod -o wide -n openshift-ingress
### 標準出力↓
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
router-default-7647bc57db-58njn 1/1 Running 0 47s 10.244.128.111 worker-2 <none> <none>
router-default-7647bc57db-csdzg 1/1 Running 0 27s 10.244.0.111 worker-0 <none> <none>
router-default-7647bc57db-vz4k6 1/1 Running 0 5s 10.244.64.111 worker-1 <none> <none>
アプリケーションPodも各workerノードに分散配置されている状態です。
oc get pod
### 標準出力↓
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
spring-liberty-84c44ffc5b-4qj8r 1/1 Running 0 10s 10.128.2.11 worker-0 <none> <none>
spring-liberty-84c44ffc5b-b68r5 1/1 Running 0 10s 10.129.2.50 worker-1 <none> <none>
spring-liberty-84c44ffc5b-ff7lk 1/1 Running 0 11s 10.131.0.24 worker-2 <none> <none>
1.3. HTTP要求経路確認
curlでHTTP要求したときのログを確認したところ、以下の経路になっていました。
・classicノード → Load Balancer(subnet2) → ルーターPod(subnet3) → アプリケーションPod(subnet1)
HTTP応答ヘッダ『set-cookie』が2行出力されています。このうち「JSESSIONID」はアプリケーションPod、「9fbc8a5f3284aea082f7cc20ad7c88d3」はルーターPodが設定したものです。
curl -v http://spring-liberty.apps.ocp.cloud.vpc/
### 標準出力↓
< HTTP/1.1 200 OK
…
< set-cookie: JSESSIONID=0000A4zWGo-VFMYZqghisaTdX4g:d3042f50-318e-4ae5-8177-63cf09146c4b; Path=/; HttpOnly
…
< set-cookie: 9fbc8a5f3284aea082f7cc20ad7c88d3=f29c864d162458e5e2097fdaef677fa3; path=/; HttpOnly
…
tail -1 /var/log/router.log
### 標準出力↓
Sep 22 04:20:57 worker-2 haproxy[132]: 10.244.64.12:9832 [22/Sep/2021:04:20:57.042] public be_http:spring-liberty:spring-liberty-xpnqv/pod:spring-liberty-84c44ffc5b-4qj8r:spring-liberty::10.128.2.11:9080 0/0/1/2/3 200 718 - - --NI 1/1/0/0/0 0/0 "GET / HTTP/1.1"
HTTP要求経路を確認し易いようにシェルも作成しました。
grep "spring-liberty" /var/log/router.log | while read line
do
node=`echo "${line}" | awk '{print $4}'`
router=`echo "${line}" | awk '{print $6}' | awk -F: '{print $1}'`
appl=`echo "${line}" | awk -F":" '{print $14}'`
echo ${router} ${node} ${appl}
done
HTTP要求経路毎のアクセス件数を確認することができます。
log.sh | sort | uniq -c
### 標準出力↓
111 10.244.64.12 worker-0 10.128.2.11
…
### 各列の意味↓
1列目: アクセス件数
2列目: LoadBalancerのIPアドレス
3列目: ルーターPodの稼働するworkerノード名
4列目: アプリケーションPodのIPアドレス
2. classicノードからのHTTP要求経路確認
2.1. Load Balancerセッション維持なし、cookie送信なし
abコマンドでHTTP要求したとき、以下の結果になりました。
(1) Load Balancerは各subnetのルーターPodへ負荷分散する。
(2) ルーターPodは各subnetのアプリケーションPodへ負荷分散する。
# ログファイル初期化後に1000回HTTP要求
ab -n 1000 -c 10 http://spring-liberty.apps.ocp.cloud.vpc/
### 標準出力↓
…
Requests per second: 357.65 [#/sec] (mean)
…
# ログ集計
log.sh | sort | uniq -c
### 標準出力↓
111 10.244.64.12 worker-0 10.128.2.11
111 10.244.64.12 worker-0 10.129.2.50
111 10.244.64.12 worker-0 10.131.0.24
111 10.244.64.12 worker-1 10.128.2.11
112 10.244.64.12 worker-1 10.129.2.50
111 10.244.64.12 worker-1 10.131.0.24
111 10.244.64.12 worker-2 10.128.2.11
111 10.244.64.12 worker-2 10.129.2.50
111 10.244.64.12 worker-2 10.131.0.24
2.2. Load Balancerセッション維持あり、cookie送信なし
abコマンドでHTTP要求したとき、以下の結果になりました。
(1) Load Balancerは(今回は)subnet1のルーターPodのみへHTTP要求を送信する。
(2) subnet1のルーターPodは各subnetのアプリケーションPodへ負荷分散する。
# ログファイル初期化後に1000回HTTP要求
ab -n 1000 -c 10 http://spring-liberty.apps.ocp.cloud.vpc/
### 標準出力↓
…
Requests per second: 334.73 [#/sec] (mean)
…
# ログ集計
log.sh | sort | uniq -c
### 標準出力↓
333 10.244.64.12 worker-0 10.128.2.11
334 10.244.64.12 worker-0 10.129.2.50
333 10.244.64.12 worker-0 10.131.0.24
2.3. Load Balancerセッション維持あり、cookie送信あり
abコマンドでcookieを付加してHTTP要求したとき、以下の結果になりました。
(1) Load Balancerは(今回は)subnet2のルーターPodのみへHTTP要求を送信する。
(2) subnet2のルーターPodは(今回は)各subnet1のアプリケーションPodのみへHTTP要求を送信する。
# ログファイル初期化後に1000回HTTP要求
cookie=9fbc8a5f3284aea082f7cc20ad7c88d3=f29c864d162458e5e2097fdaef677fa3
ab -n 1000 -c 10 -C "${cookie}" http://spring-liberty.apps.ocp.cloud.vpc/
### 標準出力
…
Requests per second: 402.08 [#/sec] (mean)
…
# ログ確認
log.sh | sort | uniq -c
### 標準出力↓
1000 10.244.64.12 worker-0 10.128.2.11
2.4. HTTP要求経路まとめ
確認結果をまとめます。
ケース3ではsubnetをまたぐ通信が少ないためか、HTTP要求/秒が最大になりました。
今回、RouteでなくIngressを作成したのですが、デフォルトでルーターPodがcookieを発行していました。
oc get ing,route
### 標準出力↓
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/spring-liberty <none> spring-liberty.apps.ocp.cloud.vpc router-default.apps.ocp.cloud.vpc 80 32h
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
route.route.openshift.io/spring-liberty-xpnqv spring-liberty.apps.ocp.cloud.vpc / spring-liberty <all> None
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: spring-liberty
spec:
rules:
- host: spring-liberty.apps.ocp.cloud.vpc
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spring-liberty
port:
number: 80
ルーターPodがcookieを発行するか否かはIngressのanotationで設定可能です。cookieを発行しない場合は、ケース3のようにHTTP要求にルーターPodのcookieを付加することはできませんし、JSESSIONIDを指定してもルーターPodは参照しません。
oc annotate ing spring-liberty haproxy.router.openshift.io/disable_cookies=false --overwrite
curl -v http://spring-liberty.apps.ocp.cloud.vpc/
### 標準出力↓
…
< HTTP/1.1 200 OK
< x-powered-by: Servlet/3.1
< content-type: text/html;charset=UTF-8
< content-language: en-US
< set-cookie: JSESSIONID=0000EiQOYjZwegeppYdfM_zIf2N:d3042f50-318e-4ae5-8177-63cf09146c4b; Path=/; HttpOnly
< transfer-encoding: chunked
< date: Thu, 23 Sep 2021 10:40:34 GMT
< expires: Thu, 01 Dec 1994 16:00:00 GMT
< cache-control: no-cache="set-cookie, set-cookie2"
< set-cookie: 9fbc8a5f3284aea082f7cc20ad7c88d3=f29c864d162458e5e2097fdaef677fa3; path=/; HttpOnly
< cache-control: private
…
oc annotate ing spring-liberty haproxy.router.openshift.io/disable_cookies=true --overwrite
curl -v http://spring-liberty.apps.ocp.cloud.vpc/
### 標準出力↓
…
< HTTP/1.1 200 OK
< x-powered-by: Servlet/3.1
< content-type: text/html;charset=UTF-8
< content-language: en-US
< set-cookie: JSESSIONID=0000UO_TiSCbO2snErS_71KsS0b:d3042f50-318e-4ae5-8177-63cf09146c4b; Path=/; HttpOnly
< transfer-encoding: chunked
< date: Thu, 23 Sep 2021 10:40:45 GMT
< expires: Thu, 01 Dec 1994 16:00:00 GMT
< cache-control: no-cache="set-cookie, set-cookie2"
…
おわりに
本記事は、Podの分散配置について調査したことを契機に作成しました。当初、ルーターPodの連携先としてsubnet内のアプリケーションPodを優先選択したかったのですが、これを実現するためのService TopologyもTopology Aware HintsもOpenShift 4.8(kubernetes v1.21ベース)のFeatureGateで有効化される機能ではありませんでした。
■ kubernetes v1.17でアルファ、v1.21で非推奨
■ kubernetes v1.21でアルファ
■ OpenShift 4.8のFeatureGate機能