gRPC 예제

gRPC 예제 (gRPC Example)

이 예제는 TLS 트래픽을 종료하고 gRPC 서비스(즉 HTTP/2 사용)로 요청을 라우팅하는 Gateway를 구성하는 방법을 보여드려요. 동작시키려면 Helm 플래그 gatewayAPI.enableAlpn을 true로 설정해서 ALPN 지원을 활성화해야 해요.

출처: gRPC Example

본문

이 예제는 TLS 트래픽을 종료하고 요청을 gRPC 서비스(즉 HTTP/2 사용)로 라우팅하는 Gateway를 구성하는 방법을 보여드려요. 이 예제가 동작하려면 Helm 플래그 gatewayAPI.enableAlpn을 true로 설정해서 ALPN 지원을 활성화해야 해요. 이렇게 하면 클라이언트가 TLS 협상을 통해 HTTP/2를 요청할 수 있어요.

apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: tls-gateway
spec:
  gatewayClassName: cilium
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    hostname: grpc-echo.cilium.rocks
    tls:
      certificateRefs:
      - kind: Secret
        name: grpc-certificate
---
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: grpc-route
spec:
  parentRefs:
  - name: tls-gateway
  rules:
  - backendRefs:
    - name: grpc-echo
      port: 7070
---
apiVersion: v1
kind: Service
metadata:
  name: grpc-echo
spec:
  selector:
    app.kubernetes.io/name: grpc-echo
  ports:
    - name: grpc
      port: 7070
      # This is needed to instruct the route to use plaintext HTTP/2 or
      # you will get protocol errors.
      appProtocol: kubernetes.io/h2c
      targetPort: grpc
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-echo
  labels:
    app.kubernetes.io/name: grpc-echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: grpc-echo
  template:
    metadata:
      labels:
        app.kubernetes.io/name: grpc-echo
    spec:
      containers:
        - name: app
          image: gcr.io/istio-testing/app:latest
          ports:
            - name: grpc
              containerPort: 7070
          readinessProbe:
            failureThreshold: 2
            grpc:
              port: 7070
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1

이 예제는 만들어낸 셀프 사이닝 CA(인증 기관)가 서명한 TLS 인증서를 사용해요. 손쉬운 방법 중 하나는 mkcert를 사용하는 거예요. 인증서는 이 예제에서 사용하는 호스트 이름 grpc-echo.cilium.rocks를 검증해요.

$ mkcert bookinfo.cilium.rocks hispter.cilium.rocks
Created a new local CA 💥
Note: the local CA is not installed in the system trust store.
Run "mkcert -install" for certificates to be trusted automatically ⚠

Created a new certificate valid for the following names 📜
 - "grpc-echo.cilium.rocks"

The certificate is at "./grpc-echo.cilium.rocks.pem" and the key at "./grpc-echo.cilium.rocks-key.pem" ✅

It will expire on 28 September 2027 🗓

이 데모 키와 인증서로 Kubernetes 시크릿을 만들어볼게요:

$ kubectl create secret tls grpc-certificate --key=grpc-echo.cilium.rocks-key.pem --cert=grpc-echo.cilium.rocks.pem

(대안) cert-manager를 설치해볼게요:

$ helm repo add jetstack https://charts.jetstack.io
$ helm install cert-manager jetstack/cert-manager --version v1.16.2 \
    --namespace cert-manager \
    --set crds.enabled=true \
    --create-namespace \
    --set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \
    --set config.kind="ControllerConfiguration" \
    --set config.enableGatewayAPI=true

이제 CA Issuer를 만들어볼게요:

$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes/servicemesh/ca-issuer.yaml

Gateway와 GRPCRoute 배포하기 (Deploy the Gateway and GRPCRoute)

간단한 gRPC echo 서버와 그것을 노출하는 Gateway를 설정해요.

$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes/gateway/grpc-tls-termination.yaml

