今さらAnsibleに入門してみた（EC2でハンズオン）

パブリッククラウド全盛の現代ですが、OSレイヤーの構築自動化や構成管理用途でAnsible使われてる方は結構いるんじゃないでしょうか。

私もちゃんと勉強したことなかったので、Kindle Unlimitedでこの本を読みつつ、手元のAWS環境でハンズオンもしてみました。

https://www.amazon.co.jp/Ansible%E3%82%AF%E3%83%83%E3%82%AF%E3%83%96%E3%83%83%E3%82%AF-impress-top-gear%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E5%A4%A7%E5%B6%8B-ebook/dp/B09V4HW2LW

# Ansibleとは

- Python製のIT自動化ツール
- OSSだが、Red Hat社が「Red Hat Ansible Automation Platform」としてサポートを提供している
- 自動化対象のノードにエージェントのインストールが不要。SSH接続できてPythonが利用できればOK

https://github.com/ansible/ansible

### 基本概念

- 自動化したい内容を「プレイブック」としてYAMLで定義する
- 自動化可能な処理は「モジュール」として提供されている。モジュールは日々追加開発されている

私はもともと、AnsibleってシェルスクリプトのようにOSコマンドを羅列して自動化定義するものかとイメージしていたので、実際のプレイブックのサンプルを見て少し驚きました。

### 配布形態

リリースサイクル短縮のため、本体と追加機能が別パッケージになっている。

- ansible-core：本体＋基本モジュール
- コレクション：その他のモジュールやプラグイン
    - Ansible Community Packageを利用すれば、本体＋推奨コレクションをまとめてインストールできる

### 構成

- コントロールノード：Ansibleをインストールするノード
    - Python 2.7以上 or 3.5以上が入っていること
- マネージドノード：自動化対象のノード
    - Python 2.6以上 or 3.5以上が入っていること
    - SSH接続ができること
    - SFTP or SCPでファイル転送ができること

Ansibleのコンポーネントは以下。

- モジュール
    - 特定の処理が作り込まれた機能の単位のこと
    - パラメータを指定することで詳細を定義する
    - 冪等性が考慮されているため、現在の状態を確認して分岐…などの考慮は不要
- インベントリ
    - 自動化対象ノードの一覧。INI or YAMLで記述する
    - ホストをまとめるグループも定義できる
    - ホストやグループ内で共用できる「インベントリ変数」を定義できる
- プレイブック
    - マネージドノードへの処理をYAMLで記述する
    - いくつかのセクションに分かれている
        - hosts：対象ホストやグループを指定する
        - vars：プレイ内で利用する変数を定義する
        - tasks：処理内容をモジュール＋パラメータで指定する
    - ファイル内で上から順にタスク処理される。繰り返しや条件制御も可能
    - `ansible-playbook` コマンドで実行できる
- プラグイン
    - 補完機能。いくつかの種別がある
        - コネクションプラグイン：マネージドノードへの接続に関するプラグイン群
        - コールバックプラグイン：プレイブック実行結果の出力に関するプラグイン群
- 設定ファイル（ansible.cfg）
    - INIで記述する。いくつかのセクションに分かれる
    - 配置可能な場所がいくつかある。どこにも無ければデフォルト設定で動作する
    - `ansible-config` コマンドで設定状況を確認できる
- コレクション
    - ansible-core に含まれないモジュール、プラグイン、プレイブックの管理単位
    - FQCNという名前空間で指定する
    - Ansible Galaxyというサイトで配布されている
    - `ansible-galaxy` コマンドでコレクション確認やインストールができる
    - `requirements.yml` を記述して一括インストールも可能


# 実際に触ってみた

AWSでサーバーを立てて、Ansibleを使ってみることにします。
サーバーワークスさんのこちらの記事を参考にさせていただきました。

https://blog.serverworks.co.jp/tech/2019/09/09/post-73961/

### 環境準備

