← 목록

Windows 서버 프로세스 원격 모니터링 (SSM + Lambda + DynamoDB)

AWS SSM을 활용해 Windows 서버의 Java 프로세스 실행 상태를 원격으로 확인하고, 연속 실패 시 Slack/SMS/Email 다중 알람을 발송하는 서버리스 모니터링 구축

2024-10-28AWSMonitoringPythonAutomation

배경

운영 환경의 Windows FTP 서버에서 FileReceiver(Java 프로세스)가 실행 중이어야 파일 수신이 정상 동작한다. 그러나 서버 재부팅이나 프로세스 비정상 종료 시 FileReceiver가 내려간 상태로 방치될 수 있어, 이를 자동으로 감지하는 모니터링이 필요했다.

Windows 서버는 CloudWatch Agent만으로는 특정 Java 프로세스의 실행 여부를 확인할 수 없으므로, AWS Systems Manager(SSM)를 통한 원격 PowerShell 실행 방식을 채택했다.


아키텍처

EventBridge (5분 주기: rate(5 minutes))
    ↓
Lambda (Python)
    ├── SSM SendCommand → Windows 서버에 PowerShell 원격 실행
    ├── SSM GetCommandInvocation → 실행 결과 수집
    ├── DynamoDB → 직전 상태 저장 (연속 실패 카운트)
    │
    └── 연속 실패 N회 이상 시 알람 발송
        ├── Slack (Incoming Webhook)
        ├── SMS (SNS Topic, Tokyo Region)
        └── Email (SES SendEmail)

SSM 원격 PowerShell 실행

Windows 서버 사전 준비

대상 Windows 서버에 SSM Agent가 설치/등록되어 있어야 한다.

# SSM Agent 설치 확인
Get-Service AmazonSSMAgent

실행할 PowerShell 스크립트

javaw.exe 프로세스 중 FileReceiver.jar를 실행 중인 프로세스를 탐지한다.

$procs = Get-WmiObject Win32_Process -Filter "Name = 'javaw.exe'" |
         Select-Object ProcessId, CommandLine |
         Where-Object { $_.CommandLine -match "FileReceiver.jar" }
 
if ($procs) {
    Write-Output "UP: FileReceiver detected. PIDs: $($procs.ProcessId -join ', ')"
    exit 0
} else {
    Write-Output "DOWN: FileReceiver not detected."
    exit 2
}

명확한 종료 코드를 반환하도록 래핑하여 Lambda에서 판단할 수 있게 한다.


Lambda 핵심 로직

SSM 명령 실행 및 결과 수집

import boto3, time
 
ssm = boto3.client('ssm')
 
def check_instance(instance_id, command):
    # 1. SSM SendCommand로 PowerShell 원격 실행
    cmd = ssm.send_command(
        InstanceIds=[instance_id],
        DocumentName='AWS-RunPowerShellScript',
        Parameters={'commands': [command]},
        TimeoutSeconds=60,
    )
    command_id = cmd['Command']['CommandId']
 
    # 2. 결과 수집 (터미널 상태까지 폴링)
    inv = wait_for_terminal(command_id, instance_id)
 
    status = inv.get('Status')
    response_code = inv.get('ResponseCode')
    output = inv.get('StandardOutputContent', '')
 
    is_up = (response_code == 0 and 'UP:' in output)
    return is_up, status, response_code, output

폴링 (SSM 전파 지연 고려)

def wait_for_terminal(command_id, instance_id, max_wait=45):
    deadline = time.time() + max_wait
    while time.time() < deadline:
        try:
            inv = ssm.get_command_invocation(
                CommandId=command_id,
                InstanceId=instance_id,
            )
            if inv['Status'] in ('Success', 'Failed', 'TimedOut', 'Cancelled'):
                return inv
            time.sleep(1.0)
        except ssm.exceptions.InvocationDoesNotExist:
            time.sleep(1.0)  # SSM 전파 지연
    raise TimeoutError("SSM command timed out")

연속 실패 감지 (DynamoDB)

DynamoDB에 인스턴스별 직전 상태와 연속 실패 횟수를 저장한다.

ddb = boto3.client('dynamodb')
 
def update_state(table, instance_id, is_up):
    prev = ddb.get_item(
        TableName=table,
        Key={'host': {'S': instance_id}}
    ).get('Item', {})
 
    prev_fails = int(prev.get('fails', {}).get('N', '0'))
    fails = 0 if is_up else prev_fails + 1
 
    ddb.put_item(
        TableName=table,
        Item={
            'host': {'S': instance_id},
            'status': {'S': 'up' if is_up else 'down'},
            'fails': {'N': str(fails)},
            'ts': {'S': datetime.utcnow().isoformat() + 'Z'},
        }
    )
    return fails

연속 실패 N회(기본 2회) 이상일 때만 알람을 발송하여 일시적 오탐을 방지한다.


다중 알람 채널

