1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Ansible Moleculeにてmolecule-vagrantを使用せずにVirtualBox上のVMを利用する例

Last updated at Posted at 2021-11-20

備忘

課題

Ansible MoleculeにてVirtualBox上のVMを利用する場合、molecule-vagrantが提供されている。
しかしサポートされる環境が少なく今後のサポートも不安が残る。
例えばfedora上のVirtualBoxはサポートされていない。

ソリューション

delegatedドライバーを使用してvagrantコマンドを実行することで制御を行う

環境

  • Fedora Linux 35 (Workstation Edition)
  • Molecule 3.5.2
  • Ansible 2.11.6
  • Vagrant 2.2.19
  • VirtualBox

構成例

molecule定義テンプレートを含むロールテンプレートを作成

$ molecule init role delegated_vagrant
...
$ cd delegated_vagrant
$ tree .
.
├── README.md
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   └── default        # ここにvagrant定義ファイルであるVagrantfileを追加
│       ├── INSTALL.rst
│       ├── converge.yml
│       ├── create.yml      # <= vm作成ロジックを追記
│       ├── destroy.yml   # <= vm削除ロジックを追記
│       ├── molecule.yml    # platforms の項を Vagrantfileの定義名と合わせる
│       └── verify.yml
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

meta/main.ymlを修正

meta/main.yml
galaxy_info:
  namespace: my_galaxy_namespace  # 適当な英小文字、数字、アンダーバーからなる文字列で追記。指定しないとauthorが使用され、スペースが許されないとエラーとなる。
  author: your name
  description: your role description
  company: your company (optional)
...

molecule.yml を編集

molecule/default/molecule.yml
---
dependency:
  name: galaxy
driver:
  name: delegated
platforms:
- name: instance1  # VagrantFile の config.vm.define の値に合わせる
- name: instance2
provisioner:
  name: ansible
verifier:
  name: ansible

Vagrantfile を配置

ここでは2つのvmを定義

molecule/default/Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  config.vm.box = "generic/rhel8"
  config.vm.box_version = "3.0.20"  # RHEL8.2

  config.vm.define "instance1" do |ins|  # molecule.yml における platforms 要素の name と一致させる
    ins.vm.hostname = "instance1"
    ins.vm.network "private_network", ip: "192.168.56.10"
    ins.vm.provider "virtualbox" do |vb|
      vb.memory = 2048
    end
  end

  config.vm.define "instance2" do |ins|  # molecule.yml における platforms 要素の name と一致させる
    ins.vm.hostname = "instance2"
    ins.vm.network "private_network", ip: "192.168.56.11"
    ins.vm.provider "virtualbox" do |vb|
      vb.memory = 1024
    end
  end

end

molecule.yml に全ての情報を記載してVagrantfileをテンプレートから作る方法もあり得る。

このVagrantfileが存在するディレクトリーで以下のようなことが可能。

$ cd molecule/default

$ ls Vagrantfile 
Vagrantfile

vmを構成して起動

$ vagrant up
Bringing machine 'instance1' up with 'virtualbox' provider...
Bringing machine 'instance2' up with 'virtualbox' provider...
==> instance1: Importing base box 'generic/rhel8'...
==> instance1: Matching MAC address for NAT networking...
==> instance1: Checking if box 'generic/rhel8' version '3.0.20' is up to date...
...

vmの状態表示

$ vagrant status
Current machine states:

instance1                 running (virtualbox)
instance2                 running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

vmへのログイン

$ vagrant ssh instance1
[vagrant@instance1 ~]$ head /etc/os-release 
NAME="Red Hat Enterprise Linux"
VERSION="8.2 (Ootpa)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="8.2"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Red Hat Enterprise Linux 8.2 (Ootpa)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:8.2:GA"
HOME_URL="https://www.redhat.com/"
[vagrant@instance1 ~]$ logout
Connection to 127.0.0.1 closed.

vmに対する接続情報を表示

$ vagrant ssh-config  
Host instance1
  HostName 127.0.0.1
  User vagrant
  Port 2205
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance1/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

Host instance2
  HostName 127.0.0.1
  User vagrant
  Port 2206
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance2/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

簡易YAML化ロジック例

パス名に空白などが入っていない前提

$ vagrant ssh-config | sed '/^$/d' | awk '$1 == "Host" {print "- "$1": "$2} $1 != "Host" {print "  "$1": "$2}'
- Host: instance1
  HostName: 127.0.0.1
  User: vagrant
  Port: 2205
  UserKnownHostsFile: /dev/null
  StrictHostKeyChecking: no
  PasswordAuthentication: no
  IdentityFile: /home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance1/virtualbox/private_key
  IdentitiesOnly: yes
  LogLevel: FATAL
- Host: instance2
  HostName: 127.0.0.1
  User: vagrant
  Port: 2206
  UserKnownHostsFile: /dev/null
  StrictHostKeyChecking: no
  PasswordAuthentication: no
  IdentityFile: /home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance2/virtualbox/private_key
  IdentitiesOnly: yes
  LogLevel: FATAL

vmを削除

$ vagrant destroy -f  
==> instance2: Forcing shutdown of VM...
==> instance2: Destroying VM and associated drives...
==> instance1: Forcing shutdown of VM...
==> instance1: Destroying VM and associated drives...

元のディレクトリーに戻る

$ cd -                

vmを作成するplaybookのテンプレートにvagrant upロジックを追記

