我們已經了解了 Ingress 資源對象只是一個路由請求描述配置文件,要讓其真正生效還需要對應的 Ingress 控制器才行,Ingress 控制器有很多,這里我們先介紹使用最多的 ingress-nginx,它是基于 Nginx 的 Ingress 控制器。
運行原理
ingress-nginx 控制器主要是用來組裝一個 nginx.conf 的配置文件,當配置文件發生任何變動的時候就需要重新加載 Nginx 來生效,但是并不會只在影響 upstream 配置的變更后就重新加載 Nginx,控制器內部會使用一個 lua-nginx-module 來實現該功能。
我們知道 Kubernetes 控制器使用控制循環模式來檢查控制器中所需的狀態是否已更新或是否需要變更,所以 ingress-nginx 需要使用集群中的不同對象來構建模型,比如 Ingress、Service、Endpoints、Secret、ConfigMap 等可以生成反映集群狀態的配置文件的對象,控制器需要一直 Watch 這些資源對象的變化,但是并沒有辦法知道特定的更改是否會影響到最終生成的 nginx.conf 配置文件,所以一旦 Watch 到了任何變化控制器都必須根據集群的狀態重建一個新的模型,并將其與當前的模型進行比較,如果模型相同則就可以避免生成新的 Nginx 配置并觸發重新加載,否則還需要檢查模型的差異是否只和端點有關,如果是這樣,則然后需要使用 HTTP POST 請求將新的端點列表發送到在 Nginx 內運行的 Lua 處理程序,并再次避免生成新的 Nginx 配置并觸發重新加載,如果運行和新模型之間的差異不僅僅是端點,那么就會基于新模型創建一個新的 Nginx 配置了,這樣構建模型最大的一個好處就是在狀態沒有變化時避免不必要的重新加載,可以節省大量 Nginx 重新加載。
下面簡單描述了需要重新加載的一些場景:
- 創建了新的 Ingress 資源
- TLS 添加到現有 Ingress
- 從 Ingress 中添加或刪除 path 路徑
- Ingress、Service、Secret 被刪除了
- Ingress 的一些缺失引用對象變可用了,例如 Service 或 Secret
- 更新了一個 Secret
對于集群規模較大的場景下頻繁的對 Nginx 進行重新加載顯然會造成大量的性能消耗,所以要盡可能減少出現重新加載的場景。
安裝
由于 ingress-nginx 所在的節點需要能夠訪問外網(不是強制的),這樣域名可以解析到這些節點上直接使用,所以需要讓 ingress-nginx 綁定節點的 80 和 443 端口,所以可以使用 hostPort 來進行訪問,當然對于線上環境來說為了保證高可用,一般是需要運行多個 ·ingress-nginx 實例的,然后可以用一個 nginx/haproxy 作為入口,通過 keepalived 來訪問邊緣節點的 vip 地址。
!!! info "邊緣節點" 所謂的邊緣節點即集群內部用來向集群外暴露服務能力的節點,集群外部的服務通過該節點來調用集群內部的服務,邊緣節點是集群內外交流的一個 Endpoint。
這里我們使用 Helm Chart(后面會詳細講解)的方式來進行安裝:
- # 如果你不喜歡使用 helm chart 進行安裝也可以使用下面的命令一鍵安裝
- # kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml
- ? helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
- ? helm repo update
- ? helm fetch ingress-nginx/ingress-nginx
- ? tar -xvf ingress-nginx-4.0.13.tgz && cd ingress-nginx
- ? tree .
- .
- ├── CHANGELOG.md
- ├── Chart.yaml
- ├── OWNERS
- ├── README.md
- ├── ci
- │ ├── controller-custom-ingressclass-flags.yaml
- │ ├── daemonset-customconfig-values.yaml
- │ ├── daemonset-customnodeport-values.yaml
- │ ├── daemonset-headers-values.yaml
- │ ├── daemonset-internal-lb-values.yaml
- │ ├── daemonset-nodeport-values.yaml
- │ ├── daemonset-podannotations-values.yaml
- │ ├── daemonset-tcp-udp-configMapNamespace-values.yaml
- │ ├── daemonset-tcp-udp-values.yaml
- │ ├── daemonset-tcp-values.yaml
- │ ├── deamonset-default-values.yaml
- │ ├── deamonset-metrics-values.yaml
- │ ├── deamonset-psp-values.yaml
- │ ├── deamonset-webhook-and-psp-values.yaml
- │ ├── deamonset-webhook-values.yaml
- │ ├── deployment-autoscaling-behavior-values.yaml
- │ ├── deployment-autoscaling-values.yaml
- │ ├── deployment-customconfig-values.yaml
- │ ├── deployment-customnodeport-values.yaml
- │ ├── deployment-default-values.yaml
- │ ├── deployment-headers-values.yaml
- │ ├── deployment-internal-lb-values.yaml
- │ ├── deployment-metrics-values.yaml
- │ ├── deployment-nodeport-values.yaml
- │ ├── deployment-podannotations-values.yaml
- │ ├── deployment-psp-values.yaml
- │ ├── deployment-tcp-udp-configMapNamespace-values.yaml
- │ ├── deployment-tcp-udp-values.yaml
- │ ├── deployment-tcp-values.yaml
- │ ├── deployment-webhook-and-psp-values.yaml
- │ ├── deployment-webhook-resources-values.yaml
- │ └── deployment-webhook-values.yaml
- ├── templates
- │ ├── NOTES.txt
- │ ├── _helpers.tpl
- │ ├── _params.tpl
- │ ├── admission-webhooks
- │ │ ├── job-patch
- │ │ │ ├── clusterrole.yaml
- │ │ │ ├── clusterrolebinding.yaml
- │ │ │ ├── job-createSecret.yaml
- │ │ │ ├── job-patchWebhook.yaml
- │ │ │ ├── psp.yaml
- │ │ │ ├── role.yaml
- │ │ │ ├── rolebinding.yaml
- │ │ │ └── serviceaccount.yaml
- │ │ └── validating-webhook.yaml
- │ ├── clusterrole.yaml
- │ ├── clusterrolebinding.yaml
- │ ├── controller-configmap-addheaders.yaml
- │ ├── controller-configmap-proxyheaders.yaml
- │ ├── controller-configmap-tcp.yaml
- │ ├── controller-configmap-udp.yaml
- │ ├── controller-configmap.yaml
- │ ├── controller-daemonset.yaml
- │ ├── controller-deployment.yaml
- │ ├── controller-hpa.yaml
- │ ├── controller-ingressclass.yaml
- │ ├── controller-keda.yaml
- │ ├── controller-poddisruptionbudget.yaml
- │ ├── controller-prometheusrules.yaml
- │ ├── controller-psp.yaml
- │ ├── controller-role.yaml
- │ ├── controller-rolebinding.yaml
- │ ├── controller-service-internal.yaml
- │ ├── controller-service-metrics.yaml
- │ ├── controller-service-webhook.yaml
- │ ├── controller-service.yaml
- │ ├── controller-serviceaccount.yaml
- │ ├── controller-servicemonitor.yaml
- │ ├── default-backend-deployment.yaml
- │ ├── default-backend-hpa.yaml
- │ ├── default-backend-poddisruptionbudget.yaml
- │ ├── default-backend-psp.yaml
- │ ├── default-backend-role.yaml
- │ ├── default-backend-rolebinding.yaml
- │ ├── default-backend-service.yaml
- │ ├── default-backend-serviceaccount.yaml
- │ └── dh-param-secret.yaml
- └── values.yaml
- 4 directories, 81 files
Helm Chart 包下載下來后解壓就可以看到里面包含的模板文件,其中的 ci 目錄中就包含了各種場景下面安裝的 Values 配置文件,values.yaml 文件中包含的是所有可配置的默認值,我們可以對這些默認值進行覆蓋,我們這里測試環境就將 master1 節點看成邊緣節點,所以我們就直接將 ingress-nginx 固定到 master1 節點上,采用 hostNetwork 模式(生產環境可以使用 LB + DaemonSet hostNetwork 模式),為了避免創建的錯誤 Ingress 等資源對象影響控制器重新加載,所以我們也強烈建議大家開啟準入控制器,ingess-nginx 中會提供一個用于校驗資源對象的 Admission Webhook,我們可以通過 Values 文件進行開啟。然后新建一個名為 ci/daemonset-prod.yaml 的 Values 文件,用來覆蓋 ingress-nginx 默認的 Values 值。
對應的 Values 配置文件如下所示:
- # ci/daemonset-prod.yaml
- controller:
- name: controller
- image:
- repository: cnych/ingress-nginx
- tag: "v1.1.0"
- digest:
- dnsPolicy: ClusterFirstWithHostNet
- hostNetwork: true
- publishService: # hostNetwork 模式下設置為false,通過節點IP地址上報ingress status數據
- enabled: false
- # 是否需要處理不帶 ingressClass 注解或者 ingressClassName 屬性的 Ingress 對象
- # 設置為 true 會在控制器啟動參數中新增一個 --watch-ingress-without-class 標注
- watchIngressWithoutClass: false
- kind: DaemonSet
- tolerations: # kubeadm 安裝的集群默認情況下master是有污點,需要容忍這個污點才可以部署
- - key: "node-role.kubernetes.io/master"
- operator: "Equal"
- effect: "NoSchedule"
- nodeSelector: # 固定到master1節點
- kubernetes.io/hostname: master1
- service: # HostNetwork 模式不需要創建service
- enabled: false
- admissionWebhooks: # 強烈建議開啟 admission webhook
- enabled: true
- createSecretJob:
- resources:
- limits:
- cpu: 10m
- memory: 20Mi
- requests:
- cpu: 10m
- memory: 20Mi
- patchWebhookJob:
- resources:
- limits:
- cpu: 10m
- memory: 20Mi
- requests:
- cpu: 10m
- memory: 20Mi
- patch:
- enabled: true
- image:
- repository: cnych/ingress-nginx-webhook-certgen
- tag: v1.1.1
- digest:
- defaultBackend: # 配置默認后端
- enabled: true
- name: defaultbackend
- image:
- repository: cnych/ingress-nginx-defaultbackend
- tag: "1.5"
然后使用如下命令安裝 ingress-nginx 應用到 ingress-nginx 的命名空間中:
- ? kubectl create ns ingress-nginx
- ? helm upgrade --install ingress-nginx . -f ./ci/daemonset-prod.yaml --namespace ingress-nginx
- Release "ingress-nginx" does not exist. Installing it now.
- NAME: ingress-nginx
- LAST DEPLOYED: Thu Dec 16 16:47:20 2021
- NAMESPACE: ingress-nginx
- STATUS: deployed
- REVISION: 1
- TEST SUITE: None
- NOTES:
- The ingress-nginx controller has been installed.
- It may take a few minutes for the LoadBalancer IP to be available.
- You can watch the status by running 'kubectl --namespace ingress-nginx get services -o wide -w ingress-nginx-controller'
- An example Ingress that makes use of the controller:
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: example
- namespace: foo
- spec:
- ingressClassName: nginx
- rules:
- - host: www.example.com
- http:
- paths:
- - backend:
- service:
- name: exampleService
- port:
- number: 80
- path: /
- # This section is only required if TLS is to be enabled for the Ingress
- tls:
- - hosts:
- - www.example.com
- secretName: example-tls
- If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:
- apiVersion: v1
- kind: Secret
- metadata:
- name: example-tls
- namespace: foo
- data:
-
tls.crt:
-
tls.key:
key> - type: kubernetes.io/tls
部署完成后查看 Pod 的運行狀態:
- ? kubectl get svc -n ingress-nginx
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
-
ingress-nginx-controller-admission ClusterIP 10.96.15.99
443/TCP 11m -
ingress-nginx-defaultbackend ClusterIP 10.97.250.253
80/TCP 11m - ? kubectl get pods -n ingress-nginx
- NAME READY STATUS RESTARTS AGE
- ingress-nginx-controller-5dfdd4659c-9g7c2 1/1 Running 0 11m
- ingress-nginx-defaultbackend-84854cd6cb-xb7rv 1/1 Running 0 11m
- ? POD_NAME=$(kubectl get pods -l app.kubernetes.io/name=ingress-nginx -n ingress-nginx -o jsonpath='{.items[0].metadata.name}')
- ? kubectl exec -it $POD_NAME -n ingress-nginx -- /nginx-ingress-controller --version
- kubectl logs -f ingress-nginx-controller-5dfdd4659c-9g7c2 -n ingress-nginxW1216 08:51:22.179213 7 client_config.go:615] Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.
- I1216 08:51:22.179525 7 main.go:223] "Creating API client" host="https://10.96.0.1:443"
- -------------------------------------------------------------------------------
- NGINX Ingress controller
- Release: v1.1.0
- Build: cacbee86b6ccc45bde8ffc184521bed3022e7dee
- Repository: https://github.com/kubernetes/ingress-nginx
- nginx version: nginx/1.19.9
- -------------------------------------------------------------------------------
- I1216 08:51:22.198221 7 main.go:267] "Running in Kubernetes cluster" major="1" minor="22" git="v1.22.2" state="clean" commit="8b5a19147530eaac9476b0ab82980b4088bbc1b2" platform="linux/amd64"
- I1216 08:51:22.200478 7 main.go:86] "Valid default backend" service="ingress-nginx/ingress-nginx-defaultbackend"
- I1216 08:51:22.611100 7 main.go:104] "SSL fake certificate created" file="/etc/ingress-controller/ssl/default-fake-certificate.pem"
- I1216 08:51:22.627386 7 ssl.go:531] "loading tls certificate" path="/usr/local/certificates/cert" key="/usr/local/certificates/key"
- I1216 08:51:22.651187 7 nginx.go:255] "Starting NGINX Ingress controller"
當看到上面的信息證明 ingress-nginx 部署成功了,這里我們安裝的是最新版本的控制器,安裝完成后會自動創建一個 名為 nginx 的 IngressClass 對象:
- ? kubectl get ingressclass
- NAME CONTROLLER PARAMETERS AGE
-
nginx k8s.io/ingress-nginx
18m - ? kubectl get ingressclass nginx -o yaml
- apiVersion: networking.k8s.io/v1
- kind: IngressClass
- metadata:
- ......
- name: nginx
- resourceVersion: "1513966"
- uid: 70340e62-cab6-4a11-9982-2108f1db786b
- spec:
- controller: k8s.io/ingress-nginx
過這里我們只提供了一個 controller 屬性,如果還需要配置一些額外的參數,則可以在安裝的 values 文件中進行配置。
第一個示例
安裝成功后,現在我們來為一個 nginx 應用創建一個 Ingress 資源,如下所示:
- # my-nginx.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: my-nginx
- spec:
- selector:
- matchLabels:
- app: my-nginx
- template:
- metadata:
- labels:
- app: my-nginx
- spec:
- containers:
- - name: my-nginx
- image: nginx
- ports:
- - containerPort: 80
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: my-nginx
- labels:
- app: my-nginx
- spec:
- ports:
- - port: 80
- protocol: TCP
- name: http
- selector:
- app: my-nginx
- ---
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: my-nginx
- namespace: default
- spec:
- ingressClassName: nginx # 使用 nginx 的 IngressClass(關聯的 ingress-nginx 控制器)
- rules:
- - host: ngdemo.qikqiak.com # 將域名映射到 my-nginx 服務
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service: # 將所有請求發送到 my-nginx 服務的 80 端口
- name: my-nginx
- port:
- number: 80
- # 不過需要注意大部分Ingress控制器都不是直接轉發到Service
- # 而是只是通過Service來獲取后端的Endpoints列表,直接轉發到Pod,這樣可以減少網絡跳轉,提高性能
直接創建上面的資源對象:
- ? kubectl apply -f my-nginx.yaml
- deployment.apps/my-nginx created
- service/my-nginx created
- ingress.networking.k8s.io/my-nginx created
- ? kubectl get ingress
- NAME CLASS HOSTS ADDRESS PORTS AGE
- my-nginx nginx ngdemo.qikqiak.com 192.168.31.31 80 30m
在上面的 Ingress 資源對象中我們使用配置 ingressClassName: nginx 指定讓我們安裝的 ingress-nginx 這個控制器來處理我們的 Ingress 資源,配置的匹配路徑類型為前綴的方式去匹配 /,將來自域名 ngdemo.qikqiak.com 的所有請求轉發到 my-nginx 服務的后端 Endpoints 中去。
上面資源創建成功后,然后我們可以將域名 ngdemo.qikqiak.com 解析到 ingress-nginx 所在的邊緣節點中的任意一個,當然也可以在本地 /etc/hosts 中添加對應的映射也可以,然后就可以通過域名進行訪問了。
下圖顯示了客戶端是如何通過 Ingress 控制器連接到其中一個 Pod 的流程,客戶端首先對 ngdemo.qikqiak.com 執行 DNS 解析,得到 Ingress 控制器所在節點的 IP,然后客戶端向 Ingress 控制器發送 HTTP 請求,然后根據 Ingress 對象里面的描述匹配域名,找到對應的 Service 對象,并獲取關聯的 Endpoints 列表,將客戶端的請求轉發給其中一個 Pod。
前面我們也提到了 ingress-nginx 控制器的核心原理就是將我們的 Ingress 這些資源對象映射翻譯成 Nginx 配置文件 nginx.conf,我們可以通過查看控制器中的配置文件來驗證這點:
- ? kubectl exec -it $POD_NAME -n ingress-nginx -- cat /etc/nginx/nginx.conf
- ......
- upstream upstream_balancer {
- server 0.0.0.1; # placeholder
- balancer_by_lua_block {
- balancer.balance()
- }
- keepalive 320;
- keepalive_timeout 60s;
- keepalive_requests 10000;
- }
- ......
- ## start server ngdemo.qikqiak.com
- server {
- server_name ngdemo.qikqiak.com ;
- listen 80 ;
- listen [::]:80 ;
- listen 443 ssl http2 ;
- listen [::]:443 ssl http2 ;
- set $proxy_upstream_name "-";
- ssl_certificate_by_lua_block {
- certificate.call()
- }
- location / {
- set $namespace "default";
- set $ingress_name "my-nginx";
- set $service_name "my-nginx";
- set $service_port "80";
- set $location_path "/";
- set $global_rate_limit_exceeding n;
- ......
- proxy_next_upstream_timeout 0;
- proxy_next_upstream_tries 3;
- proxy_pass http://upstream_balancer;
- proxy_redirect off;
- }
- }
- ## end server ngdemo.qikqiak.com
- ......
我們可以在 nginx.conf 配置文件中看到上面我們新增的 Ingress 資源對象的相關配置信息,不過需要注意的是現在并不會為每個 backend 后端都創建一個 upstream 配置塊,現在是使用 Lua 程序進行動態處理的,所以我們沒有直接看到后端的 Endpoints 相關配置數據。
Nginx 配置
如果我們還想進行一些自定義配置,則有幾種方式可以實現:使用 Configmap 在 Nginx 中設置全局配置、通過 Ingress 的 Annotations 設置特定 Ingress 的規則、自定義模板。接下來我們重點給大家介紹使用注解來對 Ingress 對象進行自定義。
Basic Auth
我們可以在 Ingress 對象上配置一些基本的 Auth 認證,比如 Basic Auth,可以用 htpasswd 生成一個密碼文件來驗證身份驗證。
- ? htpasswd -c auth foo
- New password:
- Re-type new password:
- Adding password for user foo
然后根據上面的 auth 文件創建一個 secret 對象:
- ? kubectl create secret generic basic-auth --from-file=auth
- secret/basic-auth created
- ? kubectl get secret basic-auth -o yaml
- apiVersion: v1
- data:
- auth: Zm9vOiRhcHIxJFUxYlFZTFVoJHdIZUZQQ1dyZTlGRFZONTQ0dXVQdC4K
- kind: Secret
- metadata:
- name: basic-auth
- namespace: default
- type: Opaque
然后對上面的 my-nginx 應用創建一個具有 Basic Auth 的 Ingress 對象:
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: ingress-with-auth
- namespace: default
- annotations:
- nginx.ingress.kubernetes.io/auth-type: basic # 認證類型
- nginx.ingress.kubernetes.io/auth-secret: basic-auth # 包含 user/password 定義的 secret 對象名
- nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo' # 要顯示的帶有適當上下文的消息,說明需要身份驗證的原因
- spec:
- ingressClassName: nginx # 使用 nginx 的 IngressClass(關聯的 ingress-nginx 控制器)
- rules:
- - host: bauth.qikqiak.com # 將域名映射到 my-nginx 服務
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service: # 將所有請求發送到 my-nginx 服務的 80 端口
- name: my-nginx
- port:
- number: 80
直接創建上面的資源對象,然后通過下面的命令或者在瀏覽器中直接打開配置的域名:
- ? kubectl get ingress
- NAME CLASS HOSTS ADDRESS PORTS AGE
- ingress-with-auth nginx bauth.qikqiak.com 192.168.31.31 80 6m55s
- ? curl -v http://192.168.31.31 -H 'Host: bauth.qikqiak.com'
- * Trying 192.168.31.31...
- * TCP_NODELAY set
- * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0)
- > GET / HTTP/1.1
- > Host: bauth.qikqiak.com
- > User-Agent: curl/7.64.1
- > Accept: */*
- >
- < HTTP/1.1 401 Unauthorized
- < Date: Thu, 16 Dec 2021 10:49:03 GMT
- < Content-Type: text/html
- < Content-Length: 172
- < Connection: keep-alive
- < WWW-Authenticate: Basic realm="Authentication Required - foo"
- <
-
401 Authorization Required
-
nginx - * Connection #0 to host 192.168.31.31 left intact
- * Closing connection 0
我們可以看到出現了 401 認證失敗錯誤,然后帶上我們配置的用戶名和密碼進行認證:
- ? curl -v http://192.168.31.31 -H 'Host: bauth.qikqiak.com' -u 'foo:foo'
- * Trying 192.168.31.31...
- * TCP_NODELAY set
- * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0)
- * Server auth using Basic with user 'foo'
- > GET / HTTP/1.1
- > Host: bauth.qikqiak.com
- > Authorization: Basic Zm9vOmZvbw==
- > User-Agent: curl/7.64.1
- > Accept: */*
- >
- < HTTP/1.1 200 OK
- < Date: Thu, 16 Dec 2021 10:49:38 GMT
- < Content-Type: text/html
- < Content-Length: 615
- < Connection: keep-alive
- < Last-Modified: Tue, 02 Nov 2021 14:49:22 GMT
- < ETag: "61814ff2-267"
- < Accept-Ranges: bytes
- <
-
Welcome to nginx!
-
If you see this page, the nginx web server is successfully installed and
-
working. Further configuration is required.
-
For online documentation and support please refer to
- "http://nginx.org/">nginx.org.
- Commercial support is available at
-
"http://nginx.com/">nginx.com.
-
Thank you for using nginx.
- * Connection #0 to host 192.168.31.31 left intact
- * Closing connection 0
可以看到已經認證成功了。除了可以使用我們自己在本地集群創建的 Auth 信息之外,還可以使用外部的 Basic Auth 認證信息,比如我們使用 https://httpbin.org 的外部 Basic Auth 認證,創建如下所示的 Ingress 資源對象:
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- annotations:
- # 配置外部認證服務地址
- nginx.ingress.kubernetes.io/auth-url: https://httpbin.org/basic-auth/user/passwd
- name: external-auth
- namespace: default
- spec:
- ingressClassName: nginx
- rules:
- - host: external-bauth.qikqiak.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: my-nginx
- port:
- number: 80
上面的資源對象創建完成后,再進行簡單的測試:
- ? kubectl get ingress
- NAME CLASS HOSTS ADDRESS PORTS AGE
-
external-auth
external-bauth.qikqiak.com 80 72s - ? curl -k http://192.168.31.31 -v -H 'Host: external-bauth.qikqiak.com'
- * Trying 192.168.31.31...
- * TCP_NODELAY set
- * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0)
- > GET / HTTP/1.1
- > Host: external-bauth.qikqiak.com
- > User-Agent: curl/7.64.1
- > Accept: */*
- >
- < HTTP/1.1 401 Unauthorized
- < Date: Thu, 16 Dec 2021 10:57:25 GMT
- < Content-Type: text/html
- < Content-Length: 172
- < Connection: keep-alive
- < WWW-Authenticate: Basic realm="Fake Realm"
- <
-
401 Authorization Required
-
nginx - * Connection #0 to host 192.168.31.31 left intact
- * Closing connection 0
然后使用正確的用戶名和密碼測試:
- ? curl -k http://192.168.31.31 -v -H 'Host: external-bauth.qikqiak.com' -u 'user:passwd'
- * Trying 192.168.31.31...
- * TCP_NODELAY set
- * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0)
- * Server auth using Basic with user 'user'
- > GET / HTTP/1.1
- > Host: external-bauth.qikqiak.com
- > Authorization: Basic dXNlcjpwYXNzd2Q=
- > User-Agent: curl/7.64.1
- > Accept: */*
- >
- < HTTP/1.1 200 OK
- < Date: Thu, 16 Dec 2021 10:58:31 GMT
- < Content-Type: text/html
- < Content-Length: 615
- < Connection: keep-alive
- < Last-Modified: Tue, 02 Nov 2021 14:49:22 GMT
- < ETag: "61814ff2-267"
- < Accept-Ranges: bytes
- <
-
Welcome to nginx!
-
If you see this page, the nginx web server is successfully installed and
-
working. Further configuration is required.
-
For online documentation and support please refer to
- "http://nginx.org/">nginx.org.
- Commercial support is available at
-
"http://nginx.com/">nginx.com.
-
Thank you for using nginx.
- * Connection #0 to host 192.168.31.31 left intact
- * Closing connection 0
如果用戶名或者密碼錯誤則同樣會出現401的狀態碼:
- ? curl -k http://192.168.31.31 -v -H 'Host: external-bauth.qikqiak.com' -u 'user:passwd123'
- * Trying 192.168.31.31...
- * TCP_NODELAY set
- * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0)
- * Server auth using Basic with user 'user'
- > GET / HTTP/1.1
- > Host: external-bauth.qikqiak.com
- > Authorization: Basic dXNlcjpwYXNzd2QxMjM=
- > User-Agent: curl/7.64.1
- > Accept: */*
- >
- < HTTP/1.1 401 Unauthorized
- < Date: Thu, 16 Dec 2021 10:59:18 GMT
- < Content-Type: text/html
- < Content-Length: 172
- < Connection: keep-alive
- * Authentication problem. Ignoring this.
- < WWW-Authenticate: Basic realm="Fake Realm"
- <
-
401 Authorization Required
-
nginx - * Connection #0 to host 192.168.31.31 left intact
- * Closing connection 0
當然除了 Basic Auth 這一種簡單的認證方式之外,ingress-nginx 還支持一些其他高級的認證,比如我們可以使用 GitHub OAuth 來認證 Kubernetes 的 Dashboard。
URL Rewrite
ingress-nginx 很多高級的用法可以通過 Ingress 對象的 annotation 進行配置,比如常用的 URL Rewrite 功能。很多時候我們會將 ingress-nginx 當成網關使用,比如對訪問的服務加上 /app 這樣的前綴,在 nginx 的配置里面我們知道有一個 proxy_pass 指令可以實現:
- location /app/ {
- proxy_pass http://127.0.0.1/remote/;
- }
proxy_pass 后面加了 /remote 這個路徑,此時會將匹配到該規則路徑中的 /app 用 /remote 替換掉,相當于截掉路徑中的 /app。同樣的在 Kubernetes 中使用 ingress-nginx 又該如何來實現呢?我們可以使用 rewrite-target 的注解來實現這個需求,比如現在我們想要通過 rewrite.qikqiak.com/gateway/ 來訪問到 Nginx 服務,則我們需要對訪問的 URL 路徑做一個 Rewrite,在 PATH 中添加一個 gateway 的前綴,關于 Rewrite 的操作在 ingress-nginx 官方文檔中也給出對應的說明:
按照要求我們需要在 path 中匹配前綴 gateway,然后通過 rewrite-target 指定目標,Ingress 對象如下所示:
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: rewrite
- annotations:
- nginx.ingress.kubernetes.io/rewrite-target: /$2
- spec:
- ingressClassName: nginx
- rules:
- - host: rewrite.qikqiak.com
- http:
- paths:
- - path: /gateway(/|$)(.*)
- pathType: Prefix
- backend:
- service:
- name: my-nginx
- port:
- number: 80
更新后,我們可以預見到直接訪問域名肯定是不行了,因為我們沒有匹配 / 的 path 路徑:
- ? curl rewrite.qikqiak.com
- default backend - 404
但是我們帶上 gateway 的前綴再去訪問:
我們可以看到已經可以訪問到了,這是因為我們在 path 中通過正則表達式 /gateway(/|$)(.*) 將匹配的路徑設置成了 rewrite-target 的目標路徑了,所以我們訪問 rewite.qikqiak.com/gateway/ 的時候實際上相當于訪問的就是后端服務的 / 路徑。
要解決我們訪問主域名出現 404 的問題,我們可以給應用設置一個 app-root 的注解,這樣當我們訪問主域名的時候會自動跳轉到我們指定的 app-root 目錄下面,如下所示:
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: rewrite
- annotations:
- nginx.ingress.kubernetes.io/app-root: /gateway/
- nginx.ingress.kubernetes.io/rewrite-target: /$2
- spec:
- ingressClassName: nginx
- rules:
- - host: rewrite.qikqiak.com
- http:
- paths:
- - path: /gateway(/|$)(.*)
- pathType: Prefix
- backend:
- service:
- name: my-nginx
- port:
- number: 80
這個時候我們更新應用后訪問主域名 rewrite.qikqiak.com 就會自動跳轉到 rewrite.qikqiak.com/gateway/ 路徑下面去了。但是還有一個問題是我們的 path 路徑其實也匹配了 /app 這樣的路徑,可能我們更加希望我們的應用在最后添加一個 / 這樣的 slash,同樣我們可以通過 configuration-snippet 配置來完成,如下 Ingress 對象:
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: rewrite
- annotations:
- nginx.ingress.kubernetes.io/app-root: /gateway/
- nginx.ingress.kubernetes.io/rewrite-target: /$2
- nginx.ingress.kubernetes.io/configuration-snippet: |
- rewrite ^(/gateway)$ $1/ redirect;
- spec:
- ingressClassName: nginx
- rules:
- - host: rewrite.qikqiak.com
- http:
- paths:
- - path: /gateway(/|$)(.*)
- pathType: Prefix
- backend:
- service:
- name: my-nginx
- port:
- number: 80
更新后我們的應用就都會以 / 這樣的 slash 結尾了。這樣就完成了我們的需求,如果你原本對 nginx 的配置就非常熟悉的話應該可以很快就能理解這種配置方式了。
灰度發布
在日常工作中我們經常需要對服務進行版本更新升級,所以我們經常會使用到滾動升級、藍綠發布、灰度發布等不同的發布操作。而 ingress-nginx 支持通過 Annotations 配置來實現不同場景下的灰度發布和測試,可以滿足金絲雀發布、藍綠部署與 A/B 測試等業務場景。
ingress-nginx 的 Annotations 支持以下 4 種 Canary 規則:
- nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,適用于灰度發布以及 A/B 測試。當 Request Header 設置為 always 時,請求將會被一直發送到 Canary 版本;當 Request Header 設置為 never 時,請求不會被發送到 Canary 入口;對于任何其他 Header 值,將忽略 Header,并通過優先級將請求與其他金絲雀規則進行優先級的比較。
- nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 將請求路由到 Canary Ingress 中指定的服務。當 Request Header 設置為此值時,它將被路由到 Canary 入口。該規則允許用戶自定義 Request Header 的值,必須與上一個 annotation (canary-by-header) 一起使用。
- nginx.ingress.kubernetes.io/canary-weight:基于服務權重的流量切分,適用于藍綠部署,權重范圍 0 - 100 按百分比將請求路由到 Canary Ingress 中指定的服務。權重為 0 意味著該金絲雀規則不會向 Canary 入口的服務發送任何請求,權重為 100 意味著所有請求都將被發送到 Canary 入口。
- nginx.ingress.kubernetes.io/canary-by-cookie:基于 cookie 的流量切分,適用于灰度發布與 A/B 測試。用于通知 Ingress 將請求路由到 Canary Ingress 中指定的服務的cookie。當 cookie 值設置為 always 時,它將被路由到 Canary 入口;當 cookie 值設置為 never 時,請求不會被發送到 Canary 入口;對于任何其他值,將忽略 cookie 并將請求與其他金絲雀規則進行優先級的比較。
需要注意的是金絲雀規則按優先順序進行排序:canary-by-header - > canary-by-cookie - > canary-weight
總的來說可以把以上的四個 annotation 規則劃分為以下兩類:
基于權重的 Canary 規則
基于用戶請求的 Canary 規則
下面我們通過一個示例應用來對灰度發布功能進行說明。
第一步. 部署 Production 應用
首先創建一個 production 環境的應用資源清單:
- # production.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: production
- labels:
- app: production
- spec:
- selector:
- matchLabels:
- app: production
- template:
- metadata:
- labels:
- app: production
- spec:
- containers:
- - name: production
- image: cnych/echoserver
- ports:
- - containerPort: 8080
- env:
- - name: NODE_NAME
- valueFrom:
- fieldRef:
- fieldPath: spec.nodeName
- - name: POD_NAME
- valueFrom:
- fieldRef:
- fieldPath: metadata.name
- - name: POD_NAMESPACE
- valueFrom:
- fieldRef:
- fieldPath: metadata.namespace
- - name: POD_IP
- valueFrom:
- fieldRef:
- fieldPath: status.podIP
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: production
- labels:
- app: production
- spec:
- ports:
- - port: 80
- targetPort: 8080
- name: http
- selector:
- app: production
然后創建一個用于 production 環境訪問的 Ingress 資源對象:
- # production-ingress.yaml
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: production
- spec:
- ingressClassName: nginx
- rules:
- - host: echo.qikqiak.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: production
- port:
- number: 80
直接創建上面的幾個資源對象:
- ? kubectl apply -f production.yaml
- ? kubectl apply -f production-ingress.yaml
- ? kubectl get pods -l app=production
- NAME READY STATUS RESTARTS AGE
- production-856d5fb99-d6bds 1/1 Running 0 2m50s
- ? kubectl get ingress
- NAME CLASS HOSTS ADDRESS PORTS AGE
-
production
echo.qikqiak.com 10.151.30.11 80 90s
應用部署成功后,將域名 echo.qikqiak.com 映射到 master1 節點(ingress-nginx 所在的節點)的 IP即可正常訪問應用:
- ? curl http://echo.qikqiak.com
- Hostname: production-856d5fb99-d6bds
- Pod Information:
- node name: node1
- pod name: production-856d5fb99-d6bds
- pod namespace: default
- pod IP: 10.244.1.111
- Server values:
- server_version=nginx: 1.13.3 - lua: 10008
- Request Information:
- client_address=10.244.0.0
- method=GET
- real path=/
- query=
- request_version=1.1
- request_scheme=http
- request_uri=http://echo.qikqiak.com:8080/
- Request Headers:
- accept=*/*
- host=echo.qikqiak.com
- user-agent=curl/7.64.1
- x-forwarded-for=171.223.99.184
- x-forwarded-host=echo.qikqiak.com
- x-forwarded-port=80
- x-forwarded-proto=http
- x-real-ip=171.223.99.184
- x-request-id=e680453640169a7ea21afba8eba9e116
- x-scheme=http
- Request Body:
- -no body in request-
第二步. 創建 Canary 版本
參考將上述 Production 版本的 production.yaml 文件,再創建一個 Canary 版本的應用。
- # canary.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: canary
- labels:
- app: canary
- spec:
- selector:
- matchLabels:
- app: canary
- template:
- metadata:
- labels:
- app: canary
- spec:
- containers:
- - name: canary
- image: cnych/echoserver
- ports:
- - containerPort: 8080
- env:
- - name: NODE_NAME
- valueFrom:
- fieldRef:
- fieldPath: spec.nodeName
- - name: POD_NAME
- valueFrom:
- fieldRef:
- fieldPath: metadata.name
- - name: POD_NAMESPACE
- valueFrom:
- fieldRef:
- fieldPath: metadata.namespace
- - name: POD_IP
- valueFrom:
- fieldRef:
- fieldPath: status.podIP
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: canary
- labels:
- app: canary
- spec:
- ports:
- - port: 80
- targetPort: 8080
- name: http
- selector:
- app: canary
接下來就可以通過配置 Annotation 規則進行流量切分了。
第三步. Annotation 規則配置
1. 基于權重:基于權重的流量切分的典型應用場景就是藍綠部署,可通過將權重設置為 0 或 100 來實現。例如,可將 Green 版本設置為主要部分,并將 Blue 版本的入口配置為 Canary。最初,將權重設置為 0,因此不會將流量代理到 Blue 版本。一旦新版本測試和驗證都成功后,即可將 Blue 版本的權重設置為 100,即所有流量從 Green 版本轉向 Blue。
創建一個基于權重的 Canary 版本的應用路由 Ingress 對象。
- # canary-ingress.yaml
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: canary
- annotations:
- nginx.ingress.kubernetes.io/canary: "true" # 要開啟灰度發布機制,首先需要啟用 Canary
- nginx.ingress.kubernetes.io/canary-weight: "30" # 分配30%流量到當前Canary版本
- spec:
- ingressClassName: nginx
- rules:
- - host: echo.qikqiak.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: canary
- port:
- number: 80
直接創建上面的資源對象即可:
- ? kubectl apply -f canary.yaml
- ? kubectl apply -f canary-ingress.yaml
- ? kubectl get pods
- NAME READY STATUS RESTARTS AGE
- canary-66cb497b7f-48zx4 1/1 Running 0 7m48s
- production-856d5fb99-d6bds 1/1 Running 0 21m
- ......
- ? kubectl get svc
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
-
canary ClusterIP 10.106.91.106
80/TCP 8m23s -
production ClusterIP 10.105.182.15
80/TCP 22m - ......
- ? kubectl get ingress
- NAME CLASS HOSTS ADDRESS PORTS AGE
-
canary
echo.qikqiak.com 10.151.30.11 80 108s -
production
echo.qikqiak.com 10.151.30.11 80 22m
Canary 版本應用創建成功后,接下來我們在命令行終端中來不斷訪問這個應用,觀察 Hostname 變化:
- ? for i in $(seq 1 10); do curl -s echo.qikqiak.com | grep "Hostname"; done
- Hostname: production-856d5fb99-d6bds
- Hostname: canary-66cb497b7f-48zx4
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: production-856d5fb99-d6bds
由于我們給 Canary 版本應用分配了 30% 左右權重的流量,所以上面我們訪問10次有3次訪問到了 Canary 版本的應用,符合我們的預期。
2. 基于 Request Header: 基于 Request Header 進行流量切分的典型應用場景即灰度發布或 A/B 測試場景。
在上面的 Canary 版本的 Ingress 對象中新增一條 annotation 配置 nginx.ingress.kubernetes.io/canary-by-header: canary(這里的 value 可以是任意值),使當前的 Ingress 實現基于 Request Header 進行流量切分,由于 canary-by-header 的優先級大于 canary-weight,所以會忽略原有的 canary-weight 的規則。
- annotations:
- nginx.ingress.kubernetes.io/canary: "true" # 要開啟灰度發布機制,首先需要啟用 Canary
- nginx.ingress.kubernetes.io/canary-by-header: canary # 基于header的流量切分
- nginx.ingress.kubernetes.io/canary-weight: "30" # 會被忽略,因為配置了 canary-by-headerCanary版本
更新上面的 Ingress 資源對象后,我們在請求中加入不同的 Header 值,再次訪問應用的域名。
注意:當 Request Header 設置為 never 或 always 時,請求將不會或一直被發送到 Canary 版本,對于任何其他 Header 值,將忽略 Header,并通過優先級將請求與其他 Canary 規則進行優先級的比較。
- ? for i in $(seq 1 10); do curl -s -H "canary: never" echo.qikqiak.com | grep "Hostname"; done
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
這里我們在請求的時候設置了 canary: never 這個 Header 值,所以請求沒有發送到 Canary 應用中去。如果設置為其他值呢:
- ? for i in $(seq 1 10); do curl -s -H "canary: other-value" echo.qikqiak.com | grep "Hostname"; done
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: canary-66cb497b7f-48zx4
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: production-856d5fb99-d6bds
- Hostname: canary-66cb497b7f-48zx4
- Hostname: production-856d5fb99-d6bds
- Hostname: canary-66cb497b7f-48zx4
由于我們請求設置的 Header 值為 canary: other-value,所以 ingress-nginx 會通過優先級將請求與其他 Canary 規則進行優先級的比較,我們這里也就會進入 canary-weight: "30" 這個規則去。
這個時候我們可以在上一個 annotation (即 canary-by-header)的基礎上添加一條 nginx.ingress.kubernetes.io/canary-by-header-value: user-value 這樣的規則,就可以將請求路由到 Canary Ingress 中指定的服務了。
- annotations:
- nginx.ingress.kubernetes.io/canary: "true" # 要開啟灰度發布機制,首先需要啟用 Canary
- nginx.ingress.kubernetes.io/canary-by-header-value: user-value
- nginx.ingress.kubernetes.io/canary-by-header: canary # 基于header的流量切分
- nginx.ingress.kubernetes.io/canary-weight: "30" # 分配30%流量到當前Canary版本
同樣更新 Ingress 對象后,重新訪問應用,當 Request Header 滿足 canary: user-value時,所有請求就會被路由到 Canary 版本:
- ? for i in $(seq 1 10); do curl -s -H "canary: user-value" echo.qikqiak.com | grep "Hostname"; done
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
3. 基于 Cookie:與基于 Request Header 的 annotation 用法規則類似。例如在 A/B 測試場景下,需要讓地域為北京的用戶訪問 Canary 版本。那么當 cookie 的 annotation 設置為 nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_Beijing",此時后臺可對登錄的用戶請求進行檢查,如果該用戶訪問源來自北京則設置 cookie users_from_Beijing 的值為 always,這樣就可以確保北京的用戶僅訪問 Canary 版本。
同樣我們更新 Canary 版本的 Ingress 資源對象,采用基于 Cookie 來進行流量切分,
- annotations:
- nginx.ingress.kubernetes.io/canary: "true" # 要開啟灰度發布機制,首先需要啟用 Canary
- nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_Beijing" # 基于 cookie
- nginx.ingress.kubernetes.io/canary-weight: "30" # 會被忽略,因為配置了 canary-by-cookie
更新上面的 Ingress 資源對象后,我們在請求中設置一個 users_from_Beijing=always 的 Cookie 值,再次訪問應用的域名。
- ? for i in $(seq 1 10); do curl -s -b "users_from_Beijing=always" echo.qikqiak.com | grep "Hostname"; done
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
- Hostname: canary-66cb497b7f-48zx4
我們可以看到應用都被路由到了 Canary 版本的應用中去了,如果我們將這個 Cookie 值設置為 never,則不會路由到 Canary 應用中。
HTTPS
如果我們需要用 HTTPS 來訪問我們這個應用的話,就需要監聽 443 端口了,同樣用 HTTPS 訪問應用必然就需要證書,這里我們用 openssl 來創建一個自簽名的證書:
- ? openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=foo.bar.com"
然后通過 Secret 對象來引用證書文件:
- # 要注意證書文件名稱必須是 tls.crt 和 tls.key
- ? kubectl create secret tls foo-tls --cert=tls.crt --key=tls.key
- secret/who-tls created
這個時候我們就可以創建一個 HTTPS 訪問應用的:
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: ingress-with-auth
- annotations:
- # 認證類型
- nginx.ingress.kubernetes.io/auth-type: basic
- # 包含 user/password 定義的 secret 對象名
- nginx.ingress.kubernetes.io/auth-secret: basic-auth
- # 要顯示的帶有適當上下文的消息,說明需要身份驗證的原因
- nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'
- spec:
- ingressClassName: nginx
- tls: # 配置 tls 證書
- - hosts:
- - foo.bar.com
- secretName: foo-tls
- rules:
- - host: foo.bar.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: my-nginx
- port:
- number: 80
除了自簽名證書或者購買正規機構的 CA 證書之外,我們還可以通過一些工具來自動生成合法的證書,cert-manager 是一個云原生證書管理開源項目,可以用于在 Kubernetes 集群中提供 HTTPS 證書并自動續期,支持 Let's Encrypt/HashiCorp/Vault 這些免費證書的簽發。在 Kubernetes 中,可以通過 Kubernetes Ingress 和 Let's Encrypt 實現外部服務的自動化 HTTPS。
TCP與UDP
由于在 Ingress 資源對象中沒有直接對 TCP 或 UDP 服務的支持,要在 ingress-nginx 中提供支持,需要在控制器啟動參數中添加 --tcp-services-configmap 和 --udp-services-configmap 標志指向一個 ConfigMap,其中的 key 是要使用的外部端口,value 值是使用格式 ::[PROXY]:[PROXY] 暴露的服務,端口可以使用端口號或者端口名稱,最后兩個字段是可選的,用于配置 PROXY 代理。
比如現在我們要通過 ingress-nginx 來暴露一個 MongoDB 服務,首先創建如下的應用:
- # mongo.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: mongo
- labels:
- app: mongo
- spec:
- selector:
- matchLabels:
- app: mongo
- template:
- metadata:
- labels:
- app: mongo
- spec:
- volumes:
- - name: data
- emptyDir: {}
- containers:
- - name: mongo
- image: mongo:4.0
- ports:
- - containerPort: 27017
- volumeMounts:
- - name: data
- mountPath: /data/db
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: mongo
- spec:
- selector:
- app: mongo
- ports:
- - port: 27017
直接創建上面的資源對象:
- ? kubectl apply -f mongo.yaml
- ? kubectl get svc
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
-
mongo ClusterIP 10.98.117.228
27017/TCP 2m26s - ? kubectl get pods -l app=mongo
- NAME READY STATUS RESTARTS AGE
- mongo-84c587f547-gd7pv 1/1 Running 0 2m5s
現在我們要通過 ingress-nginx 來暴露上面的 MongoDB 服務,我們需要創建一個如下所示的 ConfigMap:
- apiVersion: v1
- kind: ConfigMap
- metadata:
- name: tcp-services
- namespace: ingress-nginx
- data:
- "27017": default/mongo:27017
然后在 ingress-nginx 的啟動參數中添加 --tcp-services-configmap=$(POD_NAMESPACE)/ingress-nginx-tcp 這樣的配置即可,由于我們這里使用的是 Helm Chart 進行安裝的,我們只需要去覆蓋 Values 值重新安裝即可,修改 ci/daemonset-prod.yaml 文件:
- # ci/daemonset-prod.yaml
- # ...... 其他部分省略,和之前的保持一致
- tcp: # 配置 tcp 服務
- 27017: "default/mongo:27017" # 使用 27017 端口去映射 mongo 服務
- # 9000: "default/test:8080" # 如果還需要暴露其他 TCP 服務,繼續添加即可
配置完成后重新更新當前的 ingress-nginx:
- ? helm upgrade --install ingress-nginx . -f ./ci/daemonset-prod.yaml --namespace ingress-nginx
重新部署完成后會自動生成一個名為 ingress-nginx-tcp 的 ConfigMap 對象,如下所示:
- ? kubectl get configmap -n ingress-nginx ingress-nginx-tcp -o yaml
- apiVersion: v1
- data:
- "27017": default/mongo:27017
- kind: ConfigMap
- metadata:
- ......
- name: ingress-nginx-tcp
- namespace: ingress-nginx
在 ingress-nginx 的啟動參數中也添加上 --tcp-services-configmap=$(POD_NAMESPACE)/ingress-nginx-tcp 這樣的配置:
- ? kubectl get pods -n ingress-nginx
- NAME READY STATUS RESTARTS AGE
- ingress-nginx-controller-gc582 1/1 Running 0 5m17s
- ? kubectl get pod ingress-nginx-controller-gc582 -n ingress-nginx -o yaml
- apiVersion: v1
- kind: Pod
- ......
- containers:
- - args:
- - /nginx-ingress-controller
- - --default-backend-service=$(POD_NAMESPACE)/ingress-nginx-defaultbackend
- - --election-id=ingress-controller-leader
- - --controller-class=k8s.io/ingress-nginx
- - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- - --tcp-services-configmap=$(POD_NAMESPACE)/ingress-nginx-tcp # tcp 配置參數
- - --validating-webhook=:8443
- - --validating-webhook-certificate=/usr/local/certificates/cert
- - --validating-webhook-key=/usr/local/certificates/key
- ......
- ports:
- ......
- - containerPort: 27017
- hostPort: 27017
- name: 27017-tcp
- protocol: TCP
- ......
現在我們就可以通過 ingress-nginx 暴露的 27017 端口去訪問 Mongo 服務了:
- ? mongo --host 192.168.31.31 --port 27017
- MongoDB shell version v4.0.3
- connecting to: mongodb://192.168.31.31:27017/
- Implicit session: session { "id" : UUID("10f462eb-32b8-443b-ad85-99820db1aaa0") }
- MongoDB server version: 4.0.27
- ......
- > show dbs
- admin 0.000GB
- config 0.000GB
- local 0.000GB
- >
同樣的我們也可以去查看最終生成的 nginx.conf 配置文件:
- ? kubectl exec -it ingress-nginx-controller-gc582 -n ingress-nginx -- cat /etc/nginx/nginx.conf
- ......
- stream {
- ......
- # TCP services
- server {
- preread_by_lua_block {
- ngx.var.proxy_upstream_name="tcp-default-mongo-27017";
- }
- listen 27017;
- listen [::]:27017;
- proxy_timeout 600s;
- proxy_next_upstream on;
- proxy_next_upstream_timeout 600s;
- proxy_next_upstream_tries 3;
- proxy_pass upstream_balancer;
- }
- # UDP services
- }
TCP 相關的配置位于 stream 配置塊下面。從 Nginx 1.9.13 版本開始提供 UDP 負載均衡,同樣我們也可以在 ingress-nginx 中來代理 UDP 服務,比如我們可以去暴露 kube-dns 的服務,同樣需要創建一個如下所示的 ConfigMap:
- apiVersion: v1
- kind: ConfigMap
- metadata:
- name: udp-services
- namespace: ingress-nginx
- data:
- 53: "kube-system/kube-dns:53"
然后需要在 ingress-nginx 參數中添加一個 - --udp-services-configmap=$(POD_NAMESPACE)/udp-services 這樣的配置,當然我們這里只需要去修改 Values 文件值即可,修改 ci/daemonset-prod.yaml 文件:
- # ci/daemonset-prod.yaml
- # ...... 其他部分省略,和之前的保持一致
- tcp: # 配置 tcp 服務
- 27017: "default/mongo:27017" # 使用 27017 端口去映射 mongo 服務
- # 9000: "default/test:8080" # 如果還需要暴露其他 TCP 服務,繼續添加即可
- udp: # 配置 udp 服務
- 53: "kube-system/kube-dns:53"
然后重新更新即可。
全局配置
除了可以通過 annotations 對指定的 Ingress 進行定制之外,我們還可以配置 ingress-nginx 的全局配置,在控制器啟動參數中通過標志 --configmap 指定了一個全局的 ConfigMap 對象,我們可以將全局的一些配置直接定義在該對象中即可:
- containers:
- - args:
- - /nginx-ingress-controller
- - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- ......
比如這里我們用于全局配置的 ConfigMap 名為 ingress-nginx-controller:
- ? kubectl get configmap -n ingress-nginx
- NAME DATA AGE
- ingress-nginx-controller 1 5d2h
比如我們可以添加如下所示的一些常用配置:
- ? kubectl edit configmap ingress-nginx-controller -n ingress-nginx
- apiVersion: v1
- data:
- allow-snippet-annotations: "true"
- client-header-buffer-size: 32k # 注意不是下劃線
- client-max-body-size: 5m
- use-gzip: "true"
- gzip-level: "7"
- large-client-header-buffers: 4 32k
- proxy-connect-timeout: 11s
- proxy-read-timeout: 12s
- keep-alive: "75" # 啟用keep-alive,連接復用,提高QPS
- keep-alive-requests: "100"
- upstream-keepalive-connections: "10000"
- upstream-keepalive-requests: "100"
- upstream-keepalive-timeout: "60"
- disable-ipv6: "true"
- disable-ipv6-dns: "true"
- max-worker-connections: "65535"
- max-worker-open-files: "10240"
- kind: ConfigMap
- ......
修改完成后 Nginx 配置會自動重載生效,我們可以查看 nginx.conf 配置文件進行驗證:
- ? kubectl exec -it ingress-nginx-controller-gc582 -n ingress-nginx -- cat /etc/nginx/nginx.conf |grep large_client_header_buffers
- large_client_header_buffers 4 32k;
由于我們這里是 Helm Chart 安裝的,為了保證重新部署后配置還在,我們同樣需要通過 Values 進行全局配置:
- # ci/daemonset-prod.yaml
- controller:
- config:
- allow-snippet-annotations: "true"
- client-header-buffer-size: 32k # 注意不是下劃線
- client-max-body-size: 5m
- use-gzip: "true"
- gzip-level: "7"
- large-client-header-buffers: 4 32k
- proxy-connect-timeout: 11s
- proxy-read-timeout: 12s
- keep-alive: "75" # 啟用keep-alive,連接復用,提高QPS
- keep-alive-requests: "100"
- upstream-keepalive-connections: "10000"
- upstream-keepalive-requests: "100"
- upstream-keepalive-timeout: "60"
- disable-ipv6: "true"
- disable-ipv6-dns: "true"
- max-worker-connections: "65535"
- max-worker-open-files: "10240"
- # 其他省略
此外往往我們還需要對 ingress-nginx 部署的節點進行性能優化,修改一些內核參數,使得適配 Nginx 的使用場景,一般我們是直接去修改節點上的內核參數,為了能夠統一管理,我們可以使用 initContainers 來進行配置:
- initContainers:
- - command:
- - /bin/sh
- - -c
- - |
- mount -o remount rw /proc/sys
- sysctl -w net.core.somaxconn=65535 # 具體的配置視具體情況而定
- sysctl -w net.ipv4.tcp_tw_reuse=1
- sysctl -w net.ipv4.ip_local_port_range="1024 65535"
- sysctl -w fs.file-max=1048576
- sysctl -w fs.inotify.max_user_instances=16384
- sysctl -w fs.inotify.max_user_watches=524288
- sysctl -w fs.inotify.max_queued_events=16384
- image: busybox
- imagePullPolicy: IfNotPresent
- name: init-sysctl
- securityContext:
- capabilities:
- add:
- - SYS_ADMIN
- drop:
- - ALL
- ......
由于我們這里使用的是 Helm Chart 安裝的 ingress-nginx,同樣只需要去配置 Values 值即可,模板中提供了對 initContainers 的支持,配置如下所示:
- controller:
- # 其他省略,配置 initContainers
- extraInitContainers:
- - name: init-sysctl
- image: busybox
- securityContext:
- capabilities:
- add:
- - SYS_ADMIN
- drop:
- - ALL
- command:
- - /bin/sh
- - -c
- - |
- mount -o remount rw /proc/sys
- sysctl -w net.core.somaxconn=65535 # socket監聽的backlog上限
- sysctl -w net.ipv4.tcp_tw_reuse=1 # 開啟重用,允許將 TIME-WAIT sockets 重新用于新的TCP連接
- sysctl -w net.ipv4.ip_local_port_range="1024 65535"
- sysctl -w fs.file-max=1048576
- sysctl -w fs.inotify.max_user_instances=16384
- sysctl -w fs.inotify.max_user_watches=524288
- sysctl -w fs.inotify.max_queued_events=16384
同樣重新部署即可:
- ? helm upgrade --install ingress-nginx . -f ./ci/daemonset-prod.yaml --namespace ingress-nginx
部署完成后通過 initContainers 就可以修改節點內核參數了,生產環境建議對節點內核參數進行相應的優化。
原文鏈接:https://mp.weixin.qq.com/s/XoUaW-vBtQFxwNM3UdD8QA