Apache APISIX와 gRPC-Web 통합하기

Fei Han

January 25, 2022

Ecosystem

gRPC 웹 소개

원래 Google에서 개발한 gRPC는 HTTP/2 위에서 구현된 고성능 원격 프로시저 호출 프레임워크입니다. 그러나 브라우저가 HTTP/2를 직접 노출하지 않기 때문에 웹 애플리케이션은 gRPC를 직접 사용할 수 없습니다. gRPC 웹은 이 문제를 해결하기 위한 표준화된 프로토콜입니다.

첫 번째 gRPC-웹 구현은 2018년에 JavaScript 라이브러리로 출시되었으며, 이를 통해 웹 애플리케이션이 gRPC 서비스와 직접 통신할 수 있습니다. 원리는 HTTP/1.1 및 HTTP/2와 호환되는 종단 간 gRPC 파이프라인을 생성하는 것입니다. 그런 다음 브라우저가 일반 HTTP 요청을 보내고, 브라우저와 서버 사이에 위치한 gRPC-웹 프록시가 요청과 응답을 변환합니다. gRPC와 유사하게, gRPC 웹은 웹 클라이언트와 백엔드 gRPC 서비스 간에 미리 정의된 계약을 사용합니다. Protocol Buffers는 메시지를 직렬화하고 인코딩하는 데 사용됩니다.

gRPC-웹 작동 방식

gRPC 웹을 사용하면 사용자는 브라우저나 Node 클라이언트를 사용하여 백엔드 gRPC 애플리케이션을 직접 호출할 수 있습니다. 그러나 브라우저 측에서 gRPC 서비스를 호출하기 위해 gRPC-웹을 사용하는 데는 몇 가지 제한 사항이 있습니다.

  • 클라이언트 측 스트리밍 및 양방향 스트리밍 호출은 지원되지 않습니다.
  • 도메인 간 gRPC 서비스 호출을 위해서는 서버 측에서 CORS를 구성해야 합니다.
  • gRPC 서버 측은 gRPC-웹을 지원하도록 구성되어야 하거나, 브라우저와 서버 간의 호출을 변환할 수 있는 타사 서비스 에이전트가 있어야 합니다.

Apache APISIX gRPC 웹 프록시

Apache APISIX는 플러그인을 통해 gRPC 웹 프로토콜의 프록시를 지원하며, grpc-web 플러그인에서 gRPC 웹과 gRPC 서버 간의 통신 시 프로토콜 변환 및 데이터 코덱 작업을 수행합니다. 그 통신 과정은 다음과 같습니다.

gRPC 웹 클라이언트 -> Apache APISIX (프로토콜 변환 & 데이터 코덱) -> gRPC 서버

다음은 gRPC 웹 클라이언트를 구축하고 Apache APISIX를 통해 gRPC 웹 요청을 프록시하는 방법을 보여주는 완전한 예제입니다. 아래 예제에서는 Go를 gRPC 서버 서버 핸들러로, Node를 gRPC 웹 클라이언트 요청자로 사용합니다.

Protocol Buffer 구성

첫 번째 단계는 Protocol Buffer 컴파일러 및 관련 플러그인을 설치하는 것입니다.

  1. protocproto-grpc-* 설치.

    Protocol Buffer 컴파일러 protoc.proto에 대한 Go, JavaScript 및 gRPC 웹 인터페이스 코드를 생성하기 위한 protoc-gen-goprotoc-gen-grpc-web 플러그인을 시스템에 설치해야 합니다.

    다음 스크립트를 실행하여 위 구성 요소를 설치하십시오.

    #!/usr/bin/env bash set -ex PROTOBUF_VERSION="3.19.0" wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip unzip protoc-${PROTOBUF_VERSION}-linux-x86_64.zip mv bin/protoc /usr/local/bin/protoc mv include/google /usr/local/include/ chmod +x /usr/local/bin/protoc PROTO_GO_PLUGIN_VER="1.2.0" wget https://github.com/grpc/grpc-go/releases/download/cmd/protoc-gen-go-grpc/v${PROTO_GO_PLUGIN_VER}/protoc-gen-go-grpc.v${PROTO_GO_PLUGIN_VER}.linux.amd64.tar.gz tar -zxvf protoc-gen-go-grpc.v${PROTO_GO_PLUGIN_VER}.linux.amd64.tar.gz mv protoc-gen-go-grpc /usr/local/bin/protoc-gen-go chmod +x /usr/local/bin/protoc-gen-go PROTO_JS_PLUGIN_VER="1.3.0" wget https://github.com/grpc/grpc-web/releases/download/${PROTO_JS_PLUGIN_VER}/protoc-gen-grpc-web-${PROTO_JS_PLUGIN_VER}-linux-x86_64 mv protoc-gen-grpc-web-${PROTO_JS_PLUGIN_VER}-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web chmod +x /usr/local/bin/protoc-gen-grpc-web
  2. SayHello 예제 proto 파일 생성.

    // a6/echo.proto syntax = "proto3"; package a6; option go_package = "./;a6"; message EchoRequest { string message = 1; } message EchoResponse { string message = 1; } service EchoService { rpc Echo(EchoRequest) returns (EchoResponse); }

