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:
# 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:
# 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):
# 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:
# 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:
# 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):
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:
# 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):
# 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):
# 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):
# 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:
# 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:
# 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 :
# 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:
# 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).
# 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:
{
"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