SRE部のcw-sakamotoです。
この記事では、nginx-ingressをk8s on AWSで動かして、さらにbackendにはgRPC serverを立てて、ロードバランスできるかどうかの検証を行いたいと思います。
アジェンダ
背景
- gRPCのアプリをkubernetesで動かす要件があり、検証が必要になった
- kubernetesでgRPCをよしなにロードバランスさせるのにnginx-ingressが妥当そうであり、各機能を確認しておきたかった
- https://www.nginx.com/blog/nginx-1-13-10-grpc/
- https://twitter.com/mumoshu/status/994141513566240768
- nginxは現行アーキテクチャでも実績がある(not k8s環境)
環境
環境は下記の通りです。
- AWS
- kubernetes 1.8.4
- kubernetes構築ツールはkube-aws
なお、この記事の内容の7割ぐらいは下記と同じ内容となります。元手が@mumoshu(現在freee勤務兼ChatWork技術顧問)*1さんのため。
そのため、freeeさんのブログで書かれている内容に関しては、このブログでは説明を省略させて頂きます。 この記事と同じように、gRPC on h2cの構成です。
手順等で使用したソースは下記にあります。
今回の検証における、全体の構成は下記のようになります。
grpc client ↓ AWS ELB(classic) ↓ kubernetes(service) ↓ nginx-ingress(on k8s) ... ↓ ↓ grpc server1 grpc server2
ingressを利用する場合、GKE等ではservice経由での接続になりますが、nginx-ingressはserviceを利用せず、直接podにルーティングするので、上記のような構成になります。
nginx-ingressのingressの挙動についての補足
それでは具体的な手順等を記載してきます。
gRPC serverの起動
ここでは、2つのgRPC serverをkubernetesに起動します。起動後、gRPC server単体での確認(ingressを経由しない)のためにgrpcurlを利用して確認します。
gRPCの各リソースの作成
まずは、それぞれのdeployment, serviceを作成します。
nginx-ingressはルーティングの際には、serviceは経由しないのですが、ルーティング先のpodを取得するために、endpoint apiを利用します。そのためpodに紐付いたendpoint*2を作成するためにserviceを作成します。
使用するファイルのディレクトリ https://github.com/cw-sakamoto/grpc-example/tree/master/kubernetes
$ kubectl create -f goodbye-grpc-deployment.yaml $ kubectl create -f hello-grpc-deployment.yaml $ kubectl create -f goodbye-grpc-svc.yaml $ kubectl create -f hello-grpc-svc.yaml
それぞれのdeployment, service, endpointが作成されたことを確認します。
cluster ipはついていても構わないのですが(nginx-ingressはservice経由でアクセスしない)、serviceのvipと結びつかないように、Noneとしています。
- Kubernetes上でgRPCサービスを動かす | SOTA
- https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
$ kubectl get deployment | grep grpc-deployment goodbye-grpc-deployment 3 3 3 3 5m hello-grpc-deployment 3 3 3 3 5m $ kubectl get svc | grep grpc goodbye-grpc ClusterIP None <none> 8999/TCP 4m hello-grpc ClusterIP None <none> 8999/TCP 5m $ kubectl get endpoints | grep grpc goodbye-grpc 11.2.27.40:8999,11.2.61.36:8999,11.2.64.79:8999 20h hello-grpc 11.2.27.41:8999,11.2.61.37:8999,11.2.64.80:8999 20h
grpcurlを利用して確認
grpcurlを利用して、先程起動したgRPC serverに疎通確認してみます。
$ kubectl run golang-pod --tty -i --image=golang:1.10 bash root@golang-pod-77d8d5554c-j5p89:/go# go get github.com/fullstorydev/grpcurl root@golang-pod-77d8d5554c-j5p89:/go# go install github.com/fullstorydev/grpcurl/cmd/grpcurl root@golang-pod-77d8d5554c-j5p89:/go# grpcurl -plaintext -d '{"name":"Ryo Sakamoto"}' hello-grpc:8999 helloworld.Hello.SayHello { "message": "Hello I'm Ryo Sakamoto. years old. At hello-grpc-deployment-74f89d8cf7-dn697" } root@golang-pod-77d8d5554c-j5p89:/go# grpcurl -plaintext -d '{"name":"Ryo Sakamoto"}' goodbye-grpc:8999 helloworld.Goodbye.SayGoodbye { "message": "Goodbye Ryo Sakamoto. At goodbye-grpc-deployment-66f89b667b-ffnvf.\n I sent chandler's words in Collection of famous sayings: The more you reason the less you create." }
大丈夫そうです。
ちなみに、serviceのhostnameは、同じnamespaceであれば、service nameだけ、違うnamespaceの場合は、servce-name.namespace
で名前解決されます(FQDNは、service-name.namespace.svc.cluster.local
)。
これでgRPC serverが起動した状態になりました。
nginx-ingress-controllerのインストールとingressの作成
nginx-ingress-controllerのインストール
nginx-ingressは公式のchartsを利用します。検証段階のversionは下記のようになります。
- nginx-image 0.15
- charts 0.20.3
早速入れていきます。
使用するファイルのディレクトリ https://github.com/cw-sakamoto/grpc-example/tree/master/kubernetes/nginx-ingress
# 80番ポートでgRPCが受けられるようにpatchを当てたnginx.tmplを記載したconfigmapを作成 $ kubectl apply -f nginx-template-configmap.yaml # nginx-ingressをinstall $ helm upgrade nginx-ingress-grpc stable/nginx-ingress -f setting-grpc.yaml --install
どうしてpatchをあてるかなどは、freeeさんのブログをご参照ください。 ちなみにtemplateもnginx-imageのversion依存があるので、0.15で0.14のtemplateを利用しようとすると、nginxが起動しません。
nginx-ingressが、無事にインストールできると下記のように確認できます。 今回利用しているsetting-grpc.yamlを見ていただくとわかりますが、prometheus用のmetricsをtrueにしているため、それぞれのserviceも立ち上がっています。 これに関しては、別記事で、Datadog Prometheus Checkを試す、という内容で記載しています。
$ kubectl get svc | grep nginx-ingress-grpc nginx-ingress-grpc-controller LoadBalancer 11.3.0.29 ae7e6a1ff751c... 80:30178/TCP,443:31492/TCP 1h nginx-ingress-grpc-controller-metrics ClusterIP 11.3.0.216 <none> 9913/TCP 1h nginx-ingress-grpc-controller-stats ClusterIP 11.3.0.16 <none> 18080/TCP 1h nginx-ingress-grpc-default-backend ClusterIP 11.3.0.199 <none> 80/TCP 1h
今回はtype:LoadBalancer
で起動しているので、ELBが作成されます。http2 on ELB(classic)の場合、TCPモードにする必要がありますが、type:LoadBalancer
で作成されるELBはもともとTPCモードで作成されるので、そのままで問題ありません。
Route 53にて、作成されたELBのendpointを、ingress resourceで設定するhostsのAlias targetに設定しておきます。
ingressの作成
下記の内容のリソースを作成します。
$ kubectl apply -f nginx-ingress-grpc.yaml
ingressのpathですが、gRPCのpathなので、.
が入ります。
今回は、
- package helloworld
- service Hello
- service Goodbye
の2つのserviceを、ルーティングを確かめるために別々のgRPC serverで動かします。 grpcのexampleを少しだけ改良して利用しています。
また、ingress-classを今回は、nginx-grpc
としています。これはhttp用のnginx-ingressを別途作成する場合に、ingress-classを分けたいためです。
ところで、ingress-controllerにnginx-ingressを利用している場合、ingress resourceのservicePort
のportは、serviceのportでも、containerportでもどちらでも動きます*3。
今回はわかりやすいようにserviceのportを記載しています。
ELBの準備
下記に注意して作成してください。
- ingress-resourceに設定したhostと同じドメインである必要があります
- route53のエイリアスなどで、ELBのDNS nameを設定しましょう
- ELBのportはTPCで開けておく必要があります。
clientからの確認
ということで、すべて準備はできたので、clientから確認してみます。At ...
はpod idです。
clientのソース grpc-example/greeter_client at master · cw-sakamoto/grpc-example · GitHub
$ go build -o greeter_client main.go $ ./greeter_client nginx-ingress-grpc.example.com:80 "Ryo" 2018/06/21 16:40:32 Hello: Hello I'm Ryo. years old. At hello-grpc-deployment-74f89d8cf7-dn697 2018/06/21 16:40:32 Goodbye: Goodbye Ryo. At goodbye-grpc-deployment-66f89b667b-sjmck. I sent chandler's words in Collection of famous sayings: There are two kinds of truth: the truth that lights the way and the truth that warms the heart. The first of these is science, and the second is art. Without art, science would be as useless as a pair of high forceps in the hands of a plumber. Without science, art would become a crude mess of folklore and emotional quackery. $ ./greeter_client nginx-ingress-grpc.example.com:80 "Ryo" 2018/06/21 16:40:37 Hello: Hello I'm Ryo. years old. At hello-grpc-deployment-74f89d8cf7-6bb98 2018/06/21 16:40:37 Goodbye: Goodbye Ryo. At goodbye-grpc-deployment-66f89b667b-ffnvf. I sent chandler's words in Collection of famous sayings: There are two kinds of truth: the truth that lights the way and the truth that warms the heart. The first of these is science, and the second is art. Without art, science would be as useless as a pair of high forceps in the hands of a plumber. Without science, art would become a crude mess of folklore and emotional quackery. $ ./greeter_client nginx-ingress-grpc.example.com:80 "Ryo" 2018/06/21 16:40:39 Hello: Hello I'm Ryo. years old. At hello-grpc-deployment-74f89d8cf7-mpxlv 2018/06/21 16:40:39 Goodbye: Goodbye Ryo. At goodbye-grpc-deployment-66f89b667b-gtgm9. I sent chandler's words in Collection of famous sayings: If I wasn’t hard, I wouldn’t be alive. If I couldn’t ever be gentle, I wouldn’t deserve to be alive.
無事にpodがロードバランスされているようですね。
ということで、nginx-ingress-controllerを利用して、gRPC serverをロードバランスさせる手順でした。
まとめ
- nginx-ingress-controllerをkubernetesで動かし、ELB経由で、外部clientからアクセスできることが確認できました
- 複数のpodにロードバランスされることが確認できました
本番利用の際には、ELBのidle timeoutや、性能面など検証することがまだまだありますが、機能面では問題なさそうです。
*1:https://twitter.com/mumoshu
*2:endpointについては https://qiita.com/MahoTakara/items/d18d8f9b36416353066c#セレクター無しでサービスを定義して別途エンドポイントを作る方法
*3:GKEなどではserviceのportを記載する必要がある