⏱️ 예상 읽기 시간: 22분

서론

클라우드 컴퓨팅 시대에서 원격 서버 관리는 필수적인 업무가 되었습니다. 하지만 전통적인 SSH 클라이언트는 로컬 설치가 필요하고, 방화벽 제한이나 네트워크 환경에 따라 접근이 어려울 수 있습니다.

Wetty는 이러한 문제를 해결하는 혁신적인 솔루션입니다. 웹 브라우저를 통해 완전한 터미널 환경을 제공하는 이 도구는 4.8k GitHub Stars717 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

기술적 아키텍처

구성 요소:

  1. Node.js 서버: WebSocket 연결 관리 및 터미널 프로세스 중계
  2. xterm.js: 브라우저에서 터미널 UI 렌더링
  3. node-pty: 가상 터미널(PTY) 생성 및 관리
  4. 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는 현대적인 웹 기반 터미널 솔루션으로, 클라우드 시대에 적합한 강력한 도구입니다. 이 가이드에서 다룬 내용을 통해 다양한 클라우드 환경에서 안전하고 효율적인 웹 터미널 서비스를 구축할 수 있습니다.

핵심 포인트 요약

  1. 다양한 배포 옵션: Docker, Kubernetes, AWS, GCP 등 모든 주요 플랫폼 지원
  2. 강력한 보안: SSL/TLS, 인증, 접근 제어, 로깅을 통한 종합 보안
  3. 확장성: 클러스터링, 로드 밸런싱, 캐싱을 통한 대규모 서비스 지원
  4. 모니터링: 헬스 체크, 메트릭 수집, 로그 분석으로 안정적 운영

권장 아키텍처

개발 환경:

  • Docker Compose를 활용한 간편 배포
  • 자체 서명 인증서로 HTTPS 테스트
  • 기본 인증으로 빠른 프로토타이핑

프로덕션 환경:

  • Kubernetes 오케스트레이션
  • Let’s Encrypt 자동 SSL 갱신
  • OAuth2/SAML 기업 인증 연동
  • Prometheus/Grafana 모니터링 스택

미래 발전 방향

Wetty 생태계는 지속적으로 발전하고 있습니다:

  • 컨테이너 네이티브: Kubernetes 운영자 패턴 지원
  • AI 통합: 터미널 명령어 자동 완성 및 추천
  • 협업 기능: 다중 사용자 터미널 세션 공유
  • 성능 개선: WebAssembly 기반 터미널 에뮬레이션

Wetty를 활용하여 현대적이고 안전한 웹 터미널 환경을 구축하고, 클라우드 네이티브 시대의 개발 및 운영 효율성을 크게 향상시켜보시기 바랍니다.


참고 자료: