kubernetes 1.22安裝使用 ingress-nginx
運(yùn)行原理
ingress-nginx 控制器主要是用來(lái)組裝一個(gè) nginx.conf 的配置文件,當(dāng)配置文件發(fā)生任何變動(dòng)的時(shí)候就需要重新加載 Nginx 來(lái)生效,但是并不會(huì)只在影響 upstream 配置的變更后就重新加載 Nginx,控制器內(nèi)部會(huì)使用一個(gè) lua-nginx-module 來(lái)實(shí)現(xiàn)該功能。
我們知道 Kubernetes 控制器使用控制循環(huán)模式來(lái)檢查控制器中所需的狀態(tài)是否已更新或是否需要變更,所以 ingress-nginx 需要使用集群中的不同對(duì)象來(lái)構(gòu)建模型,比如 Ingress、Service、Endpoints、Secret、ConfigMap 等可以生成反映集群狀態(tài)的配置文件的對(duì)象,控制器需要一直 Watch 這些資源對(duì)象的變化,但是并沒(méi)有辦法知道特定的更改是否會(huì)影響到最終生成的 nginx.conf 配置文件,所以一旦 Watch 到了任何變化控制器都必須根據(jù)集群的狀態(tài)重建一個(gè)新的模型,并將其與當(dāng)前的模型進(jìn)行比較,如果模型相同則就可以避免生成新的 Nginx 配置并觸發(fā)重新加載,否則還需要檢查模型的差異是否只和端點(diǎn)有關(guān),如果是這樣,則然后需要使用 HTTP POST 請(qǐng)求將新的端點(diǎn)列表發(fā)送到在 Nginx 內(nèi)運(yùn)行的 Lua 處理程序,并再次避免生成新的 Nginx 配置并觸發(fā)重新加載,如果運(yùn)行和新模型之間的差異不僅僅是端點(diǎn),那么就會(huì)基于新模型創(chuàng)建一個(gè)新的 Nginx 配置了,這樣構(gòu)建模型最大的一個(gè)好處就是在狀態(tài)沒(méi)有變化時(shí)避免不必要的重新加載,可以節(jié)省大量 Nginx 重新加載。
下面簡(jiǎn)單描述了需要重新加載的一些場(chǎng)景:
創(chuàng)建了新的 Ingress 資源 TLS 添加到現(xiàn)有 Ingress 從 Ingress 中添加或刪除 path 路徑 Ingress、Service、Secret 被刪除了 Ingress 的一些缺失引用對(duì)象變可用了,例如 Service 或 Secret 更新了一個(gè) Secret
對(duì)于集群規(guī)模較大的場(chǎng)景下頻繁的對(duì) Nginx 進(jìn)行重新加載顯然會(huì)造成大量的性能消耗,所以要盡可能減少出現(xiàn)重新加載的場(chǎng)景。
安裝
由于 ingress-nginx 所在的節(jié)點(diǎn)需要能夠訪問(wèn)外網(wǎng)(不是強(qiáng)制的),這樣域名可以解析到這些節(jié)點(diǎn)上直接使用,所以需要讓 ingress-nginx 綁定節(jié)點(diǎn)的 80 和 443 端口,所以可以使用 hostPort 來(lái)進(jìn)行訪問(wèn),當(dāng)然對(duì)于線上環(huán)境來(lái)說(shuō)為了保證高可用,一般是需要運(yùn)行多個(gè) ·ingress-nginx 實(shí)例的,然后可以用一個(gè) nginx/haproxy 作為入口,通過(guò) keepalived 來(lái)訪問(wèn)邊緣節(jié)點(diǎn)的 vip 地址。
!!! info "邊緣節(jié)點(diǎn)" 所謂的邊緣節(jié)點(diǎn)即集群內(nèi)部用來(lái)向集群外暴露服務(wù)能力的節(jié)點(diǎn),集群外部的服務(wù)通過(guò)該節(jié)點(diǎn)來(lái)調(diào)用集群內(nèi)部的服務(wù),邊緣節(jié)點(diǎn)是集群內(nèi)外交流的一個(gè) Endpoint。
這里我們使用 Helm Chart(后面會(huì)詳細(xì)講解)的方式來(lái)進(jìn)行安裝:
#?如果你不喜歡使用?helm?chart?進(jìn)行安裝也可以使用下面的命令一鍵安裝
#?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 包下載下來(lái)后解壓就可以看到里面包含的模板文件,其中的 ci 目錄中就包含了各種場(chǎng)景下面安裝的 Values 配置文件,values.yaml 文件中包含的是所有可配置的默認(rèn)值,我們可以對(duì)這些默認(rèn)值進(jìn)行覆蓋,我們這里測(cè)試環(huán)境就將 master1 節(jié)點(diǎn)看成邊緣節(jié)點(diǎn),所以我們就直接將 ingress-nginx 固定到 master1 節(jié)點(diǎn)上,采用 hostNetwork 模式(生產(chǎn)環(huán)境可以使用 LB + DaemonSet hostNetwork 模式),為了避免創(chuàng)建的錯(cuò)誤 Ingress 等資源對(duì)象影響控制器重新加載,所以我們也強(qiáng)烈建議大家開啟準(zhǔn)入控制器,ingess-nginx 中會(huì)提供一個(gè)用于校驗(yàn)資源對(duì)象的 Admission Webhook,我們可以通過(guò) Values 文件進(jìn)行開啟。然后新建一個(gè)名為 ci/daemonset-prod.yaml 的 Values 文件,用來(lái)覆蓋 ingress-nginx 默認(rèn)的 Values 值。

