说明:如何在生产前测试ansible角色并找出问题

您好!

我在酒店预订服务部门担任 DevOps 工程师。 奥斯特罗沃克。 在这篇文章中,我想谈谈我们在测试 ansible 角色方面的经验。

在 Ostrovok.ru,我们使用 ansible 作为配置管理器。 最近,我们需要测试角色,但事实证明,没有那么多工具可以实现这一点 - 最流行的也许是 Molecule 框架,所以我们决定使用它。 但事实证明,他的文档对许多陷阱只字不提。 我们找不到足够详细的俄语手册,因此我们决定写这篇文章。

说明:如何在生产前测试ansible角色并找出问题

分子

分子 - 帮助测试 ansible 角色的框架。

简化描述:分子在您指定的平台(云、虚拟机、容器)上创建一个实例;有关更多详细信息,请参阅 驱动器),在其上运行您的角色,然后运行测试并删除该实例。 如果其中一个步骤失败,分子会通知您。

现在更多。

有些理论

考虑分子的两个关键实体:场景和驱动程序。

EventXtra XNUMX大解决方案

该脚本包含对执行内容、执行地点、执行方式以及执行顺序的描述。 一个角色可以有多个脚本,每个脚本都是路径上的一个目录 <role>/molecule/<scenario>,其中包含测试所需操作的描述。 必须包含脚本 default,如果您使用 Molecule 初始化角色,则会自动创建该角色。 以下脚本的名称由您决定。

脚本中的测试操作序列称为 矩阵,默认情况下它是:

(步骤标记为 ?,如果用户不指定则默认跳过)

  • lint - 运行短绒。 默认情况下使用 yamllint и flake8,
  • destroy - 删除上次启动 Molecule 时的实例(如果有),
  • dependency? — 安装测试角色的 ansible 依赖项,
  • syntax - 使用检查角色的语法 ansible-playbook --syntax-check,
  • create - 创建一个实例,
  • prepare? ——实例的准备; 例如检查/安装 python2
  • converge — 发布正在测试的剧本,
  • idempotence - 重新启动剧本以进行幂等性测试,
  • side_effect? - 与角色不直接相关,但测试所必需的操作,
  • verify - 使用以下命令运行结果配置的测试 testinfra(默认) /goss/inspec,
  • cleanup? -(在新版本中)-粗略地说,“清理”受分子影响的外部基础设施,
  • destroy - 删除实例。

此顺序涵盖了大多数情况,但可以根据需要进行更改。

上述每个步骤都可以单独运行 molecule <command>。 但应该理解的是,对于每个这样的 cli 命令可能有其自己的操作序列,您可以通过执行来找到 molecule matrix <command>。 例如,运行命令时 converge (运行被测试的剧本),将执行以下操作:

$ molecule matrix converge
...
└── default         # название сценария
    ├── dependency  # установка зависимостей
    ├── create      # создание инстанса
    ├── prepare     # преднастройка инстанса
    └── converge    # прогон плейбука

可以编辑这些操作的顺序。 如果列表中的某些内容已经完成,它将被跳过。 Molecule 存储在目录中的当前状态以及实例的配置 $TMPDIR/molecule/<role>/<scenario>.

添加步骤 ? 您可以用ansible-playbook格式描述所需的操作,并按照步骤命名文件名: prepare.yml/side_effect.yml。 预期这些文件分子将位于脚本文件夹中。

驱动器

驱动程序是创建测试实例的实体。
Molecule 已准备好模板的标准驱动程序列表如下:Azure、Docker、EC2、GCE、LXC、LXD、OpenStack、Vagrant、Delegate。

大多数情况下,模板是文件 create.yml и destroy.yml 在分别描述实例的创建和删除的脚本文件夹中。
Docker 和 Vagrant 是例外,因为与它们的模块的交互可以在没有上述文件的情况下进行。

值得强调的是委托驱动程序,因为如果在文件中使用它来创建和删除实例,则仅描述与实例配置相关的工作,其余部分应由工程师描述。

默认驱动程序是 Docker。

