Vagrant

In diesem Blogbeitrag werden wir die Vereinfachung des Workflows der Provisionierung und Konfiguration virtueller Maschinenumgebungen mittels Vagrant demonstrieren, und uns auch Konzepte wie Multi-Machine, Shared Folders und Port Forwarding sowie Provisionierungs-Optionen ansehen.



1. Einleitung


1.1 Was ist Vagrant?

Vagrant eignet sich als Schnittstelle zwischen Virtualisierungssoftware (VirtualBox, Hyper-V, Docker, VMWare, AWS, ...) und Konfigurations- bzw. Automatisierungstools wie z.B. Puppet, Chef oder SaltStack.

Als solche vereinfacht Vagrant das Erstellen und Warten von übertragbaren virtuellen Softwareentwicklungsumgebungen.


1.2 Wofür eignet sich Vagrant?

  • Einrichtung neuer Umgebungen ohne Beeinträchtigung des zugrunde liegenden Hauptbetriebssystems
  • Beschleunigt den Prozess der Erstellung virtueller Umgebungen erheblich u.a. durch:
    • Verwendung eines Basis-Images anstelle einer Neuinstallation des Betriebssystems
    • konfiguriert Einstellungen wie Hostname, Ressourcenzuweisung, Netzwerk etc.
    • ist in der Lage, Automatisierungswerkzeuge (z.B. Puppet, Chef, Ansible etc.) auszuführen, sobald das Basissystem aufgebaut ist.


1.3 Vagrant Vokabular

  • Provider: Grundlegende Virtualisierungstechnologien, die Vagrant verwendet (VirtualBox, Hyper-V, Docker, VMWare, AWS usw.)
  • Provisioner: Software, mit der Vagrant VMs konfiguriert und bereitstellt (Puppet, Ansible, Chef)
  • Box: Vorlage / Konfiguration für die Erstellung einer VM mit Vagrant
  • Vagrantfile:Konfiguration für Vagrant (Art der für ein Projekt benötigten Maschine(n), wie man diese Maschinen konfiguriert und bereitstellt)


2. Installation (Debian, CentOS7/RHEL7, MacOS, Windows)

Wie einleitend beschrieben wird Vagrant zur Provisionierung von diverser Virtualisierungssoftware, auf eben dieser aufsitzend, eingesetzt. Für den Rahmen dieses Blogbeitrags entscheiden wir uns für VirtualBox, welches wir somit als Voraussetzung für Vagrant mitinstallieren werden.

Folgend werden wir die Installation von VirtualBox und Vagrant unter Linux Debian, Linux CentOS/RHEL7, MacOS und Windows behandeln.


2.1 Linux Debian

2.1.1 VirtualBox

$ sudo apt-get update
$ sudo apt-get install virtualbox


2.1.2 Vagrant

$ sudo apt-get install vagrant


2.2 Linux CentOS 7 / Red Hat Enterprise Linux 7

2.2.1 VirtualBox

$ sudo cd /etc/yum.repos.d/
$ sudo wget http://download.virtualbox.org/virtualbox/rpm/rhel/virtualbox.repo
$ sudo yum update
$ sudo rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
$ sudo yum install binutils gcc make patch libgomp glibc-headers glibc-devel kernel-headers kernel-devel dkms
$ sudo yum install VirtualBox-5.2


2.2.2 Vagrant

$ sudo yum -y install ruby
$ sudo gem install vagrant


2.3 MacOS

2.3.1 VirtualBox

 Anmerkung: Für die folgenden Kommandos wird Homebrew als Voraussetzung benötigt: https://brew.sh/index_de.

$ sudo brew cask install virtualbox


2.3.2 Vagrant

$ sudo brew cask install vagrant


2.4 Windows

2.4.1 VirtualBox

https://www.virtualbox.org/wiki/Downloads


2.4.2 Vagrant

https://www.vagrantup.com/downloads.html


3. Vagrant Basis-Kommandos

  • box (add, remove, etc.) boxes
  • global-status gibt den Status aller aktiven Vagrant Umgebungen auf dem System zurück
  • haltschaltet die laufenden Maschine(n) ab, welche Vagrant verwaltet
  • init initialisiert das aktuelle Verzeichnis, indem es ein initiales Vagrantfile generiert
  • provisionführt jeden konfigurierten provisioner gegen die laufenden von Vagrant verwalteten Maschinen aus
  • reload (entspricht halt && up): normalerweise erforderlich, damit Änderungen, die im Vagrantfile vorgenommen wurden, wirksam werden
  • ssh Started eine SSH session in einer laufenden Vagrant Maschine und ermöglicht Zugriff auf deren Shell
  • uperstellt und konfiguriert guest machines gemäß Vagrantfile.


 Anmerkung: Offizielle Dokumentation mit allen Kommandos des Vagrant CLI: https://www.vagrantup.com/docs/cli/

