2022年1月25日 星期二

Azure DevOps In Action - 設計支援Docker/Container的Pipeline

近幾年Docker/Container技術相當被業界重視,而微軟的 .net 開發技術也適時的支援了容器化的功能。為了實現容器化, .net 開發技術必須支援跨平台,特別是 asp.net,這也造就了 .net core的誕生。

因此,現在您不管用何種開發工具,也可以產出能運行在Linux環境上的asp.net應用程式,這同時也表示,asp.net理所當然的也可以支援Linux Container了。

Docker file

要讓asp.net專案產出支援Docker的image非常簡單,你可以在建立asp.net專案的時候,勾選『啟用Docker』,『Docker OS』選擇Linux:
enter image description here
或者,你也可以在一開始沒有啟用Docker支援的專案中,點選滑鼠右鍵,選擇加入→Docker支援:
enter image description here
Visual Studio會幫你在專案中建立一個類似底下這樣的Docker File:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["webapp01.csproj", ""]
RUN dotnet restore "./webapp01.csproj"
COPY . .
WORKDIR  "/src/."
RUN dotnet build "webapp01.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "webapp01.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "webapp01.dll"]

這個docker file就是我們用來建立docker image的關鍵。

總的來說,透過asp.net建立一個容器化應用的流程是底下這樣:
enter image description here

asp.net的原始程式碼無須修改,只要加入了docker file,就足以產生docker image,而產生出的docker image,我們需要將其送上Docker Hub(或其他registry,例如azure container registry),以供用戶下載使用。

上面這一段動作,平時大多都可以在Visual Studio中完成。如果你的開發工具是VS Code,那你也可以透過 .net CLI搭配Docker Command來完成。從原始程式碼依照docker file的流程產出image的這個動作我們一般稱為docker build。

Docker task

知道上面的概念後,我們接著來看,現在我們想要實現的,就是在Azure DevOps當中來設計出可以自動完成docker build並且將產出的image推送上docker hub的自動化Pipeline。

其中的關鍵在於『Docker』task:
enter image description here

你可以先透過asp.net core範本建立一個Pipeline,並且在流程中加入Docker task。這個task本身就足以運行docker build與docker push指令:
enter image description here

藉此把產出的image送上docker hub(或azure container resgistry)。

其中有幾個需要稍微說明一下的參數,Container registry的部分,請選擇『New』:
enter image description here

接著,在出現的視窗中,選擇Docker Hub,然後輸入你的Docker ID與密碼即可:
enter image description here

按下Verify之後,系統會驗證你的帳密正確性,如果確定沒有錯誤,你就可以設定一個連線的名稱,然後按下Verify and Save將其儲存:
enter image description here

儲存後,你就可以在Container registry下拉選項中看到該連線項目:
enter image description here

另外,還有幾個你需要注意的選項:
enter image description here

像是 repository 路徑你必須給你在docker hub上所建立的repository名稱(上圖A),而上圖B之中的,則是專案的docker file所在路徑,一般保留上述這樣即可,這表示Pipeline會在專案的所有檔案中搜尋。

而Tags的部分,我們之所以用『$(Build.BuildId)』,是因為這個數字每一次build都不同,這樣如果你要把成品(docker image)上傳到Docker Hub的時候,才不會因為版號相同而有所衝突。

例如,這個建立好的docker image如下:
enter image description here

由於你每次建置的(Build.BuildId)都不會相同,這樣,即便pipeline被多次重覆執行,也不會發生錯誤,而(Build.BuildId)都不會相同,這樣,即便pipeline被多次重覆執行,也不會發生錯誤,而(Build.BuildId)剛好也可以做為類似版號的存在。

若pipeline正確執行,其結果會是:

你會發現,pipeline中的docker push指令順利完成,同時docker hub上的image也更新了一版:
enter image description here

從上面的介紹中你可能也會發現,其實一開始並不一定要使用asp.net core範本來做為pipeline的設計基礎,其實用空的範本(empty)也可以,因為,整個build project與build image的動作,其實都是透過docker task中的指令來完成的,因此,原本asp.net core的那幾個restore/build/pulish動作根本是可以直接槓掉(或移除)的:
enter image description here

只需要透過docker task即可幫我們完成相關的工作。除非您希望在Build Docker Image之前,先進行單元測試,這樣的話,則可以考慮保留asp.net core預設範本中的tasks。

以上,就可以輕鬆的自動化產生docker image並送上registry囉。

2022年1月17日 星期一

Azure DevOps in Action - 在CI Pipeline中發佈NuGet套件

我們在前面的章節曾經介紹過,軟體重用性提升一個很大的要素就是套件化。時至今日,不管你用哪一種軟體開發語言,大概都離不開各式各樣的套件庫。

因此,我們在前面談到Azure Repos的章節中,曾經約略介紹過如何建立套件,也介紹過利用Azure DevOps的Artifacts功能來建立的私有(Private)套件庫:
enter image description here

你大概已經知道,我們可以將組件(.dll)封裝成具有版號的套件,以便於分享給團隊甚至網際網路上所有開發人員來使用。只是先前,我們封裝好的套件(.unpkg)都是以手動方式上傳到 nuget.org,我們接著來看看,如何自動化完成這件事情。

設計自動發佈套件的Pipeline

一個自動化建置出NuGet套件並且上傳的CI Pipeline並不難設計,就建立 .net core的套件而言,你可以直接使用 ASP.NET Core的範本來修改即可:
enter image description here

接著將範本中的Publish改為(Pack):
enter image description here

因為套件本身不是網頁,因此沒必要『Publish』出什麼,倒是需要產生出 .unpkg檔案,因此我們需要執行dotnet的Pack command。

然而光Pack還不夠,你還得上傳到 nuget.org,這部分則可以透過 NuGet push task(下圖B)來完成:
enter image description here

上圖中的NuGet push task,其實是在運行NuGet的push command(上圖D),該task會在預設(上圖E)的位置嘗試尋找我們先前透過 Pack Nuget命令所建置出的 .nupkg 檔案,然後將其上傳發送到指定的套件庫。

因此,我們需要進行上傳位置的設定。

第一次連線時你必須點選(上圖F)右方的『+New』按鈕,接著會出現底下畫面:
enter image description here

在出現的畫面中,我們必須輸入ApiKey(上圖C)以便於具有連線到NuGet的權限,後續可以讓Pipeline把套件檔案上傳到Nuget。

一般來說,公開的NuGet套件庫位於:
https://api.nuget.org/v3/index.json

如同先前介紹過的,你只需要有Microsoft Account即可登入並且上傳套件。請先用你的Microsoft Account登入nuget.org
enter image description here

登入後你會發現右上角的頭像圖示選單中,就有一個API Keys選項,透過該選項,你就可以取得一組API Key。

取得API Key之後,把該Key填入下圖C的位置,設定好連線名稱(下圖D):
enter image description here

按下Save鈕之後即可。

建立好連線,並且整個Pipeline被運行之後,你應當會看到自動建置出的套件被上傳到nuget了:
enter image description here

如果要上傳到Azure DevOps的Artifacts,其實也很簡單:
enter image description here

只需要把Task的Target feed選項設為『This organization/collection』即可。

備註:

如果讀者想自己試試看,您可以參考筆者在Ggithub上的source code,位於:https://github.com/isdaviddong/HealthMgrPackageDemo.git
將其Clone到您的專案後,請修改底下檔案:
HealthMgrPackageDemo/HealthMgr.csproj
將其中程式碼 HealthMgr-[yourName] 中的 [yourName] 換掉,成為您的姓名(必須是全球唯一值),因為這個值會影響套件的ID,而該ID在整個nuget平台必須是唯一的,否則將無法上傳。

2022年1月10日 星期一

頻繁交付已不是問題,真正的挑戰在於品質

DevOps Pipeline image from Microsoft

上面這張圖,是我在講 Azure DevOps 的時候,時常跟學員分享的Pipeline,裡面粗略的談到了CI/CD Pipeline及其前段(版控)與後段(持續監控)所涉及的相關技術,可以讓對於DevOps完全沒概念的學員得以稍微一窺究竟。

但每當我繼續問學員:『你現在知道有這些內容了,那…CI/CD的目的到底是什麼? 實現CI/CD對於企業來說究竟有何好處呢? 』學員突然被問到,有時一下子會無法立刻反應過來。

我繼續說到:『倘若,CI Pipeline的主要目的是為了實現持續整合,而實現持續整合的主要目的則是為了持續交付。那…為何需要頻繁交付呢?』

過去一年,你一定有上網預約疫苗的經驗,如果沒有,你大概也有上網登記五倍券還是某種OO券的經驗,再沒有,你疫情期間總有過上網購物吧。

倘若,你上網登記或購物時,網站突然有問題,或是購物車的金額計算不太正確,你通知了網站營運單位,他們也告知您會立刻著手處理,這時候的你,會希望網站多久可以更新或修復?

一小時? 一天? 還是一兩個月?

同學幾乎都跟我說:『立刻,不然我就換一家網站購物囉~』。

是啊,立刻。

