Shirone's Blog

Managing VMs with Ansible

We got our VMs with Proxmox VE last time, and now I hope to easily manage them. For example, if I want to setting up kubernetes cluster, there are lots of dependencies need to be install, and many many variables that need to manage. So we need a tool to help us manage the VMs. Ansible will be a good choice. Here also comes an idea called infrastructure as code (IaC). We can write the code, usually configuration files to describe the infrastructure we want, and then use the code to create the infrastructure. IaC can be declarative or imperative. Declarative IaC is like the configuration files of kubernetes, we describe the infrastructure we want, and then the tool will create the infrastructure for us. For imperative, we write the code to describe the steps to create the infrastructure, and then the tool will execute the steps for us. Ansible is both declarative and imperative.

Install Ansible

There are some main concepts in Ansible:

  • Inventory: the hosts, or simply files that Ansible manage
  • Playbook: the code that Ansible execute
  • Managed node: the hosts that Ansible manage
  • Control node: the host that Ansible run on, for example, I’m using my MacBook to manage the VMs, so my MacBook is the control node, and the VMs are the managed nodes.

So what I only need to do is to install Ansible on my MacBook, and then write the config files on my MacBook to manage the VMs. Quite simple, right?

brew install ansible

Done.

Inventory

The inventory is the hosts that Ansible manage. We can write the inventory in a file, or in a directory. I prefer to use a new directory, since I have no idea about where’s the default inventory of Ansible goes on ARM Macs. On linux it’s usually /etc/ansible/hosts.

So, in the directory, we can create a file, pve.yaml that describing the managed nodes. For me, it is the codes below:

pve_vm:
  hosts:
    vm03:
      ansible_host: 192.168.0.70
      ansible_user: vm3
    vm02:
      ansible_host: 192.168.0.174
      ansible_user: vm2
    vm01:
      ansible_host: 192.168.0.108
      ansible_user: vm1

So pve_vm is the group, and vm01, vm02, vm03 are the hosts. We test if the inventory is working by running the command below:

$ ansible -i pve.yaml pve_vm --list
  hosts (3):
    vm03
    vm02
    vm01

It works pretty well. Then test if we can connect to the VMs:

$ ansible -i pve.yaml pve_vm -m ping
vm02 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
vm01 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
vm03 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Beautiful. Now we can write a playbook to installing softwares on the VMs. Lets try a simple one first, without any installation at this stage. So we create a file called install.yaml:

- name: My first play
  hosts: pve_vm
  tasks:
   - name: Ping my hosts
     ansible.builtin.ping:

This playbook will actually performs the same as what we done just now, sending a ping request to the vms.

$ ansible-playbook book.yaml -i pve.yaml
PLAY [My first play] *********************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************
- name: My first play
ok: [vm01]
ok: [vm02]
ok: [vm03]

TASK [Ping my hosts] *********************************************************************************************************************************
ok: [vm01]
ok: [vm03]
ok: [vm02]

PLAY RECAP *******************************************************************************************************************************************
vm01                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
vm02                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
vm03                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Install softwares

My vms are running ubuntu, so we actually need to use apt to install softwares. But running apt requires root permission, so we need to use become to run the command as root. So we need to add become: true to the playbook.

But this is not enough, we need to tell Ansible the password of the root user. Yes we can directly write the password in the playbook or the host file, but it is not a good idea since it is not secure. So we can use ansible-vault to encrypt the password. First we need to create a vault file:

$ ansible-vault create vault.yaml
New Vault password:
Confirm New Vault password:

Enter your password for this vault, remember it. Then write the password of the vms in the file. The format of the vault you enter should be same as a normal yaml file. For example, I have three vms, so I write the password of the root user of each vm in the file:

vm1_pass: vm1_pass
vm2_pass: vm2_pass
vm3_pass: vm3_pass

Then, quit the editor, you can have a look on the yaml vault file again using cat. It is actually encrypted!

Modified the hosts file, let ansible knows what is the password of the root user for each vm:

pve_vm:
  hosts:
    vm03:
      ansible_host: 192.168.0.70
      ansible_user: vm3
      ansible_sudo_pass: "{{vm3_pass}}"
    vm02:
      ansible_host: 192.168.0.174
      ansible_user: vm2
      ansible_sudo_pass: "{{vm2_pass}}"
    vm01:
      ansible_host: 192.168.0.108
      ansible_user: vm1
      ansible_sudo_pass: "{{vm1_pass}}"

After that, we need to add the vault file to the playbook, add the vault to the playbook, and add the software installation task.

- name: My first play
  hosts: pve_vm
  become: true
  vars_files:
    - vault.yaml
  tasks:
   - name: Ping my hosts
     ansible.builtin.ping:
   - name: Prepare software
     apt:
       name: docker
       state: present

The state in a task can be present or absent, which means install or uninstall. It is kind of a declarative state. You will figure out that when you run the note book once.

$ ansible-playbook book.yaml --ask-vault-password -i pve.yaml
Vault password:

PLAY [My first play] *********************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************
ok: [vm02]
ok: [vm01]
ok: [vm03]

TASK [Ping my hosts] *********************************************************************************************************************************
ok: [vm01]
ok: [vm03]
ok: [vm02]

TASK [Prepare software] ******************************************************************************************************************************
changed: [vm01]
changed: [vm03]
changed: [vm02]

PLAY RECAP *******************************************************************************************************************************************
vm01                       : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
vm02                       : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
vm03                       : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

It tells us the prepare software task “Changed”. Why? Because this is a declarative state, it will check if the software is installed or not, if not, it will install it. Previously we don’t have docker installed on these servers, so we got a “changed” result. If we run the playbook again, we will get a “ok” result.

$ ansible-playbook book.yaml --ask-vault-password -i pve.yaml
Vault password:

PLAY [My first play] *********************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************
ok: [vm01]
ok: [vm02]
ok: [vm03]

TASK [Ping my hosts] *********************************************************************************************************************************
ok: [vm01]
ok: [vm03]
ok: [vm02]

TASK [Prepare software] ******************************************************************************************************************************
ok: [vm03]
ok: [vm01]
ok: [vm02]

PLAY RECAP *******************************************************************************************************************************************
vm01                       : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
vm02                       : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
vm03                       : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

All done! What rest is to write more tasks to install more softwares towards a kubernetes cluster. Thanks for your reading.