먼저 EC2 인스턴스에서 CloudWatch Agent를 실행하기 위해서는 EC2 인스턴스의 IAM 역할에 정책 CloudWatchAgentServerPolicy에 대한 권한이 필요하다. 추가로 Agent가 CloudWatch Logs에 로그를 전송하고 Agent가 이러한 로그 그룹에 대한 보존 정책을 설정할 수 있도록 하려면 IAM 역할에 logs:PutRetentionPolicy 권한을 부여해야한다.

우리는 EC2에 연결된 x라는 역할에 CloudWatchFullAccess 권한을 주었다.
(역할 이름은 Naming 하기 나름이라 x라는 임의의 이름을 명시함)
sudo apt-get install amazon-cloudwatch-agent

위와 같은 네이밍으로 표준 형태로 생성해준다. (down, up)
Lambda 함수 생성 시 함수에 대한 권한을 정의하는 IAM 역할이 필요하다. 기본적으로 Lambda는 Amazon CloudWatch Logs에 로그를 업로드할 수 있는 권한을 가진 실행 역할을 생성하며, 이 기본 역할은 나중에 트리거를 추가할 때 사용자 지정이 가능하다.
우리는 lambda-fullaccess로 역할을 지정해주었다.

lambda-fullaccess 역할에는 위와 같이 권한 정책들이 세팅 되어있지만,
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-2:[Accound ID]:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-2:[Account ID]:log-group:/aws/lambda/[lambda Name]:*"
            ]
        }
    ]
}
필요한 정책 권한은 위와 같다는 것을 참고하자.

위 이미지와 같이 x-monitoring이라는 이름으로 Python 3.9 기반의 Lambda 함수를 생성하였다.
그리고 생성한 함수에 코드를 아래와 같이 정의해주고, Deploy 했는데,
import json
import boto3
import requests
def lambda_handler(event, context):
    # TODO implement
    
    message = event['Records'][0]['Sns']['Message']
    message = json.loads(message)
    servicename = message['AlarmDescription']
    topic_arn = event['Records'][0]['Sns']['TopicArn']
    
    for dimension in message['Trigger']['Dimensions']:
        if dimension['name'] == 'InstanceId':
            instanceid = dimension['value']
    
    print("instance id:", instanceid)
    print("service name:", servicename)
    
    if "down" in topic_arn:
        # Node Down시 슬랙 발송
        command = "sudo systemctl restart [프로세스명].service"
        ssm_client = boto3.client('ssm')
        response = ssm_client.send_command(
            InstanceIds=[instanceid],
            DocumentName="AWS-RunShellScript",
            Parameters={
                'commands': [command]
            },
        )
        # 슬랙 메시지 전송
        payload = {
            "channel": "[Slack 채널명]",
            "username": "[채널에서 사용할 Slack bot 이름]",
            "text": servicename + " 노드가 중지 되었습니다. 노드를 기동하겠습니다.",
            "icon_emoji": ":robot_face:",
            "attachments": [
                {
                    "color": "#FF0000",
                    "fields": [
                        {
                            "title": "재기동 진행중",
                            "value": "대상: " + servicename,
                        }
                    ]
                }
            ]
        }
    elif "up" in topic_arn:
        # Node Up시 슬랙 발송
        payload = {
            "channel": "[Slack 채널명]",
            "username": "[채널에서 사용할 Slack bot 이름]",
            "text": servicename + " 노드가 기동 되었습니다.",
            "icon_emoji": ":robot_face:",
            "attachments": [
                {
                    "color": "#00FF00",
                    "fields": [
                        {
                            "title": "재기동 완료",
                            "value": "대상: " + servicename,
                        }
                    ]
                }
            ]
        }
    webhook_url = "https://hooks.slack.com/services/[비공개]/[비공개]/[비공개]"
    requests.post(
        webhook_url, data=json.dumps(payload),
        headers={'Content-Type': 'application/json'},
    )
    return {
        'statusCode': 200
    }
처음에 아래와 같이 Lambda 함수 내 코드를 정의하고 Deploy해서 실행되게 한 후,
import json
def lambda_handler(event, context):
    # 전체 이벤트 데이터 출력
    print(json.dumps(event))
    return {
        "statusCode": 200,
        "body": json.dumps("SNS event data printed")
    }
