概要
kubernetesでmicroservicesアーキテクチャを採用している場合、envoyでgRPC通信するケースが多いかと思います。詳しくはこちらの記事を。
そんなときに瞬断の影響が大きいサービスを運用している場合、アプリをシャットダウンするタイミングで何も考えないで運用しているとパケットをロストします。
これは、serviceから切り離される前にPodが終了してしまうことや、Podに対して終了シグナルが送られた場合に、アプリより先にenvoyが終了しちゃう事などで発生します。
これはサイドカーしている他のコンテナでも同様のことが言えます。
対策
grpcはhttp2の通信なのでコネクションを永続化します。つまりserviceから張られていたコネクションは処理中のものも含めて切断されてしまいます。
それを避けるためには
① serviceから当該Podへの新規のコネクションを止める
② すべての処理が終わるまですべてのコンテナを落とさない
を行う必要があります。
しかしながら②を実現することは困難なので sleep 30
で妥協することにしました。
30秒程度あればすべての処理は終了するだろうという考えです。
事前準備
まず、複数のコンテナ間で状態を共有するためにunhealthy
という名前の共有volumeを作成します。
これを各コンテナの /tmp
にマウントしておきます。
ここに状態共有のための情報を書き込みます。
appコンテナの挙動
appコンテナはSIGTERMを受け取ると /tmp
に unhealthy
というファイルを作成し30秒sleepします。
同時に unhealthy
がある場合は readinessProbe
を失敗させ、serviceから切り離します。
envoyはheadless servicesを利用してバランシングしているので、このPodに対して新しいコネクションは張られなくなります。
envoyコンテナの挙動
envoyコンテナはSIGTERMを受け取ると unhealthy
ファイルを発見するまでループします。
これでappコンテナが終了するよりも前にenvoyコンテナが終了することを防ぎます。
unhealthy
ファイルを発見したら、こちらも同様に30秒sleepします。
30秒後に両コンテナが完全に終了するという流れになります。
具体的には下記のようなyamlになります。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: app
spec:
...
containers:
- name: sidecar-envoy
image: envoyproxy/envoy:v1.7.0
...
volumeMounts:
- name: unhealthy
mountPath: /tmp
readOnly: true
lifecycle:
preStop:
exec:
command:
- sh
- -c
- while (! test -e /tmp/unhealthy); do sleep 1; done; sleep 30
- name: app
image: path/to/app:latest
...
readinessProbe:
exec:
command:
- sh
- -c
- exit `! test -e /tmp/unhealthy; echo $?`
initialDelaySeconds: 5
timeoutSeconds : 3
periodSeconds: 3
failureThreshold: 1
lifecycle:
preStop:
exec:
command:
- sh
- -c
- touch /tmp/unhealthy; sleep 30
volumes:
- name: unhealthy
emptyDir: {}
initialDelaySeconds
とか timeoutSeconds
あたりは短めにしてるって程度で深い意味はありません!
これでHappy gRPC lifeを!!