Network & Server Factory

개인 공부 기록

Cloud/Azure

[Azure] AKS에서 Key Vault로 Secret 관리 정리

1nfra 2026. 9. 28. 07:56
Kubernetes Secret은 값을 base64로 인코딩만 해 두는 방식이라 매니페스트 저장소에 그대로 올릴 수 없습니다. 이 글에서는 비밀번호 같은 값의 원본을 Azure Key Vault에 두고, AKS의 Secrets Store CSI Driver와 Workload Identity로 Pod가 필요한 값만 파일로 받아 쓰는 구조를 정리합니다.

1. 왜 Key Vault에 두는가

[CI/CD] GitHub, Jenkins, ArgoCD로 dev/prd 배포 아키텍처 설계에서는 매니페스트를 모두 Git 저장소에 두고 ArgoCD가 그대로 클러스터에 반영했습니다. 이 구조에서 DB 비밀번호를 Kubernetes Secret 매니페스트로 만들어 올리면, 저장소를 읽을 수 있는 사람은 누구나 값을 볼 수 있습니다. Secret의 data 필드는 암호화가 아니라 base64 인코딩이라서 명령 한 줄이면 원래 값이 나오기 때문입니다.

 

그래서 값의 원본은 Key Vault(Azure가 관리하는 비밀 값 저장소)에 두고, 클러스터에는 "어느 Key Vault에서 무엇을 가져올지"만 적어 둡니다. 은행에 비유하면 Key Vault는 금고, Pod는 신분증을 보여 주고 필요한 서류만 받아 가는 손님입니다. 금고에서 서류를 꺼내 Pod의 서랍(볼륨)에 넣어 주는 직원이 Secrets Store CSI Driver입니다.

 

항목 Kubernetes Secret Key Vault
저장 위치 클러스터의 etcd Azure가 관리하는 Key Vault
값 보호 base64 인코딩 저장 시 암호화
Git 보관 매니페스트에 값이 들어감 매니페스트에는 이름만 들어감
권한 Kubernetes RBAC Azure RBAC
(Key Vault Secrets User 등)
이력 없음 버전별로 보관
감사 로그 AKS 진단 설정의 kube-audit Key Vault 진단 설정의 AuditEvent

2. 구성 요소와 신원 연결

Pod가 Key Vault에 접근하려면 Azure가 "이 요청을 보낸 Pod가 누구인지"를 알아야 합니다. 이때 쓰는 것이 Workload Identity로, Kubernetes의 ServiceAccount를 Azure의 Managed Identity와 짝지어 Pod가 비밀번호 없이 Azure에 로그인하게 해 주는 기능입니다. 예전에 쓰던 Pod-managed identity는 2022년 10월에 deprecated되었고, 지금은 Workload Identity가 권장 방식입니다.

ServiceAccount와 Managed Identity를 Federated Credential로 잇고, Managed Identity에 Key Vault 읽기 역할을 줍니다

 

항목 위치 용도
Key Vault Azure Secret 원본 보관
Managed Identity Azure Pod가 쓸 Azure 쪽 신원
Key Vault Secrets User 역할을 받음
Federated Credential Managed Identity 안 이 클러스터의 이 ServiceAccount를
믿는다는 등록
ServiceAccount AKS namespace client-id 주석으로 Managed Identity를 가리킴
SecretProviderClass AKS namespace Key Vault 이름과 가져올 Secret 목록
Secrets Store CSI Driver AKS kube-system 노드마다 하나씩 떠서
값을 받아 Pod 볼륨에 씀

 

가장 헷갈리는 부분은 Federated Credential입니다. "AKS가 발급한 이 ServiceAccount의 토큰이 오면 이 Managed Identity로 인정한다"는 신뢰 등록으로, 등록할 때 AKS의 OIDC issuer 주소와 subject(system:serviceaccount:namespace:이름)를 적습니다. 둘 중 하나라도 실제 값과 다르면 토큰 교환이 실패하므로, namespace나 ServiceAccount 이름을 바꿀 때는 Federated Credential도 같이 바꿔야 합니다.

3. 동작 순서

Pod가 뜰 때 Secret은 아래 순서로 들어옵니다. kubelet(노드에서 Pod를 띄우는 에이전트)이 볼륨을 붙이려고 CSI Driver를 부르면, CSI Driver가 Pod의 ServiceAccount 토큰으로 Entra ID에서 Access token을 받아 Key Vault를 읽습니다.

Pod의 컨테이너는 CSI 볼륨 마운트가 끝난 뒤에 시작됩니다

 

여기서 기억할 점은 두 가지입니다. 값은 노드 디스크가 아니라 메모리 기반 볼륨(tmpfs)에 파일로 들어가고, 마운트가 실패하면 컨테이너가 아예 시작하지 않아 Pod가 ContainerCreating에 머뭅니다. 그래서 권한이나 이름이 틀렸을 때는 앱 로그가 아니라 Pod 이벤트를 먼저 봐야 합니다.

4. 앱에 값을 넘기는 방식 비교

Key Vault 값을 앱에 넘기는 방법은 세 가지입니다. 저는 파일 방식을 기본으로 쓰고, 파일을 읽도록 고치기 어려운 앱에만 환경 변수를 씁니다. 환경 변수는 값이 바뀌어도 Pod를 다시 시작하기 전까지 반영되지 않기 때문입니다.

 

방식 동작 특징 용도
파일 CSI 볼륨의 파일을 앱이 읽음 rotation 시 파일이 바뀜
앱이 다시 읽어야 반영
기본 방식
환경 변수 secretObjects로 Kubernetes Secret을
만들고 env로 주입
Pod 재시작 전까지 이전 값 파일을 못 읽는 기존 앱
SDK 직접 조회 앱이 Azure SDK로 Key Vault 호출 CSI Driver 없이 동작
인증은 Workload Identity 사용
코드를 고칠 수 있는 앱

 

Secret rotation(Key Vault 값이 바뀌면 Pod 쪽 값도 주기적으로 갱신하는 기능)을 켜 두면, 값을 바꿨을 때 반영되는 범위는 아래와 같습니다.

파일과 Kubernetes Secret은 자동으로 바뀌지만, 환경 변수는 Pod를 다시 시작해야 바뀝니다

 

환경 변수 방식에서 교체까지 자동으로 하려면 Reloader처럼 Secret 변경을 감지해 롤링 업데이트를 해 주는 도구를 같이 씁니다. Key Vault 값을 Kubernetes Secret으로 복사해 두는 오픈소스 External Secrets Operator도 있지만, 이 글은 AKS Add-on으로 설치와 업그레이드를 관리받을 수 있는 CSI Driver를 기준으로 합니다.

5. 직접 해 보기

namespace demo의 demo-api가 DB 비밀번호를 파일로 받는 과정입니다. 아래 이름은 예시이고, Key Vault 이름은 전 세계에서 하나만 쓸 수 있으므로 바꿔서 씁니다.

 

항목 값
Resource Group rg-demo
AKS aks-demo
Key Vault kv-demo-1nfra
Managed Identity id-demo-api
namespace / ServiceAccount demo / demo-api

5.1 클러스터 기능 켜기

OIDC issuer와 Workload Identity를 켜고, Key Vault provider Add-on을 설치한 뒤 Secret rotation을 켭니다. 이미 켜져 있는 항목은 건너뜁니다.

az aks update -g rg-demo -n aks-demo \
  --enable-oidc-issuer --enable-workload-identity

az aks enable-addons -g rg-demo -n aks-demo \
  --addons azure-keyvault-secrets-provider

az aks addon update -g rg-demo -n aks-demo \
  --addon azure-keyvault-secrets-provider --enable-secret-rotation

kube-system namespace에 aks-secrets-store-csi-driver와 aks-secrets-store-provider-azure Pod가 노드마다 하나씩 Running이면 준비된 것입니다.

5.2 Key Vault와 Managed Identity 만들기

Key Vault는 권한을 Azure RBAC로 관리하도록 만들고, 비밀번호는 read -s로 화면에 보이지 않게 입력받습니다. Secret을 넣는 계정에는 Key Vault Secrets Officer 역할이 필요하고, 앱이 쓸 Managed Identity에는 읽기 전용인 Key Vault Secrets User 역할만 줍니다.

az keyvault create -g rg-demo -n kv-demo-1nfra --enable-rbac-authorization true
read -rs DB_PW
az keyvault secret set --vault-name kv-demo-1nfra -n db-password --value "$DB_PW"

az identity create -g rg-demo -n id-demo-api
CLIENT_ID=$(az identity show -g rg-demo -n id-demo-api --query clientId -o tsv)
KV_ID=$(az keyvault show -n kv-demo-1nfra --query id -o tsv)
az role assignment create --role "Key Vault Secrets User" \
  --assignee $CLIENT_ID --scope $KV_ID

역할의 범위를 Key Vault 하나로 잡았기 때문에, 이 Managed Identity는 다른 Key Vault의 값을 읽지 못합니다.

