먼저 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 채널로 알람에 대한 메시지가 전송되며, 프로세스를 재기동하고, 상태가 정상으로 변경된 경우에도 아래와 같이 메시지가 전송된다.