class: center, middle, inverse # Docker .red.center.footnote[Gildásio Júnior] .red.footnote[gildasio@protonmail.com] --- class: center, middle ## Você já ouviu falar de Docker? --- class: center, middle ## Por que você quer aprender isso? --- class: center, middle, inverse # Então bora lá! \o/ --- class: center, middle # Por que usar Docker? --- # Por que usar Docker? ## Ambientes semelhantes Não importa onde você irá rodar a imagem (servidor de produção, testes ou na máquina do desenvolvedor), ela terá sempre a mesma configuração. Se você quiser, pode se comportar de maneira diferente, a depender da configuração passada. :) --- # Por que usar Docker? ## Pacote completo Você poderá ter uma imagem fechada de sua aplicação. Sempre que precisar subir novamente, será a mesma coisa. Isso facilita distribuição, podendo apenas colocar a imagem no repositório público e uma documentação mínima de uso. Com a estrutura de camadas, atualizações ficam mais leves, atualizando apenas a camada necessária. Trabalha com esquema de tags, que pode ser utilizada para versionamento, por exemplo. --- # Por que usar Docker? ## Replicação facilitada Com as imagens prontas e alguns arquivos de configuração, replicar o ambiente ficou muito mais fácil, apenas com alguns comandos. Isso serve para um novo membro no time de desenvolvimento, que precisa do ambiente para começar a codar. Serve para passar o projeto para parte de testes. Serve para replicar o ambiente de produção em caso de necessidade de escalar... --- # Por que usar Docker? ## Diálogo entre o time de desenvolvimento e operação Os requisitos de infra passam a estar no código da aplicação, podendo o time de operação ver e fazer sugestões seguindo boas práticas e padrões da empresa. Todas essas coisas feitas via *merge requests*. --- # Por que usar Docker? ## Comunidade Você, muito provavelmente, não precisará criar sua imagem Docker do zero. Muitas aplicações já tem repositórios oficiais de imagens Docker, a exemplo do [PHP][docker-php] e do [MySQL][docker-mysql]. Quando precisar, bastará referenciar essas imagens que a sua será criada com base nelas. Isso agiliza o tempo de configuração das coisas. --- # Por que usar Docker? ## Limpeza do ambiente Você pode querer testar algum projeto mas além dessa vez, nunca mais precisará da dependência xyz e não quer ela rodando em seu sistema o tempo todo. Basta ter uma imagem desse projeto e rodá-la quando precisar! As dependências do projeto ficarão apenas dentro da imagem Docker, não sujando seu ambiente externo. Pense na gestão de configuração / inventário ;) --- # Por que usar Docker? ## Isolamento do ambiente O que acontece no Docker, fica [*][docker-escape] no Docker. Caso você disponibilize suas aplicações, uma em cada container, e uma delas for comprometida, o escopo de avanço do atacante é restrito ao container em execução, não teria acesso direto a demais containers nem ao host. --- # Por que usar Docker? ## Simplicidade Usar Docker é relativamente fácil. Com alguns comandos simples você já tem o ambiente pronto e funcionando. --- # Mas atenção! ## Docker é massa ## Container é massa # Mas não é resposta para .red[tudo] --- class: center, inverse, middle ## Vamos entender melhor --- # Servidores físicos ## Custo alto * Energia * Espaço * Tempo * Manutenção * Atualização --- # Máquinas virtuais ## Menor, mas ainda alto custo * Espaço * Tempo * Manutenção * Atualização --- # Containers ## Melhor curso, bom desempenho * Otimização de espaço * Tempo reduzido * Atualização simples * Menor overhead --- class: inverse # VM x Container ``` +---------------------------------+ | App 1 | App 2 | App 3 | +---------------------------------+ | Bins/Libs | Bins/Libs| Bins/Libs| +---------------------------------+ | | | | | Guest OS | | | +---------------------------------+ | | Guest OS | | | App 1 | App 2 | App 3 | | | | Guest OS | +---------------------------------+ | | | | | Container| Container | Container| +---------------------------------+ +---------------------------------+ | Hypervisor | | Docker | +---------------------------------+ +---------------------------------+ | OS | | OS | | | | | +---------------------------------+ +---------------------------------+ | | | | | Hardware | | Hardware | | | | | +---------------------------------+ +---------------------------------+ ``` --- # Techniquês * Namespace: isolamento ```bash $ man namespaces ``` * Cgroups: limitação de recursos ```bash $ man cgroups ``` * Netfilter: organização da rede ```bash $ man iptables ``` --- # Imagem & Container * Imagem é a aplicação da configuração necessária * Container é a instanciação da imagem Para não confundir, vamos pensar em POO: > **Classe** está para **imagem** assim como **objeto** está para **container**. .center[Pegou?] --- # Hub Docker Existe o [Hub Docker][docker-hub] que é o repositório oficial de imagens Docker. Nele tem imagens oficiais (como do [PostgreSQL][docker-postgres]) e de usuários (como [gjuniioor/php-pdo-composer][gjuniioor-php-pdo-composer]. Pode ser feito upload direto da imagem ou geração via Dockerfile. > Gerando via Dockerfile você pode configurar geração automática a partir de um repositório git. Esse é só um registry, o oficial. Você pode criar um para sua empresa, por exemplo. O próprio [Gitlab cria registry][gitlab-registry] para os repositórios, dá uma olhada nessa feature ^^ --- # Instalação * [Debian][install-debian] * [Ubuntu][install-ubuntu] * [ArchLinux][install-arch] * [Documentação do docker][install-docs] --- # .red[Dica]: Grupo Docker Para facilitar o uso do Docker, tirando a necessidade de `sudo` para rodar ele, coloque seu usuário no grupo `docker`: ```bash $ sudo usermod -a -G docker $USER ``` Quando você refizer a sessão já vai entrar nesse grupo e tudo certo, mas enquanto isso, pode entrar no grupo em um terminal: ```bash $ newgrp - docker $ groups ``` --- class: center, middle, inverse ## Vamos lá! # .red[Mão na massa!] --- # Comandos básicos ## Listar imagens ```bash $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ``` Esse comando mostra algumas informações das imagens que temos baixadas: * Repositório * Tag * Id da imagem * Data de criação * Tamanho ```bash $ man docker-images ``` --- # Comandos básicos ## Baixar imagens ```bash $ docker pull gjuniioor/php-pdo-composer ``` Esse comando vai baixar a imagem especificada. Se não tiver configurado algum outro registry, irá utilizar o padrão (Hub Docker). Perceba que quando baixa, aparecem vários IDs. São as camadas que falamos. Elas que permitem um melhor aproveitamento de storage. Compartilhando camadas em comum entre imagens diminui necessidade de uso de disco e banda. ```bash $ man docker-pull ``` [Demonstração][asciinema-pull] --- # Comandos básicos ## Rodar imagem ```bash $ docker run hello-world ``` Esse comando simples vai criar um container utilizando a imagem `hello-world`. Você lembra que não tínhamos essa imagem localmente, certo? Pois bem, o Docker já percebeu isso e se encarregou de fazer o download. ```bash $ man docker-run ``` [Demonstração][asciinema-run] --- # Comandos básicos ## Listar containers ```bash $ docker ps ``` Veja que não aparece nenhum container. *Mas não criamos um agora mesmo?* Pois é, mas ele teve vida curta e não está mais rodando. Será que temos algum parâmetro para ver todos containers, não só os que estão rodando? Olha o manual: ```bash $ man docker-ps ``` --- # Comandos básicos ## Labutando com container ```bash $ docker start $(docker ps -lq) $ docker restart $(docker ps -lq) $ docker stop $(docker ps -lq) $ docker rm $(docker ps -lq) ``` Esses comandos vão, respectivamente, dar start, reiniciar, parar e remover o último container criado. Veja uma lista completa de ações nos containers: ```bash $ docker container --help ``` --- # Comandos básicos ## Executando comandos no container Você pode executar comandos no container, seja na inicialização ou depois que eles está rodando já. Para ser na inicialização: ```bash $ docker run ... bash ``` Sendo quando ele está rodando: ```bash $ docker exec -it containerID/containerName bash ``` --- # Parâmetros de containers ## Deletando após executar Talvez você precise rodar um container somente para executar um determinado processo naquele instante, não quer que ele fique como um serviço. Sendo assim, se for criar o container e pronto, ele ficará armazenado, ocupando espaço. Você pode especificar que ele seja deletado após rodar: ```bash $ docker run --rm ... ``` Assim que o container retornar, ele será apagado. [Demonstração][asciinema-run-rm] --- # Parâmetros de containers ## Mapeamento de volumes Volta e meia você precisará comunicar arquivos entre host e containers, por diversos motivos, por exemplo: * Passar pro container código que está no host * Persistir arquivos Para fazer isso: ```bash $ docker run -v $PWD:/var/www/html ... ``` Esse comando vai deixar disponível o diretório em `$PWD` do host no diretório `/var/www/html` do container. --- # Parâmetros de containers ## Mapeamento de portas Container vai rodar processos, certo? Para esses processos serem usados como um serviço externo, precisará se comunicar por alguma porta. Você pode fazer um mapeamento, uma ligação, entre porta do container com porta do host. Para isso: ```bash $ docker run -p 8080:80 ... ``` Esse comando vai deixar disponível na porta 8080 do host o que está rodando na porta 80 do container. --- # Parâmetros de containers ## Link entre containers Como a ideia do container é que seja isolado, então se sua aplicação precisa de vários serviços (como aplicação e banco, ou mesmo aplicações com arquitetura de micro-services), então faz sentido cada container ter um serviço que é utilizado por outro. O Docker cria uma rede que permite que um container acesse o outro, normalmente na rede 172.17.0.0/16. Para facilitar, você pode fazer o link utilizando nomes: ```bash $ docker run --link $(docker ps -lq):banco ... ``` Isso vai permitir acesso ao último container criado utilizando o nome `banco`. --- # Parâmetros de containers ## Nomeando containers É interessante que você se organize nomeando seus containers, tanto para fazer sentido para você qual aplicação está rodando ali, como para facilitar em um processo de link, por exemplo. Para tal: ```bash $ docker run --name projeto_banco ... ``` Esse comando cria o container o nomeando para `projeto_banco`. --- # Parâmetros de container ## Limitação de recursos Ao rodar um container você pode limitar os recursos que ele irá utilizar. Por exemplo, usando o parâmetro `-m` para memória RAM e `-c` sobre compartilhamento de CPU. Não vou mostrar exemplos, esses e outros parâmetros possíveis fica como dever de casa, olhem a documentação :) ```bash $ man docker-run ``` --- ## Vamos dar mais um passo? Só em utilizar o básico do Docker assim já ajuda bastante, em questão de isolamento e tudo mais. Mas que tal se a gente avançar um pouco mais? Bora ver agora como criar nossas próprias imagens :) --- # Imagens ## Anatomia ### Da origem * Oficiais: são imagens feitas pela empresa Docker, seus nomes não tem referência a um usuário. São um ponto de partida. Ex: Debian, Alpine. * Não oficiais: são imagens feitas pelos usuários, que normalmente utilizam uma imagem oficial como ponto de partida. Ex: [gjuniioor/php-sqlsrv][gjuniioor-php-sqlsrv]. ### Da criação * Commit: você pode executar comandos dentro do seu container e quanto quiser, criar uma imagem a partir do estado daquele container com o comando `docker commit`. * Dockerfile: você pode criar um arquivo de configuração que irá criar sua imagem. Essa prática é interessante pois você consegue, por exemplo, versionar melhor sua imagem. --- # Criando imagens ## Método: Commit Para criar com o método commit, crie um container, faça modificações nele e crie a imagem: ```bash $ docker run -it imagem bash [container] # apt update [container] # apt install wget -y $ docker commit -a "Autor (email)" -m "descrição" \ $(docker ps -lq) gjuniioor/debian:wget ``` [Demonstração][asciinema-commit] --- # Criando imagens ## Método: Dockerfile Para criar uma imagem com esse método você terá que criar um arquivo `Dockerfile` que irá ter a configuração de sua imagem, e depois executar o comando de build: Veja um exemplo abaixo: ```yaml FROM alpine:latest MAINTAINER Gildásio Júnior (gildasio@protonmail.com) RUN apk add curl ``` ```bash $ docker build . -t gjuniioor/alpine:curl ``` [Demonstração][asciinema-dockerfile] --- # Dockerfile Veja alguns parâmetros do Dockerfile e o que representam: * From * Maintaner * Run * Copy * Add * Cmd * Expose ```bash $ man Dockerfile ``` --- # Dockerfile ## Dica: prestenção na ordem dos comandos * Coloque no topo instruções que são dependências de outras, afinal, Dockerfile é uma espécie de script, em que as primeiras linhas serão executadas antes. * Coloque no final instruções que são comumente alteradas, assim terá um melhor aproveitamento do cache do Docker, dado que quando o arquivo é alterado, apenas são refeitas as operações de build da parte que alterou para baixo. --- # Criando imagens ## Disponibilizando para comunidade Vamos ver duas formas de disponibilizar imagens: * Upload: Pegaremos a imagem que criamos e vamos fazer upload de toda ela para o Docker Hub. * Build automático: Vamos pegar um Dockerfile e colocar para que o Docker Hub crie a imagem. --- # Disponibilizando imagem ## Upload [Demonstração][asciinema-upload] --- # Disponibilizando imagem ## Build automático .center[
] --- # Criação de imagens ## Dicas * Não instale programas desnecessários ao funcionamento do serviço * Busque ter apenas um serviço/processo na sua imagem * Diminua a quantidade de camadas agrupando comandos (mas atente-se à volatilidade dos comandos agrupados) * Use dockerignore para diminuir cópia de arquivo * Prefira `COPY` ao `ADD` * Use imagem de base mínima, e prefira oficiais * Escreva logs no `stdout` * Exponha portas de maneira privada: `EXPOSE 80` --- class: center middle inverse # Bora ver a cereja do bolo? # :P --- class: center middle inverse # .red[Docker Compose] --- # Docker Compose * Ferramenta para rodar aplicações multi-container * Definição em arquivo YAML * Não mais geração de imagem, agora é gerência de containers ;) * Pequeno exemplo: ~~~ yaml version: '3' services: web: build: . ports: - "5000:5000" volumes: - .:/code - logvolume01:/var/log links: - redis redis: image: redis volumes: logvolume01: {} ~~~ --- # Usando Docker Compose Basicamente, é seguir os passos: 1. Definir o ambiente de sua aplicação no `Dockerfile` 1. Definir todos os serviços de sua aplicação no `docker-compose.yml` 1. Rodar a aplicação com o `docker-compose up` --- # Features interessantes * Isolamento de múltiplos ambientes em um mesmo host * Preserva dados de volumes quando containers são criados * Só recria containers que tiveram mudanças --- # Docker compose ## Comandos básicos `docker-compose up [-d] [--build]` Roda os serviços definidos no `docker-compose.yml` O `-d` faz com que essa inicialização seja feita em background, e portanto, os logs não serão mostrados na `stdout` Por padrão, só irá recriar containers que tenham sofrido mudanças A opção `--build` forçará criar as imagens antes de inicializar os containers --- # Docker compose ## Comandos básicos `docker-compose down [--rmi type] [-v]` Para e deleta os containers Com a opção `--rmi type` é possível deletar as imagens utilizadas pelos serviços definidos. `type` pode ser: * `all`: para remover todas as imagens * `local`: para remover apenas as que foram criadas Já com a opção `-v` pode forçar que delete também os volumes especificados no `docker-compose.yml` --- # Docker compose ## Comandos básicos `docker-compose build` Irá recriar as imagens dos serviços Muito útil quando você tenha mudado algo no `Dockerfile` ou mesmo em algum arquivo que é importanto na criação da imagem --- # Docker compose ## Comandos básicos Esses são os mais interessantes para se falar agora, em minha opinião Existem vários outros, como `pause`, `start`, `stop`, `kill` ... Veja mais na [referência oficial][docker-compose-commands] --- class: inverse center middle # Como isso tudo fará sua vida melhor? --- # Basta botar em prática! --- # Basta botar em prática! * Crie `Dockerfile` (ou mesmo uma imagem "base") para sua aplicação, documente e a utilize Exemplo: [gjuniioor/php-sqlsrv][gjuniioor-php-sqlsrv] ~~~ $ docker run -d --name app_banco -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=dasdasdas' \ -p 1433:1433 -v $(pwd)/database/:/var/opt/mssql \ microsoft/mssql-server-linux:2017-latest $ docker run -it --name app_src -p 8000:8000 --link app_banco:banco \ -v $(pwd)/src:/var/www/html/ gjuniioor/php-sqlsrv:7.0 bash ~~~ --- # Basta botar em prática! * Perceba que nem sempre você precisará criar as coisas do zero Exemplo: * [jekyll][jekyll] * [django][django] * [rails][rails] * [wordpress][wordpress] --- # Basta botar em prática! * Torne toda sua aplicação simples de rodar criando um `docker-compose.yml` ~~~ yaml # Exemplo: Django # Dockerfile FROM python:3 RUN mkdir /code WORKDIR /code ADD requirements.txt /code/ RUN pip install -r requirements.txt ADD . /code/ # docker-compose.yml version: '3' services: db: image: postgres web: build: . command: python3 manage.py runserver 0.0.0.0:8000 volumes: - .:/code ports: - "8000:8000" depends_on: - db ~~~ --- # Basta botar em prática! ~~~ yaml # Dockerfile FROM ruby:2.5 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # docker-compose.yml version: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/myapp ports: - "3000:3000" depends_on: - db ~~~ --- # Basta botar em prática! ~~~ yaml services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest ports: - "8000:80" restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress volumes: db_data: ~~~ --- class: inverse center middle # Obrigado! .red[Gildásio Júnior] .red[gildasio@protonmail.com] [docker-php]: https://hub.docker.com/_/php [docker-mysql]: https://hub.docker.com/_/mysql [docker-escape]: https://dadario.com.br/preventing-docker-escaping-attacks/ [docker-hub]: https://hub.docker.com [docker-postgres]: https://hub.docker.com/_/postgres [gjuniioor-php-pdo-composer]: https://hub.docker.com/r/gjuniioor/php-pdo-composer/ [install-docs]: https://docs.docker.com/install/ [install-debian]: https://docs.docker.com/install/linux/docker-ce/debian/ [install-arch]: https://wiki.archlinux.org/index.php/docker [install-ubuntu]: https://docs.docker.com/install/linux/docker-ce/ubuntu/ [gitlab-registry]: https://docs.gitlab.com/ee/user/project/container_registry.html [gjuniioor-php-sqlsrv]: https://hub.docker.com/r/gjuniioor/php-sqlsrv [asciinema-commit]: https://asciinema.org/a/v8hvtalPgSY7YYAGeWej2DCZD [asciinema-dockerfile]: https://asciinema.org/a/9nz7jKMSozxlEluTf567Z3puL [asciinema-run-rm]: https://asciinema.org/a/QYiVjAO3xegiicy4vjSq46AIo [asciinema-run]: https://asciinema.org/a/KfAC1y7rQq2jfTH1fVE6d62ka [asciinema-pull]: https://asciinema.org/a/6IJvySrteoQPWfekgOTMTxNSC [asciinema-upload]: https://asciinema.org/a/XjN5mKXC6gTEGBYkQMHX4bD9c [jekyll]: https://hub.docker.com/r/jekyll/jekyll/ [django]: https://hub.docker.com/_/django/ [rails]: https://hub.docker.com/_/rails/ [wordpress]: https://hub.docker.com/_/wordpress/ [docker-compose-commands]: https://docs.docker.com/compose/reference/