Внедрение DevOps-культуры: от «это не наша проблема» до 50 деплоев в день

Внедрение DevOps-культуры: от «это не наша проблема» до 50 деплоев в день

Начальное состояние: диагноз

Помню, как к нам обратился CTO «ИнфоСистем». Он чётко описал всю боль одной фразой: «Разработчики кидают код через стену, а ops-команда ловит и ругается». Мы не стали гадать, а сразу взялись за дело — провели двухнедельный аудит. Вот какую картину мы тогда зафиксировали:

  • Разрозненные команды: 5 команд разработки, отдельная QA-команда, отдельный ops-отдел. Коммуникация через тикеты в Jira, среднее время ответа — 3 дня.
  • Ручные процессы: деплой — 47-шаговая инструкция в Confluence, выполняет один человек (Андрей). Если Андрей в отпуске — деплоя нет.
  • Культура обвинений: после каждого инцидента — поиск виноватого. Разработчики боялись деплоить, ops-команда боялась менять инфраструктуру.
  • Нет автоматических тестов: QA-команда тестировала вручную 2-3 недели перед каждым релизом.
  • Нет мониторинга: об инцидентах узнавали от пользователей через поддержку.

DORA-метрики на старте были, мягко говоря, катастрофическими:

МетрикаЗначениеУровень (DORA)
Deployment Frequency1 раз в кварталLow
Lead Time for Changes3 месяцаLow
Change Failure Rate45%Low
Mean Time to Recovery48 часовLow

Дорожная карта трансформации

Для начала мы расписали подробный план на 2 года, разбив его на 4 фазы. Наш главный принцип был такой: стартуем по-маленькому, быстро показываем классный результат, а потом уже постепенно масштабируем.

  • Фаза 1 (месяцы 1-3): Фундамент — CI/CD для одной пилотной команды, базовый мониторинг.
  • Фаза 2 (месяцы 4-8): Масштабирование — IaC, автотесты, cross-functional команды.
  • Фаза 3 (месяцы 9-16): Культура — blameless postmortems, shared on-call, внутренняя платформа.
  • Фаза 4 (месяцы 17-24): Оптимизация — метрики, self-service, полная автономия команд.

Фаза 1: пилотная команда и первый пайплайн

Для первого шага мы выбрали одну команду, всего 8 человек, которая работала над API-платформой. Это не был случайный выбор, нет. Их тимлид оказался очень открытым к новым идеям, да и сам продукт позволял деплоить изменения, не затрагивая остальные сервисы.

Первый CI/CD-пайплайн собрали за неделю:

# .gitlab-ci.yml — первая версия пайплайна
stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
  stage: test
  script:
    - docker run --rm $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA npm test

deploy-staging:
  stage: deploy
  script:
    - ssh deploy@staging "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA &&
        docker-compose up -d"
  environment:
    name: staging
  only:
    - main

Простой? Да. Но он работал. Команда начала деплоить на staging каждый день вместо раза в квартал. Через месяц мы добавили автоматический деплой в production с ручным подтверждением.

Параллельно мы подняли Prometheus + Grafana для базового мониторинга. Представляете, команда впервые своими глазами увидела в реальном времени, что вообще творится с их сервисом в продакшене! Это был настоящий прорыв.

Ломаем стены: кросс-функциональные команды

Ох, вот этот этап оказался самым болезненным. Мы предложили довольно радикальную реструктуризацию: вместо того чтобы иметь отдельные команды разработки, QA и ops, мы решили собрать кросс-функциональные продуктовые команды. В каждую такую команду мы включили:

  • 4-5 разработчиков (backend + frontend)
  • 1 QA-инженер (в команде, не в отдельном отделе)
  • 1 SRE/DevOps-инженер (по одному на 2-3 команды на первых порах)

Конечно, сопротивление было серьёзным, нешуточным. QA-руководитель был категорически уверен, что качество тут же упадёт. Ops-лид боялся, что потеряет весь контроль над ситуацией. Чтобы снять эти страхи, мы провели целую серию воркшопов:

  • «День в чужой шкуре»: разработчики дежурили с ops-командой, ops смотрели на процесс разработки.
  • Совместная ретроспектива: впервые все сидели в одной комнате и обсуждали проблемы, а не кидали тикеты.
  • Shared on-call: разработчики начали дежурить по своим сервисам. Это радикально изменило отношение к качеству кода — когда тебя будят в 3 ночи из-за бага, ты начинаешь писать код иначе.

Infrastructure as Code: Terraform

Инфраструктура «ИнфоСистем» была, мягко говоря, уникальной. Она жила в виде так называемых «серверов-снежинок» — то есть каждый был настроен вручную, и никто понятия не имел, какова полная конфигурация. Мы принялись за дело и начали переводить всё в Terraform:

# infrastructure/main.tf
terraform {
  required_providers {
    yandex = { source = "yandex-cloud/yandex" }
  }
  backend "s3" {
    bucket   = "infosystems-terraform-state"
    key      = "production/main.tfstate"
    endpoint = "storage.yandexcloud.net"
    region   = "ru-central1"
  }
}

