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를 재시작해야 새 값이 반영된다는 점을 감안해서 씁니다.
참고
'Cloud > Azure' 카테고리의 다른 글
| [Azure] Azure Container Registry 사용법 정리 (0) | 2026.09.27 |
|---|---|
| [Azure] Entra ID, RBAC, Managed Identity 정리 (0) | 2026.09.27 |
| [Azure] AKS GPU 노드풀 구성 정리 (0) | 2026.09.27 |
| [Azure] Azure Backup과 Site Recovery 정리 (0) | 2026.09.26 |
| [Azure] Azure Storage 중복성 비교 (0) | 2026.09.26 |