k8s集群上ingress實戰

  • 2019 年 10 月 7 日
  • 筆記

有三種方法把k8s集群上的服務提供給外部訪問:

  1. 把Service類型設置為NodePort:則該服務會在k8s集群的所有工作節點上都保留一個端口,使用任一個節點IP:端口號訪問該服務,都將轉發到該服務所關聯的後台POD上;
  2. 把Service類型設置為LoadBalancer:公有雲如AWS、GCP和阿里雲等都提供LoadBalancer支持,客戶端通過LoadBalancer訪問服務;
  3. 使用ingress:ingress運行在網絡第七層HTTP層,比前兩種方式都更為方便和強大一些,本文介紹ingress方式。

使用ingress之前要先在k8s集群部署ingress controller,ingress controller本身需要LoadBalancer支持,一個基本的訪問流如下:

Internet ←-> Public Cloud LoadBalancer ←-> k8s ingress controller(ingress) ←-> k8s service ←-> k8s pods

當自己拿幾個裸機或者虛擬機搭建k8s集群時,往往沒有公有雲LoadBalancer支持,只能迂迴通過NodePort方式把ingress controller服務暴露出去,訪問流相應變為如下:

client(/etc/hosts) ←-> Node IP+port ←-> k8s ingress controller(ingress) ←-> k8s service ←-> k8s pods

客戶端訪問ingress所提供服務涉及的組件如圖所示:


下面反着訪問流逐一說明各組件部署及其檢測。

POD部署及檢測

示例使用了k8s倉庫中已有的luksa/kubia鏡像,該鏡像使用NodeJS創建了一個web服務,監聽在8080端口,將返回POD的主機名給訪問者。POD的定義文件如下:

kubia-replicaset.yaml

apiVersion: apps/v1beta2  kind: ReplicaSet  metadata:    name: kubia  spec:    replicas: 3    selector:      matchLabels:        app: kubia    template:      metadata:        labels:          app: kubia      spec:        containers:        - name: kubia          image: luksa/kubia

部署並檢查POD狀態,使用exec連接到POD,使用curl檢查POD所提供服務

# kubectl apply -f kubia-replicaset.yaml  # kubectl get pods -o wide  NAME          READY   STATUS    RESTARTS   AGE   IP            NODE  kubia-4mcp9   1/1     Running   1          20h   10.244.1.32   slcaa872.us.abc.com  kubia-ftmdz   1/1     Running   1          20h   10.244.2.31   slcaa873.us.abc.com  kubia-zwh72   1/1     Running   1          20h   10.244.1.33   slcaa872.us.abc.com    # kubectl exec -it kubia-4mcp9 bash  root@kubia-4mcp9:/# ps aux  USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND  root         1  0.0  0.0 747324 27980 ?        Ssl  Sep19   0:00 node app.js  root        84  0.0  0.0  20252  3268 pts/2    Ss   09:52   0:00 bash  root        93  0.0  0.0  17500  2072 pts/2    R+   09:52   0:00 ps aux  root@kubia-4mcp9:/# curl --noproxy  "*" localhost:8080  You've hit kubia-4mcp9  root@kubia-4mcp9:/# curl --noproxy  "*" 10.244.2.31:8080  You've hit kubia-ftmdz  root@kubia-4mcp9:/# exit

Service部署及檢測

Service使用標籤選擇器(selector)選擇POD,具體見配置文件:kubia-svc.yaml

apiVersion: v1  kind: Service  metadata:    name: kubia  spec:    ports:    - port: 80      targetPort: 8080    selector:      app: kubia

部署並檢測Service,從Service的Endpoints可以看到其和後台PODs的關聯

# kubectl apply -f kubia-svc.yaml  # kubectl get svc  NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE  kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   16d  kubia        ClusterIP   10.107.59.127   <none>        80/TCP    21h  [root@slcaa871 Chapter05]# kubectl describe svc kubia  Name:              kubia  Namespace:         default  Labels:            <none>  Annotations:       <none>  Selector:          app=kubia  Type:              ClusterIP  IP:                10.107.59.127  Port:              <unset>  80/TCP  TargetPort:        8080/TCP  Endpoints:         10.244.1.32:8080,10.244.1.33:8080,10.244.2.31:8080  Session Affinity:  None  Events:            <none>  root@kubia-4mcp9:/# curl --noproxy "*" 10.107.59.127  You've hit kubia-ftmdz  root@kubia-4mcp9:/# curl --noproxy "*" 10.244.1.32:8080  You've hit kubia-4mcp9  root@kubia-4mcp9:/# exit

