Preliminari

In più casi dovremo eseguire uno script su tutte i nodi, per farlo possiamo utilizzare il seguente script runsetup.sh:

#!/bin/bash
# Our custom function
file=$1
cust_func(){
 echo "I am ${url}"
 scp $file root@$url:/root/$file
 ssh root@$url chmod +x /root/$file
 NUMBER=$(echo $url | tr -dc '0-9')
 ssh root@$url /root/$file $NUMBER
}
while IFS= read -r url
do
        cust_func "$url $file" &
done < list.txt

wait
echo "All commands have been run."

seguito dal nome script.sh che vogliamo eseguire su tutte le macchine, e.g.,

./runsetup.sh script.sh

Installare un pacchetto apt su tutti i nodi

In alternativa allo script qui sopra, possiamo utilizzare il software Ansible. Dentro la home del nodo zero, troviamo il file inventory.ini con dentro i nomi dei nodi, e.g.,

[cluster_nodes]
steffe1 ansible_host=steffe1 ansible_user=root
steffe2 ansible_host=steffe2 ansible_user=root
steffe3 ansible_host=steffe3 ansible_user=root
steffe4 ansible_host=steffe4 ansible_user=root
steffe5 ansible_host=steffe5 ansible_user=root
steffe6 ansible_host=steffe6 ansible_user=root
steffe7 ansible_host=steffe7 ansible_user=root
steffe8 ansible_host=steffe8 ansible_user=root
steffe9 ansible_host=steffe9 ansible_user=root
steffe10 ansible_host=steffe10 ansible_user=root
steffe11 ansible_host=steffe11 ansible_user=root
steffe12 ansible_host=steffe12 ansible_user=root
steffe13 ansible_host=steffe13 ansible_user=root
steffe14 ansible_host=steffe14 ansible_user=root
steffe15 ansible_host=steffe15 ansible_user=root
steffe16 ansible_host=steffe16 ansible_user=root
steffe17 ansible_host=steffe17 ansible_user=root
steffe18 ansible_host=steffe18 ansible_user=root
steffe19 ansible_host=steffe19 ansible_user=root
steffe20 ansible_host=steffe20 ansible_user=root

Ed il file install_packages.yml con dentro il seguente contenuto:

---
- hosts: cluster_nodes
  become: yes  # Run tasks with sudo
  tasks:
    - name: Install the package
      apt:
        name:
        - exapmle-package-1
        - other-package-2
	# ...add more packages here
        state: present

Dove nome-pacchetto è il nome del pacchetto che vogliamo installare. Infine per lanciare l’installazione del pacchetto su tutti i nodi, eseguiamo il comando:

ansible-playbook -i inventory.ini install_packages.yml

Script di configurazione iniziale del nodo

Script runsetup.sh utilizzato per installare le macchine:

#!/bin/bash

mkdir /scratch

chown -R rock:rock /home/rock

# Don't ask for anything
export DEBIAN_FRONTEND=noninteractive

# Cambia il fuso orario
timedatectl set-timezone Europe/Rome

# Fixa la chiave pubblica delle repository (da https://forum.radxa.com/t/gpg-error-with-ubuntu-server-20-04/13392)
wget -O - apt.radxa.com/focal-stable/public.key | sudo apt-key add -

# Repository aggiuntive
add-apt-repository ppa:gluster/glusterfs-7 -y

# Update & Upgrade
apt update -y && apt upgrade -y

# Install required packages
apt -y install build-essential gcc openmpi-bin openmpi-common libopenmpi-dev glusterfs-server slurm python3-pip valgrind tree git curl man-db mc parallel neovim unrar atool

e che può essere eseguito come:

./runsetup.sh script.sh

Personalizzare la shell di login sul Nodo 0

Possiamo personalizzare il messaggio che appare al login sul nodo zero creando il seguente script /etc/update-motd.d/05-info:

#! /usr/bin/env bash

export TERM=xterm-256color

# Basic info
HOSTNAME=$(uname -n)
ROOT=$(df -Ph | grep mmcblk0p5 | awk '{print $4}' | tr -d '\n')
IFS=. read -r s _ < /proc/uptime; d=$((s / 60 / 60 / 24)); h=$((s / 60 / 60 % 24)); m=$((s / 60 % 60)); [ "$d" = 0 ] || UPTIME="${UPTIME}${d}d "; [ "$h" = 0 ] || UPTIME="${UPTIME}${h}h "; [ "$m" = 0 ] || UPTIME="${UPTIME}${m}m "; UPTIME="${UPTIME:-0m}"
KERNEL=$(uname -r)
RAID=$(lsblk -b -n -d | awk '$NF!~/sd[a-z]$/ && $NF!~/md[0-9]+$/ && !/loop/ {sum+=$4} END {printf "%.0f", sum/2/1024/1024/1024}')


TEMP_BIG="$(paste <(cat /sys/class/thermal/thermal_zone*/type) <(cat /sys/class/thermal/thermal_zone*/temp) | column -s $'\t' -t | sed 's/\(.\)..$/.\1°C'/ | head -1| cut -d' ' -f3 )"
TEMP_LITTLE="$(paste <(cat /sys/class/thermal/thermal_zone*/type) <(cat /sys/class/thermal/thermal_zone*/temp) | column -s $'\t' -t | sed 's/\(.\)..$/.\1°C/'| tail -1| cut -d' ' -f3 )"

# System load
MEMORY1=`free -t -m | grep Total | awk '{print $3" MB";}'`
MEMORY2=`free -t -m | grep "Mem" | awk '{print $2" MB";}'`
LOAD1=`cat /proc/loadavg | awk {'print $1'}`
LOAD5=`cat /proc/loadavg | awk {'print $2'}`
LOAD15=`cat /proc/loadavg | awk {'print $3'}`

echo "$(tput setaf 1)===================================================
 _______________________ _______ _______ _______ _
