NGINX Tutorial: How to Use GitHub Actions to Automate Microservices Canary Deployments

NGINX | March 21, 2023

This post is one of four tutorials that help you put into practice concepts from Microservices March 2023: Start Delivering Microservices:

Automating deployments is critical to the success of most projects. However, it’s not enough to just deploy your code. You also need to ensure downtime is limited (or eliminated), and you need the ability to roll back quickly in the event of a failure. Combining canary deployment and blue‑green deployment is a common approach to ensuring new code is viable. This strategy includes two steps:

  • Step 1: Canary deployment to test in isolation – Deploy your code to an isolated node, server, or container outside your environment and test to ensure the code works as intended.
  • Step 2: Blue‑green deployment to test in production – Assuming the code works in the canary deployment, port the code to newly created servers (or nodes or containers) in your production environment alongside the servers for the current version. Then redirect a portion of production traffic to the new version to test whether it continues to work well under higher load. Most often, you start by directing a small percentage (10%, say) to the new version and incrementally increase it until the new version receives all traffic. The size of the increments depends on how confident you are that the new version can handle traffic; you can even switch over completely to the new version in a single step.

If you’re unfamiliar with the different use cases for distributing traffic between different versions of an app or website (traffic splitting), read How to Improve Resilience in Kubernetes with Advanced Traffic Management on our blog to gain a conceptual understanding of blue‑green deployments, canary releases, A/B testing, rate limiting, circuit breaking, and more. While the blog is specific to Kubernetes, the concepts are broadly applicable to microservices apps.

Tutorial Overview

In this tutorial, we show how to automate the first step of a canary blue‑green deployment using GitHub Actions. In the four challenges of the tutorial you use Microsoft Azure Container Apps to deploy a new version of your application, then use Azure Traffic Manager to shift traffic from the old environment to the new environment:

Note: While this tutorial uses Azure Container Apps, the concepts and techniques can be applied to any cloud‑based host.

Prerequisites and Setup

Prerequisites

If you want to do this tutorial in your own environment, you need:

  • An Azure account. We recommend that you use an account that is not linked to your organization, because you might have problems with permissions when using an organizational account.
  • The Azure CLI.
  • The GitHub CLI, if you want to use it instead of (or in addition to) the browser‑based GitHub GUI.

Set Up

Create and configure the necessary base resources. Fork and clone the repository for the tutorial, log in to the Azure CLI, and install the extension for Azure Container Apps.

  1. In your home directory, create the microservices-march directory. (You can also use a different directory name and adapt the instructions accordingly.) Note: Throughout the tutorial the prompt on the Linux command line is omitted, to make it easier to copy and paste the commands into your terminal. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  2. Fork and clone the Microservices March platform repository to your personal GitHub account, using either the GitHub CLI or GUI.
    • If using the GitHub GUI:
      1. Click Fork on the upper right corner of the window and select your personal GitHub account on the Owner menu.
        screenshot of GitHub GUI showing fork of the repository for this tutorial
      2. Clone the repository locally, substituting your account name for <your_GitHub_account>: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    • If using the GitHub CLI, run: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  3. Login to the Azure CLI. Follow the prompts to log in using a browser: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  4. Install the containerapp extension: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Challenge 1: Create and Deploy an NGINX Container App

