Post

Automatizando a Instalação do Dokuwiki em Docker com Terraform e Ansible

Automatizando a Instalação do Dokuwiki em Docker com Terraform e Ansible

A automação de infraestrutura se tornou um componente essencial no arsenal de qualquer profissional de TI moderno. Ao automatizar processos repetitivos de configuração e implantação, não apenas economizamos tempo valioso, mas também reduzimos significativamente a possibilidade de erros humanos. Neste tutorial, vamos explorar como combinar três poderosas ferramentas de automação - Terraform, Ansible e Docker - para criar uma solução completa de implantação automatizada do Dokuwiki, um sistema de wiki leve e versátil.

O Terraform nos permitirá definir nossa infraestrutura como código, provisionando máquinas virtuais de forma consistente e reproduzível. O Ansible nos ajudará a configurar essas máquinas de maneira declarativa, garantindo que todas as dependências e configurações necessárias estejam presentes. Por fim, o Docker nos proporcionará um ambiente isolado e portátil para executar o Dokuwiki, facilitando sua manutenção e atualização.

Antes de começarmos, certifique-se de que você possui acesso a um servidor KVM/QEMU para hospedar nossas máquinas virtuais. Também é recomendável ter conhecimentos básicos sobre virtualização, redes e linha de comando Linux. Vamos começar nossa jornada de automação!

Configuração do Ambiente Terraform

O Terraform é uma ferramenta de infraestrutura como código (IaC) que permite definir recursos de infraestrutura em arquivos de configuração que podem ser versionados, reutilizados e compartilhados. Vamos começar instalando o Terraform em nossa máquina local.

Para garantir que estamos sempre utilizando a versão mais recente do Terraform, vamos usar um comando que consulta a API do GitHub para obter a última versão disponível. Isso é particularmente útil em ambientes de automação, onde queremos garantir que estamos sempre atualizados com as últimas funcionalidades e correções de segurança.

