Ansibleを使用したLinux構成管理 ～ 入門編

## はじめに
ここ数年で、ハイブリッド・クラウド、仮想化技術の利用が増えたことに伴い、稼働するLinux環境数も増えているかと思います。これらの環境を迅速に構築、構成管理を行うためには、Infrastructure as Code(以下、IaC)の利用が必要となってきます。
本記事では、主に初めてIaCに触れる方を対象に、IaCの概要、及び、代表的なIaCツールの1つであるAnsibleを使用した簡単なLinux構成管理例をご紹介します。

## IaCとは
IaCとは、ソフトウェア開発で用いられている手法を、インフラ基盤の構築にも応用しようということで始まったのがきっかけと言われています。

* 従来の構築方法
  * 紙ベースの手順書を見ながらコマンドを手動実行
  * 紙ベースの設計書を見ながらパラメータを手入力でカスタマイズ
  
この場合、以下のような問題が起きる可能性があります。
・コマンドの手動実行による人的ミスが発生し易くなる。
・パラメータ入力ミスにより、設計書の記載内容と実際のパラメータ値の整合性がとれなくなる。
その他、構築にも時間がかかりますし、間違いの元となります。

* IaCを使用する方法
従来の紙ベースの手順書や設計書で管理することはせず、これらの内容をコードとして管理します。コード管理することにより、以下のようなメリットが生まれます。
  * コードの中身と、作成した環境間の整合性がとれるようになる。
  * コードが変わらなければ、誰が環境構築しても同じ結果を得ることができるようになる。(冪等性による利点、属人性の廃止)
  * 同様の環境構築の際にも、コードを再利用することで、容易に複製しやすくなる。

## 代表的なIaCツール
Linux構成管理を行う代表的なIaCツールには、Ansibleが挙げられます。最近はあまり聞かなくなりましたが、以前はChefと呼ばれるIaCツールが有名でした。AnsibleとChefの違いについては、下記リンク先で紹介されています。

https://www.redhat.com/ja/topics/automation/ansible-vs-chef

違いを簡単に書くと、下記のようになります。
* Chef
  * 構成管理対象のサーバーに、専用のエージェントを導入する必要あり。
  * コードはrubyを使用して、Cookbookと呼ばれるファイルに記載する。
  * 多少なりともrubyの基本的な理解が必要。

* Ansible
  * 構成管理対象のサーバーに、専用のエージェントは不要。
  * コードは簡易なYAMLを使用して、Playbookと呼ばれるファイルに記載する。 

Ansibleが良く利用されるようになったのは、やはり、構成管理対象のサーバーにエージェントの導入が必要ないという点が大きいかなと思います。

## Ansibleを使用したLinux構成管理例
ここから、本題のAnsibleを使用したLinux構成管理の簡単な1例をご紹介します。

### 構成図
今回は、下記のように、AWSクラウド上で稼働している2つのLinux環境(Amazon Linux 2023)をAnsible Target(Ansibleで構成管理を行う対象のLinux)とします。
![構成管理図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/cf8c4bc5-2c5d-f8b5-06eb-d9c0bae2ee5f.png)

2つのAnsible TargetであるLinux環境のIPアドレスは下記とします。
・54.248.148.252
・18.183.196.52

また、Ansibleの処理命令を発行するAnsible Controllerには、Redhat Enterprise Linux V8.9を使用しています。

### Ansible Controller側の設定作業
まずは、Ansible ControllerとするLinuxに、「ansible」パッケージをインストールします。
```
$ sudo dnf install ansible
```
インストールが終わると、/etc/ansibleというディレクトリが作成され、その中に「ansible.cfg」、「hosts」という2つのファイルが作成されています。
なお、ansible 2.11以前は、このansible.cfgファイルがサンプルのAnsible構成ファイルの形で導入されていましたが、ansible 2.12以降のansible.cfgを見ると、下記のような記載がされています。
```
# Since Ansible 2.12 (core):
# To generate an example config file (a "disabled" one with all default settings, commented out):
#               $ ansible-config init --disabled > ansible.cfg
#
# Also you can now have a more complete file by including existing plugins:
# ansible-config init --disabled -t all > ansible.cfg

# For previous versions of Ansible you can check for examples in the 'stable' branches of each version
# Note that this file was always incomplete  and lagging changes to configuration settings

# for example, for 2.9: https://github.com/ansible/ansible/blob/stable-2.9/examples/ansible.cfg
```
ansible.cfgのサンプル定義は下記サイトで紹介されています。

