Como executar contêineres Podman no Systemd com Quadlet
Quadlet é uma ferramenta gratuita e de código aberto escrita em C que nos permite criar e executar contêineres Podman no Systemd. A ferramenta permite declarar contêineres, volumes, redes e seus relacionamentos, usando unidades Systemd dedicadas.
Neste tutorial, aprendemos como usar o Quadlet para criar contêineres, redes e volumes Podman, e como criar pilhas de vários contêineres.
Neste tutorial você aprenderá:
- Como criar contêineres, volumes e redes com as unidades Systemd correspondentes
- Como criar pilhas de vários contêineres usando Quadlet
Um exemplo básico: criando um contêiner MariaDB
Neste primeiro exemplo básico, definimos uma unidade para um servidor de banco de dados MariaDB. Aqui está o que parece:
[Unit]
Description=MariaDB container
[Container]
Image=docker.io/mariadb:latest
Environment=MYSQL_ROOT_PASSWORD=rootpassword
Environment=MYSQL_USER=testuser
Environment=MYSQL_PASSWORD=testpassword
Environment=MYSQL_DATABASE=testdb
[Install]
WantedBy=multi-user.target
Com essas poucas linhas, definimos um contêiner baseado na última imagem oficial do MariaDB, disponível no Dockerhub. Como você pode ver, um “.container” é um tipo dedicado de unidade Systemd. O que é exclusivo deste tipo de unidade é a seção “Container”, na qual podemos especificar opções que são equivalentes àquelas que podemos passar para a linha de comando do Podman. A única opção obrigatória na seção “Container” é Image
, para especificar a imagem base do container.
Se quisermos que o contêiner seja executado como root, salvamos o arquivo no diretório /etc/containers/systemd
; para executá-lo como nosso próprio usuário sem privilégios, em vez disso, salvamos o arquivo em ~/.config/containers/systemd
. Neste exemplo, usaremos a última abordagem e salvaremos nossa unidade “.container ” como ~/.config/containers/systemd/mariadb-service.container
. Para permitir que o systemd gere um serviço baseado na unidade do contêiner, executamos o seguinte comando:
$ systemctl --user daemon-reload
Depois de executarmos o comando, podemos dar uma olhada no serviço gerado executando:
$ systemctl --user cat mariadb-service
Para iniciar o contêiner, assim como qualquer outro serviço, podemos executar:
$ systemctl --user start mariadb-service
Na primeira vez que o iniciarmos, o serviço poderá demorar um pouco para iniciar, caso a imagem base do contêiner não exista em nosso sistema. Podemos usar a linha de comando podman para verificar o status do contêiner:
$ podman ps
O comando retorna a seguinte saída, que confirma que o contêiner está funcionando:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
151226b10a1c docker.io/library/mariadb:latest mariadbd 59 seconds ago Up About a minute systemd-mariadb-service
Como você deve ter notado, por padrão, o contêiner recebe o nome do arquivo da unidade “.container” mais o prefixo “systemd-” (“systemd-mariadb-service”, neste caso). Para atribuir explicitamente um nome ao contêiner, podemos, entretanto, passá-lo como o valor da opção ContainerName
dentro da seção “[Container]”.
Criando uma unidade de “volume”
Usamos volumes para persistir os dados dos contêineres. Nesse caso, queremos que os dados do nosso banco de dados persistam mesmo se descartarmos o contêiner systemd-mariadb-service. Definimos um volume nomeado na unidade “.volume” correspondente. No exemplo abaixo, fornecemos apenas uma descrição do volume. Opções específicas de volume podem ser especificadas na sub-rotina [Volume]
:
[Unit]
Description=MariaDB Volume
[Volume]
Salvamos a unidade como ~/.config/containers/systemd/mariadb-volume.volume
. Para instruir o contêiner MariaDB a usar o volume, usamos a opção Volume
dentro da seção [Container]
da unidade “.container ” e mapeamos o volume para o Diretório /var/lib/mysql
dentro do contêiner:
[Unit]
Description=MariaDB container
[Container]
Image=docker.io/mariadb:latest
Environment=MYSQL_ROOT_PASSWORD=rootpassword
Environment=MYSQL_USER=testuser
Environment=MYSQL_PASSWORD=testpassword
Environment=MYSQL_DATABASE=testdb
Volume=mariadb-volume.volume:/var/lib/mysql
[Install]
WantedBy=multi-user.target
Quando usamos um volume nomeado, o Systemd atribui automaticamente uma dependência a ele na unidade de serviço que gera para nosso contêiner. Para permitir que o systemd regenere os serviços necessários, mais uma vez, usamos o comando daemon-reload
. Assim que (re)iniciarmos o contêiner MariaDB, o volume será criado automaticamente, como podemos verificar usando o utilitário de linha de comando podman:
$ podman volume ls
Como esperado, nosso volume aparece na lista gerada pelo comando:
DRIVER VOLUME NAME
local systemd-mariadb-volume
Também podemos verificar se o volume está montado em /var/lib/mysql dentro do container usando o comando podman inspeciona
, passando o nome do container como argumento, e dando uma olhada na seção “Montagens” seção:
$ podman inspect systemd-mariadb-service
Aqui está a seção relevante da saída:
"Mounts": [
{
"Type": "volume",
"Name": "systemd-mariadb-volume",
"Source": "/home/doc/.local/share/containers/storage/volumes/systemd-mariadb-volume/_data",
"Destination": "/var/lib/mysql",
"Driver": "local",
"Mode": "",
"Options": [
"nosuid",
"nodev",
"rbind"
],
"RW": true,
"Propagation": "rprivate"
}
],
Usando montagens de ligação
E se quisermos usar montagens vinculadas em vez de volumes nomeados? Bind-mounts são úteis durante o desenvolvimento, pois nos permitem montar arquivos host dentro do contêiner. A desvantagem de usar montagens vinculadas é que os contêineres se tornam dependentes do sistema de arquivos host. Para usar um bind-mount dentro de uma unidade “.container”, apenas especificamos o caminho relativo ou absoluto do arquivo host que queremos montar. Suponha, por exemplo, que queiramos montar o diretório /var/lib/mysql
dentro do contêiner, ao diretório ~/mysql
no host. Aqui está o que poderíamos escrever na unidade “.container”:
[Unit]
Description=MariaDB container
[Container]
Image=docker.io/mariadb:latest
Environment=MYSQL_ROOT_PASSWORD=rootpassword
Environment=MYSQL_USER=testuser
Environment=MYSQL_PASSWORD=testpassword
Environment=MYSQL_DATABASE=testdb
Volume=%h/mysql:/var/lib/mysql
[Install]
WantedBy=multi-user.target
No exemplo acima, você pode notar que usamos o espaço reservado %h
, que é automaticamente expandido pelo Systemd para o caminho absoluto do nosso próprio diretório HOME. Também é possível utilizar caminhos relativos, que são resolvidos relativamente à posição do arquivo unitário.
Criando Redes
Para criar uma pilha composta por vários containers, à semelhança do que fazemos com o docker-compose, devemos colocar os containers na mesma rede, para que eles possam se comunicar entre si. Para criar uma rede com Quadlet, podemos usar unidades com extensão “.network”.
Assim como acontece com contêineres e volumes, por padrão, as redes são nomeadas de acordo com o arquivo da unidade em que estão definidas, mais o prefixo “systemd-”. Podemos fornecer explicitamente um nome, ou qualquer outra opção específica de rede, na seção [Network]
do arquivo. No exemplo abaixo, especificamos a sub-rede e o endereço do gateway da rede (isso equivale a executar podman com --subnet 192.168.30.0/24
e --gateway 192.168.30.1
opções):
[Unit]
Description=MariaDB Network
[Network]
Subnet=192.168.30.0/24
Gateway=192.168.30.1
Salvamos o arquivo como ~/.config/containers/systemd/mariadb.network
. Para “colocar” um container em uma rede específica, usamos a opção Network
dentro da seção “Container” de sua unidade container. O contêiner MariaDB que definimos anteriormente passa a ser:
[Unit]
Description=MariaDB container
[Container]
Image=docker.io/mariadb:latest
Environment=MYSQL_ROOT_PASSWORD=rootpassword
Environment=MYSQL_USER=testuser
Environment=MYSQL_PASSWORD=testpassword
Environment=MYSQL_DATABASE=testdb
Volume=mariadb-volume.volume:/var/lib/mysql
Network=mariadb.network
Criando uma pilha de vários contêineres
Temos um contêiner executando nosso servidor MariaDB. Para adicionar uma maneira fácil de gerenciar nosso banco de dados através de uma interface web, podemos criar um contêiner baseado em phpMyAdmin. Para que os dois contêineres possam se comunicar, devemos colocá-los na mesma rede.
Como o container phpMyAdmin, para funcionar corretamente, requer que o servidor MariaDB esteja ativo e em execução, podemos declarar esta dependência como faríamos para qualquer outro serviço Systemd, com os Requires
e Após
opções na seção “[Unit]”. A primeira estabelece uma forte dependência do serviço passado como seu valor; o último garante que nosso serviço comece depois dele. A propósito, se você não está familiarizado com o Systemd, pode dar uma olhada em nosso tutorial sobre como criar um serviço Systemd. Esta é a aparência da unidade de contêiner do phpMyAdmin:
[Unit]
Description=phpMyAdmin container
Requires=mariadb-service.service
After=mariadb-service.service
[Container]
Image=docker.io/phpmyadmin:latest
Network=mariadb.network
Environment=PMA_HOST=systemd-mariadb-service
PublishPort=8080:80
[Install]
WantedBy=multi-user.target
No exemplo acima, também usamos a opção PublishPort
, que é equivalente a podman -p
(--publish
): é usada para mapear uma porta host para uma porta de contêiner. Neste caso, mapeamos a porta 8080
no sistema host, para a porta 80
dentro do contêiner.
Também fornecemos um valor para a variável de ambiente PMA_HOST, através da opção Environment
: ela é usada para especificar o endereço do servidor de banco de dados (neste caso usamos o nome do contêiner MariaDB, que é resolvido para o endereço real). Observe que usamos o nome do contêiner MariaDB como o valor da variável de ambiente, não o nome da unidade “.container”. Para iniciar nossa nova configuração, mais uma vez, executamos:
$ systemd --user daemon-reload
Então, podemos iniciar o serviço “phpmyadmin-service” gerado, que, por sua vez, executa o contêiner phpMyAdmin. Por ser fortemente dependente do MariaDB, este também será iniciado automaticamente. Devemos conseguir acessar o phpMyadmin no endereço “localhost:8080”. Para fazer login, podemos usar as credenciais especificadas na unidade de contêiner MariaDB:
Pensamentos finais
Neste tutorial aprendemos como criar e executar contêineres, volumes e redes Podman no Systemd usando Quadlet. Em vez de definir pilhas de vários contêineres em um único arquivo, como fazemos ao usar o docker-compose, com o Quadlet, definimos contêineres, volumes e redes usando unidades Systemd dedicadas.