Containers e Docker



Um histórico sobre containers

Embora exista a palavra contêiner, (plural: contêineres) nos dicionários de português do Brasil, manterei nesse texto a terminologia container (plural: containers) para me referir aos objetos de software tratado neste texto.

Uma máquina virtual, ou uma VM (Virtual Machine) é uma simulação em software de um computador, incluindo CPU, memória, área de armazenamento de arquivos e conexão com à internet. Um aplicativo como o VMware, VirtualBox ou Gnome Boxes cria um arquivo no computador (a imagem) que se comporta como sendo outro computador, no sistema operacional escolhido. Essa imagem se torna um novo ambiente que pode ser isolado ou ter suas interações bem definidas com o computador hospedeiro. Máquinas virtuais foram a primeira resposta dada para o problema de executar diversos aplicativos simultaneamente e em isolamento nos servidores.

Containers são pacotes de software que contém um ambiente completo para a execução de um aplicativo. Isso inclui suas dependências, bibliotecas de sistemas, configurações e outros binários, além dos arquivos de configuração necessários para essa operação. Eles estão disponíveis no Linux e Windows e são extremamente úteis para executar da mesma forma os aplicativos, independentemente do ambiente usado. Eles são especialmente usados na fase de desenvolvimento, reduzindo os conflitos entre as equipes de TI por favorecerem a montagem de ambiente idêntico para todos os envolvidos no projeto. Elem disso facilitam a portabilidade de um para outro sistema operacional, ou mesmo entre diferentes modelos de arquitetura de hardware.

Nas máquinas virtuais um pacote de software inclui, além do aplicativo que se quer executar, um sistema operacional inteiro. No caso dos containers o kernel (o núcleo) do sistema operacional é compartilhado com outros containers que, por isso, usam menos recursos do que as máquinas virtuais. Um servidor físico executando três máquinas virtuais, por exemplo, teria que rodar três sistemas operacionais separados. O mesmo servidor executando três aplicativos em containers roda em um único sistema operacional, sendo que cada container tem acesso ao núcleo do sistema operacional hospedeiro. Dessa forma a máquina pode rodar um número muito superior de containers. Além disso aplicativos de container são iniciados mais rápido, porque não precisam inicializar todo o sistema operacional, e podem liberar recursos que não estão em uso.

Outro problema que os containers buscam resolver é questão dos diversos aplicativos instalados em uma máquina que partilham bibliotecas comuns. Não é raro que um dos aplicativos necessite de versão diferente da biblioteca, o que pode fazer com que partes do software instalado deixe de funcionar. Containers são, portanto, úteis para o desenvolvimento, a distribuição e instalação, e uso em produção de softwares.

Finalmente, containers introduzem um nível adicional de segurança aos computadores. Sabemos que existem programas deliberadamente projetados para atacar, invadir, roubar dados e tempo de máquina no computador hospedeiro. Mesmo programas bem-intencionados podem conter bugs perigosos para o sistema e usuário. Um programa rodando em um container só tem acesso ao seu próprio ambiente, a menos que tenha sido projetado de outra forma.

Todas essas caraterísticas tornam os containers especialmente úteis para a implementação de aplicativos e serviços na nuvem, usando ambientes tais como o Azure, AWS, Google Cloud, etc.

Docker

Docker é o formato de implementação de container mais conhecido. Ele foi desenvolvido primeiro para o uso no Linux, depois levado até plataformas com MacOS e Windows. Associado ao uso de docker temos o Kubernetes, um projeto de código aberto do Google que serve como orquestrador de aplicativos em container. Ele é um dos gerenciadores de containers e imagens do docker disponíveis.

O projeto é open-source e mantido pela empresa Docker Inc., EUA. Empresas e indivíduos contribuem para o projeto que é construído sobre uma arquitetura de plug-ins, onde componentes podem ser acrescentados ou removidos. A Open Container Iniciative, OCI, é um conselho fundado para zelar pela manutenção e observância de padrões, e é mantida pela Linux Foundation com a colaboração de Docker Inc. e outras.

