構成管理ツールで楽がしたい。そうだAnsible始めよう！

# ◎prologue
・一般的にLinuxサーバに導入されておりWebサービスではほぼほぼ利用されている。その上リスクが大きい脆弱性がとあるOSSで見つかったことがあります。
どう対処しようかと社内がざわつく。それもそのはず顧客のサーバ台数が尋常でなく多かったから。
基本的な対処は 
(1)そのOSSのバージョンが脆弱性の対象かどうか確認。 
(2)脆弱性の対象であった場合、顧客と調整した上アップグレードする。
簡単に言うとこれだけですが、人力でやるには難しい状況。
そんな中 (1)のリストをさっと作ってくれたのが尊敬するSuper programmer & Gentleman(比喩ではありません)の同僚。
その時彼が利用していたのがAnsible。
その窮地を解決し、彼の技術blogでよく取り上げとても推していたAnsible。
<hr/>
・それから結構時が経った今、
不毛な労力を技術力で解決し品質もあげたい！
ならば触らんことには始まらないというわけで構成管理ツール始めてみました。
同じ構成管理ツールでもPuppetやCapistranoといった実績も多そうなレガシーなツールから始めようと思っていたのですが、そのGentlemanの兄が残していってくれた参考書があったので、ちょっと背伸びしてAnsible始めてみました。

![Ansible_logo.svg.png](https://qiita-image-store.s3.amazonaws.com/0/50310/62c736fa-6724-eeb6-4f7e-43055cb3f979.png)


# ◎ 今回の検証環境
## ・Ansibleを導入した管理ホスト( mitzi_dev02 : "10.0.2.211" )

```bash
[ec2-user@mitzi_dev02 ~]$ uname -r
4.9.27-14.31.amzn1.x86_64

[ec2-user@mitzi_dev02 ~]$ ifconfig | grep "inet addr:10"
          "inet addr:10.0.2.211"  Bcast:10.0.2.255  Mask:255.255.255.0

* Python 2.7.12
* ansible 2.3.1.0
```
## ・対象ホスト( mitzi_dev : "10.0.2.21" )
```bash
[ec2-user@mitzi_dev ~]$ python --version
Python 2.6.9

[ec2-user@mitzi_dev ~]$ ifconfig | grep "inet addr:10"
          "inet addr:10.0.2.21"  Bcast:10.0.2.255  Mask:255.255.255.0
```
## ■参考
- 某参考書 2014年発行
- [公式ドキュメント](http://docs.ansible.com/)

### ▽備考
当初、2014年発行の某参考書をもとに進めていたのですが、開発・仕様変更のスピードが早いためか
・書籍どおりにやっても動かないもの
・モジュール自体が非推奨となっている(次期バージョンで無くなる等の理由より)ものが多々あり、戸惑いました。

# ◎ A.とても便利そう(特徴 と 構成)
## ■A-01.特徴
・導入時、既存システムへの変更がほぼ必要ない。
例えばPuppet や chef は 対象ホストにエージェントをインストールする必要がある。
一方 Ansibleは 対象ホストでsshが使えれば管理可能とのこと。(※)
<b>→ 例：煩雑な手続きや規則の問題から別途インストール出来ない顧客環境でも使えるということ。便利。</b>

(※) 実際には対象ホストにpythonが必要。けれどコチラも殆どのOSに標準で付属している)

・その他にも既存のshell scriptをそのまま使える。デプロイも出来るなどの特徴があるとのこと。
詳細は数多くある他Ansible関連の記事、blogに譲ります。

## ■A-02.構成
[Ansibleを実行する管理ホスト] ⇒ [管理対象のホスト群]

# ◎ B.設定
## ■事前設定(管理ホスト側)
・管理対象のホストにssh出来るように各サーバ共通の秘密鍵を管理ホストに格納

## ■B-00.設定ファイル一覧(管理ホスト側)
1. inventory ファイル
実行対象のホストを設定するファイル
→ Ansibleを使うために最低限必要なファイル

2. playbook ファイル
task と呼ばれる一つ一つの動作をまとめて記述したファイル

3. ansible.cfg ファイル
Ansible 自体の設定を記述した設定ファイル

## ■B-01. 設定ファイル (1. inventory ファイル)
・ini 形式(正確にはini 形式を拡張した形式) で記述
・yum でAnsibleをインストールした場合は、 /etc/ansible/hosts という設定ファイルが作成される模様。
・pipでインストールした場合は、
- 独自の設定ファイルを作成し
- 環境変数「ANSIBLE_HOSTS」にパスを通す

### ▽設定例
・inventoryファイル

```bash
vi ~/ansible_hosts
----
[web]
10.0.2.21
```
・環境変数

```bash
export ANSIBLE_HOSTS=~/ansible_hosts
もしくは、
上述の内容を homedir直下の ~/.bash_profileに追記し
source ~/.bash_profile で反映することで恒久対応
```

### ▽ansibleコマンド実行
<b>ここまでの設定でもうansibleが実行出来ます。</b>

・実行例: 対象ホストでuptimeコマンド実行

```bash
[ec2-user@mitzi_dev02 ~]$ ansible web -m command -a "uptime"
10.0.2.21 | SUCCESS | rc=0 >>
 04:21:23 up 33 days,  6:45,  2 users,  load average: 0.11, 0.05, 0.05
```

・基本的なオプションの説明 

   option　　　     |  説明　　
------------ | ----
  --private-key  | 秘密鍵を指定
 -i  | Ansible 設定ファイルの指定
 -m  | モジュール呼び出し
 all  | すべてのhost指定(その他、inventoryで指定した[web]などのグループを指定

↓省略せずオプションを指定した場合(pingで対象ホスト群との疎通確認)

```bash
[ec2-user@mitzi_dev02 ~]$ ansible --private-key=~/.ssh/id_rsa -i ~/ansible_hosts web -m ping
10.0.2.21 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
```

### ▽補足(モジュールとは)
・対象ホストの上で実行するものをモジュール(module)と呼ぶ
[公式ドキュメント: Module Index](http://docs.ansible.com/ansible/latest/modules_by_category.html)

### ・モジュール一例

モジュール名　　　|説明　　　　　　　　　　　
----------------| ----
setup|実行したホストの状態(IPなど)を取得するモジュール
script|管理ホスト上のスクリプトを転送し対象ホスト上で実行する
command|任意のコマンドを実行する
shell|任意のコマンドを実行する。commandモジュールと違い、 > (リダイレクト) や (パイプ)が利用出来る

### ・コマンド一例

コマンド名　　　　|説明　　　
----------------| ----
ansible | ansible のモジュールを実行する
ansible-playbook | モジュールをまとめたPlaybook を実行する
ansible-vault | ファイルの暗号化を行う
ansible-galaxy | 全世界のユーザーが投稿しているplaybook を使う

## ■B-02. 設定ファイル (2. playbook ファイル)
### ▽書式
■YAML
・playbook はYAML(yaml ain't markup language)形式で記述する
※ YAMLは入れ子になった構造や、シーケンス(配列)・マッピング(辞書) のデータ構造を読みやすく記述できるとのこと

・大きく3つのセクションに分かれている
- target: セクション実行対象の設定
- vars: セクション変数の設定
- tasks: セクション実行するtask の設定

▽注意点
・インデントの縦を合わせる（インデント幅は慣習的にスペース２つ）
・キーと値の区切り文字「:」の後ろは必ず空ける（× name:foo、○ name: foo）
・構文エラーになる場合は、クォートしてみる

▽メモ
・後述しますが、このYAMLの書式に慣れていなかったため構文エラーに参りました。

## ■B-03. 設定ファイル (3. ansible.cfg ファイル)
### 今回触っていません

# ◎ C. playbookの実行
## ■C-01. オプション
・構文チェック
**ansible-playbook ***.yml --syntax-check**

・Dry Run
**ansible-playbook ***.yml --check**

・デバッグ
**ansible-playbook ***.yml --vvv**

・その他オプション
-u:　SSH接続に使うユーザを指定
-k:　接続パスワードの入力を求める

## ■C-02. 実行例: アカウント追加
・実業務に近いことをやってみようと、鍵認証有効のアカウント追加を実施してみました。

### ▽Index
1) 新規アカウント用の秘密鍵/公開鍵を作成
2) 新規アカウント作成、公開鍵の配置

### ▽1) 新規アカウント用の秘密鍵/公開鍵を作成
・本工程は Ansibleでやる必要がないと考えるため、簡易shellスクリプトで実施しました。

```bash
vi ~/user_ssh-keygen.sh
---------------------------------
#!/bin/sh

# はじめに。一般ユーザ(ec2-user 等)で実行することを想定しています。
# ユーザ作成に必要な情報をキーボード入力で受け付ける
read -p "Please input user id: " userid
read -p "Please input group id of group name: " group
read -p "Please input user name: " username
read -p "Please input your passphrase: " passphrase

# ユーザ作成
sudo useradd -u $userid -g $group $username

# 鍵作成
sudo su - $username -c "ssh-keygen -N $passphrase; cat .ssh/id_rsa.pub >> .ssh/authorized_keys;chmod 600 .ssh/authorized_keys;mv .ssh/id_rsa .ssh/$username.pem"

# パスフレーズ解除
sudo su - $username -c "openssl rsa -in .ssh/$username.pem -out .ssh/$username.pem"
```

・実行権限付与(実行するユーザに実行権限を付与する)
``$ chmod u+x ~/user_ssh-keygen.sh``

・実行

```bash
[ec2-user@mitzi_dev02 ~]$ ~/user_ssh-keygen.sh
Please input user id: 1001
Please input group id of group name: ec2-user
Please input user name: mitzi2test_user
Please input your passphrase: <パスフレーズ>
(省略)
```

・鍵が作成されていることを確認

```bash
[ec2-user@mitzi_dev02 ~]$ sudo find /home/mitzi2test_user/.ssh/ \( -name authorized_keys -o -name mitzi2test_user.pem \) -ls
293682    4 -rw-------   1 mitzi2test_user ec2-user     1675  8月  1 07:15 /home/mitzi2test_user/.ssh/mitzi2test_user.pem
293685    4 -rw-------   1 mitzi2test_user ec2-user      409  8月  1 07:14 /home/mitzi2test_user/.ssh/authorized_keys
```

・ec2-userでplaybookを実行するため鍵を移動した上、適切な権限に変更しておく

```bash
$ mkdir /home/ec2-user/.ssh/mitzi2test_user
$ sudo cp /home/mitzi2test_user/.ssh/authorized_keys /home/ec2-user/.ssh/mitzi2test_user/authorized_keys
$ chown ec2-user:ec2-user /home/ec2-user/.ssh/mitzi2test_user/authorized_keys
```

※補足: 上記手順。対象ホスト上の新規アカウントとの鍵認証のために作成しているため、管理ホスト上でユーザアカウントは作らなくとも大丈夫だったかと思います。

### ▽2) 新規アカウント作成、公開鍵の配置
・まずハッシュ化したパスワードを生成しておく (下記はPhythonライブラリPassLibを用いた場合)
sha512でパスワードをハッシュ化

```bash
python -c "from passlib.hash import sha512_crypt; print sha512_crypt.encrypt('123PassW0rd')"
$6$rounds=656000$7lXqOYiwEzNYAVg3$O6JKCltBO.V8uqkogFjZ/Ix96HEA7iKrMPIGce/SGVjm5NntM11SlVyLh4gZVzFVJl3BXlO5zaa8.VexF0KkU/
```

・playbookの実行により、対象ホスト上に新規アカウント作成。また公開鍵も配置
(今回作成するユーザー: 『mitzi2test_user』 ※プロンプトから入力)

```yaml
vi ~/useradd2.yml
---------------------------------
- hosts: web                              # 対象グループを指定。Inventoryに定義した[web]を指定
  become: yes                             # root権限で実施

  vars_prompt:                            # 変数 username をキーボードから入力
    username: "please input username"

  tasks:                                  # 実行するtask(モジュール) の指定を開始
    - name: ユーザーを追加                 # task の名前(任意)
      user:
        name={{ username }}
        uid=1001
        groups=ec2-user,wheel
        shell=/bin/bash
        password="$6$rounds=656000$7lXqOYiwEzNYAVg3$O6JKCltBO.V8uqkogFjZ/Ix96HEA7iKrMPIGce/SGVjm5NntM11SlVyLh4gZVzFVJl3BXlO5zaa8.VexF0KkU/"
        # パスワードはハッシュ化されている必要あり(※上記で作成したもの)

    - name: wheelグループのユーザがsudo出来るように設定
      lineinfile:
        dest: /etc/sudoers               #書き換え対象ファイル
        state: present
        backup: yes                      #変更対象のバックアップを取得する
        regexp: "^# %wheel        ALL=(ALL)       ALL" #正規表現で該当行を指定
        line: '%wheel        ALL=(ALL)       NOPASSWD: ALL' #置換

    - name: 管理ホスト上の公開鍵を配置
      authorized_key:
                    user: "{{ username }}"
                    key: "{{ lookup('file', '/home/{{ username }}/.ssh/authorized_keys') }}"
```

▽実行

```bash
・まず構文チェック
[ec2-user@mitzi_dev02 ~]$ ansible-playbook ~/useradd2.yml --syntax-check

・DryRun & デバッグ
[ec2-user@mitzi_dev02 ~]$ ansible-playbook ~/useradd2.yml --check -vvv
```

※この時、lookupモジュールで鍵ファイルが見つからないといったエラーが発生しました。またForumを見るとAnsibleのバージョンによっては既知のバグであるといった書き込みがあり戸惑いました。が、私の場合は実行ユーザ(ec2-user)の権限の問題で発生していました。
https://github.com/carlalexander/debops-wordpress/issues/130

▽実行

```bash
[ec2-user@mitzi_dev02 ~]$ ansible-playbook ~/useradd2.yml
```

▽結果確認
・対象ホスト側に意図したユーザーが作成されていることを確認

```bash
[root@mitzi_dev ~]# cat /etc/passwd | grep mitzi2test_user
mitzi2test_user:x:1001:500::/home/mitzi2test_user:/bin/bash

[root@mitzi_dev ~]# id -a mitzi2test_user
uid=1001(mitzi2test_user) gid=1001(mitzi2test_user) groups=1001(mitzi2test_user),10(wheel),500(ec2-user)

・ディレクトリ、公開鍵が生成されているだけでなく 権限も想定通りであることを確認
[root@mitzi_dev ~]# find /home/mitzi2test_user/  \( -name authorized_keys -o -name .ssh \) -ls
426657    4 drwx------   2 mitzi2test_user ec2-user     4096 Aug  1 20:24 /home/mitzi2test_user/.ssh
426659    4 -rw-------   1 mitzi2test_user ec2-user      409 Aug  1 20:24 /home/mitzi2test_user/.ssh/authorized_keys
```

・sudoersの設定確認

```bash
[root@mitzi_dev ~]# visudo
-----
## Same thing without a password
%wheel        ALL=(ALL)       NOPASSWD: ALL
※ 中途半端な正規表現でしたが、どうやら下の行から評価されるためこのように想定どおりコメントアウト解除が出来ていました。
```

・パスワード設定が効いているか確認

```bash
[ec2-user@mitzi_dev ~]$ sudo su - mitzi2test_user
[mitzi2test_user@mitzi_dev ec2-user]$ passwd
ユーザー mitzi2test_user のパスワードを変更。
mitzi2test_user 用にパスワードを変更中
現在の UNIX パスワード:
新しいパスワード:

↑「現在の UNIX パスワード: 」指定を抜けることが出来たためパスワード設定が意図したものになっていると判断
ちなみにハッシュ値を求めた元のパスワードは「123PassW0rd」
```

・管理ホストより該当アカウントでssh接続出来ることを確認

```bash
[ec2-user@mitzi_dev02 ~]$ sudo ssh -i /home/mitzi2test_user/.ssh/mitzi2test_user.pem mitzi2test_user@mitzi_dev
[mitzi2test_user@mitzi_dev ~]$
```

![ansible.png](https://qiita-image-store.s3.amazonaws.com/0/50310/4cd7d260-1b03-2ec5-4411-a534dd600dd7.png)


今回はここまでになります。