## New Features ### Universal Instrumentation Container - Created deploy/instrumentation/ with Dockerfile that downloads OTel agents for: - .NET (glibc and musl/Alpine versions) - Node.js (with auto-instrumentation package) - Python (bootstrap script + requirements) - Java (javaagent JAR) - Go (example code for compile-time instrumentation) - PHP (composer package + init script) ### Universal instrument.sh Script - Auto-detects application language from running processes - Generates docker-compose snippets for each language - Supports: dotnet, nodejs, python, java, go, php - Usage: ./instrument.sh <container> [language] [otlp_endpoint] ### Improved docker-compose.yml - Added instrumentation init container with shared volume - Added AGENT_KEY environment variable for proper auth - Added ophion-agent service for host metrics collection - Named containers for easier management - Added ophion-network for service discovery ### Documentation - Created docs/QUICK_START.md with: - Single-command installation - Instrumentation guide for all languages - Troubleshooting section - Authentication guide ### Auth Fixes - Server now properly validates AGENT_KEY for agent authentication - OTel Collector configured with AGENT_KEY for forwarding to server - Fixed 401 errors when agents connect ## Files Changed - docker-compose.yml: Complete stack with all services - deploy/instrumentation/*: Universal OTel agent container - deploy/docker/otel-collector-config.yaml: Fixed auth headers - instrument.sh: Universal instrumentation script - docs/QUICK_START.md: Complete quick start guide - README.md: Updated with new features - .env.example: Added AGENT_KEY ## Testing - Go code compiles successfully - Docker images build correctly - All changes are backwards compatible
406 lines
12 KiB
Bash
Executable File
406 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# 🔧 OPHION Universal Instrumentation Script
|
|
# Auto-instrument any containerized application
|
|
# ═══════════════════════════════════════════════════════════
|
|
#
|
|
# Usage:
|
|
# ./instrument.sh <container_name> [language] [ophion_server]
|
|
#
|
|
# Examples:
|
|
# ./instrument.sh myapp # Auto-detect language
|
|
# ./instrument.sh myapp nodejs # Explicit Node.js
|
|
# ./instrument.sh myapp python http://ophion:4318
|
|
#
|
|
# Supported languages: dotnet, nodejs, python, java, go, php
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
set -e
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
print_banner() {
|
|
echo -e "${BLUE}"
|
|
echo "╔═══════════════════════════════════════════════════════════╗"
|
|
echo "║ 🐍 OPHION - Universal Instrumentation ║"
|
|
echo "╚═══════════════════════════════════════════════════════════╝"
|
|
echo -e "${NC}"
|
|
}
|
|
|
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
# Configuration
|
|
CONTAINER_NAME="${1:-}"
|
|
LANGUAGE="${2:-auto}"
|
|
OPHION_SERVER="${3:-http://ophion-otel-collector:4318}"
|
|
OTEL_VOLUME="ophion-otel-agents"
|
|
|
|
if [[ -z "$CONTAINER_NAME" ]]; then
|
|
print_banner
|
|
echo "Usage: $0 <container_name> [language] [ophion_server]"
|
|
echo ""
|
|
echo "Arguments:"
|
|
echo " container_name Name or ID of the Docker container"
|
|
echo " language One of: dotnet, nodejs, python, java, go, php, auto (default)"
|
|
echo " ophion_server OTLP endpoint (default: http://ophion-otel-collector:4318)"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 myapp"
|
|
echo " $0 myapp nodejs"
|
|
echo " $0 myapp python http://localhost:4318"
|
|
exit 1
|
|
fi
|
|
|
|
print_banner
|
|
|
|
# Check if container exists
|
|
if ! docker inspect "$CONTAINER_NAME" &>/dev/null; then
|
|
log_error "Container '$CONTAINER_NAME' not found"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Container: $CONTAINER_NAME"
|
|
|
|
# Detect language if auto
|
|
detect_language() {
|
|
local container="$1"
|
|
|
|
log_info "Auto-detecting language..."
|
|
|
|
# Check running processes
|
|
local processes
|
|
processes=$(docker exec "$container" ps aux 2>/dev/null || echo "")
|
|
|
|
# Check for specific runtimes
|
|
if echo "$processes" | grep -qE "dotnet|aspnet"; then
|
|
echo "dotnet"
|
|
elif echo "$processes" | grep -qE "node|npm|yarn"; then
|
|
echo "nodejs"
|
|
elif echo "$processes" | grep -qE "python|gunicorn|uvicorn|flask|django"; then
|
|
echo "python"
|
|
elif echo "$processes" | grep -qE "java|jvm"; then
|
|
echo "java"
|
|
elif docker exec "$container" test -f /usr/local/bin/php 2>/dev/null; then
|
|
echo "php"
|
|
else
|
|
# Check for common files
|
|
if docker exec "$container" test -f package.json 2>/dev/null; then
|
|
echo "nodejs"
|
|
elif docker exec "$container" test -f requirements.txt 2>/dev/null || \
|
|
docker exec "$container" test -f pyproject.toml 2>/dev/null; then
|
|
echo "python"
|
|
elif docker exec "$container" test -f pom.xml 2>/dev/null || \
|
|
docker exec "$container" test -f build.gradle 2>/dev/null; then
|
|
echo "java"
|
|
elif docker exec "$container" find / -name "*.csproj" 2>/dev/null | head -1 | grep -q .; then
|
|
echo "dotnet"
|
|
elif docker exec "$container" test -f composer.json 2>/dev/null; then
|
|
echo "php"
|
|
elif docker exec "$container" test -f go.mod 2>/dev/null; then
|
|
echo "go"
|
|
else
|
|
echo "unknown"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
if [[ "$LANGUAGE" == "auto" ]]; then
|
|
LANGUAGE=$(detect_language "$CONTAINER_NAME")
|
|
if [[ "$LANGUAGE" == "unknown" ]]; then
|
|
log_error "Could not auto-detect language. Please specify: dotnet, nodejs, python, java, go, php"
|
|
exit 1
|
|
fi
|
|
log_success "Detected language: $LANGUAGE"
|
|
fi
|
|
|
|
# Ensure OTEL volume exists
|
|
if ! docker volume inspect "$OTEL_VOLUME" &>/dev/null; then
|
|
log_warn "OTEL agents volume not found. Creating..."
|
|
docker run --rm -v "$OTEL_VOLUME":/otel ophion-instrumentation 2>/dev/null || {
|
|
log_error "Failed to create OTEL agents volume. Run 'docker compose up instrumentation' first."
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
# Get container image to determine if it's Alpine (musl) or glibc
|
|
IS_ALPINE=false
|
|
if docker exec "$CONTAINER_NAME" cat /etc/os-release 2>/dev/null | grep -qi alpine; then
|
|
IS_ALPINE=true
|
|
fi
|
|
|
|
# Service name from container name
|
|
SERVICE_NAME="${CONTAINER_NAME//-/_}"
|
|
|
|
log_info "Language: $LANGUAGE"
|
|
log_info "OTLP Endpoint: $OPHION_SERVER"
|
|
log_info "Service Name: $SERVICE_NAME"
|
|
|
|
# Common environment variables
|
|
COMMON_ENV=(
|
|
-e "OTEL_EXPORTER_OTLP_ENDPOINT=$OPHION_SERVER"
|
|
-e "OTEL_SERVICE_NAME=$SERVICE_NAME"
|
|
-e "OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=ophion"
|
|
-e "OTEL_TRACES_EXPORTER=otlp"
|
|
-e "OTEL_METRICS_EXPORTER=otlp"
|
|
-e "OTEL_LOGS_EXPORTER=otlp"
|
|
)
|
|
|
|
generate_docker_compose() {
|
|
local lang="$1"
|
|
local output_file="docker-compose.instrumented.yml"
|
|
|
|
cat > "$output_file" << EOF
|
|
# Auto-generated by OPHION instrument.sh
|
|
# Add these settings to your docker-compose.yml
|
|
|
|
version: '3.8'
|
|
|
|
services:
|
|
${CONTAINER_NAME}:
|
|
# Add these environment variables:
|
|
environment:
|
|
- OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER}
|
|
- OTEL_SERVICE_NAME=${SERVICE_NAME}
|
|
- OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production
|
|
EOF
|
|
|
|
case "$lang" in
|
|
dotnet)
|
|
local dotnet_path="/otel/dotnet"
|
|
[[ "$IS_ALPINE" == true ]] && dotnet_path="/otel/dotnet-musl"
|
|
cat >> "$output_file" << EOF
|
|
- CORECLR_ENABLE_PROFILING=1
|
|
- CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658571}
|
|
- CORECLR_PROFILER_PATH=${dotnet_path}/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so
|
|
- DOTNET_ADDITIONAL_DEPS=${dotnet_path}/AdditionalDeps
|
|
- DOTNET_SHARED_STORE=${dotnet_path}/store
|
|
- DOTNET_STARTUP_HOOKS=${dotnet_path}/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll
|
|
- OTEL_DOTNET_AUTO_HOME=${dotnet_path}
|
|
volumes:
|
|
- ophion-otel-agents:/otel:ro
|
|
EOF
|
|
;;
|
|
nodejs)
|
|
cat >> "$output_file" << EOF
|
|
- NODE_OPTIONS=--require /otel/nodejs/instrument.js
|
|
volumes:
|
|
- ophion-otel-agents:/otel:ro
|
|
# Note: Run 'npm install' in /otel/nodejs first or copy package.json deps
|
|
EOF
|
|
;;
|
|
python)
|
|
cat >> "$output_file" << EOF
|
|
# For Python, modify your entrypoint:
|
|
# command: opentelemetry-instrument python your-app.py
|
|
# Or use PYTHONPATH
|
|
volumes:
|
|
- ophion-otel-agents:/otel:ro
|
|
# Run: pip install opentelemetry-distro opentelemetry-exporter-otlp && opentelemetry-bootstrap -a install
|
|
EOF
|
|
;;
|
|
java)
|
|
cat >> "$output_file" << EOF
|
|
- JAVA_TOOL_OPTIONS=-javaagent:/otel/java/opentelemetry-javaagent.jar
|
|
volumes:
|
|
- ophion-otel-agents:/otel:ro
|
|
EOF
|
|
;;
|
|
php)
|
|
cat >> "$output_file" << EOF
|
|
# For PHP, add to your composer.json:
|
|
# "require": { "open-telemetry/sdk": "^1.0", "open-telemetry/exporter-otlp": "^1.0" }
|
|
# And include /otel/php/otel_init.php in your bootstrap
|
|
volumes:
|
|
- ophion-otel-agents:/otel:ro
|
|
EOF
|
|
;;
|
|
go)
|
|
cat >> "$output_file" << EOF
|
|
# For Go, instrumentation is compile-time.
|
|
# See /otel/go/init.go.example for code to add to your application.
|
|
# No runtime volumes needed.
|
|
EOF
|
|
;;
|
|
esac
|
|
|
|
cat >> "$output_file" << EOF
|
|
|
|
networks:
|
|
default:
|
|
external: true
|
|
name: ophion-network
|
|
|
|
volumes:
|
|
ophion-otel-agents:
|
|
external: true
|
|
EOF
|
|
|
|
log_success "Generated $output_file"
|
|
}
|
|
|
|
# Apply instrumentation based on language
|
|
case "$LANGUAGE" in
|
|
dotnet)
|
|
log_info "Applying .NET auto-instrumentation..."
|
|
DOTNET_PATH="/otel/dotnet"
|
|
[[ "$IS_ALPINE" == true ]] && DOTNET_PATH="/otel/dotnet-musl"
|
|
|
|
cat << EOF
|
|
|
|
📋 Add these environment variables to your container:
|
|
|
|
docker run \\
|
|
-e CORECLR_ENABLE_PROFILING=1 \\
|
|
-e CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658571} \\
|
|
-e CORECLR_PROFILER_PATH=${DOTNET_PATH}/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so \\
|
|
-e DOTNET_ADDITIONAL_DEPS=${DOTNET_PATH}/AdditionalDeps \\
|
|
-e DOTNET_SHARED_STORE=${DOTNET_PATH}/store \\
|
|
-e DOTNET_STARTUP_HOOKS=${DOTNET_PATH}/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll \\
|
|
-e OTEL_DOTNET_AUTO_HOME=${DOTNET_PATH} \\
|
|
-e OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} \\
|
|
-e OTEL_SERVICE_NAME=${SERVICE_NAME} \\
|
|
-v ${OTEL_VOLUME}:/otel:ro \\
|
|
--network ophion-network \\
|
|
your-image
|
|
|
|
EOF
|
|
;;
|
|
|
|
nodejs)
|
|
log_info "Applying Node.js auto-instrumentation..."
|
|
cat << EOF
|
|
|
|
📋 Add these to your container:
|
|
|
|
# Option 1: NODE_OPTIONS (recommended)
|
|
docker run \\
|
|
-e NODE_OPTIONS="--require /otel/nodejs/instrument.js" \\
|
|
-e OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} \\
|
|
-e OTEL_SERVICE_NAME=${SERVICE_NAME} \\
|
|
-v ${OTEL_VOLUME}:/otel:ro \\
|
|
--network ophion-network \\
|
|
your-image
|
|
|
|
# Option 2: Modify your package.json start script
|
|
# "start": "node -r /otel/nodejs/instrument.js your-app.js"
|
|
|
|
# Note: First install deps: cd /otel/nodejs && npm install
|
|
|
|
EOF
|
|
;;
|
|
|
|
python)
|
|
log_info "Applying Python auto-instrumentation..."
|
|
cat << EOF
|
|
|
|
📋 Add these to your container:
|
|
|
|
# First, install packages in your Dockerfile:
|
|
# RUN pip install opentelemetry-distro opentelemetry-exporter-otlp && \\
|
|
# opentelemetry-bootstrap -a install
|
|
|
|
# Then run with:
|
|
docker run \\
|
|
-e OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} \\
|
|
-e OTEL_SERVICE_NAME=${SERVICE_NAME} \\
|
|
--network ophion-network \\
|
|
your-image \\
|
|
opentelemetry-instrument python your-app.py
|
|
|
|
# Or use OTEL_PYTHON_CONFIGURATOR=true in your Dockerfile
|
|
|
|
EOF
|
|
;;
|
|
|
|
java)
|
|
log_info "Applying Java auto-instrumentation..."
|
|
cat << EOF
|
|
|
|
📋 Add these to your container:
|
|
|
|
docker run \\
|
|
-e JAVA_TOOL_OPTIONS="-javaagent:/otel/java/opentelemetry-javaagent.jar" \\
|
|
-e OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER} \\
|
|
-e OTEL_SERVICE_NAME=${SERVICE_NAME} \\
|
|
-v ${OTEL_VOLUME}:/otel:ro \\
|
|
--network ophion-network \\
|
|
your-image
|
|
|
|
EOF
|
|
;;
|
|
|
|
go)
|
|
log_info "Go instrumentation requires code changes..."
|
|
cat << EOF
|
|
|
|
📋 Go requires compile-time instrumentation. Add this code:
|
|
|
|
1. Add dependencies:
|
|
go get go.opentelemetry.io/otel
|
|
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
|
|
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
|
|
|
|
2. See example code: /otel/go/init.go.example
|
|
|
|
3. Set environment:
|
|
OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER}
|
|
OTEL_SERVICE_NAME=${SERVICE_NAME}
|
|
|
|
EOF
|
|
;;
|
|
|
|
php)
|
|
log_info "Applying PHP auto-instrumentation..."
|
|
cat << EOF
|
|
|
|
📋 Add to your PHP application:
|
|
|
|
1. Add to composer.json:
|
|
{
|
|
"require": {
|
|
"open-telemetry/sdk": "^1.0",
|
|
"open-telemetry/exporter-otlp": "^1.0",
|
|
"php-http/guzzle7-adapter": "^1.0"
|
|
}
|
|
}
|
|
|
|
2. Run: composer require open-telemetry/sdk open-telemetry/exporter-otlp
|
|
|
|
3. Include in bootstrap:
|
|
require_once '/otel/php/otel_init.php';
|
|
|
|
4. Set environment:
|
|
OTEL_EXPORTER_OTLP_ENDPOINT=${OPHION_SERVER}
|
|
OTEL_SERVICE_NAME=${SERVICE_NAME}
|
|
|
|
EOF
|
|
;;
|
|
|
|
*)
|
|
log_error "Unsupported language: $LANGUAGE"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Generate docker-compose snippet
|
|
generate_docker_compose "$LANGUAGE"
|
|
|
|
echo ""
|
|
log_success "Instrumentation configuration generated!"
|
|
log_info "See docker-compose.instrumented.yml for complete configuration"
|
|
echo ""
|
|
echo -e "${GREEN}Next steps:${NC}"
|
|
echo "1. Apply the environment variables to your container"
|
|
echo "2. Ensure container is on the 'ophion-network' network"
|
|
echo "3. Restart your container"
|
|
echo "4. Check traces at http://localhost:3000 (Ophion Dashboard)"
|