GreengrassでDockerアプリケーションをデプロイする

# はじめに
会社の業務でAWS IoT Greengrassについて勉強したので、Ubuntu搭載デバイスとAWS IoTを利用したアプリケーションを紹介したいと思います。
今回はGreengrassでDockerアプリケーションをデプロイする方法[^gg-docker][^gg-blog]を紹介します。

過去の記事：
[UbuntuにAWS IoT Greengrassをインストールする](https://qiita.com/Yokogawa_Ishino/items/1fb734f091e952a16fea)
[GreengrassのLambdaを作成してエッジデバイスにデプロイする](https://qiita.com/Yokogawa_Ishino/items/3ceb9029afcc7a58f2f9)

※先日、Greengrass V2[^gg-v2]がリリースされましたが、この記事ではV1を使用します。

# 環境
動作確認済デバイス（OS）

- e-RT3 Plus F3RP70-2L[^ert3]（Ubuntu 18.04 32bit）
    横河電機のエッジコントローラです。AWS IoT Greengrassの認定デバイス[^certificate]に登録されています(e-RT3のページは[こちら](https://devices.amazonaws.com/detail/a3G0h000007dE0NEAU/e-RT3-Plus-(F3RP70-2L)))。

- Raspberry Pi 4 Model B （Ubuntu Server 20.04 32bit）

これらのデバイスでは armhf アーキテクチャのパッケージが動作します。
また、Windows 10 搭載のPCでデバイスを操作しています。

# 作業の流れ
![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/213b418b-5735-25e2-b246-b3cf3d90ae22.png)

1. [**Dockerアプリケーションの作成**](#1-dockerアプリケーションの作成)
開発用PCでDockerアプリケーションを開発します。

1. [**Dockerイメージのプッシュ**](#2-dockerイメージのプッシュ)
Dockerイメージを作成してAmazon Elastic Container Registry (ECR)にプッシュします。

1. [**docker-composeのアップロード**](#3-docker-composeのアップロード)
docker-composeを作成してAmazon Simple Storage Service (S3)にアップロードします。

1. [**Dockerアプリケーションのデプロイ**](#4-dockerアプリケーションのデプロイ)
必要なロールをGreengrass Groupに付与し、Dockerアプリケーションのデプロイメントコネクタ[^gg-docker]を使用してDockerアプリケーションをデバイスにデプロイします。Dockerコンテナが起動し、アプリケーションが稼働します。

# 準備
## AWS CLIのインストール
**PCに**AWS CLIをインストールします。
インストール方法は以下のガイドを参照してください。
[Windows での AWS CLI バージョン 2 のインストール、更新、アンインストール](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-windows.html)

初回使用時には設定を行う必要があります。詳しくは以下のガイドを参照してください。
[設定の基本 - AWS Command Line Interface](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-config)

※PCがproxy環境下にある場合はproxy設定が必要です。詳しくは[コマンドのプロキシ設定](#コマンドのプロキシ設定)をご覧ください。

## Docker Desktop on Windowsのインストール
**PCに**Docker Desktop on Windowsをインストールします。
インストール方法は[こちらのページ](https://qiita.com/Yokogawa_Mita/items/e8a29f9c6682b3156ef2#%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%83%BC%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)を参照してください。

※PCがproxy環境下にある場合はproxy設定が必要です。詳しくは[こちらのページ](https://qiita.com/Yokogawa_Mita/items/e8a29f9c6682b3156ef2#docker)をご覧ください。

## Python3.7のインストール
**デバイスに**GreengrassでDockerアプリケーションをデプロイするために必要なPython3.7をインストールします。
※デバイスがproxy環境下にある場合はproxy設定が必要です。詳しくは[こちら](https://qiita.com/Yokogawa_Mita/items/f7996d539b29618d7159#proxy%E8%A8%AD%E5%AE%9A)をご覧ください。

- **e-RT3の場合**

    ```bash
    sudo apt update
    sudo apt install python3.7
    ```

    ```bash
    username@ubuntu:~$ python3.7 --version
    Python 3.7.5
    ```

- **Raspberry Pi （Ubuntu Server 20.04 32bit）の場合**
    aptでインストールできないので、ソースからビルドしてインストールします。
    ビルドに必要なパッケージをインストールします。

    ```bash
    sudo apt update
    sudo apt install build-essential libbz2-dev libdb-dev \
      libreadline-dev libffi-dev libgdbm-dev liblzma-dev \
      libncursesw5-dev libsqlite3-dev libssl-dev \
      zlib1g-dev uuid-dev
    ```
    公式サイトからPython3.7のソースをダウンロードしてビルドします。

    ```bash
    wget https://www.python.org/ftp/python/3.7.9/Python-3.7.9.tar.xz
    tar xvf Python-3.7.9.tar.xz
    cd Python-3.7.9
    ./configure
    make
    sudo make altinstall
    cd ~
    ```
    インストールの成功を確認します。

    ```bash
    username@ubuntu:~$ python3.7 --version
    Python 3.7.9
    ```

## dockerのインストール
**デバイスに**dockerとdocker-composeをインストールします。
### docker
- **e-RT3の場合**

    最新版をインストールするために、公式ドキュメント[^docker-install]に従ってインストールします。
    dockerのインストールに必要なパッケージをインストールします。

    ```bash
    sudo apt update
    sudo apt install \
        apt-transport-https \
        ca-certificates \
        curl \
        gnupg-agent \
        software-properties-common
    ```
    資格情報[^login]に必要なパッケージをインストールします。

    ```bash
    sudo apt install pass
    ```
    GPGキーを追加します。

    ```bash
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    ```
    フィンガープリントの下8桁で検索し、キーが追加されたことを確認します。

    ```bash
    username@ubuntu:~$ sudo apt-key fingerprint 0EBFCD88
    pub   rsa4096 2017-02-22 [SCEA]
          9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
    uid           [ unknown] Docker Release (CE deb) <docker@docker.com>
    sub   rsa4096 2017-02-22 [S]
    ```
    リポジトリを追加します。

    ```bash
    sudo add-apt-repository \
       "deb [arch=armhf] https://download.docker.com/linux/ubuntu \
       $(lsb_release -cs) \
       stable"
    ```
    dockerエンジンをインストールします。

    ```bash
    sudo apt update
    sudo apt install docker-ce docker-ce-cli containerd.io
    #サービス起動
    sudo systemctl start docker
    #サービス自動起動
    sudo systemctl enable docker
    ```

    ```bash
    username@ubuntu:~$ docker --version
    Docker version 20.10.1, build 831ebea
    ```

- **Raspberry Pi（Ubuntu Server 20.04 32bit）の場合**  
    
    Dockerの公式リポジトリにUbuntu20.04 armhf用のパッケージがないので、docker.ioでインストールします。

    ```bash
    sudo apt install docker.io
    #サービス起動
    sudo systemctl start docker
    #サービス自動起動
    sudo systemctl enable docker
    ```

    ```bash
    username@ubuntu:~$ docker --version
    Docker version 19.03.8, build afacb8b7f0
    ```

インストールの成功を確認します。
テスト用のイメージでコンテナを起動し、以下のようなメッセージが表示されたら成功です。

```bash
username@ubuntu:~$ sudo docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.
...
```

※デバイスがproxy環境下にある場合はproxy設定が必要です。詳しくは[dockerのプロキシ設定](#dockerのプロキシ設定)をご覧ください。

### docker-compose
pipを利用してインストールします[^docker-pip]。

必要なパッケージをインストールします。

```bash
sudo apt update
sudo apt install libffi-dev libssl-dev python3-dev python3-venv
```
仮想環境を作成してpipでインストールします。

```bash
python3 -m venv venv
source venv/bin/activate
(venv) username@ubuntu:~$ pip install wheel
(venv) username@ubuntu:~$ pip install docker-compose
(venv) username@ubuntu:~$ sudo cp venv/bin/docker-compose /usr/bin
(venv) username@ubuntu:~$ deactivate
```
インストールの成功を確認します。

```bash
username@ubuntu:~$ docker-compose --version
docker-compose version 1.27.4, build unknown
```

### ユーザー設定
GreengrassからデプロイされたDockerアプリケーションを実行するユーザーの設定を行います。
デフォルトではggc_userとなるので、今回はこのユーザーを使用します。

S3バケットからダウンロードしたdocker-composeを保存するディレクトリを作成し、アクセス権を設定します。

```bash
sudo mkdir /home/ggc_user/myCompose
sudo chown ggc_user:ggc_group /home/ggc_user/myCompose
sudo chmod 700 /home/ggc_user/myCompose
```
ggc_userをdockerグループに追加します。

```bash
sudo usermod -aG docker ggc_user
```
一度ログアウトして設定を反映させます。

# 1. Dockerアプリケーションの作成
**PCで**Dockerアプリケーションを作成します。
今回は、アクセスすると「Hello World from Docker Container!」というメッセージを表示するPythonのwebアプリケーションを作成します。
以下の3つのファイルを同一のフォルダに保存してください。

Flaskを利用したPythonのプログラムです。アクセスすると「Hello World from Docker Container!」というメッセージを表示します。

```python:app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World from Docker Container!"

if __name__ == "__main__":
    app.run()
```

必要なPythonのパッケージを記したファイルです。今回はFlaskのみ利用します。

```:requirements.txt
Flask
```

Dockerのイメージを作成する際のコマンドを記したファイルです。
32bit arm用のイメージに必要なファイルやパッケージをインストールしています。

```Dockerfile:Dockerfile
FROM arm32v7/python:3.7-alpine
WORKDIR /usr/src/app
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["flask", "run"]
```

# 2. Dockerイメージのプッシュ
Dockerイメージを作成してECRにプッシュします。
## リポジトリの作成
1. 左上のサービスからElastic Container Registryに進み、「Repositories」→「リポジトリを作成」をクリックします。
![docker-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/5c6212bf-a964-f4db-aaff-063e626feea6.png)

1. 任意のリポジトリ名を入力し、「リポジトリを作成」をクリックします。
![docker-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/b4963380-8701-0253-cb46-1a04b2ca5aef.png)

## イメージの作成とプッシュ
1. PCのDocker Desktop on Windowsを起動しておきます。

1. リポジトリのページに移動し、「プッシュコマンドの表示」をクリックします。
![docker-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/7e5e8c43-3070-a439-0c92-90cb87a2b9cd.png)

1. [Dockerアプリケーションの作成](#1-dockerアプリケーションの作成)で作成したDockerfileのあるディレクトリでコマンドプロンプトを開き、表示されたコマンドを順番に実行します。
![docker-32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/5e61ecb1-204f-3180-b554-0876a06d32a3.png)

1. ECRにdockerイメージがプッシュされたことを確認します。
イメージのURIをこの後の手順で使用するので控えておきます。
![docker-37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/61e7492a-e267-9b91-9164-001caa114073.png)


# 3. docker-composeのアップロード
## docker-composeの作成
**PCで**docker-composeを作成します。
以下の内容を`docker-compose.yml`という名前で保存してください。
`<image-uri>`の部分は[イメージの作成とプッシュ](#イメージの作成とプッシュ)で控えておいたURIで置き換えてください。

```yaml:docker-compose.yml
version: '3'
services:
  web:
    image: "<image-uri>"
    ports:
      - "5000:5000"
```
## S3バケットの作成
1. 左上のメニューからS3へ進み、「バケット」→「バケットを作成」をクリックします。
![docker-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/eb184a78-bc32-8687-3035-23acfeaf4a42.png)

1. 任意のバケット名を入力し、「バケットを作成」をクリックします。
![docker-27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/122b9a47-a973-fb8d-b7cd-073c3eab6ea2.png)

## docker-composeのアップロード
1. 作成したバケットをクリックし「アップロード」をクリックします。
![docker-28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/8941ed76-dceb-09b4-fdec-d35ac0678eb7.png)

1. 「ファイルを追加」をクリックし[docker-composeの作成](#docker-composeの作成)で作成したdocker-compose.ymlをアップロードします。
![docker-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/37399e3e-45d1-1aca-710d-02cd52f377af.png)

# 4. Dockerアプリケーションのデプロイ
## ロールの作成とアタッチ
GreengrassがS3バケット、ECRにアクセスするために必要なロールを作成して、Greengrassグループにアタッチします。
### ポリシーの作成
1. 「IAM」→「ポリシー」と進み、「ポリシーの作成」をクリックします。
![docker-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/b2661e43-1a35-0165-66a3-1a566e84cb89.png)

1. JSONタブをクリックして以下のJSONを入力し、S3バケットへのアクセスを許可します。
`<bucket-name>`の部分は[S3バケットの作成](#s3バケットの作成)で作成したS3バケット名に置き換えてください。
入力が完了したら「ポリシーの確認」をクリックします。

    ```json
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowAccessToComposeFileS3Bucket",
                "Action": [
                    "s3:GetObject"
                ],
                "Effect": "Allow",
                "Resource": "arn:aws:s3:::<bucket-name>/*" 
            }
        ]
    }
    ```
    ![docker-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/be90c54c-1dc2-471c-72ce-8cb6092cfbdf.png)

1. 任意の名前と説明を入力して「ポリシーの作成」をクリックします。
![docker-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/e5e955c5-953a-56d1-6a9e-fa54a0c333c5.png)

1. 同様の手順で、以下のJSONを使用してECRへのアクセスを許可するポリシーを作成します。
`<region>`、`<account-id>`の部分は使用しているリージョンとアカウントIDで、`<repository-name>`の部分は[リポジトリの作成](#リポジトリの作成)で作成したリポジトリ名で置き換えてください。

    ```json
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowGetEcrRepositories",
                "Effect": "Allow",
                "Action": [
                    "ecr:GetDownloadUrlForLayer",
                    "ecr:BatchGetImage"
                ],
                "Resource": [
                    "arn:aws:ecr:<region>:<account-id>:repository/<repository-name>"
                ]	
            },
            {
                "Sid": "AllowGetEcrAuthToken",
                "Effect": "Allow",
                "Action": "ecr:GetAuthorizationToken",
                "Resource": "*"
            }
        ]
    }
    ```
    
### ロールの作成
1. 「IAM」→「ロール」と進み、「ロールの作成」をクリックします。
1. ユースケースの選択でGreengrassを選択し、次へ進みます。
![docker-19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/a2982b73-2f6a-a281-9404-2ffb2087bf4e.png)

1. [ポリシーの作成](#ポリシーの作成)で作成した2つのポリシーを選択します。
![docker-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/5e55dbdb-ca18-c03c-22d0-a16001939cd6.png)

1. タグは設定せずに次へ進みます。

1. 任意の名前と説明を入力してロールを作成します。
![docker-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/bc14f476-f8b7-2b63-28aa-9f67bca7acd2.png)

### ロールのアタッチ
1. Greengrassのグループへ進み、「設定」→「ロールの追加」をクリックします。
![docker-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/94e28e7d-b74b-b16d-5564-a9e08b02f4b3.png)

1.  [ロールの作成](#ロールの作成)で作成したロールを選択して「保存」をクリックします。
![docker-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/a4cce04b-2e5a-7c11-ef77-f663ae1882c9.png)

## デプロイと動作確認
1. グループのページに進み、「コネクタ」→「コネクタの追加」をクリックします。
![docker-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/c4b2fa1f-3e94-5dae-b980-353357a008dc.png)

1. 「Docker Application Deployment」を選択し、「次へ」をクリックします。
![docker-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/072ab9a9-d610-e6fd-9a53-156215c03f1c.png)

1. S3に保存したdocker-compose.ymlと、[ユーザー設定](#ユーザー設定)で作成した、デバイス上でdocker-composeを保存するディレクトリを指定して、「追加」をクリックします。
![docker-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/40c1b01e-8007-14c0-d256-1f0735e6f60f.png)

1. グループのページに進み、デプロイをクリックします。
![docker-30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/1349a5ed-ec9d-3485-92c3-ee92aa71389f.png)

1. デプロイ完了後、しばらく待ってからPCで`http://<デバイスのIPアドレス>:5000`にアクセスします。
※デバイスとPCが同一のネットワークに属している必要があります。

1. 以下の画面が表示されたら成功です。
![docker-38-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/a54baaf5-86c0-77b9-21c0-6393872377ba.png)
※うまくいかない場合はデバイスの`/greengrass/ggc/var/log/`以下にあるログを確認してください。

# 補足
環境により設定は異なりますが、参考までに今回私が行ったproxy設定を紹介します。
## コマンドのプロキシ設定
Windowsのコマンドプロンプトで以下の内容を入力します。

```
set HTTP_PROXY=http://username:password@example.com:port/
set HTTPS_PROXY=http://username:password@example.com:port/
```
## dockerのプロキシ設定
Dockerのドキュメント[^docker-proxy]に従ってプロキシ設定を行います。

1. まずディレクトリを作成し、そのディレクトリ内にファイルを作成します。

    ```bash
    sudo mkdir -p /etc/systemd/system/docker.service.d
    sudo vi /etc/systemd/system/docker.service.d/http-proxy.conf
    ```
1. 下記のフォーマットで `HTTP_PROXY` 、`HTTPS_PROXY` の環境変数をファイルに書き込み、保存します。
どちらか一方のみで良い場合や `NO_PROXY` の設定が必要な場合など、環境に合わせて適宜内容を変更してください。

    ```bash:/etc/systemd/system/docker.service.d/http-proxy.conf
    [Service]
    Environment="HTTP_PROXY=http://username:password@example.com:port/"
    Environment="HTTPS_PROXY=http://username:password@example.com:port/"
    ```
1. 設定を反映するためにデーモンをリロードし、再起動します。

    ```bash
    sudo systemctl daemon-reload
    sudo systemctl restart docker
    ```
1. 設定内容が反映されているか確認します。

    ```bash
    # 一般ユーザusernameの場合
    username@ubuntu:~$ sudo systemctl show --property=Environment docker
    Environment=HTTP_PROXY=http://username:password@example.com:port/ HTTPS_PROXY=http://username:password@example.com:port/
    ```

# 参考
[^gg-v2]: [AWS IoT Greengrass 2.0 を発表 — オープンソースエッジランタイムと新しい開発者向け機能 | Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/announcing-aws-iot-greengrass-2-0-with-an-open-source-edge-runtime-and-new-developer-capabilities/)
[^docker-proxy]: [Control Docker with systemd | Docker Documentation](https://docs.docker.com/config/daemon/systemd/#httphttps-proxy)
[^gg-docker]: [Docker アプリケーションのデプロイコネクタ - AWS IoT Greengrass](https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/docker-app-connector.html)
[^gg-blog]: [新機能 – AWS IoT Greengrass にコンテナサポートとエッジでのデータストリームの管理を追加 | Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/new-aws-iot-greengrass-adds-docker-support-and-streams-management-at-the-edge/)
[^login]: [docker login | Docker Documentation](https://docs.docker.com/engine/reference/commandline/login/#credentials-store)
[^docker-install]: [Install Docker Engine on Ubuntu | Docker Documentation](https://docs.docker.com/engine/install/ubuntu/)
[^docker-pip]: [Install Docker Compose | Docker Documentation](https://docs.docker.com/compose/install/#install-using-pip)
[^ert3]: [産業用AIプラットフォーム | 横河電機株式会社](https://www.yokogawa.co.jp/solutions/products-platforms/control-system/real-time-os-controller/ert3-ai-platform/)
[^certificate]: [Discover qualified IoT hardware and devices | Build and deliver successful IoT solutions on AWS](https://devices.amazonaws.com/)