시스템 구조
대규모 Agent가 데이터를 수집해서 Serverless Collector로 전송하는 시스템을 설계했습니다. 핵심은 각 컴포넌트에 최적의 언어를 선택하는 것이었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| ┌──────────────────────────────────────────────────────────┐
│ Agent (대규모) │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ Linux Agent │ │ Windows Agent │ │
│ │ (Go) │ │ (PowerShell) │ │
│ │ │ │ │ │
│ │ - Binary 배포 │ │ - Native Windows │ │
│ │ - 성능 │ │ - WMI/Registry │ │
│ │ - systemd 통합 │ │ - Scheduled Task │ │
│ └────────────────────┘ └────────────────────┘ │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Collector (Serverless) │
│ (Node.js Lambda) │
│ │
│ - Serverless 환경 최적화 │
│ - AWS SDK v3 활용 │
│ - 빠른 개발 주기 │
│ - SQS/S3/DynamoDB 통합 │
└──────────────────────────────────────────────────────────┘
|
언어 선택 기준
| 언어 | 강점 | 약점 | 적합한 용도 |
|---|
| Go | 바이너리 배포, 성능, 동시성 | 생태계 작음, 빌드 필요 | CLI, Agent, 시스템 도구 |
| PowerShell | Windows 통합, WMI, 스크립트 | Windows 전용, 성능 낮음 | Windows 관리, 자동화 |
| Node.js | 빠른 개발, AWS 통합, 비동기 | 싱글 스레드, 타입 안정성 | Serverless, API, 웹 |
| Python | 범용성, 라이브러리, 쉬움 | 느림, 배포 복잡 | 데이터 분석, 스크립트 |
| Rust | 성능, 안전성, 바이너리 | 러닝 커브 높음 | 성능 critical 시스템 |
Linux Agent: Go를 선택한 이유
1. 단일 바이너리 배포
1
2
3
4
5
6
7
8
| # Go: 단일 바이너리 파일
./agent # 5MB
# Python: 의존성 패키지 필요
python3 agent.py
├── requirements.txt
├── venv/ (50MB+)
└── 의존성 관리 복잡
|
2. 크로스 컴파일
1
2
3
4
5
6
7
8
| # Makefile: 여러 플랫폼 빌드
build-linux-amd64:
GOOS=linux GOARCH=amd64 go build -o build/agent-amd64 cmd/agent/main.go
build-linux-arm64:
GOOS=linux GOARCH=arm64 go build -o build/agent-arm64 cmd/agent/main.go
build-all: build-linux-amd64 build-linux-arm64
|
한 머신에서 모든 플랫폼용 바이너리를 빌드할 수 있습니다.
3. 시스템 정보 수집 성능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // Go: 네이티브 시스템 콜
package collector
import (
"os/exec"
"syscall"
)
func GetCPUInfo() (CPUInfo, error) {
// /proc/cpuinfo 직접 읽기
data, err := os.ReadFile("/proc/cpuinfo")
// ...
}
func GetMemoryInfo() (MemoryInfo, error) {
var si syscall.Sysinfo_t
err := syscall.Sysinfo(&si)
// ...
}
|
4. 프로젝트 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| agent-linux/
├── cmd/
│ ├── agent/main.go # 메인 Agent 데몬
│ ├── collect-data/main.go # 데이터 수집 (cron)
│ ├── collect-performance/main.go # 성능 수집 (cron)
│ ├── security-scan/main.go # 보안 스캔 (cron)
│ └── fota/main.go # FOTA 업데이트
├── internal/
│ ├── config/config.go # 설정 로드
│ ├── collector/
│ │ ├── system.go # 시스템 정보
│ │ └── software.go # 소프트웨어 정보
│ ├── performance/collector.go # 성능 메트릭
│ ├── security/scanner.go # 보안 스캐너
│ ├── sender/http.go # HTTP 전송
│ ├── fota/updater.go # FOTA 업데이트
│ └── logger/logger.go # 로깅
├── Makefile # 빌드 자동화
└── install.sh # 설치 스크립트
|
5. Systemd 통합
1
2
3
4
5
6
| # install.sh
sudo cp ./build/agent /usr/local/bin/agent
sudo cp ./systemd/agent.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now agent
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| # agent.service
[Unit]
Description=System Agent for Linux
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/agent
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
|
Windows Agent: PowerShell을 선택한 이유
1. Windows 네이티브 통합
1
2
3
4
5
6
7
8
9
10
| # WMI를 통한 시스템 정보 수집
$osInfo = Get-WmiObject -Class Win32_OperatingSystem
$cpuInfo = Get-WmiObject -Class Win32_Processor
$diskInfo = Get-WmiObject -Class Win32_LogicalDisk
# Registry 접근
$regValue = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
# Windows 서비스 관리
Get-Service | Where-Object Status -eq 'Running'
|
Go나 Python으로도 가능하지만, PowerShell이 훨씬 간결하고 직관적입니다.
2. 설치 없이 실행
1
2
3
4
5
| # PowerShell은 Windows에 기본 설치됨
powershell.exe -ExecutionPolicy Bypass -File .\agent.ps1
# Python은 별도 설치 필요
python.exe agent.py # Python 설치 필요!
|
3. Scheduled Task 통합
1
2
3
4
5
6
7
8
| # PowerShell로 Scheduled Task 생성
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-File C:\Agent\Invoke-CollectData.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "00:00"
Register-ScheduledTask -TaskName "Agent-DataCollection" `
-Action $action -Trigger $trigger -RunLevel Highest
|
4. 프로젝트 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| agent-windows/
├── agent.ps1 # 메인 Agent 스크립트
├── Invoke-CollectData.ps1 # 데이터 수집
├── Invoke-CollectPerformance.ps1 # 성능 수집
├── Invoke-SecurityScan.ps1 # 보안 스캔
├── Invoke-FOTAUpdate.ps1 # FOTA 업데이트
├── modules/
│ ├── Logger.psm1 # 로깅 모듈
│ ├── Security.psm1 # 보안 모듈
│ └── Collector.psm1 # 수집 모듈
├── config/
│ └── agent-config.json # 설정 파일
└── install/
├── Install-Agent.ps1 # 설치 스크립트
└── Uninstall-Agent.ps1 # 제거 스크립트
|
5. 패키지 관리 정보
1
2
3
4
5
6
7
8
9
10
| # Windows Update 정보
Get-HotFix | Select-Object Description, HotFixID, InstalledOn
# Chocolatey 패키지
if (Get-Command choco -ErrorAction SilentlyContinue) {
choco list --local-only
}
# Windows Features
Get-WindowsOptionalFeature -Online | Where-Object State -eq 'Enabled'
|
Collector: Node.js를 선택한 이유
1. Serverless 환경 최적화
1
2
3
4
5
6
7
8
9
10
| // Lambda Handler (Node.js)
exports.handler = async (event, context) => {
// AWS SDK v3 네이티브 지원
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
// 비동기 처리 최적화
await Promise.all(
event.Records.map(record => processRecord(record))
);
};
|
AWS Lambda의 공식 런타임 지원과 Cold Start 성능이 Node.js가 가장 우수합니다.
| 언어 | Cold Start | 메모리 | 생태계 |
|---|
| Node.js | 300-500ms | 낮음 | ✅ 우수 |
| Python | 400-800ms | 중간 | ✅ 우수 |
| Go | 200-400ms | 낮음 | ⚠️ 보통 |
| Java | 2-5초 | 높음 | ⚠️ 무거움 |
2. AWS SDK 통합
1
2
3
4
5
| const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb');
const { CloudWatchClient, PutMetricDataCommand } = require('@aws-sdk/client-cloudwatch');
// 모든 AWS 서비스를 간편하게 통합
|
3. 빠른 개발 주기
1
2
3
4
5
6
7
8
| # 로컬 테스트
npm install
node scripts/run-poc.js
# 배포
serverless deploy
# 또는
npm run deploy
|
Go는 컴파일이 필요하고, Python은 의존성 관리가 복잡합니다. Node.js는 즉시 실행 가능합니다.
4. 프로젝트 구조
1
2
3
4
5
6
7
8
9
| collector-serverless/
├── src/
│ ├── validator/handler.js # 검증 Lambda
│ ├── processor/handler.js # 처리 Lambda
│ ├── fota/handler.js # FOTA API Lambda
│ └── health/handler.js # Health Check
├── serverless.yml # Serverless Framework 설정
├── package.json # 의존성
└── README.md
|
공통 기능 구현 비교
JSON 직렬화/역직렬화
Go
1
2
3
4
5
6
7
8
9
10
11
| type Payload struct {
AgentID string `json:"agent_id"`
Data Data `json:"data"`
}
// 직렬화
data, err := json.Marshal(payload)
// 역직렬화
var payload Payload
err := json.Unmarshal(data, &payload)
|
PowerShell
1
2
3
4
5
6
7
8
9
10
| $payload = @{
agent_id = "agent-001"
data = @{ ... }
}
# 직렬화
$json = $payload | ConvertTo-Json -Depth 10
# 역직렬화
$payload = $json | ConvertFrom-Json
|
Node.js
1
2
3
4
5
6
7
8
9
10
| const payload = {
agent_id: "agent-001",
data: { ... }
};
// 직렬화
const json = JSON.stringify(payload);
// 역직렬화
const payload = JSON.parse(json);
|
Gzip 압축
Go
1
2
3
4
5
6
7
8
9
10
11
| import (
"bytes"
"compress/gzip"
)
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
gz.Write(jsonData)
gz.Close()
compressedData := buf.Bytes()
|
PowerShell
1
2
3
4
5
6
7
| $stream = New-Object System.IO.MemoryStream
$gzip = New-Object System.IO.Compression.GzipStream($stream, [System.IO.Compression.CompressionMode]::Compress)
$writer = New-Object System.IO.StreamWriter($gzip)
$writer.Write($jsonData)
$writer.Close()
$compressedData = $stream.ToArray()
|
Node.js
1
2
3
| const zlib = require('zlib');
const compressedData = zlib.gzipSync(JSON.stringify(data));
|
HTTP 요청
Go
1
2
3
4
5
6
| req, _ := http.NewRequest("POST", url, bytes.NewReader(data))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Content-Encoding", "gzip")
req.Header.Set("X-API-Key", apiKey)
resp, err := http.DefaultClient.Do(req)
|
PowerShell
1
2
3
4
5
6
7
8
| $headers = @{
'Content-Type' = 'application/json'
'Content-Encoding' = 'gzip'
'X-API-Key' = $apiKey
}
$response = Invoke-RestMethod -Uri $url -Method POST `
-Headers $headers -Body $compressedData
|
Node.js
1
2
3
4
5
6
7
8
9
| const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Encoding': 'gzip',
'X-API-Key': apiKey
},
body: compressedData
});
|
성능 비교
시스템 정보 수집 (1,000회)
| 언어 | 실행 시간 | 메모리 사용 | 바이너리 크기 |
|---|
| Go | 2.1초 | 15MB | 5MB |
| PowerShell | 8.5초 | 80MB | N/A (스크립트) |
| Python | 5.3초 | 45MB | N/A |
| Node.js | 4.2초 | 35MB | N/A |
HTTP 요청 처리 (10,000회)
| 언어 | 처리량 (req/s) | 평균 지연시간 | CPU 사용률 |
|---|
| Go | 12,500 | 0.8ms | 40% |
| Node.js | 8,000 | 1.2ms | 60% |
| Python | 3,500 | 2.8ms | 75% |
| PowerShell | 1,200 | 8.3ms | 85% |
트레이드오프
Go
| 장점 | 단점 |
|---|
| ✅ 바이너리 배포 | ❌ 빌드 시간 |
| ✅ 성능 우수 | ❌ 생태계 작음 |
| ✅ 동시성 우수 | ❌ 제네릭 제한적 |
| ✅ 타입 안정성 | ❌ 에러 처리 장황 |
PowerShell
| 장점 | 단점 |
|---|
| ✅ Windows 통합 | ❌ Windows 전용 |
| ✅ 학습 곡선 낮음 | ❌ 성능 낮음 |
| ✅ 설치 불필요 | ❌ 디버깅 어려움 |
| ✅ WMI/Registry | ❌ 타입 안정성 없음 |
Node.js
| 장점 | 단점 |
|---|
| ✅ 빠른 개발 | ❌ 싱글 스레드 |
| ✅ AWS 통합 우수 | ❌ 타입 안정성 (JS) |
| ✅ 비동기 우수 | ❌ 의존성 관리 |
| ✅ Serverless 최적화 | ❌ 바이너리 배포 불가 |
실전 팁
1. 각 언어의 강점 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Linux Agent (Go):
- 시스템 정보 수집
- 파일 스캔
- 바이너리 배포
Windows Agent (PowerShell):
- WMI 쿼리
- Registry 접근
- Windows 서비스 관리
Collector (Node.js):
- API Gateway 통합
- SQS/S3/DynamoDB 처리
- 비동기 데이터 처리
|
2. 공통 프로토콜 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 모든 Agent가 동일한 JSON 스키마 사용
{
"schema_version": "v2",
"agent_metadata": {
"agent_id": "...",
"agent_version": "...",
"os_type": "linux|windows",
"group_name": "..."
},
"timestamp": "2025-12-02T00:00:00.000Z",
"system_info": { ... },
"performance": { ... }
}
|
3. 에러 처리 통일
1
2
3
4
5
| // Go
if err != nil {
logger.Error(fmt.Sprintf("Failed to collect data: %v", err))
return err
}
|
1
2
3
4
5
6
7
| # PowerShell
try {
# ...
} catch {
Write-Error "Failed to collect data: $_"
exit 1
}
|
1
2
3
4
5
6
7
| // Node.js
try {
// ...
} catch (error) {
console.error('Failed to collect data:', error);
throw error
}
|
마치며
멀티 언어 시스템 설계에서 중요한 것은 각 컴포넌트에 최적의 언어를 선택하는 것입니다.
Linux Agent는 Go의 바이너리 배포와 성능을, Windows Agent는 PowerShell의 Windows 네이티브 통합을, Collector는 Node.js의 Serverless 최적화를 활용했습니다. 공통 JSON 프로토콜로 언어 간 통신을 표준화하고, 각 언어의 장단점을 이해하여 적재적소에 활용하는 것이 핵심입니다.
“모든 문제를 못으로만 해결하려 하지 말라. 때로는 나사가 필요하다.” - 아브라함 매슬로
도움이 되셨길 바랍니다! 😀