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 !

mardi 7 octobre 2014

Premier pas avec Ansible et gestion du sudo

Et voilà, à peine je commence à maîtriser le langage autour de Puppet qu'on me demande de me pencher sur Ansible. Pour les personnes qui se sauraient pas ce que font ces deux produits, il faut savoir qu'ils appartiennent à la mouvance du DevOps qui consiste à gérer son infrastructure par du code (Dev) plutôt que par un opérateur (Ops) également appelé être humain (ou bipède, interface chaise-clavier etc.).

Installation de Ansible sur votre machine

La différence entre les deux produits se trouve au niveau de leur mode de fonctionnement : Puppet fonctionne à l'aide d'agent alors que Ansible s'appuie sur le protocole SSH et l'interpréteur python. Un petit bémol tout de même, si vous utilisez des versions antérieurs à Python 2.5, vous aurez besoin d'une librairie de gestion du json dans python. Comme il se trouve que les versions de RHEL 5.x sont concernées, ça pourrait vous arriver.

Pour les autres, c'est pour ainsi dire la fête : pas de package à installer et à maintenir sur vos machines. Vous échangez vos clés SSH avec les machines que vous voulez gérer et c'est parti.

Pour le serveur Ansible, l'installation se fait en activant le support des EPEL pour les machines à base de RHEL/CentOS (cf https://fedoraproject.org/wiki/EPEL) et en lançant un bon vieux yum install ansible. Pour Ubuntu, vous pouvez vous appuyer sur le ppa suivant : ppa:rquillo/ansible (l'ajout se fait avec la commande sudo add-apt-repository ppa:rquillo/ansible suivi d'un sudo aptitude update ; sudo aptitude install ansible).

Et voilà, c'est tout !

Prenons un exemple

Maintenant que nous avons planté le décor, nous allons créer notre première recette. Créer un répertoire de travail (~/ansible/test par exemple) et, dans ce répertoire, créer un fichier hosts avec le contenu suivant :

[test]
machine1
machine2

Ce fichier va nous servir d'inventaire des différentes machines pour nos déploiements et de mécanisme de regroupement (avec ici une section [test]). Pour tester la communication ssh, il suffit de lancer la commande ansible -i ./hosts all -m ping. Ci-dessous un exemple de résultat :

ansible -i ./hosts all -m ping
machine1 | success >> {
    "changed": false, 
    "ping": "pong"
}
machine2 | success >> {
    "changed": false, 
    "ping": "pong"
}

En cas de pépin, il faudra bien-sûr échanger les clés SSH avec un ssh-copy-id MACHINE.

Maintenant que nous avons la liste de nos machines, lançons la création du fichier site.yml et ajoutons-y le contenu suivant :

---
# Playbook de test

- name: Test
  hosts: test
  tasks:
    - name: Création d'un fichier /tmp/test
      copy: dest=/tmp/test content="Ceci est test\n" owner=root group=root

Nous avons ici associé le group de machine test (ligne hosts: test) avec la création d'un fichier de test appartenant à root.

On enregistre, et on lance maintenant l'application de cette recette avec la commande ansible-playbook -i ./hosts ./site.yml. On devrait obtenir la sortie suivante :

yannig@pupuce ~/ansible/test $ ansible-playbook -i ./hosts ./site.yml

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

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

TASK: [Création d'un fichier /tmp/test] *****************************
changed: [machine1]
changed: [machine2]

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

Ça marche : j'ai maintenant des fichiers /tmp/test sur mes deux machines :).

Et puis vint la sécurité

C'est bien beau mais le test que je viens de faire, je l'ai fait sur des machines que je maîtrise totalement. Malheureusement sur la prod sur laquelle je vais devoir travailler, il est interdit de se connecter directement en tant que root. Il faut donc passer par un mécanisme d'escalade sudo. Et comme un bonheur ne vient jamais seul, il faut également ressaisir le mot de passe pour lancer le sudo ...

Et c'est là où intervient la notion de variable dans le fichier hosts. Ci-dessous un exemple permettant de gérer l'utilisation d'un mot de passe sudo pour passer en tant qu'utilisateur root :

[machineexterne:vars]
ansible_sudo_pass=xxx
ansible_remote_user=userxxx
ansible_sudo=yes
ansible_sudo_user=root

[machineexterne]
machineexterne1
machineexterne2

[test]
machine1
machine2

Il est bien sûr entendu que le compte remote_user devra disposer des droits suffisants pour faire un sudo vers root.

Pour conclure

Vous l'aurez vu, il s'agissait juste de faire ses premiers pas avec ce merveilleux outils. Les difficultés habituelles sont bien gérées (sudo, ssh) et sa légéreté de déploiement initiale devrait en convaincre plus d'un.

La documentation du site est bien organisée. Je vous laisse consulter ça à l'adresse suivante : http://docs.ansible.com/.