WittCodešŸ’»

Useless Load Balancers

By

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.

Image

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.

Useless Load Balancers