Каков (лучший) способ управления разрешениями для общих томов Docker?

345

Некоторое время я играл с Docker и продолжаю находить ту же проблему при работе с постоянными данными.

Я создаю свой Dockerfileи выставляю том или использую --volumes-fromдля монтирования папки хоста внутри моего контейнера .

Какие разрешения я должен применить к общему объему на хосте?

Я могу придумать два варианта:

  • До сих пор я предоставил всем доступ на чтение / запись, чтобы я мог писать в папку из контейнера Docker.

  • Сопоставьте пользователей с хоста в контейнере, чтобы я мог назначить более детальные разрешения. Не уверен, что это возможно, хотя и не нашел много об этом. Пока что все, что я могу сделать, это запустить контейнер как некоторый пользователь:, docker run -i -t -user="myuser" postgresно у этого пользователя другой UID, чем у моего хоста myuser, поэтому разрешения не работают. Кроме того, я не уверен, что сопоставление пользователей будет представлять некоторую угрозу безопасности.

Есть ли другие альтернативы?

Как вы, ребята, работаете с этой проблемой?

Xabs
источник
Оформить этот ответ из аналогичного вопроса .
Батандва
1
Возможно, вас также заинтересует эта тема, в которой эта тема обсуждается более подробно: groups.google.com/forum/#!msg/docker-user/cVov44ZFg_c/…
btiernay
1
Вы видели container42.com/2014/11/18/data-only-container-madness ?
Филипп
На данный момент команда Docker не планирует реализовывать собственное решение для монтирования директорий хоста как тома с указанным uid / gid. Смотрите мой комментарий и ответы по этому вопросу: github.com/docker/docker/issues/7198#issuecomment-230636074
Quinn Comendant

Ответы:

168

ОБНОВЛЕНИЕ 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/graphiteDockerfile также должен получить тот же UID / GID, поэтому она может выглядеть следующим образом :

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 при его запуске, но имея их как отдельные изображения ИМО понятнее).

Теперь предположим, что вы хотите редактировать что-то в папке данных. Поэтому вместо того, чтобы привязывать монтирование тома к хосту и редактировать его там, создайте новый контейнер для этой работы. Давай называть это 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 для запуска команды от имени правильного пользователя. Если кто-то заинтересован в этом подходе, пожалуйста, прокомментируйте, и я могу предоставить ссылки на образец, используя этот подход.

Раман
источник
35
Боюсь, что это не решение, поскольку у вас будет та же проблема с контейнерами только для данных. В конце концов, эти контейнеры будут использовать тома, совместно используемые с хоста, поэтому вам все равно придется управлять разрешениями для этих общих папок.
Xabs
2
Помните, что мне может понадобиться отредактировать папку данных с моего хоста (то есть: удалить тестовый графитовый ключ, удалить мою тестовую домашнюю папку JIRA или обновить ее до последней производственной резервной копии ...). Насколько я понимаю из вашего комментария, я должен заниматься такими вещами, как обновление данных JIRA через третий контейнер. В любом случае, какие разрешения вы бы применили к новой папке данных /data/newcontainer? Я предполагаю, что вы работаете dockerот имени пользователя root (возможно ли это не делать?) Кроме того, есть ли разница в этих разрешениях, если данные монтируются непосредственно в основной контейнер или через контейнер только для данных ?
Xabs
2
Спасибо за ваш сложный ответ. Проверим это, как только у меня будет шанс. Кроме того, хорошие ссылки как на ваш блог, так и на использование минимальных изображений для контейнеров данных .
Xabs
3
Единственная проблема этого подхода - его очень легко удалить контейнер по ошибке. Представьте, что это ваш контейнер данных. Я думаю (CMIIW) данные все еще будут /var/lib/dockerгде-то, но все еще огромная боль
Lolski
3
«Вы можете просто использовать контейнер данных в качестве« ссылки / дескриптора »и установить владельца / права доступа в другом контейнере с помощью chown в точке входа» ... @Raman: Это раздел, который наконец спас меня после многочисленных проблем с разрешениями, не разобрался. Использование сценария точки входа и установка разрешений в этом работает для меня. Спасибо за ваше подробное объяснение. Это лучшее, что я нашел в интернете.
Вандерстаай
59

Очень элегантное решение можно увидеть на официальном изображении Redis и в целом на всех официальных изображениях.

Описано в пошаговом процессе:

  • Создайте пользователя / группу redis перед чем-либо

Как видно из комментариев Dockerfile:

сначала добавьте нашего пользователя и группу, чтобы убедиться, что их идентификаторы назначаются последовательно, независимо от того, какие зависимости добавляются

  • Установить Gosu с Dockerfile

gosu - это альтернатива su/ sudoдля легкого отказа от пользователя root. (Redis всегда запускается с redisпользователем)

  • Настройте /dataгромкость и установите его как рабочий каталог

Настроив том / data с помощью VOLUME /dataкоманды, теперь у нас есть отдельный том, который может быть либо док-томом, либо подключенным к каталогу хоста.

Конфигурирование его как workdir ( WORKDIR /data) делает его каталогом по умолчанию, из которого выполняются команды.

  • Добавьте файл docker-entrypoint и установите его как ENTRYPOINT с CMD-редис-сервером по умолчанию

Это означает, что все выполнения контейнера будут выполняться через сценарий docker-entrypoint, и по умолчанию команда, которую нужно запустить, - это redis-server.

docker-entrypointскрипт, выполняющий простую функцию: измените владельца текущего каталога (/ data) и перейдите rootк redisпользователю для запуска redis-server. (Если выполненная команда не является redis-сервером, она будет запущена непосредственно).

Это имеет следующий эффект

Если каталог / data привязан к хосту, точка входа docker подготовит права пользователя перед запуском redis-server под redisпользователем.

Это дает вам ощущение простоты, что для запуска контейнера в любой конфигурации тома не требуется никаких настроек.

Конечно, если вам нужно разделить объем между различными изображениями, вам нужно убедиться, что они используют один и тот же идентификатор пользователя / groupid, в противном случае последний контейнер перехватит права пользователя из предыдущего.

Димитрис
источник
11
Принятый ответ является информативным, но он привел меня на недельный путь разочарований в разрешениях на поиск этого ответа, который фактически обеспечивает канонический способ решения проблемы.
m0meni
3
Очень хорошо объяснено и здесь: denibertovic.com/posts/handling-permissions-with-docker-volumes
Херауд
Так? Как сделать том для записи из докера? chownэто внутри ENTRYPOINTскрипта?
Gherman
34

Возможно, это не лучший способ для большинства обстоятельств, но он еще не упоминался, поэтому, возможно, он кому-то поможет.

  1. Привязать том хоста

    Host folder FOOBAR is mounted in container /volume/FOOBAR

  2. Измените скрипт запуска вашего контейнера, чтобы найти GID нужного вам тома

    $ TARGET_GID=$(stat -c "%g" /volume/FOOBAR)

  3. Убедитесь, что ваш пользователь входит в группу с этим 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). Кроме того, это кричит взломать работу ;-)

Если вы хотите быть хардкорным, вы, очевидно, можете расширить его многими способами - например, искать все группы в любых подфайлах, нескольких томах и т. Д.

Хэми
источник
4
Предназначено ли это для чтения файлов с подключенного тома? Я ищу решение для записи файлов без того, чтобы они принадлежали другому пользователю, а не тому, кто создал Docker-контейнер.
ThorSummoner
Я использую этот подход с 15 августа. Все было ок. Только разрешения файлов, созданных внутри контейнера, были различны. Оба пользователя (внутри и снаружи контейнера) имеют права собственности на свои файлы, но оба имеют доступ для чтения, потому что они принадлежат к той же группе, созданной этим решением. Проблема началась, когда вариант использования навязал права на запись в общие файлы. Большая проблема заключалась в том, что общий объем имел git-файлы (том для тестирования исходных файлов dev в том же производственном контексте). Git начал предупреждать о проблеме доступа к общему коду.
yucer
Я думаю, что лучше вычленить информацию $TARGET_GIDбыло бы использовать grep ':$TARGET_GID:', в противном случае , если контейнер имеет, например , GID 10001 и ваш хозяин 1000, эта проверка будет проходить , но оно не должно.
Робудсон
16

Хорошо, это теперь отслеживается в проблеме докера # 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

ОБНОВЛЕНИЕ Я в настоящее время более склонен к ответу Хэми

Лео Галлуччи
источник
1
используйте команду id -u <username>, id -g <username>, id -G <username>чтобы получить идентификатор пользователя и идентификатор группы пользователя , определенного вместо
lolski
15
Это разрушает переносимость контейнера между хостами.
Раман
2
Проблема # 7198 докера пришла к выводу, что они не будут реализовывать нативное решение для этого. Смотрите мой комментарий и ответы на github.com/docker/docker/issues/7198#issuecomment-230636074
Quinn Comendant
12

Как и вы, я искал способ отобразить пользователей / группы из хоста в докер-контейнеры, и это самый короткий путь, который я нашел до сих пор:

  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 (как и для групп) с хостом. Теперь вы можете настроить параметры пользователя / группы для вашего сервиса внутри контейнера, как если бы он работал в вашей хост-системе.

