# network / ingress / TLS 기준 ## 목적 이 문서는 K3s/Kubernetes(1000+ 서비스) 환경에서 - 어떤 Service 타입을 언제 쓸지 - 외부 공개는 Ingress/Gateway 어디로 할지 - TLS를 어디서 종료할지 - 인증서는 누가 발급·회전할지 - NetworkPolicy로 L3/L4 경계를 어떻게 그을지 를 단일 ground truth로 고정한다. 이 문서의 목표는 다음과 같다. - 외부 attack surface를 최소화한다 - `ClusterIP`/`NodePort`/`LoadBalancer`/`Ingress`의 역할을 섞지 않는다 - 모든 Ingress는 cert-manager 발급 TLS + HTTPS redirect + HSTS + TLS 1.2+ 기본 - K3s 기본 Traefik을 유지하되 packaged manifest는 수정하지 않는다 - Keycloak/Vault/DB 같은 민감 컴포넌트의 노출 범위를 manifest로 증명한다 ## 공식 의미 (근거) - Service 기본 타입은 `ClusterIP`. 외부 L4 노출은 `NodePort` 또는 `LoadBalancer`, 외부 L7은 Ingress 또는 Gateway API. - Ingress v1 API는 GA이지만 spec은 frozen 상태이고, 신규 기능(L4, traffic split, header match)은 Gateway API로 이동 중이다. - Ingress v1은 `spec.ingressClassName` 필드로 컨트롤러를 선택한다. 이전의 `kubernetes.io/ingress.class` annotation은 deprecated이며 1.22에서 공식 deprecation 고지. - Ingress TLS Secret은 타입이 `kubernetes.io/tls`이고 data key는 `tls.crt`, `tls.key`여야 한다. `spec.tls[].hosts`와 `rules[].host`는 일치해야 한다. - cert-manager는 `Issuer`/`ClusterIssuer`, `Certificate`, `CertificateRequest`, `Order`, `Challenge` CRD로 구성된다. `Certificate`가 참조하는 `secretName`에 자동으로 `kubernetes.io/tls` Secret이 생성·갱신된다. - ACME HTTP-01은 public DNS + 80 reachable 필요. DNS-01은 wildcard(`*.example.com`) 발급에 필수이며 DNS provider API credential이 요구된다. - Traefik v2/v3는 `IngressRoute`(CRD) + `Middleware`(CRD)로 L7 정책(redirect, HSTS, rate-limit, auth)을 체계적으로 구성한다. 기본 Ingress API도 annotation으로 일부 기능을 쓸 수 있다. - K3s는 Traefik을 packaged component로 설치한다(`/var/lib/rancher/k3s/server/manifests/traefik.yaml`). packaged manifest 직접 수정은 재설치 시 덮어쓰인다. `HelmChartConfig`로 override한다. - NetworkPolicy는 CNI가 지원해야 enforce된다. K3s 기본 flannel + kube-router policy controller는 v1 NetworkPolicy를 지원한다. ## 기본 규칙 ### 1. 기본 Service 타입은 `ClusterIP` - 내부 통신: `ClusterIP` - 외부 HTTP/HTTPS: Ingress - 외부 TCP/UDP L4: `LoadBalancer`(ServiceLB/MetalLB/클라우드 LB 전제) - `NodePort`는 개발/bootstrap 용도 외 운영 금지. namespace `ResourceQuota.services.nodeports: 0`으로 선제 차단. ### 2. 모든 Service는 named port + `appProtocol` ```yaml ports: - name: http port: 80 targetPort: http protocol: TCP appProtocol: http ``` - `name: http|https|grpc|metrics`로 명명. - `appProtocol` 명시는 Ingress controller/서비스 메시가 L7 처리를 최적화할 수 있게 한다. - container `ports[].name`과 Service `targetPort`를 이름으로 연결해 포트 번호 drift를 방지. ### 3. Ingress는 `spec.ingressClassName: traefik` 필수 - `kubernetes.io/ingress.class` annotation은 **deprecated**. 어떤 Ingress에도 남기지 않는다. - 컨트롤러가 여러 개인 클러스터(예: Traefik + internal-only NGINX)는 `IngressClass` 리소스를 만들어 class를 명시한다. - 기본 클래스는 `ingressclass.kubernetes.io/is-default-class: "true"` annotation으로 한 개만 지정. ### 4. 외부 HTTPS는 cert-manager ClusterIssuer로 발급 - 운영 공인 도메인: `letsencrypt-prod` ClusterIssuer(ACME HTTP-01) 기본. - Wildcard/internal CA: DNS-01(`letsencrypt-prod-dns`) 또는 Vault PKI issuer. - Staging 검증: `letsencrypt-staging` ClusterIssuer로 선행 테스트 후 prod 전환. - Ingress에는 annotation으로 issuer 지정: `cert-manager.io/cluster-issuer: letsencrypt-prod`. cert-manager가 Certificate + Secret을 자동 생성·갱신한다. - Certificate CRD를 명시적으로 선언하는 방식도 허용(공유 Secret 재사용, 세밀한 `duration`/`renewBefore` 제어 필요 시). ### 5. HTTPS redirect + HSTS + TLS 1.2+ 기본 - 모든 외부 Ingress는 HTTP → HTTPS 영구 리다이렉트. - HSTS: `max-age=31536000; includeSubDomains; preload` 기본. - TLS minVersion: `VersionTLS12`(가능하면 1.3). 취약 cipher(RC4, 3DES) disable. - Traefik에서는 `Middleware`(redirectScheme, headers) + `TLSOption` CRD로 정책을 선언. Ingress annotation 방식 예: - `traefik.ingress.kubernetes.io/router.entrypoints: websecure` - `traefik.ingress.kubernetes.io/router.middlewares: default-hsts@kubernetescrd,default-https-redirect@kubernetescrd` - `traefik.ingress.kubernetes.io/router.tls: "true"` ### 6. Ingress host는 환경별로 분리, wildcard 남용 금지 - dev/staging/prod별 호스트 분리(`auth.dev.example.com`, `auth.staging.example.com`, `auth.example.com`). - Wildcard 인증서는 플랫폼 수준 Certificate로 관리하고 서비스 Ingress가 `secretName` 재사용. - `defaultBackend`(host 없음) 금지. host가 명시된 rule만 허용. ### 7. 외부 공개 범위 = "반드시 공개해야 하는 path"만 - Keycloak: `/realms/`, `/resources/`, `/.well-known/`만 노출. `/admin/`, `/metrics`, `/health`는 공개 금지. - 관리 포트(Keycloak 9000, Vault 8201, Postgres 5432, Redis 6379)는 Ingress 경유 금지. - 내부 도구(Argo CD, Grafana, Kibana)는 VPN/zero-trust proxy(예: Pomerium, cloudflared tunnel)로만 노출. ### 8. TLS 종료 위치와 내부 재암호화 정책 - 기본: Ingress(Traefik)에서 TLS 종료, 내부 Pod까지는 ClusterIP 경유 평문. - 민감 backend(Vault, Keycloak token endpoint)는 **Ingress→Pod 재암호화** 검토. Traefik `serversTransport` + `insecureSkipVerify: false`로 backend TLS 사용. - E2E mTLS가 필요하면 서비스 메시(Linkerd/Istio) 도입을 별도 ADR로 결정. ### 9. K3s 기본 Traefik은 유지·격리 - packaged manifest(`traefik.yaml`) 직접 수정 금지. - 커스터마이징은 `HelmChartConfig`(`kind: HelmChartConfig` in `helm.cattle.io/v1`)로 override. - Traefik은 `ingress-traefik` namespace에 격리, PSA `baseline`, NetworkPolicy는 80/443/8443 inbound + 모든 app namespace outbound 허용. ### 10. ServiceLB(klipper-lb) / MetalLB 결정 - 단일 노드 또는 on-prem 초기: K3s ServiceLB. - 다중 노드 + BGP/ARP 정책이 필요: MetalLB(`kubectl get deploy -n kube-system | grep servicelb`가 없어야 함, `--disable=servicelb`로 off). - 클라우드(EKS/GKE/AKS): cloud-provider LoadBalancer가 우선. - 이 결정이 ADR로 고정되기 전에는 `LoadBalancer` Service를 새로 만들지 않는다. ### 11. NetworkPolicy는 namespace default-deny 기본 모든 운영 namespace는 다음 3종 + 서비스별 allow가 기본 세트다. 1. `default-deny-all` (ingress+egress) 2. `allow-dns-egress` (to `kube-system` `k8s-app=kube-dns`, 53/UDP+TCP) 3. `allow-from-ingress-traefik` (특정 app Pod만 허용) ### 12. NetworkPolicy `from`/`to` 엔트리 AND/OR 규칙 - **동일 엔트리 내 `namespaceSelector`+`podSelector`** → AND(교집합). 권장 패턴. - **별도 엔트리로 분리** → OR(합집합). 거의 항상 버그. - `ipBlock`은 같은 엔트리 내 `namespaceSelector`/`podSelector`와 함께 쓸 수 없다. 외부 CIDR allow는 별도 엔트리. ### 13. egress NetworkPolicy는 DNS 먼저, 서비스별 allow 나중 - `default-deny-all`만 적용하면 DNS 해석 실패로 앱이 기동 불가. - kube-dns 53/UDP+TCP가 첫 번째 allow. - 외부 API(OIDC issuer, SMTP, S3)는 FQDN이 아니라 IP CIDR로 나와야 v1 NetworkPolicy로 표현 가능. FQDN 기반 egress가 필요하면 Cilium `CiliumNetworkPolicy` 또는 egress gateway 검토. ### 14. Prometheus scrape는 ingress rule로 열기 - `monitoring` namespace의 Prometheus Pod만 허용. - `namespaceSelector: kubernetes.io/metadata.name=monitoring` + `podSelector: app.kubernetes.io/name=prometheus` AND. - 포트는 `metrics`(9090/9100 등) 전용, 앱 `http` 포트 재사용 금지. ### 15. Gateway API는 단계적 도입 - 신규 요구사항(traffic split, header routing, gRPC filter)이 Ingress v1으로 표현 불가하면 Gateway API 검토. - 전환은 서비스 단위로 Ingress → `HTTPRoute`로 마이그레이션. `GatewayClass`/`Gateway`는 platform-team 소유. ### 16. health/metrics/admin endpoint 외부 공개 금지 - `/actuator/*`, `/debug/pprof/*`, `/admin/*`, `/metrics`는 Ingress path에 포함하지 않는다. - 별도 Service 포트(`name: metrics`)를 만들고 NetworkPolicy로 Prometheus만 허용. ### 17. Ingress 경로 설계는 prefix + 명시 + 최소 - `pathType: Prefix` 명시(`ImplementationSpecific` 금지). - `/`를 바로 노출하기 전 사용자 경로만 선언 가능한지 검토(Keycloak 패턴 참조). - Path rewrite가 필요하면 Traefik `Middleware.stripPrefix`를 사용하고 annotation으로 명시. ### 18. ExternalName/headless Service는 용도에 맞춰 - `ExternalName`은 클러스터 외부 CNAME alias 용도. 인증/TLS 경계와 별개 고려. - Headless(`clusterIP: None`)는 StatefulSet DNS, client-side LB 용도. Ingress 대상 아님. ### 19. 현재 스택 기본 권장안 #### auth-server / test-server - Service: `ClusterIP` with named `http`, `metrics` - Ingress: `ingressClassName: traefik`, cert-manager `letsencrypt-prod`, HSTS + HTTPS redirect - NetworkPolicy: default-deny + dns + ingress-traefik + db + vault + prometheus #### keycloak - Service: `ClusterIP`, named `http`(8080), `management`(9000) - Ingress: `/realms/`, `/resources/`, `/.well-known/`만 노출. 9000 포트는 Service로도 cluster 외부 비공개. - Certificate: 전용(`sso.example.com`), 전용 TLS Secret #### vault / db / migration-flyway - Ingress 없음. ClusterIP only. 접근은 bastion + `kubectl port-forward` 또는 zero-trust proxy. #### minio - API/Console Ingress 분리. Console은 내부 전용, API는 필요 시 signed URL 중심. #### ingress-traefik - `ingress-traefik` namespace 격리, PSA `baseline` - `Service type=LoadBalancer`(ServiceLB/MetalLB) 또는 `hostPort` 80/443만 ## 프로젝트 기준 요약 - 기본 Service 타입은 `ClusterIP`, named port 필수 - 모든 Ingress는 `spec.ingressClassName: traefik`, annotation `kubernetes.io/ingress.class` 금지 - 모든 외부 HTTPS는 cert-manager ClusterIssuer 발급 + HSTS + HTTP→HTTPS redirect + TLS 1.2+ - Keycloak/Vault/DB 노출 범위는 path/host로 증명, 관리 포트 비공개 - K3s Traefik은 packaged manifest 직접 수정 금지, `HelmChartConfig` override - NetworkPolicy default-deny + DNS allow + ingress-traefik allow 기본 세트 - `namespaceSelector`+`podSelector` AND/OR 차이를 정확히 사용 - Gateway API는 단계적 도입, 기존 Ingress 유지