I've been playing with dockers for a while now, and I had a problem extracting log files directory of a service that runs within container.
My Dockerfile looks like this:
ENV HOME=/software/service
ENV LOGS=$HOME/logs
COPY Service.jar $HOME/Service.jar
WORKDIR HOME
CMD java -jar Service.jar
I created a stub service for this that all he does is creating logfile name log.log inside LOGS environment variable and writes to it every 2 seconds.
What I wanted to achieve is to backup the log.log file inside my docker linux host. After some reading about multiple options I came across 2 popular solutions for persisting data:
Using volumes with the docker run -v options
Creating a data container that holds the data
Option 2 will not help much here since I want to view the logs inside my linux host machine so I chose option 1.
The problem with option 1 is it's creating the logs with a root permissions, which means I have to log into root to be able to delete these logs, something which can cause problem when not everyone should have root user and deleting logs is something that happens commonly.
So I read a little more and find many "work arounds" for this problem, one was mounting my /etc/group and /etc/passwd files inside the docker and use -u option and others were similar to this.
My main question is, is there any convenient and standard solution for this issue, extract the logfiles with/without -v option while letting entire group permission to rwx it.
Thanks!
Since you want the logs to be in your host you need some kind of volume sharing and the "-v" flag is definitely the simplest thing you can do.
As per the permissions issue, I see two options:
the -u flag with passwd/group bindmounting that you mentioned
passing the desired username and group IDs as environment variables and make the daemon running in the docker machine chown the file upon creation (but of course this is not always possible).
I think option 1, while tricky, is the easiest to apply.
Another option is to simply copy the logs to the host:
docker cp <container-name>:/software/service/logs .
Related
Problem
For a docker image (alpine based) that is supposed to run as non-root I have two requirements:
I have to mount a FUSE filesystem inside the docker container
The users of the docker image are able to set the UID/GID of the docker
user with docker run --user {uid}:{gid}
FUSE's fusermount command requires a valid entry for the user in /etc/passwd, otherwise it won't mount the filesystem. Given that I don't know the the UID/GID of the user at build time I can't call adduser at build time. And I can't do it at runtime either, as the user then doesn't have the appropriate privileges.
Solutions found
So far I have found two solutions that both feel not appropriate/secure
1. Make /etc/passwd writable
When adding chmod 555 /etc/passwd to the Dockerfile I can then do at runtime
echo "someuser:x:${my_uid}:$(id -g)::/tmp:/sbin/nologin" >> /etc/passwd
This does the job for fusermount. Unfortunately I did not find a way to make change the passwd file back to read-only at runtime and without that I have security concerns that someone might be able to misuse this to gain root rights back. While I could not find a simple way to use the open passwd file for some exploit (while I was able to add/modify password & configurations directly in /etc/passwd for all users and then change users via login, alpine did not allow this for user root (neither via login nor via su). But I guess there are folk out there more clever than me, and somehow the whole solution feels like a quite dirty hack. Does anyone have specific ideas how a writeable passwd file inside a container could be used for getting inappropriate rights inside the container?
2. Replace requirement #2 with two additional environment variables
By introducing DUID and DGID as environment variables and set USER to some newly added non-root user inside the Dockerfile I found a solution with the help of sudo & /etc/sudoers: In a launch script that I use as entrypoint I can call sudo adduser/addgroup for the given DUID/DGID and then launch the actual program with the user specified via sudo -u someuser someprog.
Except for the fact that the whole setup became quite ugly, I disliked the fact the user's of my docker image could no longer use the regular docker run --user option, as this would break the sudo configuration.
Well I'm running a container which will create/modify some files that I need to persist outside the life-cycle of the container. As a result, I am voluming a folder into the container for that purpose:
docker run -v $(pwd)/data:/app/data my-image
On the first run, $(pwd)/data does not exist, so Docker will create it for me. However, it creates it as the user its own daemon process is being executed as, which is root. This is problematic since the user within the container obviously is not root.
To fix this permission issue, I have created a user group in the host machine with the same ID as the user within the container, and have given them access to the parent folder of $(pwd)/data, and added group permission settings on the folder (chmod g+s). So if I run the following command as root:
sudo mkdir whatver
the folder whatever will be modifiable by that group and by extension, by the container's user. however when Docker creates the folder $(pwd)/data, it still is created as belonging to the group root, and again the user within the container cannot modify data inside it.
Right now, I have worked around this by making the required folders before running the container. However this is a dirty work-around, and I am looking for a cleaner solution. Has anyone else faced this issue? Is it an issue with Docker not respecting the group permission settings on the parent folder or am I missing something here / doing something wrong?
I don't consider making the folders first a dirty workaround, that's a best practice to me. Docker is running a bind mount for you, and this will fail when the source directory does not exist. For user convenience, docker will create this directory for you when you make a host mount and the directory is missing, but there's no need to use this functionality and you'll find other types of mounts will not perform the directory creation for you (e.g. the --mount syntax often used by swarm mode).
You can start your container as root and fix the permissions on the directory with an entrypoint, before running something like an exec gosu app_user app_server at the end to drop from root to the user. I do this in the entrypoint in my docker-base images, which is useful for a developer workflow where developers often need to dynamically update the container to work with their host environment.
Otherwise, you can switch to named volumes. These will be initialized when they are new or empty using the directory contents and permissions from the image. They are a best practice when you need persistence but not direct filesystem access to the contents of the volume from users on the host. You would instead access the named volume from inside of containers that mount the volume.
I have a Microservices based application and the services work fine if I deploy them on a host machine. But now, I'd like to learn Docker, so I started to use containers on a linux based machine. Here is a sample Docker file, it is really simple:
FROM openjdk:11-jdk-slim
MAINTAINER BeszterceKK
COPY ./tao-elszamolas-config.jar /usr/src/taoelszamolas/tao-elszamolas-config.jar
WORKDIR /usr/src/taoelszamolas
ENV SPRING_PROFILES_ACTIVE prod
EXPOSE 9001
ENTRYPOINT ["java", "-jar", "tao-elszamolas-config.jar", "-Dlog4j.configurationFile=file:/tao-elszamolas/services/tao-config/log4j2- prod.xml", "-DlogFileLocation=/tao-elszamolas/logs"]
My problem is that, I try to write my Spring boot application log to the host machine. This is why I use data volumes. At the end this is the command how I run the container:
docker run -d --name=tao-elszamolas-config-server --publish=9001:9001 -v /tao-elszamolas/logs:/tao-elszamolas/logs -v /tao-elszamolas/services/tao-config/log4j2-prod.xml:/tao-elszamolas/services/tao-config/log4j2-prod.xml tao-elszamolas-config:latest
But on a longer term all of the services will go under "docker-compose". This is just for the test, something like a proof of concept.
First question is, why it is not writing the log to the right place. (In one of the volumes defined.) That is what I set in the Log4j2 config xml. If I use the config XML on local without Docker everything works fine. When I log into the container, then I can see the mounted volumes and I can "cd" into it. And I also can do this:
touch something.txt
So the file will be created and can be seen both from container and host machine. What am I doing wrong? I think, the application can pick up the log config, because when I just set an internal folder as the location of the log file, it logs the stuff inside the container.
And I also set the permissions of the whole volume (and its children) to 777 temporarily to test out if the permissions were the problem. But not. Any help would be very much appreciated!
My second question, is there any good web based tool on linux where I can manage my containers. Start them, stop then, etc... I googled it out and found some but not sure which one is the best and free for basic needs, and which one is enough secure.
UPDATE:
Managed to resolve this problem after spending couple of nights with this.
I had multiple problems. First of all, the order of the system properties in the Dockerfile ENTRYPOINT section wasn't quite right. The
-Dsomething=something
must be before the "-jar". Otherwise it is not working in Docker. I haven't found any official documentation stating that, but this is how it is working for me. So the right ENDPOINT definition looks like this:
ENTRYPOINT ["java", "-DlogFileLocation=/tao-elszamolas/logs", "-jar", "tao-elszamolas-config.jar"]
Secondly, when I mounted some folders up to the container with docker run command like this:
-v /tao-elszamolas/logs:/tao-elszamolas/logs
then the log file wasn't written, if the folder in the Docker container doesn't exist by default. But if I create that folder at some point before the ENTRYPOINT in the Dockerfile, then the logging is fine, the system writes its logs to the host machine. I also didn't find any documentation stating these facts, but this is my experience.
Just to provide some steps for verification:
Both Log4j and spring boot in general, should not be aware of any docker-related things, like volumes, mapped folders and so forth.
Instead, configure the logging of the application as if it works without docker at all, so if you want a local file - make sure that the application indeed produces the logging file in a folder of your choice.
The next step would be mapping the folder with volumes in docker / docker-compose.
But first please validate the first step:
docker ps // to see the container id
docker exec -it <CONTAINER_ID> bash
// now check the logging file from within the docker container itself even without volumes
If the logging file does not exist its a java issue and you should configure logging properly. If not - it's a docker issue.
You have a space in your entrypoint after log4j2- and before prod.xml:
ENTRYPOINT ["java", "-jar", "tao-elszamolas-config.jar", "-Dlog4j.configurationFile=file:/tao-elszamolas/services/tao-config/log4j2- prod.xml", "-DlogFileLocation=/tao-elszamolas/logs"]
It might be a problem.
I am running the mqtt broker Mosqitto in a docker image.
I am using the following arguments
sudo docker run -d -p 1883:1883 -p 1884:1884 -v /home/mosquitto/apps/dev/mosquitto:/mosquitto --restart always -u mosquitto eclipse-mosquitto:1.4.
This should mount the host folder /home/mosquitto/apps/dev/mosquitto to the image folder /mosquitto
The problem is that the host user IDs (1001) and the docker user IDs (100) do not match.
If I do not specify -u mosquitto, the application complains about not being able to write to /mosquitto/logs/mosquitto.log
So I thought I'd specify -u mosquitto, to make the application inside the image run as user 1001, and therefore have write access to the mounted files.
Which worked.
But then, the Mosquitto application made a new database file on exit. That file was made with the 101 user as owner..
What exactly happens when I specify -U to docker.
How come it kind of did what I was expecting (allowed writing to host files) and kind of didn't do what I was expecting(still made files with the original image user id)
Maybe this is something to do with this specific docker image .. it runs some script internally that switches user?
How about making write access to log path for any user? It may be less secure. But if it is just logs, lets see application inside docker can write to it.
Or think about bootstrap some commands to the container to make permission changes inside.
If you are using Linux or OSX for your Docker location, most likely it is a security or file permissions issue. Go to this bug report Permission denied for directories created automatically by Dockerfile ADD command #1295 and jump to the end...there are several links to sub-bug reports where you can most likely find your solution. I had a very similar issue, and it turned out to be a selinux misconfiguration.
Docker kind of always had a USER command to run a process as a specific user, but in general a lot of things had to run as ROOT.
I have seen a lot of images that use an ENTRYPOINT with gosu to de-elevate the process to run.
I'm still a bit confused about the need for gosu. Shouldn't USER be enough?
I know quite a bit has changed in terms of security with Docker 1.10, but I'm still not clear about the recommended way to run a process in a docker container.
Can someone explain when I would use gosu vs. USER?
Thanks
EDIT:
The Docker best practice guide is not very clear: It says if the process can run without priviledges, use USER, if you need sudo, you might want to use gosu.
That is confusing because one can install all sorts of things as ROOT in the Dockerfile, then create a user and give it proper privileges, then finally switch to that user and run the CMD as that user.
So why would we need sudo or gosu then?
Dockerfiles are for creating images. I see gosu as more useful as part of a container initialization when you can no longer change users between run commands in your Dockerfile.
After the image is created, something like gosu allows you to drop root permissions at the end of your entrypoint inside of a container. You may initially need root access to do some initialization steps (fixing uid's, host mounted volume permissions, etc). Then once initialized, you run the final service without root privileges and as pid 1 to handle signals cleanly.
Edit:
Here's a simple example of using gosu in an image for docker and jenkins: https://github.com/bmitch3020/jenkins-docker
The entrypoint.sh looks up the gid of the /var/lib/docker.sock file and updates the gid of the docker user inside the container to match. This allows the image to be ported to other docker hosts where the gid on the host may differ. Changing the group requires root access inside the container. Had I used USER jenkins in the dockerfile, I would be stuck with the gid of the docker group as defined in the image which wouldn't work if it doesn't match that of the docker host it's running on. But root access can be dropped when running the app which is where gosu comes in.
At the end of the script, the exec call prevents the shell from forking gosu, and instead it replaces pid 1 with that process. Gosu in turn does the same, switching the uid and then exec'ing the jenkins process so that it takes over as pid 1. This allows signals to be handled correctly which would otherwise be ignored by a shell as pid 1.
I am using gosu and entrypoint.sh because I want the user in the container to have the same UID as the user that created the container.
Docker Volumes and Permissions.
The purpose of the container I am creating is for development. I need to build for linux but I still want all the connivence of local (OS X) editing, tools, etc. My keeping the UIDs the same inside and outside the container it keeps the file ownership a lot more sane and prevents some errors (container user cannot edit files in mounted volume, etc)
Advantage of using gosu is also signal handling. You may trap for instance SIGHUP for reloading the process as you would normally achieve via systemctl reload <process> or such.