https://github.com/ansible/ansible/blob/stable-2.9/examples/ansible.cfg

また、パラメータの詳細は、下記のAnsibleドキュメントで確認可能です。

https://docs.ansible.com/ansible/2.9_ja/index.html

#### Ansible構成定義ファイル(ansible.cfg)の作成

全てのパラメータのテンプレートは、上記にも記載されている下記のコマンドで作成することも可能です。この場合、全てのパラメータはコメントアウト、かつ、デフォルト値の設定で出力されます。
```
$ ansible-config init --disabled > ansible.cfg
```
今回は、下記のような構成ファイルとしました。
```cfg:ansible.cfg
[defaults]
ask_pass=False                     <== Playbook実行時にパスワードを要求させない。
inventory=/etc/ansible/hosts       <== Ansible Targetの情報を記載するファイルのパスを指定。
private_key_file=/home/testuser/.ssh/MyEC2Key.pem <== 管理対象ターゲットへのSSH接続に使用する秘密鍵ファイルのパスを指定
remote_port=22                     <== 接続先のポート番号を指定
remote_user=ec2-user               <== 接続先のログオンIDを指定

[privilege_escalation]
become=True                        <== ログオン後に特権ユーザー昇格する指定
become_ask_pass=False              <== 特権ユーザー昇格時にパスワードを要求させない。
become_method=sudo                 <== 特権ユーザー昇格時にsuduコマンドを実行する。
become_user=root                   <== rootを特権ユーザーとする。
```
Ansibleは、Ansible Targetへのアクセス時にSSH接続を使用します。また、AWSクラウド上のLinuxへのSSH接続にはパスワード認証ではなく、公開鍵認証方式でアクセスする必要があります。(AWSに限らず、AzureやGoogle Cloudも同様です。)
その為、Ansibleの処理命令を行う前に、管理対象のLinuxに公開鍵認証でのSSH接続が問題なくできることを事前に確認しておくことが必要になります。
SSHの公開鍵認証での接続方式については、下記の記事に纏めていますので、ご参考ください。

https://qiita.com/duelist2020jp/items/b7cf0bb8559b2c36b51b

#### Inventoryファイル(hosts)の作成
ansible.cfgファイル内に、Inventoryステートメントで指定したファイルがあったと思いますが、そこにAnsible Targetの情報を記載します。今回は、/etc/ansible/hostsファイルをInventoryファイルとしています。
```
[webservers]
54.248.148.252
18.183.196.52
```
対象のLinuxの情報については、ホスト名もしくはIPアドレスを指定します。ここでは、IPアドレスでの指定を行いました。

### Playbookの作成
ここでは実際にAnsible Targetに対して行う処理の内容をYAML形式で記載します。ファイルの拡張子は「.yaml」もしくは「.yml」としてください。今回は下記のような簡単な処理を行うPlaybookとしています。
```yaml:test.yaml
- hosts: webservers
  gather_facts: false
  become: yes

  tasks:

    - name: install the latest version of Apache
      yum:
        name: httpd
        state: latest
        use_backend: dnf

    - name: Start Service httpd, if not started
      service:
        name: httpd
        state: started

    - name: Enable Service httpd, if not enabled
      service:
        name: httpd
        enabled: yes
```
上記Playbookのサマリーは以下の通りです。
* 「hosts」パラメータには、Inventoryファイルに記載しているAnsible Targetのグループ名を指定します。今回は「webservers」になります。
:::note warn
「hosts」パラメータに「localhost」を指定すると、Ansibleの処理命令がAnsible Targetではなく、Ansible Controller自身に対して行われてしまいますので、ご注意ください。
:::
* 2つのAnsible Targetに対して、下記の設定を行う。
  * Webサーバーのパッケージ(httpd)をインストール
  * インストール後にhttpdサービスを起動
  * ブート時にhttpdサービスをスタートするように、「enabled」の指定を行う。

