Airflow 系列2:环境准备与单机部署


本文记录一次在本机 Ubuntu 中部署 Apache Airflow 2.3.4 的全过程。重点不是只给最终命令,而是把环境差异、命令输出、遇到的错误和修复过程都记录下来,后续排查同类问题时可以直接对照。

1. 部署目标

本次要完成的目标:

  1. 在虚拟机 10.211.55.4 上安装 Airflow 2.3.4
  2. 安装 MySQL,并把 Airflow 元数据库从默认 SQLite 切换为 MySQL。
  3. 创建 Airflow 管理员账号。
  4. 启动 webserver 和 scheduler。
  5. 记录安装命令、关键输出和过程中遇到的问题。

最终验证结果:

$ curl -fsS http://127.0.0.1:8080/health
{
    "metadatabase": {
        "status": "healthy"
    },
    "scheduler": {
        "status": "healthy",
        "latest_scheduler_heartbeat": "2023-02-15T09:36:35.583905+08:00"
    }
}

Airflow 页面地址:

http://10.211.55.4:8080
username: admin
password: admin

单机部署拓扑

2. 环境说明

远程机器是 Parallels 里的 Ubuntu 虚拟机:

$ whoami
parallels

$ hostname
ubuntu-linux-2204

$ uname -a
Linux ubuntu-linux-2204 5.15.0-60-generic #66-Ubuntu SMP Fri Jan 20 14:29:49 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
UBUNTU_CODENAME=jammy

$ python3 --version
Python 3.10.6

这里有两个关键点:

  1. 机器架构是 aarch64,也就是 ARM64,不是 x86_64。
  2. Ubuntu 22.04 默认 Python 是 3.10.6,满足 Airflow 2.3.4 的 Python 版本要求;但为了不污染系统 Python,本文仍然使用 Miniforge 创建独立环境。

确认系统 Python 版本:

$ python3 --version
Python 3.10.6

本文没有直接使用系统 Python 安装 Airflow,而是安装 ARM64 版本的 Miniforge,然后用 conda 创建 python=3.10 环境。这样做的好处是 Airflow、MySQL 驱动、constraints 依赖都被隔离在 airflow234 环境里,后续删除或重装也不会影响系统包。

3. 与参考文档的差异

参考资料是 尚硅谷大数据技术之airflow.docx,其中安装思路是:

  1. 安装 Miniconda。
  2. 创建 Python 环境。
  3. pip install apache-airflow
  4. airflow db init
  5. 启动 webserver 和 scheduler。
  6. 修改 MySQL 元数据库配置。

参考文档里有几处和这次环境不同:

项目 参考文档 本次部署
CPU 架构 x86_64 aarch64 / ARM64
Conda 安装包 Miniconda3-latest-Linux-x86_64.sh Miniforge3-Linux-aarch64.sh
Python 环境 Python 3.8 Python 3.10
Airflow 版本 文档示例是 2.4.3 本文固定 2.3.4
元数据库驱动 mysql-connector-python 示例 本文使用 mysqlclient,连接串为 mysql+mysqldb://...

为什么要换成 Miniforge?因为仓库里现有的 Miniconda3-latest-Linux-x86_64.sh 是 x86_64 安装包,不能在 ARM64 Ubuntu 上使用。

4. 安装 MySQL

先更新 apt 源:

$ sudo apt-get update
Hit:2 http://security.ubuntu.com/ubuntu jammy-security InRelease
Hit:3 http://ports.ubuntu.com/ubuntu-ports jammy InRelease
Hit:4 http://us.archive.ubuntu.com/ubuntu jammy InRelease
Hit:5 http://us.archive.ubuntu.com/ubuntu jammy-updates InRelease
Hit:6 http://ports.ubuntu.com/ubuntu-ports jammy-updates InRelease
Hit:7 http://us.archive.ubuntu.com/ubuntu jammy-backports InRelease
Hit:8 http://ports.ubuntu.com/ubuntu-ports jammy-backports InRelease
Hit:9 http://us.archive.ubuntu.com/ubuntu jammy-proposed InRelease
Hit:10 http://ports.ubuntu.com/ubuntu-ports jammy-security InRelease
Reading package lists...

