headscale-tailscale-replace.../PROXMOX_WEBSOCKET_INTEGRATION_PLAN.md
시골약사 8bd6b1f400 Add one-click installation script for Headscale clients
- Complete automated script for Tailscale + Headscale registration
- Support for Ubuntu, Debian, CentOS, RHEL, Rocky, Fedora, Arch
- Universal binary fallback for unsupported distros
- Automatic firewall configuration
- Network connectivity verification
- Fix Headscale health check (nc -> headscale version)
- Add comprehensive error handling and colored output

🚀 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 00:30:47 +09:00

12 KiB

Proxmox VNC WebSocket 직접 연결 해결 방안

🎯 목표

Flask 웹 애플리케이션에서 Proxmox VNC WebSocket에 직접 연결하여 브라우저 내에서 seamless한 VNC 경험 제공

🔍 현재 문제 상황

1. 문제 증상

  • Flask에서 VNC 버튼 클릭 시 "Unsupported"... is not valid JSON 오류 발생
  • noVNC 클라이언트가 Proxmox WebSocket 서버에 연결 실패
  • HTTP 501 응답 (nginx에서 WebSocket 요청 거부)

2. 근본 원인 분석

[브라우저] --> [Flask:5002] --> [Proxmox:443] --> [실제 WebSocket:5900]
           ❌ CORS 이슈        ❌ nginx 차단      ✅ VM VNC 서버

주요 차단 요인:

  • Proxmox nginx가 외부 WebSocket 연결 차단
  • CORS (Cross-Origin Resource Sharing) 정책
  • SSL/TLS 인증서 불일치
  • WebSocket Upgrade 헤더 처리 문제

💡 해결 방안 (3가지 접근법)

방안 1: Flask WebSocket 프록시 서버 (권장)

개념도

[브라우저] <--WebSocket--> [Flask Proxy] <--WebSocket--> [Proxmox VNC]
          ws://localhost:5002/ws/vnc/vm123    wss://pve7:443/api2/...

구현 방법

1.1 Flask-SocketIO 기반 프록시

# requirements.txt에 추가
flask-socketio==5.3.6
python-socketio==5.8.0
websockets==11.0.3

# app.py 수정
from flask_socketio import SocketIO, emit
import websockets
import asyncio
import ssl

socketio = SocketIO(app, cors_allowed_origins="*")

@socketio.on('vnc_connect')
def handle_vnc_connect(data):
    """VNC WebSocket 프록시 연결"""
    vm_id = data['vm_id']
    node = data['node']
    
    # Proxmox VNC 티켓 생성
    client = ProxmoxClient(PROXMOX_HOST, PROXMOX_USERNAME, PROXMOX_PASSWORD)
    vnc_data = client.get_vnc_ticket(node, vm_id)
    
    if vnc_data:
        # 백그라운드에서 프록시 실행
        socketio.start_background_task(proxy_vnc_connection, 
                                     request.sid, 
                                     vnc_data['websocket_url'])

def proxy_vnc_connection(session_id, proxmox_ws_url):
    """Proxmox VNC WebSocket과 브라우저 간 프록시"""
    asyncio.run(proxy_websocket(session_id, proxmox_ws_url))

async def proxy_websocket(session_id, proxmox_ws_url):
    """비동기 WebSocket 프록시"""
    try:
        # SSL 컨텍스트 설정 (인증서 검증 무시)
        ssl_context = ssl.create_default_context()
        ssl_context.check_hostname = False
        ssl_context.verify_mode = ssl.CERT_NONE
        
        # Proxmox WebSocket 연결
        async with websockets.connect(
            proxmox_ws_url, 
            ssl=ssl_context,
            extra_headers={'Origin': f'https://{PROXMOX_HOST}'}
        ) as proxmox_ws:
            
            @socketio.on('vnc_data', namespace=f'/vnc/{session_id}')
            def forward_to_proxmox(data):
                asyncio.create_task(proxmox_ws.send(data))
            
            # Proxmox → 브라우저
            async for message from proxmox_ws:
                socketio.emit('vnc_data', message, 
                            room=session_id, 
                            namespace=f'/vnc/{session_id}')
                
    except Exception as e:
        socketio.emit('vnc_error', {'error': str(e)}, room=session_id)

1.2 noVNC 클라이언트 수정

<!-- templates/vnc_console.html -->
<script src="/static/js/socket.io.min.js"></script>
<script>
// Flask-SocketIO 연결
const socket = io();

