시작하며
최근에 개발 환경을 Dev와 Prod로 나누면서 비밀키 관리에 대해서 진지하게 고민하게 됐습니다. 특히 ArgoCD를 사용하면서 이 문제가 더 두드러졌는데요, 오늘은 그 과정에서 시도했던 여러 방법들과 최종적으로 선택한 방식에 대해 공유해보려고 합니다.
문제 상황
Dev 환경에서는 로컬 PC에 K8s를 올려서 작업했기 때문에, 비밀키를 어떻게 관리할지가 큰 고민이었습니다. ArgoCD가 GitOps 방식으로 동작하다 보니, 결국 비밀키 값들이 GitHub에 올라가야 하거나, 아니면 별도로 CLI를 통해 통신해서 비밀키를 가져와야 하는 상황이었거든요.
이 문제를 해결하기 위해 여러 방법들을 찾아보기 시작했습니다.
방법 1: SealedSecret을 이용한 Dev 환경 구성
여러 방법을 살펴보다가 SealedSecret이라는 방법을 발견했습니다.
SealedSecret이란?
SealedSecret은 Bitnami에서 만든 Kubernetes용 암호화 솔루션입니다. 일반적인 Secret을 암호화해서 안전하게 Git에 저장할 수 있게 해주는 도구인데요, 작동 방식은 이렇습니다:
- 비밀키를 SealedSecret으로 암호화
- 암호화된 내용을 Git에 안전하게 커밋
- 클러스터 내의 SealedSecret Controller가 자동으로 복호화
- 일반 Kubernetes Secret으로 변환
이 방식 덕분에 Dev 환경에서는 비교적 쉽게 비밀키를 관리할 수 있었습니다.
실제 적용 예시
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: wealist-argocd-secret
namespace: wealist-dev
spec:
encryptedData:
GIT_ACCESS: AgCSRfSkYobhxGeN2QzjF+fVUviSzaaHZCcxEmjN33zrx14/av+t+/fWUEGVq6pgKGU19LNowTmqXIDNTqV222KHaVCCR0+oc0droPOo6wjqKj+kaHqo2e764Qc6eOH0nfOjRcibRVZOYnzgN9dkpO0lh4WL0pS24GNQI5xCCoXzoHsgEFs7nFRgwmiVoIwfjsWwvac7sHKGYnvMciQnYEjxEqBoOhGQHAy9HrssSeAE4Qj+4AHD8uTbhB9Qjq1nTPLB+Q1zO6SLMwMy/aVTCmiJbd6u2O5HhT2TLAiARbsrn1ss9A/OuznVrGTqgE1Mqt3iCO2Ag9J4BJUol1oQqgBJEGgijqJ/OlY571OLxg21m4gGG3LAnH+4CocQSm3H4AqQSuqq8wWOmgFujAqxkXv6yPcp9qxrwLiqq18NtOk10BV4Sijj5j5u8bOaqDd5DahMLHZg4EbEZryYDWNzEtmGiwNoMDQX9NSlq+3gj2NKBdZELu4C8VrOdm+eTr0FbuD5RxqWZzTA/x5WyrsTRDxmW3W9z/xri3LeSIgreQUh35z4YdlhJjooHXetS3fQQD25frocoXLwH13r/ZRTUKONtASEDS4mBTCNdcxkGHEu44ORNKEGvHKwFa508yRYVwNXdq4S04oubq+5vtvh+lYCSw57qLynPRHXbkK8mS961EH4f0sCcsKaqpCF7WwbjYQxzU5SJ1gnyP1EmHIm05eK4Vxr3MDcY3+WJQlZGk/9z8gkPwg/71r7
GIT_NAME: AgAeeNuIs+nLwg/r1CoX3lGLG8LMbNiGQ99822RmBkVVM1Mi5cVP3I4yro8ajCAkpf3eREP50yoxCkqhLRCmE9nXBt0YoC9O6UrLAdWNcqfYkBbmj3kFCbxHBMZrkCn6X9qcaxsBjvvi2K+DqiP/Jvvsxnlp1XP3twG2BCfAzcEIQVL0L4SbInpRtujNRlZ/ulSivfZmHVM/eC2P0WpLqx7DsRg7RkpHG2TsgBhrHFYQSqE48kFPH88XrIcecSZHh33tDOo3KukzY3tdDm00rdawKy7ssRMPBtHCYxeB3cVTCuhpisY2BIc1lOIydN4XEFwDmlUrrUuOPD05pnQ31vSyXfQNzRW/3jK8IW9eqK5sILyf+0MGCsgLLPBECtJtn+ltjIIlR7HL2DuwdwNIMnMDQ9+mKwPc9+mBWb/zP5AE0BKvZhdj1B2DqlKHSvHwqB7mQJIAHTV7Z3Wf73f6YxPgj6x1PWTKpPjAEeostf1nPWSfYU7Yr3XMjbPmBVQp2T8SZLPrGrtQQxF22Hfj9t7U4+/ZtfmL3fre+cZ3aDb19Zo0mL3teWsepVl5dVte5szbbf8QkuQvVK8GIrBMytT7O8AttGcMn9hzCwfEfdvRKwnCotClF/cmep7bXh78Oo2BXX9Og8VpudQ/tBDb3OJz5S0pz/5eXdqLgO6Iy3gCHs1Bq4uep777JkPrhDyPpAw8TloiQ10Q
GOOGLE_CLIENT_ID: AgBeS9Ql7W1Nw6N1bmASkdfHmrMRajE5tcW0JOHg8Mm6GtcIjqNqi+1GH+31uN8lhOo3zsoW0ZZZUly6Nb/aob9B8eF62r0pQeE5nwgPy441rpbhRHjXO+qfnKV16c9dYb0YtJaAVXENNKJZ9pxVQg6y4cL96BWXedeSgxj8f1sLC6GT+ynzOcGRtkzs1YpqzCC9q6XubUJF9hgSLeyJN5L80C+TkYEe2Qv74U6TaWI5/hNOIRFsb0Hq6gEWheiQhZnD/UaBlBF88/87u/Ct2rClCmznckAnbKMOIXNn9ucmuiC41rL3VZT3gBV1fvnPh9gDtTz9v8BTHFzgY2Q0xonHwwN3S8hj0WsL5eL8j49wW+yova8ubR4+/9PRzcLA72PFBZcc64zdrklnDzL7ulyDELXe1TVVulp7mtByKujhDoT2kxzo9vxhgO2do7L5lOv4MM/HsPrJsK9X5XgAJwoARaFC/OyzeEhXdJMp24rgQEO2uniTUTg7AVhRWU4WkP03C0SPZPb6X1A0bkCw+6goyHBqSMol538+nEBo2wkFKL46wxQFLLzSZtMbIqLJbAKT18/lKdxJ+EGmUv8qj6c7ngfvtjSLR6UlkHj+OPTwXRUUqqNGySygH3dSvJacHd1RLnTFJMv487yXce9mAev54kWDknKBFMt+V0YMOImG8MignUfq57hBE5PmVcjdGaBKj1cdvBu8oqyoYI9s6JkfC/sV6hISpG8beO3yLLyRkUVavzSVESaAIUfq8Ep1c8yfVZH/S6Yfp1eGjbhALA4fXA7bdpn7QQk=
GOOGLE_CLIENT_SECRET: AgBGzv2TpkzR07mPh61R+1T4moGRcFGwxOdel9/gTG4g/+jIbRgOq81hKL0zcE5kIdB3susOprj6jtXuunkDtKcK4i1zuDLePVrZuXoV1ehJSUfrDAi8RZ1CcFwiYz29b+QqB2oHK4kXVTYu5eXLlTLm+j1i80Isu8IVAGQg+ybBy9DQD46HgivDeYOobB1B4BVBvSaBJYf8zhBYpFggQj8usqKsGlpV35BWr0KFC52UTqIHEsLYDDwjR0S0spJFRbQKA/qB0ADzlH+ckYNRoBF+XjMH97NAlTitEW5LK2J0dxrp2xdQkcEepAcWEWSZ/DRkiNd7ZVRIkjZvczagIYkGKmdOjXfay82zqhvBpU1d6CoC9PgqwQFuS/Fyf6sHm7vDKALtk0lN+jSnbEDBptyeG7Kgt7TQD/+xDjxZZPSAwYBxD0qRV4w/miCEUT4PTEDddwbdiumlq3XOZupDvaLXcsI0UxF9EmhHRHaWc64MfdO8vxECMMxukUlqWHUdOCaUqZ3kGVah+VrG/j/cnUlodJbXEhFyV5aiu4bpYNDfGXhhfuIMx+D2VSozK1EllJZdNgQJ/LubQSkb/A8E52Hf6Deap6mEDlmlNfaSBIODS8wBHDdJICXDo5ZDFDVnAJcYsqKeIhIYdsK4fe32jsRtDgsbOtOuEo4N4yoB5IJ1BBiWpSIKgQ2CPbk0Moso5ak7VuutEgpreRp29Tju6rC5PLLSTdY0m9CHXlXquOajiBx4Ag==
JWT_SECRET: AgBD8v0MwnnnPUNYGRBXngmN/e0GM41pFrf+RqEzOdVumWZ33UbaSE34nHqRM5VKTobaIZbDdm0ACUDCf6TT0M6oEeNsiWwH/WV4Ta+KqL0rxZuNY6uFLziRfnFf5721KzQSyUVUojK2c9zvgHw3vo2ONwbm6pvuvpflVpHQSOgpUQZv1S4MhYtTXY9+a/dteFDCJTAPYnPlO8zBRvpIbAyxeIgomAcCC7ekIzp+qjczdE3VEYby9Sy45pzSKebeHupM1Y8fBgZAB0XL7uSLDMpU3B4omev/1M43L6UjAtWFn/7ODvfpBBPnZicW1ICu1FImsOIHGOriwaB1YYsV55KH4LzL3MBvg6T/Z/WDZuuuIc4yanN7FJibRhfJy7sIIymCe7zdbof6w/vkGDCMbCUJCPKSCM7qg7WCEuNg6SEeFR1FNSIOBFDXYo2kOGClMpPisHgkjr4B82lOrfibEm+0ZHleUt55oR9bu9yyNps716iyCLHCqr4rZANuMCBvxukhCogxuQie61Ey7VYQjqFFthVXMjjlJGeMgWO8qekPV++iKKZNZxZ7SFvteI59+G8Kqh7K5NW8Adbu7xW/z0Tn2NtymadTHqb+iEN1OqMcnrrVoaVa3aD/MFUHpAejJKb4bXQhuGfF8zV2hrlBr5DqaiHff2pIR4frQ9Rqxx9LhDo3E5bP3UDHBLo8Hzx4zb5P1NkPUHW0kUxTSzO1P/cXy7dDM5r6wqX+oAWQ4pXooTqAMm172G9crUzCoNQ0tzzJVX094xgU5xDKLLy42/K5r+t3FMy0lpRufyZXJ5yjqw==
template:
metadata:
labels:
app.kubernetes.io/component: shared-secret
app.kubernetes.io/name: wealist-argocd-secrets
name: wealist-argocd-secret
namespace: wealist-dev
type: Opaque
보시다시피 encryptedData 부분에 암호화된 값들이 들어가 있고, 이걸 그대로 Git에 올려도 안전합니다. 클러스터 내부의 컨트롤러만 복호화할 수 있거든요.
SealedSecret의 장점
- Git에 안전하게 비밀키를 저장 가능
- GitOps 워크플로우와 완벽하게 호환
- 로컬 환경이나 Dev 환경에서 사용하기 간편
- 별도의 외부 서비스 의존성 없음
SealedSecret의 한계
하지만 Prod 환경을 구성하면서 몇 가지 한계를 느꼈습니다:
- 비밀키 로테이션이 번거로움
- 중앙화된 비밀키 관리가 어려움
- 감사 로그(Audit Log) 기능 부재
- 여러 클러스터나 환경 간 비밀키 공유가 복잡함
방법 2: External Secrets Operator + AWS Secrets Manager
Prod 환경을 구성할 때는 좀 더 프로덕션 레벨의 솔루션이 필요했습니다. AWS 환경을 사용하고 있었기 때문에, AWS의 네이티브 서비스를 활용하는 것이 더 합리적이라고 판단했고, **External Secrets Operator(ESO)**와 AWS Secrets Manager를 조합해서 사용하기로 결정했습니다.
External Secrets Operator란?
ESO는 외부 비밀키 저장소(AWS Secrets Manager, HashiCorp Vault, Azure Key Vault 등)에서 비밀키를 가져와 Kubernetes Secret으로 동기화해주는 오퍼레이터입니다.
작동 방식:
- ClusterSecretStore를 설정 (AWS Secrets Manager 연결 정보)
- ExternalSecret 리소스로 필요한 비밀키 정의
- ESO가 자동으로 AWS에서 값을 가져와 K8s Secret 생성
- 주기적으로 동기화 (refreshInterval 설정 가능)
실제 구현 예시
# =============================================================================
# External Secrets - ClusterSecretStore + ExternalSecret
# =============================================================================
# AWS Secrets Manager에서 시크릿을 가져와 K8s Secret으로 생성
# Terraform에서 생성된 시크릿들을 참조
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-secrets-config-prod
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
project: wealist-prod
source:
repoURL: https://github.com/OrangesCloud/wealist-project-advanced-k8s.git
targetRevision: k8s-deploy-prod
path: k8s/argocd/base/external-secrets
destination:
server: https://kubernetes.default.svc
namespace: wealist-prod
# ESO가 자동으로 추가하는 기본값 필드 무시
ignoreDifferences:
- group: external-secrets.io
kind: ExternalSecret
jqPathExpressions:
- .spec.data[].remoteRef.conversionStrategy
- .spec.data[].remoteRef.decodingStrategy
- .spec.data[].remoteRef.metadataPolicy
- .spec.target.deletionPolicy
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
========================ESO가 AWS Secret에 연결 후 알아서 가져오기 떄문에 따로 작성할 필요 없음=============
Terrform으로 ESO 생성 후 aws Secrets Manager에 연결
AWS Secrets Manager + ESO의 장점
이 방식을 선택하면서 얻게 된 장점들:
1. 중앙화된 비밀키 관리
- AWS Secrets Manager 콘솔에서 모든 비밀키를 한 곳에서 관리
- 여러 환경, 여러 클러스터에서 동일한 비밀키 저장소 사용 가능
2. 자동 로테이션
- AWS Secrets Manager의 자동 로테이션 기능 활용
- RDS, ElastiCache 등의 비밀키를 자동으로 갱신 가능
3. 세밀한 접근 제어
- IAM 정책을 통한 세밀한 권한 관리
- 누가, 언제, 어떤 비밀키에 접근했는지 CloudTrail로 추적
4. Terraform과의 통합
- Infrastructure as Code로 비밀키 생명주기 관리
- 자동으로 생성되는 비밀키(JWT_SECRET, DB 패스워드 등)와 수동으로 업데이트하는 비밀키(OAuth 자격증명 등) 구분 가능
5. 어노테이션과 라벨을 통한 효율적인 관리
- Kubernetes 네이티브한 방식으로 비밀키 접근
- 환경별, 서비스별로 필요한 비밀키만 선택적으로 마운트
구현 시 주의사항
실제로 구현하면서 주의했던 점들:
1. IAM 권한 설정
# ServiceAccount에 IRSA(IAM Roles for Service Accounts) 설정 필요
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-secrets-sa
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/external-secrets-role
2. refreshInterval 조정
- 너무 짧으면 AWS API 호출 비용 증가
- 너무 길면 비밀키 변경사항 반영이 느림
- 1시간 정도가 적당했음
3. 비밀키 네이밍 컨벤션
{service}/{env}/{category}/{specific-name}
예: wealist/prod/database/endpoint

