Каков (лучший) способ управления разрешениями для общих томов Docker?
Некоторое время я играл с Docker и продолжаю находить ту же проблему при работе с постоянными данными.
Я создаю свой Dockerfile
и выставить объем или использование --volumes-from
смонтировать папку хоста внутри моего контейнера.
Какие разрешения я должен применить к общему объему на хосте?
Я могу придумать два варианта:
До сих пор я предоставил всем доступ на чтение / запись, чтобы я мог писать в папку из контейнера Docker.
Сопоставьте пользователей с хоста в контейнере, чтобы я мог назначить более детальные разрешения. Не уверен, что это возможно, хотя и не нашел много об этом. Пока все, что я могу сделать, это запустить контейнер от имени некоторого пользователя:
docker run -i -t -user="myuser" postgres
, но этот пользователь имеет другой UID, чем мой хостmyuser
, поэтому разрешения не работают. Кроме того, я не уверен, что сопоставление пользователей будет представлять некоторую угрозу безопасности.
Есть ли другие альтернативы?
Как вы, ребята / девочки, имеете дело с этой проблемой?
15 ответов
ОБНОВЛЕНИЕ 2016-03-02: Начиная с Docker 1.9.0, Docker имеет именованные тома, которые заменяют контейнеры только для данных. Приведенный ниже ответ, как и мой пост в блоге, все еще имеет значение в том смысле, как думать о данных внутри докера, но рассмотрите возможность использования именованных томов для реализации описанного ниже шаблона, а не контейнеров данных.
Я считаю, что канонический способ решить эту проблему - использовать контейнеры только для данных. При таком подходе весь доступ к данным тома осуществляется через контейнеры, которые используют -volumes-from
контейнер данных, поэтому хост uid/gid не имеет значения.
Например, один из вариантов использования, приведенный в документации, - резервное копирование тома данных. Для этого используется другой контейнер для резервного копирования через tar
и это тоже использует -volumes-from
для того, чтобы смонтировать том. Поэтому я думаю, что ключевой момент в работе с Grok: вместо того, чтобы думать о том, как получить доступ к данным на хосте с соответствующими разрешениями, подумайте о том, как сделать все, что вам нужно - резервное копирование, просмотр и т. Д. - через другой контейнер., Сами контейнеры должны использовать согласованные uid/gids, но им не нужно сопоставлять что-либо на хосте, тем самым оставаясь переносимым.
Для меня это тоже относительно новый вопрос, но если у вас есть конкретный вариант использования, не стесняйтесь комментировать, и я постараюсь расширить ответ.
ОБНОВЛЕНИЕ: Для данного варианта использования в комментариях, у вас может быть изображение some/graphite
запустить графит и изображение some/graphitedata
в качестве контейнера данных. Таким образом, игнорируя порты и тому подобное, Dockerfile
изображения some/graphitedata
это что-то вроде:
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"]
Создайте и создайте контейнер данных:
docker build -t some/graphitedata Dockerfile
docker run --name graphitedata some/graphitedata
some/graphite
Dockerfile также должен получить тот же uid/gids, поэтому он может выглядеть примерно так:
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"]
И это будет работать следующим образом:
docker run --volumes-from=graphitedata some/graphite
Хорошо, теперь это дает нам наш графитовый контейнер и связанный контейнер только для данных с правильным пользователем / группой (обратите внимание, вы можете повторно использовать some/graphite
Контейнер для контейнера данных, также переопределяя entrypoing / cmd при его запуске, но имея их как отдельные изображения, IMO более понятен).
Теперь предположим, что вы хотите редактировать что-то в папке данных. Поэтому вместо того, чтобы привязывать монтирование тома к хосту и редактировать его там, создайте новый контейнер для этой работы. Давай называть это some/graphitetools
, Давайте также создадим соответствующего пользователя / группу, так же как 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"]
Вы можете сделать это СУХОЙ, наследуя от some/graphite
или же some/graphitedata
в Dockerfile или вместо создания нового образа просто повторно используйте один из существующих (при необходимости переопределяя точку входа / cmd).
Теперь вы просто запускаете:
docker run -ti --rm --volumes-from=graphitedata some/graphitetools
а потом vi /data/graphite/whatever.txt
, Это прекрасно работает, потому что все контейнеры имеют одного и того же пользователя графита с совпадающим uid/gid.
Так как ты никогда не монтируешься /data/graphite
от хоста, вам все равно, как хост uid/gid отображается в uid/gid, определенный внутри graphite
а также graphitetools
контейнеры. Эти контейнеры теперь могут быть развернуты на любом хосте, и они будут продолжать работать отлично.
Хорошая вещь об этом заключается в том, что graphitetools
может иметь все виды полезных утилит и сценариев, которые теперь вы также можете развернуть в переносном режиме.
ОБНОВЛЕНИЕ 2: После написания этого ответа я решил написать более полный блог об этом подходе. Я надеюсь, что это помогает.
ОБНОВЛЕНИЕ 3: я исправил этот ответ и добавил больше деталей. Ранее в нем содержались некоторые неверные предположения о владении и привилегиях - владение обычно назначается во время создания тома, то есть в контейнере данных, поскольку именно тогда создается том. Смотрите этот блог. Это не является обязательным требованием - вы можете просто использовать контейнер данных в качестве "ссылки / дескриптора" и установить владельца / права доступа в другом контейнере с помощью chown в точке входа, который заканчивается gosu для запуска команды от имени правильного пользователя. Если кто-то заинтересован в этом подходе, пожалуйста, прокомментируйте, и я могу предоставить ссылки на образец, используя этот подход.
Очень элегантное решение можно увидеть на официальном изображении Redis и в целом на всех официальных изображениях.
Описано в пошаговом процессе:
- Создать пользователя / группу Redis прежде всего
Как видно из комментариев Dockerfile:
сначала добавьте нашего пользователя и группу, чтобы убедиться, что их идентификаторы назначаются последовательно, независимо от того, какие зависимости добавляются
- Установите gosu с помощью Dockerfile
Госу является альтернативой su
/ sudo
для легкого увольнения от пользователя root. (Redis всегда запускается с redis
пользователь)
- конфигурировать
/data
громкость и установите его как workdir
При настройке тома /data с помощью VOLUME /data
Теперь у нас есть отдельный том, который может быть либо док-томом, либо подключенным к директории хоста.
Настройка его как рабочего каталога (WORKDIR /data
) делает его каталогом по умолчанию, из которого выполняются команды.
- Добавьте файл docker-entrypoint и установите его как ENTRYPOINT с CMD-редис-сервером по умолчанию
Это означает, что все выполнения контейнера будут выполняться через сценарий docker-entrypoint, и по умолчанию команда, которую нужно запустить, - это redis-server.
docker-entrypoint
скрипт, который выполняет простую функцию: изменить владельца текущего каталога (/data) и перейти из root
в redis
пользователь запустить redis-server
, (Если выполненная команда не является redis-сервером, она будет запущена непосредственно).
Это имеет следующий эффект
Если каталог /data привязан к хосту, точка входа docker подготовит права пользователя перед запуском redis-server под redis
пользователь.
Это дает вам ощущение простоты, что для запуска контейнера в любой конфигурации тома не требуется никакой настройки.
Конечно, если вам нужно разделить объем между разными изображениями, вам нужно убедиться, что они используют один и тот же идентификатор пользователя / groupid, в противном случае последний контейнер перехватит права пользователя из предыдущего.
Возможно, это не лучший способ для большинства обстоятельств, но он еще не упоминался, поэтому, возможно, он кому-нибудь поможет.
Привязать том хоста
Host folder FOOBAR is mounted in container /volume/FOOBAR
Измените скрипт запуска вашего контейнера, чтобы найти GID интересующего вас тома.
$ TARGET_GID=$(stat -c "%g" /volume/FOOBAR)
Убедитесь, что ваш пользователь входит в группу с этим GID (возможно, вам придется создать новую группу). В этом примере я буду притворяться, что моя программа работает как
nobody
пользователь, когда внутри контейнера, поэтому я хочу убедиться,nobody
принадлежит к группе с идентификатором группы, равным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
Мне это нравится, потому что я могу легко изменять групповые разрешения на томах своего хоста и знать, что эти обновленные разрешения применяются внутри контейнера докера. Это происходит без каких-либо разрешений или изменений владельца моих папок / файлов хоста, что меня радует.
Мне это не нравится, потому что предполагается, что нет опасности добавлять себя в произвольные группы внутри контейнера, которые используют нужный GID. Он не может быть использован с USER
предложение в Dockerfile (если, я полагаю, этот пользователь не имеет привилегий root). Кроме того, это кричит взломать работу;-)
Если вы хотите быть хардкорным, вы, очевидно, можете расширить его многими способами - например, искать все группы в любых подфайлах, нескольких томах и т. Д.
Как и вы, я искал способ отобразить пользователей / группы из хоста в докер-контейнеры, и это самый короткий путь, который я нашел до сих пор:
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
.....
Это выдержка из моего docker-compose.yml.
Идея состоит в том, чтобы монтировать (в режиме только для чтения) списки пользователей / групп с хоста на контейнер, поэтому после запуска контейнера он будет иметь те же соответствия uid->username (как и для групп) с хостом. Теперь вы можете настроить параметры пользователя / группы для вашего сервиса внутри контейнера, как если бы он работал в вашей хост-системе.
Когда вы решите переместить свой контейнер на другой хост, вам просто нужно изменить имя пользователя в файле конфигурации сервиса на то, что у вас есть на этом хосте.
Попробуйте добавить команду в Dockerfile
RUN usermod -u 1000 www-data
кредиты идут на https://github.com/denderello/symfony-docker-example/issues/2
Хорошо, это теперь отслеживается в проблеме докера #7198
Сейчас я занимаюсь этим, используя ваш второй вариант:
Карта пользователей с хоста в контейнер
Dockerfile
#=======
# 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)
CLI
# DIR_HOST and DIR_GUEST belongs to uid:gid 1000:1000
docker run -d -v ${DIR_HOST}:${DIR_GUEST} elgalu/myservice:latest
Мой подход заключается в том, чтобы определить текущий UID/GID, а затем создать такого пользователя / группу в контейнере и выполнить скрипт под ним, так что все файлы, которые он создаст, будут соответствовать пользователю, который запускает скрипт:
# 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"
Для обеспечения безопасности и изменения корня для Docker-контейнера попробуйте использовать Docker-хост. --uidmap
а также --private-uids
опции
https://github.com/docker/docker/pull/4572
Также вы можете удалить несколько возможностей (--cap-drop
в контейнер докер для безопасности
http://opensource.com/business/14/9/security-for-docker
ОБНОВЛЕНИЕ поддержка должна прийти docker > 1.7.0
ОБНОВЛЕНИЕ Версия 1.10.0
(2016-02-04) добавить --userns-remap
флаг https://github.com/docker/docker/blob/master/CHANGELOG.md#security-2
Вот подход, который все еще использует контейнер только для данных, но не требует его синхронизации с контейнером приложения (с точки зрения того же uid/gid).
Предположительно, вы хотите запустить какое-то приложение в контейнере как пользователь $USER без полномочий root без оболочки входа в систему.
В Dockerfile:
RUN useradd -s /bin/false myuser
# Set environment variables
ENV VOLUME_ROOT /data
ENV USER myuser
...
ENTRYPOINT ["./entrypoint.sh"]
Затем в entrypoint.sh:
chown -R $USER:$USER $VOLUME_ROOT
su -s /bin/bash - $USER -c "cd $repo/build; $@"
Базовое изображение
Используйте это изображение: https://hub.docker.com/r/reduardo7/docker-host-user
или же
Важно: это разрушает переносимость контейнера между хостами.
1) 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
2) Dockerfile
FROM ubuntu:latest
# Volumes
VOLUME ["/home/data"]
# Copy Files
COPY /home/data/init.sh /home
# Init
RUN chmod a+x /home/init.sh
3) run.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
4) Сборка с docker
4) Беги!
sh run.sh
В конце концов я написал сценарий, который синхронизирует выбранные идентификаторы пользователей и групп от хоста к контейнеру с помощью usermod и groupmod.
докер составляет:
volumes:
- /etc/passwd:/etc/passwd.src:ro
- /etc/group:/etc/group.src:ro
environment:
- host_users=www-data,mysql
- host_groups=www-data,mysql,staff
сценарий:
#!/bin/bash
for user in ${host_users//,/ }; do
echo "syncing user $user"
uid=$(grep "^$user:" /etc/passwd.src | awk -F: '{print $3}')
if [ ! -z "$uid" ]; then
RET=1
while [[ RET -ne 0 ]]; do
usermod -u $uid $user
RET=$?
if [[ RET -eq 4 ]]; then
existing_user=$(id $uid -u)
existing_user_new_id=$(shuf -i 101-498 -n 1)
usermod -u $existing_user_new_id $existing_user
sleep 1
elif [[ RET -ne 0 ]]; then
sleep 5
fi
done
else
echo "syncing user $user, not found in host"
fi
done
for group in ${host_groups//,/ }; do
echo "syncing group $group"
gid=$(grep "^$group:" /etc/group.src | awk -F: '{print $3}')
if [ ! -z "$gid" ]; then
RET=1
while [[ RET -ne 0 ]]; do
groupmod -g $gid $group
RET=$?
if [[ RET -eq 4 ]]; then
existing_group=$(getent group $gid | awk -F: '{print $1}')
existing_group_new_id=$(shuf -i 1-32766 -n 1)
groupmod -g $existing_group_new_id $existing_group
sleep 1
elif [[ RET -ne 0 ]]; then
sleep 5
fi
done
else
echo "syncing group $group, not found in host"
fi
done
Также доступно здесь: https://github.com/Varun-garg/docker-sync-ids.
Чтобы разделить папку между хостом Docker и контейнером Docker, попробуйте команду ниже
$ docker run -v "$(pwd):$(pwd)" -i -t ubuntu
Флаг -v монтирует текущий рабочий каталог в контейнер. Если каталог хоста тома, подключенного с привязкой, не существует, Docker автоматически создаст этот каталог на хосте,
Однако здесь есть 2 проблемы:
- Вы не можете записать на том, подключенный, если вы не являетесь пользователем root, поскольку общий файл будет принадлежать другому пользователю на хосте,
- Вы не должны запускать процесс внутри ваших контейнеров как root, но даже если вы запустили его как пользователя с жестким кодом, он все равно не будет соответствовать пользователю на вашем ноутбуке /Jenkins,
Решение:
Контейнер: создайте пользователя, скажем "testuser", по умолчанию идентификатор пользователя будет начинаться с 1000,
Хост: создайте группу, скажем "testgroup" с идентификатором группы 1000, и добавьте каталог в новую группу (testgroup
Если вы делаете это для разработки , хорошим решением будет использовать:
- Исходный код должен принадлежать пользователю контейнера. (Если возможно, позвольте контейнеру клонировать исходный код.)
- Используйте и сопоставьте папку для хост-пользователя.
Вот как теперь выглядит моя настройка docker-compose:
project:
web/src # Container clones it using init scripts.
web/log
__web__/src # Host user uses this. It's just bindfs mirror.
__web__/log
Я думал над этой проблемой больше года, и
bindfs
это самый простой вариант, с которым я столкнулся. Кроме клонирования, нет никаких затрат времени выполнения.
В моем конкретном случае я пытался собрать свой пакет узла с образом докера узла, чтобы мне не пришлось устанавливать npm на сервере развертывания. Это работало хорошо до тех пор, пока вне контейнера и на хост-машине я не попытался переместить файл в каталог node_modules, который создал образ докера узла, на который мне было отказано в разрешениях, поскольку он принадлежал пользователю root. Я понял, что могу обойти это, скопировав каталог из контейнера на хост-машину. Через докер документы...
Файлы, скопированные на локальный компьютер, создаются с помощью UID:GID пользователя, который вызвал команду docker cp.
Это код bash, который я использовал для смены владельца каталога, созданного в контейнере Docker.
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
При необходимости вы можете удалить каталог со вторым док-контейнером.
docker run -v $(pwd)/build:/build -w="/build" --name $RMR_IMAGE node:6-slim rm -r node_modules
Если вы используете Docker Compose, запустите контейнер в превалирующем режиме:
wordpress:
image: wordpress:4.5.3
restart: always
ports:
- 8084:80
privileged: true