Skip to content

Create two deployment workflows using GitHub Actions and Microsoft Azure.

License

Notifications You must be signed in to change notification settings

luispujols/continuous-delivery-azure

 
 

Repository files navigation

GitHub Actions: Continuous Delivery with Azure

Welcome

Create two deployment workflows using GitHub Actions and Microsoft Azure.

  • 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.
  • Prerequisites: Before you start, you should be familiar with GitHub, GitHub Actions, and Continuous Integration with GitHub Actions.
  • How long: This course is 6 steps long and takes less than 2 hours to complete.

How to start this course

  1. Right-click Start course and open the link in a new tab.
    start-course
  2. In the new tab, follow the prompts to create a new repository.
    • For owner, choose your personal account or an organization to host the repository.
    • We recommend creating a public repository—private repositories will use Actions minutes. Create a new repository
  3. After your new repository is created, wait about 1 minute, then refresh the page. Follow the step-by-step instructions in the new repository's README.

Step 1: Trigger a job based on labels

Welcome to the course 🎉

Screen Shot 2022-06-07 at 4 01 43 PM

A lot of things go into delivering "continuously". These things can range from culture and behavior to specific automation. In this exercise, we're going to focus on the deployment part of our automation.

In a GitHub Actions workflow, the on step defines what causes the workflow to run. In this case, we want the workflow to run different tasks when specific labels are applied to a pull request.

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.

⌨️ Activity 1: Configure GITHUB_TOKEN permissions

At the start of each workflow run, GitHub automatically creates a unique GITHUB_TOKEN secret to use in your workflow. We need to make sure this token has the permissions required for this course.

  1. Open a new browser tab, and work on the steps in your second tab while you read the instructions in this tab.
  2. Go to Settings > Actions > General. Ensure that the GITHUB_TOKEN also has Read and write permissions enabled under Workflow permissions. This is required for your workflow to be able to upload your image to the container registry.

⌨️ Activity 2: Configure a trigger based on labels

For now, we'll focus on staging. We'll spin up and destroy our environment in a later step.

  1. Go to the Actions tab.
  2. Click New workflow
  3. Search for "simple workflow" and click Configure
  4. Name your workflow deploy-staging.yml
  5. Edit the contents of this file and remove all triggers and jobs.
  6. 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:
    name: Stage the app
    
    on:
      pull_request:
        types: [labeled]
    
    jobs:
      build:
        runs-on: ubuntu-latest
    
        if: contains(github.event.pull_request.labels.*.name, 'stage')
  7. Click Start commit, and choose to make a new branch named staging-workflow.
  8. Click Propose changes.
  9. Click Create pull request.

Note: Wait about 1 minute then refresh this page for GitHub Actions to run before continuing to the next step.

Step 2: Set up an Azure environment

Good job getting started ⚙️

Nice work triggering a job on specific labels

We won't be going into detail on the steps of this workflow, but it would be a good idea to become familiar with the actions we're using. They are:

⌨️ Activity 1: Store your credentials in GitHub secrets and finish setting up your workflow

  1. In a new tab, create an Azure account if you don't already have one. If your Azure account is created through work, you may encounter issues accessing the necessary resources -- we recommend creating a new account for personal use and for this course.

    Note: You may need a credit card to create an Azure account. If you're a student, you may also be able to take advantage of the Student Developer Pack for access to Azure. If you'd like to continue with the course without an Azure account, Skills will still respond, but none of the deployments will work.

  2. Create a new subscription in the Azure Portal.

    Note: your subscription must be configured "Pay as you go" which will require you to enter billing information. This course will only use a few minutes from your free plan, but Azure requires the billing information.

  3. Install Azure CLI on your machine.
  4. In your terminal, run:
    az login
  5. Copy the value of the id: field to a safe place. We'll call this AZURE_SUBSCRIPTION_ID. Here's an example of what it looks like:
    [
    {
      "cloudName": "AzureCloud",
      "id": "f****a09-****-4d1c-98**-f**********c", # <-- Copy this id field
      "isDefault": true,
      "name": "some-subscription-name",
      "state": "Enabled",
      "tenantId": "********-a**c-44**-**25-62*******61",
      "user": {
        "name": "mdavis******@*********.com",
        "type": "user"
        }
      }
    ]
  6. In your terminal, run the command below.
    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.

