Browse Category: linux

Migrationsstrategie vom alten QNAP TS-412 zum GlusterFS

Nachdem ich jetzt mehrere Wochen mit dem GlusterFS rumgespielt habe, und auch reichlich Erkenntnise sammeln konte; wird es Zeit die Daten von meinem alten QNAP TS-412 zum GlusterFS zu verschieben.

Das klingt auf den ersten Moment sehr einfach, ist aber durch einige Randbedingungen nicht ganz so trivial, wie es klingt.

Vorraussetzungen/Bedarfsbeschreibung:

  • im QNAP stecken 4x 4TB HDD, die sollen im GlusterFS zum Einsatz kommen
  • ich habe nicht beliebig viel Geld, will also nur gerade soviel hinzu kaufen, wie umbedingt notwendig. ūüėČ
  • die neue Speicherkapazit√§t soll 7x 4TB sein
  • ich spendiere zwei spare HDDs

Sehen wir uns den bisherigen QNAP TS-412 im Detail an.

Ausgangssituation, die bisherige Datensenke

Das Zielbild im GlusterFS soll wie folgt sein.

Zielbild der neuen Datensenke

In meinem selbstgebauten GlusterFS Chassis sollen zuk√ľnftig 9 active Nodes stecken und ein spare Node (Brick), f√ľr den Fall das ich mal einen defekten Node durchtauschen muss. Die oben rechts hell grau hinterlegten Nodes sollen die beiden redundanten Nodes darstellen, ist nur zur Visualisierung (im GlusterFS kann und will man, in dem von mir angestgrebten Dispersed-Modus, die Funktion nicht direkt pro Node festlegen). Diese beiden Nodes m√ľssen von vorherein also immer anwesend sein. Der Spare Brick kommt sp√§ter, wenn der ganze GlusterFS l√§uft und dient nur als Cold-Spare, sollte einer der Nodes die Gr√§tsche machen.

Jetzt kommt die erste T√ľcke eines GlusterFS im Dispersed-Modus: die Anzahl der Nodes ist nicht Ver√§nderbar! Ich kann also nicht zur Laufzeit von 7 dispersed Nodes und 2 redundand Nodes auf sagen wir 10 dispersed Nodes und 2 redundand Nodes skalieren. Was geht ist, dass man neben einen dispersed GlusterFS einen weiteren (baugleichen) GlusterFS stellt und daraus einen Distributed Dispersed macht, aber die Gedanken an solches Konstrukt sind mir noch zu weit in der Ferne.. ūüôā

Was wiederum geht, ist die HDDs in einem Dispered GlusterFS zu vergr√∂ssern, wobei immer die kleinste HDD die Menge des zur Verf√ľgung stehenden Speichers angibt. Also in einem GlusterFS mit 7 x 4TB HDD dispersed + 2 x 4TB HDD replicated errechnet sich der Speicher nach den dispersed Nodes, entsprechend 7 x 4TB = 28TB.

Jetzt zeigt sich mein Dilemma, theoretisch br√§uchte ich neben den 9 x Odroid-HC2 ja nur 5 x 4TB HDDs kaufen, um die Ziel Best√ľckung zu erhalten. Doch wohin in der Phase des Umbaus mit den Daten?

Da es ist unwarscheinlich ist, dass ich diese HDDS f√ľr die Dauer von 1-2 Monaten kostenlos geliehen bekomme, muss ich mich diesem Problem anders n√§hern…

Zum Gl√ľck habe ich aus der vorherigen Best√ľckung des QNAP TS-412 noch 4 x 2TB HDD rumliegen. Fand diese immer f√ľr zu schade zum Wegwerfen, haben ja auch mal Geld gekostet!! Daraus ergibt sich folgende Option zu Best√ľckung.

Schritt 1 des neuen GlusterFS

F√ľr den ersten Schritt ben√∂tige ich also 9 x Odroid-HC2 (zu je ~65‚ā¨ = 585‚ā¨) und zus√§tzlich 5 x 4 TB HDDs (zu je ~190‚ā¨ = 950‚ā¨). Zusammen nicht ganz billig die Datensenke, aber daf√ľr sollte die n√§chste Jahre/Jahrzehnte ruhe sein bei der Frage, wohin mit den ganzen Filmen aus den Mediatheken der √∂ffentlich rechtlichen Fernsehsender.

Zur√ľck zu meinem Dilemma. Mit der Best√ľckung des GlusterFS in Schritt 1 mit den alten 2TB HDDs ist die Speicherkapazit√§t also 7 x 2TB = 14TB

Also genau 2TB kleiner das was ich ben√∂tige… knurr…

Und hier geht der Eiertanz dann auch los.

Ich kann also nachdem ich Schritt 1 aufgesetzt habe die Daten von JBOD1 oder JBOD2 im ganzen von alt nach neu verschieben. Nehmen wir jetzt einfach mal JBOD2 und verschieben dann die Daten auf der GlusterFS.

Dann sollte nach dem Kopieren der Daten, von der initial zur Verf√ľgung gestandenen Speicherkapazit√§t 14TB – 8TB = 6TB, also noch 6TB vorhanden sein. Ich kann dann also JBOD2 aufl√∂sen und die HDD3 und HDD4 aus dem QNAP TS-412 ziehen, platt machen und durch diese beiden gebrauchten 4TB HDDs den Gluster ein St√ľck umbauen – also folgendes Szenario ist das Ziel f√ľr Schritt 2.

Schritt 2, nach dem Auflösen von JBOD2

Durch das Verschieben der zwei HDDs sind jetzt zwei kleine HDDs frei geworden, also 2 x 2TB HDDs. Diese brauchen wir im nächsten Datenverschiebezyklus.

Jetzt sind in JBOD1 noch bummelig 8TB auf 6TB unter zu bringen, was nat√ľrlich nicht geht..

