理解Kubernetes中的Nginx Ingress

語言: CN / TW / HK

Ingress有什麼作用?管理叢集外部對叢集內服務的訪問,典型如HTTP請求。它可以提供負載均衡、SSL終結和基於域名的虛擬主機訪問。我們發現這些功能都比較容易實現,將叢集內的服務暴露到叢集外部,可以使用“NodePort”型別的Service,負載均衡可以使用HAProxy來實現,SSL終結功能部署七層反向代理就可以,基於域名的虛擬主機訪問也同樣比較容易實現,那為什麼Kubernetes要引入Ingress API物件呢?

Ingress的潛力

Ingress的功能如開篇所述,可以使用其他技術實現,但是實際在操作過程中發現並沒那麼簡單。在沒有Ingress參與的情況下,將叢集內服務暴露到叢集外使用“NodePort”型別的Service,那需要給每個微服務都建立此類Service,當服務較多時,排障將非常複雜,協調主機埠使用也會讓人抓狂。在叢集外部署Nginx或Apache,SSL終結和基於域名的虛擬主機訪問可以實現,但是服務發現和配置管理又是個挑戰,叢集外的Nginx和Apache感知不到叢集中服務的增加和減少,需要人為配置,這對叢集管理員來說,簡直是個噩夢。幸好,Ingress來了。

安裝

安裝服務到Kubernetes一般都比較容易,使用“kubectl apply”後面跟上yaml檔案即可。當然也可以使用Kubernetes的包管理工具-Helm。“nginx ingress”根據環境,可選有三種安裝方法:

  • 使用helm。
  • kubectl apply + yamlfiles。
  • 在minikube或MicroK8s中,外掛方式安裝。

筆者使用這篇文章介紹的方法安裝Kubernetes叢集,這裡選用第二種方式安裝“nginx ingress ”,執行下面命令(因版本更新較快,實際部署請參考官網):

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.1/deploy/static/provider/baremetal/deploy.yaml

安裝會從“k8s.gcr.io”映象倉拉取映象,如果拉取失敗可選擇阿里雲或其他。

檢視增加的叢集資源。

[[email protected] ~]# kubectl get all -n ingress-nginx
NAME                                            READY   STATUS      RESTARTS      AGE
pod/ingress-nginx-admission-create-7k9kt        0/1     Completed   0             14d
pod/ingress-nginx-admission-patch-5bcmq         0/1     Completed   1             14d
pod/ingress-nginx-controller-687578654b-f92bq   1/1     Running     3 (42d ago)   14d
NAME                                         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             NodePort    10.1.70.249   <none>        80:30305/TCP,443:31330/TCP   14d
service/ingress-nginx-controller-admission   ClusterIP   10.1.124.31   <none>        443/TCP                      14d
NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   1/1     1            1           14d
NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/ingress-nginx-controller-687578654b   1         1         1       14d
NAME                                       COMPLETIONS   DURATION   AGE
job.batch/ingress-nginx-admission-create   1/1           5s         14d
job.batch/ingress-nginx-admission-patch    1/1           7s         14d

因為叢集是自建的,“ingress-nginx-controller”服務型別為“NodePort”,後面訪問服務需要使用這樣的方式:NodeIP+30305/31330+Path。

使用

上步操作成功執行後,便可以建立Ingress型別的API物件了,筆者叢集中提前部署一Web服務,Service資訊如下:

[[email protected] ~]# kubectl get svc
NAME    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   10.1.133.186   <none>        80/TCP    14h

建立Ingress API物件。

1> ---
2> apiVersion: networking.k8s.io/v1
3> kind: Ingress
4> metadata:
5>   name: self-nginx
6>   namespace: test
7>   annotations:
8>     nginx.ingress.kubernetes.io/rewrite-target: /
9> spec:
10>   ingressClassName: nginx
11>   rules:
12>   - host: mynginx.example.com
13>     http:
14>       paths:
15>       - path: /testpath
16>         pathType: Prefix
17>         backend:
18>           service:
19>             name: nginx
20>             port:
21>               number: 80

如果沒有將前面安裝的nginx ingress配置為預設的Ingress,需要加入第10行。否則即使ingress資源提交到API Server,“nginx ingress controller”也沒有反應。獲取“ingressClassName”的值。

[[email protected] ~]# kubectl get ingressclass
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       14d

檢視建立的Ingress。

[[email protected] ~]# kubectl get ingress
NAME         CLASS   HOSTS                 ADDRESS          PORTS   AGE
self-nginx   nginx   mynginx.example.com   192.168.52.132   80      125m

通過Ingress訪問Web服務(如果域名沒有解析,修改/etc/hosts檔案)。

[[email protected] nginx]# curl mynginx.example.com:30305/testpath
hello kubernetes!

原理

Nginx Ingress的部署和使用不難,最重要是熟悉它的工作原理,這樣在遇到問題時才能迅速定位。“ingress-nginx-controller” Pod裡面僅執行一個容器,但是這個容器裡面卻有多個守護程序,重要的有兩個:controller和nginx。進入Pod執行ps命令檢視:

[[email protected] ~]# kubectl exec -it ingress-nginx-controller-687578654b-f92bq -n ingress-nginx  -- /bin/bash
bash-5.1$ ps 
PID   USER     TIME  COMMAND
    1 www-data  0:00 /usr/bin/dumb-init -- /nginx-ingress-controller --election-id=ingress-controller-leader --controller-class=k8s.io/ingress-nginx --config
    7 www-data 11:28 /nginx-ingress-controller --election-id=ingress-controller-leader --controller-class=k8s.io/ingress-nginx --configmap=ingress-nginx/ingr
   25 www-data  0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf
  247 www-data  0:01 nginx: worker process
  248 www-data  0:00 nginx: cache manager process

