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

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

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

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

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

  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 проблемы:

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

Решение:

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

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

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

  1. Исходный код должен принадлежать пользователю контейнера. (Если возможно, позвольте контейнеру клонировать исходный код.)
  2. Используйте и сопоставьте папку для хост-пользователя.

Вот как теперь выглядит моя настройка 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
Другие вопросы по тегам