12 KiB
architecture / environments 예시
이 파일의 모든 YAML은 kubectl apply --server-side --dry-run=server 에 통과해야 한다.
모든 예시는 1000+ 서비스 운영 기준으로 작성되었고, 단독으로 복붙해서 바로 apply 할 수 있도록 self-contained 하다.
좋은 예시 1: namespace에 환경 · 도메인 · PodSecurity · 운영 label 전부 박기
apiVersion: v1
kind: Namespace
metadata:
name: prod-identity-auth
labels:
app.kubernetes.io/name: auth
app.kubernetes.io/instance: auth-prod
app.kubernetes.io/version: "1.24.3"
app.kubernetes.io/component: api
app.kubernetes.io/part-of: identity-platform
app.kubernetes.io/managed-by: argocd
example.com/environment: prod
example.com/team: identity-sre
example.com/tier: backend
example.com/slo-tier: tier-1
example.com/data-classification: confidential
example.com/cost-center: cc-1042
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
annotations:
example.com/owner-email: identity-sre@example.com
example.com/runbook: https://runbooks.example.com/identity/auth
example.com/slo-doc: https://slo.example.com/identity/auth
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: default-quota
namespace: prod-identity-auth
labels:
app.kubernetes.io/name: auth
app.kubernetes.io/instance: auth-prod
app.kubernetes.io/component: quota
app.kubernetes.io/part-of: identity-platform
app.kubernetes.io/managed-by: argocd
example.com/environment: prod
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
pods: "200"
persistentvolumeclaims: "20"
---
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: prod-identity-auth
labels:
app.kubernetes.io/name: auth
app.kubernetes.io/instance: auth-prod
app.kubernetes.io/component: limits
app.kubernetes.io/part-of: identity-platform
app.kubernetes.io/managed-by: argocd
example.com/environment: prod
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: 512Mi
defaultRequest:
cpu: "100m"
memory: 128Mi
max:
cpu: "4"
memory: 8Gi
min:
cpu: "10m"
memory: 32Mi
왜 좋은가:
app.kubernetes.io/*well-known 6종이 모두 있고, 운영 축은example.com/*로 분리되어 selector immutability를 깨지 않는다- PodSecurity admission이 namespace 레벨에서
restricted로 강제 → 이후 Pod spec이 noncompliant면 창조 시점에 거부 - ResourceQuota + LimitRange가 namespace 단위로 고정되어 하나의 서비스가 클러스터를 삼킬 수 없다
- 환경(prod)·도메인(identity)·서비스(auth)가 namespace 이름과 label 양쪽에 드러남
좋은 예시 2: multi-region prod overlay 디렉터리 (kr-main + kr-dr)
gitops/
apps/
identity-auth/
base/
kustomization.yaml
deployment.yaml
service.yaml
servicemonitor.yaml
pdb.yaml
hpa.yaml
overlays/
dev/
staging/
prod/
kr-main/
kustomization.yaml
patches/
kr-dr/
kustomization.yaml
patches/
flyway-migrate-identity/
base/
overlays/
platform/
ingress-nginx/
cert-manager/
secret-delivery/
clusters/
dev/main/
staging/main/
prod/kr-main/
prod/kr-dr/
왜 좋은가:
- 1000+ 서비스 스케일에서 단일 overlay/prod로는 region 차이를 표현할 수 없다. region이 overlay 하위 계층이 되어야 한다
- base는 region·환경을 모른다 (원칙 충족)
- DR region은 base의 image pull spec만 mirror로 패치하고 나머지는 공유
좋은 예시 3: SLO tier 별 기본 default per namespace
apiVersion: v1
kind: ConfigMap
metadata:
name: slo-defaults
namespace: prod-identity-auth
labels:
app.kubernetes.io/name: auth
app.kubernetes.io/instance: auth-prod
app.kubernetes.io/component: slo-config
app.kubernetes.io/part-of: identity-platform
app.kubernetes.io/managed-by: argocd
example.com/environment: prod
example.com/slo-tier: tier-1
data:
availability-slo: "99.95"
rpo-minutes: "5"
rto-minutes: "15"
backup-interval-minutes: "15"
multi-az-required: "true"
pdb-min-available-percent: "50"
왜 좋은가:
- SLO/RPO/RTO 숫자가 YAML로 문서화되어 audit 가능
- 같은 tier 정의가 팀마다 제각각 drift 되는 일을 막는다
example.com/slo-tierlabel이 cluster-wide 쿼리 축 제공 (kubectl get ns -l example.com/slo-tier=tier-1)
좋은 예시 4: K3s packaged component disable을 bootstrap 레벨에서 선언
# /etc/rancher/k3s/config.yaml (Git-managed, applied identically to every server node)
write-kubeconfig-mode: "0640"
cluster-cidr: "10.42.0.0/16"
service-cidr: "10.43.0.0/16"
cluster-dns: "10.43.0.10"
cluster-domain: "cluster.local"
disable:
- traefik
- servicelb
- local-storage
disable-network-policy: false
tls-san:
- "k3s.prod.example.internal"
- "10.0.0.10"
kube-apiserver-arg:
- "audit-log-path=/var/log/k3s/audit.log"
- "audit-log-maxage=30"
- "audit-log-maxbackup=10"
- "audit-log-maxsize=100"
- "audit-policy-file=/etc/rancher/k3s/audit-policy.yaml"
kubelet-arg:
- "config=/etc/rancher/k3s/kubelet.yaml"
왜 좋은가:
- prod 스케일에서 traefik / servicelb / local-storage는 전부 외부 컴포넌트로 대체되므로 disable이 기본
- critical config (
cluster-cidr,service-cidr,cluster-dns,cluster-domain)가 Git 하나의 파일에 고정 → 서버 간 mismatch 불가능 - audit log와 kubelet config가 선언형으로 박힘 → 신규 서버 조인 시 drift 없음
좋은 예시 5: 도메인 분리 + public/internal/operator ingress host 패턴
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth-public
namespace: prod-identity-auth
labels:
app.kubernetes.io/name: auth
app.kubernetes.io/instance: auth-prod
app.kubernetes.io/version: "1.24.3"
app.kubernetes.io/component: api
app.kubernetes.io/part-of: identity-platform
app.kubernetes.io/managed-by: argocd
example.com/environment: prod
example.com/exposure: public
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "8m"
spec:
ingressClassName: nginx-public
tls:
- hosts:
- auth.example.com
secretName: auth-public-tls
rules:
- host: auth.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: auth
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth-admin
namespace: prod-identity-auth
labels:
app.kubernetes.io/name: auth
app.kubernetes.io/instance: auth-prod
app.kubernetes.io/component: admin
app.kubernetes.io/part-of: identity-platform
app.kubernetes.io/managed-by: argocd
example.com/environment: prod
example.com/exposure: operator-only
annotations:
cert-manager.io/cluster-issuer: internal-ca
nginx.ingress.kubernetes.io/auth-url: "https://sso.ops.example.com/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://sso.ops.example.com/oauth2/sign_in?rd=$escaped_request_uri"
nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8"
spec:
ingressClassName: nginx-internal
tls:
- hosts:
- auth.ops.example.com
secretName: auth-admin-tls
rules:
- host: auth.ops.example.com
http:
paths:
- path: /actuator
pathType: Prefix
backend:
service:
name: auth
port:
number: 8081
왜 좋은가:
- 한 서비스(auth)가 public API와 operator-only admin 포트를 별도 ingress + 별도 ingressClass + 별도 TLS issuer로 분리
- CIDR whitelist + OAuth2 sso forward-auth가 admin endpoint에 강제
example.com/exposurelabel로 cluster-wide audit 쿼리 가능
나쁜 예시 1: default namespace에 prod workload
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-server
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: auth-server
template:
metadata:
labels:
app: auth-server
spec:
containers:
- name: auth
image: registry.example.com/auth:1.24.3
문제: default namespace는 PodSecurity / Quota / NetworkPolicy를 걸기 위한 격리 단위가 될 수 없고, 다른 팀 리소스와 섞인다. 1000-서비스 환경에서 default는 영구적으로 비워두는 것이 운영 원칙.
나쁜 예시 2: app.kubernetes.io/environment 사용 (well-known label에 없음)
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
namespace: prod-identity-auth
labels:
app.kubernetes.io/name: auth
app.kubernetes.io/environment: prod # invalid well-known key
문제: Kubernetes 공식 well-known label set은 {name,instance,version,component,part-of,managed-by} 6종뿐. environment는 여기 없으므로 자체 도메인(example.com/environment)을 써야 한다. 다른 팀이 app.kubernetes.io/env 같은 변종을 만들어 drift가 퍼진다.
나쁜 예시 3: manifests/ 디렉터리에 운영 리소스 직접 배치
/var/lib/rancher/k3s/server/manifests/auth-prod.yaml
/var/lib/rancher/k3s/server/manifests/keycloak-prod.yaml
/var/lib/rancher/k3s/server/manifests/ingress-nginx.yaml
문제: 멀티 서버 K3s는 이 디렉터리를 서버 간 동기화하지 않는다. 서버 A에만 있는 파일은 서버 B 리더가 되면 사라진 것처럼 보인다. source of truth는 Git + Kustomize여야 한다.
나쁜 예시 4: selector에 버전 / 환경 label 포함
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
namespace: prod-identity-auth
spec:
selector:
matchLabels:
app.kubernetes.io/name: auth
app.kubernetes.io/version: "1.24.3" # changes on every release
example.com/environment: prod # injected by overlay
template:
metadata:
labels:
app.kubernetes.io/name: auth
app.kubernetes.io/version: "1.24.3"
example.com/environment: prod
spec:
containers:
- name: auth
image: registry.example.com/auth:1.24.3
문제: selector.matchLabels는 Deployment/StatefulSet에서 immutable이다. version은 배포마다 바뀌고 environment는 overlay가 주입한다 → 첫 배포 이후 재apply 시 field is immutable 에러로 영구 차단. selector에는 불변 2종(name/instance)만.
나쁜 예시 5: 같은 hostname을 dev와 prod가 공유
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth
namespace: dev-identity-auth
spec:
ingressClassName: nginx-public
rules:
- host: auth.example.com # same as prod
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: auth
port:
number: 8080
문제: 환경 간 host 공유는 TLS cert race, 동일 hostname의 두 ingress 간 routing 불확실성, 외부 모니터링이 어느 환경을 보는지 혼동을 유발한다. dev는 반드시 auth.dev.example.com 같이 별도 hostname을 쓴다.
나쁜 예시 6: K3s traefik manifest 직접 수정으로 prod ingress 커스터마이즈
vim /var/lib/rancher/k3s/server/manifests/traefik.yaml
# added custom middleware config inline
systemctl restart k3s
문제: K3s는 재시작 시 이 파일을 packaged 원본으로 overwrite한다. 운영 커스터마이징이 조용히 사라진다. prod 1000-서비스 스케일에서는 --disable=traefik 후 ingress-nginx를 별도 컴포넌트로 관리하는 것이 유일한 정답. 유지한다면 반드시 HelmChartConfig 사용.