Atualmente (em 2022) o docker roda nativamente no Linux. Para MacOs e Windows ele instala uma única máquina virtual que pode executar todos os containers. Em sistemas modernos é possível rodar dentro de containers aplicativos do Windows e macOS. Docker não é uma linguagem de programação nem um ambiente de desenvolvimento mas sim uma ferramenta de comando de linha que ajuda na solução de diversos problemas comuns ao processo de desenvolvimento, tais como a construção de projetos, sua distribuição, instalação, remoção, atualização e execução. Existem hoje diversos aplicativos GUI (com interfaces gráficas) para o gerenciamento do Docker.

Resumindo

Podemos pensar um container como uma caixa que contém aplicações. Dentro dela existe tudo o que é necessário para a execução das aplicações e o container se assemelha a um computador, com seu prório nome de máquina, endereço de IP address e até seus próprios discos, todos eles virtuais e criados pelo docker.

O aplicativo dentro do container não tem contato com nada fora dele, exceto o que for explicitamente indicado. Um exemplo disso seriam os casos de interação com o usuário via teclado, ou o acesso do aplicativo a um banco de dados externo.

Terminologia

Images (imagens do Docker) contêm o código-fonte do aplicativo executável, junto com as ferramentas, bibliotecas e dependências que o código precisa para ser executado. Elas podem ser construídas pelo desenvolvedor ou baixadas prontas de repositórios como o Docker Hub. Geralmente uma imagem é composta de várias outras imagens base que são empilhadas em camadas. Vários containers podem ser criados a partir de uma imagem.

Containers são as instâncias ativas e em execução das imagens. Enquanto imagens são arquivos de leitura somente (read-only) os containers são processos ativos que podem interagir com outros processos. Administradores podem ajustar suas configurações e condições usando comandos do Docker.

Dockerfiles (arquivos Docker) são arquivos de texto com as instruções sobre como criar imagens. Um Dockerfile automatiza o processo de criação de containers, contendo todos os passos na sequência que devem ser executados para a construção da imagem. Ele é similar a um script, declarando qual é a imagem base inicial a usar e com instruções para instalar os demais programas necessários, em camadas posteriores.

Build é a ação de criar de uma imagem usando as informações fornecidas pelo Dockerfile, desde que os arquivos adicionais estejam disponíveis no diretório onde a imagem é criada.

Tag (marcação) é uma marca ou rótulo aplicado em imagens para diferenciar versões. As tags podem ser usadas para, por exemplo, baixar uma imagem em versão específica.

Layers (camadas) são modificações aplicadas à uma imagem por meio de instruções do dockerfile. Por exemplo, dentro de uma imagem do ubuntu, que está na primeira camada, pode-se instalar outro aplicativo qualquer que será construído em uma segunda camada. Layers são identificadas por ids (hashes) que são usados para identificá-las.

Repository (repositório) é uma coleção de imagens do Docker rotuladas com marcação (tags) que indicam a versão da imagem. Podem conter imagem de versões diversas diferenciadas por tags, e variantes para as diversas plataformas.

Docker Hub é o repositório público padrão de imagens do Docker considerado a “maior biblioteca para imagens de containers”. Ele contém mais de 100.000 imagens de containers provenientes de fornecedores de software comercial ou de código aberto, e desenvolvedores individuais. Usuários do Docker Hub podem compartilhar suas imagens livremente ou baixar imagens base para usar no desenvolvimento de outros projetos. Existem outros repositórios, como o GitHub.

Registro do Docker é um sistema de armazenamento e distribuição de imagens do Docker. Ele permite rastrear versões de imagens em repositórios usando o git, uma ferramenta de controle de versão. O registro padrão para imagens mais frequentemente usado é o Docker Hub (propriedade da Docker como uma organização).

Daemon Docker é um serviço que cria e gerencia imagens Docker usando os comandos do cliente. Ele serve como central de controle da implementação do Docker. O servidor onde o daemon é executado é chamado de host do Docker, que pode ser o computador local ou um serviço em nuvem.

Docker Desktop é um aplicativo que facilita a criação e compartilhamento de aplicativos e microsserviços em containers. Junto com o Desktop são instaladas ferramentas como Kubernetes, Docker Compose, BuildKit e verificação de vulnerabilidades. Inicialmente disponível apenas para Windows e MacOS, foi disponibilizado para desenvolvedores no Linux em Maio de 2022. Docker Desktop também inclui extensões do Docker para facilitar a integração com ferramentas de outros desenvolvedores da equipe ou de terceiros.

Docker Dashboard é um aplicativo GUI para gerenciamento de imagens, containers e volumes do Docker. Ele é um aplicativo WEB que roda em Node, abrindo uma página no navegador e pode ser usado para gerenciar visualmente os recursos de container.

Docker Compose é uma ferramenta utilizada na definição e compartilhamento de aplicações multi-container. Com Compose podemos criar um arquivo YAML para definir os serviços usados. Dessa forma um aplicativo pode ser iniciado ou finalizado com um único comando. Ele também facilita a colaboração entre desenvolvedores.

Instalando o Docker


Existem duas versões disponíveis para instalação:

  • Community Edition (CE), edição da comunidade,
  • Enterprise Edition (EE), edição comercial.

Docker CE é gratuita. Docker EE é o mesmo pacote de software, mas com suporte técnico e acesso à outros produtos da empresa.

Instalação: Fedora Linux, Ubuntu Linux, Archlinux, Mac, Windows.

Como exemplo, listamos o procedimento de instalação no Fedora Linux:

# Primeiro habilitamos o repositório    
$ sudo dnf -y install dnf-plugins-core

$ sudo dnf config-manager \
     --add-repo \
     https://download.docker.com/linux/fedora/docker-ce.repo

# instalamos o docker engine
$ sudo dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Para iniciar o serviço do docker via systemctl usamos start docker. Depois podemos testar se a instalação foi bem sucedida verificando a versão instalada e o status do serviço.

# para iniciar o Docker
sudo systemctl start docker

# para verificar a versão instalada
$ docker --version
  Docker version 20.10.17, build 100c701

# verificando o status do serviço
$ sudo service docker status
Redirecting to /bin/systemctl status docker.service
● docker.service - Docker Application Container Engine
     Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
     Active: active (running) since Fri 2022-08-12 18:52:19 -03; 1min 10s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 32787 (dockerd)
      Tasks: 13
     Memory: 111.8M
        CPU: 344ms     # (saída truncada)

Nessa instalação é criado um grupo de usuários que podem rodar o Docker. Por default o Docker exige privilégios de root para rodar containers, o que pode ser problemático para a segurança do sistema. Para rodar o Docker sem necessidade de emitir comandos como superuser adicionamos um usuário ao grupo do Docker.

$ sudo usermod -aG docker <nome_usuario>
# verificando
$ cat /etc/group | grep docker
  docker:x:973:nome_usuario

# para iniciar e finalizar o docker-desktop
$ systemctl --user start docker-desktop
$ systemctl --user stop docker-desktop

# para desinstalar o docker
$ sudo dnf remove docker-desktop

# para desinstalar Docker Engine, CLI, containerd, e Docker Compose
$ sudo dnf remove docker-ce docker-ce-cli containerd.io docker-compose-plugin


Temos dois componentes principais com a instalação do docker: o cliente e o docker daemon (também chamado de docker engine). Uma versão alternativa de docker version (sem os hífens) fornece mais dados sobre a instalação:

$ docker version
  Client: Docker Engine - Community
   Version:           20.10.17
   API version:       1.41
   Go version:        go1.17.11
   Git commit:        100c701
   Built:             Mon Jun  6 23:03:59 2022
   OS/Arch:           linux/amd64
   Context:           default
   Experimental:      true

Server: Docker Engine - Community (saída truncada)

Para instalar e verificar o docker-compose usamos:

$ sudo dnf install docker-compose  # (no fedora) ou
$ sudo apt install docker-compose  # (no ubuntu)

# Para verificar a instalação
$ docker-compose version 
  docker-compose version 1.29.2, build unknown
  docker-py version: 5.0.3
  CPython version: 3.10.5
  OpenSSL version: OpenSSL 3.0.5 5 Jul 2022

Obs.: Para atualizar o docker é necessário remover os pacotes antigos e realizar uma nova instalação.

Imagens do Docker

Uma imagem do docker é um objeto que contém um aplicativo e arquivos de sistema, suficientes para rodar esse aplicativo. Essas imagens podem ser construídas pelo desenvolvedor ou baixadas pela internet de algum repositório, onde outros desenvolvedores as disponibilizaram. Por default as imagens são procuradas no GitHub. Um exemplo simples pode ser dado com a imagem de hello_world. Digitamos no prompt de comando do terminal:

# o primeiro passo é baixar uma imagem
$ docker pull hello-world
# o container pode ser agora executado
$ docker run hello-world
  Hello from Docker!
  This message shows that your installation appears to be working correctly.
  (saída truncada)


