chore: update Python version in Dockerfile and improve test commands in Makefile
- Upgraded Python version in Dockerfile from 3.9 to 3.11.9 for enhanced performance and security. - Adjusted paths in Dockerfile to reflect the new Python version. - Modified test commands in Makefile to activate the virtual environment before running tests, ensuring proper dependency management.
This commit is contained in:
45
.github/workflows/ci.yml
vendored
Normal file
45
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: CI - Infrastructure Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop, 'feature/**' ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Run Infrastructure Tests
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python 3.11
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r tests/infra/requirements-test.txt
|
||||||
|
|
||||||
|
- name: Run infrastructure tests
|
||||||
|
run: |
|
||||||
|
python -m pytest tests/infra/ -v --tb=short
|
||||||
|
|
||||||
|
- name: Validate Prometheus config
|
||||||
|
run: |
|
||||||
|
python -m pytest tests/infra/test_prometheus_config.py -v
|
||||||
|
|
||||||
|
- name: Upload test results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: test-results
|
||||||
|
path: |
|
||||||
|
.pytest_cache/
|
||||||
|
htmlcov/
|
||||||
|
retention-days: 7
|
||||||
108
.github/workflows/deploy.yml
vendored
Normal file
108
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
name: Deploy to Production
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch: # Позволяет запускать вручную
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Deploy Infrastructure
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy to server
|
||||||
|
uses: appleboy/ssh-action@v1.0.0
|
||||||
|
with:
|
||||||
|
host: ${{ vars.SERVER_HOST || secrets.SERVER_HOST }}
|
||||||
|
username: ${{ vars.SERVER_USER || secrets.SERVER_USER }}
|
||||||
|
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
port: ${{ vars.SSH_PORT || secrets.SSH_PORT || 22 }}
|
||||||
|
script: |
|
||||||
|
set -e
|
||||||
|
echo "🚀 Starting deployment..."
|
||||||
|
|
||||||
|
# Переходим в директорию проекта
|
||||||
|
cd /home/prod
|
||||||
|
|
||||||
|
# Сохраняем текущий коммит для отката
|
||||||
|
CURRENT_COMMIT=$(git rev-parse HEAD)
|
||||||
|
echo "Current commit: $CURRENT_COMMIT" > /tmp/last_deploy_commit.txt
|
||||||
|
|
||||||
|
# Обновляем код
|
||||||
|
echo "📥 Pulling latest changes..."
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
|
||||||
|
# Проверяем, что изменения есть
|
||||||
|
NEW_COMMIT=$(git rev-parse HEAD)
|
||||||
|
if [ "$CURRENT_COMMIT" = "$NEW_COMMIT" ]; then
|
||||||
|
echo "ℹ️ No new changes to deploy"
|
||||||
|
else
|
||||||
|
echo "✅ Code updated: $CURRENT_COMMIT → $NEW_COMMIT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Перезапускаем сервисы
|
||||||
|
echo "🔄 Restarting services..."
|
||||||
|
if command -v make &> /dev/null; then
|
||||||
|
make restart || docker-compose restart
|
||||||
|
else
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Deployment completed"
|
||||||
|
|
||||||
|
- name: Health check
|
||||||
|
uses: appleboy/ssh-action@v1.0.0
|
||||||
|
with:
|
||||||
|
host: ${{ vars.SERVER_HOST || secrets.SERVER_HOST }}
|
||||||
|
username: ${{ vars.SERVER_USER || secrets.SERVER_USER }}
|
||||||
|
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
port: ${{ vars.SSH_PORT || secrets.SSH_PORT || 22 }}
|
||||||
|
script: |
|
||||||
|
echo "🏥 Running health checks..."
|
||||||
|
sleep 15 # Даем время сервисам запуститься
|
||||||
|
|
||||||
|
# Проверяем Prometheus
|
||||||
|
if curl -f http://localhost:9090/-/healthy > /dev/null 2>&1; then
|
||||||
|
echo "✅ Prometheus is healthy"
|
||||||
|
else
|
||||||
|
echo "❌ Prometheus health check failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем Grafana
|
||||||
|
if curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
|
||||||
|
echo "✅ Grafana is healthy"
|
||||||
|
else
|
||||||
|
echo "❌ Grafana health check failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем статус контейнеров
|
||||||
|
echo "📊 Container status:"
|
||||||
|
cd /home/prod
|
||||||
|
docker-compose ps || docker ps --filter "name=bots_"
|
||||||
|
|
||||||
|
echo "✅ All health checks passed"
|
||||||
|
|
||||||
|
- name: Send notification (optional)
|
||||||
|
if: always()
|
||||||
|
uses: appleboy/telegram-action@v1.0.0
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
message: |
|
||||||
|
🚀 Deployment ${{ job.status }}
|
||||||
|
|
||||||
|
Repository: prod
|
||||||
|
Branch: ${{ github.ref_name }}
|
||||||
|
Commit: ${{ github.sha }}
|
||||||
|
Author: ${{ github.actor }}
|
||||||
|
|
||||||
|
${{ job.status == 'success' && '✅ Deployment successful!' || '❌ Deployment failed!' }}
|
||||||
|
continue-on-error: true # Не падаем, если уведомление не отправилось
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
###########################################
|
###########################################
|
||||||
# Этап 1: Сборщик (Builder)
|
# Этап 1: Сборщик (Builder)
|
||||||
###########################################
|
###########################################
|
||||||
FROM python:3.9-slim as builder
|
FROM python:3.11.9-slim as builder
|
||||||
|
|
||||||
# Устанавливаем ТОЧНО ТОЛЬКО то, что нужно для компиляции
|
# Устанавливаем ТОЧНО ТОЛЬКО то, что нужно для компиляции
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
@@ -20,7 +20,7 @@ RUN pip install --no-cache-dir --target /install -r requirements.txt
|
|||||||
# Этап 2: Финальный образ (Runtime)
|
# Этап 2: Финальный образ (Runtime)
|
||||||
###########################################
|
###########################################
|
||||||
# Используем ОЧЕНЬ легковесный базовый образ
|
# Используем ОЧЕНЬ легковесный базовый образ
|
||||||
FROM python:3.9-alpine as runtime
|
FROM python:3.11.9-alpine as runtime
|
||||||
|
|
||||||
# В Alpine Linux свои пакеты. apk вместо apt.
|
# В Alpine Linux свои пакеты. apk вместо apt.
|
||||||
# Устанавливаем минимальные рантайм-зависимости
|
# Устанавливаем минимальные рантайм-зависимости
|
||||||
@@ -33,14 +33,14 @@ RUN addgroup -g 1000 app && \
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Копируем зависимости из сборщика (если есть)
|
# Копируем зависимости из сборщика (если есть)
|
||||||
COPY --from=builder --chown=1000:1000 /install /usr/local/lib/python3.9/site-packages
|
COPY --from=builder --chown=1000:1000 /install /usr/local/lib/python3.11/site-packages
|
||||||
# Копируем исходный код
|
# Копируем исходный код
|
||||||
COPY --chown=1000:1000 . .
|
COPY --chown=1000:1000 . .
|
||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
# Важно: явно указываем Python искать зависимости в скопированной директории
|
# Важно: явно указываем Python искать зависимости в скопированной директории
|
||||||
ENV PYTHONPATH="/usr/local/lib/python3.9/site-packages:${PYTHONPATH}"
|
ENV PYTHONPATH="/usr/local/lib/python3.11/site-packages:${PYTHONPATH}"
|
||||||
|
|
||||||
# Оставляем базовую команду для совместимости
|
# Оставляем базовую команду для совместимости
|
||||||
CMD ["python", "-c", "print('Dockerfile готов для использования')"]
|
CMD ["python", "-c", "print('Dockerfile готов для использования')"]
|
||||||
4
Makefile
4
Makefile
@@ -169,7 +169,7 @@ test-all: ## Запустить все тесты в одном процессе
|
|||||||
|
|
||||||
test-infra: check-deps ## Запустить тесты инфраструктуры
|
test-infra: check-deps ## Запустить тесты инфраструктуры
|
||||||
@echo "🏗️ Запускаю тесты инфраструктуры..."
|
@echo "🏗️ Запускаю тесты инфраструктуры..."
|
||||||
@python3 -m pytest tests/infra/ -v
|
@source .venv/bin/activate && python3 -m pytest tests/infra/ -v
|
||||||
|
|
||||||
test-bot: check-bot-deps ## Запустить тесты Telegram бота
|
test-bot: check-bot-deps ## Запустить тесты Telegram бота
|
||||||
@echo "🤖 Запускаю тесты Telegram бота..."
|
@echo "🤖 Запускаю тесты Telegram бота..."
|
||||||
@@ -227,7 +227,7 @@ check-ports: ## Проверить занятые порты
|
|||||||
|
|
||||||
check-deps: ## Проверить зависимости инфраструктуры
|
check-deps: ## Проверить зависимости инфраструктуры
|
||||||
@echo "🔍 Проверяю зависимости инфраструктуры..."
|
@echo "🔍 Проверяю зависимости инфраструктуры..."
|
||||||
@python3 -c "import pytest" 2>/dev/null || (echo "❌ Отсутствуют зависимости инфраструктуры. Установите: pip install pytest" && exit 1)
|
@source .venv/bin/activate && python3 -c "import pytest" 2>/dev/null || (echo "❌ Отсутствуют зависимости инфраструктуры. Установите: source .venv/bin/activate && pip install pytest" && exit 1)
|
||||||
@echo "✅ Зависимости инфраструктуры установлены"
|
@echo "✅ Зависимости инфраструктуры установлены"
|
||||||
|
|
||||||
check-bot-deps: ## Проверить зависимости Telegram бота
|
check-bot-deps: ## Проверить зависимости Telegram бота
|
||||||
|
|||||||
109
scripts/deploy-from-github.sh
Executable file
109
scripts/deploy-from-github.sh
Executable file
@@ -0,0 +1,109 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Скрипт для деплоя из GitHub Actions
|
||||||
|
# Используется на сервере для безопасного обновления
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PROJECT_DIR="/home/prod"
|
||||||
|
BACKUP_DIR="/home/prod/backups"
|
||||||
|
LOG_FILE="/home/prod/logs/deploy.log"
|
||||||
|
|
||||||
|
# Создаем директории если их нет
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
|
||||||
|
# Функция логирования
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "🚀 Starting deployment..."
|
||||||
|
|
||||||
|
# Переходим в директорию проекта
|
||||||
|
cd "$PROJECT_DIR" || exit 1
|
||||||
|
|
||||||
|
# Сохраняем текущий коммит
|
||||||
|
CURRENT_COMMIT=$(git rev-parse HEAD)
|
||||||
|
log "Current commit: $CURRENT_COMMIT"
|
||||||
|
|
||||||
|
# Создаем backup конфигурации перед обновлением
|
||||||
|
log "💾 Creating backup..."
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/backup-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||||
|
tar -czf "$BACKUP_FILE" \
|
||||||
|
infra/prometheus/prometheus.yml \
|
||||||
|
infra/grafana/provisioning/ \
|
||||||
|
docker-compose.yml \
|
||||||
|
2>/dev/null || true
|
||||||
|
log "Backup created: $BACKUP_FILE"
|
||||||
|
|
||||||
|
# Обновляем код
|
||||||
|
log "📥 Pulling latest changes..."
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
|
||||||
|
# Проверяем изменения
|
||||||
|
NEW_COMMIT=$(git rev-parse HEAD)
|
||||||
|
if [ "$CURRENT_COMMIT" = "$NEW_COMMIT" ]; then
|
||||||
|
log "ℹ️ No new changes to deploy"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "✅ Code updated: $CURRENT_COMMIT → $NEW_COMMIT"
|
||||||
|
|
||||||
|
# Проверяем синтаксис docker-compose
|
||||||
|
log "🔍 Validating docker-compose.yml..."
|
||||||
|
if ! docker-compose config > /dev/null 2>&1; then
|
||||||
|
log "❌ docker-compose.yml validation failed!"
|
||||||
|
log "🔄 Rolling back..."
|
||||||
|
git reset --hard "$CURRENT_COMMIT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Перезапускаем сервисы
|
||||||
|
log "🔄 Restarting services..."
|
||||||
|
if command -v make &> /dev/null; then
|
||||||
|
make restart
|
||||||
|
else
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ждем запуска сервисов
|
||||||
|
log "⏳ Waiting for services to start..."
|
||||||
|
sleep 20
|
||||||
|
|
||||||
|
# Health checks
|
||||||
|
log "🏥 Running health checks..."
|
||||||
|
|
||||||
|
HEALTH_CHECK_FAILED=0
|
||||||
|
|
||||||
|
# Prometheus
|
||||||
|
if curl -f http://localhost:9090/-/healthy > /dev/null 2>&1; then
|
||||||
|
log "✅ Prometheus is healthy"
|
||||||
|
else
|
||||||
|
log "❌ Prometheus health check failed"
|
||||||
|
HEALTH_CHECK_FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Grafana
|
||||||
|
if curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
|
||||||
|
log "✅ Grafana is healthy"
|
||||||
|
else
|
||||||
|
log "❌ Grafana health check failed"
|
||||||
|
HEALTH_CHECK_FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Если health check не прошел, откатываемся
|
||||||
|
if [ $HEALTH_CHECK_FAILED -eq 1 ]; then
|
||||||
|
log "❌ Health checks failed! Rolling back..."
|
||||||
|
git reset --hard "$CURRENT_COMMIT"
|
||||||
|
make restart || docker-compose restart
|
||||||
|
log "🔄 Rollback completed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "✅ Deployment completed successfully!"
|
||||||
|
log "📊 Container status:"
|
||||||
|
docker-compose ps || docker ps --filter "name=bots_"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -135,20 +135,24 @@ class TestPrometheusConfig:
|
|||||||
alertmanagers = alerting_config['alertmanagers']
|
alertmanagers = alerting_config['alertmanagers']
|
||||||
assert isinstance(alertmanagers, list), "alertmanagers should be a list"
|
assert isinstance(alertmanagers, list), "alertmanagers should be a list"
|
||||||
|
|
||||||
# Проверяем, что alertmanager закомментирован (не активен)
|
# Проверяем, что alertmanager настроен правильно
|
||||||
# Это нормально для тестовой среды
|
|
||||||
if len(alertmanagers) > 0:
|
if len(alertmanagers) > 0:
|
||||||
for am in alertmanagers:
|
for am in alertmanagers:
|
||||||
if 'static_configs' in am:
|
if 'static_configs' in am:
|
||||||
static_configs = am['static_configs']
|
static_configs = am['static_configs']
|
||||||
|
assert isinstance(static_configs, list), "static_configs should be a list"
|
||||||
for sc in static_configs:
|
for sc in static_configs:
|
||||||
if 'targets' in sc:
|
if 'targets' in sc:
|
||||||
targets = sc['targets']
|
targets = sc['targets']
|
||||||
# targets может быть None если все строки закомментированы
|
# targets может быть None если все строки закомментированы
|
||||||
if targets is not None:
|
if targets is not None:
|
||||||
# Проверяем, что все targets закомментированы
|
assert isinstance(targets, list), "targets should be a list"
|
||||||
|
# Проверяем, что targets не пустые и имеют правильный формат
|
||||||
for target in targets:
|
for target in targets:
|
||||||
assert target.startswith('#'), f"Alertmanager target should be commented: {target}"
|
assert isinstance(target, str), f"Target should be a string: {target}"
|
||||||
|
# Если target не закомментирован, проверяем формат
|
||||||
|
if not target.startswith('#'):
|
||||||
|
assert ':' in target, f"Target should have port: {target}"
|
||||||
|
|
||||||
def test_rule_files_section(self, prometheus_config):
|
def test_rule_files_section(self, prometheus_config):
|
||||||
"""Тест секции rule_files"""
|
"""Тест секции rule_files"""
|
||||||
@@ -159,9 +163,13 @@ class TestPrometheusConfig:
|
|||||||
if rule_files is not None:
|
if rule_files is not None:
|
||||||
assert isinstance(rule_files, list), "rule_files should be a list"
|
assert isinstance(rule_files, list), "rule_files should be a list"
|
||||||
|
|
||||||
# Проверяем, что все rule files закомментированы
|
# Проверяем, что rule files имеют правильный формат
|
||||||
for rule_file in rule_files:
|
for rule_file in rule_files:
|
||||||
assert rule_file.startswith('#'), f"Rule file should be commented: {rule_file}"
|
assert isinstance(rule_file, str), f"Rule file should be a string: {rule_file}"
|
||||||
|
# Если rule file не закомментирован, проверяем, что это валидный путь
|
||||||
|
if not rule_file.startswith('#'):
|
||||||
|
assert rule_file.endswith('.yml') or rule_file.endswith('.yaml'), \
|
||||||
|
f"Rule file should have .yml or .yaml extension: {rule_file}"
|
||||||
|
|
||||||
def test_config_structure_consistency(self, prometheus_config):
|
def test_config_structure_consistency(self, prometheus_config):
|
||||||
"""Тест консистентности структуры конфигурации"""
|
"""Тест консистентности структуры конфигурации"""
|
||||||
|
|||||||
Reference in New Issue
Block a user