AWS Systems Managerによるオンプレミスのリモートサポート

# オンプレミス エッジデバイス サポート

モチベーション: `顧客に送ったエッジデバイスのドライバとかを一括でアップデートしたい`

## やりたいこと

- 顧客ネットワーク上のオンプレミスサーバーの管理
- Ansibleによる一括管理
- プロキシ環境サポート
- リモートシェルサポート

## やったこと

AWS System Manager Session Manager を使って、自宅外から自宅内のデバイスをAnsibleで一括アプリバージョン更新。

## 検証

やったことの詳細を書いていく。

### 準備物

- Mac x 1 (ラップトップ (Ansible実行) PC)
- Raspberry Pi x 1 (管理対象エッジデバイス1)
- Jetson Nano x 1 (管理対象エッジデバイス2)

### 手順

基本的には [公式のSystems Managerセットアップ](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-setting-up.html) を行ったのみ。

#### 0. (コンソール) AWSへの登録

AWSを使ったことがない場合は、まずはAWSへのサインアップから。

#### 1. (ラップトップ) Macに AWS CLI導入

AWS CLIを使ったことがない場合は、まずはMacへのCLI導入。[公式](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-mac.html)からpkgファイルをダウンロードし実行するだけ。

#### 2. (コンソール) Systems Manager用のIAMユーザーグループ作成

<設定セクション: AWS IAM - グループ>