O uso de docker pull <imagem> não é estritamente necessário. O comando docker run procura primeiro a imagem na máquina local e, se ela não foi previamente baixada, ele a procura em Docker Hub. Se a imagem for encontrada ele a baixa e constroi um container à partir dela. No caso desse exemplo a imagem contém um aplicativo simples de demonstração que imprime a mensagem listada (que foi aqui truncada) no console. A cada execução de run um novo container é criado e seu aplicativo subjacente é rodado. Imagens baixadas são previamente compiladas (built).

O comando docker image ls permite ver quais são as imagens já baixadas:

$ docker image ls
  REPOSITORY                   TAG       IMAGE ID       CREATED        SIZE
  hello-world                  latest    feb5d9fea6a5   9 months ago   13.3kB

A tag latest indica que a última versão disponível foi baixada. Em outros casos a tag é a versão da imagem. A imagem hello-world foi preparada para teste dos iniciantes e contém instruções interessantes. Para apagar um imagem baixada usamos docker rm imagem.

Vamos apagar essa imagem e repetir o processo anterior. O output da execução será em inglês (para essa imagem) mas a exibiremos aqui com partes traduzidas.

# para apagar a imagem
$ docker rm hello-world

# para rodar novamente essa imagem
$ docker container run hello-world

  Hello from Docker!
  Essa messagem mostra que sua instalação parece estar funcionando corretamente.
  
  Para gerar essa mensagem Docker seguiu os seguintes passos:
   1. O cliente Docker fez contato com o Docker daemon.
   2. O Docker daemon baixou (pulled) a imagem "hello-world" do Docker Hub. (amd64)
   3. O Docker daemon criou um novo container à partir dessa imagem. O container
      executou código que produziu o presente output.
   4. O Docker daemon transmitiu esse output para o Docker client, que o enviou para
      o terminal.
  
  Você pode tentar algo mais ambicioso, por ex. rodando o container do Ubuntu usando:
   $ docker run -it ubuntu bash
  
  Partilhe imagens, fluxos de trabalho automatizados e mais coisas usando um Docker ID gratuito:
    https://hub.docker.com/
  
  Para ver mais exemplos e ideias visite:
        https://docs.docker.com/get-started/

Aproveite a oportunidade para entrar no Docker Hub e criar uma conta.

Comandos do Docker

Recapitulando, podemos baixar uma imagem do docker usando pull.

$ docker pull <nome_imagem>:latest

Como vimos latest significa que baixamos a última e mais atualizada versão da imagem. Outra versão pode ser especificada se necessário como, por exemplo, fazendo $ docker pull <nome_imagem>:8.0. Para executar essa imagem usamos:

$ docker run -d --name=<nome_alternativo> -p m:n -<nome_imagem>

Além do comando run para criar e rodar um container usamos os parâmetros:

  • --name: para dar um nome alternativo para o container criado,
  • -p: para tornar a porta do docker visível fora do container,
  • <nome_imagem>: especifica a imagem a ser executada,
  • m:n: mapeia portas do container em portas do host. (porta_host:porta_container)

Podemos ver quais são os containers em execução:

docker ps
container ID  IMAGE               COMMAND                 CREATED         STATUS                 
642881d63879  mysql/mysql-server  "/entrypoint.sh mysq…"  16 minutes ago  Up 16 minutes (healthy)

PORTS                                                       NAMES
0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060-33061/tcp  mysql1

Para encerrar a execução, e remover o container usamos, respectivamente:

# encerrar    
$ docker stop <nome_imagem>
# remover container
$ docker rm <nome_imagem>

No linux a configração de docker e as imagens baixadas ficam no diretório /var/lib/docker. No Windows o endereço desses arquivos varia, de acordo com o modo de instalação. Esses arquivos nunca devem ser manipulados diretamente pelo usuário.

Uma página de ajuda é exibida com docker help, sobre a sintaxe de docker.

# ajuda geral    
$ docker help

# ajuda sobre um comando específico
$ docker <comando> help
# por exemplo, para o comando cp (copy)
$ docker cp help

Podemos ver todas as imagens baixadas no computador e apagá-las, se assim for desejado.