In this initial challenge, you create an NGINX Azure Container App as the initial version of the application used as the baseline for the canary blue‑green deployment. Azure Container Apps is a Microsoft Azure service you use to easily execute application code packaged in a container in a production‑ready container environment.

  1. Create an Azure resource group for the container app: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  2. Deploy the container to Azure Container Apps (this step may take a while): [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  3. In the output in Step 2, find the name and the URL of the container app you’ve created in the Azure Container Registry (ACR). They’re highlighted in orange in the sample output. You will substitute the values from your output (which will be different from the sample output in Step 2) for the indicated variables in commands throughout the tutorial:
    • Name of container app – In the image.registry key, the character string before .azurecr.io. In the sample output in Step 2, it is cac085021b77acr. Substitute this character string for <ACR_name> in subsequent commands.
    • URL of the container app – The URL on the line that begins Container app created. In the sample output in Step 2, it is https://my-container-app.delightfulmoss-eb6d59d5.westus.azurecontainerapps.io/. Substitute this URL for <ACR_URL> in subsequent commands.
  4. Enable revisions for the container app as required by for a blue‑green deployment: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  5. (Optional) Test that the deployment is working by querying the /health endpoint in the container: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Challenge 2: Set Up Permissions for Automating Azure Container App Deployments

In this challenge, you obtain the JSON token that enables you to automate Azure container app deployments.

You start by obtaining the ID for the Azure Container Registry (ACR), and then the principal ID for your Azure managed identity. You then assign the built‑in Azure role for ACR to the managed identity, and configure the container app to use the managed identity. Finally you obtain the JSON credentials for the managed identity, which will be used by GitHub Actions to authenticate to Azure.

While this set of steps may seem tedious, you only need to perform them once when creating a new application and you can fully script the process. The tutorial has you perform the steps manually to become familiar with them.

Note: This process for creating credentials for deployment is specific to Azure.

  1. Look up the principal ID of your managed identity. It appears in the PrincipalID column of the output (which is divided across two lines for legibility). You’ll substitute this value for <managed_identity_principal_ID> in Step 3: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  2. Look up the container app’s resource ID in ACR, replacing <ACR_name> with the name you recorded in Step 3 of Challenge 1. You’ll substitute this value for <ACR_resource_ID> in the next step: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  3. Assign the built‑in Azure role for ACR to the container app’s managed identity, replacing <managed_identity_principal_ID> with the managed identity obtained in Step 1, and <ACR_resource_ID> with the resource ID obtained in Step 2: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  4. Configure the container app to use the managed identity when pulling images from ACR, replacing <ACR_name> with the container app name you recorded in Step 3 in Challenge 1 (and also used in Step 2 above): [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  5. Look up your Azure subscription ID. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  6. Create a JSON token which contains the credentials to be used by the GitHub Action, replacing <subscription_ID> with your Azure subscription ID. Save the output to paste in as the value of the secret named AZURE_CREDENTIALS in Add Secrets to Your GitHub Repository. You can safely ignore the warning about --sdk-auth being deprecated; it’s a known issue: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Challenge 3: Create a Canary Blue-Green Deployment GitHub Action

In this challenge, you add secrets to your GitHub repo (used to manage sensitive data in your GitHub Action workflows), create an Action workflow file, and execute the Action workflow.

For a detailed introduction to secrets management, see the second tutorial for Microservices March 23, How to Securely Manage Secrets in Containers on our blog.

Add Secrets to Your GitHub Repository

To deploy a new version of the application, you need to create a series of secrets in the GitHub repository you forked in Set Up. The secrets are the JSON credentials for the managed identity created in Challenge 2, and some sensitive deployment‑specific parameters necessary to deploy new versions of the NGINX image to Azure. In the next section you’ll use these secrets in a GitHub Action to automate the canary blue‑green deployment.

  • If using the GitHub GUI:
    1. Navigate to your forked GitHub repository.
    2. Select Settings > Secrets and variables > Actions.
    3. Click New repository secret.
    4. Type the following values in the indicated fields:
      • Name – AZURE_CREDENTIALS
      • Secret – The JSON credentials from Step 6 of Challenge 2
    5. Click Add secret.
    6. Repeat Steps 3–5 three times to create the secrets listed in the table. Type the values from the Secret Name and Secret Value columns into the GUI’s Name and Secret fields respectively. For the third secret, replace <ACR_name> with the name assigned to the container app which you recorded in Step 3 of Challenge 1. Secret Name Secret ValueCONTAINER_APP_NAME my-container-appRESOURCE_GROUP my-container-app-rgACR_NAME <ACR_name>
    7. Proceed to Create a GitHub Action Workflow File.
  • If using the GitHub CLI:
    1. At the root of your repo, create a temporary file. [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    2. Using your preferred text editor, open creds.json and copy in the JSON credentials you created in Step 6 of Challenge 2.
    3. Create the secret: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    4. Delete creds.json: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    5. Repeat this command to create three more secrets: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop For each repetition, replace <secret_name> with one of the values in the Secret Name column in the table. At the prompt, paste the associated value from the Secret Value column. For the third secret, replace <ACR_name> with the name assigned to the container app which you recorded in Step 3 of Challenge 1. Secret Name Secret ValueCONTAINER_APP_NAME my-container-appRESOURCE_GROUP my-container-app-rgACR_NAME <ACR_name>

Create a GitHub Action Workflow File

With the managed identity and secrets in place, you can create a workflow file for a GitHub Action that automates the canary blue‑green deployment.

Note: Workflow files are defined in YAML format, where whitespace is significant. Be sure to preserve the indentation shown in the steps below.

  1. Create a file for the Action workflow.
    • If using the GitHub GUI:
      1. Navigate to your GitHub repository.
      2. Select Actions > New workflow > Skip this and set up a workflow yourself.
    • If using the GitHub CLI, create the .github/workflows directory and create a new file called main.yml: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  2. Using your preferred text editor, add the text of the workflow to main.yml. The easiest method is to copy in the text that appears in Full Workflow File. Alternatively, you can build the file manually by adding the set of snippets annotated in this step. Note: Workflow files are defined in YAML format, where whitespace is significant. If you copy in the snippets, be sure to preserve the indentation (and to be extra sure, compare your file to Full Workflow File.
    • Define the workflow’s name: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    • Configure the workflow to run when a push or pull request is made to the main branch: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    • In the jobs section, define the build-deploy job, which checks out the code, logs into Azure, and deploys the application to Azure Container App: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    • Define the test-deployment job, which obtains the staging URL of the newly deployed revision and uses a GitHub Action to ping the API endpoint /health to ensure the new revision is responding. If the health check succeeds, the Azure Traffic Manager on the container app is updated to point all traffic at the newly deployed container. Note: Be sure to indent the test-deployment key at the same level as the build-deploy key you defined in the previous bullet: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Full Workflow File

This is the complete text for the Action workflow file.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Execute the Action Workflow

  • If using the GitHub GUI:
    1. Click Start commit, add a commit message if you wish, and in the dialog box select Commit new file. The new workflow file is merged into the main branch and begins executing.
    2. Click Actions to monitor the progress of the workflow.
  • If using the GitHub CLI:
    1. Add main.yml to the Git staging area: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    2. Commit the file: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    3. Push your changes to GitHub: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    4. Monitor the progress of the workflow: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Challenge 4: Test the GitHub Actions Workflow

In this challenge, you test the workflow. You first simulate a successful update to your Ingress load balancer and confirm the application has been updated. You then simulate an unsuccessful update (which leads to an internal server error) and confirm that the published application remains unchanged.

Make a Successful Update

Create a successful update and watch the workflow succeed.

  • If using the GitHub GUI:
    1. Select Code > ingress > default.conf.template.
    2. Open default.conf.template for editing by selecting the pencil icon with the tooltip Edit this file.
    3. In the location /health block near the end of the file, change the return directive as indicated: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    4. In the dialog box, select Create a new branch for this commit and start a pull request and then Propose changes.
    5. Click Create pull request to access the pull request template.
    6. Click Create pull request again to create the pull request.
    7. Click Actions to monitor the progress of the workflow.
    8. When the workflow completes, navigate to your container app at the <ACR_URL>/health endpoint, where the <ACR_URL> is the URL you noted in Step 3 of Challenge 1. Notice the Successful Update! message.
    9. You can confirm the message by starting a terminal session and sending a health‑check request to the app, again replacing <ACR_URL> with the value you recorded in Step 3 of Challenge 1: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    10. Proceed to Make an Unsuccessful Update.
  • If using the GitHub CLI:
    1. Create a new branch called patch-1: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    2. In your preferred text editor open ingress/default.conf.template and in the location /health block near the end of the file, change the return directive as indicated: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    3. Add default.conf.template to the Git staging area: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    4. Commit the file: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    5. Push your changes to GitHub: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    6. Create a pull request (PR): [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    7. Monitor the progress of the workflow: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    8. When the workflow completes, send a health‑check request to the app, replacing <ACR_URL> with the value you recorded in Step 3 of Challenge 1: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Make an Unsuccessful Update

Now create an unsuccessful update and watch the workflow fail. This basically involves repeating the steps in Make a Successful Update but with a different value for the return directive.

  • If using the GitHub GUI:
    1. Select Code > ingress > default.conf.template.
    2. In the upper left, select main and then the name of the branch which ends with patch-1, which you created in the previous section.
    3. Open default.conf.template for editing by selecting the pencil icon with the tooltip Edit this file.
    4. Change the return directive as indicated: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    5. Select Commit directly to the -patch-1 branch and then Commit changes.
    6. Select Actions to monitor the progress of the workflow. Notice the workflow executes again when files in the PR are updated.
    7. When the workflow completes, navigate to your container app at the <ACR_URL>/health endpoint, where the <ACR_URL> is the URL you recorded in Step 3 of Challenge 1. Notice the message is Successful Update! (the same as after the previous, successful update). Though that may seem paradoxical, it in fact confirms that this update failed – the update attempt resulted in status 500 (meaning Internal Server Error) and did not get applied.
    8. You can confirm the message by starting a terminal session and sending a health‑check request to the app, again replacing <ACR_URL> with the value you recorded in Step 3 of Challenge 1: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
  • If using the GitHub CLI:
    1. Check out the patch-1 branch you created in the previous section: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    2. In your preferred text editor open ingress/default.conf.template and again change the return directive as indicated: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    3. Add default.conf.template to the Git staging area: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    4. Commit the file: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    5. Push your changes to GitHub: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    6. Monitor the progress of the workflow: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop
    7. When the workflow completes, send a health‑check request to the app, replacing <ACR_URL> with the value you recorded in Step 3 of Challenge 1: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop It may seem paradoxical that the message is Successful Update! (the same as after the previous, successful update). Though that may seem paradoxical, in fact it confirms that this update failed – the update attempt resulted in status 500 (meaning Internal Server Error) and did not get applied.

Resource Cleanup

You probably want to remove the Azure resources you deployed in the tutorial to avoid any potential charges down the line:

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

You can also delete the fork you created if you wish.

  • If using the GitHub GUI:
    1. Click Settings.
    2. Scroll down to the bottom of the page.
    3. Click Delete this repository.
    4. Type <your_GitHub_account>/platform and select I understand the consequences, delete this repository.
  • If using the GitHub CLI: [@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Next Steps

Congratulations! You’ve learned how to use GitHub Actions to perform a canary blue‑green deployment of a microservices app. Check out these articles in the GitHub docs to continue exploring and growing your knowledge of DevOps:

If you’re ready to try out the second step of a canary deployment (testing in production) then check out the tutorial from Microservices March 2022, Improve Uptime and Resilience with a Canary Deployment on our blog. It uses NGINX Service Mesh to gradually transition to a new app version. Even if your deployments aren’t yet complex enough to need a service mesh, or you’re not using Kubernetes, the principles still apply to simpler deployments only using an Ingress controller or load balancer.

To continue your microservices education, check out Microservices March 2023. In Unit 3, Accelerate Microservices Deployments with Automation, you’ll learn more about automating deployments.


Share

Related Blog Posts

Automating Certificate Management in a Kubernetes Environment
NGINX | 10/05/2022

Automating Certificate Management in a Kubernetes Environment

Simplify cert management by providing unique, automatically renewed and updated certificates to your endpoints.

Secure Your API Gateway with NGINX App Protect WAF
NGINX | 05/26/2022

Secure Your API Gateway with NGINX App Protect WAF

As monoliths move to microservices, applications are developed faster than ever. Speed is necessary to stay competitive and APIs sit at the front of these rapid modernization efforts. But the popularity of APIs for application modernization has significant implications for app security.

How Do I Choose? API Gateway vs. Ingress Controller vs. Service Mesh
NGINX | 12/09/2021

How Do I Choose? API Gateway vs. Ingress Controller vs. Service Mesh

When you need an API gateway in Kubernetes, how do you choose among API gateway vs. Ingress controller vs. service mesh? We guide you through the decision, with sample scenarios for north-south and east-west API traffic, plus use cases where an API gateway is the right tool.

Deploying NGINX as an API Gateway, Part 2: Protecting Backend Services
NGINX | 01/20/2021

Deploying NGINX as an API Gateway, Part 2: Protecting Backend Services

In the second post in our API gateway series, Liam shows you how to batten down the hatches on your API services. You can use rate limiting, access restrictions, request size limits, and request body validation to frustrate illegitimate or overly burdensome requests.

New Joomla Exploit CVE-2015-8562
NGINX | 12/15/2015

New Joomla Exploit CVE-2015-8562

Read about the new zero day exploit in Joomla and see the NGINX configuration for how to apply a fix in NGINX or NGINX Plus.

Why Do I See “Welcome to nginx!” on My Favorite Website?
NGINX | 01/01/2014

Why Do I See “Welcome to nginx!” on My Favorite Website?

The ‘Welcome to NGINX!’ page is presented when NGINX web server software is installed on a computer but has not finished configuring

Deliver and Secure Every App
F5 application delivery and security solutions are built to ensure that every app and API deployed anywhere is fast, available, and secure. Learn how we can partner to deliver exceptional experiences every time.
Connect With Us