Ansible用于管理Windows配置。 成功故事

在SpbDotNet社区开发人员的圣彼得堡.Net社区的一次会议上,我们进行了一项实验,并决定讨论如何使用Linux界早已成为标准的方法来自动化Windows基础结构。 但是,为了不让一切陷入Ansible标志的波澜不惊,我们决定通过部署ASP.Net应用程序的示例来说明这一点。


为我们的项目开发UI组件库的团队的高级开发人员Aleksey Chernov自愿担任演讲者。 是的,在您看来,这似乎没有:一个JavaScript开发人员出现在.Net听众面前。


那些对这样的实验的结果感兴趣的人,我们恳请减少解码。



嗨,他们已经有点受宠若惊了,说我是前端,所以您可以分开了。=)我叫Alexey,我从事网络开发工作已经有一段时间了。 我从Perl开始,然后是PHP,一点RoR,一点点,一点点。 然后JavaScript席卷了我的生活,从那时起,我几乎一直在做所有这一切。


除了JS之外,最近我还编写了很多自动测试(此外,在同一JS上),因此我必须处理测试平台及其基础结构的自动化部署。


背景知识


两年前,我结束了在Veeam的工作,他们正在为Windows开发产品。 那一刻,我感到非常惊讶,但事实证明,它确实发生了=)。 但最令我惊讶的是,与部署,应用程序部署,测试等相关的所有事物的自动化程度异常低。


我们,那些为Linux开发的人,早就习惯了这样的事实:所有内容都应该在Docker中,有Kubernetes,并且单击即可显示所有内容。 当我最终在所有这些都不存在的环境中时,我感到震惊。 当我开始进行自动测试时,我意识到这只是20%的成功,其他一切都在为他们准备基础架构。



一开始我的感觉


目前状况


我会告诉您一些有关我们如何安排一切,我们必须自动化的内容以及我们要做的事情的信息。


我们有很多不同的产品,其中大多数在Windows下,在Linux下,甚至在Solaris下。 每天都会为所有产品收集大量构建。 因此,有必要在质量保证和开发人员自己的测试实验室中全部采用它,以便他们可以检查应用程序集成。 所有这一切都需要一个由大量铁服务器和虚拟机组成的庞大基础架构。 有时我们进行性能测试,需要立即启动一千台虚拟机,并查看应用程序运行的速度。


问题所在


当然,在最初阶段(很久以前阅读过),当尝试正面自动化所有内容时,就使用了PowerShell。 该工具功能强大,但是部署脚本非常复杂。 另一个问题是缺乏对该过程的集中管理。 有些脚本是由开发人员在本地运行的,有些是在猛ma时代创建的虚拟机上运行的,等等。 结果:很难获得一个单一的结果并了解哪些有效,哪些无效。 您开始工作,打开浏览器-服务器不可用。 为什么不可用,发生了什么,它在哪里破裂-完全不清楚。 没有一个入口,我不得不在工作聊天室中寻找真相,如果有人回答,那就太好了。


另一个问题(不是很明显)是新手。 对他们来说很难。 工作的第一周,他们只是研究了正在发生的事情。 我们习惯性地生活在这里,向自己保证生活是一件困难的事情,我们必须忍受它。 要理解和原谅,可以这么说。


但是在某个时候,他们发现了克服它并环顾四周的内在力量。 可能以某种方式可以处理它。



解决问题的第一步是接受它。


解决方案选择


当您不知道该怎么做时,请看看别人在做什么。


对于初学者,我们列出了最终要达到的要求的清单。


  • 统一代码库。 所有部署脚本应位于同一位置。 想要扩展某些内容或查看其展开方式:这是您的存储库,请转到此处。
  • 每个人都知道它是如何工作的。 问题应该消失“我不知道如何部署它,因此第二天我无法关闭该错误。”
  • 按按钮启动的能力。 我们需要能够控制部署。 例如,某种Web界面,您可以在其中单击按钮,然后将所需的产品部署到所需的主机。

在确保这份清单涵盖了我们幸福的最低限度的必要和充分要求之后,我们开始尝试。 传统上,他们尝试解决问题的第一件事是正面攻击方法。 我们有很多PowerShell脚本吗? 因此,让我们将它们合并到一个存储库中。 但是问题不在于脚本太多,而是不同的团队使用不同的脚本来做同一件事。 我绕过不同的团队,听取了他们的要求,收集了相同的脚本,试图以某种方式梳理和参数化它们,然后将它们放在一个存储库中。


