Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 3 из 25 ~16 мин +20 XP

Строка подключения и DSN

Строка подключения pgx — это весь конфиг базы в одном значении: postgres://user:password@host:port/dbname плюс параметры вроде sslmode и connect_timeout. Разберём анатомию DSN для Postgres в Go, дефолты каждого поля, режимы шифрования от disable до verify-full, чтение переменных PG* по конвенции libpq и безопасную сборку DSN из окружения — без секретов в коде и без сюрпризов на стейджинге.

Содержание урока · 9 разделов

История, знакомая каждому, кто выкатывал сервис в первый раз: на ноутбуке всё работает, в стейджинге — падение с ошибкой про SSL. Код один и тот же, разница целиком в строке подключения: локальный Postgres принял незащищённый канал, а облачный требует шифрования. DSN — это не «просто строка с паролем», а конфигурация протокола: кто вы, куда подключаетесь, шифровать ли канал, сколько ждать ответа и как называть своё приложение в логах сервера. pgx парсит эту строку при создании соединения или пула, и почти каждое поле имеет разумный дефолт — из-за этого короткий DSN вроде postgres://localhost/app тоже работает, ровно до первого попадания на сервер с другим поведением. Задача урока — снять с строки подключения покров магии: вы должны уметь на глаз сказать, за что отвечает каждый фрагмент и что произойдёт, если его убрать. Бонус умения: один и тот же формат понимают psql, инструменты миграций и бэкапов — настроив строку один раз, вы говорите на одном языке со всем стеком инструментов Postgres. Именно поэтому в уроке мало кода и много разборов: строка подключения — место, где сходятся безопасность, сеть и эксплуатация.

Анатомия postgres://: из чего состоит DSN#

Формат — URL: postgres://user:password@host:port/dbname?param=value. Разберём по частям. user и password — креденшалы; если пароля нет, часть с двоеточием просто опускается. host — имя хоста, IP или путь к Unix-сокету, если начинается со слэша: localhost для локальной базы, доменное имя для облака. port — по умолчанию 5432, и это чуть ли не единственное поле, которое можно смело опускать; IPv6-адреса пишутся в квадратных скобках, как в любом URL. У user тоже есть дефолт — имя пользователя операционной системы: подключение без user иногда падает с неожиданным role does not exist, потому что pgx подставил вашего системного пользователя, а такой роли в базе нет. dbname — имя базы; если его не указать, сервер попробует базу с именем пользователя, и новичок ловит ошибку database app does not exist, потому что назвал базу как пользователя. После вопросительного знака живут параметры: sslmode, connect_timeout, application_name и параметры пула. pgx понимает и второй формат — пары key=value в стиле libpq: host=localhost user=app password=secret dbname=app. Оба формата принимают pgx.Connect, pgxpool.New и pgx.ParseConfig; выбирайте один на проект и не смешивайте — половина путаницы в чужом коде растёт из того, что человек читает URL, а пишет key=value. Отдельная приятность: pgx принимает несколько хостов через запятую — host=primary,replica — и при недоступности первого пробует второй; для чтения с резервной реплики это готовый механизм без внешних балансировщиков.

Три DSN на один и тот же конфиг

GO
// локальная разработка: шифрование не нужно, трафик не покидает машину
const devDSN = "postgres://app:secret@localhost:5432/app?sslmode=disable"

// облако: шифрование с проверкой хоста, таймаут, метка приложения
const prodDSN = "postgres://app@pg.example.com:5432/app" +
        "?sslmode=verify-full&connect_timeout=5&application_name=billing"

// конвенция libpq: тот же конфиг без URL-схемы
const kvDSN = "host=localhost user=app password=secret dbname=app sslmode=disable"

Три строки подключения на одинаковый по смыслу конфиг. В dev шифрование отключено, потому что канал не покидает машину. В prod — verify-full с проверкой сертификата и имени хоста, пяти секундный потолок на установку соединения и application_name, чтобы сервис было видно в pg_stat_activity. Третий формат — key=value: удобен, когда пароль содержит символы URL.

