试用Ansible Molecule

首先

我已经尝试使用了Ansible Molecule。这是一个仅记录了在本地运行的内容的页面。

此外,还有以下Getting Started作为官方文档提供。
https://ansible.readthedocs.io/projects/molecule/working/getting_started/getting_started/

尽管没有完全按照上述步骤进行操作,但为了在本地重新创建Ansible Molecule,由于缺乏文档,我进行了多次尝试和错误,所以将其作为备忘录留下来。

假设

在这里,仅供参考,我决定以”ansible-galaxy install nginxinc.nginx”安装的集合的目录布局作为参考来创建。
由于我是初学者并且不熟悉最佳目录实践,所以我借鉴了前人的例子。

$ tree ~/.ansible/roles/nginxinc.nginx
~/.ansible/roles/nginxinc.nginx
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SUPPORT.md
├── defaults
│   └── main
│       ├── amplify.yml
│       ├── bsd.yml
│       ├── logrotate.yml
│       ├── main.yml
│       ├── selinux.yml
│       └── systemd.yml
├── files
│   ├── license
│   └── services
│       ├── nginx.conf.upstart
│       ├── nginx.openrc
│       ├── nginx.override.conf
│       ├── nginx.systemd
│       ├── nginx.sysvinit
│       └── nginx.upstart
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   ├── common
│   │   └── Dockerfile.j2
│   ├── default
│   │   ├── converge.yml
│   │   ├── molecule.yml
│   │   └── verify.yml

(snip)

│   ├── upgrade-plus
│   │   ├── converge.yml
│   │   ├── molecule.yml
│   │   ├── prepare.yml
│   │   └── verify.yml
│   └── version
│       ├── converge.yml
│       ├── molecule.yml
│       └── verify.yml
├── tasks
│   ├── amplify
│   │   ├── install-amplify.yml
│   │   ├── setup-debian.yml
│   │   └── setup-redhat.yml
│   ├── config
│   │   ├── debug-output.yml
│   │   ├── modify-systemd.yml
│   │   └── setup-logrotate.yml
│   ├── keys
│   │   └── setup-keys.yml
│   ├── main.yml
│   ├── modules
│   │   └── install-modules.yml

(snip)

│   ├── prerequisites
│   │   ├── install-dependencies.yml
│   │   ├── prerequisites.yml
│   │   └── setup-selinux.yml
│   └── validate
│       └── validate.yml
├── templates
│   ├── logrotate
│   │   └── nginx.j2
│   ├── selinux
│   │   └── nginx-plus-module.te.j2
│   └── services
│       └── nginx.service.override.conf.j2
└── vars
    └── main.yml

35 directories, 96 files

在设备进行了验证步骤。

只是为了参考,我会附上验证这个步骤的环境信息。

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 22.04.1 LTS
Release:	22.04
Codename:	jammy
$ ansible --version
ansible [core 2.15.2]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/tsuyoshi/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /home/tsuyoshi/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True
$ molecule --version
molecule 6.0.2 using python 3.10 
    ansible:2.15.2
    azure:23.5.0 from molecule_plugins
    containers:23.5.0 from molecule_plugins requiring collections: ansible.posix>=1.3.0 community.docker>=1.9.1 containers.podman>=1.8.1
    default:6.0.2 from molecule
    docker:23.5.0 from molecule_plugins requiring collections: community.docker>=3.0.2 ansible.posix>=1.4.0
    ec2:23.5.0 from molecule_plugins
    gce:23.5.0 from molecule_plugins requiring collections: google.cloud>=1.0.2 community.crypto>=1.8.0
    podman:23.5.0 from molecule_plugins requiring collections: containers.podman>=1.7.0 ansible.posix>=1.3.0
    vagrant:23.5.0 from molecule_plugins

暫時的手順, 直到在手邊運作為止。

首先,生成Ansible集合的模板。
azarashi.utils是一个由命名空间名和集合名拼接而成的字符串。