失败:尝试失败。 首先,我们开始就为什么这样做而不是那样争论很多。 为什么使用此方法,而不使用其他方法,等等。 结果,有很多人希望按照“我将为您分叉并重写所有内容”的原则“按原样”重做所有内容。 当然,将分支与这种方法结合起来是不可能的。


尝试第二次:本来要使用我们的CI服务器(TeamCity),在其上制作一些模板,然后使用继承从第一次尝试中解决主要问题。 但是,正如您可能立即猜到的那样, Fail也正在等待我们您只能将模板用于最新版本,这意味着我们将无法实现必要的版本控制。 大量团队的结果-模板变得非常多,管理它们变得更加困难,并且在地平线上清晰可见新的沼泽。



厌倦了在尝试起飞时面朝下坠落的决定,决定再次坐下来认真思考。 因此,我们一方面拥有大量的ps脚本,另一方面拥有大量的虚拟脚本。 但是我们错了,因为 那不是问题的根源。 问题在于这些事物之间总有一个人。 不管是开发人员,测试人员还是其他任何人,以下逻辑链条始终出现在头脑中:


  • 所以,我需要一台虚拟机进行测试
  • 是的,这里有一个主机池
  • 这是我需要的脚本,现在我将运行它,一切都会发生

随着这种看似简单的事情的实现,普遍的问题开始以新的色彩出现。 事实证明,我们所有的痛苦都是由于缺乏对基础架构的单一描述。 在创建它的人的脑海中,他们坐在不同的部门,没有尝试以某种方式记录它,并且通常每个人都生活在各自独立的状态中。
目前,我们得出的结论是,我们所有问题的答案是:


基础架构即代码


正是我们的整个基础架构应该用代码描述并放在存储库中。 所有虚拟机,其所有参数,此处安装的所有内容-都需要在代码中描述。


一个合理的问题出现了-为什么?


我们回答:这种方法将使我们有机会应用开发界的最佳实践,而我们对此已经非常习惯:


  • 版本控制。 我们始终可以了解更改的内容和时间。 没有更多的主机无处不在或无处不在。 总是很清楚谁进行了更改。
  • 代码审查。 我们将能够控制部署过程,以使某些过程不会侵犯其他过程。
  • 持续集成。

工具选择


众所周知,有许多配置管理工具。 我们选择Ansible,因为它包含我们需要的一组功能。
首先,我们不想从自动化系统中运行任何安装程序,某些东西已经迁移到某个地方,等等。 首先,从这样一个系统中,我们希望在按下一个按钮之后,我们将看到所需应用程序的UI。


因此,对我们而言,关键特性是幂等。 Ansible没关系,之前发生的事情。 在启动所需的剧本之后,我们总是会得到相同的结果。 当您不说“安装IIS”,而是说“应该有IIS”时,这非常重要,您不必考虑他是否曾经在那儿。 用脚本很难做到这一点,而Ansible剧本提供了这样的机会。


同样值得一提的是Ansible的无代理性。 大多数自动化系统通过代理工作。 这样做有很多优点-例如,最佳性能-但对我们而言,没有代理很重要,因此不必以某种方式额外准备系统。


PowerShell的:


$url = "http://buildserver/build.msi" $output = "$PSSscriptRoot\build.msi" Invoke-WebRequest -Uri $url -OutFile $output 

Ansible:


 name: Download build hosts: all tasks: name: Download installer win_get_url: url: "http://buildserver/build.msi" dest: "build.msi" force: no 

在这里,我们看到在基本示例中,ps脚本比Ansible剧本更加简洁。 3行脚本与7行剧本,以便下载文件。


但是,佩特卡有一个细微差别。 一旦我们想要遵循幂等原则,并且例如要确保服务器上的文件没有更改并且不需要下载,就必须在脚本中实现HEAD请求,这将增加大约200行。 在剧本中-一本。 Ansible win_get_url模块为您执行所有检查,其中包含257行代码,您无需在每个脚本中插入这些代码。


这只是一项非常简单的任务的例子。



如果您考虑一下,我们到处都需要幂等:


  • 验证是否存在虚拟机。 就脚本而言,我们冒着产生无限数量的脚本的风险,否则脚本会在一开始就崩溃。
  • 机器上有哪些msi软件包? 在最好的情况下,这里什么都不会掉落;在最坏的情况下,机器将停止正常工作。
  • 我是否需要再次下载构建工件? 如果您的构建重达12兆字节,那就太好了。 那么那些拥有几千兆字节的用户呢?

