容器化单页面应用中Nginx反向代理与Kubernetes部署
在《容器化單頁面應用中RESTful API的訪問》一文中,我介紹了一個在容器化環境中單頁面應用訪問后端服務的完整案例。這里我將繼續使用這個案例,介紹一下容器化單頁面應用部署的另一個場景:將Nginx的職責獨立出來。
注:這里單頁面應用是值一個包含前端頁面、后端服務以及后臺數據庫的一個完整應用系統,這樣符合微服務模式對于服務的定義。不過為了介紹簡單,文章案例不使用后臺數據庫,而是將數據“寫死”在后端服務中。
繼續回顧一下上篇文章中的案例,我們有兩個服務:前端單頁面應用(client),以及后端基于ASP.NET Core Web API的RESTful服務(service),案例代碼地址是:https://github.com/daxnet/name-list。在這個案例中,前端單頁面應用運行在Nginx容器中,這里的Nginx同時還承擔了反向代理的角色,用以將前端頁面發出的RESTful API請求正確地轉發到ASP.NET Core Web API上。
如果整個系統只有這一個單頁面應用,那么這么做是簡單且合理的;但如果一個系統包含多個單頁面應用,或者說一個系統包含一個前端頁面與多個后臺服務,那么,將Nginx反向代理的職責加到這個前端頁面的容器上,明顯是不合理的。為什么不合理?因為一個系統有可能不僅僅有基于Web的UI,而且還有可能會有移動客戶端,比如Andriod或者iOS的前端,甚至直接暴露API以供外部系統集成。如果運行前端頁面的容器還兼職做反向代理的話,這些訪問請求都將發送到前端單頁面應用的服務器(容器)上,這樣就會對前端應用造成壓力。
因此,一個更好的做法是,將Nginx的反向代理職責從前端頁面所運行的Nginx容器中獨立出來。拓撲結構如下圖所示:
我們將從以下幾個方面對前文所述案例進行配置調整:
簡化前端應用的Nginx配置
Nginx反向代理容器的創建
調整docker-compose.yml文件
簡化前端應用的Nginx配置
在之前的案例中,前端應用的Nginx配置中還包含了反向代理的配置,這部分內容現在可以拿掉了,于是,前端應用的Nginx配置就非常簡單了,只需要使用默認的靜態頁面服務配置即可,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | events { ????worker_connections 1024; } http { ????server { ??????listen??????? 80; ??????server_name?? localhost; ??????include? /etc/nginx/mime.types; ??????location / { ????????root /usr/share/nginx/html; ????????index? index.html? index.htm; ??????} ????} } |
因此,在docker中完成前端頁面的編譯之后,將所有的資源復制到/usr/share/nginx/html下即可。前端Dockerfile如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | FROM nginx AS base WORKDIR /app EXPOSE 80 FROM node:10.16.0-alpine AS build RUN npm install -g @angular/cli@8.0.3 WORKDIR /src COPY . . RUN npm install RUN ng build --prod --output-path /app FROM base AS final COPY --from=build /app /usr/share/nginx/html COPY --from=build /src/nginx.conf /etc/nginx/nginx.conf CMD ["nginx", "-g", "daemon off;"] |
Nginx反向代理容器的創建
下一步就是創建一個Nginx反向代理的容器,基本思路是將反向代理配置到nginx.conf文件中,然后基于Nginx容器鏡像,將nginx.conf文件復制到容器中即可。nginx.conf文件內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | events { ????worker_connections 1024; } http { ????server { ??????listen??????? 80; ??????server_name?? localhost; ??????include? /etc/nginx/mime.types; ??????location / { ????????root /usr/share/nginx/html; ????????index? index.html? index.htm; ??????} ??????location /app { ??????} ??????location ~ ^/name-service/(.*)$ { ????????rewrite ^ $request_uri; ????????rewrite ^/name-service/(.*)$ $1 break; ????????return 400; ??????} ????} ????upstream namelistsvc { ??????server namelist-service:5000; ????} ????upstream namelistcli { ??????server namelist-client:80; ????} } |
上面定義了兩個upstream,分別對應應用程序的前端和后端,然后根據不同的路徑規則分別將請求路由到不同的服務器上。在Dockerfile中,只需要將該配置文件復制到Nginx的配置路徑下即可:
1 2 3 | FROM nginx COPY nginx.conf /etc/nginx/nginx.conf CMD ["nginx", "-g", "daemon off;"] |
調整docker-compose.yml文件
我們需要相應地調整docker-compose.yml文件,以便能夠方便地將這些服務運行起來。docker-compose.yml文件非常簡單:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | version: '3' services: ??namelist-service: ????image: daxnet/namelist-service ??namelist-client: ????image: daxnet/namelist-client ??namelist-nginx: ????image: daxnet/namelist-nginx ????ports: ??????- 80:80 ????links: ??????- namelist-service ??????- namelist-client |
對于namelist-service和namelist-client兩個服務,我們沒有指定TCP端口,因為這兩個服務無需暴露出來,namelist-nginx服務會通過容器鏈接(links)由docker的DNS來解析這兩個服務并在子網內部訪問。
下面我們測試一下整個應用程序,使用下面的命令分別編譯docker鏡像,注意:編譯前先進入client或service項目的根目錄下:
1 2 | $ docker build -t daxnet/namelist-client . $ docker build -t daxnet/namelist-service . |
然后,使用docker-compose up命令,啟動所有服務,并使用瀏覽器訪問Nginx反向代理服務的/app路徑,得到如下結果:
目前無需糾結上圖中最后一個c415….是什么,它只不過是當前服務端機器的機器名稱,在接下來Kubernetes部署階段,我們會通過實驗來驗證namelist-service服務在Kubernetes中的伸縮性。
接下來,我們將name-list案例部署到Kubernetes上。在這里,我會使用Minikube來演示。Minikube是一套Kubernetes的最小集群,它只包含一個節點,但對于我們學習和實驗來說已經夠用。安裝Minikube過程也不是特別容易,尤其是在國內的網絡環境中,我推薦使用阿里云提供的相關資源以及使用Oracle Virtual Box來作為Minikube的虛擬化環境,這樣安裝過程最簡單。我的Minikube是安裝在Ubuntu 18.04的Linux機器上。
首先需要編寫Kubernetes的部署描述文件,可以使用Kubernetes官方的Kompose工具,它能夠幫助我們很方便地從docker-compose.yml文件生成Kubernetes的部署描述文件。對于name-list而言,我們已經有docker-compose.yml文件了,因此,使用Kompose工具一鍵生成即可:
1 | $ kompose convert -o k8s.deployment.yaml |
這條命令會將所有的部署腳本(包括deployment,service等)輸出到同一個yaml文件中,如果不使用-o參數,那么就會分別輸出到不同的文件中。但這都不是重點。重點是,我們還需要對生成的yaml文件進行一些修改。
第一個需要修改的地方是,要將namelist-nginx的service類型指定為NodePort,這樣我們才可以使用Node IP來訪問我們的應用程序。Minikube不支持LoadBalancer類型的service,因此,在訪問應用程序之前,我們需要獲取Node IP。在上文中我提到,namelist-service和namelist-client無需暴露端口出來,因為Nginx反向代理會將外部請求轉發到這兩個服務上。然而,由于沒有暴露可訪問的TCP端口,Kompose并不會對這兩個服務產生service的定義,這就需要我們自己添加到所產生的k8s.deployment.yaml文件中,只不過我們不需要指定service的類型,因為我們不需要直接訪問它們。
準備好部署文件之后,我們需要使用docker push命令,將namelist的三個docker鏡像推送到Docker Hub上。Minikube默認會從Docker Hub上拉取鏡像進行部署。這一步我就不多做說明了。
接下來,使用下面的命令將namelist應用部署到Kubernetes上:
1 | $ kubectl apply -f k8s.deployment.yaml |
部署完成后,查看deployments、services和pods:
然后,使用kubectl cluster-info命令以獲得Node IP:
在瀏覽器中使用Node IP和Node Port來訪問namelist應用程序:
現在,將namelist-service擴展到2個實例:
在瀏覽器中,反復刷新頁面,可以看到,頁面上顯示的機器名在變化,證明Kubernetes將API訪問請求重定向到不同的namelist-service服務實例:
本文介紹了在namelist案例中,將Nginx反向代理職責從前端容器中獨立出來的設計與實現,并介紹了Kubernetes部署的基本步驟和注意事項。基于namelist案例還可以繼續擴展,比如使用HELM打包Kubernetes應用,今后有機會我會繼續介紹。
本文源代碼可以參考:https://github.com/daxnet/name-list/tree/k8s-deployment。
原文:https://sunnycoding.cn/2019/07/27/reverse-proxy-in-containerized-spa-and-kubernetes-deployment/?
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結
以上是生活随笔為你收集整理的容器化单页面应用中Nginx反向代理与Kubernetes部署的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gRPC in ASP.NET Core
- 下一篇: Docker(二)-在Docker中部署