【萬字長(zhǎng)文】K8s部署前后端分離的web應(yīng)用避坑系列指南之一:在本地開發(fā)環(huán)境、本地docker compose和k8s云集群里跑通購物清單應(yīng)用(macOS-2023版)

做軟件的人:“工作體驗(yàn)好,好事才能來?!?/p>

1 太長(zhǎng)不讀

從2023年8月到10月,我花了3個(gè)月自學(xué)docker和k8s。踩了一路坑,到10月22日終于把一個(gè)帶有vue.js 3前端、spring boot后端以及postgres數(shù)據(jù)庫的shopping list web app,部署到azure k8s service云上,并能正常運(yùn)行。

之所以說踩了一路坑,是因?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ù)之間,是沒有前后端之間的依賴關(guān)系的。另外前后端之間的CORS跨源資源共享該如何解決,也找不到我這種前后端分離的web app場(chǎng)景下的直接資料,只能自己摸索。

在爬出坑后,很愿意寫一系列避坑指南文章分享給大家。雖然不知小伙伴們是否愿意讀,但我很想把這一系列文章,寫成網(wǎng)上通過實(shí)例講docker和k8s入門的最好的文章,而且每年至少更新一次。

這一系列文章的目標(biāo)讀者,是對(duì)docker或k8s不太熟悉的做軟件的人,不僅包括常寫代碼的程序員,也包括不常寫代碼的測(cè)試工程師和運(yùn)維工程師,如圖1所示。

圖1 做軟件的人包括程序員、測(cè)試工程師和運(yùn)維工程師

K8s和Docker能解決做軟件的人的什么痛點(diǎn)?這也是我為啥要花這么多時(shí)間寫這一系列文章的原因。因?yàn)閗8s和Docker可以讓咱們做軟件的人,能體驗(yàn)更好地做軟件。

“工作體驗(yàn)好,好事才能來。”

想想咱們做軟件的人常說的下面幾句話。

“在我這運(yùn)行得好好的,怎么你那兒不行?” docker image能將代碼的所有依賴庫都打包到一起,并能讓代碼在容器中獨(dú)立運(yùn)行。這樣就能實(shí)現(xiàn)你在測(cè)試環(huán)境中所測(cè)試的image,就是你在生產(chǎn)環(huán)境所部署的,從而能在很大程度上解決因?yàn)橐蕾噹煸诓煌h(huán)境下的差異,而導(dǎo)致這里能運(yùn)行,那里不能運(yùn)行的問題。

“這是誰改了配置又不告訴大家?” Docker和k8s都強(qiáng)調(diào)基礎(chǔ)設(shè)施即代碼,即配置不是靠做軟件的人拍腦袋臨時(shí)手工敲的,而是靠寫成與代碼同等地位的配置文件,通過團(tuán)隊(duì)代碼評(píng)審,保存到版本庫中,并讓機(jī)器執(zhí)行。這樣能讓配置的更改廣而告之,配置的執(zhí)行有據(jù)可查。同時(shí)也便于讓機(jī)器讀取,自動(dòng)執(zhí)行,而無須手工一遍一遍敲同樣的命令。

“測(cè)試環(huán)境太少得排隊(duì)使用?!?/strong> 有了本地docker compose,做軟件的人可以利用其占用存儲(chǔ)空間小,運(yùn)行速度快的特點(diǎn),在本地電腦以docker image的方式,最大限度模擬生產(chǎn)環(huán)境的方式,測(cè)試要發(fā)布的軟件,而無須排隊(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)境少的問題。

可見,Docker和k8s能讓咱們做軟件的人,工作體驗(yàn)更好,好事才更能來。

“為何選用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ù)庫微服務(wù)。這便于學(xué)習(xí)如何使用新興的故障注入實(shí)驗(yàn)工具,進(jìn)行混沌工程實(shí)踐。

圖2 購物清單shopping list web app頁面

“我對(duì)java和vue.js不熟,能讀懂這一系列文章嗎?”能。因?yàn)槲恼碌拇a命名寫得足夠表意,一看就懂。另外,這一系列文章不涉及前后端具體的編程,而重點(diǎn)關(guān)注如何把開發(fā)好的代碼用docker打成image,并部署到本地docker compose和k8s云集群上。這些都與前后端所使用的編程語言關(guān)系不大,所以文章內(nèi)容適用于所有使用JSON/HTTP協(xié)議的前后端分離的web app的技術(shù)棧。

