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

Last reviewed 2019-05-30 UTC

이 튜토리얼에서는 외부 패스 스루 네트워크 부하 분산기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를 사용해 TLS 연결을 종료하고 gRPC 트래픽을 적절한 Kubernetes 서비스로 라우팅합니다. Kubernetes 인그레스와 같은 다른 애플리케이션 레이어 솔루션과 비교할 때 Envoy를 사용하면 다음과 같은 여러 맞춤설정 옵션을 직접 이용할 수 있습니다.

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

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

아키텍처

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

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

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

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

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

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

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

다음 다이어그램은 이 튜토리얼에 사용된 Kubernetes 객체를 보여줍니다.

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

비용

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

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

이 문서에 설명된 태스크를 완료했으면 만든 리소스를 삭제하여 청구가 계속되는 것을 방지할 수 있습니다. 자세한 내용은 삭제를 참조하세요.

시작하기 전에

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

    프로젝트 선택기로 이동

  2. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다.

  3. Google Cloud 콘솔에서 Cloud Shell을 활성화합니다.

    Cloud Shell 활성화

환경 준비

  1. Cloud Shell에서 이 튜토리얼에 사용할 Google Cloud 프로젝트를 설정합니다.

    gcloud config set project PROJECT_ID
    

    PROJECT_ID를 Google Cloud 프로젝트 ID로 바꿉니다.

  2. Artifact Registry 및 GKE API를 사용 설정합니다.

    gcloud services enable artifactregistry.googleapis.com \
        container.googleapis.com
    

GKE 클러스터 만들기

  1. Cloud Shell에서 gRPC 서비스를 실행하기 위한 GKE 클러스터를 만듭니다.

    gcloud container clusters create envoy-grpc-tutorial \
        --enable-ip-alias \
        --release-channel rapid \
        --scopes cloud-platform \
        --workload-pool PROJECT_ID.svc.id.goog \
        --zone us-central1-f
    

    이 튜토리얼에서는 us-central1-f 영역을 사용합니다. 다른 영역 또는 리전을 사용할 수 있습니다.

  2. 클러스터의 노드를 나열하여 kubectl 컨텍스트가 설정되었는지 확인합니다.

    kubectl get nodes --output name
    

    결과는 다음과 비슷하게 표시됩니다.

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

Artifact Registry 저장소 만들기

  1. Cloud Shell에서 컨테이너 이미지를 저장할 새 저장소를 만듭니다.

    gcloud artifacts repositories create envoy-grpc-tutorial-images \
        --repository-format docker \
        --location us-central1
    

    GKE 클러스터와 동일한 리전에 저장소를 만들어 노드가 컨테이너 이미지를 가져올 때 지연 시간과 네트워크 대역폭을 최적화할 수 있습니다.

  2. 저장소의 Artifact Registry 리더 역할을 GKE 클러스터의 노드 VM에서 사용하는 Google 서비스 계정에 부여합니다.

    PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format 'value(projectNumber)')
    
    gcloud artifacts repositories add-iam-policy-binding envoy-grpc-tutorial-images \
        --location us-central1 \
        --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role roles/artifactregistry.reader
    
  3. Cloud Shell 홈 디렉터리의 Docker 구성 파일에 저장소 호스트 이름의 사용자 인증 정보 도우미 항목을 추가합니다.

    gcloud auth configure-docker us-central1-docker.pkg.dev
    

    사용자 인증 정보 도우미 항목을 사용하면 Cloud Shell에서 실행 중인 컨테이너 이미지 도구가 이미지를 가져오고 내보낼 수 있도록 Artifact Registry 저장소 위치에 인증할 수 있습니다.

gRPC 서비스 배포

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

  1. Cloud Shell에서 gRPC 서비스가 포함된 저장소를 클론하고 저장소 디렉터리로 전환합니다.

    git clone https://github.com/GoogleCloudPlatform/grpc-gke-nlb-tutorial.git ~/grpc-gke-nlb-tutorial
    
    cd ~/grpc-gke-nlb-tutorial
    
  2. 자체 서명 TLS 인증서와 비공개 키를 만듭니다.

    openssl req -x509 -newkey rsa:4096 -nodes -sha256 -days 365 \
        -keyout privkey.pem -out cert.pem -extensions san \
        -config \
        <(echo "[req]";
          echo distinguished_name=req;
          echo "[san]";
          echo subjectAltName=DNS:grpc.example.com
         ) \
        -subj '/CN=grpc.example.com'
    
  3. 자체 서명 TLS 인증서와 비공개 키가 포함된 envoy-certs라는 Kubernetes 보안 비밀을 만듭니다.

    kubectl create secret tls envoy-certs \
        --key privkey.pem --cert cert.pem \
        --dry-run=client --output yaml | kubectl apply --filename -
    

    Envoy는 TLS 연결을 종료할 때 이 TLS 인증서와 비공개 키를 사용합니다.

  4. 샘플 앱 echo-grpcreverse-grpc의 컨테이너 이미지를 빌드하고, Artifact Registry로 이미지를 푸시하고, Skaffold를 사용하여 앱을 GKE 클러스터에 배포합니다.

    skaffold run \
        --default-repo=us-central1-docker.pkg.dev/PROJECT_ID/envoy-grpc-tutorial-images \
        --module=echo-grpc,reverse-grpc \
        --skip-tests
    

    Skaffold는 애플리케이션을 컨테이너로 개발, 빌드, 푸시, 배포하기 위한 워크플로를 자동화하는 Google의 오픈소스 도구입니다.

  5. Skaffold를 사용하여 GKE 클러스터에 Envoy를 배포합니다.

    skaffold run \
        --digest-source=none \
        --module=envoy \
        --skip-tests
    
  6. 포드 2개가 각 배포에 맞게 준비되었는지 확인합니다.

    kubectl get deployments
    

    결과는 다음과 유사합니다. 모든 배포의 경우 READY 값은 2/2여야 합니다.

    NAME           READY   UP-TO-DATE   AVAILABLE   AGE
    echo-grpc      2/2     2            2           1m
    envoy          2/2     2            2           1m
    reverse-grpc   2/2     2            2           1m
    
  7. echo-grpc, envoy, reverse-grpc가 Kubernetes 서비스로 존재하는지 확인합니다.

    kubectl get services --selector skaffold.dev/run-id
    

    결과는 다음과 유사합니다. echo-grpcreverse-grpc 모두 TYPE=ClusterIPCLUSTER-IP=None이 있어야 합니다.

    NAME           TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)         AGE
    echo-grpc      ClusterIP      None          <none>           8081/TCP        2m
    envoy          LoadBalancer   10.40.2.203   203.0.113.1      443:31516/TCP   2m
    reverse-grpc   ClusterIP      None          <none>           8082/TCP        2m
    

gRPC 서비스 테스트

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

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

    go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
    
  2. envoy Kubernetes 서비스의 외부 IP 주소를 가져와 환경 변수에 저장합니다.

    EXTERNAL_IP=$(kubectl get service envoy \
        --output=jsonpath='{.status.loadBalancer.ingress[0].ip}')
    
  3. echo-grpc 샘플 앱으로 요청을 전송합니다.

    grpcurl -v -d '{"content": "echo"}' \
        -proto echo-grpc/api/echo.proto \
        -authority grpc.example.com -cacert cert.pem \
        $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, 02 Jun 2021 07:18:22 GMT
    hostname: echo-grpc-75947768c9-jkdcw
    server: envoy
    x-envoy-upstream-service-time: 3
    
    Response contents:
    {
      "content": "echo"
    }
    
    Response trailers received:
    (empty)
    Sent 1 request and received 1 response
    

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

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

    grpcurl -v -d '{"content": "reverse"}' \
        -proto reverse-grpc/api/reverse.proto \
        -authority grpc.example.com -cacert cert.pem \
        $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, 02 Jun 2021 07:20:15 GMT
    hostname: reverse-grpc-5c9b974f54-wlfwt
    server: envoy
    x-envoy-upstream-service-time: 1
    
    Response contents:
    {
      "content": "esrever"
    }
    
    Response trailers received:
    (empty)
    Sent 1 request and received 1 response
    

Envoy 구성

Envoy 구성을 자세히 알아보려면 Git 저장소에서 envoy/k8s/envoy.yaml 구성 파일을 살펴보세요.

route_config 섹션은 수신되는 요청이 echo-grpcreverse-grpc 샘플 앱으로 라우팅되는 방법을 지정합니다.

route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains:
    - "*"
    routes:
    - match:
        prefix: "/api.Echo/"
      route:
        cluster: echo-grpc
    - match:
        prefix: "/api.Reverse/"
      route:
        cluster: reverse-grpc

샘플 앱은 Envoy 클러스터로 정의됩니다.

clusters:
- name: echo-grpc
  connect_timeout: 0.5s
  type: STRICT_DNS
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  http2_protocol_options: {}
  load_assignment:
    cluster_name: echo-grpc
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: echo-grpc.default.svc.cluster.local
              port_value: 8081
  health_checks:
    timeout: 1s
    interval: 10s
    unhealthy_threshold: 2
    healthy_threshold: 2
    grpc_health_check: {}

클러스터 정의의 type: STRICT_DNSlb_policy: ROUND_ROBIN 필드는 Envoy가 address 필드에 지정된 호스트 이름의 DNS 조회를 수행하고 DNS 조회에 대한 응답으로 IP 주소 전체에 부하를 분산하도록 지정합니다. 응답에는 여러 IP 주소가 포함됩니다. 샘플 앱을 정의하는 Kubernetes 서비스 객체는 헤드리스 서비스를 지정하기 때문입니다.

http2_protocol_options 필드는 Envoy가 샘플 앱에 HTTP/2 프로토콜을 사용하도록 지정합니다.

health_checks 섹션의 grpc_health_check 필드는 gRPC 상태 점검 프로토콜을 사용하여 샘플 앱의 상태를 확인하도록 지정합니다.

문제 해결

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

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

  1. 관리 인터페이스를 열려면 포트 전달을 Cloud Shell에서 Envoy 포드 중 하나의 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가 제공하는 애플리케이션 레이어 기능 중 일부는 다른 부하 분산 솔루션에서도 제공하고 있습니다.

  • 외부 패스 스루 네트워크 부하 분산기 및 자체 관리형 Envoy 대신 전역 외부 애플리케이션 부하 분산기 또는 리전 외부 애플리케이션 부하 분산기를 사용할 수 있습니다. 외부 애플리케이션 부하 분산기를 사용하면 고급 트래픽 관리 기능, 관리형 TLS 인증서와 Cloud CDN, Google Cloud Armor, IAP와 같은 다른 Google Cloud 제품과의 통합 등 외부 패스 스루 네트워크 부하 분산기에 비해 여러 가지 이점이 있습니다.

    트래픽 관리 기능이 사용 사례를 충족하고 클라이언트 인증서 기반 인증(상호 TLS(mTLS) 인증이라고도 함) 지원이 필요하지 않은 경우 전역 외부 애플리케이션 부하 분산기 또는 리전 외부 애플리케이션 부하 분산기를 사용하는 것이 좋습니다. 자세한 내용은 다음 문서를 참조하세요.

  • Anthos Service Mesh 또는 Istio를 사용하는 경우 해당 기능을 사용하여 gRPC 트래픽을 라우팅하고 부하를 분산할 수 있습니다. Anthos Service Mesh와 Istio 모두 이 튜토리얼의 아키텍처와 유사하게 Envoy 백엔드를 사용하는 외부 패스 스루 네트워크 부하 분산기로 배포되는 인그레스 게이트웨이를 제공합니다. 단, Envoy가 Istio의 traffic routing 객체를 통해 구성된다는 것이 가장 큰 차이점입니다.

    이 튜토리얼의 예시 서비스를 Anthos Service Mesh 또는 Istio 서비스 메시에서 라우팅하려면 Kubernetes 서비스 매니페스트(echo-service.yamlreverse-service.yaml)에서 clusterIP: None 줄을 삭제해야 합니다. 즉, Envoy의 유사한 기능 대신 Anthos Service Mesh 또는 Istio의 서비스 검색 및 부하 분산 기능을 사용합니다.

    이미 Anthos Service Mesh 또는 Istio를 사용하는 경우 인그레스 게이트웨이를 사용하여 gRPC 서비스로 라우팅하는 것이 좋습니다.

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

내부 VPC 네트워크 연결

서비스를 VPC 네트워크 내에서만 GKE 클러스터 외부로 노출시키려면 내부 패스 스루 네트워크 부하 분산기 또는 내부 애플리케이션 부하 분산기를 사용하면 됩니다.

외부 패스 스루 네트워크 부하 분산기 대신 내부 패스 스루 네트워크 부하 분산기를 사용하려면 envoy-service.yaml 매니페스트에 cloud.google.com/load-balancer-type: "Internal" 주석을 추가합니다.

내부 애플리케이션 부하 분산기를 사용하려면 내부 애플리케이션 부하 분산기용 인그레스 구성 문서를 참조하세요.

삭제

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

프로젝트 삭제

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

    리소스 관리로 이동

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

리소스 삭제

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

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

    cd ; rm -rf ~/grpc-gke-nlb-tutorial
    
  2. GKE 클러스터를 삭제합니다.

    gcloud container clusters delete envoy-grpc-tutorial \
        --zone us-central1-f --async --quiet
    
  3. Artifact Registry에서 저장소를 삭제합니다.

    gcloud artifacts repositories delete envoy-grpc-tutorial-images \
        --location us-central1 --async --quiet
    

다음 단계