Skip to content

Commit 5ea315e

Browse files
author
Fanis Zinnurov
committed
lab07 submission
1 parent deaa0f0 commit 5ea315e

14 files changed

Lines changed: 403 additions & 155 deletions

File tree

labs/app_python/Dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ WORKDIR /app
44

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

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

9+
COPY requirements.txt .
910
RUN pip install --no-cache-dir --upgrade pip && \
10-
pip install --no-cache-dir -r requirements.txt
11+
pip install --no-cache-dir -r requirements.txt && \
12+
pip install flask python-json-logger
1113

1214
COPY . .
1315

labs/app_python/app.py

Lines changed: 87 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,132 @@
11
"""
2-
DevOps Info Service
3-
Веб-сервис для предоставления информации о системе и состоянии сервиса
2+
DevOps Info Service with JSON Logging
43
"""
54

65
import os
76
import socket
8-
import platform
7+
import logging
98
from datetime import datetime, timezone
109
from flask import Flask, jsonify, request
10+
from pythonjsonlogger import jsonlogger
1111

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

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

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

23-
# Добавляем Docker информацию
22+
# Docker info
2423
IS_DOCKER = os.path.exists('/.dockerenv')
2524
CONTAINER_ID = socket.gethostname() if IS_DOCKER else None
2625

26+
# ----------------------------
27+
# JSON LOGGING CONFIG
28+
# ----------------------------
29+
logger = logging.getLogger()
30+
logger.setLevel(logging.INFO)
31+
32+
handler = logging.StreamHandler()
33+
formatter = jsonlogger.JsonFormatter(
34+
'%(asctime)s %(levelname)s %(message)s %(method)s %(path)s %(status)s %(client_ip)s %(duration)s'
35+
)
36+
handler.setFormatter(formatter)
37+
logger.addHandler(handler)
38+
39+
# ----------------------------
40+
# HELPERS
41+
# ----------------------------
2742
def get_uptime():
28-
"""Рассчитывает время работы сервиса"""
2943
delta = datetime.now(timezone.utc) - START_TIME
3044
seconds = int(delta.total_seconds())
31-
32-
hours = seconds // 3600
33-
minutes = (seconds % 3600) // 60
34-
secs = seconds % 60
35-
36-
parts = []
37-
if hours > 0:
38-
parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
39-
if minutes > 0:
40-
parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
41-
if secs > 0 or not parts:
42-
parts.append(f"{secs} second{'s' if secs != 1 else ''}")
43-
44-
return {
45-
'seconds': seconds,
46-
'human': ', '.join(parts)
47-
}
45+
return {'seconds': seconds, 'human': f"{seconds} seconds"}
46+
47+
# ----------------------------
48+
# LOGGING MIDDLEWARE
49+
# ----------------------------
50+
@app.before_request
51+
def log_request():
52+
request.start_time = datetime.now(timezone.utc)
53+
logging.info(
54+
"request_received",
55+
extra={
56+
"method": request.method,
57+
"path": request.path,
58+
"client_ip": request.remote_addr
59+
}
60+
)
61+
62+
@app.after_request
63+
def log_response(response):
64+
duration = (datetime.now(timezone.utc) - request.start_time).total_seconds()
65+
logging.info(
66+
"request_completed",
67+
extra={
68+
"method": request.method,
69+
"path": request.path,
70+
"status": response.status_code,
71+
"client_ip": request.remote_addr,
72+
"duration": duration
73+
}
74+
)
75+
return response
4876

77+
# ----------------------------
78+
# ROUTES
79+
# ----------------------------
4980
@app.route('/')
5081
def main_endpoint():
51-
"""Основной эндпоинт - информация о сервисе и системе"""
52-
53-
# Получаем информацию о системе
54-
system_info = {
55-
'hostname': socket.gethostname(),
56-
'platform': platform.system(),
57-
'platform_version': platform.version(),
58-
'architecture': platform.machine(),
59-
'cpu_count': os.cpu_count(),
60-
'python_version': platform.python_version(),
61-
'is_docker_container': IS_DOCKER,
62-
'container_id': CONTAINER_ID
63-
}
64-
65-
# Информация о времени
66-
runtime_info = {
67-
'uptime_seconds': get_uptime()['seconds'],
68-
'uptime_human': get_uptime()['human'],
69-
'current_time': datetime.now(timezone.utc).isoformat(),
70-
'timezone': 'UTC',
71-
'start_time': START_TIME.isoformat()
72-
}
73-
74-
# Информация о запросе
75-
request_info = {
76-
'client_ip': request.remote_addr,
77-
'user_agent': request.headers.get('User-Agent', 'Unknown'),
78-
'method': request.method,
79-
'path': request.path
80-
}
81-
8282
return jsonify({
83-
'service': {
84-
'name': 'devops-info-service',
85-
'version': '2.0.0',
86-
'description': 'DevOps course info service (Dockerized)',
87-
'framework': 'Flask',
88-
'environment': 'docker' if IS_DOCKER else 'local'
89-
},
90-
'system': system_info,
91-
'runtime': runtime_info,
92-
'request': request_info,
93-
'endpoints': [
94-
{'path': '/', 'method': 'GET', 'description': 'Service information'},
95-
{'path': '/health', 'method': 'GET', 'description': 'Health check'},
96-
{'path': '/docker', 'method': 'GET', 'description': 'Docker information'}
97-
]
83+
'service': 'devops-info-service',
84+
'status': 'running',
85+
'time': datetime.now(timezone.utc).isoformat()
9886
})
9987

10088
@app.route('/health')
10189
def health_check():
102-
"""Эндпоинт проверки состояния сервиса"""
10390
return jsonify({
10491
'status': 'healthy',
105-
'timestamp': datetime.now(timezone.utc).isoformat(),
106-
'uptime_seconds': get_uptime()['seconds'],
107-
'environment': 'docker' if IS_DOCKER else 'local',
108-
'container_id': CONTAINER_ID
92+
'uptime': get_uptime()['seconds']
10993
})
11094

11195
@app.route('/docker')
11296
def docker_info():
113-
"""Эндпоинт информации о Docker окружении"""
11497
return jsonify({
11598
'is_docker': IS_DOCKER,
116-
'container_id': CONTAINER_ID,
117-
'docker_env': dict(os.environ) if IS_DOCKER else None,
118-
'message': 'Running in Docker container' if IS_DOCKER else 'Running locally'
99+
'container_id': CONTAINER_ID
119100
})
120101

102+
# ----------------------------
103+
# ERRORS
104+
# ----------------------------
121105
@app.errorhandler(404)
122106
def not_found(error):
123-
"""Обработка ошибки 404"""
124-
return jsonify({
125-
'error': 'Not Found',
126-
'message': 'Endpoint does not exist',
127-
'available_endpoints': [
128-
{'path': '/', 'method': 'GET'},
129-
{'path': '/health', 'method': 'GET'},
130-
{'path': '/docker', 'method': 'GET'}
131-
]
132-
}), 404
107+
logging.error(
108+
"not_found",
109+
extra={
110+
"method": request.method,
111+
"path": request.path,
112+
"status": 404,
113+
"client_ip": request.remote_addr
114+
}
115+
)
116+
return jsonify({'error': 'Not Found'}), 404
133117

118+
# ----------------------------
119+
# START
120+
# ----------------------------
134121
if __name__ == '__main__':
135-
print(f"Starting DevOps Info Service on {HOST}:{PORT}")
136-
print(f"Debug mode: {DEBUG}")
137-
print(f"Docker environment: {IS_DOCKER}")
138-
print(f"Container ID: {CONTAINER_ID}")
139-
122+
logging.info(
123+
"service_started",
124+
extra={
125+
"host": HOST,
126+
"port": PORT,
127+
"debug": DEBUG,
128+
"docker": IS_DOCKER,
129+
"container_id": CONTAINER_ID
130+
}
131+
)
140132
app.run(host=HOST, port=PORT, debug=DEBUG)

labs/monitoring/docker-compose.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
services:
2+
loki:
3+
image: grafana/loki:3.0.0
4+
container_name: loki
5+
ports:
6+
- "3100:3100"
7+
command: -config.file=/etc/loki/config.yml
8+
volumes:
9+
- ./loki/config.yml:/etc/loki/config.yml
10+
- loki-data:/loki
11+
networks:
12+
- logging
13+
deploy:
14+
resources:
15+
limits:
16+
cpus: '1.0'
17+
memory: 1G
18+
reservations:
19+
cpus: '0.5'
20+
memory: 512M
21+
healthcheck:
22+
test: ["CMD-SHELL", "wget --spider -q http://localhost:3100/ready || exit 1"]
23+
interval: 10s
24+
timeout: 5s
25+
retries: 5
26+
start_period: 10s
27+
28+
promtail:
29+
image: grafana/promtail:3.0.0
30+
container_name: promtail
31+
command: -config.file=/etc/promtail/config.yml
32+
volumes:
33+
- ./promtail/config.yml:/etc/promtail/config.yml
34+
- /var/lib/docker/containers:/var/lib/docker/containers:ro
35+
- /var/run/docker.sock:/var/run/docker.sock:ro
36+
ports:
37+
- "9080:9080"
38+
networks:
39+
- logging
40+
deploy:
41+
resources:
42+
limits:
43+
cpus: '0.5'
44+
memory: 512M
45+
46+
grafana:
47+
image: grafana/grafana:12.3.1
48+
container_name: grafana
49+
ports:
50+
- "3000:3000"
51+
volumes:
52+
- grafana-data:/var/lib/grafana
53+
environment:
54+
- GF_AUTH_ANONYMOUS_ENABLED=false
55+
- GF_SECURITY_ADMIN_USER=admin
56+
- GF_SECURITY_ADMIN_PASSWORD=admin123
57+
networks:
58+
- logging
59+
deploy:
60+
resources:
61+
limits:
62+
cpus: '1.0'
63+
memory: 1G
64+
healthcheck:
65+
test: ["CMD-SHELL", "wget --spider -q http://localhost:3000/api/health || exit 1"]
66+
interval: 10s
67+
timeout: 5s
68+
retries: 5
69+
70+
app-python:
71+
build: ../app_python
72+
container_name: app-python
73+
ports:
74+
- "5000:5000"
75+
networks:
76+
- logging
77+
labels:
78+
logging: "promtail"
79+
app: "devops-python"
80+
deploy:
81+
resources:
82+
limits:
83+
cpus: '0.5'
84+
memory: 512M
85+
healthcheck:
86+
test: ["CMD-SHELL", "curl -f http://localhost:5000/health || exit 1"]
87+
interval: 10s
88+
timeout: 5s
89+
retries: 5
90+
91+
volumes:
92+
loki-data:
93+
grafana-data:
94+
95+
networks:
96+
logging:

0 commit comments

Comments
 (0)