Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 122 additions & 61 deletions labs/app_python/app.py
Original file line number Diff line number Diff line change
@@ -1,132 +1,193 @@
"""
DevOps Info Service with JSON Logging
DevOps Info Service
Веб-сервис для предоставления информации о системе и состоянии сервиса
"""

import os
import socket
import logging
import platform
from datetime import datetime, timezone
from flask import Flask, jsonify, request
from pythonjsonlogger import jsonlogger

# Flask app
# Prometheus
from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST

# Создаем приложение Flask
app = Flask(__name__)

# ENV config
# Настройки из переменных окружения
HOST = os.getenv('HOST', '0.0.0.0')
PORT = int(os.getenv('PORT', 5000))
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'

# Время запуска сервиса
START_TIME = datetime.now(timezone.utc)

# Docker info
# Docker информация
IS_DOCKER = os.path.exists('/.dockerenv')
CONTAINER_ID = socket.gethostname() if IS_DOCKER else None

# ----------------------------
# JSON LOGGING CONFIG
# PROMETHEUS METRICS
# ----------------------------
logger = logging.getLogger()
logger.setLevel(logging.INFO)

handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
'%(asctime)s %(levelname)s %(message)s %(method)s %(path)s %(status)s %(client_ip)s %(duration)s'
http_requests_total = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status']
)

http_request_duration_seconds = Histogram(
'http_request_duration_seconds',
'HTTP request duration in seconds',
['method', 'endpoint']
)

http_requests_in_progress = Gauge(
'http_requests_in_progress',
'Number of HTTP requests in progress'
)
handler.setFormatter(formatter)
logger.addHandler(handler)

# ----------------------------
# HELPERS
# ----------------------------

def get_uptime():
delta = datetime.now(timezone.utc) - START_TIME
seconds = int(delta.total_seconds())
return {'seconds': seconds, 'human': f"{seconds} seconds"}

hours = seconds // 3600
minutes = (seconds % 3600) // 60
secs = seconds % 60

parts = []
if hours > 0:
parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
if minutes > 0:
parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
if secs > 0 or not parts:
parts.append(f"{secs} second{'s' if secs != 1 else ''}")

return {
'seconds': seconds,
'human': ', '.join(parts)
}

# ----------------------------
# LOGGING MIDDLEWARE
# METRICS MIDDLEWARE
# ----------------------------

@app.before_request
def log_request():
def before_request():
request.start_time = datetime.now(timezone.utc)
logging.info(
"request_received",
extra={
"method": request.method,
"path": request.path,
"client_ip": request.remote_addr
}
)
http_requests_in_progress.inc()

@app.after_request
def log_response(response):
def after_request(response):
duration = (datetime.now(timezone.utc) - request.start_time).total_seconds()
logging.info(
"request_completed",
extra={
"method": request.method,
"path": request.path,
"status": response.status_code,
"client_ip": request.remote_addr,
"duration": duration
}
)

http_requests_total.labels(
method=request.method,
endpoint=request.path,
status=str(response.status_code)
).inc()

http_request_duration_seconds.labels(
method=request.method,
endpoint=request.path
).observe(duration)

http_requests_in_progress.dec()

return response

# ----------------------------
# ROUTES
# ----------------------------

@app.route('/')
def main_endpoint():
system_info = {
'hostname': socket.gethostname(),
'platform': platform.system(),
'platform_version': platform.version(),
'architecture': platform.machine(),
'cpu_count': os.cpu_count(),
'python_version': platform.python_version(),
'is_docker_container': IS_DOCKER,
'container_id': CONTAINER_ID
}

runtime_info = {
'uptime_seconds': get_uptime()['seconds'],
'uptime_human': get_uptime()['human'],
'current_time': datetime.now(timezone.utc).isoformat(),
'timezone': 'UTC',
'start_time': START_TIME.isoformat()
}

request_info = {
'client_ip': request.remote_addr,
'user_agent': request.headers.get('User-Agent', 'Unknown'),
'method': request.method,
'path': request.path
}

return jsonify({
'service': 'devops-info-service',
'status': 'running',
'time': datetime.now(timezone.utc).isoformat()
'service': {
'name': 'devops-info-service',
'version': '2.0.0',
'description': 'DevOps course info service (Dockerized)',
'framework': 'Flask',
'environment': 'docker' if IS_DOCKER else 'local'
},
'system': system_info,
'runtime': runtime_info,
'request': request_info
})

@app.route('/health')
def health_check():
return jsonify({
'status': 'healthy',
'uptime': get_uptime()['seconds']
'timestamp': datetime.now(timezone.utc).isoformat(),
'uptime_seconds': get_uptime()['seconds'],
'environment': 'docker' if IS_DOCKER else 'local',
'container_id': CONTAINER_ID
})

@app.route('/docker')
def docker_info():
return jsonify({
'is_docker': IS_DOCKER,
'container_id': CONTAINER_ID
'container_id': CONTAINER_ID,
'docker_env': dict(os.environ) if IS_DOCKER else None,
'message': 'Running in Docker container' if IS_DOCKER else 'Running locally'
})

@app.route('/metrics')
def metrics():
return generate_latest(), 200, {'Content-Type': CONTENT_TYPE_LATEST}

# ----------------------------
# ERRORS
# ----------------------------

@app.errorhandler(404)
def not_found(error):
logging.error(
"not_found",
extra={
"method": request.method,
"path": request.path,
"status": 404,
"client_ip": request.remote_addr
}
)
return jsonify({'error': 'Not Found'}), 404
return jsonify({
'error': 'Not Found',
'message': 'Endpoint does not exist'
}), 404

# ----------------------------
# START
# ----------------------------

if __name__ == '__main__':
logging.info(
"service_started",
extra={
"host": HOST,
"port": PORT,
"debug": DEBUG,
"docker": IS_DOCKER,
"container_id": CONTAINER_ID
}
)
print(f"Starting DevOps Info Service on {HOST}:{PORT}")
print(f"Debug mode: {DEBUG}")
print(f"Docker environment: {IS_DOCKER}")
print(f"Container ID: {CONTAINER_ID}")

app.run(host=HOST, port=PORT, debug=DEBUG)
4 changes: 3 additions & 1 deletion labs/app_python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ python-dotenv==1.0.1

# Runtime dependencies
Werkzeug==3.1.3
Jinja2==3.1.4
Jinja2==3.1.4

prometheus-client==0.23.1
26 changes: 26 additions & 0 deletions labs/monitoring/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,36 @@ services:
interval: 10s
timeout: 5s
retries: 5

prometheus:
image: prom/prometheus:v3.9.0
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=15d'
- '--storage.tsdb.retention.size=10GB'
networks:
- logging
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
healthcheck:
test: ["CMD-SHELL", "wget --spider -q http://localhost:9090/-/healthy || exit 1"]
interval: 10s
timeout: 5s
retries: 5

volumes:
loki-data:
grafana-data:
prometheus-data:

networks:
logging:
Loading
Loading