* 기본적인 세팅은 되어 있는 상태 입니다. (VPC, Nat GW, ALB, EC2(private subnet) 구성)

* private ec2: etech-private-ec2(10.0.11.130)


1. bastion server를 통한 접속

사용자가 Bastion Server를 통해 private ec2에 ssh 프로토콜을 사용해 접속합니다.

 

1) bastion server를 생성합니다.

 

원하는 이름 설정
원하는 OS 이미지 선택
인스턴스 유형 선택

(t3.nano의 작은 유형으로도 bastion server에는 충분합니다.)

 

키페어 선택
private ec2의 vpc를 선택

vpc의 public subnet을 선택해주시고, public ip 활성화를 선택해줍니다.

보안 그룹은 원하는 이름을 넣어주시고 소스 유형을 "내 IP"로 선택해 관리자만 접속할 수 있게 설정해줍니다.

 

나머지 설정은 디폴트로 두고 인스턴스 시작을 클릭해 생성합니다.

 

 

2) 관리자가 사용하기 편한 ssh 접속 툴을 활용해 bastion server를 통해 private ec2에 접속합니다.

* MobaXterm을 활용했습니다.

 

새로운 session 추가

Remote host는 private ec2의 ip를 넣고 Specify username에는 OS에 맞는 username을 넣어줍니다.

private key는 인스턴스 생성 시 선택했던 key pair를 넣어 주시면 됩니다.

 

Bastion Host 작업을 위해 클릭
bastion server 설정

위에서 생성했던 bastion server의 public ip와 username, port와 ssh key pair를 넣고 세션을 연결합니다.

 

성공적으로 접속하면 아래와 같은 화면을 보실 수 있습니다.

 

2) SSM(System Manager)를 통한 접속

IAM 역할에 SSM 권한을 부여합니다. 그런 다음 해당 IAM 역할을 EC2에 연결하여, private 네트워크에서도 안전하게 접속할 수 있습니다.

 

1) AmazonSSMManagedInstanceCore 역할을 생성합니다.

 

AmazonSSMManagedInstanceCore가 잘 들어갔는지 확인

ssm 연결을 위해 ec2에 필요한 역할을 생성해줍니다.

 

원하는 이름 설정

위 설정이 끝나면 역할 생성을 클릭합니다.

 

 

2) private ec2에 위에서 생성한 역할을 연결해줍니다.

IAM 역할 수정 버튼 클릭

 

위에서 생성한 역할을 선택한 뒤 IAM 역할 업데이트를 클릭합니다.

( 해당 역할을 적용하는 데 시간이 상당히 소요될 수 있습니다. 빠르게 적용시키기 위해 인스턴스를 재부팅하여 시간을 단축시킬 수 있습니다.)

 

3) private ec2 에 접속합니다.

 

연결 버튼 클릭
session manager 연결 클릭

 

위와 같은 화면을 보면 private ec2에 접속 성공한 것을 알 수 있습니다.

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/de4e38cb-a0d9-4ffe-a777-bf00d498fa49/ko-KR

고객사에서 OpenSearch 서비스를 활용 중이라 무슨 서비스인지 알고 싶어서 무작정 따라해봤다..!


Amazon OpenSearch 클러스터 생성

  • create domain

테스트니까 네트워크는 퍼블릭으로, 나머지 설정들은 디폴트로 놔두면 된다..

(뭐시기 에러뜨면.. 고급 클러스터 설정 쪽 최대 절 수 그냥 1024로 설정..)

생성 완료까지 시간이 좀 걸린다.

 

AWS Blog 문서를 Amazon OpenSearch로 검색하기

  • cloud9 인스턴스 생성
  • OpenSearch - 인덱스 생성

OpenSearch 대시보드에 들어간다.

로그인의 경우 아까 클러스터 생성할 때 사용했던 ID/Password를 사용하면 된다.

해당 버튼을 클릭하고 접속한다.

private &rarr; confirm

메뉴의 Dev Tools 를 클릭한다.

(검색을 위한 index 생성하고 mapping 설정)

