HashiCorp Vault HA Cluster 基本動作

# 概要

HashiCorp Vault（OSS版）における、RaftやClusterの基本的な動作のメモです。

# 前提条件

- 本記事は、こちら記事の内容に沿ってVault Cluster構成を組んだ環境での説明となります。
  [HashiCorp Vault HA Cluster構築](https://qiita.com/saitamanokusa/items/35a9e0676a5f0ea41c37)
- HashiCorp Vault 1.13.2 を使って確認した内容です。
- Vaultは、Auto-Unseal を利用できるものとします。

# はじめに

Vault Cluster の管理に必要となる、Raft、Cluster、各ノードの管理、基本動作などを解説します。
障害防止のためにも、必ず押さえておくべき知識です。

# Raftの動作

Raftのnodeの状態は、大きく3パターンあります

- non-voter : Raftに参加し、まだ安定性確保できていない状態
- voter : Raftに参加し、一定時間経過で安定状態と見なされたもの
- leader : RaftのデータRead/Writeを行う

```shell
# raft のノード一覧   State や、Voter で状態を確認
vault operator raft list-peers
Node        Address                State       Voter
----        -------                -----       -----
vault-01    192.168.11.101:8201    leader      true
vault-02    192.168.11.102:8201    follower    true
vault-03    192.168.11.103:8201    follower    true
```

## Raft 必要ノード数について

- 可用性を確保するには、最低3台必要（基本は奇数台とする）
- Raft クラスターは、leaderを選出するのに十分なノード数を確保できないと、Read/Writeの一切の接続を受け付けなくなる。
  必要なノード数：`(n+1)/2` で計算 → 3台構成の場合、少なくとも２台の稼働が必要
- 推奨は5台構成とのこと
[deployment-table](https://developer.hashicorp.com/vault/docs/internals/integrated-storage#deployment-table)

## Raft 未使用ノード削除について

Raftにnodeを追加した場合、該当サーバーを停止した後でも、Raftのnodeは残ったままとなります。
これを放置すれば、いずれRaft障害となります

| 構成          | Cluster稼働台数 | Raft node台数 | 障害許容台数 |
| ------------- | --------------- | ------------- | ------------ |
| 3台でRaft構築 | 3台             | 3台           | 1台          |
| 1台追加       | 4台             | 4台           | 1台          |
| 1台停止| 3台|4台|0台 ※1台障害状態|

上記の状態では、3台の稼働を確保しているにも関わらず、あと1台でも停止すればRaft障害となり利用不能となります。
この場合、Raftから未使用ノードの削除をおこなうことで、Raftは3台構成となり、可用性の確保ができます。

| 構成          | Cluster稼働台数 | Raft node台数 | 障害許容台数 |
| ------------- | --------------- | ------------- | ------------ |
| 未使用ノード削除 | 3台             | 3台           | 1台          |

```shell
# raft のメンバーノードを確認
vault operator raft list-peers
Node        Address                State       Voter
----        -------                -----       -----
vault-01    192.168.11.101:8201    leader      true
vault-02    192.168.11.102:8201    follower    true
vault-03    192.168.11.103:8201    follower    true

# raft の不要なノードを削除
vault operator raft remove-peer vault-03
Peer removed successfully!

# vault-03 が削除され、Raftは2台構成となった
vault operator raft list-peers
Node        Address                State       Voter
----        -------                -----       -----
vault-01    192.168.11.101:8201    leader      true
vault-02    192.168.11.102:8201    follower    true
```

:::note
AutoPilotの設定を行うことで、無効化nodeを自動削除することも可能です。
:::

---

もし一度Raftから削除したnodeを再度追加しなおす場合は、該当サーバー上で下記操作が必要です。

- vaultのサービス停止
- 該当サーバー上のRaftのデータを削除
- vaultのサービス開始  ※構成ファイルにてCluster、Raftの設定が済んでいること

```shell
# 一度削除したvault-03 は、サービス再起動しても、再度追加されない
sudo systemctl restart vault

vault operator raft list-peers
Node        Address                State       Voter
----        -------                -----       -----
vault-01    192.168.11.101:8201    leader      true
vault-02    192.168.11.102:8201    follower    true

# vault-03にて、サービス停止後、データを削除したうえで、起動しなおす
sudo systemctl stop vault
sudo rm -rf /opt/vault/raft/*
sudo systemctl start vault

# vault-03が新規nodeとして追加され、データが同期される
vault status

vault operator raft list-peers
Node        Address                State       Voter
----        -------                -----       -----
vault-01    192.168.11.101:8201    leader      true
vault-02    192.168.11.102:8201    follower    true
vault-03    192.168.11.103:8201    follower    false

```

[operator raft](https://developer.hashicorp.com/vault/docs/commands/operator/raft#snapshot)

# Clusterの動作

- VaultのClusterは、Active Node 1台と、その他の Standby Node で構成される
- Raft のメンバー追加で、併せてClusterのメンバーにも追加される。
- RaftのLeader node が ClusterのActive Node となる。

Raftと区別が付きづらいですが、こちらは不要ノードの削除といった操作は不要です。
（オフライン時はメンバーからも消え、起動したら再度追加される）

```shell
# clusterの稼働ノードを確認
vault operator members
Host Name    API Address                    Cluster Address                Active Node    Version    Upgrade Version    Redundancy Zone    Last Echo
---------    -----------                    ---------------                -----------    -------    ---------------    ---------------    ---------
vault-01     https://192.168.11.101:8200    https://192.168.11.101:8201    true           1.13.2     1.13.2             n/a                n/a
vault-02     https://192.168.11.102:8200    https://192.168.11.102:8201    false          1.13.2     1.13.2             n/a                2023-05-18T03:15:24+09:00
vault-03     https://192.168.11.103:8200    https://192.168.11.103:8201    false          1.13.2     1.13.2             n/a                2023-05-18T03:15:24+09:00

```

## Cluster nodeにおけるRead/Write処理

vault cluster では、Read/Write処理は、すべてActive noddeで行われます。
Standby Nodeでvaultコマンド処理を受けた場合、Active Nodeへ転送されます。（OSS版での動作）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/47597c96-b902-758d-6c49-401e559f1630.png)

## helth check について

インターネット側からのアクセスにALBを利用する場合、ターゲットグループにてヘルスチェックの設定を行うことで、 通信をActive Node に向けることができます。

条件： `/v1/sys/health` への通信に対して、ステータスコード 200 が返ること

https://developer.hashicorp.com/vault/api-docs/system/health

## Active Node の切り替え方

メンテナンスの都合などで、Active Nodeを切り替えたい場合は、コマンド操作で強制的に切り替えることが可能です。

```shell
# 既存のClusterのActive Node, RaftのLeader node を確認
vault operator members
vault operator raft list-peers

# 現在のActive Nodeを、Standby Nodeに切り替え
vault operator step-down

# Active Node / Leader node が切り替わったことを確認
vault operator members
vault operator raft list-peers

```

## Active Node変更時のALB経由の通信チェック

Active Node変更時に、ALBからの通信がどのように推移するか、順に確認してみます。

通常は外部からの通信は、Active Node に向いており、以下のような応答が返ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/152508d5-d609-e319-1f13-c551f9e94e1b.png)

```shell
# ALB経由の応答確認のために、vaultコマンドの向け先をALBに指定
export VAULT_ADDR=https://{ALBのアドレス}


# 通常時は。ALBからの通信は Leader node に向いている
# →ヘルスチェックは standby: false が返る
vault read -format json /sys/health | jq -r .data.standby
false

# Actie Nodeのホスト名をチェック ※要認証
vault read -format json /sys/host-info | jq -r .data.host.hostname
vault-01
```

Active Node が切り替わった場合でも、ヘルスチェックで検出されるまで、引き続き同じノード（現Standby Node）に通信は流れます。
ただし、Standby Node宛ての通信は、Active Nodeへ転送されるため、vaultは通常通りの応答を返しまます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/a5aaf348-182a-e6b1-9d29-6d2003025a34.png)