molecule/default/create.yml
---
- name: Create
  hosts: localhost
  connection: local
  gather_facts: false
  no_log: "{{ molecule_no_log }}"
  tasks:

    - name: vagrant up                     # vmを作成、起動
      shell: vagrant up

    - name: vagrant ssh-config             # 作成vmへの接続情報をYAML化
      shell: >-
        vagrant ssh-config | sed '/^$/d' | awk '$1 == "Host" {print "- "$1": "$2} $1 != "Host" {print "  "$1": "$2}'
      register: r

    - name: Populate instance config dict  # 以降で作成vmへの接続情報をmoleculeに連携
      loop: "{{ r.stdout|from_yaml }}"
      set_fact:
        instance_conf_dict:
          instance: "{{ item.Host }}"
          address: "{{ item.HostName }}"
          user: "{{ item.User }}"
          port: "{{ item.Port }}"
          identity_file: "{{ item.IdentityFile }}"
      register: instance_config_dict

    - name: Convert instance config dict to a list
      set_fact:
        instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}"

    - name: Dump instance config
      copy:
        content: |
          # Molecule managed

          {{ instance_conf | to_json | from_json | to_yaml }}
        dest: "{{ molecule_instance_config }}"
        mode: 0600

vmを削除するplaybookのテンプレートにvagrant destroy -fロジックを追記

molecule/default/destroy.yml
---
- name: Destroy
  hosts: localhost
  connection: local
  gather_facts: false
  no_log: "{{ molecule_no_log }}"
  tasks:
    # Developer must implement.

    - name: vagrant destroy -f
      shell: vagrant destroy -f

    # Mandatory configuration for Molecule to function.

    - name: Populate instance config
      set_fact:
        instance_conf: {}

    - name: Dump instance config
      copy:
        content: |
          # Molecule managed

          {{ instance_conf | to_json | from_json | to_yaml }}
        dest: "{{ molecule_instance_config }}"
        mode: 0600
      when: server.changed | default(false) | bool

tasks/main.ymlの例

適当に編集

tasks/main.yml
---
- debug:
    var: ansible_distribution
- debug:
    var: ansible_distribution_version

動作確認

初期ステータス

$ molecule list
INFO     Running default > list
                ╷             ╷                  ╷               ╷         ╷            
  Instance Name │ Driver Name │ Provisioner Name │ Scenario Name │ Created │ Converged  
╶───────────────┼─────────────┼──────────────────┼───────────────┼─────────┼───────────╴
  instance1     │ delegated   │ ansible          │ default       │ false   │ false      
  instance2     │ delegated   │ ansible          │ default       │ false   │ false      
                ╵             ╵                  ╵               ╵         ╵            

インスタンス作成

$ molecule create 
...

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

TASK [vagrant up] **************************************************************
changed: [localhost]

TASK [vagrant ssh-config] ******************************************************
changed: [localhost]

TASK [Populate instance config dict] *******************************************
ok: [localhost] => (item={'Host': 'instance1', 'HostName': '127.0.0.1', 'User': 'vagrant', 'Port': 2205, 'UserKnownHostsFile': '/dev/null', 'StrictHostKeyChecking': False, 'PasswordAuthentication': False, 'IdentityFile': '/home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance1/virtualbox/private_key', 'IdentitiesOnly': True, 'LogLevel': 'FATAL'})
ok: [localhost] => (item={'Host': 'instance2', 'HostName': '127.0.0.1', 'User': 'vagrant', 'Port': 2206, 'UserKnownHostsFile': '/dev/null', 'StrictHostKeyChecking': False, 'PasswordAuthentication': False, 'IdentityFile': '/home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance2/virtualbox/private_key', 'IdentitiesOnly': True, 'LogLevel': 'FATAL'})

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

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

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

INFO     Running default > prepare
WARNING  Skipping, prepare playbook not configured.

立ち上げたインスタンスに構成変更playbook(converge.yml)を適用

この例ではdebugモジュールでOSの種類と番号を表示しているのみ。

$ molecule converge 
...

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

TASK [Gathering Facts] *********************************************************
ok: [instance2]
ok: [instance1]

TASK [Include delegated_vagrant] ***********************************************

TASK [delegated_vagrant : debug] ***********************************************
ok: [instance1] => {
    "ansible_distribution": "RedHat"
}
ok: [instance2] => {
    "ansible_distribution": "RedHat"
}

TASK [delegated_vagrant : debug] ***********************************************
ok: [instance1] => {
    "ansible_distribution_version": "8.2"
}
ok: [instance2] => {
    "ansible_distribution_version": "8.2"
}

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

moleculeが認識している状態を表示

$ molecule list
INFO     Running default > list
                ╷             ╷                  ╷               ╷         ╷            
  Instance Name │ Driver Name │ Provisioner Name │ Scenario Name │ Created │ Converged  
╶───────────────┼─────────────┼──────────────────┼───────────────┼─────────┼───────────╴
  instance1     │ delegated   │ ansible          │ default       │ true    │ true       
  instance2     │ delegated   │ ansible          │ default       │ true    │ true       
                ╵             ╵                  ╵               ╵         ╵         

vmにログイン

$ molecule login -h instance1
INFO     Running default > login
Last login: Sat Nov 20 17:56:00 2021 from 10.0.2.2
[vagrant@instance1 ~]$ hostname
instance1
[vagrant@instance1 ~]$ head -2 /etc/os-release 
NAME="Red Hat Enterprise Linux"
VERSION="8.2 (Ootpa)"
[vagrant@instance1 ~]$ logout
Connection to 127.0.0.1 closed.

vmの削除

$ molecule destroy 
...

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

TASK [vagrant destroy -f] ******************************************************
changed: [localhost]

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

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

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

INFO     Pruning extra files from scenario ephemeral directory
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?