Используя CoreOS, Docker и systemd для управления своими службами, я хочу правильно выполнять обнаружение служб. Поскольку CoreOS использует etcd (распределенный ключ-значение), есть очень удобный способ сделать это. На ExecStartPost systemd я могу без проблем вставить запущенную службу в etcd. Моему варианту использования нужно что-то вроде этого:
ExecStartPost=/usr/bin/etcdctl set /services/myServiceName '{ \"host\": \"%H\", \"port\": 5555 }'
который работает как шарм.
Но здесь и возникла моя идея. Docker может случайным образом назначить порт, если я просто запустил docker run -p 5555
что здорово, поскольку мне не нужно устанавливать его статически в файле * .service, и я мог бы запустить несколько экземпляров на одном хосте. Что, если бы я мог получить случайно назначенный порт и вставить вместо статического 5555
?
Оказывается, я могу использовать docker port
команда для получения порта и с некоторым форматированием мы можем получить только порт с
$(echo $(/usr/bin/docker port my-container-name 5555) | cut -d':' -f2)
который работает, если я установил его (используя bash) следующим образом:
/usr/bin/etcdctl set /services/myServiceName '{ \"host\": \"%H\", \"port\": '$(echo $(/usr/bin/docker port my-container-name 5555) | cut -d':' -f2)' }'
но с помощью systemd я просто не могу заставить его работать. Это код, который я использую:
ExecStartPost=/usr/bin/etcdctl set /services/myServiceName '{ \"host\": \"%H\", \"port\": '$(echo $(/usr/bin/docker port my-container-name 5555) | cut -d':' -f2)'}'
Как-то у меня что-то не так, но отладить сложно, так как при вводе в терминал работает.
Systemd ExecStart
опция не выполняет интерполяцию оболочки и поэтому не понимает таких вещей, как $(echo 'Hello')
. Но вы можете запустить оболочку, которая выполняет команду, чтобы получить подобное поведение. Попробуйте что-нибудь вроде:
ExecStartPost=/bin/sh -c "/usr/bin/etcdctl set /services/myServiceName '{ \"host\": \"%H\", \"port\": '$(echo $(/usr/bin/docker port my-container-name 5555) | cut -d':' -f2)'}'"
В итоге я использовал что-то вроде этого:
ExecStart=/bin/sh -c "port=$(docker port [CONTAINER-NAME] [CONTAINER-EXPOSED-PORT] | cut -d ':' -f 2); echo Connected to $port, publishing to etcd...; while netstat -lnt | grep :$port >/dev/null; do etcdctl set /services/[CONTAINER-NAME]/[SOME-IDENTIFIER] $port --ttl 60 >/dev/null; sleep 45; done"
Но ответ на мой вопрос действительно заключался в том, что мне нужно указать, в какой оболочке запускать команду.
Изменить: добавление логики шаблонов Go
[Service]
EnvironmentFile=/etc/environment
ExecStartPre=/bin/sh -c "until docker port {{.Name}} {{.Port}} >/dev/null 2>&1; do echo Waiting for {{.Name}}; sleep 2; done; port=$(docker port {{.Name}} {{.Port}} | cut -d ':' -f 2); echo Waiting for $port/tcp...; until netstat -lnt | grep :$port >/dev/null; do sleep 1; done"
ExecStart=/bin/sh -c "port=$(docker port {{.Name}} {{.Port}} | cut -d ':' -f 2); echo Connected to $COREOS_PRIVATE_IPV4:$port/tcp, publishing to etcd...; while netstat -lnt | grep :$port >/dev/null; do etcdctl set /services/{{.Service}}/{{.Name}} $COREOS_PRIVATE_IPV4:$port --ttl 60 >/dev/null; sleep 45; done"
ExecStop=/usr/bin/etcdctl rm --recursive /services/{{.Service}}/{{.Name}}