Help us understand the problem. What is going on with this article?

GKEでgRPCのサーバをenvoy使って外部公開したよ

More than 1 year has passed since last update.

GKEでマイクロサービスとかやってて、たまーーーにgRPCのサービスを外部に公開したくなることってありますよね?
でもGKEのL7LB(HTTPロードバランサ)はgRPCに対応してません(2018/03/18)。もうすぐ出るかも?
さらにはgRPCの通信は永続化されるので高負荷時にスケールさせられない問題があります。
そこで使えるのがenvoyproxyというわけです。
envoyはTLSのターミネーションにも対応しています。外部に公開したいgRPCサービスの前段にenvoyを噛ませればTLSのターミネーションからスケールアウト時のバランシングまで対応できちゃうというわけでとても便利です。

構成

L4LB -> envoy -> grpcService

grpcServiceはport:50051でGKE内にNodePortとして存在するとします。

この状態にenvoyを導入します。

TLSの準備

まず、TLSの鍵と証明書を用意します。何かしらの方法で用意して下さい。
用意された鍵と証明書をGKEのsecretとして登録します。

tls_secret.yaml
apiVersion: v1
data:
  tls.crt: base64 encoded cert
  tls.key: base64 encoded key
kind: Secret
metadata:
  name: tlssecret
  namespace: default
type: Opaque

証明書と鍵をbase64に変換する必要があります。
base64への変換は下記のコマンドなんかを使うと良いでしょう。

$ base64 -i path/to/wildcard.example.com.key

envoyの設定

TLSの準備ができたらenvoyの設定を作ります。設定はconfigmapで管理すると良い感じです。

envoy_configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: "envoy-config"
data:
  envoy.json: |
    {
      "listeners": [
        {
          "address": "tcp://0.0.0.0:15001",
          "ssl_context" : {
            "cipher_suites" : "[ECDHE-RSA-AES256-GCM-SHA384|ECDHE-RSA-AES128-GCM-SHA256]",
            "cert_chain_file" : "/etc/tlssecret/tls.crt",
            "private_key_file" : "/etc/tlssecret/tls.key"
          },
          "filters": [
            {
              "type": "read",
              "name": "http_connection_manager",
              "config": {
                "codec_type": "auto",
                "stat_prefix": "ingress_http",
                "route_config": {
                  "virtual_hosts": [
                    {
                      "name": "service",
                      "domains": ["*"],
                      "routes": [
                        {
                          "timeout_ms": 0,
                          "prefix": "/",
                          "cluster": "grpc"
                        }
                      ]
                    }
                  ]
                },
                "filters": [
                  {
                    "type": "decoder",
                    "name": "router",
                    "config": {}
                  }
                ]
              }
            }
          ]
        }
      ],
      "admin": {
        "access_log_path": "/dev/stdout",
        "address": "tcp://127.0.0.1:8001"
      },
      "cluster_manager": {
        "clusters": [
          {
            "name": "grpc",
            "features": "http2",
            "connect_timeout_ms": 250,
            "type": "strict_dns",
            "lb_type": "round_robin",
            "hosts": [{"url": "tcp://grpc-service:50051"}]
          }
        ]
      }
    }

このように/etc/tlssecretにTLSの設定を読みに行くような記述にします。(パスは別のところでもかまいません)

grpc-serviceのheadlessService化

上述のenvoyの設定で"hosts": [{"url": "tcp://grpc-service:50051"}]このように記述しています。
これはkubernetesのheadlessServiceを利用してサービスディスカバリーするためです。
kubernetesのheadlessServiceは問い合わせるとPodのIPアドレス一覧を返す仕組みです。
つまり既存で用意していたgrpc-serviceをheadlessService化する必要があります。

grpc_service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    name: "grpc"
  name: "grpc-service"
spec:
  clusterIP: None
  selector:
    app: "grpc"
  ports:
    - name: grpc
      port: 50051
      targetPort: 50051
      protocol: TCP

このようにclusterIP: NoneとしてあげるとheadlessService化します。

envoyのDeployment

configmapとheadlessServiceができたらdeploymentを用意します。

envoy_deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: "envoy"
spec:
  replicas: "2"
  template:
    metadata:
      labels:
        app: "envoy"
    spec:
      volumes:
        - name: envoy
          configMap:
            name: "envoy-config"
        - name: tls
          secret:
            secretName: tlssecret

      containers:
        - name: envoy
          image: envoyproxy/envoy:latest
          command:
            - "/usr/local/bin/envoy"
          args:
            - "--config-path /etc/envoy/envoy.json"
          resources:
            limits:
              memory: 512Mi
          ports:
            - containerPort: 15001
              name: app
            - containerPort: 8001
              name: envoy-admin
          volumeMounts:
            - name: envoy
              mountPath: /etc/envoy
            - name: tls
              mountPath: /etc/tlssecret
              readOnly: true

ここではenvoyのDockerImageにTLSの情報とenvoyの設定をマウントしています。
/etc/envoyにenvoyの設定を、/etc/tlssecretにTLSの証明書等をマウントしているのがvolumeMounts等でわかるかと思います。

envoyのservice

次に用意したenvoyを外の世界と繋げるためにserviceを用意します。

envoy_service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    name: "envoy"
  name: "envoy-service"
spec:
  type: LoadBalancer
  loadBalancerIP: "192.0.2.3"
  selector:
    app: "envoy"
  ports:
    - name: tcp
      port: 15001
      targetPort: 15001

type:LoadBalancerでserviceを作ります。これでGCPのL4LBが立ち上がります。
外部に公開することを考えるとStaticIPAdressを用意したほうが良いでしょう。これはGCPの画面から(terraform等のが良いかも)で準備します。
準備したIPアドレスはloadBalancerIPに記述してあげます。
DNSの設定も別途行っておいて下さい。

これで完成です。無事TLSで暗号化されたgRPCの通信ができるはず!!

cyberagent
サイバーエージェントは「21世紀を代表する会社を創る」をビジョンに掲げ、インターネットテレビ局「AbemaTV」の運営や国内トップシェアを誇るインターネット広告事業を展開しています。インターネット産業の変化に合わせ新規事業を生み出しながら事業拡大を続けています。
http://www.cyberagent.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away