一、前言
我們說k8s 的服務(service)時說暴露了service的三種方式ClusterIP、NodePort與LoadBalance,這幾種方式都是在service的維度提供的,service的作用體現在兩個方面,對集羣內部,它不斷跟蹤pod的變化,更新endpoint中對應pod的對象,提供了ip不斷變化的pod的服務發現機制,對集羣外部,他類似負載均衡器,可以在集羣內外部對pod進行訪問。但是,單獨用service暴露服務的方式,在實際生產環境中不太合適:
- ClusterIP的方式只能在集羣內部訪問。
- NodePort方式的話,測試環境使用還行,當有幾十上百的服務在集羣中運行時,NodePort的端口管理是災難。
- LoadBalance方式受限於雲平臺,且通常在雲平臺部署ELB還需要額外的費用。
所幸k8s還提供了一種集羣維度暴露服務的方式,也就是ingress。ingress可以簡單理解爲service的service,他通過獨立的ingress對象來制定請求轉發的規則,把請求路由到一個或多個service中。這樣就把服務與請求規則解耦了,可以從業務維度統一考慮業務的暴露,而不用爲每個service單獨考慮。
舉個例子,現在集羣有api、文件存儲、前端3個service,可以通過一個ingress對象來實現圖中的請求轉發:
ingress規則是很靈活的,可以根據不同域名、不同path轉發請求到不同的service,並且支持https/http。
二、ingress與ingress-controller
要理解ingress,需要區分兩個概念,ingress和ingress-controller:
- ingress對象:
指的是k8s中的一個api對象,一般用yaml配置。作用是定義請求如何轉發到service的規則,可以理解爲配置模板。 - ingress-controller:
具體實現反向代理及負載均衡的程序,對ingress定義的規則進行解析,根據配置的規則來實現請求轉發。
簡單來說,ingress-controller纔是負責具體轉發的組件,通過各種方式將它暴露在集羣入口,外部對集羣的請求流量會先到ingress-controller,而ingress對象是用來告訴ingress-controller該如何轉發請求,比如哪些域名哪些path要轉發到哪些服務等等。
2.1 ingress-controller
ingress-controller並不是k8s自帶的組件,實際上ingress-controller只是一個統稱,用戶可以選擇不同的ingress-controller實現,目前,由k8s維護的ingress-controller只有google雲的GCE與ingress-nginx兩個,其他還有很多第三方維護的ingress-controller,具體可以參考官方文檔。但是不管哪一種ingress-controller,實現的機制都大同小異,只是在具體配置上有差異。一般來說,ingress-controller的形式都是一個pod,裏面跑着daemon程序和反向代理程序。daemon負責不斷監控集羣的變化,根據ingress對象生成配置並應用新配置到反向代理,比如nginx-ingress就是動態生成nginx配置,動態更新upstream,並在需要的時候reload程序應用新配置。爲了方便,後面的例子都以k8s官方維護的nginx-ingress爲例。
2.2 ingress
ingress是一個API對象,和其他對象一樣,通過yaml文件來配置。ingress通過http或https暴露集羣內部service,給service提供外部URL、負載均衡、SSL/TLS能力以及基於host的方向代理。ingress要依靠ingress-controller來具體實現以上功能。前一小節的圖如果用ingress來表示,大概就是如下配置:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: abc-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
tls:
- hosts:
- api.abc.com
secretName: abc-tls
rules:
- host: api.abc.com
http:
paths:
- backend:
serviceName: apiserver
servicePort: 80
- host: www.abc.com
http:
paths:
- path: /image/*
backend:
serviceName: fileserver
servicePort: 80
- host: www.abc.com
http:
paths:
- backend:
serviceName: feserver
servicePort: 8080
與其他k8s對象一樣,ingress配置也包含了apiVersion、kind、metadata、spec等關鍵字段。有幾個關注的在spec字段中,tls用於定義https密鑰、證書。rule用於指定請求路由規則。這裏值得關注的是metadata.annotations字段。在ingress配置中,annotations很重要。前面有說ingress-controller有很多不同的實現,而不同的ingress-controller就可以根據"kubernetes.io/ingress.class:"來判斷要使用哪些ingress配置,同時,不同的ingress-controller也有對應的annotations配置,用於自定義一些參數。列如上面配置的'nginx.ingress.kubernetes.io/use-regex: "true"',最終是在生成nginx配置中,會採用location ~來表示正則匹配。
三、ingress初體驗
雖然 minikube 支持 LoadBalancer 類型的服務,但它並不會創建外部的負載均衡器,而是爲這些服務開放一個 NodePort。這在使用 Ingress 時需要注意。
3.1 啓動 Ingress Controller
minikube 已經內置了 ingress addon,只需要開啓一下即可
$ minikube addons enable ingress
稍等一會,nginx-ingress-controller 和 default-http-backend 就會起來
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
default-http-backend-5374j 1/1 Running 0 1m
kube-addon-manager-minikube 1/1 Running 0 2m
kube-dns-268032401-rhrx6 3/3 Running 0 1m
kubernetes-dashboard-xh74p 1/1 Running 0 2m
nginx-ingress-controller-78mk6 1/1 Running 0 1m
3.2 創建 Ingress
首先啓用一個 echo server 服務
$ kubectl run echoserver --image=gcr.io/google_containers/echoserver:1.4 --port=8080
$ kubectl expose deployment echoserver --type=NodePort
$ minikube service echoserver --url
http://192.168.64.36:31957
然後創建一個 Ingress,將 http://mini-echo.io
和 http://mini-web.io/echo
轉發到剛纔創建的 echoserver 服務上
$ cat <<EOF | kubectl create -f -
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: echo
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
backend:
serviceName: default-http-backend
servicePort: 80
rules:
- host: mini-echo.io
http:
paths:
- path: /
backend:
serviceName: echoserver
servicePort: 8080
- host: mini-web.io
http:
paths:
- path: /echo
backend:
serviceName: echoserver
servicePort: 8080
EOF
爲了訪問 mini-echo.io
和 mini-web.io
這兩個域名,手動在 hosts 中增加一個映射
$ echo "$(minikube ip) mini-echo.io mini-web.io" | sudo tee -a /etc/hosts
然後,就可以通過 http://mini-echo.io
和 http://mini-web.io/echo
來訪問服務了。
3.3 使用 xip.io
前面的方法需要每次在使用不同域名時手動配置 hosts,藉助 xip.io
可以省掉這個步驟。
跟前面類似,先啓動一個 nginx 服務
$ kubectl run nginx --image=nginx --port=80
$ kubectl expose deployment nginx --type=NodePort
然後創建 Ingress,與前面不同的是 host 使用 nginx.$(minikube ip).xip.io
:
$ cat <<EOF | kubectl create -f -
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: my-nginx-ingress
spec:
rules:
- host: nginx.$(minikube ip).xip.io
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80
EOF
然後就可以直接訪問該域名了
$ curl nginx.$(minikube ip).xip.io