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の通信ができるはず!!