¿Cuál es la (mejor) forma de administrar permisos para volúmenes compartidos de Docker?
He estado jugando con Docker por un tiempo y sigo encontrando el mismo problema al tratar con datos persistentes.
Creo Dockerfile
y expongo un volumen o lo uso --volumes-from
para montar una carpeta de host dentro de mi contenedor .
¿Qué permisos debo aplicar al volumen compartido en el host?
Puedo pensar en dos opciones:
Hasta ahora les he dado a todos acceso de lectura/escritura, para poder escribir en la carpeta desde el contenedor Docker.
Asigne a los usuarios del host al contenedor, para poder asignar permisos más granulares. Sin embargo, no estoy seguro de que esto sea posible y no he encontrado mucho al respecto. Hasta ahora, todo lo que puedo hacer es ejecutar el contenedor como algún usuario:,
docker run -i -t -user="myuser" postgres
pero este usuario tiene un UID diferente al de mi hostmyuser
, por lo que los permisos no funcionan. Además, no estoy seguro de si mapear a los usuarios planteará algunos riesgos de seguridad.
¿Existen otras alternativas?
¿Cómo están lidiando ustedes con este problema?
ACTUALIZACIÓN 2016-03-02 : A partir de Docker 1.9.0, Docker tiene volúmenes con nombre que reemplazan los contenedores de solo datos . La respuesta a continuación, así como mi publicación de blog vinculada, todavía tiene valor en el sentido de cómo pensar en los datos dentro de la ventana acoplable, pero considere usar volúmenes con nombre para implementar el patrón que se describe a continuación en lugar de contenedores de datos.
Creo que la forma canónica de resolver esto es mediante el uso de contenedores de solo datos . Con este enfoque, todo el acceso a los datos del volumen se realiza a través de contenedores que utilizan -volumes-from
el contenedor de datos, por lo que el uid/gid del host no importa.
Por ejemplo, un caso de uso que se proporciona en la documentación es realizar una copia de seguridad de un volumen de datos. Para hacer esto, se usa otro contenedor para hacer la copia de seguridad a través de tar
, y también se usa -volumes-from
para montar el volumen. Así que creo que el punto clave para asimilar es: en lugar de pensar en cómo obtener acceso a los datos en el host con los permisos adecuados, piense en cómo hacer lo que necesite (copias de seguridad, navegación, etc.) a través de otro contenedor. . Los propios contenedores necesitan utilizar uid/gids consistentes, pero no necesitan asignarse a nada en el host, por lo que siguen siendo portátiles.
Esto también es relativamente nuevo para mí, pero si tiene un caso de uso particular, no dude en comentarlo e intentaré ampliar la respuesta.
ACTUALIZACIÓN : Para el caso de uso indicado en los comentarios, es posible que tenga una imagen some/graphite
para ejecutar grafito y una imagen some/graphitedata
como contenedor de datos. Entonces, ignorando los puertos y demás, la Dockerfile
imagen some/graphitedata
es algo como:
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"]
Construya y cree el contenedor de datos:
docker build -t some/graphitedata Dockerfile
docker run --name graphitedata some/graphitedata
El some/graphite
Dockerfile también debería tener el mismo uid/gids, por lo tanto, podría verse así:
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"]
Y se ejecutaría de la siguiente manera:
docker run --volumes-from=graphitedata some/graphite
Bien, ahora eso nos da nuestro contenedor de grafito y el contenedor de solo datos asociado con el usuario/grupo correcto (tenga en cuenta que también puede reutilizar el some/graphite
contenedor para el contenedor de datos, anulando la entrada/cmd al ejecutarlo, pero teniéndolos como imágenes separadas en mi opinión son más claras).
Ahora, digamos que desea editar algo en la carpeta de datos. Entonces, en lugar de vincular el montaje del volumen al host y editarlo allí, cree un nuevo contenedor para hacer ese trabajo. Llamémoslo some/graphitetools
. Creemos también el usuario/grupo apropiado, como en la some/graphite
imagen.
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"]
Puede hacer que esto sea SECO heredando desde some/graphite
o some/graphitedata
en el Dockerfile, o en lugar de crear una nueva imagen, simplemente reutilice una de las existentes (anulando el punto de entrada/cmd según sea necesario).
Ahora simplemente ejecuta:
docker run -ti --rm --volumes-from=graphitedata some/graphitetools
y luego vi /data/graphite/whatever.txt
. Esto funciona perfectamente porque todos los contenedores tienen el mismo usuario de grafito con uid/gid coincidente.
Como nunca monta /data/graphite
desde el host, no le importa cómo el uid/gid del host se asigna al uid/gid definido dentro de los contenedores graphite
and graphitetools
. Esos contenedores ahora se pueden implementar en cualquier host y seguirán funcionando perfectamente.
Lo bueno de esto es que graphitetools
podría tener todo tipo de utilidades y scripts útiles, que ahora también puedes implementar de forma portátil.
ACTUALIZACIÓN 2 : Después de escribir esta respuesta, decidí escribir una publicación de blog más completa sobre este enfoque. Espero que ayude.
ACTUALIZACIÓN 3 : corregí esta respuesta y agregué más detalles. Anteriormente contenía algunas suposiciones incorrectas sobre la propiedad y los permisos: la propiedad generalmente se asigna en el momento de la creación del volumen, es decir, en el contenedor de datos, porque es entonces cuando se crea el volumen. Ver este blog . Sin embargo, esto no es un requisito: puede usar el contenedor de datos como una "referencia/identificador" y establecer la propiedad/permisos en otro contenedor mediante chown en un punto de entrada, que termina con gosu para ejecutar el comando como el usuario correcto. Si alguien está interesado en este enfoque, comente y puedo proporcionar enlaces a una muestra que utiliza este enfoque.
Se puede ver una solución muy elegante en la imagen oficial de Redis y, en general, en todas las imágenes oficiales.
Descrito en el proceso paso a paso:
- Crear usuario/grupo de Redis antes que nada
Como se ve en los comentarios de Dockerfile:
agregue nuestro usuario y grupo primero para asegurarse de que sus ID se asignen de manera consistente, independientemente de las dependencias que se agreguen
- Instalar gosu con Dockerfile
gosu es una alternativa de su
/ sudo
para bajar fácilmente del usuario root. (Redis siempre se ejecuta con redis
el usuario)
- Configure
/data
el volumen y configúrelo como directorio de trabajo
Al configurar el volumen /data con el VOLUME /data
comando, ahora tenemos un volumen separado que puede ser un volumen acoplable o estar vinculado a un directorio de host.
Configurarlo como workdir ( WORKDIR /data
) lo convierte en el directorio predeterminado desde donde se ejecutan los comandos.
- Agregue el archivo docker-entrypoint y configúrelo como ENTRYPOINT con el servidor CMD redis predeterminado
Esto significa que todas las ejecuciones del contenedor se ejecutarán a través del script docker-entrypoint y, de forma predeterminada, el comando que se ejecutará es redis-server.
docker-entrypoint
es un script que realiza una función simple: cambiar la propiedad del directorio actual (/data) y bajar de root
usuario redis
para ejecutarlo redis-server
. (Si el comando ejecutado no es redis-server, ejecutará el comando directamente).
Esto tiene el siguiente efecto
Si el directorio /data está vinculado al host, el punto de entrada de la ventana acoplable preparará los permisos del usuario antes de ejecutar redis-server bajo redis
el usuario.
Esto le brinda la tranquilidad de saber que no es necesaria ninguna configuración para ejecutar el contenedor en cualquier configuración de volumen.
Por supuesto, si necesita compartir el volumen entre diferentes imágenes, debe asegurarse de que utilicen el mismo ID de usuario/ID de grupo, de lo contrario, el último contenedor secuestrará los permisos de usuario del anterior.
Podría decirse que esta no es la mejor manera en la mayoría de las circunstancias, pero aún no se ha mencionado, por lo que quizás ayude a alguien.
Vincular el volumen del host de montaje
Host folder FOOBAR is mounted in container /volume/FOOBAR
Modifique el script de inicio de su contenedor para encontrar el GID del volumen que le interesa
$ TARGET_GID=$(stat -c "%g" /volume/FOOBAR)
Asegúrese de que su usuario pertenezca a un grupo con este GID (es posible que deba crear un grupo nuevo). Para este ejemplo, simularé que mi software se ejecuta como
nobody
usuario cuando está dentro del contenedor, por lo que quiero asegurarme denobody
que pertenece a un grupo con una identificación de grupo igual aTARGET_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
Me gusta esto porque puedo modificar fácilmente los permisos de grupo en mis volúmenes de host y saber que esos permisos actualizados se aplican dentro del contenedor acoplable. Esto sucede sin ningún permiso o modificación de propiedad en mis carpetas/archivos de host, lo que me hace feliz.
No me gusta esto porque asume que no hay peligro al agregarse a grupos arbitrarios dentro del contenedor que están usando el GID que desea. No se puede usar con una USER
cláusula en un Dockerfile (a menos que ese usuario tenga privilegios de root, supongo). Además, grita trabajo de hackeo ;-)
Si desea ser incondicional, obviamente puede extender esto de muchas maneras; por ejemplo, buscar todos los grupos en cualquier subarchivo, múltiples volúmenes, etc.
Al igual que usted, estaba buscando una forma de asignar usuarios/grupos desde el host a los contenedores acoplables y esta es la forma más corta que he encontrado hasta ahora:
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
.....
Este es un extracto de mi docker-compose.yml.
La idea es montar (en modo de solo lectura) listas de usuarios/grupos desde el host al contenedor, de modo que después de que se inicie el contenedor tendrá las mismas coincidencias uid->nombre de usuario (así como para grupos) con el host. Ahora puede configurar los ajustes de usuario/grupo para su servicio dentro del contenedor como si estuviera funcionando en su sistema host.
Cuando decide mover su contenedor a otro host, solo necesita cambiar el nombre de usuario en el archivo de configuración del servicio al que tiene en ese host.
Intente agregar un comando a Dockerfile
RUN usermod -u 1000 www-data
los créditos van a https://github.com/denderello/symfony-docker-example/issues/2#issuecomment-94387272