CloudNet@ 가시다님이 진행하는 쿠버네티스 네트워크 스터디 KANS 3기 내용을 정리한 글입니다.
Istio는 서비스 메쉬 내에서 트래픽을 제어하기 위한 다양한 기능들을 제공해왔습니다. 반면, Gateway API는 표준화된 인터페이스를 제공하는 것을 목표로 하다 보니, 아직 Istio가 제공하던 모든 기능을 담기에는 한계가 있는 것 같습니다. 이번 글에서는 Istio를 사용하여 Gateway API를 직접 설치해보고, 기존 Istio의 트래픽 제어 방식과 비교해보려고 합니다.
Istio 와 Gateway API 비교
📦 리소스 별 비교
Istio에서 Gateway와 VirtualService는 서비스 메쉬의 트래픽을 관리하는 핵심 리소스입니다. Gateway는 외부에서 들어오는 트래픽을 받는 진입점 역할을 하며, VirtualService는 이 트래픽이 어디로 라우팅될지를 결정합니다. 또한, DestinationRule은 트래픽이 전달될 서비스에 대한 세부 설정을 정의합니다. 이러한 리소스들은 Istio 인그레스 게이트웨이와 연결되어 서비스의 배포 및 설정을 관리합니다.
표준화된 인터페이스를 제공하는 Gateway API의 경우 Gateway를 통해 트래픽을 받고 Route(HTTPRoute, TCPRoute) 리소스에 따라 프로토콜을 다르게 갖고가며 그에 맞게 규칙을 관리합니다. Istio의 VirtualService는 하나의 리소스에서 여러 프로토콜(HTTP, TCP, gRPC 등)을 설정할 수 있어 여러 프로토콜을 일관성 있게 관리할 수 있다는 장점이 있습니다.
🚀 관리 및 배포 방식 차이
Istio에서 Gateway와 VirtualService는 사용자가 직접 배포하고 구성해야 합니다. 사용자는 Istio 인그레스 게이트웨이의 배포 및 설정을 직접 관리해야 하며, 이를 위해 Deployment와 Service 리소스를 수동으로 설정해야 합니다. 따라서 더 세부적인 설정 관리를 요구합니다.
반면, Gateway API는 더 자동화된 배포 및 관리 방식을 제공합니다. 사용자가 Gateway 리소스를 정의하면 필요한 Deployment와 Service가 자동으로 생성됩니다. 또한, Gateway 리소스에 레이블과 주석을 추가해 배포 설정을 세밀하게 조정할 수 있어 관리가 간소화되고 자동화됩니다.
Istio 설치하기
Gateway API CRD 설치
gateway crds들이 없는 상태에서 istio를 설치하게 되면 istio의 gatewayclass가 설치 되지 않게 됩니다. 그럼으로 확인 후 gateway api crd가 없다면 설치 해야합니다.
# crds 설치 여부 확인
kubectl get crds | grep "gateway.networking.k8s.io"
crds는 다음의 명령어를 사용하여 설치할 수 있습니다. 현재 가장 최근 버전인 v1.2.0으로 설치하겠습니다.
kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v1.2.0" | kubectl apply -f -
Istio 설치하기
테스트를 위해 istioctl로 설치를 하게 된다면 프로필을 minimal로 지정하라고 합니다. minimal은 default랑 비슷하지만 컨트롤 플레이만 설치되는 버전입니다. GatewayAPI를 사용하기 위한 특별한 설정은 없는 것 같습니다.
istioctl install --set profile=minimal -y
GatewayClass 생성하기
CRD가 설치된 상태에서 Istio를 설치하면 GatewayClass가 자동으로 생성됩니다. 기본적으로, Istiod가 기본 GatewayClass를 생성하며, 이를 생성할지 여부는 환경 변수로 결정됩니다.
env:
- name: PILOT_ENABLE_GATEWAY_API_GATEWAYCLASS_CONTROLLER
value: "true" # 기본 gateway class의 생성 여부
코드로 생성시점 살펴보기
코드에서 살펴보면 먼저 istio가 관리하는 기본 gatewayclass들은 여기에서 생성이 됩니다. 이때 기본 istio gateway class의 이름은 istio인데 이걸 변경하고 싶다면 PILOT_GATEWAY_API_DEFAULT_GATEWAYCLASS_NAME 이 환경변수를 수정하면 됩니다.
// pilot/pkg/config/kube/gateway/deploymentcontroller.go
// getBuiltinClasses는 Istio에서 기본적으로 지원하는 GatewayClass들을 정의합니다.
// 이 함수는 GatewayClass 이름과 해당 컨트롤러를 매핑하는 맵을 반환합니다.
func getBuiltinClasses() map[gateway.ObjectName]gateway.GatewayController {
// 기본 GatewayClass들을 저장할 맵을 초기화합니다.
res := map[gateway.ObjectName]gateway.GatewayController{
// 기본 Istio GatewayClass를 정의합니다.
// 이름은 features.GatewayAPIDefaultGatewayClass에서,
// 컨트롤러는 features.ManagedGatewayController에서 가져옵니다.
gateway.ObjectName(features.GatewayAPIDefaultGatewayClass): gateway.GatewayController(features.ManagedGatewayController),
}
// 멀티 네트워크 Gateway API 기능이 활성화된 경우
if features.MultiNetworkGatewayAPI {
// Remote Gateway Class를 추가합니다.
// 이는 다른 클러스터의 게이트웨이를 나타냅니다.
res[constants.RemoteGatewayClassName] = constants.UnmanagedGatewayController
}
// Ambient Waypoints 기능이 활성화된 경우
if features.EnableAmbientWaypoints {
// Waypoint Gateway Class를 추가합니다.
// 이는 Ambient 메시에서 사용되는 특별한 유형의 게이트웨이입니다.
res[constants.WaypointGatewayClassName] = constants.ManagedGatewayMeshController
}
// 정의된 모든 GatewayClass들을 반환합니다.
return res
}
이렇게 생성이 된 기본 gateway class의 리스트들은 ClassController의 Run이 호출되면서 Reconcile 메서드가 실행되게 되고 그리고 .이 Reconcile안에서 builtClasses에 대한 reconcileClass가 호출이 되어 GatewayClass가 생성이 되게 됩니다.
// pilot/pkg/config/kube/gateway/gatewayclass.go
// NewClassController는 GatewayClass 리소스를 관리하는 새로운 ClassController를 생성합니다.
// 이 컨트롤러는 Istio에서 정의한 내장 GatewayClass들을 처리합니다.
func NewClassController(kc kube.Client) *ClassController {
// 새로운 ClassController 인스턴스를 생성합니다.
gc := &ClassController{}
// 컨트롤러의 작업 큐를 초기화합니다.
// 이 큐는 Reconcile 함수를 사용하여 GatewayClass 리소스를 처리하며,
// 최대 25번의 재시도를 허용합니다.
gc.queue = controllers.NewQueue("gateway class",
controllers.WithReconciler(gc.Reconcile),
controllers.WithMaxAttempts(25))
// Kubernetes 클라이언트를 사용하여 GatewayClass 리소스에 대한 클라이언트를 생성합니다.
gc.classes = kclient.New[*gateway.GatewayClass](kc)
// GatewayClass 리소스에 대한 이벤트 핸들러를 추가합니다.
// 이 핸들러는 내장 GatewayClass(builtinClasses에 정의된)에 대한 이벤트만 처리합니다.
gc.classes.AddEventHandler(controllers.FilteredObjectHandler(gc.queue.AddObject, func(o controllers.Object) bool {
_, f := builtinClasses[gateway.ObjectName(o.GetName())]
return f
}))
// 생성된 ClassController를 반환합니다.
return gc
}
func (c *ClassController) reconcileClass(class gateway.ObjectName) error {
// class변수에 지정 된 이름의 GatewayClass가 이미 존재하는지 확인
if c.classes.Get(string(class), "") != nil {
log.Debugf("GatewayClass/%v already exists, no action", class)
return nil
}
// 내장된 클래스 정보 가져오기
controller := builtinClasses[class]
classInfo, f := classInfos[controller]
if !f {
// ambient 모드가 꺼져 있을 때만 발생해야 함
// 그렇지 않다면, builtinClasses와 classInfos는 항상 일치해야 함
return nil
}
// 새로운 GatewayClass 객체 생성
gc := &gateway.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: string(class),
},
Spec: gateway.GatewayClassSpec{
ControllerName: gateway.GatewayController(classInfo.controller),
Description: &classInfo.description,
},
}
// Kubernetes API 서버에 GatewayClass 생성 요청
_, err := c.classes.Create(gc)
if err != nil && !kerrors.IsConflict(err) {
// 충돌 오류가 아닌 경우에만 오류 반환
return err
} else if err != nil && kerrors.IsConflict(err) {
// 충돌 오류는 이미 GatewayClass가 생성되었음을 의미하므로 무시
log.Infof("Attempted to create GatewayClass/%v, but it was already created", class)
}
if err != nil {
return err
}
return nil
}
이렇게 해서 gateway class가 잘 설치되는 것을 확인해 볼 수 있습니다.
Gateway 만들기
Listener가 1개인 Gateway 만들기
다음의 파일을 이용하여 gateway를 배포해보도록 하겠습니다.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: istio-ingress
spec:
gatewayClassName: istio
listeners:
- name: default
hostname: "*.example.com"
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
이렇게 gateway를 생성할 경우 먼저 istio-ingress에 있는 gateway-istio deployment가 생성이 되게 됩니다.
istio의 gateway의 경우 별도로 ingress를 생성 한 후 gateway가 그 deployment와 연결이 되게 햇던거와 달리 gateway API에서는 생성하는 것만으로 해당 gateway와 연결되는 deployment가 생성되게 됩니다. 서비스의 경우 리스너로 정의한 80포트만 열려잇는 것을 확인 할 수 있습니다.
Listener 추가해보기
위의 gateway에 다른 포트를 추가해보도록 하겠습니다.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: istio-ingress # Gateway가 배포될 네임스페이스
spec:
gatewayClassName: istio # 이 Gateway가 사용할 GatewayClass 이름
listeners: # 수신할 트래픽에 대한 정의
- name: default
hostname: "*.example.com" # 호스트명 매칭 패턴
port: 80 # HTTP 트래픽을 받을 포트
protocol: HTTP
allowedRoutes:
namespaces:
from: All
- name: https
hostname: "*.example.com"
port: 443
protocol: HTTPS
allowedRoutes:
namespaces:
from: All
예상대로 기존의 80포트 외에도 443포트가 추가된 것을 확인해볼 수 있습니다. 그리고 동시에 파드도 재시작 되게 됩니다.
NodePort의 서비스로 만들어지게 수정해보기
gateway의 서비스 객체는 기본적으로 LoadBalancer로 생성되게 되어있으나 networking.istio.io/service-type를 annotations에 설정하여 타입을 변경할 수 있습니다. NodePort이외에도 ClusterIP 타입의 서비스도 생성이 가능합니다. 그리고 addresses를 사용하여 로드밸런서의 아이피를 지정하는 것 또한 가능합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
annotations:
networking.istio.io/service-type: NodePort # 여기에 원하는 타입에 대한 정보를 넣을 수 있음
name: np-gateway
namespace: istio-ingress
spec:
gatewayClassName: istio
listeners:
- name: default
hostname: "*.example.com"
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
HTTPRoute 생성하기
이제 위에서 만든 gateway랑 연결하는 HttpRoute를 생성해보려고 합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http
namespace: default
spec:
parentRefs:
- name: gateway # 이 HTTPRoute가 연결될 Gateway 리소스의 이름
namespace: istio-ingress # Gateway 리소스가 있는 네임스페이스
hostnames: ["httpbin.example.com"] # 이 HTTPRoute가 처리할 호스트네임
rules:
- matches:
- path:
type: PathPrefix # 경로 매칭의 타입
value: /get # 매칭할 경로
backendRefs:
- name: httpbin
port: 8000
예시에서는 지정한 경로로 시작하는 모든 요청을 매핑하도록 설정이 되어있는데 Exact라는 정확하게 일치하는 경우에만 응답을 보내는 설정도 할 수 있습니다.
istio에서 제공하는 샘플 파드를 실행시킨 뒤 호출을 해보면 200으로 응답이 잘 오는 것을 확인해볼 수 있습니다.
그리고 없는 경로인 /headers로 호출을 해보면 404에러가 납니다.
/headers를 받아주기 위해 다음과 같이 HTTPRoute를 업데이트해보도록 하겠습니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http
namespace: default
spec:
parentRefs:
- name: gateway
namespace: istio-ingress
hostnames: ["httpbin.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /get
- path:
type: PathPrefix
value: /headers
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: my-added-header
value: added-value
backendRefs:
- name: httpbin
port: 8000
이렇게 업데이트 하고나서는 잘 호출이 되는 것을 확인할 수 있습니다. 추가로 붙인 Header도 잘 들어간 것이 보입니다.
예제의 filters에 RequestHeaderModifier라는 타입은 기존의 VirtualService의 headers와 동일합니다.
지금까지 Istio와 Gateway API의 기본 리소스들을 비교해 보았습니다. Istio에서는 Gateway와 VirtualService, DestinationRule을 통해 서비스 메쉬 내에서의 트래픽을 세부적으로 제어할 수 있었고, Gateway API에서는 Gateway와 HTTPRoute 등의 표준 리소스를 사용하여 보다 간결한 방식으로 트래픽을 관리할 수 있었습니다.
시간이 된다면 이러한 리소스들이 실제 Envoy 프록시 내부에서 어떻게 구성되고, 트래픽 흐름에 어떤 영향을 미치는지 살펴볼 예정입니다. 이를 통해 Istio와 Gateway API가 각각 트래픽 제어를 구현하는 방식의 차이를 좀 더 깊이 이해할 수 있을 것입니다.
'K8S > 🔥 network study🔥' 카테고리의 다른 글
[네떡스터디🔥kans] CoreDNS (작성 중) (0) | 2024.10.12 |
---|---|
[네떡스터디🔥kans] Cilium Gateway API (0) | 2024.10.12 |
[네떡스터디🔥kans] Gateway API (0) | 2024.10.07 |
[네떡스터디🔥kans] Ingress (0) | 2024.10.07 |
[네떡스터디🔥kans] 쿠버네티스 서비스 이해하기 - kube-proxy IPVS 모드 (0) | 2024.09.30 |