위와 같이 AWS CloudWatch 콘솔에서 쿼리를 실행하여
필드	                           값
@ingestionTime	               1692339271137
@log	                       [AWSAccountId]:/aws/lambda/x-monitoring
@logStream                     2023/08/18/[$LATEST]6dbc7bd76f844dd397d06b7625c79fa1
@message                       {"Records": [{"EventSource": "aws:sns", "EventVersion": "1.0", "EventSubscriptionArn": "arn:aws:sns:ap-northeast-2:[AWSAccountId]:[SNS Topic name(down)]:[비공개]", "Sns": {"Type": "Notification", "MessageId": "[비공개]", "TopicArn": "arn:aws:sns:ap-northeast-2:[AWSAccountId]:[SNS Topic name(down)]", "Subject": "ALARM: \"[경보 이름]\" in Asia Pacific (Seoul)", "Message": "{\"AlarmName\":\"[경보 이름]\",\"AlarmDescription\":\"**## [메시지 내용]\uac00 \uc911\uc9c0 \ub418\uc5c8\uc2b5\ub2c8\ub2e4.**\",\"AWSAccountId\":\"[비공개]\",\"AlarmConfigurationUpdatedTimestamp\":\"2023-08-18T06:04:23.696+0000\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [0.0 (18/08/23 06:13:00)] was less than or equal to the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"2023-08-18T06:14:29.779+0000\",\"Region\":\"Asia Pacific (Seoul)\",\"AlarmArn\":\"arn:aws:cloudwatch:ap-northeast-2:[AWSAccountId]:alarm:[경보 이름]\",\"OldStateValue\":\"OK\",\"OKActions\":[\"arn:aws:sns:ap-northeast-2:[AWSAccountId]:[SNS Topic name(up)]\"],\"AlarmActions\":[\"arn:aws:sns:ap-northeast-2:[AWSAccountId]:[SNS Topic name(up)]\"],\"InsufficientDataActions\":[],\"Trigger\":{\"MetricName\":\"procstat_lookup_pid_count\",\"Namespace\":\"CWAgent\",\"StatisticType\":\"Statistic\",\"Statistic\":\"SUM\",\"Unit\":null,\"Dimensions\":[{\"value\":\"[비공개]\",\"name\":\"InstanceId\"},{\"value\":\"[비공개]\",\"name\":\"pattern\"},{\"value\":\"native\",\"name\":\"pid_finder\"}],\"Period\":60,\"EvaluationPeriods\":1,\"DatapointsToAlarm\":1,\"ComparisonOperator\":\"LessThanOrEqualToThreshold\",\"Threshold\":0.0,\"TreatMissingData\":\"missing\",\"EvaluateLowSampleCountPercentile\":\"\"}}", "Timestamp": "2023-08-18T06:14:29.821Z", "SignatureVersion": "1", "Signature": "[비공개]", "SigningCertUrl": "https://sns.ap-northeast-2.amazonaws.com/SimpleNotificationService-[비공개].pem", "UnsubscribeUrl": "https://sns.ap-northeast-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-2:[AWSAccountId]:x-monitoring:[비공개]", "MessageAttributes": {}}}]}
@timestamp                     1692339270275
Records.0.EventSource          aws:sns
Records.0.EventSubscriptionArn arn:aws:sns:ap-northeast-2:[AWSAccountId]:[SNS Topic name]:[비공개]
Records.0.EventVersion	       1.0
Records.0.Sns.Message	       {"AlarmName":"[경보 이름]","AlarmDescription":"**## [메시지 내용]**","AWSAccountId":"[비공개]","AlarmConfigurationUpdatedTimestamp":"2023-08-18T06:04:23.696+0000","NewStateValue":"ALARM","NewStateReason":"Threshold Crossed: 1 out of the last 1 datapoints [0.0 (18/08/23 06:13:00)] was less than or equal to the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).","StateChangeTime":"2023-08-18T06:14:29.779+0000","Region":"Asia Pacific (Seoul)","AlarmArn":"arn:aws:cloudwatch:ap-northeast-2:[AWSAccountId]:alarm:[경보 이름]","OldStateValue":"OK","OKActions":["arn:aws:sns:ap-northeast-2:[AWSAccountId]:[SNS Topic name(up)]"],"AlarmActions":["arn:aws:sns:ap-northeast-2:[AWSAccountId]:[SNS Topic name(down)]"],"InsufficientDataActions":[],"Trigger":{"MetricName":"procstat_lookup_pid_count","Namespace":"CWAgent","StatisticType":"Statistic","Statistic":"SUM","Unit":null,"Dimensions":[{"value":"[비공개]","name":"InstanceId"},{"value":"[비공개]","name":"pattern"},{"value":"native","name":"pid_finder"}],"Period":60,"EvaluationPeriods":1,"DatapointsToAlarm":1,"ComparisonOperator":"LessThanOrEqualToThreshold","Threshold":0.0,"TreatMissingData":"missing","EvaluateLowSampleCountPercentile":""}}
Records.0.Sns.MessageId	       [비공개]
Records.0.Sns.Signature	       [비공개]
Records.0.Sns.SignatureVersion 1
Records.0.Sns.SigningCertUrl   https://sns.ap-northeast-2.amazonaws.com/SimpleNotificationService-[비공개].pem
Records.0.Sns.Subject	       ALARM: "[경보 이름]" in Asia Pacific (Seoul)
Records.0.Sns.Timestamp	       2023-08-18T06:14:29.821Z
Records.0.Sns.TopicArn	       arn:aws:sns:ap-northeast-2:[AWSAccountId]:[SNS Topic name(down)]
Records.0.Sns.Type	           Notification
Records.0.Sns.UnsubscribeUrl   https://sns.ap-northeast-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-2:[비공개]:[SNS Topic name(down)]:[비공개]
위와 같은 형태로 찍힌 로그 중 Records 데이터 값을 바탕으로 코드를 작성해주었던 것이다.
(안맞는 내용이 있을 수도 있으니 형태가 이렇다는 것 정도만 참고)
그리고, requests 라이브러리를 import 하기 위해

lambda 계층도 위와 같이 지정해주었는데,
pip로 로컬에서 request 라이브러리를 다운받고, zip으로 묶은 후 업로드 버튼으로 업로드 해주고, 생성한다.
pip3 install requests==2.28.2 -t python
똑같이 Python 3.9 기반으로 다운 받아야 함!
EC2 기반 노드 내에서 /opt/aws/amazon-cloudwatch-agent/bin/config.json 파일을 아래와 같이 작성해준다.
{
    "agent": {
        "metrics_collection_interval": 60,
        "run_as_user": "root"
    },
    "metrics": {
        "append_dimensions": {
            "InstanceId": "${aws:InstanceId}"
        },
        "metrics_collected": {
            "procstat": [
                {
                    "pattern": "org.hyperledger.besu.Besu", # 모니터링할 프로세스 정의
                    "measurement": [
                        "pid_count"
                    ]
                }
            ]
        }
    }
}
그리고, AWS CloudWatch 콘솔에서 1분 동안 특정 인스턴스의 프로세스의 실행 수의 합계가 0보다 작거나 같을 때, 자동으로 알람이 발생하도록 지표 및 조건을 지정하여 경보를 생성한다.



CloudWatch 알람이 발생한 경우 및 프로세스가 정상 상태로 변경된 경우 위에서 생성한 SNS 주제로 해당 알림을 전송하도록 연결했다.

아래와 같이 생성이 완료되었다.



위와 같이 먼저 생성했던 SNS 주제들로 트리거를 추가해주었다.

위 이미지에 표시한 것과 같이 특정 인스턴스의 besu 프로세스가 모두 다운되어 1분 동안 프로세스의 실행 수의 합계가 0인 경우 ‘경보 상태’가 되어 CloudWatch 알람이 발생하게 된다.
또 Lambda 코드에 정의한 Slack 채널로 알람에 대한 메시지가 전송되며, 프로세스를 재기동하고, 상태가 정상으로 변경된 경우에도 아래와 같이 메시지가 전송된다.
