✻ Урок 6.4 · Тема 6: Облако
Managed-сервисы: PostgreSQL, Kubernetes, балансировщик
Содержание урока
Зачем это нужно
На ВМ из прошлого урока PostgreSQL (база данных, где «Заметки» хранят тексты, см. урок 4.4) живёт в контейнере рядом с приложением. Если ВМ умрёт, диск потеряется или ты забудешь про бэкап, «Заметки» потеряют данные. Кто и когда обновляет базу? Кто проверяет, что копии вообще восстанавливаются? Кто ночью поднимет её, если хост сломался? Пока ответ «я, вручную, когда вспомню».
Облачные провайдеры продают решение: managed-сервис (управляемый сервис), то есть готовую базу данных, кластер Kubernetes (группу серверов, на которой запускают контейнеры и сами следят, чтобы они работали; тема 5) или балансировщик (load balancer: вход, который раскидывает входящие запросы по нескольким серверам, как администратор в банке, направляющий клиентов к свободным окнам; разберём ниже), которые провайдер сам устанавливает, обновляет, копирует и чинит. На работе почти всегда спрашивают одно и то же: что мы делаем сами, что отдаём провайдеру, и сколько это стоит в деньгах и в ночных дежурствах.
Важная оговорка: managed-сервис снимает часть эксплуатации (патчи: обновления с исправлениями ошибок и уязвимостей; бэкапы: запасные копии данных; переключение при поломке: автоматический переход на запасной сервер), но не всю. Подключение, права, схема, миграции и проверка восстановления остаются на тебе.
Шаг проекта: «Заметки» переезжают на Managed PostgreSQL: DATABASE_URL (одна строка с адресом базы, логином и паролем, по которой приложение находит БД; урок 4.5) получает sslmode=verify-full (режим, при котором приложение шифрует соединение и проверяет сертификат сервера: так его не подменят по дороге), из compose.prod.yml на проде уходит сервис db. Managed Kubernetes и балансировщик ты разбираешь по спецификации и цене, ничего не создавая. Код приложения не меняется (0.4.1).
Что нужно знать
- Урок 4.4: SQL и PostgreSQL:
psql(консольный клиент PostgreSQL), таблицы,SELECT,pg_dump(программа, которая выгружает базу в текстовый файл с командами SQL). - Урок 4.5: Compose, «Заметки» и PostgreSQL: строка подключения
DATABASE_URL, сервисdb, файл.env. - Урок 6.2: ВМ, сеть и диски: сеть, подсеть, security group (облачный файрвол: список разрешённых входящих портов), CLI
yc. - Урок 6.3: деплой на ВМ:
compose.prod.yml,deploy.sh, домен и HTTPS, пользовательdeploy. - Урок 5.4: Ingress и Gateway API: вход в кластер Kubernetes, пригодится для сравнения с балансировщиком.
Картина целиком
Сравни две ситуации.
- Свой дом. Ты сам чинишь крышу, меняешь трубы, следишь за отоплением и охраной. Всё под контролем, но ночью прорвало трубу, и разбираешься ты.
- Квартира в доме с управляющей компанией. Крышу, лифт, трубы и охрану обслуживает компания. Тебе остаётся содержать саму квартиру: мебель, ремонт внутри, кто к тебе заходит. Ты платишь за услугу, но не можешь перестроить несущую стену, потому что она не твоя.
Managed-сервис это «квартира»: провайдер отвечает за здание, ты за то, что внутри.
flowchart LR
subgraph P["Делает провайдер"]
P1["железо, диск, ОС, установка"]
P2["обновления PostgreSQL"]
P3["бэкапы и журнал WAL"]
P4["вторая копия (реплика) и failover"]
P5["метрики железа"]
end
subgraph Y["Остаётся тебе"]
Y1["сеть: кто может подключиться"]
Y2["пользователи, пароли, права"]
Y3["схема БД, индексы, миграции"]
Y4["медленные запросы, число соединений"]
Y5["проверка: бэкап восстанавливается"]
Y6["счёт за услугу, удаление лишнего"]
end
VM["ВМ notes-vm<br>nginx, notes, DATABASE_URL"] -->|"TLS, порт 6432,<br>только из своей security group"| DB["Managed PostgreSQL<br>мастер (зона A), реплика (зона B)"]
P --- DB
Y --- VM
Схема показывает только идею: что в управляемой базе делает провайдер, а что остаётся тебе. Каждый термин с неё (WAL, реплику, failover, пул соединений, TLS) мы разберём по одному в своём разделе ниже.
За урок ты разберёшь по порядку: что входит в управляемую базу, как устроены бэкапы и восстановление на момент времени, чем реплика отличается от бэкапа, как безопасно подключиться, как переносить данные, а затем в обзорном режиме Managed Kubernetes и балансировщики.
Теория
Что именно продаёт Managed-сервис и где проходит граница ответственности
Поставить PostgreSQL легко. Трудно годами держать его живым: вовремя ставить исправления безопасности, делать копии и проверять их, не дать диску заполниться, ночью переключаться на запасной хост. Для небольшой команды это отдельная профессия, а ошибка стоит данных. Провайдер делает то же самое для тысяч клиентов, поэтому автоматизировал это и продаёт как услугу.
Представь: квартира с управляющей компанией из раздела «Картина целиком». Оговорка: в квартире ты хозяин, а тут ты получаешь ограниченный доступ. Например, у тебя нет ключа от техэтажа (нет суперпользователя базы).
Managed PostgreSQL (управляемая PostgreSQL) это обычный PostgreSQL. Ты пользуешься теми же SQL, psql и pg_dump. Вокруг него провайдер построил автоматику. Что он берёт на себя:
- Установка и патчи (patching): выпуск исправлений безопасности и минорных версий (например, 18.1 на 18.2) ставится в окно обслуживания, которое ты выбираешь.
- Бэкапы (backup, резервные копии) и архив журнала WAL: смысл разберём в следующем разделе.
- HA (high availability, высокая доступность): вторая копия базы на другом хосте в другой зоне и автоматическое переключение при поломке первого хоста.
- Мониторинг железа, метрики и алерты, например «диск заполнен на 90%».
- Масштабирование: сменить размер хоста или расширить диск через API, а не переустановкой.
Что остаётся тебе (это модель разделённой ответственности (shared responsibility) из урока 6.1: провайдер отвечает за платформу, ты за то, что на ней размещаешь):
- Сеть: кто и откуда может подключиться к базе.
- Пользователи, пароли, права, схема БД, индексы, миграции (миграция: изменение структуры таблиц, оформленное как скрипт).
- Медленные запросы и число соединений: провайдер видит нагрузку, но не знает, что твой запрос читает всю таблицу.
- Проверка, что бэкап восстанавливается: копия, которую ни разу не восстанавливали, это гипотеза, а не защита.
- Расходы и удаление ненужных кластеров: кластер стоит денег каждый час, пока существует.
Пример: цена автоматики. У тебя нет суперпользователя postgres. Значит, нельзя выполнить любую команду администратора: CREATE EXTENSION работает только для расширений из списка провайдера, а настройки сервера и файл pg_hba.conf (правила «кто откуда и с каким шифрованием может подключаться») меняются только через API и консоль провайдера. Провайдер так защищает автоматику: если бы ты мог править всё, он не смог бы гарантировать переключение и обновления. Это осознанная цена.
Прикинь сам: ты включил HA-кластер из двух хостов. Кто-то выполнил
DROP TABLE notes(удалить таблицу). Спасёт ли реплика?
Нет. Реплика получает те же изменения, и DROP TABLE применится на ней тоже. HA защищает от падения хоста, а не от человеческой ошибки. От ошибки спасает PITR или бэкап: восстанавливаешь кластер на момент за минуту до DROP.
Осторожно: «Managed значит, что ничего не надо делать». На деле меняется набор задач: пропадают ночные патчи и настройка бэкапов, остаются права, миграции, запросы и деньги. И ещё: «HA это бэкап», разбираем ниже.
Главное: Managed PostgreSQL это обычный PostgreSQL: провайдер берёт установку, патчи, бэкапы, реплику и железо, а сеть, пароли, схема, запросы и проверка бэкапа остаются тебе.
Главная ценность управляемой базы это бэкапы. Разберём, как они устроены и как вернуться на нужный момент.
Бэкапы, WAL и PITR: как вернуться на любой момент времени
Бэкапы делает провайдер, так зачем это знать? Затем, что провайдер восстановит базу на любой момент, но выбрать момент («14:06, до кривой миграции») и проверить, что восстановление сработало, должен ты. Ошибки людей случаются: не тот DELETE, битая миграция, случайно удалённая таблица. Один бэкап раз в сутки означает потерю до 24 часов данных. Хочется вернуться на конкретную минуту: «за минуту до того, как всё сломали». Для этого и нужен PITR (point-in-time recovery, восстановление на момент времени).
Представь: кассовая лента и ежедневный отчёт. Раз в сутки бухгалтер печатает полный отчёт о состоянии кассы. А кассовая лента записывает каждую операцию в течение дня. Чтобы узнать состояние в 14:06, берёшь утренний отчёт и «проигрываешь» ленту до 14:06. Оговорка: если ленты нет, ты вернёшься только на момент отчёта.
PostgreSQL перед изменением данных записывает его в журнал предзаписи WAL (write-ahead log): последовательный файл «что и как поменялось». Изначально он нужен для восстановления после сбоя. Провайдер использует его дважды:
- Раз в сутки делает полный бэкап (базовую копию файлов базы).
- Непрерывно архивирует WAL во внешнее хранилище.
flowchart LR
A["03:00<br>полная копия"] --> B["WAL сохраняется непрерывно"]
B --> C["14:06<br>цель PITR"]
C --> D["14:07<br>DELETE без WHERE (ошибка)"]
D --> E["15:00"]
C -.-> R["Восстановление на 14:06 =<br>копия 03:00 + WAL с 03:00 до 14:06<br>в новый кластер"]
В 14:07 кто-то выполнил DELETE FROM notes без WHERE, удалив все строки. Ты запрашиваешь PITR на 14:06. Провайдер берёт утреннюю копию, накатывает журнал до 14:06 и создаёт новый кластер. Данные в нём такие, какими были в 14:06. Потерянные данные (RPO, recovery point objective): всё, что записано после выбранной точки. Если ты выбрал 14:06, а ошибка была в 14:07, ты теряешь только последнюю минуту. Время, за которое кластер поднимется (RTO, recovery time objective, целевое время восстановления): зависит от размера базы и измеряется минутами и больше, а не секундами.
Почему новый кластер, а не тот же? Потому что ты не знаешь заранее, та ли это точка. Старый кластер остаётся нетронутым: сверяешь данные в новом, потом переключаешь приложение (меняешь DATABASE_URL) или переносишь нужные строки. После восстановления в новом кластере нужно повторить то, что не входит в данные: security group, пользователей и настройки (об этом задание 4).
Прикинь сам: в 14:07 удалили данные, последний полный бэкап был в 03:00, WAL архивируется непрерывно. На какие моменты можно восстановиться и на какой лучше?
На любой момент между 03:00 и текущим (в пределах окна хранения WAL). Лучше всего на 14:06 или последнюю секунду до ошибки: потеряешь минимум. Без WAL можно было бы вернуться только на 03:00 и потерять весь день.
Осторожно: «Бэкап включён, значит, я защищён». Нет, пока ты не восстановил его хотя бы раз и не проверил данные. И второе: PITR возможен только в пределах окна хранения бэкапов и WAL (сколько дней хранить, задаётся в настройках кластера). Момент вне окна провайдер не восстановит.
Главное: полная копия плюс непрерывный журнал WAL дают восстановление на любой момент (PITR) в новый кластер; бэкап считается рабочим, только если его хоть раз восстановили.
Бэкап защищает от ошибки человека. А от поломки хоста защищает другая вещь, и их нельзя путать.
Реплика, HA и failover: почему это не бэкап
Хост может умереть: сломался диск, обслуживание провайдера, авария в дата-центре. Восстановление из бэкапа занимает минуты и часы. Хочется, чтобы базу подхватил заранее подготовленный запасной хост за десятки секунд.
Представь: запасной пилот в кабине. Основной потерял сознание, второй сразу берёт управление. Оговорка: если основной пилот по ошибке направил самолёт не туда, второй, повторяющий его действия, полетит туда же.
Реплика (replica) это второй хост, который получает все изменения основного (мастера, master) и применяет их у себя. Мастер записывает изменение в WAL и отправляет его реплике. Если реплика стоит в другой зоне доступности (AZ, availability zone: отдельная площадка облака со своим питанием и сетью), падение одной площадки не убьёт обе копии. При падении мастера провайдер повышает реплику до мастера (failover, переключение). FQDN кластера (полное DNS-имя c-<id кластера>.rw.mdb.yandexcloud.net: rw значит «мастер», имя всегда ведёт на текущий мастер; у отдельного хоста имя вида rc1a-....mdb.yandexcloud.net, и после failover он может оказаться репликой) остаётся тем же, приложению не нужно менять настройки, но открытые соединения оборвутся на десятки секунд, а затем клиент должен переподключиться сам.
Два вида реплик различают по задаче:
- HA-реплика: резерв на случай поломки мастера.
- Read replica (реплика для чтения): отдаёт
SELECTи снимает нагрузку с мастера. Но она отстаёт на доли секунды или дольше (replication lag, задержка репликации), поэтому читать с неё только что записанное нельзя: заметка, которую ты создал секунду назад, там может ещё не появиться.
Пример: сколько данных теряется при failover. Есть два режима репликации. При синхронной мастер подтверждает запись клиенту только после того, как реплика её получила: потерь нет (RPO около нуля), но каждая запись чуть медленнее. При асинхронной мастер отвечает клиенту сразу и отправляет изменения реплике «когда получится»: быстро, но если мастер умер за 0.5 секунды до отправки, эти 0.5 секунды записей потеряны. Отсюда у HA свой RPO, а у бэкапа свой.
Прикинь сам: у тебя HA-кластер и включены бэкапы. Мастер ночью сломался. Что произойдёт с приложением и что должно быть в коде приложения?
Провайдер повысит реплику, FQDN останется тем же, соединения оборвутся на десятки секунд. Приложение должно уметь переподключаться (повторять запрос, не держать «мёртвые» соединения). «Заметки» открывают соединение на каждый запрос, поэтому переподключение у них получается само: следующий запрос создаст новое.
Осторожно: Реплика это не бэкап. Реплика повторяет любые изменения, в том числе DROP TABLE и DELETE без WHERE: через долю секунды удаление появится и на ней. Защита от человеческой ошибки это PITR и бэкап. Защита от поломки железа это HA. Нужны обе.
Главное: реплика и HA спасают от смерти хоста, но повторяют и ошибки вроде
DROP TABLE, поэтому бэкап всё равно нужен.
База переживёт отказ хоста. Теперь разберём, как к ней безопасно подключаться.
Подключение: приватная сеть, порт 6432 и пул соединений
База не должна быть видна всему интернету: единственная защита публичной базы это пароль, а пароли подбирают ботами круглосуточно. Правило проектирования: база доступна только тем, кому нужна.
Представь: служебный вход в здание: снаружи его нет, зайти можно только из коридора, куда пускают по пропуску. Оговорка: пропуск (пароль) всё равно нужен, сетевая изоляция только первая линия.
У Managed-кластера два способа доступа:
- Приватный: у хоста нет публичного адреса. Подключиться можно только с ВМ из той же облачной сети (VPC, virtual private cloud: твой изолированный кусок сети облака) по внутреннему адресу и FQDN. Так делают в проде.
- Публичный: у хоста есть публичный IP. Удобно для отладки, но опасно.
Доступ ограничивает security group (урок 6.2): входящий TCP на порт БД разрешается только из security group ВМ, а не из 0.0.0.0/0 (то есть отовсюду). Ссылка на группу, а не на IP, удобна тем, что при замене ВМ адрес меняется, а принадлежность к группе остаётся.
Порт 6432 и пул соединений. В PostgreSQL каждое соединение это отдельный процесс на сервере: он занимает память и время на создание, поэтому тысячи соединений замедляют базу. Пул соединений (connection pooler) это посредник между приложениями и базой: он держит небольшое число постоянных соединений к базе и раздаёт их клиентам по очереди. В Yandex Managed PostgreSQL такой посредник (Odyssey, аналог pgbouncer) стоит перед базой, и клиенты подключаются к порту 6432. Это удобно нашему приложению: «Заметки» открывают новое соединение на каждый запрос, а с пулом дорогое соединение с самой базой уже готово.
flowchart LR
subgraph VM["ВМ notes-vm"]
R1["notes: запрос 1"]
R2["notes: запрос 2"]
R3["notes: запрос 3"]
end
R1 --> POOL
R2 --> POOL
R3 --> POOL
subgraph DB["Managed PostgreSQL"]
POOL["порт 6432: пул (Odyssey)<br>раздаёт соединения"] --> PG["несколько постоянных<br>соединений к PostgreSQL"]
end
Шифрование и режимы sslmode. Провайдер выдаёт хосту сертификат, подписанный своим центром сертификации (CA, урок 2.6). Клиентская библиотека PostgreSQL (libpq) управляет шифрованием параметром sslmode:
sslmode |
Шифрует | Проверяет, что сервер тот |
|---|---|---|
disable |
нет | нет |
require |
да | нет (подмена возможна) |
verify-ca |
да | сертификат выдан доверенным CA |
verify-full |
да | CA и имя хоста совпадают с сертификатом |
Для прода нужен verify-full и файл корневого сертификата провайдера (параметр sslrootcert).
Пример: что проверяет verify-full. Ты подключаешься к c-abc.rw.mdb.yandexcloud.net. Сервер присылает сертификат. Клиент делает две проверки, и обе должны пройти:
- Цепочка: сертификат подписан CA, чей корневой сертификат лежит у тебя в файле
yc-ca.pem? Если нет, ошибкаcertificate verify failed. - Имя: в сертификате перечислены имена, для которых он выдан. Есть ли среди них
c-abc.rw.mdb.yandexcloud.net, то есть то имя, к которому ты подключался? Если ты подключился по IP (10.128.0.23), имени в списке нет, и проверка провалится, хотя связь работает.
Режим require шифрует, но не делает этих проверок. Злоумышленник, который встал на пути трафика (атака «человек посередине», MITM, man-in-the-middle), покажет свой сертификат, ты его примешь и отдашь ему пароль в зашифрованном виде, но именно ему.
Прикинь сам: чем
verify-fullзащищает лучше, чемrequire?
require шифрует канал, но принимает любой сертификат. Атакующий на пути (MITM) подставит свой сертификат, и ты отдашь ему пароль. verify-full проверяет цепочку до доверенного CA и то, что имя в сертификате совпадает с хостом, к которому ты подключался.
Осторожно: «Шифрование включено, значит, безопасно». sslmode=require шифрует, но не проверяет собеседника. И ещё: «сертификат нужен серверу». Файл yc-ca.pem нужен клиенту, чтобы проверить сервер, поэтому он лежит на ВМ, а не у провайдера.
Главное: база доступна только из приватной сети и с TLS, подключаться надо через порт 6432 с пулом, а
verify-fullзащищает лучшеrequire, потому что проверяет и сертификат, и имя хоста.
Подключились к пустой базе. Теперь перенесём в неё данные «Заметок».
Миграция данных: pg_dump, последовательности и окно простоя
База «Заметок» живёт в контейнере на ВМ. Чтобы перейти на managed-кластер, данные надо перенести, ничего не потеряв и не сломав нумерацию записей.
Представь: переезд в новую квартиру. Можно перевезти вещи по описи (полный дамп), а можно просто пересадить мебель, «забыв» про счётчик номеров в журнале (последовательность). Тогда по новому адресу выдадут номер, который уже занят.
Полный pg_dump (программа, которая выгружает базу в текстовый файл, содержащий команды SQL: CREATE TABLE, COPY/INSERT, установка последовательностей) снимает копию, которую можно загрузить в другую базу через psql -f. Окно простоя нужно, потому что записи, сделанные между снятием дампа и переключением, потеряются: на время переноса запись останавливают.
Слово последовательность (sequence) требует пояснения. В таблице notes колонка id объявлена как serial: это скрытый счётчик, который выдаёт 1, 2, 3, ... для каждой новой строки. Он хранится отдельно от таблицы (notes_id_seq) и помнит «какой номер выдавать дальше».
В таблице 42 заметки с id от 1 до 42, счётчик показывает last_value = 42, следующий номер будет 43.
- Полный дамп содержит и строки, и команду
SELECT setval('notes_id_seq', 42, true): счётчик приедет в новую базу со значением 42. Новая заметка получитid = 43. - Выгрузка только строк (например, через
COPYв CSV) перенесёт 42 строки с ихid, но новый счётчик в новой базе останется на значении по умолчанию, то есть1. Первая же новая заметка получитid = 1, а такая строка уже есть: ошибкаduplicate key value violates unique constraint "notes_pkey". Лечится одной командойsetval, но узнаёшь о проблеме только когда пользователь не может сохранить заметку.
Две опции pg_dump, которые ты будешь использовать: --no-owner и --no-acl убирают из дампа привязку к владельцам и правам ролей, которых нет в managed-кластере (иначе загрузка упадёт с role "postgres" does not exist). При загрузке -v ON_ERROR_STOP=1 останавливает psql на первой ошибке (по умолчанию он молча идёт дальше), а --single-transaction заворачивает всю загрузку в одну транзакцию: либо загрузилось всё, либо ничего.
Если простой недопустим. Для больших баз используют логическую репликацию (logical replication): новая база подписывается на старую и получает поток изменений, пока идёт перенос. Ты сверяешь данные, на секунды останавливаешь запись, дожидаешься, пока новая база догонит, и переключаешься. Это сложнее и в уроке не делается, но знать, что такой вариант есть, нужно.
Прикинь сам: после переноса выгрузкой строк первая новая заметка падает с
duplicate key ... notes_pkey. Что сломано и как чинишь?
Последовательность notes_id_seq осталась на начальном значении и выдаёт id, которые уже заняты. Чинится выравниванием счётчика: SELECT setval(pg_get_serial_sequence('notes','id'), (SELECT max(id) FROM notes));. В следующий раз использовать полный pg_dump, который переносит последовательность.
Осторожно: «Сверил количество строк, значит, перенос удался». Проверь ещё максимальный id и значение последовательности: last_value не должно быть меньше max(id).
Главное: после переноса сверяй не только количество строк, но и последовательности: иначе новая запись упадёт с
duplicate key; окно простоя нужно, чтобы запись не терялась между дампом и переключением.
Про базы достаточно. Посмотрим на другую управляемую услугу: Kubernetes и балансировщик.
Managed Kubernetes и балансировщик: обзор
Kubernetes входит в урок, потому что это вторая крупная управляемая услуга после баз, и решать, брать ли её, приходится так же: по цене и по тому, что остаётся на тебе. Kubernetes ты разбирал в теме 5: система, которая запускает контейнеры на группе машин, перезапускает упавшие и раскладывает нагрузку. Управлять кластером Kubernetes самому тяжело (обновления, etcd, сертификаты). Managed Kubernetes (Yandex Managed Service for Kubernetes, в AWS это EKS) передаёт провайдеру самое сложное.
Представь: аренда офиса «под ключ»: провайдер отвечает за здание, электричество и ресепшен (control plane), а мебель, сотрудников и порядок в комнатах (узлы и приложения) организуешь ты.
Кластер состоит из двух частей:
- Control plane (управляющая часть): API-сервер, база etcd, планировщик. Их запускает, обновляет и чинит провайдер. Ты платишь за них отдельной строкой: зональный мастер (в одной зоне) дешевле, региональный с высокой доступностью (в нескольких зонах) дороже.
- Группы узлов (node groups): обычные ВМ, на которых работают твои pod’ы. Их ты оплачиваешь как ВМ. Версию узлов и приложения обновляешь ты, а провайдер только обновляет control plane и даёт удобный способ обновить узлы.
Пример: «Заметкам» кластер нужен? Минимальный кластер это платный control plane плюс не меньше двух-трёх узлов. Для одного маленького приложения (nginx, приложение, база) ВМ с Compose справляется дешевле и проще. Kubernetes оправдан, когда сервисов много, команда большая, нужны единые правила выкатки, автомасштабирование и самолечение. Кластер стоит денег с первой минуты, поэтому здесь мы считаем цену и пользу, а руками кластер ты уже собирал в теме 5.
Балансировщик (load balancer) принимает входящий трафик и распределяет его по нескольким бэкендам (серверам, которые обрабатывают запросы). Он также проверяет их здоровье: если бэкенд перестал отвечать, трафик на него не отправляется. Два вида:
- L4 (Network Load Balancer): работает на уровне TCP/UDP, не смотрит внутрь HTTP. Очень быстрый, подходит для баз данных и любого TCP-трафика.
- L7 (Application Load Balancer, ALB): понимает HTTP: маршрутизирует по имени хоста и пути (
/apiв одну группу,/в другую), завершает TLS, делает редиректы и добавляет заголовки. Это то же, что делает nginx или Gateway из урока 5.4, но управляемый. Настоящий IP клиента за L7 приходит в заголовкеX-Forwarded-For.
Ещё два сервиса каталога, знать по названию: CDN (content delivery network, кэш статики ближе к пользователю) и Cloud Functions (функции, которые запускаются по событию, платишь за выполнение). «Заметкам» они не нужны.
Квоты (quotas): у каждого облака есть лимиты на число ВМ, дисков, IP-адресов и кластеров на каталог. Упрёшься в них ночью, когда нужно срочно расширить кластер, поэтому проверяй заранее и запрашивай увеличение.
Прикинь сам: какой балансировщик выберешь для веб-приложения с маршрутами
/apiи/, и какой для PostgreSQL?
Для веб-приложения L7 (ALB): он понимает пути и завершает TLS. Для PostgreSQL L4 (NLB): это произвольный TCP, HTTP внутри нет.
Осторожно: «Managed Kubernetes значит, что мне не нужно знать Kubernetes». Провайдер чинит control plane, но твои манифесты, ресурсы pod’ов и обновления приложений остаются на тебе. Второе: «балансировщик и nginx это одно и то же». Похоже по функциям, но балансировщик облака управляется провайдером, стоит денег отдельно и живёт вне твоей ВМ.
Главное: Managed Kubernetes берёт на себя управляющие узлы, но знать Kubernetes всё равно нужно; L7-балансировщик понимает пути и заголовки, L4 только адреса и порты.
Вернёмся к базе: что делать, когда подключение не получается.
Как читать ошибки подключения: timeout, refused, auth failed
Почти любая авария с базой начинается с фразы «приложение не подключается». Причин десяток, и если чинить наугад, теряешь час. Текст ошибки уже сообщает, на каком «этаже» проблема: сеть, порт, шифрование или пароль. Нужно только уметь прочитать.
Представь: ты приходишь в офис. Если дороги нет (перекрыта улица), ты просто не доходишь и ждёшь. Если дошёл, но дверь заперта, тебе сразу говорят «закрыто». Если открыли, но охранник не нашёл тебя в списке, тебе говорят «вас нет в списке». Три разных ситуации, три разных ответа. Оговорка: в сети «перекрытая улица» выглядит как тишина, без объяснений, и это самое неприятное.
Подключение к базе проходит несколько этапов по порядку, и ошибка показывает, на каком этапе оно остановилось:
- Имя в адрес (DNS):
c-....rw.mdb.yandexcloud.netпревращается в IP. Ошибка:could not translate host name ... to address(не удалось найти адрес по имени). Причина: опечатка в имени или нет DNS. - Путь до хоста и порта (сеть, маршрут, файрвол, урок 2.3 и урок 2.7 и security group из урока 6.2). Если файрвол молча отбрасывает пакеты, клиент ждёт ответа и сдаётся по времени:
timeout expired(илиconnection timed out). Если хост достигнут, но на порту никто не слушает, хост сам отвечает отказом:connection refused. Разница важна: timeout это файрвол или маршрут, refused это «добрался, но порт закрыт». - Шифрование (TLS). Сервер требует TLS, а клиент пришёл без него:
no pg_hba.conf entry ... no encryption. Сертификат не прошёл проверку:certificate verify failed. Файла сертификата нет:root certificate file ... does not exist. Все эти ошибки означают, что сеть уже в порядке: сервер ответил текстом. - Логин и пароль:
password authentication failed for user "notes". Сеть и TLS в порядке. - База и права:
database "..." does not existилиpermission denied for table .... Аутентификация прошла. - Данные:
duplicate key value violates unique constraint. Всё работает, ломается конкретная запись.
Приложение после смены настроек пишет connection timed out. Идём по этажам сверху вниз. Этап 1 пройден: в тексте ошибки уже есть IP (10.128.0.23), значит имя разрешилось. Этап 2: ошибка «timed out» это классика файрвола. Проверяем порт с самой ВМ: timeout 5 bash -c "</dev/tcp/$PGHOST_NOTES/6432". Результат closed за 5 секунд без «refused» подтверждает подозрение. Смотрим security group кластера и находим: нет правила для группы ВМ. Этапы 3-6 даже не пришлось проверять: до них соединение просто не дошло. Если бы на этапе 2 команда вернула open, мы спускались бы дальше: смотрели текст от psql.
Важное следствие: чем «ниже» этаж, тем проще проверить (порт открыт или нет), и тем раньше надо проверять. А чем «выше», тем более точный текст сообщает сервер, на него стоит просто читать.
Прикинь сам:
psqlпечатаетconnection refused. Что это значит и стоит ли смотреть security group?
Хост достигнут, но на этом порту никто не слушает (или хост сам отказал). Файрвол, который молча отбрасывает пакеты, дал бы не «refused», а таймаут. Поэтому security group смотреть не первым делом: сначала проверь порт (6432, а не 5432), имя хоста и то, что кластер в статусе RUNNING.
Осторожно: «Таймаут значит, что база упала». Нет: база может быть абсолютно здорова, а пакеты не доходят из-за файрвола. И наоборот: ошибка пароля означает, что база работает и сеть в порядке, лезть в security group бесполезно.
Главное: ошибку подключения читай по этажам сверху вниз (имя, путь, TLS, пароль, права, данные):
timeoutэто файрвол или маршрут,refusedэто порт закрыт, аauth failedзначит, что сеть в порядке.
Пароль у нас фигурирует в ошибках. Теперь о том, как им правильно распоряжаться.
Пользователи, пароли и секреты
Пароль базы это ключ ко всем данным. Если он окажется в git, в логе или в переписке, кто угодно сможет прочитать или стереть таблицы. Поэтому нужно понимать, где пароль лежит, кто его видит и как его менять.
Представь: ключ от сейфа. Нельзя приклеивать его скотчем к сейфу (в репозиторий), нельзя раздавать копии всем (общий пользователь для всех сервисов), и нужно уметь быстро сменить замок, если ключ потерялся.
В Managed PostgreSQL пользователей создаёт провайдер по твоей команде (--user name=notes,password=...). Для приложения заводят отдельного пользователя с минимальными правами: только на свою базу. Пароль попадает в строку подключения DATABASE_URL, поэтому его прячут по слоям:
- в репозитории пароля нет (файл
.envв.gitignore, урок 4.5); - на ВМ он лежит в
/opt/notes/.env, владелецdeploy, права 600: читает только этот пользователь (иroot); - в истории команд его тоже быть не должно: поэтому в задании 5 пароль вводится через
read -rs, а не пишется в командной строке; - в выводе его скрывают:
sed 's#://[^@]*@#://***@#'заменяет логин и пароль в адресе на звёздочки.
Смена пароля устроена по шагам: (1) задать новый пароль в кластере (через yc managed-postgresql user update), (2) записать его в .env, (3) пересоздать контейнер notes (up -d), потому что переменные окружения читаются при создании контейнера. Между шагами 1 и 3 приложение получает ошибку аутентификации: смену делают в окно или с двумя пользователями по очереди.
Сколько у пароля из openssl rand -base64 24 вариантов? 24 байта это 192 бита, то есть 2 в степени 192 комбинаций, число из 58 цифр. Подобрать его перебором нереально. Слабое место не пароль, а то, где он лежит: .env с правами 644 сделал бы его доступным любому пользователю на ВМ.
Долг, который остаётся. Хранение пароля в файле на диске это компромисс уровня учебного проекта. Следующий шаг в реальной работе: хранилище секретов (Yandex Lockbox, HashiCorp Vault, Kubernetes Secrets с внешним менеджером): приложение получает пароль при запуске, он не лежит рядом с кодом и меняется централизованно. Курс возвращается к этому позже.
Прикинь сам: ты поменял пароль пользователя
notesв кластере, но не менял.env. Что будет с приложением?
Новые соединения получат password authentication failed for user "notes", /readyz станет 503. Соединения, открытые до смены, могут пока работать, но «Заметки» открывают соединение на каждый запрос, так что ошибка проявится быстро. Чинится записью нового пароля в .env и docker compose up -d (пересоздание контейнера).
Осторожно: «.env в .gitignore, значит, пароль в безопасности». Нет: он защищён от git, но не от чужого доступа на ВМ и не от утечки через логи или дампы. Защита слоёв, а не одна галочка.
Главное: пароль базы лежит в защищённом
.envили секрете и не попадает в git и логи; после смены пароля в кластере его надо поменять и у приложения.
Теперь о деньгах: когда managed оправдывает свою цену.
Managed или свой PostgreSQL: как считать
Managed стоит дороже железа за тот же размер. Вопрос «стоит ли платить» решается не на глаз, а сравнением всех статей: деньги, время людей и риск.
Представь: такси и своя машина. Своя дешевле в километре, но требует страховки, ремонта и парковки. Такси дороже, но ты не думаешь о поломках. Для редких поездок такси выигрывает, для ежедневных пробегов может проиграть. Оговорка: в отличие от машины, базу нельзя «не заводить в гараж»: она работает круглосуточно.
Сравни по пунктам:
| Статья | Managed | Свой PostgreSQL на ВМ |
|---|---|---|
| Деньги за хост | выше (включает автоматику) | ниже |
| Патчи, обновления | провайдер, в окно обслуживания | ты |
| Бэкапы и PITR | включены, настраиваются | настраиваешь и проверяешь сам |
| HA и failover | включается опцией | строишь сам (Patroni и подобное) |
| Контроль | нет суперпользователя, часть настроек закрыта | полный |
| Расширения | из списка провайдера | любые |
| Ночные дежурства | провайдер берёт железо, ты остаёшься за запросы | всё на тебе |
Пример: SLA и единая точка отказа. SLA (service level agreement, соглашение об уровне сервиса) у провайдера обычно выражается в процентах доступности. Например, 99.9% в месяц: месяц это 30 × 24 × 60 = 43 200 минут. Допустимый простой равен 43 200 × 0.001 = 43.2 минуты. Для 99.99% это 4.32 минуты. Реальная доступность «Заметок» это произведение доступностей цепочки: если ВМ даёт 99.9%, а база 99.9%, то приложение целиком не лучше 0.999 × 0.999 = 0.998, то есть 99.8% (около 86 минут простоя в месяц). Поэтому одного HA у базы мало: слабое звено ВМ с единственной копией приложения остаётся единой точкой отказа (SPOF, single point of failure: компонент, поломка которого останавливает всё). Managed-база не делает сервис надёжнее сама по себе: она убирает одну из точек.
Итоговое решение для «Заметок»: managed, потому что нет человека, который восстанавливал бы PostgreSQL ночью, а данные нужны. Для учебного проекта на выходных self-hosted был бы разумнее по цене.
Прикинь сам: база даёт 99.9% в месяц, приложение на одной ВМ тоже 99.9%. Какая примерно доступность у сервиса и сколько минут простоя в 30-дневном месяце допускается?
0.999 × 0.999 = 0.998001, то есть около 99.8%. Допустимый простой (1 − 0.998001) × 43 200 ≈ 86 минут. Это вдвое больше, чем у каждого звена по отдельности: последовательные компоненты складывают свои простои.
Осторожно: «Managed дороже, значит невыгодно». Сравнивай полную стоимость: час инженера на восстановление базы стоит дороже, чем месяц небольшого кластера. И обратное: «managed всё решит»: он не спасёт от плохих запросов и плохой схемы.
Главное: сравнивай полную стоимость: цена сервиса против часов инженера, риска потери данных и простоя, а не только цену железа.
Managed не отменяет обслуживание: у него своё окно.
Окно обслуживания и версии PostgreSQL
Патчи и обновления нужны, но иногда требуют перезапуска хоста. Автоматика провайдера не знает, когда у тебя пик нагрузки, поэтому просит указать время.
В настройках кластера задаётся окно обслуживания (maintenance window): день недели и час, когда провайдер вправе ставить исправления и перезапускать хост. Для одного хоста перезапуск значит короткий простой базы (десятки секунд), для HA-кластера провайдер сначала обновляет реплику, потом переключается: простой минимален. Версии PostgreSQL делятся на минорные (18.1 на 18.2: исправления, обратно совместимы, ставятся сами) и мажорные (17 на 18: меняют внутренний формат, обновление запускаешь ты, заранее проверив приложение на копии базы). Поддержка старой мажорной версии провайдером ограничена по времени, поэтому её нужно планировать заранее.
Кластер notes-pg на одном хосте, окно «воскресенье 03:00». Ночью в воскресенье провайдер ставит 18.2 и перезапускает хост: 30 секунд connection refused из-за перезапуска. Приложение это переживёт, если оно (или пользователь) повторит запрос. Если окно оставить по умолчанию (случайное время в рабочий день), простой попадёт на клиентов.
Осторожно: Мажорное обновление не происходит «само в окне обслуживания»: его запускает человек. И обратный откат мажорной версии невозможен без восстановления из бэкапа.
Главное: окно обслуживания задаёт, когда провайдер может перезапустить хост ради патчей, а мажорное обновление версии запускает только человек.
И напоследок, что стоит держать под наблюдением самому.
Что смотреть в managed-базе: метрики и алерты
Провайдер следит за железом, но не знает, что для тебя «плохо». Диск на 95% провайдер не считает аварией, а через час база встанет на запись. Поэтому несколько показателей нужно знать и подписаться на них.
Представь: приборная панель автомобиля. Механик в сервисе смотрит двигатель, но лампочку «мало топлива» ты видишь сам и решаешь, когда заправляться.
Провайдер отдаёт метрики кластера (CPU, память, диск, число соединений, задержка репликации) в консоль и в свой мониторинг, и по ним можно настроить алерты (оповещение, когда значение вышло за порог). Минимальный набор:
- Свободное место на диске. PostgreSQL при заполнении диска переходит в режим «только чтение» или падает. Порог: 80% предупреждение, 90% срочно. Диск расширяется через API без остановки, но не мгновенно.
- Число соединений против лимита. У кластера есть максимум (
max_connections); при его достижении новые соединения получаютtoo many connections. - CPU и долгие запросы. Постоянные 100% CPU обычно значат отсутствие индекса или запрос, читающий всю таблицу, а не «мало железа».
- Replication lag у HA и read-реплик: растущая задержка значит, что после failover потеряется больше данных (RPO растёт).
- Успешность бэкапов. Не пропал ли последний бэкап: без свежей копии PITR невозможен.
Диск 10 ГБ, база растёт на 200 МБ в сутки, сейчас занято 7 ГБ. До порога 90% (9 ГБ) остаётся 2 ГБ: 2000 МБ / 200 МБ в сутки = 10 суток. Алерт на 80% (8 ГБ) сработает через 5 суток и оставит ещё 5 суток на решение (расширить диск, почистить старые данные). Без алерта об этом узнают по ошибке No space left on device.
Прикинь сам: диск 20 ГБ, занято 15 ГБ, рост 500 МБ в сутки. Через сколько суток будет 90%?
90% от 20 ГБ это 18 ГБ. Осталось 18 − 15 = 3 ГБ = 3000 МБ. 3000 / 500 = 6 суток. Значит, алерт на 80% (16 ГБ) сработает через 2 суток, и на решение будет ещё 4.
Осторожно: «Managed значит провайдер мониторит вместо меня». Провайдер мониторит доступность хостов, а насыщение диска, соединений и медленные запросы остаются на тебе: их видно только в твоих метриках.
Главное: провайдер следит за доступностью железа, а диск, соединения и медленные запросы с алертами настраиваешь ты.
Осталось сопоставить названия с AWS.
Соответствие AWS
В статьях и на собеседованиях те же услуги называют по-другому, поэтому сопоставим.
| Что | Yandex Cloud | AWS |
|---|---|---|
| Managed PostgreSQL | Managed Service for PostgreSQL | RDS for PostgreSQL / Aurora PostgreSQL |
| Managed Kubernetes | Managed Service for Kubernetes | EKS |
| Балансировщик L4 | Network Load Balancer | NLB |
| Балансировщик L7 | Application Load Balancer | ALB |
| CDN | Cloud CDN | CloudFront |
| Функции | Cloud Functions | Lambda |
| Пул соединений | Odyssey в кластере (порт 6432) | RDS Proxy |
| Фильтр доступа | Security group | Security group |
| Корневой CA | CA.pem из хранилища Yandex |
global-bundle.pem для RDS |
| Восстановление | из бэкапа или PITR в новый кластер | restore to point in time в новый инстанс |
Прикинь сам: в вакансии просят опыт с RDS и EKS. Какие услуги Yandex Cloud из этого урока им соответствуют?
RDS соответствует Managed PostgreSQL (Managed Service for PostgreSQL), EKS соответствует Managed Kubernetes. Принципы те же: провайдер ведёт железо и патчи, настройка доступа и схема остаются тебе.
Главное: Managed PostgreSQL это аналог RDS, Managed Kubernetes аналог EKS, балансировщики соответствуют ALB и NLB.
Практика
Нужны: облако Yandex Cloud из урока 6.1, ВМ notes-vm с работающими «Заметками» из урока 6.3, настроенный yc на твоём компьютере. Managed PostgreSQL платный даже минимальный (небольшие деньги в час, но не ноль): кластер удалишь в конце урока. Если облака нет, задания 1-4 читаются как разбор по выводам, а письменная часть задания 5 выполняется целиком.
Как входить на ВМ. Как в уроке 6.3, у тебя два пользователя. yc-user (админ, sudo работает) нужен для установки пакетов и файлов в /etc. deploy владеет каталогом /opt/notes и запускает Docker. В примерах адрес ВМ 203.0.113.10 (документационный, подставь свой):
# Админ: пакеты и системные файлы
ssh -i ~/.ssh/notes_ed25519 yc-user@203.0.113.10
# Деплой-пользователь: docker compose, .env
ssh -i ~/.ssh/notes-deploy deploy@203.0.113.10
Под deploy перед любой командой docker compose в этом уроке выполни (файлы и тег те же, что использует deploy.sh):
cd /opt/notes
export COMPOSE_FILE=compose.yml:compose.prod.yml NOTES_TAG=$(cat /opt/notes/.current-tag)
Разбор: COMPOSE_FILE=a:b задаёт список файлов Compose (двоеточие разделяет). NOTES_TAG подставляется в image:. $(cat /opt/notes/.current-tag) берёт тег, который записал последний успешный деплой (у тебя это 0.4.1). Без NOTES_TAG Compose остановится с сообщением нужен NOTES_TAG.
Задание 1. Кластер Managed PostgreSQL
Цель: создать минимальный кластер в той же сети, что и ВМ, и закрыть доступ security group.
Предскажи: после команды create можно сразу подключаться?
Ответ
Нет. Команда запускает долгую операцию: создание хоста и диска занимает минуты. Пока статус не RUNNING, подключаться нельзя.
Шаги (на твоём компьютере, где настроен yc):
- Возьми имена сети, подсети и зоны из урока 6.2 (в примере сеть
notes-net, подсетьnotes-subnet-a, зонаru-central1-a; подставь свои). Пароль сгенерируй и держи в переменной сессии:
# Пароль только в переменной текущей сессии, в файл не пишем
export PGPASS_NOTES="$(openssl rand -base64 24 | tr -d '=+/')"
# Минимальный кластер: один хост, диск 10 ГБ, без публичного адреса
yc managed-postgresql cluster create \
--name notes-pg \
--environment production \
--network-name notes-net \
--postgresql-version 18 \
--resource-preset s2.micro \
--disk-type network-ssd --disk-size 10 \
--host zone-id=ru-central1-a,subnet-name=notes-subnet-a \
--user name=notes,password="$PGPASS_NOTES" \
--database name=notes,owner=notes
Разбор команды. openssl rand -base64 24 даёт 24 случайных байта в текстовом виде (base64: буквы, цифры и символы + / =). tr -d '=+/' удаляет эти три символа, потому что пароль потом попадёт в адрес подключения, где они ломают разбор. --environment production это метка типа среды (влияет на настройки по умолчанию, например защиту от удаления). --resource-preset s2.micro размер хоста (ядра и память), --disk-type network-ssd --disk-size 10 сетевой SSD на 10 ГБ. --host zone-id=...,subnet-name=... один хост в зоне и подсети: публичный адрес не запрошен, значит его нет. --user и --database создают пользователя notes и базу notes с ним же во владельцах.
Пресет и версию проверь в списках провайдера: yc managed-postgresql resource-preset list и yc managed-postgresql cluster list-versions. Если версии 18 нет, выбери самую новую и запомни номер.
- Дождись статуса и посмотри хост:
# Ждём RUNNING
yc managed-postgresql cluster get notes-pg | grep -E '^(name|status|health)'
yc managed-postgresql hosts list --cluster-name notes-pg
# FQDN кластера: он всегда ведёт на текущий мастер
PG_ID=$(yc managed-postgresql cluster get notes-pg --format json | jq -r .id)
echo "c-$PG_ID.rw.mdb.yandexcloud.net"
- Создай отдельную security group для БД: входящий TCP 6432 только из группы ВМ, и привяжи её к кластеру.
# id группы ВМ: она называется notes-sg (урок 6.2)
VM_SG=$(yc vpc security-group get notes-sg --format json | jq -r .id)
yc vpc security-group create --name notes-pg-sg --network-name notes-net \
--rule "direction=ingress,port=6432,protocol=tcp,security-group-id=$VM_SG"
# Привязываем группу к кластеру
PG_SG=$(yc vpc security-group get notes-pg-sg --format json | jq -r .id)
yc managed-postgresql cluster update notes-pg --security-group-ids "$PG_SG"
Разбор: --format json | jq -r .id вытаскивает из ответа только поле id (-r без кавычек). --rule "...security-group-id=$VM_SG" разрешает вход на порт 6432 всем, кто состоит в группе ВМ, вместо IP-адресов. Про security group см. урок 6.2.
Что должно получиться:
name: notes-pg
status: RUNNING
health: ALIVE
Как читать вывод: status: RUNNING значит, что кластер создан и работает; пока там CREATING, подключаться рано. health: ALIVE значит, что хосты отвечают на проверки провайдера. Идентификаторы и FQDN у тебя будут другими. FQDN кластера (c-<id>.rw.mdb.yandexcloud.net, у тебя свой id) понадобится дальше как PGHOST_NOTES: в отличие от имени хоста из hosts list (rc1a-....), оно переживает failover.
Объясни себе:
- Почему у хоста нет публичного адреса и как к нему всё равно попадёт ВМ?
- Почему правило ссылается на security group ВМ, а не на IP ВМ?
- Что сломается, если создать кластер в другой сети?
Типичные ошибки:
ERROR: rpc error: code = ResourceExhausted desc = Quota limit ... exceeded: превышена квота (кластеры или ядра): освободи ресурсы или запроси увеличение в консоли.ERROR: rpc error: code = InvalidArgument desc = ... password ...: пароль не подошёл по требованиям (длина): сгенерируй заново.ERROR: rpc error: code = NotFound desc = Subnet ... not found: имя подсети не то или она в другой зоне: сверься сyc vpc subnet list.
Задание 2. Подключение с ВМ по TLS
Цель: зайти в БД с notes-vm через psql с sslmode=verify-full.
Предскажи: что произойдёт при sslmode=verify-full без корневого сертификата? А если подключаться по IP хоста, а не по FQDN?
Ответ
Без сертификата: root certificate file "..." does not exist. По IP: проверка имени провалится, потому что в сертификате записан FQDN, а не адрес.
Шаги:
- Зайди на ВМ админом (
yc-user) и поставь клиент:
ssh -i ~/.ssh/notes_ed25519 yc-user@203.0.113.10
sudo apt-get update && sudo apt-get install -y postgresql-client
psql --version
Разбор: postgresql-client это пакет с psql и pg_dump; сервер PostgreSQL ставить не нужно. -y отвечает «да» на вопросы apt.
- Скачай корневой сертификат Yandex в каталог конфига «Заметок». Сертификат публичный, права 644 подходят (про права см. урок 1.3): читать могут все, писать только
root.
sudo mkdir -p /etc/notes/tls
sudo curl -fsSL -o /etc/notes/tls/yc-ca.pem https://storage.yandexcloud.net/cloud-certs/CA.pem
sudo chmod 644 /etc/notes/tls/yc-ca.pem
# Проверка, что это сертификат, а не HTML со страницей ошибки
openssl x509 -in /etc/notes/tls/yc-ca.pem -noout -subject -enddate
Разбор: в curl флаг -f даёт ошибку при HTTP-коде 400 и выше (вместо сохранения страницы ошибки в файл), -s без индикатора, -S но с текстом ошибки, -L идти по редиректам, -o куда сохранить. openssl x509 -noout -subject -enddate читает сертификат и печатает только владельца и дату окончания.
- Выйди и зайди как
deploy, чтобы подключаться теми же правами, что у приложения. Пароль введёшь по запросу; FQDN возьми из выводаechoвыше:
exit
ssh -i ~/.ssh/notes-deploy deploy@203.0.113.10
export PGHOST_NOTES='<FQDN-кластера>'
psql "host=$PGHOST_NOTES port=6432 dbname=notes user=notes sslmode=verify-full sslrootcert=/etc/notes/tls/yc-ca.pem" \
-c "SELECT version();" \
-c "SELECT ssl, version FROM pg_stat_ssl WHERE pid = pg_backend_pid();"
Разбор: в кавычках строка подключения из пар ключ=значение (её понимает libpq). host и port куда, dbname и user какая база и пользователь, sslmode=verify-full и sslrootcert режим проверки из теории. -c выполняет команду SQL и выходит. pg_stat_ssl системная таблица со сведениями о шифровании соединений, pg_backend_pid() номер серверного процесса твоего соединения, поэтому запрос показывает именно твоё соединение.
Что должно получиться (значения будут другими: у сертификата свой владелец и дата, у сервера свой патч):
subject=<владелец сертификата, организация Yandex Cloud>
notAfter=<дата окончания сертификата>
version
----------------------------------------------------------------
PostgreSQL 18.x on x86_64-pc-linux-gnu, compiled by gcc ...
ssl | version
-----+---------
t | TLSv1.x
Как читать вывод: subject и notAfter из openssl подтверждают, что в файле сертификат (HTML такого не даст) и срок его действия не истёк. ssl = t значит «шифрование включено» (true), справа версия протокола TLS. Если psql печатает предупреждения про сертификат, значит что-то не так, а не «работает и ладно».
Объясни себе:
- Что проверяет
verify-fullкроме шифрования? - Почему
sslrootcertуказывает на файл на ВМ, а не на сервер БД? - Зачем порт 6432, а не 5432?
Типичные ошибки:
psql: error: connection to server at "c-....rw.mdb.yandexcloud.net" (10.128.0.23), port 6432 failed: root certificate file "/home/deploy/.postgresql/root.crt" does not exist: приverify-fullне заданsslrootcert: добавь параметр или положи файл в~/.postgresql/root.crt.psql: error: connection to server ... failed: SSL error: certificate verify failed: скачан не тот файл (например, HTML): проверьopenssl x509 -in ... -noout -subject.psql: error: connection to server ... port 6432 failed: timeout expired: security group не пускает или другая сеть: см. раздел «Сломай и почини».
Ошибка
psqlнепонятна? Вставь её текст в нейросеть без пароля и спроси, на каком этапе подключения она возникла, а затем проверь причину командами из теории.
Задание 3. Перенос данных из контейнера в Managed PostgreSQL
Цель: перенести таблицу notes из compose-БД на ВМ в Managed PostgreSQL и сверить данные.
Предскажи: новая заметка через приложение после переноса получит корректный id, если дамп сделан полным pg_dump? А если просто выгрузить строки с явными id?
Ответ
С полным pg_dump да: он переносит и значение последовательности (sequence) через setval. Выгрузка строк оставит последовательность на 1, и первый INSERT упадёт с duplicate key value violates unique constraint "notes_pkey".
Шаги (на ВМ под deploy, с export COMPOSE_FILE=... NOTES_TAG=... из начала практики):
Сначала убедись, что на ВМ лежит postgresql-client (задание 2) и что в таблице есть заметки: если пусто, создай две-три через POST /notes, иначе сверять нечего.
- Останови приложение и прокси, чтобы данные не менялись (короткое окно простоя; перенос без простоя делают логической репликацией, это отдельная тема). Запомни число заметок:
cd /opt/notes
docker compose stop notes proxy
docker compose exec -T db \
psql -U notes -d notes -Atc "SELECT count(*), max(id) FROM notes;"
Разбор: stop notes proxy останавливает только два сервиса, db продолжает работать. exec -T db выполняет команду внутри контейнера db (-T без терминала: вывод можно перенаправлять). -A выравнивание выключено, -t только строки без заголовков, -c команда: вывод получается в виде 42|42, удобном для сравнения.
- Сними дамп из контейнера.
--no-owner --no-aclубирают привязку к ролям и правам, которых нет в managed-кластере:
docker compose exec -T db \
pg_dump -U notes -d notes --no-owner --no-acl > /tmp/notes.sql
ls -l /tmp/notes.sql
grep -c setval /tmp/notes.sql
Разбор: pg_dump пишет SQL в стандартный вывод, а > /tmp/notes.sql переносит его в файл уже на ВМ (не в контейнер). grep -c setval считает строки со словом setval: если ноль, последовательность не перенесётся.
- Загрузи дамп в Managed PostgreSQL.
ON_ERROR_STOPостановит загрузку на первой ошибке,--single-transactionоткатит всё при сбое:
export PGPASSWORD="<пароль из задания 1>"
psql "host=$PGHOST_NOTES port=6432 dbname=notes user=notes sslmode=verify-full sslrootcert=/etc/notes/tls/yc-ca.pem" \
-v ON_ERROR_STOP=1 --single-transaction -f /tmp/notes.sql
Разбор: PGPASSWORD переменная, из которой psql берёт пароль без запроса (только для этой сессии, в историю команду с настоящим паролем лучше не писать: введи её через read -rs PGPASSWORD; export PGPASSWORD). -f выполнить файл. -v ON_ERROR_STOP=1 задаёт переменную psql: остановиться при ошибке.
- Сверь количество, максимальный
idи последовательность:
psql "host=$PGHOST_NOTES port=6432 dbname=notes user=notes sslmode=verify-full sslrootcert=/etc/notes/tls/yc-ca.pem" \
-Atc "SELECT count(*), max(id) FROM notes;" \
-Atc "SELECT last_value FROM notes_id_seq;"
- Удали дамп, в нём данные, и пароль из окружения:
shred -u /tmp/notes.sql; unset PGPASSWORD.
Что должно получиться:
42|42
42
Как читать вывод: первая строка это количество|максимальный id, вторая last_value последовательности. Первое число должно совпасть с записанным в шаге 1, last_value не меньше max(id). Твои числа будут другими.
Объясни себе:
- Зачем дамп с
--no-owner, если владелец в новой БД тот жеnotes? - Зачем
--single-transaction? - Что будет с записями, сделанными между дампом и переключением?
Типичные ошибки:
ERROR: permission denied to create extension "...": в дампе есть расширение, которое без суперпользователя не создать: включи его в настройках кластера или убери из дампа, если оно не нужно.ERROR: role "postgres" does not exist: в дампеALTER ... OWNER TO postgres: сделай дамп с--no-owner.pg_dump: error: aborting because of server version mismatch: клиент старше сервера: в шаге 2pg_dumpзапускается внутри контейнераdb, а не системным клиентом, поэтому версия та же, что у исходной БД.
Не уверен, что перенос прошёл полностью? Спроси нейросеть, какие проверки выполнить после
pg_dump, и сделай их сам: сверьcount,max(id)иlast_valueпоследовательности.
Задание 4. Восстановление из бэкапа в новый кластер
Цель: убедиться, что восстановление managed-сервиса создаёт новый кластер, и замерить время.
Предскажи: сколько кластеров будет после восстановления и куда указывает старый DATABASE_URL?
Ответ
Два: старый остаётся, появляется новый со своим FQDN. Старый DATABASE_URL по-прежнему указывает на старый кластер, пока не сменишь хост.
Шаги (на твоём компьютере с yc):
- Создай бэкап вручную и посмотри список. Для PITR к
restoreдобавляют--time(например,2026-09-29T12:00:00Z: момент между временем бэкапа и последним WAL, формат UTC):
yc managed-postgresql cluster backup notes-pg
BACKUP_ID=$(yc managed-postgresql cluster list-backups notes-pg --format json | jq -r '.[0].id')
yc managed-postgresql cluster restore \
--backup-id "$BACKUP_ID" --name notes-pg-restore --environment production \
--network-name notes-net --host zone-id=ru-central1-a,subnet-name=notes-subnet-a \
--resource-preset s2.micro --disk-type network-ssd --disk-size 10
Разбор: cluster backup запускает копию сейчас. list-backups ... | jq -r '.[0].id' берёт идентификатор первого элемента списка (.[0]); проверь, что это именно нужная копия, глядя на время создания (yc managed-postgresql cluster list-backups notes-pg). restore создаёт новый кластер с именем notes-pg-restore: параметры сети, хоста и размера указываются заново.
- Замерь время до
RUNNING, сверьcount(*)черезpsqlкак в задании 2 и сразу удали кластер, чтобы не платить:yc managed-postgresql cluster delete notes-pg-restore. Если квота или бюджет не позволяют, письменно опиши, что пришлось бы поменять после восстановления (DATABASE_URL, security group, пользователи).
Что должно получиться:
done (<время операции>)
name: notes-pg-restore
status: RUNNING
Как читать вывод: done (...) время ожидания операции: у тебя оно будет своим, но это минуты, а не секунды (именно поэтому RTO у managed-восстановления не «мгновенный»). RUNNING значит, что кластер готов принимать подключения.
Объясни себе:
- Что не переносится вместе с данными и что надо повторить вручную?
- Сколько данных потеряешь (RPO), если авария случилась через 3 минуты после последнего бэкапа при включённом WAL-архиве?
Типичные ошибки:
ERROR: rpc error: code = InvalidArgument desc = ... recovery time ... is out of range:--timeвне окна PITR: возьми момент между временем бэкапа и текущим.ERROR: rpc error: code = ResourceExhausted desc = Quota limit ... exceeded: нет квоты на второй кластер: удали лишнее или запроси увеличение.
Задание 5. Шаг проекта: «Заметки» на Managed PostgreSQL
Цель: переключить продовые «Заметки» на Managed PostgreSQL, убрать db из продового запуска, зафиксировать в репозитории и письменно разобрать, что ты больше не делаешь сам.
Предскажи: приложение читает DATABASE_URL из .env. Хватит поменять только эту строку?
Ответ
Нет, по двум причинам. Во-первых, в compose.yml из урока 4.6 в environment DATABASE_URL записан прямо, с адресом db, и такая запись перекрывает .env: нужно переопределить её в compose.prod.yml. Во-вторых, контейнер должен видеть файл корневого сертификата, иначе verify-full упадёт: файл монтируют в контейнер, а путь указывают в sslrootcert.
Шаги:
- В репозитории
~/notesна своём компьютере замениcompose.prod.ymlцеликом этой версией (nginx-часть та же, что в уроке 6.3). Сервисdbостаётся вcompose.ymlдля локальной разработки, а на проде его отключает профиль (profiles: сервис запускается только если профиль явно включён):
# Продовое переопределение: запускается вместе с compose.yml
services:
notes:
image: ghcr.io/<github-user>/notes:${NOTES_TAG:?нужен NOTES_TAG}
restart: unless-stopped
# DATABASE_URL берётся из .env и заменяет строку с адресом контейнера db из compose.yml
environment:
DATABASE_URL: ${DATABASE_URL:?нужен DATABASE_URL в .env}
# Сертификат провайдера только для чтения; путь совпадает с sslrootcert в DATABASE_URL
volumes:
- /etc/notes/tls/yc-ca.pem:/certs/yc-ca.pem:ro
# Ждать локальную БД больше не нужно
depends_on: !reset []
db:
restart: unless-stopped
# На проде не запускается: сервис включается только профилем local
profiles: ["local"]
proxy:
restart: unless-stopped
ports:
- "80:80"
- "443:443"
# !override заменяет список томов целиком: самоподписанный ./deploy/tls на проде не нужен
volumes: !override
- ./deploy/nginx/compose.prod.conf:/etc/nginx/conf.d/default.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
- /var/www/certbot:/var/www/certbot:ro
Разбор новых строк: ${DATABASE_URL:?...} как ${NOTES_TAG:?...} из урока 6.3: значение из .env, а без него остановка с понятным сообщением. depends_on: !reset [] тег !reset очищает список зависимостей: иначе notes ждал бы сервис db, который на проде выключен. profiles: ["local"] для db: без ключа --profile local он не стартует.
Проверка без запуска контейнеров (в ~/notes): NOTES_TAG=0.4.1 docker compose -f compose.yml -f compose.prod.yml config --services должна показать только notes и proxy.
- Отправь файл на ВМ так же, как в уроке 6.3 (или выкати релизом через CI, если
compose.prod.ymlвходит в деплой):
rsync -azR -e "ssh -i ~/.ssh/notes-deploy" compose.prod.yml deploy@203.0.113.10:/opt/notes/
- На ВМ под
deploy(сexport COMPOSE_FILE=... NOTES_TAG=...из начала практики) замени строкуDATABASE_URLв/opt/notes/.env. Владелец файла остаётсяdeploy, права 600: именно так его читаетdeploy.sh, аroot:640сломало бы деплой. Пароль пока остаётся в файле: это осознанный долг до темы секретов.
cd /opt/notes
export PGHOST_NOTES='<FQDN-кластера>'
read -rsp 'Пароль БД notes: ' PGPASS; echo
# Старую строку убираем, новую дописываем, права возвращаем
grep -v '^DATABASE_URL=' .env > .env.new
printf 'DATABASE_URL=postgresql://notes:%s@%s:6432/notes?sslmode=verify-full&sslrootcert=/certs/yc-ca.pem\n' \
"$PGPASS" "$PGHOST_NOTES" >> .env.new
chmod 600 .env.new && mv .env.new .env
unset PGPASS
ls -l .env
Разбор: read -rsp спрашивает пароль без вывода на экран (-s), -r не трактует обратные слэши, -p текст приглашения. grep -v печатает все строки, кроме подходящих под шаблон. %s в printf подставляются аргументы по порядку. mv заменяет файл целиком (это атомарно, читатель не увидит половину файла). В строке подключения после ? идут параметры через &: режим TLS и путь внутри контейнера к сертификату (/certs/yc-ca.pem, туда его монтирует compose.prod.yml).
- Запусти и проверь:
docker compose config --services
docker compose up -d
docker compose ps
curl -fsS https://notes.<твой-домен>/readyz
curl -fsS -X POST https://notes.<твой-домен>/notes -H 'Content-Type: application/json' -d '{"text":"после переезда в managed"}'
curl -fsS https://notes.<твой-домен>/notes | tail -c 200
- Убедись, что заметка видна и в managed-БД (
psqlиз задания 2,SELECT count(*) FROM notes;). Старую БД останови, а её том оставь на несколько дней как страховку:
docker compose --profile local stop db
- Зафиксируй изменение в
~/notes(.envв git не попадает, он в.gitignoreс урока 4.5). Версия приложения не меняется:app.pyостаётся 0.4.1:
cd ~/notes
git add compose.prod.yml
git commit -m "prod: Managed PostgreSQL, db только для профиля local"
git push
- Письменно (в тетради или
README.md) ответь: что ты больше не делаешь сам, а что осталось тебе. Образец:
| Больше не делаю | Осталось мне |
|---|---|
| патчи и обновления PostgreSQL | схема и миграции |
| настройка бэкапов и WAL-архива | проверка, что бэкап восстанавливается |
| failover на реплику | сеть, security group, TLS на клиенте |
| диск и его расширение | пароли и их хранение (пока в .env) |
| мониторинг «железа» БД | медленные запросы, число соединений |
Что должно получиться:
notes
proxy
ready
NAME IMAGE STATUS
notes-notes-1 ghcr.io/<github-user>/notes:... Up 20 seconds
notes-proxy-1 nginx:1.30 Up 20 seconds
Как читать вывод: первый блок это список сервисов из config --services: db в нём нет, значит профиль сработал. ready от /readyz значит, что приложение достучалось до БД (при поломке подключения здесь было бы 503). В ps два контейнера и нет db. Заметка после POST видна при повторном GET и в psql к managed-кластеру. Имена контейнеров и время Up у тебя будут своими.
Объясни себе:
- Почему
dbне удалён изcompose.yml, а спрятан профилем? - Где сейчас лежит пароль БД и почему это долг?
- Как быстро откатиться на старую БД, если что-то пошло не так?
Типичные ошибки:
psycopg.OperationalError: connection failed: connection to server at "10.128.0.23", port 6432 failed: FATAL: no pg_hba.conf entry for host "10.128.0.5", user "notes", database "notes", no encryption: клиент пришёл без TLS, вDATABASE_URLнетsslmode: добавьsslmode=verify-full.psycopg.OperationalError: ... root certificate file "/certs/yc-ca.pem" does not exist: сертификат не смонтирован: проверьvolumesи путь на ВМ.service "notes" depends on undefined service "db": invalid compose project:depends_onне сброшен: добавьdepends_on: !reset [](нужен Compose не ниже 2.24).required variable DATABASE_URL is missing a value: в.envнет строкиDATABASE_URL(например,grep -vубрал её, аprintfне отработал): повтори шаг 3.- В логах
could not translate host name "db": приложение всё ещё использует старый адрес: проверь, что вcompose.prod.ymlестьenvironment: DATABASE_URL, и пересоздай контейнер (up -d).
Сломай и почини
Здесь ты ломаешь связь «Заметок» с Managed PostgreSQL на notes-vm (нужно выполненное задание 5 и хотя бы одна заметка). Скрипт ничего не создаёт и не удаляет в облаке: он правит .env, добавляет правила iptables с меткой break-6.4 и двигает последовательность. Запускай на ВМ под yc-user (ему доступен sudo).
curl -fsSL -o /tmp/break-6.4.sh https://raw.githubusercontent.com/distinguished-sre/learning/main/devops/project/notes/break/6.4/break.sh
sudo bash /tmp/break-6.4.sh 1
Разбор: curl -fsSL -o файл адрес скачивает скрипт (флаги из задания 2), sudo bash файл 1 запускает его от root с номером сценария. Номер выбирай сам (1, 2 или 3) либо попроси напарника выбрать за тебя и не говорить. Читать скрипт до починки не нужно. Починив, проверь, что /readyz отвечает ready, а POST /notes возвращает созданную заметку. Если застрял: sudo bash /tmp/break-6.4.sh fix вернёт всё как было (его можно запускать сколько угодно раз).
Симптом
После поломки «Заметки» работают не так, как в задании 5. В сценариях 1 и 2 /readyz отвечает 503 (приложение не достучалось до БД), заметки не читаются и не пишутся. В сценарии 3 иначе: /readyz отвечает ready (соединение с БД в порядке), но POST /notes возвращает 500. Первое, что смотришь (под deploy, с export COMPOSE_FILE=... NOTES_TAG=...):
docker compose logs --tail=20 notes
Гипотезы
Прежде чем что-либо менять, пройди список:
- Клиент пришёл без TLS или с неверным режимом (в тексте
pg_hba.conf,SSL). - До БД не доходит трафик: security group, сеть, порт или FQDN (
timeout expired). - Соединение есть, но запись падает на уровне данных: дубликат ключа, сломана последовательность.
Проверки
Одна проверка на одну гипотезу, только чтение (под deploy, PGHOST_NOTES задан как в задании 5):
# Гипотеза 1: какой sslmode в строке подключения (пароль скрыт)
grep -o 'DATABASE_URL=.*' /opt/notes/.env | sed 's#://[^@]*@#://***@#'
# Гипотеза 2: доходит ли TCP до порта; если правила выглядят нормально, смотри и то, что видит ВМ
timeout 5 bash -c "</dev/tcp/$PGHOST_NOTES/6432" && echo open || echo closed
yc vpc security-group get notes-pg-sg # с твоего компьютера
# Гипотеза 3: последовательность против данных
psql "host=$PGHOST_NOTES port=6432 dbname=notes user=notes sslmode=verify-full sslrootcert=/etc/notes/tls/yc-ca.pem" \
-Atc "SELECT max(id) FROM notes;" -Atc "SELECT last_value FROM notes_id_seq;"
Разбор: grep -o печатает только совпавшую часть, sed заменяет всё между :// и @ (логин и пароль) на ***, чтобы не светить пароль. /dev/tcp/хост/порт это особая возможность bash: открыть TCP-соединение без сторонних программ; timeout 5 обрывает попытку через 5 секунд.
Исправление
Разбор всех сценариев
Сценарий 1. no pg_hba.conf entry ... no encryption или SSL is required.
Причина: в DATABASE_URL стоит sslmode=disable (скрипт заменил verify-full). Managed-кластер принимает только TLS, правила для нешифрованных соединений в его pg_hba.conf нет. Заметь: если sslmode просто не указан, libpq по умолчанию работает в режиме prefer (пробует TLS и соглашается на его отсутствие), поэтому ошибка «no encryption» бывает при явном disable, а отсутствие параметра само по себе часто просто оставляет соединение без проверки сертификата (это дыра, а не поломка).
Починка: вернуть ?sslmode=verify-full&sslrootcert=/certs/yc-ca.pem и выполнить docker compose up -d (переменные окружения подхватываются при пересоздании контейнера, restart не поможет).
Вывод: сервер ответил текстом ошибки, значит сеть в порядке.
Сценарий 2. timeout expired.
Причина в реальной жизни: в security group кластера нет правила для группы ВМ на 6432 или группа не привязана к кластеру. Пакеты молча отбрасываются, поэтому ответа нет вообще. Скрипт не трогает облако, поэтому имитирует то же самое правилом DROP в iptables на самой ВМ: снаружи симптом тот же (timeout expired), но на проверке yc vpc security-group get notes-pg-sg правило окажется на месте. Такая ситуация тоже бывает (файрвол на самом хосте), поэтому проверяй оба уровня: облачный и локальный (sudo iptables -S | grep 6432).
Починка в реальной жизни: добавить правило ingress TCP 6432 из группы ВМ и привязать группу (команды из задания 1, шаг 3). Для учебной поломки: sudo bash /tmp/break-6.4.sh fix.
Вывод: таймаут означает файрвол или маршрут; refused означает, что хост достигнут, но порт никто не слушает; ошибка аутентификации означает, что сеть уже в порядке.
Сценарий 3. duplicate key value violates unique constraint "notes_pkey" при создании заметки.
Причина: последовательность сброшена к 1, а в таблице уже есть строки. Читать заметки можно, /readyz в порядке (соединение работает), но первая же запись получает занятый id.
Починка:
-- Двигаем последовательность к максимальному id
SELECT setval(pg_get_serial_sequence('notes', 'id'), (SELECT max(id) FROM notes));
Вывод: полный pg_dump переносит setval, самодельные выгрузки нет. После любой миграции данных сверяй последовательности.
ИИ в помощь
Нейросеть хорошо переводит сообщения об ошибках PostgreSQL в понятные причины и объясняет режимы sslmode, но не видит твой кластер и security group. Пароли, содержимое .env и строки DATABASE_URL в запрос не вставляй: замени значения на <пароль>. Общие правила: ИИ-помощник.
Задача: найти причину ошибки подключения к базе.
Приложение на ВМ пишет: connection timed out при подключении к Managed PostgreSQL на порту 6432.
Разбери по этапам: имя, сеть, TLS, пароль, права. Какой этап не пройден и какую команду выполнить, чтобы подтвердить?
Security group кластера: <вставь вывод yc vpc security-group get без секретов>.
Проверь ответ: timeout это файрвол или маршрут, а не неверный пароль и не упавшая база. Если нейросеть советует менять пароль или перезапускать кластер, она ошибается. Проверь порт командой из урока и посмотри правило для группы ВМ.
Задача: проверить план переноса данных.
Вот мой план переноса БД из контейнера в Managed PostgreSQL: pg_dump, остановка записи, psql -f, переключение приложения.
Что в нём может потеряться или сломаться (последовательности, расширения, права, окно простоя)? Дай чек-лист проверок после переноса.
<вставь план по шагам>
Проверь ответ: в чек-листе должны быть последовательности (last_value против max(id)), число строк и права пользователя. Каждую проверку выполни сам на кластере, а не принимай на веру.
Задача: посчитать, когда закончится диск.
Диск кластера 20 ГБ, занято 15 ГБ, рост 500 МБ в сутки. Через сколько суток будет 90%? Какие пороги алертов поставить?
Проверь ответ: пересчитай сам: 90% от 20 ГБ это 18 ГБ, остаётся 3 ГБ, это около шести суток. Нейросети часто путают ГБ и ГиБ и забывают, что рост может ускориться.
Словарик урока
| Термин | Простыми словами |
|---|---|
| Managed-сервис | Готовая база, кластер или балансировщик: установкой, обновлением, копиями и починкой занимается провайдер |
| Shared responsibility | Модель разделённой ответственности: провайдер отвечает за платформу, ты за то, что на ней размещаешь |
| WAL | Журнал предзаписи PostgreSQL: последовательная запись всех изменений, по нему восстанавливаются после сбоя и на нужный момент |
| PITR | Point-in-time recovery: восстановление базы на выбранный момент (копия плюс проигранный WAL) |
| RPO | Сколько данных можно потерять при аварии, измеряется временем |
| RTO | За сколько времени система должна вернуться в строй |
| Реплика | Второй хост, применяющий все изменения мастера |
| HA | High availability: запасной хост и автоматическое переключение при поломке основного |
| Failover | Переключение на реплику при падении мастера |
| Replication lag | Задержка, с которой реплика догоняет мастер |
| Зона доступности (AZ) | Отдельная площадка облака со своим питанием и сетью |
| VPC | Твоя изолированная виртуальная сеть в облаке |
| Пул соединений | Посредник, раздающий клиентам небольшое число постоянных соединений с БД (порт 6432 в Yandex) |
sslmode |
Параметр libpq: нужно ли шифровать соединение и проверять сервер (verify-full проверяет всё) |
| CA | Центр сертификации: организация, подписью которой подтверждают сертификаты |
| MITM | Атака «человек посередине»: злоумышленник встаёт между клиентом и сервером |
pg_dump |
Программа, выгружающая базу в SQL-файл |
| Последовательность (sequence) | Счётчик, выдающий следующие id для serial-колонки |
| Логическая репликация | Перенос данных потоком изменений: способ миграции без долгого простоя |
| Control plane | Управляющая часть Kubernetes (API, etcd, планировщик), в managed-версии её ведёт провайдер |
| L4 / L7 балансировщик | L4 работает с TCP/UDP, L7 понимает HTTP (пути, хосты, TLS) |
| Квота | Лимит числа ресурсов (ВМ, дисков, кластеров) на каталог |
Профиль Compose (profiles) |
Сервис запускается, только если профиль включён явно |
Вопросы с собеседований
Раздел для повторения: ответь вслух, потом открой ответ. Короткие вопросы с пометкой [на скорость] тренируй на время: ответ за 30 секунд.
1. [junior] [часто] Managed или self-hosted PostgreSQL: как выбираешь?
Ответ
Смотрю на команду и требования. Нет людей, которые умеют восстанавливать и обновлять PostgreSQL, и нет особых расширений: беру managed, плачу деньгами и экономлю часы и риск. Self-hosted, если нужен контроль, особые расширения или требования по размещению данных.
Что хотят услышать: сравнение по стоимости, эксплуатации и контролю; SLA провайдера; нет суперпользователя; переносимость через дамп.
Красный флаг: «managed всегда дорого и плохо» или «managed решает все проблемы».
2. [junior] [часто] Чем L4-балансировщик отличается от L7 и что выберешь для веб-приложения?
Ответ
L4 работает с TCP/UDP и не видит HTTP. L7 понимает хост и путь, завершает TLS, делает редиректы и проверки по HTTP. Для веб-приложения с несколькими маршрутами беру L7, для БД, очередей и произвольного TCP L4.
Что хотят услышать: что умеет L7 (завершение TLS, маршрутизация), что клиентский IP за L7 приходит в X-Forwarded-For.
Красный флаг: не знает, где завершается TLS; путает балансировщик с DNS.
3. [middle] Прод отвечает 502, в логах приложения connection timed out к БД. Твои действия?
Ответ
Сначала смотрю, что менялось: деплой, правка security group, обновление кластера. С хоста приложения проверяю, открыт ли порт БД (nc или /dev/tcp: инструменты для проверки TCP-соединения без отправки данных), потом статус кластера в консоли и метрики (CPU, диск, соединения). Порт закрыт: ищу правила security group и маршрут. Порт открыт, но БД не отвечает: смотрю нагрузку и число соединений.
Что хотят услышать: порядок от дешёвого к дорогому: что менялось, сеть, статус сервиса, нагрузка; разницу между timeout (файрвол), refused и auth failed.
Красный флаг: «перезапущу приложение» без диагностики; «перезагружу БД».
4. [middle] psql говорит no pg_hba.conf entry for host ..., no encryption. Что это и как чинить?
Ответ
Клиент подключился без TLS, а managed-кластер принимает только шифрованные соединения. Значит сеть и порт в порядке. Добавляю sslmode=verify-full и путь к корневому сертификату провайдера.
Что хотят услышать: что такое pg_hba.conf и что на managed его не правят; разница режимов sslmode; почему именно verify-full.
Красный флаг: «открою доступ всем» или «поставлю sslmode=disable».
5. [middle] После переезда БД приложение падает на записи: duplicate key value violates unique constraint. Причина?
Ответ
Данные перенесли выгрузкой строк, а последовательность (sequence) не продвинули, и она выдаёт уже занятые id. Смотрю max(id) и last_value, выравниваю через setval. В следующий раз делаю полный pg_dump.
Что хотят услышать: связь serial и sequence; setval и pg_get_serial_sequence; сверка после миграции.
Красный флаг: «уберу уникальность» или «пересоздам таблицу».
6. [middle] Ночью упал мастер managed PostgreSQL. Что произойдёт и что делаешь ты?
Ответ
При включённом HA провайдер повысит реплику, FQDN кластера (c-<id>.rw.mdb.yandexcloud.net, а не имя отдельного хоста) остаётся, соединения обрываются на десятки секунд. Приложение должно переподключаться (retry) и не держать мёртвые соединения. Я проверяю, что сервис восстановился, что реплика создана заново, разбираю причину.
Что хотят услышать: HA и failover, обрыв соединений, переподключение в клиенте, RPO около нуля при синхронной репликации и небольшой при асинхронной.
Красный флаг: «ничего, облако само»; «вручную переключу DNS».
7. [middle] В 14:07 кто-то выполнил DELETE FROM notes без WHERE. Реплика есть. Как восстановиться?
Ответ
Реплика повторила удаление и не поможет. Делаю PITR в новый кластер на 14:06, сверяю данные, потом либо переключаю приложение, либо переношу нужные строки в боевой кластер. Старый не трогаю до сверки.
Что хотят услышать: HA это не бэкап; PITR идёт в новый кластер; RPO и RTO; проверка перед переключением; разбор причины (права, процесс).
Красный флаг: «откачу с реплики»; «поставлю самый свежий бэкап», потеряв данные после него.
8. [middle] Приложение открывает соединение на каждый запрос, БД отвечает too many connections. Что делать?
Ответ
Быстро: пул соединений (посредник, который держит несколько постоянных соединений с БД и раздаёт их клиентам) на стороне сервиса (в Yandex порт 6432) или pgbouncer (популярная программа-пул). Постоянно: пул в самом приложении и лимиты соединений. Отдельно проверяю утечки соединений и долгие транзакции в pg_stat_activity.
Что хотят услышать: connection pooling, режим transaction (соединение выдаётся клиенту только на время транзакции), цену соединения в PostgreSQL (процесс на соединение), диагностику через pg_stat_activity.
Красный флаг: «просто подниму max_connections».
9. [middle] Как перенести БД на managed с минимальным простоем?
Ответ
Простой вариант: короткое окно, остановка записи, pg_dump, загрузка, переключение. Для большой БД: логическая репликация (новая база подписывается на поток изменений старой: publication на источнике, subscription на приёмнике) или сервис миграции провайдера: синхронизируем, сверяем данные, на секунды останавливаем запись, догоняем и переключаем DATABASE_URL. Обязательно проверяю последовательности и держу план отката.
Что хотят услышать: дамп против логической репликации, сверка данных, откат, последовательности, заморозка записи.
Красный флаг: «остановим сайт на ночь» без плана отката; «скопируем файлы данных».
10. [middle] Тебе предлагают Managed Kubernetes для одного небольшого сервиса. Согласишься?
Ответ
Скорее нет. За control plane платим отдельно, нужно минимум несколько узлов, плюс эксплуатация кластера: версии, сеть, безопасность. Одному сервису хватит ВМ с compose или PaaS. Kubernetes оправдан, когда сервисов много и нужны единые практики деплоя и автоскейлинг.
Что хотят услышать: стоимость control plane и узлов; что провайдер обновляет control plane, а узлы и приложения нет; критерии выбора и альтернативы.
Красный флаг: «Kubernetes везде, потому что все так делают».
11. [junior] [на скорость] Что в managed-сервисе берёт на себя провайдер, а что остаётся на тебе?
Ответ
Провайдер берёт на себя установку, обновление патчей, базовые бэкапы, отказоустойчивость и мониторинг инфраструктуры. На мне остаётся схема данных, индексы и медленные запросы, пользователи и права, сетевой доступ, настройка пула соединений, проверка восстановления из бэкапа, лимиты и стоимость. Managed не отменяет ответственности за данные: я проверяю, что бэкап реально восстанавливается, и знаю окно обслуживания.
Что хотят услышать: что делегировано, что осталось, проверка бэкапов, права и сеть, оптимизация запросов.
Красный флаг: «Managed - значит, ничего делать не надо».
12. [middle] Read replica и HA-реплика в managed PostgreSQL: это одно и то же?
Ответ
Нет. HA-реплика (standby) стоит для отказоустойчивости: при падении мастера провайдер переключается на неё. Read replica нужна, чтобы разгрузить мастер чтением, и подключается приложением отдельно по своему адресу. Репликация обычно асинхронная, поэтому у реплики есть лаг, и читать с неё сразу после записи можно получить старые данные. Смотрю лаг в метриках. Для критичных чтений после записи иду на мастер.
Что хотят услышать: разное назначение, лаг репликации, чтение после записи, отдельный адрес, зависимость от провайдера.
Красный флаг: Считать read replica заменой бэкапу или HA.
13. [middle] Как безопасно обновить мажорную версию managed PostgreSQL?
Ответ
Сначала читаю список несовместимостей версии и проверяю расширения. Восстанавливаю копию из бэкапа в тестовый кластер, обновляю и прогоняю приложение и тесты, замеряю время. Проверяю, что драйвер и ORM поддерживают новую версию. Назначаю окно с запасом, делаю свежий бэкап перед работами и план отката: вернуться на старый кластер. После обновления смотрю планы запросов и статистику, потому что она может понадобиться заново.
Что хотят услышать: репетиция на копии, расширения, бэкап, окно и откат, проверка производительности.
Красный флаг: Обновить прод в рабочее время без репетиции.
Проверено на версиях
- PostgreSQL: 18 (Managed и клиент
psql; список версий провайдера проверь в консоли) - Yandex Cloud CLI (
yc): версия не закреплена, выводycв уроке не запускался и взят из документации и предыдущих уроков (значения в выводах помечены как «у тебя будут свои») - Ubuntu на ВМ: 26.04 LTS или 24.04
- nginx: 1.30 (контейнер
proxyиз урока 4.6) - Docker Compose: плагин
docker composeиз урока 4.1 (теги!resetи!overrideтребуют Compose не ниже 2.24) - «Заметки»: 0.4.1 (
app.pyv4.1) - Проверено командами:
compose.prod.ymlчерезdocker compose config(сервисыnotesиproxy,DATABASE_URLпереопределён),shellcheckдляbreak.sh. Не проверялось: создание кластера и восстановление в облаке, запускbreak.shна ВМ, настоящийpg_dumpи загрузка.
Итог урока: ты умеешь
- умею объяснить, что Managed PostgreSQL делает за тебя и что остаётся тебе
- умею создать минимальный кластер и ограничить доступ security group
- умею подключиться по TLS с
sslmode=verify-fullи корневым сертификатом провайдера - умею перенести БД полным
pg_dumpи сверить строки и последовательности - умею объяснить разницу между HA, репликой и бэкапом и почему PITR создаёт новый кластер
- умею по тексту ошибки отличить проблему TLS, сети и данных
- умею оценить, нужен ли Managed Kubernetes, и выбрать между L4 и L7
Дальше: Урок 6.5: Эксплуатация в облаке: бэкапы, стоимость, надёжность, удаление
Проверь себя
Короткий тест по уроку: 5 вопросов из банка в 30. Засчитывается только полностью правильный ответ, порог 60%. Каждая новая попытка даёт другие вопросы, пока банк не закончится. Ответы видны после проверки.
Тест работает с включённым JavaScript.