# AWS IoT Greengrass V2 Workshop 操作流程
https://catalog.workshops.aws/greengrass/en-US
# 下載資源
CloudFormation Template
https://tinyurl.com/yvf5y3zw
# Chapter 2
[Reference](https://catalog.workshops.aws/greengrass/en-US/chapter2-letsbegin)
## Chapter 2.1 建立 EC2 環境
### 建立 EC2 Key-Pair
* 進入 EC2 Console -> [Key pairs](https://ap-northeast-1.console.aws.amazon.com/ec2/home?region=ap-northeast-1#KeyPairs:),點擊右方橘色 Create key pair

* 自行命名 Key Pair,例如:**20241004-gg-ws-kp**,點擊右下方橘色 Create key pair

* 此時會自動下載 pem 檔

* 看到 Key pairs 列表中出現剛剛建立的 key pair 即表示成功。

### 建立模擬 Edge Gateway 的 EC2
* 利用 CloudFormation template的方式自動建立 EC2
* 下載CloudFormation Template:[nhoatcc-gg-imd.yaml](https://tinyurl.com/yvf5y3zw)
* 前往 CloudFormation [Console](https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks?filteringText=&filteringStatus=active&viewNested=true)

* 點擊橘色 Create stack 按鈕

* 選擇 Upload a template file 並選擇下載好的 yaml 檔 (nhoatcc-gg-imd.yaml)
* 完成後按下橘色 Next

* 可自行命名 Stack Name,EX:**GreengrassV2Workshop**
* 下拉選擇 KeyName,選擇前步驟建立的 Key pair
* 都完成後點擊橘色 Next
* 在 Step 3 Configure stack options 時保持預設值,並下滑到最底後,按下橘色 Next
* Step 4 Review and create 階段,下滑到最底。

* 勾選 **I acknowledge that AWS CloudFormation might create IAM resources.**
* 點擊橘色 Submit

* EC2 開始建置,待 Status 轉換為 CREATE_COMPLETE 後,選擇 Outputs 分頁

* Outputs 中的 SSHCommand 可以直接利用 ssh 進行連線
```
ssh -i 20241004-gg-ws-kp.pem ubuntu@ec2-57-181-31-78.ap-northeast-1.compute.amazonaws.com
```
* 後面我們會利用 VSC 進行SSH連線,並直接對 EC2 上的程式碼進行編輯。
* 參考文件:https://medium.com/@christyjacob4/using-vscode-remotely-on-an-ec2-instance-7822c4032cff
### 建立 VSC 與 EC2 SSH 連線
* 下載VSC SSH套件 [Remote - SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh)
* 點擊:Open Visual Studio Code

* 安裝:Visual Studio Code Remote - SSH


* 安裝完成後點擊左下角 ><符號

* 點擊 Connect to Host...

* 點擊 Configure SSH Hosts...

* 點擊當前 user 的 config檔案,EX: /Users/mayawc/.ssh/config

* HostName/User 即為前面 Outposts 中取得的SSHCommand裡的資訊,EX:
* User: ubuntu
* HostName: ec2-57-181-31-78.ap-northeast-1.compute.amazonaws.com
* IdentityFile 則是您下載儲存 20241004-gg-ws-kp.pem 的絕對路徑
* 另外基於安全性的要求,AWS EC2 SSH要求pem檔案的權限必須限縮,之後才能連線。需要執行chmod將權限限縮

```
chmod 400 20241004-gg-ws-kp.pem
```
### 進行 VSC 與 EC2 SSH 連線
* 點擊 VSC 左下角,並選擇 Connect to Host...

* 選擇剛剛建立的 SSH Config: NhoaGGWorkshop

* 此時會跳出一個新的VSC視窗,左下角顯示 SSH:NhoaGGWorkshop 即表示正確連線

* 點擊 Open (資料夾),在 /home/ubuntu/ 按下 OK

* 左側看見EXPLORER將檔案全部載入,表示連線與設定成功,可以開始 Greengrass V2 Workshop 內容。

## Chapter 2.2 建立 IAM User CLI credential
* 目標:建立 Greengrass 安裝部署人員所需的 IAM User credential,提供CLI指令所需要的權限。
* 我們會建立一個名為:workshop_user_delete_later 的 admin user。
* 在未來 Production 環境下,我們有建議的[最佳實踐權限設定範圍](https://docs.aws.amazon.com/greengrass/v2/developerguide/provision-minimal-iam-policy.html)
### 建立一個 IAM User
* 前往 AWS IAM [console](https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1)
* 點擊橘色 Create user

* Step1: 命名 IAM User:workshop_user_delete_later,完成後點擊橘色 Next

* Step2: 選擇 Attach policies directly,在搜尋欄填入 Admin,接著勾選 AdministratorAccess,完成後下滑點擊橘色 Next

* Step3: 確認無誤後點擊橘色 Create user

* 跳轉回 IAM User 列表後,點擊剛剛建立的 workshop_user_delete_later

### 取得 CLI Credential
* 點擊 Security credentials 分頁

* 點擊 Create access key

* Use case: Command Line Interface (CLI),記得勾選底下confirmation後點擊橘色 Next

* 點擊橘色 Create access key

* Retrieve access keys
* Download .csv file (避免日後忘記)
* 複製 Access key 以及 Secret access key(點擊show)

### 將 Access Key Secret Key (AKSK) 設定到 EC2 (Edge Gateway) 環境
#### aws configure
* 回到 VSC Terminal

* 先執行 aws sts get-caller-identity 指令,我們可以確認,目前這台新安裝的EC2上,並沒有任何AWS的權限,為了我們接下來要進行的Greengrass安裝,我們需要讓它具有 AWS IAM 的權限。
* 利用 aws configure 這個指令,我們可以將前面建立的 aksk 設定到這台EC2的環境。
* 依照 aws configure 的提示,將資訊依序輸入。
```
ubuntu@ip-10-0-1-21:~$ aws sts get-caller-identity
Unable to locate credentials. You can configure credentials by running "aws configure".
ubuntu@ip-10-0-1-21:~$ aws configure
AWS Access Key ID [None]: **************************
AWS Secret Access Key [None]: **************************
Default region name [None]: ap-northeast-1
Default output format [None]: json
ubuntu@ip-10-0-1-21:~$ aws sts get-caller-identity
{
"UserId": "AIDA5IPFWS7QCA4KORNVF",
"Account": "911551666144",
"Arn": "arn:aws:iam::911551666144:user/workshop_user_delete_later"
}
ubuntu@ip-10-0-1-21:~$
```
* 最後我們再用 aws sts get-caller-identity 指令檢查一次,可以看到他指出我們現在具有 workshop_user_delete_later 這個 IAM User的身份了,也具有他被賦予的權限。
#### export
* 接下來在環境變數 export
```
export AWS_DEFAULT_REGION="ap-northeast-1"
export AWS_ACCESS_KEY_ID="Access Key"
export AWS_SECRET_ACCESS_KEY="Secret Key"
```
* 確認正確export
```
echo $AWS_DEFAULT_REGION
echo $AWS_ACCESS_KEY_ID
echo $AWS_SECRET_ACCESS_KEY
```
### 建立 Greengrass Servoce 在雲端所需的權限
[參考步驟](https://docs.aws.amazon.com/greengrass/v2/developerguide/greengrass-service-role.html#create-service-role)
* 這個權限的建立,可以使用前面的 IAM Console,也可以使用 aws cli 直接在模擬 Edge Gateway的 EC2 Terminal 上執行。
* 由於這個EC2 上的 IAM User (workshop_user_delete_later) 擁有 admin 權限,我們就直接利用它來練習使用 aws cli 建立 IAM Role
* 複製並且執行以下[指令](https://docs.aws.amazon.com/greengrass/v2/developerguide/greengrass-service-role.html#create-service-role),建立名為Greengrass_ServiceRole的 IAM Role
```
aws iam create-role --role-name Greengrass_ServiceRole --assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "greengrass.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:greengrass:region:account-id:*"
},
"StringEquals": {
"aws:SourceAccount": "account-id"
}
}
}
]
}'
```
* 記下此 Role 的 ARN 名稱,在response的json當中
```
{
"Role": {
"Path": "/",
"RoleName": "Greengrass_ServiceRole",
"RoleId": "AROA5IPFWS7QFWCDRFRCP",
"Arn": "arn:aws:iam::911551666144:role/Greengrass_ServiceRole",
"CreateDate": "2024-10-03T06:58:24Z",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "greengrass.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:greengrass:region:account-id:*"
},
"StringEquals": {
"aws:SourceAccount": "account-id"
}
}
}
]
}
}
}
```
```
"Arn": "arn:aws:iam::911551666144:role/Greengrass_ServiceRole"
```
* 建立完成此 IAM Role之後,將名為 AWSGreengrassResourceAccessRolePolicy 的IAM Policy掛載上去
```
aws iam attach-role-policy --role-name Greengrass_ServiceRole --policy-arn arn:aws:iam::aws:policy/service-role/AWSGreengrassResourceAccessRolePolicy
```
* 最後要將 Greengrass_ServiceRole 與 Tokyo Region的 IoT Greengrass做一個綁定
```
aws greengrassv2 associate-service-role-to-account --role-arn role-arn --region region
```
EX:
```
aws greengrassv2 associate-service-role-to-account --role-arn arn:aws:iam::911551666144:role/Greengrass_ServiceRole --region ap-northeast-1
```
```
ubuntu@ip-10-0-1-21:~$ aws greengrassv2 associate-service-role-to-account --role-arn arn:aws:iam::911551666144:role/Greengrass_ServiceRole --region ap-northeast-1
{
"associatedAt": "2024-10-03T07:00:51Z"
}
```
* 進到 AWS IoT Core console 也可以做一次驗證
* Step1: 進入 [Settings](https://ap-northeast-1.console.aws.amazon.com/iot/home?region=ap-northeast-1#/settings) 頁面
* Step2: 下拉到最底:Greengrass service role

### 在 EC2 上安裝 JAVA
[官方文件](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/generic-linux-install.html)
* 執行
```
wget -O - https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg && \
echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list
```
* 輸出範例
```
buntu@ip-10-0-1-21:~$ wget -O - https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg && \
echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list
--2024-10-03 06:30:58-- https://apt.corretto.aws/corretto.key
Resolving apt.corretto.aws (apt.corretto.aws)... 3.164.110.69, 3.164.110.24, 3.164.110.83, ...
Connecting to apt.corretto.aws (apt.corretto.aws)|3.164.110.69|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1695 (1.7K) [binary/octet-stream]
Saving to: ‘STDOUT’
- 100%[=======================================================================================================>] 1.66K --.-KB/s in 0s
2024-10-03 06:30:58 (785 MB/s) - written to stdout [1695/1695]
deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main
```
* 執行
```
sudo apt-get update; sudo apt-get install -y java-11-amazon-corretto-jdk
```
* 一路 Enter 直到安裝完成
* 確認版本
```
ubuntu@ip-10-0-1-21:~$ java -version
openjdk version "11.0.24" 2024-07-16 LTS
OpenJDK Runtime Environment Corretto-11.0.24.8.1 (build 11.0.24+8-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.24.8.1 (build 11.0.24+8-LTS, mixed mode)
ubuntu@ip-10-0-1-21:~$
```
* 到此 Chapter 2 完成以下項目
2. 建立了 IAM User (workshop_user_delete_later) [權限一],例用 aksk 授權給 Greengrass Installer 安裝時所需要的 AWS 權限
3.
4. 建立了 AWS IoT Greengrass 的 Greengrass_ServiceRole,並且掛載了指定的 Policy,這是 Greengrass Cloud Service 後續再執行與設備端互動時,要求的 policy (AWSGreengrassResourceAccessRolePolicy)。並且完成與 AWS IoT Core的綁定
5. Edge Gateway (EC2) 環境準備,安裝 JAVA
# Chapter 3
[Reference](https://catalog.workshops.aws/greengrass/en-US/chapter3-greengrasssetup)
* 在 Chapter 2 我們準備好了安裝Greengrass的操作環境所需要的權限
* 在Chapter 3 當中,我們要準備 Greengrass 這個雲端服務,以及 Greengrass core device 這個設備端軟體,所需要的 IAM 權限。

## Chapter 3.1 建立第一個 Greengrass Core Device
* 在這個階段,我們要將一個Greengrass Core Device 安裝在一台 EC2 所模擬的 Edge Gateway上。
* 前往 AWS IoT Core > Greengrass devices > Core devices [console](https://ap-northeast-1.console.aws.amazon.com/iot/home?region=ap-northeast-1#/home),點擊橘色 **Set up one core device**

* 保留預設值
* Step 1: Greengrass Core Device的名稱
* Step 2: 此Core Deivce要加入的 Thing Group
* Step 3: 安裝 Greengrass Core Software

### Step 3: 安裝 Greengrass Core Software
* Step 3.1 / 3.2 接已經完成

#### Step 3.3 執行 Greengrass Installer
* 安裝 unzip
```
sudo apt install unzip
```
* 下載 Installer並解壓縮
```
curl -s https://d2s8p88vqu9w66.cloudfront.net/releases/greengrass-nucleus-latest.zip > greengrass-nucleus-latest.zip && unzip greengrass-nucleus-latest.zip -d GreengrassInstaller
```
* 運行 Installer
* 這邊的指令已經依據前面步驟對於Greengrass Code Device的設定帶入。是一段客製的指令
```
sudo -E java -Droot="/greengrass/v2" -Dlog.store=FILE -jar ./GreengrassInstaller/lib/Greengrass.jar --aws-region ap-northeast-1 --thing-name GreengrassQuickStartCore-192517e784b --thing-group-name GreengrassQuickStartGroup --component-default-user ggc_user:ggc_group --provision true --setup-system-service true --deploy-dev-tools true
```

* 執行安裝指令
```
sudo -E java -Droot="/greengrass/v2" -Dlog.store=FILE -jar ./GreengrassInstaller/lib/Greengrass.jar --aws-region ap-northeast-1 --thing-name GreengrassQuickStartCore-192517e784b --thing-group-name GreengrassQuickStartGroup --component-default-user ggc_user:ggc_group --provision true --setup-system-service true --deploy-dev-tools true
```
##### 檢視安裝 Log
* 建立 user / group
```
Creating user ggc_user
ggc_user created
Creating group ggc_group
ggc_group created
Added ggc_user to ggc_group
```
* 在Cloud side建立對應的 Thing 來代表這個裝置
```
Provisioning AWS IoT resources for the device with IoT Thing Name: [GreengrassQuickStartCore-192517e784b]...
Creating new IoT policy "GreengrassV2IoTThingPolicy"
Creating keys and certificate...
Attaching policy to certificate...
Creating IoT Thing "GreengrassQuickStartCore-192517e784b"...
Attaching certificate to IoT thing...
Successfully provisioned AWS IoT resources for the device with IoT Thing Name: [GreengrassQuickStartCore-192517e784b]!
```
* 可以到 AWS IoT Core console 確認以上設定是否如實建立
https://ap-northeast-1.console.aws.amazon.com/iot/home?region=ap-northeast-1#/thinghub
* 將這台設備的 Greengrass Core device 對應的 thing 加入到前面設定中指定的 thing group 當中,簡化未來針對機群的管理
```
Adding IoT Thing [GreengrassQuickStartCore-192517e784b] into Thing Group: [GreengrassQuickStartGroup]...
Successfully added Thing into Thing Group: [GreengrassQuickStartGroup]
```
* 設定 Greengrass Core device 裝置端所需要的權限 (GreengrassV2TokenExchangeRole)
```
Setting up resources for aws.greengrass.TokenExchangeService ...
TES role alias "GreengrassV2TokenExchangeRoleAlias" does not exist, creating new alias...
TES role "GreengrassV2TokenExchangeRole" does not exist, creating role...
IoT role policy "GreengrassTESCertificatePolicyGreengrassV2TokenExchangeRoleAlias" for TES Role alias not exist, creating policy...
Attaching TES role policy to IoT thing...
No managed IAM policy found, looking for user defined policy...
No IAM policy found, will attempt creating one...
IAM role policy for TES "GreengrassV2TokenExchangeRoleAccess" created. This policy DOES NOT have S3 access, please modify it with your private components' artifact buckets/objects as needed when you create and deploy private components
Attaching IAM role policy for TES to IAM role for TES...
Configuring Nucleus with provisioned resource details...
Downloading CA from "https://www.amazontrust.com/repository/AmazonRootCA1.pem"
Created device configuration
Successfully configured Nucleus with provisioned resource details!
```
* 進行預設的 Component Deployment
```
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
```
* 至此已經完成 Greengrass Core device 的軟體安裝在EC2的流程。
* 在上面的log中可以看到一段log說明:IAM role policy for TES "GreengrassV2TokenExchangeRoleAccess" created. This policy DOES NOT have S3 access, please modify it with your private components' artifact buckets/objects as needed when you create and deploy private components ,因此我們需要再下一個流程中,按照需求加入component所需要的權限。
## Chapter 3.2 加入 Component 所需權限
### 建立 IAM Policy
* 由於這次 workshop component 還需要以下權限
* Publish your custom component (using S3)
* MQTT Bridge
* 我們需要建立一個 IAM Policy,掛載到前面 Greengrass Core device 建立過程中,建立起來的 IAM Role,才能提供給 Greengrass Core device 端所需使用的 AWS 服務權限。
* 前往 AWS IAM Policy [console](https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/policies)
* 點擊橘色 Create policy

* 先將右側 Visual 切換至 JSON 模式

* 複製第四步之 IAM policy到 JSON Policy Editor區塊,點擊右下方橘色 Next
```
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3BucketActions",
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"s3:PutObject",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
},
{
"Effect": "Allow",
"Action": [
"iot:*"
],
"Resource": "*"
},
{
"Sid": "GreengrassActions",
"Effect": "Allow",
"Action": [
"greengrass:*"
],
"Resource": "*"
}
]
}
```

* 自行取名 Policy name,EX: workshop_s3_iot_gg_policy,並滑到右下方點擊橘色 Create policy

* 搜尋 policy 列表,確認成功建立。

* 前往 AWS IAM Role [console](https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/roles),並在搜尋列搜尋 GreengrassV2TokenExchangeRole,此 Role 應該在前面 Greengrass installer 執行階段被建立,我們要將裡授予的權限,額外加入。
* 點擊 GreengrassV2TokenExchangeRole

* 在 Permissions 分頁,點擊 Add permissions 下拉選單,選擇 Attach policies。

* 搜尋 workshop_s3_iot_gg_policy ,勾選 workshop_s3_iot_gg_policy,點擊右側橘色 Add permissions

* 完成後可以在 GreengrassV2TokenExchangeRole 的頁面看到 Permissions policies 有兩個附加 polcies

# Chapter 4 建立第一個 Component
[Reference](https://catalog.workshops.aws/greengrass/en-US/chapter4-createfirstcomp)
## Chapter 4.0 利用 CLI 檢視目前設備上的狀態
* 確認 greengrass-cli 版本
```
ubuntu@ip-10-0-1-21:~$ /greengrass/v2/bin/greengrass-cli -V
Greengrass CLI Version: 2.13.0
```
* 確認 systemctl 上的 greengrass.service 狀態
```
ubuntu@ip-10-0-1-21:~$ sudo systemctl status greengrass.service
● greengrass.service - Greengrass Core
Loaded: loaded (/etc/systemd/system/greengrass.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2024-10-03 08:52:37 UTC; 2h 59min ago
Main PID: 82324 (sh)
Tasks: 45 (limit: 18962)
Memory: 314.5M
CPU: 27.693s
CGroup: /system.slice/greengrass.service
├─82324 /bin/sh /greengrass/v2/alts/current/distro/bin/loader
└─82332 java -Dlog.store=FILE -Dlog.store=FILE -Droot=/greengrass/v2 -jar /greengrass/v2/alts/current/distro/lib/Greengrass.jar --setup-system-service false
Oct 03 08:52:37 ip-10-0-1-21 systemd[1]: Started Greengrass Core.
Oct 03 08:52:37 ip-10-0-1-21 sh[82324]: Greengrass root: /greengrass/v2
Oct 03 08:52:37 ip-10-0-1-21 sh[82324]: Java executable: java
Oct 03 08:52:37 ip-10-0-1-21 sh[82324]: JVM options: -Dlog.store=FILE -Droot=/greengrass/v2
Oct 03 08:52:37 ip-10-0-1-21 sh[82324]: Nucleus options: --setup-system-service false
Oct 03 08:52:38 ip-10-0-1-21 sh[82332]: Launching Nucleus...
Oct 03 08:52:39 ip-10-0-1-21 sh[82332]: Launched Nucleus successfully.
```
* 確認目前運行了哪些 Component?
```
ubuntu@ip-10-0-1-21:~$ sudo /greengrass/v2/bin/greengrass-cli component list
Components currently running in Greengrass:
Component Name: aws.greengrass.Nucleus
Version: 2.13.0
State: FINISHED
Configuration: {"awsRegion":"ap-northeast-1","componentStoreMaxSizeBytes":"10000000000","deploymentPollingFrequencySeconds":"15","envStage":"prod","fipsMode":"false","fleetStatus":{"periodicStatusPublishIntervalSeconds":86400.0},"greengrassDataPlaneEndpoint":"","greengrassDataPlanePort":"8443","httpClient":{},"iotCredEndpoint":"c2s1jcn8qal0l7.credentials.iot.ap-northeast-1.amazonaws.com","iotDataEndpoint":"a1ae47nl46extm-ats.iot.ap-northeast-1.amazonaws.com","iotRoleAlias":"GreengrassV2TokenExchangeRoleAlias","jvmOptions":"-Dlog.store=FILE","logging":{},"mqtt":{"spooler":{}},"networkProxy":{"proxy":{}},"platformOverride":{},"runWithDefault":{"posixUser":"ggc_user:ggc_group"},"s3EndpointType":"GLOBAL","telemetry":{}}
Component Name: UpdateSystemPolicyService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: FleetStatusService
Version: null
State: RUNNING
Configuration: null
Component Name: DeploymentService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: TelemetryAgent
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: aws.greengrass.Cli
Version: 2.13.0
State: RUNNING
Configuration: {"AuthorizedPosixGroups":null,"AuthorizedWindowsGroups":null}
ubuntu@ip-10-0-1-21:~$
```
## Chapter 4.1 Creating
https://catalog.workshops.aws/greengrass/en-US/chapter4-createfirstcomp/10-step1
[官方文件](https://docs.aws.amazon.com/greengrass/v2/developerguide/create-components.html)
### 建立一個Helloworld componet

* 建立 artifact 檔案,貼上程式碼
https://catalog.workshops.aws/greengrass/en-US/chapter4-createfirstcomp/10-step1#creating-component-artifacts
* 建立 artifacts 的檔案結構
```
mkdir -p ~/environment/GreengrassCore/artifacts/com.example.HelloWorld/1.0.0 && touch ~/environment/GreengrassCore/artifacts/com.example.HelloWorld/1.0.0/hello_world.py
```
* 範例程式碼 hello_world.py
```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
import sys
import datetime
import time
while True:
message = f"Hello, {sys.argv[1]}! Current time: {str(datetime.datetime.now())}."
# Print the message to stdout.
print(message)
# Append the message to the log file.
with open('/tmp/Greengrass_HelloWorld.log', 'a') as f:
print(message, file=f)
time.sleep(1)
```

* 建立 recipe 檔案,貼上程式碼
https://catalog.workshops.aws/greengrass/en-US/chapter4-createfirstcomp/10-step1#creating-a-recipe-for-a-component
* 建立 recipes 的檔案結構
```
mkdir -p ~/environment/GreengrassCore/recipes && touch ~/environment/GreengrassCore/recipes/com.example.HelloWorld-1.0.0.json
```
* 範例程式碼 com.example.HelloWorld-1.0.0.json
```
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.example.HelloWorld",
"ComponentVersion": "1.0.0",
"ComponentDescription": "My first AWS IoT Greengrass component.",
"ComponentPublisher": "Amazon",
"ComponentConfiguration": {
"DefaultConfiguration": {
"Message": "world"
}
},
"Manifests": [
{
"Platform": {
"os": "linux"
},
"Lifecycle": {
"Run": "python3 -u {artifacts:path}/hello_world.py '{configuration:/Message}'\n"
}
}
]
}
```
## Chapter 4.2 Run and test
https://catalog.workshops.aws/greengrass/en-US/chapter4-createfirstcomp/20-step2
* 該怎麼印出 Greengrass log 來了解他的運作狀況
```
sudo tail -F /greengrass/v2/logs/greengrass.log
```
* 接下來我們要將上面的Component Hello World 部署到greengrass上,我們會利用 greengrass cli在device上執行部署工作
```
sudo /greengrass/v2/bin/greengrass-cli deployment create \
--recipeDir ~/environment/GreengrassCore/recipes \
--artifactDir ~/environment/GreengrassCore/artifacts \
--merge "com.example.HelloWorld=1.0.0"
```
* 執行結果
```
ubuntu@ip-10-0-1-21:~$ sudo /greengrass/v2/bin/greengrass-cli deployment create \
--recipeDir ~/environment/GreengrassCore/recipes \
--artifactDir ~/environment/GreengrassCore/artifacts \
--merge "com.example.HelloWorld=1.0.0"
Local deployment submitted! Deployment Id: f7e8374a-42f4-48ed-94ff-2f61052762ab
```
* 根據程式碼,我們知道Hello world會對 /tmp/Greengrass_HelloWorld.log 持續寫入
```
ubuntu@ip-10-0-1-21:~$ tail -F /tmp/Greengrass_HelloWorld.log
Hello, world! Current time: 2024-10-03 12:44:26.965872.
Hello, world! Current time: 2024-10-03 12:44:27.967104.
Hello, world! Current time: 2024-10-03 12:44:28.968408.
Hello, world! Current time: 2024-10-03 12:44:29.969654.
Hello, world! Current time: 2024-10-03 12:44:30.970868.
Hello, world! Current time: 2024-10-03 12:44:31.972124.
Hello, world! Current time: 2024-10-03 12:44:32.973385.
Hello, world! Current time: 2024-10-03 12:44:33.974689.
Hello, world! Current time: 2024-10-03 12:44:34.975927.
Hello, world! Current time: 2024-10-03 12:44:35.977168.
Hello, world! Current time: 2024-10-03 12:44:36.978405.
Hello, world! Current time: 2024-10-03 12:44:37.979632.
```
* 此時再度 list component
```
ubuntu@ip-10-0-1-21:~$ sudo /greengrass/v2/bin/greengrass-cli component list
Components currently running in Greengrass:
Component Name: aws.greengrass.Nucleus
Version: 2.13.0
State: FINISHED
Configuration: {"awsRegion":"ap-northeast-1","componentStoreMaxSizeBytes":"10000000000","deploymentPollingFrequencySeconds":"15","envStage":"prod","fipsMode":"false","fleetStatus":{"periodicStatusPublishIntervalSeconds":86400.0},"greengrassDataPlaneEndpoint":"","greengrassDataPlanePort":"8443","httpClient":{},"iotCredEndpoint":"c2s1jcn8qal0l7.credentials.iot.ap-northeast-1.amazonaws.com","iotDataEndpoint":"a1ae47nl46extm-ats.iot.ap-northeast-1.amazonaws.com","iotRoleAlias":"GreengrassV2TokenExchangeRoleAlias","jvmOptions":"-Dlog.store=FILE","logging":{},"mqtt":{"spooler":{}},"networkProxy":{"proxy":{}},"platformOverride":{},"runWithDefault":{"posixShell":"sh","posixUser":"ggc_user:ggc_group"},"s3EndpointType":"GLOBAL","telemetry":{}}
Component Name: com.example.HelloWorld
Version: 1.0.0
State: RUNNING
Configuration: {"Message":"world"}
Component Name: UpdateSystemPolicyService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: FleetStatusService
Version: null
State: RUNNING
Configuration: null
Component Name: DeploymentService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: TelemetryAgent
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: aws.greengrass.Cli
Version: 2.13.0
State: RUNNING
Configuration: {"AuthorizedPosixGroups":null,"AuthorizedWindowsGroups":null}
```
## Chapter 4.3 Publish
https://catalog.workshops.aws/greengrass/en-US/chapter4-createfirstcomp/30-step3
### 建立 S3 bucket 來儲存 artifacts 與 recipe
* 執行
```
EPOCH_TIME=$(date +"%s") && S3_BUCKET=ggcv2-workshop-$HOSTNAME-$EPOCH_TIME && aws s3 mb s3://$S3_BUCKET --region $AWS_DEFAULT_REGION
```
* 結果
```
ubuntu@ip-10-0-1-21:~$ EPOCH_TIME=$(date +"%s") && S3_BUCKET=ggcv2-workshop-$HOSTNAME-$EPOCH_TIME && aws s3 mb s3://$S3_BUCKET --region $AWS_DEFAULT_REGION
make_bucket: ggcv2-workshop-ip-10-0-1-21-1727960283
```
* 修改 com.example.HelloWorld-1.0.0.json
* '[YOUR BUCKET NAME]': 剛剛建立的:EX: ggcv2-workshop-ip-10-0-1-21-1727960283
* 指定好 Component 的 Artifact 路徑在 recipe 之後,我們就準備將檔案上傳到指定的 S3
```
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.example.HelloWorld",
"ComponentVersion": "1.0.0",
"ComponentDescription": "My first AWS IoT Greengrass component.",
"ComponentPublisher": "Amazon",
"ComponentConfiguration": {
"DefaultConfiguration": {
"Message": "world"
}
},
"Manifests": [
{
"Platform": {
"os": "linux"
},
"Lifecycle": {
"Run": "python3 -u {artifacts:path}/hello_world.py '{configuration:/Message}'\n"
},
"Artifacts": [
{
"URI": "s3://[YOUR BUCKET NAME]/artifacts/com.example.HelloWorld/1.0.0/hello_world.py"
}
]
}
]
}
```
* 執行
```
aws s3 cp --recursive /home/ubuntu/environment/GreengrassCore/ s3://$S3_BUCKET/
```
* 結果
```
ubuntu@ip-10-0-1-21:~$ aws s3 cp --recursive /home/ubuntu/environment/GreengrassCore/ s3://$S3_BUCKET/
upload: environment/GreengrassCore/recipes/com.example.HelloWorld-1.0.0.json to s3://ggcv2-workshop-ip-10-0-1-21-1727960283/recipes/com.example.HelloWorld-1.0.0.json
upload: environment/GreengrassCore/artifacts/com.example.HelloWorld/1.0.0/hello_world.py to s3://ggcv2-workshop-ip-10-0-1-21-1727960283/artifacts/com.example.HelloWorld/1.0.0/hello_world.py
```
* 可以練習到 AWS S3 Console 找找看是否正確上傳



* 接下來就要正式將recipe註冊到 AWS Greengrass component
```
cd /home/ubuntu/environment/GreengrassCore/recipes && aws greengrassv2 create-component-version --inline-recipe fileb://com.example.HelloWorld-1.0.0.json --region $AWS_DEFAULT_REGION
```
* 執行結果
```
ubuntu@ip-10-0-1-21:~$ cd /home/ubuntu/environment/GreengrassCore/recipes && aws greengrassv2 create-component-version --inline-recipe fileb://com.example.HelloWorld-1.0.0.json --region $AWS_DEFAULT_REGION
{
"arn": "arn:aws:greengrass:ap-northeast-1:911551666144:components:com.example.HelloWorld:versions:1.0.0",
"componentName": "com.example.HelloWorld",
"componentVersion": "1.0.0",
"creationTimestamp": 1727961345.645,
"status": {
"componentState": "REQUESTED",
"message": "NONE",
"errors": {}
}
}
```
* 執行以下指令,列出目前 Greengrass 裡自建的component
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/recipes$ aws greengrassv2 list-components --region $AWS_DEFAULT_REGION
{
"components": [
{
"arn": "arn:aws:greengrass:ap-northeast-1:911551666144:components:com.example.HelloWorld",
"componentName": "com.example.HelloWorld",
"latestVersion": {
"arn": "arn:aws:greengrass:ap-northeast-1:911551666144:components:com.example.HelloWorld:versions:1.0.0",
"componentVersion": "1.0.0",
"creationTimestamp": 1727961345.645,
"description": "My first AWS IoT Greengrass component.",
"publisher": "Amazon",
"platforms": [
{
"attributes": {
"os": "linux"
}
}
]
}
}
]
}
```
* AWS Greengrass console 中也可以看見此 component

## Chapter 4.4 Deploy
https://catalog.workshops.aws/greengrass/en-US/chapter4-createfirstcomp/40-step4
* 既然已經publish,就可以正式利用 AWS Console,將指定的 Component 部署到指定的 Greengrass Core device上。
* 移除第一次的 Local Deployment
```
sudo /greengrass/v2/bin/greengrass-cli deployment create --remove "com.example.HelloWorld"
```
* 確認已經移除
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/recipes$ sudo /greengrass/v2/bin/greengrass-cli deployment create --remove "com.example.HelloWorld"
Local deployment submitted! Deployment Id: 4022ff29-a208-49d5-ac43-ce3b8e67363d
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/recipes$ sudo /greengrass/v2/bin/greengrass-cli component list
Components currently running in Greengrass:
Component Name: aws.greengrass.Nucleus
Version: 2.13.0
State: FINISHED
Configuration: {"awsRegion":"ap-northeast-1","componentStoreMaxSizeBytes":"10000000000","deploymentPollingFrequencySeconds":"15","envStage":"prod","fipsMode":"false","fleetStatus":{"periodicStatusPublishIntervalSeconds":86400.0},"greengrassDataPlaneEndpoint":"","greengrassDataPlanePort":"8443","httpClient":{},"iotCredEndpoint":"c2s1jcn8qal0l7.credentials.iot.ap-northeast-1.amazonaws.com","iotDataEndpoint":"a1ae47nl46extm-ats.iot.ap-northeast-1.amazonaws.com","iotRoleAlias":"GreengrassV2TokenExchangeRoleAlias","jvmOptions":"-Dlog.store=FILE","logging":{},"mqtt":{"spooler":{}},"networkProxy":{"proxy":{}},"platformOverride":{},"runWithDefault":{"posixShell":"sh","posixUser":"ggc_user:ggc_group"},"s3EndpointType":"GLOBAL","telemetry":{}}
Component Name: UpdateSystemPolicyService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: FleetStatusService
Version: null
State: RUNNING
Configuration: null
Component Name: DeploymentService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: TelemetryAgent
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: aws.greengrass.Cli
Version: 2.13.0
State: RUNNING
Configuration: {"AuthorizedPosixGroups":null,"AuthorizedWindowsGroups":null}
```
* 前往 AWS IoT Greengrass Deployment [console](https://ap-northeast-1.console.aws.amazon.com/iot/home?region=ap-northeast-1#/greengrass/v2/deployments)
* 勾選 Deployment for GreengrassQuickStartGroup,接著點擊 Revise

* Step 1 - Specify target 維持預設,我們還是針對我們這一台Greengrass Core device 進行 Deploy,點擊右下橘色 Next
* Step 2 - Select components,選取 com.example.HelloWorld,點擊右下橘色 Next

* Step 3 - Configure 選中的components,勾選後點擊Configure component

* 在 Configure to merge 內加上 Message
```
{
"Message": "this was deployed from AWS IoT Core"
}
```
* 點擊 Confirm

* 接著點擊右下角橘色 Next
* Step 4 - Configure advanced settings,保持預設,點擊右下角橘色 Next

* Step 5 - Review,確認無誤後,點擊最底下橘色 Deploy,接這可以開始監控 Execution 狀況

* 藉由 Execution overview dashboard 可以監控部署狀況


* 當看到Succeeded之後,可以回到 VSC Terminal,列出元件
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/recipes$ sudo /greengrass/v2/bin/greengrass-cli component list
Components currently running in Greengrass:
Component Name: aws.greengrass.Nucleus
Version: 2.13.0
State: FINISHED
Configuration: {"awsRegion":"ap-northeast-1","componentStoreMaxSizeBytes":"10000000000","deploymentPollingFrequencySeconds":"15","envStage":"prod","fipsMode":"false","fleetStatus":{"periodicStatusPublishIntervalSeconds":86400.0},"greengrassDataPlaneEndpoint":"","greengrassDataPlanePort":"8443","httpClient":{},"iotCredEndpoint":"c2s1jcn8qal0l7.credentials.iot.ap-northeast-1.amazonaws.com","iotDataEndpoint":"a1ae47nl46extm-ats.iot.ap-northeast-1.amazonaws.com","iotRoleAlias":"GreengrassV2TokenExchangeRoleAlias","jvmOptions":"-Dlog.store=FILE","logging":{},"mqtt":{"spooler":{}},"networkProxy":{"proxy":{}},"platformOverride":{},"runWithDefault":{"posixShell":"sh","posixUser":"ggc_user:ggc_group"},"s3EndpointType":"GLOBAL","telemetry":{}}
Component Name: UpdateSystemPolicyService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: FleetStatusService
Version: null
State: RUNNING
Configuration: null
Component Name: com.example.HelloWorld
Version: 1.0.0
State: RUNNING
Configuration: {"Message":"this was deployed from AWS IoT Core"}
Component Name: DeploymentService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: TelemetryAgent
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: aws.greengrass.Cli
Version: 2.13.0
State: RUNNING
Configuration: {"AuthorizedPosixGroups":null,"AuthorizedWindowsGroups":null}
```
* 可以看到剛剛 Config的 message
```
Component Name: com.example.HelloWorld
Version: 1.0.0
State: RUNNING
Configuration: {"Message":"this was deployed from AWS IoT Core"}
```
* 而目前 component 輸出的log也改為
```
ubuntu@ip-10-0-1-21:~$ tail -F /tmp/Greengrass_HelloWorld.log
Hello, this was deployed from AWS IoT Core! Current time: 2024-10-03 14:02:43.066374.
Hello, this was deployed from AWS IoT Core! Current time: 2024-10-03 14:02:44.067647.
Hello, this was deployed from AWS IoT Core! Current time: 2024-10-03 14:02:45.068968.
Hello, this was deployed from AWS IoT Core! Current time: 2024-10-03 14:02:46.070228.
Hello, this was deployed from AWS IoT Core! Current time: 2024-10-03 14:02:47.071518.
Hello, this was deployed from AWS IoT Core! Current time: 2024-10-03 14:02:48.072745.
```
## Chapter 4.5 Update
https://catalog.workshops.aws/greengrass/en-US/chapter4-createfirstcomp/40-step5
* 接下來我們要練習更新版本,
### 建立新版本的 artifact 以及 recipe
* 執行建立新的artifact檔案
```
mkdir -p ~/environment/GreengrassCore/artifacts/com.example.HelloWorld/1.1.0 && touch ~/environment/GreengrassCore/artifacts/com.example.HelloWorld/1.1.0/second_iteration.py
```
* second_iteraton.py
```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
import sys
import datetime
import time
while True:
message = f"Hello, {sys.argv[1]}! This is the second iteration. Current time: {str(datetime.datetime.now())}."
# Print the message to stdout.
print(message)
# Append the message to the log file.
with open('/tmp/Greengrass_SecondIteration.log', 'a') as f:
print(message, file=f)
time.sleep(1)
```
* 建立新的 recipe:com.example.HelloWorld-1.1.0.json在/home/ubuntu/environment/GreengrassCore/recipes
* 記得更換 [YOUR BUCKET NAME]: EX:ggcv2-workshop-ip-10-0-1-21-1727960283
```
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.example.HelloWorld",
"ComponentVersion": "1.1.0",
"ComponentDescription": "My first AWS IoT Greengrass component.",
"ComponentPublisher": "Amazon",
"ComponentConfiguration": {
"DefaultConfiguration": {
"Message": "world"
}
},
"Manifests": [
{
"Platform": {
"os": "linux"
},
"Lifecycle": {
"Run": "python3 -u {artifacts:path}/second_iteration.py '{configuration:/Message}'\n"
},
"Artifacts": [
{
"URI": "s3://[YOUR BUCKET NAME]/artifacts/com.example.HelloWorld/1.1.0/second_iteration.py"
}
]
}
]
}
```
### 上完更新到 S3
* 執行
```
aws s3 cp --recursive ~/environment/GreengrassCore/ s3://$S3_BUCKET/
```
* 結果
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/recipes$ aws s3 cp --recursive ~/environment/GreengrassCore/ s3://$S3_BUCKET/
upload: ./com.example.HelloWorld-1.0.0.json to s3://ggcv2-workshop-ip-10-0-1-21-1727960283/recipes/com.example.HelloWorld-1.0.0.json
upload: ../artifacts/com.example.HelloWorld/1.0.0/hello_world.py to s3://ggcv2-workshop-ip-10-0-1-21-1727960283/artifacts/com.example.HelloWorld/1.0.0/hello_world.py
upload: ../artifacts/com.example.HelloWorld/1.1.0/second_iteration.py to s3://ggcv2-workshop-ip-10-0-1-21-1727960283/artifacts/com.example.HelloWorld/1.1.0/second_iteration.py
upload: ./com.example.HelloWorld-1.1.0.json to s3://ggcv2-workshop-ip-10-0-1-21-1727960283/recipes/com.example.HelloWorld-1.1.0.json
```
### Publish 新版component
* 執行
```
cd ~/environment/GreengrassCore/recipes && aws greengrassv2 create-component-version --inline-recipe fileb://com.example.HelloWorld-1.1.0.json --region $AWS_DEFAULT_REGION
```
* 結果
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/recipes$ cd ~/environment/GreengrassCore/recipes && aws greengrassv2 create-component-version --inline-recipe fileb://com.example.HelloWorld-1.1.0.json --region $AWS_DEFAULT_REGION
{
"arn": "arn:aws:greengrass:ap-northeast-1:911551666144:components:com.example.HelloWorld:versions:1.1.0",
"componentName": "com.example.HelloWorld",
"componentVersion": "1.1.0",
"creationTimestamp": 1727964925.121,
"status": {
"componentState": "REQUESTED",
"message": "NONE",
"errors": {}
}
}
```
### 重複 4.4 的步驟進行更新
* 將 component revise 到 version 1.1.0,點擊 confirm

* 最後同樣點擊 Deploy
* Execution
* queued
* In progress
* Succeeded
* 部署成功後,確認日誌
```
ubuntu@ip-10-0-1-21:~$
tail -F /tmp/Greengrass_SecondIteration.log
Hello, this was deployed from AWS IoT Core! This is the second iteration. Current time: 2024-10-03 14:20:27.199849.
Hello, this was deployed from AWS IoT Core! This is the second iteration. Current time: 2024-10-03 14:20:28.201047.
Hello, this was deployed from AWS IoT Core! This is the second iteration. Current time: 2024-10-03 14:20:29.202238.
Hello, this was deployed from AWS IoT Core! This is the second iteration. Current time: 2024-10-03 14:20:30.203451.
Hello, this was deployed from AWS IoT Core! This is the second iteration. Current time: 2024-10-03 14:20:31.204732.
```
# Chapter 5 Pub / Sub IPC
[Reference](https://catalog.workshops.aws/greengrass/en-US/chapter5-pubsubipc)

* Greengrass 2.0 provides an inter-process communication (IPC) SDK for inter-process communication. By using IPC SDK feature, Publisher (Pub) and Subscriber (Sub) communication can be performed between each process of the custom component.
* In this step, you'll learn how to create a component that reads sensor data and keeps publishing to a topic, a component that subscribes to that topic logs and receives the messages, and a custom component that uses IPC.
## Chapter 5.1 Publisher
* 安裝 pip3
```
sudo apt install python3-pip
```
* 建立 artifact
```
mkdir -p ~/environment/GreengrassCore/artifacts/com.example.Publisher/1.0.0 && cd ~/environment/GreengrassCore/artifacts/com.example.Publisher/1.0.0
touch example_publisher.py dummy_sensor.py
```
* 複製以下程式碼到 dummy_sensor.py
```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
from random import gauss
class DummySensor(object):
def __init__(self, mean=25, variance=1):
self.mu = mean
self.sigma = variance
def read_value(self):
return float("%.2f" % (gauss(1000, 20)))
if __name__ == '__main__':
sensor = DummySensor()
print(sensor.read_value())
```
* 複製以下程式碼到 example_publisher.py
```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
import time
import datetime
import json
import awsiot.greengrasscoreipc
from awsiot.greengrasscoreipc.model import (
PublishToTopicRequest,
PublishMessage,
JsonMessage
)
from dummy_sensor import DummySensor
TIMEOUT = 10
publish_rate = 1.0
ipc_client = awsiot.greengrasscoreipc.connect()
sensor = DummySensor()
topic = "my/topic"
while True:
message = {"timestamp": str(datetime.datetime.now()),
"value": sensor.read_value()}
message_json = json.dumps(message).encode('utf-8')
request = PublishToTopicRequest()
request.topic = topic
publish_message = PublishMessage()
publish_message.json_message = JsonMessage()
publish_message.json_message.message = message
request.publish_message = publish_message
operation = ipc_client.new_publish_to_topic()
operation.activate(request)
future = operation.get_response()
future.result(TIMEOUT)
print("publish")
time.sleep(1/publish_rate)
```
* 建立 recipe: com.example.Publisher-1.0.0.json
```
touch ~/environment/GreengrassCore/recipes/com.example.Publisher-1.0.0.json
```

* 複製程式碼到 com.example.Publisher-1.0.0.json
```
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.example.Publisher",
"ComponentVersion": "1.0.0",
"ComponentDescription": "A component that publishes messages.",
"ComponentPublisher": "Amazon",
"ComponentConfiguration": {
"DefaultConfiguration": {
"accessControl": {
"aws.greengrass.ipc.pubsub": {
"com.example.Publisher:pubsub:1": {
"policyDescription": "Allows access to publish to all topics.",
"operations": [
"aws.greengrass#PublishToTopic"
],
"resources": [
"*"
]
}
}
}
}
},
"Manifests": [
{
"Lifecycle": {
"Install": "pip3 install awsiotsdk numpy",
"Run": "python3 -u {artifacts:path}/example_publisher.py"
}
}
]
}
```
* 執行:Local Deploy Publisher
```
sudo /greengrass/v2/bin/greengrass-cli deployment create \
--recipeDir ~/environment/GreengrassCore/recipes \
--artifactDir ~/environment/GreengrassCore/artifacts \
--merge "com.example.Publisher=1.0.0"
```
* 結果
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.Publisher/1.0.0$ sudo /greengrass/v2/bin/greengrass-cli deployment create \
--recipeDir ~/environment/GreengrassCore/recipes \
--artifactDir ~/environment/GreengrassCore/artifacts \
--merge "com.example.Publisher=1.0.0"
Local deployment submitted! Deployment Id: 2cf87f37-e0b9-4d4b-9995-ef6e2e2c5986
```
## Chapter 5.2 Subscriber
* 接下來這邊要建立一個Subscriber去訂閱上一段建立的topic
* 建立 Subsriber component
```
mkdir -p ~/environment/GreengrassCore/artifacts/com.example.Subscriber/1.0.0 && cd ~/environment/GreengrassCore/artifacts/com.example.Subscriber/1.0.0
touch example_subscriber.py
```
* 複製程式碼到 example_subscriber.py
```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
import time
import json
import awsiot.greengrasscoreipc
import awsiot.greengrasscoreipc.client as client
from awsiot.greengrasscoreipc.model import (
SubscribeToTopicRequest,
SubscriptionResponseMessage
)
TIMEOUT = 10
ipc_client = awsiot.greengrasscoreipc.connect()
class StreamHandler(client.SubscribeToTopicStreamHandler):
def __init__(self):
super().__init__()
def on_stream_event(self, event: SubscriptionResponseMessage) -> None:
message_string = event.json_message.message
with open('/tmp/Greengrass_Subscriber.log', 'a') as f:
print(message_string, file=f)
def on_stream_error(self, error: Exception) -> bool:
return True
def on_stream_closed(self) -> None:
pass
topic = "my/topic"
request = SubscribeToTopicRequest()
request.topic = topic
handler = StreamHandler()
operation = ipc_client.new_subscribe_to_topic(handler)
future = operation.activate(request)
while True:
time.sleep(1)
operation.close()
```
* 建立 recipe
```
touch ~/environment/GreengrassCore/recipes/com.example.Subscriber-1.0.0.json
```
* 複製組態到 com.example.Subscriber-1.0.0.json
```
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.example.Subscriber",
"ComponentVersion": "1.0.0",
"ComponentDescription": "A component that subscribes to messages.",
"ComponentPublisher": "Amazon",
"ComponentConfiguration": {
"DefaultConfiguration": {
"accessControl": {
"aws.greengrass.ipc.pubsub": {
"com.example.Subscriber:pubsub:1": {
"policyDescription": "Allows access to publish to all topics.",
"operations": [
"aws.greengrass#SubscribeToTopic"
],
"resources": [
"*"
]
}
}
}
}
},
"Manifests": [
{
"Lifecycle": {
"Install": "pip3 install awsiotsdk",
"Run": "python3 -u {artifacts:path}/example_subscriber.py"
}
}
]
}
```
* 執行:Local Deploy component
```
sudo /greengrass/v2/bin/greengrass-cli deployment create \
--recipeDir ~/environment/GreengrassCore/recipes \
--artifactDir ~/environment/GreengrassCore/artifacts \
--merge "com.example.Subscriber=1.0.0"
```
* 結果
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.Subscriber/1.0.0$ sudo /greengrass/v2/bin/greengrass-cli deployment create \
--recipeDir ~/environment/GreengrassCore/recipes \
--artifactDir ~/environment/GreengrassCore/artifacts \
--merge "com.example.Subscriber=1.0.0"
Local deployment submitted! Deployment Id: 27182195-27e2-4726-888a-d8f5795d1a13
```
* list component,確認 publisher / subscriber 皆為 running 狀態
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.Subscriber/1.0.0$ sudo /greengrass/v2/bin/greengrass-cli component list
Components currently running in Greengrass:
Component Name: FleetStatusService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: aws.greengrass.Nucleus
Version: 2.13.0
State: FINISHED
Configuration: {"awsRegion":"ap-northeast-1","componentStoreMaxSizeBytes":"10000000000","deploymentPollingFrequencySeconds":"15","envStage":"prod","fipsMode":"false","fleetStatus":{"periodicStatusPublishIntervalSeconds":86400.0},"greengrassDataPlaneEndpoint":"","greengrassDataPlanePort":"8443","httpClient":{},"iotCredEndpoint":"c2s1jcn8qal0l7.credentials.iot.ap-northeast-1.amazonaws.com","iotDataEndpoint":"a1ae47nl46extm-ats.iot.ap-northeast-1.amazonaws.com","iotRoleAlias":"GreengrassV2TokenExchangeRoleAlias","jvmOptions":"-Dlog.store=FILE","logging":{},"mqtt":{"spooler":{}},"networkProxy":{"proxy":{}},"platformOverride":{},"runWithDefault":{"posixShell":"sh","posixUser":"ggc_user:ggc_group"},"s3EndpointType":"GLOBAL","telemetry":{}}
Component Name: TelemetryAgent
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: DeploymentService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: com.example.HelloWorld
Version: 1.1.0
State: RUNNING
Configuration: {"Message":"this was deployed from AWS IoT Core"}
Component Name: com.example.Subscriber
Version: 1.0.0
State: RUNNING
Configuration: {"accessControl":{"aws.greengrass.ipc.pubsub":{"com.example.Subscriber:pubsub:1":{"operations":["aws.greengrass#SubscribeToTopic"],"policyDescription":"Allows access to publish to all topics.","resources":["*"]}}}}
Component Name: UpdateSystemPolicyService
Version: 0.0.0
State: RUNNING
Configuration: null
Component Name: aws.greengrass.Cli
Version: 2.13.0
State: RUNNING
Configuration: {"AuthorizedPosixGroups":null,"AuthorizedWindowsGroups":null}
```
* 確認 Subsriber log
```
ubuntu@ip-10-0-1-21:~$ tail -f /tmp/Greengrass_Subscriber.log
{'timestamp': '2024-10-03 15:49:11.648591', 'value': 970.55}
{'timestamp': '2024-10-03 15:49:12.650617', 'value': 1008.2}
{'timestamp': '2024-10-03 15:49:13.651715', 'value': 978.8}
{'timestamp': '2024-10-03 15:49:14.653623', 'value': 1008.35}
{'timestamp': '2024-10-03 15:49:15.655601', 'value': 1022.48}
{'timestamp': '2024-10-03 15:49:16.657626', 'value': 1034.62}
{'timestamp': '2024-10-03 15:49:17.659607', 'value': 1001.03}
{'timestamp': '2024-10-03 15:49:18.661541', 'value': 1003.84}
{'timestamp': '2024-10-03 15:49:19.663618', 'value': 979.3}
{'timestamp': '2024-10-03 15:49:20.665725', 'value': 971.69}
{'timestamp': '2024-10-03 15:49:21.667684', 'value': 1005.52}
```
# Chapter 7 Containers with Greengrass
[Reference](https://catalog.workshops.aws/greengrass/en-US/chapter7-containers)
## Chapter 7.1 設定
https://catalog.workshops.aws/greengrass/en-US/chapter7-containers/10-step1
### 在EC2上安裝docker
* Install docker
```
sudo apt-get update
sudo apt-get install docker.io
```
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.Subscriber/1.0.0$ docker --version
Docker version 24.0.7, build 24.0.7-0ubuntu2~22.04.1
```
* Add the Greengrass user to the Docker group, allowing it to run containers.
```
sudo usermod -aG docker ggc_user
```
### 提供 Greengrass Core device ECR 權限
* 建立一個名為 workshop_ecr_iot_gg_policy 的 IAM Policy
```
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:CreateRepository",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:CompleteLayerUpload",
"ecr:GetAuthorizationToken",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage"
],
"Resource": "*"
}
]
}
```
* 將workshop_ecr_iot_gg_policy掛載給GreengrassV2TokenExchangeRole
* 可以參考 [Chapter 3.2](https://hackmd.io/@srcuTWQ_SsWXm3i2P_fHJw/BJThqPGCA#%E5%BB%BA%E7%AB%8B-IAM-Policy) 作法

## Chapter 7.2
https://catalog.workshops.aws/greengrass/en-US/chapter7-containers/20-step2-publisher
* 建立一個publisher component 要上傳的 Elastic Container Registry (ECR) Repository
```
aws ecr create-repository --repository-name publisher-container
```
```
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.Subscriber/1.0.0$ aws ecr create-repository --repository-name publisher-container
{
"repository": {
"repositoryArn": "arn:aws:ecr:ap-northeast-1:911551666144:repository/publisher-container",
"registryId": "911551666144",
"repositoryName": "publisher-container",
"repositoryUri": "911551666144.dkr.ecr.ap-northeast-1.amazonaws.com/publisher-container",
"createdAt": 1727971115.462,
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
```
* 前往 ECR [console](https://ap-northeast-1.console.aws.amazon.com/ecr/private-registry/repositories?region=ap-northeast-1), 勾選 Publisher Component Repository 並點擊 'View Push Commands'. 複製 login command

### 開始建立Component應用程式檔案
* dummy_sensor.py
```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
import numpy as np
class DummySensor(object):
def __init__(self, mean=25, variance=1):
self.mu = mean
self.sigma = variance
def read_value(self):
return np.random.normal(self.mu, self.sigma, 1)[0]
if __name__ == '__main__':
sensor = DummySensor()
print(sensor.read_value())
```
* example_publisher.py
```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
import time
import datetime
import json
import sys
import signal
import os
import awsiot.greengrasscoreipc
from awsiot.greengrasscoreipc.model import (
PublishToTopicRequest,
PublishMessage,
JsonMessage
)
from dummy_sensor import DummySensor
#kill process
def sighandler(a,b):
sys.exit(0)
signal.signal(signal.SIGINT | signal.SIGTERM, sighandler)
TIMEOUT = 10
publish_rate = 1.0
ipc_client = awsiot.greengrasscoreipc.connect()
sensor = DummySensor()
topic = "my/topic"
while True:
message = {"timestamp": str(datetime.datetime.now()),
"value": sensor.read_value()}
message_json = json.dumps(message).encode('utf-8')
request = PublishToTopicRequest()
request.topic = topic
publish_message = PublishMessage()
publish_message.json_message = JsonMessage()
publish_message.json_message.message = message
request.publish_message = publish_message
operation = ipc_client.new_publish_to_topic()
operation.activate(request)
future = operation.get_response()
future.result(TIMEOUT)
print(f"publisher is successful! {message_json}")
time.sleep(1/publish_rate)
```
* Dockerfile
```
FROM public.ecr.aws/amazonlinux/amazonlinux:latest
# Install dependencies
RUN yum update -y && \
yum install -y python3 && \
yum install -y python3-pip && \
pip3 install numpy && \
pip3 install awsiotsdk
# run selected files
RUN mkdir /pyfiles
COPY ./dummy_sensor.py /pyfiles/dummy_sensor.py
COPY ./example_publisher.py /pyfiles/example_publisher.py
EXPOSE 80
CMD ["python3","-u", "/pyfiles/example_publisher.py"]
```
### Push Component application 到 ECR

* Build the Docker container
EX:
```
docker build -t publisher-container .
```
* Tag the Docker container
EX:
```
docker tag publisher-container:latest 911551666144.dkr.ecr.ap-northeast-1.amazonaws.com/publisher-container:latest
```
* Push the local container to the ECR Repository
EX:
```
docker push 911551666144.dkr.ecr.ap-northeast-1.amazonaws.com/publisher-container:latest
```
### Create component of container
```
touch ~/environment/GreengrassCore/recipes/com.example.PublisherContainer-1.0.0.json
```
```
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.example.PublisherContainer",
"ComponentVersion": "1.0.0",
"ComponentType": "aws.greengrass.generic",
"ComponentDescription": "A component that runs a Docker container and uses interprocess communication.",
"ComponentPublisher": "Amazon",
"ComponentConfiguration": {
"DefaultConfiguration": {
"accessControl": {
"aws.greengrass.ipc.pubsub": {
"com.example.PublisherContainer:pubsub:1": {
"policyDescription": "Allows access to publish and subscribe to all topics.",
"operations": [
"*"
],
"resources": [
"*"
]
}
}
}
}
},
"ComponentDependencies": {
"aws.greengrass.DockerApplicationManager": {
"VersionRequirement": ">=2.0.0 <2.1.0",
"DependencyType": "HARD"
},
"aws.greengrass.TokenExchangeService": {
"VersionRequirement": ">=2.0.0 <2.1.0",
"DependencyType": "HARD"
}
},
"Manifests": [
{
"Platform": {
"os": "all"
},
"Lifecycle": {
"Run": "docker run --rm -t -v /greengrass/v2:/greengrass/v2 -e AWS_REGION -e SVCUID -e AWS_GG_NUCLEUS_DOMAIN_SOCKET_FILEPATH_FOR_COMPONENT -e AWS_CONTAINER_AUTHORIZATION_TOKEN -e AWS_CONTAINER_CREDENTIALS_FULL_URI <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/publisher-container:latest"
},
"Artifacts": [
{
"Uri": "docker:<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/publisher-container:latest",
"Unarchive": "NONE",
"Permission": {
"Read": "OWNER",
"Execute": "NONE"
}
}
]
}
],
"Lifecycle": {}
}
```
## Chapter 7.3
```
ubuntu@ip-10-0-1-21:~$ aws ecr create-repository --repository-name subscriber-container
{
"repository": {
"repositoryArn": "arn:aws:ecr:ap-northeast-1:911551666144:repository/subscriber-container",
"registryId": "911551666144",
"repositoryName": "subscriber-container",
"repositoryUri": "911551666144.dkr.ecr.ap-northeast-1.amazonaws.com/subscriber-container",
"createdAt": 1728004869.495,
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
```
```
ubuntu@ip-10-0-1-21:~$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 911551666144.dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/ubuntu/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
ubuntu@ip-10-0-1-21:~$
ubuntu@ip-10-0-1-21:~$
ubuntu@ip-10-0-1-21:~$ mkdir -p ~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0 \
&& cd ~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0 \
&& touch example_subscriber.py Dockerfile
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0$ docker build -t subscriber-container .
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/
Sending build context to Docker daemon 4.608kB
Step 1/6 : FROM public.ecr.aws/amazonlinux/amazonlinux:latest
---> 2001b66b1d2c
Step 2/6 : RUN yum update -y && yum install -y python3 && yum install -y python3-pip && pip3 install numpy && pip3 install awsiotsdk
---> Using cache
---> 8254539e5f9c
Step 3/6 : RUN mkdir /pyfiles
---> Using cache
---> 0c2982af6656
Step 4/6 : COPY ./example_subscriber.py /pyfiles/example_subscriber.py
---> e0283934aa59
Step 5/6 : EXPOSE 80
---> Running in c917d543e478
Removing intermediate container c917d543e478
---> 0e36d973126c
Step 6/6 : CMD ["python3","-u", "/pyfiles/example_subscriber.py"]
---> Running in fe5de18ad3c2
Removing intermediate container fe5de18ad3c2
---> 82d03be039fc
Successfully built 82d03be039fc
Successfully tagged subscriber-container:latest
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0$
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0$
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0$ docker tag subscriber-container:latest 911551666144.dkr.ecr.ap-northeast-1.amazonaws.com/subscriber-container:latest
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0$
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0$
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0$ docker push 911551666144.dkr.ecr.ap-northeast-1.amazonaws.com/subscriber-container:latest
The push refers to repository [911551666144.dkr.ecr.ap-northeast-1.amazonaws.com/subscriber-container]
bb0886f02a66: Pushed
6f7c13e150fc: Pushed
a2b5465ec5f4: Pushed
e35bf179f8b0: Pushed
latest: digest: sha256:23fd7c2c12d219bc9e8564cd8e1669c54f4d8f404e0a6f31a90628b51b721439 size: 1155
ubuntu@ip-10-0-1-21:~/environment/GreengrassCore/artifacts/com.example.SubscriberContainer/1.0.0$
```
## Chapter 7.4