(  ____ \__   __(  ____ (  ____ (  ____ (  ____ ( )
| (    \/  ) (  | (    \/ (    \/ (    \/ (    \//
| (_____   | |  | (__   | (__   | (__   | (__
(_____  )  | |  |  __)  |  __)  |  __)  |  __)
      ) |  | |  | (     | (     | (     | (
/\____) |  | |  | (____/\ )     | )     | (____/
\_______)  )_(  (_______//      |/      (_______/
$(tput setaf 2)
===================================================
 - Hostname............: $HOSTNAME
 - Disk Space..........: $ROOT remaining
 - RAID Space..........: $RAID GB remaining
$(tput setaf 4)===================================================
 - CPU usage...........: $LOAD1, $LOAD5, $LOAD15 (1, 5, 15 min)
 - Memory used.........: $MEMORY1 / $MEMORY2
$(tput setaf 5)===================================================
 - Temperature big.....: $TEMP_BIG
 - Temperature little..: $TEMP_LITTLE
$(tput setaf 3)===================================================
 - Kernel..............: $KERNEL
 - Uptime..............: $UPTIME
$(tput sgr0)==================================================="

dnsmasq e rete interna

dnsmasq è un software libero che fornisce funzionalità di memorizzazione nella cache DNS (Domain Name System), un server DHCP (Dynamic Host Configuration Protocol), router advertisement e funzionalità di avvio di rete, destinato a reti di computer di piccole dimensioni. Nel nostro caso è utilizzato come server DHCP per assegnare gli indirizzi ai nodi del cluster sulla rete interna (NAT) e offrire un servizio di DNS (nodi raggiungibili tramite il loro nome e non tramite il loro indirizzo IP).

Configurazione dnsmasq

Per lanciare questo servizio è necessario liberare la porta 53 da eventuali servizi che la occupino già, su Ubuntu questo vuol dire disabilitare systemd-resolved.

sudo systemctl disable --now systemd-resolved
sudo apt install dnsmasq

impostare nel suo file di configurazione la funzione di DHCP de-commentando la riga dhcp-range nel file /etc/dnsmasq.conf, e finalmente lanciare il servizio:

sudo systemctl enable --now dnsmasq

Configurazione rete interna (NAT)

Per avere il funzionamento del nostro guarded Beowulf, abbiamo bisogno che il nodo 0, nodo di accesso, sia equipaggiato di due schede di rete. Una sarà connessa verso l’esterno, rendendo quindi il nodo un frontend per gli utenti, l’altra verso tutti i nodi del cluster. Dal punto di vista operativo, questo vuol dire che un pacchetto in arrivo dall’esterno dovrà essere tradotto di indirizzo per raggiugere i nodi interni della rete. Nell’ambito delle reti questa funzione è svolta dal network address translation (NAT), ovvero il meccanismo che permette proprio di modificare l’indirizzo IP dei pacchetti in transito attraverso un genrico apparato di rete - il nostro nodo 0 - all’interno di una comunicazione in corso tra un utente e gli altri nodi del cluster.

Su Ubuntu questo può essere fatto tramite il programma iptables:

sudo apt install iptables
echo "1" | sudo tee /proc/sys/net/ipv4/ip_forward
cd /etc/sysctl.d/
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/10-ip-forwarding.conf

In /etc/network/interfaces, per configurare permanentemente l’IP statico della macchina all’interno della LAN, l’IP pubblico e per creare la NAT del cluster:

auto eth0
iface eth0 inet static
        address 192.168.0.2
        netmask 255.255.255.0

auto enxd03745888a34
iface enxd03745888a34 inet static
        address 131.114.10.121
        netmask 255.255.255.0
        gateway 131.114.10.1
        post-up /usr/sbin/iptables -t nat -A POSTROUTING -o enxd03745888a34 -s 192.168.0.0/24 -j MASQUERADE

Infine:

sudo systemctl restart dnsmasq
sudo reboot

Dobbiamo inoltre comunicare il DNS a tutti i nodi in modo che possano risolvere le connessioni tra loro, ad esempio creando lo script fixdns.sh:

sudo echo "nameserver 192.168.0.2" >> /etc/resolvconf/resolv.conf.d/head
sudo ln -sf /run/resolvconf/resolv.conf /etc/resolv.conf
sudo resolvconf -u

ed eseguendo

./runsetup.sh fixdns.sh

GlusterFS

Creiamo la cartella che conterrà le home condivise in tutti i nodi e attiviamo il servizio GlusterFS:

sudo mkdir /data${i}
systemctl enable --now glusterd

Per installare GlusterFS dobbiamo per prima cosa permettere le connessioni dagli altri nodi attraverso il Firewall:

for i in {1..20}; do sudo iptables -I INPUT -p all -s steffe$i -j ACCEPT; done;

Ora dobbiamo far riconoscere i nodi tra di loro

sudo gluster peer probe steffe0
for i in {1..20}; do sudo gluster peer probe steffe${i}; done;

e creare il volume condiviso:

sudo gluster volume create data replica 3 transport tcp steffe0:/data0 steffe1:/data1 steffe2:/data2 steffe3:/data3 steffe4:/data4 steffe5:/data5 steffe6:/data6 steffe7:/data7 steffe8:/data8 steffe9:/data9 steffe10:/data10 steffe11:/data11 steffe12:/data12 steffe13:/data13 steffe14:/data14 steffe15:/data15 steffe16:/data16 steffe17:/data17 steffe18:/data18 steffe19:/data19 steffe20:/data20 force
sudo gluster volume start data
sudo gluster volume info

Possiamo quindi montare i dischi facendo

mkdir -p /mnt/data
mount -t glusterfs steffe0:data /mnt/data

(da mettere poi in /etc/fstab come steffe0:/data /mnt/data glusterfs defaults,_netdev 0 0)

Slurm Workload Manager

Slurm Workload Manager, precedentemente noto come Simple Linux Utility for Resource Management (SLURM), o semplicemente Slurm, è un job scheduler gratuito e open source per kernel Linux e Unix-like, utilizzato da molti dei supercomputer e cluster di computer del mondo.

Fornisce tre funzioni fondamentali:

Per prima cosa dobbiamo installare Slurm su tutti i nodi del cluster

sudo apt-get update -y
sudo apt-get install slurmd slurmctld -y

Generare un file di configurazione mediante il configuratore online e inserirne il contenuto in

/etc/slurm/slurm.conf

Al termine della configurazione avviare il servizio:

sudo systemctl enable --now slurmctld
sudo systemctl enable --now slurmd

Raid

Per controllare lo stato del raid, utilizzare

cat /proc/mdstat

NFS

Sul control node:

sudo apt install nfs-kernel-server

Configurare /etc/exports come desiderato, ad esempio

/mnt/raid     steffe1(rw,sync,no_root_squash,no_subtree_check)
...

e poi sudo systemctl restart nfs-kernel-server.service

Sui compute nodes:

mkdir -p /mnt/raid
mount -t nfs steffe0:/mnt/raid /mnt/raid

(da mettere poi in /etc/fstab come steffe0:/mnt/raid/ /mnt/raid nfs auto,nofail,noatime,nolock,intr,tcp,actimeo=1800 0 0)

Creazione utenti

Affinché un programma possa essere lanciato su tutti i nodi è necessario che l’utente sia disponibile su ciascuno di essi, il seguente script si occupa di replicare l’utente ovunque sia necessario:

#!/bin/bash
#
# Add a SLURM user to the Steffè cluster
# A good place for this script is in:
# /usr/local/bin/cluster-users

set -e

cluster_hosts="steffe1 steffe2 steffe3 steffe4 steffe5 steffe6 steffe7 steffe8 steffe9 steffe10 steffe11 steffe12 steffe13 steffe14 steffe15 steffe16 steffe17 steffe18 steffe19 steffe20"

function help {
  echo "Usage: $0 COMMAND username"
  echo ""
  echo "Examples: "
  echo "  -) $0 --add utente"
  echo "  -) $0 --delete utente"
}

function add_user {
    if [ "$1" == "" ]; then
      echo "Specify a valid username for the new user"
      exit 1
    fi

    if [ "$2" != "" ]; then
      echo "Unsupported option specified"
      exit 1
    fi

    sudo mkdir -p /mnt/raid/home
    sudo adduser --home=/mnt/raid/home/$1 $1

    # We obtain the UID of the new user, as the last line at the end
    # of passwd
    userid=$(grep $1 /etc/passwd | tail -n1 | cut -d ':' -f3)

    for h in ${cluster_hosts}; do
      echo -n "Creating the user $1 on $h ... "
      ssh root@${h} useradd -u ${userid} $1
      echo "done"
    done
}

function del_user {
    if [ "$1" == "" ]; then
      echo "Specify a valid username to delete"
      exit 1
    fi

    if [ "$2" != "" ]; then
      echo "Unsupported option specified"
      exit 1
    fi

    echo -n "This command will remove the user $1, proceed? [yn]: "
    read ans

    if [ "$ans" != "y" ]; then
      echo "Exiting"
      exit 0
    fi

    sudo userdel -f $1

    for h in ${cluster_hosts}; do
      echo -n "Deleting the user $1 on $h ... "
      ssh root@${h} userdel -f $1
      echo "done"
    done

    echo "Note: the home directory /mnt/raid/home/$1 has been preserved,"
    echo "      you may wish to delete that as well."
}

if [ "$1" == "--add" ]; then
  add_user $2
  exit 0
fi

if [ "$1" == "--delete" ]; then
  del_user $2
  exit 0
fi

help

Assicurarsi che tutto funzioni al riavvio del Cluster

  1. Montare RAID sul control node se non l’ha fatto in automatico (ovvero spegnere e riaccendere il case dei dischi dal power button, fa tutto /etc/fstab)
  2. Riavviare glusterd sui compute nodes (sicuramente non si sono avviati tutti prima del control node): [da rock]: ./runsetup.sh mountgluster.sh. Se /mnt/data non funziona da steffe0, riavviare il servizio mnt-data.mount (eventualmente anche sulle altre macchine, sta già in mountgluster.sh).
  3. Rimettere su i nodi (in modo che vadano tutti da DOWN ad IDLE nell’output di sinfo):
    for i in {1..20}; do sudo scontrol update nodename=steffe$i state=idle; done
    
  4. riavviare nfs-kernel-server.service su steffe0 (se steffe0 si accende prima degli altri non riesce a risolvere gli hostname all’avvio) e poi rimontare dappertutto con ./runsetup.sh mount-raid-nfs.sh (TODO {anche per gluster} trovare un modo di far funzionare gli FSTAB all’avvio…)