|
| 1 | +--- |
| 2 | +layout: docs |
| 3 | +page_title: Run Vault as zcx cluster |
| 4 | +description: >- |
| 5 | + Step-by-step guide to deploying a secure 3-node Vault cluster inside zCX using HAProxy and TLS. |
| 6 | +--- |
| 7 | + |
| 8 | +# Run Vault as zcx cluster |
| 9 | + |
| 10 | +This guide walks through deploying a fully secured 3-node HashiCorp Vault Enterprise cluster |
| 11 | +on IBM z/OS Container Extensions (zCX). The setup uses three independent zCX instances, |
| 12 | +each running Vault with its own unique IP address. A Layer-4 HAProxy load balancer sits |
| 13 | +in front to distribute traffic, and end-to-end TLS encryption is applied across all Vault nodes |
| 14 | +and the load balancer to ensure secure communication throughout the cluster. |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +## Step 1: Container Image Deployment |
| 19 | +Pull the official Vault Enterprise container image on all three zCX nodes. |
| 20 | + ```shell-session |
| 21 | + $ docker pull hashicorp/vault-enterprise:1.19.12-ent |
| 22 | + ``` |
| 23 | +Verify image integrity |
| 24 | + ```shell-session |
| 25 | + $ docker images | grep vault-enterprise |
| 26 | + ``` |
| 27 | + |
| 28 | +## Step 2: Persistent Volume Creation |
| 29 | +Create Docker volumes for configuration and data persistence on each node. |
| 30 | + |
| 31 | +Create volume for Vault data storage (Raft backend, audit logs) |
| 32 | + ```shell-session |
| 33 | + $ docker volume create vault-data |
| 34 | + ``` |
| 35 | +Create volume for Vault configuration files |
| 36 | + ```shell-session |
| 37 | + $ docker volume create vault-config |
| 38 | + ``` |
| 39 | +Verify volume creation |
| 40 | + ```shell-session |
| 41 | + $ docker volume ls | grep vault |
| 42 | + ``` |
| 43 | + |
| 44 | +Volume Mount Points: |
| 45 | +- vault-data: /vault/data (stores Raft snapshots, encrypted storage backend) |
| 46 | +- vault-config: /vault/config.d (HCL configuration files) |
| 47 | + |
| 48 | +## Step 4: Vault Configuration File Setup |
| 49 | +Create Vault configuration on each node with node-specific parameters: |
| 50 | + |
| 51 | +Access config volume |
| 52 | + ```shell-session |
| 53 | + $ docker run --rm -it -v vault-config:/vault alpine sh |
| 54 | + ``` |
| 55 | + |
| 56 | +Create configuration file for Leader and follower vault |
| 57 | +```hcl |
| 58 | + ui = true |
| 59 | +disable_mlock = true |
| 60 | +
|
| 61 | +# --- Listener Configuration --- |
| 62 | +listener "tcp" { |
| 63 | + address = "0.0.0.0:8200" |
| 64 | + tls_disable = 0 |
| 65 | + tls_cert_file = "/vault/vault.pem" |
| 66 | + tls_key_file = "/vault/vault.key" |
| 67 | + tls_client_ca_file = "/vault/ca.pem" |
| 68 | +} |
| 69 | +
|
| 70 | +# --- Raft Storage (Leader Node) --- |
| 71 | +storage "raft" { |
| 72 | + path = "/vault/data" |
| 73 | + node_id = "<LEADER_NODE_ID>" # e.g., "node1" |
| 74 | +
|
| 75 | + # No retry_join block needed for leader |
| 76 | +} |
| 77 | +
|
| 78 | +# --- Cluster Networking --- |
| 79 | +api_addr = "https://<LEADER_IP>:8200" |
| 80 | +cluster_addr = "https://<LEADER_IP>:8201" |
| 81 | +``` |
| 82 | +Exposes Vault on 8200 for API and UI traffic. |
| 83 | + |
| 84 | +- tls_disable = 0 → TLS is enabled (mandatory for secure cluster communication). |
| 85 | + |
| 86 | +### Certificates: |
| 87 | + |
| 88 | +- vault.pem → TLS certificate |
| 89 | + |
| 90 | +- vault.key → Private key |
| 91 | + |
| 92 | +- ca.pem → CA used to validate mutual TLS between nodes |
| 93 | + |
| 94 | +This ensures encrypted traffic between Vault clients, HAProxy, and other nodes. |
| 95 | + |
| 96 | +Similarly create configuration for Follower Vault Nodes |
| 97 | + |
| 98 | +```hcl |
| 99 | +ui = true |
| 100 | +disable_mlock = true |
| 101 | +
|
| 102 | +# --- Listener Configuration --- |
| 103 | +listener "tcp" { |
| 104 | + address = "0.0.0.0:8200" |
| 105 | + tls_disable = 0 |
| 106 | + tls_cert_file = "/vault/vault.pem" |
| 107 | + tls_key_file = "/vault/vault.key" |
| 108 | + tls_client_ca_file = "/vault/ca.pem" |
| 109 | +} |
| 110 | +
|
| 111 | +# --- Raft Storage (Follower Node) --- |
| 112 | +storage "raft" { |
| 113 | + path = "/vault/data" |
| 114 | + node_id = "<FOLLOWER_NODE_ID>" # e.g., "node2" or "node3" |
| 115 | +
|
| 116 | + retry_join { |
| 117 | + leader_api_addr = "https://<LEADER_IP>:8200" |
| 118 | + leader_ca_cert_file = "/vault/ca.pem" |
| 119 | + } |
| 120 | +} |
| 121 | +
|
| 122 | +# --- Cluster Networking --- |
| 123 | +api_addr = "https://<FOLLOWER_IP>:8200" |
| 124 | +cluster_addr = "https://<FOLLOWER_IP>:8201" |
| 125 | +``` |
| 126 | + |
| 127 | +## Step 5: Vault Container Deployment |
| 128 | +Deploy Vault container on each zCX node. |
| 129 | + |
| 130 | +Set Vault Enterprise license (export before running) |
| 131 | +```plaintext |
| 132 | +export VAULT_LICENSE="02MV4UU43BK5HGYYTOJZWFQMTMNNEWU33JJVVGC..." # Replace with actual license |
| 133 | +``` |
| 134 | + |
| 135 | +Deploying the Vault Enterprise Container on zCX |
| 136 | +```shell-session |
| 137 | +$ docker run -d \ |
| 138 | + --name vault-zcx \ |
| 139 | + --cap-add IPC_LOCK \ |
| 140 | + -p 8200:8200 \ |
| 141 | + -p 8201:8201 \ |
| 142 | + -v vault-config:/vault/config.d \ |
| 143 | + -v vault-data:/vault/data \ |
| 144 | + -e VAULT_LICENSE="$VAULT_LICENSE" \ |
| 145 | + hashicorp/vault-enterprise:1.19.12-ent \ |
| 146 | + server -config=/vault/config.d/ |
| 147 | +``` |
| 148 | +Verify container status |
| 149 | +```shell-session |
| 150 | +docker ps | grep vault-zcx |
| 151 | +docker logs vault-zcx |
| 152 | +``` |
| 153 | + |
| 154 | +## Cluster Initialization and Unsealing |
| 155 | +Initialize Vault Cluster (Run on ONE node only) |
| 156 | + |
| 157 | +Initialize the Leader Vault |
| 158 | +```shell-session |
| 159 | +docker exec -it vault-zcx vault operator init \ |
| 160 | + -format=json > /secure/location/vault-init.json |
| 161 | +``` |
| 162 | +Extract unseal keys and root token |
| 163 | +```shell-session |
| 164 | +cat /secure/location/vault-init.json |
| 165 | +``` |
| 166 | +Unseal Vault on All Nodes |
| 167 | + |
| 168 | +Unseal with 3 different keys (repeat on each node) |
| 169 | +```shell-session |
| 170 | +docker exec -it vault-zcx vault operator unseal <UNSEAL_KEY_1> |
| 171 | +docker exec -it vault-zcx vault operator unseal <UNSEAL_KEY_2> |
| 172 | +docker exec -it vault-zcx vault operator unseal <UNSEAL_KEY_3> |
| 173 | +``` |
| 174 | +Remember to unseal with same key that you got from leader |
| 175 | +Check seal status |
| 176 | +```shell-session |
| 177 | + vault status |
| 178 | +``` |
| 179 | + |
| 180 | +## HAProxy Load Balancer |
| 181 | + |
| 182 | +In a Vault cluster, the load balancer plays a critical role in directing traffic to the active leader |
| 183 | +while maintaining full security. In this example, we are using an HAProxy Layer-4 (TCP) load balancer, |
| 184 | +but you can use other load balancers as well depending on your environment and requirements. |
| 185 | + |
| 186 | +- End-to-end TLS encryption: L4 simply forwards TCP connections, so Vault’s TLS traffic remains fully encrypted without terminating SSL at the load balancer. |
| 187 | + |
| 188 | +- Simpler configuration: No need to manage certificates on HAProxy or re-encrypt traffic. |
| 189 | + |
| 190 | +- Leader-aware routing: Vault handles leader redirects natively, so the load balancer doesn’t need to interpret HTTP or Vault protocols. |
| 191 | + |
| 192 | +- Better performance: Forwarding raw TCP reduces overhead, giving lower latency and higher throughput. |
| 193 | + |
| 194 | +### Configuration |
| 195 | +```plaintext |
| 196 | +global |
| 197 | + maxconn 4096 |
| 198 | + log stdout format raw local0 |
| 199 | +
|
| 200 | +defaults |
| 201 | + mode tcp # TCP mode for Layer-4 forwarding |
| 202 | + timeout connect 5s |
| 203 | + timeout client 1m |
| 204 | + timeout server 1m |
| 205 | + log global |
| 206 | +
|
| 207 | +# --- Stats Page (HTTPS) --- |
| 208 | +frontend stats |
| 209 | + bind *:8404 ssl crt /usr/local/etc/haproxy/haproxy.pem |
| 210 | + mode http |
| 211 | + stats enable |
| 212 | + stats uri /stats |
| 213 | + stats refresh 10s |
| 214 | + stats admin if TRUE |
| 215 | +
|
| 216 | +# --- Vault API Frontend (TLS Passthrough) --- |
| 217 | +frontend vault_api |
| 218 | + bind *:8200 # Listen on Vault API port |
| 219 | + mode tcp # Layer-4 forwarding to preserve TLS |
| 220 | + default_backend vault_nodes |
| 221 | +
|
| 222 | +# --- Vault Nodes Backend --- |
| 223 | +backend vault_nodes |
| 224 | + mode tcp |
| 225 | + balance roundrobin # Simple load distribution among nodes |
| 226 | + option tcp-check # Health check at TCP level |
| 227 | + server node1 <NODE1_IP>:8200 check |
| 228 | + server node2 <NODE2_IP>:8200 check |
| 229 | + server node3 <NODE3_IP>:8200 check |
| 230 | +``` |
| 231 | +Copy config to volume |
| 232 | +```shell-session |
| 233 | +docker cp haproxy.cfg haproxy-tmp:/usr/local/etc/haproxy/haproxy.cfg |
| 234 | +``` |
| 235 | +Deploy haproxy load-balancer |
| 236 | +```shell-session |
| 237 | +docker run -d \ |
| 238 | + --name vault-lb \ |
| 239 | + -p 8300:8200 \ |
| 240 | + -p 8404:8404 \ |
| 241 | + -v haproxy-config:/usr/local/etc/haproxy \ |
| 242 | + ibmz-hc-registry.ngrok.dev/haproxy:3.2 |
| 243 | +``` |
| 244 | + |
| 245 | +## Verify Cluster |
| 246 | +List all the leader and follower nodes |
| 247 | +```shell-session |
| 248 | +vault operator raft list-peers |
| 249 | +``` |
| 250 | + |
| 251 | +Test container health on browser using haproxy endpoint |
| 252 | +```plaintext |
| 253 | +https://<LOAD_BALANCER_IP>:<PORT>/stats |
| 254 | +``` |
| 255 | + |
| 256 | + |
| 257 | +Test Secure Vault Connection and View Raft Configuration |
| 258 | +```shell-session |
| 259 | +curl \ |
| 260 | + --cacert <CA_CERT_FILE> \ |
| 261 | + --header "X-Vault-Token: <VAULT_TOKEN>" \ |
| 262 | + https://<LOAD_BALANCER_IP>:<PORT>/v1/sys/storage/raft/configuration \ |
| 263 | + | jq . |
| 264 | +``` |
0 commit comments