# para listar as imagens
$ docker image ls
REPOSITORY                   TAG       IMAGE ID       CREATED         SIZE
mysql/mysql-server           latest    5a9594052aec   3 months ago    438MB
hello-world                  latest    feb5d9fea6a5   10 months ago   13.3kB

# para apagar as imagens usamos os primeiros caracteres do id para identificá-las
$ docker image rm -f 5a
$ docker image rm -f fe

#alternativamente, o seguinte comando lista todos os containers
$ docker container ls --all --quiet

# podemos apagar todos os containers usando (veja descrição abaixo)
$ docker container rm --force $(docker container ls --all --quiet)

A sintaxe $() faz com que a saída de um comando seja enviada para o comando externo. Nesse caso o comando interno gera uma lista de containers instalado, que é enviada para o comando de apagamento forçado. A chave –force (ou -f) força o apagamento mesmo que exista uma imagem desse container em execução. Esse comando deve ser usado com cautela pois a remoção é feita sem nenhuma confirmação.

Você encontra mais ajuda no site do docker.

Imagem do MySQL

Como um exemplo mais completo e útil vamos carregar e executar uma imagem contendo o mysql. Começamos por baixar (pull) a imagem.

$ docker pull mysql/mysql-server:latest

Como vimos latest significa que baixamos a última e mais atualizada versão do mysql. Outra versão pode ser especificada se necessário, como em $ docker pull mysql/mysql-server:8.0. Para executar essa imagem usamos:

$ docker run --name=mysql1 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=meupassword -d mysql/mysql-server

Pode ocorrer que a porta designada esteja ocupada por outro serviço do computador host, o que provoca a emissão de uma mensagem de erro. Nesse caso podemos usar outra porta, tal como docker run --name=mysql1 -p 8080:3306....

Além do comando run para criar e rodar um container usamos os parâmetros:

  • --name: para dar um nome alternativo para o container criado,
  • -p: para tornar a porta do docker visível fora do container,
  • -e: para inserir um password de gerenciamento do mysql,
  • mysql/mysql-server: especifica a imagem a ser executada,
  • 3306:3306: mapeia portas do container em portas do host. (porta_host:porta_container)

Podemos ver quais são os containers em execução:

docker ps
container ID  IMAGE               COMMAND                 CREATED         STATUS                 
642881d63879  mysql/mysql-server  "/entrypoint.sh mysq…"  16 minutes ago  Up 16 minutes (healthy)

PORTS                                                       NAMES
0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060-33061/tcp  mysql1

A linha foi quebrada para facilitar a visualização. Para conectar com o servidor do mysql executamos:

$ docker exec -it mysql1 mysql -uroot -p
Enter password: meupassword

Welcome to the MySQL monitor.  ... (saída truncada)
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> 


Operações usuais sobre o banco de dados ficam disponíveis.

Por ex.:

# cria um banco de dados
mysql> CREATE DATABASE teste;
# use esse BD
mysql> USE teste;
# cria uma tabela 
mysql> CREATE TABLE cidades(nome varchar(60),id varchar(4));

# insere valores na tabela
mysql> INSERT INTO cidades SET nome='Belo Horizonte', id='1';
mysql> INSERT INTO cidades SET nome='Salvador', id='2';
mysql> INSERT INTO cidades SET nome='Curitiba', id='3';

mysql> SELECT * FROM cidades;
+----------------+------+
| nome           | id   |
+----------------+------+
| Belo Horizonte | 1    |
| Salvador       | 2    |
| Curitiba       | 3    |
+----------------+------+
3 rows in set (0.00 sec)

# para abandonar o programa
mysql> exit

Para encerrar a execução, e remover o container usamos, respectivamente:

# encerrar    
$ docker stop mysql1
# remover container
$ docker rm mysql1

Conectando com o container


A interação com um container se dá de modo similar à execução de comandos em um computador remoto. Para mostrar isso executaremos uma imagem do docker chamada diamol/base, criada e disponibilizada no Dockerhub por E. Stone para exemplificar um trecho de seu livro (veja bibliografia). Para interagir com o aplicativo em execução usamos as flags –interactive e –tty (ou simplesmente -i e -t) para fazer essa interação através do terminal. O primeiro comando abaixo baixa a imagem e a executa criando um container que expõe um prompt de terminal para o usuário. Os comandos aceitos no prompt dependem do sistema operacional: por ex., no linux usamos ls para listar os arquivos do diretório atual; no Windows usamos dir.