1
TER_VER=$(curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | grep tag_name | cut -d: -f2 | tr -d \"\,\v | awk '{$1=$1};1')

Este comando utiliza o curl para fazer uma requisição à API do GitHub, obtendo informações sobre a última versão do Terraform. Em seguida, utilizamos uma série de comandos de processamento de texto para extrair apenas o número da versão. O resultado é armazenado na variável TER_VER, que utilizaremos para baixar a versão correta.

Agora, vamos baixar e instalar o Terraform:

1
2
3
4
wget https://releases.hashicorp.com/terraform/${TER_VER}/terraform_${TER_VER}_linux_amd64.zip
unzip terraform_${TER_VER}_linux_amd64.zip
sudo mv terraform /usr/local/bin/
rm terraform_${TER_VER}_linux_amd64.zip

Nesta sequência de comandos, baixamos o arquivo zip do Terraform na versão que obtivemos anteriormente, descompactamos o arquivo, movemos o binário para o diretório /usr/local/bin/ (que geralmente está no PATH do sistema) e, por fim, removemos o arquivo zip para manter nosso sistema organizado.

Com o Terraform instalado, vamos criar a estrutura de diretórios para nosso projeto:

1
2
mkdir -p ~/terraform/providers/libvirt/docker
cd ~/terraform/providers/libvirt/docker

Aqui, criamos uma hierarquia de diretórios que reflete a organização do nosso projeto. O diretório providers/libvirt indica que estamos utilizando o provider Libvirt do Terraform, que nos permitirá interagir com o hypervisor KVM/QEMU. O subdiretório docker é onde armazenaremos os arquivos específicos para nossa máquina virtual que executará o Docker.

Criação da Infraestrutura com Terraform

Agora que temos nossa estrutura de diretórios pronta, vamos criar o arquivo de configuração principal do Terraform. Este arquivo definirá todos os recursos que queremos provisionar, incluindo redes, volumes e máquinas virtuais.

Vamos começar definindo o provider Libvirt e configurando a rede virtual para nossa máquina Docker:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
terraform {
  required_providers {
    libvirt = {
      source = "dmacvicar/libvirt"
    }
  }
}

provider "libvirt" {
  uri = "qemu+ssh://gean@192.168.0.254/system"
}

resource "libvirt_network" "docker" {
  name      = "docker"
  mode      = "nat"
  addresses = ["10.5.6.0/24"]
  autostart = true
  dhcp {
    enabled = false
  }
  dns {
    enabled = true
  }
}

Neste trecho, começamos declarando que nosso projeto requer o provider Libvirt, que será baixado automaticamente pelo Terraform durante a inicialização. Em seguida, configuramos o provider para se conectar ao nosso hypervisor KVM/QEMU através de SSH, utilizando o usuário “gean” e o endereço IP “192.168.0.254”.

Depois, definimos uma rede virtual chamada “docker” no modo NAT, utilizando o bloco de endereços 10.5.6.0/24. Desabilitamos o DHCP, pois configuraremos endereços IP estáticos, e habilitamos o DNS para resolução de nomes dentro da rede. A opção autostart garante que esta rede seja iniciada automaticamente quando o hypervisor for reiniciado.

Agora, vamos configurar o volume de disco e a imagem base para nossa máquina virtual:

1
2
3
4
5
6
resource "libvirt_volume" "os_image" {
  name   = "debpacker.qcow2"
  pool   = "default"
  source = "/home/gean/kvm/templates/debian-12-amd64.qcow2"
  format = "qcow2"
}

Aqui, definimos um volume chamado “debpacker.qcow2” no pool de armazenamento “default”. Este volume será baseado em uma imagem Debian 12 pré-existente, localizada em “/home/gean/kvm/templates/debian-12-amd64.qcow2”. O formato qcow2 é um formato de imagem de disco eficiente que suporta copy-on-write, permitindo que múltiplas máquinas virtuais compartilhem a mesma imagem base, economizando espaço em disco.

Para configurar nossa máquina virtual com as configurações iniciais, utilizaremos o Cloud-Init, uma ferramenta que permite a inicialização e configuração de instâncias em nuvem. Vamos definir os arquivos de configuração do Cloud-Init:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data "template_file" "user_data" {
  template = file("${path.module}/cloud_init.yml")
}

data "template_file" "network_config" {
  template = file("${path.module}/network_cfg.yml")
}

resource "libvirt_cloudinit_disk" "cloudinit_docker" {
  name           = "cloudinit_docker.iso"
  user_data      = data.template_file.user_data.rendered
  network_config = data.template_file.network_config.rendered
  pool           = "default"
}

Neste trecho, utilizamos o recurso template_file para ler os arquivos de configuração do Cloud-Init. O arquivo cloud_init.yml conterá configurações relacionadas ao usuário, pacotes e comandos a serem executados durante a inicialização, enquanto o arquivo network_cfg.yml conterá configurações de rede. Em seguida, criamos um disco ISO contendo essas configurações, que será montado na máquina virtual durante o boot.

Agora, vamos criar o arquivo de configuração de rede para o Cloud-Init:

1
vim network_cfg.yml

E adicionamos o seguinte conteúdo:

1
2
3
4
5
6
7
8
9
10
11
12
13
#cloud-config
network:
  version: 2
  ethernets:
    ens3:
      addresses:
        - 10.5.6.10/24
      nameservers:
        addresses: 
          - 10.5.6.1
      routes:
        - to: default
          via: 10.5.6.1

Esta configuração define uma interface de rede chamada “ens3” com o endereço IP estático 10.5.6.10/24. Configuramos o servidor DNS como 10.5.6.1 (que será o gateway da rede) e definimos uma rota padrão através desse mesmo gateway.

Para o arquivo de configuração do usuário, precisamos primeiro gerar uma senha criptografada:

1
mkpasswd --method=SHA-512

Este comando solicitará uma senha e retornará sua versão criptografada usando o algoritmo SHA-512, que é seguro e compatível com o sistema de autenticação do Linux.

Agora, vamos criar o arquivo de configuração do usuário:

1
vim cloud_init.yml

E adicionamos o seguinte conteúdo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#cloud-config

ssh_pwauth: yes 

users:
  - name: gean
    gecos: "Gean Martins"
    sudo: ALL=(ALL) NOPASSWD:ALL  
    groups: users, sudo
    shell: /bin/bash
    lock_passwd: false  
    passwd: $6$uGQol.HnKU0nvpEy$YJl94Y7/p1cWZVlu0gsZIeebssh4oHCIQ9VNX721T1/Lx0UbQVbjfbzS2.9.2EGz4Hdxi0ICNKAua8lo/AsuT1  

chpasswd:
  list: |
    root:$6$uGQol.HnKU0nvpEy$YJl94Y7/p1cWZVlu0gsZIeebssh4oHCIQ9VNX721T1/Lx0UbQVbjfbzS2.9.2EGz4Hdxi0ICNKAua8lo/AsuT1  
  expire: False  

packages:
  - qemu-guest-agent  

package_update: true  
package_upgrade: true 

locale: pt_BR.UTF-8
timezone: America/Sao_Paulo

runcmd:
  - hostnamectl set-hostname docker

Esta configuração cria um usuário chamado “gean” com privilégios de sudo sem necessidade de senha, define a mesma senha para o usuário root, instala o pacote qemu-guest-agent (que melhora a comunicação entre o host e a VM), atualiza todos os pacotes do sistema, configura o locale para português brasileiro e o fuso horário para São Paulo, e define o hostname da máquina como “docker”.

Finalmente, vamos definir a máquina virtual em si:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
resource "libvirt_domain" "docker" {
  name   = "docker"
  memory = "2048"
  vcpu   = 2

  cpu {
    mode = "host-passthrough"
  }

  cloudinit = libvirt_cloudinit_disk.cloudinit_docker.id

  network_interface {
    network_name = libvirt_network.docker.name
  }

  console {
    type        = "pty"
    target_port = "0"
    target_type = "serial"
  }

  disk {
    volume_id = libvirt_volume.os_image.id
  }

  graphics {
    type        = "spice"
    listen_type = "none"
  }
}

Nesta configuração, definimos uma máquina virtual chamada “docker” com 2GB de RAM e 2 vCPUs. Utilizamos o modo “host-passthrough” para a CPU, o que permite que a VM acesse diretamente as características da CPU do host, melhorando o desempenho. Associamos o disco de Cloud-Init que criamos anteriormente, conectamos a VM à rede “docker” que definimos, configuramos um console serial para acesso via terminal, associamos o volume de disco que criamos e configuramos a interface gráfica para usar o protocolo SPICE.

Agora que temos todos os arquivos de configuração necessários, podemos inicializar o Terraform e aplicar nossa configuração:

1
2
3
4
5
terraform init          # Inicializa o Terraform
terraform fmt           # Formata os arquivos Terraform
terraform validate      # Valida a configuração
terraform plan          # Mostra o que será criado
terraform apply         # Aplica as mudanças

O comando terraform init baixa os providers necessários e inicializa o diretório de trabalho. O comando terraform fmt formata os arquivos de configuração para seguir as convenções de estilo do Terraform. O comando terraform validate verifica se a configuração está sintaticamente correta e internamente consistente. O comando terraform plan mostra quais recursos serão criados, modificados ou destruídos. Finalmente, o comando terraform apply aplica as mudanças, criando efetivamente nossa infraestrutura.

Para facilitar o acesso à nossa nova máquina virtual, vamos configurar o SSH para usar um host intermediário (jump host):

1
2
3
4
5
6
7
8
9
10
11
vim ~/.ssh/config 
# JumpKVM
Host kvm
    HostName 192.168.0.254
    User gean
    IdentityFile ~/.ssh/tfvms 

Host 10.5.6.*
    User gean
    IdentityFile ~/.ssh/tfvms 
    ProxyJump kvm

Esta configuração permite que nos conectemos diretamente a qualquer máquina na rede 10.5.6.0/24 (incluindo nossa VM Docker) através do host KVM, sem precisar nos conectar manualmente ao host KVM primeiro. Isso simplifica bastante o processo de acesso às nossas máquinas virtuais.

Configuração do Ambiente Ansible

Com nossa máquina virtual provisionada pelo Terraform, agora vamos utilizar o Ansible para configurar o ambiente Docker e implantar o Dokuwiki. O Ansible é uma ferramenta de automação que permite definir o estado desejado de um sistema e, em seguida, aplicar as configurações necessárias para atingir esse estado.

Primeiro, vamos instalar o Ansible em nossa máquina local:

1
2
3
4
sudo apt update && sudo apt upgrade -y
sudo apt install software-properties-common -y
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible sshpass -y

Estes comandos atualizam o sistema, instalam as dependências necessárias, adicionam o repositório oficial do Ansible e, finalmente, instalam o Ansible e o sshpass (que permite ao Ansible usar senhas para autenticação SSH, embora seja mais seguro usar chaves SSH).

Agora, vamos criar a estrutura de diretórios para nosso projeto Ansible:

1
2
mkdir -p ~/ansible/docker
cd ~/ansible/docker

Para facilitar a autenticação SSH, vamos gerar um par de chaves específico para o Ansible e copiá-lo para nossa máquina virtual:

1
2
ssh-keygen -t ed25519 -f ansible
ssh-copy-id -i ansible.pub gean@10.5.6.10

O comando ssh-keygen gera um par de chaves usando o algoritmo Ed25519, que é moderno, seguro e eficiente. O comando ssh-copy-id copia a chave pública para a máquina virtual, permitindo que o Ansible se conecte sem precisar de senha.

Agora, vamos criar o arquivo de inventário do Ansible, que define os hosts que queremos gerenciar:

1
2
3
vim hosts
[docker]
10.5.6.10 ansible_user=gean

Este arquivo define um grupo chamado “docker” contendo um único host com o endereço IP 10.5.6.10 e o usuário “gean” para conexão SSH.

Em seguida, vamos criar o arquivo de configuração do Ansible:

1
2
3
4
5
vim ansible.cfg
[defaults]
inventory = hosts
host_key_checking = False
private_key_file = ansible

Esta configuração define o arquivo de inventário padrão, desativa a verificação de chaves de host (útil em ambientes de desenvolvimento, mas não recomendado em produção) e especifica o arquivo de chave privada a ser usado para autenticação SSH.

Para organizar melhor nossas variáveis, vamos criar um diretório para variáveis de grupo:

1
mkdir group_vars

E vamos definir algumas variáveis para o grupo “debian”:

1
2
3
vim group_vars/docker.yml
docker_users:
  - gean

Esta variável define quais usuários devem ser adicionados ao grupo “docker”, permitindo que eles executem comandos Docker sem precisar de sudo.

Automação da Instalação do Docker com Ansible

Agora, vamos criar um playbook Ansible para instalar o Docker em nossa máquina virtual:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
vim playbook.yml
---
- hosts: docker
  become: true  # Garante que as tarefas sejam executadas com privilégios de superusuário
  tasks:

    - name: Atualizar o cache de pacotes APT
      apt:
        update_cache: yes
        cache_valid_time: 3600  # Cache válido por 1 hora

    - name: Instalar dependências do Docker
      apt:
        name: 
          - apt-transport-https
          - ca-certificates
          - curl
          - gnupg
          - lsb-release
        state: present

    - name: Baixar e adicionar chave GPG oficial do Docker
      ansible.builtin.apt_key:
        url: https://download.docker.com/linux/debian/gpg
        state: present
        keyring: /usr/share/keyrings/docker-archive-keyring.gpg

    - name: Adicionar repositório Docker
      ansible.builtin.apt_repository:
        repo: "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian  stable"
        state: present

    - name: Atualizar o cache de pacotes após adicionar o repositório Docker
      apt:
        update_cache: yes

    - name: Instalar Docker e Docker Compose
      apt:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
        state: present

    - name: Adicionar usuários ao grupo Docker
      user:
        name: ""
        groups: docker
        append: yes
      loop: ""

    - name: Iniciar e habilitar o serviço Docker
      systemd:
        name: docker
        enabled: yes
        state: started

Este playbook define uma série de tarefas a serem executadas no grupo de hosts “docker”. Primeiro, atualizamos o cache de pacotes APT. Em seguida, instalamos as dependências necessárias para o Docker. Depois, adicionamos a chave GPG oficial do Docker e o repositório Docker ao sistema. Atualizamos novamente o cache de pacotes e instalamos o Docker e suas dependências. Adicionamos os usuários especificados ao grupo “docker” e, finalmente, iniciamos e habilitamos o serviço Docker.

Infelizmente, há um pequeno problema no playbook: as tarefas “Adicionar usuários ao grupo Docker” estão com os campos name e loop vazios. Vamos corrigir isso:

1
2
3
4
5
6
    - name: Adicionar usuários ao grupo Docker
      user:
        name: ""
        groups: docker
        append: yes
      loop: ""

Agora, a tarefa utilizará a variável docker_users que definimos anteriormente para adicionar os usuários ao grupo “docker”.

Vamos executar o playbook:

1
ansible-playbook playbook.yml

Este comando executa o playbook, aplicando todas as tarefas definidas aos hosts especificados. O Ansible mostrará o progresso de cada tarefa e, ao final, um resumo das alterações realizadas.

Para verificar se o Docker foi instalado corretamente, podemos executar:

1
ansible docker -m shell -a "docker --version"

Este comando executa o comando docker --version na máquina virtual e retorna o resultado, permitindo-nos verificar se o Docker foi instalado corretamente e qual versão está sendo utilizada.

Implantação do Dokuwiki com Docker Compose

Agora que temos o Docker instalado em nossa máquina virtual, vamos criar um playbook para implantar o Dokuwiki utilizando Docker Compose:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
vim playbook-compose.yml
---
- hosts: docker
  become: true
  tasks:

    - name: Criar diretório para o Docker Compose
      file:
        path: /srv/dokuwiki
        state: directory
        mode: '0755'

    - name: Copiar o arquivo docker-compose.yml para o servidor
      copy:
        src: ./docker-compose.yml  # Caminho do arquivo local no controlador
        dest: /srv/dokuwiki/docker-compose.yml  # Caminho de destino no servidor
        mode: '0644'

    - name: Criar diretório de volumes para o DokuWiki
      file:
        path: /srv/dokuwiki/html
        state: directory
        mode: '0755'

    - name: Ajustar permissões no diretório html
      file:
        path: /srv/dokuwiki/html
        owner: "1000"
        group: "1000"
        recurse: yes
        mode: '0755'

    - name: Garantir que o Docker Compose esteja instalado
      apt:
        name: docker-compose-plugin
        state: present

    - name: Executar docker-compose up
      shell: docker compose -f /srv/dokuwiki/docker-compose.yml up -d
      args:
        chdir: /srv/dokuwiki

Este playbook cria um diretório para o Docker Compose, copia o arquivo docker-compose.yml para o servidor, cria um diretório para os volumes do Dokuwiki, ajusta as permissões desse diretório, garante que o plugin Docker Compose esteja instalado e, finalmente, executa o comando docker compose up para iniciar o contêiner do Dokuwiki.

Agora, precisamos criar o arquivo docker-compose.yml que será copiado para o servidor:

1
vim docker-compose.yml

E adicionamos o seguinte conteúdo:

1
2
3
4
5
6
7
8
9
10
11
services:
  dokuwiki:
    image: dokuwiki/dokuwiki:stable
    user: "1000:1000"
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      PHP_TIMEZONE: America/Sao_Paulo
    volumes:
      - ./html:/storage

Esta configuração define um serviço chamado “dokuwiki” baseado na imagem oficial do Dokuwiki. O contêiner será executado com o usuário e grupo 1000 (que geralmente corresponde ao primeiro usuário não-root em sistemas Linux), será reiniciado automaticamente a menos que seja explicitamente parado, expõe a porta 8080 para acesso web, define o fuso horário para São Paulo e monta o diretório local html no diretório /storage dentro do contêiner, permitindo que os dados do Dokuwiki sejam persistentes.

Vamos executar o playbook para implantar o Dokuwiki:

1
ansible-playbook playbook-compose.yml

Este comando executa o playbook, criando os diretórios necessários, copiando o arquivo docker-compose.yml e iniciando o contêiner do Dokuwiki.

Acesso e Configuração Final

Agora que o Dokuwiki está em execução em nossa máquina virtual, precisamos configurar o acesso a ele. Como a máquina virtual está em uma rede privada, vamos criar um túnel SSH para acessar o Dokuwiki a partir de nossa máquina local:

1
ssh -L 127.0.0.3:8080:localhost:8080 -N -f gean@10.5.6.10

Este comando cria um túnel SSH que encaminha as conexões feitas para 127.0.0.3:8081 na máquina local para localhost:8080 na máquina remota (10.5.6.10).

O parâmetro -L 127.0.0.3:8081:localhost:8080 define que qualquer acesso local ao endereço 127.0.0.3:8081 será redirecionado pelo túnel SSH para a porta 8080 da máquina remota, vista do próprio 10.5.6.10.

O parâmetro -N indica que não queremos executar um comando remoto, apenas manter o túnel aberto. O -f coloca o processo SSH em segundo plano.

Para facilitar o acesso, vamos adicionar uma entrada no arquivo /etc/hosts para mapear o nome wiki.example.com para o endereço IP 127.0.0.3:

1
echo "127.0.0.3 wiki.example.com" | sudo tee -a /etc/hosts

Agora, podemos acessar o Dokuwiki abrindo um navegador e acessando o endereço http://wiki.example.com:8080. Na primeira execução, seremos direcionados para a página de configuração inicial do Dokuwiki, onde poderemos definir o título do wiki, o superusuário e outras configurações importantes.

Conclusão

Neste tutorial, exploramos como automatizar completamente a instalação e configuração do Dokuwiki utilizando uma combinação de ferramentas de automação modernas: Terraform para provisionar a infraestrutura virtual, Ansible para configurar o ambiente e Docker para containerizar a aplicação.

Começamos instalando e configurando o Terraform, definindo nossa infraestrutura como código. Utilizamos o provider Libvirt para criar uma máquina virtual baseada em Debian, configurando rede, armazenamento e inicialização com Cloud-Init. Em seguida, instalamos e configuramos o Ansible, criando playbooks para instalar o Docker e implantar o Dokuwiki utilizando Docker Compose. Finalmente, configuramos o acesso ao Dokuwiki através de um túnel SSH e mapeamento de hosts.

Esta abordagem de automação oferece várias vantagens:

  1. Reprodutibilidade: Todo o processo pode ser repetido facilmente, garantindo consistência entre ambientes.
  2. Versionamento: Toda a configuração está em arquivos de texto que podem ser versionados com Git.
  3. Documentação como código: Os arquivos de configuração servem como documentação do ambiente.
  4. Escalabilidade: O mesmo processo pode ser utilizado para implantar múltiplas instâncias do Dokuwiki ou outras aplicações.

Como próximos passos, você pode explorar a configuração de HTTPS para o Dokuwiki utilizando um proxy reverso como Nginx ou Traefik, a implementação de backups automatizados para os dados do Dokuwiki, ou a integração com sistemas de monitoramento para garantir a disponibilidade e desempenho da aplicação.

A automação de infraestrutura é um campo vasto e em constante evolução, e as ferramentas que exploramos neste tutorial são apenas a ponta do iceberg. Continue explorando e experimentando para descobrir como a automação pode tornar sua vida como administrador de sistemas mais fácil e eficiente.

Referências

This post is licensed under CC BY 4.0 by the author.