k8s ingress原理及ingress-nginx部署測試

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對象來實現圖中的請求轉發:

clipboard.png

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的部署,需要考慮兩個方面:

  1. ingress-controller是作爲pod來運行的,以什麼方式部署比較好
  2. 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來訪問這兩個服務:

clipboard.png

測試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的作用與關係

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章