Ansibleでサーバの構成管理を行う

本記事は[東京学芸大学 櫨山研究室 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/hazelab)の20日目の記事になります．

# はじめに

Ansibleは構成管理ツールの一つです．
本記事ではAnsibleを使ったインフラ構築の自動化について扱います．

# 構成管理ツールを導入するメリット

そもそも構成管理ツールの導入にどのようなメリットがあるのかを簡単にまとめておきます．

構成管理ツールが必要となった大きな背景としては**クラウドの登場があります．**
クラウドの登場により**ネットワークやストレージなどのインフラリソースの変更がソフトウェアを通して行えるようになり，インフラリソースのライフサイクルが短くかつ頻繁に変更されるようになりました．**

インフラリソースのライフサイクルが短くかつ頻繁に変更されるということは，システムの構築管理の変更頻度も増大します．変更頻度の多いクラウド環境のシステムの構築管理を手動で行うことは運用コストを肥大化させることになり非効率です．

そこで構成管理ツールの出番という訳です．

## オンプレミス環境で構成管理ツールを導入するメリット

ここまでだと構成管理ツールはクラウド環境のためのモノのような印象を受けるかもしれません，しかしオンプレミス環境でも構成管理ツールを導入することで多くのメリットを享受することができます．
ここでは特に研究室のサーバ管理活動で構成管理ツールを導入するメリットを載せておきます．

所属研究室では大学の講義で開発するWebアプリケーションのデプロイ環境や研究活動のために物理サーバを購入して管理しています．

**まず前提として研究室のサーバは壊れます．**
今年は2台がお陀仏になりました(泣)

壊れるたびに既存のインフラ環境を整えることは結構しんどいですし，動作していたシステムにはダウンタイムが発生します．
これらのことはなるべく早く対処したいです．

構成管理ツールを導入することによって**頻繁にシステムの構築と破棄を繰り返しても同じクオリティの環境を提供できます．**
なので機器が購入してからの環境の復帰が高速にできます．

もう一つのメリットとしては**Infrastructure as Code (IoC)の仕組みを導入できることです．**
Infrastructure as Codeの定義は以下の通りです．

> インフラの構成管理をコードで記述し，ソフトウェア開発で実施されてきた開発プロセスをインフラシステム，アプリケーション，ミドルウェアのデプロイメントやコンフィギューレーションの管理に適用すること．

Infrastructure as Codeの考え方は端的に言ってしまえば，「アプリケーション開発のノウハウをインフラの管理でも応用しましょう」というものです．
これによりGitHubなどを利用してバージョン管理された状態でインフラ構成を保管することもできます．

2~4年の周期で大きく人が入れ替わってしまう研究室のサーバ管理活動において，引き継ぎを容易にするためにもこの仕組みは大事です．

# Ansibleの動作原理

ここまでダラダラと構成管理ツールを導入するメリットについて解説しましたが，ここから本題のAnsibleについて書こうと思います．

Ansibleでは大きく**イベントリ**と**プレイブック**というファイルを利用します．
**イベントリは操作対象を記述するファイル，プレイブックは具体的な操作内容を記述するファイルになります．**

Ansibleではクライアント(コントロールノード・管理サーバとも呼ばれます)からサーバ(ターゲットノード)にssh接続を介してプレイブックに記述された処理フローを実行します．
このフローを図にすると以下のようになります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225901/6a65c939-73bb-f7de-ea9c-1eef3df4de0c.png)

# コード例

次に実際にAnsibleを使った構成管理を行ってみます．

ここではVagrantを使って立ち上げた仮想マシン(centOS 7)に対しAnsibleで環境構築をします．

Ansibleのインストール方法やVagrantのインストール方法などは公式サイトや他の情報を参考にしてください🙇‍♂️

> Ansible ドキュメント(日本語), https://docs.ansible.com/ansible/2.9_ja/index.html

> Vagrabt, https://www.vagrantup.com/


## 仮想マシンの用意