Note: The \ character works as a line break on Unix based systems. If you are on a Windows based system the \ character will cause this command to fail. Place this command on a single line if you are using Windows.**

  1. Copy the entire contents of the command's response, we'll call this AZURE_CREDENTIALS. Here's an example of what it looks like:
    {
      "clientId": "<GUID>",
      "clientSecret": "<GUID>",
      "subscriptionId": "<GUID>",
      "tenantId": "<GUID>",
      (...)
    }
  2. Back on GitHub, click on this repository's Secrets and variables > Actions in the Settings tab.
  3. Click New repository secret
  4. Name your new secret AZURE_SUBSCRIPTION_ID and paste the value from the id: field in the first command.
  5. Click Add secret.
  6. Click New repository secret again.
  7. Name the second secret AZURE_CREDENTIALS and paste the entire contents from the second terminal command you entered.
  8. Click Add secret
  9. Go back to the Pull requests tab and in your pull request go to the Files Changed tab. Find and then edit the .github/workflows/deploy-staging.yml file to use some new actions.
If you'd like to copy the full workflow file, it should look like this:
name: Deploy to staging

on:
  pull_request:
    types: [labeled]

env:
  IMAGE_REGISTRY_URL: ghcr.io
  ###############################################
  ### Replace <username> with GitHub username ###
  ###############################################
  DOCKER_IMAGE_NAME: <username>-azure-ttt
  AZURE_WEBAPP_NAME: <username>-ttt-app
  ###############################################

jobs:
  build:
    if: contains(github.event.pull_request.labels.*.name, 'stage')

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: npm install and build webpack
        run: |
          npm install
          npm run build
      - uses: actions/upload-artifact@v3
        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@v3

      - name: Download built artifact
        uses: actions/download-artifact@v3
        with:
          name: webpack artifacts
          path: public

      - name: Log in to GHCR
        uses: docker/login-action@v2
        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@v4
        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@v3
        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: Azure logout via Azure CLI
        uses: azure/CLI@v1
        with:
          inlineScript: |
            az logout
            az cache purge
            az account clear
  1. After you've edited the file, click Commit changes... and commit to the staging-workflow branch.

Note: Wait about 1 minute then refresh this page for GitHub Actions to run before continuing to the next step.

Step 3: Spin up an environment based on labels

Nicely done! ❤️

GitHub Actions is cloud agnostic, so any cloud will work. We'll show how to deploy to Azure in this course.

What are Azure resources? In Azure, a resource is an entity managed by Azure. We'll use the following Azure resources in this course:

  • A web app is how we'll be deploying our application to Azure.
  • A resource group is a collection of resources, like web apps and virtual machines (VMs).
  • An App Service plan is what runs our web app and manages the billing (our app should run for free).

Through the power of GitHub Actions, we can create, configure, and destroy these resources through our workflow files.

⌨️ Activity 1: Set up a personal access token (PAT)

Personal access tokens (PATs) are an alternative to using passwords for authentication to GitHub. We will use a PAT to allow your web app to pull the container image after your workflow pushes a newly built image to the registry.

  1. Open a new browser tab, and work on the steps in your second tab while you read the instructions in this tab.
  2. Create a personal access token with the repo and read:packages scopes. For more information, see "Creating a personal access token."
  3. 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.
  4. With this done we can move on to setting up our workflow.

Configuring your Azure environment

To deploy successfully to our Azure environment:

  1. Create a new branch called azure-configuration by clicking on the branch dropdown on the top, left hand corner of the Code tab on your repository page.
  2. Once you're in the new azure-configuration branch, go into the .github/workflows directory and create a new file titled spinup-destroy.yml by clicking Add file.
Copy and paste the following into this new file:
name: Configure Azure environment

on:
  pull_request:
    types: [labeled]

env:
  IMAGE_REGISTRY_URL: ghcr.io
  AZURE_RESOURCE_GROUP: cd-with-actions
  AZURE_APP_PLAN: actions-ttt-deployment
  AZURE_LOCATION: '"Central US"'
  ###############################################
  ### Replace <username> with GitHub username ###
  ###############################################
  AZURE_WEBAPP_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@v3

      - 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 GHCR
        if: success()
        run: |
          az webapp config container set --docker-custom-image-name nginx --docker-registry-server-password ${{secrets.CR_PAT}} --docker-registry-server-url https://${{env.IMAGE_REGISTRY_URL}} --docker-registry-server-user ${{github.actor}} --name ${{ env.AZURE_WEBAPP_NAME }} --resource-group ${{ env.AZURE_RESOURCE_GROUP }} --subscription ${{secrets.AZURE_SUBSCRIPTION_ID}}

  destroy-azure-resources:
    runs-on: ubuntu-latest

    if: contains(github.event.pull_request.labels.*.name, 'destroy environment')

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - 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
  1. Click Commit changes... and select Commit directly to the azure-configuration branch. before clicking Commit changes.
  2. Go to the Pull requests tab of the repository.
  3. There should be a yellow banner with the azure-configuration branch where you can click Compare & pull request.
  4. Set the title of the Pull request to: Added spinup-destroy.yml workflow and click Create pull request.

We will cover the key functionality below and then put the workflow to use by applying a label to the pull request.

This new workflow has two jobs:

  1. Set up Azure resources will run if the pull request contains a label with the name "spin up environment".
  2. Destroy Azure resources will run if the pull request contains a label with the name "destroy environment".

In addition to each job, there's a few global environment variables:

  • AZURE_RESOURCE_GROUP, AZURE_APP_PLAN, and AZURE_WEBAPP_NAME are names for our resource group, app service plan, and web app, respectively, which we'll reference over multiple steps and workflows
  • AZURE_LOCATION lets us specify the region for the data centers, where our app will ultimately be deployed.

Setting up Azure resources

The first job sets up the Azure resources as follows:

  1. Logs into your Azure account with the azure/login action. The AZURE_CREDENTIALS secret you created earlier is used for authentication.
  2. Creates an Azure resource group by running az group create on the Azure CLI, which is pre-installed on the GitHub-hosted runner.
  3. Creates an App Service plan by running az appservice plan create on the Azure CLI.
  4. Creates a web app by running az webapp create on the Azure CLI.
  5. Configures the newly created web app to use GitHub Packages by using az webapp config on the Azure CLI. Azure can be configured to use its own Azure Container Registry, DockerHub, or a custom (private) registry. In this case, we'll configure GitHub Packages as a custom registry.

Destroying Azure resources

The second job destroys Azure resources so that you do not use your free minutes or incur billing. The job works as follows:

  1. Logs into your Azure account with the azure/login action. The AZURE_CREDENTIALS secret you created earlier is used for authentication.
  2. Deletes the resource group we created earlier using az group delete on the Azure CLI.

⌨️ Activity 2: Apply labels to create resources

  1. Edit the spinup-destroy.yml file in your open pull request and replace any <username> placeholders with your GitHub username. Commit this change directly to the azure-configuration branch.
  2. Back in the Pull request, create and apply the spin up environment label to your open pull request
  3. Wait for the GitHub Actions workflow to run and spin up your Azure environment. You can follow along in the Actions tab or in the pull request merge box.
  4. Once the workflow succeeds, refresh this page for the next step.

Step 4: Deploy to a staging environment based on labels

Nicely done, you used a workflow to spin up your Azure environment 💃

Now that the proper configuration and workflow files are present, let's test our actions! In this step, there's a small change to the game. Once you add the appropriate label to your pull request, you should be able to see the deployment!

  1. Create a new branch named staging-test from main using the same steps as you did for the previous azure-configuration branch.
  2. Edit the .github/workflows/deploy-staging.yml file, and replace every <username> with your GitHub username.
  3. Commit that change to the new staging-test branch.
  4. Go to the Pull requests tab and there should be a yellow banner with the staging-test branch to Compare & pull request. Once the pull request is opened up, click Create pull request.

