시리즈 소개
시리즈 구성
- 왜 GitOps인가?
- Kubernetes 클러스터와 Traefik
- ExternalDNS - 자동 DNS 레코드 관리
- SOPS + age - 시크릿 암호화 (현재 글)
- ArgoCD - GitOps 배포 자동화
문제: GitOps에서 시크릿은 어떻게 관리할까?
GitOps의 원칙은 “모든 것을 Git에 저장”하는 것입니다. 하지만 API 토큰, 데이터베이스 비밀번호 같은 민감한 정보를 평문으로 Git에 올릴 수는 없습니다.
평문 시크릿을 Git에 커밋하면 히스토리에서 완전히 삭제하기 매우 어렵습니다. 한 번 노출되면 해당 시크릿은 즉시 교체해야 합니다.
시크릿 관리 방법들
| 방법 | 장점 | 단점 |
|---|---|---|
| Vault | 기능 풍부, 동적 시크릿 | 복잡함, 별도 인프라 필요 |
| Sealed Secrets | 클러스터별 암호화 | 클러스터 의존적 |
| External Secrets | 외부 시크릿 저장소 연동 | 외부 서비스 의존 |
| SOPS + age | 단순함, Git 친화적 | 수동 복호화 필요 |
저희는 SOPS + age 조합을 선택했습니다. 이유는:
- 별도 인프라 불필요
- Helmfile과 자연스럽게 통합
- 로컬에서 바로 테스트 가능
- 팀원끼리 공개키만 공유하면 됨
소규모 팀이라면 SOPS + age가 최적의 선택입니다. Vault는 강력하지만 운영 부담이 크고, Sealed Secrets는 클러스터 마이그레이션 시 문제가 됩니다.
SOPS와 age 소개
SOPS (Secrets OPerationS)
Mozilla에서 만든 암호화 도구로, YAML/JSON 파일의 특정 필드만 암호화할 수 있습니다.
apiVersion: v1
kind: Secret
data:
password: c3VwZXJzZWNyZXQ=apiVersion: v1
kind: Secret
data:
password: ENC[AES256_GCM,data:...,iv:...,tag:...]
sops:
age:
- recipient: age1...
lastmodified: "2024-01-01T00:00:00Z"핵심 특징:
data값만 암호화, 키 이름은 그대로 유지- 구조 파악 가능 (어떤 시크릿이 있는지 확인)
- Git diff가 의미있음
age
현대적인 파일 암호화 도구로, GPG의 대안입니다. 간단한 사용법과 보안이 강점입니다.
# 키 생성 (한 줄)
age-keygen -o key.txt
# 암호화
age -r age1... -o secret.enc secret.txt
# 복호화
age -d -i key.txt secret.enc설치 및 설정
1. 설치
brew install sops age2. age 키 생성
# 키 저장 디렉토리 생성
mkdir -p ~/.sops
# 키 생성
age-keygen -o ~/.sops/key.txt생성된 파일 내용:
# created: 2024-01-01T00:00:00+09:00
# public key: age1rklrkck56y95lx83j068p0us0cscutyae7j4ecsh09zwxfje4gvqe2q5ys
AGE-SECRET-KEY-1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXAGE-SECRET-KEY로 시작하는 비밀키는 절대 공유하지 마세요!
이 키가 노출되면 모든 암호화된 시크릿을 복호화할 수 있습니다.
키 파일은 반드시 백업해두세요. 키를 잃어버리면 암호화된 시크릿을 복구할 수 없습니다. 안전한 장소(1Password, 물리적 백업 등)에 보관하세요.
3. 환경 변수 설정
# ~/.zshrc 또는 ~/.bashrc에 추가
export SOPS_AGE_KEY_FILE=$HOME/.sops/key.txt
# 적용
source ~/.zshrc4. .sops.yaml 설정
프로젝트 루트에 .sops.yaml 파일을 생성합니다:
# .sops.yaml
creation_rules:
# Secret이 포함된 파일명 패턴
- path_regex: ".*[sS]ecret.*\\.yaml$"
# 암호화할 YAML 키 (data, stringData만 암호화)
encrypted_regex: ^(data|stringData)$
# 복호화할 수 있는 공개키 목록 (팀원 추가 가능)
age: >-
age1rklrkck56y95lx83j068p0us0cscutyae7j4ecsh09zwxfje4gvqe2q5ys,
age19nw8qsqy6zsvq3ydfx87nxuzmw4w6xm96g8tdvpj9t7ph0qe0y4s7zh3l3사용 방법
시크릿 파일 생성
# secrets/kohi-core-api-secrets.yaml (암호화 전)
apiVersion: v1
kind: Secret
metadata:
name: kohi-core-api-secrets
namespace: application-secrets
type: Opaque
data:
DB_PASSWORD: c3VwZXJzZWNyZXQ=
API_KEY: YWJjZGVmMTIzNDU2
stringData:
REDIS_URL: redis://redis:6379암호화
# 파일 암호화 (in-place)
sops -e -i secrets/kohi-core-api-secrets.yaml복호화 및 적용
# 복호화 후 kubectl에 파이프
sops -d secrets/kohi-core-api-secrets.yaml | kubectl apply -f -편집
# SOPS로 직접 편집 (자동으로 복호화 → 편집 → 암호화)
sops secrets/kohi-core-api-secrets.yamlsops 명령으로 파일을 열면 기본 에디터에서 복호화된 상태로 편집할 수 있습니다.
저장하면 자동으로 다시 암호화됩니다.
Helmfile과 통합
Helmfile은 SOPS를 기본 지원합니다. secrets: 항목으로 지정하면 자동으로 복호화합니다.
디렉토리 구조
infra/
├── helmfiles/
│ ├── .sops.yaml
│ └── releases/
│ ├── helmfile.yaml
│ └── values/
│ └── kohi-core-api-secrets/
│ ├── common.yaml
│ ├── dev.yaml
│ └── secrets/
│ └── secrets.dev.yaml # 암호화된 파일helmfile.yaml 설정
# helmfile.yaml
releases:
- name: kohi-core-api-secrets
namespace: application-secrets
createNamespace: true
chart: ./charts/kohi-core-api-secrets
values:
- ./values/kohi-core-api-secrets/common.yaml
- ./values/kohi-core-api-secrets/{{ .Environment.Name }}.yaml
# SOPS로 암호화된 파일 (자동 복호화)
secrets:
- ./values/kohi-core-api-secrets/secrets/secrets.{{ .Environment.Name }}.yaml
missingFileHandler: Warn배포
# Helmfile이 자동으로 secrets 파일 복호화
helmfile -e dev syncHelmfile의 secrets: 블록에 지정된 파일은 배포 시 자동으로 복호화됩니다.
별도의 sops 명령어 없이 바로 사용할 수 있습니다.
팀원 추가
새 팀원이 시크릿에 접근해야 할 때:
1. 새 팀원이 age 키 생성
age-keygen -o ~/.sops/key.txt
# public key: age19nw8qsqy6zsvq3ydfx87nxuzmw4w6xm96g8tdvpj9t7ph0qe0y4s7zh3l32. 공개키를 .sops.yaml에 추가
# .sops.yaml
creation_rules:
- path_regex: ".*[sS]ecret.*\\.yaml$"
encrypted_regex: ^(data|stringData)$
age: >-
age1rklrkck56y95lx83j068p0us0cscutyae7j4ecsh09zwxfje4gvqe2q5ys,
age19nw8qsqy6zsvq3ydfx87nxuzmw4w6xm96g8tdvpj9t7ph0qe0y4s7zh3l33. 기존 시크릿 재암호화
# 모든 시크릿 파일 재암호화 (새 공개키 추가)
sops updatekeys secrets/kohi-core-api-secrets.yaml이제 새 팀원도 자신의 비밀키로 복호화할 수 있습니다.
팀원이 퇴사하면 반드시 해당 공개키를 .sops.yaml에서 제거하고,
모든 시크릿을 updatekeys로 재암호화하세요.
실제 사용 예시
Docker Hub 자격 증명
# values/dockerhub-secrets/secrets/secrets.dev.yaml
dockerconfigjson: ENC[AES256_GCM,data:{"auths":{"https://index.docker.io/v1/":{...}}},iv:...,tag:...]애플리케이션 시크릿
# values/kohi-core-api-secrets/secrets/secrets.dev.yaml
DB_HOST: ENC[AES256_GCM,data:...,tag:...]
DB_PASSWORD: ENC[AES256_GCM,data:...,tag:...]
REDIS_PASSWORD: ENC[AES256_GCM,data:...,tag:...]
JWT_SECRET: ENC[AES256_GCM,data:...,tag:...]베스트 프랙티스
1. .gitignore 설정
# 비밀키는 절대 커밋하지 않음
*.key
key.txt
# 복호화된 파일 (실수 방지)
*.dec.yaml2. 파일명 규칙
# 암호화 대상임을 명확히
secrets.dev.yaml ✅
secret.prod.yaml ✅
kohi-secrets.yaml ✅
# 일반 values 파일
common.yaml (암호화 안됨)
dev.yaml (암호화 안됨)3. CI/CD에서 사용
GitHub Actions 예시:
- name: Setup SOPS
run: |
echo "${{ secrets.SOPS_AGE_KEY }}" > /tmp/key.txt
echo "SOPS_AGE_KEY_FILE=/tmp/key.txt" >> $GITHUB_ENV
- name: Deploy
run: helmfile -e prod syncCI/CD 환경에서 age 키를 사용할 때는 반드시 GitHub Secrets 등 안전한 저장소를 사용하세요. 로그에 키가 노출되지 않도록 주의하세요.
트러블슈팅
다음 글 예고
다음 글에서는 ArgoCD를 설치하고 ApplicationSet을 사용하여 Git Push만으로 자동 배포되는 환경을 구축합니다. manifest 저장소에 있는 Helm Chart가 클러스터에 자동으로 동기화됩니다.