OpenShiftクラスター外部からのHTTP要求経路について

# はじめに
IBM Cloudにおいて異なるサブネットにOpenShiftのworkerノードがあり、各workerノードにルーターPodとアプリケーションPodが配置されているとします。OpenShiftクラスター外部のclassicノードからHTTP要求したときの経路は以下となります。

・classicノード → Load Balancer → ルーターPod(subnetX) → アプリケーションPod(subnetX)

この記事では、Load Balancerのセッション維持設定、HTTPクライアントからのcookie送信有無によって、経路がどのように変化するか確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/87737877-2168-d777-aa48-9d54696f731d.png)

環境やアプリケーションについては下記の記事を参照ください。

■ OpenShift 4.8環境

https://qiita.com/y-akio/items/ad0093daa504be746573

■ Spring Bootアプリケーション

https://qiita.com/y-akio/items/155ae44188a6df02138c

# 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インスタンスの増減には対応できませんが、本記事の目的のためには十分です。

```bash:classcノード
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のアクセスログをシスログとして受信するための設定をします。

```bash:classicノード
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ノードに分散配置されているので良しとします。

```bash:classicノード
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ノードに分散配置されている状態です。

```bash
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>
```

https://qiita.com/y-akio/items/a08bac7c3ceb2fb45e67#1-%E3%83%88%E3%83%9D%E3%83%AD%E3%82%B8%E3%83%BC%E5%88%86%E6%95%A3%E5%88%B6%E7%B4%84

## 1.3. HTTP要求経路確認

curlでHTTP要求したときのログを確認したところ、以下の経路になっていました。

・classicノード → Load Balancer(subnet2) → ルーターPod(subnet3) → アプリケーションPod(subnet1)

HTTP応答ヘッダ『set-cookie』が2行出力されています。このうち「JSESSIONID」はアプリケーションPod、「9fbc8a5f3284aea082f7cc20ad7c88d3」はルーターPodが設定したものです。

```bash:classicノード
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要求経路を確認し易いようにシェルも作成しました。

```bash:log.sh
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要求経路毎のアクセス件数を確認することができます。

```bash:実行例
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へ負荷分散する。

```bash:classicノード
# ログファイル初期化後に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へ負荷分散する。

```bash:classicノード
# ログファイル初期化後に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
```

■ IBM Cloudコンソール設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/c853a25f-092f-2c20-cd80-9f5f78b1a5fe.png)

## 2.3. Load Balancerセッション維持あり、cookie送信あり
abコマンドでcookieを付加してHTTP要求したとき、以下の結果になりました。
(1) Load Balancerは(今回は)subnet2のルーターPodのみへHTTP要求を送信する。
(2) subnet2のルーターPodは(今回は)各subnet1のアプリケーションPodのみへHTTP要求を送信する。

```bash:classicノード
# ログファイル初期化後に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要求/秒が最大になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/80689ec0-cc56-301c-b7e9-d453945c1298.png)
今回、RouteでなくIngressを作成したのですが、デフォルトでルーターPodがcookieを発行していました。

```bash:classicノード
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
```

```yaml:spring-liberty.yaml(Ingress部分のみ)
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は参照しません。

```bash:ルータPodのcookieあり
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
…
```

```bash:ルータPodのcookieなし
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で非推奨

https://kubernetes.io/docs/concepts/services-networking/service-topology/

■ kubernetes v1.21でアルファ

https://kubernetes.io/docs/concepts/services-networking/topology-aware-hints/

■ OpenShift 4.8のFeatureGate機能

https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.8/html/nodes/nodes-cluster-enabling