티스토리 뷰

반응형

Which One is the Right Choice for the Ingress Gateway of Your Service Mesh?

위 글을 번역한 글입니다. 제 이해를 위해 중간 중간 부족한 부분에 대한 내용 및 링크를 추가하였습니다.

글과 모든 사진의 저작권은 위 글에 있습니다. (Copyright of the article and all the photos is in the post above)


Kubernetes platform에서 일부 서비스가 외부 네트워크로 노출되어야할 때 무엇을 사용해야할까요?

 

Kubernetes에서는 ClusterIP, Nodeport, LoadBalancer, Ingress, Istio Gateway 같은 다양한 수단을 제공하고 있습니다. 이 중에서 production 환경의 Service mesh에 적합한 수단은 무엇일까요?

 

이 글에서 앞의 다양한 수단들의 기술 요소들을 비교해보겠습니다.

이 글을 이해하기 위해서는 Kubernetes (Pod, Service, NodePort, LoadBalancer, Ingress)와 Istio (Gateway, VirtualService)에 대해 어느정도 알고 있어야 합니다. 만약에 앞의 요소들에 대한 개념이 부족하다면, 글 중간 중간에 있는 링크들을 참조해주세요.

 


Access Services in the Cluster

먼저, Kubernetes 클러스터 내부의 서비스에 액세스 할 수 있는 방법을 보도록 하겠습니다.

ClusterIP

가장 작은 배포 단위인 Pod는 클러스터의 노드 간에 동적으로 생성, 삭제 및 마이그레이션됩니다. 결과적으로 Pod는 일시적이며 종료되고 다시 생성되게 되면 IP가 변경됩니다. 따라서 Pod의 IP를 보고 액세스하는 것은 계속 변경될 우려가 있기 때문에 어려운 일입니다.

 

이 문제를 해결하기 위해 Kubernetes는 Service를 Pod group에 대한 추상화로써 활용합니다. Service는 가상 IP 주소인 ClusterIP에 바인딩되어, Pod가 삭제되던 다시 시작되던 간에 ClusterIP는 절대 변경되지 않습니니다. 즉, 클라이언트는 Service의 ClusterIP로 엑세스를 요청하면 위에서 말한 Pod의 문제를 해결할 수 있습니다.

 

Kube-proxy는 Go 언어로 작성된 애플리케이션이며, 클라이언트의 요청을 모든 노드 중에서 요청에 알맞은 백엔드 Pod로 Routing 하는 역할을 가집니다. 이 Kube-proxy는 3가지 모드에서 작동합니다.

 

userspace

  • 이 모드에선 Kube-proxy가 요청 Traffic을 Service의 ClusterIP로 받도록 하는 iptable rule을 설치합니다. 그리고 Kube-proxy가 수신하고 있는 포트로 해당 traffic을 리다이렉션하도록 합니다. 그런 다음 요청을 받으면, 알맞은 백엔드 Pod를 선택하고 traffic 요청을 전달합니다.
  • 이 모드에서 Kube-proxy는 OSI Layer 4 계층의 Load balancer 역할을 합니다.
  • Kube-proxy는 userspace에서 동작하기 때문에 kernelspace와 userspace 사이에서 패키지를 복사하는 과정이 필요합니다. 즉, 이러한 Proxy process에 추가적인 지연시간이 존재합니다.
  • 이 모드에서는 요청을 받은 첫 번째 Pod가 요청을 처리할 수 없는 경우, Kube-proxy가 다른 Pod에 요청을 전달할 수 있다는 장점이 있습니다.

 