- VPCを作成
    - マルチAZ（利用するのは片方のAZのみ）
    - パブリックサブネットにEC2を作成
        - ansible-controller：コントロールノード
    - プライベートサブネットにEC2を作成
        - ansible-managed：マネージドノード

コントロールノードにAnsibleをインストール
※Amazon Linux 2ではExtrasで有効化するとyumから利用できる（[参考記事](https://qiita.com/1_ta/items/92dcfa6fa2a33cb11442)）

```bash
$ sudo amazon-linux-extras enable ansible2
$ sudo yum install -y ansible
```

インストールされたことを確認

```bash
$ ansible --version
ansible 2.9.23
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/ec2-user/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.18 (default, May 25 2022, 14:30:51) [GCC 7.3.1 20180712 (Red Hat 7.3.1-15)]
```

`etc/ansible` 配下にインベントリおよび設定ファイルがあるようです。

```bash
$ ls /etc/ansible
ansible.cfg  hosts  roles
```

nanoエディターでhostsを開き、インベントリーにマネージドノードのIPアドレスを追加してみます。

```bash
$ sudo nano /etc/ansible/hosts
```
![スクリーンショット 2022-12-24 14.44.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/fe5a7c27-ebf5-7264-48b9-74d1e752fd77.png)


### プレイブック作成＆SSH接続

Apacheを起動するだけの簡単なプレイブックを作成してみます。

```bash
$ nano playbook.yml
```

ファイル内には以下を記述。Tabを使うとエラーになるため注意！

```yaml:playbook.yml
---
- hosts: 10.0.141.23

  tasks:
    -  name: start httpd
       ansible.builtin.systemd:
         name: httpd
         state: started
```

試しに一度、このプレイブックを実行してみます。

```bash
$ ansible-playbook playbook.yml 

PLAY [10.0.141.23] *************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************
The authenticity of host '10.0.141.23 (10.0.141.23)' can't be established.
ECDSA key fingerprint is SHA256:Ul7RSyNiC5QWSEF0ov88qucD1OQMs303BY7oeCLjBQI.
ECDSA key fingerprint is MD5:28:57:5b:9b:89:32:3e:d6:fc:92:ed:42:94:e9:12:9e.
Are you sure you want to continue connecting (yes/no)? yes
fatal: [10.0.141.23]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Warning: Permanently added '10.0.141.23' (ECDSA) to the list of known hosts.\r\nPermission denied (publickey,gssapi-keyex,gssapi-with-mic).", "unreachable": true}

PLAY RECAP *********************************************************************************************************
10.0.141.23                : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0
```

エラーになりました。
Ansibleコントローラー → マネージドノードへのSSH接続で認証失敗しているようですね。

それでは、マネージドノードのSSH秘密鍵をAnsibleコントローラーへ配置します。
まずSCPで作業用MacからコントローラーEC2のホームディレクトリーへ秘密鍵をアップロードします。

```zsh
scp -i "onda-ansible.pem" "onda-ansible.pem" ec2-user@ec2-***-***-***-***.ap-east-1.compute.amazonaws.com:/home/ec2-user
```

一度、AnsibleコントローラーからマネージドノードへSSH接続を試しておきます。
また、Playbookの実行前にApacheの起動状態も確認しておきます。

```bash
$ ssh -i "onda-ansible.pem" ec2-user@10.0.141.23

$ systemctl status httpd
Unit httpd.service could not be found.
```

そもそもApacheがインストールされていなさそうなことが確認できました。


### プレイブック実行＆トラブルシューティング

それでは、先ほどの秘密鍵を指定して再度Playbookを実行します。
ユーザーは指定していませんが、SSH接続元のコントローラーEC2で利用している `ec2-user` が利用されるため結果的に問題ありません。

```bash
$ ansible-playbook playbook.yml --private-key onda-ansible.pem

PLAY [10.0.141.23] *************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************
[WARNING]: Platform linux on host 10.0.141.23 is using the discovered Python interpreter at /usr/bin/python, but
future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
ok: [10.0.141.23]

TASK [start httpd] *************************************************************************************************
fatal: [10.0.141.23]: FAILED! => {"changed": false, "msg": "Could not find the requested service httpd: host"}

PLAY RECAP *********************************************************************************************************
10.0.141.23                : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0  
```

今度は実行成功しました！
しかしApacheがインストールされていないので、 `failed` で終了してしまいました。流石にインストールまではやってくれないですよね。

それでは再度マネージドノードにSSHログインし、Apacheをインストールします。

```bash
$ sudo yum install httpd

$ systemctl status httpd #起動状態の確認
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:httpd.service(8)
```

httpdがインストールされ、起動していないことまで確認できました。
それでは再度Ansibleコントローラーへ戻り、先ほどのプレイブックを実行します。

```bash
$ ansible-playbook playbook.yml --private-key onda-ansible.pem 

PLAY [10.0.141.23] *************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************
[WARNING]: Platform linux on host 10.0.141.23 is using the discovered Python interpreter at /usr/bin/python, but
future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
ok: [10.0.141.23]

TASK [start httpd] *************************************************************************************************
fatal: [10.0.141.23]: FAILED! => {"changed": false, "msg": "Unable to start service httpd: Failed to start httpd.service: The name org.freedesktop.PolicyKit1 was not provided by any .service files\nSee system logs and 'systemctl status httpd.service' for details.\n"}

PLAY RECAP *********************************************************************************************************
10.0.141.23                : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0 
```

今度は別のエラーでApache起動失敗です。
ググるとPythonインタープリターの指定で改善しそうなので、 `-e` オプションを追加してみます。

https://www.84kure.com/blog/2022/05/04/ansible-e%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A7ansible_python_interpreter%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B/

```bash
$ sudo ansible-playbook playbook.yml --private-key onda-ansible.pem -e ansible_python_interpreter=/usr/bin/python3

PLAY [10.0.141.23] *************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************
[WARNING]: sftp transfer mechanism failed on [10.0.141.23]. Use ANSIBLE_DEBUG=1 to see detailed information
[WARNING]: scp transfer mechanism failed on [10.0.141.23]. Use ANSIBLE_DEBUG=1 to see detailed information
fatal: [10.0.141.23]: FAILED! => {"ansible_facts": {}, "changed": false, "failed_modules": {"setup": {"failed": true, "module_stderr": "Shared connection to 10.0.141.23 closed.\r\n", "module_stdout": "Please login as the user \"ec2-user\" rather than the user \"root\".\r\n\r\n", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 0}}, "msg": "The following modules failed to execute: setup\n"}

PLAY RECAP *********************************************************************************************************
10.0.141.23                : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
```

メッセージを見ると「rootではなくec2-userで実行せよ」的なことが書いてありますね。
それでは `sudo` を外して実行してみます。

```bash
$ ansible-playbook playbook.yml --private-key onda-ansible.pem -e ansible_python_interpreter=/usr/bin/python3

PLAY [10.0.141.23] *************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************
ok: [10.0.141.23]

TASK [start httpd] *************************************************************************************************
fatal: [10.0.141.23]: FAILED! => {"changed": false, "msg": "Unable to start service httpd: Failed to start httpd.service: The name org.freedesktop.PolicyKit1 was not provided by any .service files\nSee system logs and 'systemctl status httpd.service' for details.\n"}

PLAY RECAP *********************************************************************************************************
10.0.141.23                : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
```

また権限エラーらしき状態に戻りました。
そろそろホリデーエンジニアの週末カフェタイムが終わってしまうので、残念ですがトラシューはここまでとします。


# まとめ
綺麗にエラー解消できなかったのが残念ですが、Ansibleの概念や実装方法がかなりイメージ湧きました。
やっぱり実際に触ってみるのが大事ですね！