Ansible入門ハンズオン

Ansible触りたいけどどうしたらいいか分からん人向け
Ansibleのインストールから簡単はハンズオンまで～

# 環境について
AnsibleではAnsibleを実行する機器をコントロールノード、自動化対象の機器をマネージドノードと呼ぶ。
今回は簡単はハンズオンなのでコントロールノードもマネージドノードも同じ機器で行う。

仮想化: Virtualbox7.0
ホストOS: Windows11
ゲストOS: debian bullseye

```
cat /proc/version
Linux version 5.10.0-23-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.179-1 (2023-05-12)
```

```
python3 -V
Python 3.9.2
```

# インストールの種類
Ansibleのインストール方法は大きく分けて2つある。
1つ目はaptやdnfなどのOSのパッケージ管理ツールを使用してインストールする方法だ。
2つ目はpipを使用してインストールする方法だ。

今回は2つ目のpipを使用してインストールする方法を紹介する。
理由は特にない。好みの方でインストールを行えばよい。
では実際にインストールを行う。

# 環境準備
まずはPythonの仮想環境を作成していく。
仮想化に使用するものはなんでもいいが私はvenvを使用する。
venvがインストールされていない場合はインストールする。
```
sudo apt -y install python3-venv
```
仮想環境用のディレクトリを作成しておく。
```
mkdir ~/venv
```
ansibleと言う名前の仮想環境を作成する。
```
python3 -m venv ~/venv/ansible
```
上記のコマンドを実行するとansibleと言う名前のディレクトリが作成される。
```
ls -ld ~/venv/ansible
drwxr-xr-x 6 mena mena 4096  8月 31 23:04 /home/mena/venv/ansible/
```
環境を有効化する。
```
source ~/venv/ansible/bin/activate
```
有効化が成功していればプロンプトの先頭に環境名が付く。
今回であれば(ansible)とついていればok
こんな感じ
```(ansible) mena@home-server:~$```

# Ansibleのインストール
さて、環境準備が完了したのでいよいよAnsibleをインストールしていく。
と言ってもAnsibleはコマンド1つでインストールできてしまうので特に解説することはない。
pipを使用してインストールを行う。
```
pip3 install ansible
```
pipコマンドが終わったら下記のコマンドを実行し正常にインストールできたか確認しよう。
```
ansible --version
```
私の環境では次のような出力となった
```
ansible [core 2.15.12]
  config file = None
  configured module search path = ['/home/mena/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/mena/venv/ansible/lib/python3.9/site-packages/ansible
  ansible collection location = /home/mena/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/mena/venv/ansible/bin/ansible
  python version = 3.9.2 (default, Feb 28 2021, 17:03:44) [GCC 10.2.1 20210110] (/home/mena/venv/ansible/bin/python3)
  jinja version = 3.1.4
  libyaml = True
  ```
# インベントリファイルの作成
Ansibleの実行対象を定義するインベントリファイルを作成する。
インベントリファイルは.ini形式か.yaml形式で記述する。
今回は私が慣れている.iniで記述する。
適当なエディタで次のものを書く。
``` inventory.ini
[ansible_target]
localhost

[ansible_target:vars]
ansible_user=mena
ansible_password=hogehoge
```
行の解説
1,2行目はホストグループを表している。
今回はansible_targetと言うグループにlocalhostが属しているといった感じ。
4-5行目はグループ変数を表している。
グループansible_targetに対して変数を定義した。
指定したのは2つ。

**ansible_user**
接続に使用するユーザ名を指定する。
**ansible_password**
接続に使用するユーザのパスワードを指定。

ユーザ名やパスワードは環境に合わせて修正する。

# 疎通確認
Ansibleを使用して実行対象に接続できるかどうかを確認しよう。
接続確認にはpingモジュールを使用する。
このモジュールは対象機器にSSHログインできるかを検証してくれるものだ。
pingと言いつつICMPは全く関係ない。

接続確認なのでコマンドラインでモジュールを指定する。いわゆるアドホックモード。
``` # ansible -i inventory.ini ansible_target -m ping ```
```
localhost | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
```
SUCCESSと表示が返ってくれば成功だ。