Параметры DSN, которые встречаются чаще всего#

ПараметрДефолтЗачем нужен
user / passwordпользователь ОСаутентификация на сервере
host / portlocalhost / 5432куда подключаться: хост, домен или путь к сокету
dbnameимя пользователябаза внутри сервера Postgres
sslmodepreferрежим шифрования канала: disable, require, verify-full и другие
connect_timeoutнетпотолок времени на установку соединения, в секундах
application_nameпустометка сервиса в pg_stat_activity и логах сервера
pool_max_connsmax(4, NumCPU)потолок одновременных соединений пула — тема четвёртого урока

sslmode: disable, require и verify-full#

Режимов шесть, но решение сводится к одному вопросу: насколько вы параноик по поводу канала между приложением и базой. disable — чистый TCP без шифрования: годится, когда соединение не покидает машину или идёт внутри доверенной приватной сети. allow и prefer — полумеры: prefer, дефолтный режим, пытается шифровать, а при неудаче тихо откатывается к открытому каналу; именно эта гибкость мешает поймать ошибку конфигурации на стейджинге — всё выглядит работающим, пока в трафик не заглянет кто-то чужой. require — шифрует обязательно, но не проверяет сертификат: канал защищён от подглядывания, а вот посредник с любым валидным сертификатом перехватит соединение незаметно. Для verify-full сертификат сервера должен подписываться доверенным CA, либо путь к корневому сертификату передаётся параметром sslrootcert — в облаках его обычно скачивают вместе с креденшалами. verify-ca добавляет проверку цепочки сертификатов, verify-full — ещё и сверку имени хоста, и это единственный режим, который действительно стоит против man-in-the-middle. Моя конвенция простая: локально disable, на проде verify-full и никаких prefer в продовых DSN — тихий откат к открытому каналу это не надёжность, а её имитация. Промежуточный вариант для внутреннего контура — verify-ca с собственным корневым сертификатом: канал шифруется, цепочка проверяется, а сверка имени хоста остаётся на совести доверенной сети. Если облачный провайдер подписал сертификат на внутреннее имя и verify-full ругается, честное решение — sslrootcert с его CA, а не откат к require. Проверить фактический режим легко: в pg_stat_ssl видно, шифруется ли каждое соединение, — без этой проверки require в конфиге остаётся благим пожеланием.

PGHOST, PGUSER и другие env-переменные libpq#

pgx читает переменные окружения по конвенции libpq — так называют набор настроек, который поддерживают официальные клиенты Postgres на всех языках. PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE, PGAPPNAME — полный список живёт в документации PostgreSQL, а имена стабильны годами, потому что их читают десятки утилит, не только pgx. Механика такая: любой параметр, который явно не указан в строке, pgx берёт из окружения, а если и там пусто — применяет дефолт. Пустая строка в качестве DSN — легальный способ сказать «всё из env»: так собраны многие двенадцатифакторные сервисы, где код приложения вообще не знает, где живёт база. Приоритет важный: явный параметр в строке бьёт переменную окружения, поэтому фрагмент ?sslmode=disable в DSN перебьёт PGSSLMODE=verify-full — и это источник незаметных багов при переносе конфигов между средами. Отладить приоритет легко: соберите конфиг через pgx.ParseConfig и напечатайте итоговые поля — сразу видно, что приехало из переменных окружения, а что из дефолтов. В Kubernetes и docker-compose переменные задаются в манифесте, и один и тот же образ работает в dev и prod без пересборки. Одна оговорка: PGPASSWORD удобна для локальных скриптов, но пароль в окружении виден в выводе ps и дампах /proc — прод-секреты живут в секрет-менеджерах и короткоживущих токенах, а не в открытом env.

pgx.ParseConfig: разобрать и дополнить

GO
config, err := pgx.ParseConfig(dsn)
if err != nil {
        log.Fatalf("битый DSN: %v", err)
}

