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.