iptables

  • 앞선 userspace 모드에서 kernelspace와 userspace간에 복사가 일어나야하기 때문에 지연시간이 일어난다고 했는데요. 이러한 추가적인 복사과정을 피하기 위해서 Kube-proxy는 iptables 모드에서 작동할 수 있습니다.
  • ClusterIP로 사용자의 요청, traffic이 전달되면, iptables는 DNAT(Destination Network Address Translation)을 이용해서 해당 traffic을 알맞은 백엔드 Pod로 직접 전달하게 됩니다. 이 모드에서 Kube-proxy는 userspace 모드에서와 달리 L4 Load balancer로 역할은 하지 않게 됩니다. 단지 iptables 규칙만 생성하게 됩니다.
  • 이 모드는 kernelspace, userspace 사이에 복사 같은 프로세스가 필요하지 않아 전환 과정이 필요 없어 앞선 모드보다 더 효율적으로 동작합니다.

ipvs

위 모드들에 대한 내용은 공식문서에서도 잘 나와있으니 참조 바랍니다.

 

Istio Sidecar ProxyIstio를 kubernetes cluster에 배포하면 Istio는 Sidecar proxy를 사용해 서비스간의 통신을 받게 됩니다. 서비스 간의 통신은 더 이상 Kube-proxy를 사용하지 않고, Istio의 Sidecar proxy를 통해 이루어집니다.

  • 클라이언트 요청이 들어오고, iptables에 의해 Sidecar proxy로 redirection 됩니다.
  • Sidecar proxy는 (istio) Control plane에서 얻은 Service discovery 및 Routing 규칙 정보에 따라 Backend pod을 선택하고 요청을 전달합니다.

 

Istio Sidecar Proxy라고해서 어느 독립된 공간에서 실행되는 것이 아닌 위에서 배운 Userspace 모드처럼 동작합니다. User space에서 동작하여 클라이언트 요청을 프록시하고 여러 Backend pod에 부하를 분산시킵니다. 차이점은 Kube-proxy는 OSI Layer 4 계층 (L4)에서만 작동하는 반면 Istio Sidecar Proxy는 OSI Layer 7 계층(L7) 처리도 할 수 있다는 것입니다. 따라서 Istio Sidecar Proxy가 더 강력하다고 볼 수 있습니다.

또한, Istio control plane과 함께 동작하는 Sidecar Proxy Mesh 형태는 Canary 배포, Traffic Mirroring, Chaos testing(Fault injection) 등과 같은 Advaned한 트래픽 관리 시나리오도 지원합니다.


Access Services from the Outside of a Cluster

클러스터 외부에서 일부 서비스에 엑세스하는 경우 어떻게 해야할까요 ? kubernetes는 다음과 같은 방법들을 제공하고 있습니다.

NodePort

NodePort를 사용하여 kubernetes는 Service에 엑세스 할 수 있도록 Host에 Service 용 Port를 생성합니다. Kubernetes 공식문서에서는 NodePort 개념만 소개하고,어떻게 구현이 되었는지 설명하고 있지 않은데요. 여러 실험을 통해 어떻게 구현되는지 알아보겠습니다.

실습환경으로 Katacoda를 사용하겠습니다.

 

Networking Introduction | Kubernetes | Katacoda

Learn the different networking approaches available. Learn Step 1 - Cluster IP, Step 2 - Target Port, Step 3 - NodePort, Step 4 - External IPs, Step 5 - Load Balancer, via free hands on training.

www.katacoda.com

 

다음 명령어를 사용해 NodePort 서비스를 생성합니다.

kubectl apply -f nodeport.yaml

 

생성된 서비스를 출력해볼까요? webapp-nodeport-svc 가 생성되었고, NodePort 30080을 생성했음을 알 수 있습니다.

master $ kubectl get svc 
NAME                   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes             ClusterIP   10.96.0.1      <none>        443/TCP        96s
webapp1-nodeport-svc   NodePort    10.105.98.32   <none>        80:30080/TCP   7s

 

생성된 Pod를 보겠습니다. Service를 위한 2개의 Backend Pod가 있습니다. 첫 번째 IP는 10.32.0.3 이고, 다른 IP는 10.32.0.5 입니다.

master $ kubectl get pod -o wide
NAME                                           READY   STATUS    RESTARTS   AGE   IP          NODE           NOMINATED NODE   READINESS GATES
webapp1-nodeport-deployment-677bd89b96-22dmm   1/1     Running   0          80s   10.32.0.6   controlplane   <none>           <none>
webapp1-nodeport-deployment-677bd89b96-89bl9   1/1     Running   0          80s   10.32.0.5   controlplane   <none>           <none>

 

netstat 명령어를 사용해봅시다. 실제로 30080 Port에서 수신중인 Kube-proxy를 볼 수 있습니다.

master $ netstat -lnp | grep 30080 
tcp6  0  0  :::30080   :::*   LISTEN  2702/kube-proxy

 

Kube-proxy는 30080 NodePort로 전송되는 Traffic을 가져와서 해당 Traffic을 2개의 Backend Pod로 Redirection하는 iptables 규칙을 생성했을 겁니다. 모든 iptables 규칙은 아래에 작성하였습니다. 설명을 위해 주석을 추가했습니다.

iptables-save > iptables-dump
# Generated by iptables-save v1.6.0 on Thu Mar 28 07:33:57 2019
*nat
# Chain for NodePort
:KUBE-NODEPORTS - [0:0]
# Chain for Service
:KUBE-SERVICES - [0:0]
# Chains used by both th NodePort and Service
:KUBE-SVC-J2DWGRZTH4C2LPA4 - [0:0]
:KUBE-SEP-4CGFRVESQ3AECDE7 - [0:0]
:KUBE-SEP-YLXG4RMKAICGY2B3 - [0:0]
# Capture external traffic sent to NodePort 30080 and jump to chain KUBE-SVC-J2DWGRZTH4C2LPA4.
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/webapp1-nodeport-svc:" -m tcp --dport 30080 -j KUBE-SVC-J2DWGRZTH4C2LPA4
# Capture internal traffic sent to ClusterIP 10.103.188.73 and jump to chain UBE-SVC-J2DWGRZTH4C2LPA4, chain KUBE-SERVICES is responsible for handling traffic sent to Service webapp1-nodeport-svc.
-A KUBE-SERVICES -d 10.103.188.73/32 -p tcp -m comment --comment "default/webapp1-nodeport-svc: cluster IP" -m tcp --dport 80 -j KUBE-SVC-J2DWGRZTH4C2LPA4
# The first rule in chain KUBE-SVC-J2DWGRZTH4C2LPA4 sends 50% of the traffic to Pod 10.32.0.3
-A KUBE-SVC-J2DWGRZTH4C2LPA4 -m comment --comment "default/webapp1-nodeport-svc:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-YLXG4RMKAICGY2B3
# This rule sends the other 50% traffic to Pod 10.32.0.5
-A KUBE-SVC-J2DWGRZTH4C2LPA4 -m comment --comment "default/webapp1-nodeport-svc:" -j KUBE-SEP-4CGFRVESQ3AECDE7
# This rule forewords traffic to Pod 10.32.0.3
-A KUBE-SEP-YLXG4RMKAICGY2B3 -p tcp -m comment --comment "default/webapp1-nodeport-svc:" -m tcp -j DNAT --to-destination 10.32.0.3:80
# This rule forewords traffic to Pod 10.32.0.5
-A KUBE-SEP-4CGFRVESQ3AECDE7 -p tcp -m comment --comment "default/webapp1-nodeport-svc:" -m tcp -j DNAT --to-destination 10.32.0.5:80

위 실험에서 알 수 있듯, Service가 NodePort 유형으로 선언되면 Kube-proxy는 Node에 Port를 생성하고 해당 Port에서 수신 대기합니다. 그러나 Kube-proxy는 노드 네트워크의 Traffic을 직접 수용하지 않고, NodePort로 전송된 Traffic을 가져와 Backend Pod로 Redirection하는 iptables 규칙을 생성합니다. 아래 다이어그램처럼 말이죠.

 

 

 

LoadBalancer