아래 코드 붙여넣고 화살표 클릭해서 실행

PUT /aws-blog
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "author": {
        "type": "keyword"
      },
      "body": {
        "type": "text"
      },
      "category": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "date": {
        "type": "date",
        "format": "dd MMM yyyy HH:mm:ss z"
      },
      "url": {
        "type": "keyword"
      }
    }
  }
}

여기까지의 단계가 AWS blog 문서들을 색인할 index를 생성한 것!

 

  • Blog 문서를 index에 저장하기

cloud9에 접속 후 아래 명령어를 입력한다.

git clone https://github.com/jinuland/aws-blog-crawler.git

해당 파일을 클릭하고 host, id, password를 입력한다. host는 OpenSearch의 도메인 엔드포인트이다.

cd aws-blog-crawler
./ingest_ko.sh

위 명령어 처럼 프로그램을 실행시킨다. (AWS Blog 글들이 OpenSearch 도메인으로 색인된다.)

 

  • OpenSearch 대시보드에서 문서 검색해보기

OpenSearch의 DevTools로 돌아가 아래처럼 입력한다.

GET /aws-blog/_search
{
  "query": {
    "match_all": {
    }
  }
}

 

Full Text 검색

  • Dev Tools에서 API 호출

(workshop 기준으론 Dev Tools로  API를 호출하지만, Application에서 검색 서비스 제공을 하고 싶으면 

API Gateway + Lambda + OpenSearch로 구현할 수 있다.)

즉, Dev Tools에서 아래처럼 쿼리를 날리면 원하는 결과를 얻을 수 있다.

# 자연어 검색
GET /aws-blog/_search
{
  "query": {
    "multi_match": {
      "query": "aws 리전 및 인프라",
      "fields": ["title","body"]
    }
  }
}

# Author 별 Aggregation
GET /aws-blog/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "title": "데이터"
          }
        }
      ]
    }
  },
  "aggs": {
    "category": {
      "terms": {
        "field": "author"
      }
    }
  }
}

위와 같이 OpenSearch를 활용한다... 

시각화를 구현하진 않았지만.. 시각화까지 가능함!

 

[OpenSearch 기능]

https://aws.amazon.com/ko/opensearch-service/features/

미리 AWS 환경은 세팅해둔 상태! (external alb - web(ec2) - was(ec2))

직접 apache와 tomcat을 올려보기 위한 작업을 할 것이다...

 

왜 web - was 분리 구성을 하는가?

https://hwanchang.tistory.com/5


WEB 서버 구성(Apache)

  • Apache 설치 및 시작
yum install -y httpd
systemctl start httpd
  • alb 의 target group 상태검사 경로 변경 

  • index.html 생성
cd /var/www/html
vi index.html

# index.html
<html>
<body>WEB-A 서버입니다.</body>
</html>
  • health check: alb dns 주소를 통해 확인

  • health check: curl 명령어 활용

 

WAS 서버 구성(Tomcat)

  • java 다운로드 & tomcat 다운로드

https://www.oracle.com/java/technologies/downloads/

https://tomcat.apache.org/에서 다운로드

tar xvzf <파일명>
cp -R <디렉토리명> /usr/local/src
  • tomcat, jdk 경로 설정
vi /etc/profile

# 경로 확인
source /etc/profile
echo $JAVA_HOME
echo $CATALINA_HOME

  • tomcat 서비스 등록
cd /etc/init.d
vi tomcat

# tomcat file
#!/bin/bash
# Startup script for the Tomcat Server
# chkconfig : 345 50 50
# description: Tomcat is a Web application server.
# processname : java
# directory :
CATALINA_HOME=/usr/local/src/apache-tomcat-10.1.11
export JAVA_HOME=/usr/local/src/jdk-17.0.8
export CATALINA_HOME=/usr/local/src/apache-tomcat-10.1.11
case "$1" in
start)
echo "Starting tomcat: "
$CATALINA_HOME/bin/startup.sh
;;
stop)
echo "Shutting down tomcat: "
$CATALINA_HOME/bin/shutdown.sh
;;
restart)
echo "Restarting tomcat: "
$CATALINA_HOME/bin/shutdown.sh
$CATALINA_HOME/bin/startup.sh
;;
*)
echo "Usage: service tomcat"
start|stop|restart
exit 1
esac
exit 0