$ ansible-galaxy collection init azarashi.utils
- Collection azarashi.utils was created successfully
$ tree .
.
└── azarashi
    └── utils
        ├── README.md
        ├── docs
        ├── galaxy.yml
        ├── meta
        │   └── runtime.yml
        ├── plugins
        │   └── README.md
        └── roles

6 directories, 4 files

接下来,我们将生成角色的模板。

$ cd azarashi/utils/roles/
$ ansible-galaxy role init azarashi.utils
- Role azarashi.utils was created successfully
$ tree .
.
└── azarashi.utils
    ├── README.md
    ├── defaults
    │   └── main.yml
    ├── files
    ├── handlers
    │   └── main.yml
    ├── meta
    │   └── main.yml
    ├── tasks
    │   └── main.yml
    ├── templates
    ├── tests
    │   ├── inventory
    │   └── test.yml
    └── vars
        └── main.yml

9 directories, 8 files

在转至 azarashi.utils 后,将 molecule 的模板指定为 “helloworld” 作为一个场景。

对于验证角色的包安装场景,场景名称可能会是 “install”、”uninstall” 或 “update”。

$ cd azarashi.utils/
$ molecule init scenario helloworld
INFO     Initializing new scenario helloworld...

PLAY [Create a new molecule scenario] ******************************************

TASK [Check if destination folder exists] **************************************
changed: [localhost]

TASK [Check if destination folder is empty] ************************************
ok: [localhost]

TASK [Fail if destination folder is not empty] *********************************
skipping: [localhost]

TASK [Expand templates] ********************************************************
changed: [localhost] => (item=molecule/helloworld/create.yml)
changed: [localhost] => (item=molecule/helloworld/converge.yml)
changed: [localhost] => (item=molecule/helloworld/molecule.yml)
changed: [localhost] => (item=molecule/helloworld/destroy.yml)

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

INFO     Initialized scenario in /home/tsuyoshi/test/azarashi/utils/roles/azarashi.utils/molecule/helloworld successfully.

通过执行上面的命令,会生成molecule目录,并在其下生成helloworld或molecule所需的yml文件。

$ tree .
.
├── README.md
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   └── helloworld
│       ├── converge.yml
│       ├── create.yml
│       ├── destroy.yml
│       └── molecule.yml
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

10 directories, 12 files

目前为止,准备工作已经完成。

在”molecule”测试中,默认情况下会在当前目录下查找相对路径为”molecule/default/molecule.yml”的文件。为了指定”helloworld”场景并将搜索路径设置为”molecule/helloworld/molecule.yml”,可以通过使用–scenario-name选项来实现。

$ pwd
/home/tsuyoshi/test/azarashi/utils/roles/azarashi.utils
$ molecule test --scenario-name helloworld
WARNING  The scenario config file ('/home/tsuyoshi/test/azarashi/utils/roles/azarashi.utils/molecule/helloworld/molecule.yml') has been modified since the scenario was created. If recent changes are important, reset the scenario with 'molecule destroy' to clean up created items or 'molecule reset' to clear current configuration.
INFO     helloworld scenario test matrix: dependency, cleanup, destroy, syntax, create, prepare, converge, idempotence, side_effect, verify, cleanup, destroy
INFO     Performing prerun with role_name_check=0...
INFO     Running from /home/tsuyoshi/test/azarashi/utils/roles/azarashi.utils : ansible-galaxy collection install -vvv --force ../..
INFO     Running helloworld > dependency
WARNING  Skipping, missing the requirements file.
WARNING  Skipping, missing the requirements file.
INFO     Running helloworld > cleanup
WARNING  Skipping, cleanup playbook not configured.
INFO     Running helloworld > destroy

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

TASK [Populate instance config] ************************************************
ok: [localhost]

TASK [Dump instance config] ****************************************************
skipping: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

INFO     Running helloworld > syntax

playbook: /home/tsuyoshi/test/azarashi/utils/roles/azarashi.utils/molecule/helloworld/converge.yml
INFO     Running helloworld > create

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

TASK [Populate instance config dict] *******************************************
skipping: [localhost]

TASK [Convert instance config dict to a list] **********************************
skipping: [localhost]

