sessionAffinityを設定したServiceの挙動を調べてみた


# 目的

KubernetesではPodはステートレスであるべきとされますが、残念ながら世の中のアプリケーションにはステートフルでないと動かないものもあります。もし複数のレプリカをもつPodで割り振り先を固定したい場合、ServiceのsessionAffinityを有効にすることで、宛先のPodを固定することができます。その挙動を少し検証してみます。

# 検証

## 検証環境

* Red Hat OpenShift on IBM Cloud 4.6

## テスト用Podの準備

待ち受け用にhttpdを2つと、クライアント想定でCentOSを1つ作成します。

```
$ oc new-project session-test
$ oc create-sa session-test
$ oc adm policy add-scc-to-user anyuid -z session-test
$ oc create deployment httpd --image httpd --replicas 2
$ oc patch deployment httpd -p '{"spec":{"template":{"spec":{"serviceAccountName":"session-test"}}}}'
$ oc run centos8 --image centos:8 --command -- tail -f /dev/null
$ oc get pods
NAME                     READY   STATUS    RESTARTS   AGE
centos8                  1/1     Running   0          26m
httpd-85b7664b5b-5nrxk   1/1     Running   0          27m
httpd-85b7664b5b-wzn4t   1/1     Running   0          27m
$ oc expose deployment httpd --port 80
$ oc get svc
NAME    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
httpd   ClusterIP   172.21.3.209   <none>        80/TCP    19s
```

## デフォルトの挙動

sessionAffinityはデフォルトでは有効ではありません。試しに何度かService経由でcurlしてみます。

```
$ oc rsh centos8 curl http://httpd/
<html><body><h1>It works!</h1></body></html>
$ oc rsh centos8 curl http://httpd/
<html><body><h1>It works!</h1></body></html>
$ oc rsh centos8 curl http://httpd/
<html><body><h1>It works!</h1></body></html>
$ oc rsh centos8 curl http://httpd/
<html><body><h1>It works!</h1></body></html>

$ stern httpd
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:12:06 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:12:10 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:12:12 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:12:14 +0000] "GET / HTTP/1.1" 200 45
```

ログを確認すると、上記のようにhttpd-85b7664b5b-wzn4tとhttpd-85b7664b5b-5nrxkいずれかに割り振りされていることがわかります。

## sessionAffinityの設定

Serviceのspecを変更してsessionAffinityを有効にします。タイムアウトは今後のテストのために短く5秒とします。

```
$ oc patch svc httpd -p '{"spec":{"sessionAffinity": "ClientIP","sessionAffinityConfig":{"clientIP":{"timeoutSeconds":5}}}}'
```

5秒以内に続けてcurlすると、httpd-85b7664b5b-wzn4tに割り振り先が固定されたことがわかります。また、タイムアウト時間は初回アクセスではなく最後のアクセスを起点にしているとも言えます。

```
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:19:40 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:19:42 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:20:44 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:20:46 +0000] "GET / HTTP/1.1" 200 45
```

## timeoutSecondsを超過した場合

リクエストの間にtimeoutSecondsである5秒空けてみます。

```
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:23:36 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:23:37 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:23:51 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:23:55 +0000] "GET / HTTP/1.1" 200 45
```

5秒を境に割り振り先のPodがhttpd-85b7664b5b-5nrxkからhttpd-85b7664b5b-wzn4tに変わったことがわかります。もちろんタイムアウト後にも同じPodに割り振られる可能性はあります。

## 割り振り先のPodが死んだ場合

まさかと思いますが、割り振り先のPodが消滅した場合、そこに割り振りを継続したりしないか検証します。

timeoutSecondsが切れないよう、2秒おきにcurlしつつ、途中で割り振り先のPodを削除してみます。

```
$ while :; do oc rsh centos8 curl http://httpd/; sleep 2; done
```

```
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:25:08 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:25:10 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd 172.17.62.35 - - [05/Aug/2021:18:25:13 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-wzn4t httpd [Thu Aug 05 18:25:15.198570 2021] [mpm_event:notice] [pid 1:tid 140244005635200] AH00492: caught SIGWINCH, shutting down gracefully
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:15 +0000] "GET / HTTP/1.1" 200 45
- httpd-85b7664b5b-wzn4t › httpd
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:18 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:20 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:23 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:25 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:28 +0000] "GET / HTTP/1.1" 200 45
+ httpd-85b7664b5b-92jgb › httpd
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:31 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:33 +0000] "GET / HTTP/1.1" 200 45
httpd-85b7664b5b-5nrxk httpd 172.17.62.35 - - [05/Aug/2021:18:25:36 +0000] "GET / HTTP/1.1" 200 45
```

当初はhttpd-85b7664b5b-wzn4tに固定されていましたが、Pod削除後はすぐにhttpd-85b7664b5b-5nrxkに割り振られ、そこで固定されるようになりました。当然ですが、再起動してきたhttpd-85b7664b5b-92jgbに割り振られることもありませんでした。

以上