chmod 755 tomcat
service tomcat start

  • EC2 SG에 8080 포트 오픈
  • WAS 서버 index.html 생성
cd /usr/local/src/apache-tomcat-10.1.11/webapps/ROOT
vi index.html

# index.html
<html>
<body>WAS-A 서버입니다.</body>
</html>

 

WEB 서버 - WAS 서버 연동

  • WEB 서버 내부에 httpd.conf 파일 수정
vi /etc/httpd/conf/httpd.conf

###New Configuration
<VirtualHost *:80>
    ServerName <alb-dns주소>

    ProxyPass / http://<was-ip>:8080/
    ProxyPassReverse / http://<was-ip>:8080/
</VirtualHost>

systemctl restart httpd.service
  • WAS 서버
cd /usr/local/src/tomcat/bin
./shutdown.sh
./startup.sh
  • alb dns 접속 시 WAS 화면이 뜨면 성공 

 

 

참고: https://sweetysnail1011.tistory.com/57

 

❗java 다운받을 때 프로세서 확인 꼭 하기 uname -i 명령어 활용❗

'Computer Science > DevOps' 카테고리의 다른 글

[Jenkins] 다양한 플러그인 활용  (0) 2023.07.24
[Jenkins] Jenkins Pipeline 구축  (0) 2023.07.03
[Jenkins] Jenkins 다뤄보기  (0) 2023.06.27
[OS] OS 모음집  (0) 2021.08.12
[CI/CD] Tool  (0) 2021.08.11

