大家好,我是喬克,一個愛折騰的運維工程,一個睡覺都被自己醜醒的雲原生愛好者。
作者:喬克
公衆号:運維開發故事
在Kubernetes中,如果僅僅是單純的部署Pod,部署Deployment,并沒有任何意義,因為我們最終的目的是要為應用和使用者提供服務。
在Kubernetes中,提供了Service和Ingress兩種對象來實作應用間通路或外部對叢集應用通路,這兩種對象在實際的工作中會時長使用,非常重要的對象。
Service
對于kubernetes整個叢集來說,Pod的位址也可變的,也就是說如果一個Pod因為某些原因退出了,而由于其設定了副本數replicas大于1,那麼該Pod就會在叢集的任意節點重新啟動,這個重新啟動的Pod的IP位址與原IP位址不同,這對于業務來說,就不能根據Pod的IP作為業務排程。kubernetes就引入了Service的概念,它為Pod提供一個入口,主要通過Labels标簽來選擇後端Pod,這時候不論後端Pod的IP位址如何變更,隻要Pod的Labels标簽沒變,那麼 業務通過service排程就不會存在問題。
當聲明Service的時候,會自動生成一個cluster IP,這個IP是虛拟IP。我們就可以通過這個IP來通路後端的Pod,當然,如果叢集配置了DNS服務,比如現在的CoreDNS,那麼也可以通過Service的名字來通路,它會通過DNS自動解析Service的IP位址。
Service的類型有4種,Cluster IP,LoadBalance,NodePort,ExternalName。其中Cluster IP是預設的類型。
(1)、Cluster IP:通過 叢集内部IP暴露服務,預設是這個類型,選擇該值,這個Service服務隻能通過叢集内部通路;
(2)、LoadBalance:使用雲提供商的負載均衡器,可以向外部暴露服務,選擇該值,外部的負載均衡器可以路由到NodePort服務和Cluster IP服務;
(3)、NodePort:顧名思義是Node基本的Port,如果選擇該值,這個Service可以通過NodeIP:NodePort通路這個Service服務,NodePort會路由到Cluster IP服務,這個Cluster IP會通過請求自動建立;
(4)、ExternalName:通過傳回 CNAME 和它的值,可以将服務映射到 externalName 字段的内容,沒有任何類型代理被建立,可以用于通路叢集内其他沒有Labels的Pod,也可以通路其他NameSpace裡的Service。
kubernetes主要通過kube-proxy建立iptables和ipvs規則,在每個Node節點上都會建立這些規則。
ClusterIP
ClusterIP是Service預設的類型,隻能在叢集的内部通路,也是工作中最常用的一個類型,定義如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
port: 80
其中:
- type使用者指定Service類型
- ports使用者指定Service的端口
- selector使用者選擇後端的Pod
如果Service暴露的端口和後端的端口不一緻,還可以這樣寫:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 8080
targetPort用于指定後端的Pod,可以直接指定端口,也可以指定後端聲明的端口名字,前提是後端必須聲明端口名,如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: http
如上定義好Service并建立過後,會在叢集生成對應的Service和Endpoints,如下:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc ClusterIP 10.103.74.70 <none> 80/TCP 12m
$ kubectl get endpoints
NAME ENDPOINTS AGE
nginx-svc 172.16.51.208:80,172.16.51.209:80,172.16.51.237:80 12m
可以看到,endpoints的名字和service的名字一樣,而且我們并沒有建立endpoints,是由Kubernetes自己建立的。其背後的邏輯是:當我們新增Pod或者删除Pod,是從Endpoints裡添加或者剔除,Service本身是不改變的,在同一個namespace下,Service和Endpoints是通過名字進行關聯的。
是以,Endpoints其實也是一個對象,除了由Kubernetes生成還可以自己定義。在實際工作,有些場景是需要自定義Endpoints的,比如在叢集外部署了一個Redis服務,叢集内部想通過Service的方式進行通路,這時候就可以通過自定義Endpints的方式實作,如下:
kind: Service
apiVersion: v1
metadata:
name: custom-endpoint-svc
spec:
ports:
- port: 30018
protocol: TCP
name: http
type: ClusterIP
---
kind: Endpoints
apiVersion: v1
metadata:
name: custom-endpoint-svc
subsets:
- addresses:
- ip: 192.168.32.8
ports:
- port: 8080
name: http
其中,在Endpoints的subsets中用于定義目的位址和端口,需要注意的是:
- Endpoints和Service的metadata.name須保持一緻
- Service.spec.ports[x].name和Endpoints.subsets[x].ports[x].name須保持一緻
NodePort
如果Service的類型是NodePort,表示把Service的端口映射到了Node節點上,這時候就可以通過NodeIP:NodePort進行通路,可以實作在外部對叢集内應用進行通路。
NodePort并不是随便選擇的,當安裝好Kubernetes叢集後,會給定一個預設的NodePort範圍,它們是從30000到32767端口,如果沒有指定特定端口,預設會從這個區間範圍内随機選擇一個。
一個NodePort類型的Service定義如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
spec:
type: NodePort
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: http
除了Type類型不一樣,其他的和定義ClusterIP類型的一樣。
當建立完成過後,生成的Service如下:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc NodePort 10.103.74.70 <none> 80:32710/TCP 54m
NodePort類型的Service,既可以在叢集内部通過ClusterIP:Port進行通路,也可以在叢集外部通過NodeIP:NodePort進行通路。如上,80:32710中,80是叢集内部端口,32710是暴露出來的節點端口。
LoadBalancer
原則上,如果要使用LoadBalancer需要雲廠商的支援,因為公網IP這些基本都是雲廠商來配置設定。定義如下:
apiVersion: v1
kind: Service
metadata:
labels:
app: redis
name: redis-master
spec:
clusterIP: 10.96.246.58
externalTrafficPolicy: Local
healthCheckNodePort: 32006
ports:
- name: redis
nodePort: 30997
port: 6379
protocol: TCP
targetPort: 6379
selector:
app: redis
release: redis
role: master
sessionAffinity: None
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 121.189.210.111
這樣就可以直接通過公網IP和端口(121.189.210.111:6379)進行通路。
但是,為了在内網環境也使用LoadBalancer功能,有很多開源方案,其中OpenELB是一個非常不錯的項目,我們下面可以來簡單使用一下。
OpenELB
OpenELB 之前叫 PorterLB,是為實體機(Bare-metal)、邊緣(Edge)和私有化環境設計的負載均衡器插件,可作為 Kubernetes、K3s、KubeSphere 的 LB 插件對叢集外暴露 LoadBalancer 類型的服務,現階段是 CNCF 沙箱項目,核心功能包括:
- 基于 BGP 與 Layer 2 模式的負載均衡
- 基于路由器 ECMP 的負載均衡
- IP 位址池管理
- 使用 CRD 進行 BGP 配置
(1)安裝
要安裝OpenELB非常簡單,直接使用以下指令即可。
$ kubectl apply -f https://raw.githubusercontent.com/openelb/openelb/master/deploy/openelb.yaml
值得注意的是,如果本地不能擷取k8s.gcr.io鏡像(需要翻牆),可以把上面的YAML檔案下載下傳下來,根據裡面的注釋做相應的鏡像替換即可。
安裝完成過後,會生成如下Pod:
$ kubectl get pod -n openelb-system
NAME READY STATUS RESTARTS AGE
openelb-admission-create-ltfcv 0/1 Completed 0 4m19s
openelb-admission-patch-h485q 0/1 Completed 0 4m19s
openelb-keepalive-vip-7mnl7 1/1 Running 0 3m8s
openelb-manager-98764b5ff-4c58s 1/1 Running 0 4m19s
(2)配置
首先,需要為kube-proxy啟動strictARP,以便Kubernetes叢集中的所有網卡停止響應其他網卡的ARP請求,而由OpenELB來處理ARP請求。
$ kubectl edit configmap kube-proxy -n kube-system
......
ipvs:
strictARP: true
......
然後重新開機kube-proxy元件,指令如下:
$ kubectl rollout restart daemonset kube-proxy -n kube-system
如果安裝 OpenELB 的節點有多個網卡,則需要指定 OpenELB 在二層模式下使用的網卡,如果節點隻有一個網卡,則可以跳過此步驟,假設安裝了 OpenELB 的 master1 節點有兩個網卡(eth0 192.168.0.2 和 ens33 192.168.0.111),并且 eth0 192.168.0.2 将用于 OpenELB,那麼需要為 master1 節點添加一個 annotation 來指定網卡:
$ kubectl annotate nodes master1 layer2.openelb.kubesphere.io/v1alpha1="192.168.0.2"
接下來就可以建立一個EIP對象來充當OpenELB的IP位址池,YAML清單如下:
apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:
name: eip-pool
spec:
address: 192.168.205.50-192.168.205.60
protocol: layer2
disable: false
interface: ens33
值得注意的是:
- address用于配置位址池,如果是在layer2層,則需要和叢集的位址保持在同一網段
- protocol使用者指定模式,預設是bgp,我們這裡指定的是layer2。
- interface用于指定網卡,由于這裡是layer2,是以需要指定interface。
- disable表示禁用Eip對象
建立完成過後,可以看看Eip位址池狀态:
$ kubectl get eip
NAME CIDR USAGE TOTAL
eip-pool 192.168.205.50-192.168.205.60 1 11
最後,我們建立一個LoadBalancer類型的Service,如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
annotations:
lb.kubesphere.io/v1alpha1: openelb
protocol.openelb.kubesphere.io/v1alpha1: layer2
eip.openelb.kubesphere.io/v1alpha2: eip-pool
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: http
注意這裡我們為 Service 添加了幾個 annotations 注解:
- lb.kubesphere.io/v1alpha1: openelb 用來指定該 Service 使用 OpenELB
- protocol.openelb.kubesphere.io/v1alpha1: layer2 表示指定 OpenELB 用于 Layer2 模式
- eip.openelb.kubesphere.io/v1alpha2: eip-pool 用來指定了 OpenELB 使用的 Eip 對象,如果未配置此注解,OpenELB 會自動使用與協定比對的第一個可用 Eip 對象,此外也可以删除此注解并添加 spec:loadBalancerIP 字段(例如 spec:loadBalancerIP: 192.168.0.108)以将特定 IP 位址配置設定給 Service。
Service建立過後,可以檢視其狀态是否正确:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc LoadBalancer 10.103.74.70 192.168.205.50 80:32710/TCP 105m
可以看到在
EXTERNAL-IP
處為我們配置設定了一個Eip,現在就可以直接使用192.168.205.50:80從外部直接通路叢集内的服務了。
ExternalName
ExternalName 是 Service 的特例,它沒有 selector,也沒有定義任何的端口和 Endpoint。 對于運作在叢集外部的服務,它通過傳回該外部服務的别名這種方式來提供服務。例如:
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
當查詢主機 my-service.prod.svc.cluster.local (後面服務發現的時候我們會再深入講解)時,叢集的 DNS 服務将傳回一個值為 my.database.example.com 的 CNAME 記錄。 通路這個服務的工作方式與其它的相同,唯一不同的是重定向發生在 DNS 層,而且不會進行代理或轉發。 如果後續決定要将資料庫遷移到 Kubernetes 叢集中,可以啟動對應的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type,完全不需要修改調用的代碼,這樣就完全解耦了。
Headless Service
上面介紹的幾種Service的類型,都會生成一個ClusterIP供叢集使用,如果在Kubernetes叢集中配置了DNS,解析Service Name會得到ClusterIP。但是在一些特殊的場景下,我們希望直接和應用Pod進行通信,比如資料庫等有狀态應用,為此,社群提供了Headless Service,也就是無頭服務。
無頭服務,也就是在配置Service的時候将spec.clusterIP的值設定為“None”,如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-headless-service
labels:
name: nginx-headless-service
spec:
clusterIP: None
selector:
name: nginx
ports:
- port: 8000
targetPort: 80
建立過後,就不會生成ClusterIP,如下:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 17d
nginx-headless-service ClusterIP None <none> 8000/TCP 3s
這種類型的Service主要用在有狀态服務,後續在有狀态應用管理的時候會感受到具體是如何使用的。
Ingress
上面介紹的Service主要用在叢集内部,當然NodePort和LoadBalancer類型也可以用于外部通路,但是它們也有一定的弊端:
(1)如果使用NodePort類型,需要維護好每個應用的端口位址,如果服務太多就不好管理
(2)如果使用LoadBalancer類型,基本是在雲上使用,需要的IP比較多,價格也比較昂貴
(3)不論是NodePort還是LoadBalancer,都是工作在四層,對于HTTPS類型的請求無法直接進行SSL校驗
是以,社群提供了Ingress對象,為叢集提供統一的入口,邏輯如下:
其中Ingress代理的并不是Pod的Service,而是Pod,之是以在配置的時候是配置的Service,是為了擷取Pod的資訊。
Ingress提供七層通路入口,但是Kubernetes的Ingress對象本身沒有任何功能,需要借助一些Controller來實作,常用的有:
- Nginx Ingress Controller
- Traefik Ingress Controller
- Kong Ingress Controller
- APISix Ingress Controller
- .....
最常用的是nginx ingress controller,這裡也會安裝nginx ingress controller來進行介紹。
安裝Nginx Ingress Controller
安裝方式非常簡單,可以使用Helm和普通的YAML進行安裝,由于我們還沒有學習Helm,是以采用YAML清單的方式。
如果網絡條件好(可以翻牆),可直接使用以下指令安裝:
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.3.0/deploy/static/provider/cloud/deploy.yaml
如果網絡條件不好,可以使用:
$ kubectl apply -f https://raw.githubusercontent.com/joker-bai/kubernetes-software-yaml/main/ingress/nginx/ingress-nginx.yaml
安裝完成過後,資源資訊如下:
$ kubectl get all -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-admission-create-kj7ch 0/1 Completed 0 8m16s
pod/ingress-nginx-admission-patch-gtncg 0/1 Completed 0 8m16s
pod/ingress-nginx-controller-d5c4c7ffb-6q5gn 1/1 Running 0 8m16s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.109.208.249 192.168.205.51 80:31306/TCP,443:31079/TCP 8m16s
service/ingress-nginx-controller-admission ClusterIP 10.103.169.99 <none> 443/TCP 8m16s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ingress-nginx-controller 1/1 1 1 8m16s
NAME DESIRED CURRENT READY AGE
replicaset.apps/ingress-nginx-controller-d5c4c7ffb 1 1 1 8m16s
NAME COMPLETIONS DURATION AGE
job.batch/ingress-nginx-admission-create 1/1 5s 8m16s
job.batch/ingress-nginx-admission-patch 1/1 6s 8m16s
在這裡,ingress-nginx對外暴露采用的是LoadBalancer,也就是說把域名解析到192.168.205.51位址上,可以直接通過80端口通路。
暴露第一個服務
上面已經把ingress-nginx controller部署好了,接下來就可以建立一個應用進行暴露。
我們這裡通過暴露一個nginx服務,如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: nginx.dev.jokerbai.com
http:
paths:
- path: /
backend:
service:
name: nginx-svc
port:
number: 80
pathType: Prefix
注意:對于不同版本的Ingress配置可能不一樣,在實際中可以通過
kubectl explain ingrss
來檢視具體的配置。
部署完成過後,檢視ingress資訊如下:
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx <none> nginx.dev.jokerbai.com 192.168.205.51 80 88s
将域名進行解析,則可以通路,如下:
$ curl -x 192.168.205.51:80 http://nginx.dev.jokerbai.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
Welcome to nginx!
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a rel="nofollow" href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a rel="nofollow" href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
進階使用
上面使用ingress暴露了第一個域名,但是在實際的生成中,可能還有更多複雜的應用,比如:
- 域名使用SSL證書通路
- 域名重定向
- 位址重寫
- 認證通路
- 黑白名單
- 域名限速
- 灰階釋出
- ......
使用SSL證書
在實際工作中,為了安全考慮,更多的是采用HTTPS進行通路,Ingress也支援配置HTTPS。
首先需要域名證書,這裡手動建立私有證書進行配置。
$ openssl genrsa -out tls.key 2048
$ openssl req -new -x509 -key tls.key -out tls.crt -subj "/C=CN/ST=Chongqing/L=Chongqing/OU=DevOps/CN=nginx.dev.jokerbai.com"
然後将證書儲存到secret對象中(在Kubernetes應用配置管理會介紹)。
$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
證書建立好了之後,就可以配置ingress了,如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- nginx.dev.jokerbai.com
secretName: tls-secret
rules:
- host: nginx.dev.jokerbai.com
http:
paths:
- path: /
backend:
service:
name: nginx-svc
port:
number: 80
pathType: Prefix
其中Ingress.spec.tls字段用來配置域名證書。
接下來就可以通過HTTPS進行通路。
PS:由于是自定義的證書,是無效的,是以通路的時候會報錯,在實際工作中是會購買專用證書。
域名重定向
有時候需要把域名請求重定向到另外的域名,在nginx中,我們可以配置redirect,在ingress中,也可以使用redirect,不過是配置在annotation中,如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-redirect
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/permanent-redirect: "https://www.baidu.com"
spec:
rules:
- host: nginx-redirect.dev.jokerbai.com
http:
paths:
- path: /
backend:
service:
name: nginx-svc
port:
number: 80
pathType: Prefix
通路的時候可以看到301重定向,如下:
$ curl -I -x 192.168.205.51:80 http://nginx-redirect.dev.jokerbai.com
HTTP/1.1 301 Moved Permanently
Date: Fri, 22 Jul 2022 05:50:59 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://www.baidu.com
其實作方式是在ingress配置的annotation中添加聲明配置:nginx.ingress.kubernetes.io/permanent-redirect: "https://www.baidu.com" 即可。
位址重寫
位址重寫在ingress中通過在annotation中添加
nginx.ingress.kubernetes.io/rewrite-target: "/$1"
這種類型的配置即可。
通過位址重寫,我們可以實作諸如通路a.com/foo 重寫到a.com,通路a.com/foo重寫到a.com/foo/bar,需要注意的是重寫後的位址需要是能真實通路到資源的位址,不然重寫也沒什麼意義。
執行個體如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-rewrite
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: "/$1"
spec:
rules:
- host: nginx-rewrite.dev.jokerbai.com
http:
paths:
- path: /foo/?(.*)
backend:
service:
name: nginx-svc
port:
number: 80
pathType: Prefix
然後通過在通路URL中帶/foo即可通路到Nginx服務,如下:
$ curl -x 192.168.205.51:80 http://nginx-rewrite.dev.jokerbai.com/foo
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
Welcome to nginx!
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a rel="nofollow" href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a rel="nofollow" href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
認證通路
有時候,我們暴露出來的域名不想被其他人使用(可能有人會說:那你不暴露出來不就完事了),比如說prometheus的背景,預設是沒有登入管理的,而運維是需要到背景去查詢監控資訊的,為了安全,我們需要給這類域名加上認證,以便在一定程度上降低風險。
ingress提供base auth的認證方式,我們可以通過這種方式為我們的域名提供認證。
(1)建立密碼
$ htpasswd -c auth joker
New password:
Re-type new password:
Adding password for user joker
(2)将密碼儲存到secret中
$ kubectl create secret generic basic-auth --from-file=auth
secret/basic-auth created
(3)在Ingress中配置認證
隻需要在ingress配置中添加兩個annotation即可完成。
- nginx.ingress.kubernetes.io/auth-type: basic 指明認證方式
- nginx.ingress.kubernetes.io/auth-secret: basic-auth 指定認證的賬号密碼
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-auth
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
spec:
rules:
- host: nginx-auth.dev.jokerbai.com
http:
paths:
- path: /
backend:
service:
name: nginx-svc
port:
number: 80
pathType: Prefix
然後不帶認證資訊通路,結果如下:
$ curl -x 192.168.205.51:80 http://nginx-auth.dev.jokerbai.com/foo
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center>401 Authorization Required</center>
<hr><center>nginx</center>
</body>
</html>
直接傳回401,需要認證才能通路。
下面帶上認證資訊通路,結果如下:
$ curl -u "joker:123" -x 192.168.205.51:80 http://nginx-auth.dev.jokerbai.com/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
Welcome to nginx!
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a rel="nofollow" href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a rel="nofollow" href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
如果是從浏覽器通路,會給我們彈出一個輸入框,輸入賬号密碼即可進入應用。
黑白名單
有時候,光有認證通路也并不安全,這時候我們可以通過配置黑白名單的方式,把通路的範圍降低,在nginx中,我們可以通過配置allow和deny來配置,在ingress中,也支援類似的配置。
配置白名單
在ingress裡配置白名單可以通過兩種方式實作:
- 添加annotation,這種是隻針對單個域名
- 在ingress-nginx的configmap中配置,全局有效
(1)通過annotation配置,如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-whitelist
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/whitelist-source-range: 10.1.10.2
spec:
rules:
- host: nginx-whitelist.dev.jokerbai.com
http:
paths:
- path: /
backend:
service:
name: nginx-svc
port:
number: 80
pathType: Prefix
配置了一個不存在的位址10.1.10.2,檢視通路效果。
$ curl -I -x 192.168.205.51:80 http://nginx-whitelist.dev.jokerbai.com
HTTP/1.1 403 Forbidden
Date: Fri, 22 Jul 2022 06:57:45 GMT
Content-Type: text/html
Content-Length: 146
Connection: keep-alive
給我們傳回403拒絕通路。
如果我們把位址配置成可以通路的IP,則可以通路。
(2)配置ConfigMap
部署好ingress-nginx後,會在ingress-nginx的namespace下生成叫“ingress-nginx-controller”的ConfigMap配置檔案,我們隻需要在這個配置檔案進行配置即可。
$ kubectl edit cm -n ingress-nginx ingress-nginx-controller
apiVersion: v1
data:
allow-snippet-annotations: "true"
compute-full-forwarded-for: "true"
use-forwarded-headers: "true"
whitelist-source-range: 172.16.0.0/24
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.3.0
name: ingress-nginx-controller
namespace: ingress-nginx
配置完儲存即可,ingress-nginx的pod會自動reload。不過這種配置是全局生效,在使用的時候慎重。
配置黑名單
有白名單就有黑名單,ingress的黑名單配置隻能通過ConfigMap來,而且是全局生效的。
目前支援以下三種黑名單:
- block-cidrs:限制IP
- block-user-agents:限制User-Agent
- block-referers:限制referer
配置很簡單,如下:
$ kubectl edit cm -n ingress-nginx ingress-nginx-controller
apiVersion: v1
data:
allow-snippet-annotations: "true"
compute-full-forwarded-for: "true"
use-forwarded-headers: "true"
whitelist-source-range: 172.16.0.0/24
block-cidrs: 10.1.10.100
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.3.0
name: ingress-nginx-controller
namespace: ingress-nginx
通路限速
有時候通路量太大,可以通過在ingress進行限速,配置如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-limit
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/limit-rate: "100K"
nginx.ingress.kubernetes.io/limit-whitelist: "10.1.10.100"
nginx.ingress.kubernetes.io/limit-rps: "1"
nginx.ingress.kubernetes.io/limit-rpm: "30"
spec:
rules:
- host: nginx-limit.dev.jokerbai.com
http:
paths:
- path: /
backend:
service:
name: nginx-svc
port:
number: 80
pathType: Prefix
其中:
- nginx.ingress.kubernetes.io/limit-rate:限制用戶端每秒傳輸的位元組數
- nginx.ingress.kubernetes.io/limit-whitelist:白名單中的IP不限速
- nginx.ingress.kubernetes.io/limit-rps:單個IP每秒的連接配接數
- nginx.ingress.kubernetes.io/limit-rpm:單個IP每分鐘的連接配接數
灰階釋出
Nginx Annotations 支援以下 4 種 Canary 規則:
-
:基于 Request Header 的流量切分,适用于灰階釋出以及 A/B 測試。當 Request Header 設定為nginx.ingress.kubernetes.io/canary-by-header
時,請求将會被一直發送到 Canary 版本;當 Request Header 設定為always
時,請求不會被發送到 Canary 入口;對于任何其他 Header 值,将忽略 Header,并通過優先級将請求與其他金絲雀規則進行優先級的比較。never
-
:要比對的 Request Header 的值,用于通知 Ingress 将請求路由到 Canary Ingress 中指定的服務。當 Request Header 設定為此值時,它将被路由到 Canary 入口。該規則允許使用者自定義 Request Header 的值,必須與上一個 annotation (即:canary-by-header)一起使用。nginx.ingress.kubernetes.io/canary-by-header-value
-
:基于服務權重的流量切分,适用于藍綠部署,權重範圍 0 - 100 按百分比将請求路由到 Canary Ingress 中指定的服務。權重為 0 意味着該金絲雀規則不會向 Canary 入口的服務發送任何請求。權重為 100 意味着所有請求都将被發送到 Canary 入口。nginx.ingress.kubernetes.io/canary-weight
-
:基于 Cookie 的流量切分,适用于灰階釋出與 A/B 測試。用于通知 Ingress 将請求路由到 Canary Ingress 中指定的服務的cookie。當 cookie 值設定為nginx.ingress.kubernetes.io/canary-by-cookie
時,它将被路由到 Canary 入口;當 cookie 值設定為always
時,請求不會被發送到 Canary 入口;對于任何其他值,将忽略 cookie 并将請求與其他金絲雀規則進行優先級的比較。never
定義兩個版本的代碼。
V1版本代碼如下:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main(){
g:=gin.Default()
g.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK,gin.H{
"version": "v1",
"data": "hello world",
})
})
_ = g.Run("8080")
}
V2版本代碼如下:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main(){
g:=gin.Default()
g.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK,gin.H{
"version": "v2",
"data": "hello world,SB",
})
})
_ = g.Run("8080")
}
然後制作鏡像,Dockerfile如下:
FROM golang AS build-env
ADD . /go/src/app
WORKDIR /go/src/app
RUN go get -u -v github.com/gin-gonic/gin
RUN govendor sync
RUN GOOS=linux GOARCH=386 go build -v -o /go/src/app/app-server-v1
FROM alpine
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=build-env /go/src/app/app-server-v1 /usr/local/bin/app-server-v1
EXPOSE 8080
CMD [ "/usr/local/bin/app-server-v1" ]
制作鏡像并上傳:
$ docker build -t registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v1 .
$ docker push registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v1
PS:V2版本操作類似
V1和V2版本的Deployment和Service如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-server-v1
spec:
selector:
matchLabels:
app: app-server-v1
replicas: 2
template:
metadata:
labels:
app: app-server-v1
spec:
containers:
- name: app-server-v1
image: registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: app-server-v1-svc
spec:
selector:
app: app-server-v1
ports:
- name: http
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-server-v1
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: canary.dev.jokerbai.com
http:
paths:
- path: /
backend:
service:
name: app-server-v1-svc
port:
number: 8080
pathType: Prefix
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-server-v2
spec:
selector:
matchLabels:
app: app-server-v2
replicas: 2
template:
metadata:
labels:
app: app-server-v2
spec:
containers:
- name: app-server-v2
image: registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: app-server-v2-svc
spec:
selector:
app: app-server-v2
ports:
- name: http
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-server-v2
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: canary.dev.jokerbai.com
http:
paths:
- path: /
backend:
service:
name: app-server-v2-svc
port:
number: 8080
pathType: Prefix
說明:
- nginx.ingress.kubernetes.io/canary: true 表示開啟canary
- nginx.ingress.kubernetes.io/canary-weight: 10 表示權重為10,也就是v1:v2大緻為9:1