“我不會(huì)編程,能讀懂這一系列文章嗎?”能。因?yàn)槲恼虏簧婕扒昂蠖斯δ艿拇a編寫,而主要涉及配置文件和命令行工具的使用,適合程序員、測(cè)試工程師和運(yùn)維工程師閱讀。

“我是做測(cè)試或運(yùn)維的,還需要按照文章的描述,在本地開發(fā)環(huán)境里跑通嗎?”需要。因?yàn)樵诒疚乃枋龅谋芸拥倪^程中,你會(huì)發(fā)現(xiàn)之前代碼中的配置有問題。當(dāng)你需要在源代碼里更改配置,并重新構(gòu)建docker image時(shí),你就需要知道如何操作。

這一系列文章的第一篇,會(huì)針對(duì)macOS、Windows10和Ubuntu這3種操作系統(tǒng),分別推出3個(gè)版本。

這一系列文章可以分為三篇。這三篇的標(biāo)題如下:

K8s部署前后端分離的web應(yīng)用避坑系列指南之一:在本地開發(fā)環(huán)境、本地docker compose和k8s云集群里跑通購物清單應(yīng)用(macOS/Windows10/Ubuntu-2023版分別寫)

K8s部署前后端分離的web應(yīng)用避坑系列指南之二:解讀購物清單應(yīng)用Dockerfile和docker-compose.yml文件

K8s部署前后端分離的web應(yīng)用避坑系列指南之三:解讀購物清單應(yīng)用k8s的deployment、service和ingress配置文件

這一系列指南相關(guān)的源代碼在這里下載:https://github.com/wubin28/shopping-list-web-app。

要想找到這一系列文章的最新版本,可以在知乎搜“體驗(yàn)更好地做軟件”專欄。

2 深入閱讀

注意,本文一萬三千字。分享了8個(gè)避坑指南。需要拿著mac邊讀邊練。沒點(diǎn)決心堅(jiān)持不下來。慎入。

2.1 需求描述

這一系列文章所選用的web app,是一個(gè)購物清單shopping list web app。它的用戶是有購物需求的顧客小吾。一天,小吾發(fā)現(xiàn)家里的瓶裝水快沒了,就想著晚上下班路過超市時(shí),順便買幾瓶。但這時(shí)老板來微信喊他開會(huì)。他很快就把買瓶裝水的事情忘掉干干凈凈。等下班路過超市,他光顧著給老婆買打折的巧克力。等到家要喝水了,發(fā)現(xiàn)水沒買。咱們這個(gè)shopping list web app,就能為小吾解決上面的痛點(diǎn)。當(dāng)他想要買水時(shí),就可以馬上在app里添加一條買水的購物項(xiàng)。過了一會(huì)兒又想買點(diǎn)香蕉,那就再加一條。等他到了超市,再查看一下這個(gè)清單,要買的東西就不會(huì)忘了。

2.2 從源代碼開始分三步部署到k8s

現(xiàn)在咱們有了這一系列指南相關(guān)的源代碼。該如何將它部署到k8s呢?

可以分三步:

第一步,在本地開發(fā)環(huán)境里跑通;

第二步,在本地docker compose里跑通;

第三步,在k8s云集群里跑通。

為何不能一次就從源代碼直接部署到k8s呢?當(dāng)然這樣做也可以,但前提是你確信部署上去后,將來再也沒有新需求或修bug而去修改源代碼并重新部署。對(duì)于坑坑洼洼的docker和k8s學(xué)習(xí)之旅,你覺得這可能嗎?

所以你需要知道當(dāng)新需求來了或要修bug時(shí),該如何把修改過的代碼,在本地開發(fā)環(huán)境里調(diào)試通。這是進(jìn)行第一輪自測(cè)。畢竟,本地電腦是你的地盤兒。在本地電腦上調(diào)試程序,比在k8w云集群里要方便得多。這是第一步的意義。

之后,你需要知道如何將通過了第一輪自測(cè)的代碼,構(gòu)建成docker image,并在本地docker compose里跑通,為之后將docker image部署到k8s做第二輪自測(cè)。畢竟,本地docker compose也在你的地盤兒上。這是第二步的意義。

最后,你需要知道如何將通過了第二輪自測(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 在本地開發(fā)環(huán)境里跑通

2.3.1 在本地開發(fā)環(huán)境里的架構(gòu)

Shopping List Web App在本地開發(fā)環(huán)境里的架構(gòu),如果用c4 model(https://c4model.com/)畫出來,就如圖3和圖4所示。

圖3是站在整個(gè)web app的邊界,向外看的context圖。在系統(tǒng)外,有user和admin這兩種用戶在使用系統(tǒng)。User使用系統(tǒng)來管理購物清單。Admin使用系統(tǒng)來管理購物清單數(shù)據(jù)。

圖3 Shopping list web app在本地開發(fā)環(huán)境里的的context架構(gòu)圖

圖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),后者代表封裝了所有代碼和依賴庫能獨(dú)立運(yùn)行的軟件運(yùn)行單元。User通過前端shopping-list-front-end來查看和修改購物清單。而前端shopping-list-front-end將用戶對(duì)購物清單的操作請(qǐng)求,發(fā)給后端shopping-list-api。后端shopping-list-api再訪問數(shù)據(jù)庫postgres查詢和更新數(shù)據(jù)。Admin通過使用pgadmin數(shù)據(jù)庫管理工具來直接管理postgres數(shù)據(jù)庫中的數(shù)據(jù)。

圖4 Shopping list web app在本地開發(fā)環(huán)境里的container架構(gòu)圖

2.3.2 本地開發(fā)環(huán)境準(zhǔn)備

我所使用的Mac,是Apple M1 Pro,32G內(nèi)存。對(duì)于在本機(jī)跑docker compose和前后端應(yīng)用,只要不同時(shí)開著intellij idea和webstorm,8G內(nèi)存應(yīng)該是夠用了。

[小心坑!不要直接使用官網(wǎng)安裝包安裝工具]

我之前安裝jdk的習(xí)慣,一直是先確定要裝哪個(gè)版本的jdk,比如jdk11,然后在oracle官網(wǎng)上找到j(luò)dk11的下載頁面,下載對(duì)應(yīng)操作系統(tǒng)的安裝包,然后解壓或安裝。

但后來發(fā)現(xiàn),jdk版本更新得很頻繁。如果想用現(xiàn)在的主流版本jdk17,就得再從官網(wǎng)下載并安裝jdk17,然后手工在/.zshrc或/.bashrc里修改JAVA_HOME和PATH環(huán)境變量。這樣才能從jdk11切換到j(luò)dk17。如果有老舊項(xiàng)目又需要用jdk11,又得手工修改環(huán)境變量。這太累了。

而像git這樣的工具,雖然版本更新得不那么頻繁,如果你也是從官網(wǎng)下載安裝包安裝,等過了幾個(gè)月,想升級(jí)版本時(shí),經(jīng)常會(huì)忘記當(dāng)初是如何安裝的,導(dǎo)致難以卸載并重新安裝。

所以像jdk和node.js甚至git這樣的工具,一般情況下不建議直接從官網(wǎng)下載安裝包安裝,而是使用熱門的包管理器來安裝。這樣當(dāng)要切換同一工具的不同版本、升級(jí)版本和卸載時(shí),就方便多了。

如果你在macOS上的git、jdk和node.js/npm之前是直接使用官網(wǎng)安裝包安裝的,而沒有使用包管理器來安裝,那么推薦你設(shè)法把它們先卸載,然后使用下面的包管理器來安裝。否則,你就要冒因工具版本與我所用的不一致,而導(dǎo)致各種問題的風(fēng)險(xiǎn)。而我下面描述的工具版本,是經(jīng)過我測(cè)試過了的。

用包管理器homebrew安裝文件版本管理工具git 2.42.0以便下載本項(xiàng)目代碼

安裝homebrew方法參見:https://brew.sh/。我用的homebrew版本是4.1.16。

安裝git:brew install git,參見: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安裝后端開發(fā)工具jdk 17.0.8.1-tem以便在本地進(jìn)行后端構(gòu)建

安裝sdkman方法參見: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,參見: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方法參見: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ù)庫及其管理工具