(참고: https://iot-sitewise.workshop.aws/en/introduction/what-you-are-going-to-build.html)


CloudFormation 스택 생성 (도쿄)

  • Cloud9 환경
  • SageMaker 노트북 인스턴스 등..

 

SiteWise gateway가 AWS IoT Greengrass에서 실행되고 Greengrass는 AWS IoT Core에 연결한다.

(IoT Core의 Rule Engine으로 데이터를 수집할 예정!)

→ 실습의 흐름 맛보기 랄까...

 

IoT에 대한 로깅 활성화

  • IoT 서비스가 계정의 CW에 쓸 수 있도록 허용하려면 서비스 역할이 필요하다.
    • CloudFormation 콘솔 상 IoT ServiceRoleName 출력 세션에서 역할 이름 확인 가능하다.

 

AWS IoT SiteWise 설정

  • 자신의 속성이 데이터 스트림과 일치하는 경우에만 데이터가 SiteWise로 수집된다.
  • 데이터 수집 방법
    • OPC-UA 서버에 연결하고 이러한 서버에서 데이터를 검색하는 SiteWise 게이트웨이를 통해
    • AWS IoT Core의 MQTT 항목에 메시지를 게시하고 AWS IoT 규칙을 사용하여 데이터를 AWS IoT SiteWise로
    • BatchPutAssetPropertyValue API를 사용하여 SiteWise로 직접

 

먼저 SiteWise gw를 만들자.

(Greengrass 구성 요소로 게이트웨이가 실행된다.)

(data collection pack: 데이터를 수집하고 클라우드로, data processing pack: 게이트웨이가 에지에서 데이터 처리)

진행할 실습 상에서 게이트웨이는 cloud9 환경이다.

엣지 기능에서 데이터 처리팩은 비활성화!

게시자 구성의 경우는 디폴트

승인을 누르면 설치 파일이 PC에 다운로드 된다.

다운로드된 파일을 cloud9으로 업로드 한다. (/home/ec2-user/sitewise/)

cd ~/sitewise
chmod +x sitewise-workshop-Gateway.deploy.sh
sudo ./sitewise-workshop-Gateway.deploy.sh

(Greengrass의 코어 디바이스 → 배포  → 구성 요소)

# greengrass 상태 확인
systemctl status greengrass.service

# greengrass process 실행 확인
ps aux |grep green

# 로그 파일
sudo su -
cd /greengrass/v2/logs/
  • 로그 확인
    • greengrass.log : AWS IoT Greengrass에 대한 로그
    • aws.iot.SiteWiseEdgeCollectorOpcua.log : OPC-UA 서버에서 데이터를 수집하는 SiteWise 구성 요소
    • aws.iot.SiteWiseEdgePublisher.log : 클라우드의 SiteWise 데이터 저장소로 데이터를 전송하는 SiteWise 구성 요소

 

OPC-UA 서버 시작을 해보자.

cd ~/sitewise/
./start-node-opcua.sh

 

  • OPC-UA 서버가 SiteWise 데이터 수집기(aws.iot.SiteWiseEdgeCollectorOpcua) 에서 연결을 수신했다.
  • 수집기의 경우 OPC-UA 서버에서 데이터를 검색하여 스트림에 기록한다.
  • SiteWise Publisher는 이 스트림을 읽고 데이터를 AWS cloud의 AWS IoT SiteWise로 전달한다.

현재 까지의 환경

 

 AWS IoT SiteWise 데이터 수집

  • SiteWise는 데이터를 소비하고 데이터 스트림을 자산 속성에 일치 시킨다.
    • 자산 모델 및 자산을 생성하고 데이터 스트림을 자산 속성과 연결한다.

 

먼저 AWS IoT SiteWise 데이터 스토리지를 확인해보자.

aws iotsitewise describe-storage-configuration

연결 해제된 데이터 저장소가 활성화 되면 위의 출력이 뜬다. → disassociated mode

(데이터 저장소가 설정된 모드는 데이터 스트림을 자산 속성과 연결하는 방식에 영향을 준다.)

 

데이터 수집의 경우 (실습 기준)

  • Generators 1 and 2: through a SiteWise gateway
  • Generators 3 and 4: through the SiteWise PUT API
  • Generators 7 and 8: by using AWS IoT’s rules engine

 

[자산 모델링]

SiteWise 자산을 사용해 산업 운영의 가상 표현을 생성할 수 있다.

(자산: 디바이스, 장비 또는 하나 이상의 데이터 스트림을 IoT SiteWise로 보내는 프로세스)

실습의 예로 /DayOneEnergyCorp/1/Generator/Power 의 경우 1번 발전기에서 생성된 에너지 측정값이다.

(측정값을 기반으로 메트릭 생성 가능)

 

Asset model → Asset → Asset property

 

[Generator 표현]

(실습: 각 발전기는 분당 생산된 에너지와 발전기의 내부 온도를 보고, 분당 20와트의 에너지 생산 예상)

생성기 템플릿에서 아래의 측정 및 지표 정의할 예정!

  • 측정: 생산된 분당 와트, 온도 및 rpm
  • 지표: 5분당 총 와트, 5분당 평균 온도 및 5분당 평균 rpm

 

자산 모델(Asset model)을 생성해보자.

자산의 경우 템플릿을 기반으로 생성된다.(정보의 일관성 보장)

위 이미지와 같이 모델을 생성한다. (상태가 active 될 때 까지 기다리기!)

 

자산(Asset)을 만들어 보자.

얘도 상태가 active 확인이 되어야 한다.


데이터 스트림 연결 및 알림을 구성해보자.

(자산이 생성된 뒤 SiteWise에 수집되는 데이터 스트림을 자산 속성과 연결하는 과정이 필요하다.)

SiteWise가 IoT에 데이터를 게시하도록 하려면 관련 속성에 대한 알림을 활성화 해야한다.

 

나의 경우 연결 해제 모드 인 것을 위에서 확인했다.

데이터 스트림 관리를 통해 속성을 선택해 업데이트 한다.

위와 같이 데이터 스트림이 Generator-1(자산)의 속성과 연결된 것을 확인할 수 있다.

 

자산에 들어가 Generator-1의 모든 측정 및 지표에 대한 MQTT 알림 상태를 ENABLED로 설정

 

데이터 플로우를 확인해보자.

(SiteWise 게이트웨이는 OPC-UA 서버에서 스트림을 가져와 SiteWise에 업로드해야 한다.

3개의 데이터 스트림이 자산(Generator-1)에 연결된다. 알림이 활성화되어 있기에 IoT Core에 게시된다.)

 

SiteWise는 알림의 데이터를 토픽에 게시한다.

$aws/sitewise/asset-models/+/assets/+/properties/+

아니면 자산에서 측정 및 지표에서 최신값에 값이 표시가 되어야 한다!

 

데이터 생성기를 시작하려면,

# AWS IoT Core로 수집 시작
cd ~/sitewise
./sitewise-iot-ingest.py

# AWS IoT SiteWise로 수집 시작
cd ~/sitewise
./sitewise-direct-ingest.py

(추가 자산을 만들어 더 많은 데이터를 수집하려면 Generator-2는 SiteWise gw, Genterator-3는 PUT API, Generator-7은 IoT Core Rule Engine을 통해 수집된다.)

 

대충 이런 흐름이다...(어렵다😥)

 

AWS IoT SiteWise Monitor

이제 마지막으로 모니터링을 할 수 있는 환경을 만들어보자.

자산의 데이터를 모니터링하려면 포털을 생성한 뒤 프로젝트와 대시보드를 만든다.

(AWS 계정 없이도 포털을 다른 사용자와 공유가 가능하다.)

루트 하나만 프로젝트와 연결 가능하기 때문에 계층 구조 생성이 필요

계층 구조를 위한 자산 모델 생성 가이드

더보기

CREATE POWER PLANT ASSET MODEL

  1. Name: Power Plant
  2. Attribute definitions
  3. Name: location, Default value: {"lat": "none", "lon": "none"}, Data type: String
  4. Hierarchy definitions
  5. Hierarchy name: Power_Plants-Generators-Hierarchy
  6. Hierarchy model: Generator model 262966 Doppler
  7. Metric definitions
  8. Name: power_per_plant_5m
  9. Formula: sum(Power_Plants-Generators-Hierarchy sum_watts_5m)
  10. Time interval: 5 minutes

CREATE POWER PLANT ASSET

You will now create a power plant asset and add generator assets to it. The Power Plant 1 will host generators 1 and 2.

  1. Model: Power Plant
  2. Name: Power Plant 1
  3. Wait until the Status becomes ACTIVE
  4. Edit
  5. Assets associated to this asset
  6. Add associated asset
  7. Hierarchy: Power_Plants-Generators-Hierarchy
  8. Asset: Generator-1
  9. Add associated asset
  10. Hierarchy: Power_Plants-Generators-Hierarchy
  11. Asset: Generator-2
  12. Metric: “power_per_plant_5m”: Notification status: ENABLED
  13. Save asset

Create

  • Power Plant 2 with associated asset Generator-3
  • Power Plant 3 with associated asset Generator-7

CREATE ASSET MODEL FOR ALL POWER PLANTS

  1. Name: All Power Plants
  2. Hierarchy definitions
  3. Hierarchy name: All_Power_Plants-Power_Plants-Hierarchy
  4. Hierarchy model: Power Plant
  5. Metric definitions
  6. Name: power_all_plants_5m
  7. Formula: sum(All_Power_Plants-Power_Plants-Hierarchy power_per_plant_5m)
  8. Time interval: 5 minutes

CREATE AN ASSET FROM THE ALL POWER PLANTS MODEL

When the model is in the status ACTIVE create assets based on this model. Add Power Plant assets.

  1. Model: All Power Plants
  2. Name: All Power Plants

After the asset has been created associate each Power Plant asset to All Power Plants

  1. Edit
  2. Assets associated to this asset
  3. Add associated asset
  4. Hierarchy: All_Power_Plants-Power_Plants-Hierarchy
  5. Asset: Power Plant 1
  6. Add associated asset
  7. Hierarchy: All_Power_Plants-Power_Plants-Hierarchy
  8. Asset: Power Plant 2
  9. Add associated asset
  10. Hierarchy: All_Power_Plants-Power_Plants-Hierarchy
  11. Asset: Power Plant 3
  12. “power_all_plants_5m”: Notification status: ENABLED
  13. Save asset

  

너무 길어지니까.. 모니터링을 위한 포털 생성의 경우 다음 포스팅에서 진행하도록 하자...

위의 실습을 이어서 진행할 것이다.

https://realyun99.tistory.com/192


AWS Cloud에서 component를 배포하려면 먼저 recipe 파일을 확장해 S3의 component artifact를 참조한 다음 component artifact를 S3로 업로드 해야한다.

배포가 구성되면 디바이스가 S3에서 아티팩트를 다운로드 한다.

 

버킷을 만들고 다음 명령을 사용해 버킷 이름을 검색하는 명령어를 입력한다.

EPOCH_TIME=$(date +"%s") && S3_BUCKET=ggcv2-workshop-$HOSTNAME-$EPOCH_TIME && aws s3 mb s3://$S3_BUCKET

아래 코드 중 버킷이름을 위에서 검색했던 이름으로 변경하고 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"
         },
         "Artifacts": [
            {
               "URI": "s3://[YOUR BUCKET NAME]/artifacts/com.example.HelloWorld/1.0.0/hello_world.py"
            }
         ]
      }
   ]
}

