Labor-VM mit Vagrant erstellen

Die Verwendung von vorkonfigurierten virtuellen Maschinen (VMs) hat sich gerade für Laborveranstaltungen bewährt. Dadurch erhalten alle Teilnehmer eine identische Arbeitsumgebung für die Übungen, außerdem verringert sich der (technische) Betreuungsaufwand enorm.

Am Beispiel eines Datenbank-Labors (FIXME Link auf Git-Repo) wird hier gezeigt, wie man eine solche VM konfiguriert und größtenteils automatisch erzeugt. Am Ende steht eine OVA-Datei (open virtualization format), die dann an die Teilnehmer verteilt werden kann. Das Format ist herstellerübergreifend und kann somit in jede gängige Virtualisierungs-Software (z.B. VirtualBox, VmWare) importiert werden.

Vagrant

Vagrant ist eines von mehreren verfügbaren Werkzeugen zur Erzeugung von VMs. Die zentrale Konfiguration steht dabei in einem Vagrantfile.

Beispiel für Vagrantfile

vagrant-vm/Vagrantfile (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

# cf. https://github.com/hashicorp/vagrant/issues/8878#issuecomment-345112810
class VagrantPlugins::ProviderVirtualBox::Action::Network
  def dhcp_server_matches_config?(dhcp_server, config)
    true
  end
end

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.define "moderne_dbs" do |moderne_dbs|
    moderne_dbs.vm.box = "debian/bullseye64"
    moderne_dbs.vm.network "private_network", type: "dhcp"
    moderne_dbs.vm.provider "virtualbox" do |vb|
      vb.memory = "2048"
      vb.name = "moderne_dbs"
    end
    moderne_dbs.vm.provision "shell", privileged: false do |s|
      s.path = "provision.sh"
      s.args = "moderne-dbs"
    end
  end

  if Vagrant.has_plugin?("vagrant-proxyconf") && ENV['http_proxy']
    config.proxy.http     = ENV['http_proxy']
    config.proxy.https    = ENV['https_proxy']
    config.proxy.no_proxy = "localhost,127.0.0.1"
  end
end

Hier wird neben dem Namen und dem RAM der VM das Basis-Image (Debian-Bullseye) sowie die Netzwerkkonfiguration definiert.

Sobald die VM (mit vagrant up) erstellt wird, wird das erste Shell-Skript (provision.sh) ausgeführt. Dieses übernimmt die erste Stufe der Konfiguration.

Shell-Skripte

provision

Das Skript erzeugt im Prinzip einen neuen Benutzer (student) und gibt diesem Administrator-Rechte (sudo). Außerdem wird die Anmeldung per SSH mit Paßwort aktiviert.

vagrant-vm/provision.sh (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
set -x

HOSTNAME=$1

export DEBIAN_FRONTEND=noninteractive

sudo apt-get update
sudo apt-get -y install gnupg curl unzip libuser net-tools

# sudo für student ohne PW 
echo "student ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/student

if [[ -d "/vagrant" ]]
then
  USERNAME=student
  PASSWORD=123456

  : "Set hostname" && {
    sudo hostname $HOSTNAME
    echo $HOSTNAME | sudo tee /etc/hostname > /dev/null
    sudo sed -i "s/bullseye/$HOSTNAME/g" /etc/hosts
  }
  : "Create user student" && {
    sudo useradd -m -G sudo,vagrant -s /bin/bash $USERNAME
    echo -e "$PASSWORD\n$PASSWORD" | sudo passwd $USERNAME
  }
  : "Enable SSH password authentication" && {
    sudo sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
    sudo systemctl restart sshd.service
  }
  : "Run install" && {
    sudo -u $USERNAME bash /vagrant/install.sh
  }
else
  bash /vagrant/install.sh
fi

Im Anschluß wird die zweite Phase der Konfiguration unter dem neuen Benutzerkonto gestartet.

install

vagrant-vm/install.sh (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
export DEBIAN_FRONTEND=noninteractive
cd $HOME

HBASE_VERSION=2.4.9
HBASE_ARCHIVE=hbase-$HBASE_VERSION-bin.tar.gz

# HBase
sudo apt-get -y install default-jdk-headless
wget -nv https://archive.apache.org/dist/hbase/$HBASE_VERSION/$HBASE_ARCHIVE
tar xvf $HBASE_ARCHIVE
rm $HBASE_ARCHIVE

echo "export PATH=$PATH:$HOME/hbase-2.4.9/bin" >> ~/.profile
echo "export JAVA_HOME=/usr/lib/jvm/default-java" >> ~/.profile
# MongoDB
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -
echo "deb https://repo.mongodb.org/apt/debian buster/mongodb-org/5.0 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list
sudo apt-get update
sudo apt-get -y install mongodb-org
# Neo4J
wget -O - https://debian.neo4j.com/neotechnology.gpg.key | sudo apt-key add -
echo 'deb https://debian.neo4j.com stable latest' | sudo tee -a /etc/apt/sources.list.d/neo4j.list
sudo apt-get update
sudo apt-get -y install neo4j
# zwei Zeilen in /etc/neo4j/neo4j.conf aktivieren (cf. https://neo4j.com/docs/operations-manual/current/configuration/password-and-user-recovery/)
# dbms.security.auth_enabled=false
sudo sed -i "s/^#dbms.security.auth_enabled/dbms.security.auth_enabled/g" /etc/neo4j/neo4j.conf
# dbms.default_listen_address=0.0.0.0
sudo sed -i "s/^#dbms.default_listen_address/dbms.default_listen_address/g" /etc/neo4j/neo4j.conf
# Zugriffsrechte Import-Verzeichnis anpassen
sudo chmod a+w /var/lib/neo4j/import
# Postgres
# cf. https://linuxhint.com/install-postgresql-debian/
sudo apt -y install postgresql postgresql-contrib
sudo systemctl disable postgresql
# externer Zugriff:
# * in /etc/postgresql/13/main/pg_hba.conf allow
echo 'host      all     all     0.0.0.0/0       md5' | sudo tee -a /etc/postgresql/13/main/pg_hba.conf
# * /etc/postgresql/13/main/postgresql.conf listen
sudo sed -i "s/^#listen_addresses.*$/listen_addresses = '*'/g" /etc/postgresql/13/main/postgresql.conf
echo "CREATE USER student WITH PASSWORD '123456';" | sudo -i -u postgres psql
echo "CREATE DATABASE student;" | sudo -i -u postgres psql
echo "GRANT ALL PRIVILEGES ON DATABASE student TO student;" | sudo -i -u postgres psql
sudo apt-get -y install redis
sudo systemctl disable redis

# cf. https://askubuntu.com/questions/217358/how-can-i-display-my-machines-ip-address-on-a-tty-login-screen
cat << 'EOF' > /tmp/update-issue
#!/bin/sh
MSG=$(cat /etc/issue | grep -v IP)
IP_ADDRESSES=$(/sbin/ifconfig | grep 'inet' | grep -v '127' |grep -v 'inet6'|awk '{ print $2 }'
)
printf "%s\n" "$MSG" > /etc/issue
for i in $IP_ADDRESSES; do
  printf "%s\n" "IP: $i" >> /etc/issue
done;
EOF
sudo mv /tmp/update-issue /etc/network/if-up.d/
sudo chmod 0755 /etc/network/if-up.d/update-issue
# Linuxlogo :-)
sudo apt-get -y install linuxlogo
echo "linuxlogo" >> ~/.profile

# set locale
sudo apt-get -y install locales
sudo sed -i 's/^# *\(de_DE.UTF-8\)/\1/' /etc/locale.gen
sudo locale-gen
# set timezone
sudo timedatectl set-timezone Europe/Berlin

#  (apt-)cache löschen
sudo rm -rf /var/cache/*

Makefile

Damit das OVA nicht manuell erzeugt werden kann, bietet sich die Automatisierung mittels make an.

vagrant-vm/Makefile (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
.SILENT: clean 
.DEFAULT_GOAL := all

clean: 
        rm dist/moderne-dbs.ova


all: clean ova

ova:
        mkdir -p dist
        vagrant destroy -f;     \
        vagrant up; \
        vagrant halt 
        vboxmanage sharedfolder remove moderne_dbs --name vagrant
        vboxmanage export moderne_dbs -o dist/moderne-dbs.ova

Nun erzeugt ein einfacher Aufruf von make die OVA-Datei im Unterverzeichnis dist. Da die VM in diesem Format bereits komprimiert ist, kann diese nun direkt an die Teilnehmer verteilt werden.

Weitere Ideen/Ausblick

  • Versionierung der VM und Anzeige beim Einloggen