在其他示例中,解决方法是使用无法充分调试且无法管理的ifs无休止分支来膨胀脚本。


除其他重要事项外,Ansible不会使用代理来管理您的主机和计算机。 当然,在Linux上,它运行在ssh上,而在Windows上,则使用WinRM。 因此,显而易见的结果是:Ansible是跨平台的。 它支持数量惊人的平台,甚至网络设备。


最后但同样重要的是YAML配置记录格式。 每个人都习惯了它,它很容易阅读,也很容易弄清楚那里发生了什么。


但是,并非一切都那么甜蜜,但存在一些问题:


  • 问题是可疑的:要运行剧本,即使您的整个基础架构都是Windows,您仍然需要一台Linux机器。 尽管这在现代世界中并不是一个大问题,因为 在Windows 10上,现在有WSL,您可以在其中运行Ubuntu,在其下驱动剧本。
  • 有时剧本真的很难调试。 Ansible是用python编写的,我最后要看的是五屏python堆栈堆栈表。 还有模块名称中的错字

如何运作?
首先,我们需要一台Linux机器。 在Ansible术语中,这称为控制机。
剧本将从此开始,所有魔力发生在上面。


在这台机器上,我们将需要:


  • Python和python包管理器pip。 许多发行版都是开箱即用的,因此在此不感到意外。
  • 通过pip安装Ansible,这是最通用的方式:pip install ansible
  • 添加Winrm模块以转到Windows机器:pip install pywinrm [credssp]
  • 在我们要控制的机器上,我们需要启用winrm,因为 默认情况下处于关闭状态。 有很多方法可以做到这一点,并且在Ansible文档中都对它们进行了描述。 但是最简单的是从Ansible存储库中获取完成的脚本,并使用所需的授权选项运行它:ConfigureRemotingForAnsible.ps1 -EnableCredSSP

我们要停止遭受ps脚本困扰的最重要部分是清单。 一个YAML文件(在我们的例子中),它描述了我们的基础架构以及您可以随时查看的位置,以了解其部署位置。 当然,还有剧本本身。 将来,这项工作看起来像是启动带有必要清单文件和其他参数的剧本。


 all: children: webservers: hosts: spbdotnet-test-host.dev.local: dbservers: hosts: spbdotnet-test-host.dev.local: vars: ansible_connection: winrm ansible_winrm_transport: credssp ansible_winrm_server_cert_validation: ignore ansible_user: administrator ansible_password: 123qweASD 

这里的一切都很简单:根组是全部和两个子组,即webserve和dbservers。 其他所有内容都是直观的,但我会提请您注意以下事实:默认情况下,Ansible认为Linux无处不在,因此对于Windows,您必须指定winrm和授权类型。


当然,明文形式的密码不需要存储在剧本中,这里仅是示例。 密码可以存储在例如Ansible-Vault中。 为此,我们使用TeamCity,它通过环境变量传递秘密,并且不会触发任何操作。


模组


Ansible所做的一切,都是借助模块来完成的。 Linux的模块是用python编写的,Windows的是PowerShell中的。 对幂等性表示敬意:模块的结果始终以json文件的形式出现,该文件指示主机上是否发生了更改。


在一般情况下,我们将运行ansible主机组清单文件模块列表的构造:



剧本


剧本描述了我们如何以及在何处执行Ansible模块。


 - name: Install AWS CLI hosts: all vars: aws_cli_download_dir: c:\downloads aws_cli_msi_url: https://s3.amazonaws.com/aws-cli/AWSCLI32PY3.msi tasks: - name: Ensure target directory exists win_file: path: "{{ aws_cli_download_dir }}" state: directory - name: Download installer win_get_url: url: "{{ aws_cli_msi_url }}" dest: "{{ aws_cli_download_dir }}\\awscli.msi" force: no - name: Install AWS CLI win_package: path: "{{ aws_cli_download_dir }}\\awscli.msi" state: present 

在此示例中,我们有三个任务。 每个任务都是一个模块调用。 在此剧本中,我们首先创建一个目录(确保它存在),然后在此处下载AWS CLI并使用win_packge模块进行安装。


通过运行此剧本,我们可以获得此结果。



该报告显示成功完成了四个任务,并且四个任务中的三个已在主机上进行了一些更改。


但是,如果您再次运行此剧本怎么办? 我们没有写任何应该创建目录,下载安装程序文件并运行它的地方。 我们只检查每个项目的可用性,如果有则跳过。