4. Das Vagrantfile

Das Vagrantfile beschreibt unter anderem den für ein Projekt erforderlichen Maschinentyp, sowie Einstellungen / Anweisungen die Konfiguration und Provisionierung dieser Maschinen betreffend.

Im Regelfall wird je Projekt ein Vagrantfile ertellt, welches - z.B. via GiT - versioniert werden sollte. Dies ermöglicht es anderen am Projekt beteiligten Entwicklern, den Code auszulesen, Vagrant auszuführen und das Projekt somit bereits up-and-running zu haben.

Der Syntax des Vagrantfiles ist Ruby, allerdings sind Ruby-Programmier-Kenntnisse nicht zwingend notwendig, um Änderungen am Vagrantfile vorzunehmen bzw. dessen Aufbau zu verstehen, da es sich meist um einfache Variablenzuweisungen handelt.


4.1 Minimales Vagrantfile

Vagrant benötigt zumindest die Angabe des Images, welches für die Box heranzuziehen ist.


 Anmerkung: Eine durchsuchbare Liste diverser Vagrant Boxes findet sich unter: https://app.vagrantup.com/boxes/search

Vagrant.configure("2") do |config|
    config.vm.box = "ubuntu/bionic64"
end


Wird im Projektverzeichnis (in welchem sich auch das Vagrantfile befindet) nun vagrant up ausgeführt, so erstellt Vagrant bereits eine Box mit Ubuntu als Betriebssystem eben dieser.


 Anmerkung: Die offizielle Liste aller im Vagrantfile anwendbarer Parameter / Variablen findet sich unter: https://www.vagrantup.com/docs/vagrantfile/

4.2 Multi-Machine

Im simpelsten Fall wird ein Vagrantfile verwendet, um Konfiguration und Provisionierungsparameter für eine einzelne guest machine zu definieren. Oftmals besteht die Infrastruktur eines Projektes aber auch aus mehreren Maschinen, die gegebenenfalls auch untereinander bekannt sein sollten oder zum Beispiel auch kommunizieren können sollten.

Folgende ein Beispiel für ein recht simples Vagrantfile, welches Beschreibungen für mehrere guest machines beinhaltet:


Vagrant.configure("2") do |config|
    
    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
    end
    
end


4.3 Shared Folders

Oft beinhaltet das Projekt files / folders / executables, welche entsprechend auf die guest machine zu übertragen sind. Natürlich könnten diese nach boot-up der guest machine zum Beispiel via scp übertragen werden - eine einfachere und vorteilhaftere Vorgehensweise ist aber das Konzept von Shared Folders - also Verzeichnisse, welche zwischen host machine und guest machine automatisch synchronisiert werden, so dass eine Änderung einer entsprechenden Datei auf dem host sofort in der guest machine entsprechend sichtbar / effektiv ist.

Wir erweitern das oben angeführte multi-machine Vagrant file, um auf der db guest machine den Ordner, in welchem die Datenbankdateien abgelegt werden, zwischen host und guest zu sharen.


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
    end
    
end


 Anmerkung: Der erste angegebene Pfad entspricht dem (in diesem Fall relativen) Pfad auf der host machine, der zweite Pfad entspricht dem absoluten Pfad auf der guest machine.

4.4 Provisionierung

In der Regel wird eine guest machine, sobald sie via Vagrant erstellt und hochgefahren wurde, weiter provisioniert, es wird Software ausgeliefert, installiert und konfiguriert, und sichergestellt dass die entsprechenden Services laufen.

Für solche Zwecke lässt sich Vagrant natürlich gut mit entsprechenden Automatisierungstools wie zum Beispiel Puppet, Ansible, Chef oder SaltStack kombinieren. Um den Rahmen dieses Blogbeitrages nicht zu sprengen beschränken wir uns in eben diesem aber erstmal auf zwei Möglichkeiten welche Vagrant selbst bietet: Inline-Provisionierung via direkter Übergabe der entsprechenden Kommandos, und Provisionierung durch Ausführung von zum Beispiel eines entsprechend vorbereiteten Shell Scripts.

Wir möchten auf der db guest machine einen Postgres Datenbank Server installieren:


4.4.1 Inline-Provisionierung

Hierzu verwenden wir die Option vm.provision:


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
        db.vm.provision "shell",
            inline: "sudo apt-get update && sudo apt-get install -y postgresql"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
    end
    
end


4.4.2 Provisionierung via Shell-Script

