Bem-vindos ao nosso curso "Containers na Prática". Meu nome é Maria Lázara. Tenho cabelo castanho, olhos castanhos, mais escuros. Estou vestindo uma camiseta preta, sou branca, e ao fundo temos uma luz levemente azul.
Neste curso, vamos abordar Docker e contêineres de forma totalmente prática e orientada ao mundo real, para que possamos nos aprofundar realmente nesta ferramenta, que é extremamente importante para o mundo de DevOps.
Vamos começar pelo início: o que são os contêineres e como funcionam. Se consultarmos a própria documentação do Docker, veremos que um contêiner é uma unidade padrão de software que empacota o código e todas as suas dependências, permitindo que a aplicação seja executada de forma rápida, confiável e em diferentes ambientes computacionais.
Se houver dúvidas sobre o que isso significa, não há problema, pois vamos nos aprofundar e entender completamente o que, de fato, é um contêiner. No entanto, antes de falarmos sobre o que é um contêiner, precisamos entender a definição de uma aplicação: o que é uma aplicação e do que ela é composta.
Uma aplicação tem como objetivo resolver um problema real. Geralmente, as empresas de tecnologia criam aplicações para resolver um problema específico. E do que é composta uma aplicação? Uma aplicação é composta pelo código-fonte, que é desenvolvido em uma linguagem de programação, seja Python, Java, ou outra, dependendo da aplicação. Geralmente, temos as dependências dessa aplicação, que são bibliotecas e frameworks. É muito raro termos uma aplicação cujo código não utilize uma dependência externa ou uma biblioteca. Normalmente, sempre importamos bibliotecas e frameworks para trabalhar e evitar reinventar a roda.
Além disso, também temos as configurações dessa aplicação, que, dependendo do ambiente em que a aplicação será implantada, terão configurações diferentes. O exemplo clássico é a configuração onde determinamos a comunicação com o banco de dados. Então, se estivermos em um ambiente de produção, por exemplo, a variável de ambiente que definirá a conexão com o banco será diferente para cada ambiente. Em produção, seria o banco de produção; em homologação, seria o banco de homologação, e assim por diante. Portanto, a aplicação depende dessas configurações, que são as variáveis de ambiente e também os arquivos de configuração.
Para sair um pouco do abstrato, vamos trazer isso para a realidade e pensar em uma aplicação real. Imagine uma API muito simples, feita com Flask, que terá um único endpoint que retornará "Olá, Mundo". Aqui está o código da aplicação, muito simples, que retornará "Olá, Mundo" quando executarmos essa aplicação.
Vamos começar importando as bibliotecas necessárias e configurando nossa aplicação Flask:
from flask import Flask, jsonify
import os
app = Flask(__name__)
Neste trecho, importamos o Flask e o jsonify para criar nossa API, além de os para acessar variáveis de ambiente. Em seguida, instanciamos nossa aplicação Flask.
Agora, vamos definir a rota principal da nossa API, que retornará uma mensagem e o ambiente em que a aplicação está rodando:
@app.route("/")
def home():
return jsonify({
"message": "Olá, mundo!",
"env": os.getenv("APP_ENV", "dev")
})
Aqui, criamos uma rota para o caminho raiz ("/") que retorna um JSON com a mensagem "Olá, mundo!" e a variável de ambiente APP_ENV, que por padrão será "dev" se não estiver definida.
Por fim, configuramos a execução da aplicação para que ela rode no host 0.0.0.0 e na porta 5000:
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Com isso, nossa aplicação Flask está pronta para ser executada.
Basicamente, se pensarmos, quais são as dependências da nossa aplicação? Temos o código-fonte, que está desenvolvido em Python. Portanto, haverá um Python em uma versão específica. Uma dependência aqui é a biblioteca, o framework Flask.
Para a API, precisamos do Flask. Observando o código, percebemos que temos a importação do Flask. Além disso, dependemos de algumas configurações, como uma variável de ambiente. Neste caso, precisamos da variável de ambiente definida, como 'dev', para que nossa aplicação funcione corretamente. Portanto, não é apenas o código-fonte que importa, mas também as dependências.
Resumindo, dependemos do Flask, do Python e de variáveis e configurações. Esses elementos compõem nosso ambiente. Nossa aplicação depende desses fatores e recursos para funcionar de forma eficiente.
O que o Docker e a conteinerização fazem é reunir todas essas dependências e transformá-las em um contêiner. Pense em um contêiner físico real: colocamos tudo isso dentro dele, e ele funcionará da mesma maneira em qualquer lugar, seja em nossa máquina ou no servidor de produção.
As principais características de um contêiner são: ele é totalmente isolado, não interferindo com outras aplicações no mesmo host ou servidor; é portátil, funcionando da mesma forma em qualquer lugar, seja em nossa máquina, no servidor de produção ou na máquina de outra pessoa desenvolvedora, independentemente do sistema operacional. Isso nos permite manter um padrão no desenvolvimento de software.
Além disso, os contêineres são independentes, podendo ser criados ou eliminados sem impacto. Foram feitos para serem efêmeros, permitindo criar, destruir e levantar novos contêineres rapidamente, o que facilita o desenvolvimento e a entrega de software pelas empresas.
Por fim, um contêiner é autossuficiente, pois inclui todas as dependências necessárias para a aplicação funcionar, tornando-se independente. No mundo real, um contêiner foi desenvolvido para se encaixar em diversos meios de transporte, estabelecendo um padrão: uma caixa gigante que serve tanto para caminhões quanto para navios e trens. Com os contêineres Docker, conseguimos a mesma portabilidade, formato e padrão, permitindo que o que é executado em um computador pessoal possa ser executado na nuvem ou em servidores físicos, independentemente do ambiente.
Os contêineres podem ser executados em Linux, Windows e outros sistemas operacionais, em centros de dados ou na nuvem, o que explica a popularidade do Docker.
Agora que já entendemos o que são os contêineres, vamos explorar um pouco a história e a evolução dos contêineres. É importante compreender que a ideia dos contêineres não surgiu com o Docker, mas sim por volta dos anos 70. Naquela época, tínhamos os mainframes, que eram computadores extremamente grandes, ocupando quase uma sala inteira, e eram muito caros. Apenas empresas de grande porte, com maior capacidade financeira, podiam adquiri-los. Devido ao alto custo, esses computadores eram compartilhados, pois não era viável ter uma máquina para cada processo da empresa. Assim, já naquela época, havia a necessidade de isolar nossos processos para evitar que um usuário afetasse outro.
Surgiu então a grande questão: como podemos separar os ambientes usando a mesma máquina, o mesmo host? Por volta de 1979, surgiu o chroot, que veio junto com o lançamento do UNIX versão 7. O chroot é uma chamada ao sistema UNIX que altera o diretório raiz de um processo. Dessa forma, o processo acredita estar no diretório raiz, mas na verdade está em um diretório falso, um subdiretório do sistema real. Como podemos ver na imagem, temos um subdiretório raiz falso. Isso já era uma forma de isolamento, mas o chroot isolava apenas os sistemas de arquivos, ainda não havia uma tecnologia capaz de isolar redes, usuários ou a CPU da máquina. Podemos dizer que o chroot foi uma peça conceitual fundamental na história dos contêineres, sendo, por assim dizer, o precursor de tudo, um ancestral distante do que fazemos hoje com os contêineres.
Nos anos 2000, surgiram os FreeBSD Jails. Jails, em inglês, significa "jaula", e era um mecanismo de isolamento do sistema operacional FreeBSD. O FreeBSD derivou do UNIX e permitiu criar vários sistemas isolados, chamados jails, dentro do mesmo sistema operacional, compartilhando o mesmo kernel. Nessa etapa, já havia separação de processos, usuários, sistemas de arquivos e também de rede. Podemos dizer que os FreeBSD Jails são uma extensão do conceito de chroot, mas o FreeBSD não é Linux, é um sistema operacional próprio com seu próprio kernel, nativo desse sistema. Portanto, ainda estávamos falando de um ecossistema muito limitado ao mundo BSD.
Em 2001, surgiu o primeiro esforço no Linux para separar ambientes e aplicações. Surgiu o Linux VServer, que permitia ter vários ambientes isolados dentro do mesmo kernel Linux. Era o mesmo conceito dos FreeBSD Jails, mas agora aplicado ao kernel do Linux. Para usar o VServer, era necessário instalar um patch adicional no kernel Linux, exigindo um kernel modificado. Na prática, isso significava que a instalação e manutenção eram muito complexas, e era necessário entender bastante de Linux para usar o VServer. Não era algo simples; era preciso ter um domínio técnico elevado. Não tínhamos uma camada amigável como a que temos hoje com o Docker, por exemplo.
Em 2004, surgiu o Solaris Zones, que foi extremamente importante para o desenvolvimento dos contêineres, pois compartilhava a mesma ideia conceitual dos anteriores: dividir o sistema Solaris, um sistema operacional da Oracle, em vários ambientes isolados, chamados de zones. Havia as Global Zones, que eram o sistema principal que rodava no host, e as Non-Global Zones, que eram os ambientes isolados em si, que podemos considerar como contêineres. Havia isolamento de sistema de arquivos, rede, processos e controle de recursos. O interessante do Solaris Zones, que foi muito popular na época, é que era muito fácil replicar ambientes, quase instantaneamente. Possuía uma tecnologia que, para a época, era impressionante, sendo amplamente utilizado. No entanto, o problema é que também estava restrito ao ecossistema Solaris, da Oracle, e Solaris é um sistema operacional da família UNIX, mas não é Linux.
Em 2005, finalmente surgiu uma solução que dividia o kernel Linux em vários ambientes isolados: o OpenVZ, que vem de Open Virtuozzo. A Virtuozzo é uma empresa de tecnologia que já trabalhava com isolamento e criou o Open Virtuozzo, que dividia o kernel Linux em ambientes isolados. O interessante é que essa ferramenta alcançou escala comercial, por meio das VPS, que eram servidores privados, Virtual Private Servers. O OpenVZ também surgiu como um patch do kernel, exigindo um kernel modificado, e era mais evoluído que o VServer, pois já possuía ferramentas de administração próprias, permitindo melhor controle de recursos e isolamento mais completo, especialmente a nível de rede.
Entre 2006 e 2008, ocorreu uma mudança significativa com o surgimento dos cgroups (grupos de controle) e dos namespaces. Esses dois são mecanismos do kernel Linux, recursos nativos que possibilitaram a criação dos contêineres. Os control groups limitam os recursos da nossa máquina, como CPU, memória, leitura/escrita em disco e número de processos. Já os namespaces são uma divisão mais lógica, que isola processos, a rede, entre outras coisas.
Em 2008, surgiu o LXC, que significa Linux Containers (Contêineres Linux), possibilitando a criação de ambientes isolados dentro do Linux. Isso foi possível pela combinação de namespaces e cgroups. O LXC foi uma evolução prática dessas duas características do kernel Linux, fornecendo ferramentas para gerenciar contêineres e utilizar recursos nativos do kernel. Dessa forma, trabalhar com contêineres no Linux tornou-se menos complexo, pois não era necessário adicionar ou modificar o kernel para isso.
Em 2013, surgiu o Docker. Inicialmente, o Docker utilizava o LXC como base. Pode-se questionar: se o Docker usava LXC e não havia diferença técnica, por que o Docker se destacou? O Docker se destacou e tornou-se extremamente popular por sua simplicidade. Ele não foi criado apenas pensando na tecnologia de isolamento, mas como uma plataforma para distribuir aplicações. Além do LXC, que era o conceito técnico de isolamento do kernel com cgroups e namespaces, o Docker trouxe a possibilidade de ter imagens versionadas e portáteis em camadas. Falaremos mais sobre as imagens posteriormente.
O Docker transformou os contêineres em uma unidade de entrega de software. Ele é extremamente simples de usar, com foco nas pessoas desenvolvedoras. Hoje, para executar contêineres, não é necessário ser um especialista em Linux. O foco passou do gerenciamento de infraestrutura para o desenvolvimento de software. As pessoas desenvolvedoras não precisam ser especialistas em Linux para criar um ambiente isolado. Além disso, o Docker criou um ecossistema e um registro central onde é possível compartilhar imagens, acelerando o desenvolvimento de software, padronizando o desenvolvimento e permitindo que as empresas entreguem seus produtos com mais velocidade e qualidade, aumentando a produtividade. Por isso, o Docker se tornou tão popular e famoso.
Entre 2014 e 2015, o Docker evoluiu, como toda tecnologia, e criou sua própria biblioteca de execução chamada LibContainer, que posteriormente se tornou o que conhecemos hoje como runc. Isso significou que o Docker deixou de usar o LXC e criou sua própria biblioteca, resultando no padrão OCI.
A OCI, Open Container Initiative, é uma organização criada por empresas e pela comunidade de desenvolvimento para estabelecer um padrão na criação de contêineres e imagens. Isso evita a fragmentação no ecossistema de contêineres e permite a padronização. A OCI define como executamos um contêiner, como devem ser as configurações e os processos, além de especificar como as imagens devem ser construídas, qual é o formato de empacotamento e a distribuição dessas imagens.
Desde 2014, surgiram tecnologias focadas em orquestrar milhares de contêineres, devido à popularidade crescente dos contêineres. A questão passou a ser como gerenciar milhares deles. Nesse contexto, surgiu o Kubernetes, uma ferramenta para orquestrar, programar e escalar contêineres. Por isso, é extremamente popular e recomendada para aprofundamento.
Os contêineres deixaram de ser locais e se tornaram uma arquitetura totalmente distribuída. Hoje, temos contêineres em praticamente tudo. Serverless utiliza contêineres por trás, plataformas como serviço usam contêineres, e o isolamento em si se tornou uma commodity, sendo o padrão no desenvolvimento de software.
Concluindo esta aula, os contêineres não são uma novidade. O que mudou não foi a ideia original, que vem dos anos 70, mas a forma como os usamos e construímos. Continuamos evoluindo, e as tecnologias não param de avançar, especialmente hoje com a IA. Certamente, encontraremos formas de fazer mais com menos, otimizando e melhorando os processos continuamente.
Nos vemos na próxima aula.
Vamos discutir um pouco agora sobre a arquitetura do Docker. A arquitetura do Docker é baseada em cliente e servidor. O cliente Docker, mais conhecido como Docker Client, se comunica com o daemon do Docker, chamado Docker G, que é o componente responsável por executar nossos objetos do Docker.
O Docker Client é a principal forma de interagirmos com o Docker. Ele envia as instruções para o daemon do Docker, o Docker G. O Docker daemon é quem realmente gerencia os objetos do Docker, realizando as operações nos bastidores. Ele escuta as solicitações da API do Docker e gerencia os objetos, criando imagens, gerenciando contêineres, redes e volumes. O Docker daemon pode ser executado tanto localmente quanto em outra máquina, em outro host.
Para simplificar, temos o Docker Client, que se comunica com o Docker daemon através da API REST. Um sempre se comunica com o outro. Em uma visão mais ampla, temos o Docker Client, que é o cliente do Docker, e o servidor, que neste caso é o host. O cliente sempre se comunica com o Docker daemon. Assim, quando executamos comandos a partir de nossa máquina, como docker run, docker build, docker pull, ele se comunica com o Docker daemon, que é quem realmente cria os componentes, os objetos do Docker, que são as imagens e os contêineres.
Vamos ver como esses comandos são utilizados na prática:
Para executar um contêiner a partir de uma imagem, usamos o comando docker run:
docker run
Esse comando solicita ao Docker daemon que inicie um contêiner baseado em uma imagem especificada.
Para construir uma imagem a partir de um Dockerfile, utilizamos o comando docker build:
docker build
Esse comando instrui o Docker daemon a criar uma imagem com base nas instruções definidas no Dockerfile.
Para baixar uma imagem de um registry, como o Docker Hub, usamos o comando docker pull:
docker pull
Esse comando faz com que o Docker daemon baixe a imagem especificada do registry para que possa ser usada localmente.
Temos também o registry. O que é um registry? É onde armazenamos nossas imagens Docker. É um local centralizado, um servidor, onde mantemos nossas imagens, permitindo que as pessoas compartilhem e armazenem suas imagens. Isso pode ser feito de forma privada, como geralmente fazem as empresas, onde apenas determinadas pessoas têm acesso às imagens, ou de forma pública, onde todos podem acessar suas imagens. O Docker Hub é o registry padrão do Docker.
Assim, temos o Docker Hub, sobre o qual falaremos mais adiante nas próximas aulas. Ele é o registry (registro) padrão do Docker, mas não temos apenas esse registry. Existem outros tipos, como, por exemplo, o ECR, que é da AWS, o ACR, que é da Azure, entre diversas outras opções.
Agora, vamos falar um pouco sobre os objetos Docker. Como explicamos, o dockerd, o Docker daemon, é quem gerencia os objetos Docker. E o que são objetos Docker? Docker constrói, distribui e executa aplicações em contêineres, e para fazer isso, manipula certas estruturas fundamentais, que chamamos de objetos Docker.
Vamos passar por esses objetos, dando uma definição simples do que são. Nas próximas aulas, à medida que avançarmos ao longo do curso, aprofundaremos em cada um desses objetos.
O primeiro objeto são as imagens. O que são as imagens? As imagens são modelos imutáveis para criar um contêiner. Pense em um modelo, uma estrutura, que define exatamente tudo o que queremos para nossa aplicação, incluindo o sistema de arquivos e as dependências. Compilamos isso em uma imagem e, com base nessa imagem, podemos criar os contêineres.
Um contêiner é uma instância executável de uma imagem. Assim, temos uma imagem e, com base nela, criamos um contêiner. Podemos ter uma imagem e criar vários contêineres a partir da mesma imagem. O contêiner, como já explicamos, é um processo isolado que será executado em nossa máquina, utilizando os namespaces e os cgroups do kernel do Linux.
Também temos os volumes, que são mecanismos para persistir os dados de nossos contêineres. Eles permitem que armazenemos informações, se assim desejarmos. Além disso, temos as redes (networks), que permitem que os contêineres se comuniquem entre si e também com o mundo externo. Cada vez que criamos um contêiner, o Docker também cria redes virtuais e conecta essas redes virtuais aos nossos contêineres, para que possam se comunicar.
Essa foi a arquitetura geral do Docker e os principais objetos Docker. Nos vemos na próxima aula.
O curso Containers e Docker: empacotamento, isolamento e gestão possui 183 minutos de vídeos, em um total de 96 atividades. Gostou? Conheça nossos outros cursos de Builds em DevOps, ou leia nossos artigos de DevOps.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
O Plano Plus evoluiu: agora com Luri para impulsionar sua carreira com os melhores cursos e acesso à maior comunidade tech.
2 anos de Alura
Matricule-se no plano PLUS 24 e garanta:
Jornada de estudos progressiva que te guia desde os fundamentos até a atuação prática. Você acompanha sua evolução, entende os próximos passos e se aprofunda nos conteúdos com quem é referência no mercado.
Programação, Data Science, Front-end, DevOps, Mobile, Inovação & Gestão, UX & Design, Inteligência Artificial
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
Acesso à inteligência artificial da Alura.
No Discord, você participa de eventos exclusivos, pode tirar dúvidas em estudos colaborativos e ainda conta com mentorias em grupo com especialistas de diversas áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Luri Vision chegou no Plano Pro: a IA da Alura que enxerga suas dúvidas, acelera seu aprendizado e conta também com o Alura Língua que prepara você para competir no mercado internacional.
2 anos de Alura
Todos os benefícios do PLUS 24 e mais vantagens exclusivas:
Chat, busca, exercícios abertos, revisão de aula, geração de legenda para certificado.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.
Para quem quer atingir seus objetivos mais rápido: Luri Vision ilimitado, vagas de emprego exclusivas e mentorias para acelerar cada etapa da jornada.
2 anos de Alura
Todos os benefícios do PRO 24 e mais vantagens exclusivas:
Catálogo de tecnologia para quem é da área de Marketing
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais de forma ilimitada.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.
Conecte-se ao mercado com mentoria individual personalizada, vagas exclusivas e networking estratégico que impulsionam sua carreira tech para o próximo nível.