diff --git a/README.md b/README.md index 8a9464f..833df01 100644 --- a/README.md +++ b/README.md @@ -409,6 +409,7 @@ load_bookings_to_stg = PostgresOperator( | Проблема | Решение | |----------|---------| | Airflow UI не открывается | Дождитесь сообщения `Listening at: http://0.0.0.0:8080` в логах (`make logs`) | +| После `docker compose stop/start` Airflow UI не отвечает, а `airflow-webserver` в `Exited (1)` | Это обычно stale PID-файл Gunicorn (`/opt/airflow/airflow-webserver.pid`). Пересоздайте webserver: `docker compose up -d --force-recreate airflow-webserver` (или просто `make up`). | | Ошибка подключения к Greenplum | Убедитесь, что контейнер `greenplum` стал статусом `healthy` (проверьте `docker compose ps`) | | Не открывается порт 8080/5433/5434/5435 | Проверьте, что эти порты не заняты локальными сервисами; при необходимости остановите их или измените порты в `.env`/`docker-compose.yml` | | Нет файла в `./data` после запуска DAG | Проверьте логи задачи `generate_csv`, убедитесь, что `CSV_DIR` смонтирован в docker-compose | diff --git a/TESTING.md b/TESTING.md index 3aaced4..8e90252 100644 --- a/TESTING.md +++ b/TESTING.md @@ -43,8 +43,8 @@ - при желании проверить данные: в `bookings-db` появился новый день, а в Greenplum в `stg.bookings` — строки с актуальным `batch_id` (см. пример запросов в разделе 5). - (опционально, для менторов/разработчиков) Smoke-тест DAG через Airflow CLI без UI: - - `docker compose -f docker-compose.yml exec gp_airflow_webserver airflow dags test bookings_to_gp_stage 2024-01-01` — прогоняет `bookings_to_gp_stage` целиком в «off-line» режиме; - - `docker compose -f docker-compose.yml exec gp_airflow_webserver airflow dags trigger bookings_to_gp_stage` — создаёт реальный запуск DAG (логи и статус можно смотреть либо через UI, либо командой `airflow tasks list`/`airflow tasks logs` внутри контейнера). + - `docker compose -f docker-compose.yml exec airflow-webserver airflow dags test bookings_to_gp_stage 2024-01-01` — прогоняет `bookings_to_gp_stage` целиком в «off-line» режиме; + - `docker compose -f docker-compose.yml exec airflow-webserver airflow dags trigger bookings_to_gp_stage` — создаёт реальный запуск DAG (логи и статус можно смотреть либо через UI, либо командой `airflow tasks list`/`airflow tasks logs` внутри контейнера). ## 5. Проверка данных в Greenplum - `make gp-psql` — запустить psql в контейнере от имени `gpadmin`. diff --git a/docker-compose.yml b/docker-compose.yml index 9b5aed9..8f0f2b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -101,7 +101,7 @@ services: env_file: .env environment: <<: *airflow-env - command: airflow webserver + command: bash -lc "rm -f /opt/airflow/airflow-webserver.pid && exec airflow webserver" ports: - "8080:8080" volumes: diff --git a/docs/internal/pxf_bookings.md b/docs/internal/pxf_bookings.md index 3a5c158..2c0d81f 100644 --- a/docs/internal/pxf_bookings.md +++ b/docs/internal/pxf_bookings.md @@ -193,3 +193,9 @@ (например, на подсеть docker‑сети или на `samehost`); - или закрепить IP контейнера в compose (static IP), чтобы он не “плавал”; - или отказаться от `stop/start` в пользу сценария, который не меняет сетевое окружение (но это хуже для UX студентов). + +### Что реализовано + +- В `pxf/init/start_greenplum_with_pxf.sh` добавлен шаг, который на каждом старте + обеспечивает в `pg_hba.conf` trust‑правило `host all gpadmin samehost trust` + (вставка перед `host all all 0.0.0.0/0 md5`), и делает `pg_ctl reload`, если GPDB уже запущен. diff --git a/pxf/init/start_greenplum_with_pxf.sh b/pxf/init/start_greenplum_with_pxf.sh index 988332a..bc1486e 100755 --- a/pxf/init/start_greenplum_with_pxf.sh +++ b/pxf/init/start_greenplum_with_pxf.sh @@ -1,14 +1,105 @@ #!/usr/bin/env bash set -euo pipefail +# Этот скрипт — "обёртка" над стандартным стартом Greenplum (exec /start_gpdb.sh), +# которая добавляет устойчивый старт PXF для учебного стенда. +# +# Задачи скрипта (почему он нужен): +# 1) Подготовить PXF_BASE на persistent volume (через ensure-скрипт). +# 2) Обеспечить стабильную аутентификацию PXF → GPDB после `docker compose stop/start` +# (не зависеть от «плавающего» IP контейнера в docker-сети). +# 3) Гарантировать наличие расширения `pxf` в БД GPDB (иначе внешние таблицы падают с +# `ERROR: protocol "pxf" does not exist`). +# +# Важно: это учебный стенд, поэтому мы сознательно выбираем простые и надёжные решения +# (например, trust для samehost), а не «боевой» hardened security. + ensure_script="/opt/pxf-scripts/ensure_pxf_bookings.sh" -# PXF CLI использует libpq, без PGPASSWORD после stop/start возможна ошибка auth. +# PXF CLI использует libpq и при `pxf cluster start` подключается к GPDB как к обычному Postgres. +# После `docker compose stop/start` может внезапно потребоваться пароль (см. ensure_pg_hba_trust ниже), +# поэтому экспортируем PGPASSWORD заранее: это уменьшает "флап" и делает поведение воспроизводимым. if [ -z "${PGPASSWORD:-}" ]; then export PGPASSWORD="${GREENPLUM_PASSWORD:-gpadmin}" fi +ensure_pg_hba_trust() { + # После `docker compose stop/start` Docker может выдать контейнеру другой IP. + # У базового образа Greenplum встречается trust-правило на конкретный /32 (старый IP), + # и тогда аутентификация по TCP начинает идти через md5 → PXF падает на `28P01`. + # + # Решение: добавить правило `host all gpadmin samehost trust`, которое срабатывает для + # подключений "с этого же контейнера" (PXF запускается рядом с master). + # Это максимально простое и стабильное правило для учебного стенда. + local gp_user="${GREENPLUM_USER:-gpadmin}" + local data_dir="${GREENPLUM_DATA_DIRECTORY:-/data}" + local attempts=60 + local pg_hba="" + local trust_line="host all ${gp_user} samehost trust" + + find_pg_hba() { + local candidate + for candidate in "${data_dir}/master"/*/pg_hba.conf; do + if [ -f "${candidate}" ]; then + echo "${candidate}" + return 0 + fi + done + return 1 + } + + for _ in $(seq 1 "${attempts}"); do + pg_hba="$(find_pg_hba || true)" + if [ -n "${pg_hba}" ]; then + break + fi + sleep 2 + done + + if [ -z "${pg_hba}" ]; then + echo "WARN - pg_hba.conf не найден, пропускаем trust для ${gp_user}" + return 0 + fi + + if grep -Eq "^[[:space:]]*host[[:space:]]+all[[:space:]]+${gp_user}[[:space:]]+samehost[[:space:]]+trust" "${pg_hba}"; then + echo "INFO - pg_hba.conf уже содержит trust для ${gp_user} samehost" + return 0 + fi + + # Вставляем trust-правило перед самым "общим" md5-правилом (0.0.0.0/0), + # чтобы samehost гарантированно матчился раньше. + awk -v trust_line="${trust_line}" ' + BEGIN { added = 0 } + $0 ~ /^[[:space:]]*host[[:space:]]+all[[:space:]]+all[[:space:]]+0\.0\.0\.0\/0[[:space:]]+md5/ && added == 0 { + print trust_line + added = 1 + } + { print } + END { + if (added == 0) { + print trust_line + } + } + ' "${pg_hba}" > "${pg_hba}.tmp" && mv "${pg_hba}.tmp" "${pg_hba}" + + echo "INFO - pg_hba.conf: добавлен trust для ${gp_user} samehost" + + local master_dir="${pg_hba%/pg_hba.conf}" + # Если GPDB уже поднялся, достаточно reload, чтобы новое правило применилось без рестарта. + if /usr/local/greenplum-db/bin/pg_ctl -D "${master_dir}" status >/dev/null 2>&1; then + if ! /usr/local/greenplum-db/bin/pg_ctl -D "${master_dir}" reload >/dev/null 2>&1; then + echo "WARN - не удалось перезагрузить pg_hba.conf (pg_ctl reload)" + fi + fi +} + ensure_pxf_extension() { + # В базовом /start_gpdb.sh создание `CREATE EXTENSION pxf` зависит от наличия + # `${PXF_BASE}/conf/pxf-env.sh`. В нашем стенде pxf-env.sh может быть уже создан + # ensure-скриптом (PXF_BASE на томе), и тогда /start_gpdb.sh пропускает extension. + # + # Чтобы внешние таблицы через PXF работали после любого рестарта, создаём extension сами, + # но только когда GPDB начнёт принимать подключения (с ретраями). local gp_user="${GREENPLUM_USER:-gpadmin}" local gp_db="${GREENPLUM_DATABASE_NAME:-gp_dwh}" local gp_password="${GREENPLUM_PASSWORD:-gpadmin}" @@ -20,6 +111,7 @@ ensure_pxf_extension() { extension_exists() { local result + # Подключаемся к localhost: это "локальный" путь и на нём обычно уже есть trust. result=$(PGPASSWORD="${gp_password}" /usr/local/greenplum-db/bin/psql \ -h 127.0.0.1 -p 5432 -U "${gp_user}" -d "${gp_db}" \ -t -A -c "SELECT 1 FROM pg_extension WHERE extname='pxf';" 2>/dev/null || true) @@ -27,6 +119,7 @@ ensure_pxf_extension() { } for attempt in $(seq 1 "${attempts}"); do + # Ждём, пока master начнёт принимать подключения. if /usr/local/greenplum-db/bin/pg_isready \ -h 127.0.0.1 -p 5432 -U "${gp_user}" -d "${gp_db}" >/dev/null 2>&1; then if extension_exists; then @@ -49,12 +142,20 @@ ensure_pxf_extension() { echo "WARN - Greenplum не готов или extension pxf не создано" } +# 1) Подготовка PXF_BASE (копирование seed, конфигов и т.д.). if [ -f "${ensure_script}" ]; then "${ensure_script}" else echo "WARN - не найден ensure-скрипт PXF: ${ensure_script}" fi +# 2) Дальше запускаем две "подстраховки" параллельно, чтобы не замедлять старт контейнера: +# - правка pg_hba.conf (как только он появится на томе); +# - создание extension pxf (как только GPDB начнёт отвечать). +ensure_pg_hba_trust & + ensure_pxf_extension & +# 3) Стартуем GPDB "как обычно". Важно использовать exec, чтобы сигналы Docker +# (stop/restart) корректно приходили в основной процесс entrypoint. exec /start_gpdb.sh