Docker: Criando um servidor de imagens privadas

há 1 mês 16

Saudações. Instruções de como instalar o Docker e criar um servidor de hospedagem de imagens prontas para uso privado e autenticado.

Pré-requisitos (consta em outros artigos aqui do blog):

  • Instalação do Linux (Debian, Alpine) e programas básicos;
  • Agente do hypervisor (Q-Emu/KVM ou VMware);
  • Data/hora via NTP;
  • Ajuste fino no kernel Linux;
  • Nome de DNS (FQDN) configurado para uso no LetsEncrypt;
  • Instalar o Docker ou Podman;

1 – Motivos para criar um repositório Docker privado

Algumas vantagens de usar seu próprio repositório de imagens OCI (containers, Docker, Podman, Kubernets) é:

  • Segurança – Imagens públicas podem ser atualizadas para corrigir falhas, mas tambem podem sofrer adição de bugs e problemas mais graves (exploit, vírus, spyware, backdoor, …);
  • Isolamento – Algumas empresas tem políticas ultra-rígidas quanto ao uso de imagens de containers, restringindo a apenas imagens construídas localmente;
  • Agilidade – ao trabalhar com imagens muito grandes (1G+), manter cópias dentro da empresa ajuda no deploy e testes de alta velocidade;
  • Privacidade e propriedade intelectual – Ter imagens privadas com conteúdo sensível restrito aos operadores da empresa, impedindo que os funcionários e operadores da nuvem pública possam analisar, vazar e explorar essas imagens;

Por último, você pode criar seu repositório privado e usá-lo como proxy para repositórios externos. Essa é uma forma de impedir sua empresa de software de usar diretamente repositórios externos e auditar quem e quando usou imagens públicas externas.

2 – Preparando ambiente

Esse bloco de código abaixo (ShellScript) instala o ambiente mínimo. Caso você já tenha instalado tudo até o Docker, ignore-a. Preparativos no Debian, instalando programas e ajustes fundamentais:

Bash

# Atualizar: apt -y update; apt -y upgrade; apt -y dist-upgrade; apt -y autoremove; # Agente do hypervisor: hostnamectl | grep -qi vmware && A=open-vm-tools; hostnamectl | grep -qi kvm && A=qemu-guest-agent; apt-get -y install $A; systemctl enable $A; systemctl start $A; # Ferramentas recomendadas: apt-get -y install mc uuid uuid-runtime; apt-get -y install iproute2 bridge-utils iputils-ping fping; apt-get -y install tcpdump strace htop psmisc iotop; apt-get -y install tar zstd xz-utils zip; apt-get -y install gnupg2 openssl curl wget ca-certificates jq; apt-get -y install openssh-client openssh-server rsync; apt-get -y install nftables conntrack; apt-get -y install apache2-utils; # Data/hora sincronizada no NTP timedatectl set-timezone America/Sao_Paulo; apt-get -y install systemd-timesyncd; ( echo '[Time]'; echo 'NTP=200.160.0.8 200.189.40.8 2001:12ff::8 2001:12f8:9:1::8' echo 'FallbackNTP=200.20.186.75 200.20.186.94 200.20.224.100 200.20.224.101'; echo 'RootDistanceMaxSec=5'; echo 'PollIntervalMinSec=32'; echo 'PollIntervalMaxSec=2048'; echo 'ConnectionRetrySec=30'; echo 'SaveIntervalSec=60'; ) > /etc/systemd/timesyncd.conf; systemctl restart systemd-timesyncd; # Prompt de shell personalizado para diferenciar servidor! export PS1='\[\033[0;99m\][\[\033[0;96m\]\u\[\033[0;99m\]@\[\033[0;93m\]\h\[\033[0;99m\]] \[\033[1;38m\]\w\[\033[0;99m\] \$\[\033[0m\] '; echo "export PS1='$PS1';" > /etc/profile.d/ps1.sh;

Fazer tuning do Kernel e instalando Docker:

Bash

# Tuning de Sysctl wget https://tmsoft.com.br/temp/sysctl-tuning.sh -O /tmp/sysctl.sh; sh /tmp/sysctl.sh; # Baixar script instalador oficial: curl -fsSL get.docker.com -o /tmp/get-docker.sh; sh /tmp/get-docker.sh;

Ambiente Docker mínimo para container com acesso HTTPs (LetsEncrypt+Traefik):

Bash

# Configure seu email para que o LetsEncrypt aceite # gerar seus certificados: EMAIL=seu-email-aqui@dominio-aqui.com.br; # Criar rede de containers # Rede de containers somente ipv4 docker network create -d bridge \ -o "com.docker.network.bridge.name"="br-net-public" \ --subnet 10.249.0.0/16 --gateway 10.249.255.254 \ network_public; # Traefik como proxy-reverso automatizado: # Diretorio de dados persistentes mkdir -p /storage/traefik-app/letsencrypt; mkdir -p /storage/traefik-app/logs; mkdir -p /storage/traefik-app/config; # Renovar execucao (remove, atualiza, reinstala, nao perde dados) docker rm -f traefik-app 2>/dev/null; docker pull traefik:latest; docker run -d --restart=unless-stopped \ --name traefik-app -h traefik-app.intranet.br \ --memory=1g --memory-swap=1g -p 80:80 -p 443:443 -p 8080:8080 \ --network network_public --ip=10.249.255.253 \ \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v /storage/traefik-app/letsencrypt:/etc/letsencrypt \ -v /storage/traefik-app/config:/etc/traefik \ -v /storage/traefik-app/logs:/logs \ \ traefik:latest \ --global.checkNewVersion=false \ --global.sendAnonymousUsage=false \ --api.insecure=true \ --log.level=INFO \ --log.filePath=/logs/error.log \ --accessLog.filePath=/logs/access.log \ --entrypoints.web.address=:80 \ --entrypoints.web.http.redirections.entryPoint.to=websecure \ --entrypoints.web.http.redirections.entryPoint.scheme=https \ --entrypoints.web.http.redirections.entryPoint.permanent=true \ --entrypoints.websecure.address=:443 \ --providers.docker=true \ --providers.file.directory=/etc/traefik \ --certificatesresolvers.letsencrypt.acme.email=$EMAIL \ --certificatesresolvers.letsencrypt.acme.storage=/etc/letsencrypt/acme.json \ --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web;

Agora temos um ambiente Docker para rodar o container de registro (register:2) para armazenar e fornecer nossas imagens de containers.

3 – Criando container de gestão de imagens

O “register” (nome escolhido para uso local) ou “registry” (nome oficial da imagem) é um container que gerencia suas imagens e provê um servidor HTTP com a API de gestão dessas imagens.

Diretório para armazenar nosso registro de imagens no HOST:

Bash

# Diretorios no HOST: mkdir -p /storage/register/auth; mkdir -p /storage/register/certs; mkdir -p /storage/register/config; mkdir -p /storage/register/data;

Criando usuários autorizados a usar nosso registro usando controle do Traefik:

Bash

# Instalar apache2-utils para geração de logins HTTP-AUTH: apt-get -y install apache2-utils; # Garantir a existencia do arquivo com base de usuários: touch /storage/traefik-app/config/register.users; # Criar usuario "admin" com poderes completos (push/pull/delete): # - login: admin # - senha: tulipa htpasswd -Bb /storage/traefik-app/config/register.users admin tulipa; # Criar o usuário "anonymous" em nosso registro para permitir acesso # de leitura (pull only) às nossas imagens: # - login: anonymous # - senha: anonymous htpasswd -Bb /storage/traefik-app/config/register.users anonymous anonymous;

Nota:

  • O traefik foi mapeado no HOST assim:
    • /storage/traefik-app/config > /etc/traefik
  • Dentro do container traefik o arquivo é:
    • /etc/traefik/register.users
  • No HOST o arquivo é:
    • /storage/traefik-app/config/register.users
  • Toda configuração em LABELs dos containers deve considerar o caminho dentro do traefik.
  • Você irá manipular no HOST os usuários e senhas no caminho:
    • /storage/traefik-app/config/register.users

Agora vamos criar o arquivo de configuração do registry para que ele próprio autentique usuários. Crie o arquivo /storage/register/config/config.yml no HOST com o conteúdo abaixo, respeitando a quantidade de espaços (sintaxe YAML):

/storage/register/config/config.yml

version: 0.1 log: fields: service: registry storage: cache: blobdescriptor: inmemory filesystem: rootdirectory: /var/lib/registry delete: enabled: true http: addr: :5000 headers: X-Content-Type-Options: [nosniff] Access-Control-Allow-Origin: ['*'] Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE', 'PUT', 'POST'] Access-Control-Allow-Headers: ['Authorization', 'Accept', 'Cache-Control', 'Content-Type'] auth: htpasswd: realm: basic-realm path: /etc/register.users

Mapeamento:

  • Arquivo no HOST:
    • /storage/register/config/config.yml
  • Arquivo no container “register“:
    • /etc/docker/registry/config.yml

Criar container do register:

Bash

