UserDataを実行しているサービス実体が分からず手こずった話

## はじめに
今回はAWS上でAuto Scalingを用いたPostfixインスタンスの冗長化および自動復旧を行うにあたり、EC2のUserDataの起動順序を制御したかったのですが、UserDataを実行しているサービス実体が分からず手こずったので、実際の事象を含めて共有しようと思います。

## 目次
[UserData実行のサービス実体(結論)](#userdata実行のサービス実体結論)
[背景](#背景)
[検証事前準備](#検証事前準備)
[検証内容](#検証内容)
[注意点](#注意点)
[まとめ](#まとめ)

## UserData実行のサービス実体(結論)
まず、事象説明等の前に結果を知りたい人もいるかと思いますので、結果から記載しようと思います。
結論としては下記になります。

**UserData実行のサービス実体：<font color="red">`cloud-final.service`</font>**

文章化してみると何でもないように思えますが、私はこれでずいぶん苦労しました。

:::note
info
AWSサポートに問い合わせた際、最初は「UserDataの起動はcloud-init.serviceにて行っています」という旨の回答があったため、UserDataの実態はAWS内部でもあまりメジャーではない(?)のかもしれないです。
:::


## 背景
そもそもどうしてUserDataの起動順序の制御が必要になったかいうと、Postfixのキューディレクトリを保管しておきたかったのですが、その際に下記のような経緯がありました。

1. インスタンスの自動復旧のためにAuto Scalingグループを設定し、それに対してELBのヘルスチェックを有効にする
2. 異常なインスタンスが発生した際に対象のインスタンスを終了し、起動テンプレートで新しいインスタンスに置き換える
3. EC2インスタンス上にメールのキューディレクトリが存在すると、`2`のようにインスタンスの置き換えが行われた際に旧インスタンスと同時に削除されてしまう
4. `3`のようにPostfixのキューディレクトリが削除されてしまう状況を回避するため、EFS上にキューディレクトリを置く構成を検討

その際にUserDataを用いてPostfixのキューディレクトリをEFS上にコピーして、シンボリックリンクを作成し、PostfixにEFS上のキューディレクトリを参照させることで、実現することになりました。

そしてそのために、Postfixの起動前にUserDataを実行する必要がありました。


## 検証事前準備
:::note
info
- VPCやSG、IAMなどは適切に設定してある前提とします
- Auto Scalingや起動テンプレートの作成手順等は主題とずれるため省略します
:::
実施した環境は以下の通りです
- EC2：RHEL8
- マウントヘルパー：amazon-efs-utils 1.33.3
- Postfix：3.5.8


## 検証内容
①EFSを作成
※EFS用のセキュリティグループを設定する必要があります
https://docs.aws.amazon.com/ja_jp/efs/latest/ug/accessing-fs-create-security-groups.html

②起動テンプレートに使用するAMI用のEC2を構築
以下の設定を行う
- amazon-efs-utilsのインストール（下記参照）
https://docs.aws.amazon.com/ja_jp/efs/latest/ug/installing-amazon-efs-utils.html#installing-other-distro

  - gitコマンドのインストール
   ```
   $ sudo yum -y install git
   ```
  - amazon-efs-utilsクローンを作成
   ```
   $ git clone https://github.com/aws/efs-utils
   ```
  - (上記のコマンドを実施した場合は)カレントディレクトリにcloneしたefs-utilsに移動

   ```
   $ cd efs-utils
   ```
  - makeコマンドのインストール
   ```
   $ sudo yum -y install make
   ```
  - rpm-buildパッケージのインストール
   ```
   $ sudo yum -y install rpm-build
   ```
  - amazon-efs-utilsパッケージをビルド
   ```
   $ sudo make rpm
   ```
  - ビルドしたamazon-efs-utilsパッケージをインストール
   ```
   $ sudo yum -y install ./build/amazon-efs-utils*rpm
   ```
<br>

 - Postfixのインストール（main.cfなどの設定は必要に応じて適宜実施）

   - Postfixインストール
   ```
   $ sudo yum install -y postfix
   ```
   - キューディレクトリの名前変更(のちほどシンボリックリンクで/var/spool/postfixとして紐づけるため)
   ```
   $ sudo mv /var/spool/postfix /var/spool/postfix_org   
   ```
<br>

 - 自動マウントの設定
   - マウント用のディレクトリをLinux上に作成
   ```
   $ sudo mkdir /Mail
   ```
   - /etc/fstabに自動マウントに必要な情報を追記する
   ```
   $ sudo vi /etc/fstab    #/etc/fstabに下記を追記
   fs-xxxxxxxxxx:                   /Mail                   efs     defaults,_netdev      0 0
   ```

※`fs-xxxxxxxxxx`はEFSのコンソールの`ファイルシステムID`で確認する（下記参照）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693493/a3daecc8-fc70-1ccc-a475-cd3ecff8359b.png)


 - systemdの依存関係設定 ★重要（今回の主題）
```
$ sudo systemctl edit postfix   #下記を追記する
[Unit]
After=remote-fs.target cloud-final.service
 ```
:::note
info
- `remote-fs.target`は`/Mail`にEFSをマウントしている
- `cloud-final.service`はUserDataを実施している
:::

③②で作成したEC2のAMIを取得する

④起動テンプレートで下記を設定
- ③で取得したAMI
- UserDataに下記のスクリプトを記載
```
#!/bin/bash
###EC2のインスタンスID取得
INSTANCEID=`curl http://169.254.169.254/latest/meta-data/instance-id` 
###キューディレクトリをEFS上にコピー
cp -ipR /var/spool/postfix_org /Mail/${INSTANCEID} 
###デフォルトキューディレクトリにシンボリックリンクがあれば削除
if [ -L /var/spool/postfix ]; then rm -f /var/spool/postfix;fi 
###デフォルトキューディレクトリをEFS上のキューディレクトリに紐づける
ln -s /Mail/${INSTANCEID} /var/spool/postfix
```

⑤Auto Scalingグループを設定（詳細は下記参照）
https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/create-asg-launch-template.html

⑥正常性確認を行う
- Postfixが正常に起動していること
- EFSが`/Mail`にマウントしてること
- キューディレクトリがEFS上(マウントしている`/Mail`上)に存在すること
- PostfixのPIDファイルが`/Mail/${Instance ID}`に存在すること


## 注意点

`cloud-final.service`（UserData実行のサービス実体）をPostfix自動起動の前に起動しないと以下のようなエラーが発生し、Postfixが正常に起動しない場合があります。

```
[/var/log/maillog]
fatal: the Postfix mail system is already running
```
このエラーは/var/spool/postfixのシンボリックリンク参照先を書き換える前にPostfixが起動することで、AMI取得時の参照先を確認しに行き、すでにキューディレクトリにPIDファイルが存在するために起こるエラーでした。


## まとめ
PostfixのキューディレクトリをEFS上に退避させること自体は一見簡単なように見えましたが、systemdの依存関係やsystemdのservice単位で何が実体なのかよくわからなかったために、今回は嵌ってしまいました。
しかしこのエラーを通じて、PIDの作成状況やログの内容からトラブル原因を探りつつ、AWSサポートと連携して何とか事象解決まで持っていけたので、良い経験になりました。