component artifact를 업로드 해준다.

aws s3 cp --recursive /home/ubuntu/environment/GreengrassCore/ s3://$S3_BUCKET/

 

IoT Greengrass의 Component에서 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

# component 목록 확인
aws greengrassv2 list-components --region $AWS_DEFAULT_REGION

 

Pub/Sub IPC

Greengrass V2는 프로세스 간 통신을 위한 IPC SDK를 제공한다.

(custom component의 각 프로세스 간 Pub/Sub 통신이 가능하다.)

  • 센서 데이터를 읽고 토픽에 계속 pub하는 component, 해당 토픽 로그를 sub하고 메시지를 수신하는 component 및 IPC를 사용하는 custom component 를 만드는 과정

 

먼저 Publisher를 만들어보자.

데이터를 토픽에 pub하기 위해 더미 센서를 생성해야한다.

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
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
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 폴더와 빈파일 생성

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

 

다음 명령을 실행해 component를 배포하자.

sudo /greengrass/v2/bin/greengrass-cli deployment create \
  --recipeDir ~/environment/GreengrassCore/recipes \
  --artifactDir ~/environment/GreengrassCore/artifacts \
  --merge "com.example.Publisher=1.0.0"

 

이젠 Subscriber의 차례다. 위 과정과 비슷하니 명령어와 코드만 적어 두겠다...

