Docker scale with deterministic port binding - docker

I would like to scale a wildfly container having exposed multiple ports with deterministic results.
docker-compose.yml
version: '3'
services:
wildfly-server:
build:
context: .
dockerfile: Dockerfile
args:
admin_user: admin
admin_password: admin
deploy:
resources:
limits:
memory: 1.5G
cpus: "1.5"
restart: always
ports:
- "8000-8099:8080"
- "8100-8199:9990"
- "8200-8299:8787"
expose:
- "8080"
- "9990"
- "8787"
Dockerfile
FROM jboss/wildfly:16.0.0.Final
# DOCKER ENV VARIABLES
ENV WILDFLY_HOME /opt/jboss/wildfly
ENV STANDALONE_DIR ${WILDFLY_HOME}/standalone
ENV DEPLOYMENT_DIR ${STANDALONE_DIR}/deployments
ENV CONFIGURATION_DIR ${STANDALONE_DIR}/configuration
RUN ${WILDFLY_HOME}/bin/add-user.sh ${admin_user} ${admin_password} --silent
# OPENING DEBUG PORT
RUN rm ${WILDFLY_HOME}/bin/standalone.conf
ADD standalone.conf ${WILDFLY_HOME}/bin/
# SET JAVA ENV VARS
RUN rm ${CONFIGURATION_DIR}/standalone.xml
ADD standalone.xml ${CONFIGURATION_DIR}/
Command to start
docker-compose up --build --force-recreate --scale wildfly-server=10
It almost works as I want to, but there is some port discrepancy. When I create the containers, I want them to have incremental ports for each container to be exposed as follows:
machine_1 8001, 8101, 82001
machine_2 8002, 8102, 82002
machine_3 8003, 8103, 82003
But what I get as a result is not deterministic and looks like this:
machine_1 8001, 8102, 82003
machine_2 8002, 8101, 82001
machine_3 8003, 8103, 82002
The problem is that every time I run the compose up command, the ports are different for each container.
Example output:
CONTAINER ID COMMAND CREATED STATUS PORTS NAMES
0232f24fbca4 "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8028->8080/tcp, 0.0.0.0:8231->8787/tcp, 0.0.0.0:8126->9990/tcp wildfly-server_7
13a6a365a552 "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8031->8080/tcp, 0.0.0.0:8230->8787/tcp, 0.0.0.0:8131->9990/tcp wildfly-server_10
bf8260d9874d "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8029->8080/tcp, 0.0.0.0:8228->8787/tcp, 0.0.0.0:8129->9990/tcp wildfly-server_6
3d58f2e9bdfe "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8030->8080/tcp, 0.0.0.0:8229->8787/tcp, 0.0.0.0:8130->9990/tcp wildfly-server_9
7824a73a09f5 "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8027->8080/tcp, 0.0.0.0:8227->8787/tcp, 0.0.0.0:8128->9990/tcp wildfly-server_3
85425462259d "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8024->8080/tcp, 0.0.0.0:8224->8787/tcp, 0.0.0.0:8124->9990/tcp wildfly-server_2
5be5bbe8e577 "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8026->8080/tcp, 0.0.0.0:8226->8787/tcp, 0.0.0.0:8127->9990/tcp wildfly-server_8
2512fc0643a3 "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8023->8080/tcp, 0.0.0.0:8223->8787/tcp, 0.0.0.0:8123->9990/tcp wildfly-server_5
b156de688dcb "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8025->8080/tcp, 0.0.0.0:8225->8787/tcp, 0.0.0.0:8125->9990/tcp wildfly-server_4
3e9401552b0a "/opt/jboss/wildfly/…" 5 minutes ago Up 5 minutes 0.0.0.0:8022->8080/tcp, 0.0.0.0:8222->8787/tcp, 0.0.0.0:8122->9990/tcp wildfly-server_1
Question
Is there any way to make the port distribution deterministic? Like disable parallel running to have serial checks on the available ports or any other method? The only alternative I found is to have a yml template and generate all the necessary files (like 10 if I need 10 containers etc). Are there any alternative solutions?

No, you cannot currently (10/14/19) make the port selection deterministic in the docker-compose file. This behavior was requested in Github issues #722 and #1247, but those issues were closed without the issue having been implemented.
If you want to semi-dynamically scale an application like it sounds like you do, then you'll need to solve this another way. Your .yml templating idea sounds like the cleanest solution IMO.
Are you sure you need the ports to be deterministic? If you use a reverse proxy like nginx that listens on one host port and balances the load between all of your docker containers, would that work for your use case? Setting up an nginx load balancer in a docker container is pretty straightforward. I suggest you look into that, and if you still need a deterministic way for a caller to know the service's port so it can send a request to a specific server repeatedly, then go with your .yml templating solution or some kind of service discovery process separate from the docker-compose configuration.

You could do this using variable substitution:
yaml:
...
ports:
${PORT}:8080
Then call docker with the specific ports:
for p in {8000..8099}; do
PORT=$p docker-compose ...
done

Related

How to configure port mapping for replicated containers in Docker Compose?

The goal is to run two containers of publisher-app. One container should be mapped to port 8080 on the host machine, and the other on 8081. Here is the docker-compose:
publisher_app:
ports:
- "8080-8081:8080"
environment:
server.port: 8080
deploy:
mode: replicated
replicas: 2
Two containers are created, but as I understand, both ports are assigned to the first one, and the second one produces this error: Ports are not available: listen tcp 0.0.0.0:8081: bind: address already in use.
Here is the output of docker ps -a:
6c7067b4ebee spring-boot-rest-kafka_publisher_app "java -jar /app.jar" 33 seconds ago Up 28 seconds 0.0.0.0:8080->8080/tcp, 0.0.0.0:8081->8080/tcp spring-boot-rest-kafka_publisher_app_2
70828ba8f370 spring-boot-rest-kafka_publisher_app "java -jar /app.jar" 33 seconds ago Created spring-boot-rest-kafka_publisher_app_1
Docker engine version: 20.10.11
Docker compose version: 2.2.1
How to handle this case? Your help will be very appreciated.
Here is the source code: https://github.com/aleksei17/springboot-rest-kafka-mysql/blob/master/docker-compose.yml
tried locally on Windows 10 and failed similarly, both with v2 and with v2 disabled.
It seems like a compose issue
when tried on arch: amd64 fedora based linux distro with package manager installed docker and manually installing docker-compose 1.29.2 (using the official guide for linux) worked:
compose file:
version : "3"
services:
web:
image: "nginx:latest"
ports:
- "8000-8020:80"
docker command:
docker-compose up --scale web=5
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b304d397b2cd nginx:latest "/docker-entrypoint.…" 14 seconds ago Up 7 seconds 0.0.0.0:8004->80/tcp, :::8004->80/tcp testdir_web_4
a8c6f177a6e6 nginx:latest "/docker-entrypoint.…" 14 seconds ago Up 7 seconds 0.0.0.0:8003->80/tcp, :::8003->80/tcp testdir_web_3
b1abe53e7d7d nginx:latest "/docker-entrypoint.…" 14 seconds ago Up 8 seconds 0.0.0.0:8002->80/tcp, :::8002->80/tcp testdir_web_2
ead91e9df671 nginx:latest "/docker-entrypoint.…" 14 seconds ago Up 9 seconds 0.0.0.0:8001->80/tcp, :::8001->80/tcp testdir_web_5
65ffd6a87715 nginx:latest "/docker-entrypoint.…" 24 seconds ago Up 21 seconds 0.0.0.0:8000->80/tcp, :::8000->80/tcp testdir_web_1

Docker container unhealthy but no error in the logs

I am using the official docker-compose file of airflow to spin it up.
Some of my containers seem unhealthy:
34d8698d67e7 apache/airflow:2.0.2 "/usr/bin/dumb-init …" 31 minutes ago Up 28 minutes (unhealthy) 0.0.0.0:5555->5555/tcp, :::5555->5555/tcp, 8080/tcp airflow_flower_1
a291cf238b9f apache/airflow:2.0.2 "/usr/bin/dumb-init …" 31 minutes ago Up 29 minutes 8080/tcp airflow_airflow-init_1
fdb20e9152f3 apache/airflow:2.0.2 "/usr/bin/dumb-init …" 31 minutes ago Up 29 minutes (unhealthy) 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp airflow_airflow-webserver_1
abf5a16aa846 apache/airflow:2.0.2 "/usr/bin/dumb-init …" 31 minutes ago Up 29 minutes 8080/tcp airflow_airflow-worker_1
f6dc352f407b apache/airflow:2.0.2 "/usr/bin/dumb-init …" 31 minutes ago Up 28 minutes 8080/tcp airflow_airflow-scheduler_1
12dfc71e518f redis:latest "docker-entrypoint.s…" 31 minutes ago Up 29 minutes (healthy) 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp airflow_redis_1
However the logs of one of them for example do not seem very informative.
# docker logs -f fdb20e9152f3
WARNING! You should run the image with GID (Group ID) set to 0
even if you use 'airflow' user (UID=50000)
You started the image with UID=50000 and GID=50000
This is to make sure you can run the image with an arbitrary UID in the future.
See more about it in the Airflow's docker image documentation
http://airflow.apache.org/docs/docker-stack/entrypoint
BACKEND=postgresql+psycopg2
DB_HOST=my-db-endpoint
DB_PORT=5432
WARNING! You should run the image with GID (Group ID) set to 0
even if you use 'airflow' user (UID=50000)
You started the image with UID=50000 and GID=50000
This is to make sure you can run the image with an arbitrary UID in the future.
See more about it in the Airflow's docker image documentation
http://airflow.apache.org/docs/docker-stack/entrypoint
BACKEND=postgresql+psycopg2
DB_HOST=my-db-endpoint
DB_PORT=5432
Regardless of any airflow - specific issues, how can I check docker - wise what's going on?
Docker seems to be aware of a couple of containers not being healty.
edit: both failing containers have the healtcheck condition
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:5555/"]
and
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8080/"]
that seems to be failing by looking into their inspect output
Failed to connect to localhost port 8080: Connection refused
but I cannot pinpoint what is causing the failure.
edit: I have tried following the instructions to start the init service first as well
# docker-compose up airflow-init
Starting airflow_redis_1 ... done
Starting airflow_airflow-init_1 ... done
Attaching to airflow_airflow-init_1
airflow-init_1 | BACKEND=postgresql+psycopg2
airflow-init_1 | DB_HOST=my-db-endpoint
airflow-init_1 | DB_PORT=5432
but it never exits, it prints the above message and that's it...
I ran into similar issue and it was docker volume causing the issue. As I was running lots of containers on my mac, there wasn;t enough disk space. I managed to fixed this issue my pruning the docker volume.
docker volume prune
This will remove any unused volume on your mac book. Before running this command please check if you got any useful data.
For docker-compose, from the entrypoint, the default value of group id is 0.
"${AIRFLOW_UID:-50000}:${AIRFLOW_GID:-0}"
Edit your docker-compose.yaml file or ad ass env.sh file in your Airflow project repository.
It does seems to be an error due to less memory allocated to docker for running this image, try to increase the resources available to docker and see the magic
I ran into similar issue and the healthchecks were "causing" this. I was running them using default container's user.
Just to give a try, I changed the healthckeck command to start using airflow user instead, as follow:
$ runuser -u airflow -- <healthckeck command>
And it solved. I'm gonna change the user whom runs docker compose up to airflow from now on.

Elastic Beanstalk & Docker: problem with elastic beanstalk spawning multiple docker containers