Slack (Incoming Webhook)

def notify_slack(webhook_url, message):
    payload = json.dumps({"text": message}).encode()
    req = urllib.request.Request(
        webhook_url,
        data=payload,
        headers={'Content-Type': 'application/json'},
    )
    urllib.request.urlopen(req, timeout=5)

SMS (SNS - Tokyo Region)

def send_sms(topic_arn, message):
    sns = boto3.client('sns', region_name='ap-northeast-1')
    sns.publish(
        TopicArn=topic_arn,
        Message=message[:120],  # SMS 길이 제한
        MessageAttributes={
            'AWS.SNS.SMS.SMSType': {
                'DataType': 'String',
                'StringValue': 'Transactional',
            }
        },
    )

Email (SES)

def send_email(sender, recipients, subject, body):
    ses = boto3.client('ses')
    ses.send_email(
        Source=sender,
        Destination={'ToAddresses': recipients},
        Message={
            'Subject': {'Data': subject, 'Charset': 'UTF-8'},
            'Body': {'Text': {'Data': body, 'Charset': 'UTF-8'}},
        },
    )

알람 조건


Lambda 환경 변수

변수설명
INSTANCE_IDS대상 Windows 인스턴스 ID (쉼표 구분)
SLACK_WEBHOOK_URLSlack Incoming Webhook URL
SNS_TOPIC_ARN_SMSSMS용 SNS Topic ARN (Tokyo Region)
SES_FROM발신 이메일 주소
SES_TO_CSV수신 이메일 주소 (쉼표 구분)
DDB_TABLEDynamoDB 테이블 이름
FAIL_THRESHOLD연속 실패 임계치 (기본 2)
SSM_TIMEOUTSSM 명령 타임아웃 (기본 60초)
MAX_WAIT_SEC결과 폴링 최대 대기 (기본 45초)

IAM 역할

Lambda 실행 역할에 필요한 권한:

{
  "Effect": "Allow",
  "Action": [
    "ssm:SendCommand",
    "ssm:GetCommandInvocation",
    "dynamodb:GetItem",
    "dynamodb:PutItem",
    "ses:SendEmail",
    "sns:Publish",
    "logs:CreateLogGroup",
    "logs:CreateLogStream",
    "logs:PutLogEvents"
  ],
  "Resource": "*"
}

Standby 서버 자동 실행 설정

Active 서버뿐 아니라 Standby 서버에서도 재부팅 후 FileReceiver가 자동 실행되도록 Windows 작업 스케줄러를 설정한다.

수동 실행 확인

# 관리자 PowerShell
cd C:\java\FileReceiver
C:\java\jre1.8.0_161\bin\javaw.exe -jar FileReceiver.jar
 
# 프로세스 확인
Get-Process javaw | Where-Object { $_.Path -like "*jre1.8.0_161*" }

작업 스케줄러 자동 실행

작업 스케줄러 (taskschd.msc) > 작업 만들기

일반 탭:
  이름: FileReceiver
  "가장 높은 권한으로 실행" 체크
  "사용자 로그온 여부와 관계없이 실행" 선택

트리거 탭:
  "컴퓨터 시작 시" 선택

동작 탭:
  프로그램: C:\java\jre1.8.0_161\bin\javaw.exe
  인수: -jar FileReceiver.jar
  시작 위치: C:\java\FileReceiver

재부팅 후 자동 실행 확인:

Get-Process javaw | Where-Object { $_.Path -like "*jre1.8.0_161*" }

트러블슈팅

Standby 서버 원격 접속 불가

보안 소프트웨어(방화벽)가 SSM Agent의 아웃바운드 통신을 차단하는 경우가 있었다. 방화벽 설정에서 SSM 관련 엔드포인트 통신을 허용하여 해결했다.


정리


배운 점

모니터링에서 가장 어려운 대상은 복잡한 시스템이 아니라 표준 경로가 없는 시스템이다. Windows 서버의 Java 프로세스 하나를 보기 위해 SSM, Lambda, DynamoDB, EventBridge가 동원됐다. 에이전트를 심을 수 없는 환경에서는 "무엇으로 볼 것인가"보다 "어디까지 우회할 것인가"가 설계가 된다.

DynamoDB에 직전 상태를 저장한 이유도 결국 오탐 때문이었다. 한 번 실패했다고 알리면 재기동 중에도 알림이 울린다. 상태를 기억해야 연속 실패를 셀 수 있고, 연속 실패를 세야 알림이 신뢰를 얻는다. 같은 고민을 EDI Agent 중앙 모니터링 시스템 구축에서도 6회 연속 조건으로 풀었다.

SSM 통신이 보안 소프트웨어에 막혔던 일은 따로 기억에 남는다. 감시 도구 자체가 보안 정책에 걸려 조용히 죽을 수 있다는 뜻이라서, 그 뒤로는 감시 경로가 살아 있는지도 같이 확인하게 됐다.


이어 읽기