Files

12 KiB

K3s-specific 예시

모든 config 파일과 manifest는 1000+ 서비스 production 기준. YAML은 kubectl apply --server-side --dry-run=server 통과. config.yaml은 k3s server --help와 공식 docs에 대응하는 키만 사용.


좋은 예시 1: prod server config.yaml (disable 세트 + audit + etcd snapshot S3)

# /etc/rancher/k3s/config.yaml
# Single source of truth, identically applied to every server node via Ansible/CI.
write-kubeconfig-mode: "0640"

# --- Cluster network (must match on ALL server nodes) ---
cluster-cidr: "10.42.0.0/16"
service-cidr: "10.43.0.0/16"
cluster-dns: "10.43.0.10"
cluster-domain: "cluster.local"
flannel-backend: "vxlan"

# --- Disable packaged components (prod defaults) ---
disable:
  - traefik
  - servicelb
  - local-storage
disable-cloud-controller: false
disable-network-policy: false
disable-helm-controller: false

# --- TLS SAN for kube-apiserver cert ---
tls-san:
  - "k3s.prod.example.internal"
  - "10.0.1.10"
  - "10.0.1.11"
  - "10.0.1.12"

# --- Audit logging ---
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"
  - "feature-gates=ServerSideApply=true"

# --- Kubelet hardening ---
kubelet-arg:
  - "config=/etc/rancher/k3s/kubelet.yaml"

# --- etcd snapshot to S3 (every 6h, keep 72h) ---
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: 12
etcd-s3: true
etcd-s3-endpoint: "s3.ap-northeast-2.amazonaws.com"
etcd-s3-bucket: "k3s-etcd-backups-prod"
etcd-s3-region: "ap-northeast-2"
etcd-s3-folder: "prod-cluster"
# etcd-s3-access-key / etcd-s3-secret-key loaded from /etc/rancher/k3s/.env via systemd EnvironmentFile

# --- Registries mirror (cluster-internal pull accelerator) ---
# Not using embedded-registry (Spegel); using external Harbor mirror instead.
# See /etc/rancher/k3s/registries.yaml.

# --- Node labels / taints applied to this server's kubelet ---
node-label:
  - "example.com/role=control-plane"
  - "example.com/environment=prod"
node-taint:
  - "node-role.kubernetes.io/control-plane=:NoSchedule"

왜 좋은가:

  • cluster-cidr / service-cidr / cluster-dns / cluster-domain / flannel-backend / disable 세트가 Git 하나의 파일에 고정 → 다음 server 노드 조인 시 mismatch 불가
  • audit log 설정이 kube-apiserver에 강제 주입됨 (SOC2/ISO27001 요구)
  • etcd snapshot이 6시간 주기 + S3 업로드로 DR 대비
  • ssm key는 파일에 없고 systemd EnvironmentFile로 주입 (Secret을 Git에 박지 않음)

좋은 예시 2: config.yaml.d drop-in 분할 (역할별 파일)

# /etc/rancher/k3s/config.yaml.d/10-networking.yaml
cluster-cidr: "10.42.0.0/16"
service-cidr: "10.43.0.0/16"
flannel-backend: "vxlan"
# /etc/rancher/k3s/config.yaml.d/20-audit.yaml
kube-apiserver-arg:
  - "audit-log-path=/var/log/k3s/audit.log"
  - "audit-policy-file=/etc/rancher/k3s/audit-policy.yaml"
# /etc/rancher/k3s/config.yaml.d/30-etcd-backup.yaml
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: 12
etcd-s3: true
etcd-s3-endpoint: "s3.ap-northeast-2.amazonaws.com"
etcd-s3-bucket: "k3s-etcd-backups-prod"

왜 좋은가:

  • 역할별 파일 = 팀별 CODEOWNERS 분리 (네트워크 / 감사 / DR)
  • 변경 diff가 좁아짐
  • K3s는 drop-in 파일들을 병합해서 로드

좋은 예시 3: Traefik을 유지해야 할 때 (dev 클러스터) HelmChartConfig

apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
  labels:
    app.kubernetes.io/name: traefik
    app.kubernetes.io/instance: traefik-dev
    app.kubernetes.io/component: ingress-controller
    app.kubernetes.io/part-of: platform
    app.kubernetes.io/managed-by: argocd
    example.com/environment: dev
spec:
  valuesContent: |-
    deployment:
      replicas: 2
    ports:
      web:
        forwardedHeaders:
          trustedIPs:
            - 10.0.0.0/8
            - 172.16.0.0/12
        proxyProtocol:
          trustedIPs:
            - 10.0.0.0/8
      websecure:
        tls:
          enabled: true
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 256Mi
    podSecurityContext:
      runAsNonRoot: true
      runAsUser: 65532
      seccompProfile:
        type: RuntimeDefault
    metrics:
      prometheus:
        enabled: true
        serviceMonitor:
          enabled: true

왜 좋은가:

  • packaged manifest를 직접 수정하지 않음
  • metadata.name + namespace가 K3s 생성 HelmChart와 일치 → override가 merge됨
  • secret/TLS 민감값은 valuesSecrets로 분리 가능 (이 예시는 non-sensitive만 보여줌)
  • ServiceMonitor 활성화로 observability 자동 연결

좋은 예시 4: embedded registry mirror (Spegel) opt-in + registries.yaml

# /etc/rancher/k3s/config.yaml (partial — applied to every node, server AND agent)
embedded-registry: true
# /etc/rancher/k3s/registries.yaml
mirrors:
  docker.io:
    endpoint:
      - "https://harbor.prod.example.internal"
  registry.k8s.io:
    endpoint:
      - "https://harbor.prod.example.internal"
  "*":
    # Spegel will also share images between nodes via p2p
configs:
  "harbor.prod.example.internal":
    auth:
      username: "robot$k3s-pull"
      password: "__HARBOR_PULL_TOKEN__"
    tls:
      insecure_skip_verify: false
      ca_file: "/etc/rancher/k3s/harbor-ca.crt"

네트워크 전제 (반드시 검증):

# From each node, to every other node:
nc -zv <other-node-ip> 5001   # Spegel p2p gossip
nc -zv <other-node-ip> 6443   # Local registry + K3s supervisor

왜 좋은가:

  • 공식 문서 기준 포트 (TCP 5001 + TCP 6443) 정확히 반영
  • external mirror (Harbor) + intra-cluster p2p 공유 조합 → airgap 경계 대비
  • embedded-registry: true모든 노드 (server+agent)의 config.yaml에 동일하게 박혀야 함

좋은 예시 5: local-path를 dev/test에만 제한 (StorageClass 레벨)

# local-path: default false, only used when explicitly requested
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-path
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"
  labels:
    app.kubernetes.io/name: local-path
    app.kubernetes.io/instance: local-path-dev
    app.kubernetes.io/component: storage
    app.kubernetes.io/part-of: platform
    app.kubernetes.io/managed-by: argocd
    example.com/environment: dev
    example.com/storage-tier: local-ephemeral
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-replicated
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
  labels:
    app.kubernetes.io/name: longhorn
    app.kubernetes.io/instance: longhorn-prod
    app.kubernetes.io/component: storage
    app.kubernetes.io/part-of: platform
    app.kubernetes.io/managed-by: argocd
    example.com/environment: prod
    example.com/storage-tier: replicated-persistent
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
parameters:
  numberOfReplicas: "3"
  staleReplicaTimeout: "30"
  fromBackup: ""
  fsType: "ext4"
  dataLocality: "best-effort"

왜 좋은가:

  • local-path는 default가 아니고 example.com/storage-tier: local-ephemeral로 dev에서만 수용
  • prod default는 Longhorn replicated (3 replica) + reclaimPolicy: Retain
  • DB/Vault/MinIO PVC는 storageClassName: longhorn-replicated 명시

좋은 예시 6: registries.yaml에서 production pull-through mirror

