GKE에서 Envoy 프록시를 사용한 gRPC 서비스 부하 분산

이 가이드에서는 네트워크 부하 분산Envoy 프록시를 사용하여 단일 외부 IP 주소의 Google Kubernetes Engine(GKE)에 배포된 여러 gRPC 서비스를 노출하는 방법을 보여줍니다. 이 가이드에서는 Envoy가 gRPC에 제공하는 몇 가지 고급 기능에 대해 중점적으로 설명합니다.

소개

gRPC는 전송 중 효율적인 표현과 빠른 직렬화를 위해 프로토콜 버퍼를 사용하면서 언어에 독립적인 HTTP/2 기반의 오픈소스 RPC 프레임워크입니다. 내부 Google RPC 프레임워크인 Stubby의 영향을 받은 gRPC는 마이크로서비스 간은 물론 모바일 클라이언트와 API 간의 지연 시간이 짧은 통신을 지원합니다.

gRPC는 HTTP/2를 통해 실행되므로 효율적인 바이너리 인코딩, 단일 연결을 통한 요청 및 응답 다중화, 자동 흐름 제어 등 HTTP/1.1에 비해 여러 가지 이점이 있습니다. gRPC에서는 여러 부하 분산 옵션도 제공합니다. 이 가이드는 모바일 클라이언트 및 클라이언트가 서비스 제공업체의 신뢰 경계 외부에서 실행되는 경우와 같이 클라이언트를 신뢰할 수 없는 상황에 대해 집중적으로 다룹니다. 이 가이드에서는 gRPC가 제공하는 부하 분산 옵션 중 프록시 기반 부하 분산을 사용합니다.

이 가이드에서는 Google Cloud에서 전송 레이어(레이어 4) 네트워크 부하 분산으로 노출되는 TYPE=LoadBalancer의 Kubernetes 서비스를 배포합니다. 이 서비스는 단일 공개 IP 주소를 제공하며 TCP 연결을 구성된 백엔드로 직접 전달합니다. 이 가이드에서 백엔드는 Envoy 인스턴스의 Kubernetes 배포입니다.

Envoy는 여러 고급 기능을 제공하는 오픈소스 애플리케이션 레이어(레이어 7) 프록시입니다. 이 가이드에서는 Envoy를 사용해 SSL/TLS 연결을 종료하고 gRPC 트래픽을 적절한 Kubernetes 서비스로 라우팅합니다. Kubernetes 인그레스와 같은 다른 애플리케이션 레이어 솔루션과 비교할 때 Envoy를 사용하면 다음과 같은 여러 맞춤설정 옵션을 직접 이용할 수 있습니다.

  • 서비스 검색
  • 부하 분산 알고리즘
  • 요청 및 응답 변환(예: JSON 또는 gRPC-Web으로 변환)
  • JWT 토큰 확인을 통한 요청 인증
  • gRPC 상태 확인

네트워크 부하 분산과 Envoy를 결합하여 GKE 클러스터에서 실행되는 Envoy 인스턴스 집합에 트래픽을 전달하는 엔드포인트(외부 IP 주소)를 설정할 수 있습니다. 그러면 인스턴스에서 애플리케이션 레이어 정보를 사용해 클러스터에서 실행 중인 다른 gRPC 서비스에 대한 요청을 프록시합니다. Envoy 인스턴스는 클러스터 DNS를 사용해 수신되는 gRPC 요청을 식별하여 각 서비스에서 실행 중인 정상 상태의 pod로 부하를 분산합니다. 즉, 클라이언트의 TCP 연결이 아닌 RPC 요청마다 트래픽이 pod로 부하 분산된다는 의미입니다.

비용

이 가이드에서는 비용이 청구될 수 있는 다음과 같은 Google Cloud 구성요소를 사용합니다.

프로젝트 사용량을 기준으로 예상 비용을 산출하려면 가격 계산기를 사용하세요. Google Cloud를 처음 사용하는 사용자는 무료 체험판을 사용할 수 있습니다.

