Eu sempre digo aos meus alunos e clientes que a melhor forma de entender uma tecnologia é entender primeiro qual problema ela veio resolver. Não tem como entender uma solução sem entender o problema a ser resolvido!
No final da década de 1990 (1998) houve uma inovação com a introdução da tecnologia de virtualização de sistemas operacionais.
A princípio, uma máquina virtual resolvia um problema: aplicações que deveriam ser isoladas em hardwares diferentes, aumentando o custo da infraestrutura, poderiam agora ser executadas no mesmo hardware (host) sobre uma camada de software responsável por emular um hardware - o hypervisor - criando uma VM (virtual machine - máquina virtual), o sistema operacional executado sobre uma VM tinha uma falsa sensação de estar no hardware real. O problema de custo foi resolvido, o mesmo hardware ocioso com uma aplicação poderia ser utilizado para hospedar vários sistemas operacionais isolados entre si.
Sem hypervisor:
Com hypervisor:
Com novas soluções vem novos problemas, com a proliferação de VMs numa infra temos que criar, manter, atualizar, auditar, fazer backup, recuperar backup, fazer inventário, descobrir porque ela parou de funcionar, monitorar via SNMP ou via protocolo específico de um sistema operacional, gerir usuários root/admin de cada uma, gerir autenticação centralizada de usuários, etc...
Em ambientes de desenvolvimento de software em equipes, qual seria a solução para isolar ambientes de testes dos programadores? Dar um super-hardware para cada um? Criar uma VM para cada desenvolvedor ou criar um usuário para cada um num servidor de teste? Dar ou não privilégios de root/sudo para eles? E se algum dedo gordo danificar o sistema? Todos os desenvolvedores ficam sem programar até o administrador reparar o sistema?
Num datacenter, gerir produtos, serviços, tráfego, monitoramentos, devemos criar uma VM para cada software ou deixar vários softwares numa mesma VM correndo o risco deles se agredirem?
Em provedores de Internet, é muito comum encontrar uma virtualização onde cada serviço tem sua VM: DNS Recursivo 1, DNS Recursivo 2, DNS autoritativo 1, DNS autoritativo 2, software de gestão/Radius, software de monitoramento, software de firewall, software de VPN, software de gestão de produtos específicos, e a lista vai crescendo. Para cada software cria-se uma máquina virtual.
Uma máquina virtual tem um defeito grave: cada sistema operacional tem seu pre-requisito mínimo de CPU, tamanho de disco, memória RAM, rede, CD/DVD.
Visualize: faz sentido alocar 10G de disco, 2G de ram, 2 núcleos para uma VM de DNS que mal consome 1/3 disso na maior parte do tempo?
Se você precisar criar 100 servidores DNS para uma solução de larga escala (1 milhão de requisições), você vai criar 100 VMs alocando esse hardware todo? Quanto vai custar tal servidor com esses recursos?
A solução que precisamos deve:
Antes de existirem máquinas virtuais havia uma solução simplória chamada CHROOT, feita para sistemas Unix (Linux e afins). A ideia do CHROOT era executar um software isolando-o num sistema de arquivos falso. Vou explicar essa parte com detalhes.
Quando você roda qualquer comando no Linux, antes da executar o software em sí, o Kernel prepara o ambiente para executá-lo, o que inclui herdar variáveis de ambiente (ENV, veremos mais adiante), determinar o PPID (PID do processo pai), determinar o usuário e grupo do processo, estabelecer prioridade de tempo de CPU (+/- NICE), estabelecer espaço de memória de pilha, definir ponteiros para arquivos (STDIN, STDOUT, STDERR), entre outros preparativos. Feito isso o processo começa a ser executado: o kernel analisa cada instrução do software como se fosse uma receita de bolo.
Infelizmente, se um processo é explorado por um atacante (bugs e exploits), tudo que o software tem acesso estará vulnerável. O atacante pode começar lendo o arquivo /etc/passwd para descobrir usuários do sistema, pode ler arquivos comuns em busca de pistas para ganhar mais acessos e privilégios especiais. Controle remoto e vazamento de dados acontecem assim.
A idéia do CHROOT era instruir o kernel a executar o processo dentro de uma jaula de arquivos. Para isso era necessário que você colocasse em uma pasta todos os arquivos que o software precisa para rodar. Tomando a pasta /tmp/jaula01 como base de uma jaula para rodar um software de DNS como o Bind9, você precisaria copiar o binário do Bind9 (/usr/sbin/named) para /tmp/jaula01/usr/sbin/named, e copiar todas as bibliotecas da qual o named precisa para rodar. Observe:
root@labserver:~# ldd /usr/sbin/named linux-vdso.so.1 liblwres.so.161 => /lib/x86_64-linux-gnu/liblwres.so.161 libdns.so.1104 => /lib/x86_64-linux-gnu/libdns.so.1104 libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 libcrypto.so.1.1 => /lib/x86_64-linux-gnu/libcrypto.so.1.1 libbind9.so.161 => /lib/x86_64-linux-gnu/libbind9.so.161 libisccfg.so.163 => /lib/x86_64-linux-gnu/libisccfg.so.163 libisccc.so.161 => /lib/x86_64-linux-gnu/libisccc.so.161 libisc.so.1100 => /lib/x86_64-linux-gnu/libisc.so.1100 libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 libprotobuf-c.so.1 => /lib/x86_64-linux-gnu/libprotobuf-c.so.1 libfstrm.so.0 => /lib/x86_64-linux-gnu/libfstrm.so.0 libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 libjson-c.so.3 => /lib/x86_64-linux-gnu/libjson-c.so.3 liblmdb.so.0 => /lib/x86_64-linux-gnu/liblmdb.so.0 libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 libGeoIP.so.1 => /lib/x86_64-linux-gnu/libGeoIP.so.1 libxml2.so.2 => /lib/x86_64-linux-gnu/libxml2.so.2 libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 /lib64/ld-linux-x86-64.so.2 libicui18n.so.63 => /lib/x86_64-linux-gnu/libicui18n.so.63 libicuuc.so.63 => /lib/x86_64-linux-gnu/libicuuc.so.63 libicudata.so.63 => /lib/x86_64-linux-gnu/libicudata.so.63 libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1
Após um longo trabalho de copiar todas essas bibliotecas, criar a pasta /etc/ dentro da jaula com alguns arquivos falsos (ld.so.conf, /etc/passwd, /etc/shadow), criar a pasta /dev/ com alguns ponteiros básicos, copiar os arquivos de configuração (/etc/bind/named.conf entre outros), sua jaula estará pronta.
root@labserver:~# chroot /tmp/jaula01 /usr/sbin/named
Se o atacante tomar o controle do software e solicitar a leitura do arquivo /etc/passwd, ele estará na verdade lendo o arquivo /tmp/jaula01/etc/passwd.
O CHROOT é um bom exemplo de isolamento de sistema de arquivos e era somente isso que ele provia. Ele não era capaz de prover outros níveis de isolamente, o software continuava num ambiente comum de processos, podendo acessar a lista de processos (ps ax), acessar a rede IP onde o servidor se encontrava, enviar sinais para outros processos, acessar uma vasta gama de recursos do kernel que não envolvem o sistema de arquivos.
O que é melhor que uma jaula? Um container.
O kernel Linux foi aperfeiçoado para permitir que os demais recursos providos a um processos pudessem ser isolados. As ferramentas LXD, LXC e LXCFS permitiam a criação de um ambiente onde o processo era executado totalmente isolado graças a um recurso do kernel chamado cgroups.
Antes de rodar um processo, o kernel agora prepara um ambiente tão isolado que se assemelha a uma máquina virtual: uma interface de rede com MAC, IP e gateway, um sistema de arquivos muito mais sofisticado que uma jaula, informações de sistema separados para o container (lscpu, free, lspci, etc...). O melhor disso tudo é que o kernel não precisa criar uma camada de software para simular um hardware.
Um software que está rodando em um container em nada difere do mesmo software rodando numa máquina virtual.
Há um pequeno problema: para rodar um software em um ambiente CGROUPS, você ainda precisa prover os arquivos FHS (/bin, /etc, /lib, /dev, ...). Essa parte chata e cansativa ainda era necessária. Depois de um tempo passando raiva descobrindo os arquivos que faltavam, você acaba fazendo o seu "kit jaula", mas se todos os usuários de containers precisassem passar por isso, pouca gente utilizaria containers hoje.
Se eu gosto de usar Debian, eu posso criar uma pasta contendo tudo que ele precisa para rodar qualquer binário em uma jaula ou container, e a cada software novo eu usaria meu pacote pronto e só acrescentaria um novo binário. Essa seria uma definição primordial de uma IMAGEM de um sistema Debian para jaulas e containers.
Scripts facilitadores e pacotes contendo as imagens de diferentes sistemas fariam toda diferença no dia-a-dia e é nesse ponto que surge o Docker.
Inicialmente o Docker foi criado como um toolkit auxiliar o LXD/LXC e a medida que foi evoluindo ele deixou de depender deles para conter todas as bibliotecas e interfaces com o kernel para criar ambientes CGROUPS.
Exemplo de container com isolamento de arquivos completos:
Alem do isolamento de arquivos, temos o isolamento de rede. Por padrão o docker cria uma rede chamada "docker0" (é uma bridge) na faixa 172.17.0.0/16, o primeiro IP dessa sub-rede - 172.17.0.1 - é o gateway padrão de todo container. No host é criado uma interface VETH (virtual ethernet) e adicionada a bridge, dentro do container essa veth é apresentada como eth0 com um ip 172.17.0.2 em diante.
Usaremos como base o Debian 10.5
Esteja logado como ROOT (comando: su -) para executar os comandos abaixo:
# Instalando ferramentas apt-get -y update apt-get -y install bridge-utils tcpdump curl mc htop # Adicionar repositorio docker GPGURL=https://download.docker.com/linux/ubuntu/gpg APTURL=https://download.docker.com/linux/debian curl -fsSL $GPGURL | apt-key add - add-apt-repository "deb [arch=amd64] $APTURL $(lsb_release -cs) stable" # Atualizando indice apt-get -y update apt-get -y upgrade apt -y autoremove # Instalando docker apt-get -y install docker-ce
Caso encontre algum erro durante a instalação via APT, tente instalar via script oficial:
# Instalando docker via script oficial
curl -fsSL get.docker.com -o /root/get-docker.sh
sh /root/get-docker.sh
Execute::
root@labserver:~# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES root@labserver:~#
Se o resultado do comando docker ps -a foi como acima, o software está 100% operacional.
Antes de aprofundar nos detalhes do uso de containers em Docker, vou dar alguns exemplos para fixar as regras envolvidas no seu uso.
Regra 1 - Um container é uma cópia de uma imagem em camada transparente (overlay).
Não temos nenhuma imagem em nosso inventário local. Observe:
root@labserver:~# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE root@labserver:~#
Para que um container seja criado, o Docker deve pegar uma imagem e descompacta-la em um diretório, para em seguida criar uma jaula CGROUP com ela.
Normalmente esses arquivos ficam em /var/lib/docker/overlay2 (não mexa lá, a menos que seja emergencia).
Para obter uma imagem, você pode:
Cada imagem tem uma identidade baseada na assinatura SHA-256 do arquivo da imagem.
Uma imagem em uso por algum container não pode ser deletada, assim a melhor forma de alterar imagens é criar novas versões. No Docker as versões são conhecidas como TAGs, que podem ser nomes ou números.
Caso você tente obter uma imagem sem informar a TAG, o Docker assume a palavra "latest" (mais recente) como TAG padrão.
Obtendo a imagem do Debian:
root@labserver:~# docker pull debian Using default tag: latest latest: Pulling from library/debian d6ff36c9ec48: Downloading [=============> ] 13.29MB/50.4MB Digest: sha256:1e74c92df240634a39d050a5e23fb18f45df30846bb222f543414da180b47a5d Status: Downloaded newer image for debian:latest docker.io/library/debian:latest root@labserver:~# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE debian latest ee11c54e6bb7 12 days ago 114MB root@labserver:~# docker pull debian:10.5 10.5: Pulling from library/debian Digest: sha256:1e74c92df240634a39d050a5e23fb18f45df30846bb222f543414da180b47a5d Status: Downloaded newer image for debian:10.5 docker.io/library/debian:10.5 root@labserver:~# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE debian 10.5 ee11c54e6bb7 12 days ago 114MB debian latest ee11c54e6bb7 12 days ago 114MB root@labserver:~#
No momento em que escrevo esse artigo, a última versão do Debian é a 10.5, portanto, quando eu baixei a imagem "debian" sem informar a tag, a tag latest fazia referencia à última imagem disponível (10.5). Quando tentei obter especificamente a imagem debian tag 10.5 a assinatura era igual, ele adicionou o novo registro no repositório mas não repetiu o download, visto que a assinatura SHA256 da imagem debian:latest é a mesma de debian:10.5.
Ao remover uma imagem que possui duas referencias, ela só é realmente apagada quando você remover a última referencia:
root@labserver:~# docker rmi debian:10.5 Untagged: debian:10.5 root@labserver:~# docker rmi debian:latest Untagged: debian:latest Untagged: debian@sha256:1e74c92df240634a39d050a5e23fb18f45df30846bb222f543414da180b47a5d Deleted: sha256:ee11c54e6bb7b1c022d7a43aaee00eac776367a7a1adb7746a79757fae175a5e Deleted: sha256:0ced13fcf9441aea6c4ee1defc1549772aa2df72017588a1e05bc11dd30b97b6
Observe que ao remover definitivamente a imagem debian:latest duas outras imagens foram removidas. Isso ocorre porque a imagem é construída em camadas num sistema de arquivos chamado AUFS (baseada em unionfs). Vou deixar para explicar como as camadas funcionam e qual problema ela resolve mais adiante.
A definição de camada transparente - overlay - vem do fato de que uma imagem é composta de camadas.
Toda imagem é construída do zero (FROM scratch) e novas camadas são adicionadas acima dessa.
Observe o Dockerfile (vou ensinar mais a frente) do Debian 10.5:
# Arquivo Dockerfile
ROM scratch ADD rootfs.tar.xz / RUN apt-get -y update CMD ["bash"]
Quando você executa um container baseado numa imagem, o Docker monta a imagem numa pasta e cria uma "camada transparente" do container em execução.
Você pode imaginar que ao rodar 10 containers baseados na mesma imagem que haverá 10x mais alocação de espaço em disco, errado. A grande novidade é que isso não ocorre.
Todos os arquivos da imagem são exclusivos de cada container em modo leitura, mas se você alterar qualquer arquivo uma cópia desse arquivo é puxada para a camada superior e armazenada lá, assim, somente há alocação (duplicação) de espaço quando você violar a imagem (camada inferior).
Quando algum arquivo é alterado, sua cópia na camada do container passa a ser definitiva, o arquivo original na camada de baixo deixa de ser acessível mesmo que você remova o arquivo alterado dentro do container.
Temos aqui uma recomendação muito importante: não altere os arquivos da imagem, o capítulo de montagens e volumes vão mostrar como trabalhar com arquivos dentro do container sem violar esse princípio, que podem levar você a ficar sem disco!
Regra 2 - Todo container precisa do seu processo principal (PID 1) em execução para sustentar sua existência
Vale informar que se você tentar usar uma imagem que não existe, o Docker tentará achá-la em algum repositório para baixá-la automaticamente, por conta disso quase não se usa o docker pull no dia-a-dia.
É bem raro ver alguém usando docker create pois o comando docker run é a união de create + start.
É mais raro ainda ver alguém usando docker stop seguido de docker rm pois o comando docker -f rm realiza essas duas tarefas (parar e remover).
Vamos criar um container de teste do Debian 10.5 para ver todas as etapas do Docker na prática. Observe:
docker pull debian:10.5 10.5: Pulling from library/debian d6ff36c9ec48: Downloading [===================> ] 22.53MB/50.4MB d6ff36c9ec48: Extracting [======================================> ] 38.84MB/50.4MB d6ff36c9ec48: Pull complete Digest: sha256:1e74c92df240634a39d050a5e23fb18f45df30846bb222f543414da180b47a5d Status: Downloaded newer image for debian:10.5 docker.io/library/debian:10.5 docker image ls debian 10.5 ee11c54e6bb7 12 days ago 114MB
docker create --name=debian01 -h debian-test.intranet.br debian:10.5 sleep 99 9446e46ec48705f64ae310619f1cbe23dbe85363b0d253bfe4f9414df607c608 docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9446e46ec487 debian:10.5 "sleep 99" 34 seconds ago Created debian01
docker start debian01 debian01 docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES 9446e46ec487 debian:10.5 "sleep 99" 3 minutes ago Up 1 minute debian01 # 99 segundos depois ... docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES 9446e46ec487 debian:10.5 "sleep 99" 5 minutes ago Exited (0) 10 seconds ago debian01 docker rm debian01 debian01
O que aconteceu acima:
Tudo que fizemos acima poderia ser feito com um único comando:
docker run -d --rm --name=debian02 -h debian-test.intranet.br debian:10.5 sleep 99 6b4b177ddcc134bbc9e5b85eebd4004ee284c9932f9d728317f83174481cfac1
Explicando: docker run é a união de docker create + docker start, -d executa o container em background, --rm informa ao docker para destruir o container quando ele encerrar a atividade.
Olha esse exemplo interessante: vamos rodar um container auto-destrutivo, vamos rodar um comando que exibe sua configuração IP:
docker run --rm debian:10.5 ip addr show 1: lo:mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 17: eth0@if18: mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
Não rodamos o container em background, não informamos nome de container nem nome de host para ele, informamos o Docker que o container deve ser destruído após sua tarefa, e informamos como tarefa o comando que exibe a configuração IP de dentro do container.
Ao listar os containers em execução nada aparece, pois era um container "Mr. Meeseeks" (se você não entende a referência, procure no Google).
Containers auto-destrutivos são muito utilizados por programadores para testar código, executar tarefas específicas em que somente o resultado importa, todo o resto é descartável.
Vou criar um container auto-destrutivo que executará um sleep de 32 anos. Em seguida vamos entrar no container para ver como ele é.
docker run -d --rm --name=debian03 debian:10.5 sleep 1009152000 docker exec -it debian03 bash root@ca61b8b430e1:/# root@ca61b8b430e1:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var root@ca61b8b430e1:/# ip addr show 1: lo:mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 25: eth0@if26: mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever root@ca61b8b430e1:/# ping -c 4 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=114 time=35.4 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=114 time=28.7 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=114 time=24.2 ms 64 bytes from 8.8.8.8: icmp_seq=4 ttl=114 time=29.0 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 13ms rtt min/avg/max/mdev = 24.180/29.318/35.409/4.003 ms root@ca61b8b430e1:/# exit docker stop debian03
O comando docker exec serve parar rodar algum programa adicional dentro do container, no dia-a-dia ele é comumente usado para entrar no container (quando vc roda algum programa de shell, como o bash ou vtysh) ou para obter algum relatório do que está acontecendo lá dentro (ps ax, ping, free, ...).
Entramos no container e temos o mesmo ambiente de um Linux Debian normal, com instalação mínima.
Saímos do container com o exit e em seguida paramos o container com docker stop, como ele é auto-destrutivo o Docker não só parou como também exterminou tudo do container.
Imagino que você esteja ansioso para criar sistemas, rodar muitos programas em Docker, mas continue o tutorial até o final para aprender tudo.
Tipo 1 - Redes bridge
Quase todo mundo usa o Docker sem precisar aprender muitos detalhes da parte de redes (docker network) por um simples motivo: todo container é associado a rede "bridge" (docker0) por padrão. A rede "bridge" é uma rede virtual de camada 2 rodando no prefixo 172.17.0.0/16, o primeiro IP (172.17.0.1/16) é associado ao host para servir de gateway padrão para todos os containers.
Por padrão, toda rede criado no docker, incluindo a rede nativa "bridge" possui isolamento entre containers a nível de rede para que uma rede docker não se comunique com outra rede docker no mesmo host, a menos que você explicitamente permita isso (publicação de portas TCP e UDP).
docker network create mynet -d bridge --subnet 10.90.0.0/16 7edc2615687a58ea561505e4eddcaa978ae930fbe5231294f45c1fae63d1d06b docker network ls NETWORK ID NAME DRIVER SCOPE 41d27617c1a4 bridge bridge local 06af2efcbcc5 host host local 7edc2615687a mynet bridge local 7033a35b727e none null local
Vamos criar alguns containers para teste e aproveitar para explorar novos argumentos:
docker run -d --name=Container-01 debian:10.5 sleep 1009152000 docker run -d --name=Container-02 debian:10.5 sleep 1009152000 docker run -d --name=Container-11 --network mynet debian:10.5 sleep 1009152000 docker run -d --name=Container-12 --network mynet debian:10.5 sleep 1009152000
Resultado:
O firewall padrão (iptables / nftables) impede que a comunicação entre redes. Os containers Container-01 e Container-02 podem se comunicar via rede IP (ping 172.17.0.2, ping 172.17.0.3) e os containers Container-11 e Container-12 podem se comunicar (ping 10.90.0.2, ping 10.90.0.3), mas a comunicação IP entre a rede 172.17.0.0/16 e 10.90.0.0/16 será bloqueada via firewall.
Para que uma rede possa se comunicar com outra, você pode desativar o firewall do docker (nada normal) ou interferir nas regras de firewall (nada normal tambem).
Publicar uma porta faz com que o Docker crie um redirecionamento de portas do ip do host (nosso Linux onde o Docker está instalado) e o ip do container.
Vamos rodar um container de um servidor Web Apache2 (detalhes em https://hub.docker.com/_/httpd),
exemplo:
docker run -d --name=Apache-01 -p 8091:80/tcp httpd:2.4 docker run -d --name=Apache-11 -p 8092:80/tcp --network mynet httpd:2.4 docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5bb9b04c6afb httpd:2.4 "htt..." 1min ago Up 1min 0.0.0.0:8092->80/tcp Apache-11 1088317fdcbf httpd:2.4 "htt..." 2min ago Up 2min 0.0.0.0:8091->80/tcp Apache-01
Se você acessar o IP do seu Linux na porta 8091 ou 8092 (http://x.x.x.x:8092) você observará a página "It works!"
Outro detalhe sobre redes em Docker é o NAT. Por padrão toda rede sofre NAT na saída para Internet, onde o IP de origem do container é substituído pelo IP da interface de rede do host.
Redes IPv4 no Docker sem NAT são usadas para dar IPs públicos direto aos containers, embora não seja uma prática comum. Para criar uma rede Docker sem NAT, observe os exemplo:
docker network create freenet1 \ -d bridge --gateway=45.255.129.1 --subnet=45.255.129.0/26 \ -o "com.docker.network.bridge.enable_icc"="true" \ -o "com.docker.network.driver.mtu"="1500" \ -o "com.docker.network.bridge.name"="brfreenet1" \ -o "com.docker.network.bridge.enable_ip_masquerade"="false"
O uso de IPv6 não é padrão do Docker, e por padrão as redes com IPv6 não sofrem NAT MASQUERADE na saída, podemos criar uma rede com IPv6 assim:
docker network create freenet2 \ -d bridge \ --gateway=45.255.130.1 --subnet=45.255.130.0/26 \ --ipv6 \ --subnet=2804:cafe:45ff:8100::/64 \ --gateway=2804:cafe:45ff:8100::1 \ -o "com.docker.network.bridge.enable_icc"="true" \ -o "com.docker.network.driver.mtu"="1500" \ -o "com.docker.network.bridge.name"="brfreenet2" \ -o "com.docker.network.bridge.enable_ip_masquerade"="false"
Caso você tenha IPv6 no seu host mas não tenha prefixos IPv6 para utilizar dentro do Docker, você pode criar uma rede IPv6 privada e fazer NAT MASQUERADE dela, exemplo:
docker network create freenet3 \ -d bridge \ --gateway=10.3.3.1 --subnet=10.3.3.0/24 \ --ipv6 \ --subnet=2001:db8:1000:3300::/64 \ --gateway=2001:db8:1000:3300::1 \ -o "com.docker.network.bridge.enable_icc"="true" \ -o "com.docker.network.driver.mtu"="1500" \ -o "com.docker.network.bridge.name"="brfreenet3" \ -o "com.docker.network.bridge.enable_ip_masquerade"="false" ip6tables -t nat -A POSTROUTING -s 2001:db8:1000:3300::/64 -j MASQUERADE
Tipo 2 - Redes MACVLAN
Até agora trabalhamos apenas com redes do tipo "bridge" (driver bridge, -d bridge), um driver interessante é o MACVLAN, ele não usa VLAN em sí (tag ethernet 802.1q), ele cria um MAC address virtual na interface de rede do host para representar o container na rede externa do host.
docker network create macvlan0 \ -d macvlan -o parent=eth0 \ --gateway=172.19.2.1 --subnet=172.19.2.0/24 --ip-range=172.19.2.224/28
docker run -d --name=Container-MV01 --network macvlan0 debian:10.5 sleep 1009152000
O uso de redes MACVLAN deve ser acompanhada de um controle do uso dos endereços IP, sem especificar um RANGE o Docker irá alocar os IPs para os containers sequencialmente, podendo provocar conflitos de IPs na rede local. No exemplo acima eu tomei o cuidado de especificar o range (--ip-range) de IPs que o Docker poderá usar nos containers criados nessa rede para evitar conflitos com minhas rede local com DHCP.
Tipo 3 - Rede HOST
Uma terceira forma de colocar containers em rede é não utilizando nenhuma rede Docker, e simplesmente rodar o container na rede "host", nesse caso o container não terá um IP específico e passará a rodar na camada de rede do proprio Linux host.
docker run -d --name=Container-Host --network host debian:10.5 sleep 1009152000
Nota: ao usar redes MACVLAN em máquinas virtuais você pode ter problemas com o controle de MAC do Hypervisor, no VMWARE ESXI você precisa ativar a permissão para clonagem de MAC na PortGroup ou no vSwitch.
Tipo 4 - Sem rede
A última forma de rodar um container é na rede "none", onde nenhuma rede será associada ao container.
Para rodar containers sem rede, use:
docker run -d --name=Container-SemRede --network none debian:10.5 sleep 1009152000
Quase todo aventureiro em Docker cai no erro de rodar um container, entrar nele e instalar um monte de programas, para, muito tempo depois descobrir que estava fazendo tudo da forma mais difícil possível.
Devido a natureza COPY-ON-WRITE das camadas transparentes (overlay) do sistema de arquivos usado nas imagens, criar e modificar arquivos dentro do container é uma tarefa fatal: aumenta o uso de espaço em disco e cria processamento adicionais no acesso dos arquivos, visto que para encontrar um arquivo o Linux precisa percorrer todas as camadas da última até a primeira.
Para evitar isso, sua imagem personalizada deve conter todos os programas que você vai precisar para rodar sua aplicação num mínimo de camadas possíveis (o limite é de 127 camadas).
Para criar uma imagem personalizada você vai precisar criar um projeto (pasta) contendo um arquivo chamado Dockerfile contendo algumas das seguindos instruções:
Você pode criar uma imagem num único comando:
docker build -t imagem-rapida:001 -t imagem-rapida:latest - <<EOF FROM debian:latest RUN apt-get -y update && apt-get -y upgrade && apt-get -y install procps CMD ["sleep","99887766"] EOF
Ou pode criar uma pasta contendo um arquivo Dockerfile, que é o método mais profissional e compatível com trabalho em equipe. Usaremos o método com Dockerfile.
Algumas imagens, como a do Debian:10.5 são construídas sem ENTRYPOINT e sem CMD, o que nos obriga a informar o comando de ENTRYPOINT no final do comando "docker run".
Para esclarecer a diferença entre ENTRYPOINT e CMD, vou dar 3 exemplos bem claros.
1 - Imagem "minha-imagem-001" usando CMD
mkdir /tmp/minha-imagem-001 cd /tmp/minha-imagem-001 touch Dockerfile
# Arquivo /tmp/minha-imagem-001/Dockerfile
FROM debian:10.5 RUN apt-get -y update && apt-get -y upgrade && apt-get -y install procps CMD ["sleep","99887766"]
docker build -t minha-imagem:001 -t minha-imagem:latest /tmp/minha-imagem-001 Sending build context to Docker daemon 2.048kB Step 1/3 : FROM debian:10.5 ---> ee11c54e6bb7 Step 2/3 : RUN apt-get -y update && apt-get -y upgrade && apt-get -y install procps ---> Running in df945d8776f1 Get:1 http://security.debian.org/debian-security buster/updates InRelease [65.4 kB] Get:2 http://deb.debian.org/debian buster InRelease [122 kB] Get:3 http://security.debian.org/debian-security buster/updates/main amd64 Packages [226 kB] Get:4 http://deb.debian.org/debian buster-updates InRelease [51.9 kB] Get:5 http://deb.debian.org/debian buster/main amd64 Packages [7906 kB] Get:6 http://deb.debian.org/debian buster-updates/main amd64 Packages [7868 B] Fetched 8379 kB in 3s (2636 kB/s) Reading package lists... Reading package lists... Building dependency tree... Reading state information... Calculating upgrade... 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. Reading package lists... Building dependency tree... Reading state information... The following additional packages will be installed: libgpm2 libncurses6 libprocps7 lsb-base psmisc Suggested packages: gpm The following NEW packages will be installed: libgpm2 libncurses6 libprocps7 lsb-base procps psmisc 0 upgraded, 6 newly installed, 0 to remove and 0 not upgraded. Need to get 612 kB of archives. After this operation, 1981 kB of additional disk space will be used. Get:1 http://deb.debian.org/debian buster/main amd64 libncurses6 amd64 6.1+20181013-2+deb10u2 [102 kB] Get:2 http://deb.debian.org/debian buster/main amd64 libprocps7 amd64 2:3.3.15-2 [61.7 kB] Get:3 http://deb.debian.org/debian buster/main amd64 lsb-base all 10.2019051400 [28.4 kB] Get:4 http://deb.debian.org/debian buster/main amd64 procps amd64 2:3.3.15-2 [259 kB] Get:5 http://deb.debian.org/debian buster/main amd64 libgpm2 amd64 1.20.7-5 [35.1 kB] Get:6 http://deb.debian.org/debian buster/main amd64 psmisc amd64 23.2-1 [126 kB] debconf: delaying package configuration, since apt-utils is not installed Fetched 612 kB in 1s (1176 kB/s) Selecting previously unselected package libncurses6:amd64. (Reading database ... 6677 files and directories currently installed.) Preparing to unpack .../0-libncurses6_6.1+20181013-2+deb10u2_amd64.deb ... Unpacking libncurses6:amd64 (6.1+20181013-2+deb10u2) ... Selecting previously unselected package libprocps7:amd64. Preparing to unpack .../1-libprocps7_2%3a3.3.15-2_amd64.deb ... Unpacking libprocps7:amd64 (2:3.3.15-2) ... Selecting previously unselected package lsb-base. Preparing to unpack .../2-lsb-base_10.2019051400_all.deb ... Unpacking lsb-base (10.2019051400) ... Selecting previously unselected package procps. Preparing to unpack .../3-procps_2%3a3.3.15-2_amd64.deb ... Unpacking procps (2:3.3.15-2) ... Selecting previously unselected package libgpm2:amd64. Preparing to unpack .../4-libgpm2_1.20.7-5_amd64.deb ... Unpacking libgpm2:amd64 (1.20.7-5) ... Selecting previously unselected package psmisc. Preparing to unpack .../5-psmisc_23.2-1_amd64.deb ... Unpacking psmisc (23.2-1) ... Setting up lsb-base (10.2019051400) ... Setting up libgpm2:amd64 (1.20.7-5) ... Setting up psmisc (23.2-1) ... Setting up libprocps7:amd64 (2:3.3.15-2) ... Setting up libncurses6:amd64 (6.1+20181013-2+deb10u2) ... Setting up procps (2:3.3.15-2) ... update-alternatives: using /usr/bin/w.procps to provide /usr/bin/w (w) in auto mode Processing triggers for libc-bin (2.28-10) ... Removing intermediate container df945d8776f1 ---> d1f316160a0a Step 3/3 : CMD ["sleep","99887766"] ---> Running in ee8e1b9371e2 Removing intermediate container ee8e1b9371e2 ---> a334f012d07a Successfully built a334f012d07a Successfully tagged minha-imagem:001 Successfully tagged minha-imagem:latest
Criamos a imagem chamada "minha-imagem" com a versão "001", que aparecerá na lista de imagens locais:
docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE minha-imagem 001 a334f012d07a 3 minutes ago 134MB minha-imagem latest a334f012d07a 3 minutes ago 134MB
Essa imagem conta com um comando CMD, logo, podemos rodar um container com essa imagem usando qualquer um dos dois comandos abaixo:
docker run -d minha-imagem docker run -d minha-imagem:001
docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES 6924d42e525b minha-imagem:001 "sleep 99.." 1 second ago Up 1min happy_ship 8a654da11b94 minha-imagem "sleep 99.." 2 seconds ago Up 1min blue_matsumoto
Nota: como não informamos o nome do container, o Docker associa um nome composto de 2 palavras aleatórias de seu dicionário, como não informamos a rede ele associa a rede "bridge" docker0 e como não informamos nenhum comando ele assume o ENTRYPOINT ou CMD da imagem (sleep 99887766).
2 - Imagem "minha-imagem-002" usando ENTRYPOINT
Vamos fazer a mesma imagem, versão 2, usando ENTRYPOINT em vez de CMD:
mkdir /tmp/minha-imagem-002 cd /tmp/minha-imagem-002 touch Dockerfile
# Arquivo /tmp/minha-imagem-002/Dockerfile
FROM debian:10.5 RUN apt-get -y update && apt-get -y upgrade && apt-get -y install procps ENTRYPOINT ["sleep","99887766"]
docker build -t minha-imagem:002 -t minha-imagem:latest /tmp/minha-imagem-002
Rodando container usando nova imagem:
docker run -d --name=ct-img-nova minha-imagem
3 - Imagem "minha-imagem-003" usando ENTRYPOINT e CMD
O resultado funcional é o mesmo, agora, se combinarmos as duas instruções ENTRYPOINT e CMD e na mesma imagem, temos um efeito de soma dos dois argumentos num único comando:
mkdir /tmp/minha-imagem-003 cd /tmp/minha-imagem-003 touch Dockerfile
# Arquivo /tmp/minha-imagem-003/Dockerfile
FROM debian:10.5 RUN apt-get -y update && apt-get -y upgrade && apt-get -y install procps ENTRYPOINT ["sleep"] CMD ["99887766"]
docker build -t minha-imagem:003 -t minha-imagem:latest /tmp/minha-imagem-003
Rodando terceiro container usando nova imagem:
docker run -d --name=ct-img-final minha-imagem
Em todos os casos nós temos o mesmo resultado, mas você pode entender a implicação da existência do ENTRYPOINT.
4 - ENTRYPOINT direto ou interpretado por comando do shell
Você deve ter muito cuidado pois há uma pegadinha na declaração desses comandos, observe:
# Arquivo /tmp/test01/Dockerfile
FROM debian:10.5 ENTRYPOINT ["sleep","99887766"]
# Arquivo /tmp/test02/Dockerfile
FROM debian:10.5 ENTRYPOINT sleep 99887766
Observe a diferença na hora de listar os processos:
docker run -d test01 docker run -d test02 docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES 9eff1d3a7938 test01 "sleep 99887766" 5 minutes ago Up 1mi sad_kalam c5d0742f1199 test02 /bin/sh -c 'sleep 9…" 5 minutes ago Up 1mi inspiring_pare
A forma específica entre chaves ["sleep","99887766"] é diretamente executada, enquanto que na ausência dela o processo que realmente é executado é o shell bash (sh -c), o que implica em um software a mais rodando no container, na ausência das chaves, um container sem o bash (comando sh) não irá funcionar.
5 - Empilhamento de camadas
Outro detalhe importante na criação de uma imagem é evitar o excesso de camadas, observe a duas imagens personalizadas abaixo:
# Arquivo /tmp/teste-camadas-01/Dockerfile
FROM debian:10.5 ENV \ MAINTAINER="Patrick Brandao" \ EMAIL=patrickbrandao@gmail.com \ TERM=xterm \ TZ=America/Sao_Paulo \ PS1='\u@\h:\w\$ ' \ LANG=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 \ DOMAIN=intranet.br RUN ( \ apt-get -y update || exit 11; \ apt-get -y upgrade || exit 12; \ apt-get -y install \ procps coreutils util-linux psmisc iproute2 net-tools vim \ tcpdump whois fping nmap nmap-common \ mtr iputils-arping iputils-ping iputils-tracepath \ snmp snmpd || exit 13; \ ) ENTRYPOINT ["sleep","99887766"]
# Arquivo /tmp/teste-camadas-02/Dockerfile
FROM debian:10.5 ENV MAINTAINER="Patrick Brandao" \ ENV EMAIL=patrickbrandao@gmail.com \ ENV TERM=xterm \ ENV TZ=America/Sao_Paulo \ ENV PS1='\u@\h:\w\$ ' \ ENV LANG=en_US.UTF-8 \ ENV LANGUAGE=en_US.UTF-8 \ ENV DOMAIN=intranet.br RUN apt-get -y update RUN apt-get -y upgrade RUN apt-get -y install procps RUN apt-get -y install coreutils RUN apt-get -y install util-linux RUN apt-get -y install psmisc RUN apt-get -y install iproute2 RUN apt-get -y install net-tools RUN apt-get -y install vim RUN apt-get -y install tcpdump RUN apt-get -y install whois RUN apt-get -y install fping RUN apt-get -y install nmap RUN apt-get -y install nmap-common RUN apt-get -y install mtr RUN apt-get -y install iputils-arping RUN apt-get -y install iputils-ping RUN apt-get -y install iputils-tracepath RUN apt-get -y install snmp RUN apt-get -y install snmpd ENTRYPOINT ["sleep","99887766"]
Ambas criam a mesma imagem funcional, mesmas variáveis, com os mesmos programas, mesmo ENTRYPOINT, no entanto, a primeira criou um número muito menor de camadas. A primeira imagem vai consumir menos recursos do sistema de arquivos, menos recursos de I/O e menos processamento.
Uma coisa que ja deve ter ficado clara é que container é lugar de programas, não é lugar de dados.
Você deve ter total liberdade de destruir um container sem que os dados sejam destruídos juntos.
Vamos trabalhar o conceito de volumes e montagens usando um exemplo de WebSite hospedado em container.
Alguém ansioso para hospedar seu WebSite em um container vai procurar uma forma de incluir os arquivos HTML e PHP dentro da imagem, não caia nessa tentação. Imagine o trabalho de gerar uma nova imagem/tag para cada alteração que você faz no website? Nada bom.
A imagem de container de seu servidor HTTP deve ser estática e só deve ser alterada se houver atualizações no software.
Os códigos e imagens do seu website são os dados e deve ser atualizados sempre que você alterá-lo.
Vamos criar um HTML simples:
mkdir /storage/site01 echo '<h1>Ola mundo</h1>' > /storage/site01/index.html docker run \ -d \ --name Meu-Site-01 \ -p 8001:80 \ -v /storage/site01/:/usr/local/apache2/htdocs/ \ httpd:2.4
Ao acessar a URL http://x.x.x.x:8001/ (onde x.x.x.x é o ip do seu Linux) você observará a pagina "Olá mundo"
O que fizemos acima foi adicionar uma nova camada de sistema de arquivos que coloca a pasta /storage/site01 do host por cima da pasta /usr/local/apache2/htdocs dentro do container. Se existiam arquivos em /usr/local/apache2/htdocs eles não serão mais acessíveis pois foram cobertos pela montagem do volume.
Você pode trabalhar normalmente na pasta /storage/site01, mexendo nos seus códigos a vontade, que sempre poderá acompanhar pelo navegador suas alterações.
Se por alguma falha de segurança o software httpd for invadido, o invasor conseguirá alterar os arquivos em /usr/local/apache2/htdocs, o que significa alterar imediatamente os arquivos em /storage/site01
Se você destruir o container meu-site01 seus arquivos em /storage/site01 não serão afetados, apenas o volume é desmontado.
Uma forma de impedir que violações e bugs no site permitam alterações é montar as pastas em somente leitura. Observe:
mkdir /storage/site02 echo '<h1>Ola mundo inseguro</h1>' > /storage/site02/index.html docker run \ -d \ --name Meu-Site-02 \ -p 8002:80 \ --mount \ type=bind,source=/storage/site02/,destination=/usr/local/apache2/htdocs/,readonly=true \ httpd:2.4
Ao acessar a URL http://x.x.x.x:8002/ (onde x.x.x.x é o ip do seu Linux) você observará a pagina "Olá mundo inseguro"
Entre no container e tente remover o arquivo:
docker exec -it Meu-Site-02 bash rm /usr/local/apache2/htdocs/index.html rm: cannot remove '/usr/local/apache2/htdocs/index.html': Read-only file system
Não é uma pratica comum, mas acontece, de você precisar rodar dois processos em um container.
Vou criar um exemplo onde eu rodo um servidor Web e um servidor SSH juntos, de maneira que eu possa entrar remotamente direto no container, sem precisar ter acesso ao Host.
Temos duas formas de fazer isso:
Método 1 - ENTRYPOINT rodando aplicações em sequência
Este é o método mais simples, rápido e comum. Simplesmente criaremos um script de ENTRYPOINT que executará os programas secundários e manterá a execução do container num processo principal.
mkdir /tmp/multiproc-01 cd /tmp/multiproc-01 touch Dockerfile
# Arquivo /tmp/multiproc-01/meu-entrypoint.sh
#!/bin/sh # Senha de root [ "x$ROOT_PASSWORD" = "x" ] || { echo "root:${ROOT_PASSWORD}" | chpasswd 2>/dev/null 1>/dev/null; } # Subir SSHD /usr/sbin/sshd # Segurar container no processo HTTPd exec lighttpd -f /etc/lighttpd/lighttpd.conf -D
# Arquivo /tmp/multiproc-01/Dockerfile
FROM debian:10.5 ENV MAINTAINER="Patrick Brandao" \ EMAIL=patrickbrandao@gmail.com \ TERM=xterm \ PS1='\u@\h:\w\$ ' \ LANG=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 USER root ADD meu-entrypoint.sh / RUN ( \ apt-get -y update || exit 11; \ apt-get -y upgrade || exit 12; \ apt-get -y install procps lighttpd openssh-server || exit 13; \ sed -i 's;#PermitRootLogin.*;PermitRootLogin yes;' /etc/ssh/sshd_config; \ chmod +x /meu-entrypoint.sh; \ mkdir -p mkdir /run/sshd; \ ) ENTRYPOINT /meu-entrypoint.sh
Construindo imagem e rodando container:
docker build -t multiproc:01 /tmp/multiproc-01 mkdir /storage/site03 echo '<h1>Ola mundo INTEGRADO</h1>' > /storage/site03/index.html docker run \ -d \ --name Meu-Site-03 \ -p 8003:80 \ -p 2203:22 \ -e ROOT_PASSWORD=tulipa \ --mount \ type=bind,source=/storage/site03/,destination=/var/www/html/,readonly=true \ multiproc:01
No container acima você pode acessar via http na porta 8003 (aparecerá a mensagem Olá mundo INTEGRADO) ou acessar via SSH na porta 2203 (usuario root senha tulipa).
O defeito do método acima é que se o processo SSHD morrer você precisará reiniciá-lo manualmente dentro do container ou reiniciar o container.
Método 2 - ENTRYPOINT rodando boot e segurando container em gerenciador de processos
Esse é meu método favorito, pois permite que o container se comporte mais parecido com um Linux independente, e caso algum processo morra o gerenciador de processos revive o processo morto.
Os defeitos dessa técnica são: memory-leak em processos são mais desastrosos, updates envolvem custo de espaço em disco com COPY-ON-WRITE, reconstrução mais demorada das imagens, maior superfície de ataque e falhas de segurança.
Um problema que eu não abordei até agora é a consequência de montar volumes nos containers. Se você roda um software complexo como o MariaDB (sucessor do MySQL) vai obviamente precisar que o banco de dados inicial esteja criado antes de rodar o processo mysqld, mas se você montou um volume encima da pasta /var/lib/mysql esta pasta se encontrará vazia quando o container for reiniciado.
Rodar o mysqld aqui resultará em morte do processo, e reiniciá-lo pelo gerenciador de tarefas resultará em loop infinito.
O ENTRYPOINT, em vez de apenas rodar um processo que segure a existência do container (PID 1), ele pode também executar funções de boot, ajustando o ambiente para que todos os softwares possam rodar de maneira correta.
Iremos usar o supervisor como gerenciador de processos, mas temos outras opções que não abordarei nesse artigo (S7, SystemD).
mkdir /tmp/multiproc-02 cd /tmp/multiproc-02 touch Dockerfile
# Arquivo /tmp/multiproc-02/entrypoint.sh
#!/bin/sh DOCKER_CMD="$@" # Senha de root [ "x$ROOT_PASSWORD" = "x" ] || { echo "root:${ROOT_PASSWORD}" | chpasswd 2>/dev/null 1>/dev/null; } # Rodar scripts de boot cd /opt && { for escript in boot-*.sh; do sh $escript done } # Segurar container no processo supervisor exec $DOCKER_CMD
# Arquivo /tmp/multiproc-02/boot-mariadb.sh
#!/bin/sh # Garantir existencia de banco de dados inicial doinst=0 [ -d /var/lib/mysql ] || doinst=1 [ -d /var/lib/mysql/mysql ] || doinst=2 [ -d /var/lib/mysql/mysql/user.frm ] || doinst=2 [ "$doinst" = "0" ] || { echo "# Instalando db inicial" #[ -f /etc/setup/mariadb-default.tgz ] && tar -xvf /etc/setup/mariadb-default.tgz -C / mysql_install_db \ --datadir=/var/lib/mysql \ --user=mysql \ --skip-test-db \ --force \ --skip-name-resolve \ --verbose \ --tmpdir=/tmp } # Garantir diretorios mkdir -p /var/lib/mysql/mysql mkdir -p /run/mysqld # Ajustar permissoes chown -R mysql:mysql /var/lib/mysql chown -R mysql:mysql /run/mysqld
# Arquivo /tmp/multiproc-02/supervisor-mariadb.conf
[program:mariadb] command=/usr/bin/mysqld_safe --user=mysql --datadir=/var/lib/mysql --pid-file=/run/mysqld/mysqld.pid --skip-name-resolve --skip-networking=0 stopwaitsecs=3 autostart=true autorestart=true user=root
# Arquivo /tmp/multiproc-02/supervisor-sshd.conf
[program:sshd] command=/usr/sbin/sshd -D stopwaitsecs=3 autostart=true autorestart=true user=root
# Arquivo /tmp/multiproc-02/supervisor-lighttpd.conf
[program:lighttpd] command=/usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf -D stopwaitsecs=3 autostart=true autorestart=true user=root
# Arquivo /tmp/multiproc-02/supervisor-mariadb.conf
[program:mariadb] command=/usr/bin/mysqld_safe --user=mysql --datadir=/var/lib/mysql --pid-file=/run/mysqld/mysqld.pid --skip-name-resolve --skip-networking=0 stopwaitsecs=3 autostart=true autorestart=true user=root
# Arquivo /tmp/multiproc-02/Dockerfile
FROM debian:10.5 ENV MAINTAINER="Patrick Brandao" \ EMAIL=patrickbrandao@gmail.com \ TERM=xterm \ PS1='\u@\h:\w\$ ' \ LANG=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 USER root ADD entrypoint.sh /opt/_entrypoint.sh ADD boot-mariadb.sh /opt/boot-mariadb.sh RUN ( \ apt-get -y update || exit 11; \ apt-get -y upgrade || exit 12; \ apt-get -y install procps tar lighttpd openssh-server || exit 13; \ apt-get -y install supervisor || exit 14; \ apt-get -y install mariadb-server|| exit 15; \ sed -i 's;#PermitRootLogin.*;PermitRootLogin yes;' /etc/ssh/sshd_config; \ chmod +x /opt/*.sh; \ mkdir -p mkdir /run/sshd; \ mkdir -p /run/mysqld; \ mkdir -p /opt/entrypoints; \ ) ADD supervisor-mariadb.conf /etc/supervisor/conf.d/ ADD supervisor-sshd.conf /etc/supervisor/conf.d/ ADD supervisor-lighttpd.conf /etc/supervisor/conf.d/ ADD supervisor-mariadb.conf /etc/supervisor/conf.d/ ENTRYPOINT ["/opt/_entrypoint.sh"] CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]
Construindo imagem e rodando container:
docker build -t multiproc:02 /tmp/multiproc-02 mkdir /storage/site04 echo '<h1>Ola mundo ATAREFADO</h1>' > /storage/site04/index.html mkdir /storage/banco04 docker run \ -d \ --name Meu-Site-04 \ --hostname site04.intranet.br \ -p 8004:80 \ -p 2204:22 \ -e ROOT_PASSWORD=tulipa \ \ --mount \ type=bind,source=/storage/site04/,destination=/var/www/html/,readonly=true \ \ --mount \ type=bind,source=/storage/banco04/,destination=/var/lib/mysql/,readonly=false \ \ multiproc:02
Observe os processo rodando dentro do container:
docker exec -it Meu-Site-04 ps ax PID TTY STAT TIME COMMAND 1 ? Ss 0:00 /usr/bin/python2 /usr/bin/supervisord -n -c /etc/sup 86 ? S 0:00 /usr/sbin/sshd -D 87 ? S 0:00 /bin/sh /usr/bin/mysqld_safe --user=mysql --datadir= 88 ? S 0:00 lighttpd -f /etc/lighttpd/lighttpd.conf -D 227 ? Sl 0:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/m 228 ? S 0:00 logger -t mysqld -p daemon error 276 pts/0 Rs+ 0:00 ps ax
Embora a demonstração seja básica, a evolução da técnica é por conta de quem deseja fazer uma imagem "tudo-em-um".
|
|