TASK [Dump instance config] ****************************************************
skipping: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0

INFO     Running helloworld > prepare
WARNING  Skipping, prepare playbook not configured.
INFO     Running helloworld > converge

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

TASK [Replace this task with one that validates your content] ******************
ok: [instance] => {
    "msg": "This is the effective test"
}
ok: [molecule-ubuntu] => {
    "msg": "This is the effective test"
}

PLAY RECAP *********************************************************************
instance                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
molecule-ubuntu            : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

INFO     Running helloworld > idempotence

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

TASK [Replace this task with one that validates your content] ******************
ok: [instance] => {
    "msg": "This is the effective test"
}
ok: [molecule-ubuntu] => {
    "msg": "This is the effective test"
}

PLAY RECAP *********************************************************************
instance                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
molecule-ubuntu            : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

INFO     Idempotence completed successfully.
INFO     Running helloworld > side_effect
WARNING  Skipping, side effect playbook not configured.
INFO     Running helloworld > verify
INFO     Running Ansible Verifier
WARNING  Skipping, verify action has no playbook.
INFO     Verifier completed successfully.
INFO     Running helloworld > cleanup
WARNING  Skipping, cleanup playbook not configured.
INFO     Running helloworld > destroy

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

TASK [Populate instance config] ************************************************
ok: [localhost]

TASK [Dump instance config] ****************************************************
skipping: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

INFO     Pruning extra files from scenario ephemeral directory

暂时我已经成功执行了分子测试。
然而,执行的主机本身是本地主机,可能会破坏主机环境。

所以,接下来我们尝试使用Docker镜像来运行测试。

直到使用Docker镜像执行测试的步骤。

为了在Docker镜像中执行测试,请整理molecule子目录下的yml文件。

我参考了下面的官方文件(※1)。

    https://ansible.readthedocs.io/projects/molecule/docker/

为了有意识地从converge.yml中调用,我将在主任务中添加以下任务。

$ cat tasks/main.yml 
---
- name: Task is running from within the role
  ansible.builtin.debug:
    msg: "This is a task from my_role."

用下述内容准备converge.yml文件。(※1)文档中添加调用上述任务的条目。

$ cat molecule/helloworld/converge.yml 
- name: Fail if molecule group is missing
  hosts: localhost
  tasks:
    - name: Print some info
      ansible.builtin.debug:
        msg: "{{ groups }}"

    - name: Assert group existence
      ansible.builtin.assert:
        that: "'molecule' in groups"
        fail_msg: |
          molecule group was not found inside inventory groups: {{ groups }}

- name: Converge
  hosts: molecule
  # We disable gather facts because it would fail due to our container not
  # having python installed. This will not prevent use from running 'raw'
  # commands. Most molecule users are expected to use containers that already
  # have python installed in order to avoid notable delays installing it.
  gather_facts: false
  tasks:
    - name: Check uname
      ansible.builtin.raw: uname -a
      register: result
      changed_when: false

    - name: Print some info
      ansible.builtin.assert:
        that: result.stdout | regex_search("^Linux")

    - name: Azarashi's Testing role
      ansible.builtin.include_role:
        name: azarashi.utils
        tasks_from: main.yml

接下来,准备create.yml,destroy.yml,molecule.yml和requirements.yml。它们的内容与(※1)相同。