Controller是管理者,實現服務發現和自動配置功能,見下圖(下載自官網)。

nginx ingress工作原理

這幅圖看起來很複雜,其實用一句話就可以概括:“Ingress Controller”(即圖中的IC)相當於系統管理員,需求者提交Ingress資源到API Server,IC從API Server獲取Ingress資源,因為IC既瞭解Ingress資源又瞭解Nginx,它完成Ingress的“翻譯”,隨即更新Nginx的配置檔案,並執行reload操作,核心邏輯就是這樣。

上面我們給“nginx” Service建立了Ingress資源,訪問路徑配置為“/testpath”,現在進入“ingress-nginx-controller”Pod看下Nginx的配置檔案:/etc/nginx/nginx.conf。

關於虛擬主機“mynginx.example.com”的配置有200多行,刪掉無關的。

## start server mynginx.example.com 
server {
    server_name mynginx.example.com ;   
    listen 80  ;
    listen 443  ssl http2 ;   
    set $proxy_upstream_name "-";   
    ssl_certificate_by_lua_block {
      certificate.call()
    }   
    location ~* "^/testpath" {      
      set $namespace      "test";
      set $ingress_name   "self-nginx";
      set $service_name   "nginx";
      set $service_port   "80";
      set $location_path  "/testpath";
      set $global_rate_limit_exceeding n;     
      ......  
      set $balancer_ewma_score -1;
      set $proxy_upstream_name "test-nginx-80";
      set $proxy_host          $proxy_upstream_name;
      set $pass_access_scheme  $scheme;     
      ......    
      rewrite "(?i)/testpath" / break;
      proxy_pass http://upstream_balancer;    
      proxy_redirect                          off;    
    } 
    location ~* "^/" {      
      set $namespace      "test";
      set $ingress_name   "self-nginx";
      set $service_name   "";
      set $service_port   "";
      set $location_path  "/";
      set $global_rate_limit_exceeding n;   
      ......    
      proxy_pass http://upstream_balancer;    
      proxy_redirect                          off;      
    } 
  }
  ## end server mynginx.example.com

從配置檔案中可以看到發往“/testpath”的請求完成一次跳轉後最終傳送給“upstream_balancer”,其在Nginx配置檔案中的定義如下:

upstream upstream_balancer {
    ### Attention!!!
    #
    # We no longer create "upstream" section for every backend.
    # Backends are handled dynamically using Lua. If you would like to debug
    # and see what backends ingress-nginx has in its memory you can
    # install our kubectl plugin https://kubernetes.github.io/ingress-nginx/kubectl-plugin.
    # Once you have the plugin you can use "kubectl ingress-nginx backends" command to
    # inspect current backends.
    #
    ###   
    server 0.0.0.1; # placeholder 
    balancer_by_lua_block {
      balancer.balance()
    }   
    keepalive 320;    
    keepalive_timeout  60s;
    keepalive_requests 10000;   
  }

因Nginx配置檔案嚴重依賴Lua,這裡看到的資訊不直觀。為了看到後端服務,按照註釋,為kubectl安裝“ingress-nginx”外掛。在前面Nginx配置檔案有下面一行:

set $proxy_upstream_name "test-nginx-80";

指出域名“mynginx.example.com”的backend名為“test-nginx-80”。檢視Ingress的backends(省略無關行)。

[[email protected] ~]# kubectl ingress-nginx backends -n ingress-nginx
[
  {
    "name": "test-nginx-80",
    "service": {
      "metadata": {
        "creationTimestamp": null
      },
      "spec": {
        "ports": [
          {
            "name": "http",
            "protocol": "TCP",
            "port": 80,
            "targetPort": 80
          }
        ],
        "selector": {
          "app": "nginx"
        },
        "clusterIP": "10.1.133.186",
        "clusterIPs": [
          "10.1.133.186"
        ],
        "type": "ClusterIP",
        "sessionAffinity": "None",
        "ipFamilies": [
          "IPv4"
        ],
        "ipFamilyPolicy": "SingleStack",
        "internalTrafficPolicy": "Cluster"
      },
      "status": {
        "loadBalancer": {}
      }
    },
    "port": 80,
    "sslPassthrough": false,
    "endpoints": [
      {
        "address": "10.244.1.26",
        "port": "80"
      }
    ],
    "sessionAffinityConfig": {
      "name": "",
      "mode": "",
      "cookieSessionAffinity": {
        "name": ""
      }
    },
    "upstreamHashByConfig": {
      "upstream-hash-by-subset-size": 3
    },
    "noServer": false,
    "trafficShapingPolicy": {
      "weight": 0,
      "weightTotal": 0,
      "header": "",
      "headerValue": "",
      "headerPattern": "",
      "cookie": ""
    }
  },  
......
  
]

從輸出中可以看到後端其實就是名為“nginx”的Service對應的Endpoints,它的IP是“10.244.1.26”。

[[email protected] ~]# kb get endpoints
NAME    ENDPOINTS        AGE
nginx   10.244.1.26:80   19h

這裡需要強調一點,Nginx Ingress並不將流量轉發給nginx service,而是直接轉發到後端的Pods,轉發策略也完全由Ingress Controller來決定。這樣不僅減少了一次DNAT,也能實現更豐富的負載均衡策略。Ingress資源中出現的Service物件只是為了選擇後端的Endpoints。

總結

文章對Nginx Ingress做了介紹,Kubernetes中可以選擇的Ingress有很多,讀者可以根據需要選擇。