安装 MySQL 和后面编译 mysqlclient 需要的系统依赖:

$ sudo apt-get install -y mysql-server curl ca-certificates bzip2 build-essential pkg-config default-libmysqlclient-dev
Reading package lists...
Building dependency tree...
Reading state information...
mysql-server is already the newest version (8.0.32-0ubuntu0.22.04.2).
curl is already the newest version (7.81.0-1ubuntu1.7).
bzip2 is already the newest version (1.0.8-5build1).
build-essential is already the newest version (12.9ubuntu3).
pkg-config is already the newest version (0.29.2-1ubuntu3).
default-libmysqlclient-dev is already the newest version (1.0.8).
0 upgraded, 0 newly installed, 0 to remove and 18 not upgraded.

启动并设置 MySQL 开机自启:

$ sudo systemctl enable --now mysql
Synchronizing state of mysql.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable mysql

查看 MySQL 服务状态:

$ sudo systemctl status mysql --no-pager -l
● mysql.service - MySQL Community Server
     Loaded: loaded (/lib/systemd/system/mysql.service; enabled; preset: enabled)
     Active: active (running) since Wed 2023-02-15 09:22:19 CST; 10min ago
   Main PID: 12733 (mysqld)
     Status: "Server is operational"
      Tasks: 38 (limit: 2206)
     Memory: 370.8M

查看 MySQL 版本:

$ mysql --version
mysql  Ver 8.0.32-0ubuntu0.22.04.2 for Linux on aarch64 ((Ubuntu))

$ sudo mysql -e "SELECT VERSION();"
VERSION()
8.0.32-0ubuntu0.22.04.2

5. 安装 ARM64 Miniforge

下载地址:

$ curl -L -o /tmp/Miniforge3-Linux-aarch64.sh \
  https://github.com/conda-forge/miniforge/releases/download/22.11.1-4/Miniforge3-22.11.1-4-Linux-aarch64.sh

本次虚拟机直接从 GitHub 下载时速度很慢,只有一百多 KB/s:

0 88.6M    0 1276k    0     0   117k      0  0:12:49  0:00:10  0:12:39  107k

后来改为先在宿主机下载,再通过局域网 scp 传到虚拟机:

$ scp /tmp/Miniforge3-Linux-aarch64.sh parallels@10.211.55.4:/tmp/Miniforge3-Linux-aarch64.sh
Miniforge3-Linux-aarch64.sh  100%   89MB 116.0MB/s   00:00

校验安装包:

$ ls -lh /tmp/Miniforge3-Linux-aarch64.sh && sha256sum /tmp/Miniforge3-Linux-aarch64.sh | sed 's/ .*//'
-rw-rw-r-- 1 parallels parallels 89M Feb 15 09:15 /tmp/Miniforge3-Linux-aarch64.sh
2c113a69297e612b01ca0f320c22a3107a11f2ab9b573d79ac868a175945ce29

安装到用户目录:

$ bash /tmp/Miniforge3-Linux-aarch64.sh -b -p /home/parallels/miniforge3
PREFIX=/home/parallels/miniforge3
Unpacking bootstrapper...
Unpacking payload...
Installing base environment...
Transaction finished
installation finished.

关闭默认自动激活 base 环境:

$ source /home/parallels/miniforge3/etc/profile.d/conda.sh
$ conda config --set auto_activate_base false
WARNING conda.cli.condarc:set_key(484): Key auto_activate_base is an alias of auto_activate; setting value with latter

$ conda --version
conda 22.11.1

6. 创建 Airflow Python 环境

创建 Python 3.10 环境:

$ conda create -y -n airflow234 python=3.10
Channels:
 - conda-forge
Platform: linux-aarch64
Collecting package metadata (repodata.json): done
Solving environment: done

## Package Plan ##

  environment location: /home/parallels/miniforge3/envs/airflow234

  added / updated specs:
    - python=3.10

