k8s ingress原理及ingress-nginx部署測試
ingress是啥東東
上篇文章介紹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要轉發到哪些服務等等。
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爲例。
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的部署
ingress的部署,需要考慮兩個方面:
- ingress-controller是作爲pod來運行的,以什麼方式部署比較好
- ingress解決了把如何請求路由到集羣內部,那它自己怎麼暴露給外部比較好
下面列舉一些目前常見的部署和暴露方式,具體使用哪種方式還是得根據實際需求來考慮決定。
Deployment+LoadBalancer模式的Service
如果要把ingress部署在公有云,那用這種方式比較合適。用Deployment部署ingress-controller,創建一個type爲LoadBalancer的service關聯這組pod。大部分公有云,都會爲LoadBalancer的service自動創建一個負載均衡器,通常還綁定了公網地址。只要把域名解析指向該地址,就實現了集羣服務的對外暴露。
Deployment+NodePort模式的Service
同樣用deployment模式部署ingress-controller,並創建對應的服務,但是type爲NodePort。這樣,ingress就會暴露在集羣節點ip的特定端口上。由於nodeport暴露的端口是隨機端口,一般會在前面再搭建一套負載均衡器來轉發請求。該方式一般用於宿主機是相對固定的環境ip地址不變的場景。
NodePort方式暴露ingress雖然簡單方便,但是NodePort多了一層NAT,在請求量級很大時可能對性能會有一定影響。
DaemonSet+HostNetwork+nodeSelector
用DaemonSet結合nodeselector來部署ingress-controller到特定的node上,然後使用HostNetwork直接把該pod與宿主機node的網絡打通,直接使用宿主機的80/433端口就能訪問服務。這時,ingress-controller所在的node機器就很類似傳統架構的邊緣節點,比如機房入口的nginx服務器。該方式整個請求鏈路最簡單,性能相對NodePort模式更好。缺點是由於直接利用宿主機節點的網絡和端口,一個node只能部署一個ingress-controller pod。比較適合大併發的生產環境使用。
ingress測試
我們來實際部署和簡單測試一下ingress。測試集羣中已經部署有2個服務gowebhost與gowebip,每次請求能返回容器hostname與ip。測試搭建一個ingress來實現通過域名的不同path來訪問這兩個服務:
測試ingress使用k8s社區的ingress-nginx,部署方式用DaemonSet+HostNetwork。
部署ingress-controller
部署ingress-controller pod及相關資源
官方文檔中,部署只要簡單的執行一個yaml
https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
mandatory.yaml這一個yaml中包含了很多資源的創建,包括namespace、ConfigMap、role,ServiceAccount等等所有部署ingress-controller需要的資源,配置太多就不粘出來了,我們重點看下deployment部分:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 33
runAsUser: 33
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
可以看到主要使用了“quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0”這個鏡像,指定了一些啓動參數。同時開放了80與443兩個端口,並在10254端口做了健康檢查。
我們需要使用daemonset部署到特定node,需要修改部分配置:先給要部署nginx-ingress的node打上特定標籤,這裏測試部署在"node-1"這個節點。
$ kubectl label node node-1 isIngress="true"
然後修改上面mandatory.yaml的deployment部分配置爲:
# 修改api版本及kind
# apiVersion: apps/v1
# kind: Deployment
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
# 刪除Replicas
# replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
serviceAccountName: nginx-ingress-serviceaccount
# 選擇對應標籤的node
nodeSelector:
isIngress: "true"
# 使用hostNetwork暴露服務
hostNetwork: true
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 33
runAsUser: 33
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
修改完後執行apply,並檢查服務
$ kubectl apply -f mandatory.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
daemonset.extensions/nginx-ingress-controller created
# 檢查部署情況
$ kubectl get daemonset -n ingress-nginx
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
nginx-ingress-controller 1 1 1 1 1 isIngress=true 101s
$ kubectl get po -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-ingress-controller-fxx68 1/1 Running 0 117s 172.16.201.108 node-1 <none> <none>
可以看到,nginx-controller的pod已經部署在在node-1上了。
暴露nginx-controller
到node-1上看下本地端口:
[root@node-1 ~]# netstat -lntup | grep nginx
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2654/nginx: master
tcp 0 0 0.0.0.0:8181 0.0.0.0:* LISTEN 2654/nginx: master
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2654/nginx: master
tcp6 0 0 :::10254 :::* LISTEN 2632/nginx-ingress-
tcp6 0 0 :::80 :::* LISTEN 2654/nginx: master
tcp6 0 0 :::8181 :::* LISTEN 2654/nginx: master
tcp6 0 0 :::443 :::* LISTEN 2654/nginx: master
由於配置了hostnetwork,nginx已經在node主機本地監聽80/443/8181端口。其中8181是nginx-controller默認配置的一個default backend。這樣,只要訪問node主機有公網IP,就可以直接映射域名來對外網暴露服務了。如果要nginx高可用的話,可以在多個node
上部署,並在前面再搭建一套LVS+keepalive做負載均衡。用hostnetwork的另一個好處是,如果lvs用DR模式的話,是不支持端口映射的,這時候如果用nodeport,暴露非標準的端口,管理起來會很麻煩。
配置ingress資源
部署完ingress-controller,接下來就按照測試的需求來創建ingress資源。
# ingresstest.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-test
annotations:
kubernetes.io/ingress.class: "nginx"
# 開啓use-regex,啓用path的正則匹配
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
rules:
# 定義域名
- host: test.ingress.com
http:
paths:
# 不同path轉發到不同端口
- path: /ip
backend:
serviceName: gowebip-svc
servicePort: 80
- path: /host
backend:
serviceName: gowebhost-svc
servicePort: 80
部署資源
$ kubectl apply -f ingresstest.yaml
測試訪問
部署好以後,做一條本地host來模擬解析test.ingress.com到node的ip地址。測試訪問
可以看到,請求不同的path已經按照需求請求到不同服務了。
由於沒有配置默認後端,所以訪問其他path會提示404:
關於ingress-nginx
關於ingress-nginx多說幾句,上面測試的例子是非常簡單的,實際ingress-nginx的有非常多的配置,都可以單獨開幾篇文章來討論了。但本文主要想說明ingress,所以不過多涉及。具體可以參考ingress-nginx的官方文檔。同時,在生產環境使用ingress-nginx還有很多要考慮的地方,這篇文章寫得很好,總結了不少最佳實踐,值得參考。
最後
- ingress是k8s集羣的請求入口,可以理解爲對多個service的再次抽象
- 通常說的ingress一般包括ingress資源對象及ingress-controller兩部分組成
- ingress-controller有多種實現,社區原生的是ingress-nginx,根據具體需求選擇
- ingress自身的暴露有多種方式,需要根據基礎環境及業務類型選擇合適的方式
參考
Kubernetes Document
NGINX Ingress Controller Document
Kubernetes Ingress Controller的使用介紹及高可用落地
通俗理解Kubernetes中Service、Ingress與Ingress Controller的作用與關係