Базы данных

MySQL в 2026 году: репликация, GTID и автоматический failover в продакшн-окружении с Kubernetes

Ruslan Ismailov Опубликовано 14 мин чтения
M

Введение: почему репликация MySQL остаётся актуальной в 2026 году

Несмотря на бурный рост NoSQL-решений и NewSQL-баз данных, MySQL сохраняет лидирующие позиции в продакшн-стеках крупных компаний. По данным DB-Engines за 2025–2026 годы, MySQL стабильно входит в топ-3 реляционных СУБД. Причины просты: зрелая экосистема, предсказуемое поведение под нагрузкой и огромное сообщество.

В контексте Kubernetes репликация MySQL решает сразу несколько критических задач:

  • Высокая доступность (HA) — при падении Primary автоматическое переключение на Replica минимизирует downtime.
  • Горизонтальное масштабирование чтения — read-реплики принимают SELECT-запросы, снижая нагрузку на Primary.
  • Бекапы без блокировок — снятие дампа с реплики не влияет на production-трафик.
  • Disaster Recovery — географически распределённые реплики защищают от datacenter-аварий.

В этой статье мы пройдём полный путь: от теории GTID до работающего кластера в Kubernetes с автоматическим failover и интеграцией в CI/CD.

Основы GTID-репликации: что изменилось и почему это важно

GTID (Global Transaction Identifier) — это уникальный идентификатор, присваиваемый каждой транзакции в MySQL. Формат: source_uuid:transaction_id, например 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100.

Отличия от классической binlog-репликации

В классической репликации реплика отслеживает позицию в бинарном логе (файл + offset). При failover администратор вручную определяет корректную позицию на новом Primary — это источник человеческих ошибок. GTID устраняет эту проблему: каждая транзакция имеет глобальный уникальный идентификатор, и реплика сама определяет, какие транзакции ещё не применила.

Ключевые преимущества GTID-репликации:

  • Автоматическое определение точки репликации при смене Primary.
  • Простота настройки Orchestrator и MySQL Operator для автоматического failover.
  • Гарантия отсутствия дублирующихся транзакций на реплике.
  • Встроенная поддержка в MySQL 5.6+ и полная зрелость в MySQL 8.0/8.4.

В MySQL 8.4 (LTS, 2024–2026) GTID-репликация стала стандартом де-факто: ряд устаревших параметров master_* полностью заменён на source_*.

Настройка MySQL Primary/Replica с GTID

Конфигурация my.cnf для Primary

[mysqld]
# Идентификатор сервера — уникален в кластере
server-id = 1

# Бинарный лог
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
binlog_row_image = FULL

# GTID
gtid_mode = ON
enforce_gtid_consistency = ON

# Производительность и надёжность
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1

# Репликация
binlog_expire_logs_seconds = 604800
max_binlog_size = 100M

# Параллельная репликация на репликах (MySQL 8.0+)
binlog_transaction_dependency_tracking = WRITESET
transaction_write_set_extraction = XXHASH64

Конфигурация my.cnf для Replica

[mysqld]
server-id = 2

# Реплика только для чтения
read_only = ON
super_read_only = ON

# GTID
gtid_mode = ON
enforce_gtid_consistency = ON

# Логирование на реплике (нужно для цепочки репликации)
log_bin = /var/log/mysql/mysql-bin.log
log_replica_updates = ON
binlog_format = ROW

# Параллельное применение транзакций
replica_parallel_workers = 4
replica_parallel_type = LOGICAL_CLOCK
replica_preserve_commit_order = ON

# Автоматический рестарт репликации при ошибке подключения
replica_net_timeout = 60

Инициализация реплики

Создаём пользователя репликации на Primary:

-- На Primary
CREATE USER 'replicator'@'%' IDENTIFIED WITH caching_sha2_password BY 'StrongPass!2026';
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'%';
FLUSH PRIVILEGES;

Снимаем дамп с Primary для инициализации реплики (без блокировки данных):

mysqldump \
  --single-transaction \
  --master-data=2 \
  --set-gtid-purged=ON \
  --all-databases \
  -u root -p > full_backup.sql

Восстанавливаем на реплике и запускаем репликацию:

-- На Replica после восстановления дампа
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='mysql-primary.mysql.svc.cluster.local',
  SOURCE_PORT=3306,
  SOURCE_USER='replicator',
  SOURCE_PASSWORD='StrongPass!2026',
  SOURCE_AUTO_POSITION=1;

START REPLICA;

-- Проверяем статус
SHOW REPLICA STATUS\G

