Ansibleを試してみた

# はじめに
インフラ管理の自動化、devopsを学ぶためにAnsibleを学ぶことにした。
OSSパッケージはバージョンによって導入手順が異なることがあるが、気軽に環境を展開するための"楽をする"方法を覚える時期が来たように思える。

# バージョン情報
| パッケージ | バージョン |
|:-:|:-:|
| CentOS | 7.6.1810 (Core) |
| Ansible | 2.9.10 |



# Ansibleとは

## 概要
RedHat社が提供する構成管理ツールで、<font color="red">コントロールノードと呼ばれる管理マシンからターゲットノードに、playbookに記載された設定変更を促し、大量のサーバ構成管理を可能とする。</font>

例えば...
Webサーバのグループに分類されるマシンにhttpdパッケージをインストールしたり、dbサーバのグループに分類されるマシンにmysqlパッケージをインストールしたりと複数サーバへの様々な変更を簡単に行うことができる。

# playbookとは
playbookというのはansibleが一連の処理を行うために読み込む設定ファイル群のことを示す。

### playbookファイルの構成と連携の概要
playbookファイルは下記のようにrolesに作ったサービス(パッケージ)の構成ファイルと連携します。
<font color="red">※下記はhttpdのセットアップ用に作られたplaybookです。</font>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184939/fcda5d5d-2100-24fe-a7e5-a70f57ec9957.png)


# サーバ構成
|No.| Ansibleの役割 | ホスト名 | IPアドレス | セットアップする機能 |
|:-:|:-:|:-:|:-:|:-:|
|1| コントロールノード | ansiblehost | 10.0.1.111 | - |
|2| ターゲットノード | ansibletest1 | 10.0.1.112 | webserver |

## playbookで実行したいタスク
ターゲットノード もしくは テスト環境にて実行したいタスクを整理する。
<font color="red">※仮想化基盤のスナップショット機能などを使って、手順を実行する前の状態に復元できるようしたほうが確認しやすい。</font>

本環境では次のタスクをコントローラノードからターゲットノードに配布する

- selinuxの無効化
- apache(httpd)のインストールとサービスの有効化(開始)
- Firewalldにhttp通信許可ポリシーの追加


# 手順

## 1.準備

### 1.1. コントロールノードにAnsibleをインストールする
epel-relaseリポジトリからパッケージを入手してセットアップすると簡単に導入できるらしいので、それに従ってAnsibleをインストールします。

#### 1.1.1.epel-releaseリポジトリのインストール

```console:
[root@ansiblehost]# yum install -y epel-release
```

#### 1.1.2.Ansibleのインストール

```console:
[root@ansiblehost]# yum install -y ansible
```

#### 1.1.3.Ansibleのインストール確認

```console:
[root@ansiblehost]# ansible --version
ansible 2.9.10
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /bin/ansible
  python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20158623 (Red Hat 4.0.5-36)]
```

### 1.2.コントロールノードで暗号鍵を作ってターゲットノードに配布する
コントロールノードからターゲットノードに<font color="red">パスワードなし</font>でログオンするため、公開鍵をターゲットノードにコピーする。

#### 1.2.1.暗号鍵を作る

```console:
[root@ansiblehost]# ssh-keygen
Generating public/private rsa pair.
Enter file in which to save the key (/root/.ssh/id_rsa): [何も入力せずにEnterキーを押下]
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase): [何も入力せずにEnterキーを押下]
Enser same passphrase again: [何も入力せずにEnterキーを押下]
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub/
The key fingerprint is:
SHA256:FXXPAU665kgaxMSrf2cogEke5j0hdVGdXWW617s8huM rootpansiblehost
+---[RSA 2040]---+
|     .00.0.+o+o+|
|   . .p. .. .. o|
|  = ... .  .  ..|
| = * o.S. p  ...|
|     +. ...  .. |
|       o o .Eoo.|
+----[SHA256]----+
```

#### 1.2.2.暗号鍵をターゲットノードに配布する

```console:
[root@ansiblehost]# cd /root/.ssh
[root@ansiblehost]# cp-p id_rsa.pub authorized_keys
[root@ansiblehost]# cd /root
[root@ansiblehost]# scp -pr .ssh root@10.0.1.112:~/
```

#### 1.2.3.ターゲットノードにsshログオンする

```console:
[root@ansiblehost]# ssh root@10.0.1.112
Last login: Fri Sep 11 16:52:42 2020 from 10.0.1.111
[root@ansibletest1]#
```

## 2.Ansobile疎通確認

### 2.1.名前解決設定をhostsファイルに設定する

```config:/etc/hosts

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

10.0.1.112 ansibletest1 ansibletest1.dev.local
```

### 2.2.インベントリファイルを作成する

```config:/etc/ansible/hosts
# This is the default ansible 'hosts' file.
#
# It should live in /etc/ansible/hosts
#
#   - Comments begin with the '#' character
#   - Blank lines are ignored
#   - Groups of hosts are delimited by [header] elements
#   - You can enter hostnames or ip addresses
#   - A hostname/ip can be a member of multiple groups

# Ex 1: Ungrouped hosts, specify before any group headers.

## green.example.com
## blue.example.com
## 192.168.100.1
## 192.168.100.10

# Ex 2: A collection of hosts belonging to the 'webservers' group

## [webservers]
## alpha.example.org
## beta.example.org
## 192.168.1.100
## 192.168.1.110

# If you have multiple hosts following a pattern you can specify
# them like this:

## www[001:006].example.com

# Ex 3: A collection of database servers in the 'dbservers' group

## [dbservers]
##
## db01.intranet.mydomain.net
## db02.intranet.mydomain.net
## 10.25.1.56
## 10.25.1.57

# Here's another example of host ranges, this time there are no
# leading 0s:

## db-[99:101]-node.example.com

[test]
ansibletest1
```

### 2.3.Ansibleコマンドで疎通確認する

```console:
[root@ansiblehost]# ansible all -m ping
ansibletest1 | SUCCESS => [
  "ansible_facts": [
    "discovered_interpreter_python": "/usr/bin/python"
  ]
  "changed": false,
  "ping": "pong"
]
```

<font color="red">※次の結果が表示された場合、名前解決ができていないか、hostsに記載した内容が誤っています</.font>

```console:例)/etc/ansible/hostsに記載されている名前が間違っている
ansibetest1.dev.local | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname ansibetest1.dev.local: Name or service not known",
    "unreachable": true
}
```



## 4.playbookを作成する
<font color="red">※playbookの作成はAnsibleコントローラノードで行います。</font>

### 4.1. playbookを管理するフォルダに移動する

```console: 
[root@ansiblehost]# cd /etc/ansible/roles
SSH password: **********
```


### 4.2. selinuxタスクを作成する

#### 4.2.1.selinuxタスクを管理するディレクトリを作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184939/f47d133f-a172-d0ee-a953-38572f777e94.png)


#### 4.2.2.タスク関連ファイルを編集する

```yml:/etc/ansible/roles/selinux/tasks/selinux.yml
- name: disable_selinux
  selinux:
    state: disable
    policy: targeted
  notify: reboot
```

```yml:/etc/ansible/roles/selinux/handlers/main.yml
- import_tasks: selinux.yml
```

#### 4.2.3.ハンドラー関連ファイルを編集する

```yml:/etc/ansible/roles/selinux/handlers/main.yml
- name: reboot
  reboot:
```

## 4.3.httpdタスクを作成する

#### 4.3.1.httpdタスクを管理するディレクトリを作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184939/0ed63b65-babf-f75e-1539-5971d74ebaf4.png)


#### 4.3.2.タスク関連ファイルを編集する

```yml:/etc/ansible/roles/httpd/tasks/install.yml
- name: install_httpd_by_yum
  yum:
    name: httpd
    state: present
  notify: enable
```

```yml:/etc/ansible/roles/httpd/handlers/main.yml
- import_tasks: install.yml
```

#### 4.3.3.ハンドラー関連ファイルを編集する

```yml:/etc/ansible/roles/httpd/handlers/main.yml
- name: enable
  systemd:
    name: httpd.service
    state: started
    enabled: yes
```

## 4.4.firewalldタスクを作成する

#### 4.4.1.firewalldタスクを管理するディレクトリを作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184939/32f20858-511e-0e82-a45f-806a0001cf79.png)


#### 4.4.2.タスク関連ファイルを編集する

```yml:/etc/ansible/roles/firewalld/tasks/allowhttpd.yml
- name: add_http_rule
  firewalld:
    service: http
    zone: public
    permanent: yes
    state: enabled
  notify: reload

- meta: flush_handlers
```

```yml:/etc/ansible/roles/firewalld/handlers/main.yml
- import_tasks: allowhttpd.yml
```

#### 4.4.3.ハンドラー関連ファイルを編集する

```yml:/etc/ansible/roles/firewalld/handlers/main.yml
- name: reload
  service:
    name: firewalld
    state: reloaded
```

## 4.5.playbookファイルを編集する

```yml:/etc/ansible/webserver.yml
---
- hosts: all
  gather_facts: false
  remote_user: root
  roles:
    - httpd
    - firewalld
    - selinux
```


## 5.playbookの実行

```console:
[root@ansiblehost]# cd /etc/ansible
[root@ansiblehost]# ansible-playbook webserver.yml -i hosts -k
SSH password: *******

PLAY [all] ********************************************************************************************************************

TASK [install_httpd_by_yum] ***************************************************************************************************
change: [ansibletest1]

TASK [firewalld: add_httpd_rules] *********************************************************************************************
change: [ansibletest1]

RUNNING HANDLER [httpd: enable] ***************************************************************************************************
change: [ansibletest1]

RUNNING HANDLER [firewalld: reload] ***************************************************************************************************
change: [ansibletest1]

TASK [disable_selinux] ********************************************************************************************************
[WARNING]: SELinux state temporarily changed from 'enforcing' to 'permissive'. State change will take effect next reboot.
changed: [ansibletest1]

RUNNING HANDLER [selinux : reboot] ********************************************************************************************
changed: [ansibletest1]

PLAY RECAP ********************************************************************************************************************
ansibletest1              : ok=6   changed=6    unreachable=0    failed=0    skipped=0     rescued=0    ignored=0
```


### エラーがあった場合

最初のテストでselinuxのconfigを書き換える手順を間違えていた。その時のエラーは下記のように表示された。

```console:
fatal: [ansibletest1]:FAILED! => ["changed": false, "msg": "value of state must be one of: enforcing, permissive, disabled. go
t: disable"]
```

# ターゲットサーバを作り直す場合
ターゲットサーバを作り直す場合、コントロールサーバで.ssh/hostsに記載されているターゲットサーバのssh情報を削除する必要がある。


# ansible-playbook コマンド書式

```:
ansible-playbook {playbook file} -i {inventory file} -l {host group} -k
```

| オプション | 説明 |
|:-:|:-:|
| -i | specify inventory host path or comma separated host list. |
| -k | ask for connection password |
| -l | further limit selected hosts to an additional pattern |

# 参考


- Ansible入門者向け学習ガイド(CentOS編) http://c.itdo.jp/technical-information/ansible/ansible-tutorial/#back3
- 5分でSSHの公開鍵認証を有効化する方法【ファイル転送ソフト不要】 https://hackers-high.com/linux/easy-ssh-publickey-authentication/
- インベントリーの構築方法 https://docs.ansible.com/ansible/2.9_ja/user_guide/intro_inventory.html
- AnsibleコマンドでPING疎通する https://docs.ansible.com/ansible/2.9_ja//user_guide/intro_getting_started.html
- systemdモジュール https://docs.ansible.com/ansible/latest/modules/systemd_module.html#systemd-module
- handlersとnotifyの関係について https://blog.amedama.jp/entry/2015/09/01/214912
- playbook実行時にホストグループを指定 https://tech.withsin.net/2017/07/19/ansible-playbook-l/