部署並檢測ingress controller

ingress controller就是部署在k8s集群上的代理服務,有多種ingress controller,這裡使用最常見的Nginx ingress controller,其部署包括定義namespace、service account、cluster role binding、configmaps等一系列組件,還好當前版本的k8s已經把這些組件的定義放到了一個名為mandatory.yaml文件中,只要應用這個文件就會把這些組件都創建好,該文件過大,這裡不予展示。

當k8s集群沒有公有雲的LoadBalancer支持時,需要採用NodePort的方式把ingress controller服務暴露出去,k8s git提供了Bare-metal的service-nodeport.yaml,應用這個文件就會創建NodePort類型的服務。由於這個文件沒有在ports列表中為NodePort定義具體的端口號,k8s將隨機選取一個端口號,每個工作節點將保留該端口用於轉發請求到後台服務。

service-nodeport.yaml

apiVersion: v1  kind: Service  metadata:    name: ingress-nginx    namespace: ingress-nginx    labels:      app.kubernetes.io/name: ingress-nginx      app.kubernetes.io/part-of: ingress-nginx  spec:    type: NodePort    ports:      - name: http        port: 80        targetPort: 80        protocol: TCP      - name: https        port: 443        targetPort: 443        protocol: TCP    selector:      app.kubernetes.io/name: ingress-nginx      app.kubernetes.io/part-of: ingress-nginx

部署並檢測ingress controller,當controller部署後,可以登錄其相應POD訪問Nginx默認服務。

# kubectl apply -f mandatory.yaml  # kubectl get ns  NAME              STATUS   AGE  default           Active   16d  ingress-nginx     Active   10s  kube-node-lease   Active   16d  kube-public       Active   16d  kube-system       Active   16d  # kubectl get pods -n ingress-nginx  NAME                                        READY   STATUS    RESTARTS   AGE  nginx-ingress-controller-79f6884cf6-rwjx5   1/1     Running   0          2m58s  # kubectl exec -it nginx-ingress-controller-79f6884cf6-rwjx5 -n ingress-nginx bash  www-data@nginx-ingress-controller-79f6884cf6-rwjx5:/etc/nginx$ curl localhost  <html>  <head><title>404 Not Found</title></head>  <body>  <center><h1>404 Not Found</h1></center>  <hr><center>openresty/1.15.8.1</center>  </body>  </html>  www-data@nginx-ingress-controller-79f6884cf6-rwjx5:/etc/nginx$ curl localhost/nginx_status  Active connections: 3  server accepts handled requests   265 265 264  Reading: 0 Writing: 1 Waiting: 2  www-data@nginx-ingress-controller-79f6884cf6-rwjx5:/etc/nginx$ exit    # kubectl apply -f service-nodeport.yaml  service/ingress-nginx created  # kubectl get all -n ingress-nginx  NAME                                            READY   STATUS    RESTARTS   AGE  pod/nginx-ingress-controller-79f6884cf6-rwjx5   1/1     Running   0          12m  NAME                    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE  service/ingress-nginx   NodePort   10.104.151.10   <none>        80:30880/TCP,443:30087/TCP   7s  NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE  deployment.apps/nginx-ingress-controller   1/1     1            1           12m  NAME                                                  DESIRED   CURRENT   READY   AGE  replicaset.apps/nginx-ingress-controller-79f6884cf6   1         1         1       12m

注意所生成的service,及其CLUSTER-IP和PORT(S),檢測其狀態:

# kubectl exec -it kubia-4mcp9 bash  root@kubia-4mcp9:/# curl 10.104.151.10  <html>  <head><title>404 Not Found</title></head>  <body>  <center><h1>404 Not Found</h1></center>  <hr><center>openresty/1.15.8.1</center>  </body>  </html>  root@kubia-4mcp9:/# curl 10.107.59.127  You've hit kubia-4mcp9  root@kubia-4mcp9:/# exit  exit  [root@slcaa871 ingress-controller-demo]# curl --noproxy "*" slcaa873:30880  <html>  <head><title>404 Not Found</title></head>  <body>  <center><h1>404 Not Found</h1></center>  <hr><center>openresty/1.15.8.1</center>  </body>  </html>

直接訪問ingress controller,返回404,通過工作節點的NodePort(30880),也能訪問到ingress controller,但一樣返回404。這都是因為目前還沒有定義ingress,所以ingress controller還沒有對應的後台服務進行相應處理。

部署並檢測ingress

ingress定義了域名和路徑所對應的後台服務,這個其實和Web服務里的虛擬主機定義類似,具體參見文件ingress-myapp.yaml

apiVersion: extensions/v1beta1  kind: Ingress  metadata:      name: ingress-myapp      namespace: default      annotations:          kubernetes.io/ingress.class: "nginx"  spec:      rules:      - host: myapp.kubia.com        http:            paths:            - path: /              backend:                  serviceName: kubia                  servicePort: 80

該文件里的serviceName定義為kubia,正是前面部署的Service。apply該文件,會在ingress controller里的Nginx的配置文件里添加對應的域名、路徑和對應的後台服務,自此就可以從外部訪問該後台服務了。注意這裡使用/etc/hosts文件定義了myapp.kubia.com,ingress定義文件里引用了該域名。

# kubectl apply -f ingress-myapp.yaml  # kubectl get ingress  NAME            HOSTS             ADDRESS   PORTS   AGE  ingress-myapp   myapp.kubia.com             80      44s  # kubectl describe ingress ingress-myapp  Name:             ingress-myapp  Namespace:        default  Address:  Default backend:  default-http-backend:80 (<none>)  Rules:    Host             Path  Backends    ----             ----  --------    myapp.kubia.com                     /   kubia:80 (10.244.1.32:8080,10.244.1.33:8080,10.244.2.31:8080)  # kubectl exec -it nginx-ingress-controller-79f6884cf6-rwjx5 -n ingress-nginx bash  www-data@nginx-ingress-controller-79f6884cf6-rwjx5:/etc/nginx$ curl -H "HOST: myapp.kubia.com" localhost  You've hit kubia-zwh72  # curl --noproxy "*" slcaa873:30880  <html>  <head><title>404 Not Found</title></head>  <body>  <center><h1>404 Not Found</h1></center>  <hr><center>openresty/1.15.8.1</center>  </body>  </html>  # grep kubia /etc/hosts  10.242.18.72  slcaa873.us.abc.com  slcaa873 myapp.kubia.com  # curl --noproxy "*" myapp.kubia.com:30880  You've hit kubia-4mcp9

注意事項

當k8s集群位於公司的代理服務器後,一定要注意對於代理服務器的處理,否則會有一些故障而難以排查。一方面訪問外部服務需要設置代理,另一方面訪問k8s集群自身的一些服務不應該使用代理,可以參見如下文件進行配置:

vi /etc/systemd/system/docker.service.d/docker-sysconfig.conf  [Service]  Environment="HTTP_PROXY=http://www-proxy.us.abc.com:80/"  Environment="NO_PROXY=localhost,127.0.0.0/8,10.96.0.0/12,10.244.0.0/16"

後記

從文中可以看到,在k8s集群上,一個簡單的HTTP服務就有很長的訪問路徑,該路徑上的任何一處配置出現問題,外部就無法訪問該服務。雖然k8s集群自身提供了豐富的服務發現、負載均衡、故障自愈等功能,但如何用好k8s這個雲操作系統,不是一件簡單的事。

關於作者

許濤,曾供職過民航信息運行部、中國惠普性能優化團隊和Oracle系統架構和性能服務團隊,目前在Oracle公司數據庫研發部門工作。

參考

Kubernetes in Action

Kubernetes: troubleshooting ingress and services traffic flows 利用公有雲上的Kubernetes集群為單點應用提供高可用

雲上構建高可用實例——應用負載均衡