
사이드 프로젝트 실제 구성으로 본 GitOps, Secret Supply Chain, Observability, DR 설계
쿠버네티스는 설치보다 운영이 어려워요. 특히 단일 노드 k3s는 시작은 쉽지만, 운영 관점에서는 SPOF·비밀관리·복구전략 같은 문제를 피할 수 없어요.
이번 글에서는 사이드 프로젝트 클러스터에서 실제로 적용한 구조를 중심으로, “홈랩을 넘어 운영 가능한 최소 플랫폼”을 어떻게 만들었는지 정리했어요.
핵심은 kubectl을 영구 운영 도구로 쓰지 않는 거예요. bootstrap 단계는 딱 한 번만 사람이 실행하고, 그 이후 상태 수렴은 전부 Argo CD가 담당해요.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/gwak2837/....git
targetRevision: main
path: k8s/argocd
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
이 패턴의 장점은 운영자 의존성을 줄인다는 점이에요. “누가 언제 클러스터에서 무엇을 고쳤는지” 대신 “Git commit이 무엇을 바꿨는지”로 사고할 수 있어요.
sync-wave로 명시해 부팅 순서를 제어실무에서 가장 자주 깨지는 건 컴포넌트 간 선후관계예요. 이 구성은 순서를 명시적으로 모델링해요:
0: Vault1: External Secrets Operator2: cloudflared / MinIO Operator3: monitoring / MinIO tenant4: Velero / 앱예를 들어 Vault 앱은 멀티소스(Helm chart + Git values + 추가 리소스)와 wave를 함께 써요.
name: platform-vault
annotations:
# Ensure Vault is created before ESO/apps that depend on it.
argocd.argoproj.io/sync-wave: '0'
spec:
project: platform
sources:
- repoURL: https://helm.releases.hashicorp.com
chart: vault
targetRevision: 0.32.0
helm:
releaseName: vault
valueFiles:
- $values/k8s/platform/vault/vault.values.yaml
- repoURL: https://github.com/gwak2837/....git
targetRevision: main
ref: values
- repoURL: https://github.com/gwak2837/....git
targetRevision: main
path: k8s/platform/vault
syncPolicy:
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
여기서 중요한 포인트는 “구성 자체가 의존성 문서”가 된다는 거예요. 운영자가 머릿속으로 순서를 외우지 않아도 되고, 신규 온보딩도 빨라져요.
이 환경은 Git에 시크릿을 커밋하지 않아요. Git에는 ExternalSecret과 SecretStore 같은 선언만 있고, 실제 값은 Vault에만 있어요.
또 하나 중요한 결정은 ClusterSecretStore를 비활성화한 점이에요. 권한 범위를 네임스페이스 단위로 강제해서 blast radius를 줄일 수 있어요.
installCRDs: true
# Reduce privileges/attack surface: we use namespaced SecretStore + ExternalSecret only.
processClusterStore: false
processClusterExternalSecret: false
processClusterGenerator: false
processClusterPushSecret: false
crds:
createClusterSecretStore: false
createClusterExternalSecret: false
createClusterGenerator: false
createClusterPushSecret: false
네임스페이스마다 eso-vault SA + Vault role을 분리해서 app-prod, app-stg, monitoring이 서로의 경로를 읽지 못해요.
이 프로젝트에서 보안은 선언적으로 설정됐어요.
automountServiceAccountToken: falseallowPrivilegeEscalation: false, capabilities.drop: [ALL], seccompProfile: RuntimeDefaultrestricted 강제NetworkPolicy로 ingress source를 app-web, app-backend로 제한예외적으로 Velero node-agent는 hostPath/mountPropagation이 필요해서 네임스페이스를 privileged로 분리했어요.
labels:
# Velero node-agent(filesystem backup)는 hostPath + mountPropagation을 사용해요.
# 그래서 PSA는 별도 namespace에서 privileged로 분리하는 걸 권장해요.
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: privileged
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn-version: latest
“전부 restricted”가 아니라 필요 최소 예외를 근거와 함께 남기는 형태예요.
HPA가 돌려면 metrics pipeline이 살아 있어야 해요. k3s/온프레미스에서는 kubelet 인증서/주소 이슈로 metrics-server가 자주 깨지기 때문에, 이 구성은 patch를 넣어 현실적인 가용성을 택해요.
# k3s/온프레미스에서 kubelet 인증서/주소 문제로 metrics가 안 뜨는 경우가 많아서 기본 플래그를 추가해요.
- target:
group: apps
version: v1
kind: Deployment
name: metrics-server
namespace: kube-system
patch: |-
- op: add
path: /spec/template/spec/containers/0/args/-
value: --kubelet-insecure-tls
- op: add
path: /spec/template/spec/containers/0/args/-
value: --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
또 kube-prometheus-stack 값에서 k3s 특성(컨트롤플레인 scrape 부재, EndpointSlice 전환)을 반영해 노이즈를 줄였어요. 이건 “모니터링을 붙였다”가 아니라 “모니터링이 운영을 방해하지 않게 튜닝했다”에 가까워요.
GitOps는 desired state 복원에는 강하지만, stateful data는 못 복원해요. 그래서 백업을 두 축으로 가져갔어요.
이 구조 덕분에 복구 시나리오를 두 가지로 운영할 수 있어요.
장점도 분명하지만 비용도 있어요.
즉, 이 구성의 본질은 “최소한의 컴포넌트로 운영 원칙을 먼저 세운 뒤, 노드 수와 장애도메인을 확장하는 전략”이에요.
좋은 Kubernetes 운영은 “도구를 많이 쓰는 것”이 아니라, 의존성·권한·복구 가능성을 선언적으로 설계하고 계속 검증하는 일이에요. 사이드 프로젝트 구성에서 배운 핵심은 세 가지예요.
단일 노드에서도 여기까지 정리해 두면, 멀티노드/멀티클러스터로 확장할 때 “다시 설계”가 아니라 “스케일 확장”만 하면 돼요.