一. 簡介
Ingress 服務是為全局的、代理不同后端 Service 而設置的負載均衡服務。所以,所謂 Ingress,就是 Service 的 “Service”。
1.2 場景
Kubernetes 的 Service 只有四層代理,暫時只支持這樣的格式:IP:Port
訪問。
而 Ingress api
支持實現七層代理,可以用來綁定外部域名。
1.2 組成
Ingress 由兩部分組成:
- Ingress Controller
這是一個標準,可以有很多實現,例如下文的ingress-nginx
,它以pod形式運行的。 - IngressRule
以 yaml 形式為載體的一組聲明式的策略,ingress-controller
會動態地按照策略生成配置文件(如:nginx.conf)
關于本文的項目的代碼,都放于鏈接:GitHub資源
二. IngressRule
IngressRule
的 Key
,就叫做:host
。它必須是一個標準的域名格式(Fully Qualified Domain Name)
的字符串,而不能是 IP 地址。
2.1 案例
“demo-ingress.yaml” 文件內容如下:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: demo-ingress
annotations:
# use the shared ingress-nginx
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- ingress.wyatt.plus
secretName: demo-secret
rules:
- host: ingress.wyatt.plus
http:
paths:
- path: /svc1
backend:
serviceName: svc-1
servicePort: 80
- path: /svc2
backend:
serviceName: svc-2
servicePort: 80
2.2 分析
上面的IngressRule
規則的定義,則依賴于 path
字段。這里的每一個 path
都對應一個后端 Service
。所以在我們的例子里,定義了兩個 path
,它們分別對應 svc-1
和 svc-2
這兩個 Deployment
的 Service
。
三. Ingress Controller
Ingress Controller
會根據定義的 Ingress 對象,提供對應的代理能力。目前,業界常用的各種反向代理項目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已經為 Kubernetes 專門維護了對應的 Ingress Controller。
當一個新的 Ingress 對象由用戶創建后,nginx-ingress-controller
就會根據 Ingress
對象里定義的內容,生成一份對應的 Nginx 配置文件(/etc/nginx/nginx.conf)
,并使用這個配置文件啟動一個Nginx
服務。而一旦 Ingress
對象被更新,nginx-ingress-controller
就會更新這個配置文件。
3.1 案例(mac平臺)
對于 “deploy.yaml” 文件,我截取核心內容如下:
# Source: ingress-nginx/templates/controller-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
helm.sh/chart: ingress-nginx-3.23.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.44.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
revisionHistoryLimit: 10
minReadySeconds: 0
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
spec:
dnsPolicy: ClusterFirst
containers:
- name: controller
image: k8s.gcr.io/ingress-nginx/controller:v0.44.0@sha256:3dd0fac48073beaca2d67a78c746c7593f9c575168a17139a9955a82c63c4b9a
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /nginx-ingress-controller
- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
- --election-id=ingress-controller-leader
- --ingress-class=nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LD_PRELOAD
value: /usr/local/lib/libmimalloc.so
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 5
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
resources:
requests:
cpu: 100m
memory: 90Mi
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
3.2 分析
以看到,在上述 YAML 文件中,我們定義了一個使用 nginx-ingress-controller
鏡像的 Pod。需要注意的是,這個 Pod 的啟動命令需要使用該 Pod 所在的 Namespace
作為參數。而這個信息,當然是通過 Downward API
拿到的,即:Pod 的 env
字段里的定義(env.valueFrom.fieldRef.fieldPath)
。
四. Demo
我們做一個如下的場景站點:https://ingress.wyatt.plus ,這是一個微服務系統。
- https://ingress.wyatt.plus/svc1,對應的是“微服務-1”
-
https://ingress.wyatt.plus/svc2,對應的是“微服務-2”
這兩個系統,分別由名叫 “svc-1” 和 “svc-2” 這樣兩個 Deployment 來提供服務。
由于每個平臺配置文件有區別,所以我將按照mac平臺的方式按照配置文件。各平臺的nginx controller配置
4.1 按照 Ingress Controller
我使用了Docker for mac
相關的配置,執行如下指令:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.44.0/deploy/static/provider/cloud/deploy.yaml
4.2 配置Service與Deployment
4.2.1 demo-svc-1
該文件包了 Deployment 和 Service 這倆個配置,內容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: svc-1
spec:
replicas: 2
selector:
matchLabels:
app: svc-1
template:
metadata:
labels:
app: svc-1
spec:
containers:
- name: svc-1
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-1
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: svc-1
該配置通過 label selector
來進行匹配 app: svc-1
的 Pod。
4.2.2 demo-svc-2
該文件包了 Deployment 和 Service 這倆個配置,內容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: svc-2
spec:
replicas: 3
selector:
matchLabels:
app: svc-2
template:
metadata:
labels:
app: svc-2
spec:
containers:
- name: svc-2
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-2
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: svc-2
該配置通過 label selector
來進行匹配 app: svc-2
的 Pod。
4.3 創建Secret
創建 Secret 有2種方式,如下:
4.3.1 命令行創建
kubectl create secret tls demo-secret --key tls.key --cert tls.crt
4.3.2 以 YAML
方式創建
apiVersion: v1
kind: Secret
metadata:
name: demo-secret
type: Opaque
data:
tls.crt: **************************
tls.key: **************************
4.4 配置Ingress
注意 Ingress 的版本,最新的 API 變動較多,最新的為 networking.k8s.io/v1
。
“demo-ingress.yaml” 配置如下:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: demo-ingress
annotations:
# use the shared ingress-nginx
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- ingress.wyatt.plus
secretName: demo-secret
rules:
- host: ingress.wyatt.plus
http:
paths:
- path: /svc1
backend:
serviceName: svc-1
servicePort: 80
- path: /svc2
backend:
serviceName: svc-2
servicePort: 80
當我們執行 kubectl apply
創建成功后,我們可以再通過如下指令查看 Ingress 情況。
kubectl get ingress
# result
NAME CLASS HOSTS ADDRESS PORTS AGE
demo-ingress <none> ingress.wyatt.plus localhost 80, 443 110m
4.5 檢查
關于當前的 “demo-ingress” ,我們可以通過更詳細的指令查看里面內容:
kubectl describe ingress demo-ingress
# result
Name: demo-ingress
Namespace: default
Address: localhost
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
demo-secret terminates ingress.wyatt.plus
Rules:
Host Path Backends
---- ---- --------
ingress.wyatt.plus
/svc1 svc-1:80 (10.1.3.124:80,10.1.3.126:80)
/svc2 svc-2:80 (10.1.3.123:80,10.1.3.125:80,10.1.3.127:80)
Annotations: kubernetes.io/ingress.class: nginx
可以看到,這個Ingress
對象最核心的部分,正是Rules
字段。其中,我們定義的 Host 是 ingress.wyatt.plus
,它有兩條轉發規則(Path)
,分別轉發給 /svc1
和 /svc2
。
實際執行結果如下:
4.6 驗證
我們可以通過訪問這個 Ingress 的地址和端口,訪問到我們前面部署的應用,
我們使用瀏覽器可以訪問 https://ingress.wyatt.plus/svc1
和 https://ingress.wyatt.plus/svc2
這倆個 url 。
我們將分別看到如下的倆個頁面:
- svc1
Server address: 10.1.3.124:80
Server name: svc-1-67c6fdbf4d-bwwlp
Date: 31/Mar/2021:22:53:06 +0000
URI: /svc1
Request ID: d37a5c0b9aaae3c71865ff7fcb3c615b
我們可以看到,訪問這個 URL 得到的返回信息是:Server name: svc-1-67c6fdbf4d-bwwlp
。這正是 svc-1
這個 Deployment 的名字。
- svc2
Server address: 10.1.3.127:80
Server name: svc-2-77f44d76b-bx77x
Date: 31/Mar/2021:22:54:13 +0000
URI: /svc2
Request ID: 17645ccb197fe3fb6daf6200aba289bf
我們可以看到,訪問這個 URL 得到的返回信息是:Server name: svc-2-77f44d76b-bx77x
。這正是 svc-2
這個 Deployment 的名字。
可以確認 Nginx Ingress Controller
創建的 Nginx 負載均衡器,已經成功地將請求轉發給了對應的后端 Service。
我們也可以在 Kubernetes Dashboard 里面查看 Ingress 對于的路徑匹配規則,如下圖:
五. Ingress部署方式
5.1 Deployment + LoadBalancer 模式的 Service
5.1.1 細節
如果要把 Ingress 部署在公有云,那可以選擇這種方式。用 Deployment 部署 ingress-controller ,創建一個 type 為 LoadBalancer
的 Service 關聯這組 pod 。大部分公有云,都會為 LoadBalancer
的 Service 自動創建一個負載均衡器,通常還綁定了公網地址。只要把域名解析指向該地址,就實現了集群服務的對外暴露。
缺點:
- 需要額外購買公有云的服務,與平臺掛鉤。
5.1.2 架構圖
5.2 Deployment + NodePort 模式的 Service
5.2.1 細節
同樣用 Deployment 模式部署 ingress-controller ,并創建對應的服務,但是 type 為 NodePort
。這樣,ingress 就會暴露在集群節點ip的特定端口上。由于 NodePort 暴露的端口是隨機端口,一般會在前面再搭建一套負載均衡器來轉發請求。該方式一般用于宿主機是相對固定的環境ip地址不變的場景。
缺點:
- NodePort 方式暴露 ingress 雖然簡單方便,但是 NodePort 多了一層 NAT ,在請求量級很大時可能對性能會有一定影響。
- 請求節點會是類似 https://www.wyatt.plus:30076, 其中
30076
是kubectl get svc -n ingress-nginx
的 svc 暴露出來的 NodePort 端口。
5.2.2 架構
5.3 DaemonSet + HostNetwork + nodeSelector
5.3.1 細節
用 DaemonSet
結合 nodeSelector
來部署 ingress-controller 到特定的node上,然后使用 HostNetwork
直接把該 pod 與宿主機 node 的網絡打通,直接使用宿主機的 80/433
端口就能訪問服務。這時,ingress-controller 所在的node機器就很類似傳統架構的邊緣節點,比如機房入口的nginx服務器。
優點:
- 該方式整個請求鏈路最簡單,性能相對 NodePort 模式更好。
缺點: - 由于直接利用宿主機節點的網絡和端口,一個 node 只能部署一個
ingress-controller
pod。
5.2.3 架構
六. 總結
Ingress
只能工作在七層,而 Service
只能工作在四層。所以當我們想要在 Kubernetes 里為應用進行 TLS
配置等 HTTP
相關的操作時,都必須通過Ingress
來進行。
有了 Ingress 這個抽象,我們就可以根據自己的需求來自由選擇 Ingress Controller。我們只需要做很少的編程工作,就可以實現一個自己的Ingress Controller
。
Ingress 帶來的靈活度和自由度,對于使用容器時代來說,其實是非常有意義的。
歡迎收藏個人博客: Wyatt's Blog ,非常感謝~
Reference
https://kubernetes.github.io/ingress-nginx/
https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md#docker-for-mac
https://time.geekbang.org/column/article/69214?utm_campaign=guanwang&utm_source=baidu-ad&utm_medium=ppzq-pc&utm_content=title&utm_term=baidu-ad-ppzq-title
https://www.cnblogs.com/baoshu/p/13255909.html
https://github.com/kubernetes/ingress-nginx/