docker-compose evaluate expression in command array - docker

I have below docker-compose.yml file. In the command section I would like to evaluate the curl expression before the command is passed to docker engine i.e. my curl should be evaluated first and then my container should run with -ip 10.0.0.2 option.
version: '2'
services:
registrator:
image: gliderlabs/registrator:v7
container_name: registrator
volumes:
- /var/run/docker.sock:/tmp/docker.sock
command: ['-ip', '$$(curl -X GET -s http://169.254.169.254/latest/meta-data/local-ipv4)']
This however is not being evaluated and my option is passed as -ip $(curl -X GET -s http://169.254.169.254/latest/meta-data/local-ipv4)
The respective docker run command however correctly evaluates the expression and my container is correctly starting with -ip 10.0.0.2 option:
docker run -v /var/run/docker.sock:/tmp/docker.sock gliderlabs/registrator:v7 -ip $(curl -X GET -s http://169.254.169.254/latest/meta-data/local-ipv4)

The docker command on the command line will work as the command will be executed by the shell and not the docker image, so it will be resolved.
the docker-compose command will override the default command (CMD) (see https://docs.docker.com/compose/compose-file/#command) so it will not be executed before the container is started but as the primary command in the container...
you could do something like:
version: '2'
services:
registrator:
image: gliderlabs/registrator:v7
container_name: registrator
volumes:
- /var/run/docker.sock:/tmp/docker.sock
command: ['-ip', '${IP}']
and run it with:
IP="$(curl -X GET -s http://169.254.169.254/latest/meta-data/local-ipv4)" docker-compose up
this will run it in the shell again and assign it to a variable called IP witch will be available during the docker-compose up command. You could put that command in a shell script to make it easier.

After searching the internet for hours and answers posted here, I finally settled for below solution. For explanation of why this works, please refer to #Ivonet answer.
I modified the Dockerfile to run a script when the container starts.
FROM gliderlabs/registrator:v7
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh && \
apk update && \
apk add curl
ENTRYPOINT ["/entrypoint.sh"]
The script entrypoint.sh is also very simple. It first checks if it can call the endpoint. A success response would trigger my container to start with correct IP address, while an un-successful response (for local testing) would not set any value.
#!/bin/sh
LOCAL_IP=$(curl -s --connect-timeout 3 169.254.169.254/latest/meta-data/local-ipv4)
if [ $? -eq 0 ]; then
/bin/registrator -ip $LOCAL_IP $#
else
/bin/registrator $#
fi

Related

command not passed to docker entrypoint

I've been trying to create a docker image that executes kubectl with custom OCI variables. It creates the OCI configuration file automatically and then generates the kube/.config file.
I thought of using this because of we have more than one cluster and jumping from them each time is time consuming and it's easy to make mistakes or confuse them.
Basically I created the Dockerfile with the following entrypoint:
FROM private.repo/oci-image:latest
# Install Kubectl client
RUN apt update && apt install -y curl gettext-base
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kubectl
RUN chmod +x ./kubectl
RUN mv ./kubectl /usr/local/bin/kubectl
...
...
...
ENTRYPOINT ["/bin/bash", "-c", "./script.sh"]
This is the script.sh file
#!/bin/sh
set -e
cat $HOME/.oci/config-template | envsubst > $HOME/.oci/config
yes | oci ce cluster create-kubeconfig --profile DEFAULT --cluster-id ${K8S_CLUSTER_ID} --file $HOME/.kube/config --region ${REGION} --token-version 2.0.0 --kube-endpoint PUBLIC_ENDPOINT
yes | oci ce cluster create-kubeconfig --profile DEFAULT --cluster-id $K8S_CLUSTER_ID --file $HOME/.kube/config --region $REGION --token-version 2.0.0 --kube-endpoint PUBLIC_ENDPOINT
exec "$#"
"
And I have been trying to run the container and pass to it kubectl commands:
docker run -e ... oci-agent:v21 kubectl get nodes
But I am not getting no response. I tried replaceing the exec "$#" with exec "kubectl $#" but I obtain the kubectl help instructions, so it's only executing kubectl and is not reading my command.
How do I do this properly please ?
Remove the bash -c wrapper from the ENTRYPOINT
ENTRYPOINT ["./script.sh"]
You're already aware that the CMD is passed as arguments to the ENTRYPOINT. With the bash -c wrapper, these arguments are passed as additional arguments to the wrapper shell, not to your script. The wrapper shell always runs ./script.sh with no arguments, because that's the command it was asked to run; the CMD arguments could be accessed as positional arguments $0, $1, ... in that command but this is pretty unusual.
This is the same reason ENTRYPOINT must be a JSON array for it to actually receive the CMD as arguments: Docker turns a string-form ENTRYPOINT (or CMD or RUN) into ["/bin/sh", "-c", "the string"] and arguments aren't actually passed on to the wrapper script. (You should almost never need to use sh -c inside a Dockerfile.)

docker-compose stop / start my_image

Is it normal to lose all data, installed applications and created folders inside a container when executing docker-compose stop my_image and docker-compose start my_image?
I'm creating container with docker-compose up --scale my_image=4
update no. 1
my containers have sshd server running in them. When I connect to a container execute touch test.txt I see that the file was created.
However, after executing docker-compose stop my_image and docker-compose start my_image a container is empty and ls -l shows absence of file test.txt
update no. 2
my Dockerfile
FROM oraclelinux:8.5
RUN (yum update -y; \
yum install -y openssh-server openssh-clients initscripts wget passwd tar crontabs unzip; \
yum clean all)
RUN (ssh-keygen -A; \
sed -i 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config; \
sed -i 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config; \
sed -i 's/#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config; \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config)
RUN (mkdir -p /root/.ssh/; \
echo "StrictHostKeyChecking=no" > /root/.ssh/config; \
echo "UserKnownHostsFile=/dev/null" >> /root/.ssh/config)
RUN echo "root:oraclelinux" | chpasswd
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 22
my docker-compose
version: '3.9'
services:
my_image:
build:
context: .
dockerfile: Dockerfile
ports:
- 30000-30007:22
when I connect to a container
Execute touch test.txt
Execute docker-compose stop my_image
Execute docker-compose start my_image
Execute ls -l
I see no file test.txt (in fact I see that the folder is empty)
update no. 3
entrypoint.sh
#!/bin/sh
# Start the ssh server
/usr/sbin/sshd -D
# Execute the CMD
exec "$#"
Other details
When containers are all up and running, I choose a container running
on a specific port, say port 30001, then using putty I connect to that specific container,
execute touch test.txt
execute ls -l
I do see that the file was created
I execute docker-compose stop my_image
I execute docker-compose start my_image
I connect via putty to port 30001
I execute ls -l
I see no file (folder is empty)
I try other containers to see if file exists inside one of them, but
I see no file present.
So, after a brutal brute force debugging I realized that I lose data
only when I fail to disconnect from ssh before stopping / restarting
container. When I do disconnect data does not disappear after stopping / restarting

Netcat (nc / ncat) cannot be interrupted in Docker

I have a service that does some tasks and then opens a port so that other services know it's finished. I use nc to do that: nc -l -k -p 1337. I use docker-compose to manage services.
When shutting down the services, the service running nc always takes several seconds to close while it should be instant. I think the process doesn't interrupt and docker has to kill it. If I run nc on the same service via docker-compose run I cannot interrupt the process via Ctrl+C.
When running nc locally it can instantly be terminated via Ctrl+C.
How can I create a service running nc -l -k -p 1337 which can be interrupted?
Dockerfile
FROM ruby:2.6.3-alpine
RUN apk add --no-cache netcat-openbsd
COPY entrypoint.sh ./
RUN chmod +x entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
entrypoint.sh
#!/bin/sh
# ...
nc -l -k -p 1337
docker-compose.yml
services:
nc:
build:
context: .
dockerfile: Dockerfile
docker-compose up --build
OR:
entrypoint.sh
#!/bin/sh
# ...
exec "$#"
docker-compose.yml
services:
nc:
build:
context: .
dockerfile: Dockerfile
command: nc -l -k -p 1337
docker-compose up --build
docker-compose run --rm nc nc -l -k -p 1337
Assuming nc actually responds to signals under normal circumstances, you need to:
Use exec
Use the list version of command.
So in the first case, add an exec to the shell script.
And in the second case, probably need command: ["nc", "-l", "-k", "-p", "1337"] in the compose file.
See https://hynek.me/articles/docker-signals/ for full checklist.

Bitbucket pipelines/Docker : Connection refused

I am trying to configure a bitbucket CI pipeline to run tests.Stripping out the details I have a make file which looks as follows to run some form of integration tests.
test-e2e:
docker-compose -f ${DOCKER_COMPOSE_FILE} up -d ${APP_NAME}
godog
docker-compose -f ${DOCKER_COMPOSE_FILE} down
Docker compose is a single webserver with ports exposed.
Pipeline looks as follows:
- step: &integration-testing
name: Run integration tests script: # do this to make go module work with private repo
- apk add libc-dev py-pip python-dev libffi-dev openssl-dev gcc libc-dev make bash
- pip install docker-compose - git config --global url."git#bitbucket.org:".insteadOf "https://bitbucket.org/"
- go get github.com/onsi/ginkgo/ginkgo
- go get github.com/onsi/gomega/...
- go get github.com/DATA-DOG/godog/cmd/godog
- make build-only && make test-e2e
I am facing two separate issues for both i have not been able to find a solution.
Keep getting connection refused when the tests are run.
To elaborate above, the docker compose brings up a server with proper host:port mapping ("127.0.0.1:10077:10077"). The command godog is intended to run the tests by querying the server. This however always ends in connection refused.This link has a possible solution , so i am exploring that.
The pipeline almost always runs commands before the container is up. I've tried fixing this by changing the invoke to.
test-e2e:
docker-compose -f ${DOCKER_COMPOSE_FILE} up -d ${APP_NAME} && sleep 10 && docker exec -i oracle-go godog && docker-compose -f ${DOCKER_COMPOSE_FILE} down
However the container is always brought up after the sleep (almost instantaneously).
Example:
Creating oracle-go ...
Sleep 10
docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
docker exec -i oracle-go godog
Creating oracle-go ... done
Error response from daemon: Container 7bab5322203756b972e7f0a3c6e5827413279914a68c705221b8af7daadc1149 is not running
Please let me know if there is a way around it.
If I understood your question correctly, you want to wait for the server to start before running tests.
Instead of manually sleeping, you should use wait-for-it.sh (or an alternative). See the relevant Docker docs for more information.
For example:
test-e2e:
bash wait-for-it.sh <HOST>:<PORT> -- docker-compose -f ${DOCKER_COMPOSE_FILE} up -d ${APP_NAME} && docker exec -i oracle-go godog && docker-compose -f ${DOCKER_COMPOSE_FILE} down
Change <HOST> and <PORT> to your service's host name and port respectively. Alternatively, you could use wait-for-it.sh in your Docker Compose command or the like.

GNU/make and docker, terminate the running process

I'm using GNU/make (linux) to wrap some docker command, I don't use docker everyday so I'll forget the usage soon, one of those wrapper is make serve, it run docker run with php -S (built-in server) as command, it start and listen for connections.
To stop it I should use ^C , I believe (but I'm not sure) the problem to be make intercepting the signal, the make's target fail but the php process (and the docker instance) remain running, I've to manually stop it.
I tried passing --sig-proxy=true and false but with no effect, but I'm running the instance with a pseudo terminal (-t) so this was expected.
I wrote a bash wrapper to echo some message when some signal is trapped (EXIT TERM SIGTERM SIGQUIT KILL SIGKILL) and exec php -S , but I don't see any message.
I'm not sure what happen when ^C is pressed on a running make, if the signal (and what) is propagated only to the forked sh -c, and why that wouldn't work with docker run.
I'd like to know if someone else had this problem and solved or if someone can think of some alternative solution.
edit
Makefile:
all:
.PHONY: docker-build serve
docker-build:
docker build -f Dockerfile -t sigtest:v1 .
serve:
docker run -t --rm sigtest:v1 gosu ubuntu:ubuntu php7.0 -S 0.0.0.0:8081 -t /home/ubuntu
Dockerfile:
FROM ubuntu:16.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends php7.0-cli gosu
RUN useradd -ms /bin/bash ubuntu
test with make docker-build and then make serve
2nd edit
I managed to wrap docker run with a shell script (I'm not sure about trap syntax in posix shells, eventually I'll take a look)
make seems to be sending INT the first time and and then EXIT
#!/bin/sh
pid=$$
trap 'trap - INT EXIT; echo " signal received, wait..."; docker stop -t 0 sig${pid}; exit' INT EXIT
docker run --name=sig${pid} "$#"
and then in the Makefile
serve:
-./docker-run.sh -t --rm sigtest:v1 gosu ubuntu:ubuntu php7.0 -S 0.0.0.0:8081 -t /home/ubuntu
I was hoping to not have to name the instances but that's the simplest way I can think of.
The problem is not in using make but in the Signal handling. Here is an article describing it.
As I said before docker-compose manages the whole lifecycle of your services.
Here is a basic example of your app using docker-compose. By make up and make down(including ^C ), so you can start and stop your service.
Makefile:
all:
.PHONY: docker-build serve
build:
docker-compose build
up:
docker-compose up
down:
docker-compose down
docker-compose.yml:
version: '3.3'
services:
my_service:
build: .
ports:
- "8080:8080"
Dockerfile:
FROM ubuntu:16.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends php7.0-cli gosu
RUN useradd -ms /bin/bash ubuntu
RUN mkdir -p /home/ubuntu
RUN php7.0 --version
ENTRYPOINT php7.0 -S 0.0.0.0:8080

Resources