Ansible で BINDサーバを構築をしてみた
Ansibleとは
Ansibleは "構成管理ツール" と呼ばれ、サーバ構築、ネットワーク機器構築などを自動で行うことができます。 構成ツールには、Chef, Puppetなどもありますが、Ansible は記述が簡易的かつ、機器へのSSH設定なども記述する必要がない(エージェントレス)なので使いやすいと思います。複数のサーバ、ネットワーク機器の構築、設定変更も一度に行えるので、オペレーションコストを大きく削減できるとともに、人的要因な設定ミス等も防ぐことが可能なすげえ便利なやつです。
今回は、Ansibleを使って CentOS7 に BIND サーバ (named)の構築にチャレンジ。環境準備から Ansible Playbook の実行まで順にまとめてみました。
※ macbook (macOS 10.14.1) で実施。
(環境準備) Vagrantfileを用意
先ずは 以下の Vagrangfile で ansilbe vmを起動
1) vagrant, virtualboxのインストール
# Homebrew
brew cask install virtualbox vagrant
2) Vagrantfileを用意
root$ cat Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'net/http'
require 'uri'
Vagrant.configure("2") do |config|
config.vm.define "ansible" do |ansible|
$script = <<-'SCRIPT'
yum update
yum install epel-release ansible -y
yum install samba-client vim git python-pip -y
pip install pywinrm
pip install pyvmomi
pip install netaddr
# Install Ansible 2.4.1 as yum repo has 2.3
pip install git+git://github.com/ansible/ansible.git@stable-2.4
SCRIPT
ansible.vm.box = "bento/centos-7.2"
ansible.vm.provision "shell" , inline: $script
ansible.vm.hostname = "ansible"
ansible.vm.network "private_network", ip: "192.168.30.10"
ansible.vm.network "forwarded_port", guest: 80, host:8900
end
config.push.define "local-exec" do |push|
# Need to change NETCONF ...
push.inline = <<-"SCRIPT"
curl -u vagrant:vagrant http://localhost:2224/restconf/api/running/native/interface/GigabitEthernet/2 -X PUT -H 'Content-type: application/vnd.yang.data+json' --data @set_if.json
SCRIPT
end
end
3) Vagrant upで起動
最初は image を引っ張ってくるので時間がかかるので注意。
root$ vagrant up ansible
Bringing machine 'ansible' up with 'virtualbox' provider...
==> ansible: Checking if box 'bento/centos-7.2' is up to date...
==> ansible: Clearing any previously set forwarded ports...
==> ansible: Clearing any previously set network interfaces...
==> ansible: Preparing network interfaces based on configuration...
ansible: Adapter 1: nat
ansible: Adapter 2: hostonly
==> ansible: Forwarding ports...
ansible: 80 (guest) => 8900 (host) (adapter 1)
ansible: 22 (guest) => 2222 (host) (adapter 1)
==> ansible: Booting VM...
==> ansible: Waiting for machine to boot. This may take a few minutes...
ansible: SSH address: 127.0.0.1:2222
ansible: SSH username: vagrant
ansible: SSH auth method: private key
ansible: Warning: Connection reset. Retrying...
ansible: Warning: Remote connection disconnect. Retrying...
ansible: Warning: Connection reset. Retrying...
ansible: Warning: Remote connection disconnect. Retrying...
ansible: Warning: Connection reset. Retrying...
ansible: Warning: Remote connection disconnect. Retrying...
==> ansible: Machine booted and ready!
==> ansible: Checking for guest additions in VM...
ansible: The guest additions on this VM do not match the installed version of
ansible: VirtualBox! In most cases this is fine, but in rare cases it can
ansible: prevent things such as shared folders from working properly. If you see
ansible: shared folder errors, please make sure the guest additions within the
ansible: virtual machine match the version of VirtualBox you have installed on
ansible: your host and reload your VM.
ansible:
ansible: Guest Additions Version: 5.1.10
ansible: VirtualBox Version: 5.2
==> ansible: Setting hostname...
==> ansible: Configuring and enabling network interfaces...
ansible: SSH address: 127.0.0.1:2222
ansible: SSH username: vagrant
ansible: SSH auth method: private key
==> ansible: Rsyncing folder: /Users/git/ansible/workspace/ => /home/temp
==> ansible: Mounting shared folders...
ansible: /vagrant => /Users/git/ansible
==> ansible: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> ansible: flag to force provisioning. Provisioners marked to run always will still run.
root$
4) ansible vm に ssh 接続
以下のように ansible vm が up (running)することを確認。
root$ vagrant status ansible
Current machine states:
ansible running (virtualbox)
The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.
5) vagrant ssh ansible で 立ち上げた ansible vm に接続します。
root$ vagrant ssh ansible
Last login: Wed Mar 20 02:33:13 2019 from 10.0.2.2
[vagrant@ansible ~]$
Ansible Role Structure
ここから Ansible Playbookを作成していくのですが、Roleというモジュール構造で作っていきます。
この例では、bindというディレクトリ配下に tasks, handlers, templates, defaults, vars というサブディレクトリを配置してます。tasks の main.yml に作業プロセスを記述し、templates配下にはbind設定に必要な正引き、逆引きzone dbファイル、named.confファイル等を置いておきます。defaults配下には変数指定がない場合のデフォルト値を記述した main.yml、handlers配下にはtasks(main.yml)で notifyされるActionを記述、vars配下には 変数を指定した main.yml を配置しています。
.
├── bind
│ ├── defaults
│ │ └── main.yml
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ │ ├── db.forward.j2
│ │ ├── db.reverse.j2
│ │ ├── named.conf.j2
│ │ ├── named.conf.local.j2
│ │ └── resolv.j2
│ └── vars
│ └── main.yml
├── hosts
├── install_bind.yml
└── uninstall_bind.yml
tasks/main.yml
1つ1つの Role を作っていきます。先ずはtasks の設定です。この main.ymlには、bindをインストールしたり、ディレクトリを作成し、bindに必要なファイルの設定、Firewallの管理、などのainsilbeの根幹ともなる手順書を記述していきます。
[vagrant@ansible]$ cat bind/tasks/main.yml
#tasks file for Bind setup
- name: Set DNS Server to host1
lineinfile:
regexp: '8.8.8.8'
insertafter: '^#'
line: nameserver 8.8.8.8
path: /etc/resolv.conf
- name: Install bind
yum:
pkg: bind
state: installed
- name: Set hostname
hostname:
name: "{{ hostname }}.{{ domain }}"
- name: Set hostname fact
set_fact:
ansible_fqdn: "{{ hostname}}.{{domain }}"
- name: Copy named conf file
template:
src: named.conf.j2
dest: /etc/named.conf
owner: root
group: named
mode: 0660
notify: Start named
- name: Make named directory
file:
path: /etc/named
state: directory
owner: root
group: named
mode: 0750
- name: Copy named conf local file
template:
src: named.conf.local.j2
dest: /etc/named/named.conf.local
owner: root
group: named
mode: 0640
notify: Start named
- name: Make zones Directory
file:
path: /etc/named/zones
state: directory
owner: root
group: named
mode: 0750
- name: Copy forward file
template:
src: db.forward.j2
dest: /etc/named/zones/db.forward
owner: root
group: named
mode: 0640
notify: Start named
- name: Copy reverse file
template:
src: db.reverse.j2
dest: /etc/named/zones/db.reverse
owner: root
group: named
mode: 0640
notify: Start named
- name: check if firewalld is running
command: systemctl is-active firewalld
register: firewalld_result
changed_when: False
ignore_errors: True
- name: Open firewall port
firewalld:
service: dns
permanent: true
state: enabled
immediate: yes
when: firewalld_result.stdout == "active"
- name: resolv.conf replace
template:
src: resolv.j2
dest: /etc/resolv.conf
owner: root
mode: 0640
notify: Start named
defualts/main.yml
デフォルトの変数 (hosts file や vars/main.ymlで指定がない場合に使われる)
gather_fact で引っ張ってきた ansible_default_ipv4.network 変数をsplitで加工して reverse_domain subnet を設定しました。
---
# vars file for Bind setup
domain: example.com
hostname: "{{ ansible_hostname }}"
reverse_domain:
- reverse: "{{ ansible_default_ipv4.network.split('.')[0] }}.{{ ansible_default_ipv4.network.split('.')[1] }}.{{ ansible_default_ipv4.network.split('.')[2] }}.in-addr.arpa."
vars/main.yml
変数を設定していきます。reverse_domainとdns_record はリスト型で記述しています。
---
# ---- for bind server ---
#hostname の指定がない時は targetで既に設定されているhostnameを使用
hostname: ansible_test
#domain 必須パラメータ 指定がない場合は、example.com を使用
domain: example.com
# reverse_domain: x.x.x.in-addr.arpa. の記述 (zone毎にリストで記載)
# 指定がない時は、設定されている NICの subnetを用いて設定する
reverse_domain:
- reverse: 1.168.192.in-addr.arpa.
- reverse: 1.31.172.in-addr.arpa.
# dns_record: 正引き、逆引き用の DNS Record. 指定がない場合は、設定SKIP
# name/type/ipaddress は必須パラメータ
dns_record:
- name: aaa
type: A
ipaddress: 192.168.1.110
- name: bbb
type: A
ipaddress: 172.31.1.100
handlers/main.yml
tasks(main.yml)で notifyされるAction (ここでは Start named service )を設定
---
# handlers file for Bind setup
- name: Start named
service:
name: named
state: started
become: yes
templates/named.conf.j2
続いて templates 配下に named.conf.j2 を置きます。これは bindの conig fileである named.conf になります。
{{ ansible_default_ipv4.address }} は gather_facts で引っ張ってくる変数で、host の ip address が設定されます。Python の template ライブラリえある Jinja2 で書きます。
//
// named.conf
//
// Provided by Red Hat bind package to configure the ISC BIND named(8) DNS
// server as a caching only nameserver (as a localhost DNS resolver only).
//
// See /usr/share/doc/bind*/sample/ for example named configuration files.
//
include "/etc/rndc.key";
options {
listen-on port 53 { 127.0.0.1; {{ ansible_default_ipv4.address }}; };
# listen-on-v6 port 53 { ::1; };
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
memstatistics-file "/var/named/data/named_mem_stats.txt";
allow-query { any; };
/*
- If you are building an AUTHORITATIVE DNS server, do NOT enable recursion.
- If you are building a RECURSIVE (caching) DNS server, you need to enable
recursion.
- If your recursive DNS server has a public IP address, you MUST enable access
control to limit queries to your legitimate users. Failing to do so will
cause your server to become part of large scale DNS amplification
attacks. Implementing BCP38 within your network would greatly
reduce such attack surface
*/
recursion yes;
dnssec-enable yes;
dnssec-validation yes;
forwarders {
8.8.8.8;
};
/* Path to ISC DLV key */
bindkeys-file "/etc/named.iscdlv.key";
managed-keys-directory "/var/named/dynamic";
pid-file "/run/named/named.pid";
session-keyfile "/run/named/session.key";
};
logging {
channel default_debug {
file "data/named.run";
severity dynamic;
};
};
zone "." IN {
type hint;
file "named.ca";
};
include "/etc/named/named.conf.local";
templates/named.conf.local.j2
BINDサーバの応答設定と、zoneファイルのパス指定。Reverseドメインは複数設定ある場合のために {% for item in reverse_domain %} でloop構造に。
zone "{{ domain }}" IN {
type master;
file "/etc/named/zones/db.forward";
allow-update { key rndc-key; };
};
{% for item in reverse_domain %}
zone "{{ item.reverse }}" IN {
type master;
file "/etc/named/zones/db.reverse";
allow-update {key rndc-key; };
};
{% endfor %}
controls {
inet 127.0.0.1 port 953
allow { 127.0.0.1; } keys { "rndc-key"; };
};
templates/db.forward.j2
正引きzoneファイル
{% if dns_record is defined %} を定義して、dns_record 変数がない場合は Record追加はskip
$TTL 604800 ; 1 week
@ IN SOA {{ ansible_fqdn }}. admin.{{ domain }}. (
8 ; serial
604800 ; refresh (1 week)
86400 ; retry (1 day)
2419200 ; expire (4 weeks)
604800 ; minimum (1 weeks)
)
@ IN NS {{ ansible_fqdn }}.
{{hostname}} IN A {{ ansible_default_ipv4.address }}
{% if dns_record is defined %}
{% for item in dns_record %}
{{ item.name }} IN {{ item.type }} {{ item.ipaddress }}
{% endfor %}
{% endif %}
templates/db.reverse.j2
逆引きzoneファイル
正引きと同様に、dns_record 変数がない場合は Record追加はskip
$TTL 604800 ; 1 week
@ IN SOA {{ ansible_fqdn }}. admin.{{ domain }}. (
7 ; serial
604800 ; refresh (1 week)
86400 ; retry (1 day)
2419200 ; expire (4 weeks)
604800 ; minimum (1 week)
)
IN NS {{ ansible_fqdn }}.
{{ ansible_default_ipv4.address.split('.')[3] }} IN PTR {{ hostname }}.{{ ansible_default_ipv4.address.split('.')[2] }}.{{ ansible_default_ipv4.address.split('.')[1] }}.{{ ansible_default_ipv4.address.split('.')[0] }}.in-addr.arpa.
{% if dns_record is defined %}
{% for item in dns_record %}
{{ item.ipaddress.split('.')[3] }} IN PTR {{item.name}}.{{ item.ipaddress.split('.')[2] }}.{{item.ipaddress.split('.')[1]}}.{{item.ipaddress.split('.')[0]}}.in-addr.arpa.
{% endfor %}
{% endif %}
templates/db.resolv.j2
resolv.confの再定義
# Generated by NetworkManager
search example.com
nameserver 127.0.0.1
nameserver 8.8.8.8
hosts ファイルの作成
今回 Bind Serverを設定する対象 (centos 192.168.1.1)を指定して、root / passを指定する。
[vagrant@ansible]$ cat hosts
[centos]
192.168.1.1
[centos:vars]
ansible_user=root
ansible_password=p@ssw0rd
実行ファイルの作成 (install_bind.yml)
---
- name: Set up Bind
hosts: centos
gather_facts: true
become: true
roles:
- bind
Playbookを実行してみる
[vagrant@ansible install_bind]$ ansible-playbook -i hosts install_bind.yml
/usr/lib/python2.7/site-packages/requests/__init__.py:80: RequestsDependencyWarning: urllib3 (1.22) or chardet (2.2.1) doesn't match a supported version!
RequestsDependencyWarning)
PLAY [Set up Bind] ****************************************************************************************************
TASK [Gathering Facts] ************************************************************************************************
ok: [192.168.1.1]
TASK [bind : Set DNS Server to host1] *********************************************************************************
ok: [192.168.1.1]
TASK [bind : Install bind] ********************************************************************************************
changed: [192.168.1.1]
TASK [bind : Set hostname] ********************************************************************************************
ok: [192.168.1.1]
TASK [bind : Set hostname fact] ***************************************************************************************
ok: [192.168.1.1]
TASK [bind : Copy named conf file] ************************************************************************************
changed: [192.168.1.1]
TASK [bind : Make named directory] ************************************************************************************
ok: [192.168.1.1]
TASK [bind : Copy named conf local file] ******************************************************************************
changed: [192.168.1.1]
TASK [bind : Make zones Directory] ************************************************************************************
changed: [192.168.1.1]
TASK [bind : Copy forward file] ***************************************************************************************
changed: [192.168.1.1]
TASK [bind : Copy reverse file] ***************************************************************************************
changed: [192.168.1.1]
TASK [bind : check if firewalld is running] ***************************************************************************
ok: [192.168.1.1]
TASK [bind : Open firewall port] **************************************************************************************
ok: [192.168.1.1]
TASK [bind : resolv.conf replace] *************************************************************************************
ok: [192.168.1.1]
RUNNING HANDLER [bind : Start named] **********************************************************************************
changed: [192.168.1.1]
PLAY RECAP ************************************************************************************************************
192.168.1.1 : ok=15 changed=7 unreachable=0 failed=0
実行結果の確認
Target Node (Centos) 側で named service が activeになっているかを確認。
[root@ansible-test named]# systemctl status named
● named.service - Berkeley Internet Name Domain (DNS)
Loaded: loaded (/usr/lib/systemd/system/named.service; disabled; vendor preset: disabled)
Active: active (running) since 土 2019-03-23 22:38:42 UTC; 3min 37s ago
Process: 6240 ExecStart=/usr/sbin/named -u named -c ${NAMEDCONF} $OPTIONS (code=exited, status=0/SUCCESS)
Process: 6238 ExecStartPre=/bin/bash -c if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then /usr/sbin/named-checkconf -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi (code=exited, status=0/SUCCESS)
Main PID: 6242 (named)
CGroup: /system.slice/named.service
└─6242 /usr/sbin/named -u named -c /etc/named.conf
正引きzoneファイル
[root@ansible-test]# cat /etc/named/zones/db.forward
$TTL 604800 ; 1 week
@ IN SOA ansible-test.example.com. admin.example.com. (
8 ; serial
604800 ; refresh (1 week)
86400 ; retry (1 day)
2419200 ; expire (4 weeks)
604800 ; minimum (1 weeks)
)
@ IN NS ansible-test.example.com.
ansible-test IN A 193.168.1.1
aaa IN A 192.168.1.110
bbb IN A 172.31.1.100
逆引きzoneファイル
[root@ansible-test]# cat /etc/named/zones/db.reverse
$TTL 604800 ; 1 week
@ IN SOA ansible-test.example.com. admin.example.com. (
7 ; serial
604800 ; refresh (1 week)
86400 ; retry (1 day)
2419200 ; expire (4 weeks)
604800 ; minimum (1 week)
)
IN NS ansible-test.example.com.
162 IN PTR ansible-test.1.168.192.in-addr.arpa.
110 IN PTR aaa.1.168.192.in-addr.arpa.
100 IN PTR bbb.1.31.172.in-addr.arpa.
こんな感じで一応動きました。Playbookを作り込むのは結構工数が掛かりますが、一度作ってしまうと本当に楽です。楽すぎて、linuxや自動化対象の設定コマンドなどを忘れてしまうのがAnsibleのこわいところでしょうか(汗。
様々なシステム、テクノロジーが複雑化する中で、ヒューマン・エラーも最低限に抑え、シンプルだけどスケーラビリティが高い自動化テクノロジーは本当に重要ですね。「楽しよう」 という感覚をどんな作業に対しても持つことの大切さを改めて感じる今日このごろです。ではではまた。
今回の"note"を気に入って頂けましたら、是非サポートをお願いいたします!