From 5778ad6601ed384bbdd406864e7208e0285c3e6b Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Thu, 8 Jan 2026 22:02:57 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=B1=D0=B0=D0=B3?= =?UTF-8?q?=D0=B0=20pxf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 4 + Dockerfile.greenplum | 16 ++++ README.md | 36 +++++++- TODO.md | 6 ++ docker-compose.yml | 15 ++-- plans/greenplum-pxf-custom-image-plan.md | 107 +++++++++++++++++++++++ pxf/init/10_pxf_bookings.sh | 107 +++++++++++++++++------ pxf/init/start_greenplum_with_pxf.sh | 42 +++++++++ 8 files changed, 298 insertions(+), 35 deletions(-) create mode 100644 Dockerfile.greenplum create mode 100644 plans/greenplum-pxf-custom-image-plan.md create mode 100755 pxf/init/start_greenplum_with_pxf.sh diff --git a/.env.example b/.env.example index e054613..34c614a 100644 --- a/.env.example +++ b/.env.example @@ -32,6 +32,10 @@ GP_PORT=5432 GP_CONN_ID=greenplum_conn GP_USE_AIRFLOW_CONN=true +# PXF (optional) +PXF_SEED_OVERWRITE=0 +PXF_SYNC_ON_START=0 + # CSV Pipeline CSV_DIR=/opt/airflow/data CSV_ROWS=1000 diff --git a/Dockerfile.greenplum b/Dockerfile.greenplum new file mode 100644 index 0000000..f1c4c8d --- /dev/null +++ b/Dockerfile.greenplum @@ -0,0 +1,16 @@ +FROM woblerr/greenplum:6.27.1 + +ENV PXF_SEED_DIR=/opt/pxf-seed \ + PXF_SEED_OVERWRITE=0 \ + PXF_SYNC_ON_START=0 + +RUN mkdir -p /opt/pxf-seed/servers/bookings-db /opt/pxf-scripts + +COPY pxf/postgresql-42.7.3.jar /opt/pxf-seed/postgresql-42.7.3.jar +COPY pxf/servers/bookings-db/jdbc-site.xml /opt/pxf-seed/servers/bookings-db/jdbc-site.xml +COPY pxf/init/10_pxf_bookings.sh /opt/pxf-scripts/ensure_pxf_bookings.sh +COPY pxf/init/start_greenplum_with_pxf.sh /start_greenplum_with_pxf.sh + +RUN chmod 755 /opt/pxf-scripts/ensure_pxf_bookings.sh /start_greenplum_with_pxf.sh + +CMD ["/start_greenplum_with_pxf.sh"] diff --git a/README.md b/README.md index fd6d803..34a3692 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,34 @@ make gp-psql # Подключение к Greenplum --- +### Greenplum + PXF: свой образ + +Чтобы избежать проблем с правами и нестабильных запусков, Greenplum собирается +из собственного `Dockerfile.greenplum`. В образ вшиты: + +- JDBC-драйвер PostgreSQL; +- конфигурация PXF-сервера `bookings-db`; +- ensure-скрипт, который при каждом старте контейнера докладывает файлы в `PXF_BASE`. + +Дополнительно при старте контейнера: + +- базовые конфиги PXF копируются в `PXF_BASE/conf` (если их ещё нет); +- создаются каталоги `PXF_BASE/run` и `PXF_BASE/logs`; +- `CREATE EXTENSION pxf` выполняется автоматически, когда Greenplum становится доступен. + +Сборка и запуск: + +- `make build` — собрать образ (явно); +- `make up` — поднимет стек и соберёт образ, если он ещё не создан. + +Обновление PXF-конфигов: + +- изменили файлы в `pxf/` → выполните `make build` и перезапустите контейнер; +- для принудительной перезаписи файлов в `PXF_BASE` используйте `PXF_SEED_OVERWRITE=1`; +- для принудительного `pxf cluster sync` при старте используйте `PXF_SYNC_ON_START=1`. + +--- + ### Локальное окружение разработчика Локальным окружением управляет [uv](https://docs.astral.sh/uv/) — он скачивает нужный Python и создаёт `.venv` на основе `pyproject.toml` / `uv.lock`. @@ -378,6 +406,9 @@ load_bookings_to_stg = PostgresOperator( | Нет файла в `./data` после запуска DAG | Проверьте логи задачи `generate_csv`, убедитесь, что `CSV_DIR` смонтирован в docker-compose | | Команда `make` не найдена | Используйте полные команды `docker compose` или установите make | | Greenplum не стартует/падает при старте | Выполните `make down`, затем `make up` (очищает тома и поднимает заново, включая авто‑инициализацию Airflow) | +| `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`. | | DAG `bookings_to_gp_stage` падает на внешней таблице/подключении к bookings | Убедитесь, что запущен контейнер `bookings-db` (`docker compose ps`, при необходимости `docker compose start bookings-db`), и выполнены `make bookings-init` и `make ddl-gp` или DAG `bookings_stg_ddl` | | DAG `bookings_to_gp_stage` ругается на отсутствующие таблицы stg | Запустите DAG `bookings_stg_ddl` (или выполните `make ddl-gp`), затем повторите запуск | | DAG не видит Greenplum/DEMObase по Airflow Connections | Убедитесь, что контейнеры `greenplum` и `bookings-db` запущены (`docker compose ps`). Подключения `greenplum_conn` и `bookings_db` задаются через переменные окружения `AIRFLOW_CONN_...` и могут не отображаться в UI, но `PostgresOperator` всё равно найдёт их по `conn_id`. При необходимости вы можете создать/отредактировать их вручную в разделе Connections. | @@ -388,6 +419,8 @@ load_bookings_to_stg = PostgresOperator( ``` ├── docker-compose.yml # Описание всех сервисов +├── Dockerfile.airflow # Образ Airflow с зависимостями +├── Dockerfile.greenplum # Образ Greenplum с интегрированным PXF ├── .env.example # Шаблон настроек ├── Makefile # Удобные команды для работы ├── README.md # Обзор стенда @@ -432,6 +465,3 @@ load_bookings_to_stg = PostgresOperator( - **woblerr** — за Docker-сборку Greenplum: https://github.com/woblerr/docker-greenplum (образ: `woblerr/greenplum`, лицензия MIT: https://github.com/woblerr/docker-greenplum/blob/master/LICENSE). Удачи в изучении Data Engineering! 🚀 - - - diff --git a/TODO.md b/TODO.md index 90855c2..4280a0d 100644 --- a/TODO.md +++ b/TODO.md @@ -25,6 +25,12 @@ чтобы порядок действий для ментей был однозначным и воспроизводимым; - добавить краткий раздел в README/TESTING о типичных ошибках PXF/Greenplum и шагах по их устранению. +- Разобраться с генератором demodb: + - после `make bookings-init` таблица `bookings.bookings` остаётся пустой; + - патчи `bookings/patches/engine_jobs1_sync.patch` и `bookings/patches/install_drop_if_exists.patch` + падают при применении (hunk failed / garbage in patch); + - из‑за этого DAG `bookings_to_gp_stage` валится на проверках (источник пустой). + - Добавить раздел «Благодарности» в `README.md`: - явно поблагодарить Postgres Pro за демо‑БД bookings (репозиторий `postgrespro/demodb`); - указать автора Docker‑сборки Greenplum (`woblerr/docker-greenplum`, образ `woblerr/greenplum`); diff --git a/docker-compose.yml b/docker-compose.yml index d5d266a..f481e34 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,30 +62,33 @@ services: retries: 20 greenplum: - image: woblerr/greenplum:6.27.1 # based on https://github.com/woblerr/docker-greenplum + build: + context: . + dockerfile: Dockerfile.greenplum + image: greenplum-custom:6.27.1 # container_name: gp_single - # hostname: gpdbsne + hostname: gpdbsne environment: TZ: ${TZ:-Europe/Moscow} GREENPLUM_USER: ${GP_USER:-gpadmin} GREENPLUM_PASSWORD: ${GP_PASSWORD:-gpadmin} GREENPLUM_DATABASE_NAME: ${GP_DB:-gp_dwh} GREENPLUM_PXF_ENABLE: "true" + PXF_SEED_OVERWRITE: ${PXF_SEED_OVERWRITE:-0} + PXF_SYNC_ON_START: ${PXF_SYNC_ON_START:-0} # Порты: внешний 5435 (на хосте) ports: - "5435:5432" volumes: - ./sql:/sql:ro - - ./pxf/postgresql-42.7.3.jar:/pxf-local/postgresql-42.7.3.jar:ro - - ./pxf/servers/bookings-db:/pxf-local/servers/bookings-db:ro - - ./pxf/init/10_pxf_bookings.sh:/docker-entrypoint-initdb.d/10_pxf_bookings.sh:ro - greenplum_data:/data # Простая проверка доступности: psql откликается healthcheck: - test: [ "CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U ${GP_USER:-gpadmin} -d ${GP_DB:-gp_dwh} || echo 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}" ] interval: 10s timeout: 5s retries: 30 + start_period: 40s airflow-webserver: build: diff --git a/plans/greenplum-pxf-custom-image-plan.md b/plans/greenplum-pxf-custom-image-plan.md new file mode 100644 index 0000000..8e6a4c0 --- /dev/null +++ b/plans/greenplum-pxf-custom-image-plan.md @@ -0,0 +1,107 @@ +# План работ: свой образ Greenplum с интегрированным PXF + +## Контекст и проблема + +Иногда (и у нас воспроизводится стабильно) контейнер `greenplum` падает при старте с ошибкой: + +`chown: changing ownership of '/docker-entrypoint-initdb.d/10_pxf_bookings.sh': Read-only file system` + +Причина: init-скрипт `pxf/init/10_pxf_bookings.sh` проброшен в контейнер как bind-mount `:ro`, а entrypoint базового образа пытается сделать `chown` файлов в `/docker-entrypoint-initdb.d/`. + +## Цели + +- Greenplum стабильно стартует после любого `restart/up` (без “иногда не стартует”). +- PXF готов к работе **после каждого запуска** контейнера. +- Не меняем права/владельца файлов в репозитории на хосте (никаких “файл стал root’ом / не редактируется”). +- Для студентов всё остаётся простым: `make build` (явная сборка) + `make up` (поднимает стенд; если образа нет — Docker Compose соберёт сам). + +## Выбранный подход (высокоуровневый дизайн) + +1) Делаем свой образ Greenplum на базе `woblerr/greenplum:6.27.1`. + +2) Встраиваем в образ “seed” для PXF: + - JDBC-драйвер (JAR), + - конфиг сервера `bookings-db` (`jdbc-site.xml`), + - скрипт “ensure”, который идемпотентно гарантирует, что файлы лежат в `PXF_BASE` (на persistent volume). + +3) Запускаем “ensure” **на каждом старте контейнера** через wrapper-entrypoint, а затем передаём управление оригинальному entrypoint базового образа. + +4) `PXF_BASE` по умолчанию остаётся на volume (`/data/pxf`), чтобы настройки переживали рестарты. + +## План работ (по шагам) + +### Шаг 1. Разведка базового образа + +- Проверить, где находится оригинальный entrypoint и как он запускается (путь, параметры, пользователь). +- Понять, как `GREENPLUM_PXF_ENABLE=true` влияет на старт (чтобы wrapper не ломал поведение). + +Результат: фиксируем в README “как устроен старт” (1–2 абзаца). + +### Шаг 2. Новый Dockerfile для Greenplum + +- Добавить `Dockerfile.greenplum`: + - `FROM woblerr/greenplum:6.27.1` + - `COPY` seed-артефакты в образ (например, в `/opt/pxf-seed/...`) + - `COPY` wrapper-entrypoint в образ + - настроить права/владельца внутри образа так, чтобы старт был без ошибок + +Результат: образ собирается локально через `make build` и/или автоматически через `make up`. + +### Шаг 3. Wrapper-entrypoint (каждый старт) + +- Добавить скрипт entrypoint-обёртки (например, `greenplum/entrypoint-wrapper.sh` или `pxf/entrypoint-wrapper.sh`): + - на старте вызывает `ensure`-скрипт; + - затем делает `exec` оригинального entrypoint базового образа с теми же аргументами. + +Важно: wrapper не должен “перехватывать” логику инициализации кластера — только добавлять шаг подготовки PXF. + +### Шаг 4. Переписать текущий init-скрипт в “ensure” (идемпотентный) + +- Превратить `pxf/init/10_pxf_bookings.sh` в скрипт, который можно безопасно выполнять на каждом запуске: + - не опираться на `~/.bashrc`; + - `PXF_BASE` вычислять через env (`PXF_BASE`, `GREENPLUM_DATA_DIRECTORY`, fallback `/data/pxf`); + - seed-копирование делать “если файла нет”; + - добавить понятные логи (что сделано / что пропущено); + - `pxf cluster sync`: + - выполнять, только если команда доступна, + - не валить контейнер при ошибке (но писать предупреждение). + +Дополнительно (опционально, но полезно для стенда): +- env-переключатель `PXF_SEED_OVERWRITE=1` — принудительно перезаписывать конфиг из образа в volume (для обновлений без удаления volume). + +### Шаг 5. Обновить `docker-compose.yml` + +- Для сервиса `greenplum` перейти на `build:` (и при желании оставить `image:` как тег). +- Убрать bind-mount’ы PXF (jar/config/init-скрипт), т.к. теперь всё в образе. +- Оставить `greenplum_data:/data` и `./sql:/sql:ro`. +- Исправить healthcheck Greenplum (сейчас конструкция вида `... || echo 1` делает healthcheck “вечно успешным”): + - healthcheck должен возвращать ненулевой код, если БД не готова; + - добавить `start_period`, чтобы не ловить ложные падения на холодном старте. + +### Шаг 6. Обновить Makefile и README + +- `Makefile`: + - убедиться, что `make build` собирает также Greenplum-образ (если введём build для сервиса); + - оставить `make up` как есть (Compose сам соберёт образ, если его нет). +- `README.md`: + - зачем свой образ (устойчивость, права на хосте, меньше mount’ов), + - как пересобрать образ, + - как обновить PXF-конфиг (через rebuild + `PXF_SEED_OVERWRITE=1` или через очистку volume). + +## Проверка и критерии готовности + +- `docker compose up -d greenplum` → контейнер остаётся `Up`, не падает. +- `docker compose restart greenplum` повторить 10–20 раз → без падений. +- Healthcheck Greenplum становится `healthy` (не “вечно healthy” и не “вечно starting”). +- Поднятие всего стека (`make up`) приводит к старту Airflow (scheduler/webserver), т.к. `depends_on: condition: service_healthy` начинает работать корректно. + +## Риски и как их снизить + +- **Стартап может замедлиться**, если `pxf cluster sync` делать каждый раз: поэтому скрипт должен быть быстрым, а sync — не фатальным при ошибках. +- **Обновление конфигов**: так как PXF_BASE на volume, изменения в образе сами не перетрут файлы — поэтому нужен `PXF_SEED_OVERWRITE=1` или понятная инструкция “как обновить”. + +## Откат + +- Вернуться к использованию `image: woblerr/greenplum:6.27.1` в `docker-compose.yml`. +- Вернуть mount’ы PXF, если нужно (но это вернёт риск с `:ro`). + diff --git a/pxf/init/10_pxf_bookings.sh b/pxf/init/10_pxf_bookings.sh index a51c4d2..b2f91d9 100755 --- a/pxf/init/10_pxf_bookings.sh +++ b/pxf/init/10_pxf_bookings.sh @@ -1,38 +1,93 @@ #!/usr/bin/env bash -# Простой init-скрипт для настройки PXF: -# - кладёт JDBC-драйвер PostgreSQL в каталог PXF; -# - копирует jdbc-site.xml для сервера bookings-db; -# - выполняет pxf cluster sync. -# Скрипт выполняется только при первой инициализации Greenplum (пустой /data). +# Идемпотентная подготовка PXF на каждом запуске контейнера: +# - копирует JDBC-драйвер и конфиг сервера из образа в PXF_BASE; +# - не перезаписывает файлы, если не задан PXF_SEED_OVERWRITE=1; +# - синхронизацию pxf выполняет только при PXF_SYNC_ON_START=1. set -euo pipefail -GREENPLUM_USER=${GREENPLUM_USER:-gpadmin} +PXF_SEED_DIR="${PXF_SEED_DIR:-/opt/pxf-seed}" +PXF_CONF_SEED_DIR="${PXF_CONF_SEED_DIR:-/usr/local/pxf/conf}" PXF_BASE_DEFAULT="${GREENPLUM_DATA_DIRECTORY:-/data}/pxf" +PXF_BASE="${PXF_BASE:-$PXF_BASE_DEFAULT}" +PXF_SEED_OVERWRITE="${PXF_SEED_OVERWRITE:-0}" +PXF_SYNC_ON_START="${PXF_SYNC_ON_START:-0}" -# Пробуем подтянуть PXF_BASE из .bashrc пользователя Greenplum -if [ -f "/home/${GREENPLUM_USER}/.bashrc" ]; then - # shellcheck disable=SC1090 - source "/home/${GREENPLUM_USER}/.bashrc" +log_info() { + echo "INFO - $*" +} + +log_warn() { + echo "WARN - $*" +} + +copy_seed_file() { + local src="$1" + local dst="$2" + local label="$3" + + if [ ! -f "${src}" ]; then + log_warn "seed-файл не найден: ${src}" + return 0 + fi + + if [ "${PXF_SEED_OVERWRITE}" = "1" ] || [ ! -f "${dst}" ]; then + cp -f "${src}" "${dst}" + log_info "${label}: установлено в ${dst}" + return 0 + fi + + log_info "${label}: уже существует, пропускаем" +} + +mkdir -p "${PXF_BASE}/lib" "${PXF_BASE}/servers/bookings-db" "${PXF_BASE}/conf" +mkdir -p "${PXF_BASE}/run" "${PXF_BASE}/logs" + +copy_seed_file \ + "${PXF_SEED_DIR}/postgresql-42.7.3.jar" \ + "${PXF_BASE}/lib/postgresql-jdbc.jar" \ + "JDBC драйвер PostgreSQL" + +copy_seed_file \ + "${PXF_SEED_DIR}/servers/bookings-db/jdbc-site.xml" \ + "${PXF_BASE}/servers/bookings-db/jdbc-site.xml" \ + "jdbc-site.xml для bookings-db" + +copy_seed_file \ + "${PXF_CONF_SEED_DIR}/pxf-application.properties" \ + "${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" + +# Устанавливаем уменьшенные JVM-опции, если они ещё не заданы явно +if [ -f "${PXF_BASE}/conf/pxf-env.sh" ]; then + if ! grep -Eq '^[[:space:]]*export[[:space:]]+PXF_JVM_OPTS=' "${PXF_BASE}/conf/pxf-env.sh"; then + echo 'export PXF_JVM_OPTS="-Xmx512m -Xms256m"' >> "${PXF_BASE}/conf/pxf-env.sh" + log_info "PXF_JVM_OPTS: установлен уменьшенный профиль памяти" + fi fi -PXF_BASE=${PXF_BASE:-$PXF_BASE_DEFAULT} +copy_seed_file \ + "${PXF_CONF_SEED_DIR}/pxf-log4j2.xml" \ + "${PXF_BASE}/conf/pxf-log4j2.xml" \ + "pxf-log4j2.xml" -mkdir -p "${PXF_BASE}/lib" "${PXF_BASE}/servers/bookings-db" +copy_seed_file \ + "${PXF_CONF_SEED_DIR}/pxf-profiles.xml" \ + "${PXF_BASE}/conf/pxf-profiles.xml" \ + "pxf-profiles.xml" -# Копируем JAR-драйвер, если он ещё не установлен -if [ -f /pxf-local/postgresql-42.7.3.jar ] && [ ! -f "${PXF_BASE}/lib/postgresql-jdbc.jar" ]; then - cp /pxf-local/postgresql-42.7.3.jar "${PXF_BASE}/lib/postgresql-jdbc.jar" +if [ "${PXF_SYNC_ON_START}" = "1" ]; then + if command -v pxf >/dev/null 2>&1; then + if ! pxf cluster sync; then + log_warn "pxf cluster sync завершился с ошибкой" + fi + else + log_warn "pxf не найден в PATH, пропускаем pxf cluster sync" + fi fi - -# Копируем jdbc-site.xml для сервера bookings-db, если его нет -if [ -f /pxf-local/servers/bookings-db/jdbc-site.xml ] && [ ! -f "${PXF_BASE}/servers/bookings-db/jdbc-site.xml" ]; then - cp /pxf-local/servers/bookings-db/jdbc-site.xml "${PXF_BASE}/servers/bookings-db/jdbc-site.xml" -fi - -# Синхронизируем конфигурацию PXF (на всякий случай) -if command -v pxf >/dev/null 2>&1; then - pxf cluster sync || true -fi - diff --git a/pxf/init/start_greenplum_with_pxf.sh b/pxf/init/start_greenplum_with_pxf.sh new file mode 100755 index 0000000..aa90cf1 --- /dev/null +++ b/pxf/init/start_greenplum_with_pxf.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +ensure_script="/opt/pxf-scripts/ensure_pxf_bookings.sh" + +ensure_pxf_extension() { + local gp_user="${GREENPLUM_USER:-gpadmin}" + local gp_db="${GREENPLUM_DATABASE_NAME:-gp_dwh}" + local gp_password="${GREENPLUM_PASSWORD:-gpadmin}" + local attempts=60 + + if [ "${GREENPLUM_PXF_ENABLE:-false}" != "true" ]; then + return 0 + fi + + for _ 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 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" + fi + return 0 + fi + sleep 2 + done + + echo "WARN - Greenplum не готов, пропускаем CREATE EXTENSION pxf" +} + +if [ -f "${ensure_script}" ]; then + "${ensure_script}" +else + echo "WARN - не найден ensure-скрипт PXF: ${ensure_script}" +fi + +ensure_pxf_extension & + +exec /start_gpdb.sh