容器化单页面应用中Nginx反向代理与Kubernetes部署
在《容器化單頁(yè)面應(yīng)用中RESTful API的訪問(wèn)》一文中,我介紹了一個(gè)在容器化環(huán)境中單頁(yè)面應(yīng)用訪問(wèn)后端服務(wù)的完整案例。這里我將繼續(xù)使用這個(gè)案例,介紹一下容器化單頁(yè)面應(yīng)用部署的另一個(gè)場(chǎng)景:將Nginx的職責(zé)獨(dú)立出來(lái)。
注:這里單頁(yè)面應(yīng)用是值一個(gè)包含前端頁(yè)面、后端服務(wù)以及后臺(tái)數(shù)據(jù)庫(kù)的一個(gè)完整應(yīng)用系統(tǒng),這樣符合微服務(wù)模式對(duì)于服務(wù)的定義。不過(guò)為了介紹簡(jiǎn)單,文章案例不使用后臺(tái)數(shù)據(jù)庫(kù),而是將數(shù)據(jù)“寫死”在后端服務(wù)中。
繼續(xù)回顧一下上篇文章中的案例,我們有兩個(gè)服務(wù):前端單頁(yè)面應(yīng)用(client),以及后端基于ASP.NET Core Web API的RESTful服務(wù)(service),案例代碼地址是:https://github.com/daxnet/name-list。在這個(gè)案例中,前端單頁(yè)面應(yīng)用運(yùn)行在Nginx容器中,這里的Nginx同時(shí)還承擔(dān)了反向代理的角色,用以將前端頁(yè)面發(fā)出的RESTful API請(qǐng)求正確地轉(zhuǎn)發(fā)到ASP.NET Core Web API上。
如果整個(gè)系統(tǒng)只有這一個(gè)單頁(yè)面應(yīng)用,那么這么做是簡(jiǎn)單且合理的;但如果一個(gè)系統(tǒng)包含多個(gè)單頁(yè)面應(yīng)用,或者說(shuō)一個(gè)系統(tǒng)包含一個(gè)前端頁(yè)面與多個(gè)后臺(tái)服務(wù),那么,將Nginx反向代理的職責(zé)加到這個(gè)前端頁(yè)面的容器上,明顯是不合理的。為什么不合理?因?yàn)橐粋€(gè)系統(tǒng)有可能不僅僅有基于Web的UI,而且還有可能會(huì)有移動(dòng)客戶端,比如Andriod或者iOS的前端,甚至直接暴露API以供外部系統(tǒng)集成。如果運(yùn)行前端頁(yè)面的容器還兼職做反向代理的話,這些訪問(wèn)請(qǐng)求都將發(fā)送到前端單頁(yè)面應(yīng)用的服務(wù)器(容器)上,這樣就會(huì)對(duì)前端應(yīng)用造成壓力。
因此,一個(gè)更好的做法是,將Nginx的反向代理職責(zé)從前端頁(yè)面所運(yùn)行的Nginx容器中獨(dú)立出來(lái)。拓?fù)浣Y(jié)構(gòu)如下圖所示:
我們將從以下幾個(gè)方面對(duì)前文所述案例進(jìn)行配置調(diào)整:
簡(jiǎn)化前端應(yīng)用的Nginx配置
Nginx反向代理容器的創(chuàng)建
調(diào)整docker-compose.yml文件
簡(jiǎn)化前端應(yīng)用的Nginx配置
在之前的案例中,前端應(yīng)用的Nginx配置中還包含了反向代理的配置,這部分內(nèi)容現(xiàn)在可以拿掉了,于是,前端應(yīng)用的Nginx配置就非常簡(jiǎn)單了,只需要使用默認(rèn)的靜態(tài)頁(yè)面服務(wù)配置即可,例如:
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中完成前端頁(yè)面的編譯之后,將所有的資源復(fù)制到/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反向代理容器的創(chuàng)建
下一步就是創(chuàng)建一個(gè)Nginx反向代理的容器,基本思路是將反向代理配置到nginx.conf文件中,然后基于Nginx容器鏡像,將nginx.conf文件復(fù)制到容器中即可。nginx.conf文件內(nèi)容如下:
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; ????} } |
上面定義了兩個(gè)upstream,分別對(duì)應(yīng)應(yīng)用程序的前端和后端,然后根據(jù)不同的路徑規(guī)則分別將請(qǐng)求路由到不同的服務(wù)器上。在Dockerfile中,只需要將該配置文件復(fù)制到Nginx的配置路徑下即可:
1 2 3 | FROM nginx COPY nginx.conf /etc/nginx/nginx.conf CMD ["nginx", "-g", "daemon off;"] |
調(diào)整docker-compose.yml文件
我們需要相應(yīng)地調(diào)整docker-compose.yml文件,以便能夠方便地將這些服務(wù)運(yùn)行起來(lái)。docker-compose.yml文件非常簡(jiǎn)單:
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 |
對(duì)于namelist-service和namelist-client兩個(gè)服務(wù),我們沒(méi)有指定TCP端口,因?yàn)檫@兩個(gè)服務(wù)無(wú)需暴露出來(lái),namelist-nginx服務(wù)會(huì)通過(guò)容器鏈接(links)由docker的DNS來(lái)解析這兩個(gè)服務(wù)并在子網(wǎng)內(nèi)部訪問(wèn)。
下面我們測(cè)試一下整個(gè)應(yīng)用程序,使用下面的命令分別編譯docker鏡像,注意:編譯前先進(jìn)入client或service項(xiàng)目的根目錄下:
1 2 | $ docker build -t daxnet/namelist-client . $ docker build -t daxnet/namelist-service . |
然后,使用docker-compose up命令,啟動(dòng)所有服務(wù),并使用瀏覽器訪問(wèn)Nginx反向代理服務(wù)的/app路徑,得到如下結(jié)果:
目前無(wú)需糾結(jié)上圖中最后一個(gè)c415….是什么,它只不過(guò)是當(dāng)前服務(wù)端機(jī)器的機(jī)器名稱,在接下來(lái)Kubernetes部署階段,我們會(huì)通過(guò)實(shí)驗(yàn)來(lái)驗(yàn)證namelist-service服務(wù)在Kubernetes中的伸縮性。
接下來(lái),我們將name-list案例部署到Kubernetes上。在這里,我會(huì)使用Minikube來(lái)演示。Minikube是一套Kubernetes的最小集群,它只包含一個(gè)節(jié)點(diǎn),但對(duì)于我們學(xué)習(xí)和實(shí)驗(yàn)來(lái)說(shuō)已經(jīng)夠用。安裝Minikube過(guò)程也不是特別容易,尤其是在國(guó)內(nèi)的網(wǎng)絡(luò)環(huán)境中,我推薦使用阿里云提供的相關(guān)資源以及使用Oracle Virtual Box來(lái)作為Minikube的虛擬化環(huán)境,這樣安裝過(guò)程最簡(jiǎn)單。我的Minikube是安裝在Ubuntu 18.04的Linux機(jī)器上。
首先需要編寫Kubernetes的部署描述文件,可以使用Kubernetes官方的Kompose工具,它能夠幫助我們很方便地從docker-compose.yml文件生成Kubernetes的部署描述文件。對(duì)于name-list而言,我們已經(jīng)有docker-compose.yml文件了,因此,使用Kompose工具一鍵生成即可:
1 | $ kompose convert -o k8s.deployment.yaml |
這條命令會(huì)將所有的部署腳本(包括deployment,service等)輸出到同一個(gè)yaml文件中,如果不使用-o參數(shù),那么就會(huì)分別輸出到不同的文件中。但這都不是重點(diǎn)。重點(diǎn)是,我們還需要對(duì)生成的yaml文件進(jìn)行一些修改。
第一個(gè)需要修改的地方是,要將namelist-nginx的service類型指定為NodePort,這樣我們才可以使用Node IP來(lái)訪問(wèn)我們的應(yīng)用程序。Minikube不支持LoadBalancer類型的service,因此,在訪問(wèn)應(yīng)用程序之前,我們需要獲取Node IP。在上文中我提到,namelist-service和namelist-client無(wú)需暴露端口出來(lái),因?yàn)镹ginx反向代理會(huì)將外部請(qǐng)求轉(zhuǎn)發(fā)到這兩個(gè)服務(wù)上。然而,由于沒(méi)有暴露可訪問(wèn)的TCP端口,Kompose并不會(huì)對(duì)這兩個(gè)服務(wù)產(chǎn)生service的定義,這就需要我們自己添加到所產(chǎn)生的k8s.deployment.yaml文件中,只不過(guò)我們不需要指定service的類型,因?yàn)槲覀儾恍枰苯釉L問(wèn)它們。
準(zhǔn)備好部署文件之后,我們需要使用docker push命令,將namelist的三個(gè)docker鏡像推送到Docker Hub上。Minikube默認(rèn)會(huì)從Docker Hub上拉取鏡像進(jìn)行部署。這一步我就不多做說(shuō)明了。
接下來(lái),使用下面的命令將namelist應(yīng)用部署到Kubernetes上:
1 | $ kubectl apply -f k8s.deployment.yaml |
部署完成后,查看deployments、services和pods:
然后,使用kubectl cluster-info命令以獲得Node IP:
在瀏覽器中使用Node IP和Node Port來(lái)訪問(wèn)namelist應(yīng)用程序:
現(xiàn)在,將namelist-service擴(kuò)展到2個(gè)實(shí)例:
在瀏覽器中,反復(fù)刷新頁(yè)面,可以看到,頁(yè)面上顯示的機(jī)器名在變化,證明Kubernetes將API訪問(wèn)請(qǐng)求重定向到不同的namelist-service服務(wù)實(shí)例:
本文介紹了在namelist案例中,將Nginx反向代理職責(zé)從前端容器中獨(dú)立出來(lái)的設(shè)計(jì)與實(shí)現(xiàn),并介紹了Kubernetes部署的基本步驟和注意事項(xiàng)。基于namelist案例還可以繼續(xù)擴(kuò)展,比如使用HELM打包Kubernetes應(yīng)用,今后有機(jī)會(huì)我會(huì)繼續(xù)介紹。
本文源代碼可以參考:https://github.com/daxnet/name-list/tree/k8s-deployment。
原文:https://sunnycoding.cn/2019/07/27/reverse-proxy-in-containerized-spa-and-kubernetes-deployment/?
.NET社區(qū)新聞,深度好文,歡迎訪問(wèn)公眾號(hào)文章匯總?http://www.csharpkit.com?
總結(jié)
以上是生活随笔為你收集整理的容器化单页面应用中Nginx反向代理与Kubernetes部署的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: gRPC in ASP.NET Core
- 下一篇: Docker(二)-在Docker中部署