# Hands-on: Container Application CI/CD with Azure Pipeline (.NET Container)
**地區建議使用 Southeast Asia**
## 1/ 建立 Azure 上 Azure Container Registry 資源
- 連結到 **[Azure Portal](https://portal.azure.com/)**
- 上方的搜尋列中搜尋 `Container Registry`,點選下方搜尋結果 **Container registries**

- 點選 **[+ Create]**

- 在 Resource Group 的欄位點選 **[+ Create New]**,自行填入要命名的資源群組名稱

- 將其餘資訊填寫完成,**[SKU]** 選擇為 **standard**,最後選擇 **Review + create**

- 確認資訊沒問題,選擇 **Create**

- 完成後到建立好的 Container Registry,選擇 Access Key,將 Admin user 開啟

<!-- - 完成後到建立好的 Container Registry 資源,點選 **Overview** 中的 **[Update]**

- 將 **Admin User** 更改為 **Enable** 後 Save
 -->
## 2/ 建立 Build Pipeline
### 建立程式碼的第一條 Build Pipeline
- 選擇左方選單列中 Pipelines 下的 **[Pipelines]**,點選 **[Create Pipeline]**

- 選擇畫面下方說明文字中的 **[Use the classic editor]**

- 選擇程式碼來源及分支,這裡無須特別操作,直接可以選擇 Continue

- 在右上方的搜尋欄位尋找 `docker`,找到 **Docker Container**,點選 **[Apply]**

<!-- - 點選在 **Agent Job 1** 旁邊的 **+**,選到第一個 **.NET Core** 按 **[Add]**

- 將 .NET Core 的 task 長按,用拖移方式移動到第一位

- 修改 .NET Core task 的內容
- **Command** : **test**
- **Path to project(s)** : `**/**.UnitTest/**.csproj`
 -->
- 選到 Build an image,完成內容
- **Azure Subscription** : **<建立 ACR 資源時使用的訂閱>**
- **Azure Container Registry** : **<建立的 ACR 名稱>**

- 選到 Push an image,完成內容
- **Azure Subscription** : **<建立 ACR 資源時使用的訂閱>**
- **Azure Container Registry** : **<建立的 ACR 名稱>**

- 選擇 **Pipeline** 的地方,將 **Agent Specification** 選到 **Ubuntu-20.04**
- 選擇上方列表的 **[Triggers]** tab,將畫面右方的 [Enable continuous integration] 打勾

- 完成後點選上方的 **[Save & queue]** ,選擇 **[Save & queue]** 後在跳出的視窗點選 **[Save and run]**

<!--
### 紀錄 Bug
- 執行到一半會發現顯示錯誤

- 點選上方 **[Tests]** 的 tab,點選下方 **[IndexPageTest]** 後會看到 Error message 顯示為: *`Assert.AreEqual failed. Expected:<DevOps Workshop>. Actual:<Success!>.`*

- 將錯誤訊息是窗關閉,點選頁面中 **[Bug]** 的按鈕,選擇 **[Create Bug]**

- 詳細錯誤訊息已自動帶入,點選 Related Work 欄位中的 **[+ Add link]** 並選擇 **[Existing item]**

- Link Type 選擇 **[Parent]**,Work items to link 選擇剛剛建立的 **[DevOps Workshop]** PBI

- 點選 **[Save & Close]**

- 點選左方選單的 **[Sprints]** 在剛剛建立的 Bug 那欄點選 **[+]**

- 填入標題 `更正標題為 DevOps Workshop`

- 點選剛剛建立的 task ,將以下資訊填寫到 Description,然後點選 **[Save & Close]**
```
修改 Controllers / HomeController.cs 將標題修正
```

### 修復 Bug
- 回到 Files ,點選到 *AspNetCore.Sample.Web* > *Controllers* > *HomeController.cs*



- 修改第 13 行,將原本的 "Success!" 修改為 "DevOps Workshop"
```csharp=11
public IActionResult Index()
{
ViewData["Message"] = "DevOps Workshop";
return View();
}
```
- 修改完成後點選右上方的 Commit

- **[Work items to link]** 欄位選擇先前建立的 `更正標題為 DevOps Workshop` task

- 此時回到 **[Pipeline]** 發現 pipeline 已自動執行,點選 pipeline

- 可看到有兩次執行紀錄,點選最新一次的執行紀錄

- 可以查第二次執行的內容,點選 **[Agent Job]**

- 可以查看各個 task 的 Log 細節
 -->
## 3/ 建立 Azure 上 App Service 資源
- 回到首頁左方的選單列表,找到 App Service 並點選

- 點選 **[+ Create]**

- 在 Resource Group 的欄位選擇和建立 Container Registry 時同一個資源群組
- 將其餘資訊填寫完成
- **Publish** : Docker Container
- **Region** : Southeast Asia
- **Linux Plan (Southeast Asia)** 的欄位點選 **[Create new]**,並輸入自行定義的名稱

- **Pricing plan** 欄位點選 **[Explorer pricing plans]**

- 選擇 **Standard S1** 後點擊 **[Select]**

<!-- - Sku and size 欄位點選 **[Change size]**

- 在跳出的視窗選擇 **[Production]**,規格選擇 **[S1]**
 -->
- 完成後點選下方 **[Next: Docker >]** 的按鈕
- 將以下資訊填寫完成,完成後點選下方 **[Review + Create]**
- **Image Source** : Azure Container Registry
- **Registry** : (選擇先前建立的 Container Registry)
- **Image** : (唯一選擇)
- **Tag** : (唯一選擇)
- **Satrup Command** : (留白即可)

<!-- - **Options**: Single Container
- **Image Source**: Quickstart
- **Sample**: NGINX
 -->
- 接下來會進入 review 的頁面,點選 **[Create]**

- 建立完成後會挑轉到資源正在建立中的頁面,先不要關閉
### 利用 App Service 建立多個部屬環境
- 建立完成後會顯示資源已建立完成,點選 **[Go to resource]**

- 選擇 App Service 左方選單中 Deployment 區塊中的 **[Deployment Slots]**

- 點選上方的 [Add Slot],將 slot 的名稱設定為 `dev`,點選 **[Add]** 添加,並將 **Clone settings from** 設定為來自 Production,已獲取相關 ACR 的設定

<!--  -->
- 出現 *Successfully created slot 'dev' 的訊息後,點選 **[Close]**

## 4/ 建立 Release Pipeline
### Dev 環境
- 在左方選單列選擇 Pipelines 下的 **[Release]**,進入後點選 **[New Pipeline]**

- 找到 **[Azure App Service deplotment]**,點選 **[Apply]**

- 將 Stage name 更改為 `Dev`,完成後點選 **[X]** 關閉視窗

- 選擇 `Dev` stage 內的 **[1 job, 1 task]**

- Azure Subscription 的部分選擇建立 App Service 資源時所選擇的訂閱,選擇完成後點選藍色的 **[Authorize]** 按鈕

- 在 **[App Type]** 選擇 **Web App for Containers (Linux)**

- 等待授權完成,在 App Service name 找到剛剛建立的 App Serivce 資源,**Registry or Namespace** 填入`<Azure Container Registry Name>.azurecr.io` (Azure Container Registry Name 為先前建立的 ACR 資源)
- 可在 Azure Portal 上,建立的 ACR 資源 Overview 中找到 **Login Server**

- 複製並填入為 Registry

- 在 Azure Portal 上的 Container Registry 資源中,左方欄位 [Repository] 找到要使用的 image 的 repository name

- 回到 Azure DevOps 將 **[Repository]** 填入 repository name

- 點選 **[Deploy Azure App Service]** 這個 task,將右邊欄位中的 **[Deploy to Slot or App Service Environment]** 打勾,Reource Group 選擇 App Service 的資源群組 (應該只會有一個),Slot 的部分將 Production 更改為 **[dev]**

- 完成後點選右上角的 **[Save]**,點選 **[OK]**

### Production 環境
- 選擇上方列表的 **[Pipeline]** 列表

- 將鼠標移動到 **[Dev]** stage,會出現 **[+]**,點選添加一個新的 stage

- 選擇在 select a template 底下的 **[Empty job]**

- 將 stage 名稱更改為 `Production`,完成後點選 **[X]** 關閉

- 點選 Production 的 **[1 job, 0 task]**

- 點選在 Agent Job 中的 **[+]**

- 在 Add tasks 中選到 **[Deploy]**,找到 **[Azure App Service manage]**,點選 **[Apply]**

- 點選 **[Swap Slots:]** task,將欄位資訊填寫完整
- **Azure Subscription** : 建立 App Service 的訂閱
- **Action** : Swap Slots
- **App Service Name** : 建立的 App Service 名稱
- **Resource Group** : 建立的 App Service 資源群組
- **Source Slot** : dev
- 確保 **[Swap with Production]** 打勾

- 點選右上角的 **[Save]**,然後 **[OK]**
- 點選上方列表的 **[Pipeline]**

### 細節調整
<!-- - 點選在 Artifacts 方格中的 **[+ Add an artifact]**
- **Source type** : Build
- **Source (build pipeline)** : 先前建立的 build pipeline

- 選擇完成後點選 **[Add]** -->
<!-- - 點選 Artifacts | + Add 的 **[+Add]** 再添加一個來源,選擇 **[6 more artifact types]** 展開
 -->
- 點選在 Artifacts 方格中的 **[+ Add an artifact]**,選擇 **[6 more artifact types]** 展開

- 選擇 **[Azure Container Registry]**,將下方欄位根據先前建立的資源依序填寫完成以連結至現有的 Azure Container Registry 資源,最後點選 **[+Add]** 添加完成

- 點選上步驟建立的 Azure Container Registry 來源方框中右上角的閃電符號,在 Continuous deployment trigger 下將 **[Disable]** 更改為 **[Enable]**,完成後點選 **[X]** 關閉視窗

- 再新增一個 Artifact 為 Build

- 在 Dev 的 stage 中點選右邊的人頭,在跳出的視窗中將 **[Post-deployment approvals]** 改為 **[Enabled]**,並在 **[Approvers]** 欄位中選擇自己,完成後點選 **[X]** 關閉視窗

- 在 Production 的 stage 中點選左邊的人頭,在跳出的視窗中將 **[Pre-deployment approvals]** 改為 **[Enabled]**,並在 **[Approvers]** 欄位中選擇自己,完成後點選 **[X]** 關閉視窗

- 將上方 All Pipelines > **[New release pipeline]** 修改為 `Release to Azure`

- 點選到上方列表的 **[Options]**,點擊到 **[Integrations]** 的欄位,將以下兩項目打勾
- [x] Report deployment status to Work
- [x] Report deployment status to Boards
- **Dev** > Testing
- **Production** > Production

- 點選回 **[Pipeline]**

- 點選 Save

- 接著選擇 **[Create Release]**

- 在跳出的視窗中點選 **[Create]**

- 視窗上方會有一則訊息,點選 **[release 1]**,可以查看目前部屬進度

### 環境驗證及審核
- 當 Dev stage 的部署狀態變更為 *Pending approval* 時(如一直未改變狀態可點選 **[Refresh]** 更新狀態),此時回到 **[Azure Portal](https://portal.azure.com)** 上,找到剛剛建立的資源群組,選擇 type 為 *App Service (slot)* 的 **dev** 資源

- 選擇頁面中 URL 的連結

- 顯示新的網頁已經完成部署

<!-- - 將網址列中的 `-dev` 拿掉,進入網頁

- Production 的環境尚未部屬,仍在舊的頁面
 -->
- 已確認在 Dev 環境中部署正確,回到 Azure DevOps 中點選 Dev stage 的 **[Approve]**

- 在跳出的視窗點選 **[Approve]**,然後 **[X]** 關閉

- 確認 Dev 完成

- 此時 Prodction 的狀態也會變更為 pending approval,點選 **[Approve]** 開始進行 Production 的部屬

- 確認 Production 的部署正確
## 5/ 實際更新上版一次
<!-- - 回到 **[Pipelines]**,點選先前建立的 build pipeline

- 進入後點選右上角的 **[Edit]**

- 將鼠標移動到 **[dotnet test]** task 按右鍵,選擇 **[Disable selected task(s)]**,點選 **[Save]**
 -->
- 回到 Sprints,在 **[DevOps Workshop]** 的 PBI 中,新增一個 task 為 `將首頁標題添加今天日期`

- 回到 Files ,點選到 *AspNetCore.Sample.Web* > *Controllers* > *HomeController.cs*
- 修改第 13 行,將原本的 "Success!" 修改為 "DevOps Workshop - <今天日期>",修改完成後點選 **[Commit]**
```csharp=11
public IActionResult Index()
{
ViewData["Message"] = "DevOps Workshop - 0824";
return View();
}
```

- **[Work items to link]** 欄位選擇先前建立的 `將首頁標題添加今天日期` task

- 此時回到 **[Pipeline]** 發現 pipeline 已自動執行

- 待 build pipeline 執行完成後回到 release pipeline 會發現已自動啟動部署流程

- 根據先前的步驟進行審核及檢查的動作
<!-- - 回到 **[Sprint]** 中,`將首頁標題添加今天日期` task 查看內容,可以在 Deployment 及 Development 中看到建置及部屬的狀況
 -->
## 6/ Dashboard
- 左邊選單列中的 Overview 下點選 **[Dashboards]**,點選 **[Add a widget]**

- 選擇 **[Assign to me]**,然後點選 **[Add]**

- 可再添加 **[Deployment Status]**、**[New Work Item]**,**[Query Tile]** 或自行添加其他項目



- 完成後選擇 **[Done Editing]**

- 點選 Deployment Status 進行設定,將 size 改為 **[3x2]**,並填入 **[Build pipeline]** 和 **[Linked release pipelines]**,設定完成後點選 **[Save]**

- 點選 **[Query Tile]**,**[Select Queries]** 選擇 **[Shared Query]** > **[Task in Progress]**(先前在 query 內建立的)

## 7/ 清除資源
- 點選左方選單列表中的 **[Project Settings]**,捲到最下方有 **[Delete]** 的按鈕可以進行移除

- 到 [Azure Portal](https://portal.azure.com/)
- 找到建立的資源群組,點選頁面中的 **[Delete resource group]**,在欄位中填入資源群組的名稱,選擇 **[Delete]** 即可完成清除

## Reference
- [Hands-on : Azure DevOps Service (.NET Core Web)](https://hackmd.io/@msazuredev/B1nlR8un_)
- [Register free Azure account](https://azure.microsoft.com/zh-tw/free/)
- [Azure DevOps Service 免費試用申請教學](https://hackmd.io/@msazuredev/HkaaHy2AO)
- [Pipeline caching](https://learn.microsoft.com/en-us/azure/devops/pipelines/release/caching?view=azure-devops)
- [Sample Code (ASP .NET Core)](https://github.com/huier23/AspNetCoreSample.git)
- [HelloWorld.Lib](https://github.com/huier23/HelloWorld.Lib.git)
- [Azure DevOps Service Tag Released](https://devblogs.microsoft.com/devops/azure-devops-service-tag-released/)
- [Hands-on : Azure DevOps Service (Azure Kubernetes Service)](https://hackmd.io/@msazuredev/ByonLS_mY)