feat: Universal auto-instrumentation for all languages
## 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
This commit is contained in:
405
instrument.sh
Executable file
405
instrument.sh
Executable file
@@ -0,0 +1,405 @@
|
||||
#!/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)"
|
||||
Reference in New Issue
Block a user