做軟件的人:“工作體驗(yàn)好,好事才能來(lái)荣月」芎牵”
1 太長(zhǎng)不讀
從2023年8月到10月,我花了3個(gè)月自學(xué)docker和k8s喉童。踩了一路坑撇寞,到10月22日終于把一個(gè)帶有vue.js 3前端、spring boot后端以及postgres數(shù)據(jù)庫(kù)的shopping list web app堂氯,部署到azure k8s service云上蔑担,并能正常運(yùn)行。
之所以說(shuō)踩了一路坑咽白,是因?yàn)榫W(wǎng)上分享的k8s部署web app的樣例啤握,都是部署一個(gè)web服務(wù)。講ingress nginx controller的樣例雖然會(huì)涉及兩個(gè)微服務(wù)晶框,但在這種根據(jù)path設(shè)定將請(qǐng)求分配給兩個(gè)hello world的web微服務(wù)場(chǎng)景中排抬,兩個(gè)微服務(wù)之間,是沒(méi)有前后端之間的依賴關(guān)系的授段。另外前后端之間的CORS跨源資源共享該如何解決蹲蒲,也找不到我這種前后端分離的web app場(chǎng)景下的直接資料,只能自己摸索侵贵。
在爬出坑后届搁,很愿意寫一系列避坑指南文章分享給大家喊废。雖然不知小伙伴們是否愿意讀燥透,但我很想把這一系列文章截亦,寫成網(wǎng)上通過(guò)實(shí)例講docker和k8s入門的最好的文章融涣,而且每年至少更新一次。
這一系列文章的目標(biāo)讀者表锻,是對(duì)docker或k8s不太熟悉的做軟件的人恕齐,不僅包括常寫代碼的程序員,也包括不常寫代碼的測(cè)試工程師和運(yùn)維工程師瞬逊,如圖1所示显歧。
K8s和Docker能解決做軟件的人的什么痛點(diǎn)码耐?這也是我為啥要花這么多時(shí)間寫這一系列文章的原因追迟。因?yàn)閗8s和Docker可以讓咱們做軟件的人,能體驗(yàn)更好地做軟件骚腥。
“工作體驗(yàn)好敦间,好事才能來(lái)∈”
想想咱們做軟件的人常說(shuō)的下面幾句話廓块。
“在我這運(yùn)行得好好的,怎么你那兒不行契沫?” docker image能將代碼的所有依賴庫(kù)都打包到一起带猴,并能讓代碼在容器中獨(dú)立運(yùn)行。這樣就能實(shí)現(xiàn)你在測(cè)試環(huán)境中所測(cè)試的image懈万,就是你在生產(chǎn)環(huán)境所部署的拴清,從而能在很大程度上解決因?yàn)橐蕾噹?kù)在不同環(huán)境下的差異,而導(dǎo)致這里能運(yùn)行会通,那里不能運(yùn)行的問(wèn)題口予。
“這是誰(shuí)改了配置又不告訴大家?” Docker和k8s都強(qiáng)調(diào)基礎(chǔ)設(shè)施即代碼涕侈,即配置不是靠做軟件的人拍腦袋臨時(shí)手工敲的沪停,而是靠寫成與代碼同等地位的配置文件,通過(guò)團(tuán)隊(duì)代碼評(píng)審裳涛,保存到版本庫(kù)中木张,并讓機(jī)器執(zhí)行。這樣能讓配置的更改廣而告之端三,配置的執(zhí)行有據(jù)可查舷礼。同時(shí)也便于讓機(jī)器讀取,自動(dòng)執(zhí)行郊闯,而無(wú)須手工一遍一遍敲同樣的命令且轨。
“測(cè)試環(huán)境太少得排隊(duì)使用浮声。” 有了本地docker compose旋奢,做軟件的人可以利用其占用存儲(chǔ)空間小,運(yùn)行速度快的特點(diǎn)然痊,在本地電腦以docker image的方式至朗,最大限度模擬生產(chǎn)環(huán)境的方式,測(cè)試要發(fā)布的軟件剧浸,而無(wú)須排隊(duì)等公司共享的測(cè)試環(huán)境锹引。這樣能更早地發(fā)現(xiàn)bug,減少因很晚才發(fā)現(xiàn)所導(dǎo)致的大量返工成本唆香。同樣嫌变,在內(nèi)部使用了k8s云集群的企業(yè),也能利用云的多租戶特點(diǎn)躬它,快速為需要測(cè)試環(huán)境的做軟件的人腾啥,分配測(cè)試環(huán)境,從而解決測(cè)試環(huán)境少的問(wèn)題冯吓。
可見(jiàn)倘待,Docker和k8s能讓咱們做軟件的人,工作體驗(yàn)更好组贺,好事才更能來(lái)凸舵。
“為何選用Shopping List Web app作為樣例項(xiàng)目?”這個(gè)樣例源自我在自學(xué)vue.js時(shí)所學(xué)的待辦清單todo list app樣例失尖,如圖2所示啊奄。我把todo list改造為shopping list。兩者的功能近似掀潮,都是為用戶提供一個(gè)備忘清單菇夸,有一個(gè)web界面可以增刪改查。這容易理解胧辽。此外峻仇,這個(gè)樣例能代表前后端分離的web app典型架構(gòu)。另外邑商,這個(gè)樣例能表現(xiàn)最小化的云原生微服務(wù)之間的依賴關(guān)系摄咆,比如前端微服務(wù)依賴后端微服務(wù),而后端微服務(wù)又依賴于數(shù)據(jù)庫(kù)微服務(wù)人断。這便于學(xué)習(xí)如何使用新興的故障注入實(shí)驗(yàn)工具吭从,進(jìn)行混沌工程實(shí)踐。
“我對(duì)java和vue.js不熟恶迈,能讀懂這一系列文章嗎涩金?”能谱醇。因?yàn)槲恼碌拇a命名寫得足夠表意,一看就懂步做。另外副渴,這一系列文章不涉及前后端具體的編程,而重點(diǎn)關(guān)注如何把開(kāi)發(fā)好的代碼用docker打成image全度,并部署到本地docker compose和k8s云集群上煮剧。這些都與前后端所使用的編程語(yǔ)言關(guān)系不大,所以文章內(nèi)容適用于所有使用JSON/HTTP協(xié)議的前后端分離的web app的技術(shù)棧将鸵。
“我不會(huì)編程勉盅,能讀懂這一系列文章嗎?”能顶掉。因?yàn)槲恼虏簧婕扒昂蠖斯δ艿拇a編寫草娜,而主要涉及配置文件和命令行工具的使用,適合程序員痒筒、測(cè)試工程師和運(yùn)維工程師閱讀宰闰。
“我是做測(cè)試或運(yùn)維的,還需要按照文章的描述凸克,在本地開(kāi)發(fā)環(huán)境里跑通嗎议蟆?”需要。因?yàn)樵诒疚乃枋龅谋芸拥倪^(guò)程中萎战,你會(huì)發(fā)現(xiàn)之前代碼中的配置有問(wèn)題咐容。當(dāng)你需要在源代碼里更改配置,并重新構(gòu)建docker image時(shí)蚂维,你就需要知道如何操作戳粒。
這一系列文章的第一篇,會(huì)針對(duì)macOS虫啥、Windows10和Ubuntu這3種操作系統(tǒng)蔚约,分別推出3個(gè)版本。
這一系列文章可以分為三篇涂籽。這三篇的標(biāo)題如下:
K8s部署前后端分離的web應(yīng)用避坑系列指南之一:在本地開(kāi)發(fā)環(huán)境苹祟、本地docker compose和k8s云集群里跑通購(gòu)物清單應(yīng)用(macOS/Windows10/Ubuntu-2023版分別寫)
K8s部署前后端分離的web應(yīng)用避坑系列指南之二:解讀購(gòu)物清單應(yīng)用Dockerfile和docker-compose.yml文件
K8s部署前后端分離的web應(yīng)用避坑系列指南之三:解讀購(gòu)物清單應(yīng)用k8s的deployment、service和ingress配置文件
這一系列指南相關(guān)的源代碼在這里下載:https://github.com/wubin28/shopping-list-web-app评雌。
要想找到這一系列文章的最新版本树枫,可以在知乎搜“體驗(yàn)更好地做軟件”專欄。
2 深入閱讀
注意景东,本文一萬(wàn)三千字砂轻。分享了8個(gè)避坑指南。需要拿著mac邊讀邊練斤吐。沒(méi)點(diǎn)決心堅(jiān)持不下來(lái)搔涝。慎入厨喂。
2.1 需求描述
這一系列文章所選用的web app,是一個(gè)購(gòu)物清單shopping list web app庄呈。它的用戶是有購(gòu)物需求的顧客小吾蜕煌。一天,小吾發(fā)現(xiàn)家里的瓶裝水快沒(méi)了抒痒,就想著晚上下班路過(guò)超市時(shí)幌绍,順便買幾瓶。但這時(shí)老板來(lái)微信喊他開(kāi)會(huì)故响。他很快就把買瓶裝水的事情忘掉干干凈凈。等下班路過(guò)超市颁独,他光顧著給老婆買打折的巧克力彩届。等到家要喝水了,發(fā)現(xiàn)水沒(méi)買誓酒。咱們這個(gè)shopping list web app樟蠕,就能為小吾解決上面的痛點(diǎn)。當(dāng)他想要買水時(shí)靠柑,就可以馬上在app里添加一條買水的購(gòu)物項(xiàng)寨辩。過(guò)了一會(huì)兒又想買點(diǎn)香蕉,那就再加一條歼冰。等他到了超市靡狞,再查看一下這個(gè)清單,要買的東西就不會(huì)忘了隔嫡。
2.2 從源代碼開(kāi)始分三步部署到k8s
現(xiàn)在咱們有了這一系列指南相關(guān)的源代碼甸怕。該如何將它部署到k8s呢?
可以分三步:
第一步腮恩,在本地開(kāi)發(fā)環(huán)境里跑通梢杭;
第二步,在本地docker compose里跑通秸滴;
第三步武契,在k8s云集群里跑通。
為何不能一次就從源代碼直接部署到k8s呢荡含?當(dāng)然這樣做也可以咒唆,但前提是你確信部署上去后,將來(lái)再也沒(méi)有新需求或修bug而去修改源代碼并重新部署内颗。對(duì)于坑坑洼洼的docker和k8s學(xué)習(xí)之旅钧排,你覺(jué)得這可能嗎?
所以你需要知道當(dāng)新需求來(lái)了或要修bug時(shí)均澳,該如何把修改過(guò)的代碼恨溜,在本地開(kāi)發(fā)環(huán)境里調(diào)試通符衔。這是進(jìn)行第一輪自測(cè)。畢竟糟袁,本地電腦是你的地盤兒判族。在本地電腦上調(diào)試程序,比在k8w云集群里要方便得多项戴。這是第一步的意義形帮。
之后,你需要知道如何將通過(guò)了第一輪自測(cè)的代碼周叮,構(gòu)建成docker image辩撑,并在本地docker compose里跑通,為之后將docker image部署到k8s做第二輪自測(cè)仿耽。畢竟合冀,本地docker compose也在你的地盤兒上。這是第二步的意義项贺。
最后君躺,你需要知道如何將通過(guò)了第二輪自測(cè)的docker image,部署到k8s云集群并跑通开缎,為之后部署到生產(chǎn)k8s云集群環(huán)境做第三輪自測(cè)棕叫。這個(gè)項(xiàng)目的k8s云集群,我選用了微軟的azure k8s service奕删,免費(fèi)使用1個(gè)月俺泣。這也算是我的地盤兒。在這里自測(cè)急侥,會(huì)比在運(yùn)維團(tuán)隊(duì)地盤兒里的生產(chǎn)k8s云集群環(huán)境砌滞,要方便多了。這是第三步的意義坏怪。
2.3 在本地開(kāi)發(fā)環(huán)境里跑通
2.3.1 在本地開(kāi)發(fā)環(huán)境里的架構(gòu)
Shopping List Web App在本地開(kāi)發(fā)環(huán)境里的架構(gòu)贝润,如果用c4 model(https://c4model.com/)畫(huà)出來(lái),就如圖3和圖4所示铝宵。
圖3是站在整個(gè)web app的邊界打掘,向外看的context圖。在系統(tǒng)外鹏秋,有user和admin這兩種用戶在使用系統(tǒng)尊蚁。User使用系統(tǒng)來(lái)管理購(gòu)物清單。Admin使用系統(tǒng)來(lái)管理購(gòu)物清單數(shù)據(jù)侣夷。
圖4是站在整個(gè)web app的邊界横朋,向內(nèi)看的container圖。在系統(tǒng)內(nèi)百拓,有4個(gè)容器琴锭。注意c4 model里的container的概念晰甚,和docker的container的概念,是不同的决帖。前者是代表架構(gòu)圖中運(yùn)行的應(yīng)用或數(shù)據(jù)存儲(chǔ)系統(tǒng)厕九,后者代表封裝了所有代碼和依賴庫(kù)能獨(dú)立運(yùn)行的軟件運(yùn)行單元。User通過(guò)前端shopping-list-front-end來(lái)查看和修改購(gòu)物清單地回。而前端shopping-list-front-end將用戶對(duì)購(gòu)物清單的操作請(qǐng)求扁远,發(fā)給后端shopping-list-api。后端shopping-list-api再訪問(wèn)數(shù)據(jù)庫(kù)postgres查詢和更新數(shù)據(jù)刻像。Admin通過(guò)使用pgadmin數(shù)據(jù)庫(kù)管理工具來(lái)直接管理postgres數(shù)據(jù)庫(kù)中的數(shù)據(jù)畅买。
2.3.2 本地開(kāi)發(fā)環(huán)境準(zhǔn)備
我所使用的Mac,是Apple M1 Pro细睡,32G內(nèi)存皮获。對(duì)于在本機(jī)跑docker compose和前后端應(yīng)用,只要不同時(shí)開(kāi)著intellij idea和webstorm纹冤,8G內(nèi)存應(yīng)該是夠用了。
[小心坑购公!不要直接使用官網(wǎng)安裝包安裝工具]
我之前安裝jdk的習(xí)慣萌京,一直是先確定要裝哪個(gè)版本的jdk,比如jdk11宏浩,然后在oracle官網(wǎng)上找到j(luò)dk11的下載頁(yè)面知残,下載對(duì)應(yīng)操作系統(tǒng)的安裝包,然后解壓或安裝比庄。
但后來(lái)發(fā)現(xiàn)求妹,jdk版本更新得很頻繁。如果想用現(xiàn)在的主流版本jdk17佳窑,就得再?gòu)墓倬W(wǎng)下載并安裝jdk17制恍,然后手工在/.zshrc或/.bashrc里修改JAVA_HOME和PATH環(huán)境變量。這樣才能從jdk11切換到j(luò)dk17神凑。如果有老舊項(xiàng)目又需要用jdk11净神,又得手工修改環(huán)境變量。這太累了溉委。
而像git這樣的工具鹃唯,雖然版本更新得不那么頻繁,如果你也是從官網(wǎng)下載安裝包安裝瓣喊,等過(guò)了幾個(gè)月坡慌,想升級(jí)版本時(shí),經(jīng)常會(huì)忘記當(dāng)初是如何安裝的藻三,導(dǎo)致難以卸載并重新安裝洪橘。
所以像jdk和node.js甚至git這樣的工具跪者,一般情況下不建議直接從官網(wǎng)下載安裝包安裝,而是使用熱門的包管理器來(lái)安裝梨树。這樣當(dāng)要切換同一工具的不同版本坑夯、升級(jí)版本和卸載時(shí),就方便多了抡四。
如果你在macOS上的git柜蜈、jdk和node.js/npm之前是直接使用官網(wǎng)安裝包安裝的,而沒(méi)有使用包管理器來(lái)安裝指巡,那么推薦你設(shè)法把它們先卸載淑履,然后使用下面的包管理器來(lái)安裝。否則藻雪,你就要冒因工具版本與我所用的不一致秘噪,而導(dǎo)致各種問(wèn)題的風(fēng)險(xiǎn)。而我下面描述的工具版本勉耀,是經(jīng)過(guò)我測(cè)試過(guò)了的指煎。
用包管理器homebrew安裝文件版本管理工具git 2.42.0以便下載本項(xiàng)目代碼
安裝homebrew方法參見(jiàn):https://brew.sh/。我用的homebrew版本是4.1.16便斥。
安裝git:brew install git
至壤,參見(jiàn):https://formulae.brew.sh/formula/git。
驗(yàn)證git是否工作:運(yùn)行命令git -v
枢纠,我用的git版本是2.42.0像街。
下載代碼:運(yùn)行git clone https://github.com/wubin28/shopping-list-web-app.git
,就能把代碼下載到項(xiàng)目文件夾shoppling-list-web-app
中晋渺。
以后就把shoppling-list-web-app
叫做項(xiàng)目文件夾镰绎。
進(jìn)入到這個(gè)文件夾,運(yùn)行命令ls -alF
木西,你會(huì)看到畴栖,這個(gè)文件夾里有3個(gè)子文件夾。
drwx------ 15 binwu staff 480 Oct 23 16:22 back-end/
drwx------ 23 binwu staff 736 Oct 23 13:47 front-end/
drwx------ 3 binwu staff 96 Oct 23 08:27 infrastructure/
其中户魏,
infrastructure文件夾存放了運(yùn)行docker compose和k8s的配置文件驶臊,如docker-compose.yml
。
back-end存放了后端代碼叼丑、后端Dockerfile和其他配置文件关翎。
front-end存放了前端代碼、前端Dockerfile和其他配置文件鸠信。
Dockerfile是一種配置文件纵寝,用于把源代碼構(gòu)建為docker image,以便以容器化的方式進(jìn)行部署。
用包管理器sdkman安裝后端開(kāi)發(fā)工具jdk 17.0.8.1-tem以便在本地進(jìn)行后端構(gòu)建
安裝sdkman方法參見(jiàn):https://sdkman.io/install爽茴。我用的sdkman的版本是script: 5.18.2葬凳,native: 0.4.2。
安裝jdk:sdk install java
室奏。
查看所安裝的jdk版本:sdk list java
火焰。
使用所安裝的jdk版本:sdk use java 17.0.8.1-tem
,參見(jiàn):https://sdkman.io/usage胧沫。
驗(yàn)證jdk是否工作:java -version
昌简。我用的jdk版本是openjdk version "17.0.8.1" 2023-08-24。
用包管理器nvm安裝前端工具node.js和npm以便在本地進(jìn)行前端構(gòu)建
安裝nvm方法參見(jiàn):https://github.com/nvm-sh/nvm绒怨。我用的nvm版本是0.39.5纯赎。
安裝node.js/npm:nvm install --lts
。
驗(yàn)證前端工具node.js是否工作:node -v
南蹂。我用的node.js版本是v18.18.0犬金。
驗(yàn)證前端構(gòu)建工具npm是否工作:npm -v
。我用的npm版本是9.8.1六剥。
安裝docker desktop以便用容器方式運(yùn)行postgres數(shù)據(jù)庫(kù)及其管理工具
參見(jiàn):https://docs.docker.com/desktop/install/mac-install/晚顷。我用的docker desktop for macOS版本是v4.24.2。
驗(yàn)證docker desktop是否工作:看docker desktop是否能正常啟動(dòng)疗疟。
2.3.3 在本地開(kāi)發(fā)環(huán)境里跑通shopping list web app
啟動(dòng)docker desktop
在容器中運(yùn)行postgres數(shù)據(jù)庫(kù)和能查看數(shù)據(jù)庫(kù)中數(shù)據(jù)的pgadmin以便在本地開(kāi)發(fā)環(huán)境里運(yùn)行g(shù)radle構(gòu)建和測(cè)試
[小心坑音同!不要再使用官網(wǎng)安裝包安裝數(shù)據(jù)庫(kù)和管理工具]
在實(shí)現(xiàn)新功能和修bug的時(shí)候,如果能在本地運(yùn)行一個(gè)數(shù)據(jù)庫(kù)和數(shù)據(jù)庫(kù)管理工具秃嗜,就能很方便地進(jìn)行自測(cè)。你當(dāng)然可以從官網(wǎng)下載數(shù)據(jù)庫(kù)和管理工具的安裝包顿膨,在本地電腦上安裝锅锨。但如前面安裝jdk類似,將來(lái)卸載或升級(jí)恋沃,會(huì)比較麻煩必搞。在容器化的時(shí)代,如果想使用數(shù)據(jù)庫(kù)及其管理工具囊咏,你完全可以從http://hub.docker.com(又叫Docker hub)上恕洲,下載數(shù)據(jù)庫(kù)和管理工具的docker image文件,然后在本地電腦用簡(jiǎn)單的一行命令梅割,啟動(dòng)相應(yīng)的容器霜第,來(lái)使用數(shù)據(jù)庫(kù)及其管理工具。將來(lái)卸載或升級(jí)户辞,也是運(yùn)行一行命令的事兒泌类,多方便。
有人會(huì)問(wèn):容器里跑數(shù)據(jù)庫(kù)底燎,要是關(guān)閉或刪除容器刃榨,那數(shù)據(jù)不就丟了弹砚?其實(shí)不用擔(dān)心,你可以為數(shù)據(jù)庫(kù)容器設(shè)置一個(gè)位于本地硬盤中的volume枢希,以便保存持久化的數(shù)據(jù)桌吃。只要你不刪除這個(gè)volume,數(shù)據(jù)庫(kù)容器關(guān)閉后再啟動(dòng)苞轿,仍然能夠獲取之前的數(shù)據(jù)茅诱。
在本地開(kāi)發(fā)環(huán)境里跑通shopping list web app,首先要把postgres數(shù)據(jù)庫(kù)和pgadmin管理工具啟動(dòng)起來(lái)呕屎。因?yàn)橹蟮暮蠖薬pp在使用gradle進(jìn)行構(gòu)建時(shí)让簿,會(huì)運(yùn)行自動(dòng)化測(cè)試,需要訪問(wèn)數(shù)據(jù)庫(kù)秀睛。如果在后端app構(gòu)建時(shí)不啟動(dòng)postgres數(shù)據(jù)庫(kù)尔当,那么gradle構(gòu)建會(huì)失敗抢蚀。
要運(yùn)行這兩個(gè)容器蹦漠,需要下載代碼缀棍。在本地電腦的terminal里励翼,進(jìn)入項(xiàng)目文件夾咙鞍,運(yùn)行命令cd infrastructure
進(jìn)入這個(gè)子文件夾败徊。然后再運(yùn)行命令docker compose up postgres pgadmin
啟動(dòng)postgres數(shù)據(jù)庫(kù)和pgadmin管理工具涧团。這個(gè)命令會(huì)讀取當(dāng)前文件夾下面的docker-compose.yml文件中的postgres和pgadmin服務(wù)簇抵,并啟動(dòng)起來(lái)允瞧。我會(huì)在系列文章的第二篇简软,解讀docker-compose.yml文件。
驗(yàn)證容器:在docker desktop的container界面里述暂,能看到運(yùn)行起來(lái)的兩個(gè)容器痹升,如圖5所示。
驗(yàn)證數(shù)據(jù)庫(kù):
打開(kāi)瀏覽器訪問(wèn)pdadmin數(shù)據(jù)庫(kù)管理工具鏈接http://localhost:5050/
疼蛾,用戶名:admin@gmail.com,密碼:admin@gmail.com艺配。這個(gè)用戶名和密碼是在docker-compose.yml文件中的pdadmin服務(wù)中設(shè)置好的察郁。
鼠標(biāo)右擊Servers -> Register -> Server… -> General里的Name: 隨便寫一個(gè),比如shopping-list -> Connection里面的Host name/address: postgres -> Port: 5432 -> Maintenance database: postgres -> Username: postgres -> Password: postgres -> 允許Save password -> 點(diǎn)擊Save按鈕 -> 點(diǎn)擊剛剛創(chuàng)建的shopping-list服務(wù)器转唉,就能在數(shù)據(jù)庫(kù)出現(xiàn)問(wèn)題時(shí)查看數(shù)據(jù)庫(kù)里的數(shù)據(jù)皮钠,如圖6所示。這里的Username和Password也是在docker-compose.yml文件中的postgres服務(wù)中設(shè)置好的赠法。
在本地開(kāi)發(fā)環(huán)境啟動(dòng)后端app
重新打開(kāi)一個(gè)terminal鳞芙,進(jìn)入項(xiàng)目文件夾,然后進(jìn)入后端代碼文件夾:cd back-end
。啟動(dòng)后端app:./gradlew bootRun
原朝。
驗(yàn)證后端app:打開(kāi)瀏覽器訪問(wèn)http://localhost:8081/swagger-ui.html
驯嘱,如果能看到OpenAPI definition頁(yè)面,就表示后端已經(jīng)起了喳坠【掀溃可以在這個(gè)頁(yè)面試用一下GET /api/v1/shopping-items
接口,應(yīng)該返回[]
空記錄壕鹉。
在本地開(kāi)發(fā)環(huán)境啟動(dòng)前端app
重新打開(kāi)一個(gè)terminal剃幌,進(jìn)入項(xiàng)目文件夾,然后進(jìn)入前端代碼文件夾:cd front-end
晾浴。先運(yùn)行命令npm install
负乡,安裝package.json文件所設(shè)置的依賴庫(kù)。
只有等依賴庫(kù)安裝好了脊凰,才能運(yùn)行命令npm run dev
啟動(dòng)前端app抖棘。之后,屏幕會(huì)出現(xiàn)提示諸如Local: http://localhost:5173/
的信息狸涌。
驗(yàn)證前端app:打開(kāi)瀏覽器訪問(wèn)http://localhost:5173
切省,能看到ShoppingList頁(yè)面。在Item輸入框中輸入“a banana”帕胆,點(diǎn)擊Add按鈕朝捆,會(huì)出現(xiàn)什么結(jié)果?”a banana”竟然沒(méi)有出現(xiàn)在下面的清單里懒豹!
[小心坑芙盘!CORS問(wèn)題導(dǎo)致前端無(wú)法訪問(wèn)后端]
此時(shí)為何無(wú)法插入數(shù)據(jù)?可以用快捷鍵Cmd+Option+I打開(kāi)Developer Tools界面脸秽,在Network頁(yè)簽的Console里何陆,能看到前端訪問(wèn)后端時(shí)出現(xiàn)了CORS錯(cuò)誤信息Access to XMLHttpRequest at 'http://localhost:8081/api/v1/shopping-items' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CORS(跨源資源共享)是瀏覽器的一種安全設(shè)置。如果后端app配置好了CORS豹储,那么后端app就能告訴瀏覽器:“雖然訪問(wèn)我的這個(gè)請(qǐng)求來(lái)自前端app,但我信任它淘这,所以你可以放心地加載和展示我所提供的信息剥扣。”于是瀏覽器就能順利展示前端app訪問(wèn)后端app所獲取的數(shù)據(jù)铝穷。而上面的錯(cuò)誤信息表明钠怯,用戶從前端app的網(wǎng)址(http://localhost:5173)訪問(wèn)后端app網(wǎng)址(http://localhost:8081)里的信息,被瀏覽器攔截了曙聂。這說(shuō)明后端app沒(méi)有設(shè)置好CORS特定的權(quán)限來(lái)告訴瀏覽器:“前端這個(gè)請(qǐng)求是允許的晦炊,你可以放心接收。”
如何查看后端app的CORS配置呢断国?此時(shí)可以查看后端代碼back-end/src/main/java/com/wuzhenben/shoppinglist
文件夾下的ShoppingListApplicationConfig.java
文件贤姆。此文件的allowedOrigins(“http://localhost:8080”)
,設(shè)置了后端app允許前端app從http://localhost:8080
這個(gè)origin來(lái)訪問(wèn)它稳衬。而除此之外的origin霞捡,瀏覽器就給用戶報(bào)上面的CORS錯(cuò)誤,并拒絕訪問(wèn)薄疚。
此時(shí)要解決這個(gè)問(wèn)題碧信,該怎么辦?既然后端已經(jīng)允許前端app從http://localhost:8080
這個(gè)origin來(lái)訪問(wèn)街夭,那么如果讓前端在8080號(hào)端口運(yùn)行砰碴,是不是就能解決問(wèn)題?
此時(shí)可以按Ctrl+C中止前端app板丽。然后運(yùn)行下面的命令呈枉,讓前端app在8080號(hào)端口啟動(dòng):npm run dev -- --port 8080
。屏幕出現(xiàn)提示Local: http://localhost:8080/
檐什。
再次驗(yàn)證前端app:打開(kāi)瀏覽器訪問(wèn)http://localhost:8080
碴卧,在Item輸入框中再次輸入“a banana”,點(diǎn)擊Add按鈕乃正∽〔幔”a banana”果真出現(xiàn)在下面的清單里!你也可以試試點(diǎn)擊a banana右邊的radio button瓮具,把這個(gè)購(gòu)物項(xiàng)設(shè)置為已購(gòu)買荧飞,或者點(diǎn)擊Delete按鈕,刪除這個(gè)購(gòu)物項(xiàng)名党。
此時(shí)還可以用快捷鍵Cmd+Option+I打開(kāi)Developer Tools界面叹阔,在Network頁(yè)簽的Console里,就看不到任何錯(cuò)誤信息了传睹。
你還可以用瀏覽器訪問(wèn)http://localhost:5050/
耳幢,用之前配置好的pgadmin數(shù)據(jù)庫(kù)管理工具,看看shoppingList數(shù)據(jù)庫(kù)中是否存入了你在前端app所添加的購(gòu)物項(xiàng)欧啤。
[小心坑睛藻!docker desktop的kubernetes里的配置會(huì)搗亂]
有一天,我使用了上面的步驟邢隧,讓前端app在端口8080上啟動(dòng)店印。但當(dāng)打開(kāi)瀏覽器訪問(wèn)http://localhost:8080
時(shí),又是前端無(wú)法訪問(wèn)后端倒慧。打開(kāi)瀏覽器chrome里的Developer Tools一看按摘,發(fā)現(xiàn)network里的console報(bào)以下錯(cuò)誤:Access to XMLHttpRequest at 'http://shopping-list-api-ingress:8081/api/v1/shopping-items' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
包券。看起來(lái)又是CORS問(wèn)題炫贤。
我想試試后端的CORS配置是否起作用了溅固。于是我在顯示前端頁(yè)面的Chrome瀏覽器的console里,輸入命令fetch(“http://localhost:8081/api/v1/shopping-items”).then(a => a.text()).then(console.log)
照激,來(lái)從后端app獲取所有購(gòu)物項(xiàng)數(shù)據(jù)发魄,結(jié)果發(fā)現(xiàn)能夠獲取到,返回結(jié)果是[{“id":1,"item":"a banana”,”purchased”:true}]
俩垃。因?yàn)槲沂菑?code>http://localhost:8080頁(yè)面的console里運(yùn)行的fetch命令励幼,這就說(shuō)明后端代碼ShoppingListApplicationConfig
類中的CORS的設(shè)置起作用了。
那究竟是什么原因?qū)е聢?bào)CORS問(wèn)題呢口柳?仔細(xì)再看錯(cuò)誤信息苹粟,說(shuō)來(lái)自前端的請(qǐng)求,要訪問(wèn)后端http://shopping-list-api-ingress:8081/api/v1/shopping-items
接口路徑時(shí)跃闹,出現(xiàn)了CORS問(wèn)題嵌削。但后端接口路徑明明是http://localhost:8081/api/v1/shopping-items
啊。這是怎么回事望艺?另外苛秕,我的代碼里,也沒(méi)有出現(xiàn)過(guò)shopping-list-api-ingress
這樣的字符串找默。那為何前端app在訪問(wèn)后端app時(shí)艇劫,卻使用了http://shopping-list-api-ingress:8081/api/v1/shopping-items
這樣的陌生路徑?
仔細(xì)回憶惩激,才想起來(lái)店煞,前兩天在docker desktop里試用了本地電腦kubernetes(簡(jiǎn)稱k8s)集群功能,并在里面配置了名為shopping-list-api-ingress
的ingress配置风钻。ingress的解釋顷蟀,參見(jiàn)本文2.5.2。
看起來(lái)前端在訪問(wèn)后端時(shí)骡技,使用了這個(gè)ingress鸣个,從而導(dǎo)致CORS錯(cuò)誤。要是我把docker desktop里的kubernetes給關(guān)掉布朦,是不是就會(huì)好了囤萤。于是在docker desktop的settings中,選擇Kubernetes喝滞,再把Enable Kubernetes的勾選項(xiàng)取消勾選,重啟docker desktop膏秫。這樣就刪除了那個(gè)ingress右遭。為了保險(xiǎn)做盅,再清除一下瀏覽器cache。再次訪問(wèn)前端窘哈。一切正常吹榴!
如果你有興趣,可以用Insomnia或postman驗(yàn)證后端app接口滚婉。之前clone下來(lái)的代碼里图筹,有一個(gè)Insomnia_2023-10-06.json
文件,可以安裝Insomnia(參見(jiàn):[https://insomnia.rest/)让腹,創(chuàng)建一個(gè)collection远剩,并在里面import這個(gè)文件來(lái)驗(yàn)證后端app接口。相比postman來(lái)說(shuō)骇窍,Insomnia對(duì)于初學(xué)者更加輕量和易用瓜晤。
至此,shopping list web app就已經(jīng)在本地開(kāi)發(fā)環(huán)境里跑通了腹纳。
清理現(xiàn)場(chǎng)
如果不清理現(xiàn)場(chǎng)痢掠,一直讓兩個(gè)容器和兩個(gè)服務(wù)一直跑著,有點(diǎn)耗內(nèi)存嘲恍。此時(shí)可以在前端足画、后端和運(yùn)行本地docker compose命令的terminal界面里,按Ctrl+C佃牛,來(lái)終止這三個(gè)程序的運(yùn)行淹辞。最后在運(yùn)行本地docker compose命令的terminal界面里,運(yùn)行命令docker compose down
吁脱,來(lái)終止并刪除postgres和pgadmin容器以及相關(guān)網(wǎng)絡(luò)資源桑涎,然后在docker desktop界面里驗(yàn)證一下。這兩個(gè)容器果然消失了兼贡。
2.4 在本地docker compose里跑通
2.4.1 在本地docker compose里的架構(gòu)
架構(gòu)圖沒(méi)變攻冷,還是見(jiàn)圖3和圖4.雖然Shopping list web app在本地docker compose里的架構(gòu),與在本地開(kāi)發(fā)環(huán)境里的架構(gòu)遍希,在c4 model架構(gòu)圖中的畫(huà)法相同等曼,但在實(shí)現(xiàn)層面有差異。前者的前端和后端app凿蒜,是運(yùn)行在docker container里的禁谦。而后者則運(yùn)行在npm和gradle命令所啟動(dòng)的服務(wù)中。
2.4.2 本地docker compose環(huán)境準(zhǔn)備
在macOS上废封,只要安裝好了docker desktop州泊,你就準(zhǔn)備好了本地docker compose運(yùn)行環(huán)境∑螅可以在docker desktop界面里查看docker compose所啟動(dòng)的容器遥皂,以及相應(yīng)的image力喷。
2.4.3 在本地docker compose里跑通shopping list web app
免費(fèi)注冊(cè)Docker hub賬號(hào)以便推送docker image為部署k8s做準(zhǔn)備
Docker hub是Docker公司搞的一個(gè)存儲(chǔ)docker image的公共注冊(cè)(registry)中心。Docker公司把容器化搞火了之后演训,很多做軟件的公司弟孟,就把它們的軟件產(chǎn)品,做成docker image样悟,并推送到Docker hub拂募。你之前所用的postgres和pgadmin的image,都是從這個(gè)中心拉取的窟她。你在Docker hub上注冊(cè)賬號(hào)后陈症,也可以把你構(gòu)建的docker image推送到Docker hub上。
這樣做有什么好處礁苗?因?yàn)檫@樣一來(lái)爬凑,在k8s云集群里跑通shopping list web app時(shí),k8s云集群就能從Docker hub里拉取你所構(gòu)建的前后端app的docker image试伙。免費(fèi)注冊(cè)Docker hub賬號(hào)嘁信,參見(jiàn):https://hub.docker.com/。
構(gòu)建后端docker image并推送到docker hub
構(gòu)建后端docker image疏叨,分為三步潘靖。
第一步,用gradle構(gòu)建后端app蚤蔓,生成jar包卦溢。
先生成jar包,再構(gòu)建docker image的好處秀又,是能讓image僅包含運(yùn)行后端所需要的jar包单寂。這樣能讓image文件盡量小。
進(jìn)入項(xiàng)目文件夾吐辙,運(yùn)行命令cd infrastructure
進(jìn)入這個(gè)子文件夾宣决。然后再運(yùn)行命令docker compose up postgres pgadmin
啟動(dòng)postgres數(shù)據(jù)庫(kù)和pgadmin管理工具。
然后新打開(kāi)一個(gè)terminal窗口昏苏,進(jìn)入項(xiàng)目文件夾尊沸,運(yùn)行cd ../back-end
,進(jìn)入后端文件夾贤惯。
運(yùn)行命令./gradlew clean build
構(gòu)建后端app洼专。之后可以在文件夾build/libs
里,找找所生成的jar包孵构,文件名是shoppinglist-0.0.1-SNAPSHOT.jar屁商。
第二步,構(gòu)建docker image颈墅。
運(yùn)行命令docker buildx build --build-arg JAR_FILE=build/libs/shoppinglist-0.0.1-SNAPSHOT.jar -t <docker-hub-username>/shopping-list-api:v1.0.docker-compose .
來(lái)構(gòu)建后端docker image蜡镶∷葜埃可以運(yùn)行命令docker image ls
查看新構(gòu)建的帶有v1.0.docker-compose
tag的image。
這個(gè)命令中帽哑,docker buildx build
是對(duì)舊的docker build
命令的擴(kuò)展,提供了后者所沒(méi)有的緩存的導(dǎo)入和導(dǎo)出叹俏,以及并發(fā)構(gòu)建多個(gè)image的功能妻枕。
在參數(shù)-t <docker-hub-username>/shopping-list-api:v1.0.docker-compose
中,-t
指給image加一個(gè)tag粘驰。這個(gè)tag就是參數(shù)中v1.0.docker-compose
屡谐,用于標(biāo)識(shí)這個(gè)image。你要把<docker-hub-username>替換為你的Docker hub用戶名蝌数。而整個(gè)<docker-hub-username>/shopping-list-api
愕掏,表示這個(gè)image將來(lái)推送到Docker hub上的鏡像庫(kù)(repository)名稱。</docker-hub-username>
[小心坑顶伞!docker buildx命令最后的那個(gè)小數(shù)點(diǎn)不要忘了]
上面命令最后有一個(gè)不起眼的小數(shù)點(diǎn)饵撑。千萬(wàn)不要把它忘了。這代表把當(dāng)前文件夾作為build的上下文唆貌,以找到諸如jar文件這樣的構(gòu)建資源滑潘。
[小心坑!如何知道所構(gòu)建的image對(duì)應(yīng)的是代碼庫(kù)中的哪些代碼锨咙?]
我們知道语卤,隨著不斷提交,代碼庫(kù)中的代碼總是在不斷變化酪刀。如果有一天粹舵,你推送到Docker hub中的image里有bug,你想打開(kāi)對(duì)應(yīng)的源代碼看一下骂倘。但距離你構(gòu)建這個(gè)image已經(jīng)過(guò)去好幾天了眼滤,你也往代碼庫(kù)里提交了不少代碼。當(dāng)初構(gòu)建這個(gè)image的代碼也改了不少稠茂。此時(shí)你該如何在代碼庫(kù)中柠偶,還原當(dāng)初構(gòu)建這個(gè)image時(shí)的代碼?解決的辦法睬关,就是你在運(yùn)行上面的docker buildx命令诱担,構(gòu)建了docker image后,就立即運(yùn)行命令git tag -a v1.0.docker-compose -m “v1.0.docker-compose”
电爹,在git庫(kù)里打一個(gè)同名的tag蔫仙。這樣通過(guò)識(shí)別這個(gè)tag,你就能把image和代碼對(duì)應(yīng)上了丐箩。最后別忘了運(yùn)行命令git push origin v1.0.docker-compose
把這個(gè)tag推送到遠(yuǎn)程git庫(kù)中摇邦。
第三步恤煞,把docker image推送到Docker hub。
運(yùn)行命令docker login
登錄Docker hub施籍。然后運(yùn)行命令docker push <docker-hub-username>/shopping-list-api:v1.0.docker-compose
居扒,將構(gòu)建好的image推送到Docker hub。你可以登錄Docker hub丑慎,看看后端帶有v1.0.docker-compose
這個(gè)tag的image是否已經(jīng)在上面了喜喂。
構(gòu)建前端docker image并推送到docker hub
構(gòu)建前端docker image,分為兩步竿裂。
第一步玉吁,構(gòu)建docker image。
有人可能會(huì)問(wèn)腻异,為何不是先用命令npm run build
來(lái)構(gòu)建前端app进副?答案是這個(gè)命令,以及納入前端的Dockerfile文件里了悔常。我會(huì)在第二篇文章中影斑,解讀這個(gè)文件。
運(yùn)行cd ../front-end
机打,進(jìn)入前端文件夾鸥昏。運(yùn)行命令docker buildx build -t <docker-hub-username>/shopping-list-front-end:v1.0.docker-compose .
來(lái)構(gòu)建后端docker image。這里的參數(shù)解讀和前面講的一樣姐帚±艨澹可以運(yùn)行命令docker image ls
查看新構(gòu)建的帶有v1.0.docker-compose
tag的image。
第二步罐旗,把docker image推送到Docker hub膳汪。
運(yùn)行命令docker push <docker-hub-username>/shopping-list-front-end:v1.0.docker-compose
,將構(gòu)建好的image推送到Docker hub九秀。你可以登錄Docker hub遗嗽,看看前端帶有v1.0.docker-compose
這個(gè)tag的image是否已經(jīng)在上面了。
在本地docker compose里跑通shopping list web app
在本地docker compose里跑通的命令很簡(jiǎn)單鼓蜒,進(jìn)入項(xiàng)目文件夾痹换,運(yùn)行命令cd infrastructure
進(jìn)入infrastructure子文件夾,再運(yùn)行命令docker compose up
來(lái)啟動(dòng)postgres都弹、pgadmin娇豫、shopping-list-api和shopping-list-front-end這四個(gè)容器即可。此時(shí)可以在docker desktop里查看這4個(gè)容器的運(yùn)行狀態(tài)畅厢。還可以在瀏覽器里訪問(wèn)http://localhost:8080/
來(lái)試用購(gòu)物列表web app冯痢。
至此,shopping list web app在本地docker compose里跑通了。
清理現(xiàn)場(chǎng)
進(jìn)入項(xiàng)目文件夾浦楣,運(yùn)行命令cd infrastructure
進(jìn)入infrastructure子文件夾袖肥,再運(yùn)行命令docker compose down
可以停止和刪除4個(gè)容器。
2.5 在k8s云集群里跑通
在k8s云集群里跑前后端分離的web app振劳,有兩種選擇椎组。
第一種,是使用云廠商所提供的免費(fèi)試用的服務(wù)历恐。
第二種庐杨,是使用在本地電腦上運(yùn)行的諸如minikube這樣的單node的服務(wù)。
因?yàn)橐嬲w驗(yàn)上云夹供,所以我選擇了第一種。
各大云廠商都會(huì)提供1~3個(gè)月不等的k8s云集群免費(fèi)試用仁堪。本文選用了微軟的azure k8s service哮洽。免費(fèi)試用1個(gè)月,提供2個(gè)node弦聂。按照之前講解的習(xí)慣鸟辅,此時(shí)應(yīng)該展示shopping list web app在k8s云集群里的架構(gòu)。但為了再現(xiàn)我踩坑的經(jīng)過(guò)莺葫,讓講解更有趣匪凉,我打算把架構(gòu)放到最后再講。
2.5.1 K8s云集群環(huán)境準(zhǔn)備
注冊(cè)Azure k8s service云平臺(tái)賬號(hào)
Azure k8s service云平臺(tái)免費(fèi)注冊(cè)方法參見(jiàn):https://azure.microsoft.com/捺檬。
注冊(cè)完后再层,可以創(chuàng)建一個(gè)名為my-k8s-cluster-1
的k8s service,以及名為my-azure-resource-group-1
的resource group堡纬。然后登錄主頁(yè)https://portal.azure.com/#home
聂受,就能看到你所擁有的資源,如圖7所示烤镐。
打開(kāi)docker desktop kubernetes讓kubectl能正常工作
接下來(lái)蛋济,你需要安裝工具kubectl,以便從macOS連上k8s云集群炮叶。做法是在docker desktop里碗旅,點(diǎn)擊settings,選擇Kubernetes镜悉,然后把Enable Kubernetes左邊的勾選框勾上祟辟。之后點(diǎn)擊Apply & reset按鈕。
驗(yàn)證docker desktop k8s能否正常工作:等reset結(jié)束后侣肄,你能在docker desktop的主界面左下角的小鯨魚(yú)圖標(biāo)上方川尖,看到一個(gè)綠色背景的小橫條,上面有k8s的舵輪圖標(biāo)。綠色背景叮喳,表示docker desktop k8s運(yùn)行正常被芳。另外,你可以打開(kāi)一個(gè)terminal窗口馍悟,在里面輸入命令kubectl version -o yaml
畔濒。如果能看到clientVersion和serverVersion,就說(shuō)明操作k8s的命令kubectl能正常工作了锣咒。
連上azure k8s service云平臺(tái)
要從你的Mac連上azure k8s service云平臺(tái)侵状,需要改一個(gè)配置文件。這個(gè)文件是你的mac電腦的~/.kube
文件夾下的config文件毅整。你可以用你喜歡用的編輯器趣兄,打開(kāi)這個(gè)文件。里面只有你的docker desktop所提供的一個(gè)k8s集群悼嫉,名字就是docker-desktop艇潭。你要連azure k8s service云平臺(tái),就需要把這個(gè)文件戏蔑,替換為azure k8s service云平臺(tái)的同名配置文件蹋凝。或者在這個(gè)文件中总棵,添加azure k8s service云平臺(tái)的配置鳍寂。即這個(gè)文件可以有多個(gè)k8s集群的配置,此時(shí)就能用kubectl命令情龄,在兩個(gè)k8s集群之間切換迄汛。因?yàn)樵诒疚闹校覀儾挥胐ocker desktop k8s所提供的單node的本地集群骤视,所以為簡(jiǎn)單起見(jiàn)隔心,可以把你mac上的~/.kube/config
文件先備份,然后用azure k8s service云平臺(tái)的同名配置文件將其替換尚胞。
那如何獲取azure k8s service云平臺(tái)的配置文件硬霍?方法是你需要在瀏覽器里,登錄你的azure k8s service云平臺(tái)笼裳。在頁(yè)面上方搜索框的右側(cè)唯卖,有一個(gè)Cloud shell圖標(biāo)。點(diǎn)擊這個(gè)圖標(biāo)躬柬,就能在屏幕下方拜轨,看到一個(gè)黑色背景的命令行界面出現(xiàn)搂抒。點(diǎn)擊命令行界面上方的兩個(gè)大括號(hào){}圖標(biāo)Open editor馒疹,就能在左側(cè)打開(kāi)一個(gè)文件樹(shù)。在文件樹(shù)中级及,找到.kube文件夾并打開(kāi),然后點(diǎn)擊config文件法牲。右側(cè)就會(huì)出現(xiàn)這個(gè)文件的內(nèi)容史汗。你把這個(gè)文件的內(nèi)容全部復(fù)制出來(lái),保存到mac電腦的~/.kube/config
文件末尾拒垃,并把這個(gè)文件原先的內(nèi)容刪除停撞。再次提醒,在刪除原內(nèi)容前悼瓮,一定要備份戈毒。
一旦改好了config文件,你就可以連接azure k8s service云平臺(tái)了横堡。
運(yùn)行命令kubectl config get-contexts
埋市,可以看到你所連接的azure k8s service云平臺(tái)。
運(yùn)行命令kubectl get nodes
命贴,可以查看azure k8s service云平臺(tái)給你分配了兩個(gè)node道宅,狀態(tài)都是ready。
2.5.2 在k8s云集群里跑通shopping list web app時(shí)踩坑
我是如何踩坑的
初次在k8s上部署前后端分離的web app套么,最自然的方式,就是按照在docker compose里部署的架構(gòu)碳蛋,來(lái)部署胚泌。但這樣想,就踩進(jìn)了一個(gè)坑肃弟。在討論坑之前玷室,先看看在k8s云集群里跑通與在本地跑通之間的差異。
在k8s云集群里跑通shopping list web app笤受,與在本地docker compose里跑通穷缤,有什么差異呢?有3個(gè)差異箩兽。
第一個(gè)差異津肛,是后端app所依賴的數(shù)據(jù)庫(kù)主機(jī)名,不再是localhost汗贫,而是k8s云集群里postgres數(shù)據(jù)庫(kù)的內(nèi)部service名身坐。這需要改動(dòng)back-end/src/main/resources/application.properties文件,將里面的localhost落包,替換為${DB_HOST}部蛇。即通過(guò)在下面介紹的deployment配置文件設(shè)置的DB_HOST環(huán)境變量,來(lái)確定postgres數(shù)據(jù)庫(kù)的service名咐蝇。
第二個(gè)差異涯鲁,是后端的CORS的配置中的allowedOrigins,不再是http://localhost:8080
,而應(yīng)該是前端app在k8s云集群中的對(duì)外域名和端口號(hào)抹腿。
第三個(gè)差異岛请,前端前端app所依賴的后端app的主機(jī)名和端口,也不再是localhost:8081幢踏,同樣也變成了k8s云集群里后端app的service名髓需。這需要改動(dòng)前端代碼的3個(gè)文件。首先房蝉,front-end/src/components/ShoppingList.vue文件中的localhost:8081僚匆,需要改為%%API_URL%%。這也是通過(guò)在下面介紹的deployment配置文件設(shè)置的API_URL環(huán)境變量搭幻,來(lái)確定后端app的服務(wù)名咧擂。為了能夠在js代碼中,替換后端app的服務(wù)名檀蹋,需要改動(dòng)front-end/Dockerfile和新增front-end/entrypoint.sh文件松申。
第二個(gè)差異,就是一個(gè)坑俯逾。后端的CORS的配置中的allowedOrigins贸桶,該如何配前端app在k8s云集群中的對(duì)外域名和端口號(hào)?我沒(méi)有為這個(gè)項(xiàng)目申請(qǐng)域名桌肴。域名也不能寫成內(nèi)部service名皇筛,因?yàn)閮?nèi)部名無(wú)法用于外部訪問(wèn)。能把域名寫成ip地址嗎坠七?在云集群中水醋,ip地址經(jīng)常會(huì)發(fā)生變化。每次ip變了就去改配置彪置,多麻煩拄踪。這個(gè)坑該如何爬出來(lái)?
我還真的把postgres拳魁、后端shopping-list-api和前端shopping-list-web-app都部署到k8s云集群里惶桐,并讓前端擁有一個(gè)外部IP。結(jié)果發(fā)現(xiàn)潘懊,當(dāng)我用瀏覽器訪問(wèn)前端外部IP的8080端口時(shí)耀盗,瀏覽器果然報(bào)了CORS錯(cuò)誤:Access to XMLHttpRequest at ‘http://shopping-list-api/api/v1/shopping-items' from origin ‘http://20.72.168.185:8080’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resources.
后來(lái)也是查了很多資料,在朋友圈求助卦尊,經(jīng)過(guò)朋友們的提醒叛拷,并嘗試了一下,發(fā)現(xiàn)為shopping list web app配置ingress能解決這個(gè)難題岂却。
在k8s里忿薇,ingress是一種規(guī)則和配置的集合裙椭,它能幫助外部的網(wǎng)絡(luò)請(qǐng)求,來(lái)查找到和訪問(wèn)集群內(nèi)的服務(wù)署浩∪嗳迹可以把它想象成一個(gè)交通指揮員,它知道如何根據(jù)特定的規(guī)則把外面來(lái)的車輛(網(wǎng)絡(luò)請(qǐng)求)引導(dǎo)到正確的停車位(服務(wù))筋栋。
2.5.3 在k8s云集群里的架構(gòu)
在k8s云集群里炊汤,就難以使用pgadmin數(shù)據(jù)庫(kù)管理工具了。所以圖8的context架構(gòu)圖只有user弊攘。
咱們這個(gè)web app抢腐,用戶不再直接訪問(wèn)前端app的對(duì)外IP和端口,而是直接訪問(wèn)ingress nginx controller的對(duì)外IP和端口襟交。之后迈倍,ingress nginx controller會(huì)把用戶的請(qǐng)求,根據(jù)請(qǐng)求的path不同捣域,分發(fā)給前端app和后端app啼染。而前后端app就不必?fù)碛袑?duì)外的IP和端口了。
既然用戶所使用的瀏覽器焕梅,只看到ingress nginx controller所對(duì)外暴露的IP和端口迹鹅,那么之后前端app訪問(wèn)后端app獲取數(shù)據(jù),就都在同一個(gè)ingress nginx controller所對(duì)外暴露的IP和端口下贞言,這樣對(duì)瀏覽器來(lái)說(shuō)斜棚,就不存在CORS的跨域問(wèn)題了。如圖9所示蜗字。
2.5.4 如何從坑里爬出來(lái)
要從坑里爬出來(lái)打肝,就需要新增k8s的deployment脂新、service和ingress的配置文件挪捕,以便使用kubectl命令將ingress和postgres、shopping-list-api和shopping-list-front-end這3個(gè)微服務(wù)部署到k8s上争便。
注意级零,ingress不是微服務(wù),而是k8s里的一組規(guī)則滞乙。
另外奏纪,每個(gè)微服務(wù)的k8s部署,一般都需要一個(gè)deployment文件和一個(gè)service文件斩启。前者供k8s為這個(gè)微服務(wù)創(chuàng)建pod序调,后者供k8s為這個(gè)微服務(wù)的pod分配穩(wěn)定的ip地址以及DNS名稱。即使容器實(shí)例被替換兔簇,ip地址以及DNS名稱也不會(huì)改變发绢。
Pod是k8s管理的最小單元硬耍,里面推薦只運(yùn)行一個(gè)docker container,這樣才算微服務(wù)边酒。
配置ingress需要一個(gè)ingress配置文件经柴。因?yàn)橐趉8s里配置3個(gè)微服務(wù),所以需要新增3個(gè)deployment文件和3個(gè)service文件墩朦。
此外坯认,原先在本地使用的pgadmin數(shù)據(jù)庫(kù)管理工具,在k8s云集群中氓涣,就不再使用了牛哺。
改動(dòng)的代碼文件列表如下:
back-end/src/main/resources/application.properties(改動(dòng))
Back-end/src/main/ShoppingListApplicationConfig.java(改動(dòng))
front-end/Dockerfile(改動(dòng))
front-end/src/components/ShoppingList.vue(改動(dòng))
front-end/entrypoint.sh(新增)
infrastructure/deployment-postgres.yml(新增)
infrastructure/ingress.yml(新增)
infrastructure/deployment-shopping-list-api.yml(新增)
infrastructure/deployment-shopping-list-front-end.yml(新增)
infrastructure/service-postgres.yml(新增)
infrastructure/service-shopping-list-api.yml(新增)
infrastructure/service-shopping-list-front-end.yml(新增)
為了減輕你寫代碼的負(fù)擔(dān),我把這些改動(dòng)和新增保存到了分支for-azure-k8s-service中春哨。運(yùn)行命令git checkout for-azure-k8s-service
就能看到進(jìn)行了這些改動(dòng)和新增后的代碼荆隘。
由于代碼改動(dòng)涉及后端和前端,所以要重新構(gòu)建后端和前端的docker image赴背。
構(gòu)建后端docker image并推送到docker hub
首先把數(shù)據(jù)庫(kù)跑起來(lái)椰拒,以便構(gòu)建代碼時(shí)運(yùn)行測(cè)試。進(jìn)入項(xiàng)目文件夾凰荚,運(yùn)行命令cd infrastructure
進(jìn)入這個(gè)子文件夾燃观。然后再運(yùn)行命令docker compose up postgres pgadmin
啟動(dòng)postgres數(shù)據(jù)庫(kù)和pgadmin管理工具。
然后新打開(kāi)一個(gè)terminal窗口便瑟,進(jìn)入項(xiàng)目文件夾缆毁,運(yùn)行cd ../back-end
,進(jìn)入后端文件夾到涂。因?yàn)楹蠖薬pp所依賴的數(shù)據(jù)庫(kù)主機(jī)名脊框,現(xiàn)在已經(jīng)改為環(huán)境變量${DB_HOST}了,所以在構(gòu)建前践啄,需要在terminal窗口中浇雹,運(yùn)行命令export DB_HOST=localhost
來(lái)設(shè)置環(huán)境變量。
之后屿讽,可以運(yùn)行命令./gradlew clean build
來(lái)生成后端jar包昭灵。
然后運(yùn)行命令docker buildx build --build-arg JAR_FILE=build/libs/shoppinglist-0.0.1-SNAPSHOT.jar -t <docker-hub-username>/shopping-list-api:v1.1.k8s .
來(lái)構(gòu)建后端docker image。注意伐谈,為了和之前為docker compose構(gòu)建image做區(qū)分烂完,上面命令中的tag改為v1.1.k8s
∷锌茫可以運(yùn)行命令docker image ls
查看新構(gòu)建的帶有v1.1.k8s
tag的image抠蚣。
運(yùn)行命令docker login
登錄Docker hub。然后運(yùn)行命令docker push <docker-hub-username>/shopping-list-api:v1.1.k8s
履澳,將構(gòu)建好的image推送到Docker hub嘶窄。你可以登錄Docker hub缓屠,看看后端shopping-list-api帶有v1.1.k8s
這個(gè)tag的image是否已經(jīng)在上面了。
構(gòu)建前端docker image并推送到docker hub
[小心坑护侮!如果用arm64架構(gòu)的mac構(gòu)建image而不做架構(gòu)設(shè)定會(huì)怎樣敌完?]
我按之前為docker compose構(gòu)建前端docker image的方式,為azure k8s service構(gòu)建了前端docker image羊初。但等我把前端的deployment文件apply到k8s云集群時(shí)滨溉,pod在啟動(dòng)時(shí)總是報(bào)一個(gè)奇怪的錯(cuò)誤:exec /usr/local/bin/docker-entrypoint.sh: exec format error。
把這個(gè)image拉下來(lái)长赞,運(yùn)行一個(gè)容器晦攒,然后進(jìn)去看文件docker-entrypoint.sh的內(nèi)容,也看不出所以然得哆。后來(lái)查了半天脯颜,才知道原因在于我用arm64架構(gòu)的mac在構(gòu)建image時(shí),沒(méi)有指定所構(gòu)建的image應(yīng)該是amd64架構(gòu)的贩据。
如果用arm64架構(gòu)的mac構(gòu)建image栋操,而不在命令中做架構(gòu)設(shè)定,那么所構(gòu)建的image就只能用于arm64架構(gòu)的容器運(yùn)行系統(tǒng)里饱亮,這也是我之前能正常在mac上的docker compose里運(yùn)行不帶架構(gòu)設(shè)定而構(gòu)建出的image的容器的原因矾芙。但我在azure k8s service云集群里所申請(qǐng)的資源,一般都是只能運(yùn)行amd64架構(gòu)的容器近上。
要爬出這個(gè)坑需要做兩件事剔宪。
第一,需要在~/.docker/config.json文件中壹无,增加下面的配置葱绒,以便讓docker buildx能夠支持在Mac arm64架構(gòu)的電腦上,構(gòu)建amd64架構(gòu)的image斗锭。
{ "experimental": "enabled" }
第二地淀,在docker buildx命令中,增加指定架構(gòu)的參數(shù)拒迅∩兀可以在項(xiàng)目文件夾中她倘,運(yùn)行cd ../front-end
璧微,進(jìn)入前端文件夾。運(yùn)行命令docker buildx build --platform linux/amd64 -t <docker-hub-username>/shopping-list-front-end:v1.1.k8s.amd64 .
來(lái)構(gòu)建前端docker image硬梁∏傲颍可以運(yùn)行命令docker image ls
查看新構(gòu)建的帶有v1.1.k8s.amd64
tag的image。還可以運(yùn)行命令docker inspect wubin28/shopping-list-front-end:v1.1.k8s.amd64 | grep “Architecture"
查看這個(gè)image是否真的是amd64架構(gòu)的荧止。
運(yùn)行命令docker push <docker-hub-username>/shopping-list-front-end:v1.1.k8s.amd64
屹电,將構(gòu)建好的image推送到Docker hub阶剑。你可以登錄Docker hub,看看前端shopping-list-front-end帶有v1.1.k8s.amd64
這個(gè)tag的image是否已經(jīng)在上面了危号。
在k8s云集群上配置postgres牧愁、shopping-list-api和shopping-list-front-end三個(gè)微服務(wù)和ingress并運(yùn)行
要在Mac的terminal里連上azure k8s service進(jìn)行操作,需要安裝azure-cli工具外莲≈戆耄可以運(yùn)行brew update
和brew install azure-cli
進(jìn)行安裝。安裝完后偷线,可以運(yùn)行命令az --version
來(lái)驗(yàn)證安裝是否成功磨确。然后可以運(yùn)行az login
來(lái)登錄azure k8s service云平臺(tái)。
我們?cè)趉8s云集群里為這個(gè)web app所創(chuàng)建的資源声邦,最好都放到一個(gè)namespace里乏奥,這樣便于管理。將來(lái)不用云服務(wù)了亥曹,要?jiǎng)h除一個(gè)namespace里所有資源以便省錢邓了,也就是運(yùn)行一條命令的事兒。具體如何做媳瞪,見(jiàn)下文“清理現(xiàn)場(chǎng)”驶悟。
因?yàn)槊總€(gè)命令一般都有掛上NAMESPACE`來(lái)創(chuàng)建這個(gè)namespace痕鳍。
前面講到,在一個(gè)操作系統(tǒng)里安裝工具龙巨,最好用包管理器笼呆。這樣便于維護(hù)工具的版本。對(duì)于云計(jì)算操作系統(tǒng)k8s來(lái)說(shuō)旨别,helm就是這樣的包管理工具诗赌。我們可以用helm來(lái)安裝ingress-nginx:
helm repo add ingress-nginx-repo https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx-release ingress-nginx-repo/ingress-nginx \
-n $NAMESPACE \
--set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz
安裝完后,可以運(yùn)行helm list -n $NAMESPACE
驗(yàn)證一下秸弛。
接下來(lái)就可以用kubectl铭若,運(yùn)行下面命令,來(lái)往k8s云集群里部署postgres递览、shopping-list-api叼屠、shopping-list-front-end和ingress了。
`cd ../infrastructure`
部署postgres的deployment:`kubectl apply -f ./deployment-postgres.yml --namespace $NAMESPACE`
驗(yàn)證image是否正確:`kubectl get deployments -o wide -n $NAMESPACE`
驗(yàn)證pod是否正常啟動(dòng):`kubectl get pods -o wide -n $NAMESPACE`
部署postgres的service:`kubectl apply -f ./service-postgres.yml --namespace $NAMESPACE`
驗(yàn)證服務(wù)是否正常啟動(dòng):`kubectl get services -o wide -n $NAMESPACE`
部署shopping-list-api的deployment:`kubectl apply -f ./deployment-shopping-list-api.yml --namespace $NAMESPACE`
驗(yàn)證image是否正確:`kubectl get deployments -o wide -n $NAMESPACE`
驗(yàn)證pod是否正常啟動(dòng):`kubectl get pods -o wide -n $NAMESPACE`
部署shopping-list-api的service:`kubectl apply -f ./service-shopping-list-api.yml --namespace $NAMESPACE`
驗(yàn)證服務(wù)是否正常啟動(dòng):`kubectl get services -o wide -n $NAMESPACE`
部署shopping-list-front-end的deployment:`kubectl apply -f ./deployment-shopping-list-front-end.yml --namespace $NAMESPACE`
驗(yàn)證image是否正確:`kubectl get deployments -o wide -n $NAMESPACE`
驗(yàn)證pod是否正常啟動(dòng):`kubectl get pods -o wide -n $NAMESPACE`
部署shopping-list-front-end的service:`kubectl apply -f ./service-shopping-list-front-end.yml --namespace $NAMESPACE`
驗(yàn)證服務(wù)是否正常啟動(dòng):`kubectl get services -o wide -n $NAMESPACE`
部署ingress:`kubectl apply -f ./ingress.yml --namespace $NAMESPACE`
查看ingress的狀態(tài):kubectl get ingresses -n $NAMESPACE
查看ingress的詳情:kubectl describe ingress <ingress name> -n $NAMESPACE
如果一切順利绞铃,沒(méi)有出錯(cuò)镜雨,那么就可以運(yùn)行命令`kubectl get services -o wide -n $NAMESPACE`,查看ingress nginx controller對(duì)外暴露的IP和端口儿捧,以便讓我們?cè)囉脀eb app荚坞。假設(shè)我們查看到的IP是20.72.130.209挑宠。而端口一般是80。
打開(kāi)瀏覽器颓影,訪問(wèn)http://20.72.130.209/
各淀。如果一切正常,就能在上面愉快地管理購(gòu)物項(xiàng)了诡挂。
清理現(xiàn)場(chǎng)
運(yùn)行命令kubectl delete namespace $NAMESPACE
揪阿,就可以刪除該namespace下所有資源。
如果你的azure k8s service云服務(wù)免費(fèi)試用快到期了咆畏,記得刪除下面的資源:my-k8s-cluster-1南捂、my-azure-resource-group-1和Azure subscription 1。
[小心坑旧找!在免費(fèi)期到期前不要忘記刪除k8s云集群中的所有資源]
在微軟溺健、谷歌、亞馬遜钮蛛、阿里鞭缭、騰訊這樣的云平臺(tái)申請(qǐng)了帶有免費(fèi)試用期的賬號(hào),如果暫時(shí)不用魏颓,在試用期到期前岭辣,一定記得刪除k8s云集群中的所有資源,否則就太破費(fèi)了甸饱。你會(huì)遇到云刺客沦童。
本文Windows 10和ubuntu版,等我有空了再寫叹话。
因篇幅所限偷遗,本文并未解讀所使用的docker compose和k8s的配置文件。我會(huì)在接下來(lái)的兩篇文章中驼壶,進(jìn)行解讀氏豌。敬請(qǐng)關(guān)注。
要想找到這一系列文章的最新版本热凹,可以在知乎搜“體驗(yàn)更好地做軟件”專欄泵喘。
如果你喜歡這一系列文章,歡迎點(diǎn)贊和收藏般妙,并在留言區(qū)寫下為何喜歡纪铺,以便我將來(lái)寫更多你喜歡的文章。
如果你不喜歡股冗,也歡迎你留言告訴我哪里可以再改進(jìn)霹陡。