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
6 changes: 4 additions & 2 deletions labs/app_python/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ WORKDIR /app

RUN groupadd -r appuser && useradd -r -g appuser appuser

COPY requirements.txt .
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
pip install --no-cache-dir -r requirements.txt && \
pip install flask python-json-logger

COPY . .

Expand Down
182 changes: 87 additions & 95 deletions labs/app_python/app.py
Original file line number Diff line number Diff line change
@@ -1,140 +1,132 @@
"""
DevOps Info Service
Веб-сервис для предоставления информации о системе и состоянии сервиса
DevOps Info Service with JSON Logging
"""

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

# Создаем приложение Flask
# Flask app
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 информацию
# Docker info
IS_DOCKER = os.path.exists('/.dockerenv')
CONTAINER_ID = socket.gethostname() if IS_DOCKER else None

# ----------------------------
# JSON LOGGING CONFIG
# ----------------------------
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'
)
handler.setFormatter(formatter)
logger.addHandler(handler)

# ----------------------------
# HELPERS
# ----------------------------
def get_uptime():
"""Рассчитывает время работы сервиса"""
delta = datetime.now(timezone.utc) - START_TIME
seconds = int(delta.total_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)
}
return {'seconds': seconds, 'human': f"{seconds} seconds"}

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

@app.after_request
def log_response(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
}
)
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': {
'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,
'endpoints': [
{'path': '/', 'method': 'GET', 'description': 'Service information'},
{'path': '/health', 'method': 'GET', 'description': 'Health check'},
{'path': '/docker', 'method': 'GET', 'description': 'Docker information'}
]
'service': 'devops-info-service',
'status': 'running',
'time': datetime.now(timezone.utc).isoformat()
})

@app.route('/health')
def health_check():
"""Эндпоинт проверки состояния сервиса"""
return jsonify({
'status': 'healthy',
'timestamp': datetime.now(timezone.utc).isoformat(),
'uptime_seconds': get_uptime()['seconds'],
'environment': 'docker' if IS_DOCKER else 'local',
'container_id': CONTAINER_ID
'uptime': get_uptime()['seconds']
})

@app.route('/docker')
def docker_info():
"""Эндпоинт информации о Docker окружении"""
return jsonify({
'is_docker': IS_DOCKER,
'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'
'container_id': CONTAINER_ID
})

# ----------------------------
# ERRORS
# ----------------------------
@app.errorhandler(404)
def not_found(error):
"""Обработка ошибки 404"""
return jsonify({
'error': 'Not Found',
'message': 'Endpoint does not exist',
'available_endpoints': [
{'path': '/', 'method': 'GET'},
{'path': '/health', 'method': 'GET'},
{'path': '/docker', 'method': 'GET'}
]
}), 404
logging.error(
"not_found",
extra={
"method": request.method,
"path": request.path,
"status": 404,
"client_ip": request.remote_addr
}
)
return jsonify({'error': 'Not Found'}), 404

# ----------------------------
# START
# ----------------------------
if __name__ == '__main__':
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}")

logging.info(
"service_started",
extra={
"host": HOST,
"port": PORT,
"debug": DEBUG,
"docker": IS_DOCKER,
"container_id": CONTAINER_ID
}
)
app.run(host=HOST, port=PORT, debug=DEBUG)
96 changes: 96 additions & 0 deletions labs/monitoring/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
services:
loki:
image: grafana/loki:3.0.0
container_name: loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/config.yml
volumes:
- ./loki/config.yml:/etc/loki/config.yml
- loki-data:/loki
networks:
- logging
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
healthcheck:
test: ["CMD-SHELL", "wget --spider -q http://localhost:3100/ready || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s

promtail:
image: grafana/promtail:3.0.0
container_name: promtail
command: -config.file=/etc/promtail/config.yml
volumes:
- ./promtail/config.yml:/etc/promtail/config.yml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "9080:9080"
networks:
- logging
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M

grafana:
image: grafana/grafana:12.3.1
container_name: grafana
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_AUTH_ANONYMOUS_ENABLED=false
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin123
networks:
- logging
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
healthcheck:
test: ["CMD-SHELL", "wget --spider -q http://localhost:3000/api/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5

app-python:
build: ../app_python
container_name: app-python
ports:
- "5000:5000"
networks:
- logging
labels:
logging: "promtail"
app: "devops-python"
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:5000/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5

volumes:
loki-data:
grafana-data:

networks:
logging:
Loading
Loading