다음 이미지는 쿠버네티스(Kubernetes) 환경에서 해시코프 볼트(HashiCorp Vault)를 사용하여 애플리케이션의 비밀 정보(Secrets)를 안전하게 관리하는 방법을 설명하는 아키텍처 다이어그램입니다.
전체적인 흐름은 쿠버네티스의 '서비스 어카운트(Service Account)'를 애플리케이션의 신원(Identity)으로 사용하여 볼트(Vault)에 인증하고, 미리 정의된 정책(Policy)에 따라 필요한 비밀 정보에만 접근 권한을 부여하는 것입니다.

위 구성의 특징은 다음과 같습니다.
- 비밀 정보의 중앙화 및 분리: 비밀 정보를 코드나 설정 파일에 하드코딩하지 않고, 외부의 안전한 Vault에 저장하여 관리합니다.
- 동적 인증 및 권한 부여: 애플리케이션의 신원(Service Account)을 기반으로 동적으로 인증하고, 필요한 최소한의 권한(Least Privilege)만 부여하여 보안을 강화합니다.
- 자동화: 이 모든 과정이 자동화되어, 애플리케이션이 배포될 때 사람의 개입 없이 안전하게 비밀 정보를 가져올 수 있습니다.
Helm을 활용한 Vault 설치하기
helm 설치하기
curl -fsSL -o get_helm.sh <https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3>
chmod 700 get_helm.sh
./get_helm.sh
쿠버네티스에서 다음과 같이 vault를 구성하고 활성화하면 쿠버네티스가 etcd에 저장할 때 암호화를 수행합니다.
helm repo add hashicorp <https://helm.releases.hashicorp.com>
helm repo update
kubectl create namespace vault
helm show values hashicorp/vault > vault-values.yaml
# helm uninstall vault -n vault
values.yaml 파일에서 다음 라인을 찾아 수정합니다.
- 775라인에서 dataStorage.enabled를 false로 설정
- 1038라인에서 ui.enabled를 true로 설정
helm install vault hashicorp/vault -n vault -f vault-values.yaml
vault 서비스가 구성되면 완전한 ready를 위해 다음과 같이 초기화를 수행해야 합니다. Vault를 설치하고 초기화(init)하면 "root token"과 "unseal key(Seal 토큰)"이 생성됩니다. 이 정보는 별도로 백업해두는 것이 좋습니다.
$ kubectl exec vault-0 -n vault -- vault operator init
Unseal Key 1: oxWekf8DE5wiHjBJDW7LHBph02DkhVkR3jXa7jC20/SR
Unseal Key 2: jQ1yM5lbJU3VrW+vGS/pGACGrrf+IEumnEObsDXzTspS
Unseal Key 3: /Iw7tLcBDt4YS2wd01ufaK2CJQZSfIV4fAkE/VsNqcui
Unseal Key 4: ELNb/V4cLSrrZqhhbJQCscG9785MLfOuKdeY3ptbVKLb
Unseal Key 5: 6Xjpw5tnJ1DSdJAxGXXBOk1XTTEoi0E+crFHQ7/lsXdj
Initial Root Token: hvs.j6JJVakSs3UhSdecubMykaKd
Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.
Vault does not store the generated root key. Without at least 3 keys to
reconstruct the root key, Vault will remain permanently sealed!
It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
각각의 역할은 다음과 같습니다.
Root Token (루트 토큰)
- Vault의 최고 권한 토큰입니다.
- Vault에 최초로 로그인하거나, 모든 설정 및 관리 작업(정책 생성, 사용자 관리, 시크릿 관리 등)에 사용됩니다3568.
- root token은 Vault에서 유일하게 만료되지 않는 토큰이며, 모든 권한을 가지고 있습니다.
- 일반적으로 보안을 위해 root token으로만 사용하지 않고, root token으로 관리자 권한을 가진 사용자 토큰을 추가로 만든 뒤 root token은 폐기(revoke)하는 것이 권장됩니다6.
Unseal Key (Seal/Unseal 토큰)
- Vault 서버는 기본적으로 "sealed(봉인)" 상태로 시작합니다.
- 이 상태에서는 Vault에 저장된 데이터(시크릿 등)에 접근할 수 없습니다78.
- unseal key는 Vault의 봉인을 해제(unseal)할 때 사용합니다.
- Vault는 보안을 위해 Shamir's Secret Sharing 알고리즘을 사용해 unseal key를 여러 개의 조각(share)으로 나누어 생성합니다.
- 봉인 해제 후에야 Vault가 정상적으로 동작하며, 시크릿 접근 등 모든 기능이 활성화됩니다.
1~3번실을 해제합니다.
kubectl exec vault-0 -n vault -- vault operator unseal oxWekf8DE5wiHjBJDW7LHBph02DkhVkR3jXa7jC20/SR
kubectl exec vault-0 -n vault -- vault operator unseal jQ1yM5lbJU3VrW+vGS/pGACGrrf+IEumnEObsDXzTspS
kubectl exec vault-0 -n vault -- vault operator unseal /Iw7tLcBDt4YS2wd01ufaK2CJQZSfIV4fAkE/VsNqcui
💡Vault에서 봉인 해제(Seal/Unseal) 키가 5개 생성됐는데, 왜 3개만 Unseal을 하는가?
이것은 Shamir's Secret Sharing(샤미르의 비밀 분할) 알고리즘의 특성 때문입니다.
원리 설명
- Vault는 보안을 위해 마스터 키를 여러 조각(share)으로 분할해서 저장합니다.
- 예를 들어, "총 5개"의 봉인 해제 키를 만들고, "3개"만 있으면 원래의 마스터 키를 복원할 수 있도록 설정할 수 있습니다.
- 이때 5개 중 어느 3개를 조합해도 마스터 키를 복원할 수 있습니다.
- 즉, 3개만 모이면 봉인 해제가 가능하고, 3개 미만이면 절대 복원할 수 없습니다1235.
왜 이렇게 설계했나?
- 보안성: 한 명이 모든 키를 갖는 것보다 여러 명이 나눠 보관하면, 내부자 위험이나 유출 위험을 줄일 수 있습니다.
- 유연성: 5명 중 3명만 모여도 봉인을 해제할 수 있으니, 일부 키 분실이나 담당자가 부재해도 운영에 문제가 없습니다.
결국 키는 3개만 있어도 데이터를 복원할 수 있으므로 init에 성공하게 됩니다. 그래서 unseal은 3개만 해도 되며, 5개 모두해도 상관 없습니다.
vault가 잘 초기화되었는지 확인합니다.
$ kubectl exec -it vault-0 -n vault -- vault status
Key Value
--- -----
Seal Type shamir # Vault의 봉인/해제 방식이 Shamir's Secret Sharing임을 의미합니다. 여러 개의 unseal key 조각을 합쳐야 봉인을 해제할 수 있습니다.
Initialized true # Vault가 초기화(Init)되어 사용 가능한 상태임을 나타냅니다.
Sealed false # 현재 Vault가 봉인(sealed) 상태가 아니며, 정상적으로 동작 중임을 의미합니다. true일 경우 봉인 해제가 필요합니다
Total Shares 5
Threshold 3
Version 1.19.0
Build Date 2025-03-04T12:36:40Z
Storage Type file
Cluster Name vault-cluster-651eb58f
Cluster ID 9c7f0bac-cc86-c8aa-3d8e-094d2822ba5b
HA Enabled false
Root 토큰을 사용해서 vault에 로그인합니다.
kubectl exec --stdin=true --tty=true vault-0 -n vault -- /bin/sh
vault login hvs.j6JJVakSs3UhSdecubMykaKd
경로에서 kv-v2 secrets 엔진의 인스턴스를 활성화합니다.
/ $ vault secrets enable -path=secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/
💡kv 버전 정리
- kv-v2는 버전 관리와 메타데이터, 삭제/복구 등 고급 기능이 추가된 버전입니다.
- kv-v1은 단순 저장소(버전 관리 없음)입니다.
- kv-v3는 현재 존재하지 않습니다.
실제로 마운트할 때 kv 또는 kv-v2로 지정해서 사용할 수 있습니다.
사용자 이름과 비밀번호를 사용하여 secret/webapp/config 경로에 비밀을 생성합니다.
/ $ vault kv put secret/webapp/config username="static-user" password="static-password"
====== Secret Path ======
secret/data/webapp/config
======= Metadata =======
Key Value
--- -----
created_time 2025-06-03T11:40:51.683275107Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
secret/webapp/config경로에 secret이 잘 저장되었는지 확인합니다.
/ $ vault kv get secret/webapp/config
====== Secret Path ======
secret/data/webapp/config
======= Metadata =======
Key Value
--- -----
created_time 2025-06-03T11:40:51.683275107Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
====== Data ======
Key Value
--- -----
password static-password
username static-user
Kubernetes 인증 구성
초기 루트 토큰은 모든 경로에서 모든 작업을 수행할 수 있는 권한이 있는 사용자입니다. 웹 애플리케이션은 단일 경로에 정의된 비밀 정보를 읽을 수 있는 권한만 필요합니다. 이 애플리케이션은 인증을 받고 제한된 액세스 권한을 가진 토큰을 부여받아야 합니다.
루트 토큰은 인증 방법 및 정책의 초기 설정에만 사용하는 것이 좋습니다. 이후에는 해지해야 합니다. 이 튜토리얼에서는 루트 토큰을 해지하는 방법을 보여주지 않습니다.
Vault는 클라이언트가 Kubernetes 서비스 계정 토큰으로 인증할 수 있도록 하는 Kubernetes 인증 방법을 제공합니다.
Kubernetes 인증 방법을 활성화합니다.
vault auth enable kubernetes
Vault는 Kubernetes 클러스터 내의 모든 클라이언트에서 이 서비스 토큰을 허용합니다. 인증 과정에서 Vault는 구성된 Kubernetes 엔드포인트에 쿼리를 보내 서비스 계정 토큰이 유효한지 확인합니다.
Kubernetes API의 위치를 사용하도록 Kubernetes 인증 방법을 구성합니다. 최신 Kubernetes 버전과의 호환성을 극대화하려면 Vault v1.9.3 이상을 사용해야 합니다.
vault write auth/kubernetes/config \\
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
이 명령의 성공적인 출력은 다음 예와 유사합니다.
Success! Data written to: auth/kubernetes/config
환경 변수 KUBERNETES_PORT_443_TCP_ADDR이 정의되어 있으며, 쿠버네티스 호스트의 내부 네트워크 주소를 참조합니다.
클라이언트가 secret/webapp/config에 정의된 비밀 데이터에 액세스하려면 경로 secret/data/webapp/config에 대한 읽기 권한이 부여되어야 합니다. 이는 정책의 예입니다. 정책은 일련의 기능을 정의합니다.
경로 secret/data/webapp/config에 있는 비밀에 대한 읽기 기능을 활성화하는 webapp이라는 정책을 작성합니다.
vault policy write webapp - <<EOF
path "secret/data/webapp/config" {
capabilities = ["read"]
}
EOF
이 명령의 성공적인 출력은 다음 예와 유사합니다.
Success! Uploaded policy: webapp
웹 애플리케이션 정책을 사용하는 인증 메서드 역할을 정의합니다. 역할은 정책과 환경 매개변수를 결합하여 웹 애플리케이션에 대한 로그인을 생성합니다.
쿠버네티스 서비스 계정 이름과 webapp 정책을 연결하는 webapp이라는 쿠버네티스 인증 역할을 생성합니다.
vault write auth/kubernetes/role/webapp \\
bound_service_account_names=vault \\
bound_service_account_namespaces=default \\
policies=webapp \\
ttl=24h
이 명령의 성공적인 출력은 다음 예와 유사합니다.
Success! Data written to: auth/kubernetes/role/webapp
이 역할은 Kubernetes 서비스 계정 vault와 네임스페이스 default를 Vault 정책 webapp과 연결합니다. 인증 후 반환된 토큰은 24시간 동안 유효합니다.
vault-0 포드를 종료합니다.
exit
웹 애플리케이션 실행 및 비밀 정보 사용
웹 애플리케이션을 생성하고 DockerHub에 게시했으며, 기존 클러스터에서 애플리케이션을 실행할 쿠버네티스 배포를 생성했습니다. 예시 웹 애플리케이션은 HTTP 요청을 수신하는 단일 기능을 수행합니다. 요청 시 쿠버네티스 서비스 토큰을 읽고 Vault에 로그인한 후 비밀번호를 요청합니다.
선호하는 텍스트 편집기를 사용하여 deployment-01-webapp.yml의 내용을 검토하고 배포합니다.
cat << EOF > deployment-01-webapp.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
labels:
app: webapp
spec:
replicas: 1
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
serviceAccountName: vault
containers:
- name: app
image: hashieducation/simple-vault-client:latest
imagePullPolicy: Always
env:
- name: VAULT_ADDR
value: 'http://vault.vault:8200' # vault 서버 위치
- name: JWT_PATH
# kubernetes와 vault가 인증을 통합한 토큰으로 vault에도 사용 가능
value: '/var/run/secrets/kubernetes.io/serviceaccount/token'
- name: SERVICE_PORT
value: '8080' # 컨테이너에서 오픈할 포트
EOF
kubectl apply -f deployment-01-webapp.yml
kubectl create sa vault
웹 애플리케이션 배포는 환경 변수 목록을 정의합니다.
- JWT_PATH는 Kubernetes에서 발급한 JSON 웹 토큰(JWT)의 경로를 설정합니다. 이 토큰은 웹 애플리케이션에서 Vault에 인증하는 데 사용됩니다.
- VAULT_ADDR는 Vault 서비스의 주소를 설정합니다. Helm 차트는 vault라는 Kubernetes 서비스를 정의했으며, 이 서비스는 요청을 엔드포인트(즉, vault-0, vault-1, vault-2라는 이름의 파드)로 전달합니다.
- SERVICE_PORT는 서비스가 수신 HTTP 요청을 수신하는 포트를 설정합니다.
deployment-01-webapp.yml 파일을 적용하여 Kubernetes에 웹앱을 배포합니다.
기본 네임스페이스 내의 모든 포드를 가져옵니다.
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
webapp-59c4db954f-kfzp5 0/1 ContainerCreating 0 7s
webapp-59c4db954f-kfzp5 1/1 Running 0 22s
웹앱 포드는 여기에 webapp 접두사가 붙은 포드로 표시됩니다.
서비스를 배포하려면 Docker Hub에서 웹 애플리케이션 컨테이너를 가져와야 합니다. 그러면 ContainerCreating 상태가 표시됩니다. 포드는 준비되지 않았습니다(0/1).
웹앱 포드가 실행 중이고 준비될 때까지(1/1) 기다리세요.
웹앱 포드는 포트 8080에서 수신 대기하는 HTTP 서비스를 실행합니다.
kubectl port-forward \\
$(kubectl get pod -l app=webapp -o jsonpath="{.items[0].metadata.name}") \\
8080:8080 &
다른 터미널에서 http://localhost:8080에 대한 모든 요청을 포트 8080의 웹앱 포드로 포트 포워딩합니다.
curl http://localhost:8080
다음과 같이 응답이 나타납니다.
password:static-password username:static-user
💡webapp이 정상적으로 응답이 오지 않으면 다음과 같은 명령을 오류를 확인합니다.
kubectl logs deployment/webapp
'클라우드 > 쿠버네티스' 카테고리의 다른 글
| 놓치면 반드시 후회! 쿠버네티스 환경에서 꼭 수집해야 할 로그 총정리 (3) | 2025.07.31 |
|---|---|
| 쿠버네티스 Ingress Controller 대표 주자 3대장: NGINX, Traefik, HAProxy 전격 비교 (5) | 2025.07.31 |
| 🤫 쿠버네티스 시크릿 관리의 정석: Vault와 KMS로 etcd 데이터 안전하게 지키기 (1) | 2025.07.30 |
| 쿠버네티스 프로브(Probe) 3대장: Liveness, Readiness, Startup은 왜 필요할까? (5) | 2025.07.30 |
| 쿠버네티스, Docker Hub 인증 Secret 설정하기 (2) | 2025.07.26 |