Skip to content

Instantly share code, notes, and snippets.

@paulo-neto
Last active January 15, 2023 12:39
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save paulo-neto/0eed0b62082a6ef5faae8228f00ae193 to your computer and use it in GitHub Desktop.
Save paulo-neto/0eed0b62082a6ef5faae8228f00ae193 to your computer and use it in GitHub Desktop.

Imagens para o docker

https://hub.docker.com/

Comandos para o docker

Rodar uma imagem

docker run nome_imagem

ver os containers que estão sendo executados no momento

docker ps

ver os containers que estão parados no momento

docker ps -a

fazer com que o terminal da nossa máquina seja integrado ao terminal de dentro do container

docker run -it nome_imagem

iniciar um container criado anteriormente

docker start id_do_container

parar um container criado anteriormente

docker stop id_do_container

removendo containers

docker rm id_do_container

limpar todos os containers inativos

docker container prune

listando e removendo imagens

docker images
docker rmi nome_container

rodar uma imagem sem "travar" o terminal

docker run -d nome_container

linkar essa porta interna do container a uma porta do nosso computador

docker run -d -P nome_container

ver as portas linkadas

docker port id_container

nomear um container

docker run -d -P --name meu-site nome_container

definindo uma porta específica

docker run -d -p 12345:80 nome_container

atribuindo uma variável de ambiente

docker run -d -P -e AUTHOR="Paulo Neto" dockersamples/static-site

parando todos os containers de uma só vez

docker stop $(docker ps -q)

Camadas de uma imagem

Toda imagem que baixamos é composta de uma ou mais camadas, e esse sistema tem o nome de Layered File System. Essas camadas podem ser reaproveitadas em outras imagens. Por exemplo, se já temos a imagem do Ubuntu, isso inclui as suas camadas, e agora queremos baixar a imagem do CentOS. Se o CentOS compartilha alguma camada que já tem na imagem do Ubuntu, o Docker é inteligente e só baixará as camadas diferentes, e não baixará novamente as camadas que já temos no nosso computador: Uma outra vantagem é que as camadas de uma imagem são somente para leitura. Mas como então conseguimos criar arquivos na aula anterior? O que acontece é que não escrevemos na imagem, já que quando criamos um container, ele cria uma nova camada acima da imagem, e nessa camada podemos ler e escrever:

Docker e DevOps

DevOps é um tópico bem amplo que envolve tanto aspectos culturais como técnicos, mas possui como principal objetivo aumentar a qualidade e eficiência da entrega de software. DevOps é uma metodologia que visa integrar os times de desenvolvimento com infraestrutura e o Docker está tendo um papel importante nessa tarefa.

Repare que com Docker os desenvolvedores não precisam se preocupar em configurar um ambiente de desenvolvimento específico de cada vez. Em vez disso, eles podem se concentrar na construção de um código de boa qualidade. Isso, obviamente, leva à aceleração nos esforços de desenvolvimento. O Docker facilita muito construtir o ambiente: é rapido, simples e confiável.

No outro lado, para a equipe de operações de TI / Sysadmins, o Docker possibilita configurar ambientes que são exatamente como um servidor de produção e permite que qualquer pessoa trabalhe no mesmo projeto com exatamente as mesmas configurações, independentemente do ambiente de host local. As configurações são descritas em arquivos simples facilmente aplicáveis pelo desenvolvedor.

Com a padronização de um entregável Docker é possível que o desenvolvedor tenha um ambiente similar ao de produção na sua máquina sem todo o custo de configuração e o sysadmin consiga lidar apenas com um tipo de entregável conseguindo, desta forma, dar atenção aos desafios de monitoramento e orquestração para que nada dê errado. Neste caso o melhor para os dois.

Trabalhando com volumes

