【问题标题】:How to debug Ansible issues?如何调试 Ansible 问题?
【发布时间】:2019-03-18 14:14:13
【问题描述】:

有时,ansible 不会做你想做的事。增加冗长也无济于事。例如,我现在正在尝试启动coturn 服务器,它带有systemd OS (Debian Jessie) 上的初始化脚本。 Ansible 认为它正在运行,但事实并非如此。我如何查看引擎盖下发生的事情?执行了哪些命令,输出/退出代码是什么?

【问题讨论】:

    标签: debugging ansible


    【解决方案1】:

    调试模块

    • 最基本的方法是运行ansible/ansible-playbook,通过将-vvv添加到执行行来增加详细级别。

    • 对于用 Python (Linux/Unix) 编写的模块,最彻底的方法是运行 ansible/ansible-playbook 并将环境变量 ANSIBLE_KEEP_REMOTE_FILES 设置为 1(在控制计算机上)。

      这会导致 Ansible 将其执行的 Python 脚本的精确副本(无论成功与否)留在目标机器上。

      脚本的路径打印在 Ansible 日志中,对于常规任务,它们存储在 SSH 用户的主目录下:~/.ansible/tmp/

      确切的逻辑嵌入在脚本中,取决于每个模块。有些使用带有标准或外部库的 Python,有些则调用外部命令。

    调试手册

    • 与使用-vvv 参数增加详细级别的调试模块类似,会导致更多数据打印到 Ansible 日志中

    • 从 Ansible 2.1 开始,Playbook Debugger 允许交互式调试失败的任务:检查、修改数据;重新运行任务。

    调试连接

    • -vvvv 参数添加到ansible/ansible-playbook 调用会导致日志包含连接的调试信息。

    【讨论】:

    • 您确定添加第四个-v 会有所不同吗?从ansible-playbook 的手册页中它没有。
    • 如果你不带任何参数运行ansible-playbook,你会看到:-v, --verbose : verbose mode (-vvv for more, -vvvv to enable connection debugging)
    • 能否详细说明“连接调试”部分?我可以看到-q 中的ssh 选项更改为-vvv,但没有其他更改。有什么区别?
    • 所以在systemd 登录目标系统时会出现额外的消息... 您能否在使用ANSIBLE_KEEP_REMOTE_FILES=1 时建议进一步的操作?模块源是压缩的,对吧?所以没有简单的方法来修改它。
    • 您使用哪个版本的ansibleansible startedzip 模块早在 2.0,如果我没记错的话。
    【解决方案2】:

    如果不是您自己的任务,调试 Ansible 任务几乎是不可能的。与 Ansible 网站所说的相反。

    不需要特殊的编码技能

    Ansible 需要高度专业化的编程技能,因为它不是 YAML 或 Python,它是两者的杂乱组合。

    以前已经尝试过使用标记语言进行编程的想法。 XML 曾经在 Java 社区中非常流行。 XSLT 也是一个很好的例子。

    随着 Ansible 项目的增长,复杂性也随之呈指数级增长。以具有以下任务的 OpenShift Ansible 项目为例:

    - name: Create the master server certificate
      command: >
        {{ hostvars[openshift_ca_host]['first_master_client_binary'] }} adm ca create-server-cert
        {% for named_ca_certificate in openshift.master.named_certificates | default([]) | lib_utils_oo_collect('cafile') %}
        --certificate-authority {{ named_ca_certificate }}
        {% endfor %}
        {% for legacy_ca_certificate in g_master_legacy_ca_result.files | default([]) | lib_utils_oo_collect('path') %}
        --certificate-authority {{ legacy_ca_certificate }}
        {% endfor %}
        --hostnames={{ hostvars[item].openshift.common.all_hostnames | join(',') }}
        --cert={{ openshift_generated_configs_dir }}/master-{{ hostvars[item].openshift.common.hostname }}/master.server.crt
        --key={{ openshift_generated_configs_dir }}/master-{{ hostvars[item].openshift.common.hostname }}/master.server.key
        --expire-days={{ openshift_master_cert_expire_days }}
        --signer-cert={{ openshift_ca_cert }}
        --signer-key={{ openshift_ca_key }}
        --signer-serial={{ openshift_ca_serial }}
        --overwrite=false
      when: item != openshift_ca_host
      with_items: "{{ hostvars
                      | lib_utils_oo_select_keys(groups['oo_masters_to_config'])
                      | lib_utils_oo_collect(attribute='inventory_hostname', filters={'master_certs_missing':True}) }}"
      delegate_to: "{{ openshift_ca_host }}"
      run_once: true
    

    我想我们都同意这是在 YAML 中编程。不是一个很好的主意。这个特定的 sn-p 可能会失败并显示类似

    的消息

    致命:[master0]:失败! => {"msg": "条件检查'item != openshift_ca_host' 失败。错误是:评估时出错 条件 (item != openshift_ca_host): 'item' 未定义\n\n 错误似乎出现在 '/home/user/openshift-ansible/roles/openshift_master_certificates/tasks/main.yml': 第 39 行,第 3 列,但可能\n位于文件中的其他位置,具体取决于 确切的语法问题。\n\n违规行似乎是:\n\n\n- name: 创建主服务器证书\n ^ here\n"}

    如果你收到这样的消息,你就完蛋了。但是我们有调试器对吗?好,我们来看看是怎么回事。

    master0] TASK: openshift_master_certificates : Create the master server certificate (debug)> p task.args
    {u'_raw_params': u"{{ hostvars[openshift_ca_host]['first_master_client_binary'] }} adm ca create-server-cert {% for named_ca_certificate in openshift.master.named_certificates | default([]) | lib_utils_oo_collect('cafile') %} --certificate-authority {{ named_ca_certificate }} {% endfor %} {% for legacy_ca_certificate in g_master_legacy_ca_result.files | default([]) | lib_utils_oo_collect('path') %} --certificate-authority {{ legacy_ca_certificate }} {% endfor %} --hostnames={{ hostvars[item].openshift.common.all_hostnames | join(',') }} --cert={{ openshift_generated_configs_dir }}/master-{{ hostvars[item].openshift.common.hostname }}/master.server.crt --key={{ openshift_generated_configs_dir }}/master-{{ hostvars[item].openshift.common.hostname }}/master.server.key --expire-days={{ openshift_master_cert_expire_days }} --signer-cert={{ openshift_ca_cert }} --signer-key={{ openshift_ca_key }} --signer-serial={{ openshift_ca_serial }} --overwrite=false"}
    [master0] TASK: openshift_master_certificates : Create the master server certificate (debug)> exit
    

    这有什么帮助?它没有。

    这里的重点是使用 YAML 作为编程语言是一个非常糟糕的主意。这是一团糟。我们正在制造的混乱的症状无处不在。

    一些额外的事实。在 Openshift Ansible 的 Azure 上提供先决条件阶段需要 +50 分钟。部署阶段需要超过 +70 分钟。每一次!首次运行或后续运行。并且没有办法将供应限制在单个节点上。这个limit 问题是 2012 年 Ansible 的一部分,今天它仍然是 Ansible 的一部分。这个事实告诉我们一些事情。

    这里的重点是应该按预期使用 Ansible。对于没有 YAML 编程的简单任务。适用于大量服务器,但不应用于复杂的配置管理任务。

    Ansible 不是基础架构即代码 (IaC) 工具。

    如果您询问如何调试 Ansible 问题,则说明您使用它的方式并非本意。不要将其用作 IaC 工具。

    【讨论】:

    • 关于您的示例代码,您可以通过在执行命令之前评估部分代码来简化它 (set_fact)。 And there is no way to limit provision to a single node. 我大部分时间都配置一个节点。这和你的剧本有关系吗? it should not be used for complex configuration management tasks你能推荐任何其他工具吗? Ansible is a not Infrastructure as Code ( IaC ) tool.我觉得还是IaC,哪怕只是简单的配置管理任务。
    • Provision of prerequisites phase on Azure of Openshift Ansible takes on +50 minutes. 尚不清楚是什么让它需要这么长时间。还有其他明显更快的解决方案吗? This fact tells us something. 此外,它在CodeTriage 上排名第二。我个人不喜欢的是,通常没有简单的方法可以更改配置文件中的一个特定设置。你不得不偶尔替换整个配置文件,或者编写复杂的代码。
    • If you ask how to debug Ansible issues, you are using it in a way it was not intended to be used. ansible 有时会以您不理解的方式行事。也许您只是未能在文档中找到它,或者它可能只是一个错误。但你有一个有效的观点。一般来说,我认为ansible 是不完美的。这个想法很好,但它缺少实现它的功能、插件、模块、角色。
    • 这不是任何一种“高级编程”。它只是将参数模板化为命令,这正是模板 的用途。尽管如此,如果您使用的是命令模块,那么您在 ansible 中暴露了一个缺失的功能(不是架构问题,尽管它当然也有这些)。不,当然不是 IaC ......它恰恰相反:基础设施作为 DATA(您可以在示例中清楚地看到:一堆嵌套字典 + 模板)。
    【解决方案3】:

    这是我想出的。

    Ansible 将模块发送到目标系统并在那里执行它们。因此,如果您在本地更改模块,您的更改将在运行 playbook 时生效。在我的机器上,模块位于/usr/lib/python2.7/site-packages/ansible/modules (ansible-2.1.2.0)。而service 模块位于core/system/service.py。 Anisble modulesAnsibleModule 类在 module_utils/basic.py 声明的实例)具有 log 方法,如果可用,它将消息发送到 systemd 日志,或者回退到 syslog。因此,在目标系统上运行journalctl -f,将调试语句(module.log(msg='test'))添加到本地模块,然后运行您的剧本。您将在ansible-basic.py 单元名称下看到调试语句。

    此外,当您使用-vvv 运行ansible-playbook 时,您可以在systemd 日志中看到一些调试输出、至少调用消息和错误消息(如果有)。

    还有一点,如果您尝试使用pdb (import pdb; pdb.set_trace()) 调试在本地运行的代码,您很可能会遇到BdbQuit 异常。那是因为pythoncloses stdin在创建线程时(ansibleworker)。这里的解决方案是按照here的建议运行pdb.set_trace()之前重新打开stdin

    sys.stdin = open('/dev/tty')
    import pdb; pdb.set_trace()
    

    【讨论】:

    • 我没有发表评论,嗯,这是一篇非常好的文章,我真的很喜欢你确实必须实际运行调试器的事实:),幸运的是只有本地代码:)
    • 感谢您的标准输入提示!这适用于 pdb,但不适用于 ipdb。但是,在 localhost 上运行我的 playbook 时,我看到 FD 0、1 和 2 仍然指向我的 TTY,所以我改用了 sys.stdin = os.fdopen(0, 'r'),ipdb 对此很满意。
    【解决方案4】:

    调试角色/剧本

    基本上调试跨大型网络的大量库存的 ansible 自动化无非就是调试分布式网络应用程序。它可能非常繁琐和精致,并且没有足够的用户友好工具。

    因此,我相信您的问题的答案也是我之前的所有答案+小加法的结合。所以在这里:

    • 绝对强制:您必须想知道发生了什么,即您正在自动化什么,您期望发生什么。例如ansible 未能检测到 systemd 单元正在运行或停止的服务通常意味着服务单元文件或服务模块中存在错误,因此您需要 1. 识别错误,2. 向供应商/社区报告错误,3. 提供您的解决方法使用 TODO 并链接到错误。 4. 修复错误后 - 删除您的解决方法

    • 尽可能让您的代码更易于调试使用模块

    • 为所有任务和变量赋予有意义的名称。

    • 使用静态代码分析工具,例如ansible-lint。这样可以避免你犯愚蠢的小错误。

    • 利用详细标志和日志路径

    • 明智地使用debug模块

    • “了解你的事实” - 有时将目标机器事实转储到文件中并将其拉到 ansible master 会很有用

      • 使用strategy: debug在某些情况下,您可能会因错误而陷入任务调试器。然后,您可以评估任务正在使用的所有参数,并决定下一步做什么

      • 最后的手段是使用 Python 调试器,将其附加到本地 ansible 运行和/或远程 Python 执行模块。这通常很棘手:您需要允许机器上的其他端口打开,如果打开端口的代码是导致问题的原因?

    此外,有时“旁观”很有用 - 连接到您的目标主机并提高它们的可调试性(更详细的日志记录)

    当然,日志收集可以更轻松地跟踪由于 ansible 操作而发生的变化。

    如您所见,与任何其他分布式应用程序和框架一样 - 调试能力仍然不如我们所愿。

    过滤器/插件

    这基本上是 Python 开发,像任何 Python 应用程序一样调试

    模块

    取决于技术,并且由于您需要查看本地和远程发生的事情这一事实,您最好选择足够容易远程调试的语言。

    【讨论】:

    • @x-yuri 您也可以尝试使用 IDE,例如pycharm 工作得很好。但当然要注意将其指向正确的 Python,打开端口等。
    【解决方案5】:

    您可以使用注册模块和调试模块来打印返回值。例如,我想知道我的脚本执行的返回码是什么,叫做“somescript.sh”,所以我会在play里面有我的任务,比如:

    - name: my task
      shell: "bash somescript.sh"
      register: output
    
    - debug:
      msg: "{{ output.rc }}"
    

    有关您可以在 Ansible 中访问的完整返回值,您可以查看此页面:http://docs.ansible.com/ansible/latest/common_return_values.html

    【讨论】:

      【解决方案6】:

      您可能需要多个级别的调试,但最简单的一个是添加 ANSIBLE_STRATEGY=debug 环境变量,这将在第一个错误时启用调试器。

      【讨论】:

        【解决方案7】:

        第一种方法:通过q 模块调试 Ansible 模块,并通过q 模块将调试日志打印为q('Debug statement')。请检查q 模块页面以检查在大多数情况下将在tmp 目录中生成日志的位置,或者在$TMPDIR\q\tmp\q 处生成日志,因此可以执行tail -f $TMPDIR\q检查 Ansible 模块播放运行后生成的日志(参考:q module)。

        第二种方法:如果播放在localhost 上运行,可以使用pdb 模块来调试播放以下相应的文档:https://docs.ansible.com/ansible/latest/dev_guide/debugging.html

        第三种方法:使用 Ansible debug 模块打印播放结果并调试模块(参考:Debug module)。

        【讨论】:

        • 关于第一种方法,你省略了很多。你必须让它把任务留在服务器上,然后你把它炸开,然后你就可以用pdb了。
        • 我不确定您将任务留在服务器上是什么意思,因为 ansible 是无代理的,q 模块调试语句应该在触发 ansible 播放的系统上运行并且调试语句也在同一系统中生成 tmp 目录,因为 pdb 不会帮助您尝试通过 localhost 以外的连接进行调试。
        • ansible 执行任务时,它通常会将执行任务的脚本复制到服务器(到~/.ansible/tmp),然后执行它。通常在执行任务后,它会删除脚本。通过“将任务留在服务器上”,我的意思是让它不删除脚本。请参阅ANSIBLE_KEEP_REMOTE_FILES 环境变量。然后,您说q 将在控制节点(而不是托管节点)上运行。它将写入控制节点上的tmp 目录。这意味着它只能调试在控制节点上执行的代码。与pdb 一样。有什么区别?
        • ...意思是,看起来,pdbq 都不允许您调试远程执行的代码。如果q 不是这种情况,它是如何实现的?在被管节点上实现调试代码,在控制节点上写入tmp。请纠正我的错误。
        猜你喜欢
        • 2012-08-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-10
        • 2010-10-07
        相关资源
        最近更新 更多