Kubernetes + Envoyでクラスタ内からクラスタ外のサーバーへの接続をロードバランシングする

Kubernetesのクラスタ内のPodからクラスタ外にあるMySQLなどのサーバに接続するときには、[Envoy](https://www.envoyproxy.io/)を使うと良さそうという話です。

まだ運用はしていませんが、設定方法を載せるので迷っている方の参考になったら幸いです。

## Envoyを選択する理由

要件にもよりますが、次のような理由からです。

- productionやstagingなどの環境によりMySQLの接続先を自由に切り替えたかったので、ELBやEC2上でプロキシを立てるのではなく、サイドカーコンテナを立てる方向で考えていた
- 今回は、マイクロサービスを構築するわけではなかったため、Istioはtoo muchと判断した
- HAProxyやnginxは[こちらの比較記事](https://blog.getambassador.io/envoy-vs-nginx-vs-haproxy-why-the-open-source-ambassador-api-gateway-chose-envoy-23826aed79ef)を読んだ結果、除外した
- KubernetesのNon-selector Serviceを使うという手もあるが、手元で検証したところヘルスチェックが動作しなかった。（もしかしたらやりようはあるのかもしれません）

## 構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116010/e6cd419a-da6b-5e77-a6b4-5c565b9353f7.png)

## 設定内容

envoyの設定はconfigmapにまとめています。

```yaml:envoy-configmap.yml
kind: ConfigMap
apiVersion: v1
metadata:
  name: envoy-conf
data:
  envoy.yaml: |
    static_resources:
      listeners:
      - name: mysql_listener
        address:
          socket_address:
            address: 0.0.0.0
            port_value: 3306
        filter_chains:
        - filters:
          - name: envoy.filters.network.mysql_proxy
            typed_config:
              "@type": type.googleapis.com/envoy.config.filter.network.mysql_proxy.v1alpha1.MySQLProxy
              stat_prefix: egress_mysql
          - name: envoy.filters.network.tcp_proxy
            typed_config:
              "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy
              stat_prefix: mysql_tcp
              cluster: mysql_cluster

      clusters:
      - name: mysql_cluster
        connect_timeout: 1s
        health_checks:
        - healthy_threshold: 3
          interval: 5s
          tcp_health_check: {}
          timeout: 1s
          unhealthy_threshold: 3
        type: strict_dns
        load_assignment:
          cluster_name: mysql_cluster
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: **.**.**.** # ここにそれぞれのMySQLのIPアドレスを設定する
                    port_value: 3306
            - endpoint:
                address:
                  socket_address:
                    address: **.**.**.** # ここにそれぞれのMySQLのIPアドレスを設定する
                    port_value: 3306

    admin:
      access_log_path: "/dev/null"
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 8001
```

Podの設定は、nginx+php-fpmのサイドカーとしてEnvoyを追加しています。

```yaml:api.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      hostname: myhostname
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
      - name: php-fpm
        image: phpfpm:latest
        ports:
        - containerPort: 9000
      - name: envoy-proxy
        image: envoyproxy/envoy-dev:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3306
          name: proxy-mysql
          protocol: TCP
        resources:
          limits:
            cpu: 0.5
            memory: 1Gi
        volumeMounts:
          - name: envoy-conf
            mountPath: /etc/envoy/envoy.yaml
            subPath: envoy.yaml
      volumes:
        - name: envoy-conf
          configMap:
            name: envoy-conf
            items:
              - key: envoy.yaml
                path: envoy.yaml
                mode: 0644
---
apiVersion: v1
kind: Service
metadata:
  name: web-service
  labels:
    app: nginx
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
    - name: https
      port: 443
      targetPort: 80
  type: LoadBalancer
  selector:
    app: nginx
```

この設定により、php-fpmのコンテナから0.0.0.0:3306に接続するとMySqlに繋がります。

## まとめ

Kubernetesのクラスタに外部から接続する方法は、ネットで探すと色々出てくるのですが、クラスタから外部サーバに接続する方法についての情報はあまりない印象だったので書くことにしました。