사실 홈 서버에 minkube로 쿠버네티스 클러스터 환경을 구축한 것 중에서 가장 큰 목표는 바로 Gateway API를 직접 사용해보는 것이었다.
이전 직장에서는 Nginx Ingress Controller를 사용하면서 Ingress를 다루긴 했었지만 공식 문서를 공부하던 도중 Ingress가 Deprecated 되었다는 것과, 이를 대체할 Gateway API가 존재한다는 사실을 알게 되면서 자연스레 Gateway API에 흥미가 생기게 되었다.
그리고 한 가지 또 다른 목표는 클라우드에서 제공하는 로드 밸런서를 사용하지 않은(즉, LoadBalancer 타입이 아닌 NodePort 타입의) Ingress 또는 Gateway API를 직접 사용해보는 것이었다.
이 두 가지 목표를 위해서 내 개인 Mac이 아닌 24시간 365일 띄워져 있는 나의 홈 서버에 직접 테스트해보는 것이 낫겠다 싶어서 minikube를 설치하자마자 테스트를 진행했었고, 이 부산물이 앞서 다룬 Cert-Manager였다.
1.29버전부터 지원을 하기 시작한 신규 기능이기도 하고, 처음 적용해보는 입장에서 문서가 많이 없어서 이런저런 실수도 많았지만 어느 정도 안정화가 되어서 이렇게 내용을 정리하게 되었다.
https://kubernetes.io/docs/concepts/services-networking/gateway/
공식 문서에 따르면 Gateway API는 Ingress의 후속 리소스이며 동적 인프라 프로비저닝, 고급 트래픽 라우팅을 제공하는 리소스로, 확장 가능하고 역할 지향적이며 프로토콜 인식 구성 메커니즘을 사용하여 네트워크 서비스를 사용 가능하게 한다.
디자인 원칙으로는 크게 역할 중심, 이식성, 표현력, 확장 가능 4가지를 내세우고 있는데, 내용은 다음과 같다.
Gateway API를 구성하고 있는 주요 리소스는 다음과 같다.
여기서 GatewayClass는 IngressClass와 어느 정도 유사하다고 볼 수 있으며, HTTPRoute는 Ingress와 유사하다고 할 수 있는데, Gateway의 경우 트래픽 처리 및 네트워크 수준의 설정을 담당하는 인스턴스를 정의하는 리소스로, Ingress에서는 존재하지 않던 유형의 리소스이다.
또한 Gateway API는 HTTPRoute 뿐만 아니라 GRPCRoute, TCPRoute, TLSRoute 등 다양한 프로토콜에 대한 라우팅을 담당하는 리소스를 제공한다. (단, 공식 문서를 확인해보니 HTTPRoute, GRPCRoute는 정식 버전에 포함되지만 TCPRoute, TLSRoute는 테스트 중에 있는 것으로 보인다.)
GatewayClass 명세 예시
GatewayClass는 Gateway에 필요한 공통 구성을 정의하고 Gateway를 생성하는 클래스 역할을 하며, 이를 컨트롤러가 관리한다.
보통은 Gateway API를 지원하는 오픈소스 별로 클래스가 존재하며, Nginx 역시 존재한다.
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: example-class
spec:
controllerName: example.com/gateway-controller
Gateway 명세 예시
Gateway는 트래픽 처리 인프라의 인스턴스를 설명하며, 백엔드에 대한 트래픽 처리(필터링, 밸런싱, 분할 등)에 사용할 수 있는 네트워크 엔드포인트를 정의한다.
Gateway는 클래스를 구현하는 컨트롤러의 이름을 포함하는 GatewayClass를 참조해야 한다.
아래의 명세에서는 'example-class' 라는 GatewayClass를 참조하며 트래픽 처리 인프라의 인스턴스는 포트 80에서 HTTP 트래픽을 수신하도록 지정. addresses 필드가 지정되지 않았으므로 구현 컨트롤러가 Gateway에 주소 또는 호스트 이름을 할당한다.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
spec:
gatewayClassName: example-class
listeners:
- name: http
protocol: HTTP
port: 80
HTTPRoute 명세 예시
HTTPRoute는 Gateway 리스너에서 백엔드 네트워크 엔드포인트로의 HTTP 요청 라우팅 동작을 지정한다.
아래의 예시에서는 호스트 헤더가 www.example.com 이고 prefix가 /login인 경우 example-svc의 8080 포트로 라우팅된다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-httproute
spec:
parentRefs:
- name: example-gateway
hostnames:
- "www.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /login
backendRefs:
- name: example-svc
port: 8080
Nginx에서는 Ingress와 Nginx Ingress Controller, Gateway API를 다음과 같이 비교하고 있다.
표준 Ingress API 대비 Gateway API의 기능이 훨씬 다양한 것으로 보이는데, 가장 눈에 띄는 것은 역시 L7 레이어 외의 프로토콜에 대한 트래픽 제어 가능 여부이다.
또한 Ingress는 Deprecated되었으며 Gateway API는 이제 정식 출시한 지 얼마 되지 않은 리소스이기 때문에 개선될 여지가 더 많다.
다만, Nginx 측에서는 Gateway API를 지원하는 Nginx Gateway Fabric이 Nginx Ingress Controller를 완전히 대체하기에는 시간이 걸릴 것으로 설명하고 있으며 단순히 HTTP 트래픽에 대한 라우팅을 위해 Ingress와 Ingress Controller를 사용했다면 당장 Gateway API로 넘어갈 필요는 없을 것으로 보인다.
Gateway API의 경우, 기본으로 설치되어 있지 않기 때문에 별도로 설치를 해야 한다.
여기서 stable 버전과 실험용 버전이 있는데, 실험용 버전에는 위에서 잠깐 소개했던 TLSRoute, TCPRoute와 BackendTLSPolicy와 같은 부가 기능이 존재한다.
다만, 필자는 실험용 버전이 아닌 stable 버전 기준으로 설치를 진행하였다.
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
Gateway API 설치 방법은 공식 문서를 확인하면 된다.
https://gateway-api.sigs.k8s.io/guides/
https://docs.nginx.com/nginx-gateway-fabric/overview/gateway-architecture
공식 문서에 따르면 Nginx Gateway Fabric은 Nginx를 데이터 플레인으로 하여 Kubernetes에서 실행되는 애플리케이션에 대한 HTTP 또는 TCP/UDP 로드 밸런서, 역방향 프록시 또는 API 게이트웨이를 구성하기 위해 핵심 Gateway API를 구현하는 오픈소스라고 한다.
Nginx를 데이터 플레인으로 하는 것은 Nginx Ingress Controller와 유사하며, Nginx Ingress Controller가 Ingress를 구현한 것처럼 Nginx Gateway Fabric은 Gateway API를 구현한 오픈소스라고 생각하면 된다.
첫번째 이미지는 Nginx Ingress Controller, 두번째 이미지는 Nginx Fabric Gateway의 아키텍처 구성도인데 한 눈에 보이는 차이점으로는 중간에 Gateway 리스너가 추가된 것이 있다. (그리고 여기서 클러스터 관리자가 Gateway를 관리한다는 점도 포인트이다.)
그리고 개인적으로 사용해본 적은 없지만 Nginx Ingress Controller에서는 Nginx가 별도로 제공하는 VirtualServer, VirtualServerRoute 리소스가 있어 고급 라우팅 기능을 지원하고 있는데, 이러한 고급 기능을 Gateway API에서 이미 제공 (또는 제공 예정) 하고 있어 오픈 소스 레벨에서의 커스텀 리소스가 아닌 Gateway API의 공식 리소스로도 고급 라우팅 기능을 사용할 수 있다는 것도 특징이다.
필자는 이전과 마찬가지로 Helm을 통해 설치하였으며, 서비스 타입을 LoadBalancer가 아닌 NodePort로 지정하였다.
원래라면 Helm Chart의 values.yaml을 커스텀해서 install 했겠지만 지금 단계에서 고칠 부분이 서비스 타입 뿐이라서 커맨드 라인에서 서비스 타입을 변경해주었다.
helm install nginx-kubernetes-gateway oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric --create-namespace -n nginx-gateway --set service.type=NodePort
이후 정상적으로 배포가 되었는지를 확인하려면 kubectl get all -n nginx-gateway
를 통해 확인할 수 있으며, NodePort로 배포했기 때문에 80, 443 포트가 호스트의 어떤 포트로 노출되었는지 확인해야한다.
필자의 경우 80번 포트는 30110번 포트에, 443 포트는 30587번 포트로 노출되고 있다.
Nginx Gateway Fabric을 배포했다면 바로 Gateway를 배포해야 한다.
필자는 TLS를 위해 Nginx Gateway Fabric의 443번 포트에 리스너를 하나 추가했으며, 이 리스너는 "*.cocoball.info" 로 들어오는 요청을 수신한다.
# gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
annotations:
cert-manager.io/cluster-issuer: letsencrypt-info
spec:
gatewayClassName: nginx
listeners:
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-cocoball-info
hostname: "*.cocoball.info"
allowedRoutes:
namespaces:
from: All
여기서 tls를 사용하려면 ClusterIssuer와 Certificate를 참조해야 하는데, 앞서 만들었던 것들을 참조한다.
https://velog.io/@mrcocoball2/Kubernetes-Kubernetes-%ED%99%88-%EC%84%9C%EB%B2%84%EC%97%90-minikube-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-2-Cert-Manager-%EC%84%A4%EC%B9%98
추가로 테스트를 해보면서 확인된 사실은, HTTPS 프로토콜의 경우 TLS 모드는 Terminate만 지원을 하기 때문에 Cleint -> Gateway -> Backend로 라우팅 할 때 Backend가 443으로 열려 있다면 TLS가 종료되어 Bad Request 응답이 전달된다. 이를 위해서는 TLSRoute와 BackendTLSPolicy를 사용해야 하는데, 이 리소스들은 stable 버전에서는 현재 지원하고 있지 않다. 이 부분에 대해서는 다음 내용에서 한 번 더 다룬다.
마지막으로 allowedRoutes의 경우 리스너가 라우팅할 백엔드 서비스가 포함될 네임스페이스를 지정할 수 있는데, 일단은 모든 네임스페이스를 지정해두었다. (실제로는 보안 문제로 권장하지는 않는다고 함)
Gateway가 제대로 수신을 하고 있는지 체크를 하기 위해선 kubectl get gateway
로 배포된 Gateway를 확인한 후, kubectl describe gateway
로 상세 정보를 확인하면 된다.
사실 클라우드 환경일 경우 LoadBalancer 타입으로 배포하면 자동적으로 클라우드 로드 밸런서가 생성되고 그 로드 밸런서가 Nginx Gateway Fabric과 연결되는 구조이기 때문에 클라우드 로드 밸런서의 Public IP(Nginx Gateway Fabric의 External-IP)와 도메인을 연결해두면 별다른 설정을 하지 않아도 된다.
그러나 필자의 경우 홈 서버이기 때문에 홈 서버의 Nginx에서 도메인 분기 처리를 해서 Nginx Gateway Fabric으로 요청을 넘겨줘야 한다.
여기서 ClusterIP가 아닌 NodePort로 배포하였기 때문에 다음과 같은 정보를 확인해야 한다.
노출된 포트는 위에서 확인하였듯이 kubectl get all -n nginx-gateway
에서 확인이 가능한데, 노드의 Internal IP는 kubectl describe node
로 확인해야 한다.
확인된 정보들을 종합했을 때, 홈 서버에서 192.168.49.2:30587 로 요청을 보내면 Nginx Gateway Fabric으로 요청이 전달된다.
이제 홈 서버의 Nginx에서는 다음과 같이 설정하면 된다.
server {
listen 443;
listen [::]:443;
server_name 원하는주소.cocoball.info;
ssl_certificate "/etc/letsencrypt/live/cocoball.info/fullchain.pem";
ssl_certificate_key "/etc/letsencrypt/live/cocoball.info/privkey.pem";
add_header Strict-Transport-Security "max-age=31536000";
location / {
proxy_pass https://192.168.49.2:30587;
proxy_ssl_server_name on;
proxy_ssl_name $host;
proxy_set_header Host $host;
}
}
참고로 한참 삽질하다가 추가한 내용인데
따라서 위 3개의 설정은 반드시 해줘야 한다.
Kubernetes Dashboard를 설치한 후 Nginx Gateway Fabric을 활용하여 포트 포워딩하지 않고 도메인으로 접근하도록 처리하는 과정을 다룬다.