이 가이드를 마치면 만든 리소스를 삭제하여 비용이 계속 청구되지 않게 할 수 있습니다. 자세한 내용은 삭제를 참조하세요.

시작하기 전에

  1. Google 계정으로 로그인합니다.

    아직 계정이 없으면 새 계정을 등록하세요.

  2. Cloud Console의 프로젝트 선택기 페이지에서 Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기 페이지로 이동

  3. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다. 프로젝트에 결제가 사용 설정되어 있는지 확인하는 방법을 알아보세요.

  4. Cloud Build, Container Registry, and Container Analysis API를 사용 설정합니다.

    API 사용 설정

아키텍처

이 가이드에서는 Google Kubernetes Engine(GKE) 클러스터에 두 가지 gRPC 서비스(echo-grpcreverse-grpc)를 배포하고 공개 IP 주소로 인터넷에 노출합니다. 다음 다이어그램은 단일 엔드포인트를 통해 이 두 서비스를 노출하는 아키텍처를 보여줍니다.

단일 엔드포인트를 통해 'echo-grpc' 및 'reverse-grpc'를 노출하는 아키텍처

네트워크 부하 분산은 인터넷(예: 기업 외부의 모바일 클라이언트 또는 서비스 소비자)에서 수신되는 요청을 수락합니다. 네트워크 부하 분산에서 수행하는 태스크는 다음과 같습니다.

  • 수신되는 연결의 부하를 풀의 워커 노드로 분산합니다. 트래픽은 클러스터의 모든 워커 노드에 노출되는 envoy Kubernetes 서비스로 전달됩니다. Kubernetes 네트워크 프록시가 Envoy를 실행하는 pod로 해당 연결을 전달합니다.
  • 클러스터의 워커 노드에 대한 HTTP 상태 확인을 수행합니다.

Envoy에서 수행하는 태스크는 다음과 같습니다.

  • SSL/TLS 연결을 종료합니다.
  • 내부 클러스터 DNS 서비스를 쿼리하여 gRPC 서비스를 실행 중인 pod를 찾습니다.
  • 트래픽을 gRPC 서비스 pod로 라우팅하고 부하를 분산합니다.
  • gRPC 상태 확인 프로토콜에 따라 gRPC 서비스의 상태 확인을 수행합니다.
  • 네트워크 부하 분산을 사용하여 상태 확인의 엔드포인트를 노출합니다.

gRPC 서비스(echo-grpcreverse-grpc)는 Kubernetes 헤드리스 서비스로 노출됩니다. 즉, clusterIP 주소가 할당되지 않고 Kubernetes 네트워크 프록시가 pod로 트래픽의 부하를 분산하지 않습니다. 대신 pod IP 주소를 포함한 DNS A 레코드가 클러스터 DNS 서비스에 생성됩니다. Envoy에서는 이 DNS 항목에서 pod IP 주소를 찾아 Envoy에 구성된 정책에 따라 해당 주소로 부하를 분산합니다.

다음 다이어그램은 이 가이드에 사용된 Kubernetes 객체를 보여줍니다.

서비스, YAML 파일, DNS A 레코드, 보안 비밀, pod, 프록시 항목 등 이 가이드에 사용된 Kubernetes 객체

환경 초기화

이 섹션에서는 가이드 뒷부분에서 사용할 환경 변수를 설정합니다.

  1. Cloud Shell을 엽니다.

    Cloud Shell로 이동

    Cloud Shell을 사용하여 가이드의 모든 명령어를 실행합니다.

  2. Cloud Shell에서 현재 프로젝트 ID를 표시합니다.

    gcloud config list --format 'value(core.project)'
    
  3. 명령어를 실행해서 선택한 프로젝트의 ID가 반환되지 않으면 project-id를 프로젝트 이름으로 바꾸어 프로젝트를 사용하도록 Cloud Shell을 구성합니다.

    gcloud config set project project-id
    
  4. 이 가이드에서 사용할 리전 및 영역의 환경 변수를 정의합니다.

    REGION=us-central1
    ZONE=$REGION-b
    

    이 가이드에서는 us-central1 리전 및 us-central1-b 영역을 사용합니다. 하지만 필요에 맞게 리전과 영역을 변경할 수 있습니다.

GKE 클러스터 만들기

  1. gRPC 서비스 실행을 위한 GKE 클러스터를 만듭니다.

    gcloud container clusters create grpc-cluster --zone $ZONE
    
  2. 클러스터의 워커 노드를 나열하여 kubectl 컨텍스트가 설정되었는지 확인합니다.

    kubectl get nodes -o name
    

    출력은 다음과 비슷합니다.

    node/gke-grpc-cluster-default-pool-c9a3c791-1kpt
    node/gke-grpc-cluster-default-pool-c9a3c791-qn92
    node/gke-grpc-cluster-default-pool-c9a3c791-wf2h
    

gRPC 서비스 배포

하나의 부하 분산기를 사용하는 여러 gRPC 서비스로 트래픽을 라우팅하려면 echo-grpcreverse-grpc라는 간단한 gRPC 서비스 2개를 배포합니다. 두 서비스 모두 콘텐츠 요청 필드의 문자열을 사용하는 단항 메서드를 노출합니다. echo-grpc는 변경되지 않은 콘텐츠로 응답하는 반면 reverse-grpc는 역방향 콘텐츠 문자열로 응답합니다.

  1. gRPC 서비스를 포함한 저장소를 클론하고 작업 디렉터리로 전환합니다.

    git clone https://github.com/GoogleCloudPlatform/grpc-gke-nlb-tutorial
    cd grpc-gke-nlb-tutorial
    
  2. Cloud Build를 사용해 Echo 및 Reverse gRPC 서비스의 컨테이너 이미지를 만들어 Container Registry에 저장합니다.

    gcloud builds submit -t gcr.io/$GOOGLE_CLOUD_PROJECT/echo-grpc echo-grpc
    
    gcloud builds submit -t gcr.io/$GOOGLE_CLOUD_PROJECT/reverse-grpc reverse-grpc
    
  3. 이 이미지가 Container Registry에 존재하는지 확인합니다.

    gcloud container images list --repository gcr.io/$GOOGLE_CLOUD_PROJECT
    

    출력은 다음과 비슷합니다.

    NAME
    gcr.io/grpc-gke-nlb-tutorial/echo-grpc
    gcr.io/grpc-gke-nlb-tutorial/reverse-grpc
    
  4. echo-grpcreverse-grpc에 대한 Kubernetes 배포를 만듭니다.

    sed s/GOOGLE_CLOUD_PROJECT/$GOOGLE_CLOUD_PROJECT/ \
        k8s/echo-deployment.yaml | kubectl apply -f -
    
    sed s/GOOGLE_CLOUD_PROJECT/$GOOGLE_CLOUD_PROJECT/ \
        k8s/reverse-deployment.yaml | kubectl apply -f -
    
  5. 각 배포에서 두 pod를 사용할 수 있는지 확인합니다.

    kubectl get deployments
    

    출력은 다음과 비슷합니다. 두 배포 모두 DESIRED, CURRENT, UP-TO-DATE, AVAILABLE의 값이 2여야 합니다.

    NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    echo-grpc      2         2         2            2           1m
    reverse-grpc   2         2         2            2           1m
    
  6. echo-grpcreverse-grpc에 대한 Kubernetes 헤드리스 서비스를 만듭니다. 이 명령어는 클러스터의 DNS 서비스에 DNS A 레코드를 만들지만 가상 IP 주소를 할당하지 않습니다.

    kubectl apply -f k8s/echo-service.yaml
    kubectl apply -f k8s/reverse-service.yaml
    
  7. echo-grpcreverse-grpc가 Kubernetes 서비스로 존재하는지 확인합니다.

    kubectl get services
    

    출력은 다음과 비슷합니다. echo-grpcreverse-grpc 모두 TYPE=ClusterIPCLUSTER-IP=None이 있어야 합니다.

    NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
    echo-grpc      ClusterIP   None         <none>        8081/TCP   35s
    kubernetes     ClusterIP   10.0.0.1     <none>        443/TCP    47m
    reverse-grpc   ClusterIP   None         <none>        8082/TCP   21s
    

