Post

docker-compose nginx load-balancing (spring, node, ws)

docker-compose nginx load-balancing (spring, node, ws)

This post was migrated from Tistory. You can find the original here.

nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    # This line is included by default in the nginx image, but it needs to be commented out if you want to mount your own conf as a volume
    # include /etc/nginx/conf.d/*.conf;

    #load balancer
    upstream test_server {
        server host.docker.internal:10010;
        server host.docker.internal:10011;
    }

    server {
        listen 80;
        server_name  localhost;

        location / {
            proxy_pass http://test_server;
        }
    }
}

Aside from the load balancer lines, this is the default conf that ships with the current latest nginx image.

If you don’t comment out include /etc/nginx/conf.d/*.conf;, the default.conf inside the conf.d folder gets applied even if you mount your own conf as a volume.

On Windows, there’s no conf.d folder when you download nginx (the Docker image does have one), so it works fine on Windows — which led me down a rabbit hole trying to figure out why it wasn’t behaving according to the conf I mounted in Docker.

Traffic that comes in through the nginx server’s listen port gets load-balanced across the upstream servers defined in location’s proxy_pass.

The default load-balancing method is round robin.

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
services:
    nginx:
        image: nginx
        ports:
            - '80:80'
        volumes:
            - ./nginx.conf:/etc/nginx/nginx.conf
        environment:
            - NGINX_PORT=80

    server1:
        image: node:lts-alpine3.16
        ports:
            - '10010:8000'
        volumes:
            - ./server1:/server1
        command:
            - /bin/sh
            - -c
            - |
                cd server1
                npm i
                node server1.js

    server2:
        image: node:lts-alpine3.16
        ports:
            - '10011:8000'
        volumes:
            - ./server1:/server1
        command:
            - /bin/sh
            - -c
            - |
                cd server1
                npm i
                node server1.js

nginx.conf (web-socket)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    # include /etc/nginx/conf.d/*.conf;

    # WebSocket-related section
    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }
    
    #load balancer
    upstream websocket {
        server host.docker.internal:10010;
        server host.docker.internal:10011;
    }

    server {
        listen 80;
		
        # WebSocket-related section
        location / {
            proxy_pass http://websocket;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header Host $host;
        }
    }
}

WebSockets can be load-balanced too.

docker-compose.yml (web-socket)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
services:
    nginx:
        image: nginx
        ports:
            - '80:80'
        volumes:
            - ./nginx.conf:/etc/nginx/nginx.conf
        environment:
            - NGINX_PORT=80

    server1:
        image: azul/zulu-openjdk-alpine:17-jre-latest
        ports:
            - '10010:8080'
        volumes:
            - ./server2:/server2
        command:
            - /bin/sh
            - -c
            - |
                cd server2
                nohup java -jar message_websocket-1.0-SNAPSHOT.jar 

    server2:
        image: azul/zulu-openjdk-alpine:17-jre-latest
        ports:
            - '10011:8080'
        volumes:
            - ./server2:/server2
        command:
            - /bin/sh
            - -c
            - |
                cd server2
                nohup java -jar message_websocket-1.0-SNAPSHOT.jar

If you reconnect, you can see the connection bouncing back and forth between server1 and server2, confirming that the load is distributed.

This post is licensed under CC BY-NC 4.0 by the author.