如何在Windows主机上使用Apache Airflow中的DockerOperator?

3
我已经成功地在本地开发了一个超级简单的ETL过程(以下称为load_staging),它从远程位置提取数据,然后将未经处理的数据写入我本地Windows机器上的MongoDB容器。现在,我想使用DockerOperator为每个任务安排这个过程,即我想创建一个源代码的docker镜像,然后使用DockerOperator在该镜像中执行源代码。由于我正在使用Windows机器工作,因此只能在Docker容器内部使用Airflow。
我已经启动了Airflow容器(以下称为webserver)和MongoDB容器(以下称为mongo),并使用“docker-compose up”手动触发了Airflow的DAG。根据Airflow的记录,任务已被成功执行,但似乎docker镜像中的代码没有被执行,因为任务完成得太快了,并且在从我的镜像启动docker容器之后,任务以错误代码0执行,即我看不到任务本身的日志输出。请参见下面的日志:
[2020-01-20 17:09:44,444] {{docker_operator.py:194}} INFO - Starting docker container from image myaccount/myrepo:load_staging_op
[2020-01-20 17:09:50,473] {{logging_mixin.py:95}} INFO - [[34m2020-01-20 17:09:50,472[0m] {{[34mlocal_task_job.py:[0m105}} INFO[0m - Task exited with return code 0[0m

所以,我的两个问题是:

  1. 我得出了正确的结论吗?还有什么其他可能是这个问题的根源呢?
  2. 如何确保图像内部的代码始终被执行?

在下面,你可以找到更多关于我如何设置DockerOperator,如何定义应该由DockerOperator执行的图像,启动web服务器和mongo容器的docker-compose.yml文件以及用于创建web服务器容器的Dockerfile的进一步信息。

在我的DAG定义文件中,我像这样指定了DockerOperator:

CONFIG_FILEPATH = "/configs/docker_execution.ini"
data_object_name = "some_name"
task_id_ = "{}_task".format(data_object_name)
cmd = "python /src/etl/load_staging_op/main.py --config_filepath={} --data_object_name={}".format(CONFIG_FILEPATH, data_object_name)
staging_op = DockerOperator(
            command=cmd,
            task_id=task_id_,
            image="myaccount/myrepo:load_staging_op",
            api_version="auto",
            auto_remove=True
)

上面提到的load_staging_op镜像的Dockerfile如下所示:
# Inherit from Python image
FROM python:3.7

# Install environment
USER root
COPY ./src/etl/load_staging_op/requirements.txt ./
RUN pip install -r requirements.txt

# Copy source code files into container
COPY ./configs /configs
COPY ./wsdl /wsdl
COPY ./src/all_constants.py /src/all_constants.py
COPY ./src/etl/load_staging_op/utils.py /src/etl/load_staging_op/utils.py
COPY ./src/etl/load_staging_op/main.py /src/etl/load_staging_op/main.py

# Extend python path so that custom modules are found
ENV PYTHONPATH "${PYTHONPATH}:/src"

ENTRYPOINT [ "sh", "-c"]

docker-compose.yml 文件的相关部分如下:

version: '2.1'
services:
    webserver:
        build: ./docker-airflow
        restart: always
        privileged: true
        depends_on:
            - mongo
            - mongo-express
        volumes:
            - ./docker-airflow/dags:/usr/local/airflow/dags
            # source code volume
            - ./src:/src
            - ./docker-airflow/workdir:/home/workdir
            # Mount the docker socket from the host (currently my laptop) into the webserver container
            # so that we can build docker images from inside the webserver container.
            - //var/run/docker.sock:/var/run/docker.sock  # the two "//" are needed for windows OS
            - ./configs:/configs
            - ./wsdl:/wsdl
        ports:
            # Change port to 8081 to avoid Jupyter conflicts
            - 8081:8080
        command: webserver
        healthcheck:
            test: ["CMD-SHELL", "[ -f /usr/local/airflow/airflow-webserver.pid ]"]
            interval: 30s
            timeout: 30s
            retries: 3
        networks:
            - mynet

    mongo:
        container_name: mymongo
        image: mongo
        restart: always
        ports:
            - 27017:27017
        networks:
            - mynet

上面Dockerfile引用的web服务器容器的Dockerfile如下所示:
FROM puckel/docker-airflow:1.10.4

# Adds DAG folder to the PATH
ENV PYTHONPATH "${PYTHONPATH}:/src:/usr/local/airflow/dags"

# Install the optional packages
COPY requirements.txt requirements.txt  # make sure something like docker==4.1.0 is in this requirements.txt file!
USER root
RUN pip install -r requirements.txt

# Install docker inside the webserver container
RUN curl -sSL https://get.docker.com/ | sh
ENV SHARE_DIR /usr/local/share

# Install simple text editor for debugging
RUN ["apt-get", "update"]
RUN ["apt-get", "-y", "install", "vim"]

感谢您的帮助,我非常感激!


1
我不熟悉Airflow或它如何启动容器,但是 ENTRYPOINT ["sh", "-c"] 的主要效果是使容器忽略其所有命令行参数。 我期望该设置运行 python,忽略所有其他选项,并立即退出。 您应该能够删除该 ENTRYPOINT 行。(还要考虑如果 data_object_name 中有空格或标点符号会发生什么。) - David Maze
1
嗨,David,谢谢你的回答。我现在无法测试你的建议,但你所说的是有道理的。当我在load_staging_op镜像的Dockerfile中将ENTRYPOINT [ "sh", "-c"]替换为CMD python /src/etl/load_staging_op/main.py --config_filepath=/configs/docker_execution.ini --data_object_name=some_name,然后使用docker build -t myaccount/myrepo:load_staging_op -f path_to_dockerfile .构建任务的镜像并运行docker run -it myaccount/myrepo:load_staging_op时,任务显然会失败,但我可以看到一些日志输出。我明天会进行测试! - Kevin Südmersen
我搞定了!我会在今天晚些时候发布答案! - Kevin Südmersen
1个回答

3

非常感谢所有抽出时间帮助我解决问题的人。为了让它正常工作,我需要实现以下更改:

DockerOperator:

  • 在容器运行时调整传递给容器的命令,即在构建容器时
  • 添加参数network_mode,并指定web服务器容器所在的网络。这对我来说很困难,因为我是Docker新手,而且在网上找不到太多教程。为了找到web服务器容器所在的网络名称,我使用类似于docker network ls的命令在我的主机(Windows笔记本电脑)上列出了所有当前活动的网络。在显示的网络列表中,我看到一个名为project_root_dirname_mynet的网络,它是我的项目根目录和docker-compose.yml文件中指定的网络名称的组合。有趣的是(显然),在列出所有网络之后,您可以使用类似于docker network inspect project_root_dirname_mynet的命令检查网络project_root_dirname_mynet。这将返回一个json文件,其中包含一个子部分"containers",其中您可以看到在docker-compose.yml文件中指定的所有容器。

然后,DockerOperator的代码如下:

cmd = "--config_filepath {} --data_object_name {}".format(CONFIG_FILEPATH.strip(), data_object_name.strip())
print("Command: {}".format(cmd))
staging_op = DockerOperator(
    command=cmd,
    task_id=task_id_,
    image="myaccount/myrepo:load_staging_op",
    api_version="auto",
    auto_remove=True,
    network_mode="project_root_dirname_mynet"
)

load_staging_op任务的Dockerfile:

  • 将最后一行从ENTRYPOINT [ "sh", "-c"]更改为ENTRYPOINT [ "python", "/src/etl/load_staging_op/main.py"]。我认为"python"参数将在容器中打开Python控制台,第二个参数只是要在docker容器内执行的脚本的路径。然后,在运行时(或构建时或无论如何称呼),将传递cmd上面的命令行参数。在镜像的源代码中,您可以使用像argparse这样的库来检索这些命令。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接