네트워크 부하 분산 설정

  1. 클러스터에 LoadBalancer 유형의 Kubernetes 서비스를 만듭니다.

    kubectl apply -f k8s/envoy-service.yaml
    

    이 명령어는 네트워크 부하 분산에 필요한 리소스를 프로비저닝하고 임시 공개 IP 주소를 할당합니다. 공개 IP 주소를 할당하는 데 몇 분이 걸릴 수 있습니다.

  2. 다음 명령어를 실행하고 envoy 서비스의 EXTERNAL-IP 값이 <pending>에서 공개 IP 주소로 변경될 때까지 기다립니다.

    kubectl get services envoy --watch
    
  3. 대기를 중지하려면 Control+C를 누릅니다.

자체 서명 SSL/TLS 인증서 만들기

Envoy는 SSL/TLS 연결을 종료할 때 인증서와 키를 사용합니다. 먼저 자체 서명 SSL/TLS 인증서를 만듭니다.

  1. 이전 섹션에서 만든 Envoy 서비스의 공개 IP 주소를 저장할 환경 변수를 만듭니다.

    EXTERNAL_IP=$(kubectl get service envoy -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    
  2. 자체 서명 SSL/TLS 인증서와 키를 만듭니다.

    openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
        -keyout privkey.pem -out cert.pem -subj "/CN=$EXTERNAL_IP"
    
  3. 자체 서명 SSL/TLS 인증서와 키가 포함된 envoy-certs라는 Kubernetes TLS 보안 비밀을 만듭니다.

    kubectl create secret tls envoy-certs \
        --key privkey.pem --cert cert.pem \
        --dry-run -o yaml | kubectl apply -f -
    

Envoy 배포

  1. Envoy 구성 파일(envoy.yaml)을 저장할 Kubernetes ConfigMap을 만듭니다.

    kubectl apply -f k8s/envoy-configmap.yaml
    
  2. Envoy에 사용할 Kubernetes 배포를 만듭니다.

    kubectl apply -f k8s/envoy-deployment.yaml
    
  3. envoy pod가 실행 중인지 확인합니다.

    kubectl get deployment envoy
    

    출력은 다음과 비슷하게 표시됩니다.

    NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    envoy     2         2         2            2           1m
    

이제 gRPC 서비스를 테스트할 준비가 되었습니다.

gRPC 서비스 테스트

서비스를 테스트하려면 grpcurl 명령줄 도구를 사용합니다.

  1. Cloud Shell에서 grpcurl를 설치합니다.

    go get github.com/fullstorydev/grpcurl
    go install github.com/fullstorydev/grpcurl/cmd/grpcurl
    
  2. Echo gRPC 서비스에 요청을 보냅니다.

    grpcurl -d '{"content": "echo"}' -proto echo-grpc/api/echo.proto \
        -insecure -v $EXTERNAL_IP:443 api.Echo/Echo
    

    출력은 다음과 비슷합니다.

    Resolved method descriptor:
    rpc Echo ( .api.EchoRequest ) returns ( .api.EchoResponse );
    
    Request metadata to send:
    (empty)
    
    Response headers received:
    content-type: application/grpc
    date: Wed, 27 Feb 2019 04:40:19 GMT
    hostname: echo-grpc-5c4f59c578-wcsvr
    server: envoy
    x-envoy-upstream-service-time: 0
    
    Response contents:
    {
      "content": "echo"
    }
    
    Response trailers received:
    (empty)
    Sent 1 request and received 1 response
    

    hostname 응답 헤더에는 요청을 처리한 echo-grpc pod의 이름이 표시됩니다. 명령어를 몇 차례 반복하면 echo-grpc pod의 이름에 해당하는 hostname 응답 헤더에 서로 다른 두 개의 값이 표시됩니다.

  3. Reverse gRPC 서비스에서 동일 동작을 확인합니다.

    grpcurl -d '{"content": "reverse"}' -proto reverse-grpc/api/reverse.proto \
        -insecure -v $EXTERNAL_IP:443 api.Reverse/Reverse
    

    출력은 다음과 비슷합니다.

    Resolved method descriptor:
    rpc Reverse ( .api.ReverseRequest ) returns ( .api.ReverseResponse );
    
    Request metadata to send:
    (empty)
    
    Response headers received:
    content-type: application/grpc
    date: Wed, 27 Feb 2019 04:45:56 GMT
    hostname: reverse-grpc-74cdc4849f-tvsfb
        server: envoy
    x-envoy-upstream-service-time: 2
    
    Response contents:
    {
      "content": "esrever"
    }
    
    Response trailers received:
    (empty)
    Sent 1 request and received 1 response
    

문제해결

이 가이드에서 문제가 발생하면 다음 문서를 검토하시기 바랍니다.

Envoy 관리 인터페이스를 탐색해 Envoy 구성 문제를 진단할 수도 있습니다.

  1. 관리 인터페이스를 열려면 포트 전달을 Cloud Shell에서 Envoy pod 중 하나의 admin 포트로 설정합니다.

    kubectl port-forward \
        $(kubectl get pods -o name | grep envoy | head -n1) 8080:8090
    
  2. 콘솔에 다음 출력이 표시될 때까지 기다립니다.

    Forwarding from 127.0.0.1:8080 -> 8090
    
  3. Cloud Shell에서 웹 미리보기 버튼을 클릭하고 포트 8080에서 미리보기를 선택합니다. 그러면 관리 인터페이스가 표시된 새 브라우저 창이 열립니다.

    미리보기가 선택된 Envoy 관리 인터페이스

  4. 단계를 모두 수행했다면 Cloud Shell로 다시 전환한 후 Control+C 키를 눌러 포트 전달을 종료합니다.

gRPC 트래픽을 라우팅하는 다른 방법

이 솔루션은 환경에 맞게 다양한 방법으로 수정할 수 있습니다.

대체 애플리케이션 레이어 부하 분산기

Envoy가 제공하는 애플리케이션 레이어 기능 중 일부는 다른 부하 분산 솔루션에서도 제공하고 있습니다.

  • Kubernetes 인그레스 객체를 사용해 HTTP(S) 부하 분산을 구성하여 네트워크 부하 분산 및 Envoy 대신 사용할 수 있습니다. HTTP(S) 부하 분산을 사용하면 Cloud CDN, IAP와 같은 다른 Google Cloud 제품과의 통합, 관리형 SSL/TLS 인증서 등 네트워크 부하 분산에 비해 여러 가지 이점이 있습니다.

    다음 항목을 지원할 필요가 없으면 HTTP(S) 부하 분산을 사용하는 것이 좋습니다.

    • gRPC 상태 확인
    • 부하 분산 알고리즘에 대한 세밀한 제어
    • 50개가 넘는 서비스 노출

    샘플 gRPC 서비스를 포함한 HTTP(S) 부하 분산 배포 방법에 대한 자세한 내용은 GitHub에서 인그레스 관련 Google Kubernetes Engine 문서GKE gRPC 인그레스 부하 분산 가이드를 참조하세요.

  • Istio를 사용하는 경우 해당 기능을 사용해 gRPC 트래픽을 라우팅하고 부하를 분산할 수 있습니다. Istio의 Ingress Gateway는 이 가이드의 아키텍처와 유사하게 Envoy 백엔드를 사용하는 네트워크 부하 분산으로 배포됩니다. 단, Envoy 프록시가 Istio의 traffic routing 객체를 통해 구성된다는 것이 가장 큰 차이점입니다. 이 가이드의 예시 서비스를 Istio 서비스 메시에서 라우팅하려면 Kubernetes 서비스 매니페스트(echo-service.yamlreverse-service.yaml)에서 clusterIP: None 줄을 삭제해야 합니다. 즉, Envoy의 유사한 기능 대신 Istio의 서비스 검색 및 부하 분산 기능을 사용합니다. 이미 Istio를 사용 중이라면 Ingress Gateway를 사용해 gRPC 서비스로 라우팅하는 것이 좋습니다.

  • 배포로 또는 Kubernetes용 NGINX 인그레스 컨트롤러를 사용해 Envoy 대신 NGINX를 사용할 수 있습니다. 이 가이드에서는 gRPC 상태 확인 프로토콜 지원 등 고급 gRPC 기능을 더 많이 제공하는 Envoy를 사용했습니다.

  • Kubernetes 인그레스 컨트롤러를 제공하며 Envoy를 기반으로 하는 AmbassadorContour를 사용할 수 있습니다.

  • HAProxy 기반의 Kubernetes 인그레스 컨트롤러인 Voyager를 사용할 수 있습니다.

내부 VPC 네트워크 연결

서비스를 VPC 네트워크 내에서만 GKE 클러스터 외부로 노출시키려면 네트워크 부하 분산 대신 내부 TCP/UDP 부하 분산을 사용하면 됩니다. 이렇게 하려면 주석 cloud.google.com/load-balancer-type: "Internal"envoy-service.yaml 매니페스트에 추가합니다.

Envoy 배포와 DaemonSet의 비교

이 가이드에서 Envoy는 Kubernetes 배포로 구성됩니다. 이 구성에서는 배포 매니페스트의 replica 설정에 따라 Envoy pod 수가 결정됩니다. 부하 분산기가 수신되는 요청을 Envoy pod를 실행하지 않는 워커 노드로 전달하면 Kubernetes 네트워크 프록시에서 이 요청을 Envoy pod를 실행 중인 워커 노드로 전달합니다.

DaemonSet를 Envoy 배포 대신 사용할 수 있습니다. DaemonSet를 사용하면 Envoy pod가 GKE 클러스터의 모든 워커 노드에서 실행됩니다. 즉, 이 대안을 사용하면 큰 클러스터의 리소스 사용량이 늘어나지만(Envoy pod가 더 많음) 수신되는 요청이 항상 Envoy pod를 실행하는 워커 노드에 도달합니다. 이때 요청이 Envoy pod에 도달할 수 있도록 워커 노드 간에 전달되는 것은 아니므로 클러스터의 네트워크 트래픽과 평균 지연 시간은 줄어듭니다.

삭제

가이드를 완료한 후에는 할당량을 차지하지 않고 이후에 요금이 청구되지 않도록 Google Cloud에서 만든 리소스를 삭제할 수 있습니다. 다음 섹션에서는 리소스를 삭제하거나 사용 중지하는 방법을 설명합니다.

프로젝트 삭제

  1. Cloud Console에서 리소스 관리 페이지로 이동합니다.

    리소스 관리 페이지로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제 를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 다음 종료를 클릭하여 프로젝트를 삭제합니다.

리소스 삭제

이 가이드에서 사용된 Google Cloud 프로젝트를 유지하려면 개별 리소스를 삭제합니다.

  1. Cloud Shell에서 로컬 Git 저장소 클론을 삭제합니다.

    cd ; rm -rf ~/grpc-gke-nlb-tutorial
    
  2. Container Registry의 이미지를 삭제합니다.

    gcloud container images list-tags gcr.io/$GOOGLE_CLOUD_PROJECT/echo-grpc \
        --format 'value(digest)' | xargs -I {} gcloud container images delete \
        --force-delete-tags --quiet gcr.io/$GOOGLE_CLOUD_PROJECT/echo-grpc@sha256:{}
    
    gcloud container images list-tags gcr.io/$GOOGLE_CLOUD_PROJECT/reverse-grpc \
        --format 'value(digest)' | xargs -I {} gcloud container images delete \
        --force-delete-tags --quiet gcr.io/$GOOGLE_CLOUD_PROJECT/reverse-grpc@sha256:{}
    
  3. Google Kubernetes Engine 클러스터를 삭제합니다.

    gcloud container clusters delete grpc-cluster --zone $ZONE --quiet  --async
    

다음 단계