Compose: O Docker Compose monta volumes nomeados como 'root' exclusivamente

Criado em 5 abr. 2016  ·  33Comentários  ·  Fonte: docker/compose

É sobre volumes nomeados (sem "contêiner de volume de dados", sem "volumes de") e docker-compose.yml.

O objetivo aqui é usar docker-compose para gerenciar dois serviços 'appserver' e 'server-postgresql' em dois contêineres separados e usar o recurso "volumes:" docker-compose.yml para tornar os dados do serviço 'server-postgresql' persistentes .

O Dockerfile para 'server-postgresql' tem a seguinte aparência:

FROM        ubuntu:14.04
MAINTAINER xxx

RUN apt-get update && apt-get install -y [pgsql-needed things here]
USER        postgres
RUN         /etc/init.d/postgresql start && \
            psql --command "CREATE USER myUser PASSWORD 'myPassword';" && \
            createdb -O diya diya
RUN         echo "host all  all    0.0.0.0/0  md5" >> /etc/postgresql/9.3/main/pg_hba.conf
RUN         echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf
CMD         ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"]

E o docker-compose.yml tem esta aparência:

version: '2'
services:
    appserver:
        build: appserver
        depends_on:
            - server-postgresql
        links:
            - "server-postgresql:serverPostgreSQL"
        ports:
            - "1234"
            - "1235"
        restart: on-failure:10
    server-postgresql:
        build: serverPostgreSQL
        ports:
            - "5432"
        volumes:
            - db-data:/volume_data
        restart: on-failure:10
volumes:
    db-data:
        driver: local

Então eu começo tudo com docker-compose up -d , entro no meu container server-postgresql com docker-compose exec server-postgresql bash , um rápido ls revela /volume_data , eu então cd nele e tente touch testFile e obteve "permissão negada. O que é normal porque um rápido ls -l mostra que volume_data é propriedade de root:root .

Agora, o que acho que está acontecendo é que, como tenho USER postgres no Dockerfile, quando executo docker-compose exec estou conectado como usuário 'postgres' (e o daemon postgresql é executado como usuário 'postgres' também, por isso não será capaz de escrever para /volume_data ).
Isto é confirmado porque quando eu executar este em vez disso: docker-compose exec --user root server-postgresql bash e repetição para cd /volume_data e touch testFile , ela não funciona (não é um erro de permissão entre o hospedeiro e o recipiente, como às vezes é o caso quando o contêiner monta uma pasta de host, este é um erro de permissão unix típico porque /volume_data é montado como 'root: root' enquanto o usuário 'postgres' está tentando escrever).

Portanto, deve haver uma maneira em docker-compose.yml de montar volumes nomeados como usuário específico, como:

version: '2'
services:
    appserver:
        [...]
    server-postgresql:
        [...]
        volumes:
            - db-data:/volume_data:myUser:myGroup
        [...]
volumes:
    db-data:
        driver: local

A única solução _dirty_ que posso pensar é remover a diretiva USER posgres do Dockerfile e alterar o ENTRYPOINT para que aponte para um "init_script.sh" personalizado (que seria executado como 'root' desde que eu removido USER postgres ), este script mudaria as permissões de /volume_data para que 'postgres' pudesse escrever nele, então su postgres e executaria o daemon postgresql (em primeiro plano). Mas isso é realmente muito sujo, porque vincula o Dockerfile e docker-compose.yml de uma maneira não padrão (o tempo de execução ENTRYPOINT dependeria do fato de que um volume montado é disponibilizado por docker-compose.yml).

arevolumes statu0-triage

Comentários muito úteis

Na verdade, venho aqui com novidades, parece que o que estou tentando alcançar é factível, mas não sei se isso é uma funcionalidade ou um bug. Aqui está porque eu mudei:

Em meu Dockerfile , _antes de mudar para o usuário 'postgres'_, adicionei estes:

# ...
RUN    mkdir /volume_data
RUN   chown postgres:postgres /volume_data

USER postgres
# ...

