Post

Automação de Infraestrutura DNS com Ansible e Pipeline GitLab CI/CD

Automação de Infraestrutura DNS com Ansible e Pipeline GitLab CI/CD

Este é um tutorial complementar à nossa série sobre implementação e configuração do DNS BIND no Oracle Linux 9. Nos artigos anteriores, abordamos desde os conceitos fundamentais do DNS até a delegação e gerenciamento de subdomínios. Agora, vamos explorar como automatizar completamente a infraestrutura DNS utilizando Ansible e GitLab CI/CD.

Introdução

A automação de infraestrutura é essencial para ambientes modernos, permitindo maior agilidade, consistência e confiabilidade. Quando aplicada à infraestrutura DNS, a automação reduz erros manuais, facilita a implementação de mudanças e permite a adoção de práticas de Infraestrutura como Código (IaC).

Neste tutorial, vamos explorar como combinar o Ansible, uma poderosa ferramenta de automação, com o GitLab CI/CD, uma plataforma completa de integração e entrega contínua, para criar um fluxo automatizado de gerenciamento de infraestrutura DNS. Abordaremos desde a estruturação do código Ansible até a configuração de pipelines de CI/CD, incluindo validação, testes e implantação controlada.

Ao final deste tutorial, você terá uma solução completa para gerenciar sua infraestrutura DNS como código, com processos automatizados de validação e implantação, seguindo as melhores práticas de DevOps e IaC.

Benefícios da Automação para Infraestrutura DNS

Antes de mergulharmos nos detalhes técnicos, vamos entender os benefícios da automação para infraestrutura DNS:

Consistência e Padronização

  • Configurações padronizadas: Todas as instâncias seguem o mesmo padrão
  • Eliminação de variações: Reduz problemas causados por configurações inconsistentes
  • Aplicação de políticas: Garante que políticas de segurança e boas práticas sejam seguidas

Agilidade e Eficiência

  • Implantação rápida: Reduz o tempo para provisionar novos servidores ou zonas
  • Resposta a mudanças: Permite implementar alterações rapidamente
  • Escalabilidade: Facilita o gerenciamento de infraestruturas grandes e complexas

Confiabilidade e Segurança

  • Redução de erros: Minimiza erros humanos em configurações críticas
  • Testes automatizados: Valida alterações antes da implantação
  • Auditoria e rastreabilidade: Mantém histórico completo de alterações

Colaboração e Governança

  • Revisão de código: Permite revisão por pares das alterações
  • Aprovações: Implementa fluxos de aprovação para mudanças
  • Documentação viva: O código serve como documentação atualizada da infraestrutura

Visão Geral da Solução

Nossa solução combinará:

  1. Ansible: Para definir a configuração desejada dos servidores DNS
  2. GitLab: Para armazenar o código e executar pipelines de CI/CD
  3. Integração: Fluxo completo desde o commit até a produção

Fluxo de Trabalho

O fluxo de trabalho típico será:

  1. Desenvolvedor faz alterações no código Ansible (configurações, zonas, etc.)
  2. Commit é enviado para o repositório GitLab
  3. Pipeline de CI/CD é acionado automaticamente
  4. Validação sintática e linting são executados
  5. Testes são realizados em ambiente de desenvolvimento
  6. Aprovação manual (opcional) para ambientes de produção
  7. Implantação controlada em produção
  8. Verificação pós-implantação
+-----------------+      +-----------------+      +-----------------+      +-----------------+
| Desenvolvedor   |----->| Repositório Git |----->| Pipeline CI/CD  |----->| Servidores DNS  |
| (Faz Alterações)|      | (GitLab)        |      | (GitLab Runner) |      | (Produção)      |
+-------+---------+      +-------+---------+      +-------+---------+      +-------+---------+
        |                        ^                        |                        |
        | 1. git commit          | 2. git push            | 3. Pipeline Triggered  | 7. Ansible Aplica |
        |    git push            |                        |                        |    Configuração   |
        V                        |                        V                        V
+-------+---------+      +-------+---------+      +-------+---------+      +-------+---------+
|                 |      |                 |      | 4. Validação    |      |                 |
|                 |      |                 |      | 5. Testes       |      |                 |
|                 |      |                 |      | 6. Implantação  |      |                 |
+-----------------+      +-----------------+      +-----------------+      +-----------------+

Figura 1: Fluxo de Trabalho Automatizado com Ansible e GitLab CI/CD

Estrutura do Repositório

Estrutura do Repositório

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
dns-infrastructure/
├── ansible/
│   ├── inventory/
│   │   ├── production/
│   │   │   ├── hosts.yml
│   │   │   └── group_vars/
│   │   └── development/
│   │       ├── hosts.yml
│   │       └── group_vars/
│   ├── roles/
│   │   ├── bind_common/
│   │   ├── bind_primary/
│   │   ├── bind_secondary/
│   │   └── bind_views/
│   ├── playbooks/
│   │   ├── site.yml
│   │   ├── primary.yml
│   │   ├── secondary.yml
│   │   └── views.yml
│   └── templates/
│       ├── named.conf.j2
│       └── zones/
├── zones/
│   ├── example.com.zone
│   └── 200.168.192.in-addr.arpa.zone
├── tests/
│   ├── syntax_check.sh
│   ├── zone_validation.sh
│   └── integration_tests.sh
├── .gitlab-ci.yml
└── README.md

Parte 1: Configuração do Ambiente

Antes de começar, precisamos configurar nosso ambiente de desenvolvimento e as ferramentas necessárias.

Requisitos

  • Oracle Linux 9 (ou outra distribuição Linux compatível)
  • Ansible 2.9+
  • Git
  • Acesso a um servidor GitLab (ou gitlab.com)
  • Servidores DNS BIND configurados conforme tutoriais anteriores

