Ich habe eine Weile mit Docker herumgespielt und finde immer wieder das gleiche Problem, wenn es um persistente Daten geht.
Ich erstelle meine Dockerfile
und mache ein Volume oder verwende --volumes-from
, um einen Host-Ordner in meinem Container einzuhängen.
Welche Berechtigungen sollte ich auf das freigegebene Volume auf dem Host anwenden?
Ich kann mir zwei Möglichkeiten vorstellen:
Bisher habe ich allen Schreib-/Lesezugriff erteilt, damit ich aus dem Docker-Container in den Ordner schreiben kann.
Ordnen Sie die Benutzer vom Host dem Container zu, sodass ich detailliertere Berechtigungen zuweisen kann. Nicht sicher, dass dies möglich ist und nicht viel darüber gefunden hat. Bisher kann ich nur den Container als Benutzer ausführen: docker run -i -t -user="myuser" postgres
, aber dieser Benutzer hat eine andere UID als mein Host myuser
, daher funktionieren Berechtigungen nicht. Ich bin auch nicht sicher, ob die Zuordnung der Benutzer Sicherheitsrisiken birgt.
Gibt es andere Alternativen?
Wie gehen Sie mit dieser Ausgabe um?
UPDATE 2016-03-02: Ab Docker 1.9.0 hat Docker Volumes benannt die Data-Only-Container ersetzen . Die nachstehende Antwort sowie mein verknüpfter Blogbeitrag haben immer noch einen Wert im Sinne von wie man Daten im Docker betrachtet. Sie sollten jedoch benannte Datenträger verwenden, um das unten beschriebene Muster anstelle von Datencontainern zu implementieren.
Ich glaube, der kanonische Weg, dieses Problem zu lösen, ist die Verwendung von Nur-Daten-Containern . Bei diesem Ansatz erfolgt der gesamte Zugriff auf die Volumendaten über Container, die -volumes-from
des Datencontainers verwenden, sodass die Host-Uid/gid keine Rolle spielt.
Ein in der Dokumentation angegebener Anwendungsfall ist beispielsweise das Sichern eines Datenvolumens. Dazu wird ein anderer Container für die Sicherung über tar
verwendet, und er verwendet ebenfalls -volumes-from
, um den Datenträger bereitzustellen. Ich denke, der Schlüsselpunkt von grok ist: Statt über den Zugriff auf die Daten auf dem Host mit den richtigen Berechtigungen nachzudenken, sollten Sie über einen anderen Container nachdenken, wie Sie das tun, was Sie benötigen - Sicherungen, Browsen usw. - . Die Container selbst müssen konsistente UIDs/Gids verwenden, müssen jedoch nichts auf dem Host zuordnen und bleiben somit portabel.
Dies ist auch für mich relativ neu, aber wenn Sie einen bestimmten Anwendungsfall haben, können Sie das gerne kommentieren und ich werde versuchen, die Antwort zu erweitern.
UPDATE: Für den angegebenen Anwendungsfall in den Kommentaren haben Sie möglicherweise ein Bild some/graphite
zum Ausführen von Graphite und ein Bild some/graphitedata
als Datencontainer. Das Ignorieren von Ports und ähnlichem ist die Dockerfile
von image some/graphitedata
wie folgt:
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
&& useradd -r -g graphite graphite
RUN mkdir -p /data/graphite \
&& chown -R graphite:graphite /data/graphite
VOLUME /data/graphite
USER graphite
CMD ["echo", "Data container for graphite"]
Erstellen und erstellen Sie den Datencontainer:
docker build -t some/graphitedata Dockerfile
docker run --name graphitedata some/graphitedata
Die some/graphite
-Docker-Datei sollte auch die gleiche Uid/Gids erhalten, daher könnte es etwa so aussehen:
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
&& useradd -r -g graphite graphite
# ... graphite installation ...
VOLUME /data/graphite
USER graphite
CMD ["/bin/graphite"]
Und es würde wie folgt laufen:
docker run --volumes-from=graphitedata some/graphite
Ok, das gibt uns nun unseren Graphitcontainer und den zugehörigen Data-Only-Container mit dem richtigen Benutzer/der richtigen Gruppe (Hinweis: Sie können den some/graphite
-Container auch für den Datencontainer erneut verwenden, wobei das Entypoing/cmd beim Ausführen außer Kraft gesetzt wird, es aber vorhanden ist als separate Bilder ist IMO klarer).
Nehmen wir an, Sie möchten etwas im Datenordner bearbeiten. Anstatt das Volume an den Host zu binden und dort zu bearbeiten, erstellen Sie einen neuen Container, um diesen Job auszuführen. Nennen wir es some/graphitetools
. Lasst auch den entsprechenden Benutzer/die entsprechende Gruppe erstellen, genau wie das Bild some/graphite
.
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
&& useradd -r -g graphite graphite
VOLUME /data/graphite
USER graphite
CMD ["/bin/bash"]
Sie können dies DRY machen, indem Sie some/graphite
oder some/graphitedata
in der Dockerfile erben, oder anstatt ein neues Bild zu erstellen, verwenden Sie einfach eines der vorhandenen Bilder (überschreiben Sie ggf. Entrypoint/cmd).
Jetzt rennst du einfach:
docker run -ti --rm --volumes-from=graphitedata some/graphitetools
und dann vi /data/graphite/whatever.txt
. Dies funktioniert perfekt, da alle Container denselben Graphitbenutzer mit der entsprechenden UID/GID haben.
Da Sie niemals /data/graphite
vom Host aus mounten, ist es Ihnen egal, wie die Host-Uid/gid der Uid/gid zugeordnet wird, die in den graphite
- und graphitetools
-Containern definiert ist. Diese Container können jetzt auf jedem Host bereitgestellt werden und funktionieren weiterhin einwandfrei.
Das Besondere daran ist, dass graphitetools
alle möglichen nützlichen Dienstprogramme und Skripts enthalten kann, die Sie jetzt auch auf tragbare Weise bereitstellen können.
UPDATE 2: Nachdem ich diese Antwort geschrieben hatte, entschied ich mich, einen ausführlicheren Blogbeitrag über diesen Ansatz zu schreiben. Ich hoffe, es hilft.
UPDATE 3: Ich habe diese Antwort korrigiert und weitere Details hinzugefügt. Zuvor enthielt es einige falsche Annahmen über den Besitz und die Perms. Der Besitz wird normalerweise zum Zeitpunkt der Datenträgererstellung zugewiesen, d. H. Im Datencontainer, da der Datenträger gerade erstellt wird. Siehe dieses Blog . Dies ist jedoch keine Anforderung - Sie können den Datencontainer einfach als "Referenz/Handle" verwenden und die Besitzrechte/Perms in einem anderen Container über chown in einem Entrypoint festlegen, der mit gosu endet, um den Befehl als korrekten Benutzer auszuführen. Wenn sich jemand für diesen Ansatz interessiert, kommentieren Sie ihn bitte und ich kann mit diesem Ansatz Links zu einem Beispiel bereitstellen.
Eine sehr elegante Lösung ist auf dem offiziellen redis image und allgemein in allen offiziellen Bildern zu sehen.
Schritt für Schritt beschrieben:
Wie man bei Dockerfile-Kommentaren sieht:
fügen Sie zuerst unseren Benutzer und die Gruppe hinzu, um sicherzustellen, dass ihre IDs konsistent zugewiesen werden, unabhängig davon, welche Abhängigkeiten hinzugefügt werden
gosu ist eine Alternative zu su
Sudo
für ein einfaches Abspringen vom Root-Benutzer. (Redis wird immer mit redis
Benutzer ausgeführt)
/data
volume und legen Sie es als workdir festDurch Konfigurieren des/data-Datenträgers mit dem Befehl VOLUME /data
verfügen wir jetzt über einen separaten Datenträger, der entweder ein Docker-Datenträger sein oder an ein Hostverzeichnis gebunden sein kann.
Wenn Sie es als workdir (WORKDIR /data
) konfigurieren, wird es zum Standardverzeichnis, von dem Befehle ausgeführt werden.
Das bedeutet, dass alle Containerausführungen durch das Docker-Entypoint-Skript ausgeführt werden. Standardmäßig ist der auszuführende Befehl Redis-Server.
docker-entrypoint
ist ein Skript, das eine einfache Funktion ausführt: Ändern Sie den Besitz des aktuellen Verzeichnisses (/ data) und wechseln Sie von root
zu redis
Benutzer, um redis-server
auszuführen. (Wenn der ausgeführte Befehl kein Redis-Server ist, wird der Befehl direkt ausgeführt.)
_/Dies hat folgende Wirkung
Wenn das Verzeichnis/data an den Host gebunden ist, bereitet der Docker-Eingangspunkt die Benutzerberechtigungen vor, bevor der redis-server unter redis
user ausgeführt wird.
Dies gibt Ihnen die Gewissheit, dass der Container unter einer beliebigen Volumenkonfiguration ausgeführt werden muss.
Wenn Sie das Volume für verschiedene Bilder freigeben müssen, müssen Sie natürlich sicherstellen, dass sie dieselbe Benutzer-ID/Gruppen-ID verwenden. Andernfalls werden die Benutzerberechtigungen des letzten Containers durch den neuesten Container entfernt.
Dies ist wohl nicht der beste Weg für die meisten Umstände, aber es wurde noch nicht erwähnt, so dass es vielleicht jemandem helfen wird.
Binden Sie das Mount-Host-Volume
Host folder FOOBAR is mounted in container /volume/FOOBAR
Ändern Sie das Startskript Ihres Containers, um die GID des Volumes zu ermitteln, an dem Sie interessiert sind
$ TARGET_GID=$(stat -c "%g" /volume/FOOBAR)
Stellen Sie sicher, dass Ihr Benutzer zu einer Gruppe mit dieser GID gehört (möglicherweise müssen Sie eine neue Gruppe erstellen). In diesem Beispiel werde ich so tun, als würde meine Software als Benutzer nobody
ausgeführt, wenn sie sich im Container befindet. Daher möchte ich sicherstellen, dass nobody
zu einer Gruppe mit einer Gruppen-ID von TARGET_GID
EXISTS=$(cat /etc/group | grep $TARGET_GID | wc -l)
# Create new group using target GID and add nobody user
if [ $EXISTS == "0" ]; then
groupadd -g $TARGET_GID tempgroup
usermod -a -G tempgroup nobody
else
# GID exists, find group name and add
GROUP=$(getent group $TARGET_GID | cut -d: -f1)
usermod -a -G $GROUP nobody
fi
Das gefällt mir, weil ich Gruppenberechtigungen auf meinen Host-Volumes leicht ändern kann und weiß, dass diese aktualisierten Berechtigungen im Docker-Container gelten. Dies geschieht ohne Erlaubnis oder Eigentumsänderungen an meinen Host-Ordnern/-Dateien, was mich glücklich macht.
Ich mag das nicht, weil es keine Gefahr darstellt, sich einer beliebigen Gruppe innerhalb des Containers hinzuzufügen, die zufällig eine von Ihnen gewünschte GID verwendet. Es kann nicht mit einer USER
-Klausel in einer Docker-Datei verwendet werden (es sei denn, dieser Benutzer hat Root-Rechte, nehme ich an). Außerdem schreit es Hackjob ;-)
Wenn Sie Hardcore sein möchten, können Sie dies natürlich auf viele Arten erweitern - z. Suche nach allen Gruppen in beliebigen Unterdateien, mehreren Volumes usw.
Ok, das wird jetzt bei Docker-Ausgabe # 7198 verfolgt
Im Moment beschäftige ich mich mit Ihrer zweiten Option:
Ordnen Sie die Benutzer vom Host dem Container zu
#=======
# Users
#=======
# TODO: Idk how to fix hardcoding uid & gid, specifics to docker Host machine
RUN (adduser --system --uid=1000 --gid=1000 \
--home /home/myguestuser --Shell /bin/bash myguestuser)
# DIR_Host and DIR_GUEST belongs to uid:gid 1000:1000
docker run -d -v ${DIR_Host}:${DIR_GUEST} elgalu/myservice:latest
Versuchen Sie, einen Befehl zu Dockerfile hinzuzufügen
RUN usermod -u 1000 www-data
credits gehen an https://github.com/denderello/symfony-docker-example/issues/2#issuecomment-94387272
Genau wie Sie, ich habe nach einem Weg gesucht, Benutzer/Gruppen von Host zu Docker-Containern abzubilden, und dies ist der kürzeste Weg, den ich bisher gefunden habe:
version: "3"
services:
my-service:
.....
volumes:
# take uid/gid lists from Host
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
# mount config folder
- path-to-my-configs/my-service:/etc/my-service:ro
.....
Dies ist ein Auszug aus meinem docker-compose.yml.
Die Idee ist, (im Nur-Lese-Modus) Benutzer-/Gruppenlisten aus dem Host in den Container zu laden. Nach dem Starten des Containers hat derselbe Uid-> Benutzername (sowie Gruppen) Übereinstimmungen mit dem Host. Jetzt können Sie Benutzer-/Gruppeneinstellungen für Ihren Dienst innerhalb des Containers so konfigurieren, als würde er auf Ihrem Host-System funktionieren.
Wenn Sie sich entscheiden, Ihren Container auf einen anderen Host zu verschieben, müssen Sie lediglich den Benutzernamen in der Dienstkonfigurationsdatei in den auf diesem Host vorhandenen Namen ändern.
Hier ist ein Ansatz, bei dem immer noch ein Nur-Daten-Container verwendet wird, der jedoch nicht mit dem Anwendungscontainer synchronisiert werden muss (in Bezug auf dieselbe UID/GID).
Vermutlich möchten Sie eine App im Container als $ USER ohne Rootberechtigung ohne Login-Shell ausführen.
In der Dockerfile:
RUN useradd -s /bin/false myuser
# Set environment variables
ENV VOLUME_ROOT /data
ENV USER myuser
...
ENTRYPOINT ["./entrypoint.sh"]
Dann in entrypoint.sh:
chown -R $USER:$USER $VOLUME_ROOT
su -s /bin/bash - $USER -c "cd $repo/build; [email protected]"
Verwenden Sie die Optionen --uidmap
und --private-uids
, um einen Docker-Container zu sichern und den Root für Docker-Container zu ändern
https://github.com/docker/docker/pull/4572#issuecomment-38400893
Aus Sicherheitsgründen können Sie auch mehrere Funktionen (--cap-drop
) im Docker-Container entfernen
http://opensource.com/business/14/9/security-for-docker
UPDATE Unterstützung sollte in docker > 1.7.0
kommen
UPDATE Version 1.10.0
(2016-02-04) füge --userns-remap
flag https://github.com/docker/docker/blob/master/CHANGELOG.md#security-2 hinzu
Verwenden Sie dieses Bild: https://hub.docker.com/r/reduardo7/docker-Host-user
Wichtig: zerstört die Container-Portabilität zwischen Hosts.
init.sh
#!/bin/bash
if ! getent passwd $DOCKDEV_USER_NAME > /dev/null
then
echo "Creating user $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME"
groupadd --gid $DOCKDEV_GROUP_ID -r $DOCKDEV_GROUP_NAME
useradd --system --uid=$DOCKDEV_USER_ID --gid=$DOCKDEV_GROUP_ID \
--home-dir /home --password $DOCKDEV_USER_NAME $DOCKDEV_USER_NAME
usermod -a -G Sudo $DOCKDEV_USER_NAME
chown -R $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME /home
fi
Sudo -u $DOCKDEV_USER_NAME bash
Dockerfile
FROM ubuntu:latest
# Volumes
VOLUME ["/home/data"]
# Copy Files
COPY /home/data/init.sh /home
# Init
RUN chmod a+x /home/init.sh
#!/bin/bash
DOCKDEV_VARIABLES=(\
DOCKDEV_USER_NAME=$USERNAME\
DOCKDEV_USER_ID=$UID\
DOCKDEV_GROUP_NAME=$(id -g -n $USERNAME)\
DOCKDEV_GROUP_ID=$(id -g $USERNAME)\
)
cmd="docker run"
if [ ! -z "${DOCKDEV_VARIABLES}" ]; then
for v in ${DOCKDEV_VARIABLES[@]}; do
cmd="${cmd} -e ${v}"
done
fi
# /home/usr/data contains init.sh
$cmd -v /home/usr/data:/home/data -i -t my-image /home/init.sh
docker
sh run.sh
Mein Ansatz ist es, die aktuelle UID/GID zu ermitteln und dann einen solchen Benutzer/eine Gruppe innerhalb des Containers zu erstellen und das Skript unter ihm auszuführen. Daher werden alle Dateien, die er erstellt, dem Benutzer entsprechen, der das Skript ausführt:
# get location of this script no matter what your current folder is, this might break between shells so make sure you run bash
LOCAL_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# get current IDs
USER_ID=$(id -u)
GROUP_ID=$(id -g)
echo "Mount $LOCAL_DIR into docker, and match the Host IDs ($USER_ID:$GROUP_ID) inside the container."
docker run -v $LOCAL_DIR:/Host_mount -i debian:9.4-slim bash -c "set -euo pipefail && groupadd -r -g $GROUP_ID lowprivgroup && useradd -u $USER_ID lowprivuser -g $GROUP_ID && cd /Host_mount && su -c ./runMyScriptAsRegularUser.sh lowprivuser"
In meinem speziellen Fall habe ich versucht, mein Knotenpaket mit dem Node-Docker-Image zu erstellen, damit ich npm nicht auf dem Implementierungsserver installieren muss. Es hat gut funktioniert, bis ich außerhalb des Containers und auf dem Host-Computer versucht habe, eine Datei in das Verzeichnis node_modules zu verschieben, das das Node-Docker-Image erstellt hatte, und dem ich Berechtigungen verweigert hatte, weil es im Besitz von root war. Ich erkannte, dass ich dies umgehen konnte, indem ich das Verzeichnis aus dem Container auf den Host-Computer kopierte. Über Docker-Dokumente ...
Auf den lokalen Rechner kopierte Dateien werden mit der UID: GID des .__ erstellt. Benutzer, der den Befehl docker cp aufgerufen hat.
Dies ist der Bash-Code, den ich verwendet habe, um den Besitz des von und innerhalb des Docker-Containers erstellten Verzeichnisses zu ändern.
NODE_IMAGE=node_builder
docker run -v $(pwd)/build:/build -w="/build" --name $NODE_IMAGE node:6-slim npm i --production
# node_modules is owned by root, so we need to copy it out
docker cp $NODE_IMAGE:/build/node_modules build/lambda
# you might have issues trying to remove the directory "node_modules" within the shared volume "build", because it is owned by root, so remove the image and its volumes
docker rm -vf $NODE_IMAGE || true
Bei Bedarf können Sie das Verzeichnis mit einem zweiten Docker-Container entfernen.
docker run -v $(pwd)/build:/build -w="/build" --name $RMR_IMAGE node:6-slim rm -r node_modules
Um den Ordner zwischen Docker-Host und Docker-Container freizugeben, versuchen Sie den folgenden Befehl
$ docker run -v "$ (pwd): $ (pwd)" -i -t ubuntu
Das Flag -v hängt das aktuelle Arbeitsverzeichnis in den Container ein. Wenn das Host-Verzeichnis eines gebundenen Volumes nicht vorhanden ist, erstellt Docker dieses Verzeichnis automatisch für Sie auf dem Host.
Es gibt jedoch zwei Probleme, die wir hier haben:
Lösung:
Container: Einen Benutzer erstellen, sagen Sie 'Testbenutzer'. Die Benutzer-ID beginnt standardmäßig mit 1000.
Host: Erstellen Sie eine Gruppe, sagen Sie 'testgroup' mit der Gruppennummer 1000 und ordnen Sie das Verzeichnis der neuen Gruppe (testgroup) zu