Bulk registration 란?

IoT Core 서비스에 사물 등록을 대량으로 할 수 있게 도와주는 기능 입니다.

https://docs.aws.amazon.com/ko_kr/iot/latest/developerguide/bulk-provisioning.html

 

이번 게시물에서는 IoT Device Management의 기능 중 하나인 Bulk registration을 활용해 IoT Core 서비스에 많은 수의 디바이스를 한번에 등록하는 과정을 보여드리겠습니다.

* 실습 참고: https://catalog.us-east-1.prod.workshops.aws/workshops/753d4f22-945d-486d-8f65-8e655441c571/ko-KR/3/1


AWS Cloud9 instance를 IoT device로 활용

Cloud9의 환경 생성 화면으로 이동합니다.

(버지니아 북부(us-east-1) 리전을 기준으로 진행됩니다.)

 

1) 이름을 지정합니다.

2) 나머지 설정값은 모두 default 값을 그대로 사용합니다. 아래로 스크롤하여 생성을 클릭합니다.

 

3) Cloud9 환경이 생성 중인 대시보드를 확인할 수 있습니다. 열림 버튼을 클릭 합니다.

4) 잠시 후 Cloud9 IDE가 생성 완료되고, 아래 이미지와 같은 화면을 볼 수 있습니다.

설정 버튼을 클릭해 Show Home in Favorites를 체크합니다.

여기까지 진행했을 때 실습에 사용할 IoT device가 준비되었습니다.

 

CloudFormation template으로 resource 생성

링크를 클릭해 이번 실습에서 사용할 IAM Role resource를 생성합니다.

 

1) '스택 생성' 화면에서 다음을 클릭합니다.

2) '스택 세부 정보 지정' 화면에서 다음을 클릭합니다.

3) '스택 옵션 구성' 화면에서 다음을 클릭합니다.

4) '검토' 화면에서 문구가 있는 체크박스에 체크하고 전송을 클릭합니다.

 

리소스의 생성이 완료되는 데까지 시간이 걸립니다.

 

Bulk registration

1) 고유한 이름의 S3 버킷을 command를 활용해 생성합니다.

(여기서부터 사용되는 command들은 cloud9 terminal에 입력합니다.)

AWS account의 number를 변수로 입력받아 버킷 이름의 일부로 사용합니다.

MY_AWS_ACCOUNT_NUMBER=`aws sts get-caller-identity --query Account --output text`
aws s3 mb s3://mybucket-$MY_AWS_ACCOUNT_NUMBER

2) 새로운 사물 그룹과 타입을 생성합니다. 

aws iot create-thing-group --thing-group-name bulk-group
aws iot create-thing-type --thing-type-name bulk-type

3) mk-bulk.sh 파일을 다운로드 합니다. 이후 해당 스크립트에 실행 권한을 부여 합니다.

bulk registration을 위한 registration template을 생성하기 위한 스크립트 파일 입니다.

wget "https://ke-eu-central-1.s3.eu-central-1.amazonaws.com/devicemanagement/mk-bulk.sh"
chmod +x mk-bulk.sh

4) 아래 command를 활용해 스크립트를 실행합니다.

뒤에 파라미터로 전달되는 bulk-thing는 대량으로 등록되는 사물의 base name이고 100은 100개의 사물에 대한 정보를 registration template에 생성하겠다는 것을 의미합니다.

openssl rand -out /home/ubuntu/.rnd -hex 256
./mk-bulk.sh bulk-thing 100

위 스크립트의 결과물로 bulk-[사물이름]-[datetime] 형식의 디렉토리에 100개의 사물에 대한 key와 CSR(Certificate Signing)가 생성된 것을 확인할 수 있습니다.

bulk registration을 위한 registration template로써 bulk.json 파일이 생성된 것을 확인할 수 있습니다.

5) 생성된 디렉토리로 이동한 뒤 위에서 생성했던 S3 bucket에 registration template 파일을 업로드 합니다.

cd  bulk-thing-2023-10-18_05-00-27/
aws s3 cp bulk.json s3://mybucket-$MY_AWS_ACCOUNT_NUMBER

6) bulk registration에 사용할 template body를 아래와 같은 명령으로 다운로드 받습니다.

template body에는 bulk registration에 사용되는 파라미터와 리소스에 대한 정의가 되어 있습니다.

sudo wget "https://ke-eu-central-1.s3.eu-central-1.amazonaws.com/devicemanagement/templateBody.json"

7) 아래의 형식의 command를 활용하여, bulk registration task를 진행합니다.

[bulk registration을 수행하기 위한 IAM Role의 ARN]은 앞서 생성하신 CloudFormation의 output 탭에서 확인할 수 있습니다.

aws iot start-thing-registration-task \
  --template-body file://templateBody.json \
  --input-file-bucket mybucket-$MY_AWS_ACCOUNT_NUMBER \
  --input-file-key bulk.json --role-arn [bulk registration을 수행하기 위한 IAM Role의 ARN]

8) "taskId"를 이용해 아래 명령어로 bulk registration 수행 중 발생된 에러 및 결과를 확인합니다.

aws iot list-thing-registration-task-reports \
  --report-type ERRORS --task-id [YOUR_TASK_ID]

aws iot list-thing-registration-task-reports \
  --report-type RESULTS --task-id [YOUR_TASK_ID]

명령의 결과로써 resourceLinks가 리턴됩니다. 이는 뒤에 이어지는 절차를 위해 사용됩니다.

 

9) resoureLinks로 부터 결과를 저장합니다.

sudo wget -O results.json [resourceLinks]

결과는 results.json이라는 이름의 파일로 저장됩니다.

10) 마지막으로 bulk registration을 통하여 등록된 100개의 사물에 대한 인증서를 results.json 파일에서 추출하여 저장하기 위해 python script를 다운로드 받고 실행합니다.

wget "https://ke-eu-central-1.s3.eu-central-1.amazonaws.com/devicemanagement/bulk-result.py"
python bulk-result.py results.json

 

아래의 명령으로 bulk registration을 통해 등록된 사물의 목록을 확인합니다.

aws iot list-things

IoT Core 콘솔 화면에서도 확인할 수 있습니다.


간단하게 위 실습에서 사용했던 스크립트들의 내용을 확인하고자 합니다.

 

bulk registration을 사용하기 위해서는 아래 이미지와 같은 형식의 JSON 파일이 필요합니다.

이와 같은 파일을 얻기 위해 사용했던 스크립트가 mk-bulk.sh 이며, 결과물은 bulk.json 입니다.

 

  • mk-bulk.sh
#!/bin/bash

# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# or in the "license" file accompanying this file. This file is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.

set -e

if [ -z $1 ] || [ -z $2 ]; then
    echo "usage: $0 <base_thingname> <num_things>"
    exit 1
fi

thing_name=$1
num_things=$2

date_time=$(date "+%Y-%m-%d_%H-%M-%S")

out_dir=$thing_name-$date_time
mkdir $out_dir || exit 1


for i in $(seq 1 $num_things) ; do
  openssl req -new -newkey rsa:2048 -nodes -keyout $out_dir/$thing_name$i.key -out $out_dir/$thing_name$i.csr -subj "/C=DE/ST=Berlin/L=Berlin/O=AWS/CN=Big Orchestra"

  one_line_csr=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' $out_dir/$thing_name$i.csr)

  echo "{\"ThingName\": \"$thing_name$i\", \"SerialNumber\": \"$i\", \"CSR\": \"$one_line_csr\"}" >> $out_dir/bulk.json
done