서버 측 애플리케이션 구성

  1. 서버 측 Go 원시 메시지 및 서비스/클라이언트 스텁 생성.

    protoc -I./a6 echo.proto --go_out=plugins=grpc:./a6
  2. 서버 측 핸들러 인터페이스 구현.

    // a6/echo.impl.go package a6 import ( "errors" "golang.org/x/net/context" ) type EchoServiceImpl struct { } func (esi *EchoServiceImpl) Echo(ctx context.Context, in *EchoRequest) (*EchoResponse, error) { if len(in.Message) <= 0 { return nil, errors.New("message invalid") } return &EchoResponse{Message: "response: " + in.Message}, nil }
  3. 서버 측 애플리케이션 런타임 진입 파일.

    // server.go package main import ( "fmt" "log" "net" "apisix.apache.org/example/a6" "google.golang.org/grpc" ) func main() { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 50001)) if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() a6.RegisterEchoServiceServer(grpcServer, &a6.EchoServiceImpl{}) if err = grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve: %s", err) } }
  4. 서버 측 서비스 컴파일 및 시작.

    go build -o grpc-server server.go ./grpc-server

클라이언트 프로그램 구성

  1. 클라이언트 측 proto 코드 생성.

    클라이언트 측 JavaScript 원시 메시지, 서비스/클라이언트 스텁 및 gRPC 웹의 JavaScript 인터페이스 코드를 생성합니다.

    gRPC 웹의 proto 플러그인은 두 가지 모드의 코드 생성을 제공합니다.

    1. mode=grpcwebtext: 기본 생성 코드는 grpc-web-text 형식으로 페이로드를 전송합니다.

    • Content-type: application/grpc-web-text

    • 페이로드는 base64 인코딩 사용

    • 단일 및 서버 스트리밍 호출 지원

    1. mode=grpcweb: 페이로드를 바이너리 protobuf 형식으로 전송합니다.

    • Content-type: application/grpc-web+proto
    • 페이로드는 바이너리 protobuf 형식
    • 현재는 단일 호출만 지원
    protoc -I=./a6 echo.proto --js_out=import_style=commonjs:./a6 --grpc-web_out=import_style=commonjs,mode=grpcweb:./a6
  2. 클라이언트 측 종속성 설치.

    npm i grpc-web npm i google-protobuf
  3. 클라이언트 측 진입 파일 실행.

    // client.js const {EchoRequest} = require('./a6/echo_pb'); const {EchoServiceClient} = require('./a6/echo_grpc_web_pb'); // Apache APISIX의 진입점에 연결 let echoService = new EchoServiceClient('http://127.0.0.1:9080'); let request = new EchoRequest(); request.setMessage("hello") echoService.echo(request, {}, function (err, response) { if (err) { console.log(err.code); console.log(err.message); } else { console.log(response.getMessage()); } });
  4. 최종 프로젝트 구조

    $ tree . ├── a6 │ ├── echo.impl.go │ ├── echo.pb.go │ ├── echo.proto │ ├── echo_grpc_web_pb.js │ └── echo_pb.js ├── client.js ├── server.go ├── go.mod ├── go.sum ├── package.json └── package-lock.json

위 단계를 완료하면 gRPC 서버 서버 애플리케이션과 gRPC 웹 클라이언트 애플리케이션을 구성하고, 서버 애플리케이션을 시작하여 50001 포트에서 요청을 수신할 준비가 되었습니다.

Apache APISIX 구성

다음으로, Apache APISIX 라우팅 플러그인 구성에서 grpc-web 플러그인을 활성화하여 gRPC 웹 요청을 프록시합니다.

  1. grpc-web 프록시 플러그인 활성화.

    라우팅은 접두사 일치를 사용해야 합니다 (예: /* 또는 /grpc/example/*), 왜냐하면 gRPC 웹 클라이언트는 URI에 proto에 선언된 패키지 이름, 서비스 인터페이스 이름, 메서드 이름 등을 전달하기 때문입니다 (예: /path/a6.EchoService/Echo), 절대 일치를 사용하면 플러그인이 URI에서 proto 정보를 추출할 수 없습니다.

    $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri":"/*", // 접두사 일치 모드 "plugins":{ "grpc-web":{} // gRPC 웹 플러그인 활성화 }, "upstream":{ "scheme":"grpc", "type":"roundrobin", "nodes":{ "127.0.0.1:50001":1 // gRPC 서버 수신 주소 및 포트 } } }'
  2. gRPC 웹 프록시 요청 검증.

    Node에서 client.js를 실행하여 gRPC 웹 프로토콜 요청을 Apache APISIX로 보낼 수 있습니다.

    위의 클라이언트 측 및 서버 측 처리 로직은 각각 다음과 같습니다: 클라이언트가 서버에 hello라는 메시지를 보내고, 서버가 메시지를 받아 response: hello로 응답합니다. 실행 결과는 다음과 같습니다.

    $ node client.js response: hello
  3. grpc-web 프록시 플러그인 비활성화.

    라우팅 플러그인 구성에서 grpc-web 속성을 제거하기만 하면 됩니다.

    $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri":"/*", "plugins":{ }, "upstream":{ "scheme":"grpc", "type":"roundrobin", "nodes":{ "127.0.0.1:50001":1 } } }'

요약

이 글은 Apache APISIX에서 grpc-web을 사용하는 방법에 대한 실습 경험을 제공합니다.

GitHub Discussions에서 토론을 시작하거나 메일링 리스트를 통해 소통해 보세요.

Tags: