I have a docker container called node
that I want to scale to n containers.
A given node
container needs to know which container it is in the set of n scaled node
containers.
The total could be in an environment variable, but the current is more troubling.
$ docker-compose scale node=100
Starting projectdir_node_1 ... done
Creating and starting projectdir_node_2 ... done
Creating and starting projectdir_node_3 ... done
Creating and starting projectdir_node_4 ... done
Creating and starting projectdir_node_5 ... done
Creating and starting projectdir_node_6 ... done
Creating and starting projectdir_node_7 ... done
Creating and starting projectdir_node_8 ... done
Creating and starting projectdir_node_9 ... done
...
Creating and starting projectdir_node_99 ... done
Creating and starting projectdir_node_100 ... done
How can projectdir_node_100
know it is node 100
?
I saw that $HOSTNAME
is the container id (e.g. 2c73136347cd
), but found no ENV variable for the hostname with number I need.
For reference, my docker-compose.yml
:
version: '2'
services:
node:
build: ./node/
volumes:
- ./node/code/:/code:ro
entrypoint: ["/bin/bash"]
I found the unsolved How to reach additional containers by the hostname after docker-compose scale?, but I still don't know which container I am.
This can be solved by using the Docker api.
While the code in this answer is Python, and my linked example is Java, this is pretty independent of your language. Just use the Docker API with a library of your choice, or make requests directly to the Docker API socket.
I used the docker-py package to access it.
The api exposes a labels
dictionary for each container, and the keys com.docker.compose.container-number
, com.docker.compose.project
and com.docker.compose.service
did what was needed to build the hostname.
The code below is a simplified for code I am now using. You can find my advanced code with caching and fancy stuff that at Github at luckydonald/pbft/dockerus.ServiceInfos (python) as well as a similar java version at luckydonald/pbft-java/de.luckydonald.utils.dockerus.Dockerus (java) which tries to do the same thing in java, but is probably harder to read, and has no caching.
something
Lets tackle this in steps:
0. Make the API available to the container.
We need to make the socket file available to the volume, so in the volume section of your docker-compose.yml
file add /var/run/docker.sock:/var/run/docker.sock
:
version: '2'
services:
node:
build: .
volumes:
- /var/run/docker.sock:/var/run/docker.sock
This maps the socket file into the docker container. Because we don't expose any socket ports, we don't have to worry about firewalls for the outside world.
1. Connect to the API
Now we can connect to it. As I am using python, I use docker-py for that.
from docker import Client # pip install docker-py
cli = Client(base_url='unix://var/run/docker.sock')
2. Getting all containers in the same scale group, by filtering the containers by our own project name and service name.
To know which container we are, we compare the $HOSTNAME
environment variable with the container Id
.
import os
HOSTNAME = os.environ.get("HOSTNAME")
all_containers = cli.containers()
# filter out ourself by HOSTNAME
our_container = [c for c in all_containers if c['Id'][:12] == HOSTNAME[:12]][0]
The hostname should be 12 characters of the Id
, so we cut the id when comparing to be sure it will be equal.
our_container
now is the api representation of ourself. Yay.
We will search for containers which have the same project and service names. That way we know they are instances of ourself.
service_name = our_container.Labels['com.docker.compose.service']
project_name = our_container.Labels['com.docker.compose.project']
filters = [
'com.docker.compose.project={}'.format(project_name),
'com.docker.compose.service={}'.format(service_name)
]
# The python wrapper has a filter function to do that work for us.
containers = cli.containers(filters={'label': filters})
We only want each container where the com.docker.compose.project
and com.docker.compose.service
label is the same as our own container's.
hostname_list = list()
for container in containers:
project = container.Labels["com.docker.compose.project"]
service = container.Labels["com.docker.compose.service"]
number = container.Labels["com.docker.compose.container-number"]
hostname = "{project}_{service}_{i}".format(project=project, service=service, i=number)
hostname_list.append(hostname)
# end for
So, we got our hostname_list
.
I am also using that as a class, with caching the values for a minute: dockerus.ServiceInfos (backup at gist.github.com)