개발환경 Vault Server를 Docker에서 사용할 때, 자동 Unseal 해보기!

 

개발환경 Vault Server를 Docker에서 사용할 때, 자동 Unseal 해보기!

로컬 개발환경에서 Vault Server를 Docker를 통해서 사용해보고 싶을 때, 고려할 내용입니다.

개발 서적을 공부하다 보면...

외부 OAuth 인증이나 API 연동을 위해 키를 사용하는 경우가 많은데,

당연히 Git에 저장되면 안 되므로 외부에 체계적으로 관리할 필요를 느끼게 됩니다.

키가 담긴 파일을 .gitignore로 처리하거나, 시스템 환경변수에 키값을 두는 방법도 있지만,

저장할 키들이 많아지면 관리가 점점 힘들어집니다.

그래서 체계적으로 시크릿을 관리할 목적으로 HashiCorp Vault 사용을 고려하게 되었고,

저는 스터디 프로젝트에서 사용하는 외부 서비스 키들을 저장하기 위해 Vault를 사용하기로 했습니다.

 

어디에 설치할지 고민하다가...

주로 쓰는 컴퓨터(16C 32T)에서 항상 실행 중인 Hyper-V 가상머신에 Rocky Linux를 사용 중이었기 때문에,

아래처럼 단순하게 yum으로 레포를 등록하고 vault 패키지를 설치해서 사용해왔습니다.

Hyper-V VM에서 Vault를 사용할 때는,

호스트 컴퓨터를 끄더라도 VM이 완전히 종료되는 것이 아니라 일시 정지(Saved State) 상태로 보존되기 때문에,

컴퓨터를 다시 켰을 때도 Vault의 Unseal 상태가 그대로 유지되어 사용에 불편함이 거의 없었습니다.

그렇게 쓰다가...

다른 컴퓨터에서 스터디 프로젝트를 사용할 일이 생겼습니다.

그 컴퓨터는 VM을 따로 쓰지 않고 Windows 환경에서 Docker만 사용하고 있어서, 결국 다음과 같이 설정해서 실행하게 되었습니다.

 

 

Docker로 실행

  • 디렉토리 미리 준비

    WSL 루트에 디렉토리 구성

    [/vault]
    ├── config
    │   └── config.hcl # 새로 만들 설정파일
    ├── data
    └── logs
  • 디렉토리 접근 소유자 설정

    $ sudo chown -R systemd-network:systemd-network /vault

    WSL 환경에서 Docker가 생성하는 소유자가 위와 같이 나왔는데, 다른 환경이라면 Docker가 만들어내는 계정명을 확인해서 적절히 바꿔서 실행하면 됩니다.

  • config.hcl 설정파일 생성

    $ tee /vault/config/config.hcl <<EOF
    ui = true
    disable_mlock = true

    storage "raft" {
    path    = "/vault/data"
    node_id = "node1"
    }

    listener "tcp" {
    address     = "0.0.0.0:8200"
    tls_disable = "true"
    }

    api_addr = "http://127.0.0.1:8200"
    cluster_addr = "https://127.0.0.1:8201"
    EOF
  • 이미지 가져오기

    $ docker pull hashicorp/vault:latest
  • 실행

    docker run --name vault_server -d --cap-add=IPC_LOCK -p 8200:8200 \
                --log-opt mode=non-blocking \
                -v /vault/config:/vault/config \
                -v /vault/data:/vault/data \
                -v /vault/logs:/vault/logs \
                hashicorp/vault:latest server
  • 로그 파일 쓰기 설정 (선택)

    $ sudo chmod g+w,o+w /vault/logs/
    $ docker logs -f vault_server >> /vault/logs/vault.log 2>&1 &

위처럼 설정해서 본격적으로 사용하려고 하니... 문제가 있었습니다.

해당 vault 컨테이너를 재시작할 때마다 항상 Unseal을 수동으로 해줘야 했습니다. 😂

Unseal Key 입력 요구 폼



✨자동 Unseal 되게 하기!

Vault 서버가 켜질 때 자동으로 Unseal이 되면 좋겠다는 생각에 시도한 방법이 아래 Repo의 내용입니다.

원리는 Docker의 엔트리포인트를 래핑(Wrapping)해서,

Vault 서버 실행이 완료된 것이 확인되면 서로 다른 Unseal 키를 순차적으로 입력해주는 방식입니다.

vault operator unseal "${UNSEAL_KEY_1}"
vault operator unseal "${UNSEAL_KEY_2}"
vault operator unseal "${UNSEAL_KEY_3}"

참고: Vault 초기화 시 기본값은 5개의 키 조각(Key Shares)과 임계값(Threshold) 3입니다. 즉, 5개의 Unseal 키가 생성되고 그 중 3개를 입력해야 Unseal됩니다. 초기화 시 -key-shares-key-threshold 옵션으로 조정할 수 있습니다.


entrypoint-wrapper.sh

#!/bin/sh
# 1. Vault Server는 백그라운드로 실행
nohup docker-entrypoint.sh server &

# 2. vault server 프로세스가 정상 실행될 때까지 1초 간격으로 최대 10번 체크
MAX_CHECKS=${VAULT_STARTUP_TIMEOUT:-10}
count=0
while ! ps aux | grep -q '[v]ault server'; do
 sleep 1
 count=$((count+1))
 if [ $count -ge ${MAX_CHECKS} ]; then
   echo "Error: vault server process not found after ${MAX_CHECKS} seconds"
   exit 1
 fi
done

# 3. vault server가 정상 실행된 것이 확인되면 Unseal 시도
. /unseal-keys.properties
vault operator unseal "${UNSEAL_KEY_1}"
vault operator unseal "${UNSEAL_KEY_2}"
vault operator unseal "${UNSEAL_KEY_3}"

tail -f /dev/null

 

 

unseal-keys.properties

UNSEAL_KEY_1='UNSEAL_KEY를_입력해주세요'
UNSEAL_KEY_2='UNSEAL_KEY를_입력해주세요'
UNSEAL_KEY_3='UNSEAL_KEY를_입력해주세요' 

💡 unseal-keys-example.properties 예제 파일을 unseal-keys.properties 이름으로 새로 복사한 후...

unseal-keys.properties 파일에 Vault 최초 초기화(vault operator init) 시 발급된 Unseal 키 중 3개를 입력해줍니다.

 

 

docker-compose.yml

services:
vault-server:
  image: hashicorp/vault:latest  # 공식 이미지 사용
  restart: always
  ports:
    - "8200:8200"
  cap_add:
    - IPC_LOCK
  logging:
    options:
      mode: non-blocking
  environment:
    - VAULT_ADDR=http://127.0.0.1:8200
    - VAULT_STARTUP_TIMEOUT=10
  volumes:
     # 1. 데이터 영구 보존을 위한 마운트
    - /vault/config:/vault/config
    - /vault/data:/vault/data
    - /vault/logs:/vault/logs
     # 2. 자동 Unseal을 담당할 스크립트들을 컨테이너 내부로 주입
    - type: bind
      source: ./init-scripts/unseal-keys.properties
      target: /unseal-keys.properties
      bind:
        create_host_path: false  # 파일이 없어도 호스트에 폴더를 자동 생성하지 않음
    - ./init-scripts/entrypoint-wrapper.sh:/entrypoint-wrapper.sh
   # 3. 컨테이너가 켜질 때 래퍼 스크립트(Unseal 로직 포함)를 실행하도록 지정
  entrypoint: ["/entrypoint-wrapper.sh"]

위 코드를 보면 environmentUNSEAL_KEY_1,2,3을 같이 전달하는 게 더 편하지 않을까 싶을 수 있지만,

그렇게 하면 docker-compose.yml을 Git으로 관리할 때 Unseal 키가 그대로 노출되는 문제가 생깁니다.

보안을 위해 별도 properties 파일로 분리하고 .gitignore에 추가해서 관리했습니다. 😅

 

  

실행

Docker Compose 명령이 낯설어서 sh 파일로 따로 만들었습니다.

rebuild-valut-server.sh

#!/bin/sh
set -e

# 필수 키 파일이 없으면 실행 차단
if [ ! -f "./init-scripts/unseal-keys.properties" ]; then
   echo "❌ [에러] 필수 파일이 없습니다: ./init-scripts/unseal-keys.properties"
   echo "자동 Unseal을 위해 키 파일을 먼저 생성해 주세요."
   exit 1
fi

echo "🚀 [1/3] 기존 Vault 컨테이너 및 네트워크 정지 중..."
docker compose down || true

echo "🐳 [2/3] 새롭게 이미지 빌드 및 백그라운드 가동 중..."
docker compose up -d --build

echo "🧹 [3/3] 이름 없는 찌꺼기(Dangling) 이미지 청소 중..."
docker image prune -f

echo "✅ 배포 및 자동 Unseal 환경 구축 완료!"



결과

Unseal 입력 폼 없이 바로 로그인 창이 나타나는 것을 확인할 수 있습니다.👍



마치며

예전에 했던 내용을 다시 한번 정리해보니 좋았습니다.

처음에는 Docker Compose 없이 쉘 스크립트로 컨테이너를 삭제하고, Dockerfile로 이미지를 직접 빌드해서 실행하는 방식을 썼었는데,

이번에 Docker Compose로 전환하면서 구성이 훨씬 단순해졌습니다. 😄👍

댓글

이 블로그의 인기 게시물

한글 2005 가정용

전자정부 프레임워크 v5.0 교육자료 IDE, 다른 드라이브에 설치하는 완벽 가이드

V3Pro 2002 Deluxe