Die Krux aus dieser Situation ist, dass ich 6 TB auf das GlusterFS kopiere und die verbleibenden 2TB auf eine der beiden √ľbrig gebliebenen 2 TB HDDs. Wenn dann die letzten beiden 4TB HDDs in der QNAP TS-412 frei sind, kommt Schritt 3 des Umbaus.

Zielzustand ist erreicht

Nachdem 3 Umbau ist der Zielzustand erreicht und die Daten von der √ľbrig gebliebenen 2TB HDD werden auch auf das GlusterFS kopiert.

…Tadaa…

Soweit zu meiner Planung – ob es klappt, ich werde berichten.

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.

Linux Arch Installation auf den SDS Odroid-HC2 Nodes

Nachdem das PowerSupply und das Chassis f√ľr die Odroid-HC2 Bricks fertig ist, habe ich mich jetzt dem Software Defined Storage selbst zugewendet. Beim Formatieren der 10 microSD Karten viel mir sofort auf, das ist ein Anwendungsfall f√ľr eine Automatisierung. Leider ist das Installieren bis zum Aufsatzpunkt einer Automatisierung sehr Zeit intensiv.

Also fange ich mal an 10 microSD Karten platt zu machen, damit sie jungfräulich an den Start gehen.

[eric@Wintermute ~]$ sudo dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=8
8+0 Datensätze ein
8+0 Datensätze aus
8388608 bytes (8,4 MB, 8,0 MiB) copied, 0,131692 s, 63,7 MB/s
[eric@Wintermute ~]$ sync

Als n√§chstes hole ich mir ein Linux Arch Image f√ľr die kleinen Bricks. Dankenswerterweise hat sich das Team von Arch Linux ARM die M√ľhe gemacht, Images f√ľr die Installation von Linux Arch im monitorless Mode zu bauen, so da√ü ich die Installation nicht von Hand machen muss. Das hat bei Linux Arch immer etwas von Tierqu√§lerei..

Das Image und die genaue Erläuterung in englisch findet ihr unter:

https://archlinuxarm.org/platforms/armv7/samsung/odroid-hc2

Allerdings werde ich die 10 microSD Karten nicht einzeln nach dem Verfahren generieren und dann fertig machen bis Ansible sich einloggen kann. Da sitze ich ja in Tagen noch hier. Ich werde eine microSD Karte fertig machen und diese dann clonen. Also hole ich mir mal ein aktuelles Linux Arch Image f√ľr eine Installation ohne Monitor!

[eric@Wintermute ~]$ wget http://os.archlinuxarm.org/os/ArchLinuxARM-odroid-xu3-latest.tar.gz
--2019-03-25 19:00:32--  http://os.archlinuxarm.org/os/ArchLinuxARM-odroid-xu3-latest.tar.gz
Auflösen des Hostnamens os.archlinuxarm.org (os.archlinuxarm.org)… 50.116.36.110
Verbindungsaufbau zu os.archlinuxarm.org (os.archlinuxarm.org)|50.116.36.110|:80 … verbunden.
HTTP-Anforderung gesendet, auf Antwort wird gewartet … 302 Found
Platz: http://de4.mirror.archlinuxarm.org/os/ArchLinuxARM-odroid-xu3-latest.tar.gz [folgend]
--2019-03-25 19:00:32--  http://de4.mirror.archlinuxarm.org/os/ArchLinuxARM-odroid-xu3-latest.tar.gz
Auflösen des Hostnamens de4.mirror.archlinuxarm.org (de4.mirror.archlinuxarm.org)… 213.202.193.253
Verbindungsaufbau zu de4.mirror.archlinuxarm.org (de4.mirror.archlinuxarm.org)|213.202.193.253|:80 … verbunden.
HTTP-Anforderung gesendet, auf Antwort wird gewartet … 200 OK
Länge: 405576169 (387M) [application/octet-stream]
Wird in ¬ĽArchLinuxARM-odroid-xu3-latest.tar.gz¬ę gespeichert.

ArchLinuxARM-odroid-xu3-latest.tar.gz 100%[======================================================================>] 386,79M  5,65MB/s    in 69s      

2019-03-25 19:01:42 (5,59 MB/s) - ¬ĽArchLinuxARM-odroid-xu3-latest.tar.gz¬ę gespeichert [405576169/405576169]

Jetzt noch flott checken, ob mir da niemand M√ľll angedreht hat. F√ľr mich reicht da ein md5 check, wer es genauer mag und ganz sicher sein m√∂chte, findet auf dem Server auch gpg .tar.gz.sig File zu dem Archiv.

[eric@Wintermute ~]$ wget http://os.archlinuxarm.org/os/ArchLinuxARM-odroid-xu3-latest.tar.gz.md5
--2019-03-25 19:08:44--  http://os.archlinuxarm.org/os/ArchLinuxARM-odroid-xu3-latest.tar.gz.md5
Auflösen des Hostnamens os.archlinuxarm.org (os.archlinuxarm.org)… 50.116.36.110
Verbindungsaufbau zu os.archlinuxarm.org (os.archlinuxarm.org)|50.116.36.110|:80 … verbunden.
HTTP-Anforderung gesendet, auf Antwort wird gewartet … 302 Found
Platz: http://de6.mirror.archlinuxarm.org/os/ArchLinuxARM-odroid-xu3-latest.tar.gz.md5 [folgend]
--2019-03-25 19:08:44--  http://de6.mirror.archlinuxarm.org/os/ArchLinuxARM-odroid-xu3-latest.tar.gz.md5
Auflösen des Hostnamens de6.mirror.archlinuxarm.org (de6.mirror.archlinuxarm.org)… 2a01:4f8:c17:5ade::f00, 138.201.244.244
Verbindungsaufbau zu de6.mirror.archlinuxarm.org (de6.mirror.archlinuxarm.org)|2a01:4f8:c17:5ade::f00|:80 … verbunden.
HTTP-Anforderung gesendet, auf Antwort wird gewartet … 200 OK
Länge: 72 [application/octet-stream]
Wird in ¬ĽArchLinuxARM-odroid-xu3-latest.tar.gz.md5¬ę gespeichert.

