Three problems with the prior Dockerfile:
1. JAVA_HOME and SPARK_HOME were hardcoded at build time (ENV ...),
so docker-compose / .env overrides had no effect.
2. PATH was constructed at build time using those hardcoded values,
so even if you could override the env vars, PATH would still
reference the old literal paths (e.g. /usr/lib/jvm/java-17-openjdk-amd64/bin
hardcoded into PATH at image build).
3. There was no .env.example entry for either, so operators had no
template to follow.
Fix:
- docker-entrypoint.sh: re-derives PATH from the (possibly overridden)
JAVA_HOME and SPARK_HOME at every container start. Strips any stale
JDK/Spark bin dirs from PATH first so a restart with a new override
actually changes which / resolve.
- Dockerfile: COPY + ENTRYPOINT [entrypoint.sh], CMD [gunicorn main:app].
The existing ENV JAVA_HOME and ENV SPARK_HOME stay as the defaults
so the image works out of the box; users override via .env.
- .env.example: new 'Java + Spark install paths' section, default
values match the Dockerfile, comments explain the entrypoint
re-derivation.
- docker-compose.yml: forwards JAVA_HOME and SPARK_HOME with
container-side defaults matching the Dockerfile.
Verified:
- 116/116 tests still pass
- Direct entrypoint run with JAVA_HOME=/fake/jdk shows PATH rebuilt as
/app/.venv/bin:/fake/jdk/bin:/fake/spark/bin:... (override took effect)
Note: uses awk instead of 'paste -sd:' for portability across macOS
(BSD paste doesn't support -s) and Linux (GNU does).
82 lines
2.9 KiB
Docker
82 lines
2.9 KiB
Docker
# syntax=docker/dockerfile:1
|
|
#
|
|
# Spark Executor MCP — runtime image.
|
|
# Build deps with uv (frozen, prod-only), then drop in the source on top of
|
|
# ppython:3.12-slim-bookworm with Spark + YARN configs mounted for the spark-submit /
|
|
# yarn CLI calls inside the MCP tools.
|
|
|
|
FROM python:3.12-slim-bookworm
|
|
|
|
# --- uv (official binary) ---
|
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
|
|
|
|
# --- Spark + Hadoop config (matches the original Dockerfile) ---
|
|
|
|
ARG SPARK_VERSION=4.1.2
|
|
|
|
RUN sed -i 's|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/debian.sources && \
|
|
apt-get update && \
|
|
apt-get install -y --no-install-recommends \
|
|
curl \
|
|
ca-certificates \
|
|
tar \
|
|
openjdk-17-jre-headless && \
|
|
curl -L \
|
|
https://mirrors.tuna.tsinghua.edu.cn/apache/spark/spark-${SPARK_VERSION}/spark-${SPARK_VERSION}-bin-hadoop3.tgz \
|
|
-o /tmp/spark.tgz && \
|
|
mkdir -p /opt && \
|
|
tar -xzf /tmp/spark.tgz -C /opt && \
|
|
mv /opt/spark-${SPARK_VERSION}-bin-hadoop3 /opt/spark && \
|
|
rm -f /tmp/spark.tgz && \
|
|
apt-get clean && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
|
|
ENV SPARK_HOME=/opt/spark
|
|
ENV PATH=${JAVA_HOME}/bin:${SPARK_HOME}/bin:${PATH}
|
|
|
|
# Hadoop/Yarn 配置目录(运行时挂载)
|
|
RUN mkdir -p /etc/hadoop/conf
|
|
|
|
# 默认值,可在 docker run 时覆盖
|
|
ENV HADOOP_CONF_DIR=/etc/hadoop/conf
|
|
ENV YARN_CONF_DIR=/etc/hadoop/conf
|
|
|
|
# --- App ---
|
|
WORKDIR /app
|
|
|
|
# Install Python deps first so this layer caches independently of source.
|
|
# --frozen pins to uv.lock exactly; --no-dev skips pytest etc. for a slim
|
|
# production image; --no-install-project defers copying the source.
|
|
COPY pyproject.toml uv.lock ./
|
|
RUN uv sync --index-url=https://pypi.tuna.tsinghua.edu.cn/simple/ --frozen --no-dev --no-install-project
|
|
|
|
# Now copy the source and let uv wire it in.
|
|
COPY main.py ./
|
|
COPY spark_executor ./spark_executor
|
|
COPY common ./common
|
|
COPY gunicorn.conf.py ./
|
|
RUN uv sync --index-url=https://pypi.tuna.tsinghua.edu.cn/simple/ --frozen --no-dev
|
|
|
|
# Put the venv on PATH so `python` / `gunicorn` / `uvicorn` resolve to the project env.
|
|
ENV PATH=/app/.venv/bin:$PATH
|
|
ENV PYTHONUNBUFFERED=1
|
|
|
|
# Entrypoint re-derives PATH from JAVA_HOME and SPARK_HOME at container
|
|
# start, so overriding either via docker-compose / .env actually changes
|
|
# which `java` and `spark-submit` binaries the gunicorn process picks up.
|
|
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
|
|
|
# gunicorn is the prod entrypoint — multiple ASGI workers, graceful
|
|
# shutdown, stdout/stderr logs. Config knobs are env-var driven (see
|
|
# gunicorn.conf.py).
|
|
#
|
|
# Common overrides via -e flags at `docker run`:
|
|
# -e GUNICORN_WORKERS=4
|
|
# -e GUNICORN_TIMEOUT=180
|
|
# -e GUNICORN_BIND=0.0.0.0:9000
|
|
EXPOSE 8000
|
|
CMD ["gunicorn", "main:app"]
|