# 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 將所有的流量導向一個服務:

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 所需的設定文件,然後依序產生新的路由轉送規則。

## 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/)