Recomendações para executar o Buildah dentro de um contêiner

Qual é a vantagem de separar o ambiente de execução de contêineres em componentes de ferramenta distintas? Em particular, o fato de que essas ferramentas podem ser combinadas para se protegerem mutuamente.

Recomendações para executar o Buildah dentro de um contêiner

Muitos se sentem atraídos pela ideia de compilar imagens OCI de contêineres dentro de Kubernetes ou um sistema semelhante. Suponha que temos um CI/CD que está constantemente criando imagens, então algo como Red Hat OpenShift/Kubernetes было бы весьма полезно с точки зрения распределения нагрузки при сборке. До недавних пор большинство людей просто давали контейнерам доступ к Docker-сокету и разрешали выполнять команду docker build. Mostramos há alguns anos, que isso é muito inseguro, de fato, é até pior do que dar acesso root ou sudo sem senha.

Portanto, as pessoas estão constantemente tentando rodar o Buildah dentro de um contêiner. Resumindo, criamos exemplo nossa visão sobre a melhor forma de executar o Buildah dentro de um contêiner e disponibilizamos as imagens correspondentes em quay.io/buildah. Vamos começar…

Configuração

Essas imagens foram construídas a partir de Dockerfiles que podem ser encontrados no repositório do Buildah na pasta buildahimage.
Aqui vamos examinar a versão estável do Dockerfile.

# stable/Dockerfile
#
# Build a Buildah container image from the latest
# stable version of Buildah on the Fedoras Updates System.
# https://bodhi.fedoraproject.org/updates/?search=buildah
# This image can be used to create a secured container
# that runs safely with privileges within the container.
#
FROM fedora:latest

# Don't include container-selinux and remove
# directories used by dnf that are just taking
# up space.
RUN yum -y install buildah fuse-overlayfs --exclude container-selinux; rm -rf /var/cache /var/log/dnf* /var/log/yum.*

# Adjust storage.conf to enable Fuse storage.
RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf

Em vez de OverlayFS, implementado no nível do kernel Linux do host, usamos dentro do contêiner o programa fuse-overlay, uma vez que, no momento, o OverlayFS só consegue montar se lhe forem dadas permissões SYS_ADMIN através das capacidades do Linux. E queremos executar nossos contêineres Buildah sem quaisquer privilégios de root. O fuse-overlay funciona bem rapidamente e tem melhor desempenho do que o driver de armazenamento VFS. Note que ao executar um contêiner Buildah usando o Fuse, é necessário fornecer o dispositivo /dev/fuse.

podman run --device /dev/fuse quay.io/buildahctr ...
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock

Em seguida, criamos um diretório para repositórios adicionais. Container/storage suporta o conceito de montagem de repositórios adicionais somente leitura de imagens. Por exemplo, é possível configurar uma área de armazenamento overlay em uma máquina e, em seguida, montá-la em outra máquina usando NFS, utilizando as imagens dela sem precisar baixar via pull. Precisamos desse armazenamento para poder conectar um repositório de imagens do host como um volume e utilizá-lo dentro do contêiner.

# Set up environment variables to note that this is
# not starting with user namespace and default to
# isolate the filesystem with chroot.
ENV _BUILDAH_STARTED_IN_USERNS="" BUILDAH_ISOLATION=chroot

E finalmente, ao usar a variável de ambiente BUILDAH_ISOLATION, dizemos que, por padrão, o contêiner Buildah deve ser executado com isolamento chroot. Um isolamento adicional não é necessário aqui, já que já estamos trabalhando em um contêiner. Para que o Buildah crie seus próprios contêineres com separação de namespaces, é necessário privilégios SYS_ADMIN, e para isso teremos que relaxar as regras do SELinux e SECCOMP para o contêiner, o que contradiz nossa configuração de realizar a construção em um contêiner seguro.

Executando Buildah dentro do contêiner

O esquema de imagem do contêiner Buildah discutido acima permite variar de forma flexível as maneiras de iniciar tais contêineres.

Velocidade versus segurança

A segurança da computação é sempre um compromisso entre a velocidade de execução do processo e a quantidade de proteção em torno dele. Esta afirmação é verdadeira também na construção de contêineres, portanto, abaixo veremos as opções para esse compromisso.

A imagem de contêiner discutida acima manterá seu armazenamento em /var/lib/containers. Portanto, precisamos montar o conteúdo nessa pasta, e a forma como faremos isso afetará bastante a velocidade de construção das imagens dos contêineres.

Vamos considerar três opções.

