AnsibleでTomcatコンテナをデプロイしてみる

# Ansibleとは
Ansibleは構成管理とOSやソフトウェアの設定作業を自動化するツールです。
Ansibleサーバから管理対象サーバでSSH接続して、デプロイを行います。
デプロイにはPlaybookというYAML形式の指示ファイルを使用します。

# 今回構成する環境
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/991082/ab5aa59a-1c3b-f2b7-976c-b5b1231be4d6.png)

大まかな流れは以下の流れで行います。
①Ansibleのインストール
②キーペアの作成、配布
③インベントリーファイルの作成
④Dockerfileの作成
⑤Playbookの作成
⑥Playbookの実行
⑦動作確認

サーバ環境は以下の通りです。
Ansibleサーバ：RHEL8.2
管理対象サーバ：RHEL8.2

管理ユーザはrootを使用せず一般ユーザを使用します。
なお、RHEL8なので、コンテナエンジンはDockerではなくPodmanを使用します。

# Ansibleのインストール
AnsibleはPython製のアプリケーションであり、インストールはdnf(yum)ではなくpipで行います。
Pythonは2系と3系どちらも使用可能ですが、今回は3系でインストールしたいと思います。

pipのインストールを行います。

```
$ sudo yum install python3-pip
```

インストールしたpipでAnsibleのインストールを行います。
Ansible使用ユーザーを一般ユーザ(ec2-user)にするために、--userオプションを使用します。

```
$ pip3 install ansible --user
```

```
$ ansible --version
ansible 2.10.5
  config file = None
  configured module search path = ['/home/ec2-user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/ec2-user/.local/lib/python3.6/site-packages/ansible
  executable location = /home/ec2-user/.local/bin/ansible
  python version = 3.6.8 (default, Aug 18 2020, 08:33:21) [GCC 8.3.1 20191121 (Red Hat 8.3.1-5)]
```

無事インストールできました。

# キーペアの作成、配布
AnsibleはSSH通信でデプロイを行うため、あらかじめパスワードなしでSSH通信できるように管理対象サーバに対して公開鍵の配布を行っておきます。

まず、ssh-keygenコマンドでキーペアを作成します。

```
$ ssh-keygen -t rsa
```

秘密鍵id_rsaと公開鍵id_rsa.pubが生成されます。

```
$ ls -l /home/ec2-user/.ssh/
total 12
-rw-------. 1 ec2-user ec2-user  388 Feb 11 04:11 authorized_keys
-rw-------. 1 ec2-user ec2-user 2655 Feb 11 04:19 id_rsa
-rw-r--r--. 1 ec2-user ec2-user  611 Feb 11 04:19 id_rsa.pub
```

公開鍵を管理対象サーバに追加します。
管理対象サーバに対してパスワードありのSSH通信ができればSCPやSSHで配布しても良いのですが、今回はEC2インスタンスを作成したばかりの状態なので構築時にダウンロードしたプライベートキーファイルを使用したSSH通信しかできない状態です。
プライベートキーファイルをEC2インスタンス上に配置するのはセキュリティ的に好ましくないと思い、直接管理対象サーバにTeratermで接続し、先ほど作成したid_rsaの内容を直接authorized_keysに追加することにしました。
AutoScalingなどによるデプロイの完全自動化を考える場合は、この辺はよく考えないといけないですね。
(EC2インスタンス起動時にS3に保存した公開鍵をダウンロードさせる、などの方法を検討するのが良いと思います。)

今回は公開鍵の手動登録の手順は省略します。
なお、公開鍵の登録後は一度SSH接続を行うなどして、known_hostsの更新をしてください。
known_hostsの登録を行わないと、ansibleコマンドの実行時にfingerprintの登録が発生することによって処理が失敗してしまうようです。

# インベントリーファイルの作成
Ansibleの管理対象サーバを定義するためのインベントリーファイルを作成します。

```
$ mkdir /home/ec2-user/ansible
$ cd /home/ec2-user/ansible
```

hostsという名前のインベントリーファイルを作成します。
内容は以下のようにしました。

```
[tomcatservers]
172.31.44.229
172.31.34.31
```
172.31.XX.XXは管理対象サーバのプライベートIPアドレスです。
2つの管理対象サーバをtomcatserversというグループに所属させました。

