배경
운영 환경의 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
}exit 0→ UP (정상)exit 2→ DOWN (프로세스 미감지)
명확한 종료 코드를 반환하도록 래핑하여 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'}},
},
)알람 조건
- UP 상태일 때는 알람을 전송하지 않음
- DOWN이 연속 N회 이상일 때만 Slack/SMS/Email 발송
- SMS는 120자 이내로 truncate
Lambda 환경 변수
| 변수 | 설명 |
|---|---|
INSTANCE_IDS | 대상 Windows 인스턴스 ID (쉼표 구분) |
SLACK_WEBHOOK_URL | Slack Incoming Webhook URL |
SNS_TOPIC_ARN_SMS | SMS용 SNS Topic ARN (Tokyo Region) |
SES_FROM | 발신 이메일 주소 |
SES_TO_CSV | 수신 이메일 주소 (쉼표 구분) |
DDB_TABLE | DynamoDB 테이블 이름 |
FAIL_THRESHOLD | 연속 실패 임계치 (기본 2) |
SSM_TIMEOUT | SSM 명령 타임아웃 (기본 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 관련 엔드포인트 통신을 허용하여 해결했습니다.
정리
- SSM을 활용한 Windows 서버 Java 프로세스 원격 모니터링
- EventBridge 5분 주기 트리거 → Lambda → SSM PowerShell 실행
- DynamoDB로 연속 실패 카운트 관리, 오탐 방지
- Slack/SMS/Email 다중 알람 채널 구성
- Standby 서버 자동 실행 설정 (작업 스케줄러)
- UP 상태에서는 알람 미발송으로 알림 피로 최소화