對(duì)應(yīng)的 Values 配置文件如下所示:
#?ci/daemonset-prod.yaml
controller:
??name:?controller
??image:
????repository:?cnych/ingress-nginx
????tag:?"v1.1.0"
????digest:
??dnsPolicy:?ClusterFirstWithHostNet
??hostNetwork:?true
??publishService:??#?hostNetwork?模式下設(shè)置為false,通過(guò)節(jié)點(diǎn)IP地址上報(bào)ingress?status數(shù)據(jù)
????enabled:?false
??#?是否需要處理不帶?ingressClass?注解或者?ingressClassName?屬性的?Ingress?對(duì)象
??#?設(shè)置為?true?會(huì)在控制器啟動(dòng)參數(shù)中新增一個(gè)?--watch-ingress-without-class?標(biāo)注
??watchIngressWithoutClass:?false
??kind:?DaemonSet
??tolerations:???#?kubeadm?安裝的集群默認(rèn)情況下master是有污點(diǎn),需要容忍這個(gè)污點(diǎn)才可以部署
??-?key:?"node-role.kubernetes.io/master"
????operator:?"Equal"
????effect:?"NoSchedule"
??nodeSelector:???#?固定到master1節(jié)點(diǎn)
????kubernetes.io/hostname:?master1
??service:??#?HostNetwork?模式不需要?jiǎng)?chuàng)建service
????enabled:?false
??admissionWebhooks:?#?強(qiáng)烈建議開啟?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:??#?配置默認(rèn)后端
??enabled:?true
??name:?defaultbackend
??image:
????repository:?cnych/ingress-nginx-defaultbackend
????tag:?"1.5"
然后使用如下命令安裝 ingress-nginx 應(yīng)用到 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:?
??type:?kubernetes.io/tls
部署完成后查看 Pod 的運(yùn)行狀態(tài):
??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"
當(dāng)看到上面的信息證明 ingress-nginx 部署成功了,這里我們安裝的是最新版本的控制器,安裝完成后會(huì)自動(dòng)創(chuàng)建一個(gè) 名為 nginx 的 IngressClass 對(duì)象:
??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
不過(guò)這里我們只提供了一個(gè) controller 屬性,如果還需要配置一些額外的參數(shù),則可以在安裝的 values 文件中進(jìn)行配置。
第一個(gè)示例
安裝成功后,現(xiàn)在我們來(lái)為一個(gè) nginx 應(yīng)用創(chuàng)建一個(gè) 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(關(guān)聯(lián)的?ingress-nginx?控制器)
??rules:
??-?host:?ngdemo.qikqiak.com??#?將域名映射到?my-nginx?服務(wù)
????http:
??????paths:
??????-?path:?/
????????pathType:?Prefix
????????backend:
??????????service:??#?將所有請(qǐng)求發(fā)送到?my-nginx?服務(wù)的?80?端口
????????????name:?my-nginx
????????????port:
??????????????number:?80
#?不過(guò)需要注意大部分Ingress控制器都不是直接轉(zhuǎn)發(fā)到Service
#?而是只是通過(guò)Service來(lái)獲取后端的Endpoints列表,直接轉(zhuǎn)發(fā)到Pod,這樣可以減少網(wǎng)絡(luò)跳轉(zhuǎn),提高性能
直接創(chuàng)建上面的資源對(duì)象:
??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 資源對(duì)象中我們使用配置 ingressClassName: nginx 指定讓我們安裝的 ingress-nginx 這個(gè)控制器來(lái)處理我們的 Ingress 資源,配置的匹配路徑類型為前綴的方式去匹配 /,將來(lái)自域名 ngdemo.qikqiak.com 的所有請(qǐng)求轉(zhuǎn)發(fā)到 my-nginx 服務(wù)的后端 Endpoints 中去。
上面資源創(chuàng)建成功后,然后我們可以將域名 ngdemo.qikqiak.com 解析到 ingress-nginx 所在的邊緣節(jié)點(diǎn)中的任意一個(gè),當(dāng)然也可以在本地 /etc/hosts 中添加對(duì)應(yīng)的映射也可以,然后就可以通過(guò)域名進(jìn)行訪問(wèn)了。

下圖顯示了客戶端是如何通過(guò) Ingress 控制器連接到其中一個(gè) Pod 的流程,客戶端首先對(duì) ngdemo.qikqiak.com 執(zhí)行 DNS 解析,得到 Ingress 控制器所在節(jié)點(diǎn)的 IP,然后客戶端向 Ingress 控制器發(fā)送 HTTP 請(qǐng)求,然后根據(jù) Ingress 對(duì)象里面的描述匹配域名,找到對(duì)應(yīng)的 Service 對(duì)象,并獲取關(guān)聯(lián)的 Endpoints 列表,將客戶端的請(qǐng)求轉(zhuǎn)發(fā)給其中一個(gè) Pod。

前面我們也提到了 ingress-nginx 控制器的核心原理就是將我們的 Ingress 這些資源對(duì)象映射翻譯成 Nginx 配置文件 nginx.conf,我們可以通過(guò)查看控制器中的配置文件來(lái)驗(yàn)證這點(diǎn):
??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 資源對(duì)象的相關(guān)配置信息,不過(guò)需要注意的是現(xiàn)在并不會(huì)為每個(gè) backend 后端都創(chuàng)建一個(gè) upstream 配置塊,現(xiàn)在是使用 Lua 程序進(jìn)行動(dòng)態(tài)處理的,所以我們沒(méi)有直接看到后端的 Endpoints 相關(guān)配置數(shù)據(jù)。
Nginx 配置
如果我們還想進(jìn)行一些自定義配置,則有幾種方式可以實(shí)現(xiàn):使用 Configmap 在 Nginx 中設(shè)置全局配置、通過(guò) Ingress 的 Annotations 設(shè)置特定 Ingress 的規(guī)則、自定義模板。接下來(lái)我們重點(diǎn)給大家介紹使用注解來(lái)對(duì) Ingress 對(duì)象進(jìn)行自定義。
Basic Auth
我們可以在 Ingress 對(duì)象上配置一些基本的 Auth 認(rèn)證,比如 Basic Auth,可以用 htpasswd 生成一個(gè)密碼文件來(lái)驗(yàn)證身份驗(yàn)證。
??htpasswd?-c?auth?foo
New?password:
Re-type?new?password:
Adding?password?for?user?foo
然后根據(jù)上面的 auth 文件創(chuàng)建一個(gè) secret 對(duì)象:
??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
然后對(duì)上面的 my-nginx 應(yīng)用創(chuàng)建一個(gè)具有 Basic Auth 的 Ingress 對(duì)象:
apiVersion:?networking.k8s.io/v1
kind:?Ingress
metadata:
??name:?ingress-with-auth
??namespace:?default
??annotations:
????nginx.ingress.kubernetes.io/auth-type:?basic??#?認(rèn)證類型
????nginx.ingress.kubernetes.io/auth-secret:?basic-auth??#?包含?user/password?定義的?secret?對(duì)象名
????nginx.ingress.kubernetes.io/auth-realm:?'Authentication?Required?-?foo'??#?要顯示的帶有適當(dāng)上下文的消息,說(shuō)明需要身份驗(yàn)證的原因
spec:
??ingressClassName:?nginx??#?使用?nginx?的?IngressClass(關(guān)聯(lián)的?ingress-nginx?控制器)
??rules:
??-?host:?bauth.qikqiak.com??#?將域名映射到?my-nginx?服務(wù)
????http:
??????paths:
??????-?path:?/
????????pathType:?Prefix
????????backend:
??????????service:??#?將所有請(qǐng)求發(fā)送到?my-nginx?服務(wù)的?80?端口
????????????name:?my-nginx
????????????port:
??????????????number:?80
直接創(chuàng)建上面的資源對(duì)象,然后通過(guò)下面的命令或者在瀏覽器中直接打開配置的域名:
??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:?*/*
>
<
401?Authorization?Required
401?Authorization?Required
nginx
*?Connection?#0?to?host?192.168.31.31?left?intact
*?Closing?connection?0
我們可以看到出現(xiàn)了 401 認(rèn)證失敗錯(cuò)誤,然后帶上我們配置的用戶名和密碼進(jìn)行認(rèn)證:
??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:?*/*
>
<
Welcome?to?nginx!
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
nginx.org.
Commercial?support?is?available?at
nginx.com.
Thank?you?for?using?nginx.
*?Connection?#0?to?host?192.168.31.31?left?intact
*?Closing?connection?0
可以看到已經(jīng)認(rèn)證成功了。除了可以使用我們自己在本地集群創(chuàng)建的 Auth 信息之外,還可以使用外部的 Basic Auth 認(rèn)證信息,比如我們使用 https://httpbin.org 的外部 Basic Auth 認(rèn)證,創(chuàng)建如下所示的 Ingress 資源對(duì)象:
apiVersion:?networking.k8s.io/v1
kind:?Ingress
metadata:
??annotations:
????#?配置外部認(rèn)證服務(wù)地址
????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
上面的資源對(duì)象創(chuàng)建完成后,再進(jìn)行簡(jiǎn)單的測(cè)試:
??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:?*/*
>
<
401?Authorization?Required
401?Authorization?Required
nginx
*?Connection?#0?to?host?192.168.31.31?left?intact
*?Closing?connection?0
然后使用正確的用戶名和密碼測(cè)試:
??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:?*/*
>
<
Welcome?to?nginx!
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
nginx.org.
Commercial?support?is?available?at
nginx.com.
Thank?you?for?using?nginx.
*?Connection?#0?to?host?192.168.31.31?left?intact
*?Closing?connection?0
如果用戶名或者密碼錯(cuò)誤則同樣會(huì)出現(xiàn)401的狀態(tài)碼:
??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:?*/*
>
*?Authentication?problem.?Ignoring?this.
<
401?Authorization?Required
401?Authorization?Required
nginx
*?Connection?#0?to?host?192.168.31.31?left?intact
*?Closing?connection?0
當(dāng)然除了 Basic Auth 這一種簡(jiǎn)單的認(rèn)證方式之外,ingress-nginx 還支持一些其他高級(jí)的認(rèn)證,比如我們可以使用 GitHub OAuth 來(lái)認(rèn)證 Kubernetes 的 Dashboard。
URL Rewrite
ingress-nginx 很多高級(jí)的用法可以通過(guò) Ingress 對(duì)象的 annotation 進(jìn)行配置,比如常用的 URL Rewrite 功能。很多時(shí)候我們會(huì)將 ingress-nginx 當(dāng)成網(wǎng)關(guān)使用,比如對(duì)訪問(wèn)的服務(wù)加上 /app 這樣的前綴,在 nginx 的配置里面我們知道有一個(gè) proxy_pass 指令可以實(shí)現(xiàn):
location?/app/?{
??proxy_pass?http://127.0.0.1/remote/;
}
proxy_pass 后面加了 /remote 這個(gè)路徑,此時(shí)會(huì)將匹配到該規(guī)則路徑中的 /app 用 /remote 替換掉,相當(dāng)于截掉路徑中的 /app。同樣的在 Kubernetes 中使用 ingress-nginx 又該如何來(lái)實(shí)現(xiàn)呢?我們可以使用 rewrite-target 的注解來(lái)實(shí)現(xiàn)這個(gè)需求,比如現(xiàn)在我們想要通過(guò) rewrite.qikqiak.com/gateway/ 來(lái)訪問(wèn)到 Nginx 服務(wù),則我們需要對(duì)訪問(wèn)的 URL 路徑做一個(gè) Rewrite,在 PATH 中添加一個(gè) gateway 的前綴,關(guān)于 Rewrite 的操作在 ingress-nginx 官方文檔中也給出對(duì)應(yīng)的說(shuō)明:

按照要求我們需要在 path 中匹配前綴 gateway,然后通過(guò) rewrite-target 指定目標(biāo),Ingress 對(duì)象如下所示:
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
更新后,我們可以預(yù)見(jiàn)到直接訪問(wèn)域名肯定是不行了,因?yàn)槲覀儧](méi)有匹配 / 的 path 路徑:
??curl?rewrite.qikqiak.com
default?backend?-?404
但是我們帶上 gateway 的前綴再去訪問(wèn):

我們可以看到已經(jīng)可以訪問(wèn)到了,這是因?yàn)槲覀冊(cè)?path 中通過(guò)正則表達(dá)式 /gateway(/|$)(.*) 將匹配的路徑設(shè)置成了 rewrite-target 的目標(biāo)路徑了,所以我們?cè)L問(wèn) rewite.qikqiak.com/gateway/ 的時(shí)候?qū)嶋H上相當(dāng)于訪問(wèn)的就是后端服務(wù)的 / 路徑。
要解決我們?cè)L問(wèn)主域名出現(xiàn) 404 的問(wèn)題,我們可以給應(yīng)用設(shè)置一個(gè) app-root 的注解,這樣當(dāng)我們?cè)L問(wèn)主域名的時(shí)候會(huì)自動(dòng)跳轉(zhuǎn)到我們指定的 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
這個(gè)時(shí)候我們更新應(yīng)用后訪問(wèn)主域名 rewrite.qikqiak.com 就會(huì)自動(dòng)跳轉(zhuǎn)到 rewrite.qikqiak.com/gateway/ 路徑下面去了。但是還有一個(gè)問(wèn)題是我們的 path 路徑其實(shí)也匹配了 /app 這樣的路徑,可能我們更加希望我們的應(yīng)用在最后添加一個(gè) / 這樣的 slash,同樣我們可以通過(guò) configuration-snippet 配置來(lái)完成,如下 Ingress 對(duì)象:
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
更新后我們的應(yīng)用就都會(huì)以 / 這樣的 slash 結(jié)尾了。這樣就完成了我們的需求,如果你原本對(duì) nginx 的配置就非常熟悉的話應(yīng)該可以很快就能理解這種配置方式了。
灰度發(fā)布
在日常工作中我們經(jīng)常需要對(duì)服務(wù)進(jìn)行版本更新升級(jí),所以我們經(jīng)常會(huì)使用到滾動(dòng)升級(jí)、藍(lán)綠發(fā)布、灰度發(fā)布等不同的發(fā)布操作。而 ingress-nginx 支持通過(guò) Annotations 配置來(lái)實(shí)現(xiàn)不同場(chǎng)景下的灰度發(fā)布和測(cè)試,可以滿足金絲雀發(fā)布、藍(lán)綠部署與 A/B 測(cè)試等業(yè)務(wù)場(chǎng)景。
ingress-nginx 的 Annotations 支持以下 4 種 Canary 規(guī)則:
nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,適用于灰度發(fā)布以及 A/B 測(cè)試。當(dāng) Request Header 設(shè)置為 always 時(shí),請(qǐng)求將會(huì)被一直發(fā)送到 Canary 版本;當(dāng) Request Header 設(shè)置為 never 時(shí),請(qǐng)求不會(huì)被發(fā)送到 Canary 入口;對(duì)于任何其他 Header 值,將忽略 Header,并通過(guò)優(yōu)先級(jí)將請(qǐng)求與其他金絲雀規(guī)則進(jìn)行優(yōu)先級(jí)的比較。nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)。當(dāng) Request Header 設(shè)置為此值時(shí),它將被路由到 Canary 入口。該規(guī)則允許用戶自定義 Request Header 的值,必須與上一個(gè) annotation (canary-by-header) 一起使用。nginx.ingress.kubernetes.io/canary-weight:基于服務(wù)權(quán)重的流量切分,適用于藍(lán)綠部署,權(quán)重范圍 0 - 100 按百分比將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)。權(quán)重為 0 意味著該金絲雀規(guī)則不會(huì)向 Canary 入口的服務(wù)發(fā)送任何請(qǐng)求,權(quán)重為 100 意味著所有請(qǐng)求都將被發(fā)送到 Canary 入口。nginx.ingress.kubernetes.io/canary-by-cookie:基于 cookie 的流量切分,適用于灰度發(fā)布與 A/B 測(cè)試。用于通知 Ingress 將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)的cookie。當(dāng) cookie 值設(shè)置為 always 時(shí),它將被路由到 Canary 入口;當(dāng) cookie 值設(shè)置為 never 時(shí),請(qǐng)求不會(huì)被發(fā)送到 Canary 入口;對(duì)于任何其他值,將忽略 cookie 并將請(qǐng)求與其他金絲雀規(guī)則進(jìn)行優(yōu)先級(jí)的比較。
需要注意的是金絲雀規(guī)則按優(yōu)先順序進(jìn)行排序:
canary-by-header - > canary-by-cookie - > canary-weight
總的來(lái)說(shuō)可以把以上的四個(gè) annotation 規(guī)則劃分為以下兩類:
基于權(quán)重的 Canary 規(guī)則

基于用戶請(qǐng)求的 Canary 規(guī)則

下面我們通過(guò)一個(gè)示例應(yīng)用來(lái)對(duì)灰度發(fā)布功能進(jìn)行說(shuō)明。
第一步. 部署 Production 應(yīng)用
首先創(chuàng)建一個(gè) production 環(huán)境的應(yīng)用資源清單:
#?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
然后創(chuàng)建一個(gè)用于 production 環(huán)境訪問(wèn)的 Ingress 資源對(duì)象:
#?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
直接創(chuàng)建上面的幾個(gè)資源對(duì)象:
??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
應(yīng)用部署成功后,將域名 echo.qikqiak.com 映射到 master1 節(jié)點(diǎn)(ingress-nginx 所在的節(jié)點(diǎn))的 IP即可正常訪問(wèn)應(yīng)用:
??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-
第二步. 創(chuàng)建 Canary 版本參考將上述 Production 版本的 production.yaml 文件,再創(chuàng)建一個(gè) Canary 版本的應(yīng)用。
#?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
接下來(lái)就可以通過(guò)配置 Annotation 規(guī)則進(jìn)行流量切分了。
第三步. Annotation 規(guī)則配置
1. 基于權(quán)重:基于權(quán)重的流量切分的典型應(yīng)用場(chǎng)景就是藍(lán)綠部署,可通過(guò)將權(quán)重設(shè)置為 0 或 100 來(lái)實(shí)現(xiàn)。例如,可將 Green 版本設(shè)置為主要部分,并將 Blue 版本的入口配置為 Canary。最初,將權(quán)重設(shè)置為 0,因此不會(huì)將流量代理到 Blue 版本。一旦新版本測(cè)試和驗(yàn)證都成功后,即可將 Blue 版本的權(quán)重設(shè)置為 100,即所有流量從 Green 版本轉(zhuǎn)向 Blue。
創(chuàng)建一個(gè)基于權(quán)重的 Canary 版本的應(yīng)用路由 Ingress 對(duì)象。
#?canary-ingress.yaml
apiVersion:?networking.k8s.io/v1
kind:?Ingress
metadata:
??name:?canary
??annotations:
????nginx.ingress.kubernetes.io/canary:?"true"???#?要開啟灰度發(fā)布機(jī)制,首先需要啟用?Canary
????nginx.ingress.kubernetes.io/canary-weight:?"30"??#?分配30%流量到當(dāng)前Canary版本
spec:
??ingressClassName:?nginx
??rules:
??-?host:?echo.qikqiak.com
????http:
??????paths:
??????-?path:?/
????????pathType:?Prefix
????????backend:
??????????service:
????????????name:?canary
????????????port:
??????????????number:?80
直接創(chuàng)建上面的資源對(duì)象即可:
??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 版本應(yīng)用創(chuàng)建成功后,接下來(lái)我們?cè)诿钚薪K端中來(lái)不斷訪問(wèn)這個(gè)應(yīng)用,觀察 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 版本應(yīng)用分配了 30% 左右權(quán)重的流量,所以上面我們?cè)L問(wèn)10次有3次訪問(wèn)到了 Canary 版本的應(yīng)用,符合我們的預(yù)期。
2. 基于 Request Header: 基于 Request Header 進(jìn)行流量切分的典型應(yīng)用場(chǎng)景即灰度發(fā)布或 A/B 測(cè)試場(chǎng)景。
在上面的 Canary 版本的 Ingress 對(duì)象中新增一條 annotation 配置 nginx.ingress.kubernetes.io/canary-by-header: canary(這里的 value 可以是任意值),使當(dāng)前的 Ingress 實(shí)現(xiàn)基于 Request Header 進(jìn)行流量切分,由于 canary-by-header 的優(yōu)先級(jí)大于 canary-weight,所以會(huì)忽略原有的 canary-weight 的規(guī)則。
annotations:
??nginx.ingress.kubernetes.io/canary:?"true"???#?要開啟灰度發(fā)布機(jī)制,首先需要啟用?Canary
??nginx.ingress.kubernetes.io/canary-by-header:?canary??#?基于header的流量切分
??nginx.ingress.kubernetes.io/canary-weight:?"30"??#?會(huì)被忽略,因?yàn)榕渲昧?canary-by-headerCanary版本
更新上面的 Ingress 資源對(duì)象后,我們?cè)谡?qǐng)求中加入不同的 Header 值,再次訪問(wèn)應(yīng)用的域名。
注意:當(dāng) Request Header 設(shè)置為 never 或 always 時(shí),請(qǐng)求將不會(huì)或一直被發(fā)送到 Canary 版本,對(duì)于任何其他 Header 值,將忽略 Header,并通過(guò)優(yōu)先級(jí)將請(qǐng)求與其他 Canary 規(guī)則進(jìn)行優(yōu)先級(jí)的比較。
??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
這里我們?cè)谡?qǐng)求的時(shí)候設(shè)置了 canary: never 這個(gè) Header 值,所以請(qǐng)求沒(méi)有發(fā)送到 Canary 應(yīng)用中去。如果設(shè)置為其他值呢:
??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
由于我們請(qǐng)求設(shè)置的 Header 值為 canary: other-value,所以 ingress-nginx 會(huì)通過(guò)優(yōu)先級(jí)將請(qǐng)求與其他 Canary 規(guī)則進(jìn)行優(yōu)先級(jí)的比較,我們這里也就會(huì)進(jìn)入 canary-weight: "30" 這個(gè)規(guī)則去。
這個(gè)時(shí)候我們可以在上一個(gè) annotation (即 canary-by-header)的基礎(chǔ)上添加一條 nginx.ingress.kubernetes.io/canary-by-header-value: user-value 這樣的規(guī)則,就可以將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)了。
annotations:
??nginx.ingress.kubernetes.io/canary:?"true"???#?要開啟灰度發(fā)布機(jī)制,首先需要啟用?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%流量到當(dāng)前Canary版本
同樣更新 Ingress 對(duì)象后,重新訪問(wèn)應(yīng)用,當(dāng) Request Header 滿足 canary: user-value時(shí),所有請(qǐng)求就會(huì)被路由到 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 用法規(guī)則類似。例如在 A/B 測(cè)試場(chǎng)景下,需要讓地域?yàn)楸本┑挠脩粼L問(wèn) Canary 版本。那么當(dāng) cookie 的 annotation 設(shè)置為 nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_Beijing",此時(shí)后臺(tái)可對(duì)登錄的用戶請(qǐng)求進(jìn)行檢查,如果該用戶訪問(wèn)源來(lái)自北京則設(shè)置 cookie users_from_Beijing 的值為 always,這樣就可以確保北京的用戶僅訪問(wèn) Canary 版本。
同樣我們更新 Canary 版本的 Ingress 資源對(duì)象,采用基于 Cookie 來(lái)進(jìn)行流量切分,
annotations:
??nginx.ingress.kubernetes.io/canary:?"true"???#?要開啟灰度發(fā)布機(jī)制,首先需要啟用?Canary
??nginx.ingress.kubernetes.io/canary-by-cookie:?"users_from_Beijing"??#?基于?cookie
??nginx.ingress.kubernetes.io/canary-weight:?"30"??#?會(huì)被忽略,因?yàn)榕渲昧?canary-by-cookie
更新上面的 Ingress 資源對(duì)象后,我們?cè)谡?qǐng)求中設(shè)置一個(gè) users_from_Beijing=always 的 Cookie 值,再次訪問(wèn)應(yīng)用的域名。
??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
我們可以看到應(yīng)用都被路由到了 Canary 版本的應(yīng)用中去了,如果我們將這個(gè) Cookie 值設(shè)置為 never,則不會(huì)路由到 Canary 應(yīng)用中。
HTTPS
如果我們需要用 HTTPS 來(lái)訪問(wèn)我們這個(gè)應(yīng)用的話,就需要監(jiān)聽(tīng) 443 端口了,同樣用 HTTPS 訪問(wèn)應(yīng)用必然就需要證書,這里我們用 openssl 來(lái)創(chuàng)建一個(gè)自簽名的證書:
??openssl?req?-x509?-nodes?-days?365?-newkey?rsa:2048?-keyout?tls.key?-out?tls.crt?-subj?"/CN=foo.bar.com"
然后通過(guò) Secret 對(duì)象來(lái)引用證書文件:
#?要注意證書文件名稱必須是?tls.crt?和?tls.key
??kubectl?create?secret?tls?foo-tls?--cert=tls.crt?--key=tls.key
secret/who-tls?created
這個(gè)時(shí)候我們就可以創(chuàng)建一個(gè) HTTPS 訪問(wèn)應(yīng)用的:
apiVersion:?networking.k8s.io/v1
kind:?Ingress
metadata:
??name:?ingress-with-auth
??annotations:
????#?認(rèn)證類型
????nginx.ingress.kubernetes.io/auth-type:?basic
????#?包含?user/password?定義的?secret?對(duì)象名
????nginx.ingress.kubernetes.io/auth-secret:?basic-auth
????#?要顯示的帶有適當(dāng)上下文的消息,說(shuō)明需要身份驗(yàn)證的原因
????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
除了自簽名證書或者購(gòu)買正規(guī)機(jī)構(gòu)的 CA 證書之外,我們還可以通過(guò)一些工具來(lái)自動(dòng)生成合法的證書,cert-manager 是一個(gè)云原生證書管理開源項(xiàng)目,可以用于在 Kubernetes 集群中提供 HTTPS 證書并自動(dòng)續(xù)期,支持 Let's Encrypt/HashiCorp/Vault 這些免費(fèi)證書的簽發(fā)。在 Kubernetes 中,可以通過(guò) Kubernetes Ingress 和 Let's Encrypt 實(shí)現(xiàn)外部服務(wù)的自動(dòng)化 HTTPS。
TCP與UDP
由于在 Ingress 資源對(duì)象中沒(méi)有直接對(duì) TCP 或 UDP 服務(wù)的支持,要在 ingress-nginx 中提供支持,需要在控制器啟動(dòng)參數(shù)中添加 --tcp-services-configmap 和 --udp-services-configmap 標(biāo)志指向一個(gè) ConfigMap,其中的 key 是要使用的外部端口,value 值是使用格式 暴露的服務(wù),端口可以使用端口號(hào)或者端口名稱,最后兩個(gè)字段是可選的,用于配置 PROXY 代理。
比如現(xiàn)在我們要通過(guò) ingress-nginx 來(lái)暴露一個(gè) MongoDB 服務(wù),首先創(chuàng)建如下的應(yīng)用:
#?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
直接創(chuàng)建上面的資源對(duì)象:
??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
現(xiàn)在我們要通過(guò) ingress-nginx 來(lái)暴露上面的 MongoDB 服務(wù),我們需要?jiǎng)?chuàng)建一個(gè)如下所示的 ConfigMap:
apiVersion:?v1
kind:?ConfigMap
metadata:
??name:?tcp-services
??namespace:?ingress-nginx
data:
??"27017":?default/mongo:27017
然后在 ingress-nginx 的啟動(dòng)參數(shù)中添加 --tcp-services-configmap=$(POD_NAMESPACE)/ingress-nginx-tcp 這樣的配置即可,由于我們這里使用的是 Helm Chart 進(jìn)行安裝的,我們只需要去覆蓋 Values 值重新安裝即可,修改 ci/daemonset-prod.yaml 文件:
#?ci/daemonset-prod.yaml
#?......?其他部分省略,和之前的保持一致
tcp:??#?配置?tcp?服務(wù)
??27017:?"default/mongo:27017"??#?使用?27017?端口去映射?mongo?服務(wù)
??#?9000:?"default/test:8080"???#?如果還需要暴露其他?TCP?服務(wù),繼續(xù)添加即可
配置完成后重新更新當(dāng)前的 ingress-nginx:
??helm?upgrade?--install?ingress-nginx?.?-f?./ci/daemonset-prod.yaml?--namespace?ingress-nginx
重新部署完成后會(huì)自動(dòng)生成一個(gè)名為 ingress-nginx-tcp 的 ConfigMap 對(duì)象,如下所示:
??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 的啟動(dòng)參數(shù)中也添加上 --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?配置參數(shù)
????-?--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
......
現(xiàn)在我們就可以通過(guò) ingress-nginx 暴露的 27017 端口去訪問(wèn) Mongo 服務(wù)了:
??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 相關(guān)的配置位于 stream 配置塊下面。從 Nginx 1.9.13 版本開始提供 UDP 負(fù)載均衡,同樣我們也可以在 ingress-nginx 中來(lái)代理 UDP 服務(wù),比如我們可以去暴露 kube-dns 的服務(wù),同樣需要?jiǎng)?chuàng)建一個(gè)如下所示的 ConfigMap:
apiVersion:?v1
kind:?ConfigMap
metadata:
??name:?udp-services
??namespace:?ingress-nginx
data:
??53:?"kube-system/kube-dns:53"
然后需要在 ingress-nginx 參數(shù)中添加一個(gè) - --udp-services-configmap=$(POD_NAMESPACE)/udp-services 這樣的配置,當(dāng)然我們這里只需要去修改 Values 文件值即可,修改 ci/daemonset-prod.yaml 文件:
#?ci/daemonset-prod.yaml
#?......?其他部分省略,和之前的保持一致
tcp:??#?配置?tcp?服務(wù)
??27017:?"default/mongo:27017"??#?使用?27017?端口去映射?mongo?服務(wù)
??#?9000:?"default/test:8080"???#?如果還需要暴露其他?TCP?服務(wù),繼續(xù)添加即可
udp:??#?配置?udp?服務(wù)
??53:?"kube-system/kube-dns:53"
然后重新更新即可。
全局配置
除了可以通過(guò) annotations 對(duì)指定的 Ingress 進(jìn)行定制之外,我們還可以配置 ingress-nginx 的全局配置,在控制器啟動(dòng)參數(shù)中通過(guò)標(biāo)志 --configmap 指定了一個(gè)全局的 ConfigMap 對(duì)象,我們可以將全局的一些配置直接定義在該對(duì)象中即可:
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,連接復(fù)用,提高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 配置會(huì)自動(dòng)重載生效,我們可以查看 nginx.conf 配置文件進(jìn)行驗(yàn)證:
??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 安裝的,為了保證重新部署后配置還在,我們同樣需要通過(guò) Values 進(jìn)行全局配置:
#?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,連接復(fù)用,提高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"
#?其他省略
此外往往我們還需要對(duì) ingress-nginx 部署的節(jié)點(diǎn)進(jìn)行性能優(yōu)化,修改一些內(nèi)核參數(shù),使得適配 Nginx 的使用場(chǎng)景,一般我們是直接去修改節(jié)點(diǎn)上的內(nèi)核參數(shù),為了能夠統(tǒng)一管理,我們可以使用 initContainers 來(lái)進(jìn)行配置:
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 值即可,模板中提供了對(duì) 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監(jiān)聽(tīng)的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
部署完成后通過(guò) initContainers 就可以修改節(jié)點(diǎn)內(nèi)核參數(shù)了,生產(chǎn)環(huán)境建議對(duì)節(jié)點(diǎn)內(nèi)核參數(shù)進(jìn)行相應(yīng)的優(yōu)化。