The following NEW packages will be INSTALLED:
  python 3.10.9 h4bb2201_0_cpython conda-forge

# To activate this environment, use
#
#     $ conda activate airflow234

确认 Python 版本:

$ conda run -n airflow234 python --version
Python 3.10.9

升级 pip:

$ conda run -n airflow234 python -m pip install --upgrade pip
Requirement already satisfied: pip in ./miniforge3/envs/airflow234/lib/python3.10/site-packages (22.3.1)

7. 安装 Airflow 2.3.4

Airflow 老版本强烈建议使用 constraints 文件固定依赖,否则很容易装到不兼容的新版本依赖。

安装命令:

$ conda run -n airflow234 python -m pip install \
  'apache-airflow[mysql]==2.3.4' \
  --constraint 'https://raw.githubusercontent.com/apache/airflow/constraints-2.3.4/constraints-3.10.txt'

关键输出:

Collecting apache-airflow==2.3.4 (from apache-airflow[mysql]==2.3.4)
  Downloading apache_airflow-2.3.4-py3-none-any.whl.metadata (112 kB)
...
Successfully built cron-descriptor dill pendulum psutil pyrsistent python-nvd3 termcolor unicodecsv
Successfully installed ... apache-airflow-2.3.4 apache-airflow-providers-mysql-3.2.0 ...
WARNING: Retrying ... raw.githubusercontent.com ... constraints-3.10.txt
WARNING: The candidate selected for download or install is a yanked version: 'python-daemon' candidate (version 2.3.1 ...)
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed.
Some dependency constraints were adjusted to match apache-airflow==2.3.4.

这里虽然最后有 ERROR: pip's dependency resolver... 字样,但 pip 命令退出码是 0,Airflow 也成功安装了。这个提示来自 conda 环境里已有包版本与 Airflow constraints 固定依赖不完全一致。对本次 Airflow 2.3.4 部署没有阻断。

确认 Airflow 版本:

$ AIRFLOW_HOME=/home/parallels/airflow234 conda run -n airflow234 airflow version
2.3.4

8. 安装 MySQL Python 驱动

一开始我以为安装 apache-airflow[mysql] 后就具备 MySQLdb 驱动,但初始化元数据库时报错:

ModuleNotFoundError: No module named 'MySQLdb'
ERROR conda.cli.main_run:execute(148): `conda run airflow db init` failed.

原因是连接串使用的是:

sql_alchemy_conn = mysql+mysqldb://airflow:******@localhost:3306/airflow_db?charset=utf8mb4

mysql+mysqldb:// 需要 Python 包 mysqlclient 提供 MySQLdb 模块。所以补装:

$ conda run -n airflow234 python -m pip install 'mysqlclient==2.1.1'
Collecting mysqlclient==2.1.1
  Downloading mysqlclient-2.1.1.tar.gz (88 kB)
Building wheels for collected packages: mysqlclient
  Building wheel for mysqlclient (pyproject.toml): finished with status 'done'
  Created wheel for mysqlclient: filename=mysqlclient-2.1.1-cp310-cp310-linux_aarch64.whl size=62064
Successfully built mysqlclient
Successfully installed mysqlclient-2.1.1

验证 MySQLdb 可导入:

$ conda run -n airflow234 python -c 'import MySQLdb; print(MySQLdb.__file__)'
/home/parallels/miniforge3/envs/airflow234/lib/python3.10/site-packages/MySQLdb/__init__.py

9. 创建 Airflow 元数据库和用户

创建数据库和用户:

$ sudo mysql -e "
CREATE DATABASE IF NOT EXISTS airflow_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'airflow'@'localhost' IDENTIFIED BY '<AIRFLOW_DB_PASSWORD>';
ALTER USER 'airflow'@'localhost' IDENTIFIED BY '<AIRFLOW_DB_PASSWORD>';
GRANT ALL PRIVILEGES ON airflow_db.* TO 'airflow'@'localhost';
FLUSH PRIVILEGES;
"

验证数据库和用户:

$ sudo mysql -e "
SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME='airflow_db';
SELECT user, host, plugin FROM mysql.user WHERE user='airflow';
"
SCHEMA_NAME  DEFAULT_CHARACTER_SET_NAME  DEFAULT_COLLATION_NAME
airflow_db   utf8mb4                     utf8mb4_unicode_ci
user         host       plugin
airflow      localhost  caching_sha2_password

说明:参考文档里提到过 MySQL 的 sql_mode 和 timestamp 默认值问题。本文使用的是 MySQL 8.0.32,执行 airflow db init 时没有遇到 Invalid default value 错误,所以没有修改 sql_mode。如果使用较老 MySQL 版本遇到该错误,再考虑按参考文档调整。

10. 配置 Airflow 使用 MySQL

设置 Airflow home:

$ export AIRFLOW_HOME=/home/parallels/airflow234
$ mkdir -p $AIRFLOW_HOME

生成默认配置文件:

$ AIRFLOW_HOME=/home/parallels/airflow234 conda run -n airflow234 airflow config list >/tmp/airflow_config_list.txt
$ ls -l /home/parallels/airflow234/airflow.cfg
-rw-rw-r-- 1 parallels parallels 8742 Feb 15 09:30 /home/parallels/airflow234/airflow.cfg

修改关键配置:

[core]
executor = LocalExecutor
load_examples = False

[database]
sql_alchemy_conn = mysql+mysqldb://airflow:<AIRFLOW_DB_PASSWORD>@localhost:3306/airflow_db?charset=utf8mb4

[webserver]
web_server_port = 8080

这里顺便把 executor 改成 LocalExecutor。默认的 SequentialExecutor 更适合 SQLite 单进程实验,不适合作为 MySQL 元数据库下的单机部署形态。

11. 初始化 Airflow 元数据库

执行初始化:

$ AIRFLOW_HOME=/home/parallels/airflow234 conda run -n airflow234 airflow db init
DB: mysql+mysqldb://airflow:***@localhost:3306/airflow_db?charset=utf8mb4
[2023-02-15 09:32:40,308] {db.py:1466} INFO - Creating tables
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> e3a246e0dc1, current schema
...
INFO  [alembic.runtime.migration] Running upgrade 3c94c427fdf6 -> f5fcbda3e651, Add indexes for CASCADE deletes on task_instance
WARNI [airflow.models.crypto] empty cryptography key - values will not be stored encrypted.

初始化后验证 MySQL 里表数量:

$ sudo mysql -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='airflow_db';"
33

12. 创建 Airflow 管理员用户

创建管理员:

$ AIRFLOW_HOME=/home/parallels/airflow234 conda run -n airflow234 airflow users create \
  --username admin \
  --firstname Admin \
  --lastname User \
  --role Admin \
  --email admin@example.com \
  --password admin

关键输出:

[2023-02-15 09:32:50,412] {manager.py:213} INFO - Added user admin
User "admin" created with role "Admin"

查看用户:

$ AIRFLOW_HOME=/home/parallels/airflow234 conda run -n airflow234 airflow users list
id | username | email             | first_name | last_name | roles
===+==========+===================+============+===========+======
1  | admin    | admin@example.com | Admin      | User      | Admin

13. 启动 webserver 和 scheduler

启动前先检查数据库连接:

$ AIRFLOW_HOME=/home/parallels/airflow234 conda run -n airflow234 airflow db check
[2023-02-15 09:36:28,341] {db.py:1610} INFO - Connection successful.

启动 webserver:

$ AIRFLOW_HOME=/home/parallels/airflow234 conda run -n airflow234 airflow webserver -p 8080 -D
Running the Gunicorn Server with:
Workers: 4 sync
Host: 0.0.0.0:8080
Timeout: 120
Logfiles: - -
Access Logformat:
=================================================================
[2023-02-15 09:36:30,096] {dagbag.py:508} INFO - Filling up the DagBag from /dev/null

启动 scheduler:

$ AIRFLOW_HOME=/home/parallels/airflow234 conda run -n airflow234 airflow scheduler -D

查看进程:

