Kubernetes 之 Ingress 剖析

【導讀】本文詳細介紹了 Kubernetes 的 Ingress 的使用。

一. 簡介

Ingress 服務是爲全局的、代理不同後端 Service 而設置的負載均衡服務。所以,所謂 Ingress,就是 Service 的 “Service”。

1.2 場景

Kubernetes 的 Service 只有四層代理,暫時只支持這樣的格式:IP:Port訪問。
Ingress api 支持實現七層代理,可以用來綁定外部域名。

1.2 組成

Ingress 由兩部分組成:

關於本文的項目的代碼,都放於鏈接:GitHub 資源 (https://github.com/wyattup/devops/tree/main/demos/kubernetes/Ingress)

二. IngressRule

IngressRuleKey,就叫做: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-1svc-2 這兩個 DeploymentService

三. 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 ,這是一個微服務系統。

由於每個平臺配置文件有區別,所以我將按照 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

實際執行結果如下:

demo-ingress

4.6 驗證

我們可以通過訪問這個 Ingress 的地址和端口,訪問到我們前面部署的應用,
我們使用瀏覽器可以訪問 https://ingress.wyatt.plus/svc1https://ingress.wyatt.plus/svc2 這倆個 url 。
我們將分別看到如下的倆個頁面:

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 的名字。

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 對於的路徑匹配規則,如下圖:

Kubernetes Dashboard

五. Ingress 部署方式

5.1 Deployment + LoadBalancer 模式的 Service

5.1.1 細節

如果要把 Ingress 部署在公有云,那可以選擇這種方式。用 Deployment 部署 ingress-controller ,創建一個 type 爲 LoadBalancer 的 Service 關聯這組 pod 。大部分公有云,都會爲 LoadBalancer 的 Service 自動創建一個負載均衡器,通常還綁定了公網地址。只要把域名解析指向該地址,就實現了集羣服務的對外暴露。
缺點:

5.1.2 架構圖

Deployment + LoadBalancer

5.2 Deployment + NodePort 模式的 Service

5.2.1 細節

同樣用 Deployment 模式部署 ingress-controller ,並創建對應的服務,但是 type 爲 NodePort。這樣,ingress 就會暴露在集羣節點 ip 的特定端口上。由於 NodePort 暴露的端口是隨機端口,一般會在前面再搭建一套負載均衡器來轉發請求。該方式一般用於宿主機是相對固定的環境 ip 地址不變的場景。

缺點:

5.2.2 架構

Deployment + NodePort

5.3 DaemonSet + HostNetwork + nodeSelector

5.3.1 細節

DaemonSet 結合 nodeSelector 來部署 ingress-controller 到特定的 node 上,然後使用 HostNetwork 直接把該 pod 與宿主機 node 的網絡打通,直接使用宿主機的 80/433 端口就能訪問服務。這時,ingress-controller 所在的 node 機器就很類似傳統架構的邊緣節點,比如機房入口的 nginx 服務器。
優點:

5.2.3 架構

DaemonSet + HostNetwork + nodeSelector

六. 總結

Ingress 只能工作在七層,而 Service 只能工作在四層。所以當我們想要在 Kubernetes 裏爲應用進行 TLS 配置等 HTTP 相關的操作時,都必須通過Ingress 來進行。
有了 Ingress 這個抽象,我們就可以根據自己的需求來自由選擇 Ingress Controller。我們只需要做很少的編程工作,就可以實現一個自己的Ingress Controller

Ingress 帶來的靈活度和自由度,對於使用容器時代來說,其實是非常有意義的。

轉自:

jianshu.com/p/75942f76467b

Go 開發大全

參與維護一個非常全面的 Go 開源技術資源庫。日常分享 Go, 雲原生、k8s、Docker 和微服務方面的技術文章和行業動態。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/j-aqoFnF3vi8Q_0yNXxoBw