방법 3: HashiCorp Vault (고려했지만 선택하지 않음)
HashiCorp Vault도 검토했었습니다. Vault는 매우 강력한 비밀키 관리 플랫폼이지만, 우리 상황에서는 AWS Secrets Manager를 선택했습니다.
Vault의 장점
- 동적 비밀키 생성 (Dynamic Secrets)
- 강력한 암호화 as a Service 기능
- 다양한 인증 백엔드 지원
- 플랫폼 중립적 (멀티 클라우드 지원)
왜 AWS Secrets Manager를 선택했나?
1. 운영 복잡도
- Vault는 별도의 클러스터 운영이 필요
- HA 구성, 백업, 업그레이드 등 관리 포인트 증가
2. AWS 생태계와의 통합
- 이미 AWS를 사용 중이므로 네이티브 통합이 자연스러움
- RDS, ElastiCache 등과의 자동 로테이션이 쉬움
- IAM, CloudTrail 등 기존 AWS 보안 인프라 활용
3. 비용
- Vault 클러스터 운영 비용 vs AWS Secrets Manager 사용료
- 우리 규모에서는 AWS Secrets Manager가 더 경제적
4. 학습 곡선
- 팀원들이 이미 AWS에 익숙함
- Vault는 새로운 개념과 운영 노하우 필요
결국 **"필요한 기능을 충분히 제공하면서, 운영 부담이 적은 솔루션"**을 선택하는 것이 중요하다고 판단했습니다.
환경별 선택 가이드
제 경험을 바탕으로 정리한 가이드입니다:
Dev/로컬 환경
추천: SealedSecret
- 빠른 셋업
- Git으로 관리 가능
- 외부 의존성 없음
- 팀원 간 쉬운 공유
Staging 환경
추천: ESO + AWS Secrets Manager (또는 Vault)
- Prod과 동일한 구조로 테스트
- 운영 프로세스 검증
- 하지만 별도의 AWS 계정/네임스페이스 사용
Production 환경
추천: ESO + AWS Secrets Manager (또는 Vault)
- 엔터프라이즈급 보안
- 감사 로깅
- 자동 로테이션
- 중앙화된 관리
멀티 클라우드 환경
추천: HashiCorp Vault
- 플랫폼 중립적
- 일관된 비밀키 관리 경험
- 하지만 운영 복잡도 증가는 감수해야 함
배운 점
이번 작업을 통해 배운 가장 큰 교훈은:
"모든 환경에 맞는 완벽한 솔루션은 없다"
각 환경의 특성, 팀의 역량, 인프라 상황에 따라 가장 적합한 도구를 선택하는 것이 중요합니다.
- Dev 환경에서는 빠른 이터레이션과 간편함이 중요
- Prod 환경에서는 보안, 감사, 안정성이 최우선
- 팀의 기존 기술 스택과 잘 통합되는지도 중요한 고려사항
처음에는 "가장 좋은" 솔루션을 찾으려고 했지만, 결국 "우리 상황에 가장 적합한" 솔루션을 찾는 것이 더 현명한 선택이었습니다.
마치며
비밀키 관리는 DevOps에서 피할 수 없는 주제입니다. 이번 경험을 통해 여러 방법들을 직접 사용해보면서, 각 도구의 장단점을 몸소 체험할 수 있었습니다.
여러분도 환경을 구성하실 때, 이 글이 조금이나마 도움이 되길 바랍니다. 궁금한 점이나 다른 경험이 있으시면 댓글로 공유해주세요!
참고 자료
'Network' 카테고리의 다른 글
| Istio로 대규모 트래픽 관리하기: 실전 적용 사례와 확장 전략 (2) | 2026.01.10 |
|---|---|
| EKS에서 ArgoCD로 GitOps 구축하기: 순환 의존성 해결부터 Single Sync까지 (0) | 2026.01.05 |
| 실전 Kubernetes 보안: 4C 모델을 프로덕션 클러스터에 적용하며 배운 것들 (0) | 2026.01.04 |
| GitHub Actions Docker CI 파이프라인 구조 이해하기: 멀티 아키텍처 빌드부터 캐시 전략 (1) | 2026.01.03 |
| Prometheus 디스코드 웹훅 (0) | 2025.10.02 |
