Load balancing helps distribute traffic across multiple servers, enhancing performance and reliability. One common strategy is Source IP Hash load balancing, which ensures that requests from the same client IP are consistently directed to the same server.
This method is particularly useful for applications requiring session persistence, such as shopping carts or user sessions. In this blog, we’ll implement Source IP Hash load balancing using Flask and HAProxy, all within Docker containers.
What is Source IP Hash Load Balancing?
Source IP Hash Load Balancing is a technique that uses a hash function on the client’s IP address to determine which server should handle the request. This guarantees that a particular client will always be directed to the same backend server, ensuring session persistence and stateful behavior.
Consistent Hashing: https://parottasalna.com/2024/06/17/why-do-we-need-to-maintain-same-hash-in-load-balancer/

Step-by-Step Implementation with Docker
Step 1: Create Flask Application
We’ll create three separate Dockerfiles, one for each Flask app.
Flask App 1 (app1.py)
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello from Flask App 1!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001)
Flask App 2 (app2.py)
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello from Flask App 2!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5002)
Flask App 3 (app3.py)
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello from Flask App 3!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5003)
Each Flask app listens on a different port (5001, 5002, 5003).
Step 2: Dockerfiles for each flask application
Dockerfile for Flask App 1 (Dockerfile.app1)
# Use the official Python image from the Docker Hub FROM python:3.9-slim # Set the working directory inside the container WORKDIR /app # Copy the current directory contents into the container at /app COPY app1.py . # Install Flask inside the container RUN pip install Flask # Expose the port the app runs on EXPOSE 5001 # Run the application CMD ["python", "app1.py"]
Dockerfile for Flask App 2 (Dockerfile.app2)
FROM python:3.9-slim WORKDIR /app COPY app2.py . RUN pip install Flask EXPOSE 5002 CMD ["python", "app2.py"]
Dockerfile for Flask App 3 (Dockerfile.app3)
FROM python:3.9-slim WORKDIR /app COPY app3.py . RUN pip install Flask EXPOSE 5003 CMD ["python", "app3.py"]
Step 3: Create a configuration for HAProxy
global
log stdout format raw local0
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http_front
bind *:80
default_backend servers
backend servers
balance source
hash-type consistent
server server1 app1:5001 check
server server2 app2:5002 check
server server3 app3:5003 check
Explanation:
- The
balance sourcedirective tells HAProxy to use Source IP Hashing as the load balancing algorithm. - The
hash-type consistentdirective ensures consistent hashing, which is essential for minimizing disruption when backend servers are added or removed. - The
serverdirectives define the backend servers and their ports.
Step 4: Create a Dockerfile for HAProxy
Create a Dockerfile for HAProxy (Dockerfile.haproxy)
# Use the official HAProxy image from Docker Hub FROM haproxy:latest # Copy the custom HAProxy configuration file into the container COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg # Expose the port for HAProxy EXPOSE 80
Step 5: Create a Dockercompose file
To manage all the containers together, create a docker-compose.yml file
version: '3'
services:
app1:
build:
context: .
dockerfile: Dockerfile.app1
container_name: flask_app1
ports:
- "5001:5001"
app2:
build:
context: .
dockerfile: Dockerfile.app2
container_name: flask_app2
ports:
- "5002:5002"
app3:
build:
context: .
dockerfile: Dockerfile.app3
container_name: flask_app3
ports:
- "5003:5003"
haproxy:
build:
context: .
dockerfile: Dockerfile.haproxy
container_name: haproxy
ports:
- "80:80"
depends_on:
- app1
- app2
- app3
Explanation:
- The
docker-compose.ymlfile defines four services:app1,app2,app3, andhaproxy. - Each Flask app is built from its respective Dockerfile and runs on its port.
- HAProxy is configured to wait (
depends_on) for all three Flask apps to be up and running.
Step 6: Build and Run the Docker Containers
Run the following commands to build and start all the containers:
# Build and run the containers docker-compose up --build
This command will build Docker images for all three Flask apps and HAProxy and start them up in the background.
Step 7: Test the Load Balancer
Open your browser or use a tool like curl to make requests to the HAProxy server:
curl http://localhost
Observation:
- With Source IP Hash load balancing, each unique IP address (e.g., your local IP) should always be directed to the same backend server.
- If you access the HAProxy from different IPs (e.g., using different devices or by simulating different client IPs), you will see that requests are consistently sent to the same server for each IP.
For the URI based hashing we just need to add,
global
log stdout format raw local0
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http_front
bind *:80
default_backend servers
backend servers
balance uri
hash-type consistent
server server1 app1:5001 check
server server2 app2:5002 check
server server3 app3:5003 check
Explanation:
- The
balance uridirective tells HAProxy to use URI Hashing as the load balancing algorithm. - The
hash-type consistentdirective ensures consistent hashing to minimize disruption when backend servers are added or removed. - The
serverdirectives define the backend servers and their ports.