왜 GitOps에서 시크릿이 문제가 되는가
GitOps는 Git을 단일 진실 공급원(single source of truth)으로 삼는다. 매니페스트를 커밋하면 Argo CD나 Flux가 클러스터 상태를 그에 맞춘다. 문제는 Secret 리소스다. 쿠버네티스 Secret은 base64 인코딩일 뿐 암호화가 아니므로, 평문에 가까운 값을 Git에 올리는 순간 저장소 접근 권한이 곧 자격증명 유출로 이어진다. 그렇다고 시크릿만 Git 밖에서 kubectl apply로 관리하면 GitOps의 선언적 일관성이 깨진다. 이 딜레마를 푸는 두 가지 대표 접근이 Sealed Secrets와 External Secrets Operator(ESO)다.
Sealed Secrets: 암호문을 Git에 올린다
Bitnami Sealed Secrets는 클러스터에 controller를 두고, 공개키로 시크릿을 암호화한 SealedSecret 리소스를 만든다. 이 암호문은 오직 클러스터 내부의 개인키로만 복호화되므로 Git에 안전하게 커밋할 수 있다. controller가 SealedSecret을 감지하면 실제 Secret으로 풀어낸다.
# kubeseal CLI로 평문 Secret을 암호화
kubectl create secret generic db-cred \
--from-literal=password='S3cr3t!' \
--dry-run=client -o yaml \
| kubeseal \
--controller-namespace kube-system \
--format yaml \
> sealed-db-cred.yaml
# 생성된 sealed-db-cred.yaml 은 그대로 git commit 가능
git add sealed-db-cred.yaml && git commit -m "add sealed db cred"
강점은 외부 의존성이 없다는 점이다. 별도 시크릿 저장소(Vault 등)가 필요 없고, 암호문 자체가 Git에 남아 이력 추적이 된다. 대신 개인키가 클러스터 안에 있으므로 백업이 생존의 핵심이다. 이 키를 잃으면 모든 SealedSecret을 다시 만들어야 한다.
External Secrets: 참조만 Git에 올린다
ESO는 시크릿 값 자체를 Git에 두지 않는다. AWS Secrets Manager, Vault, GCP Secret Manager 같은 외부 저장소를 SecretStore로 연결하고, ExternalSecret 리소스에 "어느 키를 가져올지"만 선언한다. Operator가 주기적으로 외부에서 값을 읽어 실제 Secret을 동기화한다.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-cred
spec:
refreshInterval: 1h # 주기적 재동기화
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: db-cred # 생성될 k8s Secret 이름
data:
- secretKey: password # Secret 안의 키
remoteRef:
key: prod/db # 외부 저장소의 경로
property: password
Git에는 경로 참조만 남으므로 민감값이 저장소에 존재하지 않는다. 값 회전(rotation)도 외부 저장소에서 바꾸면 refreshInterval에 맞춰 반영된다. 대신 외부 시크릿 매니저가 반드시 있어야 하고, 그 접근 자격증명(IRSA, Workload Identity 등)을 부트스트랩하는 문제가 남는다.
핵심 비교
| 항목 | Sealed Secrets | External Secrets |
|---|---|---|
| 값 저장 위치 | 암호문이 Git에 존재 | 외부 저장소, Git엔 참조만 |
| 외부 인프라 | 불필요 | Vault/클라우드 시크릿 매니저 필요 |
| 값 회전 | 재암호화·재커밋 필요 | 외부에서 변경 시 자동 동기화 |
| 단일 실패점 | controller 개인키 | 외부 저장소 및 접근 권한 |
| 이력 추적 | Git 히스토리에 남음 | 외부 저장소 감사 로그 |
| 멀티 클러스터 | 클러스터별 재암호화 | 동일 참조 재사용 용이 |
어떤 걸 선택할까
기준은 "이미 시크릿 매니저가 있는가"와 "회전 빈도"다. Vault나 클라우드 시크릿 매니저를 이미 운영하고 자격증명 회전이 잦다면 ESO가 자연스럽다. 값이 한 곳에서 관리되고 감사·회전이 중앙화된다. 반면 소규모이거나 외부 의존성을 최소화하고 싶고, 시크릿 변경이 드물다면 Sealed Secrets가 단순하다. 인프라 추가 없이 GitOps 원칙(모든 것이 Git에)을 가장 곧게 지킨다.
운영 시 주의점
- Sealed Secrets 키 백업: controller의 개인키(
kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key)를 안전한 곳에 백업하지 않으면 클러스터 재구축 시 복구 불가다. - ESO 접근 권한 부트스트랩:
SecretStore가 외부에 접근할 자격증명 자체는 IRSA·Workload Identity처럼 정적 시크릿이 아닌 방식으로 주입하라. 그렇지 않으면 "시크릿을 가져오기 위한 시크릿" 문제가 반복된다. - 복호화된 Secret은 결국 평문: 두 방식 모두 최종적으로 클러스터에 평범한
Secret을 만든다. etcd 암호화(EncryptionConfiguration)와 RBAC로get secret권한을 제한하는 것은 별개로 반드시 필요하다. - refreshInterval 남용 주의: ESO에서 간격을 너무 짧게 잡으면 외부 API 호출량과 비용이 늘고 레이트리밋에 걸릴 수 있다.
정리하면, 두 도구는 경쟁이 아니라 서로 다른 신뢰 모델이다. Sealed Secrets는 "암호문을 Git에 봉인", ESO는 "값을 외부에 두고 참조"다. 조직의 기존 인프라와 회전 요구에 맞춰 고르되, 최종 Secret의 노출면을 줄이는 기본 방어(etcd 암호화·RBAC)는 어느 쪽을 쓰든 동일하게 적용해야 한다.