// VNC 연결 설정
socket.emit('vnc_connect', {
    vm_id: {{ vmid }},
    node: '{{ node }}'
});

// noVNC WebSocket URL을 Flask 프록시로 변경
const websocketUrl = `ws://localhost:5002/socket.io/`;

// RFB 연결
const rfb = new RFB(canvas, websocketUrl, {
    wsProtocols: ['base64']
});

// 데이터 중계 설정
socket.on('vnc_data', function(data) {
    // Proxmox에서 온 데이터를 noVNC로 전달
    rfb._sock.rQshiftBytes(data.length);
});

rfb.addEventListener('connect', function() {
    // noVNC에서 보낸 데이터를 Proxmox로 전달
    rfb._sock.on('message', function(data) {
        socket.emit('vnc_data', data);
    });
});
</script>

장점

  • 완전한 제어 가능
  • CORS 문제 해결
  • Flask 애플리케이션 내 통합
  • 추가 인증/로깅 가능

단점

  • 구현 복잡성 높음
  • 성능 오버헤드 존재
  • WebSocket 프로토콜 깊이 이해 필요

방안 2: Nginx 리버스 프록시 설정

개념도

[브라우저] --> [Nginx Proxy] --> [Proxmox WebSocket]
          ws://proxy/vnc/vm123    wss://pve7:443/api2/...

구현 방법

2.1 별도 Nginx 프록시 서버 설정

# /etc/nginx/sites-available/proxmox-vnc-proxy
server {
    listen 8080;
    server_name localhost;
    
    # WebSocket 프록시 설정
    location /vnc/ {
        # URL 경로에서 VM 정보 추출: /vnc/pve7/100
        # pve7 = node, 100 = vmid
        rewrite ^/vnc/([^/]+)/(\d+)$ /api2/json/nodes/$1/qemu/$2/vncwebsocket break;
        
        proxy_pass https://pve7.0bin.in:443;
        proxy_http_version 1.1;
        
        # WebSocket 업그레이드 헤더
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host pve7.0bin.in;
        proxy_set_header Origin https://pve7.0bin.in;
        
        # 인증 헤더 전달 (VNC 티켓 포함)
        proxy_set_header Authorization $http_authorization;
        
        # SSL 설정
        proxy_ssl_verify off;
        proxy_ssl_server_name on;
        
        # 타임아웃 설정 (VNC 세션용)
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 86400s; # 24시간
        
        # 버퍼링 비활성화 (실시간 데이터용)
        proxy_buffering off;
        proxy_cache off;
    }
    
    # CORS 헤더 추가
    location / {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
        add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization";
        
        if ($request_method = 'OPTIONS') {
            return 204;
        }
    }
}

2.2 Docker Compose로 Nginx 프록시 실행

# docker-compose.vnc-proxy.yml
version: '3.8'
services:
  vnc-proxy:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx-vnc-proxy.conf:/etc/nginx/conf.d/default.conf
    restart: unless-stopped
    networks:
      - farmq-network

networks:
  farmq-network:
    external: true

2.3 Flask 코드 수정

# VNC WebSocket URL을 프록시 서버로 변경
@app.route('/api/vm/vnc', methods=['POST'])
def api_vm_vnc():
    # ... (기존 코드)
    
    # 프록시 서버를 통한 WebSocket URL 생성
    proxy_ws_url = f"ws://localhost:8080/vnc/{node}/{vmid}?port={vnc_data['port']}&vncticket={vnc_data['ticket']}"
    
    vnc_sessions[session_id] = {
        'websocket_url': proxy_ws_url,
        # ...
    }

장점

  • Flask 코드 변경 최소화
  • 높은 성능 (Nginx 네이티브)
  • 확장성 우수
  • SSL 종료 지원

단점

  • 추가 인프라 필요
  • Nginx 설정 복잡성
  • 디버깅 어려움

방안 3: Proxmox 서버 설정 변경

개념도

[브라우저] ----직접연결----> [Proxmox WebSocket (수정됨)]
          wss://pve7:443/api2/json/...

구현 방법

3.1 Proxmox Nginx 설정 수정

# Proxmox 서버에 SSH 접속
ssh root@pve7.0bin.in

# nginx 설정 백업
cp /etc/nginx/sites-available/proxmox /etc/nginx/sites-available/proxmox.backup