Quando escrevemos em um container, assim que ele for removido, os dados também serão. Mas podemos criar um local especial dentro dele, e especificamos que esse local será o nosso volume de dados. Quando criamos um volume de dados, o que estamos fazendo é apontá-lo para uma pequena pasta no Docker Host. Então, quando criamos um volume, criamos uma pasta dentro do container, e o que escrevermos dentro dessa pasta na verdade estaremos escrevendo do Docker Host. Isso faz com que não percamos os nossos dados, pois o container até pode ser removido, mas a pasta no Docker Host ficará intacta.

criar um volume

docker run -v "/var/www" ubuntu    

inspecionar um container

docker inspect id_container

especificar a pasta que será referenciada pelo volume

docker run -it -v "/home/paulo-neto/Desktop:/var/www" nome_container

Rodando código em um container

Já vimos que o que escrevemos no volume (pasta /var/www do container) aparece na pasta configurada da nossa máquina local, que no vídeo anterior foi o Desktop. Mas podemos pensar o contrário, ou seja, tudo o que escrevemos no Desktop será acessível na pasta /var/www do container. Isso nos dá a possibilidade de implementar localmente um código de uma linguagem que não está instalada na nossa máquina, e colocá-lo para compilar e rodar dentro do container. Se o container possui Node, Java, PHP, seja qual for a linguagem, não precisamos tê-los instalados na nossa máquina, nosso ambiente de desenvolvimento pode ser dentro do container.

rodando um codigo em um servidor node

docker run -p 8080:3000 -v "C:\Users\Alura\Desktop\volume-exemplo:/var/www" -w "/var/www" node npm start

melhorando o comando com pwd

docker run -p 8080:3000 -v "$(pwd):/var/www" -w "/var/www" node npm start

Montando o Dockerfile

Geralmente, montamos as nossas imagens a partir de uma imagem já existente. Nós podemos criar uma imagem do zero, mas a prática de utilizar uma imagem como base e adicionar nela o que quisermos é mais comum. Para dizer a imagem-base que queremos, utilizamos a palavra FROM mais o nome da imagem.

criando a partir da imagem do node na última versão

FROM node:latest

adicionar quem é que mantém a imagem

FROM node:latest
MAINTAINER Paulo Neto

colocando o código, ou outros arquivos, dentro da imagem

FROM node:latest
MAINTAINER Douglas Quintanilha
COPY . /var/www

atualizando as dependências do node

FROM node:latest
MAINTAINER Douglas Quintanilha
COPY . /var/www
RUN npm install

subindo o servidor node quando o container subir

FROM node:latest
MAINTAINER Douglas Quintanilha
COPY . /var/www
RUN npm install
ENTRYPOINT npm start

indicar inde os comandos serão executados(workdir)

FROM node:latest
MAINTAINER Douglas Quintanilha
COPY . /var/www
WORKDIR /var/www
RUN npm install
ENTRYPOINT npm start
EXPOSE 3000

Criando a imagem

Para criar a imagem, precisamos fazer o seu build através do comando docker build, comando utilizado para buildar uma imagem a partir de um Dockerfile. Para configurar esse comando, passamos o nome do Dockerfile através da flag -f. Dockerfile é o padrão, poderíamos omitir esse parâmetro, mas se o nome for diferente, por exemplo node.dockerfile, é preciso especificar.

passar a tag da imagem, o seu nome, através da flag -t, para imagens não-oficiais colocamos o nome no padrão

docker build -f Dockerfile -t pauloneto/node

indicar onde o docker file está. Como já estamos rodando o comando dentro da pasta utilizamos um ponto (.)

docker build -f Dockerfile -t pauloneto/node .

criando um container a partir da nossa imagem

docker run -d -p 8080:3000 douglasq/node

no Dockerfile, também podemos criar variáveis de ambiente, utilizando o ENV. Por exemplo, para criar a variável PORT, para dizer em que porta a nossa aplicação irá rodar, fazemos:

FROM node:latest
MAINTAINER Douglas Quintanilha
ENV PORT=3000
COPY . /var/www
WORKDIR /var/www
RUN npm install
ENTRYPOINT npm start
EXPOSE $PORT

Enviando a imagem criada para o docker hub

O primeiro passo é criar a nossa conta. Com ela criada, no terminal nós executamos o comando docker login e digitamos o nosso login e senha que acabamos de criar. Após isso, basta executar o comando docker push, passando para ele a imagem que queremos subir, por exemplo: docker push pauloneto/node para baixar uma imagem podemos utilizar o comando docker pull: docker pull pauloneto/node

Redes com Docker

No Docker, por padrão, já existe uma default network. Isso significa que, quando criamos os nossos containers, por padrão eles funcionam na mesma rede.

verificar o ip do container

hostname -i

instalar o iputils-ping no container

apt-get update && apt-get install iputils-ping

Comunicação entre containers utilizando os seus nomes

O Docker criar uma rede virtual, em que todos os containers fazem parte dela, com os IPs automaticamente atribuídos. Mas quando os IPs são atribuídos, cada hora em que subirmos um container, ele irá receber um IP novo, que será determinado pelo Docker. Logo, se não sabemos qual o IP que será atribuído, isso não é muito útil quando queremos fazer a comunicação entre os containers. Por exemplo, podemos querer colocar dentro do aplicativo o endereço exato do banco de dados, e para saber exatamente o endereço do banco de dados, devemos configurar um nome para aquele container. Mas nomear um container nós já sabemos, basta adicionar o --name, passando o nome que queremos na hora da criação do container, certo? Apesar de conseguirmos dar um nome a um container, a rede do Docker não permite com que atribuamos um hostname a um container, diferentemente de quando criamos a nossa própria rede. Na rede padrão do Docker, só podemos realizar a comunicação utilizando IPs, mas se criarmos a nossa própria rede, podemos "batizar" os nossos containers, e realizar a comunicação entre eles utilizando os seus nomes:

image figura 01

Isso não pode ser feito na rede padrão do Docker, somente quando criamos a nossa própria rede.

Criando a nossa própria rede do Docker

Podemos criar a nossa própria rede, através do comando docker network create, mas não é só isso, para esse comando também precisamos dizer qual driver vamos utilizar. Para o padrão que vimos, de ter uma nuvem e os containers compartilhando a rede, devemos utilizar o driver de bridge.

Especificamos o driver através do --driver e após isso nós dizemos o nome da rede. Um exemplo do comando é o seguinte: docker network create --driver bridge minha-rede

Agora, quando criamos um container, ao invés de deixarmos ele ser associado à rede padrão do Docker, atrelamos à rede que acabamos de criar, através da flag --network. Vamos aproveitar e nomear o container: docker run -it --name meu-container-de-ubuntu --network minha-rede ubuntu

Assim conseguimos realizar a comunicação entre os containers utilizando somente os seus nomes. É como se o Docker Host, o ambiente que está rodando os containers, criasse uma rede local chamada minha-rede, e o nome do container será utilizado como se fosse um hostname, lembrando que só conseguimos fazer isso em redes próprias, redes que criamos, isso não é possível na rede padrão dos containers.

Sobre o comando pull

Para baixar a imagem ubuntu do Docker Hub você pode usar docker pull ubuntu. Isso é diferente do comando run, que baixa a imagem (se não existe local) e depois criar e roda o container. O pull apenas baixa! Para baixar uma imagem de um usuário especifico existe a sintaxe: docker pull NOME_USUARIO/NOME_IMAGEM Além disso, uma imagem pode ter um tag que serve para pegar uma determinada versão dessa imagem. Existe a tag :latest e uma tag especifica :nome_tag, por exemplo: docker pull pauloneto/nome_imagem:nome_tag

Funcionamento das aplicações em geral

image figura 02

Se formos seguir esse diagrama, teríamos que criar cinco containers na mão, e claro, cada container com configurações e flags diferentes, além de termos que nos preocupar com a ordem em que vamos subi-los.