Instalação do Ansible

1
2
3
4
5
6
# Instalar Ansible no Oracle Linux 9
sudo dnf install oracle-epel-release-el9
sudo dnf install ansible

# Verificar a instalação
ansible --version

Configuração do Git

1
2
3
4
5
6
7
8
9
# Instalar Git
sudo dnf install git

# Configurar Git
git config --global user.name "Seu Nome"
git config --global user.email "seu.email@exemplo.com"

# Gerar chave SSH para GitLab (se necessário)
ssh-keygen -t ed25519 -C "seu.email@exemplo.com"

Inicialização do Repositório

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Criar estrutura de diretórios
mkdir -p dns-infrastructure/{ansible/{inventory/{production,development},roles,playbooks,templates},zones,tests}

# Inicializar repositório Git
cd dns-infrastructure
git init

# Criar arquivo .gitignore
cat > .gitignore << EOF
*.retry
*.swp
*~
.DS_Store
.idea/
.vscode/
__pycache__/
*.pyc
vault_password.txt
EOF

# Commit inicial
git add .gitignore
git commit -m "Inicialização do repositório de infraestrutura DNS"

Conexão com GitLab

1
2
3
4
5
# Adicionar remote do GitLab
git remote add origin git@gitlab.com:seu-usuario/dns-infrastructure.git

# Enviar para GitLab
git push -u origin master

Parte 2: Estruturação do Projeto Ansible

Vamos estruturar nosso projeto Ansible seguindo as melhores práticas.

Inventário

O inventário define os servidores que serão gerenciados pelo Ansible.

Inventário de Produção

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
# ansible/inventory/production/hosts.yml
cat > ansible/inventory/production/hosts.yml << EOF
---
all:
  children:
    dns_servers:
      children:
        primary:
          hosts:
            ns1.example.com:
              ansible_host: 192.168.200.49
        secondary:
          hosts:
            ns2.example.com:
              ansible_host: 192.168.200.50
        development_servers:
          hosts:
            ns1.dev.example.com:
              ansible_host: 192.168.200.51
            ns2.dev.example.com:
              ansible_host: 192.168.200.52
  vars:
    ansible_user: ansible
    ansible_python_interpreter: /usr/bin/python3
EOF

Variáveis de Grupo

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
57
58
59
60
# ansible/inventory/production/group_vars/all.yml
mkdir -p ansible/inventory/production/group_vars
cat > ansible/inventory/production/group_vars/all.yml << EOF
---
# Variáveis globais
dns_domain: example.com
dns_admin_email: admin.example.com
dns_ttl: 86400
dns_refresh: 10800
dns_retry: 3600
dns_expire: 604800
dns_minimum: 86400

# Configuração de rede
dns_allow_query:
  - localhost
  - 192.168.200.0/24

# Configuração de logging
dns_log_dir: /var/log/named
dns_log_versions: 5
dns_log_size: "20m"

# Configuração de segurança
dns_use_tsig: true
dns_tsig_key_name: "xfer-key"
dns_tsig_key_algorithm: "hmac-sha256"
dns_tsig_key_secret: "{{ vault_dns_tsig_key_secret }}"  # Armazenado no vault
EOF

# ansible/inventory/production/group_vars/primary.yml
cat > ansible/inventory/production/group_vars/primary.yml << EOF
---
dns_role: primary
dns_zones:
  - name: "example.com"
    type: "master"
    file: "zones/example.com.zone"
  - name: "200.168.192.in-addr.arpa"
    type: "master"
    file: "zones/200.168.192.in-addr.arpa.zone"
EOF

# ansible/inventory/production/group_vars/secondary.yml
cat > ansible/inventory/production/group_vars/secondary.yml << EOF
---
dns_role: secondary
dns_primary_server: 192.168.200.49
dns_zones:
  - name: "example.com"
    type: "slave"
    file: "slaves/example.com.zone"
    masters:
      - "{{ dns_primary_server }}"
  - name: "200.168.192.in-addr.arpa"
    type: "slave"
    file: "slaves/200.168.192.in-addr.arpa.zone"
    masters:
      - "{{ dns_primary_server }}"
EOF

Variáveis Sensíveis com Ansible Vault

1
2
# Criar arquivo vault para variáveis sensíveis
ansible-vault create ansible/inventory/production/group_vars/all/vault.yml

Adicione o seguinte conteúdo ao arquivo vault:

1
2
---
vault_dns_tsig_key_secret: "a2xhc2Rqa2xhanNka2xqYWtsc2RqYWtsc2RqbGFrc2o="

Roles Ansible

As roles Ansible organizam o código em unidades reutilizáveis.

Role bind_common

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# Criar estrutura da role
ansible-galaxy role init ansible/roles/bind_common

# Tarefas principais
cat > ansible/roles/bind_common/tasks/main.yml << EOF
---
- name: Instalar pacotes BIND
  dnf:
    name:
      - bind
      - bind-utils
    state: present

- name: Criar diretórios para zonas
  file:
    path: "/var/named/{{ item }}"
    state: directory
    owner: root
    group: named
    mode: '0750'
  loop:
    - zones
    - data

- name: Criar diretório para logs
  file:
    path: "{{ dns_log_dir }}"
    state: directory
    owner: named
    group: named
    mode: '0770'

- name: Configurar contextos SELinux
  sefcontext:
    target: "{{ item.target }}"
    setype: "{{ item.setype }}"
    state: present
  loop:
    - { target: '/var/named/zones(/.*)?', setype: 'named_zone_t' }
    - { target: '/var/named/slaves(/.*)?', setype: 'named_cache_t' }
    - { target: '{{ dns_log_dir }}(/.*)?', setype: 'named_log_t' }
    - { target: '/var/named/data(/.*)?', setype: 'named_cache_t' }
  notify: Restaurar contextos SELinux

