Skip to content

Instantly share code, notes, and snippets.

@luckydonald
Last active January 9, 2023 19:14
Show Gist options
  • Save luckydonald/8e13eae89925b79ba4cce3ece9fb1955 to your computer and use it in GitHub Desktop.
Save luckydonald/8e13eae89925b79ba4cce3ece9fb1955 to your computer and use it in GitHub Desktop.
How to get the hostnames and informations of other hosts in the same docker scale grouping. http://stackoverflow.com/a/39895650/3423324

The way I could do it was by using the docker api. 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 (backup at gist.github.com).

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.

Find ourself

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.

Next is to get the other containers.

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.

And finally build a list of hostnames
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 using that as a class, with caching the values for a minute: dockerus.ServiceInfos (backup at gist.github.com) Stackoverflow answer

# -*- coding: utf-8 -*-
import inspect
from DictObject import DictObject
from luckydonaldUtils.logger import logging
from luckydonaldUtils.functions import cached
from luckydonaldUtils.clazzes import Singleton
from docker import Client
from datetime import timedelta
__author__ = 'luckydonald'
logger = logging.getLogger(__name__)
class ServiceInfos(object, metaclass=Singleton):
__author__ = 'luckydonald'
LABEL_COMPOSE_CONTAINER_NUMBER = 'com.docker.compose.container-number'
LABEL_COMPOSE_PROJECT = 'com.docker.compose.project'
LABEL_COMPOSE_SERVICE = 'com.docker.compose.service'
CACHING_TIME = timedelta(seconds=5)
@property
@cached(max_age=CACHING_TIME)
def cli(self):
return Client(base_url='unix://var/run/docker.sock')
# end def
@property
@cached(max_age=max(timedelta(hours=1), CACHING_TIME))
def hostname_env(self):
import os
return os.environ.get("HOSTNAME")
# end def
@property
@cached(max_age=CACHING_TIME)
def me(self):
return [DictObject.objectify(c) for c in self.cli.containers() if c['Id'][:12] == self.hostname_env[:12]][0]
# end def
@property
@cached(max_age=CACHING_TIME)
def id(self):
return self.me.Id
# end def
@property
@cached(max_age=CACHING_TIME)
def service(self):
return self.me.Labels[self.LABEL_COMPOSE_SERVICE]
# end def
@property
@cached(max_age=CACHING_TIME)
def name(self):
return self.service
# end def
@property
@cached(max_age=CACHING_TIME)
def project(self):
return self.me.Labels[self.LABEL_COMPOSE_PROJECT]
# end def
@property
@cached(max_age=CACHING_TIME)
def number(self):
return int(self.me.Labels[self.LABEL_COMPOSE_CONTAINER_NUMBER])
# end def
@cached(max_age=CACHING_TIME)
def containers(self, exclude_self=False):
"""
Gets metadata for all containers in this scale grouping.
:return:
"""
filters = [
'{0}={1}'.format(self.LABEL_COMPOSE_PROJECT, self.project),
'{0}={1}'.format(self.LABEL_COMPOSE_SERVICE, self.service),
# '{0}={1}'.format(LABEL_ONE_OFF, "True" if one_off else "False")
]
return DictObject.objectify([
c for c in self.cli.containers(filters={'label': filters})
if not (exclude_self and c['Id'][:12] == self.hostname_env[:12])
])
# end def
@property
@cached(max_age=CACHING_TIME)
def hostname(self):
c = self.me
return "{project}_{service}_{i}".format(
project=c.Labels[self.LABEL_COMPOSE_PROJECT],
service=c.Labels[self.LABEL_COMPOSE_SERVICE],
i=c.Labels[self.LABEL_COMPOSE_CONTAINER_NUMBER]
)
# end def
@cached(max_age=CACHING_TIME)
def other_hostnames(self, exclude_self=False):
return [
"{project}_{service}_{i}".format(
project=c.Labels[self.LABEL_COMPOSE_PROJECT],
service=c.Labels[self.LABEL_COMPOSE_SERVICE],
i=c.Labels[self.LABEL_COMPOSE_CONTAINER_NUMBER]
) for c in self.containers(exclude_self=exclude_self)
]
# end def
@cached(max_age=CACHING_TIME)
def other_numbers(self, exclude_self=False):
"""
:param exclude_self:
:return:
"""
return [
c.Labels[self.LABEL_COMPOSE_CONTAINER_NUMBER]
for c in self.containers(exclude_self=exclude_self)
]
# end def
# end class
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment