Практика 8. CI/CD и DevOps на практике
Цель работы
Освоить основы CI/CD с GitHub Actions: создание пайплайнов для линтинга, тестирования, сборки и публикации Docker-образов.
Часть 1. Git для CI/CD
Задание 1.1. Подготовка репозитория
-
Создайте новый каталог проекта:
Окно терминала mkdir -p ~/cicd-lab && cd ~/cicd-labgit init -
Создайте структуру проекта:
~/cicd-lab/├── .github/│ └── workflows/│ └── ci.yml├── src/│ └── app.py├── tests/│ └── test_app.py├── Dockerfile├── docker-compose.yml├── requirements.txt├── Makefile└── .gitignore -
Создайте
.gitignore:__pycache__/*.pyc.env.venv/*.egg-info/dist/build/
Задание 1.2. Приложение и тесты
Файл src/app.py:
from flask import Flask, jsonifyfrom datetime import datetimeimport os
app = Flask(__name__)
def get_greeting(name="World"): """Возвращает приветственное сообщение.""" if not name or not name.strip(): raise ValueError("Name cannot be empty") return f"Hello, {name}!"
def add(a, b): """Складывает два числа.""" return a + b
def is_even(n): """Проверяет, является ли число чётным.""" return n % 2 == 0
@app.route("/")def index(): return jsonify({ "message": get_greeting("DevOps"), "timestamp": datetime.now().isoformat(), "version": os.environ.get("APP_VERSION", "dev"), })
@app.route("/health")def health(): return jsonify({"status": "healthy"})
if __name__ == "__main__": port = int(os.environ.get("PORT", 5000)) app.run(host="0.0.0.0", port=port)Файл tests/test_app.py:
import pytestfrom src.app import get_greeting, add, is_even, app
class TestGreeting: def test_default_greeting(self): assert get_greeting() == "Hello, World!"
def test_custom_greeting(self): assert get_greeting("Student") == "Hello, Student!"
def test_empty_name_raises(self): with pytest.raises(ValueError): get_greeting("")
def test_whitespace_name_raises(self): with pytest.raises(ValueError): get_greeting(" ")
class TestMath: def test_add_positive(self): assert add(2, 3) == 5
def test_add_negative(self): assert add(-1, -2) == -3
def test_add_zero(self): assert add(0, 0) == 0
def test_is_even_true(self): assert is_even(4) is True
def test_is_even_false(self): assert is_even(3) is False
def test_is_even_zero(self): assert is_even(0) is True
class TestAPI: @pytest.fixture def client(self): app.config["TESTING"] = True with app.test_client() as client: yield client
def test_index(self, client): response = client.get("/") assert response.status_code == 200 data = response.get_json() assert "message" in data assert "timestamp" in data
def test_health(self, client): response = client.get("/health") assert response.status_code == 200 data = response.get_json() assert data["status"] == "healthy"Файл requirements.txt:
flask==3.0.0pytest==8.0.0flake8==7.0.0Задание 1.3. Проверка локально
cd ~/cicd-labpython3 -m venv .venvsource .venv/bin/activatepip install -r requirements.txt
# Линтингflake8 src/ tests/ --max-line-length=120
# Тестыpytest tests/ -v
deactivateЧасть 2. Dockerfile
Задание 2.1. Создание Dockerfile
FROM python:3.12-slim
LABEL maintainer="student@os-course"
ENV PYTHONUNBUFFERED=1ENV APP_VERSION=1.0.0
WORKDIR /app
COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
COPY src/ ./src/
RUN adduser --disabled-password --gecos "" appuserUSER appuser
EXPOSE 5000
CMD ["python", "src/app.py"]Задание 2.2. docker-compose.yml
services: web: build: . ports: - "8080:5000" environment: APP_VERSION: "1.0.0" restart: unless-stopped healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:5000/health || exit 1"] interval: 15s timeout: 5s retries: 3 start_period: 10sЗадание 2.3. Локальная проверка
docker compose up -d --buildcurl http://localhost:8080curl http://localhost:8080/healthdocker compose downЧасть 3. GitHub Actions
Задание 3.1. Базовый CI-пайплайн
Создайте файл .github/workflows/ci.yml:
name: CI Pipeline
on: push: branches: [main] pull_request: branches: [main]
jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- uses: actions/setup-python@v5 with: python-version: "3.12"
- name: Install dependencies run: pip install -r requirements.txt
- name: Run flake8 run: flake8 src/ tests/ --max-line-length=120
test: name: Test runs-on: ubuntu-latest needs: lint steps: - uses: actions/checkout@v4
- uses: actions/setup-python@v5 with: python-version: "3.12"
- name: Cache pip uses: actions/cache@v4 with: path: ~/.cache/pip key: pip-${{ hashFiles('requirements.txt') }} restore-keys: pip-
- name: Install dependencies run: pip install -r requirements.txt
- name: Run tests run: pytest tests/ -v --tb=short
build: name: Build Docker Image runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v4
- name: Build Docker image run: docker build -t cicd-lab:${{ github.sha }} .
- name: Test Docker image run: | docker run -d --name test-app -p 5000:5000 cicd-lab:${{ github.sha }} sleep 5 curl -f http://localhost:5000/health docker stop test-appЗадание 3.2. Объяснение структуры
Объясните каждый элемент workflow-файла:
| Элемент | Описание |
|---|---|
name | ? |
on | ? |
jobs | ? |
runs-on | ? |
needs | ? |
steps | ? |
uses | ? |
run | ? |
Задание 3.3. Коммит и пуш
cd ~/cicd-labgit add .git commit -m "Initial project with CI pipeline"
# Создайте репозиторий на GitHub, затем:git remote add origin https://github.com/YOUR_USERNAME/cicd-lab.gitgit branch -M maingit push -u origin mainПерейдите на GitHub → вкладка Actions → наблюдайте за выполнением пайплайна.
Часть 4. Расширенный пайплайн
Задание 4.1. Добавление публикации образа
Создайте файл .github/workflows/release.yml:
name: Release Pipeline
on: push: tags: ["v*"]
env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}
jobs: test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install -r requirements.txt - run: pytest tests/ -v
build-and-push: name: Build & Push Image runs-on: ubuntu-latest needs: test permissions: contents: read packages: write steps: - uses: actions/checkout@v4
- name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latestЗадание 4.2. Создание релиза
git tag -a v1.0.0 -m "Release 1.0.0: initial release"git push origin v1.0.0Перейдите на GitHub → Actions → убедитесь, что образ опубликован.
Часть 5. Makefile
Задание 5.1. Создание Makefile
.PHONY: install lint test build run stop clean
install: pip install -r requirements.txt
lint: flake8 src/ tests/ --max-line-length=120
test: pytest tests/ -v
build: docker compose build
run: docker compose up -d
stop: docker compose down
logs: docker compose logs -f
clean: docker compose down -v --rmi all find . -type d -name __pycache__ -exec rm -rf {} + find . -type f -name "*.pyc" -delete
all: lint test buildЗадание 5.2. Использование
make install # Установить зависимостиmake lint # Линтингmake test # Тестыmake build # Собрать Docker-образmake run # Запуститьmake logs # Логиmake stop # Остановитьmake clean # Полная очисткаmake all # lint + test + buildЧасть 6. Сканирование безопасности
Задание 6.1. Trivy
-
Установите Trivy:
Окно терминала sudo apt install -y wget apt-transport-https gnupg lsb-releasewget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.listsudo apt update && sudo apt install -y trivy -
Просканируйте собранный образ:
Окно терминала docker compose buildtrivy image cicd-lab-web:latest -
Просканируйте только критические уязвимости:
Окно терминала trivy image --severity CRITICAL cicd-lab-web:latest -
Добавьте сканирование в CI-пайплайн (добавьте step в
ci.ymlк jobbuild):- name: Scan image with Trivyuses: aquasecurity/trivy-action@masterwith:image-ref: cicd-lab:${{ github.sha }}format: tableseverity: CRITICAL,HIGH
Мини-проект: полный CI/CD цикл
Создайте проект с полным автоматизированным циклом:
- Приложение — Flask API с минимум 3 эндпоинтами и юнит-тестами.
- Dockerfile — с лучшими практиками (slim-образ, non-root, кэширование).
- docker-compose.yml — с healthcheck.
- Makefile — с целями
lint,test,build,run,clean. - CI-пайплайн (
.github/workflows/ci.yml):- Линтинг (flake8).
- Тестирование (pytest).
- Сборка Docker-образа.
- Сканирование на уязвимости (Trivy).
- Release-пайплайн (
.github/workflows/release.yml):- Триггер: push тега
v*. - Сборка и публикация образа в GitHub Container Registry.
- Триггер: push тега
Результат: при пуше в main — автоматический lint + test + build; при создании тега — публикация образа.
Контрольные вопросы
- Что такое DevOps? Расшифруйте принципы CALMS.
- Сравните стратегии ветвления: GitFlow, GitHub Flow, Trunk-Based Development.
- В чём разница между Continuous Delivery и Continuous Deployment?
- Опишите структуру workflow-файла GitHub Actions:
on,jobs,steps,needs. - Что такое runner в GitHub Actions? Чем GitHub-hosted отличается от self-hosted?
- Как безопасно передать секрет (токен, пароль) в CI/CD-пайплайн?
- Зачем кэшировать зависимости в CI? Как работает
actions/cache? - Назовите три столпа наблюдаемости (observability). Чем метрики отличаются от логов?
- Чем Terraform отличается от Ansible? Как они дополняют друг друга?
- Что такое DevSecOps и принцип «shift left»? Какие инструменты используются для сканирования Docker-образов?