GKE Ingress + gRPC アプリケーションのヘルスチェックをどうにかする

Jin Naraoka
google-cloud-jp
Published in
15 min readDec 11, 2019

--

この記事は Google Cloud Japan Customer Engineer Advent Calendar 2019 の 9 日目の記事です。

TL;DR

  • GKE Ingress + gRPC アプリケーションはヘルスチェックに一工夫必要
  • あえて Nginx を使って解決してみる
  • すぐに使えるコマンド例と設定ファイルつき

HTTP(S) ロードバランサーのヘルスチェックは gRPC に対応していない

近頃使われることが多くなってきた gRPC ですが、GCP の HTTP(S) ロードバランサーは gRPC でヘルスチェックを行うことができません。
これは HTTP(S) ロードバランサーを Ingress として使う GKE で「GKE Ingress + gRPC アプリケーション」をデプロイしようとしても、そのままではヘルスチェックが通らないため、クライアントからのリクエストがアプリケーションへ転送されないということを意味します。

GKE Ingress で作成されたロードバランサー。ヘルスチェックを gRPC で行えないため、いつまでたっても「正常」にならない(HTTP/2 でヘルスチェックが行われている)

どう対応するか?

GKE Ingress からのヘルスチェックは HTTP/2 で行われることになるため、これに対して正常なステータスを返す必要があり、現状では 3 つの方法が考えられます。

  1. アプリケーションに HTTP/2 で応答するヘルスチェック用のエンドポイントを追加する
  2. アプリケーション Pod の前段にリバースプロキシ Pod を置きヘルスチェックに応答する(gRPC リクエストはリバースプロキシ Pod からアプリケーション Pod に転送する)
  3. アプリケーションの Pod にサイドカーとしてリバースプロキシコンテナを配置してヘルスチェックに応答する(gRPC リクエストはリバースプロキシコンテナからアプリケーションコンテナに転送する)

この記事ではアプリケーションの変更が不要でサイドカーを入れるだけの 「3」の方法を、個人的に好きな Nginx を使ってやってみます。

プロキシコンテナをサイドカーとして配置してヘルスチェックに応答させる時の構成図

手順

GKE クラスタの作成

さくっと gcloud コマンドで GKE クラスタを作成します。
ちなみに gcloud コマンドの実行は Cloud Shell からが便利です。
Cloud Shell には gcloud コマンドの他にも git や kubectl など GKE で何かをするときに必要となるコマンドが一通り揃っています : )

$ gcloud container clusters create my-cluster \
--num-nodes=1 \
--zone=asia-northeast1-a

サーバ証明書の作成

ドキュメントに以下のような記載があるとおり、HTTP/2 のヘルスチェックリクエストは TLS で行われるので、サーバ証明書を用意します。

If the backend service is configured to use an HTTPS or HTTP/2 health check, the request is encrypted on its way to the backend instance.

またこちらに以下の記載があるとおり、ヘルスチェックは証明書の検証を行わないため自己署名証明書を使用することができます。

Google Cloud health check probe systems do not perform certificate validation

ここでは自己署名証明書を使用しますが、Production 環境ではもちろんちゃんとしたものを使用してください。

秘密鍵の作成

$ openssl genrsa -out self-signed.key 2048

Certificate Signing Request の作成

$ openssl req -new -key self-signed.key -out self-signed.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:Minato-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]:GCJ
Organizational Unit Name (eg, section) []:CE
Common Name (e.g. server FQDN or YOUR name) []:*.example.com
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

証明書の作成

$ openssl x509 -req -days 3650 -in self-signed.csr -signkey self-signed.key -out self-signed.crt

Kubernetes Secret の作成

作成した証明書と秘密鍵を Kubernetes の Secret オブジェクトにします。
この Secret オブジェクトを GKE Ingress と Nginx コンテナで使用します。

$ kubectl create secret tls tls-cert \
--cert=self-signed.crt \
--key=self-signed.key

作成した Secret オブジェクトを describe してファイル名を確認します。
(ここでは tls.crttls.key になります)

$ kubectl describe secret tls-cert
Name: tls-cert
Namespace: default
Labels: <none>
Annotations: <none>
Type: kubernetes.io/tlsData
====
tls.crt: 1192 bytes
tls.key: 1675 bytes

Nginx 設定ファイルの作成

リバースプロキシコンテナには Nginx のオフィシャルイメージを使用します。
オフィシャルイメージの Nginx は /etc/nginx/nginx.conf から /etc/nginx/conf.d/*.conf をインクルードするように設定されていて、 /etc/nginx/conf.d には default.conf のみが存在しています。
今回はこの default.conf を変更して HTTP/2 ヘルスチェックと gRPC アプリケーションのリクエストに対応できるようにしてみます。

以下の内容で default.conf を作成します。

server {
listen 443 ssl http2;
ssl_certificate tls/tls.crt;
ssl_certificate_key tls/tls.key;
# for health check from load balancer
location = / {
return 200;
}
# for gRPC Application
location / {
grpc_pass grpc://localhost:9000;
}
}
  • ssl_certificatessl_certificate_key には上で確認した Secret オブジェクト内のファイル名を指定します。
    また相対パスで tls ディレクトリを指定していますが、これは Secret オブジェクトをこのパスにマウントするためです(後述)
  • location ディレクティブを使用して、ヘルスチェックリクエストとそれ以外のリクエスト(gRPC アプリケーションへのリクエスト)を処理しています。
    / 宛に来るヘルスチェックリクエストには 200 OK を返し、それ以外のパスへのリクエストは grpc_passディレクティブを使用してアプリケーションコンテナに転送します。

Kubernetes ConfigMap の作成

作成した Nginx の設定ファイルから ConfigMap を作成します。
変更頻度の多い設定ファイルを ConfigMap にすることで、コンテナイメージから切り離すことができます。
こうすることで、設定ファイルの変更のためだけにコンテナイメージの再作成を行わずにすむなど管理上のメリットが生まれます。

ConfigMap は以下の様にファイルを指定して作成することができます。

$ kubectl create configmap nginx-conf --from-file=default.conf

Kubernetes Deployment の作成

gRPC アプリケーションとして gRPC echo サーバを、リバースプロキシコンテナには上述したように Nginx を使用しています。
以下の内容で deployment.yamlを作成します。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: yages
spec:
replicas: 1
template:
metadata:
labels:
app: yages
spec:
containers:
- name: nginx
image: nginx:1.17-alpine
ports:
- containerPort: 443
protocol: TCP
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d
- name: tls-cert
mountPath: /etc/nginx/tls
- name: yages
image: quay.io/mhausenblas/yages:0.1.0
ports:
- containerPort: 9000
protocol: TCP
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
- name: tls-cert
secret:
secretName: tls-cert
  • volumes を使用して上記で作成した Kubernetes の Secret と ConfigMap を Pod にアタッチしています。
  • nginx コンテナでは volumeMounts を使用して Secret オブジェクト内のサーバ証明書 / 秘密鍵とConfigMap オブジェクト内の Nginx 設定ファイルをコンテナ内の指定のパス( /etc/nginx/tls, /etc/nginx/conf.d)へマウントしています。

作成したマニフェストは以下のようにして Kubernetes に適用します。

$ kubectl apply -f deployment.yaml

Kubernetes Service の作成

Deployment に対して Service を作成していきます。
ポイントは HTTP/2 であることを annotations を使って設定しているところです。
また、GKE Ingress をこの上に作成する場合、Service タイプは NodePort である必要があります。

以下の内容で service.yaml を作成します。

apiVersion: v1
kind: Service
metadata:
annotations:
cloud.google.com/app-protocols: '{"my-port":"HTTP2"}'
name: yages
spec:
type: NodePort
ports:
- name: my-port
port: 443
targetPort: 443
protocol: TCP
selector:
app: yages

Deployment と同じように以下のコマンドで Kubernetes に適用します。

$ kubectl apply -f service.yaml

Kubernetes Ingress の作成

最後に Ingress を作成して外部からアクセスできるようにします。
GKE Ingress となる HTTP(S) Load Balancer の TLS 証明書として、上で作成した Secret オブジェクトを指定しています。
この記事では簡単のためにリバースプロキシコンテナと同じ Secret オブジェクトを再利用していますが、必ず同じにする必要はありません。

以下の内容で ingress.yaml を作成します。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: yages
spec:
tls:
- secretName: tls-cert
backend:
serviceName: yages
servicePort: 443

Deployment, Service と同じように以下のコマンドで Kubernetes に適用します。

$ kubectl apply -f ingress.yaml

動作確認

一通りデプロイが完了したので、結果を見ていきましょう。
まずは問題のヘルスチェックがどうなったかが以下の画像になります。

ヘルスチェックが通るようになり「正常」が「1/1」になっている

追加したリバースプロキシコンテナがヘルスチェックに対してきちんと応答しているようです。

GKE 上のコンテナログは Stackdriver Logging から確認することができます。(このように GKE は 様々なGCP サービスと統合されていて、その便利な機能を使えるのもポイントです)
実際にリバースプロキシコンテナである Nginx のアクセスログを Stackdriver Logging で見てみると、アクセスログからもヘルスチェックに対してきちんと応答を返せていることがわかります。

Stackdriver Logging 上の Nginx アクセスログ(抜粋)

次に gRPC アプリケーションへのリクエストもちゃんと通るかを確認してみます。
ここでは、gRPC アプリケーションとして利用した gRPC echo サーバの README にも書かれていた gRPCurl を使ってみます。

まずはアクセス先となる Ingress の External IP を調べて、

$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
yages * 34.102.214.64 80, 443 24h

判明した IP アドレス (34.102.214.64) に対して gRPCurl を実行します。
(作成した証明書のコモンネームにあわせて servername オプションを指定します。またこの記事では自己署名証明書をしようしているので、 insecure オプションを使用して証明書の検証をしないようにしています。)

$ grpcurl -servername yages.example.com -insecure 34.102.214.64:443 yages.Echo.Ping
{
"text": "pong"
}

gRPC アプリケーションへのリクエストも問題なく行えることが確認できました!

おわりに

GKE Ingress + gRPC アプリケーションを動かす際のヘルスチェックについて書いてみましたがいかがでしたでしょうか?
リバースプロキシコンテナを Pod に追加してあげる必要がありますが、単純にヘルスチェックリクエストとそれ以外を振り分けるだけであれば、比較的簡単に設定ファイルを書くことができます。(もちろん Production で動かす時にはもっとたくさんの設定が必要になります。)
様々な理由で gRPC アプリケーションにヘルスチェック用のエンドポイントを追加できない時などにこの記事が参考になればと思います。

--

--

Jin Naraoka
google-cloud-jp

Customer Engineer at Google Cloud. All views and opinions are my own.