ArchLinuxARM-odroid-xu3-latest.tar.gz 100%[======================================================================>]      72  --.-KB/s    in 0s      

2019-03-25 19:08:44 (4,79 MB/s) - ¬ĽArchLinuxARM-odroid-xu3-latest.tar.gz.md5¬ę gespeichert [72/72]

Jetzt noch eben md5sum gegen das Archiv laufen lassen und auf ein OK warten.

[eric@Wintermute ~]$ md5sum -c ArchLinuxARM-odroid-xu3-latest.tar.gz.md5
ArchLinuxARM-odroid-xu3-latest.tar.gz: OK
[eric@Wintermute ~]$ 

Md5sum hat den gepackten Tarball f√ľr verdaulich erachtet. Es kann also weiter gehen mit der Installation auf einer microSD Karte. Diese werde ich dann wie gesagt clonen.

Ich richte mich dabei nach der orginalen Anleitung, auf der oben verlinkten Website. Dann formatiere ich also mal die microSD Karte.

[eric@Wintermute ~]$ sudo fdisk /dev/mmcblk0

Willkommen bei fdisk (util-linux 2.33.1).
√Ąnderungen werden vorerst nur im Speicher vorgenommen, bis Sie sich
entscheiden, sie zu schreiben.
Seien Sie vorsichtig, bevor Sie den Schreibbefehl anwenden.

Gerät enthält keine erkennbare Partitionstabelle.
Eine neue DOS-Festplattenbezeichnung 0x5c6a4f4f wurde erstellt.

Befehl (m f√ľr Hilfe): o
Eine neue DOS-Festplattenbezeichnung 0xf4792cc4 wurde erstellt.

Befehl (m f√ľr Hilfe): p
Festplatte /dev/mmcblk0: 14,9 GiB, 15931539456 Bytes, 31116288 Sektoren
Einheiten: Sektoren von 1 * 512 = 512 Bytes
Sektorgröße (logisch/physikalisch): 512 Bytes / 512 Bytes
E/A-Größe (minimal/optimal): 512 Bytes / 512 Bytes
Festplattenbezeichnungstyp: dos
Festplattenbezeichner: 0xf4792cc4

Befehl (m f√ľr Hilfe): n
Partitionstyp
   p   Primär (0 primär, 0 erweitert, 4 frei)
   e   Erweitert (Container f√ľr logische Partitionen)
Wählen (Vorgabe p): p
Partitionsnummer (1-4, Vorgabe 1): 1
Erster Sektor (2048-31116287, Vorgabe 2048): 4096
Last sector, +/-sectors or +/-size{K,M,G,T,P} (4096-31116287, Vorgabe 31116287): 

Eine neue Partition 1 des Typs ‚ÄěLinux‚Äú und der Gr√∂√üe 14,9 GiB wurde erstellt.

Befehl (m f√ľr Hilfe): w
Die Partitionstabelle wurde verändert.
ioctl() wird aufgerufen, um die Partitionstabelle neu einzulesen.
Festplatten werden synchronisiert.

[eric@Wintermute ~]$ 

Sehen wir uns das mal genauer an, was FDisk da draus gemacht hat. Meine Erwartung ist ein nicht gemountet Blockdevice unter /dev/mmcblk0 mit einer Partition unter /dev/mmcblk0p1.

[eric@Wintermute ~]$ lsblk -TJ /dev/mmcblk0
{
   "blockdevices": [
      {"name":"mmcblk0", "maj:min":"179:0", "rm":false, "size":"14,9G", "ro":false, "type":"disk", "mountpoint":null,
         "children": [
            {"name":"mmcblk0p1", "maj:min":"179:1", "rm":false, "size":"14,9G", "ro":false, "type":"part", "mountpoint":null}
         ]
      }
   ]
}

Schaut soweit gut aus, die Partition ist genau da, wo ich sie erwartet habe. Dann geht es jetzt daran ein Filesystem auf der Partition zu installieren. Nach der Anleitung wird es ein ext4fs, womit ich f√ľr “/” leben kann. Allerdings arbeite ich pers√∂nlich lieber mit Labeln bei den Partitionen anstelle ihrer Position im /dev Baum. Meine Root Partion bekommt folglich auch das Label ROOT.

[eric@Wintermute ~]$ sudo mkfs.ext4 -v /dev/mmcblk0p1 -L ROOT
mke2fs 1.45.0 (6-Mar-2019)
Dateisystemtypen f√ľr das Aufschl√ľsseln von mke2fs.conf: 'ext4'
Geräteblöcke werden verworfen: erledigt                        
Dateisystembezeichnung=ROOT
OS-Typ: Linux
Blockgröße=4096 (log=2)
Fragmentgröße=4096 (log=2)
Stride=0 Blöcke, Stripebreite=0 Blöcke
972944 Inodes, 3889024 Blöcke
194451 Bl√∂cke (5.00%) reserviert f√ľr den Superuser
Erster Datenblock=0
Maximale Dateisystem-Blöcke=2151677952
119 Blockgruppen
32768 Blöcke pro Gruppe, 32768 Fragmente pro Gruppe
8176 Inodes pro Gruppe
UUID des Dateisystems: 5ab9cbdc-0a5f-4fe8-8910-afdda9eafb81
Superblock-Sicherungskopien gespeichert in den Blöcken: 
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208

beim Anfordern von Speicher f√ľr die Gruppentabellen: erledigt                        
Inode-Tabellen werden geschrieben: erledigt                        
Das Journal (16384 Blöcke) wird angelegt: erledigt
Die Superbl√∂cke und die Informationen √ľber die Dateisystemnutzung werden
geschrieben: erledigt

[eric@Wintermute ~]$ 

Na das lief doch wie gew√ľnscht gut durch, es gibt jetzt also auf /dev/mmcblk0 eine Partition /dev/mmcblk0p1 mit ext4 Filesystem. Dann mounten wir die mal ins bestehende Filesystem hinein, um Daten darauf schreiben zu k√∂nnen.

[eric@Wintermute ~]$ mkdir root
[eric@Wintermute ~]$ sudo mount /dev/mmcblk0p1 root

Ist ohne Wiederworte eingehängt. Jetzt kommt der Teil mit dem Daten aus dem gezippten Tarball auspacken. Dabei ist es wichtig wirklich ROOT zu sein, ein sudo hilft hier nicht.

[eric@Wintermute ~]$ su
Passwort: ALIGATOR3 

root@Wintermute eric]# bsdtar -vxpf ArchLinuxARM-odroid-xu3-latest.tar.gz -C root
x ./bin
x ./boot/
x ./boot/tzsw.bin
x ./boot/dtbs/
x ./boot/initramfs-linux.img
x ./boot/sd_fusing.sh
x ./boot/mkscr
x ./boot/boot.txt
x ./boot/zImage
x ./boot/u-boot.bin
x ./boot/bl2.bin
<snip>
x ./var/db/nscd/
x ./var/db/Makefile
x ./var/log/lastlog
x ./var/log/wtmp
x ./var/log/journal/
x ./var/log/audit/
x ./var/log/btmp
x ./var/log/private/
x ./var/log/old/
x ./var/log/journal/remote/
[root@Wintermute eric]# 

Nicht erschrecken, wenn du das selber machst, aus dem Tarball plumpst ein ganzes Linuxsystem raus. ūüôā

Damit der Odroid-HC2 von der microSD Karte booten kann, ist noch etwas Bootcode im Bootsektor der microSD Karte notwendig. Den schaufeln wir da per mitgeliefertem Script hin.

[root@Wintermute eric]# cd root
[root@Wintermute root]# cd boot

[root@Wintermute boot]# pwd
/home/eric/root/boot

[root@Wintermute boot]# sh sd_fusing.sh /dev/mmcblk0
/dev/mmcblk0 reader is identified.
BL1 fusing
30+0 Datensätze ein
30+0 Datensätze aus
15360 bytes (15 kB, 15 KiB) copied, 0,0379681 s, 405 kB/s
BL2 fusing
28+1 Datensätze ein
28+1 Datensätze aus
14592 bytes (15 kB, 14 KiB) copied, 0,111764 s, 131 kB/s
u-boot fusing
1123+1 Datensätze ein
1123+1 Datensätze aus
575173 bytes (575 kB, 562 KiB) copied, 1,65022 s, 349 kB/s
TrustZone S/W fusing
512+0 Datensätze ein
512+0 Datensätze aus
262144 bytes (262 kB, 256 KiB) copied, 0,574115 s, 457 kB/s
U-boot image is fused successfully.
[root@Wintermute boot]# 

Selbst nach etlichen Jahren mit Linux mag ich so ein Bootcode rumgeschraube nicht wirklich..

Nach dem Bootcode √úbertragen ist das Linux Arch auf der microSD Karte drauf und ich nehme sie wieder aus dem Filesystem raus.

[root@Wintermute boot]# cd ../..
[root@Wintermute eric]# pwd
/home/eric
[root@Wintermute eric]# umount root
[root@Wintermute eric]# rmdir root
[root@Wintermute eric]# 

Noch ein letzter pr√ľfender Blick auf die microSD Karte.

[root@Wintermute eric]# lsblk -f /dev/mmcblk0
NAME        FSTYPE LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
mmcblk0                                                                      
‚ĒĒ‚ĒÄmmcblk0p1 ext4   ROOT  5ab9cbdc-0a5f-4fe8-8910-afdda9eafb81                
[root@Wintermute eric]# 

Es sieht gut aus und die microSD Karte kann in einen Odroid-HC2, um dann das Linux Arch von der Karte das erstemal zu Booten und dem System alle notwendigen Pakete f√ľr eine weiteres Setup mit Ansible zu verpassen.

Nachdem das Lampenspiel an dem Odroid-HC2 darauf schließen liess, das er online ist, habe ich auf meinem DHCP Server mal nachgesehen und tatsächlich, da ist eine 192.168.2.146 aufgetaucht. Eine kurze Kontrolle, ob die IPv4 Adresse auch lebt.

[root@Wintermute eric]# ping 192.168.2.146
PING 192.168.2.146 (192.168.2.146) 56(84) bytes of data.
64 bytes from 192.168.2.146: icmp_seq=1 ttl=64 time=1.44 ms
64 bytes from 192.168.2.146: icmp_seq=2 ttl=64 time=0.566 ms
64 bytes from 192.168.2.146: icmp_seq=3 ttl=64 time=0.733 ms
64 bytes from 192.168.2.146: icmp_seq=4 ttl=64 time=0.619 ms
^X^C
--- 192.168.2.146 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 24ms
rtt min/avg/max/mdev = 0.566/0.840/1.442/0.352 ms
[root@Wintermute eric]# 

Es scheint dem Odroid-HC2 gut zu gehen, zumindest antwortet jemand auf das Geklopfe an der Netzwerkkarte. Laut Anleitung ist er per SSH unter dem User alarm, mit dem Passwort alarm erreichbar. Pr√ľfen wir das mal und sehen auch gleich mal nach der Linux Version und den CPU Kernen des Odroid-HC2.

[root@Wintermute eric]# ssh -l alarm 192.168.2.146
alarm@192.168.2.146's password: alarm

[alarm@alarm ~]$ cat /proc/version
Linux version 4.14.102-1-ARCH (builduser@leming) (gcc version 8.2.1 20181127 (GCC)) #1 SMP PREEMPT Sat Feb 23 19:59:09 UTC 2019

[alarm@alarm ~]$ cat /proc/cpuinfo 
processor       : 0
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 90.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 3

processor       : 1
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 90.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 3

processor       : 2
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 90.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 3

processor       : 3
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 90.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 3

processor       : 4
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 120.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc0f
CPU revision    : 3

processor       : 5
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 120.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc0f
CPU revision    : 3

processor       : 6
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 120.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc0f
CPU revision    : 3

processor       : 7
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 120.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc0f
CPU revision    : 3

Hardware        : SAMSUNG EXYNOS (Flattened Device Tree)
Revision        : 0100
Serial          : 0000000000000000
[alarm@alarm ~]$ 

Es ist schon sehr verbl√ľffend, dass dieses kleine Board in dem Odroid-HC2 mit 8 Kernen daher kommt. Eine wirklich sehr potente Plattform!

Weiter geht es mit dem Anlegen und Abgleichen des Pacman Schl√ľsselrings, in welchem die Vertrauensw√ľrdigkeiten zu den Softwareentwicklern liegen.

[alarm@alarm ~]$ pacman-key --init
==> ERROR: pacman-key needs to be run as root for this operation.

[alarm@alarm ~]$ su
Password: root

[root@alarm alarm]# pacman-key --init
gpg: /etc/pacman.d/gnupg/trustdb.gpg: trustdb created
gpg: no ultimately trusted keys found
gpg: starting migration from earlier GnuPG versions
gpg: porting secret keys from '/etc/pacman.d/gnupg/secring.gpg' to gpg-agent
gpg: migration succeeded
gpg: Generating pacman keyring master key...
gpg: key 2AC1C92126FEB3C8 marked as ultimately trusted
gpg: directory '/etc/pacman.d/gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/etc/pacman.d/gnupg/openpgp-revocs.d/3189C9A002F783227C42CD222AC1C92126FEB3C8.rev'
gpg: Done
==> Updating trust database...
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u

[root@alarm alarm]# pacman-key --populate archlinuxarm
==> Appending keys from archlinuxarm.gpg...
==> Locally signing trusted keys in keyring...
  -> Locally signing key 69DD6C8FD314223E14362848BF7EEF7A9C6B5765...
  -> Locally signing key 02922214DE8981D14DC2ACABBC704E86B823CD25...
  -> Locally signing key 9D22B7BB678DC056B1F7723CB55C5315DCD9EE1A...
==> Importing owner trust values...
gpg: setting ownertrust to 4
gpg: inserting ownertrust of 4
gpg: setting ownertrust to 4
==> Updating trust database...
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   3  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: depth: 1  valid:   3  signed:   1  trust: 0-, 0q, 0n, 3m, 0f, 0u
gpg: depth: 2  valid:   1  signed:   0  trust: 1-, 0q, 0n, 0m, 0f, 0u
[root@alarm alarm]# 

Wenn ich schonmal auf dem Odroid-HC2 bin, mache ich auch gleich einen Softwareupdate. Vorweg sei gesagt, dass ich erstaunt bin wie wenig er sich aus dem Repository abholt. Die Images scheinen sehr gut gepflegt zu werden.