echo "output written to $out_dir/bulk.json"
  • bulk.json
{"ThingName": "bulk-thing1", "SerialNumber": "1", "CSR": "-----BEGIN CERTIFICATE REQUEST-----\nMIICmjCCAYICAQAwVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0G\nA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBPcmNoZXN0\ncmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrJZg2YorjtNRTTpkn\nJqaEHZDwvjixOObRNJQTKDVfpEhN5SUpxSqFZW+1aCNtH4w3Wj50lGTYomPB0BjA\n1hUlUiSqYWKgzQdXbc3EvmML/vZ2O8ky1qbTv0PiV7VyBvwPbhpTf8F+zqNwJ5WQ\nZdxkLTqNqe2b1W8EppN98mDmbKN6cN0olRTVGhbwMggHmaViVKQRgO5Mv7AcZzxX\n0MlANi52SPiP+gzx1fPj7R3SGTse2E9/eaIQfzUYWZ5woiF+L2pfvHfwAWOdRWX7\nrqiWGZmw87fq7CKox92Gid16hWuz9cifVHnpCMCQnK3j3n7eaaSYZYoFHWyHi4Kx\nnvmPAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAe/O21OLOFbzk0MiLLaIiW9EV\nR67WSaaaTSU+aurp4dQOUE7yY3u7VvdTYl4kmwSnFUipkv9KnR0WtdUvzOakXJNj\nOLlDUSFIxWWI2ovPM50kqH8u9b3f1B9XuvzU9OxBoAsTyOMPLK3SnZ5Mb1MV2wir\nUg9NqAnRAJxmOg7Zvlf4YVOw+fXvULIcbbWDEVYewu7Rxrk+uYmGOQPnDtmKi1NV\nj/Dr43YKoeweO1o86dxWkbu7ybEVCEYvX7b7n3gnAso9ppDFS0ObiveB4Jv6SIr3\nKBwDq19ojtO3f72/zqBLN5fAksWzMDlZ+pSa4cpUqqaASuOxZywS4AOGTlZk/w==\n-----END CERTIFICATE REQUEST-----\n"}
{"ThingName": "bulk-thing2", "SerialNumber": "2", "CSR": "-----BEGIN CERTIFICATE REQUEST-----\nMIICmjCCAYICAQAwVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0G\nA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBPcmNoZXN0\ncmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgbZ69aKZTREVx8ieG\nptNZsWF8sm2YB46EehpVjtFY+ITNv6Ubo22K52DanoMlFqlCWRcCPWM0wmY1q3ui\ndcx1yD/bDSXP88UR/KMbe7UhPyz6zJTynFIzSmxgtlr1G5W3XBVhSKOsOlc161JA\nouVxwQyFknkIIBxTAcloRCG4/BiFTj0Sq/W9qUSBudsoOG1WCTyGYYxkuXa7OYX6\ncCNK4BFOXsptvUXJOEV3fhQLqb++Ubngrofe1tSoSjU2xOylRwkz18sqA6R0+Wie\n7AGTiCSQJK+PJmyxct7ieqzzcEocMFt75zl3vNI1xQJ3hJCXJ+tbB6LtSlW247S2\nErDtAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAdmEQEX4efWKC0WV9jAEOgBO9\nMikEnJvFdjK38K0gfK3RYarsJKlWqnUmL6tL+jCpHfqygMuxdvKwk4ewvAYI1RQA\ntMaF87DeF19vf6pVqyBIAI1+Njniio2v8BKPWNTfgbRZXwGgru2JJL4hsIkxcvnK\nGSRSNuRKstZ9oNbRizXYKSgq1BTzt9+ufXqYYPd50ReU5AZ5o9DTeUOkJI92UihN\nIH/abMGZJ0t23mGRRMHriWWRozaonp+kxnpI6VLdZamgDVk1WVRB24K7S6pYnUgw\n0MfVXkNFeUB+xgQshPi+GaLoEDT6flPqKNiorml3aoaVQ6BtcWuJm4icDHKZ3g==\n-----END CERTIFICATE REQUEST-----\n"}
{"ThingName": "bulk-thing3", "SerialNumber": "3", "CSR": "-----BEGIN CERTIFICATE REQUEST-----\nMIICmjCCAYICAQAwVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0G\nA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBPcmNoZXN0\ncmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEVVg+gbu7hA2g+cUu\nlSnR7QkGUe3Z7ZKz/zEfN24yfY4xjJDdmcSyGYGfCUlmByfL5p80kuqv4GBvhVPK\nU9Re8xFU3CfMCjW1HEDWFbPo97SUrHWVk2glPsz9TJUi8+GcB6X6Zq5NTzkCYtcv\nF0eX+E7YeQBrSu8JqzrfCXlvNvNGS4m25Iug1i64Z+OD85opfxZogVc69Ans/PvN\nPvZH26gEtimab2Bvz74D/tMZ3GmU4Ox3lZfUU26XLgHRFEltP0leuBZJ5CTYqBHK\nhAVXExtITFdpR1jrCGWkXrR4cFibrjAxjGeCNVdnaKJvwlqyLDewEhYI5+FfCBy+\nKItbAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAls4+bShW0Oo5h7iAX6YABswk\nHVn12D3t0ab5gh1+cad15MdvlwMd4nXXxTp1eKiWoRCxERnBcfz49axkmCtO46/S\nG33A2Zf6pLmDUHToZymPwrthogKN7FadqLK9cS0OUNylCVB9pZiyhTcqEIe4uPtw\nwESKkEiKzYdtKq5904flBi/VEv01xXrh+FrnBsxprCwJ+bqFpZPpRPISPr74JX2M\nK+zqCdTZGT4J5I2yLm4jyTRiG9jyEaMt46aZnVnc0I3A0PBuKa7a9yhIph8s/6t8\nuixtflUid0kUrAodW4mHlVFiaDXNf89HvFTz27Cven7GHcJ8DZNIcwIg18Kh6A==\n-----END CERTIFICATE REQUEST-----\n"}
{"ThingName": "bulk-thing4", "SerialNumber": "4", "CSR": "-----BEGIN CERTIFICATE REQUEST-----\nMIICmjCCAYICAQAwVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0G\nA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBPcmNoZXN0\ncmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDExTtxTw21CqyV4szp\neC1T8ilN1lxn4VUrna1HtrLkj2PRcjzggz4/s5EG4pntHgM5cbdknhUZ11eQHrLk\naB/YVm/lsAo4LlzCsFnzSDQlLBUuWNggzdkJDjrvhG9ZUYt75XD9gOfD2diLFCij\nLdF4BkaPLlO/4K+M90zrW5H6P6laflUQQ2r3jVH57YfhDeEBdpd21ykF/mBq1zgp\nyNzrdeamzizrTDu9vrfvlRKrPMQqiiuH0JLaSDX26zLjp6H2LoF7d3e5LiTyWosu\nV0VCq74JktWT9T9V7FMxeHnNfvlKa8o0DPjBDc6myshReMuH9ncEmgdITi9OWcvc\nnG0fAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAXo8KdoTFwtgkwSJM+4YdYeE9\n24vbVJZiwkXcVwm20wgYgrRFDwLAADfaWylo9gWXMjM8gZh5/DH77ixkhJw833F5\nhxRRG+oFGrUZMFutUSg0jO2HoFEh7bIpzNy3CVzpO8LXPZ1rzTvBR9ZLYutdTE03\noRHwvV4XL5DLoRHoU9+KhcF0B5+hmRI9fAavJLieOw3lezfjwEaERhrB5CwGv4mS\nkQeR03sBKuI2AyKDClH0AHHDjaQgOG2kQ/uVIMJ+XzrMRtG2k5J6MqkZlTpAvh1G\n7nfc6jRKetK9pZzyqHqnaFUgal9flMfoHgmJeGdpN7HXwOKI3ioCcv9NPyidMw==\n-----END CERTIFICATE REQUEST-----\n"}
{"ThingName": "bulk-thing5", "SerialNumber": "5", "CSR": "-----BEGIN CERTIFICATE REQUEST-----\nMIICmjCCAYICAQAwVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0G\nA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBPcmNoZXN0\ncmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsGjxWIhhjVMa7YuPx\nuSHfJQmkoZuInblb4MzsYRTiRuopbqq9PKEX3ChNstRuByp/XzebxRbbfvH58kDu\n+npB8W0gb+HRHnheLfp3uDTHvo6USq5oMSpVlz5ASpdoouP1cpTkUhwK7FGdv9xi\nBQ9RDDyJSIfYcCLC+qe+ldFeoehchWFlpPhz0nU1zBDhcvekTarBl77a+5cEPu2K\nvmPGoFf8TsXqG5zBe6CSXyTa+7MUeEYBndLugcGCHTCZbmUIV4Qjm73QeRteC7q+\nwsgRgRpeDtdMVtYL0y4k0AJ15TJeBGDJtu62YH6rdNGfxecjaZ7U9smsQNGWt2WQ\nnGbtAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAah1FTcn1B2KxlPuxAKxLS6Py\nIJljoAKAXsZM0VSk7N0vfuurSepkd2XHTxFaj6gobxPpmT2Mu5zbnILpL8kC5sGX\nx+hEF1OXomkIsfw4WXMF0yKqMBrQdMTqTa+TlqG5te+TA2JfgZjp7Kot6iLdLC8H\nZuvBg4PUKtIlk2o6S0V/fORg70ZhHKb3G9qCJbotsg6zcChKTWzfSF1IgZzhe79V\nB4ZgECv60c8XpzpxYLGvcKc6CX/Yl9NppmrteXGJvTAhdzMaJUagWM58Oh3XJsQh\nUX726VmnZM78PmY46KcZt3O5z3IEv0EbmytAFoZS+leLqT7b1cDf2mAQRU7Uhg==\n-----END CERTIFICATE REQUEST-----\n"}
...

 

