我正在尝试为 Vault 设置 HA 集群,但遇到了一些无法解决的错误。我已经在网上搜索了很多并测试了各种解决方案,但我无法完成这项工作,我快疯了。
我正在尝试以最简单的方式生成 Vault 集群。一切都是手动的,没有自动加入,没有自动解封。我的测试设置是:3 个带有 Docker 引擎的虚拟机,每台机器上运行一个 Vault 实例。
我的程序是这样的:
vault.json
配置本地保存在该机器中,并且本地创建 vault-data
卷。vault operator join http://10.20.110.50:8200
,得到连接的响应:true。vault operator join http://10.20.110.50:8200
,得到连接的响应:true。此时,我可以在 UI 中以及使用 list-peers 命令看到 Vault 集群。 2 和 3 实例是追随者,实例 1 是领导者。 实例 2 和实例 3 不是选民!
然后,我尝试使用Vault操作符unseal手动解封其中一个追随者实例,其中密钥是实例一生成的密钥。这里我得到错误:
Error unsealing: Error making API request.
URL: PUT http://127.0.0.1:8200/v1/sys/unseal
Code: 500. Errors:
* Error making API request.
URL: PUT http://10.20.110.50:8200/v1/sys/storage/raft/bootstrap/answer
Code: 400. Errors:
* no expected answer for the server id provided
我在leader日志中看到的也是:
2024-01-16T10:18:21.563Z [ERROR] storage.raft: failed to appendEntries to: peer="{Nonvoter 0bff0048-5c43-c93c-b8a3-443ce621bf2c 10.20.110.150:8201}" error="dial tcp 10.20.110.150:8201: connect: connection refused"
这些日志会连续打印,因为领导者正在尝试连接到对等点。 我还测试了节点之间的连接:
Ping 正常,节点可达。 所有节点之间都可以连接到端口 8200。 仅当我连接到领导者时,到端口 8201 的连接才会成功。如果我尝试连接到机器 2 和 3 的端口 8201,我会收到连接被拒绝的消息。 奇怪的是,如果我尝试从容器内连接到端口 8201(即 127.0.0.1:8201),它会起作用。但我调查过,这似乎是正常的。在地窖解封之前我会被拒绝连接。这就是为什么 Machine 1 是我唯一可以连接的机器。 我没有启用防火墙。
我的配置:
vault.json:
{
"storage": {
"raft": {
"path": "/opt/vault/data/raft"
}
},
"listener": {
"tcp": {
"address": "0.0.0.0:8200",
"cluster_address": "0.0.0.0:8201",
"tls_disable": true
}
},
"telemetry": {
"unauthenticated_metrics_access": true
},
"cluster_name": "vc",
"api_addr": "http://{{HOST}}:8200",
"cluster_addr": "https://{{HOST}}:8201",
"ui": true,
"disable_mlock": true,
"log_level": "debug",
"default_lease_ttl": "168h",
"max_lease_ttl": "0h"
}
此配置会本地复制到每台服务器。 HOST 是托管 Vault 实例的计算机的 IP。因此,如果配置进入 10.20.110.50 服务器,则 HOST 值将为 10.20.110.50。
docker-compose.yaml:
version: '3.8'
services:
vault:
image: hashicorp/vault:1.15.2
container_name: vault
hostname: vault
entrypoint: [ "vault", "server", "-config=/vault/config/vault.json" ]
volumes:
- vault-data:/opt/vault/data/raft
- /home/root/vault/vault.json:/vault/config/vault.json:ro
environment:
- VAULT_ADDR=http://127.0.0.1:8200
networks:
- vault-network
ports:
- 8200:8200
- 8201:8201
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--spider", "--tries=1", "http://127.0.0.1:8200"]
interval: 5s
timeout: 5s
retries: 10
start_period: 5s
cap_add:
- IPC_LOCK
networks:
vault-network:
name: vault-network
volumes:
vault-data:
name: vault-data
driver: local
此 docker-compose 在每台服务器上本地运行,以在每台服务器中生成一个 Vault 实例。
您看到的错误是:
* no expected answer for the server id provided
还有:
2024-01-16T10:18:21.563Z [ERROR] storage.raft: failed to appendEntries to: peer="{Nonvoter 0bff0048-5c43-c93c-b8a3-443ce621bf2c 10.20.110.150:8201}" error="dial tcp 10.20.110.150:8201: connect: connection refused"
通常与 Raft 集群成员之间的网络连接问题相关。
我的直觉是这与 Docker 网络的复杂性有关。例如,在 docker-compose 中表示主机名与您在 Vault 配置中配置的主机名不同:
<snip>
container_name: vault
hostname: vault
<snip>
此外,作为一个简短的说明,您是正确的,使用像
8201
这样的实用程序测试集群端口 (nc
) 上的连接将无法工作,直到集群未密封并积极侦听该端口。
我会再看看您如何在容器级别配置主机名,并确保它与您在 Vault 配置中设置
api_addr
和 cluster_addr
值的方式一致。
最后,我将与您分享我创建的一个脚本,该脚本使用在单个计算机上运行的 Docker 创建一个 2 节点 Raft 集群(因此端口已稍微更新以避免端口冲突)。请注意,这确实使用企业 Vault,因此您可能需要稍微更改脚本以使其适用于非企业。
# Create a Docker network so our containers can chat
docker network create vault-net
# Spin up 2 Vault servers in Docker
# Note that each set of server params maps to a variable, e.g.
# vault-1 -> $server
# 8200 -> $api_port
# 8201 -> $cluster_port
for server_params in "vault-1,8200,8201" "vault-2,8210,8211"
do
IFS="," read server api_port cluster_port <<< "$server_params"
# Delete the server so we can recreate it
docker rm -f $server
# Delete the config directory so we can recreate it
rm -rf $server
# Create a directory where we will store our config and raft files
mkdir -p $server/config $server/file
# Write our license out
echo $VAULT_LICENSE > $server/config/license.hclic
# Create a config file
tee $server/config/vault.hcl << EOF
ui = true
license_path = "/vault/config/license.hclic"
listener "tcp" {
address = "0.0.0.0:$api_port"
tls_disable = "true"
}
storage "raft" {
path = "/vault/file"
node_id = "$server"
}
api_addr = "http://$server:$api_port"
cluster_addr = "https://$server:$cluster_port"
EOF
docker run \
--cap-add=IPC_LOCK \
-e VAULT_ADDR='http://0.0.0.0:$api_port' \
-p $api_port:$api_port \
--name $server \
--detach \
--network vault-net \
-v `pwd`/$server/config:/vault/config \
-v `pwd`/$server/file:/vault/file \
hashicorp/vault-enterprise server
done
sleep 15
# Initialize our leader node
export VAULT_ADDR=http://localhost:8200
vault operator init \
-key-shares=1 \
-key-threshold=1 \
-format=json > init.json
# Unseal our leader node
export ROOT_TOKEN=$(cat init.json | jq -r .root_token)
export UNSEAL_KEY=$(cat init.json | jq -r '.unseal_keys_b64[0]')
vault operator unseal $UNSEAL_KEY
vault login $ROOT_TOKEN
# Unseal our follower node
export VAULT_ADDR=http://localhost:8210
vault operator raft join http://vault-1:8200
vault operator unseal $UNSEAL_KEY
sleep 2
# Check our cluster membership
vault operator raft list-peers
请注意我的每个容器如何具有唯一的名称,并且我在配置
和api_addr
配置值时使用该名称。cluster_addr