入口点

如果您使用的是生产镜像的默认入口点,在容器启动时会自动执行一些操作。在某些情况下,您可以向镜像传递环境变量来触发这些行为中的部分。

控制“执行”行为的变量以 _AIRFLOW 开头,以区别于用于构建镜像的以 AIRFLOW 开头的变量。

允许任意用户运行容器

Airflow 镜像兼容 OpenShift,这意味着您可以使用随机的用户 ID 并将组 ID 设置为 0root)来启动它。如果您希望以不同于 Airflow 的用户运行镜像,必须将该用户的 GID 设置为 0。若尝试使用其他组,入口点将以错误退出。

OpenShift 在启动容器时会随机分配 UID,但在手动运行镜像时也可以利用此灵活的 UID。例如,当您希望在 Linux 上将主机系统的 daglogs 文件夹挂载进来时,UID 应设置为与主机用户相同的 ID。

可以通过多种方式实现——在扩展或自定义镜像时更改 USER,或者在 docker run 命令中动态传递用户,添加 --user 标志,采用以下任意格式(详情请参阅 Docker Run reference)。

[ user | user:group | uid | uid:gid | user:gid | uid:group ]

在 Docker Compose 环境中可以通过 user: 条目在 docker-compose.yaml 中进行更改。详情请参阅 Docker compose reference。在我们的使用 Docker-Compose 的快速入门指南中,UID 可以通过 AIRFLOW_UID 变量传递,正如 Initializing docker compose environment 中所述。

用户可以是任意 UID。如果 UID 与默认的 airflow(UID=50000)不同,进入容器时会自动创建该用户。

为了兼容大量外部库和项目,Airflow 会在 (/etc/passwd) 中自动创建这样的任意用户,并将其主目录指向 /home/airflow。许多第三方库和包需要用户的主目录存在,以便写入缓存信息,因此需要动态创建用户。

此类任意用户必须能够写入某些需要写权限的目录,且出于安全考虑不建议为“other”赋予写权限,OpenShift 指南引入了将所有此类文件夹的组 ID (GID) 设置为 0root) 的概念。Airflow 生产镜像中所有需要写入的目录的 GID 均设为 0(并对组可写)。我们遵循该概念,所有需要写入的目录亦如此。

GID=0 已设为 airflow 用户的默认值,因此其创建的任何目录默认 GID 为 0。入口点将 umask 设置为 0002——这意味着用户创建的任何目录都具有对组 0 的写权限,即其他属于 root 组的用户也可写入。同时,当任何“任意”用户创建文件夹(例如在挂载卷中),该文件夹将拥有“组写”权限且 GID=0,从而后续使用其他任意用户时仍能正常工作,即使该目录随后被其他任意用户挂载。

然而,umask 设置仅在容器运行时生效——在构建镜像时不使用。如果您想扩展镜像并添加自己的软件包,需要在 Docker 命令之前加入 umask 0002——这样任何需要组访问的安装创建的目录也会对组可写。例如可以这样操作

RUN umask 0002; \
    do_something; \
    do_otherthing;

您可以在 Openshift 最佳实践 中的“支持任意用户 ID”章节了解更多。

等待 Airflow 数据库连接

入口点会等待数据库连接(不论使用何种数据库引擎),从而提升环境的稳定性。

等待连接的方式是执行 airflow db check 命令,这实际上会执行 select 1 as is_alive; 语句。随后循环直至命令成功。它会尝试 CONNECTION_CHECK_MAX_COUNT 次,并在检查之间睡眠 CONNECTION_CHECK_SLEEP_TIME。若要禁用检查,可将 CONNECTION_CHECK_MAX_COUNT=0

等待 Celery broker 连接

如果使用 CeleryExecutor,并运行 schedulercelery 等命令,入口点会等待 Celery broker 的数据库连接可用。

脚本根据 URL scheme 检测后端类型,并在 URL 中未指定端口时分配默认端口号。随后循环直至能够建立到指定主机/端口的连接。它会尝试 CONNECTION_CHECK_MAX_COUNT 次,并在检查之间睡眠 CONNECTION_CHECK_SLEEP_TIME。若要禁用检查,设置 CONNECTION_CHECK_MAX_COUNT=0

支持的 scheme

  • amqp(s)://(RabbitMQ) - 默认端口 5672

  • redis:// - 默认端口 6379

  • postgres:// - 默认端口 5432

  • mysql:// - 默认端口 3306

等待连接的方式是检查对应端口是否打开。主机信息来源于 Airflow 配置。

执行命令

如果第一个参数等于 “bash” —— 您将进入 bash shell,或在指定额外参数时执行 bash 命令。例如

docker run -it apache/airflow:3.2.0-python3.10 bash -c "ls -la"
total 16
drwxr-xr-x 4 airflow root 4096 Jun  5 18:12 .
drwxr-xr-x 1 root    root 4096 Jun  5 18:12 ..
drwxr-xr-x 2 airflow root 4096 Jun  5 18:12 dags
drwxr-xr-x 2 airflow root 4096 Jun  5 18:12 logs

如果第一个参数等于 python —— 您将进入 python shell,或在传递额外参数时执行 python 命令。例如

> docker run -it apache/airflow:3.2.0-python3.10 python -c "print('test')"
test

如果第一个参数等于 airflow —— 其余参数将被视为要执行的 Airflow 命令。例如

docker run -it apache/airflow:3.2.0-python3.10 airflow webserver

如果有其他参数——它们会直接传递给 “airflow” 命令

> docker run -it apache/airflow:3.2.0-python3.10 help
  usage: airflow [-h] GROUP_OR_COMMAND ...

  Positional Arguments:
    GROUP_OR_COMMAND

      Groups
        assets            Manage assets
        backfill          Manage backfills
        config            View configuration
        connections       Manage connections
        dags              Manage Dags
        db                Database operations
        jobs              Manage jobs
        pools             Manage pools
        providers         Display providers
        tasks             Manage tasks
        variables         Manage variables

      Commands:
        api-server        Start an Airflow API server instance
        cheat-sheet       Display cheat sheet
        dag-processor     Start a dag processor instance
        info              Show information about current Airflow and environment
        kerberos          Start a kerberos ticket renewer
        plugins           Dump information about loaded plugins
        rotate-fernet-key
                          Rotate encrypted connection credentials and variables
        scheduler         Start a scheduler instance
        standalone        Run an all-in-one copy of Airflow
        triggerer         Start a triggerer instance
        version           Show the version

  Options:
    -h, --help            show this help message and exit

在 Airflow 入口点之前执行自定义代码

如果您想在 Airflow 的入口点之前执行一些自定义代码,可以使用自定义脚本,并在脚本的最后使用 exec 指令调用 Airflow 的入口点。然而,必须记得像 Airflow 入口点一样使用 dumb-init,否则可能出现信号传播不完整的问题(参见下一章节)。

FROM airflow:2.9.0.dev0
COPY my_entrypoint.sh /
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/my_entrypoint.sh"]

您的入口点可能会即时修改或添加变量。例如下面的入口点从镜像执行时传入的第一个参数设置数据库检查的最大次数(虽然例子有点无用,但可以给读者展示如何使用)。

#!/bin/bash
export CONNECTION_CHECK_MAX_COUNT=${1}
shift
exec /entrypoint "${@}"

确保在自定义入口点的最后一条命令使用 exec /entrypoint "${@}" 来运行 Airflow 的入口点。这样信号能够正确传播,参数亦会如常传递给入口点(如果需要传递额外参数,可使用上面的 shift)。请注意,以这种方式传递秘密值或将秘密存放在镜像中在安全性上是极不推荐的——因为镜像和运行镜像的参数对拥有 Kubernetes 日志或镜像仓库访问权限的任何人都可见。

