https://kubernetes.github.io/ingress-nginx/examples/grpc/ https://github.com/kubernetes/ingress-nginx/tree/master/docs/examples/grpc https://medium.com/@Alibaba_Cloud/accessing-grpc-services-through-container-service-for-kubernetes-ingress-controller-511f984071e1 kubernetes/ingress-nginx#5886
Start a cluster, and install nginx ingess controller, and wait while it is started:
kind create cluster --config cluster.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120s
Generate TLS certificate, and make a secret, and deploy a service:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=api.bar/O=api.bar"
If it shows an error like:
Can't load /home/nikolay/.rnd into RNG
140664075555264:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/nikolay/.rnd
then edit the config /etc/ssl/openssl.cnf
and comment line RANDFILE = $ENV::HOME/.rnd
(from here).
Add the certificate as a secret to the cluster:
kubectl create secret tls tls-secret --key tls.key --cert tls.crt
kubectl apply -f service.yaml
Check if the service is available as NodePort:
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grpcbin NodePort 10.111.126.18 <none> 9000:31002/TCP,9001:31149/TCP 11s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 8m7s
kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
kind-control-plane Ready master 8m34s v1.18.2 172.19.0.2 <none> Ubuntu 19.10 4.15.0-112-generic containerd://1.3.3-14-g449e9269
curl http://172.19.0.2:31002
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
curl -k https://172.19.0.2:31149
invalid gRPC request method
Seems it works but curl obviously doesn't understand the grpc answer.
Check it with grpcurl:
grpcurl -v -d '{"greeting": "TEST"}' -plaintext 172.19.0.2:31002 hello.HelloService.SayHello
Resolved method descriptor:
rpc SayHello ( .hello.HelloRequest ) returns ( .hello.HelloResponse );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
Response contents:
{
"reply": "hello TEST"
}
Response trailers received:
(empty)
Sent 1 request and received 1 response
grpcurl -v -d '{"greeting": "TEST"}' -insecure 172.19.0.2:31149 hello.HelloService.SayHello
Resolved method descriptor:
rpc SayHello ( .hello.HelloRequest ) returns ( .hello.HelloResponse );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
trailer: Grpc-Status
trailer: Grpc-Message
trailer: Grpc-Status-Details-Bin
Response contents:
{
"reply": "hello TEST"
}
Response trailers received:
(empty)
Sent 1 request and received 1 response
It works as NodePort.
Check if the service is available through ingress controller.
The only required thing for proxying gRPC is to add one proper annotation to a common HTTP ingress rule:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
kubectl apply -f ingress_0.yaml
grpcurl -v -d '{"greeting": "TEST"}' -plaintext localhost:80 hello.HelloService.SayHello
Failed to dial target host "localhost:80": context deadline exceeded
grpcurl -v -d '{"greeting": "TEST"}' -insecure localhost:443 hello.HelloService.SayHello
Resolved method descriptor:
rpc SayHello ( .hello.HelloRequest ) returns ( .hello.HelloResponse );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
date: Tue, 15 Sep 2020 17:31:25 GMT
server: nginx/1.19.2
strict-transport-security: max-age=15724800; includeSubDomains
Response contents:
{
"reply": "hello TEST"
}
Response trailers received:
(empty)
Sent 1 request and received 1 response
It seems gRPC proxy is only working on HTTPS port but not on HTTP. But in the target service we use the insecure
port, because TLS is terminated at the ingress level and inside of cluster gGRPC traffic travells unencripted.
Now we have to use the -authority
parameter of grpcurl:
kubectl delete ingress --all
kubectl apply -f ingress_1.yaml
grpcurl -v -d '{"greeting": "TEST"}' -plaintext -authority myexample.com localhost:80 hello.HelloService.SayHello
Failed to dial target host "localhost:80": context deadline exceeded
grpcurl -v -d '{"greeting": "TEST"}' -insecure -authority myexample.com localhost:443 hello.HelloService.SayHello
Resolved method descriptor:
rpc SayHello ( .hello.HelloRequest ) returns ( .hello.HelloResponse );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
date: Tue, 15 Sep 2020 17:36:38 GMT
server: nginx/1.19.2
strict-transport-security: max-age=15724800; includeSubDomains
Response contents:
{
"reply": "hello TEST"
}
Response trailers received:
(empty)
Sent 1 request and received 1 response
It's used in this example from nginx (or this link), so just try it for the full picture.
Delete all existed ingresses:
kubectl delete ingress --all
Then generate a certificate and make a secret, it should be done before making new ingresses:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=myexample.com/O=myexample.com"
Error "Cannot open file .rnd"
If it shows an error like:
Can't load /home/nikolay/.rnd into RNG
140664075555264:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/nikolay/.rnd
then edit the config /etc/ssl/openssl.cnf
and comment line RANDFILE = $ENV::HOME/.rnd
(solution from here).
Add the certificate as a secret to the cluster:
kubectl create secret tls tls-secret --key tls.key --cert tls.crt
Then we can make an ingress using this certificate:
kubectl apply -f ingress_2.yaml
grpcurl -v -d '{"greeting": "TEST"}' -plaintext -authority myexample.com localhost:80 hello.HelloService.SayHello
Failed to dial target host "localhost:80": context deadline exceeded
grpcurl -v -d '{"greeting": "TEST"}' -insecure -authority myexample.com localhost:443 hello.HelloService.SayHello
Resolved method descriptor:
rpc SayHello ( .hello.HelloRequest ) returns ( .hello.HelloResponse );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
date: Tue, 15 Sep 2020 17:42:21 GMT
server: nginx/1.19.2
strict-transport-security: max-age=15724800; includeSubDomains
Response contents:
{
"reply": "hello TEST"
}
Response trailers received:
(empty)
Sent 1 request and received 1 response
Output is the same. I'm not sure if the certificate is used or not, at least it doesn't break anything.
Delete existed ingresses and secrets:
kubectl delete ingress --all
kubectl delete secret tls-secret
Then recreate and test ingress (IP-address 127.0.0.1 must be used, not "localhost"):
kubectl apply -f ingress_3.yaml
grpcurl -v -d '{"greeting": "TEST"}' -plaintext 127.0.0.1.xip.io:80 hello.HelloService.SayHello
Failed to dial target host "127.0.0.1.xip.io:80": context deadline exceeded
nikolay@leo:~/Projects/learn-kuber/kind_nginx_grpc$ grpcurl -v -d '{"greeting": "TEST"}' -insecure 127.0.0.1.xip.io:443 hello.HelloService.SayHello
Resolved method descriptor:
rpc SayHello ( .hello.HelloRequest ) returns ( .hello.HelloResponse );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
date: Tue, 15 Sep 2020 17:51:56 GMT
server: nginx/1.19.2
strict-transport-security: max-age=15724800; includeSubDomains
Response contents:
{
"reply": "hello TEST"
}
Response trailers received:
(empty)
Sent 1 request and received 1 response