[TOC]
## Introduction
- Who is this for: Developers, DevOps Engineers, new GitHub users, students, and teams.
- What you'll learn: We'll learn how to create a workflow that enables Continuous Delivery using GitHub Actions and Microsoft Azure.
- What you'll build: We will create two deployment workflows - the first workflow to deploy to staging based on a label and the second workflow to deploy to production based on merging to main.
## Agenda
- 課程說明 / 課前準備
- GitHub Action 基本介紹
- 實作
- Step 1: Trigger a job based on labels
- Step 2: Set up an Azure environment
- Step 3: Spin up an environment based on labels
- Step 4: Deploy to a staging environment based on labels
- Step 5: Deploy to a production environment based on labels
- Step 6: Production deployment
## 課前準備
### Sign up or sign in your GitHub account
進入[官方網站](https://github.com/),點選 Sign in 或 Sign up
- 如尚未有帳號,點選右上角 Sign up 進行註冊
![](https://i.imgur.com/Eeawzl1.png)
![reference link](https://i.imgur.com/ew3599M.png)
### Redeem Azure Pass
**IMPORTANT**: Please note the use of the pass is subject to the following Terms and Conditions:
- DO NOT redeem promo code with an email account that is attached to an EA, the pass will not work.
Promo code needs to be redeemed within 90-days of being received.
- Customer Live ID/Org ID will be limited to one concurrent Azure Pass Sponsorship at a time.
- Monetary credit can't be used toward third party services, premier support or Azure MarketPlace and cannot be added to existing subscriptions.
- If you add a payment instrument to the subscription and the subscription is active at the conclusion of the offer it will be converted to Pay-As-You-Go.
Subscriptions are activated within minutes of the promo code being redeemed.
**To redeem a promo code, visit www.microsoftazurepass.com and follow the [Azure Pass Redemption instructions](https://www.microsoftazurepass.com/Home/HowTo)**
- Tax information 留白即可
![](https://i.imgur.com/dbh1Br9.png)
## GitHub Action
- 提供 Continuous Integration 持續整合與 Continuous Deployment 持續部署之服務
- GitHub Flow
![](https://i.imgur.com/E23sOZS.png)
- GitHub Flow with GitHub Action
![](https://i.imgur.com/Cnhd264.jpg)
- **Workflow Components**:workflow 透過 YAML 格式定義,並保存於 .github/workflow 的路徑下, GitHub 預設提供許多 workflow 可讓使用者使用
![](https://i.imgur.com/Borq9t9.png)
- **Event**:觸發自動化工作流程,如 `on: release`、`on: pull_request`、`on: push`、`on: scheduled` 等,完整列表可查看 [Events that trigger workflows](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)
- **Job**:在相同 Runner 中所執行的一組 Step,預設以平行方式執行,也可設定為循序執行
- **Step**:在 Job 中可執行命令或 action 的獨立工作
- **Action**:經包裝後的執行內容, GitHub community 中已有許多由創作者所提供之 action 可直接使用。透過 JavaScript 或 Docker container 方式撰寫並發布至 market place 上供其他使用;action 來源也可為 workflow 相同的 repository、其他 public repository 或發布於 Docker Registry。
- **Runners**:執行自動化工作的代理程式運作於伺服器上,支援 Ubuntu、Linux、Windows 和 macOS,也可設定 self-host runner
- Workflow 語意解析
![](https://i.imgur.com/o81Kekg.png =500x)
- name: workflow 的名稱,若無設定則預設以 workflow 資料夾路徑 + 檔案名稱命名
- on:觸發 workflow event,如設定 `workflow_dispatch` 則為手動執行
```yaml
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
```
- job:每個 Job 需有 JobID 且名稱需唯一,只能以英文或_開頭命名,透過添加 `runs-on`
```yaml
jobs:
my_first_job:
name: My first job
my_second_job:
name: My second job
```
- step:Job 包含一系列 steps,step 內執行 action 時使用 `uses`,或使用 `run` 直接在 runner 上執行命令
```yaml
steps:
- uses: actions/checkout@2
- name: Run a one-line script
run: echo Hello, world!
```
## 實作
![](https://i.imgur.com/XR9bnDh.png)
- `main`
- `staging-workflow` => new workflow for staging deployment
- `azure-configuration` => new workflow for azure resource provision
- `staging-test` => commit new version and deploy to staging
- `production-deployment-workflow` => new workflow for production deployment
We'll use labels as triggers for multiple tasks:
- When someone applies a "spin up environment" label to a pull request, that'll tell GitHub Actions that we'd like to set up our resources on an Azure environment.
- When someone applies a "stage" label to a pull request, that'll be our indicator that we'd like to deploy our application to a staging environment.
- When someone applies a "destroy environment" label to a pull request, we'll tear down any resources that are running on our Azure account.
### Setup repository
- Naviagte to https://github.com/skills/continuous-delivery-azure
- Select Use this template
![](https://i.imgur.com/Q0WbBnI.png)
<!-- ![](https://i.imgur.com/NJKG5Cj.png)
![](https://i.imgur.com/ozK5pYU.png)
![](https://i.imgur.com/3QMuFpa.png)
Copy the repository URL`https://github.com/huier23/devopsdaylab-continuous-delivery-azure.git`
![](https://i.imgur.com/cVudzt1.png) -->
![](https://i.imgur.com/hrO8JVf.png)
### Step 1: Trigger a job based on labels
**Activity 1: Configure GITHUB_TOKEN permissions**
GitHub Actions provide a default GITHUB_TOKEN that can be used by steps in your workflow that require access to your GitHub repository.
- Navigate to **Settings** > **Actions | General**
![](https://i.imgur.com/MoBi8Ad.png)
- Under **Workflow permissions**, ensure that the `GITHUB_TOKEN` for this repository has **Allow GitHub Actions to create and approve pull requests** enabled and also has **Read and write permissions** enabled. This is required for your workflow to be able to upload your image to the container registry.
![](https://i.imgur.com/jHrMKxD.png)
**Activity 2: Configure a trigger based on labels**
- Go to the **Actions** tab. Click **New workflow**
![](https://i.imgur.com/QVuA2RM.png)
- Search for `simple workflow` and click Configure
![](https://i.imgur.com/mJTrc9e.png)
- Name your workflow `deploy-staging.yml`
![](https://i.imgur.com/wAeLoSq.png)
- Edit the contents of the file to add a conditional that filters the build job when there is a label present called stage. Your resulting file should look like this:
```yml
name: Stage the app
on:
# Triggers the workflow on push or pull request events but only for the "main" branch
pull_request:
types: [labeled]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'stage')
steps:
- uses: actions/checkout@v3
- name: Run a one-line script
run: echo Hello, world!
- name: Run a multi-line script
run: |
echo Add other actions to build,
echo test, and deploy your project.
```
- Click **Start commit**, choose to **make a new branch** named `staging-workflow`, and click **Propose a new file**.
![](https://i.imgur.com/xDSgqc7.png)
- Click Create pull request.
![](https://i.imgur.com/H4p7ZUK.png)
### Step 2: Set up an Azure environment
- Go to [Azure Portal](https://portal.azure.com/#home), Open Azure Cloud Shell
- Need to setup cloud shell environment when first use
![](https://i.imgur.com/MmCFacg.png)
- Click **Create storage**
![](https://i.imgur.com/iAX4YBL.png =500x)
- Use the command below
```.sh
az account show
```
- Copy the value of the id: field to a safe place. We'll call this *AZURESUBSCRIPTIONID*. Here's an example of what it looks like:
![](https://i.imgur.com/HtBiCJe.png =500x)
- In your terminal, run the command below
```.sh
az ad sp create-for-rbac --name "GitHub-Actions" --role contributor \
--scopes /subscriptions/{subscription-id} \
--sdk-auth
# Replace {subscription-id} with the same id stored in AZURE_SUBSCRIPTION_ID.
```
- Copy the entire contents of the command's response, we'll call this *AZURECREDENTIALS*. Here's an example of what it looks like:
![](https://i.imgur.com/f0XIGsZ.png)
- Back on GitHub, click on this repository's Secrets in the **Settings** tab. Click **New secret**
![](https://i.imgur.com/gagL49T.png)
- Name your new secret `AZURE_SUBSCRIPTION_ID` and paste the value from the id: field in the first command. Click **Add secret**
![](https://i.imgur.com/v3Ixtgv.png)
- Click **New secret** again. Name the second secret `AZURE_CREDENTIALS` and paste the entire contents from the second terminal command you entered. Click **Add secret**
![](https://i.imgur.com/5NXltqQ.png)
**Activity 2: Set up Azure resource provision workflow**
- New another workflow named `spinup-destroy.yml`
![](https://i.imgur.com/F7cnEqv.png)
- Use the workflow as below
```yaml!
name: Configure Azure environment
on:
pull_request:
types: [labeled]
env:
PACKAGES_TOKEN: ${{secrets.PACKAGES_TOKEN}}
AZURE_RESOURCE_GROUP: cd-with-actions
AZURE_CONTAINERAPPS_ENVIRONMENT: actions-ttt-deployment
AZURE_LOCATION: '"eastus2"'
#################################################
### USER PROVIDED VALUES ARE REQUIRED BELOW ###
#################################################
#################################################
### REPLACE <username> WITH GH USERNAME ###
AZURE_CONTAINER_APP_NAME: <username>-ttt-app
#################################################
jobs:
setup-up-azure-resources:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'spin up environment')
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Azure login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Create Azure resource group
if: success()
run: |
az group create --location ${{env.AZURE_LOCATION}} --name ${{env.AZURE_RESOURCE_GROUP}} --subscription ${{secrets.AZURE_SUBSCRIPTION_ID}}
# - name: Create Azure app service plan
# if: success()
# run: |
# az appservice plan create --resource-group ${{env.AZURE_RESOURCE_GROUP}} --name ${{env.AZURE_APP_PLAN}} --is-linux --sku F1 --subscription ${{secrets.AZURE_SUBSCRIPTION_ID}}
# - name: Create webapp resource
# if: success()
# run: |
# az webapp create --resource-group ${{ env.AZURE_RESOURCE_GROUP }} --plan ${{ env.AZURE_APP_PLAN }} --name ${{ env.AZURE_WEBAPP_NAME }} --deployment-container-image-name nginx --subscription ${{secrets.AZURE_SUBSCRIPTION_ID}}
# - name: Configure webapp to use GitHub Packages
# if: success()
# run: |
# az webapp config container set --docker-custom-image-name nginx --docker-registry-server-password ${{secrets.GITHUB_TOKEN}} --docker-registry-server-url https://docker.pkg.github.com --docker-registry-server-user ${{github.actor}} --name ${{ env.AZURE_WEBAPP_NAME }} --resource-group ${{ env.AZURE_RESOURCE_GROUP }} --subscription ${{secrets.AZURE_SUBSCRIPTION_ID}}
- name: Install the Azure Container Apps extension for the CLI
if: success()
run: |
az extension add --name containerapp --upgrade
- name: Register the Microsoft.App namespace
if: success()
run: |
az provider register --namespace Microsoft.App
- name: Create the container app environment
if: success()
run: |
az containerapp env create --location ${{env.AZURE_LOCATION}} --resource-group ${{env.AZURE_RESOURCE_GROUP}} --name ${{env.AZURE_CONTAINERAPPS_ENVIRONMENT}} --subscription ${{secrets.AZURE_SUBSCRIPTION_ID}}
- name: Create a container app
if: success()
run: |
az containerapp create --name ${{env.AZURE_CONTAINER_APP_NAME}} --resource-group ${{env.AZURE_RESOURCE_GROUP}} --environment ${{env.AZURE_CONTAINERAPPS_ENVIRONMENT}} --image nginx --target-port 80 --ingress 'external' --query properties.configuration.ingress.fqdn
destroy-azure-resources:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'destroy environment')
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Azure login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Destroy Azure environment
if: success()
run: |
az group delete --name ${{env.AZURE_RESOURCE_GROUP}} --subscription ${{secrets.AZURE_SUBSCRIPTION_ID}} --yes
```
- Start commit and new a branch named `azure-configuration`
![](https://i.imgur.com/RzWlx1y.png)
- No need to create pull request
**Activity 3: Edit deploy-staging file**
- Edit the `.github/workflows/deploy-staging.yml` file at staging-workflow branch to use some new actions.
```yaml!
name: Deploy to staging
on:
pull_request:
types: [labeled]
env:
IMAGE_REGISTRY_URL: ghcr.io
AZURE_RESOURCE_GROUP: cd-with-actions
###############################################
### Replace <username> with GitHub username ###
###############################################
DOCKER_IMAGE_NAME: <username>-azure-ttt
AZURE_CONTAINER_APP_NAME: <username>-ttt-app
###############################################
jobs:
build:
if: contains(github.event.pull_request.labels.*.name, 'stage')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: npm install and build webpack
run: |
npm install
npm run build
- uses: actions/upload-artifact@main
with:
name: webpack artifacts
path: public/
Build-Docker-Image:
runs-on: ubuntu-latest
needs: build
name: Build image and store in GitHub Container Registry
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Download built artifact
uses: actions/download-artifact@main
with:
name: webpack artifacts
path: public
- name: Log in to GHCR
uses: docker/login-action@v1.14.1
with:
registry: ${{ env.IMAGE_REGISTRY_URL }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v3.7.0
with:
images: ${{env.IMAGE_REGISTRY_URL}}/${{ github.repository }}/${{env.DOCKER_IMAGE_NAME}}
tags: |
type=sha,format=long,prefix=
- name: Build and push Docker image
uses: docker/build-push-action@v2.10.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Deploy-to-Azure:
runs-on: ubuntu-latest
needs: Build-Docker-Image
name: Deploy app container to Azure
steps:
- name: "Login via Azure CLI"
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- uses: azure/docker-login@v1
with:
login-server: ${{env.IMAGE_REGISTRY_URL}}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# - name: Deploy web app container
# uses: azure/webapps-deploy@v2
# with:
# app-name: ${{env.AZURE_WEBAPP_NAME}}
# images: ${{env.IMAGE_REGISTRY_URL}}/${{ github.repository }}/${{env.DOCKER_IMAGE_NAME}}:${{ github.sha }}
- name: Set up Azure extension installation
if: success()
run: |
az config set extension.use_dynamic_install=yes_without_prompt
- name: Deploy to app container
if: success()
run: |
az containerapp update -n ${{env.AZURE_CONTAINER_APP_NAME}} -g ${{env.AZURE_RESOURCE_GROUP}} --image ${{env.IMAGE_REGISTRY_URL}}/${{ github.repository }}/${{env.DOCKER_IMAGE_NAME}}:${{github.sha}}
- name: Azure logout
run: |
az logout
```
- Click Start commit and commit to the `staging-workflow` branch.
![](https://i.imgur.com/ww69nGp.png)
**Activity 3: Preparing `staging-test` branch**
- New a `staging-test` branch based on `main`
![](https://i.imgur.com/29kOslU.png =500x)
- Under `staging-test` branch, modify `index.html`
```htmlembedded=6
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TicTacToe - DevOps Day</title>
<link href="public/index.css" type="text/css" rel="stylesheet" />
```
- After edit the title of page, click commit
![](https://i.imgur.com/cHQUkoE.png)
### Step 3: Spin up an environment based on labels
**Activity 1: Set up a personal access token (PAT)**
Personal access tokens (PATs) are an alternative to using passwords for authentication to GitHub.
- Use a PAT to allow your web app to pull the container image after your workflow pushes a newly built image to the registry. Navigate to **Personal profile** > **Settings**
![](https://i.imgur.com/t1WPheR.png =300x)
- Under Developer settings, click **Personal access tokens** > **Generate new token**
![](https://i.imgur.com/CQ2UDSe.png)
<!-- - Create a personal access token with the repo and `write:packages` scopes.
![](https://i.imgur.com/jaf1NIa.png) -->
- Create a personal access token with the repo and `read:packages` scopes.
![](https://i.imgur.com/KGME5OY.png)
- Once you have generated the token we will need to store it in a secret so that it can be used within a workflow. Create a new repository secret named `CR_PAT` and paste the PAT token in as the value.
![](https://i.imgur.com/lvbI6KW.png)
**Activity 2: Apply labels to create resources**
- Apply the `spin up environment` label to your open pull request. Wait for the GitHub Actions workflow to run and spin up your Azure environment.
![](https://i.imgur.com/bHJ1erW.png)
![](https://i.imgur.com/hHKNpQr.png)
- After apply the label to pull request, the workflow would be triggered
![](https://i.imgur.com/H17RTak.png)
### Step 4: Deploy to a staging environment based on labels
**Activity 1: Set up `production-deployment-workflow` branch**
- Navigate to **Actions** > **New workflow** > **set up a workflow yourself**
![](https://i.imgur.com/6q1Grec.png)
- Use the workflow as below
```yaml!
name: Deploy to production
on:
push:
branches:
- main
env:
IMAGE_REGISTRY_URL: ghcr.io
AZURE_RESOURCE_GROUP: cd-with-actions
###############################################
### Replace <username> with GitHub username ###
###############################################
DOCKER_IMAGE_NAME: <username>-azure-ttt
AZURE_CONTAINER_APP_NAME: <username>-ttt-app
###############################################
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: npm install and build webpack
run: |
npm install
npm run build
- uses: actions/upload-artifact@main
with:
name: webpack artifacts
path: public/
Build-Docker-Image:
runs-on: ubuntu-latest
needs: build
name: Build image and store in GitHub Container Registry
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Download built artifact
uses: actions/download-artifact@main
with:
name: webpack artifacts
path: public
- name: Log in to GHCR
uses: docker/login-action@v1.14.1
with:
registry: ${{ env.IMAGE_REGISTRY_URL }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v3.7.0
with:
images: ${{env.IMAGE_REGISTRY_URL}}/${{ github.repository }}/${{env.DOCKER_IMAGE_NAME}}
tags: |
type=sha,format=long,prefix=
- name: Build and push Docker image
uses: docker/build-push-action@v2.10.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Deploy-to-Azure:
runs-on: ubuntu-latest
needs: Build-Docker-Image
name: Deploy app container to Azure
steps:
- name: "Login via Azure CLI"
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- uses: azure/docker-login@v1
with:
login-server: ${{env.IMAGE_REGISTRY_URL}}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# - name: Deploy web app container
# uses: azure/webapps-deploy@v2
# with:
# app-name: ${{env.AZURE_WEBAPP_NAME}}
# images: ${{env.IMAGE_REGISTRY_URL}}/${{ github.repository }}/${{env.DOCKER_IMAGE_NAME}}:${{github.sha}}
- name: Set up Azure extension installation
if: success()
run: |
az config set extension.use_dynamic_install=yes_without_prompt
- name: Deploy to app container
if: success()
run: |
az containerapp update -n ${{env.AZURE_CONTAINER_APP_NAME}} -g ${{env.AZURE_RESOURCE_GROUP}} --image ${{env.IMAGE_REGISTRY_URL}}/${{ github.repository }}/${{env.DOCKER_IMAGE_NAME}}:${{github.sha}}
- name: Azure logout
run: |
az logout
```
- Save the file named `deploy-prod.yml`
- Click **Start commit** with create a new branch named `production-deployment-workflow`
![](https://i.imgur.com/6LRK5JC.png)
- No need to create pull request
**Activity 2: Apply labels to deploy to staging**
- Apply the `stage` label to your open pull request of `staging-test` to `main`
![](https://i.imgur.com/e4AasNb.png)
- Wait for the GitHub Actions workflow to run and deploy the application to your Azure environment
![](https://i.imgur.com/0mPDT5O.png)
### Step 5: Deploy to a production environment based on labels
- Click Merge pull request and leave this tab open as we will be applying a label to the closed pull request in the next step.
![](https://i.imgur.com/mZHhVPv.png)
![](https://i.imgur.com/XRQLDtw.png)
- Apply the `destroy environment` label to your merged production-deployment-workflow pull request. If you have already closed the tab with your pull request, you can open it again by clicking Pull requests and then clicking the Closed filter to view merged pull requests.
![](https://i.imgur.com/tgcVgee.png)
### Issue
![](https://i.imgur.com/QZYQB5b.png)
## Reference
- [今日課程官方教學文件](https://github.com/skills/continuous-delivery-azure)
- [Install extensions automatically](https://docs.microsoft.com/en-us/cli/azure/azure-cli-extensions-overview#install-extensions-automatically)