# para baixar e rodar a imagem    
$ docker container run --interactive --tty diamol/base
  Unable to find image 'diamol/base:latest' locally
  latest: Pulling from diamol/base
  31603596830f: Pull complete 
  792f5419a843: Pull complete 
  Digest: sha256:787fe221a14f46b55e224ea0436aca77d345c3ded400aaf6cd40125e247f35c7
  Status: Downloaded newer image for diamol/base:latest

# O aplicativo expõe o prompt do terminal (usaremos os sinais / ⧣)
/ ⧣
# no linux podemos listar os arquivos na pasta ativa
/ ⧣ ls
  bin    etc    lib    mnt    proc   run    srv    tmp    var
  dev    home   media  opt    root   sbin   sys    usr

# o nome da máquina em execução é o mesmo que o id da imagem
/ ⧣ hostname
2c33c99fcaf7
# para ver a data
/ ⧣ date
Sat Aug  6 15:38:21 UTC 2022

Sem fechar esse terminal podemos abrir um segundo terminal e listar detalhes dos containers em execução:

$ docker container ls
container ID  IMAGE        COMMAND    CREATED        STATUS       PORTS  NAMES
2c33c99fcaf7  diamol/base  "/bin/sh"  3 minutes ago  Up 3 minutes        strange_lehmann

# para listar os processos dentro desse container
$ docker container top 2c
UID      PID      PPID      C    STIME    TTY     TIME      CMD
root     13436    13410     0    12:36    pts/0   00:00:00  /bin/sh

# para listar as ações (log) executadas nesse container
$ docker container top 2c
# ls
bin    etc    lib    mnt    proc   run    srv    tmp    var
dev    home   media  opt    root   sbin   sys    usr

(retorna uma lista de comandos utilizados, aqui truncada)

# inspect retorna uma descrição detalhada do container
$ docker container inspect 2c
  [
    {
        "Id": "2c33c99fcaf7fb94393ce1e7e31ce05ab6f5bcc914fc5eac8c90e600cf711995",
        "Created": "2022-08-06T15:36:14.327115583Z",
        "Path": "/bin/sh",
        "Args": [],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,                        ... (saída truncada)
        },
    }
]        

Os comandos usam o atalho 2c para se referir ao ID do container 2c33c99fcaf7. inspect retorna dados no formato JSON, aqui truncados, incluindo informações sobre caminhos usados pelo filesystem virtual e muitos outros.

Podemos ver uma lista de imagens instaladas usando docker image list ou docker image ls, e apagar uma imagem que não será mais necessária com docker image rm <id>, onde <id> é o id listado no passo anterior. No nosso caso, se usamos:

# para listar imagens baixadas
$ docker image list
  REPOSITORY                   TAG       IMAGE ID       CREATED         SIZE
  mysql/mysql-server           latest    5a9594052aec   3 months ago    438MB
  hello-world                  latest    feb5d9fea6a5   10 months ago   13.3kB
  diamol/base                  latest    9fc3f74c8b53   17 months ago   7.13MB

# para apagar a imagem referente à diamol/base (a imagem está em uso no container 5f73d74675a7)
$ docker image rm 9fc3f74c8b53
  Error response from daemon: conflict: unable to delete 9fc3f74c8b53 (must be forced)
  - image is being used by stopped container 5f73d74675a7

# para forçar a interrupção desse container e apagar a imagem
$ docker image rm -f 9fc3f74c8b53
  Deleted: sha256:9fc3f74c8b533bed2ac40ccfc6a5235ebb626a26a230dc3731fcdf926d984106

# lista todos os containers (em execução ou não)
$ docker container ls --all
  container ID   IMAGE               COMMAND                  CREATED        STATUS                  PORTS    NAMES
  5f73d74675a7   9fc3f74c8b53        "/bin/sh"                3 hours ago    Exited (0) 2 hours ago            intelligent_wescoff
  2c33c99fcaf7   9fc3f74c8b53        "/bin/sh"                6 hours ago    Exited (0) 3 hours ago            strange_lehmann
  e0ca567900b9   nginx               "/docker-entrypoint.…"   25 hours ago   Created                           webserver
  5ccc8d7d5ff3   hello-world         "/hello"                 32 hours ago   Exited (0) 32 hours ago           hopeful_banach
  fb9dc473c7f5   hello-world         "/hello"                 32 hours ago   Exited (0) 32 hours ago           loving_swartz
  5e5c65bb00c4   hello-world         "/hello"                 32 hours ago   Exited (0) 32 hours ago           cool_cannon
  (... saída truncada)