O que isso faz é criar um diretório / volume_data e alterar suas permissões para que o usuário 'postgres' possa escrever nele.
Esta é a parte do Dockerfile .

Agora eu não mudei nada no docker-compose.yml : então docker-compose ainda cria o Volume Nomeado directory_name_db-data e o monta em /volume_data e as permissões persistiram! .
O que significa que agora, eu tenho meu Volume Nomeado montado no diretório _pre-existente_ /volume_data com as permissões preservadas, para que 'postgres' possa escrever nele.

Então, se esse for o comportamento pretendido ou uma violação de segurança? (No entanto, serve-me neste caso!)

Todos 33 comentários

Não acho que isso seja compatível com o mecanismo do docker, portanto, não há como oferecer suporte no Compose até que seja adicionado à API. No entanto, não acho que seja necessário adicionar esse recurso. Você sempre pode executar o chown dos arquivos para o usuário correto:

version: '2'
services:
  web:
    image: alpine:3.3
    volumes: ['random:/path']

volumes:
  random:
$ docker-compose run web sh
/ # touch /path/foo
/ # ls -l /path
total 0
-rw-r--r--    1 root     root             0 Apr  5 16:11 foo
/ # chown postgres:postgres /path/foo
/ # ls -l /path
total 0
-rw-r--r--    1 postgres postgres         0 Apr  5 16:11 foo
/ # 
$ docker-compose run web sh
/ # ls -l /path
total 0
-rw-r--r--    1 postgres postgres         0 Apr  5 16:11 foo

O problema que você está enfrentando é sobre como inicializar um volume nomeado. Isso é reconhecidamente algo que não é tratado pelo Compose (porque está um pouco fora do escopo), mas você pode usar facilmente o docker cli para inicializar um volume nomeado antes de executar docker-compose up .

Na verdade, eu não tinha certeza se era um problema do Docker ou do Compose, desculpe se o enviei incorretamente.
Este é um plano na API Docker? Devo registrar um problema lá?

Eu entendo a possibilidade de logar manualmente no container e chown -ing o volume para o usuário 'postgres'. Mas o que acontece é que, no meu caso, estou usando o Compose para poder instanciar imediatamente novos contêineres para novos clientes ( docker-compose -p client_name up ) e o Compose criará uma rede personalizada client_name_default , ele criará os contêineres com os nomes client_name _appserver_1 e client_name _server-postgresql_1 e _mais importante_, ele criará o volume client_name_db-data . Tudo o que não preciso fazer manualmente, para que possa ser executado pelo script que trata do registro do cliente.

Com a solução que você descreveu (_manualmente_ "registrar" no contêiner com sh e chown -ing o rótulo do volume), não posso ter um procedimento simples para adicionar novos clientes, e isso deve ser cuidado à mão.

É por isso que acho que esse recurso deve ser implementado. Na API Docker, podemos especificar permissões ro ou rw (para somente leitura ou leitura e gravação) ao montar um volume, acho que devemos ser capazes de especificar user:group .

O que você acha, meu pedido faz sentido?

Na verdade, venho aqui com novidades, parece que o que estou tentando alcançar é factível, mas não sei se isso é uma funcionalidade ou um bug. Aqui está porque eu mudei:

Em meu Dockerfile , _antes de mudar para o usuário 'postgres'_, adicionei estes:

# ...
RUN    mkdir /volume_data
RUN   chown postgres:postgres /volume_data

USER postgres
# ...

O que isso faz é criar um diretório / volume_data e alterar suas permissões para que o usuário 'postgres' possa escrever nele.
Esta é a parte do Dockerfile .

Agora eu não mudei nada no docker-compose.yml : então docker-compose ainda cria o Volume Nomeado directory_name_db-data e o monta em /volume_data e as permissões persistiram! .
O que significa que agora, eu tenho meu Volume Nomeado montado no diretório _pre-existente_ /volume_data com as permissões preservadas, para que 'postgres' possa escrever nele.

Então, se esse for o comportamento pretendido ou uma violação de segurança? (No entanto, serve-me neste caso!)