Ключевые поля в выводе SHOW REPLICA STATUS: Replica_IO_Running: Yes, Replica_SQL_Running: Yes, Seconds_Behind_Source: 0, Retrieved_Gtid_Set и Executed_Gtid_Set должны совпадать при нулевой задержке.

Запуск MySQL-кластера в Kubernetes

Архитектурные компоненты

Для stateful-приложений в Kubernetes используют StatefulSets: они гарантируют стабильные сетевые идентификаторы (mysql-0, mysql-1) и привязку к конкретным Persistent Volumes. Это критично для MySQL, где каждый узел должен иметь уникальный server-id и стабильный hostname.

Headless Service

apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: mysql
  labels:
    app: mysql
spec:
  clusterIP: None  # Headless — без виртуального IP
  selector:
    app: mysql
  ports:
    - name: mysql
      port: 3306
      targetPort: 3306

ConfigMap с конфигурацией MySQL

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
  namespace: mysql
data:
  primary.cnf: |
    [mysqld]
    server-id=1
    log_bin=/var/log/mysql/mysql-bin.log
    binlog_format=ROW
    gtid_mode=ON
    enforce_gtid_consistency=ON
    log_replica_updates=ON
    binlog_transaction_dependency_tracking=WRITESET
  replica.cnf: |
    [mysqld]
    log_bin=/var/log/mysql/mysql-bin.log
    binlog_format=ROW
    gtid_mode=ON
    enforce_gtid_consistency=ON
    read_only=ON
    super_read_only=ON
    log_replica_updates=ON
    replica_parallel_workers=4
    replica_parallel_type=LOGICAL_CLOCK

