init: 폴더구조 설계 및 인프라 설계
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# 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 유지
|
||||
Reference in New Issue
Block a user