大多数运维团队都在高度自动化的配置和供应系统的道路上取得了显著进展。有时这种转变是 DevOps 转型的一部分,有时是因为这是管理环境中变更的最佳方式。
这些系统非常擅长创建满足我们需求的系统制品,但当开发团队首次将应用程序部署到节点时,仍然会出现问题。在人工干预之前,问题无法被发现,而故障排除是一个漫长而手动的过程,涉及到检查清单和临时修复。我们如何才能在运维制品和开发制品相遇的崎岖道路上行驶得更顺畅?
引入 CI/CD
持续集成和交付 (CI/CD) 多年来一直是 IT 团队中的流行语,但主要作为一种开发流程。CI 是关于自动化测试对代码所做的更改,以防止引入到代码库的更改破坏应用程序。CD 是关于确保任何最终制品都适合在生产环境中使用。任何通过集成测试的应用程序构建都可以被识别以便于部署。
从运维的角度来看,我们的“应用程序”适合服务器或容器。我们的“代码”是执行操作的单个自动化代码片段。我们的“构建”是将这些代码片段串联起来以获得工作系统的蓝图。对自动化脚本或文件的所有更改都应经过测试,以确保它们不会引起问题。
将自动化测试视为 测试台 或 冒烟测试。 大多数自动化不是由每个系统的庞大代码块组成。相反,我们采用了其他模式,例如“不要重复自己” (DRY),来构建可重用的自动化块,我们可以重新组合这些自动化块以获得我们想要的配置。 这些类型的测试使我们能够发现角色之间的集成问题,在问题出现在生产环境中之前发现问题,并大致证明该系统适合其用途。 CI/CD 管道是旨在运行和管理这些类型的测试和签名的工具。
构建可靠管道的基础
我们需要就一些原则达成一致,以便在运维中利用管道
- 所有基础设施代码都在版本控制中
- 版本控制在非管道环境中很重要,但对于管道的运行来说至关重要。运维需要了解哪些更改“破坏了构建”并提供关于哪些是可以部署的明确指导。这意味着你可以确定由管道构建并存储在注册表中的容器镜像,或者由自动化配置的虚拟机,将是相同的并且功能齐全。
- 所有基础设施代码更改都会被单独测试
- 我们对代码库进行小幅更改,并且这些更改会经过基本正确性的审查。 这包括语法检查、功能、依赖项等。此级别的测试类似于应用程序的单元测试。
- 所有基础设施代码都作为一个组合系统进行测试
- 基础设施组件由离散的、较小的块组成,需要作为一个整体进行测试。 这些测试针对我们认为的“工作系统”的特性和行为。 我们的自动化可能是正确且有效的,但仍然不完整或者在不同的角色中存在冲突的步骤(例如,我们启动了 MySQL 但没有打开防火墙,或者我们在安全角色中锁定了端口)。
具体的 Python 示例
以上都是抽象的,因此我将介绍一个简单的例子。 这些角色和测试的质量达不到生产级别,但是,希望它们的功能足够强大,可以作为你进行调查的起点。 我还将使用我最熟悉的工具。 你的环境会有所不同,但是这些概念应该可以在你的工具箱中的任何工具之间进行转换。 如果你想查看示例代码,可以查看 GitHub 存储库。
这是我的工具箱中的内容
- Ansible: 一种流行的自动化引擎,用 Python 编写,我已经使用了好几年,我将使用它来构建一个用于测试的角色
- Molecule: 一个较新的、基于角色的 Ansible 测试工具,它将一些测试驱动的设计概念引入到角色开发中
- Testinfra: 一个基于 Pytest 的框架,用于检查系统状态,我将使用它来测试角色的行为
- Jenkins Blue Ocean: Jenkins 的一个新管道插件,它为管道提供了一个新的 UI,并支持管道的 Jenkinsfile 定义
以下是关于 Fedora 28 系统上设置的一些其他详细信息
- 由于 Ansible、Molecule 和 Testinfra 都是通过 PyPi 分发的,我已经使用 pip 在全局范围内安装了它们。
- 有一个用于 Jenkins 的容器,其中包含新的 UI 插件,因此我在同一台 Fedora 28 主机上运行它。
- Molecule 支持在容器内进行测试,并且 Jenkins 可以将该容器用作管道中的构建器。 为了让 Jenkins 容器中的 Jenkins docker 插件与主机上的 Docker 通信,我以 `privileged` 身份运行该容器,挂载了 docker socket 文件,并更改了主机上的 SELinux 上下文。 你需要确定最适合你环境的选项,因为对于超出此概念验证范围的任何内容,这都不是最佳选择。
稍后我将向你展示我为 Molecule 构建的 CentOS 7 基础镜像,其中包括与我们在其中开发角色的 Fedora 28 主机相同的所有依赖项。
创建角色目录
让我们构建一个角色来安装 Apache Web 服务器。 在顶级项目文件夹中,我们将拥有我们的清单、站点 playbook 和一个 roles
目录。 在 roles
目录中,我们将使用 Molecule 来初始化角色目录结构。
molecule init role -r webserver
--> Initializing new role webserver...
Initialized role in /root/iac-ci/blog/ansible/roles/webserver successfully.
在新创建的 webserver
目录中,你将看到类似于 ansible-galaxy init
命令的结果,并添加了一个 molecule
目录。 我没有更改命令行上的任何默认值,这意味着 Molecule 将使用 Docker 作为运行 playbook 的目标,并使用 Testinfra 作为运行测试的验证器。 你可以查看 molecule/default/molecule.yml
以获取这些详细信息或更改选项。
编写我们的角色
通常,我们会启动编辑器在 tasks/main.yml
上并开始编写 Ansible 任务。 但是由于我们正在考虑测试,所以让我们从那里开始(也称为测试驱动的设计)。 由于我们需要一个正在运行的 Web 服务器,因此我们有两个要求
- 服务是否正在运行?
- 是否有页面可以提供服务?
因此,我们可以打开 Molecule 为 Testinfra 创建的默认 Python 脚本,运行 molecule/default/tests/test_default.py
,并在测试后添加以下内容。
def test_httpd_runing(host):
httpd = host.service("httpd")
assert httpd.is_running
def test_index_contents(host):
index = host.file("/var/www/html/index.html")
assert index.exists
我们使用两个内置模块 Service 和 File 来检查 Ansible 角色执行后系统的状态。 我们将对冒烟测试使用相同的测试,但是在实时环境中,你需要对预期行为进行更复杂的检查。
现在我们可以将所需的任务和模板添加到角色以满足需求。我们将安装该软件包,创建模板化的 index.htmltasks/main.yml
,你可以在存储库中看到其余内容。
- name: Install Apache
package:
name: "{{ item }}"
state: present
with_items:
- httpd
- name: Create index
template:
src: templates/index.html.j2
dest: /var/www/html/index.html
notify:
- restart httpd
运行测试
使用 Molecule 或 Testinfra 运行任何测试之前的最后一步是创建我们需要的基础镜像。 我们不仅需要框架的依赖项,而且还需要使用具有 init
系统的容器。 这使我们能够测试虚拟机的最终目标,而无需第二个可用的虚拟机。
FROM centos/systemd
RUN yum -y install epel-release && \
yum -y install gcc python-pip python-devel openssl-devel docker openssh-clients && \
pip install docker molecule testinfra ansible && \
yum clean all
给容器一个你记住的名字,因为我们将在 Jenkins 管道中使用它。
docker build . -t molecule-base
现在可以在构建管道之前在主机上运行测试。 从 roles/webserver
目录中,运行 molecule test
,它将执行其默认矩阵,包括 Testinfra 测试。 你可以控制该矩阵,但是当我们构建管道时,我们将选择单独运行这些步骤,而不是使用 test
命令。
编写了我们的角色并进行了测试后,我们可以创建管道来控制我们的构建。
构建管道
Jenkins 安装指南 向你展示了如何获取容器以及如何在运行后解锁 Jenkins。 或者,你可以使用此 管道教程,它还将引导你完成将 Jenkins 实例连接到 GitHub 的过程。 管道每次运行都会签出你的代码,因此在源代码控制下拥有 Ansible、Molecule 和 Testinfra 至关重要。
前往 Jenkins Blue Ocean 的 Web UI,网址为 localhost:8080/blue
,然后单击“新建管道”。 如果你使用的是我的 GitHub 存储库的一个分支,则 Jenkins 将检测到现有的 Jenkinsfile 并立即开始运行管道。 你可能需要选择一个没有 Jenkinsfile 的新存储库。
在新管道上,你应该在右侧看到一个“管道设置”列。 在下拉框中选择 Docker,然后将基础镜像的名称添加到标记为“镜像”的框中。 这将是此管道创建的所有 Docker 容器使用的基础镜像。
在“环境”下,单击蓝色的 + 符号,然后在“名称”下添加 ROLEDIR
,在“值”下添加 ansible/roles/webserver
。 我们将在管道中多次使用它。 在顶层设置环境变量意味着可以在任何阶段访问它。
点击页面中心的 + 号来创建一个新的阶段(Stage)。阶段是流水线作业完成的工作块,每个阶段可以由多个顺序步骤组成。 对于此流水线,我们将为要运行的每个 Molecule 命令、针对 VM 运行的 Ansible Playbook 以及针对 VM 运行的 Testinfra 测试创建一个阶段。
Molecule 阶段都将是 shell 命令,因此单击“添加步骤(Add Step)”并选择“Shell 脚本(Shell Script)”。在框中,添加以下行:
cd $ROLEDIR
molecule syntax
这将确保我们在调用 Molecule 之前位于本地 Jenkins 工作目录中的角色目录中。 您可以查看测试矩阵以查看要运行的特定检查。 您无需创建或销毁任何实例,因为 Jenkins 将管理这些容器。
添加几个阶段后,您可以点击“保存(Save)”。 这将自动将 Jenkinsfile 提交到存储库并启动流水线作业。 您可以选择提交到 master 分支或新分支,这意味着您可以在不破坏生产环境的情况下测试新代码。
或者,Jenkinsfile 与我们的其余代码提交在同一个存储库中。 您可以直接编辑该文件以复制 Molecule 阶段,并使用 Git 从命令行提交更改。 您可以让 Jenkins 扫描存储库并获取新的阶段。
对于 Ansible 阶段,我们需要确保在 inventory
文件中的测试主机中有一个条目,并且有一个包含我们要运行的角色的站点 Playbook。
# inventory
iac-tgt.example.com ansible_user=root
# site.yml
---
- hosts: all
roles:
- role: webserver
此阶段的步骤类型是“调用 Ansible Playbook(Invoke an Ansible playbook)”。 填写所有适当的值。 对于任何需要路径的内容,例如 Playbook
,请使用存储库根目录的相对路径,例如 ansible/site.yml
。 您可以导入 SSH 密钥或使用 Ansible Vault 文件进行身份验证。
我们的最后一个阶段是 Testinfra 阶段,它也是一个 shell 脚本。 要从命令行运行 Testinfra 而不调用 Molecule,我们需要确保传递一些变量。 Testinfra 可以使用 Ansible 作为连接后端,因此我们可以使用与之前相同的 inventory 和凭据。
在“Shell 脚本(Shell Script)”框中,添加以下内容:
testinfra --ssh-identity-file=${KEYFILE} --connection=ansible --ansible-inventory=${MOLECULE_INVENTORY_FILE}
${ROLEDIR}/molecule/default/tests/test_default.py
在阶段的“设置(Settings)”中,创建以下环境变量:
MOLECULE_INVENTORY_FILE ansible/inventory
KEYFILE
变量由凭据的变量绑定创建。 这需要在 Jenkinsfile 中完成,因为该步骤的配置尚未在界面中得到支持。 这会将为 Ansible 阶段配置的相同 SSH 密钥作为文件提供给阶段的持续时间。
通过示例存储库中的 Jenkinsfile 和这些步骤,您应该有一个可用的流水线。 希望您不仅了解它的工作原理,而且还了解为什么值得付出努力以与我们的开发人员同事测试应用程序代码更改的方式相同的方式来测试我们的基础设施。 虽然这些示例很简单,但您可以构建一个测试套件,以确保基础设施代码部署一个应用程序代码可以依赖的系统。 本着 DevOps 的精神,您需要与您的开发团队合作来制定这些验收测试。
评论已关闭。