Ansible中用include_vars加载YAML变量作环境变量失败排查
操作步骤与初始问题
我尝试在Ansible剧本中从YAML文件加载变量,作为后续任务的环境变量,操作步骤如下:
- 以YAML格式模板化主机变量
- name: builtin | template | template host variables file ansible.builtin.template: src: templates/django/hostvars.j2 dest: "host_vars/{{ inventory_hostname_short }}.yml" mode: 0640 output_encoding: "utf-8" delegate_to: localhost
生成的文件内容示例:
-- # Host variables to be set as environment variables in tasks that need it POSTGRES_PASSWORD: "<password>" POSTGRES_USER: "dbuser" POSTGRES_DB: "dbname" POSTGRES_HOST: "dbhost" POSTGRES_PORT: 5432 POSTGRES_SSLMODE: "verify-full" POSTGRES_SSLCA: "/etc/ssl/certs/ISRG_Root_X1.pem" POSTGRES_APPNAME: "myproject" DJANGO_SITE_NAME: "mysite" DJANGO_SITE_PASSWORD: "mypassword" DJANGO_SITE_USER: "myuser" DJANGO_SITE_ID: 2 DJANGO_SECRET_KEY: "<very-long-and-random-secret>" [...]
- 使用
include_vars将变量加载到剧本中
- name: builtin | include_vars | load host vars ansible.builtin.include_vars: file: "host_vars/{{ inventory_hostname_short }}.yml" name: envvars
- 验证变量已加载
- name: builtin | debug | print variable 'envvars' ansible.builtin.debug: var: envvars
调试结果显示变量已成功加载,输出示例:
TASK [builtin | debug | print variable 'envvars'] *************************************************************** ok: [django1.mydomain.com] => { "envvars": { "DJANGO_DEBUG": 0, "DJANGO_LOGS_DIR": "/opt/django/logs", "DJANGO_MEDIA_BASE": "/opt/django/media", "DJANGO_SECRET_KEY": "<very-long-and-random-secret>", [...]
- 使用
django_manage更新数据库schema
- name: community.general | django_manage | update database schema community.general.django_manage: command: migrate settings: myproject.settings project_path: "/opt/django/src" virtualenv: "/opt/django/venv" become: true become_user: django become_method: su environment: "{{ envvars }}"
但该任务失败,Django提示找不到SECRET_KEY环境变量(本应基于DJANGO_SECRET_KEY生成)。此外,执行env命令的任务未输出任何内容:
- name: print environment variables ansible.builtin.command: env become: true become_user: django become_method: su environment: "{{ envvars }}"
编辑后的测试与新报错
修改任务列表后:
- name: builtin | shell | capture DJANGO_ environment variables debugger: on_failed ansible.builtin.shell: cmd: "env | grep DJANGO_" register: out environment: "{{ envvars }}" become: true become_user: django become_method: su - name: builtin | debug | print content of out.stdout ansible.builtin.debug: var: out.stdout - name: builtin | debug | print variable 'envvars' ansible.builtin.debug: var: envvars - name: community.general | django_manage | populate the static subdirectory community.general.django_manage: command: collectstatic clear: yes project_path: "/opt/django/src" virtualenv: "/opt/django/venv" become: true become_user: django become_method: su environment: "{{ envvars }}"
此时shell任务能正确获取到DJANGO_开头的环境变量,debug也能输出envvars的内容,但django_manage的collectstatic任务仍失败,报错核心信息:
ModuleNotFoundError: No module named 'None'
完整报错回溯:
TASK [community.general | django_manage | populate the static subdirectory] ************************************************************************************************ fatal: [django1.domain.com]: FAILED! => {"changed": false, "cmd": ["./manage.py", "collectstatic", "--noinput", "--clear"], "msg": " :stderr: Traceback (most recent call last): File "/opt/django/venv/lib/python3.9/site-packages/django/core/management/__init__.py", line 204, in fetch_command app_name = commands[subcommand] KeyError: 'collectstatic' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/django/src/./manage.py", line 22, in <module> execute_from_command_line(sys.argv) File "/opt/django/venv/lib/python3.9/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line utility.execute() File "/opt/django/venv/lib/python3.9/site-packages/django/core/management/__init__.py", line 375, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/opt/django/venv/lib/python3.9/site-packages/django/core/management/__init__.py", line 211, in fetch_command settings.INSTALLED_APPS File "/opt/django/venv/lib/python3.9/site-packages/django/conf/__init__.py", line 57, in __getattr__ self._setup(name) File "/opt/django/venv/lib/python3.9/site-packages/django/conf/__init__.py", line 44, in _setup self._wrapped = Settings(settings_module) File "/opt/django/venv/lib/python3.9/site-packages/django/conf/__init__.py", line 107, in __init__ mod = importlib.import_module(self.SETTINGS_MODULE) File "/usr/lib/python3.9/importlib/__init__.py", line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "<frozen importlib._bootstrap>", line 1030, in _gcd_import File "<frozen importlib._bootstrap>", line 1007, in _find_and_load File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 680, in _load_unlocked File "<frozen importlib._bootstrap_external>", line 790, in exec_module File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed File "/opt/django/src/black_pearl/settings/production.py", line 3, in <module> from black_pearl.settings.common import * File "/opt/django/src/black_pearl/settings/common.py", line 301, in <module> path_app = import_module(app).__path__ File "/usr/lib/python3.9/importlib/__init__.py", line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) ModuleNotFoundError: No module named 'None' ", "path": "/opt/django/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "syspath": ["/tmp/ansible_community.general.django_manage_payload_l53eeo4g/ansible_community.general.django_manage_payload.zip", "/usr/lib/python39.zip", "/usr/lib/python3.9", "/usr/lib/python3.9/lib-dynload", "/usr/local/lib/python3.9/dist-packages", "/usr/lib/python3/dist-packages"]}
问题分析与解决步骤
核心问题定位
报错的根源是common.py第301行尝试导入名为None的模块,说明代码中某个app变量的值被设置为None,而这个变量大概率是从环境变量中读取的。
具体排查与修复
检查生成的YAML变量文件
打开host_vars/{{ inventory_hostname_short }}.yml,确认是否有变量值为None(而非空字符串),比如类似DJANGO_EXTRA_APPS: None的条目。Ansible会将YAML中的None解析为Python的None类型,传递给环境变量时会被转为字符串"None",导致Django代码尝试导入这个不存在的模块。统一django_manage任务的settings参数
之前的migrate任务指定了settings: myproject.settings,但collectstatic任务未指定,可能导致加载了不同的配置文件(比如默认的production.py)。给collectstatic任务加上相同的settings参数:- name: community.general | django_manage | populate the static subdirectory community.general.django_manage: command: collectstatic clear: yes project_path: "/opt/django/src" virtualenv: "/opt/django/venv" settings: myproject.settings # 新增该行 become: true become_user: django become_method: su environment: "{{ envvars }}"修复Django配置中的环境变量处理逻辑
查看common.py第301行的代码,确认app变量的来源。如果是从环境变量读取后直接拆分,需要处理空值情况:# 错误示例:未处理空环境变量 extra_apps = os.getenv('DJANGO_EXTRA_APPS') for app in extra_apps.split(','): path_app = import_module(app).__path__修改为:
# 正确处理:给默认空字符串,过滤空条目 extra_apps = os.getenv('DJANGO_EXTRA_APPS', '').split(',') for app in extra_apps: app = app.strip() if app: # 仅导入非空的app名称 path_app = import_module(app).__path__验证环境变量的完整性
在django_manage任务前添加debug任务,打印envvars的所有键值对,确认没有异常值:- name: builtin | debug | print full envvars details ansible.builtin.debug: var: envvars verbosity: 2
内容的提问来源于stack exchange,提问作者Jaume Sabater

