418 lines
12 KiB
Markdown
418 lines
12 KiB
Markdown
# architecture / environments 예시
|
|
|
|
이 파일의 모든 YAML은 `kubectl apply --server-side --dry-run=server` 에 통과해야 한다.
|
|
모든 예시는 1000+ 서비스 운영 기준으로 작성되었고, 단독으로 복붙해서 바로 apply 할 수 있도록 self-contained 하다.
|
|
|
|
---
|
|
|
|
## 좋은 예시 1: namespace에 환경 · 도메인 · PodSecurity · 운영 label 전부 박기
|
|
|
|
```yaml
|
|
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)
|
|
|
|
```text
|
|
k8s/
|
|
base/
|
|
app/
|
|
units/
|
|
identity/
|
|
auth/
|
|
kustomization.yaml
|
|
deployment.yaml
|
|
service.yaml
|
|
servicemonitor.yaml
|
|
pdb.yaml
|
|
hpa.yaml
|
|
plugins/
|
|
ingress-nginx/
|
|
cert-manager/
|
|
external-secrets/
|
|
managing/
|
|
flyway-migrate-identity/
|
|
overlays/
|
|
dev/
|
|
kustomization.yaml
|
|
staging/
|
|
kustomization.yaml
|
|
prod/
|
|
kr-main/
|
|
kustomization.yaml
|
|
patches/
|
|
auth-replicas.yaml
|
|
auth-resources.yaml
|
|
auth-topology-spread.yaml
|
|
kr-dr/
|
|
kustomization.yaml
|
|
patches/
|
|
auth-replicas.yaml
|
|
auth-image-pull-mirror.yaml
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- 1000+ 서비스 스케일에서 단일 overlay/prod로는 region 차이를 표현할 수 없다. region이 overlay 하위 계층이 되어야 한다
|
|
- base는 region·환경을 모른다 (원칙 충족)
|
|
- DR region은 base의 image pull spec만 mirror로 패치하고 나머지는 공유
|
|
|
|
---
|
|
|
|
## 좋은 예시 3: SLO tier 별 기본 default per namespace
|
|
|
|
```yaml
|
|
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-tier` label이 cluster-wide 쿼리 축 제공 (`kubectl get ns -l example.com/slo-tier=tier-1`)
|
|
|
|
---
|
|
|
|
## 좋은 예시 4: K3s packaged component disable을 bootstrap 레벨에서 선언
|
|
|
|
```yaml
|
|
# /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 패턴
|
|
|
|
```yaml
|
|
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/exposure` label로 cluster-wide audit 쿼리 가능
|
|
|
|
---
|
|
|
|
## 나쁜 예시 1: `default` namespace에 prod workload
|
|
|
|
```yaml
|
|
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에 없음)
|
|
|
|
```yaml
|
|
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/` 디렉터리에 운영 리소스 직접 배치
|
|
|
|
```text
|
|
/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 포함
|
|
|
|
```yaml
|
|
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에는 불변 3종(`name`/`instance`/`component`)만.
|
|
|
|
---
|
|
|
|
## 나쁜 예시 5: 같은 hostname을 dev와 prod가 공유
|
|
|
|
```yaml
|
|
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 커스터마이즈
|
|
|
|
```bash
|
|
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` 사용.
|