PHP Composer & other Docker services start-up sequence problem - docker

sp-composer is a one-time service that builds the PHP Composer vendor volume into a volume composer; this takes a few minutes as it downloads the packages. As I (partly) understand it, dependent services in the same composer/stack file don't wait for the RUN task to complete, so they start up with an empty composer volume and they never see it subsequently as populated (for some reason).
The current work-around is an 8 second sleep in the production start-up sequence, with the Swarm stack file split into one just for Composer, and the other for the rest. Occasionally we need to increase the delay. This is of course terrible and should be fixed.
There are many posts about using a healthcheck to delaying a service based on a dependency such as a network service (e.g. a database) but sp-composer is a service that just runs and then exits.
We just need to delay the rest of the sequence starting until sp-composer has exited. It occured to me that docker wait (should?) solve half the problem, but we still need the stack file split into two parts, and two times docker stack deploy.
What is the current best practice to fix this situation?
For reference, this is the Dockerfile in question:
FROM php:7.1
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y libxml2-dev zlib1g-dev \
&& docker-php-ext-install soap zip
COPY . /composer
WORKDIR /composer
RUN php ./composer.phar install --no-dev --no-interaction --optimize-autoloader

I think the proper approach is editing the Dockerfiles of the containers that depend on the PHP Composer container so that they wait a little before running.
The easy solution would be to add sleep to the RUN command (e.g. RUN sleep 5s; entrypoint.sh).
A cleaner solution is to periodically verify inside the entrypoint script that the volume is populated correctly. Something like:
$VOLUME_POPULATED = false
while [[ $VOLUME_POPULATED != true ]]
do
# check if volume is populated
sleep 5s
done
# execute rest of command
Using either method, you won't need two Swarm files since the containers are built to wait for the prerequisites to be completed.
Also, if you are open to trying other orchestrations tools, I would suggest giving Kubernetes a try as it's de facto standard for container orchestration. In particular, init containers are built for the problem you are facing.

Related

Starting ssh service through ENTRYPOINT not working

I'm having a lot of difficulties running an linux container with SSH service on it. To skip the details, SSH is not optional, I must have it.
I installed the openssh-server with:
RUN
echo "**** Setting up openssh-server ****" &&
apt-get install -y openssh-server &&
sed -i "s|# PasswordAuthentication yes|PasswordAuthentication yes|g" /etc/ssh/sshd_config &&
mkdir /var/run/sshd
And am trying to open the service with:
ENTRYPOINT service ssh restart && bash
However it does not work. I tried in multiple way to get it started, by using CMD, by making a script that would start the service, and it's not working. What's worse is that this seems to have worked for others (pull access denied repository does not exist or may require docker login)
The image that I am using as base is ubuntu:18.04. However I switched to jre/systemd-ubuntu:18.04 as I thought the lack of systemd could prevent the service from running however that did not work either. Any suggestions what the possibly issue could be?
I managed to get my service to run, as a first advice I recommend making sure that the service runs by itself before putting it together with other services. In my case it seems the ssh service was not being started because a previous non-returning service was started which would keep the shell occupied and would not let it continue it's ENTRYPOINT execution to start the SSH.
One other thing that I had done previously and could have been part of the solution is that I manually created the folder /var/run/sshd. It seems some ssh service versions need that to exist otherwise they won't run. At this point I can't verify though if that was the only issue, as I've tried multiple solution at once.

How to install JIRA-SERVER on OPEN-SHIFT (docker-image)

