Docker 101: fundamentos e prática

Se você está cansado de ouvir seus colegas de trabalho elogiarem o Docker e seus benefícios a cada chance que eles têm, ou está cansado de balançar a cabeça e ir embora toda vez que se encontra em uma dessas conversas, você veio para a direita Lugar, colocar.

Além disso, se você está procurando uma nova desculpa para vagar sem ser demitido, continue lendo e você me agradecerá mais tarde.

Docker

Esta é a definição de Docker, de acordo com a Wikipedia:

Docker é um programa de computador que realiza virtualização em nível de sistema operacional.

Muito simples, certo? Bem, não exatamente. Tudo bem, aqui está minha definição do que é docker:

Docker é uma plataforma para criar e executar contêineres a partir de imagens .

Ainda perdido? Não se preocupe, porque provavelmente você não sabe o que são contêineres ou imagens .

As imagens são arquivos únicos contendo todas as dependências e configurações necessárias para executar um programa, enquanto os contêineres são as instâncias dessas imagens. Vamos ver um exemplo disso na prática para deixar as coisas mais claras.

Observação importante: antes de continuar, certifique-se de instalar o docker usando as etapas recomendadas para o seu sistema operacional.

Parte 1. "Olá, mundo!" de uma imagem Python

Digamos que você não tenha o Python instalado em sua máquina - ou pelo menos não a versão mais recente - e precise do python para imprimir "Hello, World!" em seu terminal. O que você faz? Você usa o docker!

Vá em frente e execute o seguinte comando:

docker run --rm -it python:3 python

Não se preocupe, explicarei esse comando em um segundo, mas agora você provavelmente está vendo algo assim:

Isso significa que estamos dentro de um contêiner do docker criado a partir de uma imagem do docker do Python 3 , executando o pythoncomando. Para finalizar o exemplo, digite print("Hello, World!")e observe a mágica acontecer.

Tudo bem, você conseguiu, mas antes de começar a dar tapinhas nas próprias costas, vamos dar um passo para trás e entender como isso funcionou.

Quebrando isso

Vamos começar do começo. O docker runcomando é a ferramenta padrão do docker para ajudá-lo a iniciar e executar seus contêineres.

O --rmsinalizador está lá para dizer ao Docker Daemon para limpar o contêiner e remover o sistema de arquivos após a saída do contêiner. Isso ajuda a economizar espaço em disco depois de executar containers de curta duração como este, que apenas começamos a imprimir "Hello, World!".

O -t (or --tty)sinalizador diz ao Docker para alocar uma sessão de terminal virtual dentro do contêiner. Isso é comumente usado com a -i (or --interactive)opção, que mantém o STDIN aberto, mesmo se executando no modo separado (mais sobre isso mais tarde).

Nota: Não se preocupe muito com essas definições agora. Saiba que você usará o -itsinalizador sempre que quiser digitar alguns comandos no seu contêiner.

Por último, python:3é a imagem base que usamos para este contêiner. No momento, esta imagem vem com o python versão 3.7.3 instalado, entre outras coisas. Agora, você deve estar se perguntando de onde veio essa imagem e o que há dentro dela. Você pode encontrar as respostas para essas duas perguntas aqui, junto com todas as outras imagens python que poderíamos ter usado neste exemplo.

Por último, mas não menos importante, pythonfoi o comando que pedimos ao Docker para executar dentro de nossa python:3imagem, que iniciou um shell Python e permitiu que nossa print("Hello, World!")chamada funcionasse.

Mais uma coisa

Para sair do python e encerrar nosso contêiner, você pode usar CTRL / CMD + D ou exit(). Vá em frente e faça isso agora. Depois disso, tente executar nosso docker runcomando novamente e você verá algo um pouco diferente e muito mais rápido.

Isso porque já baixamos a python:3imagem, então nosso contêiner inicia muito mais rápido agora.

Parte 2. Automatizado "Hello World!" de uma imagem Python

O que é melhor do que escrever "Hello, World!" no seu terminal uma vez? Você entendeu, escrevendo duas vezes!

Já que não podemos esperar para ver "Hello, World!" impresso em nosso terminal novamente, e não queremos passar pela confusão de abrir o python e digitar printnovamente, vamos prosseguir e automatizar um pouco esse processo. Comece criando um hello.pyarquivo onde desejar.

# hello.py
print("Hello, World!")

Em seguida, vá em frente e execute o seguinte comando dessa mesma pasta.

docker run --rm -it -v $(pwd):/src python:3 python /src/hello.py

Este é o resultado que procuramos:

Nota: Usei lsantes do comando para mostrar que estava na mesma pasta em que criei o hello.pyarquivo.

Como fizemos antes, vamos dar um passo atrás e entender como isso funcionou.

Quebrando isso

Estamos executando praticamente o mesmo comando que executamos na última seção, exceto por duas coisas.

A -v $(pwd):/srcopção diz ao Docker Daemon para iniciar um volume em nosso contêiner . Os volumes são a melhor maneira de persistir os dados no Docker. Neste exemplo, estamos dizendo ao Docker que queremos que o diretório atual - recuperado de $(pwd)- seja adicionado ao nosso contêiner na pasta /src.

Nota: Você pode usar qualquer outro nome ou pasta que desejar, não apenas /src

Se você quiser verificar se /src/hello.pyrealmente existe dentro do nosso contêiner, você pode alterar o final do nosso comando de python hello.pypara bash. Isso abrirá um shell interativo dentro de nosso contêiner, e você pode usá-lo como esperava.

Nota: Só podemos usar bashaqui porque vem pré-instalado na python:3imagem. Algumas imagens são tão simples que nem têm bash. Isso não significa que você não pode usá-lo, mas você mesmo terá que instalá-lo, se quiser.

A última parte do nosso comando é a python /src/hello.pyinstrução. Ao executá-lo, estamos dizendo ao nosso contêiner para olhar dentro de sua /srcpasta e executar o hello.pyarquivo usando python.

Talvez você já possa ver as maravilhas que pode fazer com este poder, mas vou destacá-lo de qualquer maneira. Usando o que acabamos de aprender, podemos executar praticamente qualquer código de qualquer linguagem dentro de qualquer computador sem precisar instalar nenhuma dependência na máquina host - exceto para Docker, é claro.É muito texto em negrito para uma frase, então certifique-se de lê-lo duas vezes!

Parte 3. Mais fácil "Olá, mundo!" possível a partir de uma imagem Python usando Dockerfile

Você já está cansado de dizer olá ao nosso lindo planeta? É uma pena, porque vamos fazer de novo!

O último comando que aprendemos foi um pouco prolixo, e já posso me ver ficando cansado de digitar todo aquele código toda vez que quero dizer "Olá, mundo!" Vamos automatizar um pouco mais as coisas agora. Crie um arquivo chamado Dockerfilee adicione o seguinte conteúdo a ele:

# Dockerfile
FROM python:3
WORKDIR /src/app
COPY . .
CMD [ "python", "./hello.py" ]

Agora execute este comando na mesma pasta em que criou Dockerfile:

docker build -t hello .

Tudo o que resta a fazer agora é enlouquecer usando este código:

docker run hello

Você já sabe como é. Vamos entender como um Dockerfile funciona agora.

Quebrando isso

Começando com nosso Dockerfile, a primeira linha FROM python:3diz ao Docker para iniciar tudo com a imagem base com a qual já estamos familiarizados python:3,.

A segunda linha WORKDIR /src/app,, define o diretório de trabalho dentro de nosso contêiner. Isso é para algumas instruções que executaremos posteriormente, como CMDou COPY. Você pode ver o resto das instruções com suporte para WORKDIRaqui.

A terceira linha COPY . .basicamente diz ao Docker para copiar tudo de nossa pasta atual (primeiro .) e colá-lo /src/app(segundo .). O local da colagem foi definido com o WORKDIRcomando logo acima.

Nota: Poderíamos obter os mesmos resultados removendo a WORKDIRinstrução e substituindo-a COPY . .por COPY . /src/app. Nesse caso, também precisaríamos alterar a última instrução CMD ["python", "./hello.py"]para CMD ["python", "/src/app/hello.py"].

Finalmente, a última linha CMD ["python", "./hello.py"]fornece o comando padrão para nosso contêiner. Basicamente, está dizendo que toda vez que runum contêiner a partir dessa configuração, ele deve ser executado python ./hello.py. Lembre-se de que estamos executando implicitamente em /src/app/hello.pyvez de apenas hello.py, uma vez que é para onde apontamos WORKDIR.

Nota: O CMDcomando pode ser substituído em tempo de execução. Por exemplo, se você quiser executar em bashvez disso, faça docker run hello bashdepois de construir o contêiner.

Com nosso Dockerfile concluído, vamos em frente e iniciamos nosso buildprocesso. O docker build -t hello .comando lê toda a configuração que adicionamos ao nosso Dockerfile e cria uma imagem docker a partir dela. Isso mesmo, assim como a python:3imagem que usamos para todo este artigo. O .no final informa ao Docker que queremos executar um Dockerfile em nosso local atual e a -t helloopção dá o nome a essa imagem hello, para que possamos referenciá-la facilmente no tempo de execução.

Depois de tudo isso, tudo o que precisamos fazer é executar a docker runinstrução usual , mas desta vez com o hellonome da imagem no final da linha. Isso iniciará um contêiner a partir da imagem que construímos recentemente e, finalmente, imprimirá o bom e velho "Hello, World!" em nosso terminal.

Ampliando nossa imagem de base

O que faremos se precisarmos de alguma dependência para executar nosso código que não vem pré-instalado com nossa imagem base? Para resolver esse problema, docker tem as RUNinstruções.

Seguindo nosso exemplo python, se precisássemos da numpybiblioteca para executar nosso código, poderíamos adicionar a RUNinstrução logo após nosso FROMcomando.

# Dockerfile
FROM python:3
# NEW LINERUN pip3 install numpy
WORKDIR /src/app
COPY . .
CMD [ "python", "./hello.py" ]

A RUNinstrução basicamente dá um comando a ser executado pelo terminal do contêiner. Assim, como nossa imagem base já vem com pip3instalada, podemos usar pip3 install numpy.

Nota: Para uma aplicação real python, você provavelmente adicionar todas as dependências que você precisa para um requirements.txtarquivo, copie-o para o recipiente, em seguida, atualizar a RUNinstrução para RUN pip3 install -r requirements.txt.

Parte 4. "Olá, mundo!" de uma imagem Nginx usando um contêiner desanexado de longa duração

Sei que você provavelmente está cansado de me ouvir dizer isso, mas tenho mais um "olá" para dizer antes de ir. Vamos seguir em frente e usar nosso docker power recém-adquirido para criar um contêiner simples de longa duração, em vez dos de curta duração que usamos até agora.

Crie um index.htmlarquivo em uma nova pasta com o seguinte conteúdo.

# index.html

Hello, World!

Agora, vamos criar um novo Dockerfile na mesma pasta.

# Dockerfile
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY . .

Construa a imagem e dê um nome a ela simple_nginx, como fizemos anteriormente.

docker build -t simple_nginx .

Por último, vamos executar nossa imagem recém-criada com o seguinte comando:

docker run --rm -d -p 8080:80 simple_nginx

Você pode estar pensando que nada aconteceu porque você voltou ao seu terminal, mas vamos dar uma olhada mais de perto com o docker pscomando.

O docker pscomando mostra todos os contêineres em execução em sua máquina. Como você pode ver na imagem acima, tenho um contêiner chamado simple_nginxem execução na minha máquina agora. Vamos abrir um navegador da web e ver se nginxfunciona acessando localhost:8080.

Tudo parece estar funcionando conforme o esperado e estamos servindo uma página estática por meio da nginxexecução em nosso contêiner. Vamos dedicar um momento para entender como conseguimos isso.

Quebrando isso

Vou pular a explicação do Dockerfile porque já aprendemos esses comandos na última seção. A única coisa "nova" nessa configuração é a nginx:alpineimagem, sobre a qual você pode ler mais aqui.

Além do que é novo, essa configuração funciona porque nginxusa a usr/share/nginx/htmlpasta para procurar um index.htmlarquivo e começar a exibi-lo, portanto, como nomeamos nosso arquivo index.htmle configuramos o WORKDIRpara ser usr/share/nginx/html, essa configuração funcionará imediatamente.

O buildcomando é exatamente igual ao que usamos na última seção, estamos usando apenas a configuração do Dockerfile para construir uma imagem com um determinado nome.

Agora, a parte divertida, a docker run --rm -d -p 8080:80 simple_nginxinstrução. Aqui temos duas novas bandeiras. O primeiro é o -dsinalizador detached ( ), o que significa que queremos executar este contêiner em segundo plano, e é por isso que estamos de volta ao nosso terminal logo após usar o docker runcomando, embora nosso contêiner ainda esteja em execução.

A segunda nova bandeira é a -p 8080:80opção. Como você deve ter adivinhado, este é o portsinalizador e basicamente está mapeando a porta 8080de nossa máquina local para a porta 80dentro de nosso contêiner. Você poderia ter usado qualquer outra porta em vez de 8080, mas não pode alterar a porta 80sem adicionar uma configuração adicional à nginximagem, uma vez que 80é a porta padrão que a nginximagem expõe.

Nota: Se você quiser parar um contêiner desanexado como este, você pode usar o docker pscomando para obter o nome do contêiner (não imagem) e, em seguida, usar a docker stopinstrução com o nome do contêiner desejado no final da linha.

Parte 5. O fim

É isso aí! Se você ainda está lendo isso, você tem todos os fundamentos para começar a usar o Docker hoje em seus projetos pessoais ou no trabalho diário.

Deixe-me saber o que você achou deste artigo nos comentários, e eu irei escrever um artigo de acompanhamento cobrindo tópicos mais avançados, como docker-composeem algum lugar no futuro próximo.

Se você tiver alguma dúvida, por favor me avise.

Felicidades!