Useless Load Balancers
You should not be exposing your API servers if they are sitting behind a load balancer. This defeats the purpose of a load balancer. We will go over a demonstration using Docker Compose, Nginx, and a cluster of Node servers.
Table of Contents š
Load Balancers
Load balancers are helpful for sites that receive a lot of traffic/requests. For example, a ton of requests might be too much for a single server to handle. To handle this, multiple servers can be deployed. These servers often host the same content and the load balancer distributes the requests among the servers in a way that optimally uses each server, preventing the servers from overloading and making maximum use of each server's capacity.
Exposing the API Servers
The key here is that load balancers distribute the requests among the API servers. The clients are not contacting the API servers directly. In other words, any contact with the API servers should be coming from the load balancer directly. To ensure this is the case, the API servers should not be exposed over the network.
Nginx Configuration
Take the following Nginx configuration as an example. This configuration creates a group of Node servers all accessible on the same port, it is the host name that is different. Using this configuration, Nginx will distribute requests among these servers in a round robin fashion (with extra weight placed on server 1).
upstream backend {
server ${PROJECT_NAME}-server-1:${NODE_PORT} weight=5;
server ${PROJECT_NAME}-server-2:${NODE_PORT};
server ${PROJECT_NAME}-server-3:${NODE_PORT};
server ${PROJECT_NAME}-server-4:${NODE_PORT};
server ${PROJECT_NAME}-server-5:${NODE_PORT};
server ${PROJECT_NAME}-server-6:${NODE_PORT};
}
server {
listen ${NGINX_PORT};
server_name ${NGINX_CONTAINER_NAME};
root /usr/share/nginx/html;
location /api {
proxy_pass http://backend;
}
}
WARNING: The ${VARIABLE} here are substitutions for environment variables.
Nginx and these upstream servers are all contained inside a Docker network created with Docker Compose. The following docker-compose.yaml file creates this network and spins up these containers.
name: ${PROJECT_NAME}
services:
server:
pull_policy: build
image: my-node-i
build:
context: server
dockerfile: Dockerfile
env_file: .env
deploy:
replicas: 6
volumes:
- my-node-v:/server/node_modules
reverse-proxy:
pull_policy: build
image: my-nginx-i
container_name: ${NGINX_CONTAINER_NAME}
env_file: .env
build:
context: reverse-proxy
dockerfile: Dockerfile
ports:
- ${NGINX_PORT}:${NGINX_PORT}
depends_on:
- server
volumes:
my-node-v:
name: my-node-v
Here, the server service is for creating the Node servers and the reverse-proxy service is for creating the Nginx load balancer. The most relevant part of this configuration is that the reverse-proxy service has a port mapping while the server service does not.
INFO: The port mapping is specified with HOST_PORT:CONTAINER_PORT. For the reverse-proxy service the port mapping is ${NGINX_PORT}:${NGINX_PORT}.
Inspecting the Containers
After spinning up this environment, we can inspect the containers to see if everything is working as expected.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
999a20435efe my-nginx-i "/docker-entrypoint.ā¦" 19 hours ago Up 19 hours 80/tcp, 0.0.0.0:1234->1234/tcp my-nginx-c
1e1ce1b90734 my-node-i "docker-entrypoint.sā¦" 19 hours ago Up 19 hours my-project-server-2
ee475753ef72 my-node-i "docker-entrypoint.sā¦" 19 hours ago Up 19 hours my-project-server-1
613ffb62fc2d my-node-i "docker-entrypoint.sā¦" 19 hours ago Up 19 hours my-project-server-5
ad45000ad3bb my-node-i "docker-entrypoint.sā¦" 19 hours ago Up 19 hours my-project-server-6
cc8192003a48 my-node-i "docker-entrypoint.sā¦" 19 hours ago Up 19 hours my-project-server-3
b44a1677c3d9 my-node-i "docker-entrypoint.sā¦" 19 hours ago Up 19 hours my-project-server-4
From listing out the containers we can see that the load balancer has a port mapping while none of the Node API servers are accessible. To access them, we would have to navigate into the Docker network and ping them there.
docker exec -it my-project-server-1 bash
curl localhost:1235/api/users
["WittCepter","Mike","WittCode","Spencer"]
ERROR: If you are not using Docker like in the example here, you can instead create firewall rules to deny direct access to the API servers.