參見: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 在本地開發(fā)環(huán)境里跑通shopping list web app

啟動(dòng)docker desktop

在容器中運(yùn)行postgres數(shù)據(jù)庫和能查看數(shù)據(jù)庫中數(shù)據(jù)的pgadmin以便在本地開發(fā)環(huán)境里運(yùn)行g(shù)radle構(gòu)建和測(cè)試

[小心坑!不要再使用官網(wǎng)安裝包安裝數(shù)據(jù)庫和管理工具]

在實(shí)現(xiàn)新功能和修bug的時(shí)候,如果能在本地運(yùn)行一個(gè)數(shù)據(jù)庫和數(shù)據(jù)庫管理工具,就能很方便地進(jìn)行自測(cè)。你當(dāng)然可以從官網(wǎng)下載數(shù)據(jù)庫和管理工具的安裝包,在本地電腦上安裝。但如前面安裝jdk類似,將來卸載或升級(jí),會(huì)比較麻煩。在容器化的時(shí)代,如果想使用數(shù)據(jù)庫及其管理工具,你完全可以從http://hub.docker.com(又叫Docker hub)上,下載數(shù)據(jù)庫和管理工具的docker image文件,然后在本地電腦用簡(jiǎn)單的一行命令,啟動(dòng)相應(yīng)的容器,來使用數(shù)據(jù)庫及其管理工具。將來卸載或升級(jí),也是運(yùn)行一行命令的事兒,多方便。

有人會(huì)問:容器里跑數(shù)據(jù)庫,要是關(guān)閉或刪除容器,那數(shù)據(jù)不就丟了?其實(shí)不用擔(dān)心,你可以為數(shù)據(jù)庫容器設(shè)置一個(gè)位于本地硬盤中的volume,以便保存持久化的數(shù)據(jù)。只要你不刪除這個(gè)volume,數(shù)據(jù)庫容器關(guān)閉后再啟動(dòng),仍然能夠獲取之前的數(shù)據(jù)。

在本地開發(fā)環(huán)境里跑通shopping list web app,首先要把postgres數(shù)據(jù)庫和pgadmin管理工具啟動(dòng)起來。因?yàn)橹蟮暮蠖薬pp在使用gradle進(jìn)行構(gòu)建時(shí),會(huì)運(yùn)行自動(dòng)化測(cè)試,需要訪問數(shù)據(jù)庫。如果在后端app構(gòu)建時(shí)不啟動(dòng)postgres數(shù)據(jù)庫,那么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ù)庫和pgadmin管理工具。這個(gè)命令會(huì)讀取當(dāng)前文件夾下面的docker-compose.yml文件中的postgres和pgadmin服務(wù),并啟動(dòng)起來。我會(huì)在系列文章的第二篇,解讀docker-compose.yml文件。

驗(yàn)證容器:在docker desktop的container界面里,能看到運(yùn)行起來的兩個(gè)容器,如圖5所示。

圖5 在docker desktop的container界面里,能看到與數(shù)據(jù)庫相關(guān)的兩個(gè)容器

驗(yàn)證數(shù)據(jù)庫:

打開瀏覽器訪問pdadmin數(shù)據(jù)庫管理工具鏈接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ù)庫出現(xiàn)問題時(shí)查看數(shù)據(jù)庫里的數(shù)據(jù),如圖6所示。這里的Username和Password也是在docker-compose.yml文件中的postgres服務(wù)中設(shè)置好的。

圖6 用pgadmin工具管理postgres數(shù)據(jù)庫

在本地開發(fā)環(huán)境啟動(dòng)后端app

重新打開一個(gè)terminal,進(jìn)入項(xiàng)目文件夾,然后進(jìn)入后端代碼文件夾:cd back-end。啟動(dòng)后端app:./gradlew bootRun。

驗(yàn)證后端app:打開瀏覽器訪問http://localhost:8081/swagger-ui.html,如果能看到OpenAPI definition頁面,就表示后端已經(jīng)起了??梢栽谶@個(gè)頁面試用一下GET /api/v1/shopping-items接口,應(yīng)該返回[]空記錄。

在本地開發(fā)環(huán)境啟動(dòng)前端app