NodePort는 Local Kubernetes Cluster에서 테스트하기엔 편리하지만, 여러 제한으로 인해 Production에는 적합하지 않은 방법입니다.

  • 모든 Node가 충돌할 수 있고, Kubernetes Cluster에서 제거 될 수 있습니다. 새로운 Node가 생성되면, Node IP는 Address Pool에서 동적 할당되므로 Node IP를 잘 알려진 IP로 설정할 수 없습니다. 따라서, 클라이언트 측에서 Node IP를 미리 구성해 활용할 수 없습니다.
  • 단일 Node는 시스템의 SPOF(Single Point Of Failure)입니다. Node가 다운되면 클라이언트는 더 이상 Cluster에 엑세스 할 수 없습니다. 물론 클라이언트 측에서 여러 Node IP를 구성해 이런 위험을 조금은 해결할 수 있지만, 잠재적으로 충돌할 수 있는 IP와 이러한 IP를 구성해야하는 시기 등의 문제로 완벽히 해결을 할 수는 없습니다.
  • 단일 Node는 시스템의 병목현상(Bottleneck)이 됩니다. 클라이언트 측에서 여러 Node를 구성하고 클라이언트에서 Loadbalance을 구성할 수도 있지만 이러한 솔루션은 Server side Loadbalance보다 훨씬 더 문제가 많습니다.

 

Service를 Loadbalancer 유형으로 선언해 여러 Node 앞에 L4 Loadbalancer를 만들 수 있습니다. 이 L4 Loadbalancer는 네트워크 외부에 있으므로 프로비저닝을 위해 클라우드 공급자 컨트롤러(Cloud Provider Controller)가 필요합니다. 이 클라우드 공급자 컨트롤러는 Kubernetes Master에서 Service resource 추가 및 제거를 감시하고, 클라우드 공급자 네트워크에서 L4 Loadbalancer를 구성하여 여러 Kubernetes Node에서 NodePort를 프록시합니다.

 

아래 다이어그램이 위 설명을 그림으로 보여주는 예입니다. LoadBalancer를 사용하여 외부 트래픽이 Kubernetes Cluster로 들어옵니다. 2개의 NodePort가 LoadBalancer에 연결되어 외부 트래픽이 들어오게 됩니다. Cluster에는 클라이언트 요청을 처리하는 3개의 Pod가 존재합니다. NodePort, Pod의 수는 시스템 작업의 부하에 따라 확장 및 축소 될 수 있습니다.

 

LoadBalancer 유형의 Service는 Load Balancer를 생성하기 위한 요청일 뿐이며, 실제 작업은 클라우드 공급자(AWS, Azure, OpenStack ...)가 수행합니다.

 

Ingress

Kubernetes LoadBalancer는 OSI 4 Layer (L4)에서 작동합니다. 즉, IP 및 Port 만을 기반으로 해서 Backend Service로만 Inbound traffic을 전달할 수 있습니다. 결과적으로 클러스터 외부에 여러 Service를 노출해야하는 경우, 각 서비스에 대해 LoadBalancer를 만들어야합니다. 그러나 여러 LoadBalancer를 만드는 경우, 몇 가지 문제가 발생할 수 있습니다.

  • 더 많은 공용 IP가 필요합니다. 일반적으로 이러한 공용 IP는 제한된(limited) 리소스 입니다.
  • 클라이언트와 서버간의 결합(coupling)을 하게 만드는 것이므로, 비즈니스 요구사항이 변경될 때 Backend Service를 조정하기 어렵게 만듭니다.

이러한 문제를 해결하기 위해 Kubernetes Ingress 리소스는 HTTP Protocol을 이해하고 HTTP URL 또는 Host를 기반으로 Inbound traffic을 dispatch 할 수 있는 OSI 7 Layer (L7) Load balancer를 선언하는데 사용됩니다.

 

  • URL을 기반으로 다른 Backend Service로 요청 Routing

  • Host를 기반으로 다른 Backend Service로 요청 Routing

 