Docker Compose

Ao invés de subir todos esses containers na mão, o que vamos fazer é utilizar uma tecnologia aliada do Docker, chamada Docker Compose, feito para nos auxiliar a orquestrar melhor múltiplos containers. Ele funciona seguindo um arquivo de texto YAML (extensão .yml), e nele nós descrevemos tudo o que queremos que aconteça para subir a nossa aplicação, todo o nosso processo de build, isto é, subir o banco, os containers das aplicações, etc. Assim, não precisamos ficar executando muitos comandos no terminal sem necessidade.

Para utilizar o Docker Compose, devemos criar o seu arquivo de configuração, o docker-compose.yml, na raiz do projeto. Em todo arquivo de Docker Compose, que é uma espécie de receita de bolo para construirmos as diferentes partes da nossa aplicação, a primeira coisa que colocamos nele é a versão do Docker Compose que estamos utilizando: version:'3' O YAML lembra um pouco o JSON, mas ao invés de utilizar as chaves para indentar o código, ele utiliza espaços. Agora, começamos a descrever os nossos serviços, os nossos services:

   version: '3'
   services:

Seguindo a figura 02 uma aplicação tem vários serviços. Temos NGINX, três Node, e o MongoDB como serviços. Logo, se queremos construir cinco containers, vamos construir cinco serviços, cada um deles com um nome específico. Então, vamos começar construindo o NGINX, que terá o nome nginx:

version: '3'
services:
    nginx:

Em cada serviço, devemos dizer como devemos construí-lo, como devemos fazer o seu build:

version: '3'
services:
    nginx:
        build:

O serviço será construído através de um Dockerfile, então devemos passá-lo onde ele está. E também devemos passar um contexto, para dizermos a partir de onde o Dockerfile deve ser buscado. Como ele será buscado a partir da pasta atual, vamos utilizar o ponto:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .

Construída a imagem, devemos dar um nome para ela, por exemplo pauloneto/nginx:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx

E quando o Docker Compose criar um container a partir dessa imagem, vamos dizer que o seu nome será nginx:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx
        container_name: nginx

Sabemos também que o NGINX trabalha com duas portas, a 80 e a 443. Como não estamos trabalhando com HTTPS, vamos utilizar somente a porta 80, e no próprio arquivo, podemos dizer para qual porta da nossa máquina queremos mapear a porta 80 do container. Vamos mapear para a porta de mesmo número da nossa máquina:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx
        container_name: nginx
        ports:
            - "80:80"

No YAML, toda vez que colocamos um traço, significa que a propriedade pode receber mais de um item. Agora, para os containers conseguirem se comunicar, eles devem estar na mesma rede, então vamos configurar isso também. Primeiramente, devemos criar a rede, que não é um serviço, então vamos escrever do começo do arquivo, sem as tabulações:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx
        container_name: nginx
        ports:
            - "80:80"

networks:

O nome da rede será production-network e utilizará o driver bridge:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx
        container_name: nginx
        ports:
            - "80:80"

networks: 
    production-network:
        driver: bridge

Com a rede criada, vamos utilizá-la no serviço:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx
        container_name: nginx
        ports:
            - "80:80"
        networks: 
            - production-network

networks: 
    production-network:
        driver: bridge

Isso é para construir o serviço do NGINX, agora vamos construir o serviço do MongoDB, com o nome mongodb. Como ele será construído a partir da imagem mongo, não vamos utilizar nenhum Dockerfile, logo não utilizamos a propriedade build. Além disso, não podemos nos esquecer de colocá-lo na rede que criamos:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx
        container_name: nginx
        ports:
            - "80:80"
        networks: 
            - production-network

    mongodb:
        image: mongo
        networks: 
            - production-network

networks: 
    production-network:
        driver: bridge

