概述
一般情況下,我們的系統對外暴露HTTP/HTTPS的接口,內部使用rpc(GRPC)通訊,這時GRPC在服務之間通過service訪問,本地調試時通過service nodePort方式調用。
但隨着業務壯大,需要跨集群的GRPC通訊,或者pod數量太多、nodePort端口管理混亂時,就可以考慮用ingress來統一管理和暴露GRPC服務了。
本文詳細介紹如何在kubernetes用ingress負載grpc服務。
- 首先需要特別留意:ingress-nginx不支持負載明文的grpc(這個官方居然沒明說,巨坑,詳情請自行搜索github上的issues),所以只能在443端口上用TLS來負載。
搭建
生成公私鑰
前面說了ingress只支持在443端口上負載加密的grpc,所以在正式搭建前需要准備一組公私鑰。
這里我們可以自行生成一組普通的公私鑰,下面的命令將在當前目錄生成一組公私鑰文件:
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout grpcs.key -out grpcs.crt -subj "/CN=*.grpcs.aurayou/O=grpcs.aurayou"
$ ls
grpcs.crt grpcs.key
參數說明:
- grpcs.key: 私鑰文件名
- grpcs.crt: 公鑰文件名
- .grpcs.aurayou: 域名后綴,后續所有使用此公私鑰對的域名必須以.grpcs.aurayou為后綴
創建secret
將剛才生成的公私鑰對導入k8s secret,創建名為grpcs-secret的secret:
$ kubectl create secret tls grpcs-secret --key grpcs.key --cert grpcs.crt
$ kube get secret
NAME TYPE DATA AGE
default-token-bhp5l kubernetes.io/service-account-token 3 88d
grpcs-secret kubernetes.io/tls 2 6h
創建ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
name: data-product
namespace: aurayou
spec:
rules:
- host: data-product.grpcs.aurayou
http:
paths:
- backend:
serviceName: data-product
servicePort: 50051
tls:
- secretName: grpcs-secret
hosts:
- data-product.grpcs.aurayou
參數說明:
- 啟用nginx:kubernetes.io/ingress.class: "nginx"
- 啟用TLS,下面兩項2選1:
- 由nginx負責tls,服務內部還是用明文傳輸:nginx.ingress.kubernetes.io/ssl-redirect: "true"、nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
- 由服務自行實現TLS,nginx只負責路由:nginx.ingress.kubernetes.io/backend-protocol: "GRPCS"
- 域名:rules.host、tls.hosts這兩個是該grpc服務的ingress域名,可以自定義,但必須以前面生成公私鑰對時使用的后綴結尾
- 服務: grpc服務的服務名和端口由rules.http.backend配置
- 公私鑰: tls.secretName指向前面創建的grpcs-secret
注意:ingress-nginx的搭建,最好使用官方的yaml文件,自定義的配置可能會有沖突之處。
訪問
前面已經搭建好了grpc服務的ingress,外部服務訪問該ingress時,需要加載剛才生成的公鑰文件,golang代碼關鍵點:
- 訪問明文的服務時,選項使用grpc.WithInsecure()
- 訪問加密的ingress時,選項使用grpc.WithTransportCredentials,並用credentials.NewClientTLSFromFile加載前面生成的公鑰並指定需要訪問的域名
tcp_port := viper.GetString("server.tcp.port")
tcp_host := viper.GetString("server.tcp.host")
tcp_address := tcp_host + tcp_port
var dailOpts []grpc.DialOption
if tcp_port == ":443" {
// 走ingress 443端口,加密傳輸
// NewClientTLSFromFile()函數的參數1是前面生成的公鑰文件,參數2是待訪問的域名
creds, err := credentials.NewClientTLSFromFile("./grpcs.crt", "data-product.grpcs.aurayou")
if err != nil {
log.Fatalf("Failed to create TLS credentials %v", err)
}
dailOpts = append(dailOpts, grpc.WithTransportCredentials(creds))
} else {
// 訪問明文的集群內部服務
dailOpts = append(dailOpts, grpc.WithInsecure())
}
// Set up a connection to the server.
conn, err := grpc.Dial(tcp_address, dailOpts...)
if err != nil {
log.Fatalf("did not connect: %v", err)
}