# /etc/rancher/k3s/registries.yaml (every node)
mirrors:
  docker.io:
    endpoint:
      - "https://harbor.prod.example.internal/v2/dockerhub-proxy"
  quay.io:
    endpoint:
      - "https://harbor.prod.example.internal/v2/quay-proxy"
  registry.k8s.io:
    endpoint:
      - "https://harbor.prod.example.internal/v2/k8s-proxy"
  ghcr.io:
    endpoint:
      - "https://harbor.prod.example.internal/v2/ghcr-proxy"
configs:
  "harbor.prod.example.internal":
    tls:
      ca_file: "/etc/rancher/k3s/harbor-ca.crt"
    auth:
      username: "robot$k3s-pull"
      password: "__HARBOR_PULL_TOKEN__"

왜 좋은가:

  • public registry rate limit / downtime이 클러스터 pull을 못 죽임
  • Harbor에서 CVE scan + image signing 검증
  • 모든 노드에 동일 파일 (Ansible/Fleet push)

나쁜 예시 1: packaged traefik.yaml 직접 edit

ssh k3s-server-1
sudo vim /var/lib/rancher/k3s/server/manifests/traefik.yaml
# added forwardedHeaders.trustedIPs inline
sudo systemctl restart k3s

문제: K3s는 재시작 시 이 파일을 packaged 기본값으로 overwrite한다. 커스터마이징이 조용히 사라지고 서버별로 drift까지 생긴다. HelmChartConfig만 허용되는 경로.


나쁜 예시 2: server 간 서로 다른 critical 플래그

# k3s-server-1: /etc/rancher/k3s/config.yaml
cluster-cidr: "10.42.0.0/16"
disable: [ traefik, servicelb ]
# k3s-server-2: /etc/rancher/k3s/config.yaml
cluster-cidr: "10.44.0.0/16"     # mismatched
disable: [ traefik ]             # mismatched

문제: critical configuration value mismatch 로 server-2의 join이 실패하거나, 최악의 경우 이전 값이 캐시되어 silent drift가 생긴다. critical 값은 Git 하나의 파일로 통일해야 한다.


나쁜 예시 3: embedded registry mirror를 켜고 firewall 포트 미개방

# /etc/rancher/k3s/config.yaml (all nodes)
embedded-registry: true
# On each node, firewalld / iptables only allows 6443, 10250, 8472
# Port 5001 is CLOSED between nodes

문제: Spegel은 TCP 5001 (p2p) + TCP 6443 (registry + supervisor) 양쪽이 모든 노드 간 reachable해야 한다. 5001이 막혀있으면 p2p gossip 실패로 image sharing이 작동하지 않고, pull 지연이 오히려 커진다. 공식 기준: https://docs.k3s.io/installation/registry-mirror.


나쁜 예시 4: 운영 AddOn을 서버마다 scp로 push

scp ingress-custom.yaml k3s-server-1:/var/lib/rancher/k3s/server/manifests/
# forgot server-2 and server-3

문제: K3s는 이 디렉터리를 server 간 동기화하지 않는다. 리더가 server-2로 바뀌면 AddOn이 사라진 것처럼 보인다. Git + ArgoCD/Flux가 단일 진입점이어야 한다.


나쁜 예시 5: prod postgres StatefulSet을 local-path에 배치

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-postgres-0
  namespace: prod-data-postgres
spec:
  storageClassName: local-path
  accessModes: [ ReadWriteOnce ]
  resources:
    requests:
      storage: 200Gi

문제: local-path = 노드 hostPath. 노드가 죽으면 PVC 데이터도 죽는다. 스냅샷 불가, 복제 불가, 마이그레이션 불가. prod DB는 Longhorn replicated / Ceph RBD / 외부 CSI 필수.


나쁜 예시 6: Traefik 유지하면서 HelmChartConfig 이름을 잘못 박음

apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik-custom          # WRONG: must match the packaged HelmChart name
  namespace: kube-system
spec:
  valuesContent: |-
    deployment:
      replicas: 3

문제: HelmChartConfigmetadata.name은 K3s가 생성한 HelmChart이름·namespace 모두 일치해야 override가 merge된다. traefik-custom은 무시되고, override가 반영되지 않는다. 올바른 이름은 traefik.