bulk registration은 start-thing-registration-task 명령을 사용해 진행합니다.

inputFileBucketS3 bucket, inputFileKeybulk.json, roleArn은 위에서 cloudformation으로 생성했던 role이며

templatebody는 아래의 스크립트 입니다. 

templatebody는 bulk registration에 사용되는 파라미터와 리소스에 대한 정의가 되어 있어야 합니다.

  • templateBody.json
{
  "Parameters" : {
     "ThingName" : {
       "Type" : "String"
     },
     "SerialNumber" : {
       "Type" : "String"
     },
     "Location" : {
        "Type" : "String",
        "Default" : "WA"
     },
     "CSR" : {
       "Type" : "String"
     }
  },
  "Resources" : {
    "thing" : {
      "Type" : "AWS::IoT::Thing",
      "Properties" : {
        "ThingName" : {"Ref" : "ThingName"},
        "AttributePayload" : { "version" : "v1", "serialNumber" :  {"Ref" : "SerialNumber"}},
        "ThingTypeName" :  "bulk-type",
        "ThingGroups" : ["bulk-group"]
      }
    },
    "certificate" : {
      "Type" : "AWS::IoT::Certificate",
      "Properties" : {
        "CertificateSigningRequest": {"Ref" : "CSR"},
        "Status" : "ACTIVE"
      }
    },
    "policy" : {
      "Type" : "AWS::IoT::Policy",
      "Properties" : {
        "PolicyDocument": "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Action\": [\"iot:*\"],\"Resource\": [\"*\"]}]}"
      }
    }
  }
}

 

결과로 Task ID를 확인할 수 있으며 resourceLinks 리턴 값을 확인하기 위해 필요합니다.