// метка, видимая в pg_stat_activity и логах сервера
config.ConnConfig.RuntimeParams["application_name"] = "billing"

// потолок времени на установку одного соединения
config.ConnectTimeout = 5 * time.Second

pool, err := pgxpool.NewWithConfig(ctx, config)

pgx.ParseConfig разбирает строку в структуру Config: так можно проверить DSN на старте и дописать то, что неудобно выражать в URL. RuntimeParams — параметры, которые уходят на сервер при подключении; ConnectTimeout ограничивает установку каждого соединения. NewWithConfig собирает пул из готовой конфигурации — путь, который предпочитают сервисы с настройками из env. ParseConfig читает и PG* переменные: пустой DSN из окружения — легальный вход.

Сборка DSN из env: паттерн для сервиса#

Рабочий приём, который я копирую из проекта в проект: приложение читает переменные окружения с дефолтами и собирает строку подключения при старте. os.Getenv для хоста, порта и имени базы, при пустом значении — дефолт для локальной разработки; пароль — без дефолта, его отсутствие падает явно. Дальше два пути: собрать строку через fmt.Sprintf или сконструировать url.URL и не думать об экранировании. Второй путь надёжнее: спецсимволы в пароле — классика, из-за которой подключение падает на сервере, работая на ноутбуке. Обязательный шаг — валидация: pgx.ParseConfig возвращает ошибку для битой строки, и вызывать его на старте стоит всегда — это дешёвый fail fast: сервис упадёт через секунду после запуска с понятным текстом, а не через час после деплоя с загадочным таймаутом. Код урока, как и весь курс, исполняется локально: песочница сайта не умеет сеть и Postgres — вам понадобится Go и контейнер из второго урока. Собранная в переменной DATABASE_URL, строка подключения поедет и в миграции, и в тесты — одна конфигурация на весь проект. В docker-compose она передаётся через environment, в Kubernetes — через ConfigMap и Secret: один и тот же бинарник меняет среду без пересборки, меняются только значения окружения.

ParseConfig возвращает ошибку до всякой сети, и её текст стоит читать внимательно — там обычно назван символ, на котором споткнулся парсер. Причины повторяются из проекта в проект: кавычки вокруг значения, прилетевшие из .env-файла вместе с переменной — их снимают не все загрузчики переменных, и парсер ищет хост по имени в кавычках; пробелы внутри пар key=value, которые в этом формате означают отдельный синтаксис с кавычками, а чаще просто ломают разбор; неотэкранированные спецсимволы пароля — соседний callout целиком про них; и перепутанный порядок, когда имя базы оказалось после вопросительного знака и ушло в параметры. Дешёвая привычка для отладки: печатайте в лог не сам DSN, а поля собранной структуры Config — увидите, что парсер понял на самом деле, и не раскроете секрет в логах.

Параметры пула в той же строке: pool_max_conns#

У pgxpool нет отдельной строки подключения — пул принимает тот же DSN, и его параметры живут после вопросительного знака: pool_max_conns, pool_min_conns, pool_max_conn_lifetime, pool_max_conn_idle_time, pool_health_check_period. Синтаксис единый, читается одна строка — удобно, когда конфигурация хранится в одной переменной окружения DATABASE_URL и раздаётся всему приложению. Подробная настройка лимитов, жизненный цикл коннектов и расчёт под max_connections сервера — тема четвёртого урока; пока достаточно знать, что такие параметры существуют и работают без отдельного кода конфигурации.

Проверка: дефолты DSN

Какой порт Postgres используется, если в строке подключения его не указать?

Проверка: sslmode

Чем sslmode=require хуже verify-full?

Что напечатает программа?

Прочитайте код и выберите, что он выведет:

config, err := pgx.ParseConfig("postgres://app:secret@localhost/mydb")
if err != nil {
    panic(err)
}

fmt.Println(config.ConnConfig.Host, config.ConnConfig.Port)

Соберите DSN

