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 automação com Ansible e GitLab CI/CD. Agora, vamos explorar Containerização de Servidores DNS BIND.
Benefícios da Containerização para Servidores DNS
Antes de mergulharmos nos detalhes técnicos, vamos entender os benefícios específicos da containerização para servidores DNS:
- Isolamento: Cada instância do BIND opera em seu próprio ambiente isolado
- Consistência: Garante que o ambiente de execução seja idêntico em todos os ambientes
- Portabilidade: Facilita a migração entre diferentes infraestruturas
- Escalabilidade: Permite escalar horizontalmente com facilidade
- Implantação rápida: Reduz o tempo de provisionamento de novos servidores
- Rollback simplificado: Facilita o retorno a versões anteriores em caso de problemas
- Eficiência de recursos: Utiliza recursos de forma mais eficiente que máquinas virtuais tradicionais
+-----------------------------------+ +-----------------------------------+
| Servidor Tradicional (VM ou Bare Metal) | | Servidor Containerizado (Docker) |
+-----------------------------------+ +-----------------------------------+
| Sistema Operacional Host | | Sistema Operacional Host |
|-----------------------------------| |-----------------------------------|
| Bibliotecas e Dependências | | Engine Docker |
|-----------------------------------| |-----------------------------------|
| Processo BIND (named) | | Contêiner BIND |
| | | |-- Bibliotecas Isoladas |
| | | |-- Processo BIND (named) |
| | | `-- Configuração Isolada |
+-----------------------------------+ +-----------------------------------+
| Vantagens: | | Vantagens: |
| - Familiaridade | | - Isolamento, Portabilidade |
| - Acesso direto ao hardware | | - Consistência, Escalabilidade |
| Desvantagens: | | - Implantação Rápida, Eficiência |
| - Conflitos de dependência | | Desvantagens: |
| - Provisionamento lento | | - Curva de aprendizado |
| - Menos portável | | - Overhead da engine Docker |
+-----------------------------------+ +-----------------------------------+
Figura 1: Comparação entre Servidor DNS Tradicional e Containerizado
Abordagens para Containerização de DNS
Abordagens para Containerização de DNS
Existem várias abordagens para containerizar servidores DNS:
- Contêiner único: Uma instância do BIND por contêiner
- Sidecar: Contêiner BIND acompanhado de contêineres auxiliares (ex: exportador de métricas)
- Orquestração: Gerenciamento de múltiplos contêineres DNS com Kubernetes ou Docker Swarm
Neste tutorial, focaremos principalmente na abordagem de contêiner único, mas também abordaremos aspectos de orquestração para ambientes mais complexos.
Criação de Imagem Docker para BIND
Vamos começar criando uma imagem Docker personalizada para o BIND.
Estrutura de Diretórios
1
2
3
4
5
6
7
8
9
10
11
12
| docker/
├── bind/
│ ├── Dockerfile
│ ├── config/
│ │ ├── named.conf
│ │ └── rndc.key
│ ├── zones/
│ │ ├── example.com.zone
│ │ └── 200.168.192.in-addr.arpa.zone
│ └── scripts/
│ ├── entrypoint.sh
│ └── healthcheck.sh
|
Dockerfile
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
| # Dockerfile para BIND DNS Server
FROM oraclelinux:9
# Instalar BIND e utilitários
RUN dnf -y install bind bind-utils bind-dnssec-utils && \
dnf clean all && \
mkdir -p /var/named/zones /var/named/data /var/named/dynamic /var/log/named && \
chown -R named:named /var/named /var/log/named
# Copiar arquivos de configuração
COPY config/named.conf /etc/named.conf
COPY config/rndc.key /etc/rndc.key
COPY zones/ /var/named/zones/
# Configurar permissões
RUN chown root:named /etc/named.conf /etc/rndc.key && \
chmod 640 /etc/named.conf /etc/rndc.key && \
chown named:named /var/named/zones/* && \
chmod 640 /var/named/zones/*
# Copiar scripts
COPY scripts/entrypoint.sh /entrypoint.sh
COPY scripts/healthcheck.sh /healthcheck.sh
RUN chmod +x /entrypoint.sh /healthcheck.sh
# Expor portas
EXPOSE 53/tcp 53/udp 953/tcp
# Configurar healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD ["/healthcheck.sh"]
# Definir usuário
USER named
# Definir entrypoint
ENTRYPOINT ["/entrypoint.sh"]
CMD ["named", "-g", "-c", "/etc/named.conf"]
|
Script de Entrypoint
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
| #!/bin/bash
# entrypoint.sh - Script de inicialização para contêiner BIND
# Verificar arquivos de configuração
echo "Verificando configuração do BIND..."
named-checkconf /etc/named.conf
if [ $? -ne 0 ]; then
echo "Erro na configuração do BIND. Verifique o arquivo named.conf."
exit 1
fi
# Verificar arquivos de zona
for zone_file in /var/named/zones/*.zone; do
if [ -f "$zone_file" ]; then
zone_name=$(basename "$zone_file" .zone)
echo "Verificando zona $zone_name..."
named-checkzone "$zone_name" "$zone_file"
if [ $? -ne 0 ]; then
echo "Erro na zona $zone_name. Verifique o arquivo $zone_file."
exit 1
fi
fi
done
# Executar comando fornecido
echo "Iniciando BIND..."
exec "$@"
|
Script de Healthcheck
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
| #!/bin/bash
# healthcheck.sh - Script para verificar a saúde do servidor BIND
# Verificar se o processo named está em execução
pgrep named > /dev/null
if [ $? -ne 0 ]; then
echo "Processo named não está em execução."
exit 1
fi
# Verificar se o servidor está respondendo a consultas
dig @127.0.0.1 localhost > /dev/null
if [ $? -ne 0 ]; then
echo "Servidor DNS não está respondendo a consultas."
exit 1
fi
# Verificar se o servidor está respondendo a consultas de zonas configuradas
if [ -f /var/named/zones/example.com.zone ]; then
dig @127.0.0.1 example.com SOA +short > /dev/null
if [ $? -ne 0 ]; then
echo "Servidor DNS não está respondendo a consultas para example.com."
exit 1
fi
fi
# Tudo OK
exit 0
|
Arquivo de Configuração Named.conf
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
| // named.conf para contêiner Docker
options {
directory "/var/named";
listen-on port 53 { any; };
listen-on-v6 port 53 { ::1; };
allow-query { any; };
recursion no;
dnssec-validation auto;
pid-file "/var/run/named/named.pid";
// Logging configuration
logging {
channel general_log {
file "/var/log/named/general.log" versions 3 size 5m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
channel security_log {
file "/var/log/named/security.log" versions 3 size 5m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
category default { general_log; };
category general { general_log; };
category security { security_log; };
};
};
// Zone definitions
zone "example.com" IN {
type master;
file "zones/example.com.zone";
allow-transfer { none; };
};
zone "200.168.192.in-addr.arpa" IN {
type master;
file "zones/200.168.192.in-addr.arpa.zone";
allow-transfer { none; };
};
// Include standard zones
include "/etc/named.rfc1912.zones";
// Include DNSSEC root key
include "/etc/named.root.key";
|
Construção e Execução da Imagem Docker
1
2
3
4
5
6
7
8
9
10
11
| # Construir a imagem
docker build -t bind-dns:latest docker/bind/
# Executar o contêiner
docker run -d --name bind-dns \
-p 53:53/tcp \
-p 53:53/udp \
-v $(pwd)/zones:/var/named/zones \
-v $(pwd)/logs:/var/log/named \
--restart=unless-stopped \
bind-dns:latest
|
Configuração de Contêiner Secundário
Para configurar um servidor DNS secundário em contêiner, precisamos ajustar a 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
| // named.conf para contêiner secundário
options {
directory "/var/named";
listen-on port 53 { any; };
listen-on-v6 port 53 { ::1; };
allow-query { any; };
recursion no;
dnssec-validation auto;
pid-file "/var/run/named/named.pid";
// Logging configuration
logging {
channel general_log {
file "/var/log/named/general.log" versions 3 size 5m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
category default { general_log; };
};
};
// TSIG key
key "xfer-key" {
algorithm hmac-sha256;
secret "a2xhc2Rqa2xhanNka2xqYWtsc2RqYWtsc2RqbGFrc2o=";
};
server 192.168.200.49 {
keys { xfer-key; };
};
// Zone definitions
zone "example.com" IN {
type slave;
masters { 192.168.200.49; };
file "slaves/example.com.zone";
};
zone "200.168.192.in-addr.arpa" IN {
type slave;
masters { 192.168.200.49; };
file "slaves/200.168.192.in-addr.arpa.zone";
};
// Include standard zones
include "/etc/named.rfc1912.zones";
// Include DNSSEC root key
include "/etc/named.root.key";
|
Docker Compose para Múltiplos Servidores DNS
Para ambientes que requerem múltiplos servidores DNS, podemos usar 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
42
43
44
45
46
47
48
49
50
| # docker-compose.yml
version: '3.8'
services:
bind-primary:
build:
context: ./docker/bind
dockerfile: Dockerfile
image: bind-dns:latest
container_name: bind-primary
volumes:
- ./config/primary/named.conf:/etc/named.conf:ro
- ./config/primary/rndc.key:/etc/rndc.key:ro
- ./zones:/var/named/zones:ro
- ./logs/primary:/var/log/named
ports:
- "53:53/tcp"
- "53:53/udp"
networks:
dns_network:
ipv4_address: 172.20.0.2
restart: unless-stopped
bind-secondary:
build:
context: ./docker/bind
dockerfile: Dockerfile
image: bind-dns:latest
container_name: bind-secondary
volumes:
- ./config/secondary/named.conf:/etc/named.conf:ro
- ./config/secondary/rndc.key:/etc/rndc.key:ro
- ./slaves:/var/named/slaves
- ./logs/secondary:/var/log/named
ports:
- "54:53/tcp"
- "54:53/udp"
networks:
dns_network:
ipv4_address: 172.20.0.3
depends_on:
- bind-primary
restart: unless-stopped
networks:
dns_network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24
|
Agora, vamos integrar nossa abordagem de containerização com Ansible para automatizar o gerenciamento dos contêineres.
Role Ansible para Gerenciamento de Contêineres 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
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
| # Criar estrutura da role
ansible-galaxy role init ansible/roles/bind_container
# Tarefas principais
cat > ansible/roles/bind_container/tasks/main.yml << EOF
---
- name: Instalar dependências
dnf:
name:
- docker
- python3-docker
state: present
- name: Iniciar e habilitar serviço Docker
systemd:
name: docker
state: started
enabled: yes
- name: Criar diretórios para BIND
file:
path: "{{ item }}"
state: directory
owner: "{{ bind_container_user }}"
group: "{{ bind_container_group }}"
mode: '0750'
loop:
- "{{ bind_container_config_dir }}"
- "{{ bind_container_zones_dir }}"
- "{{ bind_container_logs_dir }}"
- "{{ bind_container_slaves_dir }}"
- name: Copiar Dockerfile
template:
src: Dockerfile.j2
dest: "{{ bind_container_config_dir }}/Dockerfile"
owner: "{{ bind_container_user }}"
group: "{{ bind_container_group }}"
mode: '0640'
- name: Copiar scripts
template:
src: "{{ item.src }}"
dest: "{{ bind_container_config_dir }}/{{ item.dest }}"
owner: "{{ bind_container_user }}"
group: "{{ bind_container_group }}"
mode: '0750'
loop:
- { src: 'entrypoint.sh.j2', dest: 'entrypoint.sh' }
- { src: 'healthcheck.sh.j2', dest: 'healthcheck.sh' }
- name: Copiar configuração do BIND
template:
src: "named.conf.{{ bind_container_role }}.j2"
dest: "{{ bind_container_config_dir }}/named.conf"
owner: "{{ bind_container_user }}"
group: "{{ bind_container_group }}"
mode: '0640'
- name: Copiar arquivos de zona
template:
src: "zones/{{ item.name }}.zone.j2"
dest: "{{ bind_container_zones_dir }}/{{ item.name }}.zone"
owner: "{{ bind_container_user }}"
group: "{{ bind_container_group }}"
mode: '0640'
loop: "{{ bind_container_zones }}"
when: bind_container_role == 'primary' and item.type == 'master'
- name: Construir imagem Docker
docker_image:
name: "{{ bind_container_image_name }}"
tag: "{{ bind_container_image_tag }}"
build:
path: "{{ bind_container_config_dir }}"
pull: yes
source: build
state: present
- name: Executar contêiner BIND
docker_container:
name: "{{ bind_container_name }}"
image: "{{ bind_container_image_name }}:{{ bind_container_image_tag }}"
state: started
restart_policy: unless-stopped
ports:
- "{{ bind_container_port_tcp }}:53/tcp"
- "{{ bind_container_port_udp }}:53/udp"
volumes:
- "{{ bind_container_config_dir }}/named.conf:/etc/named.conf:ro"
- "{{ bind_container_zones_dir }}:/var/named/zones:{% if bind_container_role == 'primary' %}ro{% else %}rw{% endif %}"
- "{{ bind_container_logs_dir }}:/var/log/named:rw"
{% if bind_container_role == 'secondary' %}
- "{{ bind_container_slaves_dir }}:/var/named/slaves:rw"
{% endif %}
networks:
- name: "{{ bind_container_network }}"
ipv4_address: "{{ bind_container_ip }}"
EOF
# Variáveis padrão
cat > ansible/roles/bind_container/defaults/main.yml << EOF
---
# Configurações do contêiner
bind_container_user: "root"
bind_container_group: "root"
bind_container_role: "primary" # primary ou secondary
bind_container_name: "bind-{{ bind_container_role }}"
bind_container_image_name: "bind-dns"
bind_container_image_tag: "latest"
bind_container_port_tcp: 53
bind_container_port_udp: 53
bind_container_network: "dns_network"
bind_container_ip: "172.20.0.2" # Para primary, usar 172.20.0.3 para secondary
# Diretórios
bind_container_config_dir: "/opt/bind/config"
bind_container_zones_dir: "/opt/bind/zones"
bind_container_logs_dir: "/opt/bind/logs"
bind_container_slaves_dir: "/opt/bind/slaves"
# Zonas
bind_container_zones:
- name: "example.com"
type: "master"
- name: "200.168.192.in-addr.arpa"
type: "master"
# Configuração de rede
bind_container_allow_query:
- "localhost"
- "172.20.0.0/24"
- "192.168.200.0/24"
# Configuração de transferência
bind_container_use_tsig: true
bind_container_tsig_key_name: "xfer-key"
bind_container_tsig_key_algorithm: "hmac-sha256"
bind_container_tsig_key_secret: "a2xhc2Rqa2xhanNka2xqYWtsc2RqYWtsc2RqbGFrc2o="
# Configuração de secundário
bind_container_primary_server: "172.20.0.2" # IP do servidor primário
EOF
# Templates
mkdir -p ansible/roles/bind_container/templates/zones
# Template para Dockerfile
cat > ansible/roles/bind_container/templates/Dockerfile.j2 << EOF
# Dockerfile para BIND DNS Server
FROM oraclelinux:9
# Instalar BIND e utilitários
RUN dnf -y install bind bind-utils bind-dnssec-utils && \\
dnf clean all && \\
mkdir -p /var/named/zones /var/named/data /var/named/dynamic /var/named/slaves /var/log/named && \\
chown -R named:named /var/named /var/log/named
# Copiar scripts
COPY entrypoint.sh /entrypoint.sh
COPY healthcheck.sh /healthcheck.sh
RUN chmod +x /entrypoint.sh /healthcheck.sh
# Expor portas
EXPOSE 53/tcp 53/udp 953/tcp
# Configurar healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD ["/healthcheck.sh"]
# Definir usuário
USER named
# Definir entrypoint
ENTRYPOINT ["/entrypoint.sh"]
CMD ["named", "-g", "-c", "/etc/named.conf"]
EOF
# Template para entrypoint.sh
cat > ansible/roles/bind_container/templates/entrypoint.sh.j2 << EOF
#!/bin/bash
# entrypoint.sh - Script de inicialização para contêiner BIND
# Verificar arquivos de configuração
echo "Verificando configuração do BIND..."
named-checkconf /etc/named.conf
if [ \$? -ne 0 ]; then
echo "Erro na configuração do BIND. Verifique o arquivo named.conf."
exit 1
fi
# Verificar arquivos de zona
for zone_file in /var/named/zones/*.zone; do
if [ -f "\$zone_file" ]; then
zone_name=\$(basename "\$zone_file" .zone)
echo "Verificando zona \$zone_name..."
named-checkzone "\$zone_name" "\$zone_file"
if [ \$? -ne 0 ]; then
echo "Erro na zona \$zone_name. Verifique o arquivo \$zone_file."
exit 1
fi
fi
done
# Executar comando fornecido
echo "Iniciando BIND..."
exec "\$@"
EOF
# Template para healthcheck.sh
cat > ansible/roles/bind_container/templates/healthcheck.sh.j2 << EOF
#!/bin/bash
# healthcheck.sh - Script para verificar a saúde do servidor BIND
# Verificar se o processo named está em execução
pgrep named > /dev/null
if [ \$? -ne 0 ]; then
echo "Processo named não está em execução."
exit 1
fi
# Verificar se o servidor está respondendo a consultas
dig @127.0.0.1 localhost > /dev/null
if [ \$? -ne 0 ]; then
echo "Servidor DNS não está respondendo a consultas."
exit 1
fi
# Tudo OK
exit 0
EOF
# Template para named.conf primário
cat > ansible/roles/bind_container/templates/named.conf.primary.j2 << EOF
// named.conf para contêiner Docker - Servidor Primário
options {
directory "/var/named";
listen-on port 53 { any; };
listen-on-v6 port 53 { ::1; };
allow-query {
{% for net in bind_container_allow_query %}
{{ net }};
{% endfor %}
};
recursion no;
dnssec-validation auto;
pid-file "/var/run/named/named.pid";
// Logging configuration
logging {
channel general_log {
file "/var/log/named/general.log" versions 3 size 5m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
channel security_log {
file "/var/log/named/security.log" versions 3 size 5m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
category default { general_log; };
category general { general_log; };
category security { security_log; };
};
};
{% if bind_container_use_tsig %}
// TSIG key
key "{{ bind_container_tsig_key_name }}" {
algorithm {{ bind_container_tsig_key_algorithm }};
secret "{{ bind_container_tsig_key_secret }}";
};
{% endif %}
// Zone definitions
{% for zone in bind_container_zones %}
zone "{{ zone.name }}" IN {
type master;
file "zones/{{ zone.name }}.zone";
{% if bind_container_use_tsig %}
allow-transfer { key {{ bind_container_tsig_key_name }}; };
{% else %}
allow-transfer { none; };
{% endif %}
notify yes;
};
{% endfor %}
// Include standard zones
include "/etc/named.rfc1912.zones";
// Include DNSSEC root key
include "/etc/named.root.key";
EOF
# Template para named.conf secundário
cat > ansible/roles/bind_container/templates/named.conf.secondary.j2 << EOF
// named.conf para contêiner Docker - Servidor Secundário
options {
directory "/var/named";
listen-on port 53 { any; };
listen-on-v6 port 53 { ::1; };
allow-query {
{% for net in bind_container_allow_query %}
{{ net }};
{% endfor %}
};
recursion no;
dnssec-validation auto;
pid-file "/var/run/named/named.pid";
// Logging configuration
logging {
channel general_log {
file "/var/log/named/general.log" versions 3 size 5m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
category default { general_log; };
};
};
{% if bind_container_use_tsig %}
// TSIG key
key "{{ bind_container_tsig_key_name }}" {
algorithm {{ bind_container_tsig_key_algorithm }};
secret "{{ bind_container_tsig_key_secret }}";
};
server {{ bind_container_primary_server }} {
keys { {{ bind_container_tsig_key_name }}; };
};
{% endif %}
// Zone definitions
{% for zone in bind_container_zones %}
zone "{{ zone.name }}" IN {
type slave;
masters { {{ bind_container_primary_server }}; };
file "slaves/{{ zone.name }}.zone";
};
{% endfor %}
// Include standard zones
include "/etc/named.rfc1912.zones";
// Include DNSSEC root key
include "/etc/named.root.key";
EOF
# Template para zona example.com
cat > ansible/roles/bind_container/templates/zones/example.com.zone.j2 << EOF
\$TTL {{ dns_ttl | default('86400') }}
@ IN SOA ns1.{{ dns_domain | default('example.com') }}. {{ dns_admin_email | default('admin.example.com') }}. (
{{ '%Y%m%d01' | strftime }} ; Serial
{{ dns_refresh | default('10800') }} ; Refresh
{{ dns_retry | default('3600') }} ; Retry
{{ dns_expire | default('604800') }} ; Expire
{{ dns_minimum | default('86400') }} ) ; Minimum TTL
IN NS ns1.{{ dns_domain | default('example.com') }}.
IN NS ns2.{{ dns_domain | default('example.com') }}.
ns1 IN A {{ bind_primary_ip | default('192.168.200.49') }}
ns2 IN A {{ bind_secondary_ip | default('192.168.200.50') }}
www IN A {{ bind_primary_ip | default('192.168.200.49') }}
mail IN A {{ bind_primary_ip | default('192.168.200.49') }}
@ IN MX 10 mail.{{ dns_domain | default('example.com') }}.
EOF
# Template para zona reversa
cat > ansible/roles/bind_container/templates/zones/200.168.192.in-addr.arpa.zone.j2 << EOF
\$TTL {{ dns_ttl | default('86400') }}
@ IN SOA ns1.{{ dns_domain | default('example.com') }}. {{ dns_admin_email | default('admin.example.com') }}. (
{{ '%Y%m%d01' | strftime }} ; Serial
{{ dns_refresh | default('10800') }} ; Refresh
{{ dns_retry | default('3600') }} ; Retry
{{ dns_expire | default('604800') }} ; Expire
{{ dns_minimum | default('86400') }} ) ; Minimum TTL
IN NS ns1.{{ dns_domain | default('example.com') }}.
IN NS ns2.{{ dns_domain | default('example.com') }}.
; Registros PTR
49 IN PTR ns1.{{ dns_domain | default('example.com') }}.
50 IN PTR ns2.{{ dns_domain | default('example.com') }}.
EOF
|
Playbook para Contêineres 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
| # ansible/playbooks/container.yml
cat > ansible/playbooks/container.yml << EOF
---
- name: Configurar servidores DNS primários em contêineres
hosts: primary
become: yes
vars:
bind_container_role: "primary"
bind_container_port_tcp: 53
bind_container_port_udp: 53
bind_container_ip: "172.20.0.2"
roles:
- bind_container
- name: Configurar servidores DNS secundários em contêineres
hosts: secondary
become: yes
vars:
bind_container_role: "secondary"
bind_container_port_tcp: 53
bind_container_port_udp: 53
bind_container_ip: "172.20.0.3"
bind_container_primary_server: "172.20.0.2"
roles:
- bind_container
EOF
|
Agora, vamos integrar nossa abordagem de containerização com o pipeline GitLab CI/CD.
Atualização do .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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
| ---
stages:
- validate
- build
- test
- deploy_dev
- deploy_prod
variables:
ANSIBLE_CONFIG: ansible/ansible.cfg
DOCKER_IMAGE_NAME: bind-dns
DOCKER_REGISTRY: registry.example.com
# 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-playbook ansible/playbooks/container.yml --syntax-check
- ansible-lint ansible/playbooks/site.yml ansible/playbooks/container.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 ansible/roles/bind_container/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
# Construção da imagem Docker
build_docker:
stage: build
image: docker:20.10
services:
- docker:20.10-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $DOCKER_REGISTRY/$DOCKER_IMAGE_NAME:$CI_COMMIT_SHORT_SHA -t $DOCKER_REGISTRY/$DOCKER_IMAGE_NAME:latest docker/bind/
- docker push $DOCKER_REGISTRY/$DOCKER_IMAGE_NAME:$CI_COMMIT_SHORT_SHA
- docker push $DOCKER_REGISTRY/$DOCKER_IMAGE_NAME:latest
only:
- master
- development
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/container.yml --check -e "docker_image_tag=$CI_COMMIT_SHORT_SHA"
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/container.yml -e "docker_image_tag=$CI_COMMIT_SHORT_SHA"
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/container.yml -e "docker_image_tag=$CI_COMMIT_SHORT_SHA"
environment:
name: production
when: manual
tags:
- shell
only:
- master
|
Para ambientes mais complexos, podemos usar Kubernetes para orquestrar nossos contêineres DNS.
Manifesto Kubernetes para BIND
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
| # kubernetes/bind-deployment.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bind-config
data:
named.conf: |
// named.conf para Kubernetes
options {
directory "/var/named";
listen-on port 53 { any; };
listen-on-v6 port 53 { ::1; };
allow-query { any; };
recursion no;
dnssec-validation auto;
pid-file "/var/run/named/named.pid";
// Logging configuration
logging {
channel general_log {
file "/var/log/named/general.log" versions 3 size 5m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
category default { general_log; };
};
};
// Zone definitions
zone "example.com" IN {
type master;
file "zones/example.com.zone";
allow-transfer { none; };
};
zone "200.168.192.in-addr.arpa" IN {
type master;
file "zones/200.168.192.in-addr.arpa.zone";
allow-transfer { none; };
};
// Include standard zones
include "/etc/named.rfc1912.zones";
// Include DNSSEC root key
include "/etc/named.root.key";
---
apiVersion: v1
kind: ConfigMap
metadata:
name: bind-zones
data:
example.com.zone: |
$TTL 86400
@ IN SOA ns1.example.com. admin.example.com. (
2025052501 ; Serial
10800 ; Refresh
3600 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL
IN NS ns1.example.com.
IN NS ns2.example.com.
ns1 IN A 192.168.200.49
ns2 IN A 192.168.200.50
www IN A 192.168.200.49
mail IN A 192.168.200.49
@ IN MX 10 mail.example.com.
200.168.192.in-addr.arpa.zone: |
$TTL 86400
@ IN SOA ns1.example.com. admin.example.com. (
2025052501 ; Serial
10800 ; Refresh
3600 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL
IN NS ns1.example.com.
IN NS ns2.example.com.
; Registros PTR
49 IN PTR ns1.example.com.
50 IN PTR ns2.example.com.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bind-dns
labels:
app: bind-dns
spec:
replicas: 2
selector:
matchLabels:
app: bind-dns
template:
metadata:
labels:
app: bind-dns
spec:
containers:
- name: bind
image: registry.example.com/bind-dns:latest
ports:
- containerPort: 53
protocol: TCP
name: dns-tcp
- containerPort: 53
protocol: UDP
name: dns-udp
volumeMounts:
- name: config
mountPath: /etc/named.conf
subPath: named.conf
- name: zones
mountPath: /var/named/zones
- name: logs
mountPath: /var/log/named
livenessProbe:
exec:
command:
- /healthcheck.sh
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- dig
- @127.0.0.1
- example.com
- SOA
- +short
initialDelaySeconds: 5
periodSeconds: 10
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"
volumes:
- name: config
configMap:
name: bind-config
- name: zones
configMap:
name: bind-zones
- name: logs
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: bind-dns
spec:
selector:
app: bind-dns
ports:
- name: dns-tcp
port: 53
protocol: TCP
targetPort: dns-tcp
- name: dns-udp
port: 53
protocol: UDP
targetPort: dns-udp
type: LoadBalancer
|
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
| # ansible/roles/bind_kubernetes/tasks/main.yml
cat > ansible/roles/bind_kubernetes/tasks/main.yml << EOF
---
- name: Instalar dependências
dnf:
name:
- kubectl
- python3-kubernetes
state: present
- name: Criar diretório para manifestos Kubernetes
file:
path: "{{ kubernetes_manifest_dir }}"
state: directory
owner: "{{ kubernetes_user }}"
group: "{{ kubernetes_group }}"
mode: '0750'
- name: Copiar manifesto Kubernetes
template:
src: bind-deployment.yaml.j2
dest: "{{ kubernetes_manifest_dir }}/bind-deployment.yaml"
owner: "{{ kubernetes_user }}"
group: "{{ kubernetes_group }}"
mode: '0640'
- name: Aplicar manifesto Kubernetes
k8s:
state: present
src: "{{ kubernetes_manifest_dir }}/bind-deployment.yaml"
kubeconfig: "{{ kubernetes_kubeconfig }}"
EOF
# ansible/roles/bind_kubernetes/defaults/main.yml
cat > ansible/roles/bind_kubernetes/defaults/main.yml << EOF
---
kubernetes_user: "root"
kubernetes_group: "root"
kubernetes_manifest_dir: "/opt/kubernetes/manifests"
kubernetes_kubeconfig: "/root/.kube/config"
kubernetes_namespace: "default"
kubernetes_replicas: 2
kubernetes_image: "registry.example.com/bind-dns:latest"
EOF
# ansible/roles/bind_kubernetes/templates/bind-deployment.yaml.j2
cat > ansible/roles/bind_kubernetes/templates/bind-deployment.yaml.j2 << EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: bind-config
namespace: {{ kubernetes_namespace }}
data:
named.conf: |
// named.conf para Kubernetes
options {
directory "/var/named";
listen-on port 53 { any; };
listen-on-v6 port 53 { ::1; };
allow-query { any; };
recursion no;
dnssec-validation auto;
pid-file "/var/run/named/named.pid";
// Logging configuration
logging {
channel general_log {
file "/var/log/named/general.log" versions 3 size 5m;
severity info;
print-time yes;
print-severity yes;
print-category yes;
};
category default { general_log; };
};
};
// Zone definitions
{% for zone in bind_container_zones %}
zone "{{ zone.name }}" IN {
type master;
file "zones/{{ zone.name }}.zone";
allow-transfer { none; };
};
{% endfor %}
// Include standard zones
include "/etc/named.rfc1912.zones";
// Include DNSSEC root key
include "/etc/named.root.key";
---
apiVersion: v1
kind: ConfigMap
metadata:
name: bind-zones
namespace: {{ kubernetes_namespace }}
data:
{% for zone in bind_container_zones %}
{{ zone.name }}.zone: |
{{ lookup('template', 'zones/' + zone.name + '.zone.j2') | indent(4, True) }}
{% endfor %}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bind-dns
namespace: {{ kubernetes_namespace }}
labels:
app: bind-dns
spec:
replicas: {{ kubernetes_replicas }}
selector:
matchLabels:
app: bind-dns
template:
metadata:
labels:
app: bind-dns
spec:
containers:
- name: bind
image: {{ kubernetes_image }}
ports:
- containerPort: 53
protocol: TCP
name: dns-tcp
- containerPort: 53
protocol: UDP
name: dns-udp
volumeMounts:
- name: config
mountPath: /etc/named.conf
subPath: named.conf
- name: zones
mountPath: /var/named/zones
- name: logs
mountPath: /var/log/named
livenessProbe:
exec:
command:
- /healthcheck.sh
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- dig
- @127.0.0.1
- example.com
- SOA
- +short
initialDelaySeconds: 5
periodSeconds: 10
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"
volumes:
- name: config
configMap:
name: bind-config
- name: zones
configMap:
name: bind-zones
- name: logs
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: bind-dns
namespace: {{ kubernetes_namespace }}
spec:
selector:
app: bind-dns
ports:
- name: dns-tcp
port: 53
protocol: TCP
targetPort: dns-tcp
- name: dns-udp
port: 53
protocol: UDP
targetPort: dns-udp
type: LoadBalancer
EOF
# ansible/playbooks/kubernetes.yml
cat > ansible/playbooks/kubernetes.yml << EOF
---
- name: Implantar BIND no Kubernetes
hosts: kubernetes_master
become: yes
roles:
- bind_kubernetes
EOF
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # Adição ao .gitlab-ci.yml
deploy_kubernetes:
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/kubernetes.yml -e "kubernetes_image=$DOCKER_REGISTRY/$DOCKER_IMAGE_NAME:$CI_COMMIT_SHORT_SHA"
environment:
name: kubernetes
when: manual
tags:
- shell
only:
- master
|
Monitoramento de Contêineres DNS
Para monitorar contêineres DNS, podemos usar ferramentas como Prometheus e Grafana.
Exportador de Métricas BIND para Prometheus
1
2
3
4
5
6
7
8
9
10
11
12
| # Dockerfile para BIND Exporter
FROM golang:1.18 as builder
RUN go install github.com/prometheus-community/bind_exporter@latest
FROM oraclelinux:9-slim
COPY --from=builder /go/bin/bind_exporter /usr/local/bin/bind_exporter
EXPOSE 9119
ENTRYPOINT ["/usr/local/bin/bind_exporter"]
|
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
| # docker-compose-monitoring.yml
version: '3.8'
services:
bind-primary:
build:
context: ./docker/bind
dockerfile: Dockerfile
image: bind-dns:latest
container_name: bind-primary
volumes:
- ./config/primary/named.conf:/etc/named.conf:ro
- ./config/primary/rndc.key:/etc/rndc.key:ro
- ./zones:/var/named/zones:ro
- ./logs/primary:/var/log/named
ports:
- "53:53/tcp"
- "53:53/udp"
networks:
dns_network:
ipv4_address: 172.20.0.2
restart: unless-stopped
bind-secondary:
build:
context: ./docker/bind
dockerfile: Dockerfile
image: bind-dns:latest
container_name: bind-secondary
volumes:
- ./config/secondary/named.conf:/etc/named.conf:ro
- ./config/secondary/rndc.key:/etc/rndc.key:ro
- ./slaves:/var/named/slaves
- ./logs/secondary:/var/log/named
ports:
- "54:53/tcp"
- "54:53/udp"
networks:
dns_network:
ipv4_address: 172.20.0.3
depends_on:
- bind-primary
restart: unless-stopped
bind-exporter-primary:
build:
context: ./docker/bind-exporter
dockerfile: Dockerfile
container_name: bind-exporter-primary
command: --bind.stats-url=http://bind-primary:8053/ --web.listen-address=:9119
ports:
- "9119:9119"
networks:
dns_network:
depends_on:
- bind-primary
restart: unless-stopped
bind-exporter-secondary:
build:
context: ./docker/bind-exporter
dockerfile: Dockerfile
container_name: bind-exporter-secondary
command: --bind.stats-url=http://bind-secondary:8053/ --web.listen-address=:9120
ports:
- "9120:9120"
networks:
dns_network:
depends_on:
- bind-secondary
restart: unless-stopped
prometheus:
image: prom/prometheus:latest
container_name: prometheus
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
ports:
- "9090:9090"
networks:
dns_network:
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: grafana
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
- ./grafana/dashboards:/var/lib/grafana/dashboards
ports:
- "3000:3000"
networks:
dns_network:
depends_on:
- prometheus
restart: unless-stopped
networks:
dns_network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24
volumes:
prometheus_data:
grafana_data:
|
Configuração do Prometheus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'bind_primary'
static_configs:
- targets: ['bind-exporter-primary:9119']
labels:
instance: 'bind-primary'
- job_name: 'bind_secondary'
static_configs:
- targets: ['bind-exporter-secondary:9120']
labels:
instance: 'bind-secondary'
|
Melhores Práticas para DNS Containerizado
Segurança
- Imagens mínimas: Use imagens base mínimas para reduzir a superfície de ataque
- Usuário não-root: Execute o BIND como usuário não-root dentro do contêiner
- Read-only filesystem: Monte volumes como somente leitura quando possível
- Secrets: Use ferramentas de gerenciamento de secrets (Docker Secrets, Kubernetes Secrets)
- Escaneamento de vulnerabilidades: Escaneie imagens regularmente com ferramentas como Trivy ou Clair
Desempenho
- Recursos adequados: Aloque CPU e memória adequados para os contêineres
- Cache otimizado: Configure o cache do BIND para o tamanho apropriado
- Volumes persistentes: Use volumes persistentes para dados que precisam sobreviver a reinicializações
- Rede otimizada: Configure a rede do contêiner para desempenho ideal
Alta Disponibilidade
- Múltiplas réplicas: Execute múltiplas instâncias do BIND
- Distribuição geográfica: Distribua contêineres em diferentes zonas/regiões
- Health checks: Implemente verificações de saúde robustas
- Atualizações sem downtime: Configure atualizações rolling para evitar interrupções
Monitoramento
- Métricas: Exporte métricas do BIND para Prometheus
- Logs centralizados: Envie logs para um sistema centralizado (ELK, Graylog)
- Alertas: Configure alertas para problemas críticos
- Dashboards: Crie dashboards no Grafana para visualização
Conclusão
A containerização de servidores DNS BIND, combinada com automação Ansible e pipelines GitLab CI/CD, oferece uma solução moderna, escalável e gerenciável para infraestrutura DNS. Esta abordagem permite:
- Implantação consistente: Garante que todos os ambientes sejam idênticos
- Ciclo de vida gerenciado: Facilita atualizações, rollbacks e manutenção
- Escalabilidade: Permite escalar horizontalmente conforme necessário
- Portabilidade: Funciona em qualquer ambiente que suporte contêineres
- Integração com DevOps: Alinha-se com práticas modernas de DevOps e IaC
Ao adotar esta abordagem, você estará preparado para gerenciar infraestruturas DNS complexas e de grande escala, com maior eficiência, segurança e confiabilidade, aproveitando ao máximo as tecnologias modernas de containerização e orquestração.
Referências e Recursos Adicionais