Affichage des articles dont le libellé est yaml. Afficher tous les articles
Affichage des articles dont le libellé est yaml. Afficher tous les articles

mardi 3 mars 2015

Écriture de module avec Ansible

Depuis quelques temps, je m'étais mis en tête de vouloir tester l'espace disque disponible. En effet, il m'arrive assez régulièrement d'avoir un bon gros message d'erreur plus d'espace disponible alors que je viens de faire la moitié de l'installation.

Partant de ce constat, je me suis dit qu'il vaudrait mieux avoir une erreur franche en début d'exécution plutôt qu'au milieu de mon installation (sachant que généralement je peux avoir un truc à moitié bancal que je dois supprimé pour avoir une installation correcte). J'ai donc cherché un moyen de tester la quantité de disque restant pour un emplacement donné évitant ainsi ce type de désagrément.

Petit problème, je n'ai rien trouvé me permettant de faire ce test simplement. N'étant pas non plus emballé de faire ça avec un bout de shell, j'ai donc cherché à écrire le module Ansible qui me permettrait de gérer ce test.

Fichier playbook

Prenons un playbook check_tmp.yml qui va s'assurer que j'ai au moins 40 Mo d'espace disque dans /tmp. Pour se faire, il va faire appel à un module space_available :

---
# Playbook de test

- name: "Test"
  hosts: "localhost"
  gather_facts: no
  tasks:
    - space_available: path=/tmp size=40M

Fichier du module (library/space_available)

Dans le même répertoire que le fichier check_tmp.yml, créer un répertoire library. Dans ce répertoire, il nous suffit de créer un fichier space_available que nous allons alimenter avec le contenu suivant :

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Author: Yannig Perre 
#
# Check for available space on a given path.
#
 
# Documentation
DOCUMENTATION = '''
---
version_added: "1.8"
module: space_available
short_description: space_available
description:
  - This module check if there's enough space available
options:
  path:
    description:
      path to check
  size:
    description:
      size needed.
notes:
requirements: []
author: Yannig Perre
'''
 
EXAMPLES = '''
- name: "40M space available on /tmp"
  space_available: path=/tmp size=40M
'''
 
# static hash map converter
size_convertion = { "k": 1024, "m": 1024*1024, "g": 1024*1024*1024, "t": 1024*1024*1024*1024, "p": 1024*1024*1024*1024*1024 }

# Here we go...
def main():
    # module declaration
    module = AnsibleModule(
        argument_spec=dict(
            path=dict(required=True),
            size=dict(required=True),
        ),
        supports_check_mode=True
    )
    # Retrieving parameters value
    path = module.params['path']
    size = module.params['size']
    # Retrieve size (code part stolen from lvol module)
    if size[-1].isalpha():
        if size[-1].lower() in 'kmgtpe':
            size_unit = size[-1]
            if not size[0:-1].isdigit():
                module.fail_json(msg="Bad size specification for unit %s" % size_unit)
            size_opt = size[-1]
            size = int(size[0:-1])
    elif not size.isdigit():
        module.fail_json(msg="Bad size specification")
    else:
        size = int(size)
        size_opt = 'm'

    # Convert everything in bytes please
    unit_size = size_convertion[size_opt.lower()]
    size = size * unit_size
    # Try to get ...
    try:
        # ... size available
        statvfs = os.statvfs(path)
    except OSError, e:
        # if we fail dying with exception message
        module.fail_json(msg="%s" % e)
    space_available = statvfs.f_bavail * statvfs.f_frsize

    # If not enough room => fail
    if space_available < size:
        module.fail_json(msg="Not enough space avaible on %s (found = %0.2f%s, needed = %0.2f%s)" % (path, space_available / unit_size, size_opt, size / unit_size, size_opt))
    module.exit_json(msg=path)
 
# Import Ansible Utilities
from ansible.module_utils.basic import *
main()

Premier test

Lançons maintenant notre playbook avec la commande suivante :
ansible-playbook space_available.yml 

PLAY [Test] ***************************************************** 

TASK: [space_available path=/tmp size=40M] ********************** 
ok: [localhost]

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

\o/ Youpi ! Il semblerait que mon poste dispose de plus de 40 Mo de disque dans /tmp !

Quelques tests supplémentaires

Changeons maintenant notre playbook pour voir le comportement de notre module :

---
# Playbook de test

- name: "Test"
  hosts: "localhost"
  gather_facts: no
  tags: "poste-packaging"
  tasks:
    - space_available: path=/does/not/exist size=400M
      ignore_errors: yes
    - space_available: path=/root/.aptitude size=400M
      ignore_errors: yes
    - space_available: path=/tmp size=400M
    - space_available: path=/tmp size=1G
    - space_available: path=/tmp size=37G
      ignore_errors: yes
    - space_available: path=/tmp size=40M
    - space_available: path=/ size=3000M

Lançons notre playbook :

ansible-playbook space_available.yml 

PLAY [Test] **************************************************** 

TASK: [space_available path=/does/not/exist size=400M] ********* 
failed: [localhost] => {"failed": true}
msg: [Errno 2] No such file or directory: '/does/not/exist'
...ignoring

TASK: [space_available path=/root/.aptitude size=400M] ********* 
failed: [localhost] => {"failed": true}
msg: [Errno 13] Permission denied: '/root/.aptitude'
...ignoring

TASK: [space_available path=/tmp size=400M] ******************** 
ok: [localhost]

TASK: [space_available path=/tmp size=1G] ********************** 
ok: [localhost]

TASK: [space_available path=/tmp size=37G] ********************* 
failed: [localhost] => {"failed": true}
msg: Not enough space avaible on /tmp (found = 9.00G, needed = 37.00G)
...ignoring

TASK: [space_available path=/tmp size=40M] ********************* 
ok: [localhost]

TASK: [space_available path=/ size=3000M] ********************** 
ok: [localhost]

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

Nous avons bien des indications sur :

  • L'absence d'un fichier sur la machine ;
  • Un manque de permission ;
  • Et enfin, un manque de place.
A noter que les instructions en erreur sont passées du fait de la présence du mot clé ignore_errors: yes.

Pour aller plus loin

Pour plus de détail sur le fonctionnement des modules, je vous invite à vous reporter à la documentation sur le sujet présente sur ansible.com (http://docs.ansible.com/developing_modules.html).

jeudi 9 octobre 2014

Comparaison de la gestion des FS avec Puppet et Ansible

Suite de mes aventures avec Ansible : aujourd'hui c'est du système Linux.

Les données du problème : je veux pouvoir gérer le LVM Linux et retailler mes FS à la demande.

La recette sous Puppet

Sous Puppet, en utilisant un fichier yaml/Hiera, l'écriture de tout ceci se ferait avec quelque chose de ce genre :

---
classes: ['lvm']

lvm::volume_groups:
  rootvg:
    physical_volumes:
      - /dev/sda2
    logical_volumes:
      tmp:
        size: 2G
        mountpath: /tmp
  datavg:
    physical_volumes:
      - /dev/sdb
    logical_volumes:
      test:
        size: 128
        mountpath: /test

Ne pas oublier non plus d'inclure le code suivant dans le fichier site.pp :

hiera_include('classes','')

Bien penser également à installer le module lvm, sinon ça ne marchera pas (puppetlabs-lvm).

Bon, sous Ansible, toutes ces belles choses n'existent pas (encore) en l'état mais sous forme de brique à assembler. On va voir comment recréer un mécanisme à peu près similaire.

La recette sous Ansible

Je me place dans le cadre d'un playbook Ansible avec un fichier system.yml se trouvant dans un répertoire de travail ~/ansible avec le contenu suivant :

---
# Playbook de gestion du système

- name: Propriétés communes système
  hosts: all
  remote_user: root

  roles:
    - system

Autre fichier présent : group_vars/all avec le contenu suivant :

---
# Variable applicable à tous

vgs:
  rootvg: "/dev/sda2"
  datavg: "/dev/sdb"

lvs:
  test:
    vg: rootvg
    mountpoint: /test
    size: 256

Ce fichier sera a spécialiser par groupe de machine mais est l'équivalent de notre fichier yaml/Hiera. Reste maintenant à écrire notre liste de tâche qui nous permettra de gérer ces FS (qu'on mettra dans le fichier roles/system/tasks/main.yml) :

---
- name: VG/PV Configuration
  lvg: vg={{ item.key }} pvs={{ item.value }} state=present
  with_dict: vgs

- name: LV creation
  lvol: vg={{ item.value.vg }} lv={{item.key}} size={{item.value.size}}
  register: task
  with_dict: lvs

- name: LV format
  filesystem: fstype=ext4 dev=/dev/mapper/{{item.value.vg}}-{{item.key}}
  with_dict: lvs

- name: Mount
  mount: name={{item.value.mountpoint}} src=/dev/mapper/{{item.value.vg}}-{{item.key}} dump=1 passno=2 fstype=ext4 state=mounted
  when: item.value.has_key('mountpoint')
  with_dict: lvs

- name: FS resize
  command: resize2fs /dev/mapper/{{item.item.value.vg}}-{{item.item.key}}
  with_items: task.results
  when: item.changed == True

NB : cette recette ne gère que des fs de type ext4. Si vous voulez gérer du xfs/btrfs/fegafatfs, il faudra changer la commande resize2fs par autre chose.

Le fonctionnement de la recette est assez simple : on enchaine les opérations (création du PV/VG, création du LV, formatage et montage) et on exécute la commande resize2fs qu'en cas de changement sur le LV (cf FS resize, ligne when: item.changed == True).

Reste maintenant à lancer notre playbook avec la commande ansible-playbook -i ./hosts ./system.yml. Vous devriez obtenir le résultat suivant :

PLAY [Propriétés communes système] ******************************

GATHERING FACTS *************************************************
ok: [machine1]

TASK: [common | VG/PV Configuration] ****************************
ok: [machine1] => (item={'key': 'datavg', 'value': '/dev/sdb'})
ok: [machine1] => (item={'key': 'rootvg', 'value': '/dev/sda2'})

TASK: [common | LV creation] ************************************
changed: [machine1] => (item={'key': 'test', 'value': ...})

TASK: [common | LV format] **************************************
changed: [machine1] => (item={'key': 'test', 'value': ...})

TASK: [common | Mount] ******************************************
changed: [machine1] => (item={'key': 'test', 'value': ...})

TASK: [common | FS resize] **************************************
changed: [machine1] => (item={u'msg': u'', 'item': ...})

PLAY RECAP ******************************************************
machine1        : ok=6    changed=4    unreachable=0    failed=0   

Changeons la taille de notre FS pour autre chose et relançons ansible pour vérifier la bonne prise en compte de tout ceci :

PLAY [Propriétés communes système] ******************************

GATHERING FACTS *************************************************
ok: [machine1]

[...]

TASK: [common | LV creation] ************************************
changed: [machine1] => (item={'key': 'test', 'value': ...})

[...]

TASK: [common | FS resize] **************************************
changed: [machine1] => (item={u'msg': u'', 'item': ...})

PLAY RECAP ******************************************************
machine1        : ok=6    changed=2    unreachable=0    failed=0   

Tout c'est bien passé. Il ne me reste plus qu'à vous souhaiter une bonne gestion de vos LV !