⌨️ Activity 1: Add the proper label to your pull request

  1. Ensure that the GITHUB_TOKEN for this repository has read and write permissions under Workflow permissions. Learn more. This is required for your workflow to be able to upload your image to the container registry.
  2. Create and apply the stage label to your open pull request
  3. Wait for the GitHub Actions workflow to run and deploy the application to your Azure environment. You can follow along in the Actions tab or in the pull request merge box. The deployment may take a few moments but you've done the right thing. Once the deployment is successful, you'll see green check marks for each run, and you'll see a URL for your deployment. Play the game!
  4. Once the workflow has completed, refresh this page for the next step.

Step 5: Deploy to a production environment based on labels

Deployed! 🚢

Nicely done

As we've done before, create a new branch called production-deployment-workflow from main. In the .github/workflows directory, add a new file titled deploy-prod.yml. This new workflow deals specifically with commits to main and handles deployments to prod.

Continuous delivery (CD) is a concept that contains many behaviors and other, more specific concepts. One of those concepts is test in production. That can mean different things to different projects and different companies, and isn't a strict rule that says you are or aren't "doing CD".

In our case, we can match our production environment to be exactly like our staging environment. This minimizes opportunities for surprises once we deploy to production.

⌨️ Activity 1: Add triggers to production deployment workflow

Copy and paste the following to your file, and replace any <username> placeholders with your GitHub username. Note that not much has changed from our staging workflow, except for our trigger, and that we won't be filtering by labels.

name: Deploy to production

on:
  push:
    branches:
      - main

env:
  IMAGE_REGISTRY_URL: ghcr.io
  ###############################################
  ### Replace <username> with GitHub username ###
  ###############################################
  DOCKER_IMAGE_NAME: <username>-azure-ttt
  AZURE_WEBAPP_NAME: <username>-ttt-app
  ###############################################

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: npm install and build webpack
        run: |
          npm install
          npm run build
      - uses: actions/upload-artifact@v3
        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@v3

      - name: Download built artifact
        uses: actions/download-artifact@v3
        with:
          name: webpack artifacts
          path: public

      - name: Log in to GHCR
        uses: docker/login-action@v2
        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@v4
        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@v3
        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: Azure logout via Azure CLI
        uses: azure/CLI@v1
        with:
          inlineScript: |
            az logout
            az cache purge
            az account clear
  1. Update every <username> to your GitHub username.
  2. Commit your changes to the production-deployment-workflow branch.
  3. Go to the Pull requests tab and click Compare & pull request for the production-deployment-workflow branch and create a Pull request.

Great! The syntax you used tells GitHub Actions to only run that workflow when a commit is made to the main branch. Now we can put this workflow into action to deploy to production!

⌨️ Activity 2: Merge your pull request

  1. You can now merge your pull request!
  2. 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.
  3. Now we just have to wait for the package to be published to GitHub Container Registry and the deployment to occur. When the workflow is finished running, refresh this page for the next step.

Step 6: Production deployment

Nice work! ❇️

Great work, you've done it! You should be able to see your container image in the Packages section of your account on the main repository page. You can get the deployment URL in the Actions log, just like the staging URL.

The cloud environment

Throughout the course you've spun up resources that, if left unattended, could incur billing or consume your free minutes from the cloud provider. Once you have verified your application in production, let's tear down those environments so that you can keep your minutes for more learning!

⌨️ Activity 1: Destroy any running resources so you don't incur charges

  1. Create and 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.

Now that you've applied the proper label, let's wait for the GitHub Actions workflow to complete. When it's finished, you can confirm that your environment has been destroyed by visiting your app's URL, or by logging into the Azure portal to see it is not running.

  1. Wait about 1 minute then refresh this page for the next step.

Finish

celebrate

Congratulations, you've completed this course!

Here's a recap of all the tasks you've accomplished in your repository:

  • Trigger a job based on labels
  • Set up the Azure environment
  • Spin up environment based on labels
  • Deploy to a staging environment based on labels
  • Deploy to a production environment based on labels
  • Destroy environment based on labels

What's next?


Get help: Post in our discussion boardReview the GitHub status page

© 2022 GitHub • Code of ConductCC-BY-4.0 License

About

Create two deployment workflows using GitHub Actions and Microsoft Azure.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 77.9%
  • HTML 15.7%
  • CSS 6.0%
  • Dockerfile 0.4%