kubernetes1.22安裝使用ingress-nginx
運行原理
ingress-nginx 控制器主要是用來組裝一個 nginx.conf 的配置文件,當配置文件發(fā)生任何變動的時候就需要重新加載 Nginx 來生效,但是并不會只在影響 upstream 配置的變更后就重新加載 Nginx,控制器內部會使用一個 lua-nginx-module 來實現(xiàn)該功能。
我們知道 Kubernetes 控制器使用控制循環(huán)模式來檢查控制器中所需的狀態(tài)是否已更新或是否需要變更,所以 ingress-nginx 需要使用集群中的不同對象來構建模型,比如 Ingress、Service、Endpoints、Secret、ConfigMap 等可以生成反映集群狀態(tài)的配置文件的對象,控制器需要一直 Watch 這些資源對象的變化,但是并沒有辦法知道特定的更改是否會影響到最終生成的 nginx.conf 配置文件,所以一旦 Watch 到了任何變化控制器都必須根據(jù)集群的狀態(tài)重建一個新的模型,并將其與當前的模型進行比較,如果模型相同則就可以避免生成新的 Nginx 配置并觸發(fā)重新加載,否則還需要檢查模型的差異是否只和端點有關,如果是這樣,則然后需要使用 HTTP POST 請求將新的端點列表發(fā)送到在 Nginx 內運行的 Lua 處理程序,并再次避免生成新的 Nginx 配置并觸發(fā)重新加載,如果運行和新模型之間的差異不僅僅是端點,那么就會基于新模型創(chuàng)建一個新的 Nginx 配置了,這樣構建模型最大的一個好處就是在狀態(tài)沒有變化時避免不必要的重新加載,可以節(jié)省大量 Nginx 重新加載。
下面簡單描述了需要重新加載的一些場景:
創(chuàng)建了新的 Ingress 資源 TLS 添加到現(xiàn)有 Ingress 從 Ingress 中添加或刪除 path 路徑 Ingress、Service、Secret 被刪除了 Ingress 的一些缺失引用對象變可用了,例如 Service 或 Secret 更新了一個 Secret
對于集群規(guī)模較大的場景下頻繁的對 Nginx 進行重新加載顯然會造成大量的性能消耗,所以要盡可能減少出現(xiàn)重新加載的場景。
安裝
由于 ingress-nginx 所在的節(jié)點需要能夠訪問外網(wǎng)(不是強制的),這樣域名可以解析到這些節(jié)點上直接使用,所以需要讓 ingress-nginx 綁定節(jié)點的 80 和 443 端口,所以可以使用 hostPort 來進行訪問,當然對于線上環(huán)境來說為了保證高可用,一般是需要運行多個 ·ingress-nginx 實例的,然后可以用一個 nginx/haproxy 作為入口,通過 keepalived 來訪問邊緣節(jié)點的 vip 地址。
!!! info "邊緣節(jié)點" 所謂的邊緣節(jié)點即集群內部用來向集群外暴露服務能力的節(jié)點,集群外部的服務通過該節(jié)點來調用集群內部的服務,邊緣節(jié)點是集群內外交流的一個 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 文件中包含的是所有可配置的默認值,我們可以對這些默認值進行覆蓋,我們這里測試環(huán)境就將 master1 節(jié)點看成邊緣節(jié)點,所以我們就直接將 ingress-nginx 固定到 master1 節(jié)點上,采用 hostNetwork 模式(生產環(huán)境可以使用 LB + DaemonSet hostNetwork 模式),為了避免創(chuàng)建的錯誤 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,通過節(jié)點IP地址上報ingress?status數(shù)據(jù)
????enabled:?false
??#?是否需要處理不帶?ingressClass?注解或者?ingressClassName?屬性的?Ingress?對象
??#?設置為?true?會在控制器啟動參數(shù)中新增一個?--watch-ingress-without-class?標注
??watchIngressWithoutClass:?false
??kind:?DaemonSet
??tolerations:???#?kubeadm?安裝的集群默認情況下master是有污點,需要容忍這個污點才可以部署
??-?key:?"node-role.kubernetes.io/master"
????operator:?"Equal"
????effect:?"NoSchedule"
??nodeSelector:???#?固定到master1節(jié)點
????kubernetes.io/hostname:?master1
??service:??#?HostNetwork?模式不需要創(chuàng)建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:?
??type:?kubernetes.io/tls
部署完成后查看 Pod 的運行狀態(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"
當看到上面的信息證明 ingress-nginx 部署成功了,這里我們安裝的是最新版本的控制器,安裝完成后會自動創(chuàng)建一個 名為 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 屬性,如果還需要配置一些額外的參數(shù),則可以在安裝的 values 文件中進行配置。
第一個示例
安裝成功后,現(xiàn)在我們來為一個 nginx 應用創(chuàng)建一個 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(關聯(lián)的?ingress-nginx?控制器)
??rules:
??-?host:?ngdemo.qikqiak.com??#?將域名映射到?my-nginx?服務
????http:
??????paths:
??????-?path:?/
????????pathType:?Prefix
????????backend:
??????????service:??#?將所有請求發(fā)送到?my-nginx?服務的?80?端口
????????????name:?my-nginx
????????????port:
??????????????number:?80
#?不過需要注意大部分Ingress控制器都不是直接轉發(fā)到Service
#?而是只是通過Service來獲取后端的Endpoints列表,直接轉發(fā)到Pod,這樣可以減少網(wǎng)絡跳轉,提高性能
直接創(chuàng)建上面的資源對象:
??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 的所有請求轉發(fā)到 my-nginx 服務的后端 Endpoints 中去。
上面資源創(chuàng)建成功后,然后我們可以將域名 ngdemo.qikqiak.com 解析到 ingress-nginx 所在的邊緣節(jié)點中的任意一個,當然也可以在本地 /etc/hosts 中添加對應的映射也可以,然后就可以通過域名進行訪問了。

下圖顯示了客戶端是如何通過 Ingress 控制器連接到其中一個 Pod 的流程,客戶端首先對 ngdemo.qikqiak.com 執(zhí)行 DNS 解析,得到 Ingress 控制器所在節(jié)點的 IP,然后客戶端向 Ingress 控制器發(fā)送 HTTP 請求,然后根據(jù) Ingress 對象里面的描述匹配域名,找到對應的 Service 對象,并獲取關聯(lián)的 Endpoints 列表,將客戶端的請求轉發(fā)給其中一個 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 資源對象的相關配置信息,不過需要注意的是現(xiàn)在并不會為每個 backend 后端都創(chuàng)建一個 upstream 配置塊,現(xiàn)在是使用 Lua 程序進行動態(tài)處理的,所以我們沒有直接看到后端的 Endpoints 相關配置數(shù)據(jù)。
Nginx 配置
如果我們還想進行一些自定義配置,則有幾種方式可以實現(xiàn):使用 Configmap 在 Nginx 中設置全局配置、通過 Ingress 的 Annotations 設置特定 Ingress 的規(guī)則、自定義模板。接下來我們重點給大家介紹使用注解來對 Ingress 對象進行自定義。
Basic Auth
我們可以在 Ingress 對象上配置一些基本的 Auth 認證,比如 Basic Auth,可以用 htpasswd 生成一個密碼文件來驗證身份驗證。
??htpasswd?-c?auth?foo
New?password:
Re-type?new?password:
Adding?password?for?user?foo
然后根據(jù)上面的 auth 文件創(chuàng)建一個 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 應用創(chuàng)建一個具有 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(關聯(lián)的?ingress-nginx?控制器)
??rules:
??-?host:?bauth.qikqiak.com??#?將域名映射到?my-nginx?服務
????http:
??????paths:
??????-?path:?/
????????pathType:?Prefix
????????backend:
??????????service:??#?將所有請求發(fā)送到?my-nginx?服務的?80?端口
????????????name:?my-nginx
????????????port:
??????????????number:?80
直接創(chuàng)建上面的資源對象,然后通過下面的命令或者在瀏覽器中直接打開配置的域名:
??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 認證失敗錯誤,然后帶上我們配置的用戶名和密碼進行認證:
??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
可以看到已經認證成功了。除了可以使用我們自己在本地集群創(chuàng)建的 Auth 信息之外,還可以使用外部的 Basic Auth 認證信息,比如我們使用 https://httpbin.org 的外部 Basic Auth 認證,創(chuàng)建如下所示的 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
上面的資源對象創(chuàng)建完成后,再進行簡單的測試:
??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
然后使用正確的用戶名和密碼測試:
??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
如果用戶名或者密碼錯誤則同樣會出現(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
當然除了 Basic Auth 這一種簡單的認證方式之外,ingress-nginx 還支持一些其他高級的認證,比如我們可以使用 GitHub OAuth 來認證 Kubernetes 的 Dashboard。
URL Rewrite
ingress-nginx 很多高級的用法可以通過 Ingress 對象的 annotation 進行配置,比如常用的 URL Rewrite 功能。很多時候我們會將 ingress-nginx 當成網(wǎng)關使用,比如對訪問的服務加上 /app 這樣的前綴,在 nginx 的配置里面我們知道有一個 proxy_pass 指令可以實現(xiàn):
location?/app/?{
??proxy_pass?http://127.0.0.1/remote/;
}
proxy_pass 后面加了 /remote 這個路徑,此時會將匹配到該規(guī)則路徑中的 /app 用 /remote 替換掉,相當于截掉路徑中的 /app。同樣的在 Kubernetes 中使用 ingress-nginx 又該如何來實現(xiàn)呢?我們可以使用 rewrite-target 的注解來實現(xiàn)這個需求,比如現(xiàn)在我們想要通過 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/ 的時候實際上相當于訪問的就是后端服務的 / 路徑。
要解決我們訪問主域名出現(xiàn) 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 的配置就非常熟悉的話應該可以很快就能理解這種配置方式了。
灰度發(fā)布
在日常工作中我們經常需要對服務進行版本更新升級,所以我們經常會使用到滾動升級、藍綠發(fā)布、灰度發(fā)布等不同的發(fā)布操作。而 ingress-nginx 支持通過 Annotations 配置來實現(xiàn)不同場景下的灰度發(fā)布和測試,可以滿足金絲雀發(fā)布、藍綠部署與 A/B 測試等業(yè)務場景。
ingress-nginx 的 Annotations 支持以下 4 種 Canary 規(guī)則:
nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,適用于灰度發(fā)布以及 A/B 測試。當 Request Header 設置為 always 時,請求將會被一直發(fā)送到 Canary 版本;當 Request Header 設置為 never 時,請求不會被發(fā)送到 Canary 入口;對于任何其他 Header 值,將忽略 Header,并通過優(yōu)先級將請求與其他金絲雀規(guī)則進行優(yōu)先級的比較。nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 將請求路由到 Canary Ingress 中指定的服務。當 Request Header 設置為此值時,它將被路由到 Canary 入口。該規(guī)則允許用戶自定義 Request Header 的值,必須與上一個 annotation (canary-by-header) 一起使用。nginx.ingress.kubernetes.io/canary-weight:基于服務權重的流量切分,適用于藍綠部署,權重范圍 0 - 100 按百分比將請求路由到 Canary Ingress 中指定的服務。權重為 0 意味著該金絲雀規(guī)則不會向 Canary 入口的服務發(fā)送任何請求,權重為 100 意味著所有請求都將被發(fā)送到 Canary 入口。nginx.ingress.kubernetes.io/canary-by-cookie:基于 cookie 的流量切分,適用于灰度發(fā)布與 A/B 測試。用于通知 Ingress 將請求路由到 Canary Ingress 中指定的服務的cookie。當 cookie 值設置為 always 時,它將被路由到 Canary 入口;當 cookie 值設置為 never 時,請求不會被發(fā)送到 Canary 入口;對于任何其他值,將忽略 cookie 并將請求與其他金絲雀規(guī)則進行優(yōu)先級的比較。
需要注意的是金絲雀規(guī)則按優(yōu)先順序進行排序:
canary-by-header - > canary-by-cookie - > canary-weight
總的來說可以把以上的四個 annotation 規(guī)則劃分為以下兩類:
基于權重的 Canary 規(guī)則

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

下面我們通過一個示例應用來對灰度發(fā)布功能進行說明。
第一步. 部署 Production 應用
首先創(chuàng)建一個 production 環(huán)境的應用資源清單:
#?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)建一個用于 production 環(huán)境訪問的 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
直接創(chuàng)建上面的幾個資源對象:
??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 節(jié)點(ingress-nginx 所在的節(jié)點)的 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-
第二步. 創(chuàng)建 Canary 版本參考將上述 Production 版本的 production.yaml 文件,再創(chuàng)建一個 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 規(guī)則進行流量切分了。
第三步. Annotation 規(guī)則配置
1. 基于權重:基于權重的流量切分的典型應用場景就是藍綠部署,可通過將權重設置為 0 或 100 來實現(xiàn)。例如,可將 Green 版本設置為主要部分,并將 Blue 版本的入口配置為 Canary。最初,將權重設置為 0,因此不會將流量代理到 Blue 版本。一旦新版本測試和驗證都成功后,即可將 Blue 版本的權重設置為 100,即所有流量從 Green 版本轉向 Blue。
創(chuàng)建一個基于權重的 Canary 版本的應用路由 Ingress 對象。
#?canary-ingress.yaml
apiVersion:?networking.k8s.io/v1
kind:?Ingress
metadata:
??name:?canary
??annotations:
????nginx.ingress.kubernetes.io/canary:?"true"???#?要開啟灰度發(fā)布機制,首先需要啟用?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
直接創(chuàng)建上面的資源對象即可:
??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 版本應用創(chuà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 版本應用分配了 30% 左右權重的流量,所以上面我們訪問10次有3次訪問到了 Canary 版本的應用,符合我們的預期。
2. 基于 Request Header: 基于 Request Header 進行流量切分的典型應用場景即灰度發(fā)布或 A/B 測試場景。
在上面的 Canary 版本的 Ingress 對象中新增一條 annotation 配置 nginx.ingress.kubernetes.io/canary-by-header: canary(這里的 value 可以是任意值),使當前的 Ingress 實現(xiàn)基于 Request Header 進行流量切分,由于 canary-by-header 的優(yōu)先級大于 canary-weight,所以會忽略原有的 canary-weight 的規(guī)則。
annotations:
??nginx.ingress.kubernetes.io/canary:?"true"???#?要開啟灰度發(fā)布機制,首先需要啟用?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 時,請求將不會或一直被發(fā)送到 Canary 版本,對于任何其他 Header 值,將忽略 Header,并通過優(yōu)先級將請求與其他 Canary 規(guī)則進行優(yōu)先級的比較。
??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 值,所以請求沒有發(fā)送到 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 會通過優(yōu)先級將請求與其他 Canary 規(guī)則進行優(yōu)先級的比較,我們這里也就會進入 canary-weight: "30" 這個規(guī)則去。
這個時候我們可以在上一個 annotation (即 canary-by-header)的基礎上添加一條 nginx.ingress.kubernetes.io/canary-by-header-value: user-value 這樣的規(guī)則,就可以將請求路由到 Canary Ingress 中指定的服務了。
annotations:
??nginx.ingress.kubernetes.io/canary:?"true"???#?要開啟灰度發(fā)布機制,首先需要啟用?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 用法規(guī)則類似。例如在 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"???#?要開啟灰度發(fā)布機制,首先需要啟用?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 來訪問我們這個應用的話,就需要監(jiān)聽 443 端口了,同樣用 HTTPS 訪問應用必然就需要證書,這里我們用 openssl 來創(chuàng)建一個自簽名的證書:
??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
這個時候我們就可以創(chuàng)建一個 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
除了自簽名證書或者購買正規(guī)機構的 CA 證書之外,我們還可以通過一些工具來自動生成合法的證書,cert-manager 是一個云原生證書管理開源項目,可以用于在 Kubernetes 集群中提供 HTTPS 證書并自動續(xù)期,支持 Let's Encrypt/HashiCorp/Vault 這些免費證書的簽發(fā)。在 Kubernetes 中,可以通過 Kubernetes Ingress 和 Let's Encrypt 實現(xiàn)外部服務的自動化 HTTPS。
TCP與UDP
由于在 Ingress 資源對象中沒有直接對 TCP 或 UDP 服務的支持,要在 ingress-nginx 中提供支持,需要在控制器啟動參數(shù)中添加 --tcp-services-configmap 和 --udp-services-configmap 標志指向一個 ConfigMap,其中的 key 是要使用的外部端口,value 值是使用格式 暴露的服務,端口可以使用端口號或者端口名稱,最后兩個字段是可選的,用于配置 PROXY 代理。
比如現(xiàn)在我們要通過 ingress-nginx 來暴露一個 MongoDB 服務,首先創(chuà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)建上面的資源對象:
??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)在我們要通過 ingress-nginx 來暴露上面的 MongoDB 服務,我們需要創(chuàng)建一個如下所示的 ConfigMap:
apiVersion:?v1
kind:?ConfigMap
metadata:
??name:?tcp-services
??namespace:?ingress-nginx
data:
??"27017":?default/mongo:27017
然后在 ingress-nginx 的啟動參數(shù)中添加 --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?服務,繼續(xù)添加即可
配置完成后重新更新當前的 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 的啟動參數(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)在我們就可以通過 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 的服務,同樣需要創(chuàng)建一個如下所示的 ConfigMap:
apiVersion:?v1
kind:?ConfigMap
metadata:
??name:?udp-services
??namespace:?ingress-nginx
data:
??53:?"kube-system/kube-dns:53"
然后需要在 ingress-nginx 參數(shù)中添加一個 - --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?服務,繼續(xù)添加即可
udp:??#?配置?udp?服務
??53:?"kube-system/kube-dns:53"
然后重新更新即可。
全局配置
除了可以通過 annotations 對指定的 Ingress 進行定制之外,我們還可以配置 ingress-nginx 的全局配置,在控制器啟動參數(shù)中通過標志 --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 部署的節(jié)點進行性能優(yōu)化,修改一些內核參數(shù),使得適配 Nginx 的使用場景,一般我們是直接去修改節(jié)點上的內核參數(shù),為了能夠統(tǒng)一管理,我們可以使用 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監(jiān)聽的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 就可以修改節(jié)點內核參數(shù)了,生產環(huán)境建議對節(jié)點內核參數(shù)進行相應的優(yōu)化。