더보기
# 아티팩트 폴더, 빈 스크립트 파일 생성
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

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

# 레시피 폴더와 빈파일 생성
touch ~/environment/GreengrassCore/recipes/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"
      }
    }
  ]
}

# 구성요소 배포
sudo /greengrass/v2/bin/greengrass-cli deployment create \
  --recipeDir ~/environment/GreengrassCore/recipes \
  --artifactDir ~/environment/GreengrassCore/artifacts \
  --merge "com.example.Subscriber=1.0.0"

받은 메시지가 계속 표시되면 성공한 것!

tail -f /tmp/Greengrass_Subscriber.log

 

Local MQTT Broker

여기까지는 Greengrass 디바이스가 데이터 공급자 역할을 할 수 있도록 허용하는 로컬 Pub/Sub 개념이였다.

그러나 데이터 공급자가 Greengrass 장치 외부에 있는 경우 MQTT 형식으로 게시 대상을 도입할 수 있다.

(Greengrass 장치를 로컬 MQTT 게이트웨이로 바꾸는 브릿지)

MQTT 브로커 구성 요소 배포를 진행하면 된다..

(배포 → 개정)

위의 구성요소 구성을 클릭한 뒤 아래의 코드를 넣어주면 된다.

# Auth
{
  "deviceGroups": {
    "formatVersion": "2021-03-05",
    "definitions": {
      "MyDeviceGroup": {
        "selectionRule": "thingName: MyClientDevice* OR thingName: MyOtherClientDevice*",
        "policyName": "MyClientDevicePolicy"
      }
    },
    "policies": {
      "MyClientDevicePolicy": {
        "AllowAll": {
          "statementDescription": "Allow client devices.",
          "operations": [
            "mqtt:connect",
            "mqtt:publish",
            "mqtt:subscribe"
          ],
          "resources": [
            "*"
          ]
        }
      }
    }
  }
}

# Bridge
{
   "mqttTopicMapping": {
     "HelloWorldIotCoreMapping": {
       "topic": "clients/+/hello/world",
       "source": "LocalMqtt",
       "target": "IotCore"
     }
   }
 }

이 실습에선 Mosquitto 클라이언트를 사용해 장치를 시뮬레이션 할 예정이다.

인증 구성 요소는 AWS IoT Core에서 AWS IoT 사물로 등록되고 사물과 연결된 동일한 인증서를 제공하는 디바이스를 지원한다.

 

Auth component 구성을 보면 이름이 MyClientDevice 로 시작하는 장치를 허용하고 있다.

cloud9 에 아래의 내용을 입력하자.

cd ~/environment
mkdir -p devices
cd devices
THING_NAME=MyClientDevice1
aws iot create-thing --thing-name $THING_NAME
CERTIFICATE_ARN=$(aws iot create-keys-and-certificate --private-key-out $THING_NAME.key --certificate-pem-out $THING_NAME.crt --query "certificateArn" --out text --set-as-active)
aws iot attach-thing-principal --thing-name $THING_NAME --principal $CERTIFICATE_ARN

cd ~/environment
mkdir -p devices
cd devices
THING_NAME=MyOtherClientDevice1
aws iot create-thing --thing-name $THING_NAME
CERTIFICATE_ARN=$(aws iot create-keys-and-certificate --private-key-out $THING_NAME.key --certificate-pem-out $THING_NAME.crt --query "certificateArn" --out text --set-as-active)
aws iot attach-thing-principal --thing-name $THING_NAME --principal $CERTIFICATE_ARN

 

MQTT를 통한 기기 통신 테스트를 해보면,

동일한 터미널에서 Mosquitto 클라이언트를 설치한다.

sudo apt install mosquitto-clients

 

다음으로 로컬 브로커 서버 인증서엥 서명하는 CA 인증서를 검색

(클라이언트가 브로커 엔드포인트를 확인하는데 사용됨)

cd ~/environment/devices
sudo cp /greengrass/v2/work/aws.greengrass.clientdevices.Auth/ca.pem .
sudo chmod 444 ca.pem

 

토픽 Subscribe를 해보자.

cd ~/environment/devices
mosquitto_sub -h localhost -p 8883 -q 0 -t clients/+/hello/world --cafile ca.pem --cert MyOtherClientDevice1.crt --key MyOtherClientDevice1.key -i MyOtherClientDevice1

 

메시지 publish (새 터미널 열고)

cd ~/environment/devices
mosquitto_pub -h localhost -p 8883 -q 0 -t clients/test/hello/world --cafile ca.pem --cert MyClientDevice1.crt --key MyClientDevice1.key -m "{\"message\": \""$2" @ "$(date +"%m-%d-%Y-%H-%M-%s")"\"}" -i MyClientDevice1

mosquitto_sub 명령을 실행하는 터미널에 메시지가 표시되어야 한다.

 

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

[AWS] Amazon OpenSearch Service 사용해보기  (0) 2023.08.23
[AWS] AWS IoT SiteWise 실습  (0) 2023.01.27
[AWS] AWS IoT Greengrass V2 실습  (0) 2023.01.26
[AWS] Firebase를 AWS Amplify로  (0) 2023.01.04
[AWS] re:Invent 2022 정리  (0) 2022.12.19

+ Recent posts