LoginSignup
27
17

More than 5 years have passed since last update.

k8sで、HTTP/2(gRPCサーバ)を負荷分散したい

Last updated at Posted at 2019-02-06

kubernetesで、HTTP/2を負荷分散してみた

はじめに

背景

現在学生プロジェクトでインフラとして、GKE(Google Kubernetes Engine)を利用させて頂いています。
REST APIサーバを、gRPCに換装してみようとした時にある問題にぶち当たりました。

前提知識として

Kubernetesのロードバランサ

Services are a “layer 4” (TCP/UDP over IP) construct, the proxy was purely in userspace
現行のKubernetesのIngressは、OSI参照モデルのトランスポート層(L4)のロードバランサを提供しています。(beta版はその限りではないです!)

gRPCの通信の仕方

gRPCでの通信は、 一つのTCPコネクションを使い回します
つまり、コネクションが確立している間はサーバとクライアントは繋がり続けます。

問題とは

gRPC is a modern RPC protocol implemented on top of HTTP/2. HTTP/2 is a Layer 7 (Application layer) protocol (参照: 公式サイトより)
というように、gRPCは、アプリケーション層(L7)で動作するのです。
つまり、L4のロードバランサでは、 適切に負荷分散できない場合 があるのです!
Ingrees使えないじゃん!

Envoyに出会いました

L7 LoadBalancerと調べていたら記事が見つかって来ました。
ref: kubernetesでgRPCするときにenvoy挟んでみたよ
Envoyとは、クラウドネイティブ向けつまりマイクロサービス向けのProxyとして設計されたオープンソースのソフトです。
L7プロキシももちろん対応です!

セットアップ

では早速導入していきましょう!
流れはこんな感じです。
1. gRPCサーバのServiceをLoad BalancerからHeadless Serviceにする
2. Envoyをクラスタにデプロイし外部公開

gRPCサーバのServiceをLoad BalancerからHeadless Serviceにする

Envoyのコンフィグファイルはこんな感じになっています。

apiVersion: v1
kind: ConfigMap
metadata:
  name: "envoy-config"
data:
  envoy.json: |
    {
      "listeners": [
        {
          "address": "tcp://0.0.0.0:15001",
          "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": ""
                        }
                      ]
                    }
                  ]
                },
                "filters": [
                  {
                    "type": "decoder",
                    "name": "router",
                    "config": {}
                  }
                ]
              }
            }
          ]
        }
      ],
      "admin": {
       "access_log_path": "/dev/stdout",
       "address": "tcp://127.0.0.1:8001"
      },
      "cluster_manager": {
        "clusters": [
          {
            "name": "rakusale-grpc",
            "features": "http2",
            "connect_timeout_ms": 250,
            "type": "strict_dns",
            "lb_type": "round_robin",
            "hosts": [{"url": "tcp://Host名:Port番号"}]
         }
        ]
      }
    }

注目して欲しいところはここです!

"cluster_manager": {
        "clusters": [
          {
            "name": "rakusale-grpc",
            "features": "http2",
            "connect_timeout_ms": 250,
            "type": "strict_dns",
            "lb_type": "round_robin",
            "hosts": [{"url": "tcp://Host名:Port番号"}]
         }
        ]
      }

clusters["hosts"]のところに、サーバ側のIPとポートを指定します。
つまり、Envoy側が負荷分散するためにPodのIPを知っている必要があります。

ここで、Headless Service

Headless Serviceは、Serviceに対してDNSリクエストを送るとPodのIPを返してくれるものです。
動かしたいサーバのサービスはこのように設定しています。

apiVersion: v1
kind: Service
metadata:
  labels: 
    name: ""
  name: ""
spec:
  clusterIP: None
  ports:
    - name: ""
      port: 3001
      protocol: TCP
      targetPort: 3001
  selector:
    app: ""

ClusterIP: Noneとするだけで、Headless Serviceになります!

Envoyをクラスタにデプロイし外部公開

必要なものは3つです。
- envoy_configmap.yaml : さっき紹介しています!
- envoy_deployment.yaml
- envoy_service.yaml

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:6e3633496f5a9412abdca8bac7db6b701ae8ce14
          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_service.yaml

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

以上を用意して kubectl apply してGIPに接続すれば、負荷分散が完成しているはずです!

終わりに

Kubernetesクラスタ内のパケットとかネットワーク監視するツールで良いのがあったら教えて欲しいです!
あと、gRPCサーバ書く時にログをきちんと書く実装にしないと心底不便だなと思いました、、、
いつもはフレームワークがやってくれていたので、、、
今後も精進していきます

27
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
17