Opção 1. Se a máxima segurança for necessária, pode-se criar uma pasta própria para cada contêiner para containers/image e conectá-la ao contêiner via volume-mount. Além disso, colocar o diretório de contexto dentro do próprio contêiner, na pasta /build:

# mkdir /var/lib/containers1
# podman run -v ./build:/build:z -v /var/lib/containers1:/var/lib/containers:Z quay.io/buildah/stable
buildah  -t image1 bud /build
# podman run -v /var/lib/containers1:/var/lib/containers:Z quay.io/buildah/stable buildah  push  image1 registry.company.com/myuser
# rm -rf /var/lib/containers1

Segurança. Um Buildah funcionando nesse contêiner possui a máxima segurança: não é concedido nenhum privilégio root através de capabilities, e todas as restrições do SECOMP e SELinux se aplicam a ele. Tal contêiner pode até ser executado com isolamento de User Namespace, adicionando uma opção como —uidmap 0:100000:10000.

Desempenho. No entanto, o desempenho aqui é mínimo, pois qualquer imagem de registros de contêiner é copiada para o host a cada vez, e o cache não funciona de forma nenhuma. Ao concluir seu trabalho, o contêiner Buildah deve enviar a imagem ao registro e destruir o conteúdo no host. Quando a imagem do contêiner for construída novamente, será necessário baixá-la novamente do registro, pois até lá já não restará nada no host.

Opção 2. Se for necessário um desempenho a nível do Docker, é possível montar diretamente o container/storage do host dentro do contêiner.

# podman run -v ./build:/build:z -v /var/lib/containers:/var/lib/containers --security-opt label:disabled quay.io/buildah/stable buildah  -t image2 bud /build
# podman run -v /var/lib/containers:/var/lib/containers --security-opt label:disabled  quay.io/buildah/stable buildah push image2 registry.company.com/myuser

Segurança. Este é o método menos seguro de montar contêineres, pois permite que o contêiner modifique o armazenamento no host, potencialmente inserindo uma imagem maliciosa no Podman ou CRI-O. Além disso, será necessário desabilitar a separação do SELinux para que os processos no contêiner Buildah possam interagir com o armazenamento no host. Vale a pena notar que esta opção ainda é melhor que o soquete do Docker, pois o contêiner é bloqueado pelas demais funções de segurança e não pode simplesmente iniciar qualquer contêiner no host.

Desempenho. Aqui, ela é máxima, pois o cache é totalmente utilizado. Se o Podman ou CRI-O já baixaram a imagem necessária no host, o processo Buildah dentro do contêiner não precisará baixá-la novamente, e as compilações subsequentes com base nessa imagem também poderão utilizar o que está no cache.

Opção 3. A essência desse método é combinar várias imagens em um único projeto com uma pasta compartilhada para as imagens do contêiner.

# mkdir /var/lib/project3
# podman run --security-opt label_level=s0:C100, C200 -v ./build:/build:z 
-v /var/lib/project3:/var/lib/containers:Z quay.io/buildah/stable buildah  -t image3 bud /build
# podman run --security-opt label_level=s0:C100, C200 
-v /var/lib/project3:/var/lib/containers quay.io/buildah/stable buildah push image3  registry.company.com/myuser

Neste exemplo, não estamos removendo a pasta do projeto (/var/lib/project3) entre as execuções, então todas as compilações subsequentes no projeto se beneficiam do cache.

Segurança. Algo intermediário entre as opções 1 e 2. Por um lado, os contêineres não têm acesso ao conteúdo no host e, consequentemente, não podem inserir algo negativo no armazenamento de imagens do Podman/CRI-O. Por outro lado, dentro de seu projeto, o contêiner pode interferir na construção de outros contêineres.

Desempenho. Aqui, é pior do que usar um cache compartilhado no nível do host, pois não é possível usar imagens que já foram baixadas anteriormente por meio do Podman/CRI-O. No entanto, depois que o Buildah baixar a imagem, essa imagem pode ser usada em quaisquer construções subsequentes dentro do projeto.

Armazenamentos adicionais

Na containers/storage Há uma coisa incrível chamada armazéns adicionais (additional stores), que permite que, ao iniciar e montar contêineres, os motores de contêineres possam usar armazéns externos de imagens em modo somente leitura. Basicamente, é possível adicionar um ou mais armazéns 'só para leitura' ao arquivo storage.conf, para que, ao iniciar o contêiner, o motor de contêineres possa procurar a imagem necessária neles. Ele fará o download da imagem do registro apenas se não encontrá-la em nenhum desses armazéns. O motor de contêineres poderá gravar apenas nos armazéns que têm permissão para escrita...