StatefulSet

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
        - name: init-mysql
          image: mysql:8.4
          command:
            - bash
            - "-c"
            - |
              set -ex
              # Генерируем server-id на основе порядкового номера пода
              [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
              ordinal=${BASH_REMATCH[1]}
              echo [mysqld] > /mnt/conf.d/server-id.cnf
              echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
              # Копируем конфиг Primary или Replica
              if [[ $ordinal -eq 0 ]]; then
                cp /mnt/config-map/primary.cnf /mnt/conf.d/
              else
                cp /mnt/config-map/replica.cnf /mnt/conf.d/
              fi
          volumeMounts:
            - name: conf
              mountPath: /mnt/conf.d
            - name: config-map
              mountPath: /mnt/config-map
      containers:
        - name: mysql
          image: mysql:8.4
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: root-password
          ports:
            - name: mysql
              containerPort: 3306
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
            - name: conf
              mountPath: /etc/mysql/conf.d
          resources:
            requests:
              cpu: "500m"
              memory: "1Gi"
            limits:
              cpu: "2"
              memory: "4Gi"
          readinessProbe:
            exec:
              command: ["mysqladmin", "ping", "-u", "root", "-p$(MYSQL_ROOT_PASSWORD)"]
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
          livenessProbe:
            exec:
              command: ["mysqladmin", "ping", "-u", "root", "-p$(MYSQL_ROOT_PASSWORD)"]
            initialDelaySeconds: 60
            periodSeconds: 20
      volumes:
        - name: conf
          emptyDir: {}
        - name: config-map
          configMap:
            name: mysql-config
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 100Gi

Автоматический failover: инструменты и настройка

MySQL Operator for Kubernetes (Oracle)

MySQL Operator for Kubernetes — официальный оператор от Oracle, реализующий управление кластером InnoDB Cluster (MySQL Group Replication). В 2026 году это рекомендуемый подход для production-окружений.

Установка через Helm:

helm repo add mysql-operator https://mysql.github.io/mysql-operator/
helm repo update

helm install mysql-operator mysql-operator/mysql-operator \
  --namespace mysql-operator \
  --create-namespace \
  --set image.tag=8.4.0

Создание кластера через CRD:

apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
  name: mycluster
  namespace: mysql
spec:
  secretName: mysql-secret
  tlsUseSelfSigned: true
  instances: 3
  router:
    instances: 2
  datadirVolumeClaimTemplate:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 100Gi
    storageClassName: fast-ssd

MySQL Operator автоматически:

  • Настраивает Group Replication с автоматическим выбором Primary.
  • Деплоит MySQL Router для прозрачной маршрутизации запросов.
  • Выполняет failover при недоступности Primary (обычно за 5–30 секунд).
  • Управляет сертификатами TLS между узлами.

Orchestrator — альтернатива для классической репликации

Если вы используете классическую Primary/Replica репликацию (не Group Replication), Orchestrator от GitHub/Pinterest — проверенный инструмент для автоматического failover. Он строит топологию репликации, определяет недоступные узлы и автоматически промотирует лучшую реплику в Primary.

Развёртывание Orchestrator в Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: orchestrator
  namespace: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: orchestrator
  template:
    metadata:
      labels:
        app: orchestrator
    spec:
      containers:
        - name: orchestrator
          image: openarkcode/orchestrator:latest
          ports:
            - containerPort: 3000
          env:
            - name: ORC_TOPOLOGY_USER
              value: orchestrator
            - name: ORC_TOPOLOGY_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: orc-password
          volumeMounts:
            - name: orc-config
              mountPath: /etc/orchestrator
      volumes:
        - name: orc-config
          configMap:
            name: orchestrator-config

Ключевые параметры конфигурации Orchestrator для GTID-репликации:

{
  "AutomatedRecoveryMasterDetachLostReplicas": true,
  "RecoverMasterClusterFilters": ["*"],
  "RecoveryPeriodBlockSeconds": 3600,
  "FailMasterPromotionOnLagMinutes": 0,
  "DetachLostReplicasAfterMasterFailover": true,
  "MasterFailoverLostInstancesDowntimeMinutes": 0,
  "PostMasterFailoverProcesses": [
    "update-dns.sh --old={failedHost} --new={successorHost}"
  ]
}

Мониторинг репликации: метрики, Prometheus и алерты

mysqld_exporter для Prometheus

Для мониторинга MySQL в Kubernetes используем mysqld_exporter. Разворачиваем его как sidecar-контейнер или отдельный Deployment.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-exporter
  namespace: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql-exporter
  template:
    metadata:
      labels:
        app: mysql-exporter
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9104"
    spec:
      containers:
        - name: mysqld-exporter
          image: prom/mysqld-exporter:v0.15.1
          args:
            - --collect.slave_status
            - --collect.slave_hosts
            - --collect.info_schema.innodb_metrics
            - --collect.global_status
          env:
            - name: DATA_SOURCE_NAME
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: exporter-dsn
          ports:
            - containerPort: 9104

Ключевые метрики репликации

  • mysql_slave_status_seconds_behind_master — задержка реплики в секундах (переименована в mysql_replica_status_seconds_behind_source в новых версиях).
  • mysql_slave_status_slave_io_running — статус IO-потока репликации (1 = работает).
  • mysql_slave_status_slave_sql_running — статус SQL-потока репликации.
  • mysql_global_status_binlog_cache_disk_use — использование дискового кеша бинлога.
  • mysql_global_status_threads_running — активные потоки.

Правила алертинга для Prometheus Alertmanager

groups:
  - name: mysql-replication
    rules:
      - alert: MySQLReplicationLag
        expr: mysql_slave_status_seconds_behind_master > 30
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MySQL репликация отстаёт более чем на 30 секунд"
          description: "Под {{ $labels.pod }} имеет задержку {{ $value }}с"

      - alert: MySQLReplicationIOThreadDown
        expr: mysql_slave_status_slave_io_running == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "IO-поток репликации MySQL остановлен"

      - alert: MySQLReplicationSQLThreadDown
        expr: mysql_slave_status_slave_sql_running == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "SQL-поток репликации MySQL остановлен"

      - alert: MySQLReplicationNotRunning
        expr: mysql_slave_status_slave_io_running == 0 OR mysql_slave_status_slave_sql_running == 0
        for: 2m
        labels:
          severity: page
        annotations:
          summary: "КРИТИЧНО: репликация MySQL не работает"

Типичные проблемы репликации и их решение

1. Ошибка 1062: Duplicate entry

Возникает при нарушении уникального ключа на реплике. Причина — данные на реплике расходятся с Primary (например, из-за прямых записей на реплику). Решение: никогда не писать напрямую в реплику (super_read_only=ON). При возникновении — пропустить проблемную транзакцию через GTID:

-- Находим проблемный GTID в SHOW REPLICA STATUS\G
-- Пропускаем его:
STOP REPLICA;
SET GTID_NEXT='3E11FA47-71CA-11E1-9E33-C80AA9429562:101';
BEGIN; COMMIT;
SET GTID_NEXT='AUTOMATIC';
START REPLICA;

2. Большая задержка репликации под нагрузкой

При высоком write-трафике однопоточное применение транзакций на реплике создаёт бутылочное горлышко. Решение — многопоточная репликация:

SET GLOBAL replica_parallel_workers = 8;
SET GLOBAL replica_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL replica_preserve_commit_order = ON;

3. Потеря соединения между Primary и Replica

Увеличиваем таймаут и включаем автоматический реконнект:

CHANGE REPLICATION SOURCE TO
  SOURCE_CONNECT_RETRY=10,
  SOURCE_RETRY_COUNT=86400,
  SOURCE_HEARTBEAT_PERIOD=5;

4. Split-brain при failover

Опасная ситуация, когда старый Primary «воскресает» и начинает принимать записи параллельно с новым. Защита: использовать STONITH/fencing на уровне Kubernetes (PodDisruptionBudget), а также включить super_read_only=ON на всех узлах по умолчанию — Orchestrator/Operator снимает его только с актуального Primary.

5. Проблемы GTID при восстановлении из бекапа

При восстановлении дампа, созданного без --set-gtid-purged=ON, возникают конфликты. Всегда используйте этот флаг при создании дампов для репликации. Если GTID-множества конфликтуют, можно сбросить их:

RESET MASTER;
SET @@GLOBAL.gtid_purged='3E11FA47-71CA-11E1-9E33-C80AA9429562:1-500';

CI/CD интеграция: автоматические миграции схемы с учётом репликации

Миграции схемы в среде с репликацией требуют особого внимания. Типичная ошибка — запуск ALTER TABLE без учёта влияния на реплики и репликационный лаг.

Принципы безопасных миграций

  • Online DDL: используйте ALTER TABLE ... ALGORITHM=INPLACE, LOCK=NONE для MySQL 8.x или инструмент gh-ost, который выполняет миграцию через теневую таблицу и не блокирует Production.
  • Мониторинг лага перед миграцией: CI/CD пайплайн должен проверять Seconds_Behind_Source перед запуском ALTER. Если лаг больше порога — миграция откладывается.
  • Backward compatibility: добавляйте колонки как NULL или с дефолтом, чтобы новый код работал со старой схемой и наоборот.

Пример GitLab CI/CD шага с миграцией через gh-ost

migrate-schema:
  stage: deploy
  image: github/gh-ost:latest
  script:
    - |
      # Проверяем лаг репликации
      LAG=$(mysql -h $MYSQL_REPLICA_HOST -u root -p$MYSQL_ROOT_PASSWORD \
        -e "SHOW REPLICA STATUS\G" | grep Seconds_Behind_Source | awk '{print $2}')
      if [ "$LAG" -gt 10 ]; then
        echo "Replication lag is $LAG seconds, aborting migration"
        exit 1
      fi
    - |
      gh-ost \
        --host=$MYSQL_PRIMARY_HOST \
        --port=3306 \
        --user=root \
        --password=$MYSQL_ROOT_PASSWORD \
        --database=myapp \
        --table=orders \
        --alter="ADD COLUMN status_v2 TINYINT NOT NULL DEFAULT 0" \
        --execute \
        --max-lag-millis=1500 \
        --chunk-size=1000 \
        --ok-to-drop-table
  only:
    - main

Флаг --max-lag-millis заставляет gh-ost автоматически замедлиться или остановиться, если задержка репликации превышает порог — это защищает Production от деградации во время миграций.

Интеграция с Flyway / Liquibase

При использовании Flyway или Liquibase в Kubernetes запускайте миграции как Kubernetes Job с restartPolicy: Never, чтобы гарантировать однократное выполнение. Используйте таблицу блокировок (flyway_schema_history) — она реплицируется на все узлы и предотвращает двойной запуск.

Заключение и практические рекомендации

Настройка MySQL-репликации с GTID в Kubernetes в 2026 году — это зрелое, хорошо документированное решение с богатым инструментарием. Подведём итоги:

  • GTID — обязателен: не используйте позиционную репликацию в новых проектах. GTID упрощает failover, администрирование и интеграцию с операторами.
  • MySQL Operator for Kubernetes — рекомендуемый выбор для новых production-деплойментов. Group Replication с автоматическим failover «из коробки» экономит месяцы работы по автоматизации.
  • StatefulSets + headless services — правильная абстракция для stateful-приложений в Kubernetes. Не пытайтесь запустить MySQL в обычных Deployments.
  • Многопоточная репликация: replica_parallel_workers и LOGICAL_CLOCK критически важны под высокой нагрузкой.
  • Мониторинг — не опционален: настройте алерты на лаг репликации и остановку потоков до того, как проблема затронет пользователей.
  • gh-ost для DDL: блокирующие ALTER TABLE в production с репликацией — путь к инциденту. Всегда используйте онлайн-инструменты миграции.
  • Тестируйте failover регулярно: Chaos Engineering (например, Chaos Mesh в Kubernetes) должен включать сценарии убийства Primary и проверки времени восстановления.

Правильно настроенный MySQL-кластер в Kubernetes обеспечивает надёжность уровня 99.99% при минимальном операционном overhead — именно это делает его актуальным выбором для продакшн-окружений в 2026 году и далее.

Технологии

Теги

Руслан Исмаилов

Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →