這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
這段日子,一直在搞與Kubernetes有關的東東:像什麼Kubernetes叢集搭建、DNS外掛程式安裝和配置、整合Ceph RBD持久卷、Private Registry鏡像庫訪問等,這些都緣於正在開發的一個類PaaS小平台的需要:“平台雖小,五髒俱全”。整個平台由Kubernetes叢集承載,對於K8s叢集內部的Service來說,目前還欠缺一個服務入口。之前的《Kubernetes叢集中的Nginx配置熱更新方案》一文實際上就是入口方案設計的一個前奏,而本文則是說明一下Nginx入口服務部署設計和實施過程中遇到的一些坑。
一、Nginx入口方案簡述
Nginx作為叢集入口服務,從功能上說,一般都是充當反向 Proxy和負載平衡的角色。在我們這裡它更多是用於反向 Proxy,因為負載平衡的事情“移交”給了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 destinationKUBE-SEP-Z7UQLD332S673VAF all -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx-kit: */ statistic mode random probability 0.50000000000KUBE-SEP-TWOIACCAJCPK3HWO all -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx-kit: */... ..
接下來,我們簡單說說我們的Nginx入口方案。事先聲明:這絕對不是一個理想的方案,因為它還有諸多缺陷,只是在目前平台需求上下文和資源的約束前提下,它可以作為我們的一個可用的過渡方案,方案如下:
- 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=entrynode "10.47.136.60" labeled# kubectl label node/10.47.136.60 role=entrynode "10.47.136.60" labeled# kubectl get nodes --show-labelsNAME STATUS AGE LABELS10.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=ceph10.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.yamlapiVersion: extensions/v1beta1kind: Deploymentmetadata: name: nginx-kitspec: 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: v1kind: Servicemetadata: name: nginx-kit labels: run: nginx-kitspec: 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節選如下:
//DockerfileFrom 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所反向 Proxy的Service將會收到/api/v1/pools這樣的訪問URl路徑。
2016, bigwhite. 著作權.