Files
project-infra/docs/standards/infra/network-ingress-tls.md
T

11 KiB

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[].hostsrules[].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

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 유지