还需注意,在 Airflow 入口点执行之前的代码不应在容器内部创建任何文件或目录,否则可能导致后续行为不一致。在 Airflow 入口点执行之前,以下功能尚不可用:

  • umask 未正确设置,无法允许 group 写入权限

  • 如果使用任意用户运行镜像,用户尚未在 /etc/passwd 中创建

  • 数据库和 broker 可能尚未可用

添加自定义镜像行为

Airflow 镜像在入口点中执行了大量步骤并设置了正确的环境,但您可能希望在入口点创建用户、设置 umask、配置变量并检查数据库运行后再执行额外代码。

与其直接运行常规命令 —— schedulerwebserver,您可以运行可嵌入镜像的 自定义 脚本。完成自定义设置后,甚至可以在该脚本中执行 Airflow 的常规组件,如 schedulerwebserver。与自定义入口点类似,可通过扩展镜像将其加入。

FROM airflow:2.9.0.dev0
COPY my_after_entrypoint_script.sh /

构建镜像后,您可以通过运行以下命令来执行此脚本

docker build . --pull --tag my-image:0.0.1
docker run -it my-image:0.0.1 bash -c "/my_after_entrypoint_script.sh"

信号传播

Airflow 在入口点中使用 dumb-init 作为 “init”。这样可正确传播信号并回收子进程。这意味着您运行的进程无需自行安装信号处理程序,即可在容器优雅终止时被正确杀死。信号传播行为由 DUMB_INIT_SETSID 变量控制,默认设置为 1——即信号会传播到整个进程组。若将其设为 0,则启用 dumb-initsingle-child 模式,仅将信号传播给单一子进程。

下面的表格汇总了 DUMB_INIT_SETSID 的可能取值及其使用场景。

变量值

使用场景

1 (default)

将信号传播给容器中主进程所在的进程组内的所有进程。

如果通过 ["bash", "-c"] 命令运行进程且 bash 在不使用 exec 的情况下生成新进程,这将帮助容器优雅终止,因为所有进程都会收到信号。

0

仅将信号传播给主进程。

当主进程能够优雅地处理信号时此模式有用。一个典型例子是 Celery worker 的平滑关闭。在此情况下,dumb-init 只会将信号传播给主进程,而不会传播到同一进程组中由主进程生成的其他进程。例如,对于 Celery,主进程会将 worker 置于 “offline” 模式,并等待所有运行中的任务完成后再终止所有进程。

对于 Airflow 的 Celery worker,您应将该变量设为 0,并使用 ["celery", "worker"] 命令。如果通过 ["bash", "-c"] 运行,需要在最后一条命令中使用 exec airflow celery worker 启动 worker。

额外快速测试选项

以下选项主要用于快速测试镜像,例如使用快速启动的 docker-compose,或在添加新包后进行本地测试。它们不应在生产环境中使用,因为会为执行额外命令带来额外开销。生产环境中应通过数据库维护操作实现,或将其嵌入自定义镜像(当您想添加新包时)。

升级 Airflow 数据库

如果将 _AIRFLOW_DB_MIGRATE 变量设为非空值,入口点将在验证连接后立即运行 airflow db migrate 命令。您也可以在使用内部 SQLite 数据库(默认)时使用此方式在入口点升级数据库并创建管理员用户,从而能够立即启动 Webserver。若容器未收到任何命令且已设置 _AIRFLOW_DB_MIGRATE,容器将在完成数据库迁移后干净退出。这允许一次性初始化容器(如 airflow-init)在无需占位命令来抑制 CLI 错误的情况下完成设置。注意——SQLite 仅用于测试目的,切勿在生产环境使用,因为在并发方面有严重限制。

创建管理员用户

入口点还可以在启动时自动创建 Webserver 用户。为此需将 _AIRFLOW_WWW_USER_CREATE 设置为非空值。此功能不适用于生产,仅在使用生产镜像进行快速测试时有用。您需要通过 _AIRFLOW_WWW_USER_PASSWORD_AIRFLOW_WWW_USER_PASSWORD_CMD(类似其他 *_CMD 变量)提供至少密码,以创建该用户。*_CMD 的内容会作为 shell 命令执行,其输出将被设为密码。

如果未设置任何 PASSWORD 变量,用户创建将失败——出于安全原因默认没有密码。

参数

默认值

环境变量

username

admin

_AIRFLOW_WWW_USER_USERNAME

password

_AIRFLOW_WWW_USER_PASSWORD_CMD or _AIRFLOW_WWW_USER_PASSWORD

firstname

Airflow

_AIRFLOW_WWW_USER_FIRSTNAME

lastname

Admin

_AIRFLOW_WWW_USER_LASTNAME

email

airflowadmin@example.com

_AIRFLOW_WWW_USER_EMAIL

role

Admin

_AIRFLOW_WWW_USER_ROLE

如果指定了密码,将尝试创建用户,但即使创建失败入口点也不会报错(这考虑到用户可能已存在的情况)。

例如,您可以使用以下命令在生产镜像中启动 Webserver,同时初始化内部 SQLite 数据库并创建一个 admin/admin 管理员用户。

docker run -it -p 8080:8080 \
  --env "_AIRFLOW_DB_MIGRATE=true" \
  --env "_AIRFLOW_WWW_USER_CREATE=true" \
  --env "_AIRFLOW_WWW_USER_PASSWORD=admin" \
    apache/airflow:3.2.0-python3.10 webserver
docker run -it -p 8080:8080 \
  --env "_AIRFLOW_DB_MIGRATE=true" \
  --env "_AIRFLOW_WWW_USER_CREATE=true" \
  --env "_AIRFLOW_WWW_USER_PASSWORD_CMD=echo admin" \
    apache/airflow:3.2.0-python3.10 webserver

上述命令会初始化 SQLite 数据库,创建拥有管理员密码和 Admin 角色的 admin 用户。同时将本地端口 8080 转发至 Webserver 端口,最终启动 Webserver。

安装额外的依赖

警告

以这种方式安装依赖是运行 Airflow 的一种非常便捷的方法,对测试和调试非常有用。但不要被其便利性所迷惑,绝不可在生产环境中使用。我们有意将其设为开发/测试依赖,并在使用时打印警告。将此方法用于生产会带来固有的安全风险。此方式的依赖安装可能在任何时刻发生——当容器重启、K8S 集群机器重启时均可能触发。在 K8S 集群中,这类事件随时可能发生。这会导致一个严重漏洞:只要 PyPI 上的某个依赖(甚至是您依赖的依赖)被移除,您的生产环境就可能宕机。这相当于将生产服务的可用性交付给第三方开发者。无论何时,包括周末和假期,这些第三方开发者都可能导致您的生产 Airflow 实例宕机,而您甚至不会知道。这种漏洞类似于臭名昭著的 leftpad 问题。您可以通过构建自己的、不可变的自定义镜像(将依赖预先烘焙进去)来彻底防止此类风险。已警告。

可以通过设置 _PIP_ADDITIONAL_REQUIREMENTS 变量来安装额外依赖。该变量应包含在容器启动时额外安装的依赖列表。请注意,此选项会导致 Airflow 启动变慢,因为每次容器启动时都必须安装新包,并且在生产环境使用时会带来巨大的安全风险(见下文)。因此该选项仅应在测试时使用。测试完成后,您应构建自定义镜像,将依赖预装进去。

示例

docker run -it -p 8080:8080 \
  --env "_PIP_ADDITIONAL_REQUIREMENTS=lxml==4.6.3 charset-normalizer==1.4.1" \
  --env "_AIRFLOW_DB_MIGRATE=true" \
  --env "_AIRFLOW_WWW_USER_CREATE=true" \
  --env "_AIRFLOW_WWW_USER_PASSWORD_CMD=echo admin" \
    apache/airflow:3.2.0-python3.10 webserver

此方法仅在 Airflow 2.1.1 及以上版本的 Docker 镜像中可用。

此条目是否有帮助?