パスワードが間違っていて失敗すれば結果は次のようになる
```
localhost | UNREACHABLE! => {
    "changed": false,
    "msg": "Invalid/incorrect password: Permission denied, please try again.",
    "unreachable": true
}
```
## トラブルシュート
上記のようにSUCCESSが返ってくれば接続は成功なのだが私の環境ではパッケージが不足しており次のエラーが出た。
```
localhost | FAILED! => {
    "msg": "to use the 'ssh' connection type with passwords or pkcs11_provider, you must install the sshpass program"
}
```
今回はインベントリファイルにパスワードを指定したが、sshpassが環境に入っていなかったためSSH接続できずエラーとなった。
ではsshpassをインストールしよう。
```
sudo apt -y install sshpass
```
再度試行するとちゃんと成功した。

### 補足
今回作成したインベントリファイルではSSH接続に使用するパスワードを指定した。
そのため上記のsshpassパッケージをインストールしたが、SSHの認証方式に公開鍵を使用した場合、インベントリファイルでパスワードを指定する必要がなくなる。そしてsshpassパッケージも不要になる。

# Playbookを書こう
接続確認までできたのでここからはいよいよPlaybookを書く。
今回書くPlaybookでは次の処理を自動化しようと思う。
1. ディレクトリの作成
2. ファイルの作成
3. ユーザの作成
4. Apache2のインストール
5. Apache2の起動

行いたい処理に対してどのようなモジュールを使えばいいかはドキュメントのモジュール名を見ればおおよそ分かる。
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/index.html
先の処理が行えるモジュールはすべてここに記載があるため、モジュールを探す練習に覗いてみるのもいいだろう。

ではPlaybookを書いていく。
...
できたものがこちら。
``` handon.yaml
---
- hosts: ansible_target
  gather_facts: false

  tasks:
    - name: create directory
      ansible.builtin.file:
        path: ~/test_dir
        state: directory

    - name: create file
      ansible.builtin.file:
        path: ~/test_file
        state: touch

    - name: create user
      ansible.builtin.user:
        name: test_user
        uid: 1500
        comment: create by ansible
        state: present
      become: true

    - name: install apache2
      ansible.builtin.apt:
        name:
          - apache2
        state: present
      become: true

    - name: start apache2
      ansible.builtin.systemd_service:
        name: apache2
        state: started
        enabled: true
      become: true

```
## Playbookの解説
目的に対してPlaybookをどのように書いたかを解説していく。
まずはタスクの前の部分について解説する。
```
---
- hosts: ansible_target
  gather_facts: false
```
この部分は接続や動作に関して記述している。
hostsの行ではインベントリファイルで指定したホストグループを指定した。グループと言いつつホストは1台なので対象は1台だが。

gather_factsはおまじないである。
これはAnsibleが対象機器の情報を詳細に取得するかどうかを決めている。
情報を収集するプロセスがある分処理は当然遅くなる。
今回はこれによって取得する情報を使用していないので無効化して高速化を図っている。

becomeについて、以降のタスクに登場するものだが先に説明しておく。
becomeは権限昇格である。
今回のタスクではユーザの作成とApacheのインストールなどスーパーユーザの権限が必要な処理でこのオプションを有効にしている。
今回のように使用するにはAnsibleで接続しているユーザがパスワード入力無しで権限昇格できる必要がある。
becomeはhostsやgather_factsと同じセクションに記述することも可能である。その場合Playbook全体の処理で昇格して行う。
今回の場合、その方法を使ってしまうとファイルやディレクトリを作成するユーザもスーパーユーザになってしまうため、権限が必要なタスクでだけ昇格を行っている。

ここからは各タスクについて解説していく。
```
  tasks:
    - name: create directory
      ansible.builtin.file:
        path: ~/test_dir
        state: directory

    - name: create file
      ansible.builtin.file:
        path: ~/test_file
        state: touch
```
このタスクはディレクトリの作成とファイルの作成を行っている。
使用するモジュールはどちらもfileモジュールだが、指定するオプションによって作成されるものが異なる。


```
    - name: create user
      ansible.builtin.user:
        name: test_user
        uid: 1500
        comment: create by ansible
        state: present
      become: true
```
このタスクではユーザの作成を行っている。
ユーザ名はtest_userとして。uidとコメントも指定してみた。