앞선 단계의 self-signed 인증서 Secrets는 이 Gateway가 사용해요.

cert-manager에게 이 Gateway에 인증서가 필요하다고 알리려면, 앞에서 만든 CA issuer의 이름으로 Gateway에 어노테이션을 달아주세요:

$ kubectl annotate gateway tls-gateway cert-manager.io/issuer=ca-issuer

그러면 TLS 인증서가 포함된 Certificate 객체와 Secret이 생성돼요.

$ kubectl get certificate,secret grpc-certificate
NAME                                           READY   SECRET             AGE
certificate.cert-manager.io/grpc-certificate   True    grpc-certificate   83s
NAME                      TYPE                DATA   AGE
secret/grpc-certificate   kubernetes.io/tls   3      78s

외부 IP 주소는 Gateway에 표시돼요. 또한 호스트 이름은 관련 HTTPRoute에 표시돼야 해요.

$ kubectl get gateway tls-gateway
NAME          CLASS    ADDRESS         PROGRAMMED   AGE
tls-gateway   cilium   10.104.247.23   True         29s

$ kubectl get grpcroutes
NAME         HOSTNAMES   AGE
grpc-route               116s

gRPC-web 변환 (gRPC-web Translation)

Cilium Gateway API는 기본적으로 Envoy의 gRPC-web → gRPC 요청 변환을 활성화해요. 파라미터화된 GatewayClass를 사용하는 모든 Gateway에 대해 gRPC-web 요청을 변경 없이 그대로 통과시키려면, GatewayClass가 참조하는 CiliumGatewayClassConfig에서 httpOptions.grpcWebTranslation.enabled를 false로 설정하면 돼요. 예제는 Disable gRPC-web translation을 참고해주세요.

Gateway의 호스트 이름과 IP 주소로 /etc/hosts를 업데이트해볼게요:

$ sudo perl -ni -e 'print if !/\.cilium\.rocks$/d' /etc/hosts; sudo tee -a /etc/hosts \
  <<<"$(kubectl get gateway tls-gateway -o jsonpath='{.status.addresses[0].value}') grpc-echo.cilium.rocks"

gRPC 요청 보내기 (Make gRPC Requests)

grpcurl CLI 도구를 사용해서 서비스가 올바르게 동작하는지 검증할 수 있어요. 이 예제에서 사용하는 echo 서버는 클라이언트가 만든 HTTP/2 요청에 대한 정보로 응답해요.

curl 요청에 CA의 인증서를 지정하면 해당 CA가 서명한 인증서를 신뢰한다고 말하는 셈이 돼요.

$ grpcurl -cacert ~/.local/share/mkcert/rootCA.pem grpc-echo.cilium.rocks:443 proto.EchoTestService/Echo

원한다면 CA를 제공하는 대신 -insecure를 지정해서 curl 클라이언트가 서버의 인증서를 검증하지 않도록 할 수도 있어요. 둘 중 어느 것도 없으면 알 수 없는 기관이 서명한 인증서라는 오류가 나요.

$ grpcurl grpc-echo.cilium.rocks:443 proto.EchoTestService/Echo
{
  "message": "Host=grpc-echo.cilium.rocks:443\nRequestHeader=:authority:grpc-echo.cilium.rocks:443\nRequestHeader=content-type:application/grpc\nRequestHeader=grpc-accept-encoding:gzip\nRequestHeader=x-forwarded-proto:https\nRequestHeader=x-request-id:f7889cda-08b2-45cf-9329-833633ae8d9c\nRequestHeader=user-agent:grpcurl/dev-build (no version set) grpc-go/1.61.0\nRequestHeader=x-forwarded-for:172.22.0.7\nRequestHeader=x-envoy-internal:true\nStatusCode=200\nServiceVersion=\nServicePort=7070\nIP=10.244.1.101\nProto=GRPC\nEcho=\nHostname=grpc-echo-6879fc6969-2kh6r\n"
}

더 알아보기 (Learn more)