# nginx 설정 편집
nano /etc/nginx/sites-available/proxmox
# /etc/nginx/sites-available/proxmox 수정
server {
    listen 443 ssl http2;
    server_name pve7.0bin.in;
    
    # ... 기존 설정 유지 ...
    
    # CORS 헤더 추가 (VNC WebSocket용)
    location /api2/json/nodes/*/qemu/*/vncwebsocket {
        # CORS 헤더
        add_header Access-Control-Allow-Origin "http://localhost:5002";
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
        add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization";
        add_header Access-Control-Allow-Credentials true;
        
        # Preflight 요청 처리
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        
        # 기존 proxy_pass 설정 유지
        proxy_pass http://localhost:8006;
        proxy_http_version 1.1;
        
        # WebSocket 업그레이드 헤더
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_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;
        
        # 타임아웃 설정
        proxy_connect_timeout 60s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        
        # 버퍼링 비활성화
        proxy_buffering off;
        proxy_cache off;
    }
    
    # ... 나머지 설정 ...
}

3.2 Proxmox nginx 재시작

# 설정 검증
nginx -t

# nginx 재시작
systemctl restart nginx

# 방화벽 확인 (필요시)
iptables -L -n | grep 8006

3.3 Flask noVNC 클라이언트 수정

// 직접 Proxmox WebSocket 연결
const websocketUrl = '{{ websocket_url }}'; // wss://pve7.0bin.in:443/api2/json/...

// CORS 설정으로 직접 연결 가능
const rfb = new RFB(canvas, websocketUrl, {
    credentials: { password: '' }, // VNC 티켓으로 인증
    shared: true
});

장점

  • 가장 직접적인 해결책
  • 최고 성능
  • 추가 인프라 불필요

단점

  • Proxmox 서버 수정 필요
  • 업데이트 시 설정 초기화 위험
  • 보안 정책 변경

🛠️ 구현 우선순위 및 권장사항

1단계: Flask WebSocket 프록시 (권장)

기간: 2-3일 난이도: 중상

# 구현 단계
1. Flask-SocketIO 설치 및 설정
2. VNC WebSocket 프록시 함수 구현  
3. noVNC 클라이언트 수정
4. 테스트 및 디버깅

2단계: Nginx 프록시 (백업 방안)

기간: 1-2일
난이도: 중

# 구현 단계
1. nginx 프록시 설정 작성
2. Docker Compose로 프록시 실행
3. Flask WebSocket URL 수정
4. 연결 테스트

3단계: Proxmox 설정 변경 (최후 수단)

기간: 1일 난이도: 하 (위험도: 상)

# 주의사항
- Proxmox 서버 직접 수정
- 백업 필수
- 업데이트 시 재설정 필요

🔧 즉시 구현 가능한 임시 해결책

현재 리다이렉트 방식을 개선하여 사용성 향상:

# app.py - 자동 팝업 + 인증 정보 전달
@app.route('/vnc/<session_id>')  
def vnc_console(session_id):
    # Proxmox 자동 로그인 URL 생성
    client = ProxmoxClient(PROXMOX_HOST, PROXMOX_USERNAME, PROXMOX_PASSWORD)
    login_ticket = client.ticket
    
    proxmox_url = f"https://{PROXMOX_HOST}:443/?console=kvm&vmid={vmid}&node={node}&PVEAuthCookie={login_ticket}"
    
    return render_template('vnc_auto_redirect.html', 
                         proxmox_url=proxmox_url,
                         auto_open=True)

📋 구현 체크리스트

Flask WebSocket 프록시 구현

  • Flask-SocketIO 설치 및 기본 설정
  • Proxmox WebSocket 연결 테스트
  • 비동기 프록시 함수 구현
  • noVNC 클라이언트 SocketIO 연동
  • 에러 처리 및 재연결 로직
  • 성능 테스트 및 최적화

테스트 시나리오

  • 단일 VM VNC 연결 테스트
  • 동시 다중 VNC 연결 테스트
  • 네트워크 끊김 시 재연결 테스트
  • 브라우저별 호환성 테스트
  • 모바일 디바이스 테스트

운영 준비

  • 로깅 및 모니터링 설정
  • 에러 알림 시스템 구축
  • 백업 연결 방법 준비
  • 사용자 매뉴얼 작성

💡 결론

권장 접근법: Flask WebSocket 프록시 (방안 1)

  1. 완전한 제어: Flask 애플리케이션 내에서 모든 것을 제어
  2. 확장성: 향후 추가 기능 (녹화, 공유 등) 쉽게 추가 가능
  3. 안정성: Proxmox 서버 수정 없이 구현
  4. 통합성: 기존 Flask 인증/권한 시스템과 완벽 통합

다음 단계: Flask-SocketIO 기반 WebSocket 프록시 구현 시작