现在让我们继续练习并考虑其中的进一步功能。

入门

作为“hello world”,我们来测试一个简单的 nginx 安装角色。 让我们选择 docker 作为驱动程序 - 我想大多数人都安装了它(请记住 docker 是默认驱动程序)。

准备 virtualenv 并安装在其中 molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule установит ansible как зависимость; docker для драйвера

下一步是初始化新角色。
使用以下命令执行新角色以及新脚本的初始化 molecule init <params>:

> molecule init role -r nginx
--> Initializing new role nginx...
Initialized role in <path>/nginx successfully.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars

6 directories, 1 file

结果是一个典型的 ansible 角色。 此外,与 CLI 分子的所有交互都是从角色的根进行的。

我们看看角色目录里有什么:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Jinja-шаблон для Dockerfile
├── INSTALL.rst.   # Немного информации об установке зависимостей сценария
├── molecule.yml   # Файл конфигурации
├── playbook.yml   # Плейбук запуска роли
└── tests          # Директория с тестами стадии verify
    └── test_default.py

1 directory, 6 files

我们来分析一下配置 molecule/default/molecule.yml (仅替换 docker 镜像):

---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: instance
    image: centos:7
provisioner:
  name: ansible
  lint:
    name: ansible-lint
scenario:
  name: default
verifier:
  name: testinfra
  lint:
    name: flake8

依赖

本节描述依赖项的来源。

可能的选择: 星系, 镀金, 壳。

Shell 只是一个命令 shell,用于在 Galaxy 和 gilt 不能满足您的需求的情况下使用。

我不会在这里停留太久,描述已经足够了 文件资料.

司机

司机的姓名。 我们的是码头工人。

皮棉

linter 是 yamllint。

这部分配置中的有用选项是能够为 yamllint 指定配置文件、转发环境变量或禁用 linter:

lint:
  name: yamllint
  options:
    config-file: foo/bar
  env:
    FOO: bar
  enabled: False

防水台 高跟鞋

描述实例的配置。
在使用 docker 作为驱动程序的情况下,Molecule 会在此部分上进行迭代,并且列表中的每个元素都可在 Dockerfile.j2 作为变量 item.

如果驾驶员需要 create.yml и destroy.yml,该部分在其中可用 molecule_yml.platforms,并且这些文件中已经描述了它的迭代。

由于 Molecule 向 ansible 模块提供实例控制,因此还应该在那里查找可能的设置列表。 以docker为例,使用模块 docker_container_module。 其他驱动中使用了哪些模块可以在 文件资料.

以及各种驱动程序的使用示例可以找到 在分子本身的测试中.

在这里替换 分:7Ubuntu的.

供应商

“供应商”- 管理实例的实体。 就 Molecule 而言,这是 ansible,没有计划对其他人的支持,因此本节可以称为 ansible 扩展配置,但需要注意。
这里你可以指定很多东西,我将强调我认为的要点:

  • 剧本:您可以指定在某些阶段应使用哪些剧本。

provisioner:
  name: ansible
  playbooks:
    create: create.yml
    destroy: ../default/destroy.yml
    converge: playbook.yml
    side_effect: side_effect.yml
    cleanup: cleanup.yml

provisioner:
  name: ansible
  config_options:
    defaults:
      fact_caching: jsonfile
    ssh_connection:
      scp_if_ssh: True

  • 连接选项: 选项 连接

provisioner:
  name: ansible  
  connection_options:
    ansible_ssh_common_args: "-o 'UserKnownHostsFile=/dev/null' -o 'ForwardAgent=yes'"

  • 选项:Ansible 选项和环境变量

provisioner:
  name: ansible  
  options:
    vvv: true
    diff: true
  env:
    FOO: BAR

脚本

脚本序列的名称和描述。
您可以通过添加键来更改任何命令的默认操作矩阵 <command>_sequence 并通过定义我们需要的步骤列表作为它的值。
假设我们想要更改运行 playbook run 命令时的操作顺序: molecule converge

# изначально:
# - dependency
# - create
# - prepare
# - converge
scenario:
  name: default
  converge_sequence:
    - create
    - converge

验证

建立一个测试框架和一个 linter。 默认的 linter 是 testinfra и flake8。 可能的选项与上面相同:

verifier:
  name: testinfra
  additional_files_or_dirs:
    - ../path/to/test_1.py
    - ../path/to/test_2.py
    - ../path/to/directory/*
  options:
    n: 1
  enabled: False
  env:
    FOO: bar
  lint:
    name: flake8
    options:
      benchmark: True
    enabled: False
    env:
      FOO: bar

让我们回到我们的角色。 我们来编辑文件 tasks/main.yml 对于这种:

---
- name: Install nginx
  apt:
    name: nginx
    state: present

- name: Start nginx
  service:
    name: nginx
    state: started

并将测试添加到 molecule/default/tests/test_default.py

def test_nginx_is_installed(host):
    nginx = host.package("nginx")
    assert nginx.is_installed

def test_nginx_running_and_enabled(host):
    nginx = host.service("nginx")
    assert nginx.is_running
    assert nginx.is_enabled

def test_nginx_config(host):
    host.run("nginx -t")

完成后,剩下的只是运行(从角色的根源,让我提醒你):

> molecule test

扰流板下方长排气:

--> Validating schema <path>/nginx/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix

└── default
    ├── lint
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    └── destroy

--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in <path>/nginx/...
Lint completed successfully.
--> Executing Flake8 on files found in <path>/nginx/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on <path>/nginx/molecule/default/playbook.yml...
Lint completed successfully.
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) deletion to complete] *******************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Delete docker network(s)] ************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=1    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'syntax'

    playbook: <path>/nginx/molecule/default/playbook.yml

--> Scenario: 'default'
--> Action: 'create'

    PLAY [Create] ******************************************************************

    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None)

    TASK [Create Dockerfiles from image names] *************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Build an Ansible compatible image] ***************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Create docker network(s)] ************************************************

    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) creation to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=4    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'

    PLAY [Converge] ****************************************************************

    TASK [Gathering Facts] *********************************************************
    ok: [instance]

    TASK [nginx : Install nginx] ***************************************************
    changed: [instance]

    TASK [nginx : Start nginx] *****************************************************
    changed: [instance]

    PLAY RECAP *********************************************************************
    instance                   : ok=3    changed=2    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in <path>/nginx/molecule/default/tests/...
    ============================= test session starts ==============================
    platform darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
    rootdir: <path>/nginx/molecule/default, inifile:
    plugins: testinfra-1.16.0
collected 4 items

    tests/test_default.py ....                                               [100%]

    ========================== 4 passed in 27.23 seconds ===========================
Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) deletion to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Delete docker network(s)] ************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=2    unreachable=0    failed=0

我们的简单角色经过测试没有问题。
值得记住的是,如果工作过程中出现问题 molecule test,那么如果您没有更改默认序列,则 Molecule 将删除该实例。

以下命令对于调试很有用:

> molecule --debug <command> # debug info. При обычном запуске Молекула скрывает логи.
> molecule converge          # Оставляет инстанс после прогона тестируемой роли.
> molecule login             # Зайти в созданный инстанс.
> molecule --help            # Полный список команд.

现有角色

将新脚本添加到现有角色是 从角色目录 使用以下命令:

# полный список доступных параметров
> molecule init scenarion --help
# создание нового сценария
> molecule init scenario -r <role_name> -s <scenario_name>

如果这是角色中的第一个场景,则参数 -s 可以省略,因为它将创建一个脚本 default.

结论

正如您所看到的,Molecule 并不是很复杂,通过使用您自己的模板,部署新脚本可以简化为在实例创建和删除 playbook 中编辑变量。 该分子与 CI 系统无缝集成,使您可以通过减少手动测试剧本的时间来提高开发速度。

感谢您的关注。 如果您有测试 ansible 角色的经验,并且与 Molecule 无关,请在评论中告诉我们!

来源: habr.com

添加评论