Im trying to installing jira-server via docker-image on openshift.
I pulled the image from docker-desktop for windows. Added simple dockerfile includes USER ROOT etc.
When trying to deploy the pod. I get error and pod enters to loop.
The errror is: Permission Error in diffrent locations.
Tried many times to relocate the jira-home directory but without success.
(Trying to install on closed network)
Thanks for helping!
Short Answer
The official Atlassian Images are incompatible with Kubernetes Derivatives /e.g. Openshift as they violate some key concepts.
In Openshift for example, containers are running with arbitrary user ids, which means a nameless user is executing the processes in the container.
This is a safety mechanism, prevents containers running as root and limits the risk of escaping the container gaining privileges on the cluster host.
Solution
You do need to rebuild the image from scratch.
Furthermore, the behaviour of the startup python script trying to modify file system permissions need to be removed.
Clone official Repo
https://bitbucket.org/atlassian-docker/docker-atlassian-jira/src/master/
Modify the Dockerfile and add to the UserGroup creation Step:
RUN groupadd --gid ${RUN_GID} ${RUN_GROUP} \
...
&& chown -R ${RUN_USER}:${RUN_GROUP} ${JIRA_HOME} \
# make the image compatible to run as an arbitrary uid
&& chgrp -R 0 /etc/container_id \
&& chmod -R g=u /etc/container_id \
&& chmod -R 460 /etc/container_id \
&& chgrp -R 0 ${JIRA_INSTALL_DIR} \
&& chmod -R g=u ${JIRA_INSTALL_DIR} \
&& chgrp -R 0 ${JIRA_HOME} \
&& chmod -R g=u ${JIRA_HOME}
Modify the gen_cfg function from entrypoint_helpers.py and remove the else clause at the end.
The necessary permissions have already been set in step2.
def gen_cfg(tmpl, target, user='root', group='root', mode=0o644, overwrite=True):
if not overwrite and os.path.exists(target):
logging.info(f"{target} exists; skipping.")
return
logging.info(f"Generating {target} from template {tmpl}")
cfg = jenv.get_template(tmpl).render(env)
try:
with open(target, 'w') as fd:
fd.write(cfg)
except (OSError, PermissionError):
logging.warning(f"Container not started as root. Bootstrapping skipped for '{target}'")
# else:
# set_perms(target, user, group, mode)
Rebuild the image using the --build-arg JIRA_VERSION= --build-arg ARTEFACT_NAME
Run and Enjoy
Detail inspection
Firing up the atlassian images, user root is the first to enter, doing modifications (chown...) and later dropping down to user "jira".
All these operations are not possible in openshift.
In most cases, building a new Dockerfile starting from the official image and modifing permissions of the needed files and folders before deploying to a cluster is the solution,
but to make things worse, atlassian choose to "mount" the necessary directories as VOLUME.
They have even referenced the issue in their comments.
VOLUME ["${JIRA_HOME}"] # Must be declared after setting perms
After the volume mount, the permissions can only be modified persistently at runtime.
This forces them to rebuild and set permissions after container startup, using a python function in the entrypoint_helpers.py.
This is also the place, where the container fails with several "permission denied"s.
Would be glad to issue a pull request on this, but unfortunatelly they are hosted on bitbucket.

Docker container does not run crontab

I have a dockerfile image based on ubuntu. Iam trying to make a bash script run each day but the cron never runs. When the container is running, i check if cron is running and it is. the bash script works perfectly and the crontab command is well copied inside the container. i can't seem to find where the problem is coming from.
Here is the Dockerfile:
FROM snipe/snipe-it:latest
ENV TZ=America/Toronto
RUN apt-get update \
&& apt-get install awscli -y \
&& apt-get clean \
&& apt-get install cron -y \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir /var/www/html/backups_scripts /var/www/html/config/scripts
COPY config/crontab.txt /var/www/html/backups_scripts
RUN /usr/bin/crontab /var/www/html/backups_scripts/crontab.txt
COPY config/scripts/backups.sh /var/www/html/config/scripts
CMD ["cron","-f"]
The last command CMD doesn't work. And as soon as i remove the cmd command i get this message when i check the cron task inside the container:
root#fcfb6052274a:/var/www/html# /etc/init.d/cron status
* cron is not running
Even if i start the cron process before the crontab, the crontab is still not launched
This dockerfile is called by a docker swarm file (compose file type). Maybe the cron must be activated with the compose file.
How can i tackle this problem ??? Thank you
You need to approach this differently, as you have to remember that container images and containers are not virtual machines. They're a single process that starts and is maintained through its lifecycle. As such, background processes (like cron) don't exist in a container.
What I've seen most people do is have the container just execute whatever you're looking for it to do on a job like do_the_thing.sh and then using the docker run command on on the host machine to call it via cron.
So for sake of argument, let's say you had an image called myrepo/task with a default entrypoint of do_the_thing.sh
On the host, you could add an entry to crontab:
# m h dom mon dow user command
0 */2 * * * root docker run --rm myrepo/task
Then it's down to a question of design. If the task needs files, you can pass them down via volume. If it needs to put something somewhere when it's done, maybe look at blob storage.
I think this question is a duplicate, with a detailed response with lots of upvotes here. I followed the top-most dockerfile example without issues.
Your CMD running cron in the foreground isn't the problem. I ran a quick version of your docker file and exec'ing into the container I could confirm cron was running. Recommend checking how your cron entries in the crontab file are re-directing their output.
Expanding on one of the other answers here a container is actually a lot like a virtual machine, and often they do run many processes concurrently. If you happen to have any other containers running you might be able to see this most easily by running docker stats and looking at the PID column.
Also, easy to examine interactively yourself like this:
$ # Create a simple ubuntu running container named my-ubuntu
$ docker run -it -h my-ubuntu ubuntu
root#my-ubuntu$ ps aw # Shows bash and ps processes running.
root#my-ubuntu$ # Launch a ten minute sleep in the background.
root#my-ubuntu$ sleep 600 &
root#my-ubuntu$ ps aw # Now shows sleep also running.