얻은 resourceLinks로 부터 실행 결과를 저장합니다. 저장한 파일이 results.json 파일 입니다.

  • results.json
{"lineNumber":1,"offset":1057,"response":{"CertificatePem":"-----BEGIN CERTIFICATE-----\nMIIDkDCCAnigAwIBAgIUWS2Okfw8JY6kC/+jEJfv8vvwyPEwDQYJKoZIhvcNAQEL\nBQAwTTFLMEkGA1UECwxCQW1hem9uIFdlYiBTZXJ2aWNlcyBPPUFtYXpvbi5jb20g\nSW5jLiBMPVNlYXR0bGUgU1Q9V2FzaGluZ3RvbiBDPVVTMB4XDTIzMTAxODA1MTEz\nMloXDTQ5MTIzMTIzNTk1OVowVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxp\nbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBP\ncmNoZXN0cmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrJZg2Yorj\ntNRTTpknJqaEHZDwvjixOObRNJQTKDVfpEhN5SUpxSqFZW+1aCNtH4w3Wj50lGTY\nomPB0BjA1hUlUiSqYWKgzQdXbc3EvmML/vZ2O8ky1qbTv0PiV7VyBvwPbhpTf8F+\nzqNwJ5WQZdxkLTqNqe2b1W8EppN98mDmbKN6cN0olRTVGhbwMggHmaViVKQRgO5M\nv7AcZzxX0MlANi52SPiP+gzx1fPj7R3SGTse2E9/eaIQfzUYWZ5woiF+L2pfvHfw\nAWOdRWX7rqiWGZmw87fq7CKox92Gid16hWuz9cifVHnpCMCQnK3j3n7eaaSYZYoF\nHWyHi4KxnvmPAgMBAAGjYDBeMB8GA1UdIwQYMBaAFDLldBsUWUJZzfI2E7nzlgYh\nSyocMB0GA1UdDgQWBBSoES/LeLU38FdLKaCbwqDZeXYEszAMBgNVHRMBAf8EAjAA\nMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAj+SDRZTvuqib7bTH\nz5YRxuCTNlFFV/n0VgI6NY2J/taZTscYQowanA6dQoKK3Run00qsW/MjbBpVQGrb\nC67SabsZNIjkGmCHpiW1VwlMYwapMAbZJNzRVi9amO0EEJYS+Gdy1wlT/qwE2YRD\n2hOtTwG/L9E+Oy9xTVMzRDDU4snuVQfYuHH3HL/KxEEg9MZbM8VMYI+VQmiKHZe8\nYQXYR37t9Hxq7bfyQvpfSIsKsZ283aYxY3dmHB+xrFQ2tjS8dfuXpEzrgjdYwddm\nmR6SEf7kwsPgTPDJM/U5FciNcuXmnrVki4wuGIqeI3Iwt9qHpWcnuixqmb7rHf7c\nM7mCDQ==\n-----END CERTIFICATE-----\n","ResourceArns":{"certificate":"arn:aws:iot:us-east-1:986611521344:cert/df6c6420c47571595daba7446b203a2ffdabafc68e2e7a94972e9e77a696cb38","policy":"arn:aws:iot:us-east-1:986611521344:policy/EA1A0CDFCEDEAEE65D65B7AB012F0D7D89208F1EDA825C85F28063F9DC4005BC","thing":"arn:aws:iot:us-east-1:986611521344:thing/bulk-thing1"}}}
{"lineNumber":2,"offset":2114,"response":{"CertificatePem":"-----BEGIN CERTIFICATE-----\nMIIDkDCCAnigAwIBAgIUS/f0saWs0Lc5U/Hy407Gj8wN3A4wDQYJKoZIhvcNAQEL\nBQAwTTFLMEkGA1UECwxCQW1hem9uIFdlYiBTZXJ2aWNlcyBPPUFtYXpvbi5jb20g\nSW5jLiBMPVNlYXR0bGUgU1Q9V2FzaGluZ3RvbiBDPVVTMB4XDTIzMTAxODA1MTEz\nMloXDTQ5MTIzMTIzNTk1OVowVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxp\nbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBP\ncmNoZXN0cmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgbZ69aKZT\nREVx8ieGptNZsWF8sm2YB46EehpVjtFY+ITNv6Ubo22K52DanoMlFqlCWRcCPWM0\nwmY1q3uidcx1yD/bDSXP88UR/KMbe7UhPyz6zJTynFIzSmxgtlr1G5W3XBVhSKOs\nOlc161JAouVxwQyFknkIIBxTAcloRCG4/BiFTj0Sq/W9qUSBudsoOG1WCTyGYYxk\nuXa7OYX6cCNK4BFOXsptvUXJOEV3fhQLqb++Ubngrofe1tSoSjU2xOylRwkz18sq\nA6R0+Wie7AGTiCSQJK+PJmyxct7ieqzzcEocMFt75zl3vNI1xQJ3hJCXJ+tbB6Lt\nSlW247S2ErDtAgMBAAGjYDBeMB8GA1UdIwQYMBaAFCehQabmnQRs29ETKNaNKTJo\nFGySMB0GA1UdDgQWBBQgQze4qB6CJG06d8Xjbh7WqsQzJTAMBgNVHRMBAf8EAjAA\nMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAi2JZadms+v2srXlJ\nDgcg4gvVWXoPaQk0ujnaHqE4545urRn70vy8JMpB1IeBsbPxLFML2C/gCARdcXE2\nOEhcIw1Y3XKDxYTxSbUU6U7XilXpPNItJf4AnS2e37y9ufHHTDq7dMuNV91SyCNn\nnlscbdbatjkLDojeu3/Wv5WIiKHf8WGHN1Hrso+uZeDJxYrtkgTD9G/ypkdzCPw6\nuSCpN4j0J6Qg8pvR/TaL7lisXoRqFna9BTm9B1FIrheZlDCcvi6XtbaPoDAkuqeQ\nIuKeV9N5DR9k1fuuu7RENftkVfOiIJnS76FI7N9cuU9KL/LB2yYDh/Z0bEPz2kd5\nwO6RaA==\n-----END CERTIFICATE-----\n","ResourceArns":{"certificate":"arn:aws:iot:us-east-1:986611521344:cert/c305346f959b269a9e434397ffd6b3c8dbe7ed12539440483fd4bbf5f55bef14","policy":"arn:aws:iot:us-east-1:986611521344:policy/EA1A0CDFCEDEAEE65D65B7AB012F0D7D89208F1EDA825C85F28063F9DC4005BC","thing":"arn:aws:iot:us-east-1:986611521344:thing/bulk-thing2"}}}
{"lineNumber":3,"offset":3171,"response":{"CertificatePem":"-----BEGIN CERTIFICATE-----\nMIIDkDCCAnigAwIBAgIUBeVIR2wCJdZMO2XFaxph/dSWBJgwDQYJKoZIhvcNAQEL\nBQAwTTFLMEkGA1UECwxCQW1hem9uIFdlYiBTZXJ2aWNlcyBPPUFtYXpvbi5jb20g\nSW5jLiBMPVNlYXR0bGUgU1Q9V2FzaGluZ3RvbiBDPVVTMB4XDTIzMTAxODA1MTEz\nMloXDTQ5MTIzMTIzNTk1OVowVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxp\nbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBP\ncmNoZXN0cmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEVVg+gbu7\nhA2g+cUulSnR7QkGUe3Z7ZKz/zEfN24yfY4xjJDdmcSyGYGfCUlmByfL5p80kuqv\n4GBvhVPKU9Re8xFU3CfMCjW1HEDWFbPo97SUrHWVk2glPsz9TJUi8+GcB6X6Zq5N\nTzkCYtcvF0eX+E7YeQBrSu8JqzrfCXlvNvNGS4m25Iug1i64Z+OD85opfxZogVc6\n9Ans/PvNPvZH26gEtimab2Bvz74D/tMZ3GmU4Ox3lZfUU26XLgHRFEltP0leuBZJ\n5CTYqBHKhAVXExtITFdpR1jrCGWkXrR4cFibrjAxjGeCNVdnaKJvwlqyLDewEhYI\n5+FfCBy+KItbAgMBAAGjYDBeMB8GA1UdIwQYMBaAFF2PWXbNwWujfogNBhqjK4/6\nNPg/MB0GA1UdDgQWBBSB+SGoDg4XKzQWd3NFY8Qg8VOVrjAMBgNVHRMBAf8EAjAA\nMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAllSQWPysC1p3IkyX\nJ4Pzvl2chkRCBFR9/tob90SOXJSJ3WYN/5Bbgl/WURppj9VJXCxawj3AxqBxBMfx\n66a0uL1dxEgYYOJf79ODxupipUFxIg1yTfQSKpIoL9UDZ+84y9h4wMpzIHyIXxMP\nntKuOIivw2qizIEuSyqvIc2nZ2W9gMxxVVvN2KhfBkJ9QBDdM4Q2TJ8gSKoSDt8r\noP5b4oeVVPYKuP+QYlth2MsJJEr0Zwzlq6jZe8tLofbqkl+Vk+nAVj8DyoPqANi4\n17zbWYb37zMfrx21Y5NEZY25JxgkU5lnONzbpoTIZpJCM/0D7hvhgPWdXidCDzW1\nn/eObQ==\n-----END CERTIFICATE-----\n","ResourceArns":{"certificate":"arn:aws:iot:us-east-1:986611521344:cert/4d3db30b101486d79bdba67acdd044e006b03be805662e2fcdf54c98b83425e0","policy":"arn:aws:iot:us-east-1:986611521344:policy/EA1A0CDFCEDEAEE65D65B7AB012F0D7D89208F1EDA825C85F28063F9DC4005BC","thing":"arn:aws:iot:us-east-1:986611521344:thing/bulk-thing3"}}}
{"lineNumber":4,"offset":4228,"response":{"CertificatePem":"-----BEGIN CERTIFICATE-----\nMIIDkTCCAnmgAwIBAgIVAIWvypjuGj6W2AIslOrGetxD0ZFrMA0GCSqGSIb3DQEB\nCwUAME0xSzBJBgNVBAsMQkFtYXpvbiBXZWIgU2VydmljZXMgTz1BbWF6b24uY29t\nIEluYy4gTD1TZWF0dGxlIFNUPVdhc2hpbmd0b24gQz1VUzAeFw0yMzEwMTgwNTEx\nMzJaFw00OTEyMzEyMzU5NTlaMFUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJs\naW4xDzANBgNVBAcMBkJlcmxpbjEMMAoGA1UECgwDQVdTMRYwFAYDVQQDDA1CaWcg\nT3JjaGVzdHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMU7cU8N\ntQqsleLM6XgtU/IpTdZcZ+FVK52tR7ay5I9j0XI84IM+P7ORBuKZ7R4DOXG3ZJ4V\nGddXkB6y5Ggf2FZv5bAKOC5cwrBZ80g0JSwVLljYIM3ZCQ4674RvWVGLe+Vw/YDn\nw9nYixQooy3ReAZGjy5Tv+CvjPdM61uR+j+pWn5VEENq941R+e2H4Q3hAXaXdtcp\nBf5gatc4Kcjc63Xmps4s60w7vb6375USqzzEKoorh9CS2kg19usy46eh9i6Be3d3\nuS4k8lqLLldFQqu+CZLVk/U/VexTMXh5zX75SmvKNAz4wQ3OpsrIUXjLh/Z3BJoH\nSE4vTlnL3JxtHwIDAQABo2AwXjAfBgNVHSMEGDAWgBQy5XQbFFlCWc3yNhO585YG\nIUsqHDAdBgNVHQ4EFgQUhYszV4B85CElJU6VACJzmZDWEhgwDAYDVR0TAQH/BAIw\nADAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBALQjwi6ksFANrA6t\nVm0OAKC6yRYD3yNr7BubqnIWlhyNhzYCvUbC/tmOh2lCA/20Cnrkf4BLofYVb227\nt1jsE65zVgPtAt5dAiG0DrJqdSKtieftkGsZ0iSKsKtFUc3s5HxGuwsn/eJn+GTG\nOBWlK9J1PeQJf07iHWimWzZCaPJwK9aT+m8s7vfgmOJ01dtyOW2yrrxYiCEQAyA6\nYLL2/uA8X/WqJVsqPaVku0t0LEKmg6Ir6MkPi3auLDEUKkuKljsQ3CuD4eqON19T\nJzNHFlsiYOhtDn+G0KYiqMmt495hsegvIbdiPyseYeU/DcNLocp8fEeiNS2WSWnA\n4fE3ges=\n-----END CERTIFICATE-----\n","ResourceArns":{"certificate":"arn:aws:iot:us-east-1:986611521344:cert/0f084113392b5c78e57dfc1e00040617eb1e5d71c17215d3963b7ca40fb3cf74","policy":"arn:aws:iot:us-east-1:986611521344:policy/EA1A0CDFCEDEAEE65D65B7AB012F0D7D89208F1EDA825C85F28063F9DC4005BC","thing":"arn:aws:iot:us-east-1:986611521344:thing/bulk-thing4"}}}
{"lineNumber":5,"offset":5285,"response":{"CertificatePem":"-----BEGIN CERTIFICATE-----\nMIIDkDCCAnigAwIBAgIUFcHa6lHkMdUDK7SSUkMWxe4tbZkwDQYJKoZIhvcNAQEL\nBQAwTTFLMEkGA1UECwxCQW1hem9uIFdlYiBTZXJ2aWNlcyBPPUFtYXpvbi5jb20g\nSW5jLiBMPVNlYXR0bGUgU1Q9V2FzaGluZ3RvbiBDPVVTMB4XDTIzMTAxODA1MTEz\nMloXDTQ5MTIzMTIzNTk1OVowVTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxp\nbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQKDANBV1MxFjAUBgNVBAMMDUJpZyBP\ncmNoZXN0cmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsGjxWIhhj\nVMa7YuPxuSHfJQmkoZuInblb4MzsYRTiRuopbqq9PKEX3ChNstRuByp/XzebxRbb\nfvH58kDu+npB8W0gb+HRHnheLfp3uDTHvo6USq5oMSpVlz5ASpdoouP1cpTkUhwK\n7FGdv9xiBQ9RDDyJSIfYcCLC+qe+ldFeoehchWFlpPhz0nU1zBDhcvekTarBl77a\n+5cEPu2KvmPGoFf8TsXqG5zBe6CSXyTa+7MUeEYBndLugcGCHTCZbmUIV4Qjm73Q\neRteC7q+wsgRgRpeDtdMVtYL0y4k0AJ15TJeBGDJtu62YH6rdNGfxecjaZ7U9sms\nQNGWt2WQnGbtAgMBAAGjYDBeMB8GA1UdIwQYMBaAFOopp+NqKhItJByY4EHCINF9\nPRqTMB0GA1UdDgQWBBQdKrfT2/+w3Or5AF6icH14JjXJjjAMBgNVHRMBAf8EAjAA\nMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAUUvQ8JaYwaUW99Ma\nE+Yk5GdqSzkR4hqlKzdt/GOWw+KP3dVa8fUlSmWwQd3B7IgbBegwFLbSykjPWfru\n+rpYvzkV9PQjV1PRDPbtEf4CUEj2skOMWj/bz0nOpi38cbNcCZTNDQ61Oq+h0Qt9\nLYmyl+Dg4gwlzmyfMArlmUqQVT3+9mbo4DLkX2DcMiElYkF9soJ8RlKImLf266UR\nNLV4NCafl70h/3fAnDSI4/ch+Jhcs+h2yEPMk5RksFGhvSf8cPqiZ00ndCE++yAG\nMpfhYyNVL1NsixL4wBlgylwS2zlD5OvSQatXp5VzoqGQKjexgr/5cBxS8Juzw28n\nHN7X3A==\n-----END CERTIFICATE-----\n","ResourceArns":{"certificate":"arn:aws:iot:us-east-1:986611521344:cert/125249f7baa1510da78a560e83de53fb4c59730837d7a91e2ede0a93c1cc333e","policy":"arn:aws:iot:us-east-1:986611521344:policy/EA1A0CDFCEDEAEE65D65B7AB012F0D7D89208F1EDA825C85F28063F9DC4005BC","thing":"arn:aws:iot:us-east-1:986611521344:thing/bulk-thing5"}}}
...

 

인증서(pem 파일)를 results.json 파일에서 추출하여 파일로 저장하기 위해 아래의 python 스크립트를 실행합니다.

(해당 응답을 잃어버리면 인증서를 얻을 방법이 없기 때문에 파일로 추출해 저장하는 것이 좋습니다.)

  • bulk-result.py
#!/usr/bin/python

# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# or in the "license" file accompanying this file. This file is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.

import json
import os
import sys


def process_line(line):
    d = json.loads(line)
    crt = d["response"]["CertificatePem"]
    thing = d["response"]["ResourceArns"]["thing"].split('/')[1]
    print("creating file {}.cert.pem for thing {}".format(thing, thing))
    file = open(thing + ".cert.pem", "w")
    file.write(crt)
    file.close()

def process_results(file):
    try:
        with open(file) as f:
            for line in f:
                process_line(line)
        f.close()
    except Exception as e:
        print("error opening file {}: {}".format(file,e))
        return None

def main(argv):
    if len(argv) == 0:
        print("usage: {} <result_filename>".format(os.path.basename(__file__)))
        sys.exit(1)

    process_results(argv[0])

if __name__ == "__main__":
    main(sys.argv[1:])

 

감사합니다.

(참고: https://catalog.us-east-1.prod.workshops.aws/workshops/5ecc2416-f956-4273-b729-d0d30556013f/en-US)


Greengrass 란?

  • 디바이스 소프트웨어를 구축, 배포 및 관리하는데 도움이되는 IoT 오픈 소스 엣지 런타임 및 클라우드 서비스
  • 디바이스가 생성하는 데이터에 따라 로컬로 작동, 기계 학습 모델 기반 예측 실행, 데이터 필터링 및 집계, 필요한 정보만 클라우드로 전송하도록 프로그래밍 가능
    • 로컬 처리, 메시징, 데이터 관리, ML 추론을 지원하는 지능형 디바이스 소프트웨어 쉽게 구축

 

Components

  • ML 추론, 로컬 처리, 메시징 및 데이터 관리와 같은 복잡한 워크플로를 쉽게 생성할 수 있는 빌딩 블록
  • Stream Manager: 로컬 및 클라우드 대상으로의 데이터 내보내기 지원
  • Greengrass Core device
    • Greengrass core software를 실행하는 디바이스
    • Core device는 AWS IoT 사물, 여러 Core device를 IoT 사물 그룹에 추가해 Greengrass Core device 그룹 생성
  • Greengrass Component
    • Greengrass Core device에 배포되고 실행되는 소프트웨어 모듈
    • Recipe: component 세부 정보, 구성 및 매개 변수를 정의하여 소프트웨어 묘듈을 설명하는 JSON/YAML 파일
    • Artifact: 디바이스에서 실행될 소프트웨어를 정의하는 소스 코드, 바이너리 또는 스크립트
    • Dependency: 종속 구성 요소의 자동 업데이트 또는 다시 시작을 강제할 수 있는 구성 요소 간의 관계

 

Deployment

  • 단일 Greengrass Core device 또는 Greengrass Core device group이 될 수 있는 destination target device(엣지)에 구성 요소를 보내고 원하는 component 구성을 적용하는 프로세스
  • 업데이트된 모든 component를 대상에 자동으로 적용하고 종속성으로 정의 된 다른 모든 구성 요소들을 포함
  • deployment의 component 또는 component 구성에 대한 모든 업데이트가 자동으로 모든 destination targets 으로 전송됨

실습의 목표는 AWS IoT Greengrass Core software V2를 실행하는 Edge Gateway 구축하기!

  • AWS IoT Greengrass Core software V2 배포 및 설정
  • 에지에서 IPC(프로세스 간 통신)을 위한 사용자 지정 구성 요소 만들기
    • Publisher component 개발 및 배포
    • Subscriber component 개발 및 배포
  • Lambda를 사용해 사용자 지정 component를 생성하여 IPC에서 AWS IoT Core로 엣지 데이터 가져오기
  • AWS IoT Greengrass V2에서 MQTT 브리지 설정

(리전의 경우 워크숍 기반으로 진행할 것이기 때문에 도쿄(ap-northeast-1)에서 진행한다!)

CloudFormation 스택 실행


Cloud9 인스턴스 구성

: Greengrass edge runtime을 실행하기 위한 환경

 

임시 자격 증명을 비활성화 해준다.(cloud9 아이콘 선택 후 Preferences 클릭)

 

IAM user를 생성해서 해당 사용자로 AWS CLI 설정을 해준다.

나는 오류가 발생해 sudo aws configure 를 사용했다.

 

세부정보로 환경 변수 설정을 해주자.

# 환경 변수 설정
export AWS_DEFAULT_REGION="Region you want to use, e.g.for Ireland: eu-west-1" 
export AWS_ACCESS_KEY_ID="The AccessKeyId value of the result above"
export AWS_SECRET_ACCESS_KEY="The SecretAccessKey value of the result above"

# 환경 변수 확인
echo $AWS_DEFAULT_REGION
echo $AWS_ACCESS_KEY_ID
echo $AWS_SECRET_ACCESS_KEY

 

Greengrass 설정

  • AWS IoT Core의 초기 설정을 수행하고 AWS IoT Greengrass V2 설치 프로그램을 사용해 Cloud9(emulating edge gateway)에 Greengrass V2 설치
    • 설치 프로그램은 사물, 정책, 디바이스 인증서를 프로비저닝
    • IoT Core에서 Greengrass 코어 디바이스 설정 및 AWS IoT Core 연결을 완료

Greengrass Core device로 들어가 코어 디바이스를 1개 생성해보자.

3단계에 보이는 명령어를 cloud9에 입력해주자.

스크립트 완료 후 Greengrass CLI 명령을 실행하고 버전이 표시되면 성공한 것!

/greengrass/v2/bin/greengrass-cli -V

 

Greengrass의 상태 확인을 하고 싶으면 아래와 같은 명령어를 입력하자.

# 서비스 상태 확인
sudo systemctl status greengrass.service

# 부팅 시 서비스 시작
sudo systemctl enable greengrass.service

 

추가적인 IAM 정책 허용이 필요하다.

  • Publish your custom component (using S3)
  • MQTT Bridge

위에서 생성한 정책을 GreengrassV2TokenExchangeRole에 연결 한다.

 

첫번째 Component 만들기

  • component의 단위로 모듈식 소프트웨어를 임의로 개발하고 배포할 수 있다.
  • 디바이스에 샘플 애플리케이션을 만들고 이를 구성 요소로 배포할 예정~

component artifact 생성을 먼저 진행한다.

mkdir -p ~/environment/GreengrassCore/artifacts/com.example.HelloWorld/1.0.0 && touch ~/environment/GreengrassCore/artifacts/com.example.HelloWorld/1.0.0/hello_world.py

위의 명령어를 실행하면 아티팩트 폴더와 빈 스크립트 파일이 생성될 것이다.

(아티팩트의 경로의 경우 항상 artifacts/componentName/componentVersion/. 의 형식을 따라야 한다!)

 

파이썬 파일에 아래의 코드를 붙여 넣는다.

# 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)

 

component recipe 생성을 진행한다.

mkdir -p ~/environment/GreengrassCore/recipes && touch ~/environment/GreengrassCore/recipes/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"
         }
      }
   ]
}

 

Component 실행 및 테스트

터미널을 새로 열어주고 아래의 로그 확인 명령어를 넣고 열린 상태로 유지하자.

sudo tail -F /greengrass/v2/logs/greengrass.log

대충 요론 느낌

Component를 Greengrass 코어 디바이스에 배포하자.

sudo /greengrass/v2/bin/greengrass-cli deployment create \
  --recipeDir ~/environment/GreengrassCore/recipes \
  --artifactDir ~/environment/GreengrassCore/artifacts \
  --merge "com.example.HelloWorld=1.0.0"
# 배포 성공 확인 로그
tail -F /tmp/Greengrass_HelloWorld.log

여기까지는 테스트를 위해 개발하는 동안 component의 로컬 배포를 만드는 과정이었고,

이제는 component의 특정 버전에 만족하면 현장에서 디바이스에 배포할 구성 요소를 준비하는 과정에 대해 실습할 예정이다.

 

글이 너무 길어지니 이 내용은 다음 블로그 글에서 다룰 예정~

'Cloud > AWS' 카테고리의 다른 글

[AWS] AWS IoT SiteWise 실습  (0) 2023.01.27
[AWS] AWS IoT Greengrass V2 실습 -2  (0) 2023.01.26
[AWS] Firebase를 AWS Amplify로  (0) 2023.01.04
[AWS] re:Invent 2022 정리  (0) 2022.12.19
[AWS] Windows Server RDP 접속  (0) 2022.12.09

참고 1: https://realyun99.tistory.com/153

참고 2: https://realyun99.tistory.com/154

참고 3: https://realyun99.tistory.com/155

얼마 안남았다...! 마지막 lab!!


  • S3 버킷

버킷을 생성한다.(나머진 default로 진행)

 

  • Kinesis Data Firehose 생성

Browse를 눌러 위에서 생성했던 버킷 지정해준다.

위와 같이 추가 설정을 진행한 후 생성을 클릭

 

  • IoT Core 규칙 생성

위와 같이 규칙 작업을 추가해준다.

 

  • S3 버킷 확인하기

.gz 파일 객체 확인

→ 디바이스의 메시지가 S3 데이터 레이크에 저장되었음을 확인.

 

 

 

❗ 위의 설계도 처럼 사용하기도 하지만 보통 OpenSearch의 경우 비싸 많이 사용하지 않는다. 오히려 S3 뒤쪽으로 분석 쪽 서비스들을 더해 주로 사용하게 될듯! ❗

sample

(S3 + Glue + Athena로 분석하고 QuickSight로 시각화)

참고 1: https://realyun99.tistory.com/153

참고 2: https://realyun99.tistory.com/154

위의 포스팅 부터 확인하고 오세요~


OpenSearch Service

(Apache 2.0 라이선스 하에 제공되는 분산형 커뮤니티 기반 100% 오픈소스 검색 및 분석 제품군)

(실시간 애플리케이션 모니터링, 로그 분석 및 웹 사이트 검색과 같이 다양한 사용 사례에 사용됨)

  • CloudFormation 작업

해당 파일을 다운로드.

cfn.yaml

그 후에 스택을 생성해주자.

파라미터에 SourceIP에는 내 ip를, ThingName에는 저번에  생성했던 iot 디바이스 이름을 넣어주자.

(꽤나 오래 걸린다..)

 

스택 리소스에서 Domain을 클릭해 들어가자.

해당 url에 들어가자.

 

<Stack 생성 완료의 결과물>

- OpenSearch 도메인

- IoT 규칙(opensearch_rule)

- IoT 규칙에서 사용되는 IAM 역할

 

  • OpenSearch 작업

(메뉴 → Discover)

스크립트를 돌리는 상태에서 실행해야함(cloud9)

위와 같은 화면이 보이고 Create index pattern을 클릭하자.

자동으로 데이터가 분석되는 상황을 확인할 수 있다.

 

다시 메뉴 → Discover로 돌아가보면

OpenSearch 대시보드 화면에 데이터가 등록되어 있는지 확인이 가능하다.

 

(메뉴 → Visualize)

Line 선택 → timestamp* 선택

위와 같이 설정해주고 업데이트 클릭

SAVE 버튼 누르고 원하는 title 달아서 저장 가능

(입맛대로 설정을 변경해서 새 그래프를 추가해 사용 사례에 맞는 대시보드로 활용하면 될듯!)

 

 

❗ 세상이 참 좋아졌다는 것을 느낀다😁 ❗

1편에서 장치 설치 하고 오기!

https://realyun99.tistory.com/153

이어서 진행해보자..


Kinesis Data Streams

  • 스트림 생성

 

DynamoDB

  • DB 생성

 

Lambda

  • IAM 역할 생성 - Lambda

  • IAM 역할 생성 - IoT

권한의 경우 알아서 추가해줌.. 그대로 진행하면 됨

 

  • Lambda 함수 생성 및 트리거 추가

역할의 경우 위에서 만들었던 IAM Lambda 역할을 지정해주자.

 

다음으로 트리거 추가를 하자. 먼저 Kinesis!

함수에 코드를 추가해주자.. 추가 후에 꼭 Deploy 버튼 누르기!!!!!!

더보기
from __future__ import print_function
import base64
import boto3
from boto3.dynamodb.conditions import Key, Attr
import json
import os
import traceback

#-----Dynamo Info change here------
TABLE_NAME = os.environ.get('TABLE_NAME', "default")
DDB_PRIMARY_KEY = "deviceid"
DDB_SORT_KEY = "timestamp"
DDB_ATTR = "temp"
#-----Dynamo Info change here------

dynamodb = boto3.resource('dynamodb')
table  = dynamodb.Table(TABLE_NAME)

'''
This Kinesis data(Json Image) is below
{
    "DEVICE_NAME": $device_name,
    "TIMESTAMP": $TimeStamp(yyyy-mm-ddThh:MM:SS),
    "HUMIDITY" : int,
    "TEMPERATURE" : int
}
'''
def checkItem(str_data):
    try:
        #String to Json object
        json_data = json.loads(str_data)
        # adjust your data format
        resDict = {
            DDB_PRIMARY_KEY:json_data['DEVICE_NAME'],
            DDB_SORT_KEY:json_data['TIMESTAMP'],
            "HUMIDITY": json_data['HUMIDITY'],
            "TEMPERATURE": json_data['TEMPERATURE']

        }
        print("resDict:{}".format(resDict))
        return resDict

    except Exception as e:
        print(traceback.format_exc())
        return None

def writeItemInfo(datas):
    ItemInfoDictList = []
    try:
        for data in datas:
            itemDict = checkItem(data)
            if None != itemDict:
                ItemInfoDictList.append(itemDict)
            # if data does not have key info, just pass
            else:
                print("Error data found:{}".format(data))
                pass

    except Exception as e:
        print(traceback.format_exc())
        print("Error on writeItemInfo")

    return ItemInfoDictList

def DynamoBulkPut(datas):
    try:
        putItemDictList = writeItemInfo(datas)
        with table.batch_writer() as batch:
            for putItemDict in putItemDictList:
                batch.put_item(Item = putItemDict)
        return

    except Exception as e:
        print("Error on DynamoBulkPut()")
        raise e

def decodeKinesisData(dataList):
    decodedList = []
    try:
        for data in dataList:
            payload =  base64.b64decode(data['kinesis']['data'])
            print("payload={}".format(payload))
            decodedList.append(payload)

        return decodedList

    except Exception as e:
        print("Error on decodeKinesisData()")
        raise e

#------------------------------------------------------------------------
# call by Lambda here.
#------------------------------------------------------------------------
def lambda_handler(event, context):
    print("lambda_handler start")

    try:
        print("---------------json inside----------------")
        print(json.dumps(event))
        encodeKinesisList = event['Records']
        print(encodeKinesisList)
        decodedKinesisList = decodeKinesisData(encodeKinesisList)
        # Dynamo Put
        if 0 < len(decodedKinesisList):
            DynamoBulkPut(decodedKinesisList)
        else:
            print("there is no valid data in Kinesis stream, all data passed")

        return

    except Exception as e:
        print(traceback.format_exc())
        # This is sample source. When error occur this return success and ignore the error.
        raise e

 

환경 변수 지정을 위해 설정을 들어가자 (구성 → 환경변수)

DynamoDB 테이블을 넣어주자

 

IoT Core 확인

  • 메시지 라우팅 규칙 생성

(메시지 라우팅 → 규칙)

from 뒤에는 아까 생성했던 디바이스의 topic을 넣어준다.

 

IoT와 Kinesis를 연결해준다. IoT 디바이스에서 데이터를 받아와 Kinesis로 흐르게

Role도 아까 생성했던 iot 역할로 지정해준다.

 

현재 iot 디바이스 연결도 잘 되어 있는 것을 볼 수 있음.

 

  • Kinesis Stream 모니터링

또, 위에서 연결했던 Kinesis의 모니터링을 보면 데이터가 잘 들어오는 것을 볼 수 있다.

(지표들 중에서 GetRecords - 합계 | PutRecords - 합계 값이 0이 아닌지 확인)

시간이 좀 지나야 해당 그래프를 볼 수 있음...!

  • DynamoDB 모니터링

라이브 항목 수 가져오기 클릭

항목 탐색을 확인해보자.

 

  • Lambda 모니터링

(Invocations 그래프가 끝에 떨어지는 모양은 내가 cloud9 실행을 멈췄기 때문/ 스크립트 계속 돌리면 그래프 유지됨.)

 

API 용 Lambda 구성

  • Lambda 함수 만들기

해당 코드를 넣어준다.(Deploy 클릭)

더보기
from __future__ import print_function
import boto3
from boto3.dynamodb.conditions import Key
import datetime
import json
import traceback
import os

#-----Dynamo Info change here------
TABLE_NAME = os.environ.get('TABLE_NAME', "default")
DDB_PRIMARY_KEY = "deviceid"
DDB_SORT_KEY = "timestamp"
#-----Dynamo Info change here------

dynamodb = boto3.resource('dynamodb')
table  = dynamodb.Table(TABLE_NAME)

#------------------------------------------------------------------------
def dynamoQuery(deviceid, requestTime):
    print("dynamoQuery start")
    valList = []
    res = table.query(
        KeyConditionExpression=
            Key(DDB_PRIMARY_KEY).eq(deviceid) &
            Key(DDB_SORT_KEY).lt(requestTime),
            ScanIndexForward = False,
            Limit = 30
        )

    for row in res['Items']:
        val = row['TEMPERATURE']
        itemDict = {
            "timestamp":row['timestamp'],
            "value":int(val)
        }
        valList.append(itemDict)

    return valList

#------------------------------------------------------------------------
# call by Lambda here.
#  Event structure : API-Gateway Lambda proxy post
#------------------------------------------------------------------------
def lambda_handler(event, context):
    #Lambda Proxy response back template
    HttpRes = {
        "statusCode": 200,
        "headers": {"Access-Control-Allow-Origin" : "*"},
        "body": "",
        "isBase64Encoded": False
    }

    try:
        print("lambda_handler start")
        print(json.dumps(event))

        # get Parameters
        pathParameters = event.get('pathParameters')
        deviceid = pathParameters["deviceid"]
        requestTime = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')

        resItemDict = { deviceid : ""}
        resItemDict[deviceid] = dynamoQuery(deviceid, requestTime)
        HttpRes['body'] = json.dumps(resItemDict)

    except Exception as e:
        print(traceback.format_exc())
        HttpRes["statusCode"] = 500
        HttpRes["body"] = "Lambda error. check lambda log"

    print("response:{}".format(json.dumps(HttpRes)))
    return HttpRes

위에서 했던 것과 같이 DynamoDB 환경변수를 넣어준다.

 

API Gateway

  • REST API 생성

(작업 → 리소스 생성)

(/datas 클릭 후 리소스 생성)

리소스 경로 괄호에 주의하라

(/deviceid에서 매서드 생성)

권한 추가는 확인을 클릭해준다.

위와 같은 화면이 구성되면 완료.

(작업 → CORS 활성화)

/{deviceid} 에서 cors 활성화!

(작업 → API 배포)

/{deviceid} 에서 api 배포를 진행하자.

생성된 url을 복사해두자.

 

DrawGraph.zip을 받고

(js/createGraph.js 파일)

첫번째 줄 device_name에 만들었던 디바이스 입력

두번째 줄 hosturl에 api url 입력

 

다운받았던 폴더에서 html 브라우저에서 실행하면

다음과 같은 그래프가 나오면 성공이다.(해당 그래프 나올 때까지의 시간이 걸릴 수 있음!)

위의 값은 DynamoDB에 저장된 디바이스의 Temperature값을 표시한다.

 

 

❗ 다음엔 실시간 시각화 OpenSearch를 사용해보자. ❗

참고: https://catalog.us-east-1.prod.workshops.aws/workshops/b3e0b830-79b8-4c1d-8a4c-e10406600035/ja-JP/phase1

일본어로 된 workshop 자료라... 최신 버전이 이것밖에 없었따..😥 번역기 돌려서 사용하세요

 


Device Setup

  • Cloud9 인스턴스 생성

서울 리전 기준으로 t3.small 이상으로 잡아야 생성 가능!

이 인스턴스가 디바이스 역할을 해줄 예정(나중에 라즈베리파이로도 진행해보자)

 

  • AWS IoT Device SDK Python v2 설치

아래의 명령어를 Cloud9 인스턴스에 입력하자.

pip3 install --user awsiotsdk
mkdir -p ~/environment/dummy_client/certs/
cd ~/environment/dummy_client/
wget https://awsj-iot-handson.s3-ap-northeast-1.amazonaws.com/aws-iot-core-workshop/dummy_client/device_main.py -O device_main.py

 

IoT Core Setup

  • IoT Core 정의

IoT 설정의 디바이스 데이터 엔드포인트 값을 복사한다.

다음으로 로그 관리를 클릭 하고 로그 설정을 해주자.

IAM 역할은 새로 생성해주자!

 

  • IoT Policy 생성

(side menu → 보안 → 정책)

❗ 현재 정책 설정을 모든 작업 모든 리소스를 허용하게끔 되어 있는데, 이건 테스트 용이니까 광범위하게 설정한 것..

프로젝트를 할 때에는 최소한의 권한으로 설정해둘 것! ❗
보통 최소한의 권한의 경우(workshop 기준, 도쿄 리전)

더보기

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "arn:aws:iot:ap-northeast-1:123456789012:client/${iot:ClientId}"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:topic/data/${iot:Connection.Thing.ThingName}",
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update",
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/get"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/delta",
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/accepted",
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/rejected",
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/get/accepted",
        "arn:aws:iot:ap-northeast-1:123456789012:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/get/rejected"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Subscribe"
      ],
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/delta",
        "arn:aws:iot:ap-northeast-1:123456789012:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/accepted",
        "arn:aws:iot:ap-northeast-1:123456789012:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/rejected",
        "arn:aws:iot:ap-northeast-1:123456789012:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/get/accepted",
        "arn:aws:iot:ap-northeast-1:123456789012:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/get/rejected"
      ]
    }
  ]
}

 

  • 디바이스 생성

(모든 디바이스 → 사물)

단일 사물 생성

새 인증서 자동 생성(권장)

정책의 경우 위에서 생성했던 정책 클릭

인증서랑 키 파일 다운로드

 

  • Cloud9 인스턴스에 키파일 등록

private.pem.key와 certificate.pem.crt 업로드

아래의 명령어 입력

cd ~/environment/dummy_client
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O certs/AmazonRootCA1.pem

 

  • Device SDK

해당 명령어들 터미널에 입력한다.

cd ~/environment/dummy_client/
python3 device_main.py --device_name {Device_name} --endpoint {AWS IoT endpoint_url}

해당 커멘드를 실행하면 dummy_client는 IoT Core에 MQTT로 연결하고 5초 마다 메세지 전송(default)

 

  • Device Test

(테스트 → MQTT 테스트 클라이언트)

아까 위에서 실행했던 커멘드 출력들 중 topic을 알아두자.

해당 topic을 구독하면 device에서 보내는 메시지를 확인할 수 있다.

 

  • Device Shadow 확인

해당 클래식 섀도우에 들어가 디바이스 섀도우 문서 편집을 누른다.

해당 코드를 

{
    "state": {
        "reported": {
            "wait_time": 5
        },
        "desired": {
            "wait_time": 1
        }
    }
}

로 변경하고 업데이트 해준다.

업데이트 후 문서에 "welcome": "aws-iot" 가 남아있으면, 

위 이미지처럼 null 값을 넣어준다. wait_time 값을 변경하면 device가 data를 보내는 빈도가 바뀜.

desired: 디바이스에 지시하고 있는 상태

reported: 디바이스로부터 보고된 상태

delta: desired와 reported 상태에 차이가 있을 때 표시됨

섀도우에서 다시 해당 문서를 wait_time을 2로 변경하면 터미널에서도 확인할 수 있다.

MQTT 테스트 쪽에서 주제를 다시 구독한 후 확인하면 데이터가 전송되는 간격이 변경되었다는 것을 확인할 수 있음.

 

 

❗ 다음 포스팅은 애플리케이션용 DB 만들기에 관해 포스팅 올릴 계획!  ❗

+ Recent posts