$ ps -ef | grep -E "airflow (webserver|scheduler)|gunicorn" | grep -v grep
paralle+ 30044 1 3 09:36 ? 00:00:00 /home/parallels/miniforge3/envs/airflow234/bin/python /home/parallels/miniforge3/envs/airflow234/bin/airflow webserver -p 8080 -D
paralle+ 30051 1 2 09:36 ? 00:00:00 gunicorn: master [airflow-webserver]
paralle+ 30056 30051 11 09:36 ? 00:00:01 [ready] gunicorn: worker [airflow-webserver]
paralle+ 30057 30051 11 09:36 ? 00:00:01 [ready] gunicorn: worker [airflow-webserver]
paralle+ 30058 30051 11 09:36 ? 00:00:01 [ready] gunicorn: worker [airflow-webserver]
paralle+ 30059 30051 11 09:36 ? 00:00:01 [ready] gunicorn: worker [airflow-webserver]
paralle+ 30069 1 6 09:36 ? 00:00:00 /home/parallels/miniforge3/envs/airflow234/bin/python /home/parallels/miniforge3/envs/airflow234/bin/airflow scheduler -D
paralle+ 30201 30069 0 09:36 ? 00:00:00 airflow scheduler -- DagFileProcessorManager

健康检查:

$ curl -fsS http://127.0.0.1:8080/health
{
    "metadatabase": {
        "status": "healthy"
    },
    "scheduler": {
        "status": "healthy",
        "latest_scheduler_heartbeat": "2023-02-15T09:36:35.583905+08:00"
    }
}

部署完成后,浏览器打开 Airflow 会先进入登录页: http://10.211.55.4:8080/login/

Airflow 2.3.4 登录页

浏览器访问 Airflow Web UI 后,可以看到当前实例已经是 v2.3.4,并且由于本次关闭了示例 DAG,DAG 列表为空:

Airflow 2.3.4 DAG 列表页

也可以直接打开 /health 页面确认元数据库和 scheduler 状态:

Airflow 2.3.4 健康检查页面

14. 常用管理命令

每次操作前建议先设置环境变量并加载 conda:

export AIRFLOW_HOME=/home/parallels/airflow234
source /home/parallels/miniforge3/etc/profile.d/conda.sh
conda activate airflow234

查看版本:

airflow version

检查数据库连接:

airflow db check

启动 webserver:

airflow webserver -p 8080 -D

启动 scheduler:

airflow scheduler -D

查看进程:

ps -ef | grep -E "airflow (webserver|scheduler)|gunicorn" | grep -v grep

停止服务建议使用 pid 文件或精确 pid,不要直接在脚本里写宽泛的 pkill -f airflow

kill $(cat /home/parallels/airflow234/airflow-webserver-monitor.pid)

如果要批量清理,先看清楚进程再 kill:

ps -ef | grep -E "airflow (webserver|scheduler)|gunicorn" | grep -v grep
kill <PID>

15. 本次踩坑记录

15.1 不要直接把 Airflow 装进系统 Python

现象:

$ python3 --version
Python 3.10.6

$ which python3
/usr/bin/python3

Ubuntu 22.04 自带 Python 3.10,版本本身可以满足 Airflow 2.3.4。但如果直接在系统 Python 里执行 pip install apache-airflow,后续依赖回滚、重装和排错都会比较麻烦。

处理方式:使用 ARM64 Miniforge 创建独立的 python=3.10 环境。

15.2 参考文档里的 x86_64 Miniconda 不适合 ARM64 虚拟机

参考文档使用的是:

Miniconda3-latest-Linux-x86_64.sh

本机虚拟机是:

$ uname -m
aarch64

处理方式:改用:

Miniforge3-Linux-aarch64.sh

15.3 非交互 SSH 执行 sudo 时需要处理密码输入

第一次脚本里直接执行:

sudo apt-get update

报错:

sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required

处理方式:在脚本里使用 sudo -S -v 先刷新 sudo 凭据,后续短时间内可用 sudo -n 执行;需要再次输入密码的地方使用 sudo -S

15.4 GitHub 下载在虚拟机里很慢

虚拟机里下载 Miniforge 时输出:

0 88.6M    0 1276k    0     0   117k      0  0:12:49  0:00:10  0:12:39  107k

处理方式:宿主机下载后传入虚拟机:

scp /tmp/Miniforge3-Linux-aarch64.sh parallels@10.211.55.4:/tmp/Miniforge3-Linux-aarch64.sh

传输速度:

Miniforge3-Linux-aarch64.sh 100% 89MB 116.0MB/s 00:00

15.5 apache-airflow[mysql] 不等于一定有 MySQLdb

初始化数据库时报错:

ModuleNotFoundError: No module named 'MySQLdb'

处理方式:补装 mysqlclient

pip install mysqlclient==2.1.1

系统层还需要这些依赖,否则 mysqlclient 可能编译失败:

sudo apt-get install -y build-essential pkg-config default-libmysqlclient-dev

15.6 环境中缺少 pkg_resources,导致 webserver 和 scheduler 启动失败

webserver 日志:

ModuleNotFoundError: No module named 'pkg_resources'

scheduler 日志也有同样错误:

File ".../gunicorn/util.py", line 25, in <module>
  import pkg_resources
ModuleNotFoundError: No module named 'pkg_resources'

原因:当前 conda 环境里的 setuptools 状态不完整,导致 Airflow 2.3.4 / gunicorn 依赖的 pkg_resources 模块不可导入。

处理方式:降级 setuptools:

$ conda run -n airflow234 python -m pip install "setuptools==65.5.1"
Successfully installed setuptools-65.5.1

$ conda run -n airflow234 python -c "import pkg_resources; print(pkg_resources.__file__)"
/home/parallels/miniforge3/envs/airflow234/lib/python3.10/site-packages/pkg_resources/__init__.py

15.7 脚本里不要用宽泛的 pkill -f

我曾经在安装脚本里写过:

pkill -f 'airflow webserver' || true
pkill -f 'airflow scheduler' || true

结果执行到这一步时脚本自己被匹配并终止:

$ stop old airflow processes if any
Terminated
exit_code=143
FAILED: stop old airflow processes if any

后面又在远端内联 SSH 命令里触发过同类问题,因为当前 SSH 命令行本身包含 airflow scheduler 字样,也会被 pkill -f 匹配。

处理方式:

  1. 优先使用 pid 文件停止 webserver。
  2. 使用 ps 先确认 pid,再手动 kill <PID>
  3. 自动化脚本中避免宽泛的 pkill -f airflow

16. 最终目录和日志

Airflow 安装位置:

/home/parallels/miniforge3/envs/airflow234

Airflow Home:

/home/parallels/airflow234

主要配置文件:

/home/parallels/airflow234/airflow.cfg

关键日志文件:

/home/parallels/airflow234/airflow-webserver.err
/home/parallels/airflow234/airflow-webserver.log
/home/parallels/airflow234/airflow-scheduler.err
/home/parallels/airflow234/airflow-scheduler.log

本次安装过程日志:

/home/parallels/airflow234_install_20230215_092103.log
/home/parallels/airflow234_install_20230215_092139.log
/home/parallels/airflow234_install_20230215_092515.log
/home/parallels/airflow234_install_20230215_092603.log
/home/parallels/airflow234_install_20230215_092640.log
/home/parallels/airflow234_install_20230215_092948.log
/home/parallels/airflow234_install_20230215_093213.log
/home/parallels/airflow234_manual_start_20230215_093356.log
/home/parallels/airflow234_start_final_20230215_093627.log

17. 小结

这次部署的关键不是 pip install apache-airflow 本身,而是版本兼容:

  1. Airflow 2.3.4 使用 Python 3.10 更稳。
  2. ARM64 机器不能使用 x86_64 Miniconda 安装包。
  3. MySQL 元数据库连接串使用 mysql+mysqldb 时必须安装 mysqlclient
  4. 服务是否真正可用要以 /health、进程和日志为准,不能只看启动命令是否返回 0。

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录