resource "yandex_compute_instance" "api" {
  count       = var.api_instance_count
  name        = "api-${count.index}"
  platform_id = "standard-v3"

  resources {
    cores  = 4
    memory = 8
  }

  boot_disk {
    initialize_params {
      image_id = data.yandex_compute_image.ubuntu.id
      size     = 50
    }
  }

  network_interface {
    subnet_id = yandex_vpc_subnet.default.id
    nat       = true
  }

  metadata = {
    ssh-keys = "deploy:${file("~/.ssh/deploy.pub")}"
  }
}

Процесс перевода занял 4 месяца. Каждую неделю мы импортировали в Terraform очередной кусок инфраструктуры, пока вся среда не оказалась под управлением кода. Теперь любое изменение проходит через PR, code review и terraform plan в CI:

# CI pipeline для Terraform
plan:
  script:
    - terraform init
    - terraform plan -out=tfplan
    - terraform show -json tfplan > plan.json
  artifacts:
    paths: [tfplan, plan.json]

apply:
  script:
    - terraform apply -auto-approve tfplan
  when: manual    # Ручное подтверждение для production
  only:
    - main

Культура автоматического тестирования

Ручное QA-тестирование длилось 2-3 недели. Мы поставили цель: 80% покрытие автотестами за 6 месяцев. Звучит амбициозно, но мы шли итеративно.

Первым делом мы внедрили контрактные тесты. Это работает так: если, допустим, сервис A зависит от сервиса B, то мы фиксируем контракт между ними с помощью теста:

# tests/contracts/test_user_api_contract.py
import pytest
from pact import Consumer, Provider

pact = Consumer('BookingService').has_pact_with(Provider('UserService'))

def test_get_user():
    expected = {
        'id': '123',
        'email': Like('user@example.com'),
        'name': Like('Иван Петров'),
    }

    (pact
        .given('user 123 exists')
        .upon_receiving('a request for user 123')
        .with_request('GET', '/api/users/123')
        .will_respond_with(200, body=expected))

    with pact:
        result = user_client.get_user('123')
        assert result['id'] == '123'

Параллельно ввели правило: каждый новый PR содержит тесты. Старый код покрывали тестами при каждом изменении. За 6 месяцев покрытие выросло с 4% до 72%.

Blameless Postmortems

Вот это изменение повлияло на культуру сильнее всего:

# Шаблон postmortem

## Инцидент: [Название]
**Дата:** YYYY-MM-DD
**Длительность:** X часов
**Severity:** SEV-1/SEV-2/SEV-3
**Ответственный за разбор:** [Имя]

## Хронология
- HH:MM — Обнаружено (как?)
- HH:MM — Эскалация
- HH:MM — Причина найдена
- HH:MM — Fix задеплоен
- HH:MM — Мониторинг подтвердил восстановление

## Корневая причина
[Технический анализ, не «Вася забыл»]

## Что сработало хорошо
- [Что помогло быстрее решить]

## Что можно улучшить
- [Без имён, только процессы и системы]

## Action Items
- [ ] [Конкретное действие] — владелец: [команда] — срок: [дата]
- [ ] [Конкретное действие] — владелец: [команда] — срок: [дата]

Первые три postmortem прошли тяжело. Люди по привычке искали виноватого. Мы ввели правило: фасилитатор прерывает любую попытку обвинения фразой «Мы ищем системные причины, не людей». К пятому postmortem команды привыкли. К десятому — начали сами предлагать системные улучшения.

Internal Developer Platform

К 12-му месяцу у нас уже было 8 кросс-функциональных команд, и у каждой, конечно, свой пайплайн. И вот тут вылезла одна неожиданная проблема: каждая команда настраивала CI/CD как ей вздумается, конфигурации начали «разъезжаться», а ops-инженеры тратили уйму времени на поддержку всех этих 8 разных пайплайнов.

Мы взялись за эту проблему и построили Internal Developer Platform (IDP) — это такой набор инструментов, который берёт на себя всю сложность и скрывает её от разработчиков:

# service.yaml — декларативное описание сервиса
apiVersion: platform.infosystems.ru/v1
kind: Service
metadata:
  name: booking-api
  team: booking
spec:
  language: python
  version: "3.11"

  build:
    dockerfile: Dockerfile
    cache: true

  deploy:
    replicas:
      staging: 1
      production: 3
    resources:
      cpu: "500m"
      memory: "512Mi"
    healthCheck:
      path: /health
      interval: 30s

  dependencies:
    - postgres:15
    - redis:7

  monitoring:
    alerts:
      - type: error_rate
        threshold: 5%
        channel: "#booking-alerts"
      - type: latency_p99
        threshold: 500ms
        channel: "#booking-alerts"

Разработчик описывает сервис декларативно, а платформа генерирует Dockerfile, CI/CD пайплайн, Kubernetes-манифесты, мониторинг и алерты. Команды перестали думать об инфраструктуре и сфокусировались на продукте.

Метрики, которые имеют значение: DORA

