こちらは AWS Containers Advent Calendar 2020 の23日目の記事です。
re:Invent 2020 で AWS IoT Greengrass 2.0 が発表されました。
v1 がそうであったように、v2 でも Docker コンテナ の動作がサポートされています。
さっそくやってみました。
やったこと
- EC2 インスタンス上に AWS IoT Greengrass 2.0 をインストールする
- Docker コンテナを動かす(ここでは nginx コンテナを動かしました)
AWS IoT Greengrass 2.0 のインストール on EC2
手元にラズパイはあるのですが、別の用途で使っているため、今回は EC2 インスタンスをたてて、そのうえで検証しました。
(t2.micro, x86, Amazon Linux2 の EC2 インスタンスを使っていますが、EC2インスタンスの作成手順は省略しています)
EC2 へ必要なソフトウェアのインストール
Core Device の作成
AWS IoT の管理コンソールで、Core device の作成を行います(画像右側のオレンジボタンです)
Core Device名 と Things group名を入力します。
ここでは、それぞれ greengrass-v2-qiita-core
と greengrass-v2-qiita-group
としています。
ページをスクロールするとインストール手順が表示されるため、1つ1つやって行きましょう。
Step0: 追加手順
私はこれはやらずに次に進んだため、ハマってしまいました。
sudo visudo
で以下のように root の設定を少しいじります。
root ALL=(ALL) ALL
↓
root ALL=(ALL:ALL) ALL
Step1: Install Java on the device
こちらを参考に Java 8(Corretto 8) を入れます。
sudo amazon-linux-extras enable corretto8
sudo yum install -y java-1.8.0-amazon-corretto
Step2: Configure AWS credentials on the device
Greengrass のインストール(インストール後の AWS IoT への登録など)には、AWSの認証情報が必要になります。
必要な Policy だったり、環境変数を使った設定方法がドキュメントに記載されています。
- https://docs.aws.amazon.com/greengrass/v2/developerguide/install-greengrass-core-v2.html?icmpid=docs_gg_console
- https://docs.aws.amazon.com/greengrass/v2/developerguide/install-greengrass-core-v2.html#provision-minimal-iam-policy
ただ、この検証では、EC2 インスタンスを使っていますので、IAM Role を EC2 インスタンスにアタッチすることで、完了しています。手順は省略
検証なので、アタッチした IAM Role には Administratorの権限が紐付いています(本番だと絶対にやってはいけないですね)
Step3: Run the installer
表示されたコマンドをコピペして、立ち上げた EC2 インスタンスにSSHで入って、コマンドを実行して、Greengrass をインストールします。
curl -s https://d2s8p88vqu9w66.cloudfront.net/releases/greengrass-nucleus-latest.zip > greengrass-nucleus-latest.zip && unzip greengrass-nucleus-latest.zip -d GreengrassCore
Archive: greengrass-nucleus-latest.zip
inflating: GreengrassCore/bin/greengrass.service.template
inflating: GreengrassCore/bin/loader
inflating: GreengrassCore/conf/nucleus-build.properties
inflating: GreengrassCore/lib/Greengrass.jar
sudo -E java -Droot="/greengrass/v2" -Dlog.store=FILE -jar ./GreengrassCore/lib/Greengrass.jar --aws-region us-east-1 --thing-name greengrass-v2-qiita-core --thing-group-name greengrass-v2-qiita-group --component-default-user ggc_user:ggc_group --provision true --setup-system-service true --deploy-dev-tools true
Creating user ggc_user
ggc_user created
Creating group ggc_group
ggc_group created
Added ggc_user to ggc_group
Provisioning AWS IoT resources for the device with IoT Thing Name: [greengrass-v2-qiita-core]...
Creating new IoT policy "GreengrassV2IoTThingPolicy"
Creating keys and certificate...
Attaching policy to certificate...
Creating IoT Thing "greengrass-v2-qiita-core"...
Attaching certificate to IoT thing...
Successfully provisioned AWS IoT resources for the device with IoT Thing Name: [greengrass-v2-qiita-core]!
Adding IoT Thing [greengrass-v2-qiita-core] into Thing Group: [greengrass-v2-qiita-group]...
Successfully added Thing into Thing Group: [greengrass-v2-qiita-group]
Setting up resources for aws.greengrass.TokenExchangeService ...
TES role alias "GreengrassV2TokenExchangeRoleAlias" does not exist, creating new alias...
IoT role policy "GreengrassTESCertificatePolicyGreengrassV2TokenExchangeRoleAlias" for TES Role alias not exist, creating policy...
Attaching TES role policy to IoT thing...
IAM policy named "GreengrassV2TokenExchangeRoleAccess" already exists. Please attach it to the IAM role if not already
Configuring Nucleus with provisioned resource details...
Downloading Root CA from "https://www.amazontrust.com/repository/AmazonRootCA1.pem"
Created device configuration
Successfully configured Nucleus with provisioned resource details!
Creating a deployment for Greengrass first party components to the thing group
Configured Nucleus to deploy aws.greengrass.Cli component
Successfully set up Nucleus as a system service
AWS IoTの管理コンソールを見て、正常にインストールされていることを確認します
正常にインストールされていれば、図のように Core Device名 が表示されているはずです。
Docker コンテナを動かす
前提条件を満たすためにいろいろする
Docker のインストールと権限設定
Docker をインストールして、ggc_user に docker を実行できる権限を付加します。
(greengrass 経由で実行されるdocker コマンドはこの ggc_user で実行されるためです)
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -a -G docker ggc_user
sudo usermod -a -G docker ec2-user #このコマンドは必須ではないですが、いちいち sudo をつけるのがめんどくさいので実行しています
usermod が有効になるように、一度 EC2インスタンスに入り直します。
ローカルでの動作確認
ここでは local 環境でデプロイを行って、動作することを確認します
docker image の用意
mkdir -p ~/GreengrassCore/artifacts/com.example.MyDockerComponent/1.0.0
docker pull public.ecr.aws/nginx/nginx
docker save public.ecr.aws/nginx/nginx > ~/GreengrassCore/artifacts/com.example.MyDockerComponent/1.0.0/nginx.tar
docker image rm public.ecr.aws/nginx/nginx #デプロイ時にローカルの image を使わないように削除しておく
recipe の用意
mkdir -p ~/GreengrassCore/recipes
touch ~/GreengrassCore/recipes/com.example.MyDockerComponent-1.0.0.yaml
---
RecipeFormatVersion: '2020-01-25'
ComponentName: com.example.MyDockerComponent
ComponentVersion: '1.0.0'
ComponentDescription: A component that runs a Docker container.
ComponentPublisher: Amazon
Manifests:
- Platform:
os: linux
Lifecycle:
Install:
Script: docker load -i {artifacts:path}/nginx.tar
Run:
Script: docker run --rm -p 8080:80 public.ecr.aws/nginx/nginx
ローカルでのデプロイ
sudo /greengrass/v2/bin/greengrass-cli deployment create \
--recipeDir ~/GreengrassCore/recipes \
--artifactDir ~/GreengrassCore/artifacts \
--merge "com.example.MyDockerComponent=1.0.0"
# output
Dec 23, 2020 4:22:52 AM software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection$1 onConnectionSetup
INFO: Socket connection /greengrass/v2/ipc.socket:8033 to server result [AWS_ERROR_SUCCESS]
Dec 23, 2020 4:22:52 AM software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection$1 onProtocolMessage
INFO: Connection established with event stream RPC server
Local deployment submitted! Deployment Id: f86c969b-ce30-4f62-9cf4-fddb85987bc1
ローカルデプロイの確認
ログを確認します(なんとかく docker run されていそうなのが分かります)
[ec2-user@ip-10-0-12-139 recipes]$ sudo tail -f /greengrass/v2/logs/com.example.MyDockerComponent.log
...
2020-12-23T05:43:26.313Z [INFO] (pool-2-thread-33) com.example.MyDockerComponent: shell-runner-start. {scriptName=services.com.example.MyDockerComponent.lifecycle.Install.Script, serviceName=com.example.MyDockerComponent, currentState=NEW, command=["docker load -i /greengrass/v2/packages/artifacts/com.example.MyDockerComponent..."]}
2020-12-23T05:43:32.796Z [INFO] (Copier) com.example.MyDockerComponent: stdout. Loaded image: public.ecr.aws/nginx/nginx:latest. {scriptName=services.com.example.MyDockerComponent.lifecycle.Install.Script, serviceName=com.example.MyDockerComponent, currentState=NEW}
2020-12-23T05:43:32.849Z [INFO] (pool-2-thread-33) com.example.MyDockerComponent: shell-runner-start. {scriptName=services.com.example.MyDockerComponent.lifecycle.Run.Script, serviceName=com.example.MyDockerComponent, currentState=STARTING, command=["docker run --rm -p 8080:80 public.ecr.aws/nginx/nginx"]}
2020-12-23T05:43:34.532Z [INFO] (Copier) com.example.MyDockerComponent: stdout. /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration. {scriptName=services.com.example.MyDockerComponent.lifecycle.Run.Script, serviceName=com.example.MyDockerComponent, currentState=RUNNING}
2020-12-23T05:43:34.532Z [INFO] (Copier) com.example.MyDockerComponent: stdout. /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/. {scriptName=services.com.example.MyDockerComponent.lifecycle.Run.Script, serviceName=com.example.MyDockerComponent, currentState=RUNNING}
2020-12-23T05:43:34.544Z [INFO] (Copier) com.example.MyDockerComponent: stdout. /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh. {scriptName=services.com.example.MyDockerComponent.lifecycle.Run.Script, serviceName=com.example.MyDockerComponent, currentState=RUNNING}
2020-12-23T05:43:34.569Z [INFO] (Copier) com.example.MyDockerComponent: stdout. 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf. {scriptName=services.com.example.MyDockerComponent.lifecycle.Run.Script, serviceName=com.example.MyDockerComponent, currentState=RUNNING}
2020-12-23T05:43:34.605Z [INFO] (Copier) com.example.MyDockerComponent: stdout. 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf. {scriptName=services.com.example.MyDockerComponent.lifecycle.Run.Script, serviceName=com.example.MyDockerComponent, currentState=RUNNING}
2020-12-23T05:43:34.605Z [INFO] (Copier) com.example.MyDockerComponent: stdout. /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh. {scriptName=services.com.example.MyDockerComponent.lifecycle.Run.Script, serviceName=com.example.MyDockerComponent, currentState=RUNNING}
2020-12-23T05:43:34.615Z [INFO] (Copier) com.example.MyDockerComponent: stdout. /docker-entrypoint.sh: Configuration complete; ready for start up. {scriptName=services.com.example.MyDockerComponent.lifecycle.Run.Script, serviceName=com.example.MyDockerComponent, currentState=RUNNING}
コンテナが動いていることも確認できますね!
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5f5281942e5b public.ecr.aws/nginx/nginx "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 0.0.0.0:8080->80/tcp heuristic_sanderson
nginx も動いていることが確認できますね!
curl localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
さて、これでローカルで動くことが確認できたので、実際にマネジメントコンソールからデプロイを行いましょう
デプロイ実施(準備編)
ローカルのデプロイメントを削除します
これで起動している docker コンテナが消えるはずです
sudo /greengrass/v2/bin/greengrass-cli deployment create --remove="com.example.MyDockerComponent"
tar の Docker イメージ を S3 にアップロードします
S3の場所は各自でお好きな場所を指定してください
この場合は、s3://xxxxxx/greengrassv2/docker/nginx.tar
に保存
aws s3 cp ~/GreengrassCore/artifacts/com.example.MyDockerComponent/1.0.0/nginx.tar s3://xxxxxx/greengrassv2/docker/
デプロイ実施(デプロイ編)
デプロイの方法は、AWS CLI, SDK などいろいろあるようですが、ここでは マネジメントコンソールからデプロイを行いました。
Component の作成
Yamlを入力(ローカルデプロイのyamlと似ていますが、Artifacts という項目を追加しています)
---
RecipeFormatVersion: '2020-01-25'
ComponentName: com.example.MyDockerComponent
ComponentVersion: '1.0.0'
ComponentDescription: A component that runs a Docker container.
ComponentPublisher: Amazon
Manifests:
- Platform:
os: linux
Lifecycle:
Install:
Script: docker load -i {artifacts:path}/nginx.tar
Run:
Script: docker run --rm -p 8080:80 public.ecr.aws/nginx/nginx
Artifacts:
- Uri: s3://xxxxx/greengrassv2/docker/nginx.tar
yaml を保存したら、Deploy をクリックします。
これでデプロイされるはずので、動作確認をしてみましょう。
動作確認
docker コマンドで確認したり、localhostでアクセスしたりしてみます。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4c3181250b11 public.ecr.aws/nginx/nginx "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp recursing_montalcini
curl localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
docker コンテナも動いてて、nginx の動作も確認できました!
まとめ
発表された Greengrass 2.0 を使って、デバイス(今回はEC2インスタンス)に docker コンテナをデプロイしました。
今回は、EC2 インスタンスなので、有り難みはあまり感じないですが、これが IoT機器で、しかも、それが大量にあるという環境だとかなり便利に使えるのではないでしょうか?
現時点では、データの取得元(artifacts)は、S3 のみのため、docker image を tar にして S3 にアップロードするというのはやや残念ではありますが、まだリリースされたばかりなので今後のアップデートに期待ですね。