본문 바로가기

[NCP 쿠버네티스] HashiCorp Vault, logging/2. HashiCorp Vault

18-2. Transit 엔진 기반 암호화 및 KMS 실무 가이드 (시크릿 데이터 암호화값 사용)

단순히 시크릿을 저장하는 것을 넘어, 데이터를 암호화하여 저장하고 서비스 앱이 실행될 때 이를 복호화하여 사용하는 '보안 심층 방어' 전략을 정리한다.

 

1. 암호화 및 복호화 원리  (AES-256-GCM)

Vault Transit 엔진은 확률적 암호화(Stochastic Encryption) 방식을 사용한다. 동일한 평문을 암호화하더라도 결과값이 매번 다르게 생성되는 것이 특징이다.

 

왜 매번 암호문이 달라지는가?

  • 원리: 암호문 = 알고리즘(평문 + 비밀키 + Nonce)
  • Nonce(IV)의 역할: 암호화할 때마다 내부적으로 고유한 일회성 값(Nonce)을 생성하여 결합한다.
  • 이유: 동일한 평문이 항상 동일한 암호문으로 치환되면 해커가 패턴을 분석(빈도 분석 공격)할 수 있다. 매번 다른 결과값을 생성함으로써 데이터의 기밀성을 극대화한다.

복호화 원리: 암호문의 구조

매번 암호문이 달라짐에도 정확히 복호화가 가능한 이유는 암호문 본문에 정보가 포함되어 있기 때문이다.

  • 구조 예시: vault:v1:base64_data...
    1. vault: Vault에서 생성된 데이터임을 나타내는 접두사.
    2. v1: 사용된 키의 버전 (키 로테이션 시 이전 버전 대응용).
    3. 데이터 본문: 실제 암호화된 값과 복호화에 필요한 Nonce(IV) 값이 포함되어 있다.

 

 

2. KV 엔진 생성 (기존에 생성한 엔진 사용하여도 된다)

접근을 허용한 IP 대역에서 브라우저를 통한 UI 접근
(예: https://vault.main.domain.kr)

 

Token: [Vault Initial Root Token]

 

 

엔진 활성화 (Enable Engine)

  1. Vault UI 접속 후 Secrets 메뉴 선택
  2. Enable engine 버튼 클릭
  3. KV 타입 선택 후 Next
  4. Path 입력 (ex. secret): 이 이름이 모든 데이터 접근의 최상위 주소가 된다.
  5. Method에서 Version 2 확인 후 Enable Engine 클릭

 

 

 

3. 엔진 활성화 및 암호화 키 생성 (Vault Pod에서 작업)

UI에서도 설정이 가능하지만, 실무 환경에서는 정확한 설정값 확인과 히스토리 관리를 위해 Vault Pod에 직접 접속하여 CLI로 작업하는 것을 권장한다. 이 과정은 최초 1회만 수행하면 된다.

 

3-1. Vault Pod 접속 및 로그인

 

먼저 쿠버네티스 클러스터에 배포된 Vault Pod 내부로 접속하여 관리자 권한을 획득해야 한다.

# 1. Vault 0번 Pod 접속
kubectl exec -it vault-0 -n vault -- /bin/sh

# 2. Root 토큰을 사용하여 로그인
vault login [Vault Initial Root Token]
 

3-2. Transit 엔진 활성화

데이터 암복호화 기능을 담당하는 Transit 엔진을 활성화한다. 이미 활성화되어 있다면 이 단계는 생략 가능하다.

# Transit 엔진 활성화 (이미 되어 있다면 생략 가능)
vault secrets enable transit

# 엔진 활성화 상태 확인 (목록에 transit이 포함되어야 함)
vault secrets list

 

3-3. 암호화 전용 키 생성

데이터를 암호화하고 복호화할 때 사용할 '열쇠'를 생성한다. 이 키는 Vault 내부에 안전하게 보관되며 외부로 절대 노출되지 않는다.

# 암호화 키 생성 (-f 옵션은 데이터 입력 없이 강제 생성함을 의미)
vault write -f transit/keys/[my-service-key]

# 예시: vault write -f transit/keys/vaultunseal

 

3-4. 생성 결과 확인

키가 정상적으로 생성되었는지, 그리고 어떤 알고리즘이 적용되었는지 확인한다.

# 생성된 키의 상세 정보 조회
vault read transit/keys/[my-service-key]

성공 확인: 출력 결과 중 type: aes256-gcm96 항목이 보인다면, 앞서 설명한 AES-256-GCM 방식의 강력한 보안 키가 성공적으로 생성된 것이다.

 

 

4. 데이터 암호화 및 KV 엔진 저장 (Transit 활용)

Vault의 KV 엔진은 데이터를 저장하는 '창고'일 뿐, 스스로 데이터를 암호화하는 기능은 없다. 따라서 Transit 엔진을 이용해 평문을 암호문(vault:v1:...)으로 먼저 변환한 뒤, 그 결과값을 KV 엔진에 저장해야 한다.

 

4-1. 안전한 암호화 데이터 생성 방법

터미널 히스토리에 평문이 남는 것을 방지하기 위해 임시 파일을 활용하는 것이 가장 안전하다.

 

작업 순서 (Vault Pod 내부):

# 1. 권한이 있는 임시 폴더로 이동
cd /tmp

# 2. 암호화할 평문 데이터를 파일로 작성 (예: 비밀번호나 인증키)
cat <<EOF > test.txt
my-very-secret-data-1234
EOF

# 3. 파일을 읽어 Base64 인코딩 후 Transit 엔진으로 암호화 요청
# (엔터값(\n) 제거 후 인코딩하여 전송)
vault write transit/encrypt/[my-service-key] \
    plaintext=$(cat test.txt | tr -d '\n' | base64 -w 0)
# 예시
# vault write transit/encrypt/vaultunseal \
#     plaintext=$(cat test.txt | tr -d '\n' | base64 -w 0)

# 4. 작업 완료 후 임시 파일 삭제
rm test.txt

 

4-2. 대용량 평문 데이터 처리 (File Transfer)

평문이 너무 길어 터미널에 직접 입력하기 어려운 경우, 로컬 파일을 Pod로 복사하여 처리한다.

# 1. 로컬의 평문 파일을 Vault Pod의 /tmp 경로로 복사
kubectl cp ./large-test.txt vault-0:/tmp/large-test.txt -n vault

# 2. Pod 접속 후 위와 동일한 방식으로 암호화 실행
vault write transit/encrypt/[my-service-key] \
    plaintext=$(cat /tmp/large-test.txt | tr -d '\n' | base64 -w 0)

 

 

5. KV 엔진에 암호문 저장

위 단계에서 출력된 결과값(ciphertext)은 vault:v1:asdf...와 같은 형태다. 이 값을 복사하여 서비스 앱이 참조하는 KV 엔진 경로에 저장한다.

 

  1. UI 접속: https://vault.main.domain.kr 접속 후 로그인.
  2. 경로 이동: 시크릿이 저장될 KV 엔진 경로(예: secret/data/dev-config)로 이동.
  3. 데이터 입력:
    1. Key: DB_PASSWORD
    2. Value: vault:v1:TThSdzl... (위에서 복사한 암호문 전체)
  4. 저장: Save 버튼 클릭.

결과: 이제 관리자나 외부인이 Vault UI에서 해당 데이터를 열어봐도 실제 내용은 알 수 없고, 암호화된 vault:v1:... 문자열만 보이게 된다. 데이터 저장소 수준에서의 보안(At-rest Encryption)이 완성된 것이다.

 

 

 

6. 데이터 사용을 위한 Policy(정책) 생성

Policy는 "어떤 앱이 어떤 경로의 데이터에 대해 어떤 행위(읽기, 쓰기 등)를 할 수 있는지" 정의하는 규칙이다. 보안을 위해 필요한 최소한의 권한만 부여하는 것이 원칙이다.

 

6-1. KV 엔진 버전 2의 경로 구조 이해 

우리가 사용하는 KV 버전 2는 데이터의 버전 관리를 위해 내부적으로 경로를 분리하여 운영한다.

  • 실제 데이터 조회: secret/data/[경로명] (시크릿 값을 가져올 때 사용)
  • 메타데이터 조회: secret/metadata/[경로명] (버전 정보 확인 및 UI 목록 표시용)

 

6-2. 주요 설정 항목 및 권한 범위

 

  1. 시크릿 데이터 권한 (data/, metadata/):
    • 앱이 실제 시크릿 값을 읽으려면 data/ 경로에 read 권한이 있어야 한다.
    • UI에서 시크릿 목록을 확인하려면 metadata/ 경로에 list 권한이 포함되어야 한다.
  2. UI 브라우징 시스템 권한 (sys/internal/...):
    • 이 권한이 없으면 로그인을 하더라도 왼쪽 사이드바에서 시크릿 엔진(Mount)을 클릭하거나 확인할 수 없다. 사용자가 직접 주소를 타이핑하지 않고 UI를 통해 접근하려면 반드시 추가해야 한다.
  3. Transit 복호화 권한 (transit/decrypt/...):
    • 주의: 복호화는 단순히 읽는 것이 아니라 암호문을 보내서 결과를 받아오는 과정이므로 read가 아닌 update 권한이 필요하다.

 

6-3. Policy 구문 작성 (예시: dev-idaas-policy)

Vault UI의 Policies 메뉴에서 아래 내용을 복사하여 생성한다. (예시 파일명: vaultunseal 키 사용 기준)

# 1. 시크릿 데이터 읽기 권한
path "secret/data/dev-config" {
  capabilities = ["read"]
}

# 2. 메타데이터 읽기 권한 (UI 목록 및 버전 표시용)
path "secret/metadata/dev-config" {
  capabilities = ["read", "list"]
}

# 3. UI에서 'secret' 엔진(마운트 지점)을 시각적으로 확인하기 위한 권한
path "sys/internal/ui/mounts/secret" {
  capabilities = ["read"]
}

path "sys/internal/ui/mounts/secret/*" {
  capabilities = ["read"]
}

# 4. Transit 엔진을 통한 데이터 복호화 권한 (가장 중요)
# 복호화 API 호출을 위해 'update' 권한을 부여한다.
path "transit/decrypt/vaultunseal" {
  capabilities = ["update"]
}

# (참고) 앱이 직접 암호화까지 수행해야 한다면 아래 주석을 해제한다.
# path "transit/encrypt/vaultunseal" {
#   capabilities = ["update"]
# }

 

 

설정 팁

  • 이름 규칙: [네임스페이스]-policy 혹은 [서비스명]-policy로 명명하면 추후 쿠버네티스 Auth Method와 연결할 때 관리하기 수월하다.
  • 권한 최소화: 실무 환경에서는 path "secret/data/*" 처럼 와일드카드를 쓰기보다, 정확한 시크릿 경로를 명시하여 다른 서비스의 데이터에 접근하는 것을 차단해야 한다.

 

 

7. 쿠버네티스 인증 및 Role 설정 (Vault Pod에서 실행)

Vault가 쿠버네티스 클러스터를 믿고 데이터를 내어줄 수 있도록 인증 환경을 구성하고, 특정 조건(네임스페이스, SA 이름)을 만족하는 Pod에게만 정책(Policy)을 부여하는 과정이다.

 

7-1. 쿠버네티스 API 접속 정보 등

Vault가 쿠버네티스에게 "이 접속 요청이 진짜 우리 클러스터의 Pod이 맞니?"라고 물어볼 수 있도록 통신 창구를 알려준다.

# 1. Vault Pod 접속 및 로그인
kubectl exec -it vault-0 -n vault -- /bin/sh
vault login [Vault Initial Root Token]

# 2. 쿠버네티스 인증 활성화 (최초 1회만 수행)
vault auth enable kubernetes

# 3. 내부 API 서버 주소 등록
# 내부 통신용 주소인 kubernetes.default.svc를 사용한다.
vault write auth/kubernetes/config \
    kubernetes_host="https://kubernetes.default.svc:443"

 

 

8. Role 생성: 인증과 정책의 매칭

Role은 "누구에게(SA)", "어떤 정책(Policy)을" 줄 것인지 결정하는 규칙이다.

 

8-1. 주요 설정 옵션 설명

 

  • bound_service_account_names: 시크릿 접근을 허용할 ServiceAccount 이름이다.
    • 특정 SA만 허용하려면 "sa-test, sa-test1"과 같이 명시적으로 나열한다.
    • "*"(와일드카드)를 사용하면 해당 네임스페이스 내의 모든 SA를 허용한다. 단, 실무에서는 보안을 위해 명시적 나열을 권장한다.
  • bound_service_account_namespaces: 해당 Role이 적용될 서비스 Pod의 네임스페이스다.
  • policies: 앞서 생성한 Policy 이름을 연결한다 (예: dev-idaas-policy).
  • ttl=24h: 발급된 토큰의 유효기간이다. 사이드카(Agent)가 알아서 갱신하므로 보통 12~24시간 정도로 설정하여 보안 사고 시 피해를 최소화한다.

8-2. Role 생성 실행 (예시)

네임스페이스 dev-idaas의 모든 Pod이 dev-idaas-policy 권한을 갖도록 설정하는 예시다.

# Role 생성
vault write auth/kubernetes/role/dev-idaas-role \
    bound_service_account_names="*" \
    bound_service_account_namespaces="dev-idaas" \
    policies="dev-idaas-policy" \
    ttl=24h

# 생성된 Role 정보 확인
vault read auth/kubernetes/role/dev-idaas-role

 

 

실무 팁: SA 관리 전략

실무에서 와일드카드(*)를 사용하는 경우는 관리 편의성 때문이지만, 민감도가 높은 데이터를 다룰 때는 반드시 특정 앱 전용 SA를 생성하고(bound_service_account_names="app-a-sa") 해당 이름만 허용하도록 Role을 분리해야 한다.

 

[보안 필수] 작업 종료 시 Vault 세션 정리

Vault Pod에서 CLI 작업을 마친 후에는 반드시 인증 토큰을 삭제해야 한다. 쿠버네티스 환경의 특성상 exit로 세션을 종료하더라도 컨테이너 내부의 파일은 유지되기 때문이다.

 

왜 삭제해야 하는가?

  • 토큰 저장 위치: vault login 성공 시, 인증 토큰은 해당 유저의 홈 디렉토리(~/.vault-token)에 텍스트 형태로 저장된다.
  • 보안 취약점: 토큰을 삭제하지 않고 나갈 경우, 다른 작업자가 kubectl exec로 접속했을 때 이전 작업자의 권한(Root 권한 등)을 그대로 사용하여 Vault 명령어를 실행할 수 있는 위험이 있다.

토큰 삭제 및 세션 종료 명령어

작업을 마친 후에는 아래 명령어를 순서대로 입력하여 흔적을 지우는 것을 원칙으로 한다.

# 1. (선택사항) 작업 중 생성한 임시 평문 파일 삭제
rm /tmp/test.txt

# 2. 저장된 Vault 인증 토큰 삭제
rm ~/.vault-token

# 3. Pod 세션 종료
exit

 

 

9. 서비스 Pod에 Sidecar(인젝터) 주입

각 서비스의 Deployment.yaml 파일 내 spec.template.metadata.annotations 부분에 설정을 추가해야 한다. 이때 기존에 env envFrom으로 시크릿을 주입하던 설정은 삭제한다.

 

9-1. 주요 Annotation 설정

  1. 인젝터 활성화 (vault.hashicorp.com/agent-inject: "true")
    • Vault Agent 사이드카를 주입하고 /vault/secrets 공유 볼륨(tmpfs 기반 메모리 볼륨)을 자동 생성한다.
  2. 권한 일관성 유지 (vault.hashicorp.com/agent-run-as-same-user: "true")
    • 사이드카 컨테이너의 UID/GID를 메인 컨테이너와 동일하게 강제한다.
    • 필요성: 사이드카가 생성한 파일의 소유권 불일치로 인한 'Permission Denied' 에러를 방지하며 최소 권한 원칙을 적용한다.
    • 조건: 메인 컨테이너의 securityContext에 runAsUser, runAsGroup이 반드시 명시되어야 한다.
  3. 사이드카 실행 모드 (vault.hashicorp.com/agent-pre-populate-only: "true")
    • 시크릿을 한 번 가져온 뒤 사이드카 프로세스를 종료(Completed)시킨다.
    • 장점: 리소스 낭비를 방지하며, 실시간 시크릿 변경으로 인한 예기치 못한 사이드 이펙트를 차단한다. 정보 갱신 시에는 Pod를 재기동(Rollout)해야 한다.
  4. 시크릿 경로 및 파일명 정의 (vault.hashicorp.com/agent-inject-secret-config)
    • 형식: [엔진명]/data/[시크릿경로] (KV2 엔진은 중간에 data 필수 포함).
    • 예시: secret/data/dev-config → /vault/secrets/config 파일 생성.
  5. 데이터 가공 템플릿 (vault.hashicorp.com/agent-inject-template-config)
    • Go 템플릿을 사용하여 모든 Key-Value 쌍을 export KEY="VALUE" 형식으로 자동 변환한다.
    • 특히 값이 vault:v1:으로 시작하는 암호문일 경우, Transit 엔진을 호출해 자동으로 복호화하여 평문으로 주입하도록 구성했다.

 

9-2. Deployment.yaml 예시

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dev-test-service-deployment
  namespace: dev-idaas
  labels:
    app: dev-test-service
spec:
  selector:
    matchLabels:
      app: dev-test-service
      
  template:
    metadata:
      labels:
        app: dev-test-service
      annotations:
        # 1. 인젝터 활성화(활성화 시 Vault Agent 사이드카 주입)
        vault.hashicorp.com/agent-inject: "true"
        # 2. 메인 컨테이너의 UID/GID를 사이드카에도 동일 적용 (권한 충돌 방지 및 보안 강화)
        vault.hashicorp.com/agent-run-as-same-user: "true"
        # 3. 사용할 Vault Role (Kubernetes Auth Method에 등록된 Role 명)
        vault.hashicorp.com/role: "dev-idaas-role"
        # 4. Init 컨테이너 단계에서 시크릿을 모두 가져온 후 메인 컨테이너를 실행하도록 설정
        vault.hashicorp.com/agent-pre-populate-only : "true" 
        # 5. Vault에서 가져올 시크릿의 실제 경로 (kv 엔진 경로)
        vault.hashicorp.com/agent-inject-secret-config: "secret/data/dev-config"
        # 6. 시크릿 데이터를 환경변수(export) 형식의 파일로 가공하여 생성
          vault.hashicorp.com/agent-inject-template-config: |
            {{- with secret "secret/data/dev-config" -}}
            {{- range $k, $v := .Data.data -}}
              {{- /* 1. 값이 문자열이고 'vault:v1:'으로 시작하는지 확인 */ -}}
              {{- if $v | printf "%s" | contains "vault:v1:" -}}
                {{- /* 2. 암호문이라면 Transit 엔진으로 복호화 요청 */ -}}
                {{- with secret "transit/decrypt/vaultunseal" (printf "ciphertext=%s" $v) -}}
            export {{ $k }}="{{ .Data.plaintext | base64Decode | trimSpace }}"{{ "\n" }}
                {{- end -}}
              {{- else -}}
                {{- /* 3. 일반 평문이라면 그대로 출력 */ -}}
            export {{ $k }}="{{ $v }}"{{ "\n" }}
              {{- end -}}
            {{- end -}}
            {{- end -}}
          
    spec:
    # Pod 수준 보안 컨텍스트
      securityContext:
        runAsUser: 2000 
        runAsGroup: 2000 
        runAsNonRoot: true 
        fsGroup: 2000                        # 마운트된 볼륨(/vault/secrets)에 대한 그룹 소유권 할당
			
      containers:
        - name: dev-test-service
          
          # 컨테이너 수준 보안 컨텍스트
          securityContext:
            runAsUser: 2000                  # 컨테이너 실행 유저 (Vault 인젝터 참조용)
            runAsGroup: 2000                 # 컨테이너 실행 그룹 (Vault 인젝터 참조용)
            runAsNonRoot: true               # 컨테이너 단위 비루트 강제
            allowPrivilegeEscalation: false  # 권한 상승 차단
            readOnlyRootFilesystem: true     # 루트 파일 시스템 보호
            capabilities:
              drop: ["ALL"]                  # 불필요한 Linux 권한 제거
            seccompProfile:
              type: RuntimeDefault           # 위험 syscall 제한
            appArmorProfile:
              type: RuntimeDefault           # OS 레벨 접근 제어

 

[참고] 동작 원리: 사이드카 컨테이너의 복호화 메커니즘

Vault Agent가 어떻게 서비스 앱 수정 없이 암호화된 데이터를 복호화해서 전달하는지, 그 내부 흐름을 이해하는 것이 중요하다.

 

1. Vault Agent의 기동 및 인증

서비스 Pod이 시작될 때 Vault Agent가 사이드카(또는 Init 컨테이너) 형태로 함께 실행된다. 이 Agent는 설정된 쿠버네티스 Auth Method를 통해 Vault 서버와 통신하며 본인의 신원을 인증받는다.

 

2. 템플릿 기반 복호화 및 주입

Agent는 agent-inject-template에 정의된 로직을 실행한다.

  • API 호출: Agent가 직접 Vault의 Transit API(transit/decrypt/[키이름])를 호출한다.
  • 복호화 수행: 암호문을 보낸 뒤, 복호화된 평문 결과값을 받아온다.
  • 파일 생성: 복호화된 데이터를 공유 볼륨(tmpfs 기반의 /vault/secrets/) 내에 지정된 파일명으로 저장한다.

3. 서비스 컨테이너의 데이터 로드

메인 서비스 컨테이너는 이미 복호화가 완료되어 파일로 존재하고 있는 /vault/secrets/config를 읽기만 하면 된다.

  • 코드 수정 불필요: 앱 소스 코드 내부에서 직접 Vault API를 호출하거나 복호화 로직을 짤 필요가 없다.
  • 환경 변수 활용: entrypoint.sh를 통해 해당 파일을 source 함으로써, 앱은 마치 일반 환경 변수를 사용하는 것처럼 복호화된 데이터를 즉시 사용할 수 있다.

 

10. 애플리케이션에서 시크릿  로드 ( Java, Go, Nuxt )

Vault Injector를 사용해 시크릿을 가져왔더라도, 그것은 단지 /vault/secrets/config라는 경로에 생성된 **'단순 텍스트 파일'**일 뿐이다. 이 데이터를 애플리케이션(Java, Go, Nuxt 등)이 인식할 수 있는 **환경 변수(Environment Variable)**로 변환하는 과정이 실무에서 가장 중요하다.

 

10-1. 텍스트 파일을 환경 변수로: . (source) 명령

Deployment의 Annotation 설정에서 데이터를 export KEY="VALUE" 형식으로 생성하도록 템플릿을 짰다면, 이를 시스템 메모리에 올리는 작업이 필요하다.

 

원리와 역할

서비스 실행 스크립트(entrypoint.sh)에서 . /vault/secrets/config (또는 source) 명령을 수행하면 다음과 같은 변화가 일어난다.

  • 파일 실행: 파일 내부의 export 문들이 현재 쉘 환경에서 실제로 실행된다.
  • 메모리 등록: 텍스트 파일에 머물던 데이터가 실제 OS 환경 변수로 등록되어 앱에서 System.getenv()나 os.Getenv() 등으로 즉시 접근 가능해진다.

파일명 생성 규칙 (/vault/secrets/config)

  • /vault/secrets (고정): Vault Agent가 파일을 생성하는 고정 디렉토리다.
  • config (가변): Annotation 설정 중 vault.hashicorp.com/agent-inject-secret- 뒤에 붙는 단어가 파일명이 된다.
    • 예: ...-secret-db-env로 설정했다면, 로드 경로도 /vault/secrets/db-env가 되어야 한다.

 

10-2. 언어별 적용 가이드

 

1. Java / Spring 기준

Java 앱은 entrypoint.sh 최상단에서 시크릿을 로드한 뒤 java -jar를 실행한다.

[entrypoint.sh]

#!/bin/sh

# --- Vault 시크릿 로드 (항상 최상단 위치) ---
# 파일이 존재할 때만 로드하여 환경에 따른 오류를 방지한다.
if [ -f /vault/secrets/config ]; then
  echo "[INFO] Loading Vault secrets..."
  . /vault/secrets/config
fi

# 이후 기존 Java 실행 로직...
exec java -jar /app/service.jar

 

2. Go 기준

Go는 바이너리 실행 파일이므로, Dockerfile의 Entrypoint를 수정하여 entrypoint.sh를 거치게 해야 한다.

[Dockerfile 예시] (Runtime 단계)

FROM alpine:3.23 AS runtime

# 1. 빌드된 바이너리와 entrypoint.sh 복사
COPY --from=builder /build/bin/${PROGRAM} /app/bin/
COPY ./entrypoint.sh /app/bin/entrypoint.sh

# 2. 소유권 및 실행 권한 설정
RUN chown ${APP_USER}:${APP_GROUP} /app/bin/entrypoint.sh && \
    chmod +x /app/bin/entrypoint.sh

USER ${APP_USER}
WORKDIR /app/bin

# 3. ENTRYPOINT 변경: 스크립트를 통해 바이너리 실행
ENTRYPOINT ["/app/bin/entrypoint.sh"]
CMD ["./go-test-app"]

 

[entrypoint.sh]

#!/bin/sh

if [ -f /vault/secrets/config ]; then
  echo "[INFO] Loading Vault secrets..."
  . /vault/secrets/config
fi

# 실제 Go 애플리케이션 실행
# exec를 사용해야 시그널 전달(Graceful Shutdown)이 정상 작동한다.
exec "$@"

 

3. Nuxt (Node.js) 기준

Nuxt는 entrypoint.sh를 /usr/local/bin 등의 공용 경로에 두고 활용하는 것이 관례다.

[Dockerfile 예시] (Runtime 단계)

FROM alpine:3.23 AS runtime

# 1. 스크립트 복사 및 권한 부여
COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chown ${APP_USER}:${APP_GROUP} /usr/local/bin/entrypoint.sh && \
    chmod +x /usr/local/bin/entrypoint.sh

USER ${APP_USER}
WORKDIR /app/bin

# 2. ENTRYPOINT 설정
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["node", ".output/server/index.mjs"]

 

[entrypoint.sh]

#!/bin/sh

if [ -f /vault/secrets/config ]; then
  echo "[INFO] Loading Vault secrets..."
  . /vault/secrets/config
fi

# Node.js 앱 실행
exec "$@"

 

요약 및 주의사항

  1. 최상단 로드: 시크릿 로드 로직은 반드시 entrypoint.sh의 최상단에 위치해야 앱이 실행될 때 변수를 가진 상태로 시작할 수 있다.
  2. Graceful Shutdown: 스크립트 마지막에 반드시 exec "$@"를 사용하여 메인 프로세스가 시그널을 직접 받을 수 있게 해야 한다.
  3. 파일 체크: if [ -f ... ] 구문을 통해 Vault를 사용하지 않는 환경(로컬 테스트 등)에서도 스크립트 에러가 나지 않도록 방어 코드를 작성한다.

 

 

11. 적용 확인 및 검증

11-1. 서비스 Pod의 사이드카 상태 확인

배포된 Pod의 상세 정보를 조회하여 Vault 인젝터가 정상적으로 주입되었는지 확인하다.

kubectl describe pod [서비스 Pod명] -n [네임스페이스명]

 

11-2. 인젝터(사이드카) Log 확인

# 형식: kubectl logs -f [서비스 Pod명] -c [사이드카명] -n [네임스페이스]
kubectl logs -f [서비스 Pod명] -c vault-agent-init -n dev-idaas

 

11-3. 서비스 Pod 내부에 export 값 확인

kubectl exec -it [서비스 Pod명] -n [네임스페이스명] -- /bin/bash

# 컨테이너 내부에서 /vault/secrets 경로의 config 파일 확인해보기
cd /vault/secrets/
cat config