Acredito que isso foi adicionado no Docker 1.10.x para que os volumes nomeados fossem inicializados a partir do primeiro contêiner que os utilizou. Acho que é um comportamento esperado.

Também estou fazendo volumes nomeados com propriedade sendo definida no Dockerfile e na composição estou adicionando user: postgres para que mesmo PID 1 seja propriedade de um usuário não root.

Docker-compose para volumes tem driver_opts para fornecer opções.
Será muito bom ver opções como chmod , chown mesmo para o motorista local .
E, especialmente, quero que também seja aplicado a diretórios de host criados localmente, se não estiver presente na inicialização.

Relacionado (até certo ponto) https://github.com/moby/moby/pull/28499

Alguém já abriu um problema no projeto Moby?

A resposta de @dnephin não está funcionando. O problema é porque estamos executando o contêiner como um usuário padrão, os comandos chown ou chmod estão falhando, o volume sendo de propriedade do root, um usuário padrão não pode alterar as permissões.

@jcberthon o método sugerido é executar o contêiner com root como o usuário inicial e então usar o comando USER APÓS o chown / chmod para que seja basicamente "eliminando privilégios".

Tudo bem se você estiver no controle da imagem Docker, mas se estiver usando imagens existentes, isso não é realmente uma opção.

@ dragon788 e @micheljung , resolvi meu problema.

Na verdade, o verdadeiro problema é que, em meu Dockerfile, declarei VOLUME e modifiquei a propriedade e as permissões dos arquivos naquele volume. Essas mudanças foram perdidas. Simplesmente movendo a declaração VOLUME para o final do Dockerfile (ou removendo-o, pois é opcional), meu problema é resolvido. As permissões estão corretas.

Então, o erro foi:

FROM blabla
RUN do stuff
VOLUME /vol
RUN useradd foo && chown -R foo /vol
USER foo
CMD ["blabla.sh"]

Os chown no Dockerfile de exemplo acima são perdidos durante a construção porque declaramos VOLUME antes dele. Ao executar o contêiner, o dockerd copia dentro do volume nomeado o conteúdo de /vol antes da declaração VOLUME (portanto, com permissões de root). Portanto, os processos em execução não podem modificar ou alterar as permissões, portanto, mesmo forçar o chown no script blabla.sh não pode funcionar.

Alterando o arquivo para:

FROM blabla
RUN do stuff
RUN useradd foo && chown -R foo /vol
USER foo
VOLUME /vol
CMD ["blabla.sh"]

o problema está resolvido.

@jcberthon, você poderia compartilhar como vincular seu volume / vol com o sistema host em seu docker-compose.yml?

Estou trabalhando com o Docker no Fedora (portanto, SELinux habilitado) e nenhum dos métodos mencionados acima funcionou para mim. Idealmente, eu quero executar aplicativos em meus contêineres no contexto de um usuário (sem root), mas este problema de volume é um bloqueador para isso.

A única solução alternativa que funciona para mim é eliminar o usuário do meu aplicativo e executar / possuir tudo como usuário root.

Olá @renanwilliam e @ egee-irl

Tenho usado a solução mencionada acima em vários sistemas operacionais, incluindo Fedora 26 e CentOS 7, ambos com SELinux reforçado, Ubuntu 16.04, 17.10 e Raspbian 9, todos os três com AppArmor ativado (com uma mistura de plataformas amd64 e armhf).

Como eu disse, agora movi a declaração VOLUME ... no final do meu Dockerfile, mas você pode removê-la completamente, ela não é necessária. O que eu geralmente faço é corrigir o ID do usuário ao criar o usuário no Dockerfile (por exemplo, useradd -u 8002 -o foo ). Então, posso simplesmente reutilizar esse UID no host para dar as permissões adequadas à pasta.

O próximo passo é criar o "pendente" do diretório /vol no host, digamos que seja /opt/mycontainer1/vol , então isso é

$ sudo mkdir -p /opt/mycontainer1/vol
$ sudo chown -R 8002 /opt/mycontainer1/vol
$ sudo chmod 0750 /opt/mycontainer1/vol

Então, ao executar o contêiner como usuário foo, ele poderá gravar no diretório /opt/mycontainer1/vol . Algo como:

$ sudo -u docker-adm docker run --name mycontainer1 -v /opt/mycontainer1/vol:/vol mycontainer1-img

Em hosts baseados em SELinux, você pode querer adicionar a opção :z :Z ao volume para que o Docker marque a pasta apropriadamente. A diferença entre z e Z é que a letra minúscula z marcará o volume para que potencialmente todos os contêineres deste host possam ser permitidos pelo SELinux para acessar esse diretório (mas obviamente somente se você vinculá-lo a outro contêiner), enquanto Z maiúscula o marcará de forma que apenas aquele contêiner específico possa acessar o diretório. Portanto, no Fedora com SELinux, você pode querer tentar:

$ sudo -u docker-adm docker run --name mycontainer1 -v /opt/mycontainer1/vol:/vol:Z mycontainer1-img

Atualização : você pode verificar meu repo aqui https://github.com/jcberthon/unifi-docker Estou usando este método e explicando como configurar o host e executar seu contêiner. Espero que isso possa ajudá-lo a resolver ainda mais seus problemas.

A propósito , peço desculpas a

Portanto, é uma longa história curta para os impacientes:

RUN mkdir /volume_data
RUN chown postgres:postgres /volume_data

Criar o diretório de volume de antemão e um chown resolve isso, pois o volume preservará as permissões do diretório preexistente.

Esta é uma solução pobre, pois não é óbvia (fazer um chown em um Dockerfile e, em seguida, herdar essa propriedade durante a montagem). Expor o proprietário e o controle de grupo no docker-compose e docker CLI seria o caminho menos surpreendente para comandos de estilo unix.

@villasv

Uma pequena dica: mescle os 2 RUN ... em um, isso evita a criação de camadas extras e é uma prática recomendada. Portanto, suas 2 linhas devem ser

RUN mkdir /volume_data && chown postgres:postgres /volume_data

Mas cuidado (como mencionei em um comentário acima) que você precisa fazer o comando RUN ... antes de declarar o volume (ou realmente não declarar o volume) usando VOLUME ... . Se você fizer (como eu fiz) a mudança de propriedade após declarar o volume, essas mudanças não serão registradas e perdidas.

@colbygk seria realmente útil, mas não é assim que o Linux funciona. O Docker usa o namespace de montagem do Linux para criar diferentes hierarquias de diretório único ( / e subpastas), mas AFAIK não há mapeamentos de usuário / grupo ou permissões substituindo atualmente no namespace de montagem do Linux. Essas "montagens" dentro de um contêiner (e que incluem volumes de montagem de ligação) estão em um sistema de arquivos no host (a menos que você use algum outro plug-in de volume do Docker, é claro), e este sistema de arquivos está respeitando a camada VFS do Linux que faz tudo a verificação de permissões de arquivo. Pode até haver no host algum MAC (por exemplo, SELinux, AppArmor, etc.) que pode interferir no acesso de um contêiner aos arquivos dentro do contêiner. Na verdade, se você fizer chroot , poderá encontrar problemas semelhantes ao vincular pastas de montagem dentro do chroot, você também terá o problema de que os processos em execução no ambiente chroot podem ter o UID / GID efetivo incorreto para acessar os arquivos em a montagem de ligação.

Regras simples do Linux (e, na verdade, do Unix também) se aplicam ao contêiner. O truque é ver e entender as possibilidades e limites dos namespaces do Linux hoje, e então fica mais claro como resolver problemas como esse. Eu resolvi tudo usando comandos Unix clássicos.

@jcberthon Obrigado por sua resposta atenciosa:

Eu diria que esse deve ser um problema que é colocado na camada de plug-in conforme você sugere e, portanto, pode se tornar parte do plug-in de manipulador de volume genérico que vem com o Docker. Parece-me muito obscuro / contêiner forçar um recurso externo (externo a um contêiner específico) a aderir a relacionamentos essencialmente estáticos que são definidos na imagem da qual um contêiner é derivado.

Existem outros exemplos desse tipo exato de mapeamento uid / gid que ocorre em outras áreas semelhantes do "unix":

Por favor corrija-me se eu estiver errado; https://github.com/zfsonlinux/zfs/issues/4177 parece ser originado pelo "líder do LXC / LXD" como um problema relacionado ao ZFS no linux que não fornece informações UID / GID corretamente para permitir o mapeamento daqueles em um recipiente quase da maneira _exata_ que estamos discutindo aqui. Olhando de perto em https://github.com/zfsonlinux/zfs/issues/4177 , parece que o tipo de volume zfs realmente já poderia suportar esse mapeamento uid / gid entre namespaces, mas não expõe os controles para fazer isso.

a maioria das pessoas que usam o Docker é para dev / CI, usamos imagens "genéricas", como php / nginx (runner) ou gradle / python (builder), então uma boa solução irá:

  1. não há necessidade de criar / editar Dockerfile para substituir a imagem
  2. use sintaxe simples no arquivo docker-compose yml

Uma vez que podemos decidir facilmente a permissão de gravação de um volume (X: X: OPTION_READ_WRITE), que tal adicionar da mesma forma, o proprietário?

SOURCE:TARGET:RW:OWNER

Estou tendo o mesmo problema. Provavelmente existe uma prática recomendada, e meu método _não_ é:

version: '3.5'
services:
    something:
        image: someimage
        user: '1000'
        expose:
            - 8080
        volumes:
            - dev:/app

volumes:
    dev:

Isso causa EACCES: permission denied, access '/app' .

Como devemos fazer isso? Ou seja, definir um novo volume e conseguir acessá-lo com um usuário não root?

Oi @Redsandro

Seria melhor definir na imagem someimage Docker o UID como 1000 para /app . Ou se você não pode controlar isso, você deve usar para user: ... em seu arquivo de composição o UID ou GID pretendido pelo autor da imagem.

Claro, se o autor da imagem usou UID 0 e você não deseja executar o serviço como root (e ele pode ser executado como um usuário sem privilégios), então levante um problema para o autor da imagem Docker.

uma vez que isso não é algo para o docker gerenciar, você pode criar outro contêiner para provisionar seus volumes logo antes dos contêineres relacionados começarem, por exemplo, usando https://hub.docker.com/r/hasnat/volumes-provisioner

version: '2'
services:
  volumes-provisioner:
    image: hasnat/volumes-provisioner
    environment:
      PROVISION_DIRECTORIES: "65534:65534:0755:/var/data/prometheus/data"
    volumes:
      - "/var/data:/var/data"

  prometheus:
    image: prom/prometheus:v2.3.2
    ports:
      - "9090:9090"
    depends_on:
      - volumes-provisioner
    volumes:
      - "/var/data/prometheus/data:/prometheus/data"

Não entendo por que o Docker não está consertando este, concordo que podemos hackear para propósitos de desenvolvimento, mas na produção, de jeito nenhum!

IMHO podman pode ser executado como um usuário ( aqui ) e provavelmente resolverá isso. Alguém também trabalha em uma solução de composição e a API Podman é intencionalmente compatível com o Docker na maioria das partes.

[podman] pode ajudar algumas pessoas aqui, porque é compatível com o Docker em grandes partes.

Concordo totalmente, infelizmente o podman não funciona no Mac

Concordo totalmente, infelizmente o podman não funciona no Mac


Bem, IMHO nem Docker nem podman nunca funcionarão nativamente. Mas as instalações do Docker no OS / X estão escondendo muito bem as coisas da máquina virtual.
Mas eu concordo que configurar VMs manualmente para ter um sistema de desenvolvimento adequado pode ser doloroso.
Mas está ficando um pouco fora do assunto aqui.

Não sou mais um usuário OS / X, mas acabei de ver que existe um dmg de podman _experimental_ .

