Post

[Architecture] 멀티 언어 Agent 시스템 설계 - Go, PowerShell, Node.js 조합

Linux Agent는 Go, Windows Agent는 PowerShell, Collector는 Node.js를 선택한 이유와 실전 경험

시스템 구조

대규모 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, 시스템 도구
PowerShellWindows 통합, 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.js300-500ms낮음✅ 우수
Python400-800ms중간✅ 우수
Go200-400ms낮음⚠️ 보통
Java2-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회)

언어실행 시간메모리 사용바이너리 크기
Go2.1초15MB5MB
PowerShell8.5초80MBN/A (스크립트)
Python5.3초45MBN/A
Node.js4.2초35MBN/A

HTTP 요청 처리 (10,000회)

언어처리량 (req/s)평균 지연시간CPU 사용률
Go12,5000.8ms40%
Node.js8,0001.2ms60%
Python3,5002.8ms75%
PowerShell1,2008.3ms85%

트레이드오프

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 프로토콜로 언어 간 통신을 표준화하고, 각 언어의 장단점을 이해하여 적재적소에 활용하는 것이 핵심입니다.

“모든 문제를 못으로만 해결하려 하지 말라. 때로는 나사가 필요하다.” - 아브라함 매슬로

도움이 되셨길 바랍니다! 😀

This post is licensed under CC BY 4.0 by the author.