From a2f4c709c5eb6d2fa47a7a2239e666f6ec53abfa Mon Sep 17 00:00:00 2001 From: bright8192 Date: Tue, 1 Jul 2025 17:22:45 +0800 Subject: [PATCH] add docker support --- .dockerignore | 80 ++++++++++++ Dockerfile | 67 ++++++++++ Makefile | 114 +++++++++++++++++ README_DOCKER.md | 286 +++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 49 ++++++++ docker-entrypoint.sh | 93 ++++++++++++++ 6 files changed, 689 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README_DOCKER.md create mode 100644 docker-compose.yml create mode 100644 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..437e30b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,80 @@ +# Git related files +.git +.gitignore +.gitattributes + +# Python related +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual environments +venv/ +env/ +ENV/ +.venv/ + +# IDEs and editors +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Operating systems +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Log files +logs/ +*.log + +# Temporary files +tmp/ +temp/ +.tmp/ + +# Docker related +Dockerfile* +.dockerignore +docker-compose*.yml +Makefile + +# Documentation +*.md +docs/ + +# Configuration files (avoid including sensitive information) +config.yaml +*.yaml +!config.yaml.sample +config/ +.env +.env.* + +# Backup files +*.bak +*.backup \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f201b4b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,67 @@ +# Use official Python runtime as base image +FROM python:3.11-slim as builder + +# Set working directory +WORKDIR /app + +# Install system dependencies for building +RUN apt-get update && apt-get install -y \ + gcc \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements file +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir --user -r requirements.txt + +# Production stage +FROM python:3.11-slim + +# Create non-root user for security +RUN groupadd -r mcpuser && useradd -r -g mcpuser mcpuser + +# Set working directory +WORKDIR /app + +# Install only runtime system dependencies (including bash for entrypoint script) +RUN apt-get update && apt-get install -y \ + ca-certificates \ + bash \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Copy Python packages from builder stage +COPY --from=builder /root/.local /home/mcpuser/.local + +# Copy application code +COPY server.py . +COPY config.yaml.sample . +COPY docker-entrypoint.sh . + +# Make entrypoint script executable +RUN chmod +x docker-entrypoint.sh + +# Create necessary directories and set permissions +RUN mkdir -p /app/logs /app/config \ + && chown -R mcpuser:mcpuser /app + +# Set environment variables +ENV PATH=/home/mcpuser/.local/bin:$PATH +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 + +# Switch to non-root user +USER mcpuser + +# Expose port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080')" || exit 1 + +# Set entrypoint and default command +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["python", "server.py", "--config", "/app/config/config.yaml"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..60e6204 --- /dev/null +++ b/Makefile @@ -0,0 +1,114 @@ +# ESXi MCP Server Makefile +# Provides convenient commands for Docker operations + +# Variables +DOCKER_IMAGE = esxi-mcp-server +DOCKER_TAG = latest +CONTAINER_NAME = esxi-mcp-server +COMPOSE_FILE = docker-compose.yml + +# Default target +.PHONY: help +help: ## Show this help message + @echo "ESXi MCP Server Docker Commands" + @echo "================================" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +# Build commands +.PHONY: build +build: ## Build Docker image + docker build -t $(DOCKER_IMAGE):$(DOCKER_TAG) . + +.PHONY: build-no-cache +build-no-cache: ## Build Docker image without cache + docker build --no-cache -t $(DOCKER_IMAGE):$(DOCKER_TAG) . + +# Run commands +.PHONY: run +run: ## Run container with docker-compose + docker-compose -f $(COMPOSE_FILE) up -d + +.PHONY: run-logs +run-logs: ## Run container with docker-compose and show logs + docker-compose -f $(COMPOSE_FILE) up + +.PHONY: stop +stop: ## Stop running containers + docker-compose -f $(COMPOSE_FILE) down + +.PHONY: restart +restart: ## Restart containers + docker-compose -f $(COMPOSE_FILE) restart + +# Development commands +.PHONY: logs +logs: ## Show container logs + docker-compose -f $(COMPOSE_FILE) logs -f + +.PHONY: shell +shell: ## Open bash shell in running container + docker exec -it $(CONTAINER_NAME) bash + +.PHONY: status +status: ## Show container status + docker-compose -f $(COMPOSE_FILE) ps + +# Maintenance commands +.PHONY: clean +clean: ## Remove containers and volumes + docker-compose -f $(COMPOSE_FILE) down -v + docker rmi $(DOCKER_IMAGE):$(DOCKER_TAG) 2>/dev/null || true + +.PHONY: clean-all +clean-all: ## Remove everything including images and volumes + docker-compose -f $(COMPOSE_FILE) down -v --rmi all + docker system prune -f + +.PHONY: update +update: ## Update container (rebuild and restart) + make stop + make build + make run + +# Setup commands +.PHONY: setup +setup: ## Initial setup - create directories and sample config + mkdir -p logs config + cp config.yaml.sample config/config.yaml || true + @echo "Setup complete! Please edit config/config.yaml with your vCenter details." + +.PHONY: env-example +env-example: ## Create .env.example file + @echo "# VMware vCenter/ESXi Configuration" > .env.example + @echo "VCENTER_HOST=your-vcenter-ip-or-hostname" >> .env.example + @echo "VCENTER_USER=administrator@vsphere.local" >> .env.example + @echo "VCENTER_PASSWORD=your-password" >> .env.example + @echo "" >> .env.example + @echo "# Optional VMware Configuration" >> .env.example + @echo "VCENTER_DATACENTER=your-datacenter-name" >> .env.example + @echo "VCENTER_CLUSTER=your-cluster-name" >> .env.example + @echo "VCENTER_DATASTORE=your-datastore-name" >> .env.example + @echo "VCENTER_NETWORK=VM Network" >> .env.example + @echo "" >> .env.example + @echo "# Security Settings" >> .env.example + @echo "VCENTER_INSECURE=true" >> .env.example + @echo "MCP_API_KEY=your-api-key-here" >> .env.example + @echo "" >> .env.example + @echo "# Logging Configuration" >> .env.example + @echo "MCP_LOG_LEVEL=INFO" >> .env.example + @echo ".env.example file created!" + +# Health check +.PHONY: health +health: ## Check container health + docker exec $(CONTAINER_NAME) python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080')" && echo "✅ Health check passed" || echo "❌ Health check failed" + +# Quick start +.PHONY: quick-start +quick-start: ## Quick start with environment variables (requires .env file) + @echo "Starting with environment variables..." + @echo "Make sure you have created a .env file with your configuration!" + docker-compose -f $(COMPOSE_FILE) up -d + +.PHONY: dev +dev: build run-logs ## Development mode: build and run with logs \ No newline at end of file diff --git a/README_DOCKER.md b/README_DOCKER.md new file mode 100644 index 0000000..e706d18 --- /dev/null +++ b/README_DOCKER.md @@ -0,0 +1,286 @@ +# ESXi MCP Server - Docker Guide + +This guide provides instructions for running the ESXi MCP Server using Docker and Docker Compose. + +## Quick Start + +### Prerequisites + +- Docker 20.10+ +- Docker Compose 2.0+ +- Access to a VMware vCenter Server or ESXi host + +### 1. Setup + +```bash +# Clone the repository +git clone +cd esxi-mcp-server + +# Create necessary directories and configuration +make setup + +# Create environment variables file (optional) +make env-example +cp .env.example .env +``` + +### 2. Configuration + +You have two options for configuration: + +#### Option A: Configuration File (Recommended) + +Edit `config/config.yaml`: + +```yaml +vcenter_host: "your-vcenter-ip" +vcenter_user: "administrator@vsphere.local" +vcenter_password: "your-password" +datacenter: "your-datacenter" +cluster: "your-cluster" +datastore: "your-datastore" +network: "VM Network" +insecure: true +api_key: "your-api-key" +log_level: "INFO" +``` + +#### Option B: Environment Variables + +Edit `.env` file: + +```bash +VCENTER_HOST=your-vcenter-ip +VCENTER_USER=administrator@vsphere.local +VCENTER_PASSWORD=your-password +VCENTER_DATACENTER=your-datacenter +VCENTER_CLUSTER=your-cluster +VCENTER_DATASTORE=your-datastore +VCENTER_NETWORK=VM Network +VCENTER_INSECURE=true +MCP_API_KEY=your-api-key +MCP_LOG_LEVEL=INFO +``` + +### 3. Run the Server + +```bash +# Build and run +make dev + +# Or run in background +make run + +# Check status +make status + +# View logs +make logs +``` + +## Available Commands + +Use `make help` to see all available commands: + +```bash +make help +``` + +### Build Commands + +- `make build` - Build Docker image +- `make build-no-cache` - Build without cache + +### Run Commands + +- `make run` - Run in background +- `make run-logs` - Run with logs +- `make stop` - Stop containers +- `make restart` - Restart containers + +### Development Commands + +- `make dev` - Development mode (build + run with logs) +- `make logs` - Show logs +- `make shell` - Open bash shell in container +- `make status` - Show container status +- `make health` - Check container health + +### Maintenance Commands + +- `make clean` - Remove containers and volumes +- `make clean-all` - Remove everything +- `make update` - Rebuild and restart + +## Docker Architecture + +### Multi-stage Build + +The Dockerfile uses a multi-stage build process: + +1. **Builder Stage**: Installs build dependencies and Python packages +2. **Production Stage**: Creates a minimal runtime image + +### Security Features + +- Runs as non-root user (`mcpuser`) +- Minimal base image (python:3.11-slim) +- Only necessary runtime dependencies +- Configurable resource limits + +### Directory Structure + +``` +/app/ +├── server.py # Main application +├── config.yaml.sample # Configuration template +├── docker-entrypoint.sh # Startup script +├── config/ # Configuration directory (mounted) +│ └── config.yaml # Runtime configuration +└── logs/ # Log directory (mounted) + └── vmware_mcp.log # Application logs +``` + +## Configuration Options + +### Volume Mounts + +- `./config.yaml:/app/config/config.yaml:ro` - Configuration file (read-only) +- `./logs:/app/logs` - Log directory + +### Environment Variables + +All configuration options can be set via environment variables: + +| Variable | Description | Default | +|----------|-------------|---------| +| `VCENTER_HOST` | vCenter/ESXi hostname | Required | +| `VCENTER_USER` | Username | Required | +| `VCENTER_PASSWORD` | Password | Required | +| `VCENTER_DATACENTER` | Datacenter name | Auto-detect | +| `VCENTER_CLUSTER` | Cluster name | Auto-detect | +| `VCENTER_DATASTORE` | Datastore name | Auto-detect | +| `VCENTER_NETWORK` | Network name | VM Network | +| `VCENTER_INSECURE` | Skip SSL verification | true | +| `MCP_API_KEY` | API authentication key | None | +| `MCP_LOG_LEVEL` | Log level | INFO | + +### Resource Limits + +Default resource limits in docker-compose.yml: + +- **Memory**: 512MB limit, 256MB reserved +- **CPU**: 0.5 cores limit, 0.25 cores reserved + +## Health Checks + +The container includes automatic health checks: + +- **Interval**: 30 seconds +- **Timeout**: 10 seconds +- **Retries**: 3 +- **Start Period**: 40 seconds + +Check health manually: + +```bash +make health +``` + +## Networking + +The server exposes: + +- **Port 8080**: HTTP API endpoint +- **Path `/sse`**: Server-Sent Events endpoint +- **Path `/sse/messages`**: JSON-RPC messages endpoint + +## Troubleshooting + +### Check Logs + +```bash +make logs +``` + +### Check Container Status + +```bash +make status +``` + +### Open Shell in Container + +```bash +make shell +``` + +### Common Issues + +1. **Configuration not found**: Ensure `config/config.yaml` exists or environment variables are set +2. **Permission denied**: Check that the `logs` directory is writable +3. **Connection failed**: Verify vCenter/ESXi connectivity and credentials +4. **Health check failed**: Check if the server is responding on port 8080 + +### Debug Mode + +Run with debug logging: + +```bash +# Set in .env file +MCP_LOG_LEVEL=DEBUG + +# Or in config.yaml +log_level: "DEBUG" +``` + +## Production Deployment + +### Security Recommendations + +1. Use a dedicated user account for vCenter access +2. Enable API key authentication +3. Use valid SSL certificates (set `insecure: false`) +4. Limit container resources +5. Use Docker secrets for sensitive data + +### High Availability + +For production deployments, consider: + +- Running multiple container instances +- Using a load balancer +- Implementing persistent storage for logs +- Setting up monitoring and alerting + +## Examples + +### Basic Usage + +```bash +# Start the server +make run + +# Check if it's working +curl http://localhost:8080/sse +``` + +### API Authentication + +```bash +# With API key +curl -H "Authorization: Bearer your-api-key" http://localhost:8080/sse +``` + +### Development + +```bash +# Development workflow +make build +make dev + +# Make changes to code +# Rebuild and restart +make update +``` \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c2f4cea --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +version: '3.8' + +services: + esxi-mcp-server: + build: + context: . + dockerfile: Dockerfile + container_name: esxi-mcp-server + restart: unless-stopped + ports: + - "8080:8080" + volumes: + # Mount configuration file + - ./config.yaml:/app/config/config.yaml:ro + # Mount logs directory + - ./logs:/app/logs + environment: + # Configuration can be overridden via environment variables + - VCENTER_HOST=${VCENTER_HOST:-} + - VCENTER_USER=${VCENTER_USER:-} + - VCENTER_PASSWORD=${VCENTER_PASSWORD:-} + - VCENTER_DATACENTER=${VCENTER_DATACENTER:-} + - VCENTER_CLUSTER=${VCENTER_CLUSTER:-} + - VCENTER_DATASTORE=${VCENTER_DATASTORE:-} + - VCENTER_NETWORK=${VCENTER_NETWORK:-VM Network} + - VCENTER_INSECURE=${VCENTER_INSECURE:-true} + - MCP_API_KEY=${MCP_API_KEY:-} + - MCP_LOG_LEVEL=${MCP_LOG_LEVEL:-INFO} + networks: + - mcp-network + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + # Resource limits + deploy: + resources: + limits: + memory: 512M + cpus: '0.5' + reservations: + memory: 256M + cpus: '0.25' + +networks: + mcp-network: + driver: bridge \ No newline at end of file diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..6506e5f --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# Docker entrypoint script for ESXi MCP Server +set -e + +# Function to wait for configuration file +wait_for_config() { + local config_file="/app/config/config.yaml" + local max_wait=30 + local count=0 + + echo "Waiting for configuration file..." + while [ ! -f "$config_file" ] && [ $count -lt $max_wait ]; do + echo "Configuration file not found, waiting... ($count/$max_wait)" + sleep 2 + count=$((count + 1)) + done + + if [ ! -f "$config_file" ]; then + echo "Warning: Configuration file not found. Using environment variables." + return 1 + fi + + echo "Configuration file found: $config_file" + return 0 +} + +# Function to validate required environment variables +validate_env() { + local required_vars=("VCENTER_HOST" "VCENTER_USER" "VCENTER_PASSWORD") + local missing_vars=() + + for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + missing_vars+=("$var") + fi + done + + if [ ${#missing_vars[@]} -ne 0 ]; then + echo "Error: Missing required environment variables: ${missing_vars[*]}" + echo "Please set these variables or provide a configuration file." + exit 1 + fi +} + +# Function to create configuration from environment variables +create_config_from_env() { + local config_file="/app/config/config.yaml" + + echo "Creating configuration from environment variables..." + + cat > "$config_file" << EOF +vcenter_host: "${VCENTER_HOST}" +vcenter_user: "${VCENTER_USER}" +vcenter_password: "${VCENTER_PASSWORD}" +EOF + + # Add optional configuration + [ -n "$VCENTER_DATACENTER" ] && echo "datacenter: \"${VCENTER_DATACENTER}\"" >> "$config_file" + [ -n "$VCENTER_CLUSTER" ] && echo "cluster: \"${VCENTER_CLUSTER}\"" >> "$config_file" + [ -n "$VCENTER_DATASTORE" ] && echo "datastore: \"${VCENTER_DATASTORE}\"" >> "$config_file" + [ -n "$VCENTER_NETWORK" ] && echo "network: \"${VCENTER_NETWORK}\"" >> "$config_file" + [ -n "$VCENTER_INSECURE" ] && echo "insecure: ${VCENTER_INSECURE}" >> "$config_file" + [ -n "$MCP_API_KEY" ] && echo "api_key: \"${MCP_API_KEY}\"" >> "$config_file" + [ -n "$MCP_LOG_LEVEL" ] && echo "log_level: \"${MCP_LOG_LEVEL}\"" >> "$config_file" + + # Always set log file path + echo "log_file: \"/app/logs/vmware_mcp.log\"" >> "$config_file" + + echo "Configuration file created successfully." +} + +# Main execution +echo "Starting ESXi MCP Server..." + +# Create logs directory if it doesn't exist +mkdir -p /app/logs + +# Check if configuration file exists, if not try to create from environment +if ! wait_for_config; then + validate_env + create_config_from_env +fi + +# Print configuration info (without sensitive data) +echo "Server starting with configuration:" +echo " Host: ${VCENTER_HOST:-'from config file'}" +echo " User: ${VCENTER_USER:-'from config file'}" +echo " Log Level: ${MCP_LOG_LEVEL:-INFO}" +echo " Port: 8080" + +# Execute the main command +exec "$@" \ No newline at end of file