インベントリーファイルの作成がうまくいったことを確認するために、Ansibleのpingモジュールを実行してみます。

```
$ ansible -u ec2-user -i /home/ec2-user/ansible/hosts tomcatservers -m ping
172.31.34.31 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
172.31.44.229 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
```
"pong"のレスポンスが返れば通信成功です。

# Dockerfileの作成

次に管理対象サーバに配布するDockerfileを作成します。
今回Dockerfileを作成するのは、コンテナ起動時にsample.warをダウンロードさせるためです。

作成するDockerfileの内容は以下の通り。

```
FROM docker.io/library/tomcat
RUN cd /usr/local/tomcat/webapps && wget http://tomcat.apache.org/tomcat-8.5-doc/appdev/sample/sample.war
```

# Playbookの作成

Playbookで定義するタスクは以下の通りです。
①podmanのインストール
②Dockerfileの配布
③Dockerイメージのビルド
④Dockerコンテナの起動

Dockerコンテナの起動時、Dockerfileで定義した通りサンプルアプリのダウンロードが行われるため、アプリケーションのビルドも実行されます。

具体的なPlaybook(tomcat.yml)の内容は以下の通り。

```
- hosts: tomcatservers
  remote_user: ec2-user

  tasks:
  - name: install podman
    yum:
      name: podman
      state: present
    become: yes
  - name: copy Dockerfile
    copy:
      src: /home/ec2-user/ansible/Dockerfile
      dest: /home/ec2-user
  - name: build image
    command: podman build -t tomcat:1 /home/ec2-user
    notify:
    - run container
  handlers:
  - name: run container
    command: podman run -d -p 8081:8080 tomcat:1
```

podmanのインストールのみ管理者権限で実行するため「become: yes」を指定しています。(sudo実行に相当)

# Playbookの実行

作成したPlaybookを実行します。

```
$ ansible-playbook -i /home/ec2-user/ansible/hosts /home/ec2-user/ansible/tomcat.yml

PLAY [tomcatservers] ***************************************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************************
ok: [172.31.34.31]
ok: [172.31.44.229]

TASK [install podman] **************************************************************************************************************************************************
changed: [172.31.34.31]
changed: [172.31.44.229]

TASK [copy Dockerfile] *************************************************************************************************************************************************
changed: [172.31.34.31]
changed: [172.31.44.229]

TASK [build image] *****************************************************************************************************************************************************
changed: [172.31.34.31]
changed: [172.31.44.229]

RUNNING HANDLER [run container] ****************************************************************************************************************************************
changed: [172.31.34.31]
changed: [172.31.44.229]

PLAY RECAP *************************************************************************************************************************************************************
172.31.34.31               : ok=5    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
172.31.44.229              : ok=5    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

# 動作確認

Ansibleサーバからcurlコマンドでsampleアプリを実行してみます。
AWSのセキュリティグループで8081ポートを許可するのを忘れずに。

```
$ curl -XGET http://172.31.34.31:8081/sample/
<html>
<head>
<title>Sample "Hello, World" Application</title>
</head>
<body bgcolor=white>

<table border="0">
<tr>
<td>
<img src="images/tomcat.gif">
</td>
<td>
<h1>Sample "Hello, World" Application</h1>
<p>This is the home page for a sample application used to illustrate the
source directory organization of a web application utilizing the principles
outlined in the Application Developer's Guide.
</td>
</tr>
</table>

<p>To prove that they work, you can execute either of the following links:
<ul>
<li>To a <a href="hello.jsp">JSP page</a>.
<li>To a <a href="hello">servlet</a>.
</ul>

</body>
</html>
$ curl -XGET http://172.31.44.229:8081/sample/
<html>
<head>
<title>Sample "Hello, World" Application</title>
</head>
<body bgcolor=white>

<table border="0">
<tr>
<td>
<img src="images/tomcat.gif">
</td>
<td>
<h1>Sample "Hello, World" Application</h1>
<p>This is the home page for a sample application used to illustrate the
source directory organization of a web application utilizing the principles
outlined in the Application Developer's Guide.
</td>
</tr>
</table>

<p>To prove that they work, you can execute either of the following links:
<ul>
<li>To a <a href="hello.jsp">JSP page</a>.
<li>To a <a href="hello">servlet</a>.
</ul>

</body>
</html>
```

無事sampleアプリが実行されたことが確認できました。