- name: Configurar logrotate para BIND
  template:
    src: named-logrotate.j2
    dest: /etc/logrotate.d/named
    owner: root
    group: root
    mode: '0644'

- name: Configurar firewall
  firewalld:
    service: dns
    permanent: yes
    state: enabled
  notify: Recarregar firewall
EOF

# Handlers
cat > ansible/roles/bind_common/handlers/main.yml << EOF
---
- name: Restaurar contextos SELinux
  command: restorecon -Rv /var/named /etc/named.conf {{ dns_log_dir }}
  listen: Restaurar contextos SELinux

- name: Reiniciar BIND
  systemd:
    name: named
    state: restarted
  listen: Reiniciar BIND

- name: Recarregar BIND
  command: rndc reload
  listen: Recarregar BIND

- name: Recarregar firewall
  command: firewall-cmd --reload
  listen: Recarregar firewall
EOF

# Template para logrotate
cat > ansible/roles/bind_common/templates/named-logrotate.j2 << EOF
{{ dns_log_dir }}/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 0640 named named
    sharedscripts
    postrotate
        /usr/bin/systemctl reload named.service > /dev/null 2>&1 || true
    endscript
}
EOF

Role bind_primary

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# Criar estrutura da role
ansible-galaxy role init ansible/roles/bind_primary

# Tarefas principais
cat > ansible/roles/bind_primary/tasks/main.yml << EOF
---
- name: Configurar named.conf
  template:
    src: named.conf.primary.j2
    dest: /etc/named.conf
    owner: root
    group: named
    mode: '0640'
  notify: Reiniciar BIND

- name: Copiar arquivos de zona
  template:
    src: "zones/{{ item.name }}.zone.j2"
    dest: "/var/named/{{ item.file }}"
    owner: root
    group: named
    mode: '0640'
  loop: "{{ dns_zones }}"
  when: item.type == 'master'
  notify: Recarregar BIND

- name: Habilitar e iniciar serviço BIND
  systemd:
    name: named
    enabled: yes
    state: started
EOF

# Template para named.conf
cat > ansible/roles/bind_primary/templates/named.conf.primary.j2 << EOF
// named.conf para servidor primário
options {
    directory "/var/named";
    
    listen-on port 53 { 
        127.0.0.1;
        {{ ansible_default_ipv4.address }};
    };
    listen-on-v6 port 53 { ::1; };
    
    allow-query { 
        {% for net in dns_allow_query %}
        {{ net }};
        {% endfor %}
    };
    
    recursion yes;
    allow-recursion { 
        {% for net in dns_allow_query %}
        {{ net }};
        {% endfor %}
    };
    
    dnssec-validation auto;
    
    // Logging configuration
    logging {
        channel general_log {
            file "{{ dns_log_dir }}/general.log" versions {{ dns_log_versions }} size {{ dns_log_size }};
            severity info;
            print-time yes;
            print-severity yes;
            print-category yes;
        };
        
        channel security_log {
            file "{{ dns_log_dir }}/security.log" versions {{ dns_log_versions }} size {{ dns_log_size }};
            severity info;
            print-time yes;
            print-severity yes;
            print-category yes;
        };
        
        channel query_log {
            file "{{ dns_log_dir }}/query.log" versions {{ dns_log_versions }} size {{ dns_log_size }};
            severity info;
            print-time yes;
        };
        
        channel error_log {
            file "{{ dns_log_dir }}/error.log" versions {{ dns_log_versions }} size {{ dns_log_size }};
            severity error;
            print-time yes;
            print-severity yes;
            print-category yes;
        };
        
        category default { general_log; };
        category general { general_log; };
        category security { security_log; };
        category queries { query_log; };
        category client { general_log; };
        category network { general_log; };
        category dnssec { security_log; };
        category resolver { error_log; };
    };
};

// ACLs
acl "dns_servers" {
    localhost;
    {{ ansible_default_ipv4.address }};
    {% for host in groups['secondary'] %}
    {{ hostvars[host]['ansible_host'] }};
    {% endfor %}
};

{% if dns_use_tsig %}
// TSIG key
key "{{ dns_tsig_key_name }}" {
    algorithm {{ dns_tsig_key_algorithm }};
    secret "{{ dns_tsig_key_secret }}";
};

{% for host in groups['secondary'] %}
server {{ hostvars[host]['ansible_host'] }} {
    keys { {{ dns_tsig_key_name }}; };
};
{% endfor %}
{% endif %}

// Zone definitions
{% for zone in dns_zones %}
zone "{{ zone.name }}" IN {
    type {{ zone.type }};
    file "{{ zone.file }}";
    {% if zone.type == "master" %}
    {% if dns_use_tsig %}
    allow-transfer { key {{ dns_tsig_key_name }}; };
    {% else %}
    allow-transfer { dns_servers; };
    {% endif %}
    also-notify { 
        {% for host in groups['secondary'] %}
        {{ hostvars[host]['ansible_host'] }};
        {% endfor %}
    };
    notify yes;
    {% endif %}
};
{% endfor %}

// Include standard zones
include "/etc/named.rfc1912.zones";

// Include DNSSEC root key
include "/etc/named.root.key";
EOF

Role bind_secondary

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# Criar estrutura da role
ansible-galaxy role init ansible/roles/bind_secondary