これらの処理を行うためには特権ユーザーに昇格する必要がある(rootユーザーになる)ので、「become」パラメータ値に「yes」を指定しています。

今回はシンプルなPlaybookなので、エディタは何を使用しても良いと思いますが、ロジックが長くなりそうであれば、VSCodeを使用して作成されることをお奨めします。それについては、下記の記事で纏めていますので、ご参考ください。

https://qiita.com/duelist2020jp/items/e4ec9bdd81e6f4d030f3


### Playbookの実行
Ansible Controller側で、最初にPlaybookに文法の誤りがないか否かを確認するために、下記のコマンドを実行します。
```
$ ansible-playbook test.yaml --syntax-check
```
何か構文ミスなどがある場合には、赤字で派手にエラーメッセージが出力されますので、修正します。問題なければ、下記のコマンドでPlaybookを実行します。
```
$ ansible-playbook test.yaml
```
コンソールには以下のような出力が行われます。
```
PLAY [webservers] **************************************************************

TASK [install the latest version of Apache] ************************************
[WARNING]: Platform linux on host 54.248.148.252 is using the discovered Python
interpreter at /usr/bin/python3.9, but future installation of another Python
interpreter could change the meaning of that path. See
https://docs.ansible.com/ansible-
core/2.14/reference_appendices/interpreter_discovery.html for more information.
changed: [54.248.148.252]
[WARNING]: Platform linux on host 18.183.196.52 is using the discovered Python
interpreter at /usr/bin/python3.9, but future installation of another Python
interpreter could change the meaning of that path. See
https://docs.ansible.com/ansible-
core/2.14/reference_appendices/interpreter_discovery.html for more information.
changed: [18.183.196.52]

TASK [Start Service httpd, if not started] *************************************
changed: [54.248.148.252]
changed: [18.183.196.52]

TASK [Enable Service httpd, if not enabled] ************************************
changed: [54.248.148.252]
changed: [18.183.196.52]

PLAY RECAP *********************************************************************
18.183.196.52              : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
54.248.148.252             : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
途中のWARNINGメッセージはAnsible Targetの環境にも依存する内容なので、ここでは特に気にする必要はありません。最終的に、「PLAY RECAP」の下に出力されている行が、各々のAnsible Targetに対する処理命令の結果を表示しており、これらが全て「OK」であれば問題ありません。エラーがある場合には、途中で赤字のエラーメッセージが出てきますので、それを参考にPlaybookの修正などを行います。

コンソールには表示されませんが、Ansible Targetでは内部的に下記のコマンドが実行されています。
```
$ sudo dnf install httpd
$ sudo systemctl start httpd
$ sudo systemctl enable httpd
```

### Ansible Targetの処理結果確認
実際に上記の処理が問題なく行われていることを、2つのAnsible TargetにSSH接続して確認します。下記の通り、いずれもWebサーバーのhttpdサービスはスタートしており、「enabled」の設定もされていることがわかります。(下記画面の緑色の部分を参照)
![web1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/42e6c5f0-e0dd-465d-a52a-b7345ae857c3.jpeg)
![web2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/c4c3ee9e-4c68-6c80-e872-d921e8c6b4e9.jpeg)

## さいごに
今回は、IaCに初めて触れる方を対象に、IaCの概要、及び、Ansibleを使用したLinuxの構成管理の1例をご紹介しました。Ansibleには様々なモジュールが提供されていますので、他にも色々な処理を行うPlaybookを書くことが可能です。
ただ、IaCに初めて挑戦される方は、いきなり複雑な処理をPlaybookで書こうとすると、上手く書けずに途中で挫折してしまうことになるかなと思います。ですので、まずは本記事でご紹介したような簡単な処理をPlaybookで書いてみて実行して、上手く稼働するかを確認してみるというのが良いかなと考えます。
Playbookの書き方として、roleベースの記述法や、プログラムのループ文のようなコーディングもできますので、それらについては、また別の機会にご紹介したいと思います。