Alternativ können wir die Kommandos auch in ein Shell-Script auslagern, und dieses gesamt ausführen. Dies macht bei umfangreicheren Kommandos gegenübergestellt der inline Provisionierung natürlich Sinn, vor dem Hintergrund von Lesbarkeit und Wartbarkeit. Auch hierfür wird die Option vm.provision verwendet, und es wird der relative Pfad zum entsprechenden Shell-Script auf der host machine angegeben:


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
        db.vm.provision "shell", path: "provision-db.sh"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
    end
    
end


4.5 Networking

Vagrant stellt einige high-level Netzwerkoptionen zur Verfügung, so zum Beispiel zwecks Portweiterleitung, Anbindung an ein öffentliches Netzwerk oder Erstellung eines privaten Netzwerks. Diese Optionen funktionieren als Abstraktions-Layer auch über mehrere Anbieter (VirtualBox, VMWare, ...) hinweg.


4.5.1 Private Networks

Vagrant private networks ermöglichen es über eine IP Adresse, welche aus dem globalen Internet nicht öffentlich zugänglich ist, auf die guest machine zuzugreifen.

Mehrere guest machines innerhalb eines privaten Netzwerks wiederum können über eben dieses miteinander kommunizieren, was natürlich insbesondere für multi-machine setups vorteilhaft ist.

Die einfachste Möglichkeit ein entsprechendes private network zu erstellen ist die Vergabe einer privaten IP Adresse via DHCP unter Verwendung der vm.network Option und des private_network identifiers:


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.network "private_network", type: "dhcp"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
        db.vm.provision "shell", path: "provision-db.sh"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
        web.vm.network "private_network", type: "dhcp"
    end
    
end


Manchmal ist es allerdings vorteilhaft, die IP Adresse im Vorfeld statisch zu definieren. Auch dies ist via vm.network und private_network identifier möglich:


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.network "private_network", ip: "10.0.2.1"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
        db.vm.provision "shell", path: "provision-db.sh"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
        web.vm.network "private_network", ip: "10.0.2.2"
    end
    
end

4.5.2 Public Networks

Vagrant public networks können, im Gegensatz zu privaten Netzwerken, aus dem globalen Internet öffentlich zugänglich sein.

Auch hier ist einfachste Möglichkeit die Vergabe einer dynamischen IP Adresse via DHCP unter Verwendung der vm.network Option und des public_network identifiers:


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.network "public_network"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
        db.vm.provision "shell", path: "provision-db.sh"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
        web.vm.network "public_network"
    end
    
end

Und ein Beispiel unter Verwendung einer statischen IP Adresse:


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.network "public_network", ip: "192.168.0.1"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
        db.vm.provision "shell", path: "provision-db.sh"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
        web.vm.network "public_network", ip: "192.168.0.2"
    end
    
end


Sollte mehr als ein network interface auf der host machine verfügbar sein, so kann das gewünschte interface definiert weden, zu welchem die guest machine eine Bridge erstellen soll, indem eine :bridge-Klausel hinzugefügt wird wie folgt:


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.network "public_network", bridge: "en0"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
        db.vm.provision "shell", path: "provision-db.sh"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
        web.vm.network "public_network", bridge: "en0"
    end
    
end


4.6 Port Forwarding

Manchmal ist es nötig, einen Port der host machine auf einen entsprechenden Port in einer Vagrant guest machine weiterzuleiten. Nehmen wir an unser webserver läuft virtualisiert in einer entsprechenden Vagrant guest machine auf Port 80; wir werden höchstwahrscheinlich zum Beispiel Port 80 der host machine entsprechend weiterleiten wollen.

Auch für lokale Entwicklungsumgebungen macht dies Sinn; so könnte Port 8080 der host machine auf Port 80 des guests weitergeleitet werden, was dem Entwickler ermöglicht im lokalen Browser unter http://localhost:8080 den virtualisierten Webserver anzusteuern.

Letzteres setzen wir in unserem Vagrantfile von oben entsprechend um:


Vagrant.configure("2") do |config|

    # database server
    config.vm.define "db" do |db|
        db.vm.box = "ubuntu/bionic64"
        db.vm.synced_folder "./data/", "/usr/local/pgsql/data/"
        db.vm.provision "shell", path: "provision-db.sh"
    end
    
    # web server
    config.vm.define "web" do |web|    
        web.vm.box = "ubuntu/bionic64"
        web.vm.network "forwarded_port", guest: 80, host: 8081
    end
    
end


 Anmerkung: Sofern in der guest machine eine Firewall aktiviert ist, ist zu bedenken dass die entsprechenden Firewall Rules, je nach Port, entsprechend zu setzen / anzupassen sind.

Kommentare

Noch keine Kommentare

Hinterlassen Sie gerne einen Kommentar.