Допишите строку подключения: пользователь, пароль и режим шифрования для локальной машины.

const dsn = "postgres://:@localhost:5432/app?sslmode="

Соберите разбор конфига из env

Расставьте строки в порядке исполнения: чтение окружения, разбор, обработка ошибки, настройка, создание пула.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: строка подключения

Термин

DSN

нажмите, чтобы перевернуть
Определение

Data Source Name — строка подключения: postgres://user:password@host:port/dbname?params. Весь конфиг базы в одном значении.

1 / 6

Строка собрана, дефолты поняты — дальше по курсу: в четвёртом уроке настраиваем пул соединений — лимиты под max_connections, жизненный цикл коннектов и Stat для наблюдения. В пятом наконец отправим первые запросы. А в двадцать первом уроке та же строка подключения понесёт миграции схемы — соберите её в переменную окружения один раз и переиспользуйте везде: сервис, пул, миграции и тесты едут на одном значении. Для локальной отладки держите под рукой psql из того же контейнера — он принимает ту же строку подключения, и расхождение между ним и программой почти всегда означает разницу в переменных окружения.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • DSN — это URL: user:password@host:port/dbname плюс параметры; пропущенные поля подставляются из PG* env или дефолтов.
  • Дефолтный порт — 5432; пропущенное dbname подставит имя пользователя и подарит загадочную ошибку.
  • sslmode: локально disable, на проде verify-full; prefer молча откатывается к открытому каналу.
  • require шифрует канал, но не проверяет сертификат — против man-in-the-middle только verify-full.
  • pgx читает PGHOST, PGUSER, PGPASSWORD и остальные переменные libpq: пустой DSN — конфиг из окружения.
  • Пароль живёт в env или секрет-менеджере; в git-истории и логах ему не место; спецсимволы экранируются.
  • Параметры пула — pool_max_conns и другие — передаются в той же строке подключения.

Домашнее задание

Переведите программу из второго урока на DSN из переменной окружения: соберите DATABASE_URL со sslmode=disable и запустите. Затем поднимите второй контейнер с требованием шифрования и посмотрите, какая ошибка придёт при disable. После этого соберите конфиг через pgx.ParseConfig, поставьте application_name=pgx-course и найдите своё приложение в pg_stat_activity: docker exec -it pgx-course psql -U app -c 'select application_name, state from pg_stat_activity'.

Личные заметкиПусто
Хранятся только в вашем браузере (pgx · Строка подключения и DSN)

Вопросы и ответы по уроку

Чем DSN отличается от conninfo?

По сути ничем: оба термина означают строку подключения. DSN — имя из мира ODBC и других языков, conninfo — термин libpq. pgx принимает оба формата: URL postgres:// и пары key=value — и внутри приводит их к одной структуре Config.

Как безопасно хранить пароль?

В переменной окружения, которая прилетает из секрет-хранилища: Kubernetes Secret, Vault, SOPS — что доступно. В коде пароля нет вообще: строка подключения собирается при старте из env, а часть параметров можно не собирать вовсе — pgx прочитает PGHOST и PGUSER сам.

Что делать, если база требует сертификат клиента?

К DSN добавляются sslcert и sslkey — пути к клиентскому сертификату и ключу, а sslrootcert указывает на корневой CA. Параметры работают и в URL, и через переменные окружения PGSSLCERT и PGSSLKEY; в облаках так подключаются к управляемым базам с взаимной TLS-аутентификацией.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

MongoDB25 уроков~12 чпрактика25 уроков уже доступны

MongoDB в Go: официальный драйвер mongo-driver v2

Курс MongoDB для Go: подключение, bson-теги, CRUD, фильтры, агрегации, индексы, транзакции и Change Streams. 25 уроков по mongo-driver v2.

  • Подключаться к MongoDB и настраивать клиента
  • Моделировать документы структурами и bson-тегами
  • Делать CRUD, сложные фильтры и проекции
  • Строить агрегации $match/$group/$lookup