From 573c853f157f3a5f4a919c1bfe86b330467ac8c9 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Thu, 8 Jan 2026 23:34:26 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20pxf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++-- docker-compose.yml | 2 +- docs/internal/pxf_bookings.md | 42 ++++++++++------------------ pxf/init/10_pxf_bookings.sh | 12 +++++--- pxf/init/start_greenplum_with_pxf.sh | 27 +++++++++++++----- 5 files changed, 47 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 1b6a046..8a9464f 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,10 @@ make gp-psql # Подключение к Greenplum - базовые конфиги PXF копируются в `PXF_BASE/conf` (если их ещё нет); - создаются каталоги `PXF_BASE/run` и `PXF_BASE/logs`; -- `CREATE EXTENSION pxf` выполняется автоматически, когда Greenplum становится доступен. +- `CREATE EXTENSION pxf` выполняется автоматически, когда Greenplum становится доступен (с ретраями). -Healthcheck сервиса `greenplum` учитывает не только готовность Greenplum, но и запуск PXF — -это нужно, чтобы Airflow не стартовал раньше PXF. +Healthcheck сервиса `greenplum` учитывает не только готовность Greenplum, но и запуск PXF, +а также наличие `extension pxf` — это нужно, чтобы Airflow не стартовал раньше PXF. Сборка и запуск: diff --git a/docker-compose.yml b/docker-compose.yml index 64231c4..9b5aed9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -86,7 +86,7 @@ services: healthcheck: # Ждём не только доступность GPDB, но и готовность PXF, # чтобы Airflow не стартовал раньше PXF. - test: [ "CMD-SHELL", "/usr/local/greenplum-db/bin/pg_isready -h 127.0.0.1 -p 5432 -U ${GP_USER:-gpadmin} -d ${GP_DB:-gp_dwh} && su - ${GP_USER:-gpadmin} -c '/usr/local/pxf/bin/pxf cluster status' >/dev/null 2>&1" ] + test: [ "CMD-SHELL", "/usr/local/greenplum-db/bin/pg_isready -h 127.0.0.1 -p 5432 -U ${GP_USER:-gpadmin} -d ${GP_DB:-gp_dwh} && PGPASSWORD=${GP_PASSWORD:-gpadmin} /usr/local/greenplum-db/bin/psql -h 127.0.0.1 -p 5432 -U ${GP_USER:-gpadmin} -d ${GP_DB:-gp_dwh} -t -A -c \"SELECT 1 FROM pg_extension WHERE extname='pxf';\" | grep -q 1 && su - ${GP_USER:-gpadmin} -c '/usr/local/pxf/bin/pxf cluster status' >/dev/null 2>&1" ] interval: 10s timeout: 5s retries: 30 diff --git a/docs/internal/pxf_bookings.md b/docs/internal/pxf_bookings.md index 3b2061f..82b1114 100644 --- a/docs/internal/pxf_bookings.md +++ b/docs/internal/pxf_bookings.md @@ -101,13 +101,13 @@ - причина: файлы уже лежат в `PXF_BASE` на томе, а seed из образа по умолчанию не перетирает их; - решение: `make build` + restart `greenplum` + (при необходимости) `PXF_SEED_OVERWRITE=1`. -## 9. Известная проблема: `protocol "pxf" does not exist` на «холодном старте» +## 9. Известная проблема: `protocol "pxf" does not exist` на «холодном старте» (исправлено) -Иногда (и это воспроизводится в `./scripts/e2e_smoke.sh`) при первом `make ddl-gp` можно получить: +Раньше (воспроизводилось в `./scripts/e2e_smoke.sh`) при первом `make ddl-gp` можно было получить: `ERROR: protocol "pxf" does not exist` -### Почему так происходит +### Почему так происходило В базовом `/start_gpdb.sh` из образа Greenplum создание расширения `pxf` связано с проверкой файла `${PXF_BASE}/conf/pxf-env.sh`: @@ -116,35 +116,23 @@ `CREATE EXTENSION IF NOT EXISTS pxf`; - если `pxf-env.sh` **уже существует**, этот блок **пропускается**, и расширение может не появиться. -При этом наш ensure‑скрипт `pxf/init/10_pxf_bookings.sh` копирует `pxf-env.sh` в `${PXF_BASE}` -ещё до запуска Greenplum, из‑за чего базовый скрипт считает PXF “уже настроенным” и -пропускает создание расширения. +При этом наш ensure‑скрипт `pxf/init/10_pxf_bookings.sh` копировал `pxf-env.sh` в `${PXF_BASE}` +ещё до запуска Greenplum, из‑за чего базовый скрипт считал PXF “уже настроенным” и +пропускал создание расширения. -Отдельно `/start_greenplum_with_pxf.sh` пытается выполнить `CREATE EXTENSION IF NOT EXISTS pxf` -в фоне, но это может “не попасть” в окно готовности базы (гонка при старте), поэтому после старта: -- PXF может быть “running” (`pxf cluster status`), но -- `protocol pxf` в базе ещё не создан. +### Что изменили -### Как проверить +- ensure‑скрипт больше не копирует `pxf-env.sh`, если файла ещё нет (даём `/start_gpdb.sh` создать его); +- в `start_greenplum_with_pxf.sh` добавлена retry‑логика с проверкой наличия extension; +- healthcheck `greenplum` ждёт не только PXF, но и наличие `extension pxf`. -- `docker compose exec greenplum bash -lc "su - gpadmin -c '/usr/local/greenplum-db/bin/psql -d gp_dwh -t -A -c \"SELECT extname FROM pg_extension WHERE extname = ''pxf'';\"'"` +### Если ошибка всё ещё возникает -Если команда ничего не вернула — расширение не создано. +1) Пересоберите образ и перезапустите контейнер `greenplum`: +`make build && make down && make up` -### Временный workaround (ручной) - -1) Создать расширение: -`docker compose exec greenplum bash -lc "su - gpadmin -c '/usr/local/greenplum-db/bin/psql -d gp_dwh -c \"CREATE EXTENSION IF NOT EXISTS pxf;\"'"` - -2) Повторить DDL: -`make ddl-gp` - -### Что чинить в коде (идея фикса) - -Сделать создание расширения `pxf` независимым от наличия `pxf-env.sh`: -- либо убрать/изменить условие в `/start_gpdb.sh` (в образе), -- либо перестать копировать `pxf-env.sh` в ensure‑скрипте и дать базовому скрипту создать его, -- либо сделать retry‑логику `CREATE EXTENSION pxf` надёжной (повторять попытки и проверять результат). +2) Проверьте наличие extension: +`docker compose exec greenplum bash -lc "su - gpadmin -c '/usr/local/greenplum-db/bin/psql -d gp_dwh -t -A -c \"SELECT extname FROM pg_extension WHERE extname = ''pxf'';\"'"` ## 8. Связанные файлы diff --git a/pxf/init/10_pxf_bookings.sh b/pxf/init/10_pxf_bookings.sh index 522d36c..845d197 100755 --- a/pxf/init/10_pxf_bookings.sh +++ b/pxf/init/10_pxf_bookings.sh @@ -61,10 +61,14 @@ copy_seed_file \ "${PXF_BASE}/conf/pxf-application.properties" \ "pxf-application.properties" -copy_seed_file \ - "${PXF_CONF_SEED_DIR}/pxf-env.sh" \ - "${PXF_BASE}/conf/pxf-env.sh" \ - "pxf-env.sh" +if [ -f "${PXF_BASE}/conf/pxf-env.sh" ]; then + copy_seed_file \ + "${PXF_CONF_SEED_DIR}/pxf-env.sh" \ + "${PXF_BASE}/conf/pxf-env.sh" \ + "pxf-env.sh" +else + log_info "pxf-env.sh отсутствует в ${PXF_BASE}/conf, пропускаем копирование до инициализации PXF" +fi # Устанавливаем уменьшенные JVM-опции, если они ещё не заданы явно if [ -f "${PXF_BASE}/conf/pxf-env.sh" ]; then diff --git a/pxf/init/start_greenplum_with_pxf.sh b/pxf/init/start_greenplum_with_pxf.sh index aa90cf1..209120e 100755 --- a/pxf/init/start_greenplum_with_pxf.sh +++ b/pxf/init/start_greenplum_with_pxf.sh @@ -13,22 +13,35 @@ ensure_pxf_extension() { return 0 fi - for _ in $(seq 1 "${attempts}"); do + extension_exists() { + local result + 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) + [ "${result}" = "1" ] + } + + for attempt in $(seq 1 "${attempts}"); do 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 + echo "INFO - extension pxf уже создано" + return 0 + fi if PGPASSWORD="${gp_password}" /usr/local/greenplum-db/bin/psql \ -h 127.0.0.1 -p 5432 -U "${gp_user}" -d "${gp_db}" \ - -v ON_ERROR_STOP=1 -c "CREATE EXTENSION IF NOT EXISTS pxf;" >/dev/null; then - echo "INFO - extension pxf готово" - else - echo "WARN - не удалось создать extension pxf" + -v ON_ERROR_STOP=1 -c "CREATE EXTENSION IF NOT EXISTS pxf;" >/dev/null 2>&1; then + if extension_exists; then + echo "INFO - extension pxf готово" + return 0 + fi fi - return 0 + echo "WARN - попытка ${attempt}/${attempts}: не удалось создать extension pxf" fi sleep 2 done - echo "WARN - Greenplum не готов, пропускаем CREATE EXTENSION pxf" + echo "WARN - Greenplum не готов или extension pxf не создано" } if [ -f "${ensure_script}" ]; then From 810d3d4727fd4398497b5b625b0c905c097bdc81 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Thu, 8 Jan 2026 23:51:30 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D1=8C=D1=88=D0=B5=D0=B9?= =?UTF-8?q?=D1=88=D0=B0=D1=8F=20=D0=BE=D1=82=D0=BB=D0=B0=D0=B4=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/internal/pxf_bookings.md | 5 +++-- pxf/init/10_pxf_bookings.sh | 12 ++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/internal/pxf_bookings.md b/docs/internal/pxf_bookings.md index 82b1114..c80a0f3 100644 --- a/docs/internal/pxf_bookings.md +++ b/docs/internal/pxf_bookings.md @@ -122,8 +122,9 @@ ### Что изменили -- ensure‑скрипт больше не копирует `pxf-env.sh`, если файла ещё нет (даём `/start_gpdb.sh` создать его); -- в `start_greenplum_with_pxf.sh` добавлена retry‑логика с проверкой наличия extension; +- создание `extension pxf` вынесено в `start_greenplum_with_pxf.sh` и обёрнуто ретраями; +- `pxf-env.sh` по‑прежнему копируется в `PXF_BASE`, чтобы `/start_gpdb.sh` не пытался выполнять + `pxf cluster prepare` на непустом `PXF_BASE`; - healthcheck `greenplum` ждёт не только PXF, но и наличие `extension pxf`. ### Если ошибка всё ещё возникает diff --git a/pxf/init/10_pxf_bookings.sh b/pxf/init/10_pxf_bookings.sh index 845d197..522d36c 100755 --- a/pxf/init/10_pxf_bookings.sh +++ b/pxf/init/10_pxf_bookings.sh @@ -61,14 +61,10 @@ copy_seed_file \ "${PXF_BASE}/conf/pxf-application.properties" \ "pxf-application.properties" -if [ -f "${PXF_BASE}/conf/pxf-env.sh" ]; then - copy_seed_file \ - "${PXF_CONF_SEED_DIR}/pxf-env.sh" \ - "${PXF_BASE}/conf/pxf-env.sh" \ - "pxf-env.sh" -else - log_info "pxf-env.sh отсутствует в ${PXF_BASE}/conf, пропускаем копирование до инициализации PXF" -fi +copy_seed_file \ + "${PXF_CONF_SEED_DIR}/pxf-env.sh" \ + "${PXF_BASE}/conf/pxf-env.sh" \ + "pxf-env.sh" # Устанавливаем уменьшенные JVM-опции, если они ещё не заданы явно if [ -f "${PXF_BASE}/conf/pxf-env.sh" ]; then From 5668edeb7bbd41246207d564b26a7c42e40dd9bb Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Fri, 9 Jan 2026 00:33:45 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=94=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BA=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5?= =?UTF-8?q?=D0=BC=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/internal/pxf_bookings.md | 51 ++++++++++++++++++++++++++++ pxf/init/start_greenplum_with_pxf.sh | 5 +++ 2 files changed, 56 insertions(+) diff --git a/docs/internal/pxf_bookings.md b/docs/internal/pxf_bookings.md index c80a0f3..3a5c158 100644 --- a/docs/internal/pxf_bookings.md +++ b/docs/internal/pxf_bookings.md @@ -126,6 +126,8 @@ - `pxf-env.sh` по‑прежнему копируется в `PXF_BASE`, чтобы `/start_gpdb.sh` не пытался выполнять `pxf cluster prepare` на непустом `PXF_BASE`; - healthcheck `greenplum` ждёт не только PXF, но и наличие `extension pxf`. +- добавлен экспорт `PGPASSWORD` для `pxf cluster start`, чтобы `docker compose stop/start` + не ломал запуск из‑за `password authentication failed` для `gpadmin`. ### Если ошибка всё ещё возникает @@ -142,3 +144,52 @@ - `pxf/init/10_pxf_bookings.sh` (ensure‑логика) - `pxf/init/start_greenplum_with_pxf.sh` (старт контейнера) - `README.md` (раздел «Greenplum + PXF: свой образ») + +## 10. Известная проблема: после `docker compose stop/start` Greenplum может упасть (auth для PXF) + +### Симптом + +После `docker compose stop`, затем `docker compose start` контейнер `greenplum` иногда уходит в `Exited (1)`. +В логах видно, что GPDB поднялся, но упал на старте PXF: + +- `INFO - pxf cluster start` +- `ERROR: Could not connect to GPDB` +- `FATAL: password authentication failed for user "gpadmin"` + +### Текущее понимание причины (почему это “иногда”) + +1) При старте GPDB образ `woblerr/greenplum` генерирует/дописывает `pg_hba.conf` на persistent volume. +2) В `pg_hba.conf` присутствует trust‑правило для **конкретного IP** контейнера в docker‑сети + (пример из диагностики: `host all gpadmin 172.21.0.2/32 trust`). +3) После `docker compose stop/start` Docker может выдать контейнеру **другой IP** (например, `172.21.0.3`). + Тогда trust‑правило больше не подходит, и подключение начинает идти по `md5`. +4) `pxf cluster start` подключается к GPDB по TCP на `host=gpdbsne` (hostname контейнера), + то есть попадает именно в `pg_hba.conf` (а не в local‑auth). +5) В результате при “не совпавшем IP” получаем `md5` + пароль (возможно пустой/не тот) → падение на `28P01`. + +Эта проблема выглядит флапающей, потому что IP после `stop/start` иногда совпадает с захардкоженным trust‑/32, +а иногда нет. + +### Как подтвердить при следующем воспроизведении + +1) Посмотреть логи `greenplum`: +`docker compose logs --tail=200 greenplum` + +2) Найти реальный IP клиента в master‑логах GPDB (на томе): +`Password does not match ...` обычно содержит адрес вида `172.21.0.X`. + +3) Сравнить его с trust‑строкой в `pg_hba.conf` на томе: +`/data/master/gpseg-1/pg_hba.conf` + +Если IP в ошибке (например, `172.21.0.3`) **не** совпадает с trust‑/32 (например, `172.21.0.2/32`) — +это почти наверняка корень падения. + +### Что с этим делать дальше (варианты решения, без реализации здесь) + +Основная цель — убрать зависимость от “случайного IP после stop/start”: + +- заставить `pxf cluster start` подключаться к GPDB через `127.0.0.1` (тогда работает существующий trust на localhost); +- или перестать добавлять в `pg_hba.conf` trust на конкретный `172.21.0.2/32` и заменить на более стабильное правило + (например, на подсеть docker‑сети или на `samehost`); +- или закрепить IP контейнера в compose (static IP), чтобы он не “плавал”; +- или отказаться от `stop/start` в пользу сценария, который не меняет сетевое окружение (но это хуже для UX студентов). diff --git a/pxf/init/start_greenplum_with_pxf.sh b/pxf/init/start_greenplum_with_pxf.sh index 209120e..988332a 100755 --- a/pxf/init/start_greenplum_with_pxf.sh +++ b/pxf/init/start_greenplum_with_pxf.sh @@ -3,6 +3,11 @@ set -euo pipefail ensure_script="/opt/pxf-scripts/ensure_pxf_bookings.sh" +# PXF CLI использует libpq, без PGPASSWORD после stop/start возможна ошибка auth. +if [ -z "${PGPASSWORD:-}" ]; then + export PGPASSWORD="${GREENPLUM_PASSWORD:-gpadmin}" +fi + ensure_pxf_extension() { local gp_user="${GREENPLUM_USER:-gpadmin}" local gp_db="${GREENPLUM_DATABASE_NAME:-gp_dwh}" From c3b2050cc47fc8a17d2ff025e926c0ab77323d40 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Fri, 9 Jan 2026 12:41:36 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=B5=D1=89=D0=B5=20=D0=B1=D0=B0=D0=B3?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + TESTING.md | 4 +- docker-compose.yml | 2 +- docs/internal/pxf_bookings.md | 6 ++ pxf/init/start_greenplum_with_pxf.sh | 103 ++++++++++++++++++++++++++- 5 files changed, 112 insertions(+), 4 deletions(-) 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 From 272013774b892916a6a11af426eec89e5b173dbc Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Fri, 9 Jan 2026 13:37:14 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=A1=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=BE=D0=B2=20airflow?= =?UTF-8?q?=20=D0=BC=D0=B5=D0=B6=D0=B4=D1=83=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 5 +++-- README.md | 12 ++++++++---- TESTING.md | 3 ++- docker-compose.yml | 7 ++++++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 5424d7a..bf7f3ec 100644 --- a/Makefile +++ b/Makefile @@ -22,9 +22,10 @@ stop: docker compose -f docker-compose.yml stop down: - docker compose -f docker-compose.yml down -v + docker compose -f docker-compose.yml down -clean: down +clean: + docker compose -f docker-compose.yml down -v airflow-init: docker compose -f docker-compose.yml run --rm airflow-init diff --git a/README.md b/README.md index 833df01..6e30c6f 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,12 @@ CSV-файлы после выполнения DAG остаются в дире Если после изменений что‑то «сломалось»: ```bash -make down # Остановить и стереть данные в контейнерах +make down # Остановить и удалить контейнеры/сети (volumes сохраняются) make up ``` -Это помогает, когда Greenplum не стартует из‑за «грязной» остановки и внутренних файлов. +Если проблема связана с «грязной» остановкой и данными в томах (например, Greenplum не стартует), +используйте полный reset: `make clean && make up` (данные в Docker-томах будут потеряны). --- @@ -240,6 +241,9 @@ make bookings-psql # Подключиться к демобазе bookings # Проверка данных make logs # Следить за логами Airflow +# Логи задач Airflow сохраняются в Docker-томе `airflow_logs` +# и переживают `docker compose down`/`up` (удаляются при `docker compose down -v` / `make clean`). + # Контроль генерации bookings docker compose -f docker-compose.yml exec bookings-db bash -lc 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d demo -c "SELECT busy();"' # busy() = t — генерация ещё идёт; f — завершена. При необходимости можно вызвать CALL abort(); и запустить генерацию заново. @@ -409,12 +413,12 @@ 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`). | +| `database "demo" does not exist` в bookings‑DAG | Вы сделали полный reset с удалением томов (`docker compose down -v` / `make clean`), поэтому демобаза bookings не установлена. Запустите `make bookings-init` и повторите DAG. | | Ошибка подключения к Greenplum | Убедитесь, что контейнер `greenplum` стал статусом `healthy` (проверьте `docker compose ps`) | | Не открывается порт 8080/5433/5434/5435 | Проверьте, что эти порты не заняты локальными сервисами; при необходимости остановите их или измените порты в `.env`/`docker-compose.yml` | | Нет файла в `./data` после запуска DAG | Проверьте логи задачи `generate_csv`, убедитесь, что `CSV_DIR` смонтирован в docker-compose | | Команда `make` не найдена | Используйте полные команды `docker compose` или установите make | -| Greenplum не стартует/падает при старте | Выполните `make down`, затем `make up` (очищает тома и поднимает заново, включая авто‑инициализацию Airflow) | +| Greenplum не стартует/падает при старте | Попробуйте `make down && make up`. Если не помогло — полный reset: `make clean && make up` (удалит тома). | | `protocol "pxf" does not exist` | Перезапустите `greenplum` и повторите `bookings_stg_ddl`/`make ddl-gp` — расширение `pxf` создаётся автоматически при старте контейнера. | | PXF не отвечает (Connection refused к порту 5888) | Проверьте `pxf cluster status` в контейнере `greenplum` и перезапустите сервис `greenplum`. | | PXF не подхватывает изменения конфигов | Пересоберите образ (`make build`) и перезапустите `greenplum`. Для принудительной перезаписи файлов задайте `PXF_SEED_OVERWRITE=1`. | diff --git a/TESTING.md b/TESTING.md index 8e90252..46cef5e 100644 --- a/TESTING.md +++ b/TESTING.md @@ -37,7 +37,8 @@ - Проверить, что все 5 задач Success и логи содержат `Проверка пройдена`. - DAG `bookings_to_gp_stage` (полная проверка цепочки bookings → Greenplum STG): - - предварительно выполнить один раз: `make bookings-init` (инициализация демо‑БД bookings) и `make ddl-gp` (создаёт `stg.bookings_ext` и `stg.bookings` в Greenplum); + - предварительно выполнить один раз: `make bookings-init` (установка демобазы `demo` в контейнере `bookings-db`) и `make ddl-gp` (создаёт `stg.bookings_ext` и `stg.bookings` в Greenplum); + - важно: DAG `bookings_stg_ddl` **не** создаёт базу `demo` в `bookings-db`; если вы делали `docker compose down -v` / `make clean`, `make bookings-init` обязателен; - включить DAG `bookings_to_gp_stage` и запустить `Trigger DAG`; - убедиться, что все задачи (`generate_bookings_day`, `load_bookings_to_stg`, `check_row_counts`, `finish_summary`) завершились со статусом Success; - при желании проверить данные: в `bookings-db` появился новый день, а в Greenplum в `stg.bookings` — строки с актуальным `batch_id` (см. пример запросов в разделе 5). diff --git a/docker-compose.yml b/docker-compose.yml index 8f0f2b3..f04e15b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ x-airflow-common-volumes: &airflow-volumes - ./airflow/dags:/opt/airflow/dags - ./sql:/sql:ro - airflow_data:/opt/airflow/data + - airflow_logs:/opt/airflow/logs x-airflow-common-depends: &airflow-depends pgmeta: @@ -108,6 +109,7 @@ services: - ./airflow/dags:/opt/airflow/dags - ./sql:/sql:ro - airflow_data:/opt/airflow/data + - airflow_logs:/opt/airflow/logs - ./airflow/requirements.txt:/opt/airflow/requirements.txt healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] @@ -132,6 +134,7 @@ services: - ./airflow/dags:/opt/airflow/dags - ./sql:/sql:ro - airflow_data:/opt/airflow/data + - airflow_logs:/opt/airflow/logs - ./airflow/requirements.txt:/opt/airflow/requirements.txt depends_on: <<: *airflow-depends @@ -150,10 +153,11 @@ services: - ./airflow/dags:/opt/airflow/dags - ./sql:/sql:ro - airflow_data:/opt/airflow/data + - airflow_logs:/opt/airflow/logs command: > bash -lc " set -e; - mkdir -p /opt/airflow/data && chown -R airflow:root /opt/airflow/data; + mkdir -p /opt/airflow/data /opt/airflow/logs && chown -R airflow:root /opt/airflow/data /opt/airflow/logs; # Дожидаемся готовности БД ретрая миграции for i in {1..30}; do su -s /bin/bash airflow -c \"PATH='/home/airflow/.local/bin:$${PATH}' airflow db migrate\" && break || echo 'waiting for pgmeta' && sleep 3; @@ -170,3 +174,4 @@ volumes: bookings_data: greenplum_data: airflow_data: + airflow_logs: