AWS CloudWatch를 통한 프로세스 모니터링 및 재시작 적용

Devvoo·2024년 5월 22일
0

AWS

목록 보기
1/4
post-thumbnail

Amazon EC2 인스턴스에 통합 CloudWatch Agent와 SSM Agent 설치

IAM 역할

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

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

CloudWatch Agent

  1. CloudWatch Agent 다운로드 (EC2 인스턴스 내에서)
sudo apt-get install amazon-cloudwatch-agent

Amazon SNS 주제 생성

위와 같은 네이밍으로 표준 형태로 생성해준다. (down, up)

AWS Lambda 함수 생성 및 코드 작성

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 기반으로 다운 받아야 함!

CloudWatch 알람 생성

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 주제로 해당 알림을 전송하도록 연결했다.

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

AWS Lambda 트리거 추가

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

테스트 결과

위 이미지에 표시한 것과 같이 특정 인스턴스의 besu 프로세스가 모두 다운되어 1분 동안 프로세스의 실행 수의 합계가 0인 경우 ‘경보 상태’가 되어 CloudWatch 알람이 발생하게 된다.

또 Lambda 코드에 정의한 Slack 채널로 알람에 대한 메시지가 전송되며, 프로세스를 재기동하고, 상태가 정상으로 변경된 경우에도 아래와 같이 메시지가 전송된다.

profile
Cloud & DevOps Engineer; 루트 노드를 향하여!

0개의 댓글