重新打開一個(gè)terminal,進(jìn)入項(xiàng)目文件夾,然后進(jìn)入前端代碼文件夾:cd front-end。先運(yùn)行命令npm install,安裝package.json文件所設(shè)置的依賴庫。

只有等依賴庫安裝好了,才能運(yùn)行命令npm run dev啟動(dòng)前端app。之后,屏幕會(huì)出現(xiàn)提示諸如Local: http://localhost:5173/的信息。

驗(yàn)證前端app:打開瀏覽器訪問http://localhost:5173,能看到ShoppingList頁面。在Item輸入框中輸入“a banana”,點(diǎn)擊Add按鈕,會(huì)出現(xiàn)什么結(jié)果?”a banana”竟然沒有出現(xiàn)在下面的清單里!

[小心坑!CORS問題導(dǎo)致前端無法訪問后端]

此時(shí)為何無法插入數(shù)據(jù)?可以用快捷鍵Cmd+Option+I打開Developer Tools界面,在Network頁簽的Console里,能看到前端訪問后端時(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就能告訴瀏覽器:“雖然訪問我的這個(gè)請(qǐng)求來自前端app,但我信任它,所以你可以放心地加載和展示我所提供的信息?!庇谑菫g覽器就能順利展示前端app訪問后端app所獲取的數(shù)據(jù)。而上面的錯(cuò)誤信息表明,用戶從前端app的網(wǎng)址(http://localhost:5173)訪問后端app網(wǎng)址(http://localhost:8081)里的信息,被瀏覽器攔截了。這說明后端app沒有設(shè)置好CORS特定的權(quán)限來告訴瀏覽器:“前端這個(gè)請(qǐng)求是允許的,你可以放心接收?!?/p>

如何查看后端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來訪問它。而除此之外的origin,瀏覽器就給用戶報(bào)上面的CORS錯(cuò)誤,并拒絕訪問。

此時(shí)要解決這個(gè)問題,該怎么辦?既然后端已經(jīng)允許前端app從http://localhost:8080這個(gè)origin來訪問,那么如果讓前端在8080號(hào)端口運(yù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:打開瀏覽器訪問http://localhost:8080,在Item輸入框中再次輸入“a banana”,點(diǎn)擊Add按鈕。”a banana”果真出現(xiàn)在下面的清單里!你也可以試試點(diǎn)擊a banana右邊的radio button,把這個(gè)購物項(xiàng)設(shè)置為已購買,或者點(diǎn)擊Delete按鈕,刪除這個(gè)購物項(xiàng)。

此時(shí)還可以用快捷鍵Cmd+Option+I打開Developer Tools界面,在Network頁簽的Console里,就看不到任何錯(cuò)誤信息了。

你還可以用瀏覽器訪問http://localhost:5050/,用之前配置好的pgadmin數(shù)據(jù)庫管理工具,看看shoppingList數(shù)據(jù)庫中是否存入了你在前端app所添加的購物項(xiàng)。

[小心坑!docker desktop的kubernetes里的配置會(huì)搗亂]

有一天,我使用了上面的步驟,讓前端app在端口8080上啟動(dòng)。但當(dāng)打開瀏覽器訪問http://localhost:8080時(shí),又是前端無法訪問后端。打開瀏覽器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.??雌饋碛质荂ORS問題。

我想試試后端的CORS配置是否起作用了。于是我在顯示前端頁面的Chrome瀏覽器的console里,輸入命令fetch(“http://localhost:8081/api/v1/shopping-items”).then(a => a.text()).then(console.log),來從后端app獲取所有購物項(xiàng)數(shù)據(jù),結(jié)果發(fā)現(xiàn)能夠獲取到,返回結(jié)果是[{“id":1,"item":"a banana”,”purchased”:true}]。因?yàn)槲沂菑?code>http://localhost:8080頁面的console里運(yùn)行的fetch命令,這就說明后端代碼ShoppingListApplicationConfig類中的CORS的設(shè)置起作用了。

那究竟是什么原因?qū)е聢?bào)CORS問題呢?仔細(xì)再看錯(cuò)誤信息,說來自前端的請(qǐng)求,要訪問后端http://shopping-list-api-ingress:8081/api/v1/shopping-items接口路徑時(shí),出現(xiàn)了CORS問題。但后端接口路徑明明是http://localhost:8081/api/v1/shopping-items啊。這是怎么回事?另外,我的代碼里,也沒有出現(xiàn)過shopping-list-api-ingress這樣的字符串。那為何前端app在訪問后端app時(shí),卻使用了http://shopping-list-api-ingress:8081/api/v1/shopping-items這樣的陌生路徑?

仔細(xì)回憶,才想起來,前兩天在docker desktop里試用了本地電腦kubernetes(簡(jiǎn)稱k8s)集群功能,并在里面配置了名為shopping-list-api-ingress的ingress配置。ingress的解釋,參見本文2.5.2。

看起來前端在訪問后端時(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。再次訪問前端。一切正常!

如果你有興趣,可以用Insomnia或postman驗(yàn)證后端app接口。之前clone下來的代碼里,有一個(gè)Insomnia_2023-10-06.json文件,可以安裝Insomnia(參見:[https://insomnia.rest/),創(chuàng)建一個(gè)collection,并在里面import這個(gè)文件來驗(yàn)證后端app接口。相比postman來說,Insomnia對(duì)于初學(xué)者更加輕量和易用。

至此,shopping list web app就已經(jīng)在本地開發(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,來終止這三個(gè)程序的運(yùn)行。最后在運(yùn)行本地docker compose命令的terminal界面里,運(yùn)行命令docker compose down,來終止并刪除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)圖沒變,還是見圖3和圖4.雖然Shopping list web app在本地docker compose里的架構(gòu),與在本地開發(fā)環(huán)境里的架構(gòu),在c4 model架構(gòu)圖中的畫法相同,但在實(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)境??梢栽赿ocker 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)檫@樣一來,在k8s云集群里跑通shopping list web app時(shí),k8s云集群就能從Docker hub里拉取你所構(gòu)建的前后端app的docker image。免費(fèi)注冊(cè)Docker hub賬號(hào),參見: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ù)庫和pgadmin管理工具。

然后新打開一個(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 .來構(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ò)展,提供了后者所沒有的緩存的導(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將來推送到Docker hub上的鏡像庫(repository)名稱。</docker-hub-username>

[小心坑!docker buildx命令最后的那個(gè)小數(shù)點(diǎn)不要忘了]

上面命令最后有一個(gè)不起眼的小數(shù)點(diǎn)。千萬不要把它忘了。這代表把當(dāng)前文件夾作為build的上下文,以找到諸如jar文件這樣的構(gòu)建資源。

[小心坑!如何知道所構(gòu)建的image對(duì)應(yīng)的是代碼庫中的哪些代碼?]

我們知道,隨著不斷提交,代碼庫中的代碼總是在不斷變化。如果有一天,你推送到Docker hub中的image里有bug,你想打開對(duì)應(yīng)的源代碼看一下。但距離你構(gòu)建這個(gè)image已經(jīng)過去好幾天了,你也往代碼庫里提交了不少代碼。當(dāng)初構(gòu)建這個(gè)image的代碼也改了不少。此時(shí)你該如何在代碼庫中,還原當(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庫里打一個(gè)同名的tag。這樣通過識(shí)別這個(gè)tag,你就能把image和代碼對(duì)應(yīng)上了。最后別忘了運(yùn)行命令git push origin v1.0.docker-compose把這個(gè)tag推送到遠(yuǎn)程git庫中。

第三步,把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ì)問,為何不是先用命令npm run build來構(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 .來構(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來啟動(dòng)postgres、pgadmin、shopping-list-api和shopping-list-front-end這四個(gè)容器即可。此時(shí)可以在docker desktop里查看這4個(gè)容器的運(yùn)行狀態(tài)。還可以在瀏覽器里訪問http://localhost:8080/來試用購物列表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)過,讓講解更有趣,我打算把架構(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è)方法參見:https://azure.microsoft.com/。

注冊(cè)完后,可以創(chuàng)建一個(gè)名為my-k8s-cluster-1的k8s service,以及名為my-azure-resource-group-1的resource group。然后登錄主頁https://portal.azure.com/#home,就能看到你所擁有的資源,如圖7所示。

圖7 你在azure k8s service云平臺(tái)上所擁有的資源

打開docker desktop kubernetes讓kubectl能正常工作

接下來,你需要安裝工具kubectl,以便從macOS連上k8s云集群。做法是在docker desktop里,點(diǎn)擊settings,選擇Kubernetes,然后把Enable Kubernetes左邊的勾選框勾上。之后點(diǎn)擊Apply & reset按鈕。

驗(yàn)證docker desktop k8s能否正常工作:等reset結(jié)束后,你能在docker desktop的主界面左下角的小鯨魚圖標(biāo)上方,看到一個(gè)綠色背景的小橫條,上面有k8s的舵輪圖標(biāo)。綠色背景,表示docker desktop k8s運(yùn)行正常。另外,你可以打開一個(gè)terminal窗口,在里面輸入命令kubectl version -o yaml。如果能看到clientVersion和serverVersion,就說明操作k8s的命令kubectl能正常工作了。

連上azure k8s service云平臺(tái)

要從你的Mac連上azure k8s service云平臺(tái),需要改一個(gè)配置文件。這個(gè)文件是你的mac電腦的~/.kube文件夾下的config文件。你可以用你喜歡用的編輯器,打開這個(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)樵诒疚闹?,我們不用docker desktop k8s所提供的單node的本地集群,所以為簡(jiǎn)單起見,可以把你mac上的~/.kube/config文件先備份,然后用azure k8s service云平臺(tái)的同名配置文件將其替換。

那如何獲取azure k8s service云平臺(tái)的配置文件?方法是你需要在瀏覽器里,登錄你的azure k8s service云平臺(tái)。在頁面上方搜索框的右側(cè),有一個(gè)Cloud shell圖標(biāo)。點(diǎn)擊這個(gè)圖標(biāo),就能在屏幕下方,看到一個(gè)黑色背景的命令行界面出現(xiàn)。點(diǎn)擊命令行界面上方的兩個(gè)大括號(hào){}圖標(biāo)Open editor,就能在左側(cè)打開一個(gè)文件樹。在文件樹中,找到.kube文件夾并打開,然后點(diǎn)擊config文件。右側(cè)就會(huì)出現(xiàn)這個(gè)文件的內(nèi)容。你把這個(gè)文件的內(nèi)容全部復(fù)制出來,保存到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),來部署。但這樣想,就踩進(jìn)了一個(gè)坑。在討論坑之前,先看看在k8s云集群里跑通與在本地跑通之間的差異。

在k8s云集群里跑通shopping list web app,與在本地docker compose里跑通,有什么差異呢?有3個(gè)差異。

第一個(gè)差異,是后端app所依賴的數(shù)據(jù)庫主機(jī)名,不再是localhost,而是k8s云集群里postgres數(shù)據(jù)庫的內(nèi)部service名。這需要改動(dòng)back-end/src/main/resources/application.properties文件,將里面的localhost,替換為${DB_HOST}。即通過在下面介紹的deployment配置文件設(shè)置的DB_HOST環(huán)境變量,來確定postgres數(shù)據(jù)庫的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%%。這也是通過在下面介紹的deployment配置文件設(shè)置的API_URL環(huán)境變量,來確定后端app的服務(wù)名。為了能夠在js代碼中,替換后端app的服務(wù)名,需要改動(dòng)front-end/Dockerfile和新增front-end/entrypoint.sh文件。

第二個(gè)差異,就是一個(gè)坑。后端的CORS的配置中的allowedOrigins,該如何配前端app在k8s云集群中的對(duì)外域名和端口號(hào)?我沒有為這個(gè)項(xiàng)目申請(qǐng)域名。域名也不能寫成內(nèi)部service名,因?yàn)閮?nèi)部名無法用于外部訪問。能把域名寫成ip地址嗎?在云集群中,ip地址經(jīng)常會(huì)發(fā)生變化。每次ip變了就去改配置,多麻煩。這個(gè)坑該如何爬出來?

我還真的把postgres、后端shopping-list-api和前端shopping-list-web-app都部署到k8s云集群里,并讓前端擁有一個(gè)外部IP。結(jié)果發(fā)現(xiàn),當(dāng)我用瀏覽器訪問前端外部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.

后來也是查了很多資料,在朋友圈求助,經(jīng)過朋友們的提醒,并嘗試了一下,發(fā)現(xiàn)為shopping list web app配置ingress能解決這個(gè)難題。

在k8s里,ingress是一種規(guī)則和配置的集合,它能幫助外部的網(wǎng)絡(luò)請(qǐng)求,來查找到和訪問集群內(nèi)的服務(wù)??梢园阉胂蟪梢粋€(gè)交通指揮員,它知道如何根據(jù)特定的規(guī)則把外面來的車輛(網(wǎng)絡(luò)請(qǐng)求)引導(dǎo)到正確的停車位(服務(wù))。

2.5.3 在k8s云集群里的架構(gòu)

在k8s云集群里,就難以使用pgadmin數(shù)據(jù)庫管理工具了。所以圖8的context架構(gòu)圖只有user。

圖8 Shopping list web app在k8s云集群里的的context架構(gòu)圖

咱們這個(gè)web app,用戶不再直接訪問前端app的對(duì)外IP和端口,而是直接訪問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訪問后端app獲取數(shù)據(jù),就都在同一個(gè)ingress nginx controller所對(duì)外暴露的IP和端口下,這樣對(duì)瀏覽器來說,就不存在CORS的跨域問題了。如圖9所示。

圖9 Shopping list web app在k8s云集群里的的container架構(gòu)圖

2.5.4 如何從坑里爬出來

要從坑里爬出來,就需要新增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ù)庫管理工具,在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ù)庫跑起來,以便構(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ù)庫和pgadmin管理工具。

然后新打開一個(gè)terminal窗口,進(jìn)入項(xiàng)目文件夾,運(yùn)行cd ../back-end,進(jìn)入后端文件夾。因?yàn)楹蠖薬pp所依賴的數(shù)據(jù)庫主機(jī)名,現(xiàn)在已經(jīng)改為環(huán)境變量${DB_HOST}了,所以在構(gòu)建前,需要在terminal窗口中,運(yùn)行命令export DB_HOST=localhost來設(shè)置環(huán)境變量。