I'm forced to use elastic beanstalk (eb) and Docker in deploying. When I build & run my container locally it boots up and runs well. I'm using supervisord to boot some ruby code (clockwork and Rails/puma)
When deploying using eb, I see how eb spawns several consecutive containers until all just chokes down:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
232bbe498977 a4a6fd70537b "supervisord -c /etc…" About a minute ago Up About a minute 80/tcp silly_williams
a9e21774575e a4a6fd70537b "supervisord -c /etc…" 2 minutes ago Up 2 minutes 80/tcp trusting_murdock
945f51ef510f a4a6fd70537b "supervisord -c /etc…" 3 minutes ago Up 3 minutes 80/tcp blissful_stonebraker
6e51470ddce8 a4a6fd70537b "supervisord -c /etc…" 4 minutes ago Up 4 minutes 80/tcp lucid_ramanujan
2689568ceb6d a4a6fd70537b "supervisord -c /etc…" 4 minutes ago Up 4 minutes 80/tcp keen_mestorf
Where should I be looking for the root to this behavior? Can the container be creating this behaviour or is eb configured in a wrong way?
(I apologize that I'm a bit too unspecific with details since I'm not in full control of the environment)
I eventually realized I had been tampering with some settings, and had set monitoring to basic. Once put to Enhanced it only booted one container and things started to work again!
In:
Elastic Beanstalk > [my application] > Configuration > monitoring > System: Enhanced.

Docker healthcheck for nginx container

I have a project using the official nginx docker container from Docker Hub, launching via Docker Compose. I have healthchecks configured in Docker Compose for each of my containers, and recently the healthcheck for this nginx container has been behaving strangely; on launching with docker-compose up -d, all my containers launch, and begin running healthchecks, but the nginx container looks like it never runs the healthcheck. I can manually run the script just fine if I docker exec into the container, and the healthcheck runs normally if I restart the container.
Example output from docker ps:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
458a55ae8971 my_custom_image "/tini -- /usr/local…" 7 minutes ago Up 7 minutes (healthy) project_worker_1
5024781b1a73 redis:3.2 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes (healthy) 127.0.0.1:6379->6379/tcp project_redis_1
bd405dde8ce7 postgres:9.6 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes (healthy) 127.0.0.1:15432->5432/tcp project_postgres_1
93e15c18d879 nginx:mainline "nginx -g 'daemon of…" 7 minutes ago Up 7 minutes (health: starting) 127.0.0.1:80->80/tcp, 127.0.0.1:443->443/tcp nginx
Example (partial, for brevity) output from docker inspect nginx:
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 11568,
"ExitCode": 0,
"Error": "",
"StartedAt": "2018-02-13T21:04:22.904241169Z",
"FinishedAt": "0001-01-01T00:00:00Z",
"Health": {
"Status": "unhealthy",
"FailingStreak": 0,
"Log": []
}
},
The portion of the docker-compose.yml defining the nginx container:
nginx:
image: nginx:mainline
# using container_name means there will only ever be one nginx container!
container_name: nginx
restart: always
networks:
- proxynet
volumes:
- /etc/nginx/conf.d
- /etc/nginx/vhost.d
- /usr/share/nginx/html
- tlsdata:/etc/nginx/certs:ro
- attachdata:/usr/share/nginx/html/uploads:ro
- staticdata:/usr/share/nginx/html/static:ro
- ./nginx/healthcheck.sh:/bin/healthcheck.sh
healthcheck:
test: ['CMD', '/bin/healthcheck.sh']
interval: 1m
timeout: 5s
retries: 3
ports:
# Make the http/https ports available on the Docker host IPv4 loopback interface
- '127.0.0.1:80:80'
- '127.0.0.1:443:443'
The healthcheck.sh I am loading in as a volume:
#!/bin/bash
service nginx status || exit 1
It looks like the problem is just an issue with systemd never returning from the status check when the container initially launches, and at the same time the configured healthcheck timeout does not trigger. Everything else works, and nginx is up and responding, but it would be nice for the healthcheck to function properly without needing to manually restart each time I start up.
Is there something missing in my configuration, or a better check I can run?
I think that there is no need for a custom script in this case.
Try just change your healthcheck test to
test: ["CMD", "service", "nginx", "status"]
That works fine for me.
Try to use " instead of ' as well, just in case :)
EDIT
If you really want to force an exit 1, in case of failure, you could use:
test: service nginx status || exit 1
for the official alpine nginx image you can also do:
healthcheck:
test: ["CMD-SHELL", "wget -O /dev/null http://localhost || exit 1"]
timeout: 10s
wget is part of the standard image. What this does is download your index.html/php/whatever to nowhere (/dev/null), and it should timeout and fail otherwise.
I attempted the same script and encountered the same issue. I changed the healthcheck.sh to instead run like this:
#!/bin/bash
if service nginx status; then
exit 0
else
exit 1
fi
Running this in the docker container resulted in successful health checks.
Over a year later, I have found a solution. First, an additional clarification on the environment, what I believe is happening, and speculation on a possible bug with the Docker Engine.
The Compose file I am using now is launching a lightly modified version of the 'official' Alpine NGINX image, which uses COPY to load in the healthcheck script and adds HEALTHCHECK explicitly in the image. This image is used for an nginx service, and is used in concert with an image running jwilder/docker-gen to use container metadata from Docker to generate NGINX configuration files. This container is running as a service named nginx-gen. When containers change, configuration is re-generated, and if there are any changes, a SIGHUP is sent to the nginx service.
What I discovered is the following:
If all services are launched together, the nginx service never runs healthchecks;
If the nginx service is restarted soon after launch, healthchecks complete normally;
If the nginx service is launched by itself, healthchecks complete normally;
If all services other than nginx-gen are launched together, healthchecks complete normally;
If all services are launched together, but nginx-gen is modified to sleep 60 before doing anything, healthchecks complete normally;
So, it appears that there is some obscure interaction with signal processing, Docker, and NGINX. If a SIGHUP is sent to an NGINX process in a container before the first healthcheck runs in that container, no healthchecks ever run.
The final iteration I came up with modifies the nginx-gen container to poll the health of the nginx container. It looks up the health status of a container with a defined label in a loop, with a short sleep. Once the nginx container reports healthy, nginx-gen proceeds to generate configuration files. I also changed the notification method to docker exec a script to explicitly test and reload configuration in the nginx container, rather than rely on SIGHUP.
End result: I can docker-compose up -d, and everything eventually reports healthy without further intervention. Success!

Cannot start Portainer which compose

I don't know why but I cannot start portainer.
I downloaded https://github.com/portainer/portainer-compose
I did a docker-compose up
Everything seems fine:
portainer-proxy
docker ps
CONTAINER ID IMAGE COMMAND CREATED
9c01c18dcc23 portainer/portainer:latest "/portainer --temp..." 5 minutes ago
2de6b22cadb0 portainer_proxy "nginx -g 'daemon ..." 10 minutes ago
1c0166b3f870 v2tec/watchtower "/watchtower --cle..." 10 minutes ago
893a507f62e3 portainer/templates "nginx -g 'daemon ..." 10 minutes ago
And I have this in the logs :
portainer-app | 2017/11/12 15:01:54 Warning: the --templates / -t flag is deprecated and will be removed in future versions.
portainer-app | 2017/11/12 15:01:54 Starting Portainer 1.15.1 on :9000
I should be able to access portainer on port 9000, but nothing happens here.
If I try to access localhost then I have 404 from nginx.
Do you have any idea?
In the doc of the partainer compose, the link to access to partainer is: http://localhost/portainer (replace localhost by the ip of your server if necessary). So it uses the 80 port.
If you need to use the 9000 port, replace this line ine the docker-compose.yml:
ports:
- "80:80"
by
ports:
- "9000:80"
And access to it: http://localhost:9000/portainer
HTH

Resources