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プロキシももちろん対応です!
セットアップ
では早速導入していきましょう!
流れはこんな感じです。
- gRPCサーバのServiceをLoad BalancerからHeadless Serviceにする
- 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サーバ書く時にログをきちんと書く実装にしないと心底不便だなと思いました、、、
いつもはフレームワークがやってくれていたので、、、
今後も精進していきます