AnsibleでSSM接続できない時に見るページ

## やりたいこと
AWSのEC2に対して、SSM経由でAnsibleを実行したい

## 前提
### ローカル環境
Mac OS（macOS Sequoiaのバージョン15.2）

### 現時点で用意したAnsibleのコード
```ansible.cfg
[defaults]
remote_user = ec2-user
host_key_checking = False

[inventory]
enable_plugins = aws_ec2
```

```aws_ec2.yml
plugin: aws_ec2

regions:
  - ap-northeast-1

filters:
  instance-state-name: running

keyed_groups:
  - key: tags.Name
    separator: ""

hostnames:
  - tag:Name
  - private-ip-address

compose:
  ansible_host: instance_id
  ```
  
  ```test.yml
  - hosts: test
  become: yes
  gather_facts: false
  vars:
    ansible_connection: aws_ssm
    ansible_aws_ssm_bucket_name: hogehoge-tfstate
  tasks:
    - name: install package
      hostname: 
        name: "{{ inventory_hostname }}"

    - name: install package
      dnf:
        name:
          - mysql
        state: present
        releasever: 9.5
```
### 現時点で整備済みの環境
- Ansibleの導入
- AWS CLIの導入
- AWS Session Managerプラグインの導入
- Pythonの導入
- ssm connectionプラグインの導入
SSM経由でEC2インスタンスに接続するためには、ssm connectionプラグインが必要です
このプラグインはAnsibleの"community.aws"コレクションの一部のため、下記コマンドでインストールを行います

```
ansible-galaxy collection install community.aws
```

なお、Ansibleをインストールした後にssm connectionプラグインをインストールしようとすると、下記のようなメッセージが出る場合があります

```
$ ansible-galaxy collection install community.aws
Starting galaxy collection install process
Nothing to do. All requested collections are already installed. If you want to reinstall them, consider using `--force`.
```

この場合、Ansible本体と合わせてcommunity.awsコレクションがインストールされているため、プラグインを改めてインストールする必要はありません

参考記事：[community.aws.aws_ssm connection – connect to EC2 instances via AWS Systems Manager](https://docs.ansible.com/ansible/latest/collections/community/aws/aws_ssm_connection.html)

- boto3のインストール
community.aws コレクションは内部で boto3 を使用しているため、下記コマンドでboto3とその基盤であるBotocoreをインストールします

```
pip3 install boto3 botocore
```

なお、上記コマンドの実行時に以下のエラーが出ることがあります

```
pip3 install boto3
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to
    install.

    If you wish to install a Python library that isn't in Homebrew,
    use a virtual environment:

    python3 -m venv path/to/venv
    source path/to/venv/bin/activate
    python3 -m pip install xyz

    If you wish to install a Python application that isn't in Homebrew,
    it may be easiest to use 'pipx install xyz', which will manage a
    virtual environment for you. You can install pipx with

    brew install pipx

    You may restore the old behavior of pip by passing
    the '--break-system-packages' flag to pip, or by adding
    'break-system-packages = true' to your pip.conf file. The latter
    will permanently disable this error.

    If you disable this error, we STRONGLY recommend that you additionally
    pass the '--user' flag to pip, or set 'user = true' in your pip.conf
    file. Failure to do this can result in a broken Homebrew installation.

    Read more about this behavior here: <https://peps.python.org/pep-0668/>

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
```
これはHomebrewでのPythonの管理と、MacのシステムでのPythonの管理が競合していることによるエラーなので、仮想環境を立てて対応しましょう

```
# 仮想環境で使用するディレクトリ名の作成
python3 -m venv <ディレクトリ名> 

# 仮想環境の有効化
source <ディレクトリ名>/bin/activate

# boto3とbotocoreのインストール
pip3 install boto3 botocore

# 仮想環境を終了する場合は下記コマンドを実行
deactivate
```

### Ansibleを実行してみると…
```
ansible-playbook -i inventory/aws_ec2.yml playbooks/test.yml
[WARNING]: Could not match supplied host pattern, ignoring: test

PLAY [test] ********************************************************************
skipping: no hosts matched

PLAY RECAP *********************************************************************
```
「"test"というホストが見つからない」とのことで、うまく動きません

そこで、まずは「実行対象が認識できるようになること」に定めてエラーを修正していきます

## コードの修正
先ほど「ホストが見つからない」と言われたので、ひとまずtest.ymlの"hosts"をインスタンス名に書き換えます

```test.yml
 - hosts: hogehoge.test
  become: yes
  gather_facts: false
  vars:
    ansible_connection: aws_ssm
    ansible_aws_ssm_bucket_name: hogehoge-tfstate
  tasks:
    - name: install package
      hostname: 
        name: "{{ inventory_hostname }}"

    - name: install package
      dnf:
        name:
          - mysql
        state: present
        releasever: 9.5
```
## 2回目の実行
```
ansible/ $ ansible-playbook -i inventory/aws_ec2.yml playbooks/test.yml
PLAY [hogehoge.test] **********************************

TASK [install package] *********************************************************
ERROR! A worker was found in a dead state
```

エラーが変わりました
このエラーはMac OSに関連する事象とのことなので、エラーが出たら以下の環境変数を一時的もしくは.bash(.zsh)ファイルに設定しておきましょう

```
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
```

## 3回目の実行
```
ansible/ $ ansible-playbook -i inventory/aws_ec2.yml playbooks/test.yml   

PLAY [hogehoge.test] **********************************************************************

TASK [install package] *********************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: botocore.errorfactory.TargetNotConnected: An error occurred (TargetNotConnected) when calling the StartSession operation: i-xxxxxxxxxxxxxxxx is not connected.
fatal: [hogehoge.test]: FAILED! => {"msg": "Unexpected failure during module execution: An error occurred (TargetNotConnected) when calling the StartSession operation: i-xxxxxxxxxxxxxxxx is not connected.", "stdout": ""}

PLAY RECAP *********************************************************************************************************
hogehoge.test : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
```

今度は「対象インスタンスにSSM接続できないよ〜」みたいなことを言われました
なので、今度は「SSM接続ができるようにすること」を目標にエラー修正をしていきましょう
ちなみに、私はここの修正でかなり沼りました

### SSMエージェント周りの確認
まず確認すべきは、そもそもSSM接続ができる環境が整っているか、の部分です

- インスタンスにSSM Agentがインストールされているか
    - この点については、ローカルからインスタンスにSSM接続をしてstatusを確認し、SSM Agentがインストールされていて、起動していることを確認しました
- 適切なIAMポリシーがインスタンスのIAMロールにアタッチされているか
    - この点についても、コンソール上で"AmazonSSMManagedInstanceCore"がアタッチされていることを確認しました

- SSM Agentのバージョンを確認する
    - 正直これはあまり関係なかったような気がしますが、念のため確認して、最新バージョンがインストールされているか確認しました

上記3点が問題ないことを確認して、私は途方に暮れました
…これでSSM接続できないの、おかしくね…？？？

この時、私の頭の中にはSSM接続に関係する設定が悪さをしている、という考えしか思い浮かばず、「SGが悪いのでは？」とか「SSM Agentの設定が悪いのでは？」と堂々巡りをしていました

## コードの見直し
AWS側の設定に問題ないとしたら、次に疑うべきはAnsibleのコードです
AnsibleのSSM connectionプラグインのページ([Parameters](https://docs.ansible.com/ansible/latest/collections/community/aws/aws_ssm_connection.html#parameters))を見てみると、SSMを使うための接続・認証情報として幾つかの変数が必要とのこと

自分のtest.ymlを見直すと、
- ansible_connection: aws_ssm(SSM経由でplaybookを実行するための変数)
- ansible_aws_ssm_bucket_name: hogehoge-tfstate(ファイル転送に使用されるS3バケットの名前)
は設定されていますが、その他は未設定でした

そこで、以下のようにコードを修正し、
- ansible_aws_ssm_region: ap-northeast-1(EC2 インスタンスが配置されているリージョン)
- ansible_user: ssm-user(接続ユーザーを"ssm-user"に設定)
の変数を追加しました
※アクセスキーとシークレットアクセスキー、セッショントークンは、今回別途取得して設定しているため記載なしです

```test.yml
- hosts: <Nameタグの値（インスタンス名）>
  become: yes
  gather_facts: false
  vars:
    ansible_connection: aws_ssm
    ansible_aws_ssm_bucket_name: hogehoge-tfstate
    ansible_aws_ssm_region: ap-northeast-1
    ansible_user: ssm-user
  tasks:
    - name: install package
      hostname: 
        name: "{{ inventory_hostname }}"

    - name: install package
      dnf:
        name:
          - mysql
        state: present
        releasever: 9.5
```
## 4回目の実行

```
ansible-playbook -i inventory/aws_ec2.yml playbooks/test.yml

PLAY [hogehoge.test] *********************************************************************************

TASK [install package] **********************************************************************************************************
[WARNING]: Platform linux on host hogehoge.test 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.18/reference_appendices/interpreter_discovery.html for more information.
changed: [hogehoge.test]

TASK [install package] **********************************************************************************************************
changed: [hogehoge.test]

TASK [ensure always running] ****************************************************************************************************
changed: [hogehoge.test]

PLAY RECAP **********************************************************************************************************************
hogehoge.test : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

```

何か言っているようですが、実行自体は通ったようです
とはいえ、[WARNING]の部分が気になるので、メッセージが出なくなるかやってみましょう

## 最終調整
調べてみると、このメッセージは、現在使用するPythonインタプリタとしてpython3.9が使用されており、もし別のPythonインタプリタをインストールすると、インタプリタのパスが変わって不具合が出るかもしれないよ、大丈夫？みたいな意味で出ているそうです（[Interpreter Discovery](https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html)）

対策としては、ansible.cfgに
```
interpreter_python=/usr/bin/python3.9
```
みたいに具体的なパスを指定するように記載するか、

```
interpreter_python = auto_silent
```
と記載すれば大丈夫です（私は後者を設定しました）

```ansible.cfg
[defaults]
remote_user = ec2-user
host_key_checking = False
interpreter_python = auto_silent

[inventory]
enable_plugins = aws_ec2
```

## 5回目の実行

```
ansible-playbook -i inventory/aws_ec2.yml playbooks/test.yml

PLAY [hogehoge.test] **********************************************************************************************************

TASK [install package] **********************************************************************************************************
changed: [hogehoge.test]

TASK [install package] **********************************************************************************************************
changed: [hogehoge.test]

TASK [ensure always running] ****************************************************************************************************
changed: [hogehoge.test]

PLAY RECAP **********************************************************************************************************************
hogehoge.test                  : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

問題なく動作しているようです！
これでSSM経由でのAnsibleの実行ができるようになりました

## まとめ
### SSM接続ができない＝設定が悪い、ではない
エラーメッセージを見るとついAWS側の設定（SSM Agent、IAMロールなど）が悪いのでは？？と思いがちですが、IaCツールを使っているときは「コードに問題があるのかも」という考えも持っておきましょう

### ローカル環境によるエラーに注意する
boto3導入時のエラーのように、個々人の環境ゆえのエラーもあるため、エラー修正や検索の際はどのような環境で実行しているかも含めて見るようにしましょう

### コードの書き方を工夫する
これはまとめというより個人的な反省点です
今回はスタートが「Ansibleがうまく動かないから治したい」だったため、「とりあえずエラーなく実行できるようになる」ことをゴールにして作業していました
そのため、inventoryに書いた方が良い変数をplaybookに書いてみたり、そもそもhostsの指定をインスタンス名にしたり、かなり不格好でよろしくない書き方になってしまいました
そのため、コードの書き方を工夫して、見やすく・手間なく・ミスしにくいコードにするように心がけましょう

以上です！！また今度！！