본문 바로가기
K8S

Kubernetes에서 Vault로 비밀 다루기: Seal, Sync, 주입

by Rainbound-IT 2025. 11. 27.
반응형

들어가며

클라우드 네이티브 환경에서 비밀 정보를 안전하게 다루는 것은 DevOps와 플랫폼 팀의 핵심 과제다. Kubernetes에서 HashiCorp Vault를 연동해 Secret을 주입하는 방식은 크게 3가지 흐름으로 나뉜다. 각 방식의 동작 주체, Secret 저장 위치, 갱신 전략이 완전히 다르다. 또한, Vault의 Seal/Unseal 개념은 Vault 서버의 보안 상태를 제어하는 관문 역할을 한다.


🔒 Vault Seal / 🔓 Unseal 이란?

Vault는 모든 Secret 데이터를 마스터 키(Master Key) 로 암호화하여 storage(기본적으로는 etcd가 아닌 Vault 자체 저장소)에 저장한다. 하지만 마스터 키가 평문으로 디스크에 저장되면 탈취 위험이 있기 때문에, Vault는 Sealed(봉인된) 상태로 항상 부팅한다.

  • Sealed 상태
    • 마스터 키가 메모리에 없음
    • Secret 읽기/쓰기, 복호화 불가
    • Health/Status API 정도만 제한적으로 가능

Seal은 데이터 파괴가 아닌 잠금 상태를 의미한다. Vault가 Unseal되기 전까지는 정상적인 시크릿 사용이 불가능하다.

  • Unseal 과정복원된 마스터 키는 메모리에 로딩되어 Vault가 Unsealed(정상 동작 가능) 상태로 전환된다.
  • 마스터 키를 여러 개의 키 조각(Shards)으로 나눠 저장하고, 최소 N개의 키 조각(예: 3/5)을 입력해야 마스터 키를 복원하는 Shamir Secret Sharing 메커니즘을 사용한다.

클라우드 환경(AWS, Azure, GCP 등)에서는 사람이 키를 직접 입력하는 대신 KMS/HSM이 마스터 키 복원 및 로딩을 대신 해주는 Auto-Unseal을 자주 사용한다.


Kubernetes에서 Secret을 주입하는 3가지 방식

방식 동작 주체 사이드카 Secret 형태 갱신(rotate/renew)

방식  동작  사이드카  Secret 형태 갱신(rotate/renew)
Vault Sidecar Agent Injector Pod 안의 Vault Agent Container ✅ 있음 파일 ✅ 실시간 자동 갱신
Vault CSI Provider Node의 Vault CSI Driver ❌ 없음 Ephemeral Volume 파일 옵션 따라 가능
Vault Secrets Operator (VSO) Cluster 내부의 Controller ❌ 없음 Kubernetes Secret Polling/TTL 기반 자동 Sync

1. Vault Sidecar Agent Injector

가장 오래된 방식으로, Pod의 Vault Agent 사이드카 컨테이너가 Vault 서버와 직접 통신하여 Secret을 가져온 뒤 파일로 렌더링해준다.

동작 흐름

  1. Mutating Webhook이 Deployment의 Pod spec에 vault.hashicorp.com/agent-inject: true 등의 annotations를 자동 주입
  2. Pod 안의 Vault Agent 컨테이너가 Kubernetes Auth(ServiceAccount JWT)로 Vault에 인증
  3. 인증 성공 후 Vault 서버에서 Secret을 직접 Pull
  4. 필요한 key/value를 Template으로 렌더링 → /vault/secrets/... 파일로 생성
  5. TTL 만료/rotation 필요 시 파일 내용을 실시간 재갱신 (앱 재시작 불필요)

장점

  • 파일 기반 실시간 rotate 최적
  • 앱 재시작 없이 secret 변경 반영

단점

  • Pod당 추가 컨테이너 필요 → 리소스 비용 증가
  • Helm values에서 직접 사용하는 구조 아님

2. Vault CSI Provider

CSI 방식은 Node 레벨의 Vault CSI Driver가 Pod에 ephemeral volume을 붙여주며, Vault와 직접 통신하여 Secret을 volume 내부의 파일로 생성해준다. 이 volume은 실제 디스크가 아니라, tmpfs/FUSE 기반의 Ephemeral 파일 시스템 볼륨인 경우가 대부분이다.

동작 흐름

  1. Pod spec에 CSI Volume 정의
  2. Node의 Vault CSI Driver가 Kubernetes Auth로 Vault에 인증
  3. Secret 읽어와 ephemeral volume 파일로 생성
  4. Pod에 해당 볼륨을 read-only로 마운트해 Secret 제공

장점

  • 사이드카 없이 가벼움
  • 구성 심플

단점

  • Template 렌더링 기능 제한적
  • 실시간 rotation/live update는 옵션 따라 제한적

3. Vault Secrets Operator (VSO)

Cluster 내부에서 실행되는 컨트롤러(Operator)가 Vault의 Secret을 읽어서 Kubernetes Secret 오브젝트로 동기화해 주는 방식이다. 이때 Secret을 실제로 Pod와 묶어 attach하지 않는다. Kubernetes Secret은 여러 워크로드가 ‘참조’해서 읽는 독립 오브젝트이기 때문이다.

동작 흐름

  1. DevOps가 VaultStaticSecret 등 VSO CRD를 생성하여 Vault path → 생성할 K8s Secret 이름을 선언
  2. VSO controller가 Vault에 인증하여 Secret을 Read
  3. 대응되는 K8s Secret 리소스를 생성 또는 업데이트(sync)
  4. Helm values.yaml이나 Deployment YAML에서 Secret 이름만 secretRef, envFrom으로 선언하여 참조
  5. Vault 값 변경 시 VSO가 polling(i.e. refreshAfter)이나 TTL 기준으로 K8s Secret 갱신
  6. 변경 감지 후 Deployment rollout 또는 앱 재로드로 반영

장점

  • Pod 구성 부하 없음 → 리소스 비용 최소
  • 개발자/배포 YAML 단순
  • GitOps에 매우 적합 (ArgoCD sync 트리거 가능)

단점

  • K8s Secret(etcd)에 저장되므로 클러스터의 Encryption at Rest 필요
  • 보안 정책에 따라 etcd 저장을 아예 금지하는 조직에서는 사용 불가 (이 경우 Sidecar/CSI 선택)

VSO를 쓰면 무조건 좋을까?

대부분의 GitOps 기반 Kubernetes 환경(EKS/ALB/CloudFront/ArgoCD/Helm)에서는 VSO가 운영 편의와 리소스 효율 측면에서 가장 좋은 선택이라는 데 이견이 없다. 하지만 아래 같은 경우는 예외다.

  • 금융/보안 정책상 etcd에 Secret 저장 자체를 금지하는 회사
  • → VSO 사용 ❌, Sidecar/CSI 사용 ⭕
  • 매우 높은 주기의 실시간 rotation이 필요하고 앱이 File re-read 중심
  • → Sidecar가 더 적합할 수 있음
  • Config 파일 템플릿 렌더링이 많이 필요
  • → Sidecar 또는 CSI 선택

로컬 개발 환경에서 Secret 사용 가능 여부

  • VSO가 만든 K8s Secret은 Kubernetes 클러스터 내부에 있기 때문에 로컬 PC로 자동 제공되진 않는다
  • 하지만 로컬 개발에서 Vault를 직접 조회하면 사람 친화 table 형식 또는 JSON 형식으로 값을 얻을 수 있고, 이를 scripts나 CI에서 parsing해서 외부에서 환경변수로 export해 사용하는 것은 가능하다.

예: JSON 출력으로 Secret 읽기

vault kv get -format=json kv/apps/myapp/db | jq -r '.data.data.DB_PASSWORD'

이건 VSO를 거치지 않고, Vault Server에서 직접 값을 받아오는 방식이다.


마무리

Kubernetes에서 Vault를 사용할 때는 Secret이 생성되는 위치와 사용되는 방향을 이해하는 것이 중요하다.

Secret은 Pod에 묶여서 attach되는 것이 아니라, Pod가 Deployment YAML에서 “이 이름의 Secret을 읽어 쓰겠다”라고 선언하면서 참조함으로써 연결이 성립된다.

반응형

댓글