Eu acho que algum ecossistema semelhante pode se desenvolver em um futuro próximo, porque já é possível acessar a programática do podman ou mesmo uma composição do podman .

Isso se torna um problema especialmente quando os usuários sem direitos sudo em um cluster compartilhado acidentalmente criam algumas pastas pertencentes ao root por meio de docker-compose e, então, eles próprios não conseguem excluir essas pastas.

Eu também encontrei esse problema. Estamos tentando usar docker no docker conforme orientado no posto de jpetazzo .

Queremos iniciar este contêiner a partir do arquivo docker-compose e poder montar o soquete docker da máquina host na máquina contêiner sob o grupo docker. Isso é para que possamos usar outro usuário que não seja o root para executar o docker de dentro do contêiner.

Atualmente, como não posso especificar a propriedade e permissão dos arquivos de montagem de ligação, isso não é possível.

Coloque meus dois centavos aqui:

Para uma solução "nativa" docker-compose, eu fiz isso, uma vez que a maioria das pessoas já terá alpine em sua biblioteca de imagens:

volumes:
  media:
services:
  mediainit:
    image: alpine
    entrypoint: /bin/sh -c "chown -v nobody:nogroup /mnt/media && chmod -v 777 /mnt/media"
    container_name: mediainit
    restart: "no"
    volumes: 
      - "media:/mnt/media"

Não é o mais seguro dos métodos, é claro, mas apenas os contêineres que têm privilégios concedidos ao contêiner o verão, então não é um grande problema, mas você poderia facilmente torná-lo chown user: user ou fazer alguma fantasia setacl se seu kernel suportar isto.

EDIT: Parece que você deve executar o chown na pasta antes de chmod ou ele não 'pega', pelo menos em meus testes.

a propriedade do volume não está sob controle do docker-compose, portanto, essa discussão deve ocorrer no repositório de projetos moby.

Algumas notas para quem procura aqui uma solução alternativa:
O volume do Docker, quando usado pela primeira vez por um contêiner, obtém seu conteúdo inicial e permissão herdada do contêiner. O que significa que você pode configurar sua imagem assim:

#Dockerfile
FROM alpine
RUN addgroup -S nicolas && adduser -S nicolas -G nicolas
RUN mkdir /foo && chown nicolas:nicolas /foo  
# empty, but owned by `nicolas`. Could also have some initial content
VOLUME /foo  
USER nicolas

Essa imagem, quando executada sem um volume explícito (que será criado propositalmente com um ID aleatório) ou com um volume nomeado que ainda não existe, "propagará" permissões para o volume:

➜  docker run --rm -it -v some_new_volume:/foo myimage
/ $ ls -al /foo
total 8
drwxr-xr-x    2 nicolas  nicolas       4096 Oct 18 08:30 .
drwxr-xr-x    1 root     root          4096 Oct 18 08:30 ..

O mesmo se aplica ao usar volumes declarados em um arquivo de composição:

#docker-compose.yml
version: "3"
services:
  web:
    image: myimage
    command: ls -al /foo
    volumes:
      - db-data:/foo
volumes:
    db-data:

➜  docker-compose up
Creating volume "toto_db-data" with default driver
Creating toto_web_1 ... done
Attaching to toto_web_1
web_1  | total 8
web_1  | drwxr-xr-x    2 nicolas  nicolas       4096 Oct 18 08:30 .
web_1  | drwxr-xr-x    1 root     root          4096 Oct 18 08:37 ..
toto_web_1 exited with code 0

Isso não funcionará se você reconectar um volume que já foi usado por outro contêiner. Alterar a propriedade do volume ou controlar isso no momento da criação deve ser implementado pelo mecanismo ou por drivers de volume com algumas opções específicas. Caso contrário, você terá que contar com alguns truques de chown sugeridos ^.

Espero que isto ajude.
Estou encerrando este problema porque o Compose não tem controle sobre a criação do volume, mas a API do mecanismo exposta.

Esta página foi útil?
0 / 5 - 0 avaliações