Como nenhum container está ativo (no meu caso) eles têm o status Exited. Quando um container está em execução o mesmo ocorre com o aplicativo que ele contém. Quando o processo é terminado o container entra no estado de encerrado (Exited). Containers encerrados não usam recursos de memória nem tempo de CPU do computador mas são mantidos em disco e ocupam espaço. Containers não desaparecem quando são terminados mas continuam a existir e podem ser executados novamente. Nesse estado eles retêm logs e arquivos no sistema de arquivos. Para remover containers encerrados você deve emitir o controle específico para tal.

Outros comandos associados:

$ docker ps -a                  # Lista containers, informando a imagem que os gerou

$ docker images                 # Lista imagens 

$ docker rm <container_id>      # Remove um container interrompido (não em execução)

$ docker rm -f <container_id>   # Força a remoção de um container mesmo que em execução

$ docker rmi <image_id>         # Remove uma imagem que não esteja
                                # associada um container em execução

$ docker rmi -f <image_id>      # Força a remoção da imagem mesmo que esteja em uso

$ docker system prune -a        # remove: todos os containers parados,
                                # todas as redes não utilizadas 
                                # todas as imagens não ativas
                                # todo o cache de docker                           

Claro que o comando docker system prune -a deve ser usada com bastante cuidado.

Rodando um webserver


Outro bom exemplo de funcionamento do docker consiste em rodar um container preparado pelo DockerHub, que roda um tutorial interativo para iniciantes. Outras instruções podem ser encontradas em Docker Docs: Getting started.

$ docker run -d -p 8080:80 docker/getting-started

# as flags podem ser unidas
docker run -dp 8080:80 docker/getting-started

# para acessar o tutorial abra o navegador em http://localhost:8080. 

As flags ou chaves significam o seguinte:

  • -d: execute o container em mode destacado (detached ou em background).
  • -p 8080:80: associa a porta 8080 do host à porta 80 do container. Se a porta 8080 já estiver em uso especifica outra porta, como 80 ou 3000.
  • docker/getting-started especifica o nome da imagem a ser usada.

Como já vimos, a imagem é baixada (se não estiver no computador) e rodada. Ela cria uma seção de um servidor que pode ser acessado em http://localhost:8080. Essa imagem foi preparada pelos desenvolvedores do docker para fornecer instruções iniciais para seu uso.

Se iniciarmos uma imagem em segundo plano e quisermos um terminal interativo podemos executar

$ docker exec -it <container> bash

Rodando Oracle Linux


Mostraremos outro exemplo rodando um container com Oracle Linux.

Verificamos primeiro o status do docker:

$ service docker status
  Redirecting to /bin/systemctl status docker.service
  ○ docker.service - Docker Application Container Engine
       Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
       Active: inactive (dead)
  TriggeredBy: ○ docker.socket
       Docs: https://docs.docker.com

Para terminar um comando no prompt do bash usamos CTRL-C. Vemos que o docker está inativo, como exibido em Active: inactive (dead). Então reiniciamos o serviço:

$ sudo service docker start
  [sudo] password for guilherme: 
  Redirecting to /bin/systemctl start docker.service

$ service docker status
  Redirecting to /bin/systemctl status docker.service
  ● docker.service - Docker Application Container Engine
       Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
       Active: active (running) since Mon 2022-08-15 16:32:32 -03; 2s ago
       (saída truncada)

Para baixar a imagem do Oracle Linux usamos pull oraclelinux:9, sendo 9 a versão mais atual. Caso seja necessária uma versão anterior usamos pull oraclelinux:8 (ou a versão necessária). No caso do Oracle Linux a tag latest foi removida, como se pode ver na página docker: oraclelinux.

$ sudo docker pull oraclelinux:9
  9: Pulling from library/oraclelinux
  16c5055e8b97: Pull complete 
  Digest: sha256:93514816e192d51fbb34c008a479c789c43fb1fe0c2f91bd5be1ba7919dafa77
  Status: Downloaded newer image for oraclelinux:9
  docker.io/library/oraclelinux:9

