5

Using CoreOS, Docker and systemd to manage my services I want to properly perform service discovery. Since CoreOS utilizes etcd (distributed key-value) there is a very convenient way to do this. On systemd's ExecStartPost I can just insert the started service into etcd without problems. My usecase needs something like this:

ExecStartPost=/usr/bin/etcdctl set /services/myServiceName '{ \"host\": \"%H\", \"port\": 5555 }' 

which works like a charm.

But this is where my idea popped up. Docker has the power to randomly assign a port if I just run docker run -p 5555 which is awesome since I don't have to set it statically in the *.service file and I could possibly run multiple instances on the same host. What if I could get the randomly assigned port and insert instead of the static 5555?

Turns out I can use the docker port command to get the port and with some formatting we can get just the port with

$(echo $(/usr/bin/docker port my-container-name 5555) | cut -d':' -f2) 

which works if I set it (using bash) like this:

/usr/bin/etcdctl set /services/myServiceName '{ \"host\": \"%H\", \"port\": '$(echo $(/usr/bin/docker port my-container-name 5555) | cut -d':' -f2)' }' 

but using systemd I just can't get it to work. This is the code I'm using:

ExecStartPost=/usr/bin/etcdctl set /services/myServiceName '{ \"host\": \"%H\", \"port\": '$(echo $(/usr/bin/docker port my-container-name 5555) | cut -d':' -f2)'}' 

Somehow I got something wrong but it's hard to debug since it works when typed in the terminal.

2
  • So did you have any luck with this? I am having a very similar issue. I even have Go format which works in the command line to get hold of the port dynamically but can't get it to work in the Unit file. Commented Jul 22, 2014 at 3:31
  • See my answer below Commented Aug 17, 2014 at 21:53

2 Answers 2

2

The systemd ExecStart option doesn't do any shell interpolation and thus doesn't understand stuff like $(echo 'Hello'). But, you can start a shell that executes the command to get behavior like this. Try something like:

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)'}'" 
0

I ended up using something like this:

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" 

But the answer for my question was indeed that I needed to state which shell to run the command.

Edit: Adding the Go templating logic

[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}} 
1
  • That will do it but I specifically want to fix it with Go template formatting rather than string of commands. Commented Aug 18, 2014 at 2:00

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.