```
    - name: install apache2
      ansible.builtin.apt:
        name:
          - apache2
        state: present
      become: true

    - name: start apache2
      ansible.builtin.systemd_service:
        name: apache2
        state: started
        enabled: true
      become: true
```
このタスクではApacheのインストールと起動を行っている。
インストールにはaptモジュールを使用した。
起動にはsystemd_serviceモジュールを使用した。
起動といってもaptでApacheをインストールするとデフォルトで起動しているので分かりづらいのだが。

# いざいざ実行
では実行していこう。
```
ansible-playbook -i inventory.ini handon.yaml
```
```
PLAY [ansible_target] **********************************************************************************************

TASK [create directory] ********************************************************************************************
changed: [localhost]

TASK [create file] *************************************************************************************************
changed: [localhost]

TASK [create user] *************************************************************************************************
changed: [localhost]

TASK [install apache2] *********************************************************************************************
changed: [localhost]

TASK [start apache2] ***********************************************************************************************
ok: [localhost]

PLAY RECAP *********************************************************************************************************
localhost                  : ok=5    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```
成功したタスクの数だけokのカウントがアップする。
逆に失敗したタスクの数だけfailedのカウントがアップする。
また、タスクによって変更がなされた場合okと共にchangedもカウントアップする。
変更がなかった場合はchangedをカウントアップせずokだけカウントアップする。

# 実行結果の確認
Ansibleによって作成できているのか確認しにいこう。

まずはファイルとディレクトリ。
Playbookで指定したtest_dirとtest_fileが作成されていることが分かる。
``` 
ls -l ~/ 
-rw-r--r-- 1 mena mena  723  8月 31 23:23 handon.yaml
-rw-r--r-- 1 mena mena   94  8月 31 23:24 invenrory.ini
drwxr-xr-x 2 mena mena 4096  8月 31 23:32 test_dir
-rw-r--r-- 1 mena mena    0  8月 31 23:32 test_file
drwxr-xr-x 3 mena mena 4096  8月 31 23:04 venv
```
次にユーザ。
test_userと言う名前のユーザが作成されていることが分かる。
uidやコメントも指定したものになっていることが確認できる。
``` 
cat /etc/passwd | grep test_user
test_user:x:1500:1500:create by ansible:/home/test_user:/bin/sh
```
最後にApache
Apacheがインストールされていることも確認できる。
```
apt list installed apache2
一覧表示... 完了
apache2/oldstable-security,now 2.4.61-1~deb11u1 amd64 [インストール済み]
```

一応Apacheが起動していることも確認する。
``` 
systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2024-08-31 01:18:55 JST; 14min ago
``` 

さっきも書いたがApacheはインストールされた段階で起動される。そのためこれはAnsibleによって起動されたわけではない。
Playbookの実行結果からもAnsibleによって起動されたわけではないことを確認できる。
Apacheを起動するタスクの実行結果に注目すると、他のタスクはchangedになっているのに対してこのタスクはokとなっている。これはAnsibleが変更しようとした設定が既にされているため処理を飛ばした結果である。
```
TASK [start apache2] **************************************************************************************************
ok: [localhost]
```


# お片付け
以上でAnsibleを使用してディレクトリ、ファイルの作成、ユーザの作成、アプリのインストールが行えることを確認できた。
最後にお片付けをして締める。
お片付け用のPlaybookはこちら。
``` handon_delete.yaml
---
- hosts: ansible_target
  gather_facts: false

  tasks:
    - name: delete directory
      ansible.builtin.file:
        path: ~/test_dir
        state: absent

    - name: delete file
      ansible.builtin.file:
        path: ~/test_file
        state: absent

    - name: delete user
      ansible.builtin.user:
        name: test_user
        uid: 1500
        comment: create by ansible
        state: absent
      become: true

    - name: uninstall apache2
      ansible.builtin.apt:
        name:
          - apache2
        state: absent
        purge: true
      become: true
```
各タスクのstateにabsentを指定する。
これにより指定されたディレクトリなどが存在する場合は削除される。
aptに関してはabsentだけだと完全に消えないのでpurgeまで指定しておいた。
実行して終わり。
``` ansible-playbook -i inventory.ini handon_delete.yaml ```

Ansibleの環境もいらないなら仮想環境を消してきれいさっぱり。
```rm -rf ~/venv/ansible```

以上。