Browse Tag: Ansible

Installing GlusterFS on Odroid-HC2

Das GlusterFS lebt davon, dass es unterscheiden kann welches Veränderung im Filesystem zu welchem Zeitpunkt, auf welchem Node zu erst stattgefunden hat. Andernfalls könnte es ja nicht die Aktualität beurteilen. Aus diesem Grund ist es, laut Anleitung, sehr wichtig ein durchgehend zeitsynchrones Setup zu pflegen. An der Installation und Nutzung von NTP führt also kein Weg vorbei. Also zu erst mal NTP installieren und aktivieren.

---
- name: All hosts install ntp
  hosts: glusterfs
  become: yes
  
  tasks:
    - name: install ntp
      pacman:
        name: ntp
        update_cache: yes

Und ausrollen, mittlerweile traue ich mich es auch gleich auf den ganzen Node-Stapel auszurollen.

[ansible@barney ~]$ ansible-playbook ntp.yml

PLAY [All hosts install ntp] *******************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]

TASK [install ntp] *****************************************************************************************************************************************
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]

PLAY RECAP *************************************************************************************************************************************************
192.168.2.xx               : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.xx               : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.xx               : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.xx               : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.xx               : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.xx               : ok=2    changed=1    unreachable=0    failed=0   

[ansible@barney ~]$ 

Der nächste Schritt ist also NTP Server für die interne Uhr der Gluster-Nodes zu nutzen. Ich hab mir folgendes Playbook zusammen geschrieben.

---
- name: Build NTP config
  hosts: glusterfs
  become: yes
  
  tasks:
    - name: Stop service ntpd, if started
      service:
        name: ntpd
        state: stopped

    - name: delete orginal config
      file:
        path: /etc/ntp.conf
        state: absent

    - name: Touch on /etc/ntp.conf
      file:
        path: /etc/ntp.conf
        state: touch
        mode: "u=rw,g=r,o=r"

    - name: insert ntp.conf
      blockinfile:
        path: /etc/ntp.conf
        block: |
          #Associate to PTB's NTP pool
          server 0.arch.pool.ntp.org
          server 1.arch.pool.ntp.org
          server 2.arch.pool.ntp.org
          server 3.arch.pool.ntp.org

          # By default, the server allows:
          # - all queries from the local host
          # - only time queries from remote hosts, protected by rate limiting and kod
          restrict default kod limited nomodify nopeer noquery notrap
          restrict 127.0.0.1
          restrict ::1

          # Location of drift file
          driftfile /var/lib/ntp/ntp.drift

    - name: Start service ntpd, if not started
      service:
        name: ntpd
        state: started

    - name: Enable service ntpd, and not touch the state
      service:
        name: ntpd
        enabled: yes

    - name: set timezone to Europe/Berlin
      timezone:
        name: Europe/Berlin

Ein Kontrolle, mit einigen Minuten abstand zum ausgeführten Playbook, zeigt auch, dass die NTP Server erreicht werden. Leider war es mir nicht möglich die Zeitserver der PTB in Deutschland zu nutzen. Auf deren Website standen zwar die FQDNs, welche aber weder auf NTP Anfragen noch auf Ping reagierten..

[ansible@GFS1 ~]$ ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
+217.79.179.106  192.53.103.108   2 u   38   64  177   12.028   -1.624   5.113
+eta.h6g-server. 205.46.178.169   2 u   39   64  177   15.882   -0.296   5.146
*89.163.128.33 ( 192.53.103.104   2 u   37   64  177   13.343   -6.665   5.282
+mx.pingless.com 130.149.17.21    2 u   35   64  177    8.285   -6.129   4.892
[ansible@GFS1 ~]$ 

Die Uhrzeit sollte also über alle Nodes dann Synchron sein, nachdem ich das Playbook auf alle Gluster Nodes geworfen habe. Was ich gleich mal mache.

[ansible@barney ~]$ ansible-playbook ntp-config.yml

PLAY [Build NTP config] ************************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]

TASK [Stop service ntpd, if started] ***********************************************************************************************************************
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
changed: [192.168.2.xx]
ok: [192.168.2.xx]

TASK [delete orginal config] *******************************************************************************************************************************
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]

TASK [Touch on /etc/ntp.conf] ******************************************************************************************************************************
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]

TASK [insert ntp.conf] *************************************************************************************************************************************
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]

TASK [Start service ntpd, if not started] ******************************************************************************************************************
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]

TASK [Enable service ntpd, and not touch the state] ********************************************************************************************************
ok: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]

TASK [set timezone to Europe/Berlin] ***********************************************************************************************************************
ok: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]
changed: [192.168.2.xx]

PLAY RECAP *************************************************************************************************************************************************
192.168.2.xx               : ok=8    changed=5    unreachable=0    failed=0   
192.168.2.xx               : ok=8    changed=6    unreachable=0    failed=0   
192.168.2.xx               : ok=8    changed=6    unreachable=0    failed=0   
192.168.2.xx               : ok=8    changed=6    unreachable=0    failed=0   
192.168.2.xx               : ok=8    changed=6    unreachable=0    failed=0   
192.168.2.xx               : ok=8    changed=6    unreachable=0    failed=0   

[ansible@barney ~]$ 

Nachdem nun die Uhrzeit klar ist, geht es weiter mit dem aktivieren des GlusterfS Daemon, ohne welchen ein GlusterFS nicht funktioniert.

Dafür habe ich mir folgendes Playbook ausgedacht.

---
- name: Start GlusterFS Daemon
  hosts: glusterfs
  become: yes
  
  tasks:
    - name: Start GlusterFS Daemon, if not started
      service:
        name: glusterd
        state: started

    - name: Enable GlusterFS Daemon service, and not touch the state
      service:
        name: glusterd
        enabled: yes

Dieses Playbook lief ebenfalls durch ohne Fehlermeldungen. Ich hab mit den GlusterFS Boardmitteln mal lokal auf Node GFS2 probiert, ob die anderen Nodes im Gluster-Service erreichbar sind.

[ansible@GFS2 ~]$ sudo gluster peer probe gfs1
peer probe: success. 

[ansible@GFS2 ~]$ sudo gluster peer probe gfs2                    
peer probe: success. Probe on localhost not needed

[ansible@GFS2 ~]$ sudo gluster peer probe gfs3
peer probe: success. 

[ansible@GFS2 ~]$ sudo gluster peer probe gfs4
peer probe: success. 

[ansible@GFS2 ~]$ sudo gluster peer probe gfs5
peer probe: success. 

[ansible@GFS2 ~]$ sudo gluster peer probe gfs6
peer probe: success. 

[ansible@GFS2 ~]$ 

Auf allen Nodes wurde der GlusterFS Daemon erfolgreich gestartet. Da ich von Hause aus sehr mistrauisch bin, habe ich den selben Test nach einem reboot ebenso nochmal durchgeführt. Das Ergebnis war das selbe.

Nachdem also der GlusterFS Daemon sauber läuft, geht es daran die einzelnen Nodes als Peers per Playbook bekannt zu machen. Das Playbook dafür habe ich wie folgt formuliert.

---
- name: Start GlusterFS Daemon
  hosts: glusterfs
  become: yes
  
  tasks:
    - name: Create a trusted storage pool
      gluster_peer:
            state: present
            nodes:
                 - GFS1
                 - GFS2
                 - GFS3
                 - GFS4
                 - GFS5
                 - GFS6

Beim Ausrollen ist mir aufgefallen, dass es scheinbar nur notwendig ist, es gegen einen GlusterFS Node laufen zu lassen, da dieser dann wohl die Peers untereinander informiert. Anders kann ich mir das erfolgreiche Setup trotz der vielen Fehlermeldungen nicht recht erklären. Sei es drum, das Playbook ist durchgelaufen.

[ansible@barney ~]$ ansible-playbook gluster-peers.yml 

PLAY [Start GlusterFS Daemon] ******************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]
ok: [192.168.2.xx]

TASK [Create a trusted storage pool] ***********************************************************************************************************************
fatal: [192.168.2.xx]: FAILED! => {"changed": false, "msg": "peer probe: failed: GFS1 is either already part of another cluster or having volumes configured\n", "rc": 1}
fatal: [192.168.2.xx]: FAILED! => {"changed": false, "msg": "peer probe: failed: GFS1 is either already part of another cluster or having volumes configured\n", "rc": 1}
fatal: [192.168.2.xx]: FAILED! => {"changed": false, "msg": "peer probe: failed: GFS1 is either already part of another cluster or having volumes configured\n", "rc": 1}
fatal: [192.168.2.xx]: FAILED! => {"changed": true, "msg": "peer probe: failed: GFS3 is either already part of another cluster or having volumes configured\n", "rc": 1}
fatal: [192.168.2.xx]: FAILED! => {"changed": false, "msg": "peer probe: failed: GFS1 is either already part of another cluster or having volumes configured\n", "rc": 1}
changed: [192.168.2.xx]
        to retry, use: --limit @/home/ansible/gluster-peers.retry

PLAY RECAP *************************************************************************************************************************************************
192.168.2.xx               : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.xx               : ok=1    changed=0    unreachable=0    failed=1   
192.168.2.xx               : ok=1    changed=0    unreachable=0    failed=1   
192.168.2.xx               : ok=1    changed=0    unreachable=0    failed=1   
192.168.2.xx               : ok=1    changed=0    unreachable=0    failed=1   
192.168.2.xx               : ok=1    changed=0    unreachable=0    failed=1   

[ansible@barney ~]$

Aber wie bereits geschrieben geht es den Peers gut, denn die Abfrage des Glsuer Peer Status sieht richtig gut aus. Einmal auf GFS:

[ansible@GFS1 ~]$ sudo gluster peer status
Number of Peers: 5

Hostname: GFS2
Uuid: a8e86d86-d13d-45d6-97a1-b46619a4dfea
State: Peer in Cluster (Connected)

Hostname: GFS3
Uuid: a9696e90-0707-4de4-8430-479e7d3af8ac
State: Peer in Cluster (Connected)

Hostname: GFS4
Uuid: e01c2d0a-6491-4250-b531-5895be4788f0
State: Peer in Cluster (Connected)

Hostname: GFS5
Uuid: c26b3841-ff62-456a-b1d9-8e2842291535
State: Peer in Cluster (Connected)

Hostname: GFS6
Uuid: 67604b4f-85ba-47d5-96e4-a9dee66569ae
State: Peer in Cluster (Connected)

[ansible@GFS1 ~]$ 

Und einmal zur Sicherheit auch noch auf GFS6:

[ansible@GFS6 ~]$ sudo gluster peer status
Number of Peers: 5

Hostname: GFS1
Uuid: 48851509-5126-4d70-918d-841fc185ce21
State: Peer in Cluster (Connected)