5.3 ServiceAccount와 Managed Identity 연결

Federated Credential에 AKS의 OIDC issuer 주소와 ServiceAccount를 등록합니다.

ISSUER=$(az aks show -g rg-demo -n aks-demo \
  --query oidcIssuerProfile.issuerUrl -o tsv)
az identity federated-credential create -n fc-demo-api \
  --identity-name id-demo-api -g rg-demo \
  --issuer $ISSUER --subject system:serviceaccount:demo:demo-api

이어서 같은 이름의 ServiceAccount를 만들고, 주석에 Managed Identity의 client ID를 적습니다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: demo-api
  namespace: demo
  annotations:
    azure.workload.identity/client-id: <CLIENT_ID>

5.4 SecretProviderClass 작성

어느 Key Vault에서 어떤 Secret을 가져올지 적습니다.

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: kv-demo-api
  namespace: demo
spec:
  provider: azure
  parameters:
    clientID: <CLIENT_ID>
    keyvaultName: kv-demo-1nfra
    tenantId: <TENANT_ID>
    objects: |
      array:
        - |
          objectName: db-password
          objectType: secret

 

항목 값
clientID Managed Identity의 client ID
(ServiceAccount 주석과 같은 값)
keyvaultName Key Vault 이름
tenantId Key Vault가 있는 Tenant ID
objects 가져올 Secret 목록
objectName이 그대로 파일 이름이 됨

5.5 Deployment에 붙이기

Deployment의 Pod 템플릿에는 세 가지를 추가합니다. metadata.labels의 azure.workload.identity/use: "true", spec의 serviceAccountName, 그리고 CSI 볼륨입니다. 아래는 Pod 템플릿의 spec 중 바뀌는 부분만 옮긴 것입니다.

serviceAccountName: demo-api
containers:
  - name: demo-api
    volumeMounts:
      - name: secrets
        mountPath: /mnt/secrets-store
        readOnly: true
volumes:
  - name: secrets
    csi:
      driver: secrets-store.csi.k8s.io
      readOnly: true
      volumeAttributes:
        secretProviderClass: kv-demo-api

배포한 뒤 파일이 생겼는지 확인합니다.

kubectl -n demo exec deploy/demo-api -- ls /mnt/secrets-store

목록에 db-password가 보이면 끝입니다. cat으로 값까지 볼 수 있지만 화면과 터미널 기록에 비밀번호가 남으므로, 실제 값은 파일 이름까지만 확인합니다.

6. 운영 시 주의점

항목 조치
Secret rotation Add-on 기본값은 꺼져 있음. --enable-secret-rotation으로 켜고
주기는 기본 2분, --rotation-poll-interval로 변경
subPath 마운트 subPath로 붙인 파일은 rotation이 반영되지 않으므로
볼륨을 디렉터리째 마운트
동기화된 Secret secretObjects로 만든 Kubernetes Secret은 볼륨을 마운트한 Pod가 있을 때만 생기고
그 Pod가 모두 지워지면 같이 지워짐
마운트 실패 Pod가 ContainerCreating에 멈춤
kubectl describe pod의 Events(FailedMount)에서 403, 이름 오타 확인
Secret 이름 영문, 숫자, 하이픈만 가능(DB_PASSWORD 불가)
파일 이름을 바꾸려면 objectAlias 사용
네트워크 Key Vault 방화벽이나 Private Endpoint를 쓰면 노드 서브넷에서 가는 경로와 DNS 확인
UDR로 Azure Firewall을 거치면 필요한 FQDN 허용
권한 범위 앱마다 Managed Identity를 따로 두고 Key Vault Secrets User만 부여
dev와 prd는 Key Vault를 나눔
Federated Credential Managed Identity 하나에 최대 20개
클러스터와 namespace가 많으면 Managed Identity를 나눔
값 입력 명령줄에 값을 그대로 쓰면 셸 기록에 남으므로
read -s로 받은 변수나 Portal로 입력

7. 정리

  • Secret 원본은 Key Vault에 두고, 클러스터에는 SecretProviderClass로 "어디서 무엇을 가져올지"만 남깁니다.
  • Pod의 신원은 ServiceAccount, Federated Credential, Managed Identity를 잇는 Workload Identity로 만들고, 역할은 Key Vault Secrets User만 줍니다.
  • 값은 파일로 받는 것이 기본이며, 환경 변수는 Pod를 재시작해야 새 값이 반영된다는 점을 감안해서 씁니다.

참고

728x90
서울
--:--:--
-전체 글
-카테고리
오늘 방문