当Ansible连接到Windows时使用代理的情况

首先

这篇帖子是Ansible 3 Advent Calendar 2019第12天的文章。虽然题目是Ansible,但内容主要是关于pywinrm…。

如果使用Ansible操作Windows主机时处于代理环境下,有时可能会由于经过代理而导致错误。为了解决这个问题并避免通过代理,我确认了pywinrm模块的行为并进行介绍。

前提条件 (Paraphrased in Chinese)

我已经准备好参考这篇文章《准备在Ansible中操作Windows》,并在Ansible和Windows上完成了准备工作。

$ ansible --version
ansible 2.9.2

$ python3 --version
Python 3.6.8
[windows]
windows-server ansible_host=192.168.1.1

[windows:vars]
ansible_user=hogeuser
ansible_password=hogepass
ansible_port=5986
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore

通过代理服务器引发的错误

$ ansible -i inventory -m win_ping windows-server
windows-server | UNREACHABLE! => {
    "changed": false,
    "msg": "ntlm: HTTPSConnectionPool(host='192.168.1.1', port=5986): Max retries exceeded with url: /wsman (Caused by ProxyError('Cannot connect to proxy.', timeout('timed out',)))",
    "unreachable": true
}

1. 参考Ansible控制节点的环境变量no_proxy。

如果要连接到Windows,则使用ansible_connection=winrm,并使用名为pywinrm的模块进行http/https连接。

在默认情况下,pywinrm模块会参考Ansible控制节点的环境变量http_proxy,https_proxy和no_proxy。如果您想在代理环境中执行Ansible时直接连接到Windows而不需要通过代理,请将目标主机名或IP地址(网络地址)设置为环境变量no_proxy的值。

顺便提一下,虽然也会加载NO_PROXY,但由于no_proxy优先级更高,因此如果分别进行设置,可能会导致意料之外的行为。(这是winrm参考requests模块的规范)

http_proxy=[proxyserver]
https_proxy=[proxyserver]
no_proxy=192.168.0.0/16
NO_PROXY=192.168.0.0/16,172.16.0.0/12
# no_proxyが優先されてNO_PROXYの'172.16.0.0/12'は反映されないため、172.17.0.1などはプロキシ経由となってしまう

2. 修改pywinrm模块(不推荐使用)

如果pywinrm参考环境变量http_proxy和https_proxy,会通过代理进行访问。如果不希望这样,只需不加载环境变量即可。

在pywinrm的transport.py文件中,引用了requests模块,并且当session.trust_env = True时,会读取环境变量。根据requests模块的说明,trust_env表示”相信执行环境的环境变量吗?如果相信,就让我来使用它们!”。

因此,如果将transport.py的session.trust_env = False修改为不读取环境变量,就可以直接连接到Windows主机而不经过代理。

在我的环境中,/usr/local/lib/python3.6/site-packages/winrm/下存在transport.py文件。由于pywinrm版本的不同,transport.py的实现也有所差异,因此在Ansible中必须使用0.3.0版本和当前最新版本0.4.1。

版本0.3.0

    def build_session(self):
        session = requests.Session()

        session.verify = self.server_cert_validation == 'validate'
        if session.verify and self.ca_trust_path:
                session.verify = self.ca_trust_path

        # configure proxies from HTTP/HTTPS_PROXY envvars
        #session.trust_env = True # 変更前
        session.trust_env = False # 変更後

只需将0.3.0版本中的session.trust_env = True更改为False。

版本0.4.1。

    def build_session(self):
        session = requests.Session()
        proxies = dict()

        if self.proxy is None:
            proxies['no_proxy'] = '*'
        elif self.proxy != 'legacy_requests':
            # If there was a proxy specified then use it
            proxies['http'] = self.proxy
            proxies['https'] = self.proxy

        session.trust_env = False #この行を追加

        # Merge proxy environment variables
        settings = session.merge_environment_settings(url=self.endpoint,
                      proxies=proxies, stream=None, verify=None, cert=None)

从版本0.4.0开始,已经删除了session.trust_env = True的说明。由于session.trust_env的默认值为True,即使没有说明,Ansible控制节点的环境变量也会被引用。

如果你添加”session.trust_env = False”,结果将与0.3.0版本相同。

$ ansible -i inventory -m win_ping windows-server
windows-server | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

将pywinrm变更为非推荐的原因。

只是个人观点,我认为不推荐使用pywinrm。

每次升级pywinrm版本都需要相应的适配。

如上所述,版本0.3.0和0.4.1中的transport.py实现已经有所改变。未来随着版本的升级,将需要特别对此进行处理。顺便提一句,

使用AWX公式容器映像变得更加不便。

如果您在AWX的部署中使用官方的awx_task容器,那么您需要修改容器内的transport.py文件。然而,在容器重新启动时,配置会被重置。

综上所述

我已经确认了Ansible连接Windows时pywinrm的行为。总而言之,只需要在环境变量no_proxy中设置目标主机即可。但是,如果因为某些原因无法设置no_proxy,则也可以通过修改pywinrm来实现。

bannerAds