Когда вы решите переместить свой контейнер на другой хост, вам просто нужно изменить имя пользователя в файле конфигурации службы на то, что у вас есть на этом хосте.

Алекс Мызников
источник
Это отличный ответ, очень простой, если вы хотите запускать контейнеры, которые обрабатывают файлы в базовой системе, не подвергая остальную часть системы.
Икарито
Это мой любимый ответ. Кроме того, я видел аналогичную рекомендацию в другом месте с командой docker run, в которой вы передаете свое текущее имя пользователя / группы через, -u $( id -u $USER ):$( id -g $USER )и вам больше не нужно беспокоиться об имени пользователя. Это хорошо работает для локальных сред разработки, где вы хотите создавать файлы (например, двоичные файлы), к которым у вас есть права на чтение / запись по умолчанию.
matthewcummings516
5

Вот подход, который все еще использует контейнер только для данных, но не требует его синхронизации с контейнером приложения (с точки зрения наличия того же uid / gid).

Предположительно, вы хотите запустить какое-то приложение в контейнере от имени пользователя US $ без полномочий 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; $@"
Итан
источник
5

Для обеспечения безопасности и изменения корня для Docker-контейнера попробуйте использовать Docker host --uidmapи --private-uidsопции

https://github.com/docker/docker/pull/4572#issuecomment-38400893

Также вы можете удалить несколько возможностей ( --cap-drop) в контейнере Docker для безопасности

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

размонтировать
источник
Я использую Docker 1.3.2 build 39fa2fa (последняя версия) и не вижу ни следов --uidmapни --private-uidsпараметров. Похоже, пиар не сделал этого и не был объединен.
Лео Галлуччи
Это не слияние в ядре, если вы хотите, вы можете использовать его как патч. Теперь возможно только ограничить некоторые возможности и запускать ваше приложение в контейнере от пользователя без полномочий root.
размонтировать
Июнь 2015, и я не вижу этого слияния в Docker 1.6.2. Ваш ответ все еще действителен?
Лео Галлуччи
1
Вопрос еще открыт. Разработчик должен добавить поддержку в версии 1.7. (--root опция) github.com/docker/docker/pull/12648
размонтировать
2
Похоже, разработчики в очередной раз перенесли релиз с этим функционалом. Разработчик Docker «icecrime» говорит "We apparently do have so some of conflicting designs between libnetwork and user namespaces ... and something we'd like to get in for 1.8.0. So don't think we're dropping this, we're definitely going to take a break after all these, and see how we need to reconsider the current design and integration of libnetwork to make this possible. Thanks!" github.com/docker/docker/pull/12648 Так что я думаю, что нам следует подождать следующей стабильной версии.
учета
4

Мой подход заключается в том, чтобы обнаружить текущий 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"
muni764
источник
3

Базовое изображение

Используйте это изображение: 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
Эдуардо Куомо
источник
0

В моем конкретном случае я пытался собрать свой пакет узла с образом докера узла, чтобы мне не пришлось устанавливать 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
johnklawlor
источник
0

Чтобы разделить папку между хостом Docker и контейнером Docker, попробуйте команду ниже

$ docker run -v "$ (pwd): $ (pwd)" -i -t ubuntu

Флаг -v монтирует текущий рабочий каталог в контейнер. Если каталог хоста подключенного тома тома не существует, Docker автоматически создаст этот каталог на хосте для вас,

Однако здесь есть 2 проблемы:

  1. Вы не можете записать на том, подключенный, если вы не являетесь пользователем root, так как общий файл будет принадлежать другому пользователю на хосте,
  2. Вы не должны запускать процесс внутри ваших контейнеров как root, но даже если вы запускаете его как пользователя с жестким кодом, он все равно не будет соответствовать пользователю на вашем ноутбуке / Jenkins,

Решение:

Контейнер: создайте пользователя, скажем «testuser», по умолчанию идентификатор пользователя будет начинаться с 1000,

Хост: создайте группу, скажем «testgroup» с идентификатором группы 1000, и добавьте каталог в новую группу (testgroup

Правин Мутусами
источник
-5

Если вы используете Docker Compose, запустите контейнер в превалирующем режиме:

wordpress:
    image: wordpress:4.5.3
    restart: always
    ports:
      - 8084:80
    privileged: true
Ренато Аленкар
источник
2
Это может упростить монтирование томов, но ... Wordpress запущен в привилегированном режиме? Это ужасная идея - она ​​требует компромисса. wpvulndb.com/wordpresses/453
Колин Харрингтон