# How does Ingress Nginx Controller work? 本文介紹 Ingress 基本概念、Ingress Controller 工作原理和 Nginx Ingress Controller 的使用說明。 ## 1. Ingress 基本概念 在 Kubernetes 叢集中,Ingress 作為叢集內服務對外暴露的存取存取點,幾乎承載著叢集內服務存取的所有流量。 Ingress 是 Kubernetes 中的一個 resource object,用來管理叢集外部存取叢集內部服務的方式。可以透過 Ingress 資源來設定不同的轉送規則,從而實現根據不同的規則設定存取叢集內不同的 Service 所對應的後端 Pod。 ## 2. What is Ingress? Ingress 將叢集外部的 HTTP 和 HTTPS 請求路由到叢集內的服務。流量路由是根據 Ingress 資源上定義的規則來控制的。 以下是一個簡單的例子,Ingress 將所有的流量導向一個服務: ![image](https://hackmd.io/_uploads/rkqmvVjxJl.png) Ingress 可以設定為給服務一個外部可訪問的網址、負載均衡的流量、terminate SSL / TLS,以及提供 name-based ( 基於名稱 ) 的 virtual hosting 功能。[Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/) 負責實現 Ingress,一般會搭配 load balancer 一起使用,也可能設定 edge router 或額外的前端來幫助處理流量。 Ingress 不會暴露任意的 ports (埠) 或 protocols (通訊協定)。如果需要將 HTTP 和 HTTPS 以外的服務暴露到互聯網,通常會使用 [Service.Type=NodePort](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport) 或 [Service.Type=LoadBalancer](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) 類型的服務來實現。 ## 3. Ingress Controllers 使用須知 要讓 Ingress 在你的 K8s 叢集中運作,必須有一個 Ingress controller 在運行。你需要選擇至少一個 Ingress controller 並確保它已經在叢集中設定好。 和其他在 `kube-controller-manager` 二進位程式中運行的 controller 不同,Ingress controller 並不會隨著叢集自動啟動。 ## 4. Ingress Controller 工作原理 Ingress Controller 用於解析 Ingress 的轉送規則。 Ingress Controller 收到請求,匹配 Ingress 轉發規則轉發到後端 Service 所對應的 Pod,由 Pod 處理請求。 Kubernetes 中 Service、Ingress 與 Ingress Controller 有著以下關係: * Service 是後端真實服務的抽象,一個 Service 可以代表多個相同的後端服務。 * Ingress 是反向代理規則,用來規定 HTTP、HTTPS 請求應該被轉送到哪個 Service 所對應的 Pod。例如根據請求中不同的 Host 和 URL 路徑,讓請求落在不同 Service 所對應的 Pod上。 * Ingress Controller 是一個反向代理程序,負責解析 Ingress 的反向代理規則。如果Ingress 有增刪改的變動,Ingress Controller 會及時更新自己對應的轉送規則,當 Ingress Controller 收到請求後就會根據這些規則將請求轉送到對應 Service 的 Pod 上。 Ingress Controller 透過 API Server 取得 Ingress 資源的變化,動態地產生 Load Balancer 所需的設定文件,然後依序產生新的路由轉送規則。 ![image](https://hackmd.io/_uploads/SJXcqrjgJl.png) ## 5. Deploying the Ingress Nginx Controller as a Deployment Workload ```! # curl -sL https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml | kubectl apply -f - ``` 執行結果 : ``` namespace/ingress-nginx created serviceaccount/ingress-nginx created serviceaccount/ingress-nginx-admission created ``` 確認 pod 的狀態是 up 和 running ```! # kubectl wait -n ingress-nginx pod -l app.kubernetes.io/component=controller --for=condition=Ready --timeout=120s ``` 執行結果 : ```! pod/ingress-nginx-controller-7d8d8c7b4c-pctt4 condition met ``` ## 6. 了解 Ingress Nginx Controller Kubernetes Resources ``` $ kubectl get all -n ingress-nginx -o name ``` 執行結果 : ``` pod/ingress-nginx-admission-create-lgfnk pod/ingress-nginx-admission-patch-55p8j pod/ingress-nginx-controller-7d8d8c7b4c-pctt4 service/ingress-nginx-controller service/ingress-nginx-controller-admission deployment.apps/ingress-nginx-controller replicaset.apps/ingress-nginx-controller-7d8d8c7b4c job.batch/ingress-nginx-admission-create job.batch/ingress-nginx-admission-patch ``` 這是一個典型的 Deployment,前面有兩個 Service 在支援,以下是為什麼需要這樣做的原因: * 第一個 service 叫做 `ingress-nginx-controller`,它是一個 load balancer 類型的 service,用來將 Ingress controller 的功能暴露給外部。它透過穩定的 IP 地址和 DNS 名稱,讓外部客戶端可以連接到 Ingress controller。load balancer service 也幫助 Ingress controller 將進來的流量分散到多個 Pod 分身上,提升可用性和擴展性。 * 第二個 service 叫做 `ingress-nginx-controller-admission`,它是一個 cluster IP service,用來控制 Ingress controller 的准入。這個 service 在 Ingress 資源被 Ingress controller 處理之前,用於驗證和修改這些資源。它在叢集內部運行,不對外部客戶端暴露,使用 Kubernetes 的 admission control mechanism (准入控制機制) 攔截並修改 Ingress 資源。 * 簡而言之,第一個 service 提供 Ingress controller 的外部進入點,負責 traffic routing 和 load balancing;而第二個 service 負責在 Ingress 資源被 Ingress controller 處理前進行驗證和修改。 如果我們查看 Endpoints,就會發現兩個服務都指向相同的 Pod ```! $ kubectl -n ingress-nginx get endpoints ``` 執行結果 : ``` NAME ENDPOINTS AGE ingress-nginx-controller 10.244.2.30:443,10.244.2.30:80 24m ingress-nginx-controller-admission 10.244.2.30:8443 24m ``` 而這個 pod 就是 `ingress-nginx-controller` ``` $ kubectl -n ingress-nginx get pods -l "app.kubernetes.io/component=controller" -o wide ``` 執行結果 : ``` NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ingress-nginx-controller-7d8d8c7b4c-pctt4 1/1 Running 0 27m 10.244.2.30 worker2 <none> <none> ``` ## 7. 設定 ingress nginx controller 使用 host network 在某些環境中,如果沒有 load balancer 可用,而且不能使用 NodePorts,可以設定 ingress-nginx 的 Pod 使用它們所在主機的網路,而不是使用獨立的 network namespace。這樣做的好處是 Ingress-Nginx Controller 可以直接將 80 和 443 埠綁定到 Kubernetes 節點的網卡上,而不需要透過 NodePort 服務進行額外的網路轉換。 !!! 注意:這種做法不會利用任何 Service 物件來暴露 Ingress-Nginx Controller。如果目標叢集中存在 ingress-nginx 的 Service,建議將其刪除。 ``` $ kubectl edit -n ingress-nginx deployment ingress-nginx-controller ``` 執行結果 : <pre> … spec: <font color=red>## 1. 新增以下這行 hostNetwork: true</font> containers: - args: - /nginx-ingress-controller <font color=red>## 2. 註解掉以下這行的設定 #- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller</font> ... <font color=red>## 3. 新增以下這行 - --report-node-internal-ip-address</font> </pre> !!! 危險 "安全考量" 啟用這個選項會讓每個系統的 daemon 都會在任何網路介面(包括主機的 loopback)上暴露給 Ingress-Nginx Controller。請仔細評估這樣做對系統安全性的影響。 !!! 例子 考慮這個由 2 個副本組成的 ingress-nginx-controller 部署,NGINX Pod 繼承了它們所在主機的 IP 地址,而不是使用內部的 Pod IP。 ``` $ kubectl -n ingress-nginx get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ingress-nginx-admission-create-lgfnk 0/1 Completed 0 49m 10.244.2.28 worker2 <none> <none> ingress-nginx-admission-patch-55p8j 0/1 Completed 0 49m 10.244.2.29 worker2 <none> <none> ingress-nginx-controller-67f877d9d8-9sr6q 1/1 Running 0 3m24s 172.22.8.203 worker2 <none> <none> ``` 這種部署方式的一個主要限制是,每個叢集節點上只能調度一個 Ingress-Nginx 控制器 Pod,因為在同一個網路介面上綁定相同的埠多次在技術上是不可能的。因為這個原因而無法調度的 Pod 會出現以下的事件錯誤: ```! $ kubectl -n ingress-nginx get events \ --sort-by='.lastTimestamp' \ --field-selector involvedObject.kind=Pod ``` 執行結果 ```! LAST SEEN TYPE REASON OBJECT MESSAGE 5m13s Warning FailedScheduling pod/ingress-nginx-controller-67f877d9d8-8z2ws 0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) didn't have free ports for the requested pod ports. preemption: 0/3 nodes are available: 1 Preemption is not helpful for scheduling, 2 No preemption victims found for incoming pod. ... ``` 確保只建立可排程 Pod 的方法之一,是將 Ingress-Nginx Controller 部署為 DaemonSet,而非傳統的 Deployment。 除非某個節點被設定為拒絕這些 Pod,否則 DaemonSet 只會為每個叢集節點(包括主節點)調度一種 Pod。 像使用 NodePorts 一樣,這種方法也有一些需要注意的小問題。 * DNS 解析 設定了 `hostNetwork: true` 的 Pod 不會使用內部的 DNS resolver(例如 kube-dns 或 CoreDNS),除非它們的 `dnsPolicy` 設定為 `ClusterFirstWithHostNet`。如果 NGINX 需要解析內部名稱,建議使用這個設定。 * Ingress 狀態 由於使用主機網路的設定中沒有任何 Service 來暴露 Ingress-Nginx Controller,因此在標準雲端環境中常用的 `--publish-service` flag 不適用,所有 Ingress 物件的狀態會保持為空白。 ``` $ kubectl get ingress NAME HOSTS ADDRESS PORTS test-ingress myapp.example.com 80 ``` 相反,由於 bare-metal 節點通常沒有 ExternalIP,因此必須啟用 `--report-node-internal-ip-address` flag,將所有 Ingress 物件的狀態設定為執行 Ingress-Nginx Controller 的所有節點的內部 IP 位址。 !!! 範例 給定一個由 2 個 replicas 組成的 ingress-nginx-controller DaemonSet ``` $ kubectl -n ingress-nginx get pod -o wide NAME READY STATUS IP NODE default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2 ingress-nginx-controller-5b4cf5fc6-7lg6c 1/1 Running 203.0.113.3 host-3 ingress-nginx-controller-5b4cf5fc6-lzrls 1/1 Running 203.0.113.2 host-2 ``` controller 會將其管理的所有 Ingress 物件的狀態設定為下列值: ``` $ kubectl get ingress -o wide NAME HOSTS ADDRESS PORTS test-ingress myapp.example.com 203.0.113.2,203.0.113.3 80 ``` !!! 注意 另外,也可以使用 `--publish-status-address` 標誌來覆寫寫入 Ingress 物件的位址。請參閱[命令列參數](https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/cli-arguments.md)。 ## 8. Deploying an Application (Comprising of Deployment, Service, and Ingress) ```bash! $ kubectl create ns ing-internal namespace/ing-internal created $ echo 'kind: Pod apiVersion: v1 metadata: name: hi-app namespace: ing-internal labels: app: hi-app spec: containers: - name: hi-app image: quay.io/flysangel/hashicorp/http-echo args: - "-text=hi" --- kind: Service apiVersion: v1 metadata: name: hi-service namespace: ing-internal spec: selector: app: hi-app ports: - port: 5678' | kubectl apply -f - $ echo 'apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ping namespace: ing-internal annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx rules: - http: paths: - path: /hi pathType: Prefix backend: service: name: hi-service port: number: 5678' | kubectl apply -f - ``` ## 9. Observing the Ingress Nginx Controller Pod 如果我們現在查看 Ingress Nginx Controller pod log,我們會發現上一步建立的 Ingress 有一些 log 資訊: ``` $ kubectl -n ingress-nginx logs deployments/ingress-nginx-controller ``` 執行結果 : ```! ... "successfully validated configuration, accepting" ingress="ing-internal/ping" "Found valid IngressClass" ingress="ing-internal/ping" ingressclass="nginx" "Configuration changes detected, backend reload required" Event(v1.ObjectReference{Kind:"Ingress", Namespace:"ing-internal", Name:"ping", UID:"b77f8877-1a41-4530-95ce-6aaa4032cbb8", APIVersion:"networking.k8s.io/v1", ResourceVersion:"78690", FieldPath:""}): type: 'Normal' reason: 'Sync' Scheduled for sync "Backend successfully reloaded" Event(v1.ObjectReference{Kind:"Pod", Namespace:"ingress-nginx", Name:"ingress-nginx-controller-67f877d9d8-9sr6q", UID:"a371a524-53c5-4e3a-8ea3-6b3858275c52", APIVersion:"v1", ResourceVersion:"71406", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration "updating Ingress status" namespace="ing-internal" ingress="ping" currentValue=null newValue=[{"ip":"172.22.8.203"}] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"ing-internal", Name:"ping", UID:"b77f8877-1a41-4530-95ce-6aaa4032cbb8", APIVersion:"networking.k8s.io/v1", ResourceVersion:"78765", FieldPath:""}): type: 'Normal' reason: 'Sync' Scheduled for sync ``` 基本上,Ingress Nginx Controller pod 會一直觀察 Kubernetes API Server。一旦接收到 Ingress 資源已建立的事件,它就會檢查其語法,以及是否已定義了 `IngressClass=nginx`。 如果新的 Ingress manifest 符合規定,Nginx Controller 就會更新用於 routing 的設定檔,並在不關閉 Pod 的情況下啟動重新載入。 ## 10. 查看 Ingress Nginx Controller Pod Configuration File routing 設定是存儲在 Ingress Nginx Controller Pod 的設定文件中。這裡我提取了一些高層次的相關部分: ```! # 將 Ingress Nginx Controller Pod 名稱設為變數 $ PN=$(kubectl -n ingress-nginx get pods -l "app.kubernetes.io/component=controller" -o name | head -n 1) $ kubectl -n ingress-nginx exec ${PN#*/} -- cat nginx.conf ``` 執行結果 : ``` (...) http { (...) server { (...) location ~* "^/hi" { set $namespace "ing-internal"; set $ingress_name "ping"; set $service_name "hi-service"; set $service_port "5678"; set $location_path "/hi"; set $global_rate_limit_exceeding n; rewrite_by_lua_block { lua_ingress.rewrite({ force_ssl_redirect = false, ssl_redirect = true, force_no_ssl_redirect = false, preserve_trailing_slash = false, use_port_in_redirects = false, global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } }, }) balancer.rewrite() plugins.run() ... # Custom Response Headers rewrite "(?i)/hi" / break; proxy_pass http://upstream_balancer; proxy_redirect off; } (...) } } } ``` 基本上,如果這個 Nginx 伺服器收到針對根路徑(也就是請求中沒有指定路徑)的請求,它會使用 OpenResty 的 `ngx_http_lua_module` 模組來重寫請求,並將其傳遞給應用服務。 讓我們來看看目前為止的設定: - 通過 `hi-service` Service 將流量以 load balanced 或 random 的方式傳給網站應用的 Pod。 - load balanced 或 random 將取決於 kube-proxy 的 mode - 創建了一個帶有 `class=nginx` 屬性的 Ingress ,來將流量 routes 到這個 Service。這會在 Kubernetes 中觸發一個事件。 - Ingress Nginx Controller Pod 看到這個事件並做出反應:它會驗證 Ingress 設定是否語法正確,以及是否設置了必要的屬性。 - 如果所有檢查都通過,Ingress Nginx Controller 會將 routing 細節加入到其內部設定文件中,並進行 hot reloads。 - Ingress Nginx Controller Pod 作為反向代理,將它收到的流量 routing 到叢集中的相應服務。 - 使用 Ingress 資源來創建這些路由規則。 檢視 Ingress 狀態 ``` $ kubectl -n ing-internal get ing NAME CLASS HOSTS ADDRESS PORTS AGE ping nginx * 172.22.8.203 80 28m ``` 可以看到 Ingress 的 Address 和 Ports 對應的就是 Ingress Nginx Controller Pod 的 IP 跟 Port。 ## 11. Ref - [Ingress - Kubernetes Docs](https://kubernetes.io/docs/concepts/services-networking/ingress/) - [Ingress Controllers - Kubernetes Docs](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/) - [Nginx Ingress 概述 - 阿里云官網文件](https://help.aliyun.com/zh/ack/serverless-kubernetes/user-guide/overview-2?spm=a2c4g.11186623.0.0.1cd82873FaSP3i) - [Bare-metal considerations - ingress-nginx Docs](https://github.com/kubernetes/ingress-nginx/blob/main/docs/deploy/baremetal.md) - [How the Ingress Nginx Controller Works](https://luppeng.wordpress.com/2024/06/09/how-the-nginx-ingress-controller-works-in-a-deployment-on-an-on-premise-kubernetes-cluster/)