まず仮想マシンのテンプレートとなる`Box`を追加します．

```bash
vagrant box add centos/7 
```

プロバイダには`virtualbox`を選択します．

次に作業を行うディレクトリ`ansible-sample`を作成して仮想マシンの管理ファイルとなる`Vagrantfile`を作成します．

```bash
mkdir ansible-sample && cd ansible-sample

vagrant init centos/7
```

これで`ansible-sample`ディレクトリ配下に`Vagrantfile`ができました．
`vagrant up`コマンドで仮想マシンを起動しましょう．

```bash
vagrabt up
```

## イベントリの用意

ansibleのイベントリファイルを用意しましょう．
ファイル名は`hosts`とします．

Vagrantはデフォルトではlocalhostの`2222`ポートでssh接続を受け付けます．

今回はあくまでサンプルなのでホスト名を`sample`としています．

```conf:hosts
[sample]
127.0.0.1

[sample:vars]
ansible_port=2222
ansible_ssh_private_key_file=./.vagrant/machines/default/virtualbox/private_key
ansible_become=yes
ansible_user=vagrant
ansible_become_methods=su
ansible_become_user=root
```

## プレイブックの用意

実際にプレイブックを用意していきます．
Ansibleのプレイブックは単一のファイルではなくロール毎に整理して配置するのが一般的です．

今回は以下のような内容を想定して用意していきます．

1. サーバのロケールの設定
2. NTPサーバの設定
3. パッケージ管理の設定

これらを以下のようなディレクトリ構成で記述します．

```bash
.
├── Vagrantfile
├── hosts
├── roles
│   └── common
│       ├── locale // 1. サーバのロケールの設定
│       │   ├── defaults
│       │   │   └── main.yml
│       │   └── tasks
│       │       └── main.yml
│       ├── ntp // 2. NTPサーバの設定
│       │   ├── handlers
│       │   │   └── main.yml
│       │   └── tasks
│       │       └── main.yml
│       └── packages // 3. パッケージ管理の設定
│           ├── tasks
│           │   └── main.yml
│           └── vars
│               └── RedHat.yml
├── site.yml
└── webservers.yml
```

### サーバのロケールの設定

まず`defaults/main.yml`に設定する値を記述します．

```yml:roles/common/locale/defaults/main.yml
---
locale_timezone: "Asia/Tokyo"
locale_locale: "ja_JP.UTF-8"
locale_keymap: "jp"
```

そして`tasks/main.yml`で操作を記述します．
この時`defaults/main.yml`の値を`{{ 変数名 }}`で取り出すことができます．

```yml:roles/common/locale/tasks/main.yml
---
- name: Setup timezone
  timezone:
    name: "{{ locale_timezone }}"

- name: Setup locale
  command: localectl set-locale LANG={{ locale_locale }}
  changed_when: false

- name: Setup keymap
  command: localectl set-keymap {{ locale_keymap }}
  changed_when: false
```

### パッケージ管理の設定

次にパッケージ管理の設定をします．
パッケージ管理は対象とするLInuxディストリビューションによって異なるので柔軟性を持たせたいです．

今回対象とする仮想マシンは`centOS`なので`RedHat`系になります．
そこでまず`vars/RedHat.yml`に`RedHat`系のディストリビューションで管理したい値を記述しておきます．

```yml:roles/common/packages/vars/RedHat.yml
---
required_packages:
  - "@Development Tools"
```

次に実際の操作を`tasks/main.yml`に記述します．

```yml:roles/common/packages/tasks/main.yml
---
- name: Add the OS specific variables
  include_vars: "{{ ansible_os_family }}.yml"

- name: Update yum packages
  yum:
    name: "{{ item }}"
    state: present
    update_cache: yes
  loop:
    - "epel-release"
    - "*"
  when: ansible_os_family == 'RedHat'

- name: Instrall the required packages
  package:
    name: "{{ required_packages }}"
    state: present
```

`ansible_os_family`の情報を利用して`var`配下から読み取るymlファイルを変化させるのがポイントです．
Debian系なら`Debian,yml`を用意することで柔軟な記述が可能です．


### NTPサーバの設定

`chorony`を使ったNTPサーバの設定を行います．
NTPサーバの設定はchoronyの再起動が必要なタスクになります．

この再起動の操作についてはansibleでは`handlers/main.yml`に記述します．

```yml:roles/common/ntp/handlers/main.yml
---
- name: Restart chrony
  service:
    name: chronyd
    state: restarted
```

具体的なNTPサーバの操作については`tasks/main.yml`に記述します．

```yml:roles/common/ntp/tasks/main.yml
---
- name: Install chorony
  package:
    name: chrony
    state: present
  when: not (ansible_distribution == "CentOS" and ansible_distribution_major_version == "7")
  become: yes

- name: Disable default servers
  replace:
    dest: /etc/chrony.conf
    regexp: '^(server \d+.centos.pool.ntp.org iburst)'
    replace: '# \1'
  become: yes

- name: Add ntp servers
  blockinfile:
    dest: /etc/chrony.conf
    content: |
      # ntp servers
      server ntp.nict.jp iburst
      # disabel ntp server only use client
      port 0
  notify:
    - Restart chrony
  become: yes

- name: Execute chorony makestep
  command: chronyc -a makestep
  changed_when: false
  become: yes

- name: Enable chorony service
  service:
    name: chronyd
    enabled: yes
  become: yes
```

choronyの再起動が必要な操作を行ったあとは`notify`で`handlers/main.yml`の操作を呼び出します．

### site.ymlの設定

ここまで個別のロールやタスクを設定してきました．
Ansibleではどのホストにどのロールを実行するかを設定することができます．

これらの設定を`site.yml`にまとめます．

```site.yml
- hosts: sample
  roles:
    - common/locale
    - common/packages
    - common/ntp
```

## 実行

それでは実行しましょう，といきたいところですが文法的な間違いがないかやタスクの確認をしておきましょう．
文法のチェックとタスクの一覧の確認は以下のコマンドで行うことができます．

```bash
# 文法チェック
ansible-playbook -i hosts site.yml --syntax-check

# タスクの一覧を確認
ansible-playbook -i hosts site.yml --list-tasks
```

```:出力（タスクの一覧）
playbook: site.yml

  play #1 (sample): sample	TAGS: []
    tasks:
      common/locale : Setup timezone	TAGS: []
      common/locale : Setup locale	TAGS: []
      common/locale : Setup keymap	TAGS: []
      common/packages : Add the OS specific variables	TAGS: []
      common/packages : Update yum packages	TAGS: []
      common/packages : Instrall the required packages	TAGS: []
      common/ntp : Install chorony	TAGS: []
      common/ntp : Disable default servers	TAGS: []
      common/ntp : Add ntp servers	TAGS: []
      common/ntp : Execute chorony makestep	TAGS: []
      common/ntp : Enable chorony service	TAGS: []
```

では実際に実行してみましょう．
以下のコマンドで実行可能です．

```bash
# リハーサル
ansible-playbook -i hosts site.yml --check

# 本番実行
ansible-playbook -i hosts site.yml
```

最終的に以下のよう出力でエラーなく実行できていれば問題ありません．

```:出力
PLAY RECAP **********************************************************************************************************************************************************
127.0.0.1                  : ok=12   changed=6    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
```

なおスキップされていたタスクは`TASK [common/ntp : Install chorony]`でchoronyのインストールでした．

# おわりに

本記事では構成管理ツールの概要からAnsibleを使った構成管理の例をサンプルコード付きで解説しました．

何かに役立てば幸いです🙏


# 参考

- Ansible実践ガイド 第3版 (impress top gear) , https://www.amazon.co.jp/Ansible%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%E7%AC%AC3%E7%89%88-impress-top-gear/dp/4295007641/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=ansible&qid=1609311074&sr=8-1
- Playbookの使用 - ベストプラクティス, https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_best_practices.html