Spring Boot 2.3.0 M3のGraceful Shutdownを試してみる


## はじめに

Spring Bootの次期リリースである2.3.0では**Kubernetes関連の機能追加**が行われているようです。
今回は[Spring Boot 2.3.0 M3](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3.0-M3-Release-Notes)で追加された **Graceful Shutdown** 機能を試してみました。
2.3.0の正式リリース時には挙動が変わっている可能性もあるので参考程度にどうぞ。

## Graceful Shutdown とは

停止要求が行われた時点で処理中のリクエストがあった場合でも、猶予期間(Grace Period)まで完了を待機することで**アプリケーションを安全に停止する仕組み**です。

たとえば、下記のような処理があったとして

```
public void update() {

    updateSystemA();

    updateSystemB();

    updateSystemC();
}
```

Aの更新直後に処理が中断されてしまった場合、B・Cへの更新は行われず**不整合が発生**してしまいます。
Graceful Shutdownでは猶予時間の範囲内でB・Cへの更新も待って停止されるため、このような**不整合を回避**できます。

(ただし通常 Graceful Shutdown が働くのはあくまでも**正常な停止要求が来た場合**であり、Ctrl+CやOS自体の強制終了などでは処理続行できず普通に打ち切られます)

## Spring Boot 2.3.0 で追加された Graceful Shutdown 機能

`server.shutdown.grace-period` プロパティが追加されており、**このプロパティで猶予期間を設定するだけ**です。
下記の例では秒(s)単位で指定していますが、単位を指定しなかった場合はミリ秒での指定になるようです。
　<FONT color="Red">※ 2.3.0 の正式リリースではプロパティ名が変わっていました。
　　・server.shutdown=graceful
　　・spring.lifecycle.timeout-per-shutdown-phase=30s
　　機能自体に変更はなさそうでした。</FONT>

```
server.shutdown.grace-period=30s
```


たとえば、下記のような25秒後に"OK"を返すだけのAPIが Kubernetes 上で動いていたとします。

```java:TestController.java
@RestController
public class TestController {

    @GetMapping("/")
    public ResponseEntity<String> doTest() throws Exception {

        Thread.sleep(25_000L);

        return new ResponseEntity<>("OK", HttpStatus.OK);
    }

}
```


猶予期間を設定しなかった場合、リクエストの処理中に Pod の停止要求を行うと Spring Boot アプリケーションは実行途中で停止してしまい `502 Bad Gateway` や `504 Gateway Time-out` が返ってきました。

```
>curl http://192.168.99.100/
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>openresty/1.15.8.2</center>
</body>
</html>
```

一方、猶予期間を適切に設定していれば、リクエスト処理中に Pod の停止要求を行っても処理中のリクエストは中断されずに結果が返るまで待機してくれることが確認できました。

```
>curl http://192.168.99.100/
OK
```

猶予期間を設定した場合でも、**実行中の処理がない状態であれば待機はせずに停止してくれる**ようでした。

## おまけ

Spring Boot アプリケーションのプロパティは application.properties (application.yaml) ファイルで設定するのが一般的ですが **環境変数でも設定できます**。(詳細は [Spring Boot Features#Externalized Configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config) 参照)

猶予期間は **Kubernetes側でも調整が必要（terminationGracePeriodSeconds：デフォルト 30秒）** になるため、下記のように設定を deployment.yaml にまとめておくとやりやすいかもしれません。

```yaml:deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shutdown
  namespace: test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: shutdown
  template:
    metadata:
      labels:
        app: shutdown
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: shutdown
        image: satrbeta/shutdown:latest
        env:
        - name: SERVER_SHUTDOWN_GRACE_PERIOD
          value: 60s
        ports:
        - containerPort: 8080
```


## まとめ

Spring Boot 2.2.x まではこのような**待機処理を自前で実装する必要があった**のですが、Spring Boot 2.3.0ではほぼ自動でやってくれるようになり、だいぶ楽になっていました。

今回は組み込み tomcat 上での動作しか試していませんが、他の Webサーバでもだいたい同じ挙動になるのではないかと思います。
([2.3.0.M4時点のリファレンス](https://docs.spring.io/spring-boot/docs/2.3.0.M4/reference/html/spring-boot-features.html#boot-features-graceful-shutdown)によればUndertowのみリクエストを受け付けなくなるのではなく `503 Service Unavailable` を返す仕様とのこと)