С самого начала мы мерили четыре ключевые метрики DORA:

  • Deployment Frequency — как часто деплоим в production.
  • Lead Time for Changes — от коммита до production.
  • Change Failure Rate — процент деплоев, вызвавших инцидент.
  • Mean Time to Recovery — среднее время восстановления после инцидента.

Мы собирали эти метрики полностью автоматически — брали данные из GitLab, Kubernetes и, конечно, из системы инцидентов:

# scripts/dora_metrics.py
from datetime import datetime, timedelta
import gitlab
import json

gl = gitlab.Gitlab('https://gitlab.infosystems.ru', private_token=TOKEN)

def deployment_frequency(project_id, days=30):
    """Частота деплоев за последние N дней"""
    project = gl.projects.get(project_id)
    deployments = project.deployments.list(
        environment='production',
        updated_after=(datetime.now() - timedelta(days=days)).isoformat(),
        per_page=100
    )
    return len(deployments) / days  # деплоев в день

def lead_time(project_id, days=30):
    """Среднее время от merge в main до деплоя"""
    project = gl.projects.get(project_id)
    mrs = project.mergerequests.list(
        state='merged',
        updated_after=(datetime.now() - timedelta(days=days)).isoformat(),
        per_page=100
    )
    lead_times = []
    for mr in mrs:
        merged_at = datetime.fromisoformat(mr.merged_at)
        # Находим деплой, содержащий этот коммит
        deploy_time = find_deployment_time(project, mr.merge_commit_sha)
        if deploy_time:
            lead_times.append((deploy_time - merged_at).total_seconds() / 3600)
    return sum(lead_times) / len(lead_times) if lead_times else None

Работа с сопротивлением

Любая трансформация встречает сопротивление. Вот конкретные ситуации, с которыми мы столкнулись, и как их решали:

  • «У нас нет времени на тесты». Мы показали статистику: 45% деплоев вызывают инцидент, каждый инцидент стоит в среднем 16 человеко-часов. Инвестиция в тесты окупилась за 2 месяца.
  • «Ops потеряет работу». Мы переквалифицировали ops-инженеров в SRE. Вместо ручной настройки серверов они проектируют платформу. Зарплата выросла, работа стала интереснее.
  • «Мой менеджер не поддержит». Мы провели серию презентаций для менеджмента с конкретными цифрами: время выхода на рынок, стоимость инцидентов, скорость найма (инженеры хотят работать с современными практиками).
  • «Это работало 10 лет, зачем менять». Самые скептичные инженеры стали адвокатами перемен после того, как увидели результаты пилотной команды. Показывайте, а не рассказывайте.

Buy-in от руководства

Без поддержки CTO и CEO трансформация невозможна. Поэтому мы подготовили бизнес-кейс на языке, понятном руководству:

  • Сокращение Time-to-Market с 3 месяцев до 1 дня = конкурентное преимущество.
  • Снижение Change Failure Rate = меньше потерянных клиентов.
  • Автоматизация = тот же объём работы с меньшим количеством ручного труда (но не увольнения — переквалификация).
  • Привлечение талантов: сильные инженеры не хотят работать с устаревшим стеком.

В итоге CTO стал нашим главным спонсором этой трансформации. Он не просто согласился, а выделил 20% времени каждой команды на инфраструктурные улучшения и, что важно, сумел отстоять это решение перед самим CEO.

Хронология: 24 месяца трансформации

МесяцMilestone
1Аудит, дорожная карта, выбор пилотной команды
2Первый CI/CD пайплайн, базовый мониторинг
3Пилотная команда деплоит ежедневно на staging
5Terraform для 30% инфраструктуры
6Реструктуризация: 3 кросс-функциональные команды
8Terraform для 100% инфраструктуры, 5 команд с CI/CD
10Первый blameless postmortem, shared on-call
12IDP v1, все 8 команд деплоят самостоятельно
15Автотесты: покрытие 72%, контрактные тесты
18DORA-метрики на общем дашборде
20Self-service инфраструктура, feature flags
2450 деплоев в день, DORA Elite уровень

Результаты через 2 года

МетрикаДоПослеУровень DORA
Deployment Frequency1/квартал50/деньElite
Lead Time for Changes3 месяца2 часаElite
Change Failure Rate45%3%Elite
MTTR48 часов25 минутElite

DevOps — это, на мой взгляд, не просто Jenkins, не Kubernetes и уж точно не Terraform. Это прежде всего культура, где люди работают плечом к плечу ради одной важной цели: как можно быстрее и надёжнее доставлять реальную ценность нашим пользователям. Инструменты, безусловно, помогают, но без кардинальной смены культуры они остаются просто бесполезными. Если вы хотите запустить такую трансформацию у себя, знайте — мы всегда готовы помочь.

Нужна помощь с проектом?

Наши специалисты из АйТи Фреш с радостью помогут вам с архитектурой, DevOps, безопасностью и разработкой — у нас за плечами более 15+ лет опыта.

📞 Связаться с нами
#blameless#buyin#code#developer#devops#dora#infrastructure#internal
Комментарии 0

Оставить комментарий

загрузка...

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.