$ cat molecule/helloworld/create.yml 
- name: Create
  hosts: localhost
  gather_facts: false
  vars:
    molecule_inventory:
      all:
        hosts: {}
        molecule: {}
  tasks:
    - name: Create a container
      community.docker.docker_container:
        name: "{{ item.name }}"
        image: "{{ item.image }}"
        state: started
        command: sleep 1d
        log_driver: json-file
      register: result
      loop: "{{ molecule_yml.platforms }}"

    - name: Print some info
      ansible.builtin.debug:
        msg: "{{ result.results }}"

    - name: Fail if container is not running
      when: >
        item.container.State.ExitCode != 0 or
        not item.container.State.Running
      ansible.builtin.include_tasks:
        file: tasks/create-fail.yml
      loop: "{{ result.results }}"
      loop_control:
        label: "{{ item.container.Name }}"

    - name: Add container to molecule_inventory
      vars:
        inventory_partial_yaml: |
          all:
            children:
              molecule:
                hosts:
                  "{{ item.name }}":
                    ansible_connection: community.docker.docker
      ansible.builtin.set_fact:
        molecule_inventory: >
          {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml) }}
      loop: "{{ molecule_yml.platforms }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Dump molecule_inventory
      ansible.builtin.copy:
        content: |
          {{ molecule_inventory | to_yaml }}
        dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        mode: 0600

    - name: Force inventory refresh
      ansible.builtin.meta: refresh_inventory

    - name: Fail if molecule group is missing
      ansible.builtin.assert:
        that: "'molecule' in groups"
        fail_msg: |
          molecule group was not found inside inventory groups: {{ groups }}
      run_once: true # noqa: run-once[task]

# we want to avoid errors like "Failed to create temporary directory"
- name: Validate that inventory was refreshed
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Check uname
      ansible.builtin.raw: uname -a
      register: result
      changed_when: false

    - name: Display uname info
      ansible.builtin.debug:
        msg: "{{ result.stdout }}"
$ cat molecule/helloworld/destroy.yml 
- name: Destroy molecule containers
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Stop and remove container
      delegate_to: localhost
      community.docker.docker_container:
        name: "{{ inventory_hostname }}"
        state: absent
        auto_remove: true

- name: Remove dynamic molecule inventory
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Remove dynamic inventory file
      ansible.builtin.file:
        path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        state: absent
$ cat molecule/helloworld/molecule.yml 
dependency:
  name: galaxy
  options:
    requirements-file: requirements.yml
platforms:
  - name: molecule-ubuntu
    image: ubuntu:18.04
$ cat molecule/helloworld/reqirements.yml 
collections:
  - community.docker

因为前面的准备工作已经完成,所以我们现在进行执行,但是需要事先注意一下是否存在docker镜像。

$ sudo docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$ molecule list
INFO     Running helloworld > list
                  ╷             ╷                  ╷               ╷         ╷            
  Instance Name   │ Driver Name │ Provisioner Name │ Scenario Name │ Created │ Converged  
╶─────────────────┼─────────────┼──────────────────┼───────────────┼─────────┼───────────╴
  molecule-ubuntu │ default     │ ansible          │ helloworld    │ false   │ false      
                  ╵             ╵                  ╵               ╵         ╵         

为了执行molecule的测试,通过以下命令进行操作。
由于没有附加destroy=never,生成的docker容器将会被删除,所以我们故意附加了该选项。

$ molecule test --scenario-name helloworld --destroy=never
INFO     helloworld scenario test matrix: dependency, cleanup, destroy, syntax, create, prepare, converge, idempotence, side_effect, verify, cleanup, destroy
INFO     Performing prerun with role_name_check=0...
INFO     Running from /home/tsuyoshi/test/azarashi/utils/roles/azarashi.utils : ansible-galaxy collection install -vvv --force ../..
INFO     Running helloworld > dependency
WARNING  Skipping, missing the requirements file.
WARNING  Skipping, missing the requirements file.
INFO     Running helloworld > cleanup
WARNING  Skipping, cleanup playbook not configured.
INFO     Running helloworld > destroy
WARNING  Skipping, '--destroy=never' requested.
INFO     Running helloworld > syntax
[WARNING]: Could not match supplied host pattern, ignoring: molecule

playbook: /home/tsuyoshi/test/azarashi/utils/roles/azarashi.utils/molecule/helloworld/converge.yml
INFO     Running helloworld > create

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

TASK [Create a container] ******************************************************
changed: [localhost] => (item={'image': 'ubuntu:18.04', 'name': 'molecule-ubuntu'})