这是PowerShell无法实现的幂等性。


练习


这是一个稍微简化的示例,但原则上,这正是我们每天要做的事情。
我们将在IIS下部署一个由Windows服务和Web应用程序组成的应用程序。


 - name: Setup App hosts: webservers tasks: - name: Install IIS win_feature: name: - Web-Server - Web-Common-Http include_sub_features: True include_management_tools: True state: present register: win_feature - name: reboot if installing Web-Server feature requires it win_reboot: when: win_feature.reboot_required 

首先,我们需要查看主机上是否有任何IIS,如果没有,请安装它。 立即添加管理工具和所有相关功能将非常不错。 如果需要的话,重启主机非常好。


我们解决的第一个任务是win_feature模块,该模块用于管理Windows功能。 这是我们第一次在注册项目中使用环境变量Ansible。 记住,我说过任务总是返回一个json对象? 现在,在完成“安装IIS”任务之后,win_feature模块的输出位于win_feature变量中(对我来说是重言式吧)。


在下一个任务中,我们调用win_reboot模块。 但是我们不需要每次都重新启动服务器。 仅当win_feature模块以变量形式向我们返回此要求时,我们才会重新加载它。


下一步是安装SQL。 已经发明了百万种方法来做到这一点。 我在这里使用win_chocolatey模块。 这是Windows的程序包管理器。 是的,这正是我们在Linux上所惯用的。 这些模块得到了社区的支持,现在已经有六千多个了。 我强烈建议您尝试。


 - name: SQL Server hosts: dbservers tasks: - name: Install MS SQL Server 2014 win_chocolatey: name: mssqlserver2014express state: present 

因此,我们准备了启动应用程序的主机,让我们对其进行部署!


 - name: Deploy binaries hosts: webservers vars: myapp_artifacts: files/MyAppService.zip myapp_workdir: C:\myapp tasks: - name: Remove Service if exists win_service: name: MyAppService state: absent path: "{{ myapp_workdir }}\\MyAppService.exe" 

以防万一,我们要做的第一件事是删除现有服务。


 - name: Delete old files win_file: path: "{{ myapp_workdir }}\\" state: absent - name: Copy artifacts to remote machine win_copy: src: "{{ myapp_artifacts }}" dest: "{{ myapp_workdir }}\\" - name: Unzip build artifacts win_unzip: src: "{{ myapp_workdir }}\\MyAppService.zip" dest: "{{ myapp_workdir }}" 

下一步是将新的工件上传到主机。 这本剧本暗示它在构建服务器上运行,所有档案都位于一个已知的文件夹中,并且我们用变量来指示它们的路径。 复制(win_copy)后,将压缩文件解压缩(win_unzip)。 然后,我们只注册该服务,说出exe的路径并应启动它。


  - name: Register and start the service win_service: name: ReporterService start_mode: auto state: started path: "{{ myapp_workdir }}\\MyAppService.exe" 

做完了?


看来我们的服务已准备就绪,可以进行工作和防御,但是有一个“但是”-我们没有遵守幂等原则。 我们总是删除现有代码,然后部署新代码。


这就是问题所在。 如果我们删除了旧的服务中断服务,并且在发生某种错误并且剧本未能完成其工作之后,我们将得到一台损坏的主机。 或者,例如,我们同时部署多个应用程序,其中一个没有更改,那么我们也不需要部署它。


该怎么办? 或者,您可以检查工件的校验和,并将其与服务器上的工件进行比较。


  - name: Get arifacts checksum stat: path: "{{ myapp_artifacts }}" delegate_to: localhost register: myapp_artifacts_stat - name: Get remote artifacts checksum win_stat: path: "{{ myapp_workdir }}\\MyAppService.zip" register: myapp_remote_artifacts_stat 

我们使用stat模块,该模块提供有关文件的所有信息,包括校验和。 接下来,使用熟悉的指令寄存器,将结果写入变量。 来自有趣的:delegate_to指示必须在启动剧本的本地计算机上完成此操作。


  - name: Stop play if checksums match meta: end_play when: - myapp_artifacts_stat.stat.checksum is defined - myapp_remote_artifacts_stat.stat.checksum is defined - myapp_artifacts_stat.stat.checksum == myapp_remote_artifacts_stat.stat.checksum 

并且在meta模块的帮助下,我们说如果本地计算机和远程计算机上的工件的校验和匹配,则需要完成剧本。 这就是我们观察幂等原理的方式。


  - name: Ensure that the WebApp application exists win_iis_webapplication: name: WebApp physical_path: c:\webapp site: Default Web Site state: present 

现在让我们看一下我们的Web应用程序。 我们省略了有关复制文件的部分,直截了当。 我们的构建服务器使发布者,所有松散的文件上传到主机,并使用内置的模块与IIS应用程序一起使用。 他将创建应用程序并运行它。


代码重用


我们设置的任务之一是:使公司中的任何工程师都可以轻松启动部署。 他用现成的模块编写剧本,说他需要在这样的主机上运行这样的产品。


为此,Ansible具有角色。 这本质上是一个约定。 我们在服务器上创建一个文件夹/角色/并将角色放入其中。 每个角色都是一组配置文件:对我们的设备,变量,服务文件等的描述。 通常,一些孤立的实体会起作用。 如果我们不仅需要安装IIS,而且还需要以某种方式配置它或通过其他任务检查它,则安装IIS是一个很好的例子。 我们扮演一个单独的角色,因此将所有与IIS相关的剧本都隔离在角色文件夹中。 将来,我们只需使用include_role%role_name%指令调用此角色。


自然,我们为所有应用程序扮演了角色,为工程师提供了使用配置参数以某种方式自定义流程的机会。


 - name: Run App hosts: webservers tasks: - name: "Install IIS" include_role: name: IIS - name: Run My App include_role: name: MyAppService vars: myapp_artifacts: ./buld.zip 

在此示例中,“运行我的应用程序”角色具有将某些路径转移到工件的能力。


在这里,您必须说说Ansible Galaxy-常用标准解决方案的存储库。 像在一个体面的社会中一样,许多问题已经摆在我们面前。 如果感觉现在我们将开始重新发明轮子,那么首先您需要查看内置模块的列表,然后深入研究Ansible Galaxy。 您所需的剧本可能已经由其他人制作。 在所有情况下,那里都有大量的模块。


更大的灵活性


但是,如果Galaxy中没有内置模块或合适的角色怎么办? 有两种选择:要么我们做错了事,要么我们确实有一个独特的任务。


在第二种选择的情况下,我们总是可以编写模块。 正如我在开始时向您展示的那样,Ansible使您可以在10分钟内编写出最简单的模块,而当您深入研究时,非常详尽的文档将为您提供帮助,涵盖了许多问题。


CI
在我们的部门中,我们真的很喜欢TeamCity,但是可以选择其他任何CI服务器。 为什么我们需要分享它们?


首先,我们始终可以检查剧本的语法。 虽然YAML认为制表符是语法错误,但这是一个非常有用的功能。


同样在CI服务器上,我们运行ansible-lint。 这是Ansible配置的静态分析器,提供了一系列建议。



例如,他在这里说,我们在行的末尾有一个额外的空间,对于一个任务,没有给出任何名称。 这很重要,因为 在同一个剧本中,模块名称可能会出现多次,并且必须命名所有任务。


当然,您仍然可以为剧本编写测试。 我们负担不起这样做,因为 我们将部署到测试环境,不会发生任何严重的事情。 但是,如果您将产品部署到产品上,则最好检查所有内容。 ansible的好处是,您不仅可以测试剧本,还可以测试各个模块。 所以一定要注意。


使用CI服务器的第二个主要原因是启动剧本。 这是相同的魔术按钮“ Do well”,为我们提供了TeamCity。 我们只是为不同的产品创建一些简单的配置,我们在其中说:ansible-playbook reporter_vm.yml -iventure.yml -vvvv并获取Deploy按钮。


额外的便利:您可以根据构建来构建构建。 一旦出现问题,TeamCity将立即开始重新部署过程,此后,我们仅在某些事件突然中断时才能查看日志。


合计


  • 我们用YAML-configs替换了混乱而截然不同的PowerShell脚本。
  • 我们用可以重复使用的通用角色替换了相同问题的各种实现。 已创建角色所在的存储库。 如果角色适合您,则只需使用它即可。 如果它不适合您,您只发送池请求,它就适合您=)
  • 现在,您可以在一个地方验证部署是否成功。
  • 每个人都知道在哪里查找日志。
  • 通过公共存储库和TeamCity也解决了通信问题。 所有感兴趣的人都知道剧本在哪里以及它们如何工作。

PS:本文中的所有示例都可以在github上获取

Source: https://habr.com/ru/post/zh-CN455604/


All Articles