天天看點

【K8S專欄】Kubernetes應用通路管理ServiceIngress總結

大家好,我是喬克,一個愛折騰的運維工程,一個睡覺都被自己醜醒的雲原生愛好者。

作者:喬克

公衆号:運維開發故事

在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節點上都會建立這些規則。

【K8S專欄】Kubernetes應用通路管理ServiceIngress總結

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對象,為叢集提供統一的入口,邏輯如下:

【K8S專欄】Kubernetes應用通路管理ServiceIngress總結

其中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進行通路。

【K8S專欄】Kubernetes應用通路管理ServiceIngress總結
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 規則:

  • nginx.ingress.kubernetes.io/canary-by-header

    :基于 Request Header 的流量切分,适用于灰階釋出以及 A/B 測試。當 Request Header 設定為

    always

    時,請求将會被一直發送到 Canary 版本;當 Request Header 設定為

    never

    時,請求不會被發送到 Canary 入口;對于任何其他 Header 值,将忽略 Header,并通過優先級将請求與其他金絲雀規則進行優先級的比較。
  • nginx.ingress.kubernetes.io/canary-by-header-value

    :要比對的 Request Header 的值,用于通知 Ingress 将請求路由到 Canary Ingress 中指定的服務。當 Request Header 設定為此值時,它将被路由到 Canary 入口。該規則允許使用者自定義 Request Header 的值,必須與上一個 annotation (即:canary-by-header)一起使用。
  • nginx.ingress.kubernetes.io/canary-weight

    :基于服務權重的流量切分,适用于藍綠部署,權重範圍 0 - 100 按百分比将請求路由到 Canary Ingress 中指定的服務。權重為 0 意味着該金絲雀規則不會向 Canary 入口的服務發送任何請求。權重為 100 意味着所有請求都将被發送到 Canary 入口。
  • nginx.ingress.kubernetes.io/canary-by-cookie

    :基于 Cookie 的流量切分,适用于灰階釋出與 A/B 測試。用于通知 Ingress 将請求路由到 Canary Ingress 中指定的服務的cookie。當 cookie 值設定為

    always

    時,它将被路由到 Canary 入口;當 cookie 值設定為

    never

    時,請求不會被發送到 Canary 入口;對于任何其他值,将忽略 cookie 并将請求與其他金絲雀規則進行優先級的比較。

定義兩個版本的代碼。

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

總結

繼續閱讀