Docker Compose로 Vault 실행
Docker Compose로 Vault 실행 (Run Vault on Docker)
Docker Compose로 완전히 보안된 3노드 HashiCorp Vault Enterprise 클러스터를 배포하는 방법을 다룹니다.
출처: 문서
본문
Docker Compose로 완전히 보안된 3노드 HashiCorp Vault Enterprise 클러스터를 배포해 보아요.
시작하기 전에
- Vault 봉인·봉인 해제(sealing and unsealing)에 대해 읽어보세요. Vault를 봉인 해제하지 않으면 배포를 테스트할 수 없어요.
- Docker CLI를 설치 하세요. 배포 과정에서 Docker 컨테이너와 상호작용할 수 있어야 해요.
- 권한을 확인하세요. 다음 권한이 있어야 해요.
- Docker 컨테이너 배포·업데이트.
- vault operator 명령 실행.
- 로드밸런서 구성·배포.
- 암호화 인증서가 있어야 해요. 클러스터에서 Vault 클라이언트와 다른 클라이언트 간의 통신을 보호하려면 다음 인증서가 필요합니다.
- vault.pem — 노드 간 통신을 보호하는 TLS 인증서
- vault.key — Vault가 사용하는 개인 암호화 키
- ca.pem — 노드 간 상호 TLS를 검증하는 데 사용하는 인증 기관(CA)
- 선택: Vault CLI를 설치 하세요. Vault CLI 호출을 하려면 Vault를 로컬에 설치해야 해요. Vault를 설치하고 싶지 않다면 명령줄에서 API 호출을 할 수도 있습니다.
1단계: 영속적인 Docker 볼륨 만들기
클러스터의 노드 간 Vault 구성 영속화를 위해 영속적인 Docker 볼륨을 만드는 것을 권장해요.
- Vault 구성 파일용 vault-config 볼륨을 만들어요.
$ docker volume create vault-config
- 볼륨 생성을 확인해요.
$ docker volume ls | grep 'config'
2단계: 로컬 디렉터리 구조 만들기
Vault 배포 파일과 관련 구성 파일을 보관·버전 관리하려면 로컬 디렉터리 vault-deploy를 만드는 것을 권장해요.
shared-deploy
vault-deploy
|-- local-config
|-- certs
|-- hcl
나머지 단계들은 다음을 저장한다고 가정해요.
- 공유 docker compose 파일은 shared-deploy 아래에.
- Vault용 Docker compose와 Dockerfile은 vault-deploy 아래에.
- Vault 라이선스 파일 vault-license.hclic은 vault-deploy/local-config 아래에.
- 암호화 인증서는 vault-deploy/local-config/certs 아래에.
- Vault 노드 구성 파일은 vault-deploy/local-config/hcl 아래에.
3단계: Vault용 Docker 환경 파일 만들기
유지보수를 단순화하려면 공통 Vault 환경 변수를 모아 설정하는 단일 파일 vault-deploy/vault.env를 만드는 것을 권장해요.
# vault-config의 마운트 경로
VAULT_CONFIG="/vault/config"
# 라이선스 파일 경로
VAULT_LICENSE_PATH="${VAULT_CONFIG}/vault-license.hclic"
# 노드별 구성 파일을 선택하는 커스텀 변수
NODE_IDX=""
4단계: 네트워크 환경 구성
클러스터 환경을 위한 Docker compose 파일 shared-deploy/cluster-env.yml을 만들어 영속 볼륨과 공유 네트워크를 정의해 구성 파일에서 이름으로 컨테이너·볼륨을 참조할 수 있게 해요.
networks:
<NETWORK_NAME>:
name: <NETWORK_NAME>
driver: bridge
ipam:
config:
- subnet: "<SUBNET>"
ip_range: "<IP_RANGE>"
gateway: "<GATEWAY>"
volumes:
vault-config:
external: true
name: vault-config
예:
networks:
vault_cluster:
name: vault-network
driver: bridge
ipam:
config:
- subnet: "192.168.42.128/25"
ip_range: "192.168.42.128/25"
gateway: "192.168.42.129"
volumes:
vault-config:
external: true
name: vault-config
5단계: Vault용 기본 compose 파일 만들기
Vault 노드의 기본 빌드 프로세스를 정의하는 Docker compose 파일 vault-deploy/vault.compose.yml을 만들어요.
name: <NAME>
services:
<SERVICE_NAME>:
volumes:
- vault-config:/vault/config
env_file:
- path: <ENV_FILE_PATH>
required: true
networks:
- <NETWORK_NAME>
build:
include:
- path: <CLUSTER_ENV_PATH>
예:
name: prod
services:
vault-base:
volumes:
- vault-config:/vault/config
env_file:
- path: ./vault.env
required: true
networks:
- vault_cluster
build:
include:
- path: ../shared-deploy/cluster-env.yml
6단계: Vault용 기본 Dockerfile 만들기
빌드 파일 vault-deploy/vault.Dockerfile을 만들어 키 파일을 영속 스토리지에 복사하고, Vault 데이터 파일용 디렉터리를 만들고, Vault에 대한 키 디렉터리 소유권·접근 권한을 설정하고, 환경 변수로 관련 노드별 구성 파일로 Vault를 시작하게 해요.
예:
# syntax=docker/dockerfile:1
FROM hashicorp/vault-enterprise:<VERSION>
# config와 license 파일 복사
COPY local-config/hcl/vault-*.hcl /vault/config/hcl/
COPY local-config/vault-license.hclic /vault/config/vault-license.hclic
# cert 파일 복사
COPY local-config/certs/vault.pem /vault/config/certs/vault.pem
COPY local-config/certs/vault.key /vault/config/certs/vault.key
COPY local-config/certs/ca.pem /vault/config/certs/ca.pem
RUN mkdir /vault/data/
RUN mkdir /vault/plugins/
RUN mkdir /vault/plugins/tmp
# Vault가 Vault 관련 파일을 소유하도록 함
RUN chown -R vault:vault /vault
# 인증서 파일의 소유권과 모드 설정
RUN chown root:vault /vault/config/certs/vault-key.pem
RUN chmod 0644 /vault/config/certs/vault-cert.pem
RUN chmod 0644 /vault/config/certs/vault-ca.pem
RUN chmod 0640 /vault/config/certs/vault-key.pem
CMD vault server -config=${VAULT_CONFIG}/hcl/vault-${NODE_IDX}.hcl
7단계: Vault compose 파일 업데이트
- vault.compose.yml 파일의 기본 빌드를 vault.Dockerfile을 사용하도록 업데이트해요.
name: prod
services:
vault-base:
volumes:
- vault-config:/vault/config
env_file:
- path: ./vault.env
required: true
networks:
- vault_cluster
build:
context: .
dockerfile: vault.Dockerfile
include:
- path: ../shared-deploy/cluster-env.yml
- 기본 빌드 정의를 확장해 클러스터의 각 노드에 대한 빌드 지침을 추가하고, 관련 VAULT_ADDR 환경 변수를 설정하고, NODE_IDX 환경 변수로 vault.Dockerfile의 동작을 커스터마이즈해요. 예:
name: prod
services:
vault-base:
...
vault-1:
extends:
service: vault-base
container_name: prod-vault-1
hostname: prod-vault-1
environment:
VAULT_ADDR: https://prod-vault-1:8200
NODE_IDX: "1"
vault-2:
extends:
service: vault-base
container_name: prod-vault-2
hostname: prod-vault-2
environment:
VAULT_ADDR: https://prod-vault-2:8200
NODE_IDX: "2"
vault-3:
extends:
service: vault-base
container_name: prod-vault-3
hostname: prod-vault-3
environment:
VAULT_ADDR: https://prod-vault-3:8200
NODE_IDX: "3"
include:
- path: ../shared-deploy/cluster-env.yml
8단계: Vault 구성 파일 만들기
다음 템플릿을 사용해 local-config/hcl/ 아래 각 Vault 노드에 대한 개별 구성 파일(vault-N.hcl)을 만들어요.
ui = true
disable_mlock = true
license_path = "<LICENSE_PATH>"
# --- 루프백이 아닌 인터페이스 구성 ---
api_addr = "https://<NODE_HOSTNAME>:8200"
cluster_addr = "https://<NODE_HOSTNAME>:8201"
cluster_name = "<CLUSTER_NAME>"
# --- 리스너 구성 ---
listener "tcp" {
address = "[::]:8200"
tls_disable = "false"
tls_cert_file = "<CERT_FILE>/vault.pem"
tls_key_file = "<KEY_FILE>/vault.key"
tls_client_ca_file = "<CA_FILE>/ca.pem"
}
# --- 플러그인 구성 ---
plugin_directory = "<PLUGIN_DIR>"
plugin_tmpdir = "<PLUGIN_TMPDIR>"
# --- 통합 스토리지 ---
storage "raft" {
path = "/vault/data"
node_id = "<NODE_ID>"
# 클러스터 노드 N+1용 재조인/리스너 구성
retry_join {
leader_api_addr = "https://<NEXT_NODE>:8200"
leader_client_cert_file = "<CERT_FILE>/vault.pem"
leader_client_key_file = "<KEY_FILE>/vault.key"
leader_ca_cert_file = "<CA_FILE>/ca.pem"
}
# 클러스터 노드 N+2용 재조인/리스너 구성
retry_join {
leader_api_addr = "https://<NEXT_NODE2>:8200"
leader_client_cert_file = "<CERT_FILE>/vault.pem"
leader_client_key_file = "<KEY_FILE>/vault.key"
leader_ca_cert_file = "<CA_FILE>/ca.pem"
}
}
클러스터 내 TLS 통신을 강제하려면 tls_disable을 false로 설정해야 해요. 또한 GUI, 라이선스 경로, 플러그인 디렉터리 정보를 설정할 것을 권장해요. 지금 커스텀·엔터프라이즈 플러그인을 등록할 계획이 없더라도, 나중에 플러그인 정보를 업데이트하려면 클러스터를 재시작해야 하므로 설정 시점에 플러그인 위치를 정하는 것을 권장합니다.
예:
ui = true
disable_mlock = true
license_path = "/vault/config/vault-license.hclic"
# --- 루프백이 아닌 인터페이스 구성 ---
api_addr = "https://prod-vault-1:8200"
cluster_addr = "https://prod-vault-1:8201"
cluster_name = "prod-vault"
# --- 플러그인 구성 ---
plugin_directory = "/vault/plugins/"
plugin_tmpdir = "/vault/plugins/tmp"
# --- 리스너 구성 ---
listener "tcp" {
address = "[::]:8200"
tls_disable = "false"
tls_cert_file = "/vault/config/certs/vault.pem"
tls_key_file = "/vault/config/certs/vault.key"
tls_client_ca_file = "/vault/config/certs/ca.pem"
}
# --- 통합 스토리지 ---
storage "raft" {
path = "/vault/data"
node_id = "prod-vault-1"
# 클러스터 노드 N+1용 재조인/리스너 구성
retry_join {
leader_api_addr = "https://prod-vault-2:8200"
leader_client_cert_file = "/vault/config/certs/vault.pem"
leader_client_key_file = "/vault/config/certs/vault.key"
leader_ca_cert_file = "/vault/config/certs/ca.pem"
}
# 클러스터 노드 N+2용 재조인/리스너 구성
retry_join {
leader_api_addr = "https://prod-vault-3:8200"
leader_client_cert_file = "/vault/config/certs/vault.pem"
leader_client_key_file = "/vault/config/certs/vault.key"
leader_ca_cert_file = "/vault/config/certs/ca.pem"
}
}
9단계: Vault 클러스터 시작
전체 클러스터를 띄우기 전에 단일 노드를 먼저 띄워 Vault를 초기화하고 루트 토큰을 생성하고 봉인 해제 키를 만들어야 해요. 그런 다음 나머지 노드를 띄우고 봉인 해제할 수 있습니다.
- Vault 서비스 중 하나를 시작해요. 예를 들어 vault-1을 시작하려면:
$ docker compose \
-f vault-deploy/vault.compose.yml up vault-1 --build --detach
- Docker CLI로 컨테이너에 대해
vault operator init을 실행하고 초기화 세부 정보를 안전한 위치의 vault_init.json에 저장해요. 예를 들어 prod-vault-1 컨테이너로 Vault를 초기화하려면:
$ docker exec -it prod-vault-1 \
vault operator init -format=json > /secure/location/vault-init.json
- 초기화 세부 정보로 생성된 봉인 해제 키를 사용해 컨테이너에 대해
vault operator unseal을 실행해요. 예를 들어 prod-vault-1 컨테이너의 Vault 인스턴스를 봉인 해제하려면:
$ for token in $(
cat /secure/location/vault-init.json | \
jq -r '.unseal_keys_b64[0:3] | join(" ")'
) ; do
docker exec -it prod-vault-1 vault operator unseal $token
done
- 컨테이너의 봉인 상태를 확인해요. 예를 들어 prod-vault-1 컨테이너의 Vault 상태를 확인하려면:
$ docker exec -it prod-vault-1 vault status
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 5
Threshold 3
Version 1.21.2+ent
Build Date 2026-01-06T16:58:57Z
Storage Type raft
Cluster Name prod-vault
Cluster ID 520515c4-887f-4ace-b267-8625cfd5fb43
Removed From Cluster false
HA Enabled true
HA Cluster https://prod-vault-1:8201
HA Mode active
Active Since 2026-02-04T02:51:25.540535695Z
Raft Committed Index 9945
Raft Applied Index 9945
Last WAL 3819
- 나머지 Vault 노드에 대해 docker compose와 봉인 해제 단계를 반복해요.
예를 들어 vault-2를 빌드하려면:
$ docker compose \
-f vault-deploy/vault.compose.yml up vault-2 --build --detach
vault-2를 봉인 해제하려면:
$ for token in $(
cat /secure/location/vault-init.json | \
jq -r '.unseal_keys_b64[0:3] | join(" ")'
) ; do
docker exec -it prod-vault-2 vault operator unseal $token
done
10단계: 배포 확인
- vault-1 컨테이너의 IP 주소를 가져와요.
$ docker inspect -f \
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' prod-vault-1
- 로컬 VAULT_ADDR 환경 변수를 vault-1 컨테이너 IP로 설정해요.
$ export VAULT_ADDR=https://<IP>:8200
- 로컬 VAULT_TOKEN 환경 변수를 초기화 출력 파일(vault-init.json)에서 반환된 root_token 값으로 설정해요.
$ export VAULT_TOKEN=hvs.000000000000000000000000
- Vault를 호출해 현재 노드를 나열해요.
CLI:
$ vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
prod-vault-1 prod-vault-1:8201 leader true
prod-vault-2 prod-vault-2:8201 follower true
prod-vault-3 prod-vault-3:8201 follower true
API:
$ curl \
--request GET \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/sys/storage/raft/configuration | jq .data.config.servers
[
{
"node_id": "prod-vault-1",
"address": "prod-vault-1:8201",
"leader": true,
"protocol_version": "3",
"voter": true
},
{
"node_id": "prod-vault-2",
"address": "prod-vault-2:8201",
"leader": false,
"protocol_version": "3",
"voter": true
},
{
"node_id": "prod-vault-3",
"address": "prod-vault-3:8201",
"leader": false,
"protocol_version": "3",
"voter": true
}
]