Zero-Downtime Blue-Green Deployment Pipelines for PHP Applications on OVH
Understanding the Blue-Green Deployment Strategy
Blue-Green deployment is a strategy that minimizes downtime and risk by running two identical production environments, referred to as “Blue” and “Green.” At any given time, only one environment is live, serving production traffic. The other environment is idle, used for deploying new versions. Once the new version is deployed and tested on the idle environment, traffic is switched over, making the previously live environment the idle one for the next deployment. This allows for instant rollback if issues arise.
OVH Infrastructure for Blue-Green Deployments
For this setup, we’ll leverage OVH’s Public Cloud instances, specifically focusing on load balancing and instance management. The core components will be:
- Two distinct sets of identical Public Cloud Instances: One set for the “Blue” environment, the other for “Green.”
- An OVH Load Balancer: To direct traffic to either the Blue or Green environment.
- A mechanism for automated deployment: This could be a CI/CD pipeline using tools like GitLab CI, GitHub Actions, or Jenkins.
- A method for traffic switching: This will be managed via the OVH Load Balancer configuration.
Setting Up the Environments
We’ll assume you have a base PHP application that can be deployed to a standard web server stack (e.g., Nginx/Apache with PHP-FPM). The key is to have a consistent deployment artifact that can be replicated across both environments.
Instance Configuration (Terraform Example)
Using Infrastructure as Code (IaC) is paramount for consistency. Here’s a simplified Terraform snippet to provision two identical sets of instances. We’ll use tags to differentiate them.
resource "ovh_compute_instance" "app_blue" {
count = 2 # Number of instances per environment
name = "app-blue-${count.index}"
flavor_name = "s1-2" # Example flavor
image_name = "Debian 11"
region = "GRA" # Example region
ssh_key_name = "my-ssh-key"
user_data = file("scripts/bootstrap.sh") # Script to install web server, PHP, etc.
tags = ["app", "blue"]
}
resource "ovh_compute_instance" "app_green" {
count = 2 # Number of instances per environment
name = "app-green-${count.index}"
flavor_name = "s1-2" # Example flavor
image_name = "Debian 11"
region = "GRA" # Example region
ssh_key_name = "my-ssh-key"
user_data = file("scripts/bootstrap.sh") # Script to install web server, PHP, etc.
tags = ["app", "green"]
}
# bootstrap.sh example (simplified)
# #!/bin/bash
# apt-get update -y
# apt-get install -y nginx php-fpm
# systemctl enable nginx php-fpm
# # ... further configuration for web server, app deployment
Load Balancer Configuration
We need an OVH Load Balancer to manage traffic. Initially, it will point to the “Blue” environment. We’ll configure backend servers for both Blue and Green, but only enable one at a time.
Let’s assume your application runs on port 80. The OVH Load Balancer will listen on port 80 and forward traffic to your instances on port 80.
# Example OVH Load Balancer configuration (conceptual, actual API/CLI usage varies) # Create a Load Balancer # ovh lb create my-app-lb --region GRA --plan professional # Add frontend (listening on port 80) # ovh lb frontend create my-app-lb --name http --port 80 --protocol http # Add backend pool for Blue environment # ovh lb backend create my-app-lb --name blue-pool --port 80 --protocol http --method round_robin # Add instances to Blue backend pool # For each instance in ovh_compute_instance.app_blue: # ovh lb backend add-server my-app-lb --backend blue-pool --address--weight 100 # Add backend pool for Green environment (initially disabled or empty) # ovh lb backend create my-app-lb --name green-pool --port 80 --protocol http --method round_robin # Configure frontend to use Blue pool # ovh lb frontend set my-app-lb --name http --default-backend blue-pool # Initially, the Green pool is not used.
Automating Deployments with CI/CD
A robust CI/CD pipeline is crucial. This pipeline will be responsible for building your application artifact, deploying it to the *idle* environment (e.g., Green), and performing health checks.
Deployment Script Logic
The deployment script needs to be idempotent and capable of deploying to a specific target environment. It should handle fetching dependencies, updating code, and potentially running database migrations.
#!/bin/bash
# deploy.sh
TARGET_ENV=$1 # "blue" or "green"
APP_VERSION=$2 # e.g., git commit hash or tag
echo "Deploying version ${APP_VERSION} to ${TARGET_ENV} environment..."
# 1. Get list of servers for the target environment
# This would typically involve querying OVH API or using an inventory file
SERVERS=$(ovh --json vm list --tag ${TARGET_ENV} --tag app | jq -r '.[].ipAddresses[] | select(.type == "public") | .address')
if [ -z "$SERVERS" ]; then
echo "Error: No servers found for environment ${TARGET_ENV}"
exit 1
fi
# 2. Deploy application artifact to each server
for server in $SERVERS; do
echo "Deploying to ${server}..."
# Use rsync, scp, or a deployment agent
rsync -avz --delete --exclude '.git' --exclude 'vendor/' /path/to/your/app/code/ ${server}:/var/www/html/
# Install dependencies (example using Composer)
ssh ${server} "cd /var/www/html/ && composer install --no-dev --optimize-autoloader"
# Run database migrations (example)
# ssh ${server} "cd /var/www/html/ && php artisan migrate --force" # For Laravel
# Clear cache, etc.
ssh ${server} "cd /var/www/html/ && php artisan cache:clear" # For Laravel
done
echo "Deployment to ${TARGET_ENV} complete."
exit 0
CI/CD Pipeline Integration (GitLab CI Example)
Your CI/CD configuration will trigger deployments. A common pattern is to deploy to the “Green” environment when a new tag is pushed, after tests pass.
stages:
- build
- test
- deploy_blue
- deploy_green
- switch_traffic
variables:
APP_VERSION: $CI_COMMIT_TAG
OVH_CLI_AUTH_FILE: /etc/gitlab-runner/.ovh.conf # Ensure OVH CLI credentials are set up
build_app:
stage: build
script:
- echo "Building application..."
# - composer install --no-dev
# - npm install && npm run build # if you have frontend assets
artifacts:
paths:
- vendor/
- public/build/ # Example for compiled assets
expire_in: 1 day
run_tests:
stage: test
script:
- echo "Running tests..."
# - vendor/bin/phpunit
# - npm test
deploy_to_green:
stage: deploy_green
script:
- echo "Deploying to Green environment..."
- ./deploy.sh green $APP_VERSION
only:
- tags # Trigger only on new tags
when: on_success # Only run if previous stages succeed
# Manual step to switch traffic after verification
switch_to_green:
stage: switch_traffic
script:
- echo "Switching traffic to Green environment..."
# This script will update the OVH Load Balancer configuration
- ./switch_traffic.sh green
only:
- tags
when: manual # Requires manual approval
The Traffic Switching Mechanism
This is the critical step where zero downtime is achieved. It involves reconfiguring the load balancer to point to the newly deployed environment.
Switching Script Logic
The switch_traffic.sh script will update the OVH Load Balancer’s frontend to use the desired backend pool. It also needs to handle the rollback scenario.
#!/bin/bash
# switch_traffic.sh
TARGET_ENV=$1 # "blue" or "green"
CURRENT_LB_BACKEND="blue-pool" # Assume Blue is currently active
NEW_LB_BACKEND=""
ROLLBACK_BACKEND=""
if [ "$TARGET_ENV" == "green" ]; then
NEW_LB_BACKEND="green-pool"
ROLLBACK_BACKEND="blue-pool"
elif [ "$TARGET_ENV" == "blue" ]; then
NEW_LB_BACKEND="blue-pool"
ROLLBACK_BACKEND="green-pool"
else
echo "Invalid target environment: $TARGET_ENV"
exit 1
fi
echo "Switching traffic from ${CURRENT_LB_BACKEND} to ${NEW_LB_BACKEND}..."
# 1. Add the new backend pool to the frontend (if not already there)
# This step might be implicit if both pools are always configured but only one is active.
# 2. Update the frontend to use the new backend pool
# This is the atomic switch. OVH Load Balancer API/CLI command to update frontend.
# Example: ovh lb frontend set my-app-lb --name http --default-backend $NEW_LB_BACKEND
# 3. (Optional but recommended) Health check the new environment
echo "Performing health checks on ${NEW_LB_BACKEND}..."
# Implement a script to check application health endpoints on the new environment.
# If health checks fail, immediately switch back.
# 4. If health checks pass, disable the old backend pool (or remove it from frontend)
# This is crucial for rollback safety. The old environment remains available.
# Example: ovh lb frontend set my-app-lb --name http --default-backend $NEW_LB_BACKEND --fallback-backend $ROLLBACK_BACKEND
# Or simply remove the old backend from the frontend if the LB supports it.
echo "Traffic switched to ${TARGET_ENV} environment."
exit 0
Rollback Procedure
If issues are detected post-deployment (either automatically via health checks or manually), a rollback is a simple matter of switching the load balancer back to the previous stable environment.
#!/bin/bash
# rollback.sh
# This script assumes the previous environment is still active and healthy.
# It would be triggered manually or by an automated monitoring alert.
echo "Initiating rollback..."
# Determine the current active and previous stable environments
# This logic needs to be robust, perhaps by querying LB config or maintaining state.
# For simplicity, let's assume we are rolling back from Green to Blue.
PREVIOUS_STABLE_ENV="blue" # The environment that was live before the last deployment
echo "Switching traffic back to ${PREVIOUS_STABLE_ENV} environment..."
# Use the switch_traffic.sh script or a dedicated rollback command
./switch_traffic.sh $PREVIOUS_STABLE_ENV
echo "Rollback complete. Traffic is now on ${PREVIOUS_STABLE_ENV}."
exit 0
Advanced Considerations
- Database Migrations: This is often the most complex part. Ensure your migrations are backward-compatible (e.g., add new columns/tables before deploying code that uses them, then remove old columns/tables in a subsequent deployment). A common pattern is to run migrations on the *new* environment before switching traffic, and have a “no-op” or backward-compatible migration for the rollback.
- Stateful Applications: If your application relies on local state (e.g., file uploads not stored in a shared volume), ensure this state is handled correctly during deployments or that instances are treated as ephemeral.
- Health Checks: Implement comprehensive health check endpoints in your PHP application that verify database connectivity, cache status, and core functionality. The load balancer should use these.
- Zero-Downtime Updates of Infrastructure: Consider how you will update the underlying infrastructure (OS patches, web server versions) without downtime. This might involve a rolling update of instances within an environment before a full blue-green switch.
- Canary Releases: For even lower risk, consider a canary release where a small percentage of traffic is directed to the new version first, before a full blue-green switch. This requires more sophisticated load balancer capabilities.
- OVH API/CLI Automation: Thoroughly understand the OVH API or CLI commands for managing load balancers and instances. Automate as much as possible.
Implementing a zero-downtime blue-green deployment pipeline requires careful planning and automation. By leveraging OVH’s infrastructure and a well-defined CI/CD process, you can significantly reduce deployment risks and ensure continuous availability for your PHP applications.