之后,可以運(yùn)行命令./gradlew clean build來生成后端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 .來構(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拉下來,運(yùn)行一個(gè)容器,然后進(jìn)去看文件docker-entrypoint.sh的內(nèi)容,也看不出所以然。后來查了半天,才知道原因在于我用arm64架構(gòu)的mac在構(gòu)建image時(shí),沒有指定所構(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 .來構(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 updatebrew install azure-cli進(jìn)行安裝。安裝完后,可以運(yùn)行命令az --version來驗(yàn)證安裝是否成功。然后可以運(yùn)行az login來登錄azure k8s service云平臺(tái)。

我們?cè)趉8s云集群里為這個(gè)web app所創(chuàng)建的資源,最好都放到一個(gè)namespace里,這樣便于管理。將來不用云服務(wù)了,要?jiǎng)h除一個(gè)namespace里所有資源以便省錢,也就是運(yùn)行一條命令的事兒。具體如何做,見下文“清理現(xiàn)場(chǎng)”。

因?yàn)槊總€(gè)命令一般都有掛上NAMESPACE,所以把它設(shè)置到環(huán)境變量里比較方便:`export NAMESPACE=shopping-list-web-app`。然后可以運(yùn)行`kubectl create namespaceNAMESPACE`來創(chuàng)建這個(gè)namespace。

前面講到,在一個(gè)操作系統(tǒng)里安裝工具,最好用包管理器。這樣便于維護(hù)工具的版本。對(duì)于云計(jì)算操作系統(tǒng)k8s來說,helm就是這樣的包管理工具。我們可以用helm來安裝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)證一下。

接下來就可以用kubectl,運(yùn)行下面命令,來往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
如果一切順利,沒有出錯(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。

打開瀏覽器,訪問http://20.72.130.209/。如果一切正常,就能在上面愉快地管理購物項(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ì)在接下來的兩篇文章中,進(jìn)行解讀。敬請(qǐng)關(guān)注。


要想找到這一系列文章的最新版本,可以在知乎搜“體驗(yàn)更好地做軟件”專欄。

如果你喜歡這一系列文章,歡迎點(diǎn)贊和收藏,并在留言區(qū)寫下為何喜歡,以便我將來寫更多你喜歡的文章。

如果你不喜歡,也歡迎你留言告訴我哪里可以再改進(jìn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容