[AWS CDK] GreengrassとLambdaを作成して、Raspberry Piにデプロイしてみた
概要
CX事業本部の佐藤です。
最近弊社でAWS CDKの人気が高まり始めています。私自身あまり触ったことがなかったため、今回勉強も兼ねて、AWS CDKでGreengrassとLambdaを作成してみました。ちょうど使ってないRaspberry Piがあったため、実際にデプロイしてRaspberry Pi上でLambdaを実行させてみたいと思います。
下記のリポジトリをベースに説明していきます。
環境
項目 | バージョン |
---|---|
macOS | Mojave 10.14.5 |
AWS CDK | 1.6.1 build a09203a |
node | v10.16.0 |
npm | 6.9.0 |
Raspberry Pi | Raspberry Pi 3 Model B+(SSHで接続できる) |
Raspbian OS | Raspbian Stretch, 2018-06-29 |
Greengrass Core | v1.9.2 |
Lambdaランタイム | Python 3.7.2 |
Raspberry PiでGreengrass Lambdaを実行する
Greengrassを使用して、Raspberry PiでLambdaを実行させてみます。以下の手順で行なっていきます。
事前にAWS IoTで証明書を作成する
AWS IoTで証明書を作成しておきます。証明書の作成はマネージメントコンソール上から行います。
- Iot Coreコンソールに移動します。
- 安全性メニューの証明書メニューを選択します。
- 作成ボタンを押します。
- 1-Click証明書作成(推奨)の証明書の作成を押します。
- モノの証明書、パブリックキー、プライベートキーがダウンロードできるようになるため、すべてダウンロードして自PC上に保管します。
- 有効化を押します。
- 完了を押します。
これで、デバイス証明書の作成ができました。この証明書を後ほど、Raspberry Piに設定します。
AWS CDKのインストール
npmでインストールします。
npm install -g aws-cdk
AWS CDKプロジェクトの作成とライブラリのインストール
mkdir aws-cdk-greengrass-sample cd aws-cdk-greengrass-sample cdk init --language typescript npm install --save @aws-cdk/aws-lambda npm install --save @aws-cdk/aws-greengrass npm install --save @aws-cdk/aws-iot
Greengrass、LambdaをCDKで作成する
Greengrassを使用するため、AWSにLambda関数とGreengrassグループを作成します。今回はタイトル通り、AWS CDKを使用してデプロイしてみたいと思います。GreengrassがLambda関数に依存している構成のため、スタックを2つに分けて、クロススタック参照を使ってデプロイしています。
Lambda用スタック
import cdk = require('@aws-cdk/core'); import lambda = require('@aws-cdk/aws-lambda'); export class GreengrassLambdaStack extends cdk.Stack { public readonly greengrassLambdaAlias: lambda.Alias; constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // GreengrassにデプロイするLambda関数の作成 const greengrassLambda = new lambda.Function(this, 'GreengrassSampleHandler', { runtime: lambda.Runtime.PYTHON_3_7, code: lambda.Code.asset('handlers'), handler: 'handler.handler', }); const version = greengrassLambda.addVersion('GreengrassSampleVersion'); // Greengrass Lambdaとして使用する場合、エイリアスを指定する必要がある this.greengrassLambdaAlias = new lambda.Alias(this, 'GreengrassSampleAlias', { aliasName: 'rasberrypi', version: version }) } }
Greengrass用スタック
import cdk = require('@aws-cdk/core'); import iot = require('@aws-cdk/aws-iot'); import lambda = require('@aws-cdk/aws-lambda'); import greengrass = require('@aws-cdk/aws-greengrass'); interface GreengrassRaspberryPiStackProps extends cdk.StackProps { greengrassLambdaAlias: lambda.Alias } export class GreengrassRaspberryPiStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props: GreengrassRaspberryPiStackProps) { super(scope, id, props); const certArn: string = '先ほど作った証明書のARNを設定' const region: string = cdk.Stack.of(this).region; const accountId: string = cdk.Stack.of(this).account; // AWS IoTのモノの作成 const iotThing = new iot.CfnThing(this, 'Thing', { thingName: 'Raspberry_Pi_Thing' }); if (iotThing.thingName !== undefined) { const thingArn = `arn:aws:iot:${region}:${accountId}:thing/${iotThing.thingName}`; // ポリシーを作成 const iotPolicy = new iot.CfnPolicy(this, 'Policy', { policyName: 'Raspberry_Pi_Policy', policyDocument: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iot:*", "greengrass:*", ], "Resource": [ "*" ] } ] } }); iotPolicy.addDependsOn(iotThing); // 証明書にポリシーをアタッチ if (iotPolicy.policyName !== undefined) { const policyPrincipalAttachment = new iot.CfnPolicyPrincipalAttachment(this, 'PolicyPrincipalAttachment', { policyName: iotPolicy.policyName, principal: certArn }) policyPrincipalAttachment.addDependsOn(iotPolicy) } // モノに証明書をアタッチ const thingPrincipalAttachment = new iot.CfnThingPrincipalAttachment(this, 'ThingPrincipalAttachment', { thingName: iotThing.thingName, principal: certArn }); thingPrincipalAttachment.addDependsOn(iotThing) // Greengrass Coreの作成 const coreDefinition = new greengrass.CfnCoreDefinition(this, 'CoreDefinition', { name: 'Raspberry_Pi_Core', initialVersion: { cores: [ { certificateArn: certArn, id: '1', thingArn: thingArn } ] } }); coreDefinition.addDependsOn(iotThing) // Greengrassリソースの作成 const resourceDefinition = new greengrass.CfnResourceDefinition(this, 'ResourceDefinition', { name: 'Raspberry_Pi_Resource', initialVersion: { resources: [ { id: '1', name: 'log_file_resource', resourceDataContainer: { localVolumeResourceData: { sourcePath: '/log', destinationPath: '/log' } } } ] } }); // Greengrass Lambdaの作成 const functionDefinition = new greengrass.CfnFunctionDefinition(this, 'FunctionDefinition', { name: 'Raspberry_Pi_Function', initialVersion: { functions: [ { id: '1', functionArn: props.greengrassLambdaAlias.functionArn, functionConfiguration: { encodingType: 'binary', memorySize: 65536, pinned: true, timeout: 3, environment: { // ログファイルを書き出すため、リソースの書き込み権限を与える resourceAccessPolicies: [ { resourceId: '1', permission: 'rw' } ] } } } ] } }); // Greengrassグループの作成 const group = new greengrass.CfnGroup(this, 'Group', { name: 'Raspberry_Pi', initialVersion: { coreDefinitionVersionArn: coreDefinition.attrLatestVersionArn, resourceDefinitionVersionArn: resourceDefinition.attrLatestVersionArn, functionDefinitionVersionArn: functionDefinition.attrLatestVersionArn } }); // 一連のDefinitionの作成が終わったらグループを作成 group.addDependsOn(coreDefinition) group.addDependsOn(resourceDefinition) group.addDependsOn(functionDefinition) } } }
上記2つのスタックを、クロススタック参照で作成します。
#!/usr/bin/env node import 'source-map-support/register'; import cdk = require('@aws-cdk/core'); import { GreengrassRaspberryPiStack } from '../lib/greengrass-raspberry-pi-stack'; import { GreengrassLambdaStack } from '../lib/greengrass-lambda-stack'; const app = new cdk.App(); const lambdaStack = new GreengrassLambdaStack(app, 'GreengrassLambdaStack'); new GreengrassRaspberryPiStack(app, 'GreengrassRasberryPiStack', { greengrassLambdaAlias: lambdaStack.greengrassLambdaAlias });
Lambdaコード
5秒ごとに、hello!!
の文字列をログファイルに書き込む処理です。
from logging import config, getLogger import time import os config.dictConfig({ "version": 1, "disable_existing_loggers": False, "root": { "level": "INFO", "handlers": [ "logFileHandler" ] }, "handlers": { "logFileHandler": { "class": "logging.FileHandler", "level": "INFO", "formatter": "logFileFormatter", "filename": "/log/test.log", "mode": "w", "encoding": "utf-8" } }, "formatters": { "logFileFormatter": { "format": "%(asctime)s|%(levelname)-8s|%(name)s|%(funcName)s|%(message)s" } } }) logger = getLogger(__name__) while True: logger.info('hello!!') time.sleep(5) def handler(event, context): return
デプロイ
AWSにデプロイします。デプロイするためにはS3バケットが必要なため、CDKのコマンドで作成します。
cdk bootstrap
TypeScriptのためビルドします。
npm run build
デプロイします。
cdk GreengrassRaspberryPiStack deploy
Including dependency stacks: GreengrassLambdaStack GreengrassLambdaStack This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening). Please confirm you intend to make the following modifications: IAM Statement Changes ┌───┬────────────────────────────────────────────┬────────┬────────────────┬──────────────────────────────┬───────────┐ │ │ Resource │ Effect │ Action │ Principal │ Condition │ ├───┼────────────────────────────────────────────┼────────┼────────────────┼──────────────────────────────┼───────────┤ │ + │ ${GreengrassSampleHandler/ServiceRole.Arn} │ Allow │ sts:AssumeRole │ Service:lambda.amazonaws.com │ │ └───┴────────────────────────────────────────────┴────────┴────────────────┴──────────────────────────────┴───────────┘ IAM Policy Changes ┌───┬────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐ │ │ Resource │ Managed Policy ARN │ ├───┼────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤ │ + │ ${GreengrassSampleHandler/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │ └───┴────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘ (NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np) Do you wish to deploy these changes (y/n)? y GreengrassLambdaStack: deploying... GreengrassLambdaStack: creating CloudFormation changeset... 0/6 | 10:16:55 PM | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata 0/6 | 10:16:56 PM | CREATE_IN_PROGRESS | AWS::IAM::Role | GreengrassSampleHandler/ServiceRole (GreengrassSampleHandlerServiceRole33F33CB8) 0/6 | 10:16:56 PM | CREATE_IN_PROGRESS | AWS::IAM::Role | GreengrassSampleHandler/ServiceRole (GreengrassSampleHandlerServiceRole33F33CB8) Resource creation Initiated 0/6 | 10:16:57 PM | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated 1/6 | 10:16:58 PM | CREATE_COMPLETE | AWS::CDK::Metadata | CDKMetadata 2/6 | 10:17:15 PM | CREATE_COMPLETE | AWS::IAM::Role | GreengrassSampleHandler/ServiceRole (GreengrassSampleHandlerServiceRole33F33CB8) 2/6 | 10:17:18 PM | CREATE_IN_PROGRESS | AWS::Lambda::Function | GreengrassSampleHandler (GreengrassSampleHandler0B069A6B) 2/6 | 10:17:19 PM | CREATE_IN_PROGRESS | AWS::Lambda::Function | GreengrassSampleHandler (GreengrassSampleHandler0B069A6B) Resource creation Initiated 3/6 | 10:17:19 PM | CREATE_COMPLETE | AWS::Lambda::Function | GreengrassSampleHandler (GreengrassSampleHandler0B069A6B) 3/6 | 10:17:22 PM | CREATE_IN_PROGRESS | AWS::Lambda::Version | GreengrassSampleHandler/VersionGreengrassSampleVersion (GreengrassSampleHandlerVersionGreengrassSampleVersion357ED95A) 3/6 | 10:17:22 PM | CREATE_IN_PROGRESS | AWS::Lambda::Version | GreengrassSampleHandler/VersionGreengrassSampleVersion (GreengrassSampleHandlerVersionGreengrassSampleVersion357ED95A) Resource creation Initiated 4/6 | 10:17:23 PM | CREATE_COMPLETE | AWS::Lambda::Version | GreengrassSampleHandler/VersionGreengrassSampleVersion (GreengrassSampleHandlerVersionGreengrassSampleVersion357ED95A) 4/6 | 10:17:26 PM | CREATE_IN_PROGRESS | AWS::Lambda::Alias | GreengrassSampleAlias (GreengrassSampleAlias7D31FBCD) 4/6 | 10:17:26 PM | CREATE_IN_PROGRESS | AWS::Lambda::Alias | GreengrassSampleAlias (GreengrassSampleAlias7D31FBCD) Resource creation Initiated 5/6 | 10:17:26 PM | CREATE_COMPLETE | AWS::Lambda::Alias | GreengrassSampleAlias (GreengrassSampleAlias7D31FBCD) 6/6 | 10:17:28 PM | CREATE_COMPLETE | AWS::CloudFormation::Stack | GreengrassLambdaStack ✅ GreengrassLambdaStack Outputs: GreengrassLambdaStack.ExportsOutputRefGreengrassSampleAlias7D31FBCDFD224BE2 = arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:GreengrassLambdaStack-GreengrassSampleHandlerXXXXXXXXXXXXXXXXXXXX:rasberrypi Stack ARN: arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/GreengrassLambdaStack/d5739a10-XXXXXXXXXXXXXXXXXX GreengrassRaspberryPiStack This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening). Please confirm you intend to make the following modifications: IAM Statement Changes ┌───┬──────────┬────────┬───────────────────┬───────────┬───────────┐ │ │ Resource │ Effect │ Action │ Principal │ Condition │ ├───┼──────────┼────────┼───────────────────┼───────────┼───────────┤ │ + │ ??? │ Allow │ greengrass:* │ │ │ │ │ │ │ iot:* │ │ │ └───┴──────────┴────────┴───────────────────┴───────────┴───────────┘ (NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np) Do you wish to deploy these changes (y/n)? y GreengrassRaspberryPiStack: deploying... GreengrassRaspberryPiStack: creating CloudFormation changeset... 0/10 | 10:17:48 PM | CREATE_IN_PROGRESS | AWS::IoT::Thing | Thing 0/10 | 10:17:48 PM | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata 0/10 | 10:17:48 PM | CREATE_IN_PROGRESS | AWS::Greengrass::ResourceDefinition | ResourceDefinition 0/10 | 10:17:48 PM | CREATE_IN_PROGRESS | AWS::Greengrass::FunctionDefinition | FunctionDefinition 0/10 | 10:17:50 PM | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated 1/10 | 10:17:50 PM | CREATE_COMPLETE | AWS::CDK::Metadata | CDKMetadata 1/10 | 10:17:51 PM | CREATE_IN_PROGRESS | AWS::Greengrass::FunctionDefinition | FunctionDefinition Resource creation Initiated 2/10 | 10:17:51 PM | CREATE_COMPLETE | AWS::Greengrass::FunctionDefinition | FunctionDefinition 2/10 | 10:17:52 PM | CREATE_IN_PROGRESS | AWS::Greengrass::ResourceDefinition | ResourceDefinition Resource creation Initiated 3/10 | 10:17:52 PM | CREATE_COMPLETE | AWS::Greengrass::ResourceDefinition | ResourceDefinition 3/10 Currently in progress: Thing 3/10 | 10:18:49 PM | CREATE_IN_PROGRESS | AWS::IoT::Thing | Thing Resource creation Initiated 4/10 | 10:18:49 PM | CREATE_COMPLETE | AWS::IoT::Thing | Thing 4/10 | 10:18:51 PM | CREATE_IN_PROGRESS | AWS::IoT::ThingPrincipalAttachment | ThingPrincipalAttachment 4/10 | 10:18:51 PM | CREATE_IN_PROGRESS | AWS::Greengrass::CoreDefinition | CoreDefinition 4/10 | 10:18:52 PM | CREATE_IN_PROGRESS | AWS::IoT::Policy | Policy 4/10 | 10:18:53 PM | CREATE_IN_PROGRESS | AWS::Greengrass::CoreDefinition | CoreDefinition Resource creation Initiated 5/10 | 10:18:54 PM | CREATE_COMPLETE | AWS::Greengrass::CoreDefinition | CoreDefinition 5/10 | 10:18:58 PM | CREATE_IN_PROGRESS | AWS::Greengrass::Group | Group 5/10 | 10:19:00 PM | CREATE_IN_PROGRESS | AWS::Greengrass::Group | Group Resource creation Initiated 6/10 | 10:19:00 PM | CREATE_COMPLETE | AWS::Greengrass::Group | Group 6/10 Currently in progress: ThingPrincipalAttachment, Policy 6/10 | 10:19:52 PM | CREATE_IN_PROGRESS | AWS::IoT::ThingPrincipalAttachment | ThingPrincipalAttachment Resource creation Initiated 7/10 | 10:19:52 PM | CREATE_COMPLETE | AWS::IoT::ThingPrincipalAttachment | ThingPrincipalAttachment 7/10 | 10:19:52 PM | CREATE_IN_PROGRESS | AWS::IoT::Policy | Policy Resource creation Initiated 8/10 | 10:19:52 PM | CREATE_COMPLETE | AWS::IoT::Policy | Policy 8/10 | 10:19:54 PM | CREATE_IN_PROGRESS | AWS::IoT::PolicyPrincipalAttachment | PolicyPrincipalAttachment 8/10 Currently in progress: PolicyPrincipalAttachment ✅ GreengrassRaspberryPiStack
スタックが2つ作成されて、デプロイできました。
Greengrass Coreが動作できるように、Raspberry Piの環境設定を行う
Raspberry PiでGreengrassを動作させるために、各種設定を行います。以降の作業はRaspberry PiにSSHで接続して作業します。
userとgroupの作成
sudo adduser --system ggc_user sudo addgroup --system ggc_group
ハードリンクとソフトリンクの保護を有効
viなどを使用して、/etc/sysctl.d/98-rpi.conf
ファイルに以下を追加します。
fs.protected_hardlinks = 1 fs.protected_symlinks = 1
再起動します。
sudo reboot
再起動後、設定が反映されているか確認します。以下のように表示されていれば、OKです。
pi@raspberrypi~ $ sudo sysctl -a 2> /dev/null | grep fs.protected fs.protected_hardlinks = 1 fs.protected_symlinks = 1
メモリ cgroups を有効
viなどを使用して、/boot/cmdline.txt
ファイルの最初の行に以下の内容を追加します。
cgroup_enable=memory cgroup_memory=1
再起動します。
sudo reboot
Raspberry PiにGreengrass Coreのインストール
Raspberry Piに、Greengrass Coreをインストールします。以下のコマンドを実行します。実行すると、/greengrass
フォルダが作成されます。
wget https://d1onfpft10uf5o.cloudfront.net/greengrass-core/downloads/1.9.2/greengrass-linux-armv7l-1.9.2.tar.gz sudo tar -xzvf greengrass-linux-armv7l-1.9.2.tar.gz -C /
Raspberry Piに証明書を組み込む
AWS IoTと接続するために、デバイス証明書を設定します。
- 先ほど作成してダウンロードしてある証明書や秘密鍵、公開鍵をRaspberry Pi上の
/greengrass/certs
配下に保存します。SCPなどを使用して、Raspberry Pi上にアップロードします。
- XXXXXXXX-certificate.pem.crt - XXXXXXXX-private.pem.key - XXXXXXXX-public.pem.key
- ルートCA証明書をダウンロードします。
cd /greengrass/certs sudo wget -O root.ca.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
/greengrass/config/config.json
を以下の内容で作成します。証明書の保存先の指定や、AWS IoTのエンドポイントの指定、対象のモノの指定などを行なっています。
{ "coreThing" : { "caPath" : "root.ca.pem", "certPath" : "XXXXXXXX-certificate.pem.crt", "keyPath" : "XXXXXXXX-private.pem.key", "thingArn" : "Greengrass Coreに設定されているモノのARNを設定", "iotHost" : "AWS IoTのエンドポイントを設定", "ggHost" : "greengrass-ats.iot.ap-northeast-1.amazonaws.com", "keepAlive" : 600 }, "runtime" : { "cgroup" : { "useSystemd" : "yes" } }, "managedRespawn" : false, "crypto" : { "principals" : { "SecretsManager" : { "privateKeyPath" : "file:///greengrass/certs/XXXXXXXX-private.pem.key" }, "IoTCertificate" : { "privateKeyPath" : "file:///greengrass/certs/XXXXXXXX-private.pem.key", "certificatePath" : "file:///greengrass/certs/XXXXXXXX-certificate.pem.crt" } }, "caPath" : "file:///greengrass/certs/root.ca.pem" } }
- 証明書の組み込みが終わったので、Greengrass Coreを実行します。
sudo /greengrass/ggc/core/greengrassd start
Raspberry PiにGreengrassをデプロイする
Raspberry PiにGreengrassグループをデプロイします。
- マネージメントコンソールから、IoT Greengrassを選択します。
- グループメニューを選択します。
- Rasberry_PiというGreengrassグループがあるので、それを選択します。
- 右上のアクション→デプロイを押します。
ステータスが「正常に完了しました」になれば成功です。
Raspberry Pi上のLambdaの動作確認
実際にログファイルが書き込まれているか確認します。確認すると以下のように、5秒ごとにログが書き込まれていました。うまく動いてそうです。
cat /log/test.log 2019-09-08 19:21:53,760|INFO |handler||hello!! 2019-09-08 19:21:58,766|INFO |handler||hello!! 2019-09-08 19:22:03,772|INFO |handler||hello!! 2019-09-08 19:22:08,778|INFO |handler||hello!! 2019-09-08 19:22:13,784|INFO |handler||hello!!
まとめ
AWS CDKを使って、Greengrassを動作させることができました。Lambdaをデバイス上で動作させるほかにも、コネクタという機能を使って、Raspberry PiのGPIOを簡単に制御できたりもするので、興味のある方は試してみてはいかがでしょうか。