Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Blue/Green deployments with Hetzner Load Balancer Inside GitHub Actions

profile picture
Author
Andy Presto
Published
2024-01-19
Time to read
8 minutes reading time

Introduction

This article will guide you through Blue/Green deployments using a Hetzner Load Balancer and GitHub Actions.

In this scenario, we are going to update a Node.js application with zero downtime. However, most of the steps in this article are also relevant for deploying other application types.

Before looking at the code, we're going to start by clarifying the process and any technologies being used.

  • The Essence of Blue/Green Deployment

    Updating an application usually involves some downtime while the old version is switched off, and the new version is installed and switched on. The Blue/Green deployment strategy ingeniously addresses this by maintaining two identical production environments: Blue and Green. At any point, one of these environments is live, handling all production traffic, while the other holds the upcoming version.

    The switch from the current live environment to the new version is what defines the core of this strategy, ensuring a swift and almost seamless transition. It also ensures that you can roll back to the previous environment in the event of an issue.

  • Hetzner Load Balancer

    Hetzner Cloud Load Balancers allow you to evenly distribute traffic to multiple targets, or a single target.

    Instead of pointing traffic directly to your server, you may route traffic via the Load Balancer to fairly distribute incoming requests between multiple servers, this can help with scaling to handle more users. Load Balancers also help us reduce downtime by allowing for Blue/Green deployments, since there will always be an active production environment during updates.

  • GitHub Actions

    To prevent mistakes and free up your time, deployments shouldn't involve many manual steps. GitHub Actions allow us to create repeatable automation scripts that run when a pull request is completed or a merge takes place.

    We are going to use GitHub Actions to compile our application and make the necessary changes to the Hetzner Load Balancer.

  • Hetzner API

    To query and update our Load Balancer, we will use the Hetzner API.

    The API allows us to do basically everything that can be done via the Hetzner Cloud Console, but instead of clicking buttons we will be sending HTTP requests.

Prerequisites

Before embarking on setting up this process, ensure you're equipped with:

  • A Node.js application ready for deployment.
  • Two configured servers on Hetzner Cloud, labeled Blue and Green.
  • GitHub repository with Actions enabled.
  • Two GitHub Secrets
    • Hetzner Cloud API token
    • Password for users on both servers. You should create a new user for GitHub Actions only and add a secure password.

Step 1 - Initiate with the Workflow File

Let's dive into the steps to create a .yml file that orchestrates your Blue/Green deployment process within the GitHub Actions framework.

This tutorial uses the following example:

└── <repository_name>
    ├── package.json
    ├── README.md
    └── src
        └── index.js
  • package.json

    {
        "version": "1.1.0",
        "name": "blue-green-example",
        "description": "Example package.json file.",
        "repository": {
            "type": "git",
            "url": "git+https://github.com/<owner>/<repository_name>.git"
        },
        "scripts": {
           "build": "echo \"Building the blue/green project\""
        }
    }
  • README.md

    ## Example repository for Blue/Green deployments
    
    This is an example project to follow the tutorial.
  • src/index.js

    console.log('Hello, World!');

A whole GitHub Actions script is shown at the end, but you should understand what each step is doing before running it. Therefore, this tutorial will discuss each section of the script step-by-step.

Our script is written in a language called YAML, hence the .yml file extension. Remember that spacing matters in this file.

Create a YAML file in your GitHub repository under .github/workflows/, like blue-green-deploy.yml.

Step 2 - Triggering the Deployment

The deployment is typically triggered by a push to the main branch:

name: Blue/Green Deployment

on:
  push:
    branches:
      - main

Step 3 - Defining the Job

Set up the job to run on GitHub's virtual environment:

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      HETZNER_API_TOKEN: ${{ secrets.HETZNER_API_TOKEN }}
      HETZNER_SERVER_PASSWORD: ${{ secrets.HETZNER_SERVER_PASSWORD }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

Step 4 - Node.js Setup

Configure the Node.js environment.

If you are running a different type of application such as .NET Core or Ruby on Rails then make the necessary changes in the upcoming script sections:

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '14' # adapt this to your Node.js version

Step 5 - Dependencies and Build

Install and build your application:

      - name: Install dependencies
        run: npm install

      - name: Build
        run: npm run build

Step 6 - Identifying the Live Environment

Now we are going to populate the live_env variable with the details of the current target.

Remember to change your-load-balancer-name to the name of your own Load Balancer.

      - name: Determine Live Environment
        id: live_env
        run: |
          live_env=$(curl -H "Authorization: Bearer $HETZNER_API_TOKEN" "https://api.hetzner.cloud/v1/load_balancers" | jq -r '.load_balancers[] | select(.name=="your-load-balancer-name") | .public_net.ipv4.ip')
          echo "LIVE_ENV=$live_env" >> $GITHUB_ENV
          echo "live_env=$live_env" >> $GITHUB_OUTPUT

Step 7 - Deployment to Idle Environment

Deploy to the environment that's currently not in use.

I suggest that you SSH the files to the applicable machine here, or transfer the files by other means:

      - name: Deploy to Non-Live Environment
        run: |
          if [[ ${{ steps.live_env.outputs.live_env }} == "blue" ]]; then
            target_env="green"
          else
            target_env="blue"
          fi
          echo "Deploying to $target_env environment"
          # Your deployment script

To copy the example file from "Step 1" to the applicable machine, you can replace # Your deployment script with the commands below.

Replace scr/index.js, <your_user>, and ~/target/path with your actual information.

          server_ip=$(curl -H "Authorization: Bearer $HETZNER_API_TOKEN" "https://api.hetzner.cloud/v1/servers" | jq -r --arg target_env "$target_env" '.servers[] | select(.labels[$target_env] != null) | .public_net.ipv4.ip')
          sshpass -p "$HETZNER_SERVER_PASSWORD" scp -o StrictHostKeyChecking=no -r scr/index.js <your_user>@$server_ip:/target/path

Step 8 - Load Balancer Update

Switch the Load Balancer to point to the new environment.

Replace your-load-balancer-id with the ID of your own Load Balancer.

      - name: Update Load Balancer
        run: |
          if [[ ${{ steps.live_env.outputs.live_env }} == "blue" ]]; then
            target_env="green"
          else
            target_env="blue"
          fi
          curl -X POST -H "Authorization: Bearer $HETZNER_API_TOKEN" \
               -H "Content-Type: application/json" \
               -d '{"target": "$target_env"}' \
               "https://api.hetzner.cloud/v1/load_balancers/your-load-balancer-id/actions/change_target"

Step 9 - Wrapping Up

Final steps, including cleanups or notifications:

      - name: Final Steps
        run: |
          echo "Deployment to $target_env environment completed successfully"

You will need to commit this file to your GitHub repository, and ensure that it has been pushed to the main branch.

Whole file

The final YAML file should look something like this.

name: Blue/Green Deployment

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      HETZNER_API_TOKEN: ${{ secrets.HETZNER_API_TOKEN }}
      HETZNER_SERVER_PASSWORD: ${{ secrets.HETZNER_SERVER_PASSWORD }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '14'

      - name: Install dependencies
        run: npm install

      - name: Build
        run: npm run build

      - name: Determine Live Environment
        id: live_env
        run: |
          live_env=$(curl -H "Authorization: Bearer $HETZNER_API_TOKEN" "https://api.hetzner.cloud/v1/load_balancers" | jq -r '.load_balancers[] | select(.name=="your-load-balancer-name") | .public_net.ipv4.ip')
          echo "LIVE_ENV=$live_env" >> $GITHUB_ENV
          echo "live_env=$live_env" >> $GITHUB_OUTPUT

      - name: Deploy to Non-Live Environment
        run: |
          if [[ ${{ steps.live_env.outputs.live_env }} == "blue" ]]; then
            target_env="green"
          else
            target_env="blue"
          fi
          echo "Deploying to $target_env environment"
          # Your deployment script or:
          # server_ip=$(curl -H "Authorization: Bearer $HETZNER_API_TOKEN" "https://api.hetzner.cloud/v1/servers" | jq -r --arg target_env "$target_env" '.servers[] | select(.labels[$target_env] != null) | .public_net.ipv4.ip')
          # sshpass -p "$HETZNER_SERVER_PASSWORD" scp -o StrictHostKeyChecking=no -r scr/index.js <your_user>@$server_ip:/target/path

      - name: Update Load Balancer
        run: |
          if [[ ${{ steps.live_env.outputs.live_env }} == "blue" ]]; then
            target_env="green"
          else
            target_env="blue"
          fi
          curl -X POST -H "Authorization: Bearer $HETZNER_API_TOKEN" \
               -H "Content-Type: application/json" \
               -d '{"target": "$target_env"}' \
               "https://api.hetzner.cloud/v1/load_balancers/your-load-balancer-id/actions/change_target"

      - name: Final Steps
        run: |
          echo "Deployment to $target_env environment completed successfully"

Conclusion

This article described how to use a Hetzner Cloud Load Balancer to perform Blue/Green deployments. It's expected that you should tailor the scripts to match your own use case.

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get 20€ free credit!

Valid until: 31 December 2024 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more