Best Practice to run commands after starting up a prebuilt docker image

What is best practice to run code after downloading AND spinning up a prebuilt Docker Image? For example, I use Bitnamis RabbitMQ image that spins up a container running RabbitMQ (obviously): https://hub.docker.com/r/bitnami/rabbitmq
After the container has spun up and RabbitMQ is running I want to download and enable an additional RabbitMQ-Plugin. There are two problems I encounter as a Docker-Newbie:
RabbitMQ takes some time to initialize, maybe 20-30 seconds
The Plugin needs to be enabled after said initialization has finished
Right now, I spin up the container, wait for 30 seconds and manually exec a command on the terminal like
docker exec -it wget -O /plugins/folder/newplugin.ez https://pluginlocation.com/newplugin.ez && rabbitmq-plugins enable newplugin`
This works, but is there a recommended way to do all this in one go? I tried using my own Dockerfile on top of the process:
FROM bitnami/rabbitmq:latest
RUN wget -O /plugins/folder/newplugin.ez https://pluginlocation.com/newplugin.ez
RUN rabbitmq-plugins enable newplugin
But at this point, RabbitMQ is not initialized yet so enabling the plugin fails.
Best
-act
You can enable a plugin without having a running broker with the --offline option.
So your Dockerfile could be:
FROM bitnami/rabbitmq:latest
RUN curl -o /opt/bitnami/rabbitmq/plugins/newplugin.ez \
https://pluginlocation.com/newplugin.ez \
&& rabbitmq-plugins enable --offline newplugin
BTW, it's a good practice to do related things (e.g. downloading and enabling a plugin) in one layer.

Docker multiple entrypoints