```shell
# ヘルスチェック検出前は、元のノード（現Standby）に向いている
# →ヘルスチェックは standby: true が返る
vault read -format json /sys/health | jq -r .data.standby
true

# Actie Node は変更されている
vault read -format json /sys/host-info | jq -r .data.host.hostname
vault-02
```

ヘルスチェックで検出されると、現時点のActive Nodeに通信が向きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/8f06bfd8-9d8e-5aa2-5c01-a96b75c776e6.png)

```shell
# ヘルスチェック検出により、新たなActive Node宛てに切り替わった
# →ヘルスチェックは standby: false が返る
vault read -format json /sys/health | jq -r .data.standby
false

# 現状のActie Node が返る
vault read -format json /sys/host-info | jq -r .data.host.hostname
vault-02
```

このように、ほぼ無停止でActive-Standby 切り替えが可能です。

:::note warn
`vault operator step-down`コマンド実行直後は、（Leader node 選出の間？）わずかな時間、応答がエラーとなります。
:::
:::note alert
Active Nodeや、ALBからの通信先Nodeが突然ダウンした場合は、ヘルスチェック検出までは応答がエラーとなります
:::

なお、上記の確認方法では、応答を返すノードがactiveまたはstandbyの区別しかできませんでした。
しかし`/etc/vault.d/vault.hcl`の設定によっては、httpの応答ヘッダーにて、ホスト名、ノード名を出力することも可能です。
※認証なしでも確認できてしまうため、設定の有効化についてはご判断ください。

- `enable_response_header_hostname = true`
- `enable_response_header_raft_node_id = true`

```shell
# 構成ファイルの設定にて、httpの応答ヘッダーにhostname、node_idを出すようにしている場合
# ヘルスチェック用のURLを使って、ヘッダーの値で確認することも可能。
curl -k https://{ALBのアドレス}/v1/sys/health --head
（省略）
x-vault-hostname: vault-01
x-vault-raft-node-id: vault-01
```

[High Availability Mode (HA)](https://developer.hashicorp.com/vault/docs/concepts/ha)
[/sys/health](https://developer.hashicorp.com/vault/api-docs/system/health)

# Autopilotについて

## 動的な追加削除が行われる場合の危険性

例えば、auto-join 構成を用いれば、Auto Scaling を利用して一定のnode数を維持する構成も可能です。
しかしこの場合、前述したとおり、停止したノードがRaftメンバーから削除されなければ、障害の原因となります。

この場合、Autopilot の設定を行うことで、一定のしきい値を超えた障害ノードを、自動的に削除することが可能となります。

## 動作チェック

Autopilot による自動削除の設定例

```shell
# 自動削除を有効化
vault operator raft autopilot set-config -cleanup-dead-servers
# 10分以上停止ノードは自動削除
vault operator raft autopilot set-config -dead-server-last-contact-threshold 10m
# 最低3台は確保（3台未満とならないように）
vault operator raft autopilot set-config -min-quorum 3
# Raftに追加されてから安定状態とみなされるまで１分（1分でvoter node となる）
vault operator raft autopilot set-config -server-stabilization-time 1m
```

（障害を想定して）vault-03を停止、vault-04を新規追加してみる

```shell
# 通常時は、Health、１台の耐障害性
vault operator raft autopilot state
Healthy:                         true
Failure Tolerance:               1

# vault-03 を停止すると、Healty false、耐障害性が0となる
vault operator raft autopilot state
Healthy:                         false
Failure Tolerance:               0

# Raftの状態をみると、vault-03は残ったままとなっている
vault operator raft list-peers
Node        Address                State       Voter
----        -------                -----       -----
vault-01    192.168.11.101:8201    leader      true
vault-02    192.168.11.102:8201    follower    true
vault-03    192.168.11.103:8201    follower    true


# vault-04 を新規に追加  追加した直後は、Voter = false となっている
vault operator raft list-peers
Node        Address                State       Voter
----        -------                -----       -----
vault-01    192.168.11.101:8201    leader      true
vault-02    192.168.11.102:8201    follower    true
vault-03    192.168.11.103:8201    follower    true
vault-04    192.168.11.104:8201    follower    false

# 追加後1分経過で、vault-04 のvoter = true となり、vault-03 は自動削除された
# (停止後 10分以上経過済み, Raftの最低node数3台が確保でき、自動削除の条件をクリア)
vault operator raft list-peers
Node        Address                State       Voter
----        -------                -----       -----
vault-01    192.168.11.101:8201    leader      true
vault-02    192.168.11.102:8201    follower    true
vault-04    192.168.11.104:8201    follower    true
```

[Autopilot](https://developer.hashicorp.com/vault/docs/concepts/integrated-storage/autopilot)

# 最後に

初めて触れる場合は、RaftやらClusterやら、分かりづらいですが、Cluster構成を組むうえでは、それらを区別し、目的に応じた操作を行うことが必須です。
実際に操作を行うことで、設定によってどのようにnodeが追加されるか、Active／Standbyの動作の違いなど理解できるようになるかと思います。