# para visualizar as imagens baixadas
$ docker images
REPOSITORY               TAG       IMAGE ID       CREATED        SIZE
docker/getting-started   latest    cb90f98fd791   4 months ago   28.8MB


Podemos rodar essa imagem, gerando um container, com o comando docker run <nome_imagem>. Por default a imagem é criada, se já não existe, e roda em primeiro plano (attached mode). Para rodá-la no segundo plano (em background ou dettached mode) usamos o marcador (flag) -d. Para usar o terminal de modo interativo use as opções –i, –t. As flags podem ser juntas, como em –dit. A flag –rm serve para apagar o container quando ele for encerrado. A opção --name é usada para dar um nome para a imagem sendo rodada. Esse nome deve ser único (não pode ter o mesmo nome de um que já está rodando).

# para rodar a imagem em primeiro plano (attached mode)    
$ sudo docker run  
    
# para rodar a imagem em segundo plano ou background (dettached mode)
$ sudo docker run -d  

No caso de nossa imagem para o oracle linux usaremos run com as flags já descritas. Um terminal é apresentado, onde podem ser digitados comandos apropriados.

$ docker run -i -t  --rm --name oraclelinux oraclelinux:9
  [root@723e7c1a71ea /]#
# se executado em outro terminal (para não fechar o terminal do oraclelinux)
# podemos ver que temos uma imagem rodando. 
$ docker images
  REPOSITORY    TAG       IMAGE ID       CREATED      SIZE
  oraclelinux   9         a0ad236ea4f0   6 days ago   218MB

# no prompt digitamos comandos. Por ex.
[root@723e7c1a71ea /]# cat /etc/oracle-release
  Oracle Linux Server release 9.0

[root@03d35e4c35b1 teste_oracle]# cat /etc/os-release
  NAME="Oracle Linux Server"
  VERSION="9.0"

Os comandos usuais do linux pode ser executados no prompt:

[root@723e7c1a71ea /]# mkdir teste_oracle      # cria um novo diretório
[root@723e7c1a71ea /]# cd teste_oracle         # move cursor para o novo diretório

# envia texto para arquivo.txt
[root@723e7c1a71ea teste_oracle]# echo "teste de docker no oraclelinux" > arquivo.txt
# exibe conteúdo do arquivo.txt
[root@723e7c1a71ea teste_oracle]# cat arquivo.txt
  teste de docker no oraclelinux
  
# para interromper a execução usamos exit [root@723e7c1a71ea teste_oracle]# exit # retornamos ao prompt do sistema hospedeiro $

Docker Dashboard (GUI)

Um aplicativo web pode ser usado com Docker Dashboard. O Dashboard roda com Node.js que, portanto, precisa estar instalado em seu sistema. Depois você deve clonar um repositório do Github.

# clone repositório

$ git clone git@github.com:rakibtg/docker-web-gui.git

# mude para o novo directório criado

$ cd ./docker-web-gui

# execute o app app.js usando o node. Essa ação instalará os módulos necessários ainda não instalados.

$ node app.js

# agora você pode abrir no navegador a página http://localhost:3230/

O navegador abrirá uma página exibindo as imagens instaladas, containers em execução ou parados, visualização dos logs, criação e vizualização de grupos de containers.

Para baixar o código do aplicativo usando git clone git@github.com:rakibtg/docker-web-gui.git, você deve ter uma conta do GitHub com chaves certificadas de ssh. Instruções em Connecting to GitHub with ssh. Alternativamente você pode entrar no site do GitHub para esse aplicativo, baixar e instalar o código.

A continuação desse artigo está em preparação. Coloque nos comentários se você gostaria de ler mais sobre Docker, em particular sobre como preparar a sua própria imagem.

Bibliografia


Livros:

  • Ashley, David: Foundation Dynamic Web Pages with Python, Apress, 2020,
  • Miel, Ian: Sayers, Aidan H.: Docker in Practice, 2 Ed., Manning, 2019,
  • Nickoloff, Jeff; Kuenzli, Stephen: Docker in Action, 2 Ed., Manning, 2019,
  • Poulton, Nigel: Docker Deep Dive, disponível em Leanpub.com, 2018,
  • Stoneman, Elton: Learn Docker in a Month of Lunches, Manning, 2020,
  • Vohra, Deepak: Pro Docker, Apress, 2016.

Sites: