Unable to load balance using Docker, Consul and nginx - nginx

What I want to achive is load balancing using this stack: Docker, Docker Compose, Registrator, Consul, Consul Template, NGINX and, finally, a tiny service that prints out "Hello world" in browser. So, at this moment I have a docker-compose.yml file. It looks like so:
version: '2'
services:
accent:
build:
context: ./accent
image: accent
container_name: accent
restart: always
ports:
- 80
consul:
image: gliderlabs/consul-server:latest
container_name: consul
hostname: ${MYHOST}
restart: always
ports:
- 8300:8300
- 8400:8400
- 8500:8500
- 8600:53/udp
command: -advertise ${MYHOST} -data-dir /tmp/consul -bootstrap -client 0.0.0.0
registrator:
image: gliderlabs/registrator:latest
container_name: registrator
hostname: ${MYHOST}
network_mode: host
restart: always
volumes:
- /var/run/docker.sock:/tmp/docker.sock
command: -ip ${MYHOST} consul://${MYHOST}:8500
nginx:
container_name: nginx
image: nginx:latest
restart: always
volumes:
- /etc/nginx
ports:
- 8181:80
consul-template:
container_name: consul-template
build:
context: ./consul-template
network_mode: host
restart: always
volumes_from:
- nginx
volumes:
- /var/run/docker.sock:/tmp/docker.sock
command: -consul=${MYHOST}:8500 -wait=5s -template="/etc/ctmpl/nginx.ctmpl:/etc/nginx/nginx.conf:docker kill -s HUP nginx"
The first service - accent - is that my web service that I need to load balance. When I run this command:
$ docker-compose up
I see that all services start to run and I see no error messages. It looks as if everything is just perfect. When I run
$ docker ps
I see this in the console:
... NAMES STATUS PORTS
consul-template Up 45 seconds
consul Up 56 seconds 0.0.0.0:8300->8300/tcp, 0.0.0.0:8400->8400/tcp, 8301-8302/tcp, 8301-8302/udp, 0.0.0.0:8500->8500/tcp, 8600/tcp, 8600/udp, 0.0.0.0:8600->53/udp
nginx Up 41 seconds 0.0.0.0:8181->80/tcp
registrator Up 56 seconds
accent Up 56 seconds 0.0.0.0:32792->80/tcp
Please, pay attention to the last row and especially to PORTS column. As you can see, this service publishes 32792 port. To check that my web service is achievable I go to 127.0.0.1:32972 on my host machine (the machine where I run docker compose up) and see this in browser:
Hello World
This is exactly what I wanted to see. However, it is not what I finally want. Please, have a look at the output of docker ps command and you will see, that my nginx service published 8181 port. So, my expectation is that when I go to this address - 127.0.0.1:8181 - I will see exactly the same "Hello world" page. However, it is not. In browser I see Bad Gateway error message and in nginx logs I see this error message
nginx | 2017/01/18 06:16:45 [error] 5#5: *5 connect() failed (111: Connection refused) while connecting to upstream, client: 172.18.0.1, server: , request: "GET /favicon.ico HTTP/1.1", upstream: "http://127.0.0.1:32792/index.php", host: "127.0.0.1:8181"
It is really interesting, because nginx does what I expect it to do - upstreams to "http://127.0.0.1:32792/index.php". But I'm not sure why does it fail. By the way, this is how nginx.conf (created automatically with Consul Template) looks like:
worker_processes 1;
events {
worker_connections 1024;
}
http {
sendfile on;
upstream app_servers {
server 127.0.0.1:32792;
}
server {
listen 80;
root /code;
index index.php index.html;
location / {
try_files $uri/ $uri/ /index.php;
}
location ~ \.php$ {
proxy_pass http://app_servers;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location ~ /\.ht {
deny all;
}
}
}
I wouldn't change anything, since this nginx.conf looks good to me. Trying to understand why it does not work, I shelled to nginx container and made a couple of commands:
$ curl accent
Hello World
$ curl 127.0.0.1:32972
curl: (7) Failed to connect to 127.0.0.1 port 32972: Connection refused
$ curl accent:32972
curl: (7) Failed to connect to accent port 32972: Connection refused
Again, it is interesting, because nginx container sees my web service under port 80 and not under its published 32972 port. Anyway, at this stage I do not know why it does not work and how to fix it. I just have a guess, that it is somehow connected to the way, how network is configured in docker-compose.yml. I tried various combinations of network_mode: host on accent and nginx service, but to no avail - either accent stops working or nginx or both. So, I need some help.

When you do port binding it publish some port from container (80 in accent e.g.) and some port on your host (random 32792 on host e.g.).Containers in same network as your accent container can access your container port 80 by accent (same as accent:80) due to docker-compose services name resolving. You can access accent:80 from your host with accent:32792. When you are requesting 127.0.0.1:32792 from your nginx container you can access only nginx container 32792 port, not accent. accent:32792 is not correct url from anyway (80 port open on accent, 32792 on host). But 127.0.0.1:32792 should work when you add nginx container to host network. But I noticed that you use incorrect port in curl call. Your accent:80 published to host 32792 but you request 32972.

Related

FastAPI served through NGINX with gunicorn and docker compose

I have a FastAPI API that I want to serve using gunicorn, nginx and docker compose.
I manage to make the FastApi and Gunicorn work with docker compose, now I add nginx. But I cannot manage to make it work. When I do curl http://localhost:80 I get this messsage: If you see this page, the nginx web server is successfully installed and working. Further configuration is required.
So this is my docker compose file:
version: '3.8'
services:
web:
build:
dockerfile: Dockerfile.prod
context: .
command: gunicorn main:app --bind 0.0.0.0:8000 --worker-class uvicorn.workers.UvicornWorker
expose:
- 8000
env_file:
- ./.env.prod
nginx:
build:
dockerfile: Dockerfile.prod
context: ./nginx
ports:
- 1337:80
depends_on:
- web
On this one, if I set ports to 80:80 I get an error when the image is composed: Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use, which I don't know why.
If I put [some random number]:80 (e.g. 1337:80) then the docker build works, but I get the If you see this page, the nginx web server is successfully installed but... error message state before. I think 1337 is not where nginx is listening, and that's why.
This is my nginx conf file:
upstream platic_service {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://platic_service;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
I tried to change it to listen to 8080 but does not work.
What am I doing wrong?

Config nginx to reverse proxy for mqtt communication

I have an MQTT (EMQX) server running on an ip and a port. And I communicate directly between my service and that port using the nodejs MQTT library.
I want to use a reverse proxy (nginx) to be able to use a DNS in order to prune the communication.
At this moment my nginx is configured like this:
events { worker_connections 1024; }
stream {
upstream websocket {
server ******:7053;
}
server {
listen 8888;
proxy_pass websocket;
}
}
http {
server {
listen 884;
server_name *******;.
error_log /var/log/errors.log;
location / {
proxy_pass *******;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
}
So when I try to connect through port 8888 the nginx always timeout
2020/12/03 16:23:48 [error] 22#22: *31 upstream timed out (110: Connection timed out) while connecting to upstream, client: 89.155.0.10, server: 0.0.0.0:8888, upstream: "192.16.102.26:7053", bytes from/to client:0/0, bytes from/to upstream:0/0
both services are in docker containers. and are started by a docker compose.
the compose for MQTT service is:
version: "2.1"
services:
mqtt-broker:
build:
context: .
dockerfile: Dockerfile
container_name: evio_mqtt_broker
environment:
- EMQX_LISTENER__SSL__EXTERNAL=8883
- EMQX_DASHBOARD__LISTENER__HTTP=18083
- EMQX_LOADED_PLUGINS="emqx_auth_username,emqx_recon,emqx_retainer,emqx_management,emqx_dashboard"
- EMQX_LISTENER__SSL__EXTERNAL__TLS_VERSIONS=tlsv1.2
#- EMQX_LISTENER__SSL__EXTERNAL__KEYFILE=etc/certs/key.pem
#- EMQX_LISTENER__SSL__EXTERNAL__CERTFILE=etc/certs/cert.pem
#- EMQX_LISTENER__SSL__EXTERNAL__CACERTFILE=etc/certs/cacert.pem
- EMQX_LISTENER__SSL__EXTERNAL__VERIFY=verify_peer
#- EMQX_LISTENER__SSL__EXTERNAL__FAIL_IF_NO_PEER_CERT=true
- EMQX_LISTENER__SSL__EXTERNAL__REUSE_SESSIONS=on
- EMQX_LISTENER__SSL__EXTERNAL__HONOR_CIPHER_ORDER=on
- EMQX_ALLOW_ANONYMOUS=false
- EMQX_AUTH__USER__1__USERNAME=****
- EMQX_AUTH__USER__1__PASSWORD=****
#- EMQX_AUTH__USER__2__USERNAME=umdc
#- EMQX_AUTH__USER__2__PASSWORD=umdc_buddy
- EMQX_DASHBOARD__DEFAULT_USER__PASSWORD=****
ports:
- "7053:1883" # MQTT Port
- "8883:8883" # MQTT SSL Port
#- "8083:8083" # MQTT WebSocket Port
#- "8084:8084" # MQTT WebSocket SSL Port
#- "8080:8080" # HTPP Management Port
- "1884:18083" # Web Dashboard Port
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
networks:
- evio_network
stop_signal: SIGKILL
networks:
evio_network:
and for nginx are:
version: "2.0"
networks:
evio_network:
services:
reverse_proxy:
container_name: reverse_proxy
image: nginx
networks:
- evio_network
ports:
- 8888:8888
- 8843:8843
- 1883:1883
- 8883:8883
volumes:
- /home/evio/src/evio_nginx_reverse_proxy/config/nginxDEV.conf:/etc/nginx/nginx.conf
restart: always
Do I have to change anything in mqtt or is something wrong with my reverse proxy?
As hashed out in the comments.
The problem here was that the 2 services were being started from seperate docker-compose files. While they were both binding to networks with the same name, those networks were separate because they were being prefixed by different orchestration names.
There are 2 solutions to this problem:
Combine the 2 docker compose files, this will mean that they are then in the same namespace and will share the common named network.
Create a "external" network and reference this from both files.
For the second option you use the docker network command to create the network, e.g. docker network create evio_network and then at the end of each compose file include the following:
networks:
evio_network:
external:
name: "evio_network"

Docker nginx proxy to host

Short description:
Nginx running on docker, how to configure nginx so that it forwards calls to host.
Long description:
We have one web application which communicates to couple of backends (lets says rest1, rest2 and rest3). We are responsible for rest1.
Lets consider that I started rest1 manually on my pc and running on 2345 port. I want nginx (which is running in docker) to redirect all call torest1 to my own running instance(note, the instance is running on host, not any container and not in docker). And for rest2 and rest3 to some other docker node or may be some other server (who cares).
What I am looking for is:
docker-compose.yml configurations (if needed).
nginx configuration.
Thanks in advance.
Configure nginx like the following (make sure you replace IP of Docker Host) and save it as default.conf:
server {
listen 80;
server_name _;
location / {
proxy_pass http://<IP of Docker Host>;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Now bring up the container:
docker run -d --name nginx -p 80:80 -v /path/to/nginx/config/default.conf:/etc/nginx/conf.d/default.conf nginx
If you are using Docker Compose file version 3 you don't need any special config for docker-compose.yml file at all, just use the special DNS name host.docker.internal to reach a host service, as on the following nginx.conf example:
events {
worker_connections 1024;
}
http {
upstream host_service {
server host.docker.internal:2345;
}
server {
listen 80;
access_log /var/log/nginx/http_access.log combined;
error_log /var/log/nginx/http_error.log;
location / {
proxy_pass http://host_service;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $realip_remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}
Solution 1
Use network_mode: host, this will bind your nginx instance to host's network interface.
This could result in conflicts when running multiple nginx containers: every exposed port is binded to host's interface.
Solution 2
I'm running more nginx instances for every service I would like expose to outside world.
To keep the nginx configurations simple and avoid binding every nginx to host use the container structure:
dockerhost - a dummy container with network_mode: host
proxy - nginx container used as a proxy to host service,
link dockerhost to proxy, this will add an /etc/hosts entry in proxy contianer - we can use 'dockerhost' as a hostname in nginx configuration.
docker-compose.yaml
version: '3'
services:
dockerhost:
image: alpine
entrypoint: /bin/sh -c "tail -f /dev/null"
network_mode: host
proxy:
image: nginx:alpine
links:
- dockerhost:dockerhost
ports:
- "18080:80"
volumes:
- /share/Container/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
default.conf
location / {
proxy_pass http://dockerhost:8080;
This method allows us to have have automated let's encrtypt certificates generated for every service running on my server. If interested I can post a gist about the solution.
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://host.docker.internal:3000;
}
}
Docker expose host address is host.docker.internal in Mac os
There a couple of things you have to keep in mind:
Docker compose (from version 3) by default uses the service name as hostname for inter container networking
Nginx need to know the upstream first
I strongly recommend mounting the default.conf directly into your docker-compose.yml.
Lastly you have to dockerize your backend to make use of docker internal networking.
An example repo where I use nginx and docker-compose in a full-stack project: https://gitlab.com/datails/api.
The following example have some prerequisites:
you have a folder structure like:
- backend/
- frontend/
- default.conf
- docker-compose.yml
Secondly the backend and front-end dit have a Dockerfile that exposes an application on port 3000.
Example default.conf:
upstream backend {
server backend:3000;
}
upstream frontend {
server frontend:3000;
}
server {
listen 80;
location /api {
proxy_pass http://backend;
}
location / {
proxy_pass http://frontend/;
}
}
Example docker-compose.yml:
version: '3.8'
services:
nginx:
image: nginx:1.19.4
depends_on:
- server
- frontend
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- '8080:80'
Then make sure you have your backend dockerized and called (in this case) backend as a service and a front-end (if needed) called frontend as a service in your docker-compose:
version: '3.8'
services:
nginx:
image: nginx:1.19.4
depends_on:
- server
- frontend
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf
ports:
- '8080:80'
frontend:
build: ./frontend
backend:
build: ./backend
This is a bare minimum example to get started. Hope this will help future developers.

docker compose: rebuild of one linked container breaks nginx's upstream

I'm using docker-compose with "Docker for Mac" and I have two containers: one NGINX, one container serving a node-app on port 3000.
docker-compose.yml looks like this:
version: "2"
services:
nginx:
build: ./nginx
ports:
- "80:80"
links:
- api
api:
build: ./api
volumes:
- "./api:/opt/app"
In the NGINX's config I say:
upstream api {
server api:3000;
}
server {
# ....
location ~ ^/api/?(.*) {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass http://api;
proxy_redirect off;
}
}
Now, when I change something in the node code and rebuild the container
$ docker-compose stop api && docker-compose up -d --build --no-deps api
the container is getting rebuilt and started. The problem is, that sometimes the internal IP of the container changes and NGINX won't know about that. Funny enough, when I go into the NGINX container and ping api I get the new IP address
$ ping api
PING api (172.19.0.3): 56 data bytes
64 bytes from 172.19.0.3: icmp_seq=0 ttl=64 time=0.236 ms
but NGINX logs still say
2016/10/20 14:20:53 [error] 9#9: *9 connect() failed (113: No route to host) while connecting to upstream, client: 172.19.0.1, server: localhost, request: "GET /api/test HTTP/1.1", upstream: "http://172.19.0.7:3000/api/test", host: "localhost"
where the upstream's 172.19.0.7 is still the old IP address.
PS: this doesn't happen every time I rebuild the container.
This is because Nginx caches the DNS response for upstream servers - in your workflow you're only restarting the app container, so Nginx doesn't reload and always uses its cached IP address for the api container.
When you run a new api container, as you've seen, it can have a different IP address so the cache in Nginx is not valid. The ping works because it doesn't cache Docker's DNS response.
Assuming this is just for dev and downtime isn't an issue, docker-compose restart nginx after you rebuild the app container will restart Nginx and clear the DNS cache.

How to forward upstream from nginx to upstream server

I am trying to reverse-proxy an ejabberd connection manager with nginx in docker.
Following is my docker-compose file
version: '2'
services:
nginx:
image: nginx
ports:
- "80:80"
- "443:443"
depends_on:
- ejabberd
links:
- ejabberd
ejabberd:
image: ejabberd:16.04
depends_on:
- mysql
ports:
- "5280:5280"
links:
- mysql
mysql:
image: mysql:5.6
ports:
- "3306:3306"
The images nginx, ejabberd:16.04 and mysql:5.6 are available in my local docker.
Following is my server config file which is included in the nginx.conf file
upstream ejabberd-server {
server ejabberd:5280;
}
server {
# regular silverstripe things here
# location should match your JabberPage::BOSHUrl
# with a leading slash
listen 80;
#server_name oops.hereim.co
location /http-bind {
# Local ejabberd with http-bind
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://ejabberd-server;
}
}
But when I try to access the url http://192.168.99.102/http-bind
I get the error 404 not found.
Am I missing something in the configuration above?
192.168.99.102 is the IP of my docker machine.
docker-compose exec nginx ping ejabberd
The above command returns a reply.
The upstream has to be setup to host ejabberd as nginx and ejabberd are not on the same ejabberd container.
If you see the docker-compose.yml, the ejabberd and mysql connection works fine and I am able to connect to mysql from ejabberd. But there seems to be some missing piece while connection nginx to ejabberd.
The issue is probably due to an nginx issue. nginx does not pick up .conf files under the /etc/nginx/conf.d directory.
I had to change the file /etc/nginx/sites-enabled/default and enter the location inside that file. This solved my problem for this question.
The next action item is to check why nginx does not read conf file from /etc/nginx/conf.d directory
Looks like your server part in upstream is a bit wrong.
Can you try server 127.0.0.1:5280; instead of server ejabberd:5280; ?

Resources