diff --git a/docs/2025-12-13-k8s-gateway-api/gabiacloud.md b/docs/2025-12-13-k8s-gateway-api/gabiacloud.md new file mode 100644 index 0000000..d4fb233 --- /dev/null +++ b/docs/2025-12-13-k8s-gateway-api/gabiacloud.md @@ -0,0 +1,40 @@ +# Gabiacloud + +- [로드 밸런서 사용하기](https://customer.gabia.com/manual/cloud/23381/24281) +- [nginx.yml](./nginx.yml) + +`nginx.service`는 `clusterIp`로 기본적으로 노출되는 것이 아니라, `NodePort`로 control-plane에 노출됨. + + +## TODO + +- [ ] gabiacloud TLS 인증서를 어떻게 연결하는지 + +```mermaid +sequenceDiagram + autonumber + actor Client as External Client + participant LB as nginx-service (Service: LoadBalancer, EXTERNAL-IP) + participant Nginx as NGINX Pods (nginx-deployment) + participant EGW as Envoy Gateway Proxy (Service: ClusterIP) + participant GWAPI as Gateway API (Gateway/HTTPRoute/GRPCRoute) + participant SVC as Backend Service (ClusterIP) + participant POD as Backend Pods + + Client->>LB: 1) TCP 443 (or 80) to EXTERNAL-IP + LB->>Nginx: 2) L4 forward to targetPort (NGINX Pod) + Note over Nginx: TLS termination happens here\n(nginx has cert/key) + + Nginx->>EGW: 3) HTTP request proxied to Envoy Proxy Service (ClusterIP:80) + Note over Nginx,EGW: Preserve Host header for HTTPRoute.hostnames\nAdd X-Forwarded-* headers + + EGW->>GWAPI: 4) Match Gateway listener + Route rules\n(host/path/headers or gRPC methods) + GWAPI->>SVC: 5) Select backendRef (Service:port) + SVC->>POD: 6) Kube-proxy routes to one Pod endpoint + + POD-->>SVC: 7) Response + SVC-->>EGW: 8) Response + EGW-->>Nginx: 9) Response (HTTP) + Nginx-->>LB: 10) Response (HTTPS re-encrypted to client) + LB-->>Client: 11) HTTPS response +``` diff --git a/docs/2025-12-13-k8s-gateway-api/nginx.yml b/docs/2025-12-13-k8s-gateway-api/nginx.yml new file mode 100644 index 0000000..00f138c --- /dev/null +++ b/docs/2025-12-13-k8s-gateway-api/nginx.yml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 2 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 +--- + +apiVersion: v1 +kind: Service +metadata: + name: nginx-service + annotations: + loadbalancer.openstack.org/flavor-id: adc12270-f56a-4ef1-bb00-429aad71ef6e + loadbalancer.openstack.org/availability-zone: KR1-Zone1-LB + loadbalancer.openstack.org/enable-health-monitor: "true" + service.beta.kubernetes.io/openstack-internal-load-balancer: "false" + # SSL 인증서 콘솔에서 등록이 필요함. + loadbalancer.openstack.org/default-tls-container-ref: "https://external.gabiacloud.com:9311/v1/secrets/88750a57-9a44-41d5-bc89-602ab2b131f8" +spec: + type: LoadBalancer + selector: + app: nginx + ports: + - port: 443 + protocol: TCP + targetPort: 80 diff --git a/docs/2025-12-13-k8s-gateway-api/readme.md b/docs/2025-12-13-k8s-gateway-api/readme.md new file mode 100644 index 0000000..87de666 --- /dev/null +++ b/docs/2025-12-13-k8s-gateway-api/readme.md @@ -0,0 +1,12 @@ +# K8s Gateway API 적용 + +## 적용 대상 + +- gabiacloud managed k8s 환경 +- local k8s + +| 항목 | EKS | 직접 구성 | 로컬 | +|----|---------|-------|----| +| LB | AWS NLB | | | +| | | | | +| | | | | diff --git "a/docs/2025-12-13-k8s-gateway-api/\352\260\234\353\205\220.md" "b/docs/2025-12-13-k8s-gateway-api/\352\260\234\353\205\220.md" new file mode 100644 index 0000000..8830ab2 --- /dev/null +++ "b/docs/2025-12-13-k8s-gateway-api/\352\260\234\353\205\220.md" @@ -0,0 +1,13 @@ +# Gateway API + +## GatewayClass (클러스터 공용) + +클러스터에 어떤 **Gateway 컨트롤러**를 쓸 것인가? + +## Gateway + +외부 트래픽이 들어오는 **엔드포인트(주소/포트/TLS)** 를 어떻게 열 것인가? + + + +## CRD diff --git a/docs/k8s/crd.md b/docs/k8s/crd.md new file mode 100644 index 0000000..f541a74 --- /dev/null +++ b/docs/k8s/crd.md @@ -0,0 +1,86 @@ +# CRD (Custom Resource Definition) + +## 1. 무엇인가? + +Kubernetes에 새로운 리소스 타입(API 오브젝트) 을 사용자 정의로 추가하는 메커니즘. + +Pod, Service, Deployment 같은 기본 리소스처럼 +`kubectl get xxx, kubectl apply -f`로 다룰 수 있는 나만의 리소스 종류를 만드는 방법. + +한 줄로 요약하면, **CRD = Kubernetes API를 확장하는 공식적인 방법** + +## 2. 목적 + +고수준 도메인 개념을 하나의 리소스로 표현하고 컨트롤러가 그 의미를 구현하도록 만듭니다. + +- 고수준 도메인 개념: `이 앱은 외부 노출 + TLS + WAF`, `이 데이터베이스는 자동 백업 + 복제 정책이 필요함` + +## 3. 구조: "정의"와 "인스턴스"의 분리 + +### 3.1 CRD(Definition) + +- “이런 종류의 리소스가 있다”를 API 서버에 등록 +- 스키마(OpenAPI), 버전(v1alpha1/v1beta1/v1) 포함 + +```yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: widgets.example.com +spec: + group: example.com + names: + kind: Widget + plural: widgets + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + size: + type: string +``` + +→ 이 시점부터 K8s는 Widget 이라는 리소스를 알게 됨 + +### 3.2 CR(Instance) + +CRD로 정의된 실제 객체, Pod/Service처럼 생성/삭제/조회 가능 + +```yaml +apiVersion: example.com/v1 +kind: Widget +metadata: + name: my-widget +spec: + size: large +``` + +→ 하지만 이 자체로는 아무 일도 일어나지 않음 + +### 3.3 Controller + +CRD는 “명세(계약)”일 뿐이고, 실제 동작은 Controller가 구현 + +특정 CR을 감시(watch)하다가 CR의 spec을 읽고 실제 K8s 리소스(Pod, Service, ConfigMap 등)를 생성/수정 및 현제 상태를 `status`에 반영 + +#### 흐름 요약 + +```text +CR 생성 + ↓ +API Server 저장 + ↓ +Controller가 감지 + ↓ +원하는 상태(spec)를 실제 리소스로 구현 + ↓ +결과를 status에 기록 +``` diff --git a/k8s/gateway-api/BEST-PRACTICES.md b/k8s/gateway-api/BEST-PRACTICES.md new file mode 100644 index 0000000..26c7724 --- /dev/null +++ b/k8s/gateway-api/BEST-PRACTICES.md @@ -0,0 +1,615 @@ +# Gateway API 베스트 프랙티스 - 자체 구축 K8s (k3s, kubeadm) + +## 환경별 권장 구성 + +### 1. 단일 서버 (홈랩, 개발 서버) + +**추천: k3s + Traefik (기본 내장) ✅** + +```bash +# k3s 설치 (Traefik 자동 설치됨) +curl -sfL https://get.k3s.io | sh - + +# Gateway API CRDs 설치 +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml + +# Traefik은 이미 설치되어 있고 GatewayClass도 자동 생성됨 +kubectl get gatewayclass +NAME CONTROLLER +traefik traefik.io/gateway-controller +``` + +**Gateway 생성:** +```yaml +# gateway.yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: main-gateway +spec: + gatewayClassName: traefik + listeners: + - name: http + protocol: HTTP + port: 80 + - name: https + protocol: HTTPS + port: 443 + tls: + certificateRefs: + - kind: Secret + name: tls-cert +``` + +**접근:** +```bash +# k3s는 기본적으로 HostPort 사용 +curl http://your-server-ip +``` + +**장점:** +- 추가 설치 불필요 (Traefik 내장) +- 가볍고 빠름 (single binary) +- 80/443 포트 직접 바인딩 +- 자동 TLS (Let's Encrypt) + +--- + +### 2. 소규모 클러스터 (3-5 노드) + +**추천: kubeadm + MetalLB + Envoy/Istio Gateway ✅** + +#### 아키텍처 +``` +Internet + ↓ +방화벽/라우터 (포트포워딩) + ↓ +MetalLB (Virtual IP: 192.168.1.200) + ↓ +Gateway Controller (Envoy/Istio) + ↓ +Application Pods +``` + +#### 설치 순서 + +**1단계: Kubernetes 설치** +```bash +# 마스터 노드 +kubeadm init --pod-network-cidr=10.244.0.0/16 + +# CNI 설치 (Calico) +kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml + +# 워커 노드 조인 +kubeadm join ... +``` + +**2단계: MetalLB 설치** +```bash +# MetalLB 설치 +kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml + +# IP 풀 설정 (네트워크 환경에 맞게 조정) +cat < + +# 2. Gateway 연결 확인 +kubectl get gateway -o jsonpath='{.status}' + +# 3. Gateway Controller 로그 +kubectl logs -n envoy-gateway-system -l app=envoy-gateway + +# 4. DNS 확인 +nslookup your-domain.com +``` + +--- + +## 비용 최적화 + +### 리소스 요청/제한 설정 + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: envoy-gateway +spec: + template: + spec: + containers: + - name: envoy + resources: + requests: + cpu: 100m # 최소 필요 리소스 + memory: 128Mi + limits: + cpu: 1000m # 최대 사용 가능 + memory: 512Mi +``` + +### HPA (Horizontal Pod Autoscaler) + +```yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: envoy-gateway-hpa + namespace: envoy-gateway-system +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: envoy-gateway + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 +``` + +--- + +## 요약: 환경별 최종 권장 + +### 개인 서버 / 홈랩 +```bash +✅ k3s + Traefik (내장) +- 설치 명령어 하나로 완료 +- 추가 구성 최소화 +- Let's Encrypt 자동 +``` + +### 소규모 스타트업 (3-10 노드) +```bash +✅ kubeadm + MetalLB + Envoy Gateway +- 간단하면서도 확장 가능 +- CNCF 표준 스택 +- 적은 리소스 사용 +``` + +### 프로덕션 / 대규모 (10+ 노드) +```bash +✅ kubeadm + MetalLB + Istio + Cert-Manager +- 고가용성 보장 +- Service Mesh 통합 +- 고급 트래픽 관리 +- 자동 TLS 관리 +``` + +### 멀티 테넌트 환경 +```bash +✅ kubeadm + MetalLB + 여러 Gateway Controller +- 팀별 독립 Gateway +- 네임스페이스 격리 +- RBAC 세밀하게 설정 +``` diff --git a/k8s/gateway-api/Chart.yaml b/k8s/gateway-api/Chart.yaml new file mode 100644 index 0000000..f5706b0 --- /dev/null +++ b/k8s/gateway-api/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: k8s-gateway-demo +description: Kubernetes Gateway API Demo with Helm +type: application +version: 0.1.0 +appVersion: "1.0" diff --git a/k8s/gateway-api/README-NETWORK.md b/k8s/gateway-api/README-NETWORK.md new file mode 100644 index 0000000..686a5cc --- /dev/null +++ b/k8s/gateway-api/README-NETWORK.md @@ -0,0 +1,259 @@ +# Gateway API 네트워크 접근 - 클라우드 vs 로컬 차이 + +## 핵심 차이점 + +**Gateway API 자체는 똑같습니다.** 하지만 **Gateway Controller가 만드는 Service의 동작이 완전히 다릅니다.** + +## 실제 동작 방식 + +### ☁️ 클라우드 환경 (AWS EKS, GCP GKE, Azure AKS) + +#### 1단계: Gateway 리소스 생성 +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: demo-gateway +spec: + gatewayClassName: envoy-gateway + listeners: + - name: http + protocol: HTTP + port: 80 +``` + +#### 2단계: Gateway Controller가 자동으로 수행 +```bash +# Gateway Controller가 자동으로 LoadBalancer Service 생성 +kubectl get svc -n envoy-gateway-system +NAME TYPE EXTERNAL-IP PORT(S) +envoy-gateway-envoy-gateway LoadBalancer 34.123.45.67 80:31234/TCP + ↑ + 클라우드 LB가 자동 할당! +``` + +#### 3단계: 클라우드 프로바이더가 개입 +- AWS: ELB/ALB/NLB 자동 생성 (실제 IP: 34.123.45.67) +- GCP: Cloud Load Balancer 자동 생성 +- Azure: Azure Load Balancer 자동 생성 + +#### 결과 +```bash +# 인터넷에서 바로 접근 가능! +curl http://34.123.45.67 +# Internet → AWS LoadBalancer → K8s Service → Envoy Gateway → App +``` + +### 💻 로컬 환경 (Docker Desktop, Minikube, Kind) + +#### 1단계: 동일한 Gateway 리소스 생성 +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: demo-gateway +spec: + gatewayClassName: envoy-gateway + listeners: + - name: http + protocol: HTTP + port: 80 +``` + +#### 2단계: Gateway Controller가 Service 생성 +```bash +# 하지만 로컬에서는... +kubectl get svc -n envoy-gateway-system +NAME TYPE CLUSTER-IP PORT(S) +envoy-gateway-envoy-gateway ClusterIP 10.96.123.45 80/TCP + ↑ + 클러스터 내부 IP만 있음! +``` + +#### 3단계: 클라우드 프로바이더가 없음! +- ❌ LoadBalancer를 만들어줄 클라우드 프로바이더 없음 +- ❌ External IP 할당 불가 +- ✅ ClusterIP만 생성됨 (기본값) + +#### 결과 +```bash +# 클러스터 외부에서 접근 불가! +curl http://10.96.123.45 # ❌ 실패 (클러스터 내부 IP) +curl http://localhost # ❌ 실패 (연결 안됨) + +# 클러스터 내부에서만 가능 +kubectl run test --rm -it --image=nginx:alpine -- curl http://10.96.123.45 # ✅ 성공 +``` + +## 왜 이런 차이가 발생하나? + +### Service Type: LoadBalancer의 동작 차이 + +```yaml +# Gateway Controller가 만드는 Service (단순화) +apiVersion: v1 +kind: Service +metadata: + name: envoy-gateway-envoy-gateway +spec: + type: LoadBalancer # ← 여기가 핵심! + ports: + - port: 80 + selector: + app: envoy-gateway +``` + +**클라우드:** +```bash +kubectl get svc +NAME TYPE EXTERNAL-IP +my-svc LoadBalancer 34.123.45.67 ← 클라우드가 실제 LB 프로비저닝 +``` +- Cloud Controller Manager가 실제 LoadBalancer 생성 +- 외부 IP 자동 할당 +- 인터넷 트래픽 라우팅 + +**로컬:** +```bash +kubectl get svc +NAME TYPE EXTERNAL-IP +my-svc LoadBalancer ← 영원히 pending... +# 또는 +my-svc ClusterIP ← 아예 ClusterIP로 생성 +``` +- LoadBalancer를 만들어줄 컴포넌트 없음 +- External IP 할당 불가 +- Pending 상태로 남거나 ClusterIP로 fallback + +## 해결 방법 비교 + +### 방법 1: Envoy Gateway 설치 시 NodePort 지정 + +```bash +# 로컬에서만 필요한 설정 +helm install eg oci://docker.io/envoyproxy/gateway-helm \ + --set service.type=NodePort \ + --set service.ports[0].nodePort=30080 +``` + +**결과:** +```bash +kubectl get svc -n envoy-gateway-system +NAME TYPE PORT(S) +envoy-gateway-envoy-gateway NodePort 80:30080/TCP + ↑ + 노드의 30080 포트로 접근 가능! +``` + +**접근:** +```bash +curl http://localhost:30080 # ✅ 작동! +# localhost:30080 → K8s Node:30080 → Service → Envoy → App +``` + +### 방법 2: MetalLB 설치 (로컬 LoadBalancer 에뮬레이션) + +```bash +# MetalLB 설치 +kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml + +# IP 풀 설정 +cat </dev/null || true + +echo "" +echo "=== 테스트 완료 ===" +echo "" +echo "수동으로 테스트하려면:" +echo " 1. 포트포워딩: kubectl port-forward -n envoy-gateway-system service/envoy-gateway-envoy-gateway 8080:80" +echo " 2. 접속: curl http://demo.local:8080" diff --git a/k8s/gateway-api/uninstall.sh b/k8s/gateway-api/uninstall.sh new file mode 100755 index 0000000..52ba7c7 --- /dev/null +++ b/k8s/gateway-api/uninstall.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -e + +echo "=== Envoy Gateway + Helm 제거 스크립트 ===" +echo "" + +# 색상 정의 +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 1. Demo 애플리케이션 제거 +echo -e "${YELLOW}[1/3] Demo 애플리케이션 제거 중...${NC}" +helm uninstall demo-gateway || echo "demo-gateway가 이미 제거되었거나 존재하지 않습니다." +echo -e "${GREEN}✓ Demo 애플리케이션 제거 완료${NC}" +echo "" + +# 2. Envoy Gateway 제거 +echo -e "${YELLOW}[2/3] Envoy Gateway 제거 중...${NC}" +helm uninstall eg -n envoy-gateway-system || echo "Envoy Gateway가 이미 제거되었거나 존재하지 않습니다." +kubectl delete namespace envoy-gateway-system || echo "Namespace가 이미 제거되었습니다." +echo -e "${GREEN}✓ Envoy Gateway 제거 완료${NC}" +echo "" + +# 3. Gateway API CRDs 제거 (선택사항) +echo -e "${YELLOW}[3/3] Gateway API CRDs 제거 여부${NC}" +read -p "Gateway API CRDs를 제거하시겠습니까? (y/N): " -n 1 -r +echo "" +if [[ $REPLY =~ ^[Yy]$ ]] +then + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml || echo "CRDs 제거 실패" + echo -e "${GREEN}✓ Gateway API CRDs 제거 완료${NC}" +else + echo "Gateway API CRDs는 유지됩니다." +fi +echo "" + +echo -e "${GREEN}제거가 완료되었습니다!${NC}" diff --git a/k8s/gateway-api/values-local.yaml b/k8s/gateway-api/values-local.yaml new file mode 100644 index 0000000..7d84f04 --- /dev/null +++ b/k8s/gateway-api/values-local.yaml @@ -0,0 +1,29 @@ +# 로컬 환경 설정 파일 +app: + name: demo-app + replicas: 1 # 로컬에서는 1개로 충분 + image: nginx:alpine + port: 80 + +gateway: + name: demo-gateway + gatewayClassName: envoy-gateway + namespace: envoy-gateway-system + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + +routes: + - name: demo-route + hostnames: + - demo.local + - api.demo.local + rules: + - path: / + pathType: PathPrefix + backendService: demo-app + backendPort: 80 diff --git a/k8s/gateway-api/values-prod.yaml b/k8s/gateway-api/values-prod.yaml new file mode 100644 index 0000000..e419144 --- /dev/null +++ b/k8s/gateway-api/values-prod.yaml @@ -0,0 +1,35 @@ +# 프로덕션 환경 설정 파일 +app: + name: demo-app + replicas: 3 # 고가용성을 위해 3개 + image: nginx:alpine + port: 80 + +gateway: + name: demo-gateway + gatewayClassName: envoy-gateway + namespace: envoy-gateway-system + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same # 같은 네임스페이스만 허용 + - name: https + protocol: HTTPS + port: 443 + allowedRoutes: + namespaces: + from: Same + +routes: + - name: demo-route + hostnames: + - demo.example.com + - api.example.com + rules: + - path: / + pathType: PathPrefix + backendService: demo-app + backendPort: 80 diff --git a/k8s/gateway-api/values.yaml b/k8s/gateway-api/values.yaml new file mode 100644 index 0000000..df5a47c --- /dev/null +++ b/k8s/gateway-api/values.yaml @@ -0,0 +1,27 @@ +app: + name: demo-app + replicas: 2 + image: nginx:alpine + port: 80 + +gateway: + name: demo-gateway + gatewayClassName: envoy-gateway # Envoy Gateway Controller + namespace: envoy-gateway-system + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All # All, Same, Selector + +routes: + - name: demo-route + hostnames: + - demo.local + rules: + - path: / + pathType: PathPrefix + backendService: demo-app + backendPort: 80