Ingress 리소스는 HTTP URL/Host, TLS key, certification configuration을 기반으로 요청을 Backend Service로 Routing 하는 방법과 L7 Load balancer에 대한 요구사항만 정의합니다.

 

Ingress 리소스가 작동하려면 Cluster에 Ingress Controller가 실행 중이어야 하는데요. 가장 널리 사용된 Ingress Controller는 Nginx, HAProxy, Envoy 등이 있습니다.

 

Ingress Controller는 Cluster의 HTTP Service에 대한 통합된 입구를 제공하지만, Ingerss Controller 자체도 Cluster 내부에 Pod로 배포되기 때문에 외부에서 직접 Access 할 수 없습니다. Ingress Controller는 NodePort와 LoadBalancer와 함께 작동하여 외부 트래픽이 클러스터로 들어가는 전체 경로를 제공해야합니다.

 

 

위 다이어그램을 통해 전체 시스템의 확장성이 높다는 것을 볼 수 있는데요. NodePort, Ingress, Pod 계층은 다양한 작업 부하를 처리하기 위해 적절하게 확장 및 축소할 수 있습니다.

 

아래 다이어그램은 전체 진입 경로가 어떻게 구현되는지 보여줍니다.

프로세스는 다음과 같이 진행됩니다.

  1. 인터넷/외부 Traffic이 L4 LoadBalancer에 도착합니다.
  2. LoadBalacner는 여러 NodePort로 Traffic을 dispatch 합니다. 이 단계는 사용자 공간(userspace)에서 일어나게 됩니다.
  3. Traffic은 iptables에 의해 캡쳐되고, Ingress Controller Pod로 redirection 됩니다. 이 단계는 커널 공간(kernelspace)에서 일어나게 됩니다.
  4. Ingress Controller는 Ingress 규칙에 따라 다른 서비스로 트래픽을 전송합니다. 이 단계는 사용자 공간(userspace)에서 일어나게 됩니다.
  5. 마지막으로 Traffic은 ipatables에 의해 Backend Pod로 redirection 됩니다. 이 단계는 커널 공간(kernelspace)에서 일어나게 됩니다.

 

각 진입 경로(entry path)에서 IP 주소는 다음과 같습니다.

  • Client Request → Load Balancer (External IP) → Load Balancer (Node IP) → Ingress Controller Service (ClusterIP) → Ingress Controller Pod (Pod IP) → Backend Service (Cluster IP) → Backend Pod (Pod IP)

How to Choose the Ingress Gateway for your Service Mesh?

Istio는 Service Mesh에서 실행되는 모든 서비스에 대한 통신 인프라 계층을 제공함으로써 훌륭한 작업을 수행할 수 있습니다.

Kubernetes Ingress

Istio 0.8v 이전에는 Kubernetes Ingress 리소스를 사용해 외부 Traffic을 구성했습니다. (참고로, 지금 version은 1.10.1입니다) Kubernetes Ingress는 외부 트래픽에 대한 단일 입구를 제공하는 장점이 있지만, 사실 여러 단점이 존재합니다.

  • Kubernetes Ingress는 Istio Control Plane에서 관리할 수 없습니다. Kubernetes Ingress 규칙으로 생성해야만 하고, 결과적으로 시스템에는 입구용 서비스(Kubernetes Ingress)과 Mesh 내부 Sidecar Proxy이 존재하게 됬습니다. 이러다보니 Service Mesh 작업이 매우 복잡해졌습니다.
  • Kubernetes Ingress는 위에서 살펴봤다싶이 매우 기본적인 L7 기능만 제공합니다. Advanced Routing 규칙, 분산추적, Policy checking, metrics 수집 등 Mesh Sidecar Proxy와 동일한 기능들이 제공되지 않습니다.

 

Istio Gateway

위 문제를 해결하기 위해 Istio Gateway 리소스가 도입되었습니다.