既然我們都這麼要求其他人,那我們自己所建立起來的網站呢? 能不能經得起這樣的要求? 當我們的網站有問題,或是客戶提出新的需求時,我們能夠多快的將新功能或修正交付到用戶手上?

而且你知道的,緊急狀況下,往往兵荒馬亂,即便你知道程式碼在某個地方可能有錯,但這段程式碼最初不是你寫的,你敢立刻改嗎? 你怎麼知道不會改了這邊,就壞了另一邊? 你怎麼知道這個修正會不會引起其他額外的副作用? 有些時候,程式碼明明在你的電腦上是好的,但整合了團隊中其他人開發的程式碼之後,佈署到測試機上,就是無法運行,怎麼回事呢!?

就算開發人員真的把所有問題都在測試機上改好了,是不是還要經過QA的人工測試把關才能交付給客戶呢? 但測試需要時間啊,如何才能實現『立刻』將成品交付給客戶?

上面這些,都是CI/CD想幫助你解決的問題。

持續交付,聽來很容易。也確實,因為技術的進步,現在要快速的把更新後的成品佈署到正式機上讓用戶使用,也許真的也不難。但你有把握在『快速』的同時,還能保有高品質與安全性嗎? 你有勇氣讓開發人員把程式碼修改完之後,透過Pipeline『全自動』的直接上版到正式機上嗎?

從你收到bugs或需求,一直到交付到用戶手上的這一刻,你能夠多快呢? 你的交付頻率,可以達到一周數次甚至一天數次嗎?

沒有持續整合,就沒有頻繁交付

也是,一周數次或一天數次的高品質交付,在幾年前聽起來似乎有點不可思議,但現在市場上很多網站或是應用服務,正以這樣的速度在和你的產品競爭,而你心中理想的交付,想要多頻繁呢?

然而,頻繁交付不是問題,真正的挑戰其實在於品質

現在很多網站或軟體服務的廠商,更新bugs也是挺『頻繁』的。只是這個頻繁,完全是『人工』所堆積出來的。當碰到問題,要求工程師加班熬夜立刻解決,一旦改好程式碼,開發人員自己在電腦上隨便按兩下測試一下,接著就直接把成品手動複製貼上到正式機,然後就…祈禱不要再出問題。這也難怪綠色包裝的乖乖會成為長銷商品了。

吃燒餅哪有不掉芝麻的,有bugs在所難免,特別是高壓又加班的緊張環境下,品質肯定會大打折扣,但工程師就在這樣的輪迴下一天天的過著日子。是啊,大夥拚著新鮮的肝,快速地把一堆含有潛在問題的產品交付到用戶手中,然後再快速的修復bugs,然後再快速的收到用戶傳來的新bug,然後…就這樣日復一日、年復一年,你覺得很有趣嗎?

頻繁交付的基礎,是頻繁的程式碼整合

而且,我們必須要在CI Pipeline當中,設法加入各種快速的檢查,以確保持續維持著高品質的產出,前面提到的單元測試,就是其中之一。但單元測試只是基本,除了單元測試之外,我們還應該要做靜態程式碼掃描,還應該要做套件的安全性掃描,我們要讓團隊適當地做Code Review,並且對程式碼的品質有方法、有步驟的持續進行提升,這些都是持續整合要做的事情,都做到了,你『才』算是有了一個持續交付的基礎。

沒找到真正的需求,就沒有有價值的成果

是不是這樣就夠了,差不多,但還缺一個重點,就是『需求』。

軟體開發的一切都是從需求來的,如果我們對需求沒有好的管理,我們的快速交付只是徒勞,有點像是薛西弗斯那個巨大的石頭,我們只是一次又一次,一再一再的奮力把成品快速的交到用戶手上,但卻沒辦法讓用戶滿意 – 如果你忘了需求才是一切的核心。

而需求是得要被探索和釐清的,特別是這個快速變化的時代。時間有限、資源有限,當我們想要快速將成果交付到用戶手上的同時,我們必須和時間賽跑,如何能夠快速地將用戶最需要的功能,在第一時間『先』交付到用戶手上,然後『再』持續慢慢地補齊用戶想要的其他功能,如何在交付功能之後,持續的蒐集用戶的真實反饋,調整開發的優先順序,把用戶真正需要的功能先實做出來,這其實是一門藝術。困難,但必須。因為找到真正能幫用戶產生價值的需求,才是軟體開發一切的根本。

持續高品質、同時快速的交付,是近代軟體開發一個很大的挑戰。現在我們已經有相當好用的工具(像是Azure DevOps)來幫助我們,作為頻繁交付的基礎。如果你還沒有開始,那如何善用工具來實現高品質的持續交付與整合,是你今年肯定該面對的議題。

熱門文章