# Tarefas principais
cat > ansible/roles/bind_secondary/tasks/main.yml << EOF
---
- name: Criar diretório slaves
  file:
    path: /var/named/slaves
    state: directory
    owner: named
    group: named
    mode: '0770'
  notify: Restaurar contextos SELinux

- name: Configurar named.conf
  template:
    src: named.conf.secondary.j2
    dest: /etc/named.conf
    owner: root
    group: named
    mode: '0640'
  notify: Reiniciar BIND

- name: Habilitar e iniciar serviço BIND
  systemd:
    name: named
    enabled: yes
    state: started
EOF

# Template para named.conf
cat > ansible/roles/bind_secondary/templates/named.conf.secondary.j2 << EOF
// named.conf para servidor secundário
options {
    directory "/var/named";
    
    listen-on port 53 { 
        127.0.0.1;
        {{ ansible_default_ipv4.address }};
    };
    listen-on-v6 port 53 { ::1; };
    
    allow-query { 
        {% for net in dns_allow_query %}
        {{ net }};
        {% endfor %}
    };
    
    recursion yes;
    allow-recursion { 
        {% for net in dns_allow_query %}
        {{ net }};
        {% endfor %}
    };
    
    dnssec-validation auto;
    
    // Logging configuration
    logging {
        channel general_log {
            file "{{ dns_log_dir }}/general.log" versions {{ dns_log_versions }} size {{ dns_log_size }};
            severity info;
            print-time yes;
            print-severity yes;
            print-category yes;
        };
        
        channel security_log {
            file "{{ dns_log_dir }}/security.log" versions {{ dns_log_versions }} size {{ dns_log_size }};
            severity info;
            print-time yes;
            print-severity yes;
            print-category yes;
        };
        
        channel query_log {
            file "{{ dns_log_dir }}/query.log" versions {{ dns_log_versions }} size {{ dns_log_size }};
            severity info;
            print-time yes;
        };
        
        channel error_log {
            file "{{ dns_log_dir }}/error.log" versions {{ dns_log_versions }} size {{ dns_log_size }};
            severity error;
            print-time yes;
            print-severity yes;
            print-category yes;
        };
        
        category default { general_log; };
        category general { general_log; };
        category security { security_log; };
        category queries { query_log; };
        category client { general_log; };
        category network { general_log; };
        category dnssec { security_log; };
        category resolver { error_log; };
    };
};

// ACLs
acl "dns_servers" {
    localhost;
    {{ ansible_default_ipv4.address }};
    {{ dns_primary_server }};
};

{% if dns_use_tsig %}
// TSIG key
key "{{ dns_tsig_key_name }}" {
    algorithm {{ dns_tsig_key_algorithm }};
    secret "{{ dns_tsig_key_secret }}";
};

server {{ dns_primary_server }} {
    keys { {{ dns_tsig_key_name }}; };
};
{% endif %}

// Zone definitions
{% for zone in dns_zones %}
zone "{{ zone.name }}" IN {
    type {{ zone.type }};
    {% if zone.type == "slave" %}
    masters { 
        {% for master in zone.masters %}
        {{ master }};
        {% endfor %}
    };
    {% endif %}
    file "{{ zone.file }}";
};
{% endfor %}

// Include standard zones
include "/etc/named.rfc1912.zones";

// Include DNSSEC root key
include "/etc/named.root.key";
EOF

Playbooks

Os playbooks Ansible definem as tarefas a serem executadas nos servidores.

Playbook Principal

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
# ansible/playbooks/site.yml
cat > ansible/playbooks/site.yml << EOF
---
- name: Configurar servidores DNS primários
  hosts: primary
  become: yes
  roles:
    - bind_common
    - bind_primary

- name: Configurar servidores DNS secundários
  hosts: secondary
  become: yes
  roles:
    - bind_common
    - bind_secondary
EOF

# ansible/playbooks/primary.yml
cat > ansible/playbooks/primary.yml << EOF
---
- name: Configurar servidores DNS primários
  hosts: primary
  become: yes
  roles:
    - bind_common
    - bind_primary
EOF

# ansible/playbooks/secondary.yml
cat > ansible/playbooks/secondary.yml << EOF
---
- name: Configurar servidores DNS secundários
  hosts: secondary
  become: yes
  roles:
    - bind_common
    - bind_secondary
EOF

Arquivos de Zona

Os arquivos de zona definem os registros DNS.

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
# Criar diretório para templates de zona
mkdir -p ansible/roles/bind_primary/templates/zones

# Template para zona example.com
cat > ansible/roles/bind_primary/templates/zones/example.com.zone.j2 << EOF
$TTL {{ dns_ttl }}
@       IN SOA  ns1.{{ dns_domain }}. {{ dns_admin_email }}. (
                    {{ '%Y%m%d01' | strftime }}  ; Serial
                    {{ dns_refresh }}       ; Refresh
                    {{ dns_retry }}        ; Retry
                    {{ dns_expire }}      ; Expire
                    {{ dns_minimum }} )     ; Minimum TTL

        IN NS   ns1.{{ dns_domain }}.
        IN NS   ns2.{{ dns_domain }}.

ns1     IN A    {{ hostvars[groups['primary'][0]]['ansible_host'] }}
ns2     IN A    {{ hostvars[groups['secondary'][0]]['ansible_host'] }}
www     IN A    {{ hostvars[groups['primary'][0]]['ansible_host'] }}
mail    IN A    {{ hostvars[groups['primary'][0]]['ansible_host'] }}
@       IN MX   10 mail.{{ dns_domain }}.

; Delegação para dev.example.com
dev             IN NS   ns1.dev.{{ dns_domain }}.
dev             IN NS   ns2.dev.{{ dns_domain }}.
ns1.dev         IN A    {{ hostvars[groups['development_servers'][0]]['ansible_host'] }}
ns2.dev         IN A    {{ hostvars[groups['development_servers'][1]]['ansible_host'] }}
EOF

# Template para zona reversa
cat > ansible/roles/bind_primary/templates/zones/200.168.192.in-addr.arpa.zone.j2 << EOF
$TTL {{ dns_ttl }}
@       IN SOA  ns1.{{ dns_domain }}. {{ dns_admin_email }}. (
                    {{ '%Y%m%d01' | strftime }}  ; Serial
                    {{ dns_refresh }}       ; Refresh
                    {{ dns_retry }}        ; Retry
                    {{ dns_expire }}      ; Expire
                    {{ dns_minimum }} )     ; Minimum TTL

        IN NS   ns1.{{ dns_domain }}.
        IN NS   ns2.{{ dns_domain }}.

; Registros PTR
49      IN PTR  ns1.{{ dns_domain }}.
50      IN PTR  ns2.{{ dns_domain }}.
EOF

Testes

Vamos criar scripts de teste para validar nossa configuração.

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# Script para verificação de sintaxe
cat > tests/syntax_check.sh << EOF
#!/bin/bash
# Verificar sintaxe do Ansible

set -e

echo "Verificando sintaxe do Ansible..."
ansible-playbook ansible/playbooks/site.yml --syntax-check

echo "Verificando lint do Ansible..."
ansible-lint ansible/playbooks/site.yml

echo "Verificação de sintaxe concluída com sucesso."
EOF
chmod +x tests/syntax_check.sh

# Script para validação de zonas
cat > tests/zone_validation.sh << EOF
#!/bin/bash
# Validar arquivos de zona

set -e

echo "Gerando arquivos de zona temporários..."
mkdir -p /tmp/zones

# Gerar arquivos de zona a partir dos templates
for template in ansible/roles/bind_primary/templates/zones/*.zone.j2; do
    zone_file="/tmp/zones/\$(basename \$template .j2)"
    ansible-playbook -i localhost, -c local ansible/playbooks/generate_zone.yml -e "template=\$template output=\$zone_file" --skip-tags=all
    
    echo "Validando zona \$zone_file..."
    named-checkzone \$(basename \$zone_file .zone) \$zone_file
done

echo "Validação de zonas concluída com sucesso."
rm -rf /tmp/zones
EOF
chmod +x tests/zone_validation.sh

# Playbook para gerar zonas
cat > ansible/playbooks/generate_zone.yml << EOF
---
- name: Gerar arquivo de zona para teste
  hosts: localhost
  connection: local
  gather_facts: no
  vars:
    dns_domain: example.com
    dns_admin_email: admin.example.com
    dns_ttl: 86400
    dns_refresh: 10800
    dns_retry: 3600
    dns_expire: 604800
    dns_minimum: 86400
  tasks:
    - name: Criar arquivo de zona a partir do template
      template:
        src: "{{ template }}"
        dest: "{{ output }}"
      tags: never
EOF

# Script para testes de integração
cat > tests/integration_tests.sh << EOF
#!/bin/bash
# Testes de integração para servidores DNS

set -e

# Definir variáveis
PRIMARY_SERVER="192.168.200.49"
SECONDARY_SERVER="192.168.200.50"
DOMAIN="example.com"

echo "Executando testes de integração..."

# Teste 1: Verificar se o servidor primário responde
echo "Teste 1: Verificar se o servidor primário responde..."
dig @\$PRIMARY_SERVER \$DOMAIN SOA +short
if [ \$? -ne 0 ]; then
    echo "Falha: Servidor primário não responde."
    exit 1
fi
echo "Sucesso: Servidor primário responde."

# Teste 2: Verificar se o servidor secundário responde
echo "Teste 2: Verificar se o servidor secundário responde..."
dig @\$SECONDARY_SERVER \$DOMAIN SOA +short
if [ \$? -ne 0 ]; then
    echo "Falha: Servidor secundário não responde."
    exit 1
fi
echo "Sucesso: Servidor secundário responde."

# Teste 3: Verificar se os servidores retornam o mesmo serial
echo "Teste 3: Verificar se os servidores retornam o mesmo serial..."
PRIMARY_SERIAL=\$(dig @\$PRIMARY_SERVER \$DOMAIN SOA +short | awk '{print \$3}')
SECONDARY_SERIAL=\$(dig @\$SECONDARY_SERVER \$DOMAIN SOA +short | awk '{print \$3}')

if [ "\$PRIMARY_SERIAL" != "\$SECONDARY_SERIAL" ]; then
    echo "Falha: Seriais diferentes. Primário: \$PRIMARY_SERIAL, Secundário: \$SECONDARY_SERIAL"
    exit 1
fi
echo "Sucesso: Seriais iguais (\$PRIMARY_SERIAL)."

# Teste 4: Verificar registros específicos
echo "Teste 4: Verificar registros específicos..."
dig @\$PRIMARY_SERVER www.\$DOMAIN A +short
if [ \$? -ne 0 ]; then
    echo "Falha: Não foi possível resolver www.\$DOMAIN."
    exit 1
fi
echo "Sucesso: Registro www.\$DOMAIN encontrado."

echo "Todos os testes de integração concluídos com sucesso."
EOF
chmod +x tests/integration_tests.sh

Parte 3: Configuração do GitLab CI/CD

Agora, vamos configurar o pipeline de CI/CD no GitLab.

Arquivo .gitlab-ci.yml

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
cat > .gitlab-ci.yml << EOF
---
stages:
  - validate
  - test
  - deploy_dev
  - deploy_prod

variables:
  ANSIBLE_CONFIG: ansible/ansible.cfg

# Validação de sintaxe e lint
validate:
  stage: validate
  image: python:3.9-slim
  before_script:
    - apt-get update && apt-get install -y bind9utils
    - pip install ansible ansible-lint
  script:
    - ansible-playbook ansible/playbooks/site.yml --syntax-check
    - ansible-lint ansible/playbooks/site.yml
  tags:
    - docker

# Validação de zonas
validate_zones:
  stage: validate
  image: ubuntu:22.04
  before_script:
    - apt-get update && apt-get install -y bind9utils python3-pip
    - pip3 install ansible jinja2
  script:
    - mkdir -p /tmp/zones
    - for template in ansible/roles/bind_primary/templates/zones/*.zone.j2; do
        zone_file="/tmp/zones/\$(basename \$template .j2)";
        ansible-playbook -i localhost, -c local ansible/playbooks/generate_zone.yml -e "template=\$template output=\$zone_file" --skip-tags=all;
        named-checkzone \$(basename \$zone_file .zone) \$zone_file;
      done
  tags:
    - docker

# Testes em ambiente de desenvolvimento
test_dev:
  stage: test
  before_script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval \$(ssh-agent -s)
    - echo "\$SSH_PRIVATE_KEY" | tr -d '\\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo -e "Host *\\n\\tStrictHostKeyChecking no\\n\\n" > ~/.ssh/config
  script:
    - ansible-playbook -i ansible/inventory/development/hosts.yml ansible/playbooks/site.yml --check
  environment:
    name: development
  tags:
    - shell
  only:
    - master
    - development

# Implantação em ambiente de desenvolvimento
deploy_dev:
  stage: deploy_dev
  before_script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval \$(ssh-agent -s)
    - echo "\$SSH_PRIVATE_KEY" | tr -d '\\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo -e "Host *\\n\\tStrictHostKeyChecking no\\n\\n" > ~/.ssh/config
  script:
    - ansible-playbook -i ansible/inventory/development/hosts.yml ansible/playbooks/site.yml
  environment:
    name: development
  tags:
    - shell
  only:
    - master
    - development

# Implantação em ambiente de produção
deploy_prod:
  stage: deploy_prod
  before_script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval \$(ssh-agent -s)
    - echo "\$SSH_PRIVATE_KEY" | tr -d '\\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo -e "Host *\\n\\tStrictHostKeyChecking no\\n\\n" > ~/.ssh/config
  script:
    - ansible-playbook -i ansible/inventory/production/hosts.yml ansible/playbooks/site.yml
  environment:
    name: production
  when: manual
  tags:
    - shell
  only:
    - master
EOF

Configuração de Variáveis no GitLab

No GitLab, configure as seguintes variáveis:

  1. Acesse Settings > CI/CD > Variables
  2. Adicione as seguintes variáveis:
    • SSH_PRIVATE_KEY: Chave privada SSH para acessar os servidores
    • ANSIBLE_VAULT_PASSWORD: Senha para descriptografar o Ansible Vault

Configuração de Runners

Para executar os jobs do pipeline, você precisa configurar GitLab Runners:

  1. Acesse Settings > CI/CD > Runners
  2. Siga as instruções para instalar e registrar um runner
  3. Configure tags apropriadas para os runners (docker, shell)

Parte 4: Fluxo de Trabalho Completo

Agora que temos toda a estrutura configurada, vamos explorar o fluxo de trabalho completo para gerenciar a infraestrutura DNS.

Fluxo para Alterações em Zonas

  1. Clonar o repositório:
    1
    2
    
    git clone git@gitlab.com:seu-usuario/dns-infrastructure.git
    cd dns-infrastructure
    
  2. Criar uma branch para a alteração:
    1
    
    git checkout -b feature/add-new-record
    
  3. Editar o template de zona:
    1
    2
    3
    4
    5
    
    # Editar o template da zona
    vi ansible/roles/bind_primary/templates/zones/example.com.zone.j2
       
    # Adicionar um novo registro
    # app     IN A    192.168.200.100
    
  4. Testar localmente:
    1
    2
    3
    4
    5
    6
    7
    
    # Verificar sintaxe
    ansible-playbook ansible/playbooks/site.yml --syntax-check
       
    # Gerar e verificar zona
    mkdir -p /tmp/zones
    ansible-playbook -i localhost, -c local ansible/playbooks/generate_zone.yml -e "template=ansible/roles/bind_primary/templates/zones/example.com.zone.j2 output=/tmp/zones/example.com.zone" --skip-tags=all
    named-checkzone example.com /tmp/zones/example.com.zone
    
  5. Commit e push:
    1
    2
    3
    
    git add ansible/roles/bind_primary/templates/zones/example.com.zone.j2
    git commit -m "Adicionar registro app.example.com"
    git push origin feature/add-new-record
    
  6. Criar Merge Request:
    • Acesse o GitLab e crie um Merge Request
    • Atribua revisores
    • Aguarde a execução do pipeline
  7. Revisão e aprovação:
    • Revisores verificam as alterações
    • Pipeline de CI/CD valida a sintaxe e as zonas
    • Aprovação do Merge Request
  8. Merge para master:
    • Merge da branch para master
    • Pipeline de CI/CD é acionado automaticamente
    • Implantação automática em desenvolvimento
    • Aprovação manual para produção

Fluxo para Alterações em Configuração

  1. Clonar o repositório:
    1
    2
    
    git clone git@gitlab.com:seu-usuario/dns-infrastructure.git
    cd dns-infrastructure
    
  2. Criar uma branch para a alteração:
    1
    
    git checkout -b feature/update-logging
    
  3. Editar a configuração:
    1
    2
    3
    4
    5
    6
    
    # Editar as variáveis de grupo
    vi ansible/inventory/production/group_vars/all.yml
       
    # Atualizar configuração de logging
    # dns_log_versions: 10
    # dns_log_size: "50m"
    
  4. Testar localmente:
    1
    2
    
    # Verificar sintaxe
    ansible-playbook ansible/playbooks/site.yml --syntax-check
    
  5. Commit e push:
    1
    2
    3
    
    git add ansible/inventory/production/group_vars/all.yml
    git commit -m "Aumentar retenção de logs"
    git push origin feature/update-logging
    
  6. Seguir o mesmo processo de Merge Request e aprovação

Fluxo para Adição de Novo Servidor

  1. Clonar o repositório:
    1
    2
    
    git clone git@gitlab.com:seu-usuario/dns-infrastructure.git
    cd dns-infrastructure
    
  2. Criar uma branch para a alteração:
    1
    
    git checkout -b feature/add-new-server
    
  3. Atualizar o inventário:
    1
    2
    3
    4
    5
    6
    
    # Editar o inventário
    vi ansible/inventory/production/hosts.yml
       
    # Adicionar novo servidor
    # ns3.example.com:
    #   ansible_host: 192.168.200.53
    
  4. Atualizar templates e configurações conforme necessário

  5. Testar localmente:
    1
    2
    
    # Verificar sintaxe
    ansible-playbook ansible/playbooks/site.yml --syntax-check
    
  6. Commit e push:
    1
    2
    3
    
    git add ansible/inventory/production/hosts.yml
    git commit -m "Adicionar novo servidor DNS secundário"
    git push origin feature/add-new-server
    
  7. Seguir o mesmo processo de Merge Request e aprovação

Parte 5: Melhores Práticas e Considerações Avançadas

Segurança

Gerenciamento de Segredos

  • Ansible Vault: Use para criptografar variáveis sensíveis
    1
    2
    3
    4
    5
    6
    7
    8
    
    # Criptografar arquivo
    ansible-vault encrypt ansible/inventory/production/group_vars/all/vault.yml
      
    # Editar arquivo criptografado
    ansible-vault edit ansible/inventory/production/group_vars/all/vault.yml
      
    # Executar playbook com arquivo criptografado
    ansible-playbook -i ansible/inventory/production/hosts.yml ansible/playbooks/site.yml --ask-vault-pass
    
  • GitLab CI/CD Variables: Use para armazenar segredos no GitLab
    • Defina variáveis como protegidas e mascaradas
    • Limite o escopo a branches específicas

Controle de Acesso

  • Permissões do GitLab: Configure permissões adequadas para o repositório
    • Desenvolvedores: Podem criar branches e MRs
    • Maintainers: Podem aprovar MRs
    • Owners: Podem gerenciar configurações do repositório
  • Proteção de Branches: Configure proteção para branches importantes
    • Exija aprovação para merge em master
    • Exija pipeline de CI/CD bem-sucedido

Monitoramento e Alertas

Integração com Sistemas de Monitoramento

  • Prometheus e Grafana: Configure para monitorar servidores DNS ```yaml

    Exemplo de configuração do Prometheus

    • job_name: ‘bind_exporter’ static_configs:
      • targets: [‘ns1.example.com:9119’, ‘ns2.example.com:9119’] ```
  • Alertas: Configure alertas para problemas críticos ```yaml

    Exemplo de regra de alerta

    groups:

    • name: dns_alerts rules:
      • alert: DNSServerDown expr: up{job=”bind_exporter”} == 0 for: 5m labels: severity: critical annotations: summary: “DNS server down” description: “DNS server {{ $labels.instance }} has been down for more than 5 minutes.” ```

Integração com Sistemas de Tickets

  • GitLab Issues: Use para rastrear problemas e melhorias
    • Crie templates para diferentes tipos de issues
    • Vincule issues a MRs
  • Webhooks: Configure webhooks para integração com sistemas externos
    1
    2
    3
    4
    
    # Exemplo de configuração de webhook no GitLab
    # Settings > Webhooks
    # URL: https://jira.example.com/webhook
    # Trigger: Merge requests, Pipeline events
    

Automação Avançada

Automação de Rotinas

  • Renovação de DNSSEC: Automatize a renovação de chaves DNSSEC
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    # Criar script para renovação
    cat > scripts/renew_dnssec.sh << EOF
    #!/bin/bash
    # Script para renovação de chaves DNSSEC
      
    # Gerar novas chaves
    dnssec-keygen -a ECDSAP256SHA256 -b 256 -n ZONE example.com
    dnssec-keygen -a ECDSAP256SHA256 -b 256 -f KSK -n ZONE example.com
      
    # Atualizar zona
    dnssec-signzone -A -3 $(head -c 16 /dev/random | od -v -t x | head -1 | cut -d' ' -f2- | tr -d ' ') -N INCREMENT -o example.com -t /var/named/zones/example.com.zone
      
    # Recarregar BIND
    rndc reload example.com
    EOF
    chmod +x scripts/renew_dnssec.sh
      
    # Adicionar ao crontab
    echo "0 0 1 * * /path/to/scripts/renew_dnssec.sh" | sudo tee -a /etc/crontab
    
  • Backup Automatizado: Configure backups automáticos
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    # Criar script de backup
    cat > scripts/backup_dns.sh << EOF
    #!/bin/bash
    # Script para backup de configuração DNS
      
    BACKUP_DIR="/backup/dns"
    DATE=$(date +%Y%m%d)
      
    # Criar diretório de backup
    mkdir -p $BACKUP_DIR
      
    # Backup de configuração
    tar -czf $BACKUP_DIR/dns_config_$DATE.tar.gz /etc/named* /var/named/zones
      
    # Manter apenas os últimos 30 backups
    find $BACKUP_DIR -name "dns_config_*.tar.gz" -type f -mtime +30 -delete
    EOF
    chmod +x scripts/backup_dns.sh
      
    # Adicionar ao crontab
    echo "0 2 * * * /path/to/scripts/backup_dns.sh" | sudo tee -a /etc/crontab
    

Integração com Outros Sistemas

  • DHCP: Integre com servidores DHCP para atualizações dinâmicas
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    # Configuração no named.conf
    zone "example.com" IN {
        type master;
        file "zones/example.com.zone";
        allow-update { key dhcp-key; };
    };
      
    # Configuração no dhcpd.conf
    key dhcp-key {
        algorithm hmac-sha256;
        secret "a2xhc2Rqa2xhanNka2xqYWtsc2RqYWtsc2RqbGFrc2o=";
    };
      
    zone example.com. {
        primary 192.168.200.49;
        key dhcp-key;
    }
    
  • IPAM: Integre com sistemas de gerenciamento de endereços IP
    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
    
    # Exemplo de script para sincronização com IPAM
    import requests
    import json
      
    # Obter dados do IPAM
    response = requests.get('https://ipam.example.com/api/subnets/1/addresses', 
                           headers={'Authorization': 'Token abc123'})
    addresses = response.json()
      
    # Gerar arquivo de zona
    with open('example.com.zone', 'w') as f:
        f.write('$TTL 86400\n')
        f.write('@       IN SOA  ns1.example.com. admin.example.com. (\n')
        f.write('                    2025052501  ; Serial\n')
        f.write('                    10800       ; Refresh\n')
        f.write('                    3600        ; Retry\n')
        f.write('                    604800      ; Expire\n')
        f.write('                    86400 )     ; Minimum TTL\n\n')
          
        f.write('        IN NS   ns1.example.com.\n')
        f.write('        IN NS   ns2.example.com.\n\n')
          
        for address in addresses:
            if 'hostname' in address and address['hostname']:
                f.write(f"{address['hostname']}     IN A    {address['ip']}\n")
    

Documentação e Conhecimento

Documentação Automatizada

  • README.md: Mantenha um README detalhado e atualizado
    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
    
    # Exemplo de README.md
    cat > README.md << EOF
    # Infraestrutura DNS
      
    Este repositório contém a configuração da infraestrutura DNS gerenciada com Ansible e GitLab CI/CD.
      
    ## Estrutura
      
    - \`ansible/\`: Configuração Ansible
      - \`inventory/\`: Inventários para diferentes ambientes
      - \`roles/\`: Roles Ansible
      - \`playbooks/\`: Playbooks Ansible
    - \`zones/\`: Arquivos de zona
    - \`tests/\`: Scripts de teste
      
    ## Fluxo de Trabalho
      
    1. Clone o repositório
    2. Crie uma branch para sua alteração
    3. Faça as alterações necessárias
    4. Teste localmente
    5. Commit e push
    6. Crie um Merge Request
    7. Aguarde revisão e aprovação
      
    ## Ambientes
      
    - **Desenvolvimento**: Implantação automática após merge
    - **Produção**: Implantação manual após aprovação
      
    ## Contato
      
    Para dúvidas ou problemas, entre em contato com a equipe de infraestrutura.
    EOF
    
  • Wiki do GitLab: Use para documentação mais detalhada
    • Crie páginas para procedimentos comuns
    • Documente arquitetura e decisões de design
    • Mantenha um registro de alterações importantes

Treinamento e Onboarding

  • Guias de Onboarding: Crie guias para novos membros da equipe
    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
    
    # Exemplo de guia de onboarding
    cat > docs/onboarding.md << EOF
    # Guia de Onboarding para Infraestrutura DNS
      
    Este guia ajudará novos membros da equipe a começar a trabalhar com a infraestrutura DNS.
      
    ## Pré-requisitos
      
    - Acesso ao GitLab
    - Conhecimento básico de Git
    - Conhecimento básico de Ansible
    - Conhecimento básico de DNS
      
    ## Primeiros Passos
      
    1. Clone o repositório
    2. Configure seu ambiente local
    3. Familiarize-se com a estrutura do projeto
    4. Faça uma alteração simples para praticar o fluxo de trabalho
      
    ## Recursos
      
    - [Documentação do BIND](https://www.isc.org/bind/)
    - [Documentação do Ansible](https://docs.ansible.com/)
    - [Documentação do GitLab CI/CD](https://docs.gitlab.com/ee/ci/)
      
    ## Contato
      
    Para dúvidas ou problemas, entre em contato com a equipe de infraestrutura.
    EOF
    

Conclusão

Neste tutorial, exploramos como automatizar completamente a infraestrutura DNS utilizando Ansible para gerenciamento de configuração e GitLab CI/CD para implementar pipelines de entrega contínua. Abordamos desde a estruturação do projeto Ansible até a configuração de pipelines de CI/CD, incluindo validação, testes e implantação controlada.

A combinação de Ansible e GitLab CI/CD oferece uma solução robusta para gerenciar infraestrutura DNS como código, seguindo as melhores práticas de DevOps e IaC. Com esta abordagem, você pode:

  • Garantir consistência e padronização em toda a infraestrutura DNS
  • Implementar alterações de forma rápida e confiável
  • Validar e testar alterações antes da implantação
  • Manter um histórico completo de alterações
  • Colaborar efetivamente com outros membros da equipe

Ao adotar estas práticas, você estará preparado para gerenciar infraestruturas DNS complexas e de grande escala, com maior eficiência, segurança e confiabilidade.

Referências e Recursos Adicionais

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