Istio Gateway 리소스는 Kubernetes Ingress보다 훨씬 간단합니다. Port, Host, TLS key, 인증 같은 L4-L6 기능만을 구성할 수 있습니다.

 

그러나 Gateway는 메시 내 라우팅 구성에 사용되는 동일한 리소스 인 Istio VirtualService 리소스에 바인딩 됩니다. 이를 통해 Mesh 내부와 입구에서 위에서 언급한 동일한 기능들을 다 제공할 수 있습니다. 이러한 기능(Gatway, Sidecar Proxy)들은 모두 Istio Control Plane에서 관리됩니다.

 

API Gateway

Istio에서 제공하는 다양한 기능을 갖춘 Istio gateway는 Service mesh의 external traffic entrance에 적합한 선택처럼 보입니다. 그러나, 아직 빠진 것이 있습니다.

Production에서 실행되는 서비스에는 일반적으로 Traffic entrance에 대한 요구사항들이 있습니다.

  • Authentication & Authorization for users / 3rd-party systems
  • Enforce SLAs for different users / 3rd-party systems
  • Data transformation / translation
  • API lifecycle management
  • Rate limiting
  • Billing
  • Other customized requirements ….

 

이러한 요구사항을 충족하기 위해 Ambassador, Kong, Traefik, Gloo, Nignx 등을 포함해 수십개의 API Gateway가 존재합니다. 이러한 모든 API Gateway는 kubernetes Ingress Controller로도 사용이 가능하나, 몇가지 설정이 따로 필요합니다.

 

그래도 이러한 API Gateway로 위에서 언급한 기능들을 사용할 수 있습니다. API gateway는 Kubernetes Ingress 또는 Istio Gateway에 포함되어 지원되지는 않기 때문에 Istio Routing 규칙을 사용하고 위해 Istio Control Plane과 통합은 지원하지 않을 수 있습니다.

  • 위 글은 19년도 4월 글이므로, 현재는 여러 API gateway에서 Istio를 활용하는 확장 기능들을 지원하고 있습니다.

API Gateway + Sidecar Proxy as the External Traffic Entrance for a Service Mesh

다시 게시물의 시작 부분에서 던진 질문으로 돌아가보겠습니다. Service Mesh에서의 제일 적합한 Ingress Gateway는 무엇인가요? Kubernetes Ingress, Istio Gateway, API Gateway ? 내 의견으로는 각자 기능들이 다르고 부족한 기능들이 존재하기 때문에 하나만으로는 모든 기능을 충족시킬 수 없다는 것입니다.

 

Application Layer의 API Gateway와 Istio Ingress Gateway 기능을 모두 제공하는 이상적인 기본 구현체를 찾기 어렵다는 점을 감안할 때 (지금은 아닐 수 있습니다), External Traffic entrance로써 API Gateway와 mesh sidecar proxy를 같이 사용하는 것이 실용적인 해결책이 될 수 있습니다.

 

아래 다이어그램처럼 API Gateway와 Sidecar proxy가 Service mesh의 ingress gateway로 사용됩니다. API Gateway에는 이미 L7 기능이 있으므로 그뒤에서 진행되는 Sidecar proxy는 Istio VirutalService 리소스의 Routing 기능만 제공하면 되고, Istio Gateway 리소스 기능은 제공할 필요가 없습니다.

 

  • 이 다이어그램에서 입구의 Sidecar proxy가 Mesh 내부의 Proxy와 매우 유사하다는 것을 볼 수 있는데요. 유일한 차이점은 입구의 Sidecar proxy가 API Gateway의 Outbound traffic을 인수하고 Mesh 내부의 Sidecar proxy가 Application Pod의 Inbound, Outbound traffic을 모두 인수한다는 것입니다.
  • 네트워크 처리량이 병목이 되는 경우, 수신 Traffic을 처리하기 위해서 여러 API Gateway, Sidecar proxy 조합을 배포해 Mesh ingress를 확장할 수 있습니다.
320x100
반응형
댓글
반응형
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함