Say I have the following Dockerfile:
FROM ubuntu
RUN apt-get update
RUN apt-get install -y apache2
RUN apt-get install -y mongod #pretend this exists
EXPOSE 80
ENTRYPOINT ["/usr/sbin/apache2"]
The ENTRYPOINT command makes it so that apache2 starts when the container starts. I want to also be able to start mongod when the the container starts with the command service mongod start. According to the documentation however, there must be only one ENTRYPOINT in a Dockerfile. What would be the correct way to do this then?
As Jared Markell said, if you wan to launch several processes in a docker container, you have to use supervisor. You will have to configure supervisor to tell him to launch your different processes.
I wrote about this in this blog post, but you have a really nice article here detailing how and why using supervisor in Docker.
Basically, you will want to do something like:
FROM ubuntu
RUN apt-get update
RUN apt-get install -y apache2
RUN apt-get install -y mongod #pretend this exists
RUN apt-get install -y supervisor # Installing supervisord
ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf
EXPOSE 80
ENTRYPOINT ["/usr/bin/supervisord"]
And add a configuration a file supervisord.conf
[supervisord]
nodaemon=true
[program:mongodb]
command=/etc/mongod/mongo #To adapt, I don't know how to launch your mongodb process
[program:apache2]
command=/usr/sbin/apache2 -DFOREGROUND
EDIT: As this answer has received quite lot of upvotes, I want to precise as a warning that using Supervisor is not considered as a best practice to run several jobs. Instead, you may be interested in creating several containers for your different processes and managing them through docker compose.
In a nutshell, Docker Compose allows you to define in one file all the containers needed for your app and launch them in one single command.
My solution is to throw individual scripts into /opt/run/ and execute them with:
#!/bin/bash
LOG=/var/log/all
touch $LOG
for a in /opt/run/*
do
$a >> $LOG &
done
tail -f $LOG
And my entry point is just the location of this script, say it's called /opt/bin/run_all:
ADD 00_sshd /opt/run/
ADD 01_nginx /opt/run/
ADD run_all /opt/bin/
ENTRYPOINT ["/opt/bin/run_all"]
The simple answer is that you should not because it breaks the single responsibility principle: one container, one service. Imagine that you want to spawn additional cloud images of MongoDB because of a sudden workload - why increasing Apache2 instances as well and at a 1:1 ratio?
Instead, you should link the boxes and make them speak through TCP. See https://docs.docker.com/userguide/dockerlinks/ for more info.
Typically, you would not do this. It is an anti-pattern because:
You typically have different update cycles for the two processes
You may want to change base filesystems for each of these processes
You want logging and error handling for each of these processes that are independent of each other
Outside of a shared network or volume, the two processes likely have no other hard dependencies
Therefore the best option is to create two separate images, and start the two containers with a compose file that handles the shared private network.
If you cannot follow that best practice, then you end up in a scenario like the following. The parent image contains a line:
ENTRYPOINT ["/entrypoint-parent.sh"]
and you want to add the following to your child image:
ENTRYPOINT ["/entrypoint-child.sh"]
Then the value of ENTRYPOINT in the resulting image is replaced with /entrypoint-child.sh, in other words, there is only a single value for ENTRYPOINT. Docker will only call a single process to start your container, though that process can spawn child processes. There are a couple techniques to extend entrypoints.
Option A: Call your entrypoint, and then run the parent entrypoint at the end, e.g. /entrypoint-child.sh could look like:
#!/bin/sh
echo Running child entrypoint initialization steps here
/usr/bin/mongodb ... &
exec /entrypoint-parent.sh "$#"
The exec part is important, it replaces the current shell by the /entrypoint-parent.sh shell or process, which removes issues with signal handling. The result is you run the first bit of initialization in the child entrypoint, and then delegate to the original parent entrypoint. This does require that you keep track of the name of the parent entrypoint, would could change between versions of your base image. This also means you lose error handling and graceful termination on mongodb since it is run in the background. This could result in a false healthy container and data lose, neither of which I would recommend for a production environment.
Option B: Run the parent entrypoint in the background. This is less than ideal since you will no longer have error handling on the parent process unless you take some extra steps. At the simplest, this looks like the following in your /entrypoint-child.sh:
#!/bin/sh
# other initialization steps
/entrypoint-parent.sh "$#" &
# potentially wait for parent to be running by polling
# run something new in the foreground, that may depend on parent processes
exec /usr/bin/mongodb ...
Note, the "$#" notation I keep using is passing through the value of CMD as arguments to the parent entrypoint.
Option C: Switch to a tool like supervisord. I'm not a huge fan of this since it still implies running multiple daemons inside your container, and you are usually best to split that into multiple containers. You need to decide what the proper response is when a single child process keeps failing.
Option D: Similar to Options A and B, I often create a directory of entrypoint scripts that can be extended at different levels of the image build. The entrypoint itself is unchanged, I just add new files into a directory that gets called sequentially based on the filename. In my scenarios, these scripts are all run in the foreground, and I exec the CMD at the end. You can see an example of this in my base image repo, in particular the entrypoint.d directory and bin/entrypointd.sh script which includes the section:
# ...
for ep in /etc/entrypoint.d/*; do
ext="${ep##*.}"
if [ "${ext}" = "env" -a -f "${ep}" ]; then
# source files ending in ".env"
echo "Sourcing: ${ep}"
set -a && . "${ep}" && set +a
elif [ "${ext}" = "sh" -a -x "${ep}" ]; then
# run scripts ending in ".sh"
echo "Running: ${ep}"
"${ep}"
fi
done
# ...
# run command with exec to pass control
echo "Running CMD: $#"
exec "$#"
However, the above is more for extending the initialization steps, and not for running multiple daemons inside the container. Given the bad options and issues they each have, I hope it's clear why running two containers would be preferred in your scenario.
I was not able to get the usage of && to work. I was able to solve this as described here: https://stackoverflow.com/a/19872810/2971199
So in your case you could do:
RUN echo "/usr/sbin/apache2" >> /etc/bash.bashrc
RUN echo "/path/to/mongodb" >> /etc/bash.bashrc
ENTRYPOINT ["/bin/bash"]
You may need/want to edit your start commands.
Be careful if you run your Dockerfile more than once, you probably don't want multiple copies of commands appended to your bash.bashrc file. You could use grep and an if statement to make your RUN command idempotent.
You can't specify multiple entry points in a Dockerfile. To run multiple servers in the same docker container you must use a command that will be able to launch your servers. Supervisord has already been cited but I could also recommend multirun, a project of mine which is a lighter alternative.
There is an answer in docker docs:
https://docs.docker.com/config/containers/multi-service_container/
But in short
If you need to run more than one service within a container, you can accomplish this in a few different ways.
The first one is to run script which mange your process.
The second one is to use process manager like supervisord
I can think of several ways:
you can write a script to put on the container (ADD) that does all the startup commands, then put that in the ENTRYPOINT
I think you can put any shell commands on the ENTRYPOINT, so you can do service mongod start && /usr/sbin/apache2
If you are trying to run multiple concurrent npm scripts such as a watch script and a build script for example, check out:
How can I run multiple npm scripts in parallel?

Resources