[公式手順](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/setup-create-users-nonadmin-groups.html) に従い、[IAM コンソール](https://console.aws.amazon.com/iam/) にて、IAMユーザーグループ `SSMUserGroup` を作成する。

#### 3. (コンソール) Systems Manager用のIAMユーザー作成

<設定セクション: AWS IAM - ユーザー>

[公式手順](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/setup-create-users-nonadmin-users.html) に従い、[IAM コンソール](https://console.aws.amazon.com/iam/) にて、IAMユーザー `SSMUser` を作成する。★ `Access Key`　と `Secret Access Key` をメモしておく。

* 注意1: 手順2で作ったユーザーグループに所属させること
* 注意2: [Programmatic access (プログラムによるアクセス)] をチェックすること

#### 4. (ラップトップ)　IAM権限をMacのAWS CLIで登録

以下の通り、手順３でメモした情報を登録する。

```shell
$ aws configure
AWS Access Key ID [None]: ???
AWS Secret Access Key [None]: ???
Default region name [None]: us-east-2
Default output format [None]: JSON
```

#### 5. (コンソール) Systems Managerのアクティベーションを作成する

<設定セクション: AWS Systems Manager - ハイブリッドアクティベーション>

[公式手順](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-managed-instance-activation.html) に従い、[Systems Manager コンソール](https://console.aws.amazon.com/systems-manager/) にて、アクティベーションを作成する。★ 作成される `activation Code`, `activation ID`をメモしておく。また、

* 注意: [Use the system created default command execution role] (システムの作成したデフォルトコマンド実行ロールを使用) を選択したので良い。

#### 6. (デバイス) SSM エージェントをインストール

[公式手順](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-install-managed-linux.html) に従い、エージェントをインストールする。Raspberry Piは以下のように設定する。手順5でメモしたactivation-code, activation-idがちゃんと設定されるように注意。

```shell
$ mkdir /tmp/ssm
$ sudo curl https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/debian_arm/amazon-ssm-agent.deb -o /tmp/ssm/amazon-ssm-agent.deb
$ sudo dpkg -i /tmp/ssm/amazon-ssm-agent.deb
$ sudo service amazon-ssm-agent stop
# 手順5でメモしたactivation-code, activation-idを設定する
$ sudo -E amazon-ssm-agent -register -code "activation-code" -id "activation-id" -region "region" 
$ sudo service amazon-ssm-agent start
```

プロキシ環境の場合は、[公式手順](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-install-managed-linux.html) に従い、以下のように設定しておく。

```shell
# HTTP プロキシサーバーの場合
export http_proxy=http://hostname:port
export https_proxy=http://hostname:port
# HTTPS プロキシサーバの場合
export http_proxy=http://hostname:port
export https_proxy=https://hostname:port
```

#### 7. (コンソール) advanced-instances tier へ変更

[Systems Manager コンソール](https://console.aws.amazon.com/systems-manager/) の `フリートマネージャー` の `設定` から、 `
アカウント設定の変更` を選択し、`高度なインスタンス` へ変更する。

#### 8. (コンソール) インスタンスID を確認する

手順6まで完了すると、 [Systems Manager コンソール](https://console.aws.amazon.com/systems-manager/) の `フリートマネージャー` に手順６でエージェントを起動したデバイスが接続されるインスタンスが表示される。★ `mi-???` というインスタンスIDが表示されているのでメモする。また、この時点でリモートシェルが可能。インスタンスIDをクリックし、`インスタンスアクション` -> `セッションを開始` にて、リモートシェル実行され、コマンド実行可能になっている。

#### 9. (ラップトップ) Macに Session Manager プラグインをインストール

[公式の手順](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-macos) に従い、インストールする。

```shell
$ curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"
$ unzip sessionmanager-bundle.zip
$ sudo ./sessionmanager-bundle/install -i /usr/local/sessionmanagerplugin -b /usr/local/bin/session-manager-plugin
```

#### 10. (ラップトップ) セッション開始

`target` に手順8でメモした `インスタンスID` を設定し、セッションを開始する。この場合はラップトップの9090ポートとsshの22ポートをポートフォワーディングしている。`Waiting for connections` となったら完了。

```shell
$ aws ssm start-session \
 --target mi-??? \
 --document-name AWS-StartPortForwardingSession \
 --parameters '{"portNumber":["22"],"localPortNumber":["9090"]}'

Starting session with SessionId: SSMUser-???
Port 9090 opened for sessionId SSMUser-???.
Waiting for connections...
```

また、ラズベリーパイは自宅のセンサと繋がり、port3000でGrafanaを表示しているため、port3000でポートフォワーディングすると外部から自宅センサのGrafanaが見えたりする運用でも使える。

![スクリーンショット 2021-09-05 2.29.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242768/01877ab4-ec41-4b9e-eff6-a9be31cf629f.png)

#### 11. (ラップトップ+デバイス) Ansibleで `sl` パッケージをインストールしてみる

対象エッジデバイスに `sl` パッケージがないことを確認

```shell
pi@raspberrypi:~ $ sl
-bash: sl: command not found
```

以下のようにinventoryを設定する

```
[testServer1]
localhost

[testServer1:vars]
ansible_port=9090
ansible_ssh_user=pi
ansible_ssh_pass=???
ansible_sudo_pass=???
```
以下のような簡単なymlを作成

```yaml
- hosts: all
  become: true
  gather_facts: no
  tasks:

    - name: Ansible動作開始
      debug:
        msg: "これから自動更新作業を開始します。"

    - name: 必要なパッケージをインストールします (apt)
      apt:
        name:
          - sl
        state: present

    - name: Ansible動作終了
      debug:
        msg: "対象ノードにて自動更新作業が終了しました。"
```

実行してみる

```shell
$ sudo ansible-playbook -vvv -i inventories/hosts site.yml
PLAYBOOK: site.yml **************************************************************************************************************************************************************
1 plays in site.yml

PLAY [all] **********************************************************************************************************************************************************************
META: ran handlers

TASK [Ansible動作開始] **************************************************************************************************************************************************************
task path: site.yml:6
ok: [localhost] => {
    "msg": "これから自動更新作業を開始します。"
}

...(省略)

TASK [Ansible動作終了] **************************************************************************************************************************************************************
task path: site.yml:16
ok: [localhost] => {
    "msg": "対象ノードにて自動更新作業が終了しました。"
}
META: ran handlers
META: ran handlers

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

対象エッジデバイスにて確認してみる

```shell
pi@raspberrypi:~ $ sl
# slコマンドが正常実行されることを確認
```

神

## 続-検証 (なんかもっと良い方法があるらしい・・)

`S3` + `コマンド実行機能` でもっと簡単にAnsible実行できるらしいことを後から知る・・。しかも、最初に書いた方法だと、`advanced-instances tier` 必須なので、若干費用がかかるが、今回の方法だと接続デバイス1000台まで無料。

### 手順 (最初の検証終わりから開始)

#### 1. (コンソール) インスタンス設定変更

<設定セクション: AWS Systems Manager - フリートマネージャー>

`advanced-instances tier` を解除する　（解除しなくても使えるが、解除したら無料になるため解除。） `アカウント設定の変更` を押すだけ。

![スクリーンショット 2021-09-05 1.20.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242768/a75ea8b7-8261-69b9-78b5-00593774b11e.png)

#### 2. (コンソール) S3 に Ansible Playbook ymlをアップ

以下のymlをS3にアップロードし、公開設定にしオブジェクトURLをメモ (https://ansible-on-premise.s3.us-east-2.amazonaws.com/ansible-on-premise.yml)。

```shell
- hosts: all
  become: true
  gather_facts: no
  tasks:

    - name: Ansible動作開始
      debug:
        msg: "これから自動更新作業を開始します。"

    - name: 必要なパッケージをインストールします (apt)
      apt:
        name:
          - sl
        state: present

    - name: Ansible動作終了
      debug:
        msg: "対象ノードにて自動更新作業が終了しました。"
```

* 注意：　今回は検証なので公開設定にしたが、基本的には権限付与して対応する

#### 3. (コンソール) Ansible実行

<設定セクション: AWS Systems Manager - Run Command - コマンドの実行>

`AWS Systems Manager -> ノード管理 -> Run Command` を開き、`Run Command` をクリック。 `AWS-ApplyAnsiblePlaybooks` をチェックし、Source Typeは `S3` を選択し、手順１でメモしたURLをpathに設定する。

```shell
{
   "path":"https://ansible-on-premise.s3.us-east-2.amazonaws.com/ansible-on-premise.yml"
}
```

Install Dependencies がTrueだと失敗した。なので手動でAnsibleを入れて実行。（後で分かったがデバイス側がメモリMax状態でinstallできていなかったっぽい。）

Playbook File には、ymlファイル名を入れておく。今回だと、`ansible-on-premise.yml`。

ターゲットは `インスタンスを手動で選択する` を選び、対象インスタンスにチェックを入れる。

出力オプションを設定しておくと、S3に出力ログを残すことができる。

上記設定終わったら、`RUN` 実行！

#### 4. (コンソール) 実行結果確認

<設定セクション: AWS Systems Manager - Run Command - コマンドの実行>

コマンド履歴欄に、結果が表示される。

![スクリーンショット 2021-09-05 1.12.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242768/44abbc26-d1b7-7c27-caf1-5040df31316d.png)

成功コマンドの詳細をみるとAnsible出力結果が・・・

```shell
...(省略)
TASK [Ansible動作終了] *********************************************************

ok: [localhost] => {

"msg": "対象ノードにて自動更新作業が終了しました。"

}

PLAY RECAP *********************************************************************

localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
```

エッジデバイスを見てみると、正常にパッケージがインストールされている。

これが無料・・・？ 神

## コスト

[公式記載のコスト](https://aws.amazon.com/jp/systems-manager/pricing/) によると、`advanced-instances`　で1台1ヶ月起動し続けると 5.004 USD = (0.00695 USD * 30日間 * 24時間) となる。advancedでなくstandardの場合は、最大 1,000 インスタンスまで無料。

「`アドバンスドオンプレミスインスタンスが 1 時間ずっと登録解除、シャットダウン、または削除されていた場合、その時間に対する料金は発生しません。`」と書かれているので、リモートシェル作業する時だけ `advanced-instances` に変更する運用にすれば、ほとんどお金かからない見込み。

- 今回やったことの有料枠 ... リモートシェル, ポートフォワーディング
- 今回やったことの無料枠 ... リモートシェル, ポートフォワーディング以外全て

## 参考記事

- https://www.soudegesu.com/post/aws/provisioning-with-ansible-via-ssm/
- https://dev.classmethod.jp/articles/systems-manager-to-execute-complex-ansible-playbooks/