【APP】為Kubernetes叢集中服務部署Nginx入口服務

天府雲創發表於2017-03-03

這段日子,一直在搞與Kubernetes有關的東東:像什麼Kubernetes叢集搭建DNS外掛安裝和配置整合Ceph RBD持久卷Private Registry映象庫訪問等,這些都緣於正在開發的一個類PaaS小平臺的需要:“平臺雖小,五臟俱全”。整個平臺由Kubernetes叢集承載,對於K8s叢集內部的Service來說,目前還欠缺一個服務入口。之前的《Kubernetes叢集中的Nginx配置熱更新方案》一文實際上就是入口方案設計的一個前奏,而本文則是說明一下Nginx入口服務部署設計和實施過程中遇到的一些坑。

一、Nginx入口方案簡述

Nginx作為叢集入口服務,從功能上說,一般都是充當反向代理和負載均衡的角色。在我們這裡它更多是用於反向代理,因為負載均衡的事情“移交”給了K8s去實現了。k8s通過ClusterIP- 一種VIP機制,預設基於iptables的負載分擔實現服務請求的負載均衡(如iptable nat table的規則:-m statistic –mode random –probability 0.33332999982),檢視iptables nat鏈的rules,可以看到如下樣例:

# iptables -t nat -nL
... ...
Chain KUBE-SVC-UQG6736T32JE3S7H (2 references)
target     prot opt source               destination
KUBE-SEP-Z7UQLD332S673VAF  all  --  0.0.0.0/0            0.0.0.0/0            /* default/nginx-kit: */ statistic mode random probability 0.50000000000
KUBE-SEP-TWOIACCAJCPK3HWO  all  --  0.0.0.0/0            0.0.0.0/0            /* default/nginx-kit: */
... ..

接下來,我們簡單說說我們的Nginx入口方案。事先宣告:這絕對不是一個理想的方案,因為它還有諸多缺陷,只是在目前平臺需求上下文和資源的約束前提下,它可以作為我們的一個可用的過渡方案,方案示意圖如下:

img{512x368}

  • Nginx以Kubernetes service的形式執行於K8s cluster內部,並限制只能被K8s排程到帶有label: role=entry的Node上;
  • 最外層,通過DNS域名的輪詢機制,實現使用者請求在Node這一層上的“負載均衡”;
  • 訪問某個NodeIP:NodePort的請求,被轉發到Nginx ClusterIP: Port,並通過iptables nat的負載機制,分發到Nginx service的多個real endpoints上;
  • 位於real endpoint上的Nginx程式處理使用者請求,並根據配置,將請求proxy_pass到後端服務的ClusterIP:Port上,並最終由k8s實現將請求均衡分發到後端服務的endpoint。

二、Nginx入口服務部署

部署前,我們先來給執行Nginx Pod的Node打label:

# kubectl label node/10.47.136.60 role=entry
node "10.47.136.60" labeled

# kubectl label node/10.47.136.60 role=entry
node "10.47.136.60" labeled

# kubectl get nodes --show-labels
NAME            STATUS    AGE       LABELS
10.46.181.146   Ready     39d       beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.46.181.146,role=entry,zone=ceph
10.47.136.60    Ready     39d       beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.47.136.60,role=entry,zone=ceph

Nginx配置熱載入方案一文中,我們提到一個nginx pod中包含三個Container:nginx、nginx-conf-generator和init container,Nginx service的yaml示例如下:

//nginx-kit.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-kit
spec:
  replicas: 2
  template:
    metadata:
      labels:
        run: nginx-kit
      annotations:
        pod.beta.kubernetes.io/init-containers: '[
          {
               "name": "nginx-kit-init-container",
               "image": "registry.cn-beijing.aliyuncs.com/xxxx/nginx-conf-generator",
               "imagePullPolicy": "IfNotPresent",
               "command": ["/root/conf-generator/nginx-conf-gen", "-mode", "gen-once"],
               "volumeMounts": [
                   {
                      "name": "conf-volume",
                      "mountPath": "/etc/nginx/conf.d"
                   }
               ]
          }
        ]'
    spec:
      containers:
      - name: nginx-conf-generator
        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: conf-volume
        image: registry.cn-beijing.aliyuncs.com/xxxx/nginx-conf-generator:latest
        imagePullPolicy: IfNotPresent
      - name: xxxx-nginx
        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: conf-volume
        image: registry.cn-hangzhou.aliyuncs.com/xxxx/nginx:latest
        imagePullPolicy: IfNotPresent
        command: ["/home/auto-reload-nginx.sh"]
        ports:
        - containerPort: 80
      volumes:
      - name: conf-volume
        emptyDir: {}
      nodeSelector:
        role: entry
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-kit
  labels:
    run: nginx-kit
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 28888
    protocol: TCP
  selector:
    run: nginx-kit

關於這個yaml,有幾點我們是必須要說說的:

1、關於init container

通過上述yaml檔案內容,我們可以看到init container和nginx-conf-generator container都是基於同一映象建立的,只是工作mode不同罷了。在deployment描述檔案中,init container的描述需要放在deployment.spec.template.metadata下面,而不是deployment的metadata下面。如果按照後者編寫,那麼init container將不會被建立和啟動,nginx container啟動後也就會提示:找不到”default.conf”。

另外,雖然源自同一個image,但init container啟動時卻提示在$PATH裡找不到名為”-mode”的可執行程式,顯然init container中的ENTRYPOINT並不起作用,nginx-conf-generator的Dockerfile節選如下:

//Dockerfile
From ubuntu:14.04
... ...
ENTRYPOINT ["/root/conf-generator/nginx-conf-gen"]

為此我們在init container的”command”命令引數中增加了可執行程式全路徑以供container執行:

 "command" : ["/root/conf-generator/nginx-conf-gen", "-mode", "gen-once"],

最後,通過上面yaml檔案建立nginx-kit服務依舊要用kubectl apply,而不是kubectl create,否則init container不會被理會。

2、關於nginx conf模板

由於種種原因,當前我們是通過server host的location path來對映後端cluster中的不同Service的,nginx default.conf模板如下:

server {
    listen 80;
    #server_name opp.neusoft.com;

    {{range .}}
    location {{.Path}} {
        proxy_pass http://{{.ClusterIP}}:{{.Port}}/;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    {{end}}

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

這裡要注意的是proxy_pass directive後面值的寫法,如果你選擇這樣寫:

proxy_pass http://{{.ClusterIP}}:{{.Port}};

那麼當訪問某個路徑時,比如:localhost/volume/api/v1/pools時,nginx後端的Service收到的url訪問路徑將是:/volume/api/v1/pools,volume這個location path並不能被去除,後端的Service在做路由匹配時基本都是會出錯的。fix的方法是賦予proxy_pass directive下面這樣的值:

proxy_pass http://{{.ClusterIP}}:{{.Port}}/;

沒錯,在最後加上一個”/”,這樣nginx所反向代理的Service將會收到/api/v1/pools這樣的訪問URl路徑。

© 2016~2018, Enweitech. 版權所有.

相關文章