## Einleitung Dieses Kapitel bietet einen Überblick über Nomad, ein leistungsstarkes Open-Source-Tool für die Orchestrierung von Anwendungen und Arbeitslasten. Nomad wurde von HashiCorp entwickelt und hat sich zu einem beliebten Tool in der Entwickler- und DevOps-Community entwickelt. Mit Nomad können Entwickler und DevOps-Teams die Bereitstellung und Verwaltung von Anwendungen in einer Vielzahl von Umgebungen automatisieren und skalieren, sei es in der Cloud, im Rechenzentrum oder in hybriden Umgebungen. Nomad zeichnet sich durch seine einfache Konfiguration und seinen deklarativen Charakter aus. Ähnlich wie bei Terraform, welches wir im letzten Kapitel behandelt haben, können Benutzer den gewünschten Zustand ihrer Infrastruktur oder Anwendungen in einer Konfigurationsdatei definieren. Nomad liest diese Datei und übernimmt die Aufgabe, Anwendungen auf den verfügbaren Ressourcen zu planen, zu verteilen und auszuführen. Dies vereinfacht das manuelle Deployment von Anwendungen und die Handhabung von Skalierung und Load Balancing erheblich. ### Motivation Als Team haben wir uns für Nomad entschieden, weil wir erkannt haben, dass die effiziente Verwaltung und Skalierung von Anwendungen in einer dynamischen und agilen Umgebung entscheidend ist. Unternehmen stehen heute vor der Herausforderung, flexibel zu sein und schnell auf sich ändernde Anforderungen reagieren zu können. Nomad ermöglicht es uns, die Bereitstellung unserer Anwendungen zu automatisieren und, ähnlich zu Terraform, die Vorteile von Infrastructure as Code zu nutzen. ## Grundlagen ### Architektur Die Architektur von Nomad basiert auf einem dezentralen Ansatz, bei dem die Aufgabenverteilung und -ausführung in einem Cluster von Nomad-Agenten erfolgt. Nomad verwendet eine Client-Server-Architektur, bei der der Nomad-Server die Konfiguration und Planung der Workloads verwaltet, während die Nomad-Agenten die eigentliche Ausführung der Aufgaben übernehmen. Nomad bedient sich, ähnlich wie Terraform, dem HCL-Format für die Konfigurationsdateien. Diese Konfigurationsdateien enthält Informationen über die zu verwaltenden Workloads, Ressourcenanforderungen, Skalierungsoptionen und andere relevante Einstellungen des jeweiligen Deployments. Nomad unterstützt auch die Verwendung von Vorlagedateien, um die Konfiguration zu parametrisieren und wiederholbare Implementierungen zu ermöglichen. Wie bei Terraform ist es auch hier einfach, in den Syntax der HCL reinzukommen, wie man später in der Versuchsdurchführung sehen wird. Die Architektur von Nomad ermöglicht eine hoch skalierbare und verteilte Ausführung von Workloads. Nomad-Agenten werden auf den Ziel-Hosts installiert und kommunizieren mit dem Nomad-Server, um die aktuelle Auslastung der Ressourcen zu liefern. Der Nomad-Server analysiert die Konfiguration und plant die Aufgaben auf der Grundlage von Ressourcenanforderungen, verfügbarem Speicherplatz und anderen Kriterien. Anschließend werden die Aufgaben an die Agenten der jeweiligen "Worker-Nodes" verteilt und von diesen ausgeführt. ### Wichtige Begriffe - Job: Ein Job in Nomad repräsentiert eine einzelne Arbeitslast oder eine Gruppe von Aufgaben, die zusammen ausgeführt werden sollen. - Task: Ein Task definiert die auszuführende Aufgabe, einschließlich des auszuführenden Befehls, der Ressourcenanforderungen und anderer Parameter. - Group: Eine Group ist eine Sammlung von Aufgaben innerhalb eines Jobs, die gemeinsame Eigenschaften oder Abhängigkeiten haben. - Allocation: Eine Allocation enthält Informationen über den Zustand der Aufgabe, die zugewiesenen Ressourcen und andere relevante Metadaten. - Scheduler: Der Scheduler berücksichtigt die Ressourcenanforderungen, Prioritäten, Ausfallsicherheit und andere Faktoren, um die optimale Verteilung der Aufgaben zu gewährleisten. - State: Der State speichert Informationen über die Allokationen, den Fortschritt der Aufgaben und andere relevante Daten. ### Warum Nomad anstelle von Kubernetes? Nomad und Kubernetes sind zwei beliebte Tools für die Orchestrierung von Containern und Workloads, aber es gibt Gründe, warum man Nomad wählen sollte. Nomad zeichnet sich durch seine Einfachheit und Benutzerfreundlichkeit aus. Im Vergleich zu Kubernetes ist Nomad weniger komplex in der Einrichtung und Konfiguration geht schneller und eingängiger von statten. Nomad bietet eine leichtgewichtige und minimalistische Lösung für die Container-Orchestrierung, die sich gut für kleinere oder weniger komplexe Umgebungen eignet. Ein weiterer Vorteil von Nomad ist seine Flexibilität. Nomad unterstützt verschiedene Arten von Workloads, darunter Container, virtuelle Maschinen und sogar nicht containerisierte Anwendungen. Nomad lässt sich außerdem nahtlos in andere HashiCorp-Tools wie Consul und Vault integrieren und bietet so eine umfassende Lösung für Service Discovery, Konfigurationsmanagement und Sicherheit. Außerdem bietet Nomad auch eine native Integration mit Terraform an, was bedeutet, dass Sie Infrastrukturressourcen und Workload-Orchestrierung in einem einzigen Workflow definieren und verwalten können. Diese Integration erleichtert die Verwaltung und Automatisierung der gesamten Infrastruktur und fördert die Konsistenz der Infrasturktur. Darüber hinaus verfügt Nomad über eine wachsende Community, die aktiv an der Entwicklung und Verbesserung des Tools beteiligt ist. Die Community bietet Support, Guides und Ressourcen, um bei Fragen und Probleme zu helfen. Insgesamt ist Nomad eine attraktive Alternative zu Kubernetes, insbesondere für Unternehmen, die eine einfache, flexible und gut integrierte Lösung für die Orchestrierung ihrer Workloads suchen und man z.B. bereits auf Terraform setzt. # Nomad verwenden Um Nomad zu nutzen, muss es auf dem System installiert werden. Die Installation von Nomad erfolgt über den Bezug der Binärdateien von der offiziellen Website https://developer.hashicorp.com/nomad/downloads. ## Die Basics Die Konfiguration von Nomad erfolgt über sogenannte Job-Dateien, in denen die gewünschten Arbeitslasten und deren Anforderungen definiert werden. Jeder Job definiert eine oder mehrere Aufgaben, die auf den verfügbaren Ressourcen ausgeführt werden sollen. Ein Beispiel für einen einfachen Nomad-Job könnte wie folgt aussehen: ```json job "example" { datacenters = ["dc1"] group "webapp" { count = 3 task "app" { driver = "docker" config { image = "myapp:latest" port_map { http = 8080 } } } } } ``` In diesem Beispiel wird ein Job mit dem Namen "example" definiert. Der Job besteht aus einer Gruppe mit dem Namen "webapp", die aus drei Instanzen der Aufgabe "app" besteht. Die Aufgabe wird mit dem Docker-Treiber konfiguriert und verwendet das Docker-Image "myapp:latest". Außerdem wird der Port 8080 auf die HTTP-Anwendung innerhalb des Containers gemappt. ### Ressourcenverwaltung Nomad bietet verschiedene Möglichkeiten zur Ressourcenverwaltung, um sicherzustellen, dass Arbeitslasten entsprechend ihren Anforderungen platziert werden. Einige wichtige Aspekte der Ressourcenverwaltung in Nomad: - **Constraints:** Durch die Verwendung von Constraints können spezifische Anforderungen an die Platzierung von Arbeitslasten definiert werden, z.B. die Zuordnung zu bestimmten Rechenressourcen oder der Ausschluss von bestimmten Maschinen. - **Task Groups:** Task Groups ermöglichen die Gruppierung von Aufgaben und definieren gemeinsame Eigenschaften wie die Anzahl der Instanzen und die Platzierungseinschränkungen. - **Allocation Strategies:** Nomad bietet verschiedene Allokationsstrategien, um die Verteilung von Aufgaben auf verfügbare Ressourcen zu steuern. Beispiele dafür sind bin packing, spread oder random. ### Skalierbarkeit und Verfügbarkeit Nomad wurde mit Fokus auf Skalierbarkeit und Verfügbarkeit entwickelt. Es unterstützt die horizontale Skalierung von Arbeitslasten durch die einfache Definition von Gruppen und die Anpassung der Anzahl der Instanzen. Nomad überwacht automatisch den Zustand der Arbeitslasten und kann fehlerhafte Instanzen neu starten oder auf anderen Ressourcen neu platzieren, um die Verfügbarkeit sicherzustellen. Nomad integriert sich nahtlos mit anderen HashiCorp-Tools wie Consul und Vault, um Funktionen wie Service Discovery und Secrets Management bereitzustellen. Dadurch wird die Gesamtlösung zur Bereitstellung und Verwaltung von Anwendungen und Infrastruktur weiter verbessert. Insgesamt bietet Nomad eine einfache und flexible Möglichkeit, die Bereitstellung und Verwaltung von Arbeitslasten in einer skalierbaren und zuverlässigen Umgebung zu automatisieren. Es ist gut geeignet für Szenarien, in denen eine leichtgewichtige Orchestrierungslösung benötigt wird und die Integration mit anderen HashiCorp-Tools von Vorteil ist. ## Versuchsdurchführung 1 ### Installation von Nomad auf Control Host Als erstes müssen wir ein neues Verzeichnis für Nomad erstellen. ```bash $ mkdir ~/nomad $ cd ~/nomad ``` Als nächstes soll Nomad heruntergeladen & extrahiert werden. ```bash $ wget https://releases.hashicorp.com/nomad/1.5.6/nomad_1.5.6_linux_amd64.zip $ unzip nomad_*_linux_amd64.zip ``` Damit Nomad nun auch außerhalb des `~/nomad`-Verzeichnisses aufgerufen werden kann, müssen wir den Pfad noch zu unserem Profil hinzufügen ```bash $ echo 'export PATH="$PATH:$HOME/nomad"' >> ~/.profile $ source ~/.profile ``` Nun sollte Nomad aufrufbar sein und Output zurückgeben ```bash $ nomad -v Nomad v1.5.0 BuildDate 2023-03-01T10:11:42Z Revision fc40c491cacec3d8ec3f2f98cd82b9068a50797c ``` ## Versuchsdurchführung 2 ### Infrastructure as Code Als erstes müssen die Server-Instanzen in der Cloud erstellt werden. Diese werden via Terraform (siehe letzter Versuch) gemanaged. main.tf: ```json resource "linode_instance" "nomad_server" { count = 3 image = "linode/debian11" label = "nomad-server-${count.index}" group = "Nomad Server" region = "eu-central" type = "g6-standard-1" authorized_keys = [ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDAA3Ww8kpBtsr2At9TwIxAorgv5scoEqdJ59JL1Gi6aC4dst1GsTnUTWvH7s2TR3qEPY4YgMrOSQsNLdxPHllUEmWwewpWkVE+fzqQGeG+BlyXueG/pRX7KeMBefUkKs8dmA6KD6i/OFhxfn52u/WabzrxP4JZRQC0msz7LlZMtH/ZanhkhxyQfAbXlkN5DugJAcr1mdHqaJ1juzD176EE9sH/cK3NFOoDq37KEVsWrj/tBmtwGuULd6ZCA09gg8a7owf5H4oJNOAercZA7l50CJnXzwx4S+jJAjLJW/7m9Zrj6lJMZRn2EvmX4MKgS5ixmCmPstlsrBGb+vpmaT4tFyPRZOYM2JSC7AucbhlEarzksScRvrRsmhKsemPCzBdscqz25yNjQZXcYXD9OvCI4DQrSPB/EzOCd8/DKPbm4h+aN5uZHkYIaMUefQoDEPMjkLm8XV/ZjqFi9667dDpTjT7Fqn2w5GZA1dPqvixIQaKLqPJjnIlgx1OYI4A1NNTLsxrh4jw1hgN5JcTjWp7BhnmFLdp27dlib8SxYWZxYBviSyFP+x+OG9IPzKrw8d7TfTej0pxgeauLELcCyv+FYVEg7Ou7Wm2HnGWDQRpYhoQwbl0ff7w6/3FtUB4DH7vpVig6fUwGuruZzgZcruvhPag121q2MtfmCKeqI4QKyQ== monitor@fabianhammerschmidt.de" ] root_pass = "WeLoveTerraform" tags = ["nomad","server"] } resource "linode_instance" "nomad_client" { count = 5 image = "linode/debian11" label = "nomad-client-${count.index}" group = "Nomad Clients" region = "eu-central" type = "g6-standard-1" authorized_keys = [ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDAA3Ww8kpBtsr2At9TwIxAorgv5scoEqdJ59JL1Gi6aC4dst1GsTnUTWvH7s2TR3qEPY4YgMrOSQsNLdxPHllUEmWwewpWkVE+fzqQGeG+BlyXueG/pRX7KeMBefUkKs8dmA6KD6i/OFhxfn52u/WabzrxP4JZRQC0msz7LlZMtH/ZanhkhxyQfAbXlkN5DugJAcr1mdHqaJ1juzD176EE9sH/cK3NFOoDq37KEVsWrj/tBmtwGuULd6ZCA09gg8a7owf5H4oJNOAercZA7l50CJnXzwx4S+jJAjLJW/7m9Zrj6lJMZRn2EvmX4MKgS5ixmCmPstlsrBGb+vpmaT4tFyPRZOYM2JSC7AucbhlEarzksScRvrRsmhKsemPCzBdscqz25yNjQZXcYXD9OvCI4DQrSPB/EzOCd8/DKPbm4h+aN5uZHkYIaMUefQoDEPMjkLm8XV/ZjqFi9667dDpTjT7Fqn2w5GZA1dPqvixIQaKLqPJjnIlgx1OYI4A1NNTLsxrh4jw1hgN5JcTjWp7BhnmFLdp27dlib8SxYWZxYBviSyFP+x+OG9IPzKrw8d7TfTej0pxgeauLELcCyv+FYVEg7Ou7Wm2HnGWDQRpYhoQwbl0ff7w6/3FtUB4DH7vpVig6fUwGuruZzgZcruvhPag121q2MtfmCKeqI4QKyQ== monitor@fabianhammerschmidt.de" ] root_pass = "WeLoveTerraform" tags = ["nomad","client"] } ``` output.tf file: ```json output "server_ips" { value = linode_instance.nomad_server[*].ip_address description = "The IP addresses of the Nomad servers." } output "client_ips" { value = linode_instance.nomad_client[*].ip_address description = "The IP addresses of the Nomad clients." } output "all_ips" { value = concat(linode_instance.nomad_server[*].ip_address, linode_instance.nomad_client[*].ip_address) description = "The IP addresses of all the Nomad servers and clients." } ``` ### Dynamische Erstellung des Inventories Wir müssen aufgrund der dynamischen IPv4-Adressen der Hosts unser Inventory File genererieren, bevor wir das Ansible-Playbook ausführen können. Das Playbook: ```yaml - name: Generate Ansible Inventory hosts: localhost connection: local gather_facts: false tasks: - name: Write Terraform output to file shell: terraform output -json > output.json args: chdir: ~/terraform/ - name: Read Terraform output from file command: cat ~/terraform/output.json register: terraform_output - name: Print Terraform output debug: var: terraform_output.stdout | from_json - name: Create Ansible inventory file template: src: ~/ansible/nomadagent/inventory_template.j2 dest: ~/ansible/nomadagent/inventory.ini vars: nomad_servers: "{{ (terraform_output.stdout | from_json).server_ips.value }}" nomad_clients: "{{ (terraform_output.stdout | from_json).client_ips.value }}" ``` Template zum Parsen des Inventories: ```yaml # Ansible Inventory [nomad_servers] {% for server in nomad_servers %} server_{{ loop.index }} ansible_host={{ server }} {% endfor %} [nomad_clients] {% for client in nomad_clients %} client_{{ loop.index }} ansible_host={{ client }} {% endfor %} ``` Ein Beispiel: ```ini # Ansible Inventory // Bogus IPs [nomad_servers] server_1 ansible_host=172.104.135.238 server_2 ansible_host=172.105.93.65 server_3 ansible_host=172.105.93.191 [nomad_clients] client_1 ansible_host=172.104.135.232 client_2 ansible_host=172.105.93.92 client_3 ansible_host=139.144.78.26 client_4 ansible_host=172.105.93.123 client_5 ansible_host=172.105.93.166 ``` ### Playbook zur Erstellung des Nomad Clusters ```yaml # install_nomad.yaml --- - hosts: all remote_user: root vars: ansible_ssh_private_key_file: ~/.ssh/linode nomad_version: "1.1.4" tasks: - name: Aktualisiere APT-Paketlisten apt: update_cache=yes - name: Installiere notwendige Pakete apt: name: ['curl', 'unzip', 'software-properties-common','gpg','lsb-release'] state: present - name: Füge den HashiCorp GPG-Schlüssel hinzu apt_key: url: https://apt.releases.hashicorp.com/gpg state: present - name: Füge das offizielle HashiCorp Linux Repository hinzu apt_repository: repo: "deb [arch=amd64] https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main" state: present - name: Aktualisiere APT-Paketlisten apt: update_cache=yes - name: Installiere Nomad apt: name: nomad state: present - name: Start Nomad client service service: name: nomad state: started - name: Install ufw apt: name: ufw state: present - name: Allow Nomad communication ufw: rule: allow port: '{{ item }}' proto: '{{ "tcp" if item != 4648 else "udp" }}' with_items: - 4646 - 4647 - 4648 - name: Erstelle Nomad Konfigurationsverzeichnis file: path: /etc/nomad.d state: directory - name: Set leader IP set_fact: leader_ip: "{{ hostvars[groups['nomad_servers'][0]].ansible_host }}" # - name: Setze Nomad Konfiguration # copy: # dest: /etc/nomad.d/config.hcl # content: | # data_dir = "/var/nomad" # bind_addr = "0.0.0.0" # Add your specific configuration here - name: Configure Nomad template: src: ~/ansible/nomadagent/config_template.j2 dest: /etc/nomad.d/config.hcl owner: root group: root mode: '0644' - hosts: nomad_servers remote_user: root tasks: - name: Install Nomad apt: name: nomad state: present update_cache: yes - name: Create Nomad job file copy: content: | job "http-echo" { datacenters = ["dc1"] group "example" { count = 1 task "http-echo" { driver = "docker" config { image = "hashicorp/http-echo" args = ["-text", "Hello, World!"] port_map { http = 5678 } } resources { network { port "http" { static = 5678 } } } } } } dest: /tmp/http-echo.nomad - name: Run Nomad job command: nomad job run /tmp/http-echo.nomad - hosts: nomad_clients remote_user: root tasks: - name: Install Nomad apt: name: nomad state: present update_cache: yes - name: Start Nomad client service service: name: nomad state: started ``` Nomad Config Template: ```json data_dir = "/var/nomad" advertise { # Use the actual IP address of the server http = "{{ ansible_host }}:4646" rpc = "{{ ansible_host }}:4647" serf = "{{ ansible_host }}:4648" # <- This is the Serf advertise address } server { enabled = true bootstrap_expect = 3 server_join { retry_join = ["{{ leader_ip }}:4648"] } } ``` ## Literaturverzeichnis - https://developer.hashicorp.com/nomad/tutorials, abgerufen am 22.05.2023 - https://www.nomadproject.io/, abgerufen am 10.05.2023 - https://www.devoteam.com/expert-view/is-hashicorp-nomad-a-smart-choice-for-your-container-orchestration/, abgerufen am 21.05.2023