TASK [Print some info] *********************************************************
ok: [localhost] => {
    "msg": [
        {
            "ansible_loop_var": "item",

(snip)

TASK [Fail if container is not running] ****************************************
skipping: [localhost] => (item=/molecule-ubuntu) 
skipping: [localhost]

TASK [Add container to molecule_inventory] *************************************
ok: [localhost] => (item=molecule-ubuntu)

TASK [Dump molecule_inventory] *************************************************
changed: [localhost]

TASK [Force inventory refresh] *************************************************

TASK [Fail if molecule group is missing] ***************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

PLAY [Validate that inventory was refreshed] ***********************************

TASK [Check uname] *************************************************************
ok: [molecule-ubuntu]

TASK [Display uname info] ******************************************************
ok: [molecule-ubuntu] => {
    "msg": "Linux af07e92dba0a 5.15.0-43-generic #46-Ubuntu SMP Tue Jul 12 10:30:17 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux\n"
}

PLAY RECAP *********************************************************************
localhost                  : ok=5    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
molecule-ubuntu            : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

INFO     Running helloworld > prepare
WARNING  Skipping, prepare playbook not configured.
INFO     Running helloworld > converge

PLAY [Fail if molecule group is missing] ***************************************

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

TASK [Print some info] *********************************************************
ok: [localhost] => {
    "msg": {
        "all": [
            "molecule-ubuntu"
        ],
        "molecule": [
            "molecule-ubuntu"
        ],
        "ungrouped": []
    }
}

TASK [Assert group existence] **************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

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

TASK [Check uname] *************************************************************
ok: [molecule-ubuntu]

TASK [Print some info] *********************************************************
ok: [molecule-ubuntu] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Azarashi's Testing role] *************************************************

TASK [azarashi.utils : Task is running from within the role] *******************
ok: [molecule-ubuntu] => {
    "msg": "This is a task from my_role."
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
molecule-ubuntu            : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

INFO     Running helloworld > idempotence

PLAY [Fail if molecule group is missing] ***************************************

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

TASK [Print some info] *********************************************************
ok: [localhost] => {
    "msg": {
        "all": [
            "molecule-ubuntu"
        ],
        "molecule": [
            "molecule-ubuntu"
        ],
        "ungrouped": []
    }
}

TASK [Assert group existence] **************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

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

TASK [Check uname] *************************************************************
ok: [molecule-ubuntu]

TASK [Print some info] *********************************************************
ok: [molecule-ubuntu] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Azarashi's Testing role] *************************************************

TASK [azarashi.utils : Task is running from within the role] *******************
ok: [molecule-ubuntu] => {
    "msg": "This is a task from my_role."
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
molecule-ubuntu            : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

INFO     Idempotence completed successfully.
INFO     Running helloworld > side_effect
WARNING  Skipping, side effect playbook not configured.
INFO     Running helloworld > verify
INFO     Running Ansible Verifier
WARNING  Skipping, verify action has no playbook.
INFO     Verifier completed successfully.
INFO     Running helloworld > cleanup
WARNING  Skipping, cleanup playbook not configured.
INFO     Running helloworld > destroy
WARNING  Skipping, '--destroy=never' requested.

您可以使用以下方式将该句子翻译成中文:

在收敛时,我们可以确认已经执行了任务主文件中有意设置的任务。此外,Playbook将会执行两次,包括确认幂等性。

最后,我们要确认通过分子测试的执行,已生成了 Docker 映像。

$ sudo docker ps -a
CONTAINER ID   IMAGE          COMMAND      CREATED              STATUS              PORTS     NAMES
af07e92dba0a   ubuntu:18.04   "sleep 1d"   About a minute ago   Up About a minute             molecule-ubuntu
$ molecule list
INFO     Running helloworld > list
                  ╷             ╷                  ╷               ╷         ╷            
  Instance Name   │ Driver Name │ Provisioner Name │ Scenario Name │ Created │ Converged  
╶─────────────────┼─────────────┼──────────────────┼───────────────┼─────────┼───────────╴
  molecule-ubuntu │ default     │ ansible          │ helloworld    │ true    │ true       
                  ╵             ╵                  ╵               ╵         ╵         

总结

我已经记录了在本地主机或Docker镜像上运行Ansible Molecule的步骤。

广告
将在 10 秒后关闭
bannerAds