Hostname: GFS2
Uuid: a8e86d86-d13d-45d6-97a1-b46619a4dfea
State: Peer in Cluster (Connected)

Hostname: GFS3
Uuid: a9696e90-0707-4de4-8430-479e7d3af8ac
State: Peer in Cluster (Connected)

Hostname: GFS4
Uuid: e01c2d0a-6491-4250-b531-5895be4788f0
State: Peer in Cluster (Connected)

Hostname: GFS5
Uuid: c26b3841-ff62-456a-b1d9-8e2842291535
State: Peer in Cluster (Connected)

[ansible@GFS6 ~]$ 

Die Peers sehen sich und haben eine Verbindung untereinander. Weiter geht es mit dem eigentlichen interessanten Teil, das Aufsetzen des Node übergreifenden GlusterFWS Volumes.

Leider gelingt mir dieses zur Zeit nicht mit Ansible, was eigentlich sehr schade ist – da ich es umbedingt nutzen wollte. Auf der anderen Seite ist es nicht notwendigerweise mit Ansible einfacher, da die gesammte GlusterFS Konfiguration nur auf einem Node erfolgt und der verteilt es dann an die anderen Node Clustermember. Hier jetzt also die Zeilen zum Aufsetzen des GlusterFS Volumes auf der Bash von gfs1:

[ansible@GFS1 ~]$ sudo gluster volume create GV0 disperse 4 redundancy 2 transport tcp gfs1:/export/sda1 gfs2:/export/sda1 gfs3:/export/sda1 gfs4:/export/sda1 gfs5:/export/sda1 gfs6:/export/sda1 force 
volume create: GV0: success: please start the volume to access data

[ansible@GFS1 ~]$ 

Da mich nach so einem Befehl immer brennend die Auswirkung interessiert, hier die Kontrolle des Ergebnis.

[ansible@GFS1 ~]$ sudo gluster volume info
 
Volume Name: GV0
Type: Disperse
Volume ID: a917ef77-f247-4460-a3bb-bf4a7cb1fad9
Status: Created
Snapshot Count: 0
Number of Bricks: 1 x (4 + 2) = 6
Transport-type: tcp
Bricks:
Brick1: gfs1:/export/sda1
Brick2: gfs2:/export/sda1
Brick3: gfs3:/export/sda1
Brick4: gfs4:/export/sda1
Brick5: gfs5:/export/sda1
Brick6: gfs6:/export/sda1
Options Reconfigured:
transport.address-family: inet
nfs.disable: on

[ansible@GFS1 ~]$

Solange das Volume nur angelegt (Created) ist, findet noch keine Replikation zwischen den Nodes statt. Dafür muss das Volume erst gestartet werden.

[ansible@GFS1 ~]$ sudo gluster volume start GV0  
volume start: GV0: success
[ansible@GFS1 ~]$

Anschließend ändert sich der Status von Created zu Started. Jetzt sollte das GlusterFS Volume erreichbar sein.

[ansible@GFS1 ~]$ sudo gluster volume info
 
Volume Name: GV0
Type: Disperse
Volume ID: a917ef77-f247-4460-a3bb-bf4a7cb1fad9
Status: Started
Snapshot Count: 0
Number of Bricks: 1 x (4 + 2) = 6
Transport-type: tcp
Bricks:
Brick1: gfs1:/export/sda1
Brick2: gfs2:/export/sda1
Brick3: gfs3:/export/sda1
Brick4: gfs4:/export/sda1
Brick5: gfs5:/export/sda1
Brick6: gfs6:/export/sda1
Options Reconfigured:
transport.address-family: inet
nfs.disable: on

[ansible@GFS1 ~]$ sudo gluster volume status
Status of volume: GV0
Gluster process                             TCP Port  RDMA Port  Online  Pid
------------------------------------------------------------------------------
Brick gfs1:/export/sda1                     49152     0          Y       1746 
Brick gfs2:/export/sda1                     49152     0          Y       916  
Brick gfs3:/export/sda1                     49152     0          Y       678  
Brick gfs4:/export/sda1                     49152     0          Y       681  
Brick gfs5:/export/sda1                     49152     0          Y       671  
Brick gfs6:/export/sda1                     49152     0          Y       679  
Self-heal Daemon on localhost               N/A       N/A        Y       1767 
Self-heal Daemon on GFS2                    N/A       N/A        Y       937  
Self-heal Daemon on GFS3                    N/A       N/A        Y       699  
Self-heal Daemon on GFS4                    N/A       N/A        Y       702  
Self-heal Daemon on GFS5                    N/A       N/A        Y       692  
Self-heal Daemon on GFS6                    N/A       N/A        Y       700  
 
Task Status of Volume GV0
------------------------------------------------------------------------------
There are no active volume tasks
 
[ansible@GFS1 ~]$ 

Ich hab das GlusterFS Volume GV0 mal probeweise gemountet. Das geht sehr einfach von einem anderen Linux Arch, welcher nicht als Peer defineriert sein muss. Okay, ich habe jetzt auch keine Security features aktiviert. Was wichtig ist, dass der Client der in den GlusterFS mounten möchte die Nodes per DNS genauso auflösen können muss, wie die GlusterFS Peers untereinander. Dann geht das mounten auch recht einfach von der Hand.

[eric@Wintermute ~]$ sudo mount -t glusterfs gfs6:GV0 /media/GV0/

[eric@Wintermute ~]$ df -aH /media/GV0
Dateisystem    Größe Benutzt Verf. Verw% Eingehängt auf
gfs6:GV0         10T    111G  9,9T    2% /media/GV0

[eric@Wintermute ~]$ ls -la /media/GV0/
insgesamt 4
drwxr-xr-x  3 root root   24  1. Apr 20:10 .
drwxr-xr-x 10 root root 4096  1. Apr 21:13 ..

[eric@Wintermute ~]$ 

Ich hoffe, ich konnte etwas Licht in das GlusterFS Thema bringen.

:o)

Odroid-HC2 mit Ansible für das GlusterFS vorbereiten

Nachdem Ansible nun auf die Nodes zugreifen kann, folgt der nächste Schritt. Ich werde mich Stück für Stück an die notwendigen Bestandteile eines Playbooks an nähern. Ziel ist es, ein Playbook zu erhalten, welches mir die Nodes so Aufbaut, dass sie in das GlusterFS integriert werden können.

Zuerst schaue ich mir mal an, wie das von https://archlinuxarm.org/platforms/armv7/samsung/odroid-hc2 erzeugte Setup aussieht. Mich interessiert vor allem, wie das Arm Team die microSD Karte gemountet hat und wo diese und die eingebaute HDD im /dev Baum sitzt.

[ansible@alarm ~]$ cat /etc/fstab 
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir> <type> <options> <dump> <pass>
[ansible@alarm ~]$ sudo cat /etc/fstab
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir> <type> <options> <dump> <pass>
[ansible@alarm ~]$ 

Okay, kann man so machen.. seufz, damit hält man sich natürlich alle Freiheitsgrade offen, indem man die Medien gar nicht erst mountet.

Bin ich persönlich jetzt keine Freund davon etwas ungemountet zu booten, aber ich kann es nachvollziehen, dass das Team es so gemacht hat. Dadurch können die drei verschiedenen Arten von Mount-Points nach dem ersten Booten bedient werden. (/dev/sdX versus Label versus UUID). Ich selber nutze sehr gerne zum Mounten die Label, weil meiner Meinung nach dadurch in einem späteren Update sich verändernde Mount-Points a’la /dev/sda1 abgefangen werden und diese schwer handlebaren UUIDs umgangen werden können. Aber wie schon erwähnt, was für mich war ist, mag noch lange nicht für jedes Szenario gelten.

Na dann schau ich mir mal die Optionen zum Mounten im /dev/disk Baum genauer an.

[ansible@alarm ~]$ sudo tree /dev/disk/
/dev/disk/
|-- by-id
|   |-- ata-SAMSUNG_HD203WI_S1UYJ1KZ100183 -> ../../sda
|   |-- ata-SAMSUNG_HD203WI_S1UYJ1KZ100183-part1 -> ../../sda1
|   |-- mmc-SC16G_0xb48f4515 -> ../../mmcblk0
|   |-- mmc-SC16G_0xb48f4515-part1 -> ../../mmcblk0p1
|   |-- wwn-0x50024e9002b89270 -> ../../sda
|   `-- wwn-0x50024e9002b89270-part1 -> ../../sda1
|-- by-label
|   `-- ROOT -> ../../mmcblk0p1
<snip>

Am Ende steht mein gesetztes Label ROOT für die mmcblk0p1 Partition. Diese Partition möchte ich jetzt auch gerne fest in der /etc/fstab stehen haben, damit nach späteren Software-Updates nichts ins Schlingern gerät.

Ich baue mir das Zielplaybook aus einzelnen kleinen Playbook-Configlets zusammen, deshalb prüfe ich jetzt erstmal, ob das Mounten von einem / auf einem Node geht. Das Playbook-Configlets habe ich mir wie folgt ausgedacht.

[ansible@barney ~]$ cat label_root_mount.yml 
---
- name: All mount / by label ROOT
  hosts: glusterfs
  become: yes

  tasks:
   - name: generate LABEL=ROOT entry
     mount:
       path: /
       src: LABEL=ROOT
       fstype: ext4
       opts: defaults,noatime
       state: present

[ansible@barney ~]$ 

Damit ich das Playbook jetzt nicht gleich gegen alle Nodes lasse, habe ich mir die /etc/ansible/host aufgeteilt, so daß ich einzelne Nodes gezielt ansprechen kann. Die /etc/ansible/host Textdatei sieht wie folgt aus.

[root@barney ansible]# cat /etc/ansible/hosts
[glusterfs]
192.168.2.143
192.168.2.144
192.168.2.145
192.168.2.146
192.168.2.25

[g1]
192.168.2.143

[g2]
192.168.2.144

[g3]
192.168.2.145

[g4]
192.168.2.146

[g5]
192.168.2.25
[root@barney ansible]# 

Ich vermute, in größeren Installationen wird wohl eher mit dem DNS Namen der Nodes hantiert als mit der IPv4 Adresse, ist mir jetzt aber erstmal Wurst..

Okay, dann lassen wir mal das Playbook-Configlets gegen den Node g5 laufen und schauen uns an, wie das Ergebnis aussieht.

[ansible@barney ~]$ ansible-playbook label_root_mount.yml --limit g5

PLAY [All mount / by label ROOT] ***************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.25]

TASK [generate LABEL=ROOT entrie] **************************************************************************************************************************
changed: [192.168.2.25]

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

[ansible@barney ~]$ 

Spannend war es jetzt für mich, da ich mir nicht ganz sicher war, was der Node denn nun wirklich als Ergebnis in der /etc/fstab stehen hat. Ich seh mal nach.

[ansible@alarm ~]$ cat /etc/fstab 
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir> <type> <options> <dump> <pass>
LABEL=ROOT / ext4 defaults,noatime 0 0
[ansible@alarm ~]$ 

Sieht gut aus, allerdings gibt es noch ein Delta zwischen /etc/fstab und gelebter /etc/mtab. Dafür werde ich den Node jetzt mach rebooten und dann sollten die beiden symmetrisch sein.

[ansible@barney ~]$ ansible-playbook reboot.yml --limit g5

PLAY [All hosts reboot] ************************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.25]

TASK [Unconditionally reboot the machine with all defaults] ************************************************************************************************
changed: [192.168.2.25]

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

[ansible@barney ~]$ 

Nach dem kleinen Klaps sieht es jetzt so aus, wie ich es haben wollte. Die microSD Karte ist über das Label gemountet.

[ansible@alarm ~]$ lsblk -Tf
NAME        FSTYPE LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
sda                                                                          
`-sda1      xfs          03df1da5-89d0-4fd3-8bf4-69cbbe8d87ce                
mmcblk0                                                                      
`-mmcblk0p1 ext4   ROOT  5ab9cbdc-0a5f-4fe8-8910-afdda9eafb81   12.4G    10% /

Nun wird es Zeit, die verbaute HDD der Nodes für die Nutzung im GlusterFS einzurichten. Dafür verwerfe ich das auf der HDD existierende Setup erst einmal weg.

Ich hab jetzt eine gute Stunde gebraucht, um festzustellen, dass auf meinen Nodes alle Befehle vom Modul parted nicht ausgeführt werden. Ich stell da mal eine steile Hypothese auf, das Modul parted braucht auf dem Nodes das Programm parted. Ich werde das also mal Installieren und gucken ob das Playbook dann sauber durch läuft.

Also schraub ich mir ein Playbook zum Ausrollen von parted.

[ansible@barney ~]$ cat parted.yml
---
- name: All hosts install parted
  hosts: glusterfs
  become: yes
  
  tasks:
    - name: install parted
      pacman:
        name: parted
        update_cache: yes
[ansible@barney ~]$ 

Und läuft es durch?

[ansible@barney ~]$ ansible-playbook parted.yml --limit g5

PLAY [All hosts install parted] ****************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.25]

TASK [install parted] **************************************************************************************************************************************
changed: [192.168.2.25]

PLAY RECAP *************************************************************************************************************************************************
192.168.2.25               : ok=2    changed=1    unreachable=0    failed=0   
[ansible@barney ~]$

Okay, jetzt ist auf Node g5 also das Programm parted ausgerollt und jetzt lass ich nochmal das Playbook zum Löschen der Partition auf Node g5 los.

---
- name: All remove all partitions on sda
  hosts: glusterfs
  become: yes

  tasks:
   - parted:
       device: /dev/sda
       number: 1
       state: absent

Diese Partition stammt aus meinen Versuchen mit unterschiedlichen Softgware Defined Storages (Ceph/Hadoop/GlusterFs). Für mich stellt sich GlusterFS als am leichtesten handlebar dar, auch hier mag jeder zu seiner eigenen Sichtweisekommen auf Basis seines Bedarfs.

Nun gut, was macht denn nun das Playbok zum Löschen der Partion #1 auf SDA?

[ansible@barney ~]$ ansible-playbook remove_all_partitions_on_hdd.yml --limit g5

PLAY [All remove all partitions on sda] ********************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.25]

TASK [parted] **********************************************************************************************************************************************
changed: [192.168.2.25]

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

[ansible@barney ~]$ 

Jetzt noch flott eon prüfender Blick direkt ins Filesystem des Node g5.

[ansible@alarm ~]$ lsblk -Tf
NAME        FSTYPE LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
sda                                                                          
mmcblk0                                                                      
`-mmcblk0p1 ext4   ROOT  5ab9cbdc-0a5f-4fe8-8910-afdda9eafb81   12.4G    10% /
[ansible@alarm ~]$ 

Es hat funktioniert, die Partition sda1 ist gelöscht. Sicherlich geistern die Testdaten jetzt noch auf der HDD rum, aber was solls.

Als nächstes möchte ich jetzt eine Partition auf /dev/sda anlegen mit den Label=BRICK. Das Filesystem dieser BRICK Partition muss XFS sein. Das Playbook dafür habe ich mir wiefolgt vorgestellt.

[ansible@barney ~]$ cat make_sda1_partition.yml 
---
- name: All make one partition on sda1 with xfs
  hosts: glusterfs
  become: yes

  tasks:
   - name: Create sda1 partition
     parted:
       device: /dev/sda
       label: gpt
       number: 1
       state: present
       part_start: 0%
       part_end: 100%

   - name: Create a xfs filesystem on /dev/sda1
     filesystem:
       fstype: xfs
       dev: /dev/sda1
       opts: -i size=512

   - name: Label sda1 partition with LABEL=BRICK
     command: "xfs_admin -L BRICK /dev/sda1"
     become: true

[ansible@barney ~]$ 

Der Knackpunkt in diesem Ablauf ist, dass ich es auf Biegen und Brechen nicht hinbekommen habe mit den standard Ansible Modulen ein Label für die neue Partition zu setzen. Deshalb steht da im Playbook so ein hässliches Bash Command drin, aber nun ja. Dann lass ich mal mein erstes Playbook mit drei Tasks ausrollen. Mal sehen, ob das so geht, wie ich es mir vorstelle.

[ansible@barney ~]$ ansible-playbook make_sda1_partition.yml --limit g5

PLAY [All make one partition on sda1 with xfs] *************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.25]

TASK [Create sda1 partition] *******************************************************************************************************************************
changed: [192.168.2.25]

TASK [Label sda1 partition with LABEL=BRICK] ***************************************************************************************************************
changed: [192.168.2.25]

TASK [Create a xfs filesystem on /dev/sda1] ****************************************************************************************************************
ok: [192.168.2.25]

PLAY RECAP *************************************************************************************************************************************************
192.168.2.25               : ok=4    changed=2    unreachable=0    failed=0   

[ansible@barney ~]$

Ausgerollt ist es jedenfalls. Bei Filesystemveränderungen sehe ich lieber genau nach, bevor ich weiter mache.

[ansible@alarm ~]$ ls -l /dev/disk/by-label/
total 0
lrwxrwxrwx 1 root root 10 Mar 31 15:52 BRICK -> ../../sda1
lrwxrwxrwx 1 root root 15 Mar 31 09:56 ROOT -> ../../mmcblk0p1


[ansible@alarm ~]$ lsblk -Tf
NAME        FSTYPE LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
sda                                                                          
`-sda1      xfs    BRICK cdc27797-15eb-4e4d-b58e-3e6ad3315112                
mmcblk0                                                                      
`-mmcblk0p1 ext4   ROOT  5ab9cbdc-0a5f-4fe8-8910-afdda9eafb81   12.4G    10% /

[ansible@alarm ~]$ sudo xfs_db -c info /dev/sda1
meta-data=/dev/sda1              isize=512    agcount=4, agsize=122091705 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=0
data     =                       bsize=4096   blocks=488366820, imaxpct=5
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=238460, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0

ansible@alarm ~]$ 

Das sieht doch nach einem gültigen Versuch aus. Es gibt eine Partition /dev/sda1 mit dem Label BRICK. Die orginale Anleitung zum GlusterFS sieht vor, dass die XFS Storage-Partitionen nach /export/sdX1 gemountet wird. Also lassen wir Ansible mal ein Verzeichnis /export erzeugen, worin danach /export/sda1 erzeugt wird. das Playbook dafür stelle ich mir wie folgt vor.

[ansible@barney ~]$ cat mkdir_export_mount_label_brick.yml 
---
- name: Generate /export/sdb1 and mount into fstab
  hosts: glusterfs
  become: yes

  tasks:
  - name: Creates directory and subdirectory
    file:
      path: /export/sda1
      state: directory

  - name: generate LABEL=BRICK entry
    mount:
      path: /export/sda1
      src: LABEL=BRICK
      fstype: xfs
      opts: defaults
      state: present

[ansible@barney ~]$ 

Wenn ich mir das Ergebnis des Playbooks ansehe, bin ich zufrieden. Nach einem Reboot sollte dann die Partition sauber gemountet sein.

[ansible@alarm ~]$ tree -dfugA /export/
/export
└── [root     root    ]  /export/sda1

1 directory

[ansible@alarm ~]$ cat /etc/fstab 
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir> <type> <options> <dump> <pass>
LABEL=ROOT / ext4 defaults,noatime 0 0
LABEL=BRICK /export/sda1 xfs defaults 0 0

[ansible@alarm ~]$ 

Also steht als letzes für die Vorbereitung der GlusterFS installation noch ein Reboot an, dafür nehme ich das oben bereits beschriebene Playbook reboot.yml nochmal zur Hand und lasse es auf Node g5 los und sehe mir nachdem Reboot das Ergebnis auf dem Node g5 an.

[ansible@alarm ~]$ lsblk -Tf 
NAME        FSTYPE LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
sda                                                                          
`-sda1      xfs    BRICK cdc27797-15eb-4e4d-b58e-3e6ad3315112    1.8T     0% /export/sda1
mmcblk0                                                                      
`-mmcblk0p1 ext4   ROOT  5ab9cbdc-0a5f-4fe8-8910-afdda9eafb81   12.4G    10% /

[ansible@alarm ~]$ 

Das sieht genau so aus, wie ich es haben wollte. Damit sind die Vorbereitung des Dateisystems und der Partitonen abgeschlossen. Die einzelnen Code-Schnipsel baue ich jetzt noch in ein zusammenhängendes Playbook zusammen und lasse es dann über das gesammte Setup laufen.

Das finale Plabook lief fast zufriedenstellend durch. 😉

[ansible@barney ~]$ ansible-playbook preinstallation_glusterfs.yml

PLAY [Vorbereiten der Nodes vor der GlusterFS installation] ************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
ok: [192.168.2.146]
ok: [192.168.2.144]
ok: [192.168.2.145]
ok: [192.168.2.143]
ok: [192.168.2.25]
ok: [192.168.2.137]

TASK [Installiere parted] **********************************************************************************************************************************
ok: [192.168.2.25]
changed: [192.168.2.145]
changed: [192.168.2.144]
changed: [192.168.2.143]
changed: [192.168.2.146]
ok: [192.168.2.137]

TASK [Installiere glusterfs] *******************************************************************************************************************************
changed: [192.168.2.25]
changed: [192.168.2.144]
changed: [192.168.2.145]
changed: [192.168.2.146]
changed: [192.168.2.143]
ok: [192.168.2.137]

TASK [Erzeuge einen FSTAB Eintrag für LABEL=ROOT] **********************************************************************************************************
changed: [192.168.2.145]
changed: [192.168.2.143]
changed: [192.168.2.144]
ok: [192.168.2.25]
changed: [192.168.2.146]
ok: [192.168.2.137]

TASK [Lösche existierende Partitionen SDA1 auf SDA] ********************************************************************************************************
fatal: [192.168.2.25]: FAILED! => {"changed": false, "err": "Warning: Partition /dev/sda1 is being used. Are you sure you want to continue?\n", "msg": "Error while running parted script: /usr/bin/parted -s -m -a optimal /dev/sda -- rm 1", "out": "", "rc": 1}
changed: [192.168.2.143]
changed: [192.168.2.144]
changed: [192.168.2.146]
changed: [192.168.2.145]
fatal: [192.168.2.137]: FAILED! => {"changed": false, "err": "Warning: Partition /dev/sda1 is being used. Are you sure you want to continue?\n", "msg": "Error while running parted script: /usr/bin/parted -s -m -a optimal /dev/sda -- rm 1", "out": "", "rc": 1}

TASK [Erzeuge eine SDA1 Partition] *************************************************************************************************************************
changed: [192.168.2.143]
changed: [192.168.2.144]
changed: [192.168.2.145]
changed: [192.168.2.146]

TASK [Formatiere SDA1 mit XFS] *****************************************************************************************************************************
changed: [192.168.2.143]
changed: [192.168.2.145]
changed: [192.168.2.144]
changed: [192.168.2.146]

TASK [Umlabeln der SDA1 mit LABEL=BRICK] *******************************************************************************************************************
changed: [192.168.2.145]
changed: [192.168.2.146]
changed: [192.168.2.144]
changed: [192.168.2.143]

TASK [Erzeuge Verzeichnis /export/sda1] ********************************************************************************************************************
changed: [192.168.2.143]
changed: [192.168.2.144]
changed: [192.168.2.146]
changed: [192.168.2.145]

TASK [Erzeuge einen FSTAB Eintrag für LABEL=BRICK] *********************************************************************************************************
changed: [192.168.2.143]
changed: [192.168.2.144]
changed: [192.168.2.145]
changed: [192.168.2.146]

TASK [Durchbooten um die FSTAB Einträge zu aktivieren] *****************************************************************************************************
changed: [192.168.2.144]
changed: [192.168.2.146]
changed: [192.168.2.143]
changed: [192.168.2.145]
        to retry, use: --limit @/home/ansible/preinstallation_glusterfs.retry

PLAY RECAP *************************************************************************************************************************************************
192.168.2.137              : ok=4    changed=0    unreachable=0    failed=1   
192.168.2.143              : ok=11   changed=10   unreachable=0    failed=0   
192.168.2.144              : ok=11   changed=10   unreachable=0    failed=0   
192.168.2.145              : ok=11   changed=10   unreachable=0    failed=0   
192.168.2.146              : ok=11   changed=10   unreachable=0    failed=0   
192.168.2.25               : ok=4    changed=1    unreachable=0    failed=1   

[ansible@barney ~]$ 

Ansible auf Linux Arch installieren

Die Installation von Ansible auf Linux Arch ist weitestgehend ein No-Brainer. Für das Ansible selber ist das Paket “ansible” notwendig. Wer, wie ich, seine Playbooks checken lassen möchte, installiert das Paket “ansible-lint” einfach mit.

[root@barney ansible]# pacman -S ansible ansible-lint
Löse Abhängigkeiten auf...
Suche nach in Konflikt stehenden Paketen...

Pakete (3) python-ruamel-yaml-0.15.89-1  ansible-2.7.9-1  ansible-lint-4.1.0-1

Gesamtgröße des Downloads:            0,33 MiB
Gesamtgröße der installierten Pakete:  81,56 MiB

:: Installation fortsetzen? [J/n] 
:: Empfange Pakete...
 python-ruamel-yaml-0.15.89-1-x86_64                                                                                                                                  279,1 KiB  5,80M/s 00:00 [#######################################################################################################################] 100%
 ansible-lint-4.1.0-1-any                                                                                                                                              60,9 KiB  0,00B/s 00:00 [#######################################################################################################################] 100%
(3/3) Prüfe Schlüssel im Schlüsselring                                                                                                                                                         [#######################################################################################################################] 100%
(3/3) Überprüfe Paket-Integrität                                                                                                                                                               [#######################################################################################################################] 100%
(3/3) Lade Paket-Dateien                                                                                                                                                                       [#######################################################################################################################] 100%
(3/3) Prüfe auf Dateikonflikte                                                                                                                                                                 [#######################################################################################################################] 100%
(3/3) Überprüfe verfügbaren Festplattenspeicher                                                                                                                                                [#######################################################################################################################] 100%
:: Verarbeite Paketänderungen...
(1/3) Installiere ansible                                                                                                                                                                      [#######################################################################################################################] 100%
Optionale Abhängigkeiten für ansible
    sshpass: for ssh connections with password
    python-passlib: crypt values for vars_prompt
    python-pyopenssl: openssl modules
    python-netaddr: for the ipaddr filter
    python-systemd: log to journal
    python-pywinrm: connect to Windows machines
    python-dnspython: for dig lookup
    python-ovirt-engine-sdk: ovirt support
    python-boto3: aws_s3 module
    python-jmespath: json_query support
    acme-tiny: openssl_certificate module
(2/3) Installiere python-ruamel-yaml                                                                                                                                                           [#######################################################################################################################] 100%
(3/3) Installiere ansible-lint                                                                                                                                                                 [#######################################################################################################################] 100%
:: Starte post-transaction hooks...
(1/1) Arming ConditionNeedsUpdate...
[root@barney ansible]# 

Nachdem Installieren ist es wichtig seine zu managenden Nodes in der /etc/ansible/hosts einzutragen oder wer eine erheblich größere Infrastruktur hat, nutzt die Variante mit den Unterverzeichnissen. Für mich reicht die Variante mit den Textdateien allemale aus. Na dann trage ich mal die Nodes ein.

[root@barney bak]# nano /etc/ansible/hosts 

[glusterfs]
192.168.2.143
192.168.2.144
192.168.2.145
192.168.2.146
192.168.2.25

Damit sind die fünf Nodes des zu bauenden GlusterFS eingetragen und ich prüfe auch gleich mal, ob sie für Ansible erreichbar sind, in dem in den klassichen PING Test mache.

[ansible@barney ~]$ ansible all -m ping
192.168.2.144 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.2.146 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.2.143 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.2.145 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.2.25 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
[ansible@barney ~]$ 

Läuft ja wie geschmiert. Alle fünf Odroid-HC2 melden sich, ergo kann sich Ansible über SSH einloggen und Python Befehle absetzen. Dann werde ich den fünf mal ein kleines Playbook vorspielen lassen, was sie zum Software-Update animieren sollte. Das Playbook ist ganz kurz und stößt nur lokal den Update selbst an.

[ansible@barney ~]$ cat syu.yml 
---
- name: All hosts up-to-date
  hosts: glusterfs
  become: yes
  
  tasks:
    - name: full system upgrade
      pacman:
        update_cache: yes
        upgrade: yes

[ansible@barney ~]$ 

Fertig ist das Playbook. Ich habe das Playbook einmal mittels ansible-lint geprüft.

[ansible@barney ~]$ ansible-lint syu.yml 
[201] Trailing whitespace
syu.yml:5
  

[ansible@barney ~]$

Ja okay, es sind hinter dem Text tatäschlich noch Leerzeichen drin, kann ich mir verkneifen – ist aber funktional irrelevant. Na dann mal sehen, was die Nodes zu dem Playbook sagen..

[ansible@barney ~]$ ansible-playbook syu.yml

PLAY [All hosts up-to-date] ******************************************************

TASK [Gathering Facts] ***********************************************************
ok: [192.168.2.146]
ok: [192.168.2.143]
ok: [192.168.2.144]
ok: [192.168.2.145]
ok: [192.168.2.25]

TASK [full system upgrade] **************************************************************************************************************************************************************************************************************************************************************************************************
changed: [192.168.2.146]
changed: [192.168.2.144]
changed: [192.168.2.143]
changed: [192.168.2.145]
changed: [192.168.2.25]

PLAY RECAP ******************************************************************************************************************************************************************************************************************************************************************************************************************
192.168.2.143              : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.144              : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.145              : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.146              : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.25               : ok=2    changed=1    unreachable=0    failed=0   

[ansible@barney ~]$ 

Ich freue mich, dass alle fünf sich neue Software geholt haben und der Software Update gut durch lief. Natürlich kann man da noch deutlich mehr steuern, aber für den ersten Test reicht mir das aus. Nach dem Software Update wollte ich auch noch probieren, ob ich alle fünf gleichzeitig Rebooten kann. Dafür habe ich ebenso ein Playbook geschrieben. Auch kein weltbewegendes Zeug.

[ansible@barney ~]$ cat reboot.yml 
---
- name: All hosts reboot
  hosts: glusterfs
  become: yes
  
  tasks:
    - name: Unconditionally reboot the machine with all defaults
      reboot:

[ansible@barney ~]$ 

Und das Playbook werfe ich jetzt den fünf Nodes mal vor die Füße und wenn ich das richtig Verstanden habe, rebooten alle fünf Nodes.

[ansible@barney ~]$ ansible-playbook reboot.yml 

PLAY [All hosts reboot] **********************************************************

TASK [Gathering Facts] ***********************************************************
ok: [192.168.2.146]
ok: [192.168.2.143]
ok: [192.168.2.144]
ok: [192.168.2.145]
ok: [192.168.2.25]

TASK [Unconditionally reboot the machine with all defaults] **********************
changed: [192.168.2.143]
changed: [192.168.2.145]
changed: [192.168.2.146]
changed: [192.168.2.144]
changed: [192.168.2.25]

PLAY RECAP ***********************************************************************
192.168.2.143              : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.144              : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.145              : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.146              : ok=2    changed=1    unreachable=0    failed=0   
192.168.2.25               : ok=2    changed=1    unreachable=0    failed=0   

[ansible@barney ~]$ 

Ist schon ein merkwürdiges Gefühl, wenn da fünf Nodes gleichzeitig rebooten und man das Anlaufen ihrer HDDs hören kann.

Für meinen ersten Ansible Test bin ich zufrieden. In dem nächsten Beitrag werde ich mich Stück für Stück an das automatische Aufsetzen des GlusterFS machen. Denn wenn später weitere Nodes dazu kommen, möchte ich das ja genau mit Playbooks ausführen.