The goal
You have two web servers and want one public address in front of them, so traffic is shared and one server can go down without an outage. This post builds an OCI load balancer (LB) and explains each part as you go.
The parts
| Part | What it does |
|---|---|
| Load balancer | The public IP and the capacity, measured in bandwidth |
| Listener | Waits for traffic on a port, such as HTTP on 80 |
| Backend set | A group of servers plus a balancing policy and health check |
| Backend | One server: an IP address and port |
| Health check | Regularly tests each backend, and stops sending traffic to failed ones |
Traffic flows like this: client > listener > backend set > healthy backend.
Before you start
You need:
- A public subnet for the load balancer.
- Two instances running a web server on port 80, in a private or public subnet of the same VCN.
- The CLI set up. See OCI CLI install and setup.
Set variables so the commands stay short:
export C=ocid1.compartment.oc1..aaaaexample
export LB_SUBNET=ocid1.subnet.oc1.ap-mumbai-1.aaaaexample
Step 1: open the network
This is where most first attempts go wrong. Traffic needs to be allowed at each hop.
| Where | Direction | Port | From or to |
|---|---|---|---|
| LB subnet | Ingress | TCP 80 | 0.0.0.0/0 or your clients' range |
| LB subnet | Egress | TCP 80 | The backend subnet |
| Backend subnet | Ingress | TCP 80 | The LB subnet range |
| Backend instance | OS firewall | TCP 80 | Open it, see below |
Oracle Linux images have firewalld turned on by default:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
curl -I http://localhost/
For more detail, see when a port is open but the instance still times out.
Step 2: create the load balancer
oci lb load-balancer create \
--compartment-id "$C" \
--display-name lb-web \
--shape-name flexible \
--shape-details '{"minimumBandwidthInMbps":10,"maximumBandwidthInMbps":100}' \
--subnet-ids "[\"$LB_SUBNET\"]" \
--is-private false \
--wait-for-state SUCCEEDED
Get its ID and public address:
export LB=$(oci lb load-balancer list --compartment-id "$C" --display-name lb-web \
--query "data[0].id" --raw-output)
oci lb load-balancer get --load-balancer-id "$LB" \
--query "data.\"ip-addresses\""
Load balancer changes run as background work requests. Add --wait-for-state SUCCEEDED to each step, so that the next step doesn't fail because one is still running.
Step 3: create the backend set and health check
oci lb backend-set create \
--load-balancer-id "$LB" \
--name bs-web \
--policy ROUND_ROBIN \
--health-checker-protocol HTTP \
--health-checker-port 80 \
--health-checker-url-path / \
--health-checker-return-code 200 \
--wait-for-state SUCCEEDED
The balancing policies:
| Policy | Sends the next request to |
|---|---|
ROUND_ROBIN | The next server in turn |
LEAST_CONNECTIONS | The server with the fewest active connections |
IP_HASH | The same server each time for a given client IP |
Tip
Use a small page such as
/healththat returns 200 for the health check. A path that redirects returns 301 and counts as a failure unless you change the expected return code.
Step 4: add the backends
for IP in 10.0.2.11 10.0.2.12; do
oci lb backend create \
--load-balancer-id "$LB" \
--backend-set-name bs-web \
--ip-address "$IP" \
--port 80 \
--wait-for-state SUCCEEDED
done
Step 5: create the listener
oci lb listener create \
--load-balancer-id "$LB" \
--name http \
--default-backend-set-name bs-web \
--port 80 \
--protocol HTTP \
--wait-for-state SUCCEEDED
Step 6: test it
oci lb backend-set-health get --load-balancer-id "$LB" --backend-set-name bs-web
oci lb load-balancer-health get --load-balancer-id "$LB"
curl -I http://<lb-public-ip>/
Make each server's page show its hostname, then send several requests and watch the answers alternate:
for i in 1 2 3 4; do curl -s http://<lb-public-ip>/ ; done
Now stop the web server on one backend. Within a minute or so, its health turns Critical and all traffic goes to the other one. Start it again to finish your first failover test.
If health shows Critical
Check for details on one backend. The backend's name is ip:port:
oci lb backend-health get \
--load-balancer-id "$LB" \
--backend-set-name bs-web \
--backend-name 10.0.2.11:80
| Health check result | Meaning | Fix |
|---|---|---|
CONNECT_FAILED | Blocked or not listening | Check security rules, firewalld and ss -ltn |
INVALID_STATUS_CODE | The page returned another status | Fix the path or the expected code |
TIMED_OUT | The app is too slow, or packets are dropped | Check the app and the routes |
UNKNOWN | Just created | Wait a couple of minutes |
The all backends Critical runbook goes further.
Warning
Health checks come from the load balancer's subnet, not from the client. If the backend's rule allows only your office range, every backend turns Critical, even though the server is fine.
Prevent it next time
- Put each backend in a different fault domain, so one hardware failure can't take down both.
- Add HTTPS with a certificate from the OCI Certificates service, and track its expiry. See LB certificate expired.
- Add an alarm for unhealthy backends with Monitoring alarms.
- Delete test load balancers when you're done, because they keep billing.