前篇文章說了service,這篇文章來折騰下ingress這個玩意。html
上篇文章介紹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對象來實現圖中的請求轉發:node
ingress規則是很靈活的,能夠根據不一樣域名、不一樣path轉發請求到不一樣的service,而且支持https/http。nginx
要理解ingress,須要區分兩個概念,ingress和ingress-controller:git
簡單來講,ingress-controller纔是負責具體轉發的組件,經過各類方式將它暴露在集羣入口,外部對集羣的請求流量會先到ingress-controller,而ingress對象是用來告訴ingress-controller該如何轉發請求,好比哪些域名哪些path要轉發到哪些服務等等。github
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爲例。web
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 ~來表示正則匹配。api
ingress的部署,須要考慮兩個方面:服務器
下面列舉一些目前常見的部署和暴露方式,具體使用哪一種方式仍是得根據實際需求來考慮決定。
若是要把ingress部署在公有云,那用這種方式比較合適。用Deployment部署ingress-controller,建立一個type爲LoadBalancer的service關聯這組pod。大部分公有云,都會爲LoadBalancer的service自動建立一個負載均衡器,一般還綁定了公網地址。只要把域名解析指向該地址,就實現了集羣服務的對外暴露。
一樣用deployment模式部署ingress-controller,並建立對應的服務,可是type爲NodePort。這樣,ingress就會暴露在集羣節點ip的特定端口上。因爲nodeport暴露的端口是隨機端口,通常會在前面再搭建一套負載均衡器來轉發請求。該方式通常用於宿主機是相對固定的環境ip地址不變的場景。
NodePort方式暴露ingress雖然簡單方便,可是NodePort多了一層NAT,在請求量級很大時可能對性能會有必定影響。
用DaemonSet結合nodeselector來部署ingress-controller到特定的node上,而後使用HostNetwork直接把該pod與宿主機node的網絡打通,直接使用宿主機的80/433端口就能訪問服務。這時,ingress-controller所在的node機器就很相似傳統架構的邊緣節點,好比機房入口的nginx服務器。該方式整個請求鏈路最簡單,性能相對NodePort模式更好。缺點是因爲直接利用宿主機節點的網絡和端口,一個node只能部署一個ingress-controller pod。比較適合大併發的生產環境使用。
咱們來實際部署和簡單測試一下ingress。測試集羣中已經部署有2個服務gowebhost與gowebip,每次請求能返回容器hostname與ip。測試搭建一個ingress來實現經過域名的不一樣path來訪問這兩個服務:
測試ingress使用k8s社區的ingress-nginx,部署方式用DaemonSet+HostNetwork。
官方文檔中,部署只要簡單的執行一個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上了。
到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-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,因此不過多涉及。具體能夠參考ingress-nginx的官方文檔。同時,在生產環境使用ingress-nginx還有不少要考慮的地方,這篇文章寫得很好,總結了很多最佳實踐,值得參考。
Kubernetes Document
NGINX Ingress Controller Document
Kubernetes Ingress Controller的使用介紹及高可用落地
通俗理解Kubernetes中Service、Ingress與Ingress Controller的做用與關係