Se você rolar para cima e olhar o Dockerfile que estamos usando para construir a imagem quay.io/buildah/stable, encontrará as seguintes linhas:

# Adjust storage.conf to enable Fuse storage.
RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock

Na primeira linha, modificamos /etc/containers/storage.conf dentro da imagem do contêiner, instruindo o driver de armazenamento a usar 'additionalimagestores' na pasta /var/lib/shared. Na linha seguinte, criamos uma pasta compartilhada e adicionamos alguns arquivos de bloqueio para evitar conflitos com containers/storage. Basicamente, estamos criando um armazém vazio de imagens de contêineres.

Se montarmos containers/storage um nível acima dessa pasta, o Buildah poderá usar as imagens.

Agora, voltemos à Variante 2 discutida anteriormente, onde o contêiner Buildah pode ler e gravar em containers/store nos hosts e, consequentemente, tem o máximo de desempenho devido ao cache das imagens no nível do Podman/CRI-O, mas oferece segurança mínima, uma vez que pode escrever diretamente nos armazéns. Agora, ao adicionar armazéns adicionais, teremos o melhor dos dois mundos.

# mkdir /var/lib/containers4
# podman run -v ./build:/build:z -v /var/lib/containers/storage:/var/lib/shared:ro -v  /var/lib/containers4:/var/lib/containers:Z  quay.io/buildah/stable 
 buildah  -t image4 bud /build
# podman run -v /var/lib/containers/storage:/var/lib/shared:ro  
-v >/var/lib/containers4:/var/lib/containers:Z quay.io/buildah/stable buildah push image4  registry.company.com/myuser
# rm -rf /var/lib/continers4

Observe que /var/lib/containers/storage do host está montado em /var/lib/shared dentro do contêiner em modo somente leitura. Portanto, ao trabalhar no contêiner, o Buildah pode usar quaisquer imagens que já foram baixadas pelos meios do Podman/CRI-O (olá, velocidade), mas pode gravar apenas em seu próprio armazém (olá, segurança). Também note que isso é feito sem desativar a separação do SELinux para o contêiner.

Ponto importante

De forma alguma deve-se remover quaisquer imagens do armazém subjacente. Caso contrário, o contêiner Buildah pode falhar.

E isso não é tudo, há muitas outras vantagens.

As possibilidades de armazenamento adicional não se limitam apenas ao cenário descrito acima. Por exemplo, é possível armazenar todas as imagens de contêiner em um armazenamento de rede compartilhado e conceder acesso a todos os contêineres Buildah. Suponha que tenhamos centenas de imagens que nosso sistema CI/CD usa regularmente para construir imagens de contêiner. Concentramos todas essas imagens em um único host de armazenamento e, em seguida, usando os meios preferidos de armazenamento em rede (NFS, Gluster, Ceph, ISCSI, S3...), abrimos o acesso a esse armazenamento para todos os nós do Buildah ou Kubernetes.

Agora, basta montar esse armazenamento de rede no contêiner Buildah em /var/lib/shared e pronto, os contêineres Buildah não precisarão mais baixar imagens via pull. Assim, eliminamos a fase de pré-preenchimento e estamos prontos para implantar contêineres imediatamente.

E claro, isso pode ser utilizado dentro de um sistema Kubernetes ou infraestrutura de contêiner já existente, permitindo executar e rodar contêineres em qualquer lugar sem ter que baixar imagens via pull. Além disso, o registro de contêiner, ao receber um pedido de push para o upload de uma imagem atualizada, pode automaticamente enviar essa imagem para o armazenamento de rede compartilhado, onde se torna instantaneamente acessível a todos os nós.

Os tamanhos das imagens de contêiner podem às vezes alcançar vários gigabytes. A funcionalidade de armazenamento adicional permite evitar a clonagem dessas imagens nos nós e torna o lançamento de contêineres praticamente instantâneo.

Além disso, neste momento estamos trabalhando em uma nova função de montagens de volume overlay, que tornará a construção de contêineres ainda mais rápida.

Conclusão

Executar Buildah dentro de um contêiner em um ambiente Kubernetes/CRI-O, Podman ou até mesmo no Docker é absolutamente viável, além de ser simples e muito mais seguro do que usar docker.socket. Aumentamos consideravelmente a flexibilidade ao trabalhar com imagens, e agora você pode executá-las de várias maneiras para otimizar a segurança e o desempenho.

A funcionalidade de armazenamento adicional permite acelerar ou até mesmo eliminar completamente o download de imagens nos nós.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster