# infra architecture / environments 기준 ## 목적 이 문서는 1000+ 서비스 규모의 K3s 기반 production 클러스터에서 - 환경을 어떻게 나눌지 - namespace / label / selector를 어떻게 고정할지 - K3s 기본 컴포넌트와 GitOps source of truth를 어떻게 구분할지 - cross-cluster / multi-region / DR(RPO·RTO)을 어떻게 문서화할지 를 먼저 고정한다. 이 문서의 목표: - dev / staging / prod 환경 분리를 **label·namespace·selector 레벨에서** 일관되게 만든다 - 서비스별 리소스 소유권(팀·도메인·컴포넌트)을 label로 쿼리 가능하게 한다 - K3s packaged component와 사용자 AddOn을 혼동하지 않는다 - 멀티 서버에서 `manifests/` 디렉터리를 source-of-truth로 쓰는 사고를 원천 차단한다 - 이후 storage / secrets / ingress / workload / observability 표준의 전제 조건을 고정한다 ## 공식 의미 (근거) - Kubernetes well-known label set (공식, SIG-Apps 공인): `app.kubernetes.io/{name,instance,version,component,part-of,managed-by}` — 총 6개. `environment`는 포함되지 **않는다** (`https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/`). - `app.kubernetes.io/*` 외의 운영 차원(environment, team, tier, region 등)은 **자체 도메인 네임스페이스**(`example.com/*`)를 붙여 선언해야 한다. - K3s는 `coredns`, `traefik`, `local-storage`, `metrics-server`를 packaged component로 제공한다. - `/var/lib/rancher/k3s/server/manifests` 아래 파일은 서버 시작 시와 파일 변경 시 자동 적용된다(AddOn auto-deploy). - packaged component manifest는 K3s가 재기록하므로 직접 수정 금지. - 멀티 서버 K3s는 AddOn 파일을 자동 동기화하지 않는다. - Kustomize v5+부터는 `labels:` 필드(기본 `includeSelectors: false`)가 `commonLabels`보다 안전한 기본이다. `commonLabels`는 항상 `selector.matchLabels`에 주입되며, Deployment/StatefulSet의 selector는 **immutable**이므로 운영 중 label 추가만으로 apply가 실패한다. - GitOps 기본 apply 방식은 **Server-Side Apply** (`kubectl apply --server-side --field-manager=...`)다. CI/ArgoCD/Flux 모두 SSA 기본. ## 기본 규칙 ### 1. 환경은 명시적으로 분리하고 **label + namespace 양쪽에** 박는다 기본 환경: - `dev` - `staging` - `prod` 필요 시 `sandbox` / `canary` / `dr`을 추가할 수 있으나 dev/staging/prod 의미를 흐리지 않는다. 각 리소스는 두 곳에 동시에 환경이 드러나야 한다. - `metadata.namespace` — 물리적 격리 - `metadata.labels["example.com/environment"]` — 쿼리·정책용 (well-known label에는 환경이 없으므로 **자체 도메인 label** 사용) ### 2. 환경 간 혼합 배포 전면 금지 하나의 namespace / hostname / PVC / Secret / TLS cert scope 안에서 서로 다른 환경 리소스가 섞이지 않는다. 금지: - `auth-dev`, `auth-prod`가 같은 namespace 공유 - dev와 prod가 같은 ingress host (`auth.example.com`) 공유 - staging과 prod가 같은 PostgreSQL schema / S3 bucket / Vault mount 공유 - NetworkPolicy / ResourceQuota / LimitRange가 환경 경계를 걸치지 않음 ### 3. namespace 전략은 “환경 prefix + 서비스 이름” 고정 1000+ 서비스 스케일에서 초기에 하나의 포맷을 박는다. 본 표준 권장은: ``` -- ``` 예: - `prod-identity-auth` - `prod-identity-keycloak` - `staging-identity-auth` - `dev-identity-auth` - `prod-platform-ingress-nginx` - `prod-data-postgres-identity` 이유: - `kubectl -n prod-*` 와일드카드 RBAC / 모니터링 쿼리가 쉬움 - `prod-` prefix로 PodSecurity admission (`pod-security.kubernetes.io/enforce=restricted`)을 one-shot으로 강제 가능 - `default` namespace는 production workload 배포 전면 금지 ### 4. `app.kubernetes.io/*` 6종은 전 리소스 필수 모든 워크로드·서비스·ingress·PVC·ConfigMap·Secret에 아래 6개가 반드시 붙는다. - `app.kubernetes.io/name` — 애플리케이션 이름 (예: `auth`) - `app.kubernetes.io/instance` — 인스턴스 (예: `auth-prod`) - `app.kubernetes.io/version` — semver 또는 image tag - `app.kubernetes.io/component` — 역할 (예: `api`, `worker`, `database`) - `app.kubernetes.io/part-of` — 상위 도메인 (예: `identity-platform`) - `app.kubernetes.io/managed-by` — 관리 도구 (예: `kustomize`, `argocd`, `flux`) ### 5. 운영 차원 label은 **자체 도메인**으로 선언 well-known label 6종으로 표현되지 않는 축은 다음 키로 고정한다. - `example.com/environment` — `dev|staging|prod|canary|dr` - `example.com/team` — 소유 팀 (예: `identity-sre`) - `example.com/tier` — `frontend|backend|data|platform` - `example.com/data-classification` — `public|internal|confidential|restricted` - `example.com/cost-center` — FinOps tag - `example.com/slo-tier` — `tier-1|tier-2|tier-3` 금지: - `app.kubernetes.io/environment` 사용 (well-known set에 없음) - 도메인 없는 커스텀 키 (`environment: prod` 같은 top-level key) ### 6. selector에 들어가는 label은 **불변 3종만** Deployment / StatefulSet의 `selector.matchLabels`는 일단 apply 후 수정 불가다. 여기에는 운영 중 **절대 바뀌지 않는** 값만 넣는다. 허용: - `app.kubernetes.io/name` - `app.kubernetes.io/instance` - `app.kubernetes.io/component` 금지 (selector에 넣지 말 것): - `app.kubernetes.io/version` (배포 때마다 바뀜) - `app.kubernetes.io/managed-by` (툴 교체 시 drift) - `example.com/environment` (overlay에서 주입되면 selector immutable 위반) ### 7. K3s packaged component는 “기본 제공”일 뿐 “무조건 사용”이 아니다 다음 컴포넌트는 클러스터 bootstrap 초기에 유지/비활성 결정을 박는다. - `traefik` - `servicelb` - `local-storage` - `metrics-server` - `coredns` (교체는 특수 케이스) 기본: - 무엇을 끄는지 Git에 기록 - packaged manifest 직접 수정 금지 — `--disable` 플래그 또는 `HelmChartConfig` - prod 1000-서비스 스케일에서는 traefik / servicelb 모두 disable 후 **ingress-nginx DaemonSet + MetalLB/외부 LB** 조합이 일반적 ### 8. `/var/lib/rancher/k3s/server/manifests`는 source of truth 아님 이 디렉터리는 AddOn auto-deploy 경로다. 멀티 서버 환경에서 자동 동기화가 **안 되므로**, Git이 source of truth고 이 디렉터리는 apply sink에 지나지 않는다. 기본: - Git repo의 `k8s/` 디렉터리가 SoT - CI/ArgoCD/Flux가 `kubectl apply --server-side`로 push - 서버별 scp / vim 절대 금지 - 멀티 서버 bootstrap AddOn도 Git 관리(예: `k8s/bootstrap/*`를 첫 서버에만 배치) ### 9. GitOps apply는 Server-Side Apply가 기본 ``` kubectl apply --server-side --field-manager= -k kubectl diff --server-side -k ``` 이유: - multi-controller 환경(ArgoCD + HPA + VPA + operator)에서 ownership 충돌을 `managedFields`로 명시적 해결 - `last-applied-configuration` annotation 2MB 한계 회피 - 3-way merge 실패로 인한 silent drift 제거 ### 10. base는 환경 중립, overlay는 환경 차이만 이후 `kustomize.md`에서 상세히 다룬다. 이 문서에서는 원칙만 박는다. - `k8s/base/` — 공통 shape, 환경-agnostic - `k8s/overlays/{dev,staging,prod}/` — patches / images / replicas / resources / labels overlay는 base를 재작성하지 않는다. overlay diff가 100줄을 넘으면 base 설계 실패 신호다. ### 11. `app/managing/plugins` 책임 분리 `k8s/base/` 하위는 다음 3축으로 고정한다. - `app/units///` — 애플리케이션 유닛 (auth, keycloak, test-server) - `managing/` — Job/CronJob 운영 작업 (flyway-migrate, backup, restore, bootstrap admin) - `plugins/` — 플랫폼 (ingress-controller, cert-manager, external-secrets, observability, policy) 이 축은 **소유 팀이 다르다**는 가정 위에 있다. 각 축은 독립된 Git owner (CODEOWNERS)를 가진다. ### 12. 상태 저장 / 외부 공개 범위를 architecture 단계에서 분류 모든 서비스는 아래 2축으로 초기 분류한다. | 축 | 값 | |-----------------|-----------------------------------------------------------------| | workload 성격 | `stateless` / `stateful` / `job` / `cronjob` / `daemonset` | | 공개 범위 | `public` / `internal-only` / `operator-only` / `cluster-only` | 예: - `auth-server` — stateless / public - `keycloak` — stateless(앱) + stateful(외부 DB) / public (관리 포트는 internal-only) - `vault` — stateful / operator-only (+ cluster-only service endpoint) - `minio-tenant` — stateful / internal-only - `postgres-identity` — stateful / cluster-only - `fluent-bit` — daemonset / cluster-only - `flyway-migrate` — job / cluster-only ### 13. SLO·RPO·RTO를 환경 문서에서 먼저 박는다 환경 분리가 의미 있으려면 각 환경의 목표를 숫자로 고정해야 한다. 서비스 tier별로 아래 항목을 환경 문서에서 표로 둔다. | tier | availability SLO | RPO | RTO | backup 주기 | multi-AZ | PDB minAvailable | |--------|------------------|------|------|-------------|----------|------------------| | tier-1 | 99.95% | 5m | 15m | 15m | required | 50% | | tier-2 | 99.9% | 1h | 1h | 1h | required | 1 | | tier-3 | 99.5% | 24h | 4h | 24h | optional | 0 | tier는 `example.com/slo-tier` label로 리소스마다 붙는다. ### 14. 멀티 서버 K3s는 critical config를 Git에서 통일 K3s multi-server에서는 아래가 모든 서버에서 동일해야 한다(불일치 시 `critical configuration value mismatch`로 join 실패). - `cluster-cidr`, `service-cidr`, `cluster-dns`, `cluster-domain` - `disable` 플래그 세트 - `flannel-backend` / CNI 관련 - `embedded-registry` 활성화 여부 기본: - `/etc/rancher/k3s/config.yaml` Git 관리 - 서버별 ad-hoc 수정 금지 - 신규 서버 조인 전 `config.yaml` diff 확인 ### 15. 공개 범위별 ingress host 패턴 고정 - public: `.example.com` - internal: `.internal.example.com` - operator: `.ops.example.com` (mTLS + SSO 필수) - cluster-only: ingress 없음, ClusterIP + NetworkPolicy로만 접근 ### 16. 네이밍 규칙 정리 (요약) - namespace: `--` - Deployment/StatefulSet 이름: `` (namespace로 환경 구분, 이름에 env 중복 금지) - Service 이름: Deployment 이름과 동일 (headless면 `-headless` suffix) - PVC 이름: `--` (StatefulSet volumeClaimTemplate은 자동) - Kustomize overlay 디렉터리: `overlays///` (멀티 region 시) ## 추천 디렉터리 구조 ```text k8s/ base/ app/ shared/ units/ identity/ auth/ kustomization.yaml keycloak/ kustomization.yaml data/ postgres-identity/ kustomization.yaml managing/ flyway-migrate-identity/ backup-postgres/ plugins/ ingress-nginx/ cert-manager/ external-secrets/ kube-prometheus-stack/ overlays/ dev/ kustomization.yaml staging/ kustomization.yaml prod/ kustomization.yaml region-kr-main/ region-kr-dr/ bootstrap/ k3s-addons-disabled/ scripts/ render.sh diff.sh apply.sh ``` ## 프로젝트 기준 요약 - 환경 3종(`dev`/`staging`/`prod`) + namespace prefix 고정 - well-known `app.kubernetes.io/*` 6개 + 자체 도메인 운영 label 필수 - `app.kubernetes.io/environment` 사용 금지, `example.com/environment`로 대체 - selector에는 불변 3종만 - K3s packaged component는 초기에 disable 여부 결정, 직접 수정 금지 - `manifests/`는 apply sink, Git이 SoT - `kubectl apply --server-side` GitOps 기본 - SLO / RPO / RTO 표가 환경 문서의 일부