なぜK8sでWebアプリのセッションが壊れるか? ～原因と対処～

# 0. 要約

- KubernetesにセッションをもつWebアプリをデプロイした場合を考える．
- セッションの保管先は，マシンの内部と外部に分類される．
- セッションをマシンの内部にもつと，スケーラビリティが損なわれる．
- セッションをマシン内部に保持するアプリをスケーラビリティをもたせる方法は次の2つがある．
    - Session Affinity
    - セッション切り出し
- スケーラビリティで重要なのはソフトウェアアーキテクチャである．
- コンテナでもVMでもマシン内部に状態を保持するとスケーラビリティに課題が生じる．

# 1. はじめに

KubernetesでスケーラビリティをもたせたWebアプリケーションのデプロイをするときに苦労したセッション管理について紹介します．アプリケーションをKubernetesにデプロイする場合の注意点と対策を紹介します．

# 2. セッションとは

HTTPはステートレス(状態をもたない)プロトコルです．そのため，Cookieに代表される識別子を付与することでセッションを実現しています．この記事ではWebアプリケーションにおいてCookieにより実現されるセッションの管理について説明します．

# 3. セッションの保管先

Webアプリケーションにおいてセッションはログイン状態の維持でよく使われています．セッションを保持する方法の例には以下があります．

- マシン内のメモリやファイル
- マシン外のデータベースやKVS

セッションを識別するための識別子の例には以下があります．

- Cookie
- HTTPヘッダ

この章では，セッションの保管先を比較・検討します．

## 3.1. マシン内のメモリやファイル

メモリやファイルによる方法では，マシンごとに内部でセッションを保持しています．スケールアウトによりマシンの台数を増えると，マシンごとにセッションが独立して保持されます．その結果，マシンによってセッションの存在するものと，存在しないものが生まれます．

![Qiita_Kubernetesセッション管理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103672/bbab8286-5eb1-9f2e-aa81-88e116e92a1d.png)

## 3.2. マシン外のデータベースやKVS

データベースやKVSによる方法では，マシンの外部でセッションを保持しています．マシンが増えてもセッションはマシンの外部で保持するため，マシン間でセッションが共有されます．これにより，どのマシンにアクセスしても同一のセッションを利用することができます．

![Qiita_Kubernetesセッション管理 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103672/41718092-d540-89c4-7c28-aed5e54345cd.png)

# 4. スケーラビリティの検討

**マシン内のメモリやファイル**を利用する場合，スケールアウトするとマシンごとにセッションが独立するためセッションの不整合が発生します．**マシン外のデータベースやKVS**を利用する場合は，セッションがマシン間で共有されるためスケールアウトした場合もセッションが壊れません．

![Qiita_Kubernetesセッション管理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103672/4cf27a6b-aab4-ef1d-596a-52e37db641da.png)

以下では，**マシン内のメモリやファイル**をセッション管理に使うアプリケーションをスケールアウトさせる方法を検討します．

## 4.1. (方法1) セッション固定

マシン内でセッションを保持するアプリケーションをスケールさせるための方法としてIngress(Serviceのtype:ingress)によるセッション固定があります．これはSession Sticky(Session Affinity)とよばれます．Session StickyをKubernetes上で実現する方法には，Nginx IngressやAmbassadorの利用があります．

以下は，Nginx IngressによるCookieベースのSession Stickyです．
[Sticky Sessions - NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/)より引用

```yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: nginx-test
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "route"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"

spec:
  rules:
  - host: stickyingress.example.com
    http:
      paths:
      - backend:
          serviceName: http-svc
          servicePort: 80
        path: /
```

以下は，AmbassadorによるCookieベースのSession Stickyです．
[Sticky Sessions / Session Affinity - Load Balancing in Ambassador Edge Stack | Ambassador](https://www.getambassador.io/docs/latest/topics/running/load-balancer/#sticky-sessions--session-affinity)より引用

```yaml
apiVersion: getambassador.io/v2
kind:  Mapping
metadata:
  name:  quote-backend
spec:
  prefix: /backend/
service: quote
resolver: my-resolver
load_balancer:
  policy: ring_hash
  cookie:
    name: sticky-cookie
    ttl: 60s
```

## 4.2. (方法2) セッションの切り出し

セッションをマシン内に保管するWebアプリケーションをスケールさせるには，セッションを外部へ保管する実装への変更が必要となります．

例えばNode.jsのWebフレームワークであるExpressの場合，[connect-redis](https://github.com/tj/connect-redis)を使った外部のRedisにセッションを保管する実装へ修正が必要です．

PHPの場合，[php-redis-extension](https://github.com/phpredis/phpredis) を使うことで，実装を変更せずにセッションをRedisへ保管できます．

マシン間のメモリを共有するIn-Memory Data Gridも利用することでメモリ空間を共有できるかもしれません．

参考: [“限界”を突破する技術、「インメモリデータグリッド」 (1/2) - ITmedia エンタープライズ](https://www.itmedia.co.jp/im/articles/1209/19/news139.html)

## 4.3. 比較

Session Affinity

- メリット: ソフトウェア実装を変更せずにスケーラビリティを高められる．
- デメリット: Session Affinityを実装しているロードバランサ(Ingress)でしか実現できない．

セッションの切り出し

- メリット: Session Affinityに比べてアプリケーションとミドルウェアの依存度を下げられる．
- デメリット: ソフトウェア実装の変更が必要になる．

# 5. おわりに

KubernetesでスケーラビリティをもたせたWebアプリケーションのデプロイをするときに苦労したセッション管理について紹介しました．マシン内部に状態を保持するアーキテクチャのWebアプリケーションはVMやコンテナに関わらず，スケーラビリティに課題が発生することが分かります．ソフトウェア設計においてスケーラビリティを意識して設計できるかは，重要な観点だといえます．

既存のDockerイメージがスケーラビリティを持つかは，Dockerfileやソースコードを読まない限り把握できないのが課題だと考えています．わたし自身もDocker Hubで公開されているDockerイメージがスケーラビリティをもつかの判断に苦労しています．Helmを使った場合も同様にチャートの作者以外がスケーラビリティをもつかは判断しにくいです．

ソフトウェアのアーキテクチャ設計においてスケーラビリティを配慮すると，Kubernetesに載せたときに幸せになれると思います．

# 参考資料

- ACID特性
    - [「トランザクションのACID特性」を理解する：「データベーススペシャリスト試験」戦略的学習のススメ（21） - ＠IT](https://www.atmarkit.co.jp/ait/articles/1703/01/news194.html)

- Session Affinity
    - AWS [Kubernetes on AWS で sticky session を実現する – ゆびてく](https://ubiteku.oinker.me/2017/02/10/enable-sticky-session-in-kubernetes-on-aws/)
    - Nginx Ingress [KubernetesでのIngress経由のSession Sticky - Qiita](https://qiita.com/sotoiwa/items/14ab3e16202a71ac89a9)
    - Azure [Session affinityを構成したい – Logico Inside](https://logico-jp.io/2020/03/19/configured-session-affinity-but/)
- セッション切り出し
    - [PHPセッションをPhpRedisに保存する - Qiita](https://qiita.com/zurazurataicho/items/a71437412410955afd44)