[root@alarm alarm]# pacman -Syu
:: Synchronizing package databases...
 core                                                            215.4 KiB  2.50M/s 00:00 [####################################################] 100%
 extra                                                             2.2 MiB  5.96M/s 00:00 [####################################################] 100%
 community                                                         5.1 MiB  4.70M/s 00:01 [####################################################] 100%
 alarm                                                           117.3 KiB  3.18M/s 00:00 [####################################################] 100%
 aur                                                               5.2 KiB  0.00B/s 00:00 [####################################################] 100%
:: Starting full system upgrade...
resolving dependencies...
looking for conflicting packages...

Packages (15) bash-5.0.002-1  ca-certificates-mozilla-3.43-1  coreutils-8.31-1  e2fsprogs-1.45.0-1  glib2-2.60.0-1  gnupg-2.2.14-1
              iana-etc-20190308-1  iproute2-5.0.0-1  libgpg-error-1.36-1  libseccomp-2.4.0-1  libssh2-1.8.1-1  linux-firmware-20190313.efd2c1c-1
              linux-odroid-xu3-4.14.107-1  s-nail-14.9.13-2  texinfo-6.6-1

Total Download Size:   107.30 MiB
Total Installed Size:  559.27 MiB
Net Upgrade Size:        1.78 MiB

:: Proceed with installation? [Y/n] 
:: Retrieving packages...
 iana-etc-20190308-1-any                                         

<snip>

==> Creating gzip-compressed initcpio image: /boot/initramfs-linux.img
==> Image generation successful
(2/7) Reloading system manager configuration...
(3/7) Creating temporary files...
(4/7) Reloading device manager configuration...
(5/7) Arming ConditionNeedsUpdate...
(6/7) Updating the info directory file...
(7/7) Rebuilding certificate stores...
[root@alarm alarm]# 

Damit Ansible zum Fliegen kommt, benötige ich noch die Pakete Python und Sudo. Welche ich gleich installiere, dabei wird Pacman die Abhängigkeiten automatisch Auflösen und die notwendigen weiteren Pakete selbstständig nachinstallieren.

[root@alarm alarm]# pacman -S sudo python
resolving dependencies...
looking for conflicting packages...

Packages (3) libnsl-1.2.0-1  python-3.7.2-3  sudo-1.8.27-1

Total Download Size:    16.42 MiB
Total Installed Size:  111.02 MiB

:: Proceed with installation? [Y/n] 
:: Retrieving packages...
 sudo-1.8.27-1-armv7h                                            715.7 KiB  10.9M/s 00:00 [####################################################] 100%
 libnsl-1.2.0-1-armv7h                                            51.2 KiB  2.50M/s 00:00 [####################################################] 100%
 python-3.7.2-3-armv7h                                            15.7 MiB  12.2M/s 00:01 [####################################################] 100%
(3/3) checking keys in keyring                                                            [####################################################] 100%
(3/3) checking package integrity                                                          [####################################################] 100%
(3/3) loading package files                                                               [####################################################] 100%
(3/3) checking for file conflicts                                                         [####################################################] 100%
(3/3) checking available disk space                                                       [####################################################] 100%
:: Processing package changes...
(1/3) installing sudo                                                                     [####################################################] 100%
(2/3) installing libnsl                                                                   [####################################################] 100%
(3/3) installing python                                                                   [####################################################] 100%
Optional dependencies for python
    python-setuptools
    python-pip
    sqlite [installed]
    mpdecimal: for decimal
    xz: for lzma [installed]
    tk: for tkinter
:: Running post-transaction hooks...
(1/2) Creating temporary files...
(2/2) Arming ConditionNeedsUpdate...
[root@alarm alarm]# 

Ich pr√ľfe lieber nach, was f√ľr ein Python da genau installiert wurde. Dabei geht es mir darum, ob es sich um Python2 oder Python3 handelt.

[root@alarm alarm]# ls -la /usr/bin/python*
lrwxrwxrwx 1 root root    7 Jan 13 15:56 /usr/bin/python -> python3
lrwxrwxrwx 1 root root   14 Jan 13 15:56 /usr/bin/python-config -> python3-config
lrwxrwxrwx 1 root root    9 Jan 13 15:56 /usr/bin/python3 -> python3.7
lrwxrwxrwx 1 root root   16 Jan 13 15:56 /usr/bin/python3-config -> python3.7-config
-rwxr-xr-x 2 root root 5540 Jan 13 15:56 /usr/bin/python3.7
lrwxrwxrwx 1 root root   17 Jan 13 15:56 /usr/bin/python3.7-config -> python3.7m-config
-rwxr-xr-x 2 root root 5540 Jan 13 15:56 /usr/bin/python3.7m
-rwxr-xr-x 1 root root 3180 Jan 13 15:56 /usr/bin/python3.7m-config
[root@alarm alarm]# 

Sehr sch√∂n, es ist ein aktuelles Python3 geworden, was mich beruhigt – denn ich mag einem neuen System auch gerne ein frisches Python3. ūüėČ

Damit Ansible auch auf dem System rumfuhrwerken kann, bedarf es eines Users mit sudo-Rechten ohne Passwort abfrage. Ich f√ľr meinen Teil nehme gerne den User ansible und verteile den SSH Schl√ľssel des Users ansible vom Control Server (master) auf die zu betreuenden Systeme (nodes), aber da gibt es verschiedene Ans√§tze.

[root@alarm alarm]# useradd -m -g users -s /bin/bash ansible
       
[root@alarm alarm]# passwd ansible
New password: SUPERGEHEIM 
Retype new password: SUPERGEHEIM

passwd: password updated successfully
[root@alarm alarm]# 

Okay, f√ľr den ein oder anderen stellt sich die Frage wie kriege ich diese SSH-Keys vom Master zu den Nodes. Hier eine kurze Darstellung des Ablaufs auf dem Master, ab der Stelle… es gibt keinen User ansible auf der Master. ūüėČ

eric@barney ~]$ sudo useradd -m -g users -s /bin/bash ansible
[sudo] Passwort f√ľr eric: 

[eric@barney ~]$ sudo passwd ansible
Geben Sie ein neues Passwort ein: SUPERGEHEIM 
Geben Sie das neue Passwort erneut ein: SUPERGEHEIM
passwd: Passwort erfolgreich geändert

[ansible@barney ~]$ ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/ansible/.ssh/id_ed25519): 
Created directory '/home/ansible/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/ansible/.ssh/id_ed25519.
Your public key has been saved in /home/ansible/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:B4A2txMe0ho2TcF0S4gMH3BIxJhvrIjra1JpuWF4WYw ansible@barney
The key's randomart image is:
+--[ED25519 256]--+
<snip>
+----[SHA256]-----+


[ansible@barney ~]$ ls -la .ssh/
insgesamt 16
drwx------ 2 ansible users 4096 25. Mär 19:58 .
drwx------ 3 ansible users 4096 25. Mär 19:57 ..
-rw------- 1 ansible users  411 25. Mär 19:58 id_ed25519
-rw-r--r-- 1 ansible users   96 25. Mär 19:58 id_ed25519.pub

[ansible@barney ~]$ ssh-copy-id -i ~/.ssh/id_ed25519.pub ansible@192.168.2.146
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/ansible/.ssh/id_ed25519.pub"
The authenticity of host '192.168.2.146 (192.168.2.146)' can't be established.
ECDSA key fingerprint is SHA256:tatuetata.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
ansible@192.168.2.146's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'ansible@192.168.2.146'"
and check to make sure that only the key(s) you wanted were added.

[ansible@barney ~]$ ssh ansible@192.168.2.146

[ansible@alarm ~]$ ls -la
total 24
drwx------ 3 ansible users 4096 Mar 25 20:01 .
drwxr-xr-x 4 root    root  4096 Mar 25 19:50 ..
-rw-r--r-- 1 ansible users   21 Mar 16 15:27 .bash_logout
-rw-r--r-- 1 ansible users   57 Mar 16 15:27 .bash_profile
-rw-r--r-- 1 ansible users  141 Mar 16 15:27 .bashrc
drwx------ 2 ansible users 4096 Mar 25 20:01 .ssh
[ansible@alarm ~]$ 

Der User ansible kann sich ohne Passwort vom Master im Node, mittels SSH einloggen. Jetzt fehlen nur noch die sudo Rechte f√ľr den User ansible, damit der Ansible Master auf dem Node auch √Ąnderungen am System vornehmen kann.

[ansible@alarm ~]$ su
Password: GEHEIM

[root@alarm ansible]# visudo

ansible ALL=(ALL) NOPASSWD: ALL

[root@alarm ansible]# visudo -c    
/etc/sudoers: parsed OK
[root@alarm ansible]#       

Wie immer erfolgt danach eine kurze Kontrolle, ob der User sich einloggen und auch tats√§chlich ohne Passwortabfrage sudo ausf√ľhren darf.

[ansible@barney ~]$ ssh ansible@192.168.2.146
Last login: Mon Mar 25 20:15:11 2019 from 192.168.2.10

[ansible@alarm ~]$ ls -lah /root/
ls: cannot open directory '/root/': Permission denied

[ansible@alarm ~]$ sudo ls -lah /root/
total 16K
drwxr-x---  3 root root 4.0K Mar 25 19:55 .
drwxr-xr-x 16 root root 4.0K Mar 25 18:57 ..
-rw-------  1 root root  305 Mar 25 20:15 .bash_history
drwx------  3 root root 4.0K Mar  9 01:57 .gnupg
[ansible@alarm ~]$ 

Des gew√ľnschte Setup ist erreicht – JUPIEEE.

Die Replikation von der nun fertigen microSD Karte auf die restlichen neuen Karten kann starten. Fahren wir also sanft unseren ersten Node runter.

[ansible@alarm ~]$ sudo shutdown -P now
Connection to 192.168.2.146 closed by remote host.
Connection to 192.168.2.146 closed.
[ansible@barney ~]$ 

Anschließend ziehe ich mir von der microSD Karte ein Image mittels dd. Der Ouput beim Image ziehen sieht dann wiefolgt aus.

[root@Wintermute GlusterFS-Image]# sudo dd if=/dev/mmcblk0 of=20190325_GlusterFS-Node.img bs=4M status=progress
8824815616 bytes (8,8 GB, 8,2 GiB) copied, 110 s, 80,2 MB/s
[root@Wintermute GlusterFS-Image]#

Auf eine leere microSD Karte Schreiben sieht dann auch nicht wesentlich anders aus. Nur dann halt neunmal nach einander.

[root@Wintermute GlusterFS-Image]# sudo dd if=20190325_GlusterFS-Node.img of=/dev/mmcblk0 bs=4M status=progress
4970250240 bytes (5,0 GB, 4,6 GiB) copied, 6 s, 825 MB/s
3798+1 Datensätze ein
3798+1 Datensätze aus
15931539456 bytes (16 GB, 15 GiB) copied, 1163,94 s, 13,7 MB/s
[root@Wintermute GlusterFS-Image]#

Das einzig Blöde ist nur, dass diese microSD Karten nicht gerade Raketen beim Schreiben sind. Am Ende dauert es pro microSD Karte ~20Minuten. Zeit etwas Grand Tour zu sehen und nebenbei die ganzen Karten fertig zu machen. Mehr zum Aufsetzen des GlusterFS kommt dann im nächsten Beitrag.

VSFTPD auf Linux Arch geht nicht mehr..

Wer von euch auch einen VSFTPd nutzt auf seiner NextCloud zum Ablegen von Scans von seinem MultiFunktionsPrinter/Scanner benötigt seit dem letzten Update eine eigene Datei in /etc/pam.d/vsftpd

%PAM-1.0
auth required pam_unix.so
account required pam_unix.so
password required pam_unix.so
session required pam_unix.so

In der /etc/vsftpd.conf muss dazu noch ein Eintrag gemacht werden, damit der VSFTPd dann auch die PAM nutzt.

pam_service_name=vsftpd

Größe einer .vmdk unter Virtualbox anpassen

Nun war es soweit, meine .vmdk war bis zum letzten GByte voll gelaufen und leider kann man unter VirtualBox, anders als unter VMware, nicht einfach ein resize der .vmdk machen. Das geht bei VirtualBox nur √ľber einen kleinen Umweg.

Am besten ist es, in der VirtualBox zuerst die .vmdk in der Gastsystem zu trennen/detach. Anschließend wird die .vmdk in eine .vdi gecloned:

vboxmanage clonehd ./DISK1.vmdk clone.vdi –format vdi

Sollte er dabei zu reichlich Fehlermeldungen in der Bash folgender Art kommen:

VBoxManage: error: Cannot register the hard disk ‘/DISK1.vmdk’ {730daceb-67b6-4e5f-993a-9808fghijklmnopq because a hard disk ‘/DISK1.vmdk’ with UUID {730daceb-67b6-4e5f-993a-9808fghijklmnopq} already exists
VBoxManage: error: Details: code NS_ERROR_INVALID_ARG (0x80070057), component VirtualBoxWrap, interface IVirtualBox, callee nsISupports
VBoxManage: error: Context: “OpenMedium(Bstr(pszFilenameOrUuid).raw(), enmDevType, enmAccessMode, fForceNewUuidOnOpen, pMedium.asOutParam())” at line 179 of file VBoxManageDisk.cpp

Muss die UUID √ľberschrieben werden, das geht mit einem internen Befehl bei VirtualBox:

vboxmanage internalcommands sethduuid ./DISK1.vmdk

Und dann sollte das Clonen wie folgt flutschen:

vboxmanage clonehd ./DISk1.vmdk clone.vdi –format vdi

0%…10%…20%…30%…40%…50%…60%…70%…80%…90%…100%
Clone medium created in format ‘vdi’. UUID: 24605153-4237-46e5-b41f-zz1k32a38

Bei einer .vdi kann VirtualBox die Größe anpassen. Ich wollte 100GByte haben also lautete das bei mir wie folgt:

VBoxManage modifyhd ./clone.vdi –resize 102400

Auch hier folgte eine kleine Beschwerde auf der Bash:

VBoxManage: error: Cannot register the hard disk ‘./clone.vdi’ {24605153-4237-46e5-b41f-zz1k32a38} because a hard disk ‘/home/eric/VirtualBox VMs/Computacenter/clone.vdi’ with UUID {24605153-4237-46e5-b41f-zz1k32a38} already exists
VBoxManage: error: Details: code NS_ERROR_INVALID_ARG (0x80070057), component VirtualBoxWrap, interface IVirtualBox, callee nsISupports
VBoxManage: error: Context: “OpenMedium(Bstr(pszFilenameOrUuid).raw(), enmDevType, enmAccessMode, fForceNewUuidOnOpen, pMedium.asOutParam())” at line 179 of file VBoxManageDisk.cpp

Also erneut die UUID setzen, diesmal halt bei der .vdi Datei..

vboxmanage internalcommands sethduuid ./clone.vdi

Mir ist zwar nicht klar, warum das f√ľr die VirtualBox jedesmal in “rum Trompeten” endet… aber nun ja. Jetzt l√§√üt sich bei der .vdi endlich die Gr√∂√üe anpassen. In meinem Falle wie gesagt sollte die .vdi auf 100GByte erh√∂ht werden ohne den Inhalt zu ver√§ndern. Das geht nun endlich mittels:

VBoxManage modifyhd ./clone.vdi –resize 102400

0%…10%…20%…30%…40%…50%…60%…70%…80%…90%…100%

Kurzer Check ob alles da ist:

ls -la

insgesamt 146302296
drwx—— 4 eric users 4096 11. Jul 12:09 .
drwx—— 3 eric users 4096 11. Jul 10:33 ..
-rw——- 1 eric users 74624991232 11. Jul 12:09 DISK1.vmdk
-rw——- 1 eric users 8454 11. Jul 10:33 Arbeitsbox.vbox
-rw——- 1 eric users 75190239232 11. Jul 12:13 clone.vdi

Sieht soweit gut aus, jetzt wieder zur√ľck von der .vdi zu einer .vmdk.

vboxmanage clonehd ./clone.vdi ./DISK2.vmdk –format vmdk

0%…10%…20%…30%…40%…50%…60%…70%…80%…90%…100%
Clone medium created in format ‘vmdk’. UUID: 8f315836-5209-48f5-a3c9-zzk3@@alpha

Die fertige .vmdk nun noch in der Arbeits.vbox an den virtuellen SATA Controller gebunden und fertig ist das Resize. Hat gute 2h gedauert… Noch aufr√§umen auf der HDD:

rm clone.vdi

rm DISK1.vmdk

Linux Arch Netctl

Ich hab mich von Zeit zu Zeit, also eigentlich immer nach einem Softwareupdate per pacman, dar√ľber √§rgern d√ľrfen, dass das Ethernet Interface nicht mit statischer Adresse hoch kam. Es sah so aus, als ob das Interface auf dem Switch einfach zu sp√§t seinen Link aktiviert kriegt. Das Problem l√§√üt sich fixen, in dem man in der netctl.config des Interfaces einfach folgendes mit einf√ľgt “SkipNoCarrier=yes” und siehe da, das Interface rappelt sich einfach etwas sp√§ter aber mit statischer Adresse.

Terratec Noxon Radio2IPod / MPD Stream

auch kleine Erfolge bereiten mir ja immer freude. Nachdem das Setup f√ľr den MPD/Icecast gefunden ist l√§uft der Stream jetzt auch problemlos in der K√ľche auf dem guten Terratec Noxon Radio2IPod, wird auch sauber aks 256KBps Stream √ľber Wlan angezeigt.

Stellt sich nur noch die Frage, was stelle ich eigentlich ins Bad?

Finales MPD Setup

Der Fehler mit dem Rauschen bei einigen MP3 Files ist offenbar viel trivialer als gedacht. Der Fehler tritt dann auf, wenn man den MPD im Modus “–no-daemon” betreibt. L√§uft er als Daemon kommt das rosa Rauschen nicht mehr vor. Ob im Kernel 4.8.13-1-arch oder 4.9.6-1-arch ist dabei Wurscht. Meine entg√ľltige Konfig f√ľr den MPD sieht jetzt wie folgt aus:
pid_file “/run/mpd/mpd.pid”
db_file “/var/lib/mpd/mpd.db”
state_file “/var/lib/mpd/mpdstate”
playlist_directory “/var/lib/mpd/playlists”
log_file “/var/log/mpd.log”

bind_to_address “127.0.0.1”
bind_to_address “192.168.2.256”
port “6600”

music_directory “/mnt/musichdd/Musik/”

audio_output {
type¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “shout”
encoding¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “mp3”
name¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “mp3 stream”
host¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “hotte”
port¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “8000”
mount¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “/mpd.mp3”
bitrate¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “256”
format¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “44100:32:2”
always_on¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “yes”¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† # prevent MPD from disconnecting all listeners when playback is stopped.
tags¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “yes”¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† # httpd supports sending tags to listening streams.
protocol¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “icecast2”
user¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “source”
password¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† “xxxxxxxxx”
}

# Need this so that mpd still works if icecast is not running
audio_output {
type “null”
name “fake out”
}

  • 1
  • 2