试用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的步骤。