# Nome de DNS publico do container, edite para o nome # que voce configurou FQDN="register.ajustefino.net"; # Diretorio de dados persistentes do container: mkdir -p /storage/register/data; # Arquivo de usuarios e senhas dentro do container Traefik: PWFILE=/etc/traefik/register.users; # Arquivo de usuarios para conferencia do container register: PWHOSTF=/storage/traefik-app/config/register.users; PWLOCAL=/etc/register.users; # Mapeamento do arquivo de config HOSTF_AUTHFILE=/storage/register/config/config.yml; LOCAL_AUTHFILE=/etc/docker/registry/config.yml; # Atualizar e rodar imagem do register: docker rm -f register 2>/dev/null; docker pull "docker.io/registry:2"; docker run -d --restart=always \ --name register -h register.intranet.br \ --network network_public --ip=10.249.255.252 \ --memory=1g --memory-swap=1g \ \ -p "127.0.0.1:5000:5000" \ -p "[::1]:5000:5000" \ \ -v /storage/register/data:/var/lib/registry \ -v $HOSTF_AUTHFILE:$LOCAL_AUTHFILE:ro \ -v $PWHOSTF:$PWLOCAL:ro \ \ --label "traefik.enable=true" \ --label "traefik.http.routers.rtry.rule=Host(\`$FQDN\`)" \ --label "traefik.http.routers.rtry.entrypoints=websecure" \ --label "traefik.http.routers.rtry.tls=true" \ --label "traefik.http.routers.rtry.tls.certresolver=letsencrypt" \ --label "traefik.http.routers.rtry.middlewares=rtry-auth" \ --label "traefik.http.middlewares.rtry-auth.basicauth.usersfile=$PWFILE" \ --label "traefik.http.services.rtry.loadbalancer.server.port=5000" \ \ "docker.io/registry:2"; # Acesso: echo; echo "Acesso:"; echo "Web......: https://$FQDN"; echo;

Vale destacar que o container não terá autenticação quando acessado de dentro do HOST e dos containers vizinhos, para evitar problemas de segurança a porta 5000 do registry será disponibilizado somente no LOCALHOST (127.0.0.1 ou ::1). O acesso externo via Internet depende 100% do Traefik.

4 – Testando funcionamento básico

Testando acesso HTTP (somente via LOCALHOST do HOST, sem senha):

Bash

# Testando acesso localhost: # - IPv4: curl -v "http://127.0.0.1:5000"; # http 200, vazio curl -v "http://localhost:5000/v2/"; # http 200, JSON vazio # - IPv6: curl -v "http://[::1]:5000"; # http 200, vazio curl -v "http://[::1]:5000/v2/"; # http 200, JSON vazio

Testando acesso HTTPs para acesso externo (Internet):

Bash

# Obs: troque pelo nome do seu servidor: FQDN="register.ajustefino.net"; # Testando: curl "https://$FQDN"; # 401 Unauthorized # O retorno "401 Unauthorized" é correto, significa que o Traefik só permite # acesso com autenticacao ao container do registry. # Testando acesso com usuário anonymous senha anonymous: curl -s -u "anonymous:anonymous" "https://$FQDN"; # Retorno esperado: HTTP/2 200, sem conteudo. curl -s -u "anonymous:anonymous" "https://$FQDN/v2/"; # Retorno esperado: HTTP/2 200, JSON vazio. curl -s -u "anonymous:anonymous" "https://$FQDN/v2/_catalog"; # Retorno esperado: HTTP/2 200, JSON vazio (se nao houver imagens registradas)

5 – Criar uma imagem, hospedar e distribuir

Vamos criar uma imagem básica de exemplo no nosso Docker e em seguida hospedá-la no nosso registry para que os usuários externos possam usá-la em seus servidores.

A princípio, vamos apenas criar uma imagem local (não relacionada com o registry):

Bash

# Criar um projeto de imagem docker para teste: mkdir -p /tmp/hello-world-test; cd /tmp/hello-world-test; ( echo 'FROM debian:bookworm'; echo 'RUN (apt -y update; apt -y upgrade; apt -y dist-upgrade; )'; echo 'RUN (apt -y install supervisor; )'; echo 'WORKDIR /root'; echo -n 'CMD ['; echo -n '"/usr/bin/supervisord",'; echo -n '"--nodaemon",'; echo -n '"-c","/etc/supervisor/supervisord.conf"'; echo -n ']'; echo; ) > /tmp/hello-world-test/Dockerfile; # Construir imagem chamada 'hello-world-test', tag: 'hello-world-test:latest': cd /tmp/hello-world-test; docker build . -t hello-world-test; # Colocar TAG de versão especifica '1.2.3': # - obs: vc pode colocar mais tags se desejar (1.2, 1.2.3beta, 1.2.3alpha, ...): docker tag hello-world-test hello-world-test:1.2.3; # Conferindo imagem local: docker image ls; docker image ls hello-world-test; docker image ls hello-world-test:latest; docker image ls hello-world-test:1.2.3; # Conferindo detalhes do historico de construcao imagem local: docker image history hello-world-test; docker image history hello-world-test:latest; docker image history hello-world-test:1.2.3; # Conferindo todos os metadados da imagem local: docker image inspect hello-world-test; docker image inspect hello-world-test:latest; docker image inspect hello-world-test:1.2.3;

Conectar Docker local no servidor registry – fazendo login no repositório:

Bash

# Login como admin (push/pull) docker login localhost:5000 # Username: admin # Password: [senha do admin], padrao:tulipa # WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'. # Configure a credential helper to remove this warning. See # https://docs.docker.com/go/credential-store/ # Login Succeeded # Visualizar servidores de imagens conectados no Docker local: cat ~/.docker/config.json; # { # "auths": { # "localhost:5000": { # "auth": "YWRtaW46dHVsaXBh" # } # } # } # Caso deseje para de usar o registry local, execute: #- docker logout localhost:5000;

Agora vamos enviar nossa imagem para o registro central:

Bash

# Taggear uma imagem local para marcar informacao de tag vinculada no # registry (via acesso HOST > container) - Acao local docker tag hello-world-test:latest localhost:5000/hello-world-test:latest # Fazer push (upload para o registry - requer usuario com poder de admin) docker push localhost:5000/hello-world-test:latest # The push refers to repository [localhost:5000/hello-world-test] # 5f70bf18a086: Pushed # 5848ef4a0019: Pushing [======================> ] 24.84MB/55.41MB # 03bbca755e3f: Pushed # 175a19836175: Pushing [==========> ] # 24.41MB/116.5MB

Conferindo se a imagem enviada (push) consta no servidor registry :

Bash

# Instalar JQ para visualizar JSON no shell: apt-get -y install jq; # Consultar inventário do registry: curl -s "http://localhost:5000/v2/_catalog"; # Consultar inventário do registry, visualizar melhor interpretando o JSON: curl -s "http://localhost:5000/v2/_catalog" | jq; # { # "repositories": [ # "hello-world-test" # ] # }

6 – Importar imagens públicas para seu registry

Você pode importar as imagens públicas e adicionar a TAG para subir ela no seu registry local, ou pode renomear a imagem para garantir uma referência que só exista localmente, observe:

Bash

# Baixar imagem do Debian do Docker.io docker pull docker.io/debian:trixie # Listar imagens locais: docker image ls --filter "reference=debian*" # Adicionar TAG local: docker tag docker.io/debian:trixie localhost:5000/debian:trixie; docker tag docker.io/debian:trixie localhost:5000/dockerio_debian:trixie; # Upar imagem com TAG local para o registry privado: docker push localhost:5000/debian:trixie; docker push localhost:5000/dockerio_debian:trixie; # Consultar inventário do registry, visualizar melhor interpretando o JSON: curl -s "http://localhost:5000/v2/_catalog" | jq; # { # "repositories": [ # "debian", # "dockerio_debian", # "hello-world-test" # ] # } # Consultar tags da imagem debian no registry: curl -s -X GET "http://localhost:5000/v2/debian/tags/list"; # { # "name": "debian", # "tags": [ # "trixie" # ] # }

7 – Usando nosso repositório nos clientes

Nos servidores e clientes da Intranet ou Internet, usaremos a URL oficial do nosso repositório (nome DNS global = FQDN).

Bash

# Nome DNS oficial do repositorio: FQDN=register.ajustefino.net; # Login: docker login $FQDN; # Username: admin # Password: [senha do admin], padrao:tulipa # WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'. # Configure a credential helper to remove this warning. See # https://docs.docker.com/go/credential-store/ # Login Succeeded # Visualizar servidores de imagens conectados no Docker local: cat ~/.docker/config.json; # { # "auths": { # "register.ajustefino.net": { # "auth": "YWRtaW46dHVsaXBh" # } # } # } # Caso deseje para de usar o registry local, execute: docker logout $FQDN;

8 – Definir nosso registry como padrão

Esse procedimento é opcional.

Vamos definir a preferência de nosso servidor Docker local para usar nosso próprio repositório. Edite o arquivo /etc/docker/daemon.json adicionando:

/etc/docker/daemon.json

{ "registry-mirrors": [ "https://register.ajustefino.net" ] }

Observação: vai ser necessário fazer login. Você pode gerar o JSON em ~/.docker/config.json para deixar tudo pronto via script.

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com

Ler artigo completo

users online free counter