Falta agora criarmos os três serviços em que ficará a nossa aplicação, node1, node2 e node3. Para eles, será semelhante ao NGINX, com Dockerfile alura-books.dockerfile, contexto, rede production-network e porta 3000:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx
        container_name: nginx
        ports:
            - "80:80"
        networks: 
            - production-network

    mongodb:
        image: mongo
        networks: 
            - production-network

    node1:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: pauloneto/alura-books
        container_name: alura-books-1
        ports:
            - "3000"
        networks: 
            - production-network

    node2:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: pauloneto/alura-books
        container_name: alura-books-2
        ports:
            - "3000"
        networks: 
            - production-network

    node3:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: pauloneto/alura-books
        container_name: alura-books-3
        ports:
            - "3000"
        networks: 
            - production-network

networks: 
    production-network:
        driver: bridge

Ordem dos serviços

Por último, quando subimos os containers na mão, temos uma ordem, primeiro devemos subir o mongodb, depois a nossa aplicação, ou seja, node1, node2 e node3 e após tudo isso subimos o nginx. Mas como que fazemos isso no docker-compose.yml? Nós podemos dizer que os serviços da nossa aplicação dependem que um serviço suba antes deles, o serviço do mongodb:

version: '3'
services:
    nginx:
        build:
            dockerfile: ./docker/nginx.dockerfile
            context: .
        image: pauloneto/nginx
        container_name: nginx
        ports:
            - "80:80"
        networks: 
            - production-network
        depends_on: 
            - "node1"
            - "node2"
            - "node3"

    mongodb:
        image: mongo
        networks: 
            - production-network

    node1:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: pauloneto/alura-books
        container_name: alura-books-1
        ports:
            - "3000"
        networks: 
            - production-network
        depends_on:
            - "mongodb"

    node2:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: pauloneto/alura-books
        container_name: alura-books-2
        ports:
            - "3000"
        networks: 
            - production-network
        depends_on:
            - "mongodb"

    node3:
        build:
            dockerfile: ./docker/alura-books.dockerfile
            context: .
        image: pauloneto/alura-books
        container_name: alura-books-3
        ports:
            - "3000"
        networks: 
            - production-network
        depends_on:
            - "mongodb"

networks: 
    production-network:
        driver: bridge

Subindo os serviços

Garantir que temos todas as imagens envolvidas neste arquivo na nossa máquina. docker-compose build Com os serviços criados, podemos subi-los através do comando docker-compose up. Esse comando irá seguir o que escrevemos no docker-compose.yml, ou seja, cria a rede, o container do MongoDB, os três containers da aplicação e o container do NGINX. Depois, são exibidos alguns logs, sendo que cada um dos containers fica com uma cor diferente, para podermos distinguir melhor.

Para parar a execução, utilizamos o atalho CTRL + C. E não somos obrigados a ficar vendo esses logs, podemos utilizar a já conhecida flag -d: docker-compose up -d

E com o comando docker-compose ps, podemos ter uma visualização simples dos serviços que estão rodando:

Agora que não estamos mais vendo os logs, como paramos os serviços? Para isso, utilizamos o comando docker-compose down. Esse comando para os containers e os remove.

E não é por que eles são serviços, que eles não tem um container por debaixo dos panos, então nós conseguimos interagir com os containers utilizando todos os comandos que já vimos no treinamento, por exemplo para testar a comunicação entre eles: docker exec -it alura-books-1 ping alura-books-2

Mas também podemos utilizar o nome do serviço, não precisamos necessariamente utilizar o nome do container: docker exec -it alura-books-1 ping node2

Docker Compose no linux

O Docker Compose não é instalado por padrão no Linux, então você deve instalá-lo por fora. Para tal, baixe-o na sua versão mais atual, que pode ser visualizada no seu GitHub, executando o comando abaixo: sudo curl -L https://github.com/docker/compose/releases/download/1.15.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose Após isso, dê permissão de execução para o docker-compose: sudo chmod +x /usr/local/bin/docker-compose

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment