Wetty 웹 터미널 완벽 가이드 - RunPod 등 클라우드 플랫폼 통합 전략
⏱️ 예상 읽기 시간: 22분
서론
클라우드 컴퓨팅 시대에서 원격 서버 관리는 필수적인 업무가 되었습니다. 하지만 전통적인 SSH 클라이언트는 로컬 설치가 필요하고, 방화벽 제한이나 네트워크 환경에 따라 접근이 어려울 수 있습니다.
Wetty는 이러한 문제를 해결하는 혁신적인 솔루션입니다. 웹 브라우저를 통해 완전한 터미널 환경을 제공하는 이 도구는 4.8k GitHub Stars와 717 Forks를 기록하며 많은 개발자들의 신뢰를 받고 있습니다.
이 가이드에서는 Wetty의 기본 개념부터 RunPod, AWS, Docker 등 다양한 클라우드 환경에서의 실전 배포까지 상세히 다루겠습니다.
Wetty란 무엇인가?
핵심 개념과 장점
Wetty는 “Web TTY”의 줄임말로, 웹 브라우저를 통해 터미널에 접근할 수 있게 해주는 Node.js 기반 애플리케이션입니다.
주요 특징:
- 브라우저 기반: 별도 클라이언트 설치 없이 웹 브라우저만으로 터미널 사용
- xterm.js 기반: 현대적이고 강력한 터미널 에뮬레이터
- HTTP/HTTPS 지원: 안전한 암호화 통신 가능
- MIT 라이선스: 상업적 사용 포함 자유로운 활용
기존 솔루션과의 차별점
기능 | SSH 클라이언트 | Ajaxterm/Anyterm | Wetty |
---|---|---|---|
설치 필요성 | 클라이언트 설치 필요 | 웹 기반 | 웹 기반 |
성능 | 높음 | 낮음 | 높음 |
현대적 UI | 클라이언트 의존 | 기본적 | 현대적 |
기능 완성도 | 완전 | 제한적 | 완전 |
보안 | SSH 프로토콜 | HTTP | HTTPS/WSS |
기술적 아키텍처
구성 요소:
- Node.js 서버: WebSocket 연결 관리 및 터미널 프로세스 중계
- xterm.js: 브라우저에서 터미널 UI 렌더링
- node-pty: 가상 터미널(PTY) 생성 및 관리
- WebSocket: 실시간 양방향 통신
기본 설치 및 설정
로컬 환경 설치
1. 시스템 요구사항
# Node.js 16+ 필수
node --version # v16.0.0 이상
# npm 또는 yarn 패키지 매니저
npm --version
2. NPM을 통한 글로벌 설치
# 글로벌 설치
npm install -g wetty
# 기본 실행 (포트 3000)
wetty
# 커스텀 포트 실행
wetty --port 8080
3. 소스코드에서 빌드
# 저장소 클론
git clone https://github.com/butlerx/wetty.git
cd wetty
# 의존성 설치
npm install
# 빌드
npm run build
# 실행
npm start
Docker를 활용한 설치
기본 Docker 실행
# 공식 이미지 사용
docker run -d \
--name wetty \
-p 3000:3000 \
wettyoss/wetty
# 브라우저에서 http://localhost:3000 접속
고급 Docker 설정
# 환경변수와 함께 실행
docker run -d \
--name wetty-advanced \
-p 8080:3000 \
-e WETTY_PORT=3000 \
-e WETTY_HOST=0.0.0.0 \
-e WETTY_TITLE="My Web Terminal" \
wettyoss/wetty \
--ssh-host=your-server.com \
--ssh-port=22
Docker Compose 설정
docker-compose.yml 예시
version: '3.8'
services:
wetty:
image: wettyoss/wetty:latest
container_name: wetty-terminal
ports:
- "3000:3000"
environment:
- WETTY_PORT=3000
- WETTY_HOST=0.0.0.0
- WETTY_TITLE=Cloud Terminal
command: ["--ssh-host=localhost", "--ssh-port=22"]
restart: unless-stopped
# 옵션: Nginx 리버스 프록시
nginx:
image: nginx:alpine
container_name: wetty-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- wetty
restart: unless-stopped
RunPod 클라우드 플랫폼 통합
RunPod 환경 이해
RunPod은 GPU 중심의 클라우드 컴퓨팅 플랫폼으로, AI/ML 개발자들에게 인기가 높습니다. Wetty를 RunPod에 통합하면 강력한 웹 기반 개발 환경을 구축할 수 있습니다.
RunPod 환경에서의 Wetty 구축
1. RunPod 인스턴스 생성
# RunPod 대시보드에서 인스턴스 생성 후 SSH 접속
ssh root@your-runpod-instance.com
# 시스템 업데이트
apt update && apt upgrade -y
# Node.js 설치
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
apt-get install -y nodejs
# 빌드 도구 설치
apt-get install -y build-essential python3
2. Wetty 설치 및 설정
# 글로벌 설치
npm install -g wetty
# 서비스 디렉토리 생성
mkdir -p /opt/wetty
cd /opt/wetty
# 설정 파일 생성
cat > wetty-config.json << EOF
{
"port": 3000,
"host": "0.0.0.0",
"title": "RunPod Terminal",
"allowIframe": false,
"forceSSH": false
}
EOF
3. SSL 인증서 설정
# Let's Encrypt 설치
apt-get install -y certbot
# SSL 인증서 발급 (도메인이 있는 경우)
certbot certonly --standalone -d your-domain.com
# 자체 서명 인증서 생성 (개발용)
openssl req -x509 -newkey rsa:4096 -keyout /opt/wetty/key.pem \
-out /opt/wetty/cert.pem -days 365 -nodes \
-subj "/C=US/ST=State/L=City/O=Organization/OU=OrgUnit/CN=localhost"
4. 시스템 서비스 등록
# systemd 서비스 파일 생성
cat > /etc/systemd/system/wetty.service << EOF
[Unit]
Description=Wetty Web Terminal
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/wetty
ExecStart=/usr/bin/wetty --port 3000 --host 0.0.0.0 --title "RunPod Terminal" --ssl-key /opt/wetty/key.pem --ssl-cert /opt/wetty/cert.pem
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# 서비스 활성화
systemctl daemon-reload
systemctl enable wetty
systemctl start wetty
# 상태 확인
systemctl status wetty
RunPod 특화 설정
1. GPU 액세스 확인
# nvidia-smi 명령어 테스트용 스크립트
cat > /opt/wetty/gpu-check.sh << EOF
#!/bin/bash
echo "=== GPU Information ==="
nvidia-smi
echo ""
echo "=== CUDA Version ==="
nvcc --version
echo ""
echo "=== PyTorch GPU Test ==="
python3 -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}'); print(f'GPU count: {torch.cuda.device_count()}')"
EOF
chmod +x /opt/wetty/gpu-check.sh
2. RunPod 환경 변수 설정
# 환경 설정 파일
cat > /opt/wetty/runpod-env.sh << EOF
#!/bin/bash
export RUNPOD_POD_ID=\${RUNPOD_POD_ID:-"unknown"}
export RUNPOD_API_KEY=\${RUNPOD_API_KEY:-""}
export CUDA_VISIBLE_DEVICES=\${CUDA_VISIBLE_DEVICES:-"0"}
# 프롬프트 커스터마이징
export PS1="\[\033[01;32m\]RunPod:\[\033[01;34m\]\w\[\033[00m\]$ "
echo "RunPod Environment Initialized"
echo "Pod ID: \$RUNPOD_POD_ID"
echo "Available GPUs: \$(nvidia-smi -L | wc -l)"
EOF
# bashrc에 추가
echo "source /opt/wetty/runpod-env.sh" >> /root/.bashrc
AWS 클라우드 환경 통합
EC2 인스턴스 설정
1. EC2 인스턴스 생성 및 기본 설정
# Amazon Linux 2 기준
sudo yum update -y
# Node.js 18 설치
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 18
nvm use 18
# 필수 패키지 설치
sudo yum install -y gcc-c++ make git
2. Application Load Balancer 연동
# ALB Target Group 설정 예시
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: wetty-targets
Port: 3000
Protocol: HTTP
VpcId: !Ref VPC
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckIntervalSeconds: 30
HealthyThresholdCount: 2
UnhealthyThresholdCount: 5
3. CloudFormation 템플릿
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Wetty Web Terminal on AWS'
Parameters:
InstanceType:
Type: String
Default: t3.micro
Description: EC2 instance type
Resources:
WettyInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0abcdef1234567890 # Amazon Linux 2
InstanceType: !Ref InstanceType
KeyName: your-key-pair
SecurityGroupIds:
- !Ref WettySecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source /home/ec2-user/.bashrc
nvm install 18
npm install -g wetty
wetty --port 3000 --host 0.0.0.0
WettySecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for Wetty
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3000
ToPort: 3000
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
ECS Fargate 배포
1. ECS Task Definition
{
"family": "wetty-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::account:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "wetty",
"image": "wettyoss/wetty:latest",
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{
"name": "WETTY_PORT",
"value": "3000"
},
{
"name": "WETTY_HOST",
"value": "0.0.0.0"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/wetty",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
2. ECS Service 설정
# ECS CLI를 사용한 서비스 생성
aws ecs create-service \
--cluster wetty-cluster \
--service-name wetty-service \
--task-definition wetty-task:1 \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-12345,subnet-67890],securityGroups=[sg-12345],assignPublicIp=ENABLED}"
Google Cloud Platform 통합
GKE (Google Kubernetes Engine) 배포
1. Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: wetty-deployment
labels:
app: wetty
spec:
replicas: 3
selector:
matchLabels:
app: wetty
template:
metadata:
labels:
app: wetty
spec:
containers:
- name: wetty
image: wettyoss/wetty:latest
ports:
- containerPort: 3000
env:
- name: WETTY_PORT
value: "3000"
- name: WETTY_HOST
value: "0.0.0.0"
- name: WETTY_TITLE
value: "GKE Terminal"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
name: wetty-service
spec:
selector:
app: wetty
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
2. Helm Chart 배포
# Helm 레포지토리 추가 (커뮤니티 차트 사용 시)
helm repo add stable https://charts.helm.sh/stable
helm repo update
# 커스텀 values.yaml
cat > wetty-values.yaml << EOF
replicaCount: 2
image:
repository: wettyoss/wetty
tag: latest
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
targetPort: 3000
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "gce"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: wetty.yourdomain.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: wetty-tls
hosts:
- wetty.yourdomain.com
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
EOF
# Helm으로 배포
helm install wetty ./wetty-chart -f wetty-values.yaml
고급 설정 및 커스터마이징
SSL/TLS 보안 설정
1. Let’s Encrypt 자동 갱신
# certbot 자동 갱신 스크립트
cat > /opt/wetty/ssl-renew.sh << EOF
#!/bin/bash
certbot renew --quiet
systemctl reload wetty
EOF
# crontab 등록
echo "0 2 * * 0 /opt/wetty/ssl-renew.sh" | crontab -
2. 고급 SSL 설정
// wetty-ssl-config.js
const wetty = require('wetty');
const fs = require('fs');
const options = {
port: 443,
host: '0.0.0.0',
sslkey: fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/privkey.pem'),
sslcert: fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/fullchain.pem'),
sshHost: 'localhost',
sshPort: 22,
title: 'Secure Web Terminal',
allowIframe: false
};
wetty(options);
인증 및 권한 관리
1. OAuth2 통합 (예: Google OAuth)
// oauth-wetty.js
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const wetty = require('wetty');
const app = express();
// 세션 설정
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true
}));
// Passport 설정
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback"
}, (accessToken, refreshToken, profile, done) => {
// 사용자 인증 로직
return done(null, profile);
}));
app.use(passport.initialize());
app.use(passport.session());
// 인증 라우트
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/terminal');
}
);
// 인증 미들웨어
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/auth/google');
}
// Wetty 연동
app.use('/terminal', ensureAuthenticated, wetty());
2. JWT 기반 인증
// jwt-auth.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const JWT_SECRET = process.env.JWT_SECRET || 'your-jwt-secret';
// 사용자 로그인
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
// 사용자 인증 (예: 데이터베이스 확인)
const user = await authenticateUser(username, password);
if (user) {
const token = jwt.sign(
{ userId: user.id, username: user.username },
JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({ token });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
// JWT 검증 미들웨어
function verifyToken(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = decoded;
next();
});
}
다중 사용자 환경 구성
1. 사용자별 컨테이너 격리
# Docker 기반 사용자 격리 스크립트
#!/bin/bash
# user-container.sh
USER_ID=$1
USER_NAME=$2
# 사용자별 컨테이너 생성
docker run -d \
--name "wetty-user-${USER_ID}" \
--network wetty-network \
-e WETTY_USER="${USER_NAME}" \
-e WETTY_PORT=3000 \
-v "/home/${USER_NAME}:/home/user" \
wettyoss/wetty:latest
# 로드 밸런서에 등록
echo "User ${USER_NAME} container started on user-${USER_ID}"
2. Nginx 리버스 프록시 설정
# /etc/nginx/sites-available/wetty
upstream wetty_backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 80;
server_name wetty.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name wetty.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# SSL 보안 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
location / {
proxy_pass http://wetty_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 타임아웃 설정
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}
보안 모범 사례
네트워크 보안
1. 방화벽 설정
# UFW (Ubuntu Firewall) 설정
ufw enable
ufw default deny incoming
ufw default allow outgoing
# SSH 포트 (필요한 경우만)
ufw allow 22/tcp
# Wetty HTTPS 포트
ufw allow 443/tcp
# HTTP to HTTPS 리다이렉트용
ufw allow 80/tcp
# 상태 확인
ufw status verbose
2. Fail2ban 설정
# Fail2ban 설치
apt-get install -y fail2ban
# Wetty용 설정 파일
cat > /etc/fail2ban/filter.d/wetty.conf << EOF
[Definition]
failregex = ^.*"ip":"<HOST>".*"level":"error".*authentication failed.*$
ignoreregex =
EOF
# jail 설정
cat > /etc/fail2ban/jail.d/wetty.conf << EOF
[wetty]
enabled = true
port = http,https
filter = wetty
logpath = /var/log/wetty/wetty.log
maxretry = 5
bantime = 3600
findtime = 600
EOF
systemctl restart fail2ban
접근 제어
1. IP 화이트리스트
// ip-whitelist.js
const express = require('express');
const app = express();
const allowedIPs = [
'192.168.1.0/24', // 로컬 네트워크
'10.0.0.0/8', // 내부 네트워크
'203.0.113.0/24' // 허용된 외부 IP 대역
];
function ipFilter(req, res, next) {
const clientIP = req.ip || req.connection.remoteAddress;
const isAllowed = allowedIPs.some(range => {
return ipInRange(clientIP, range);
});
if (isAllowed) {
next();
} else {
res.status(403).json({ error: 'Access denied from your IP' });
}
}
app.use(ipFilter);
2. 시간 기반 접근 제어
// time-based-access.js
function timeBasedAccess(req, res, next) {
const now = new Date();
const hour = now.getHours();
const day = now.getDay(); // 0 = Sunday, 6 = Saturday
// 업무 시간만 허용 (월-금, 9-18시)
if (day >= 1 && day <= 5 && hour >= 9 && hour < 18) {
next();
} else {
res.status(403).json({
error: 'Access denied outside business hours',
currentTime: now.toISOString()
});
}
}
로깅 및 모니터링
1. 상세 로깅 설정
// advanced-logging.js
const winston = require('winston');
const path = require('path');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'wetty' },
transports: [
new winston.transports.File({
filename: '/var/log/wetty/error.log',
level: 'error'
}),
new winston.transports.File({
filename: '/var/log/wetty/combined.log'
}),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
// 연결 로깅
function logConnection(req, res, next) {
logger.info('New connection', {
ip: req.ip,
userAgent: req.get('User-Agent'),
timestamp: new Date().toISOString()
});
next();
}
// 명령어 실행 로깅
function logCommand(command, user, ip) {
logger.info('Command executed', {
command: command,
user: user,
ip: ip,
timestamp: new Date().toISOString()
});
}
2. Prometheus 메트릭 수집
// prometheus-metrics.js
const client = require('prom-client');
// 기본 메트릭 수집
const register = new client.Registry();
client.collectDefaultMetrics({ register });
// 커스텀 메트릭
const connectionsTotal = new client.Counter({
name: 'wetty_connections_total',
help: 'Total number of connections',
labelNames: ['status']
});
const activeConnections = new client.Gauge({
name: 'wetty_active_connections',
help: 'Number of active connections'
});
const commandsExecuted = new client.Counter({
name: 'wetty_commands_total',
help: 'Total number of commands executed',
labelNames: ['user']
});
register.registerMetric(connectionsTotal);
register.registerMetric(activeConnections);
register.registerMetric(commandsExecuted);
// 메트릭 엔드포인트
app.get('/metrics', (req, res) => {
res.set('Content-Type', register.contentType);
res.end(register.metrics());
});
성능 최적화
리소스 최적화
1. Node.js 클러스터링
// cluster-wetty.js
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// CPU 수만큼 워커 생성
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // 워커 재시작
});
} else {
// 워커 프로세스에서 Wetty 실행
const wetty = require('wetty');
wetty({
port: process.env.PORT || 3000,
host: '0.0.0.0'
});
console.log(`Worker ${process.pid} started`);
}
2. Redis 세션 클러스터링
// redis-session.js
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const redisClient = redis.createClient({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD
});
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24시간
}
}));
캐싱 전략
1. Nginx 캐싱 설정
# 정적 자원 캐싱
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
gzip_static on;
}
# 압축 설정
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/json
application/xml+rss;
2. CDN 통합
// cdn-config.js
const express = require('express');
const app = express();
// 정적 자원을 CDN에서 제공
app.locals.cdnUrl = process.env.CDN_URL || '';
// 템플릿에서 사용
// <script src="<%= cdnUrl %>/js/wetty.bundle.js"></script>
모니터링 및 운영
헬스 체크
1. 종합 헬스 체크 엔드포인트
// health-check.js
const express = require('express');
const app = express();
app.get('/health', async (req, res) => {
const healthCheck = {
uptime: process.uptime(),
message: 'OK',
timestamp: Date.now(),
checks: {
memory: checkMemory(),
cpu: await checkCPU(),
websocket: checkWebSocket(),
ssh: await checkSSH()
}
};
const isHealthy = Object.values(healthCheck.checks)
.every(check => check.status === 'healthy');
res.status(isHealthy ? 200 : 503).json(healthCheck);
});
function checkMemory() {
const used = process.memoryUsage();
const memoryUsagePercent = (used.heapUsed / used.heapTotal) * 100;
return {
status: memoryUsagePercent < 80 ? 'healthy' : 'unhealthy',
usage: `${memoryUsagePercent.toFixed(2)}%`,
details: used
};
}
async function checkCPU() {
// CPU 사용률 측정 로직
return {
status: 'healthy',
usage: '15%'
};
}
function checkWebSocket() {
// WebSocket 연결 상태 확인
return {
status: 'healthy',
activeConnections: getActiveConnectionCount()
};
}
async function checkSSH() {
// SSH 연결 테스트
try {
// SSH 연결 테스트 로직
return { status: 'healthy' };
} catch (error) {
return {
status: 'unhealthy',
error: error.message
};
}
}
2. Kubernetes Liveness/Readiness Probes
# k8s-probes.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wetty
spec:
template:
spec:
containers:
- name: wetty
image: wettyoss/wetty:latest
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
로그 집계 및 분석
1. ELK Stack 통합
// elk-logging.js
const winston = require('winston');
const ElasticsearchTransport = require('winston-elasticsearch');
const logger = winston.createLogger({
transports: [
new ElasticsearchTransport({
level: 'info',
clientOpts: {
host: process.env.ELASTICSEARCH_HOST || 'localhost:9200'
},
index: 'wetty-logs'
})
]
});
// 구조화된 로깅
function logUserSession(userId, action, metadata = {}) {
logger.info({
event: 'user_session',
userId: userId,
action: action,
timestamp: new Date().toISOString(),
metadata: metadata
});
}
2. Grafana 대시보드 설정
{
"dashboard": {
"title": "Wetty Monitoring Dashboard",
"panels": [
{
"title": "Active Connections",
"type": "graph",
"targets": [
{
"expr": "wetty_active_connections",
"legendFormat": "Active Connections"
}
]
},
{
"title": "Response Time",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, wetty_request_duration_seconds_bucket)",
"legendFormat": "95th percentile"
}
]
}
]
}
}
트러블슈팅 가이드
일반적인 문제들
1. WebSocket 연결 실패
# 원인 진단
netstat -tlnp | grep :3000
curl -I http://localhost:3000
# 방화벽 확인
iptables -L -n
ufw status
# 프록시 설정 확인
nginx -t
nginx -s reload
2. 높은 메모리 사용량
# 메모리 사용량 모니터링
top -p $(pgrep -f wetty)
ps aux | grep wetty
# Node.js 힙 덤프 생성
kill -USR2 $(pgrep -f wetty)
# 메모리 누수 분석
node --inspect wetty.js
3. SSL 인증서 문제
# 인증서 유효성 검사
openssl x509 -in /path/to/cert.pem -text -noout
openssl verify /path/to/cert.pem
# 인증서 만료일 확인
openssl x509 -in /path/to/cert.pem -noout -dates
# Let's Encrypt 갱신
certbot renew --dry-run
디버깅 도구
1. 연결 상태 모니터링
// connection-monitor.js
const WebSocket = require('ws');
function monitorConnections() {
const connections = new Map();
// 연결 추적
function trackConnection(ws, req) {
const id = generateConnectionId();
const info = {
id: id,
ip: req.ip,
userAgent: req.headers['user-agent'],
connectedAt: new Date(),
lastActivity: new Date()
};
connections.set(id, info);
ws.on('close', () => {
connections.delete(id);
});
ws.on('message', () => {
if (connections.has(id)) {
connections.get(id).lastActivity = new Date();
}
});
}
// 상태 리포트
function getConnectionReport() {
return {
total: connections.size,
connections: Array.from(connections.values())
};
}
return { trackConnection, getConnectionReport };
}
2. 성능 프로파일링
// performance-profiler.js
const v8Profiler = require('v8-profiler-next');
function startProfiling() {
const title = `profile-${Date.now()}`;
v8Profiler.startProfiling(title, true);
setTimeout(() => {
const profile = v8Profiler.stopProfiling(title);
profile.export()
.pipe(fs.createWriteStream(`${title}.cpuprofile`))
.on('finish', () => {
profile.delete();
console.log(`Profile saved: ${title}.cpuprofile`);
});
}, 30000); // 30초 후 프로파일링 종료
}
결론
Wetty는 현대적인 웹 기반 터미널 솔루션으로, 클라우드 시대에 적합한 강력한 도구입니다. 이 가이드에서 다룬 내용을 통해 다양한 클라우드 환경에서 안전하고 효율적인 웹 터미널 서비스를 구축할 수 있습니다.
핵심 포인트 요약
- 다양한 배포 옵션: Docker, Kubernetes, AWS, GCP 등 모든 주요 플랫폼 지원
- 강력한 보안: SSL/TLS, 인증, 접근 제어, 로깅을 통한 종합 보안
- 확장성: 클러스터링, 로드 밸런싱, 캐싱을 통한 대규모 서비스 지원
- 모니터링: 헬스 체크, 메트릭 수집, 로그 분석으로 안정적 운영
권장 아키텍처
개발 환경:
- Docker Compose를 활용한 간편 배포
- 자체 서명 인증서로 HTTPS 테스트
- 기본 인증으로 빠른 프로토타이핑
프로덕션 환경:
- Kubernetes 오케스트레이션
- Let’s Encrypt 자동 SSL 갱신
- OAuth2/SAML 기업 인증 연동
- Prometheus/Grafana 모니터링 스택
미래 발전 방향
Wetty 생태계는 지속적으로 발전하고 있습니다:
- 컨테이너 네이티브: Kubernetes 운영자 패턴 지원
- AI 통합: 터미널 명령어 자동 완성 및 추천
- 협업 기능: 다중 사용자 터미널 세션 공유
- 성능 개선: WebAssembly 기반 터미널 에뮬레이션
Wetty를 활용하여 현대적이고 안전한 웹 터미널 환경을 구축하고, 클라우드 네이티브 시대의 개발 및 운영 효율성을 크게 향상시켜보시기 바랍니다.
참고 자료:
- 🌐 공식 저장소: github.com/butlerx/wetty
- 📖 공식 문서: butlerx.github.io/wetty
- 🐳 Docker Hub: wettyoss/wetty
- 📦 NPM 패키지: npmjs.com/package/wetty