OpenShiftのKubernetes APIサーバー用のロードバランサーにヘルスチェックのエンドポイント(/readyz)の設定を追加する

OpenShift(以下OCP)をベアメタルのUPIの方法で導入する際には、前提としてAPIのロードバランサーとアプリケーションIngressのロードバランサーが必要です。
  
少し昔のOCPのドキュメントでは、Kubernetes APIサーバー(Port: 6443)用とMachine Configサーバー(Port: 22623)用のAPIロードバランサーと、HTTPSトラフィック(Port: 443)用とHTTPトラフィック(Port: 80)用のアプリケーションIngressロードバランサーのどちらについても、ただ単にLayer 4のロードバランサーが必要で、ポート番号とバックエンドマシン(プールメンバー)の情報ぐらいしか記載がなかった様に思います。
  
但し、ちょっと(と言っても結構)前から、OCPのドキュメントには、これらのPort番号とバックエンドマシンの情報に加えて、Kubernetes APIサーバー(Port: 6443)用のロードバランサーについては、ヘルスチェックのエンドポイントの情報(`/readyz`)が記載される様になっていました。    
    
- [OCP 4.6 Docs / 1. ベアメタルへのインストール / 1.3. ネットワークが制限された環境でのクラスターのベアメタルへのインストール / 1.3.4. ユーザーによってプロビジョニングされるインフラストラクチャーを使用する場合のクラスターのマシン要件](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.6/html-single/installing_on_bare_metal/index#installation-requirements-user-infra_installing-restricted-networks-bare-metal)

  - (上述はインターネット非接続環境のベアメタルのUPIの場合のリンクですが、インターネット非接続ではない場合のドキュメントのリンクの場所でも記載内容は同じです。)
  - (しかもこれらは現在最新のOCP 4.6のドキュメントだけではなく、OCP 4.3などの過去のOCPのバージョンのドキュメントにわたって更新されている模様です。)  
    
ですので、今回はOCPのドキュメントに従ってHAProxyにヘルスチェックの設定を追加してみたいと思います。

(2022/09/21追記)
「[Qiita / OpenShift 4.10 ベアメタルUPI (iPXE) インストール / ロードバランサー](https://qiita.com/t_sweetfish/items/81268b29a3a7ad2f3f09#%E3%83%AD%E3%83%BC%E3%83%89%E3%83%90%E3%83%A9%E3%83%B3%E3%82%B5%E3%83%BC)」の方では、設定内容を少し更新しました。ただ、以下の記載自体も参考になると思いますのでそのまま残しておきます。
(追記終わり)
  

# Kubernetes APIサーバー(Port: 6443)の`/readyz`エンドポイント

- master nodeが正常な場合は「ok」と表示され、HTTPの戻り値は「200」となります。

```
[user01@bastion-01 ~]$ curl https://master-01.test2.example.local:6443/readyz -k
ok[user01@bastion-01 ~]$
[user01@bastion-01 ~]$
[user01@bastion-01 ~]$ curl https://master-01.test2.example.local:6443/readyz -k -o /dev/null -w '%{http_code}\n' -s
200
[user01@bastion-01 ~]$
```

- master nodeに障害がある場合は返答がなく、HTTPの戻り値は「000」になります。(ステータスコードを返さない)

```
[user01@bastion-01 ~]$ curl https://master-01.test2.example.local:6443/readyz -k
curl: (7) Failed connect to master-01.test2.example.local:6443; 接続を拒否されました
[user01@bastion-01 ~]$
[user01@bastion-01 ~]$ curl https://master-01.test2.example.local:6443/readyz -k -o /dev/null -w '%{http_code}\n' -s
000
[user01@bastion-01 ~]$
```
  

# 前提の環境

今回はインターネット非接続環境でベアメタルのUPIの方法でOCP4.4を導入しています。  
また、以下のQiitaの記事と同じ様に、役割ごとに専用のInfra nodeを構成しています。  
  
- [Qiita / OCP 4.2で専用のInfra nodeを作成してルーター、内部レジストリー、モニタリング、ロギングを移動する](https://qiita.com/t_sweetfish/items/fe23d0f50df3179c4b91)

  
|node名|ノードの役割|備考|
|:--|:--|:--|
|master-0[1-3]|master|master node|
|worker-0[1-2]|worker|worker node(アプリケーションPod用)|
|infra-0[1-3]|infra|ルーター、内部レジストリー用|
|infra-mon-0[1-3]|infra(logging)|モニタリング用|
|infra-log-0[1-3]|infra(monitoring)|ロギング用|
  
  
  - 検証環境のOCPのバージョンは少し古いですが、今回のロードバランサーの設定内容はOCP 4.6のドキュメントでも同じだったため、この記事ではドキュメントのリンクはOCP 4.6のリンクを添付します。)


# 今までの設定 (ヘルスチェックプローブの設定なし)

検証環境でベアメタルのUPIの方法でOCPを導入する際に昔はHAProxyでは大体以下の様な設定をしていました。
- Kubernetes APIサーバー(Port: 6443)の`mode`は`tcp`(L4のロードバランサー)
- Kubernetes APIサーバー(Port: 6443)のバックエンドは`option ssl-hello-chk`のチェックのみ

(参考)
- [赤帽ブログ / OpenShift 4.2におけるネットワーク制限環境下でのインストール](https://rheb.hatenablog.com/entry/openshift42-restricted-upi) 
- [赤帽ブログ / OpenShift 4.1をベアメタルへUPIインストールする](https://rheb.hatenablog.com/entry/openshift41-baremetal-upi)


`/etc/haproxy/haproxy.conf`の設定例

```(/etc/haproxy/haproxy.conf)
(global、defaultのセクションはデフォルトのまま)

(略)

frontend K8s-api
    bind *:6443
    option tcplog
    mode tcp
    default_backend     api-6443

frontend Machine-config
    bind *:22623
    option tcplog
    mode tcp
    default_backend     config-22623

frontend Ingress-http
    bind *:80
    option tcplog
    mode tcp
    default_backend http-80

frontend Ingress-https
    bind *:443
    option tcplog
    mode tcp
    default_backend     https-443

backend api-6443
    mode tcp
    balance     roundrobin
    option  ssl-hello-chk
    # server  bootstrap-01 bootstrap-01.test2.example.local:6443 check
    server  master-01 master-01.test2.example.local:6443 check
    server  master-02 master-02.test2.example.local:6443 check
    server  master-03 master-03.test2.example.local:6443 check

backend config-22623
    mode tcp
    balance     roundrobin
    # server  bootstrap-01 bootstrap-01.test2.example.local:22623 check
    server  master-01 master-01.test2.example.local:22623 check
    server  master-02 master-02.test2.example.local:22623 check
    server  master-03 master-03.test2.example.local:22623 check

backend http-80
    mode tcp
    balance     roundrobin
    server  infra-01 infra-01.test2.example.local:80 check
    server  infra-02 infra-02.test2.example.local:80 check
    server  infra-03 infra-03.test2.example.local:80 check

backend https-443
    mode tcp
    balance     roundrobin
    option      ssl-hello-chk
    server  infra-01 infra-01.test2.example.local:443 check
    server  infra-02 infra-02.test2.example.local:443 check
    server  infra-03 infra-03.test2.example.local:443 check
```



# 今回の設定 (ヘルスチェックプローブの設定あり)

## 要件の確認

先ほども記載した様に、最近のOCP 4.6のドキュメントでは、Kubernetes APIサーバー(Port: 6443)用のAPIロードバランサーについては、ヘルスチェックのエンドポイントとして`/readyz`の情報が追記されています。    
    
- [OCP 4.6 Docs / 1. ベアメタルへのインストール / 1.3. ネットワークが制限された環境でのクラスターのベアメタルへのインストール / 1.3.4. ユーザーによってプロビジョニングされるインフラストラクチャーを使用する場合のクラスターのマシン要件](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.6/html-single/installing_on_bare_metal/index#installation-requirements-user-infra_installing-restricted-networks-bare-metal)
  
(抜粋)

> 1. API ロードバランサー: プラットフォームと対話およびプラットフォームを設定するためのユーザー向けの共通のエンドポイントを提供します。以下の条件を設定します。
> 
>   - Layer 4 の負荷分散のみ。これは、Raw TCP、SSL パススルー、または SSL ブリッジモードと呼ばれます。  
> (略)

> 表1.20 API ロードバランサー
> 
> |ポート|バックエンドマシン (プールメンバー)|内部|外部|説明|
> |:--|:--|:--|:--|:--|
> |6443|ブートストラップおよびコントロールプレーン。ブートストラップマシンがクラスターのコントロールプレーンを初期化した後に、ブートストラップマシンをロードバランサーから削除します。API サーバーのヘルスチェックプローブの `/readyz` エンドポイントを設定する必要があります。|X|X|Kubernetes API サーバー|
> |22623|ブートストラップおよびコントロールプレーン。ブートストラップマシンがクラスターのコントロールプレーンを初期化した後に、ブートストラップマシンをロードバランサーから削除します。|X||マシン設定サーバー|
  
> 注記  
> ロードバランサーは、API サーバーが `/readyz` エンドポイントをオフにしてからプールから API サーバーインスタンスを削除するまで最大 30 秒かかるように設定する必要があります。`/readyz` の後の時間枠内でエラーが返されたり、正常になったりする場合は、エンドポイントが削除または追加されているはずです。5 秒または 10 秒ごとにプローブし、2 つの正常な要求が正常な状態になり、3 つの要求が正常な状態になりません。これらは十分にテストされた値です

(日本語の注記がよくわからない場合はちゃんと英語版のドキュメントも合わせて参照...)
  
> NOTE
> The load balancer must be configured to take a maximum of 30 seconds from the time the API server turns off the `/readyz` endpoint to the removal of the API server instance from the pool. Within the time frame after `/readyz` returns an error or becomes healthy, the endpoint must have been removed or added. Probing every 5 or 10 seconds, with two successful requests to become healthy and three to become unhealthy, are well-tested values.
  



## 設定方法

先ほどの記述だけだけだとHAProxyに具体的にどのように設定すればよいのかすぐにはわからなかったので、少し検索してみたところ、OpenShiftのinstallerのGitHubに参考になりそうな情報がありました。
(vSphereのUPIの場合のインストーラーのロードバランサーの設定のテンプレート??)  
- [GitHub / Openshift/installer / upi/vsphere/lb/haproxy.tmpl](https://github.com/openshift/installer/blob/master/upi/vsphere/lb/haproxy.tmpl)
(抜粋)

```
defaults
  (略)
  mode    tcp
  (略)

frontend api-server
    bind ${lb_ip_address}:6443
    default_backend api-server

(略)
backend api-server
    option  httpchk GET /readyz HTTP/1.0
    option  log-health-checks
    balance roundrobin
    %{ for addr in api ~}
    server ${addr} ${addr}:6443 weight 1 verify none check check-ssl inter 1s fall 2 rise 3
    %{ endfor ~}
(略)
```

- `mode`は`tcp` (L4のロードバランサー)
- ヘルスチェックのエンドポイント`/readyz`に関する設定として、`option  httpchk GET /readyz HTTP/1.0`の定義を追加
- `server`では`server ${addr} ${addr}:6443 weight 1 verify none check check-ssl inter 1s fall 2 rise 3`と設定
  - バックエンドのサーバーに重みはつけないので全て`weight 1`と設定
  - 自己署名証明書を使用するので`verify none`を追加
  - `check`でヘルスチェックを有効にする
  - `option httpchk`とSSL checkを組み合わせて使用するため、`check-ssl`オプションを追加
      - (参考) [HAProxy Documentation / check-ssl](https://cbonte.github.io/haproxy-dconv/2.4/configuration.html#5.2-check-ssl)
  - `inter 1s fall 2 rise 3`では1秒ごとにヘルスチェックをし、失敗2回でエンドポイントを削除し、成功3回でエンドポイントを追加している様に思われる
      - (参考) [HAProxy Documentation / inter](https://cbonte.github.io/haproxy-dconv/2.4/configuration.html#5.2-inter)
      - (参考) [HAProxy Documentation / fall](https://cbonte.github.io/haproxy-dconv/2.4/configuration.html#5.2-fall)
      - (参考) [HAProxy Documentation / rise](https://cbonte.github.io/haproxy-dconv/2.4/configuration.html#rise)



  
## 今回の設定内容
  
上述の設定値は今回のOCPのドキュメントに記載があった設定値とは少し違いますが、設定項目は参考になると思いますので、上述の設定項目を参考に、今回のOCPのドキュメントに記載のロードバランサーの要件に従って、検証環境のHAProxyに以下の様に設定を追加してみました。
  
- `mode`は`tcp` (L4のロードバランサー)
- ヘルスチェックのエンドポイント`/readyz`に関する設定として、`option  httpchk GET /readyz HTTP/1.0`の定義を追加
- `server`では`server ${addr} ${addr}:6443 weight 1 verify none check check-ssl inter [5|10]s fall 3 rise 2`と設定
  - バックエンドのサーバーに重みはつけないので全て`weight 1`と設定
  - 自己署名証明書を使用するので`verify none`を追加
  - `check`でヘルスチェックを有効になる。
  - `option httpchk`とSSL checkを組み合わせて使用するため、`check-ssl`オプションを追加
  - OCPのドキュメントでは、ヘルスチェックの間隔は5秒または10秒で、2回成功でエンドポイントを追加し、3回失敗でエントポイントを削除するのが「*これらは十分にテストされた値です*」とのことなので、`inter [5|10]s fall 3 rise 2`と設定してみる
  - (最初は一旦10秒にしてみる)
  
  
`inter 10 fall 3 rise 2`の場合の`/etc/haproxy/haproxy.conf`の設定例

```(/etc/haproxy/haproxy.conf)
(global、defaultのセクションはデフォルトのまま)

(略)

frontend K8s-api
    bind *:6443
    option tcplog
    mode tcp
    default_backend     api-6443

frontend Machine-config
    bind *:22623
    option tcplog
    mode tcp
    default_backend     config-22623

frontend Ingress-http
    bind *:80
    option tcplog
    mode tcp
    default_backend http-80

frontend Ingress-https
    bind *:443
    option tcplog
    mode tcp
    default_backend     https-443

backend api-6443
    mode tcp
    option  httpchk GET /readyz HTTP/1.0
    option  log-health-checks
    balance roundrobin
    # server  bootstrap-01 bootstrap-01.test2.example.local:6443 weight 1 verify none check check-ssl inter 10s fall 3 rise 2
    server  master-01 master-01.test2.example.local:6443 weight 1 verify none check check-ssl inter 10s fall 3 rise 2
    server  master-02 master-02.test2.example.local:6443 weight 1 verify none check check-ssl inter 10s fall 3 rise 2
    server  master-03 master-03.test2.example.local:6443 weight 1 verify none check check-ssl inter 10s fall 3 rise 2

backend config-22623
    mode tcp
    balance     roundrobin
    # server  bootstrap-01 bootstrap-01.test2.example.local:22623 check
    server  master-01 master-01.test2.example.local:22623 check
    server  master-02 master-02.test2.example.local:22623 check
    server  master-03 master-03.test2.example.local:22623 check

backend http-80
    mode tcp
    balance     roundrobin
    server  infra-01 infra-01.test2.example.local:80 check
    server  infra-02 infra-02.test2.example.local:80 check
    server  infra-03 infra-03.test2.example.local:80 check

backend https-443
    mode tcp
    balance     roundrobin
    option      ssl-hello-chk
    server  infra-01 infra-01.test2.example.local:443 check
    server  infra-02 infra-02.test2.example.local:443 check
    server  infra-03 infra-03.test2.example.local:443 check
```

設定の反映(haproxyの再起動)

```
$ sudo systemctl restart haproxy
$ sudo systemctl status haproxy
```

# 稼働確認

任意のmaster nodeの1台に障害があっても、ロードバランサーが障害のあったnodeをエンドポイントから切り離し、ocコマンドなどは継続して実行できるというのがあるべき姿だと思います。

そこで、[今までの設定(ヘルスチェックプローブの設定なし)](#今までの設定-ヘルスチェックプローブの設定なし)の場合と、[今回の設定(ヘルスチェックProbeの設定あり)](#今回の設定-ヘルスチェックプローブの設定あり)の場合に、master nodeを1台再起動中にocコマンドを1秒毎に繰り返し実行して挙動を確認してみてみました。  
  
## 確認手順
  
1. 一秒ごとに`oc get node master-01`を実行し続ける。

    ```
    $ while true; do date; oc get node master-01; sleep 1; done
    ```

2. 別terminalを開いてmaster-01を再起動する。

    ```
    ssh -i .ssh/<ssh key> core@master-01 sudo shutdown -r now
    ```

3. `oc get node`コマンドが実行できない時間を比較してみる。
   
  
  
## [今までの設定(ヘルスチェックプローブの設定なし)](#今までの設定-ヘルスチェックプローブの設定なし)の場合

- master-01を再起動

```
[user01@bastion-01 ~]$ date; ssh -i .ssh/ocp_rsa core@master-01 sudo shutdown -r now
2020年 12月 30日 水曜日 20:05:10 JST
Connection to master-01 closed by remote host.
[user01@bastion-01 ~]$
```
  
- `oc get node master-01`の実行結果
  - ReadyからNotReadyになる際に、実行毎に30秒から1分ぐらいコマンドが返ってこず、`Unable to connect to the server: unexpected EOF`や`Error from server (Timeout): the server was unable to return a response in the time allotted, but may still be processing the request (get nodes master-01)`が表示されることがありました。
  - NotReadyからReadyになる際は特に途切れはありませんでした。

(以下は50秒ぐらいで`error: You must be logged in to the server (Unauthorized)`が出力された例)

```
[user01@bastion-01 ~]$ while true; do date; oc get node master-01; sleep 1; done

(略)

2020年 12月 30日 水曜日 20:05:15 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 20:05:16 JST
error: You must be logged in to the server (Unauthorized)
2020年 12月 30日 水曜日 20:06:06 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8

(略)

2020年 12月 30日 水曜日 20:07:24 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 20:07:25 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 20:07:27 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
```

(以下は60秒ぐらいで`Error from server (Timeout): the server was unable to return a response in the time allotted, but may still be processing the request (get nodes master-01)`が出力された例)

```
2020年 12月 30日 水曜日 20:23:00 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 20:23:01 JST
Error from server (Timeout): the server was unable to return a response in the time allotted, but may still be processing the request (get nodes master-01)
2020年 12月 30日 水曜日 20:24:02 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8
```  
  

## [今回の設定(ヘルスチェックプローブの設定あり)](#今回の設定-ヘルスチェックプローブの設定あり)の場合
  
### ヘルスチェック間隔10秒の場合

- master-01を再起動

```
[user01@bastion-01 ~]$ date; ssh -i .ssh/ocp_rsa core@master-01 sudo shutdown -r now
2020年 12月 30日 水曜日 19:35:46 JST
Connection to master-01 closed by remote host.
[user01@bastion-01 ~]$
```
  
- `oc get node master-01`の実行結果
  - ReadyからNotReadyになる際に、大体30秒ぐらいコマンドが返ってこず、1-2回`error: You must be logged in to the server (Unauthorized)`のメッセージが表示されました。
  - NotReadyからReadyになる際は特に途切れはありませんでした。

(以下は30秒ぐらいで`error: You must be logged in to the server (Unauthorized)`が出力された例)

```
[user01@bastion-01 ~]$ while true; do date; oc get node master-01; sleep 1; done

(略)

2020年 12月 30日 水曜日 19:35:51 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 19:35:52 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 19:35:53 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 19:35:54 JST
error: You must be logged in to the server (Unauthorized)
2020年 12月 30日 水曜日 19:36:25 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 19:36:26 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8

(略)

2020年 12月 30日 水曜日 19:38:00 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 19:38:01 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
```
  

### ヘルスチェック間隔5秒の場合

- master-01を再起動

```
[user01@bastion-01 ~]$ date; ssh -i .ssh/ocp_rsa core@master-01 sudo shutdown -r now
2020年 12月 30日 水曜日 22:45:56 JST
Connection to master-01 closed by remote host.
[user01@bastion-01 ~]$
```
  
- `oc get node master-01`の実行結果
  - ReadyからNotReadyになる辺りで、大体は30-50秒ぐらいコマンドが返ってこず、1-2回`error: You must be logged in to the server (Unauthorized)`のメッセージが表示されました。
  - NotReadyからReadyになる際は特に途切れはありませんでした。

(以下は30秒ちょっとで`error: You must be logged in to the server (Unauthorized)`が出力された例)

```
$ while true; do date; oc get node master-01; sleep 1; done

(略)

2020年 12月 30日 水曜日 22:46:04 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 22:46:06 JST
error: You must be logged in to the server (Unauthorized)
2020年 12月 30日 水曜日 22:46:39 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 22:46:40 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8

(略)

2020年 12月 30日 水曜日 22:48:11 JST
NAME        STATUS     ROLES    AGE   VERSION
master-01   NotReady   master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 22:48:12 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
2020年 12月 30日 水曜日 22:48:13 JST
NAME        STATUS   ROLES    AGE   VERSION
master-01   Ready    master   59d   v1.17.1+c5fd4e8
```


  
# まとめ

OCPのドキュメントにあるKubernetes APIサーバー(Port: 6443)用のロードバランサーのヘルスチェックのエンドポイントとして`/readyz`をHAProxyに設定してみました。

コマンドが戻ってこない時間などは以前と比べて多少は改善した(様な気がする)ものの、`/readyz`へのヘルスチェックを間隔10秒、失敗3回、成功2回に設定した場合も、間隔5秒、失敗3回、成功2回にした場合のいずれも30秒程度コマンドが返ってこないタイミングがありました。  
(やってみた感じでは、間隔5秒の場合はコマンドが返ってくるタイミングはまちまちでしたが、間隔10秒の場合は30秒程度でコマンドが返ってくる感じで、大体同じ挙動をしていました。)
  
もし、ocコマンドなどでステータスを取得するような運用スクリプトなどを作成している場合は、このあたりの検証結果を参考に、コマンドのリトライ間隔や回数などを検討する必要があると思います。