Bei einem befristeten Kundenansturm ist es manchmal sinnvoll, aus dem eigenen Rechenzentrum kurzzeitig in die Cloud zu expandieren. Das läuft nicht ganz ohne Aufwand ab, lässt sich dank Ansibles Playbooks und einigen AWS-Skripten aber machen.
Firmen benutzen Public-Cloud-Dienste wie AWS [1], Microsoft Azure [2] oder Googles Cloud [3] häufig nicht ausschließlich. Vielmehr setzen sie auf eine Mischung namens Hybrid Cloud. Dabei verbinden sie die eigenen Rechenzentren (Private Cloud) mit den Ressourcen bei einem Public-Cloud-Anbieter. Der Begriff Private Cloud führt insofern etwas in die Irre, als der Betrieb vieler Rechenzentren recht wenig mit cloudbasierten Arbeitsmethoden zu tun hat, er hat sich aber eingebürgert.
Der Vorteil einer Hybrid Cloud: Die Unternehmen fangen damit Lastspitzen oder spezielle Anforderungen ab, ohne dazu für fünf- oder sechsstellige Euro-Summen neue Hardware beschaffen zu müssen.
Entschwebende Server
Im klassischen Rechenzentrumsbetrieb kommt für ein Projekt üblicherweise ein Server zum Einsatz, den ein Admin installiert. Er durchläuft dann einen Lebenszyklus, in dem er regelmäßig Patches erhält. Irgendwann wird er nicht mehr benötigt oder ist veraltet. In der virtualisierten Welt passiert im Prinzip dasselbe, nur mit virtuellen Servern. Die schickt der Admin allerdings aus Performance-Gründen nicht mehr unbedingt in Rente: Mit ein paar Kommandos oder Klicks weist er ihnen einfach mehr und schnellere Ressourcen zu.
Anders läuft es in der Cloud: Hier hat der Admin eher einen Dienst im Auge. Um ihn zu betreiben, muss er für eine bestimmte Zeit definierte Ressourcen bereitstellen. Diese Dienste baut er möglichst automatisiert auf (manchmal von Grund auf), verwendet sie und bezahlt bei Public-Cloud-Providern auch nur für diesen Zeitraum. Anschließend fährt er die Maschinerie wieder herunter und senkt so den Ressourcenbedarf auf null.
Gehören zu diesen Ressourcen virtuelle Maschinen, so baut der Admin auch sie automatisiert auf, verwendet sie und löscht sie dann wieder. Der klassische Lebenszyklus eines Servers fällt hier also weg. Der Server ist zur Komponente in einer Architektur degradiert, die der Admin per Knopfdruck zum Leben erweckt.
Diese Automatisierung betrifft mittlerweile aber nicht mehr nur die Cloud. Auch in der Welt der klassischen Server hält die Automatisierung dank Werkzeugen wie Ansible [4] oder Puppet [5] immer häufiger Einzug. Dieser Artikel zeigt, wie ein Admin Ansible-Rollen, die eigentlich lokale Server adressieren, um einen Cloud-Anbau erweitert. Dafür erweitert er ein lokales Playbook für einen Elasticsearch-Cluster so, dass er diesen auch in der Cloud einsetzen kann und die Ressourcen nach dem Einsatz wieder verschwinden.
Sichtbar für alle?
Ein beliebtes Vorurteil gegenüber dem Einsatz von Public-Cloud-Diensten lautet, dass diese Dienste ja “frei im Internet” ständen. Das stimmt so nicht ganz, da die meisten Cloud-Provider es dem Admin überlassen, ob er einen Dienst oder eine VM mit einer öffentlich erreichbaren IP-Adresse versehen möchte. Zudem muss er meist alle Dienste, die er von außen erreichen möchte, explizit freischalten.
Das betrifft in der Regel aber nicht die zur Administration nötigen Dienste, also SSH bei Linux-VMs und RDP bei Windows-VMs. Ein Beispiel: Pickt sich der Admin bei AWS etwa eine Datenbank aus dem Database-as-a-Service-Angebot heraus, erreicht er sie in der Standardeinstellung nur über die IP-Adresse, über die er die AWS Console bedient.
Richtet der Admin in der Public Cloud die virtuellen Netze nur mit privaten Adressen ein, sind sie aus dem Internet ebenso wenig zu sehen wie die Server im eigenen Rechenzentrum.
Cloudbnb
Bei AWS, aber auch in den Clouds von Google und Microsoft, gibt es zum Beispiel das Konzept der Virtual Private Cloud (VPC). Diese fungiert quasi als eigener Backbone des Kontos. Admins dürfen über ein AWS-Konto pro Region sogar mehrere nebeneinander stehende VPC-Instanzen betreiben.
Um sich mit diesem Netzbereich zu verbinden, bieten die Cloud-Provider einen Site-to-Site-VPN-Dienst an. Alternativ baut der Admin ein eigenes VPN-Gateway auf, etwa in Form einer virtuellen Maschine (zum Beispiel Linux mit IPsec/OpenVPN) oder einer virtuellen Firewall-Appliance. Letzteres bietet ein höheres Maß an Sicherheit, ist aber meist auch kostenpflichtig.
Damit entsteht am Ende eine Struktur, wie sie Abbildung 1 darstellt. Sie unterscheidet sich konzeptionell nicht grundsätzlich davon, wie Admins Außenstellen an die Zentrale anbinden – mit einem Unterschied: Der Public-Cloud-Provider kann womöglich auf die Daten der Maschinen und Container zugreifen.

Abbildung 1: Eine Firewall trennt ein externes und ein internes Subnetz (rechts). Im Detail sieht die AWS-Verbindung etwas anders aus.
Daten schützen
Die zweite große Sorge bezüglich der Sicherheit betrifft das Ablegen von Daten. Gerade beim Verarbeiten personenbezogener Informationen muss der Admin schon aus rechtlichen Gründen aufpassen, in welcher Region eines Cloud-Anbieters er die Daten einquartiert. Die Kundendatenbank einfach mal so nach Japan zu verlagern, entpuppt sich womöglich als weniger gute Idee. Aber selbst beim Verstauen der Daten auf Servern innerhalb der EU gilt es, die Frage zu klären, wer Zugriff erhält.
Mit der Möglichkeit, Daten in AWS zu verschlüsseln, hat sich bereits ein Artikel in LM 08/2019 beschäftigt [6]. Genügt das Vertrauen in diese Maßnahmen noch nicht, besteht die Option, eine Linux-VM mit einem selbst verschlüsselten Volume (etwa LUKS [7]) auszustatten und das Passwort nicht auf dem Server zu hinterlegen. Das funktioniert bei AWS zwar nicht für Systemplatten, aber immerhin für Daten-Volumes. Nach dem Start der VM muss der Admin das Passwort dann übermitteln. Das darf automatisiert passieren, aber aus dem eigene Rechenzentrum heraus. Als einzig möglicher Zugriff des Providers bleibt dann noch das Auslesen des Arbeitsspeichers der Maschine. Dieses Risiko besteht auch dann, wenn moderne Hardware eine Live-Verschlüsselung ermöglicht.
Als letzter Ausweg kann der Rechnerverwalter dafür sorgen, dass die Rechenressourcen in der Cloud ausschließlich auf Daten zugreifen, die das lokale Rechenzentrum verwaltet. Das setzt allerdings eine entsprechend leistungsfähige Internet-Anbindung voraus.
Das zu lösende Problem
Gegeben sei ein lokaler Elasticsearch-Cluster aus drei Knoten. Ein Master Node, der auch Logstash und Kibana beherbergt sowie zwei Data Nodes, welche die gesammelten Daten an Bord haben (Abbildung 1).
Diesem Cluster will der Admin nun vorübergehend zwei weitere Data Nodes in der Public Cloud spendieren. Dafür kann es verschiedene Gründe geben – etwa, dass der Admin die physischen Data Nodes wegen Hardware-Problemen austauschen möchte oder dass er vorübergehend für eine Datenanalyse eine höhere Leistung braucht. Da es sich meist nicht lohnt, dafür zeitweilig neue Hardware zu beschaffen, muss die Public Cloud herhalten. Der Aufbau soll dabei der Logik aus Abbildung 1 folgen, die dort gestarteten Maschinen müssen Teil des Elastic-Clusters werden.
Die Ausführungen gehen im Folgenden davon aus, dass der Admin bereits Ansible-Rollen für die Installation des ELK-Clusters verfasst hat; das passende Listing zum Playbook finden Sie im LM-Download-Bereich [9]. Die Struktur dieser Rollen erlaubt dem Admin, weitere Knoten zu ergänzen, indem er die Hosts-Datei um die passenden Parameter erweitert. Das schließt auch die Installation der Software auf dem Knoten mit ein.
Welche Rollen Ansible aufruft, entscheiden die Hosts-Datei (meist unter »/etc/ansible/hosts«) und die in ihr für jeden Host gesetzten Variablen. Listing 1 zeigt die ursprüngliche Datei.
Listing 1
/etc/ansible/hosts für den ELK-Stack
10.0.2.25 ansible_ssh_user=root logstash=1 kibana=1 masternode=1 grafana=1 do_ela=1 10.0.2.26 ansible_ssh_user=root masternode=0 do_ela=1 10.0.2.44 ansible_ssh_user=root masternode=0 do_ela=1
Beim Host »10.0.2.25« handelt es sich um den Masternode, auf dem sämtliche Software läuft. Die anderen beiden Hosts sind die Data Nodes des Clusters. Die Variable »do_ela« steuert, ob die Elasticsearch-Rolle tatsächlich Installationen vornehmen darf. Dies sorgt beim Erweitern des Clusters dafür, dass Ansible die bestehenden Nodes nicht umkonfiguriert – doch zu den Details später mehr.
Cluster in AWS erweitern
Die virtuelle Infrastruktur in AWS besteht aus einem eigenen VPC mit zwei Subnetzen. Das eine Subnetz lässt sich über das Internet erreichen. Das andere stellt den internen Bereich dar, in dem auch die beiden Server stehen, auf denen die Data Nodes 3 und 4 laufen sollen. Dazwischen befindet sich eine virtuelle Firewall, hier vom Hersteller Fortinet, die den VPN-Tunnel terminiert und über Firewall-Regeln den Zugriff steuert.
Vom Admin erfordert das in AWS mehrere Konfigurationsschritte. Zunächst legt er das VPC mit einem Hauptnetz an. Daraus vergibt er dann alle Subnetze, ein internes (innen) und ein über das Internet erreichbares (außen). Im äußeren Subnetz legt er dann ein Internet-Gateway an. Darüber findet der in Richtung Internet wandernde Datenverkehr einen Ausgang aus der Cloud. Dazu definiert der Admin eine Routing-Tabelle für das äußere Subnetz, die dieses Internet-Gateway als Standardroute angibt (Abbildung 1).
Cloud-Firewall
Im nächsten Schritt legt der Administrator eine Security Group an. Dabei handelt es sich um hostbezogene Firewall-Regeln für AWS. Weil sich die Firewall selbst schützen kann, öffnet die Gruppe die Firewall für sämtlichen ein- und ausgehenden Traffic, was sich aber auch einschränken ließe. Der Admin legt dazu einen S3-Bucket an, der die Startkonfiguration und die Lizenz für die Firewall enthalten soll. Er generiert dann die »config«-Datei für die Firewall und lädt sie zusammen mit der Lizenz hoch. Bei einer komplett gemieteten, aber teureren Firewall darf diese Lizenzangabe auch entfallen.
Nun richtet er Netzwerk-Interfaces für die Firewall in den beiden Subnetzen ein. Er erzeugt zudem eine Rolle, die es der Firewall-Instanz später erlaubt, auf den S3-Bucket zuzugreifen. Die Netzwerk-Interfaces und die Rolle ordnet er der dann zu erzeugenden Firewall-Instanz zu. Danach verknüpft er die Subnetze mit der Firewall. Er legt eine Routing-Tabelle für das innere Subnetz an und gibt die für das innere Netzwerk zuständige Netzwerkkarte der Firewall als Ziel an. Dann generiert er eine öffentliche IP-Adresse und weist sie der äußeren Netzwerkschnittstelle zu.
Weiter geht es mit dem Einrichten einer Security Group für die Server. Dazu erzeugt der Admin zunächst zwei Server-Instanzen im inneren Subnetz. Dann stellt er das innere Firewall-Interface von DHCP-Client auf die statische IP-Adresse um, die AWS-Firewall der Karte momentan zugewiesen hat. Jetzt setzt er einen VPN-Tunnel vom lokalen Netzwerk in die AWS-Cloud auf. Dabei muss er die Regeln und Routen auf der Firewall im lokalen Netzwerk festlegen. Am Ende dieser Konfigurationsorgie, und wenn sich alle neuen Cloud-Server erreichen lassen, installiert und konfiguriert der Admin schließlich den Elastic-Stack auf den beiden neuen AWS-Servern (Abbildung 2).
Cloud formieren
Prinzipiell wäre Ansible in der Lage, all diese Aufgaben zu erfüllen. Das würde sich aber spätestens beim Aufräumen des Ensembles in der Cloud rächen. Der Admin müsste die Informationen der dort erzeugten Komponenten entweder lokal speichern, oder er fragt im Aufräum-Playbook über eine Suche die tatsächlich zu entfernenden Komponenten ab, bevor das Playbook sie aufräumt.
Einfacher geht es mit einem Stack (ähnlich wie in OpenStack), in dem der Admin die komplette Infrastruktur parametrisierbar im YAML- oder JSON-Format beschreibt. Er baut diese dann mit einem Aufruf (auch von Ansible) auf und räumt mit einem weiteren Aufruf auch wieder auf. Die AWS-eigene Technologie hierfür heißt CloudFormation.
CloudFormation erlaubt es, dem Stack einen Aufbauparameter mit auf den Weg zu geben, im Beispiel die IP-Adressen der Netzwerke in der VPC. Der Autor des Stacks darf auch Rückgabewerte eintragen. Dabei handelt es sich typischerweise um die externe IP-Adresse eines erzeugten Objekts, damit der Anwender des Stacks weiß, wie er den Cloud-Dienst verwenden kann.
Die meisten Images für virtuelle Maschinen in AWS beherrschen die Cloud-init-Technologie (siehe Kasten “Cloud-init”). CloudFormation kann zudem einer virtuellen Maschine Cloud-init-Daten mitgeben. Wo zieht der Admin jetzt also die Grenze zwischen Ansible und CloudFormation? Die Antwort lautet: wo es praktikabel ist und den Gesamtaufwand vermindert.
Cloud-init
Bei Cloud-init handelt es sich um einen ursprünglich von Canonical entwickelten und mittlerweile herstellerübergreifend genutzten Standard, um auf Basis von Betriebssystem-Images Instanzen in der Cloud zu starten. Diese Instanzen setzen eine selbstdefinierte Locale, einen Hostnamen, SSH-Keys und vorübergehende Einhängepunkte, aber auch Benutzerdaten. Cloud-init arbeitet dabei mit einer Konfigurationsmanagementlösung der Wahl zusammen, egal, ob Chef, Puppet, Ansible oder Salt.
Fix und variabel
Einen fixen Bestandteil der Zielinfrastruktur bilden die VMs (die Firewall und die zwei Server für Elastic), der Netzwerkaufbau, die Routing-Struktur und die Logik der Security Groups. All diese Informationen gehören also auf jeden Fall in das CloudFormation-Template.
Variabel sind die IP-Adressen der Netze, die AWS-Region sowie Namen der Objekte. Sie gehen also als Parameter in die Stack-Definition ein, der Admin muss sie beim Aufruf des Stacks mitgeben. Zu den Variablen zählen zudem der Name des S3-Buckets für die Cloud-init-Konfiguration der Firewall und der bei AWS hinterlegte Public Key für SSH, der den Zugriff auf die Linux-VMs ermöglichen soll.
Schließlich benötigt der Admin zur weiteren Konfiguration die internen IP-Adressen der Linux-VMs, die externe öffentliche IP-Adresse der Firewall und die interne private IP-Adresse der Firewall. Dementsprechend gehören diese zu den Rückgabewerten des Stacks.
Ansible erledigt dabei alle Zuarbeiten. Es befüllt die Variablen, erzeugt die Firewall-Konfiguration, die die AWS-Firewall per Cloud-init erhält, und installiert die Software auf den Linux-VMs. Zwar könnte auch Cloud-init die Software installieren, aber Ansible richtet wieder genau die Rollen ein, die schon zu Beginn beim Konfigurieren der lokalen Server halfen.
Das CloudFormation-Template hat der Autor aus der Version des Firewall-Herstellers Fortinet [8] entwickelt. Dabei hat er den Aufbau gegenüber der Version auf Github vereinfacht, damit das Template in der Cloud nur eine Firewall und keinen Cluster hochzieht. Außerdem verwenden die Autoren des Fortinet-Templates eine Lambda-Funktion, um die Firewall-Konfiguration zu modifizieren. Diese Aufgabe erledigt aber das Playbook, das seinerseits das Template verwendet.
Im CloudFormation-Template darf der Prozess statisch erfolgen. Die zwei Linux-VMs verwenden Cent-OS als Betriebssystem und sollen im internen Subnetz laufen. Sie hängt der Admin einfach an das Template und die Rückgabewerte. Listing 2, Listing 3 und Listing 4 zeigen Auszüge der Stack-Definition im YAML-Format. Das komplette YAML-File finden Sie auf der LM-Website zu Download [9].
Listing 2
Stack-Definition im YAML-Format
[...]
Resources:
FortiVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock:
Ref: VPCNet
Tags:
- Key: Name
Value:
Ref: VPCName
FortiVPCFrontNet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock:
Ref: VPCSubnetFront
MapPublicIpOnLaunch: true
VpcId:
Ref: FortiVPC
FortiVPCBackNet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock:
Ref: VPCSubnetBack
MapPublicIpOnLaunch: false
AvailabilityZone: !GetAtt FortiVPCFrontNet.AvailabilityZone
VpcId:
Ref: FortiVPC
FortiSecGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Group for FG
GroupName: fg
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 0
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 0
ToPort: 65535
CidrIp: 0.0.0.0/0
VpcId:
Ref: FortiVPC
InstanceProfile:
Properties:
Path: /
Roles:
- Ref: InstanceRole
Type: AWS::IAM::InstanceProfile
InstanceRole:
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Version: 2012-10-17
Path: /
Policies:
- PolicyDocument:
Statement:
- Action:
- ec2:Describe*
- ec2:AssociateAddress
- ec2:AssignPrivateIpAddresses
- ec2:UnassignPrivateIpAddresses
- ec2:ReplaceRoute
- s3:GetObject
Effect: Allow
Resource: '*'
Version: 2012-10-17
PolicyName: ApplicationPolicy
Type: AWS::IAM::Role
Bei den Objekten vom Typ »AWS::EC2::Instance« handelt es sich um die eigentlichen virtuellen Maschinen, die den Elastic-Stack erweitern sollen (Listing 3 und Listing 5). Dabei ist die VM aufgrund der Firewall etwas aufwendiger zu konfigurieren, da sie zwei dedizierte Interface-Objekte besitzen muss, damit das Routing auf sie zeigen kann (Listing 3, Zeile 11).
Wichtig ist, dass die Firewall-Instanz und beide erzeugten Interfaces in derselben Verfügbarkeitszone liegen, sonst schlägt der Durchlauf des Stacks fehl. Daher gibt es in den virtuellen Maschinen Beschreibungen und im zweiten Subnetz den Bezug auf die Verfügbarkeitszone des ersten Subnetzes.
Im »UserData:«-Teil der Firewall-Instanz (Listing 3, Zeile 18) befindet sich eine Beschreibungsdatei, die der virtuellen Maschine mitteilt, wo sie die zuvor von Ansible hochgeladene Konfiguration und Lizenzdatei findet.
Listing 3
Fortsetzung von Listing 2
FortiInstance:
Type: "AWS::EC2::Instance"
Properties:
IamInstanceProfile:
Ref: InstanceProfile
ImageId: "ami-06f4dce9c3ae2c504" # for eu-west-3 paris
InstanceType: t2.small
AvailabilityZone: !GetAtt FortiVPCFrontNet.AvailabilityZone
KeyName:
Ref: KeyName
NetworkInterfaces:
- DeviceIndex: 0
NetworkInterfaceId:
Ref: fgteni1
- DeviceIndex: 1
NetworkInterfaceId:
Ref: fgteni2
UserData:
Fn::Base64:
Fn::Join:
- ''
-
- "{\n"
- '"bucket"'
- ' : "'
- Ref: S3Bucketname
- '"'
- ",\n"
- '"region"'
- ' : '
- '"'
- Ref: S3Region
- '"'
- ",\n"
- '"license"'
- ' : '
- '"'
- /
- Ref: LicenseFileName
- '"'
- ",\n"
- '"config"'
- ' : '
- '"'
- /fg.txt
- '"'
- "\n"
- '}'
InternetGateway:
Type: AWS::EC2::InternetGateway
AttachGateway:
Properties:
InternetGatewayId:
Ref: InternetGateway
VpcId:
Ref: FortiVPC
Type: AWS::EC2::VPCGatewayAttachment
RouteTablePub:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: FortiVPC
DefRoutePub:
DependsOn: AttachGateway
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
RouteTableId:
Ref: RouteTablePub
Type: AWS::EC2::Route
RouteTablePriv:
[...]
DefRoutePriv:
[...]
SubnetRouteTableAssociationPub:
Properties:
RouteTableId:
Ref: RouteTablePub
SubnetId:
Ref: FortiVPCFrontNet
Type: AWS::EC2::SubnetRouteTableAssociation
SubnetRouteTableAssociationPriv:
[...]
Listing 4
Fortsetzung von Listing 3
[...]
ServerInstance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: "ami-0e1ab783dc9489f34" # Centos7 for paris
InstanceType: t3.2xlarge
AvailabilityZone: !GetAtt FortiVPCFrontNet.AvailabilityZone
KeyName:
Ref: KeyName
SubnetId:
Ref: FortiVPCBackNet
SecurityGroupIds:
- !Ref ServerSecGroup
Server2Instance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: "ami-0e1ab783dc9489f34" # Centos7 for paris
[...]
Die Netzwerkkonfiguration wurde bereits beschrieben und ist im oberen Teil von Listing 2 niedergelegt. Das fertige Template kann der Admin jetzt auch allein auf der Kommandozeile mit dem Befehl »aws cloudformation create-stack« laufen lassen. Er gibt dabei den Namen der erzeugten YAML-Datei an und befüllt die Parameter am Beginn des Stacks. Dafür muss der S3-Bucket, den er übergeben möchte, bereits existieren. Sowohl die Lizenz als auch die erzeugte Konfiguration sollte er zudem im Vorfeld hochladen. Alle diese Aufgaben erledigt das Ansible-Playbook, das die Listings 5 bis**9 zeigen.
Das Playbook verwendet mehrere Plays. Das erste (Listing 5) dient dazu, wie beschrieben die Konfiguration für die Firewall sowie, falls nicht vorhanden, den S3-Bucket zu erzeugen (Zeile 20) und mitsamt der Lizenz hochzuladen.
Listing 5
Das Ansible-Playbook
---
- name: Create VDC in AWS with fortigate as front
hosts: localhost
connection: local
gather_facts: no
vars:
region: eu-west-3
licensefile: license.lic
wholenet: 10.100.0.0/16
frontnet: 10.100.254.0/28
netmaskback: 17
backnet: "10.100.0.0/{{ netmaskback }}"
lnet: 10.0.2.0/24
rnet: "{{ backnet }}"
s3name: stackdata
keyname: mgtkey
fgtpw: Firewall-Passwort
tasks:
- name: Create S3 Bucket for data
aws_s3:
bucket: "{{ s3name }}"
region: "{{ region }}"
mode: create
permission: public-read
register: s3bucket
- name: Upload License
aws_s3:
bucket: "{{ s3name }}"
region: "{{ region }}"
overwrite: different
object: "/{{ licensefile }}"
src: "{{ licensefile }}"
mode: put
- name: Generate Config
template:
src: awsforti-template.conf.j2
dest: fg.txt
- name: Upload Config
aws_s3:
bucket: "{{ s3name }}"
region: "{{ region }}"
overwrite: different
object: "/fg.txt"
src: "fg.txt"
mode: put
[...]
Die nächste Task erzeugt bereits den vollständigen Stack (Listing 6). Neu dazu kommt die Verbindung zum alten Elasticsearch-Playbook beziehungsweise Hosts-File. Das besitzt eine Gruppe namens »elahosts«, die das Playbook um die IP-Adressen der beiden neuen Server ergänzt, sodass für den weiteren Lauf des Playbooks insgesamt fünf Hosts in der Liste stehen. Einige Operationen sollen aber nur auf den neuen Hosts stattfinden. Dazu erzeugt Listing 6 in den Zeilen 44 und 49 die Gruppe »newhosts«, zu der es die beiden Hosts steckt.
Listing 6
Fortsetzung von Listing 5
[...]
- name: Create Stack
cloudformation:
stack_name: VPCFG
state: present
region: "{{ region }}"
template: fortistack.yml
template_parameters:
InstanceType: c5.large
FGUserName: admin
KeyName: "{{ keyname }}"
VPCName: VDCVPC
VPCNet: "{{ wholenet }}"
Kubnet: "{{ lnet }}"
VPCSubnetFront: "{{ frontnet }}"
VPCSubnetBack: "{{ backnet }}"
S3Bucketname: "{{ s3name }}"
LicenseFileName: "{{ licensefile }}"
S3Region: "{{ region }}"
register: stackinfo
- name: Print Results
[...]
- name: Wait for VM to be up
[...]
- name: New Group
add_host:
groupname: fg
hostname: "{{ stackinfo.stack_outputs.FortiGatepubIp }}"
- name: Add ElaGroup1
add_host:
groupname: elahosts
hostname: "{{ stackinfo.stack_outputs.Server1Address }}"
- name: Add ElaGroup2
add_host:
groupname: elahosts
hostname: "{{ stackinfo.stack_outputs.Server2Address }}"
- name: Add NewGroup1
add_host:
groupname: newhosts
hostname: "{{ stackinfo.stack_outputs.Server1Address }}"
- name: Add NewGroup2
add_host:
groupname: newhosts
hostname: "{{ stackinfo.stack_outputs.Server2Address }}"
- name: Set Fact
set_fact:
netmaskback: "{{ netmaskback }}"
- name: Set Fact
set_fact:
fgtpw: "{{ fgtpw }}"
[...]
Das nächste Play (Listing 7) konfiguriert die Firewall. In deren bereits vorhandener Konfiguration fehlt die statische IP-Adresse für die Netzwerkkarte der Innenseite, die AWS erst beim Erzeugen der Instanz setzt. Da die Daten nun aber bekannt sind, kann das Playbook sie setzen.
Beim ersten Anmelden an der Firewall fordert diese allerdings einen Passwortwechsel. Es gibt mehrere Methoden, mit denen der Admin eine Fortigate über Ansible einrichten kann. Die seit einer Weile in Ansible enthaltenen Forti-OS-Netzwerkmodule funktionieren jedoch noch nicht so richtig. Daher kommt an dieser Stelle einfach der »raw«-Ansatz zum Tragen (ab Zeile 10). Er schiebt die Kommandos wie im CLI auf das Gerät.
Die ersten beiden Zeilen des »raw«-Tasks setzen das Passwort, das in der AWS-Version auf der ID der Instanz liegt. Da die Lizenz bereits eingespielt wurde, bootet die Firewall nach deren Installation neu. Am Ende wartet das Ansible-Skript in Listing 7 darauf, dass der Reboot eintritt und dann, dass es die Firewall wieder erreicht.
Listing 7
Fortsetzung von Listing 6
[...]
- name: ChangePW
hosts: fg
vars:
ansible_user: admin
ansible_ssh_common_args: -o StrictHostKeyChecking=no
ansible_ssh_pass: "{{ hostvars['localhost'].stackinfo.stack_outputs.FortiGateId }}"
gather_facts: no
tasks:
- raw: |
"{{ hostvars['localhost'].fgtpw }}"
"{{ hostvars['localhost'].fgtpw }}"
config system interface
edit port2
set mode static
set ip "{{ hostvars['localhost'].stackinfo.stack_outputs.FGIntAddress }}/17"
next
end
tags: pw
- name: Wait for License Reboot
pause:
minutes: 1
- name: Wait for VM to be up
wait_for:
host: "{{ inventory_hostname }}"
port: 22
state: started
delegate_to: localhost
[...]
Nun folgt ein Play, das der lokalen Firewall beibringt, wie der VPN-Tunnel zur Firewall in AWS aussieht (Listing 8). Die VPN-Definition auf der Gegenseite steckte schon in der bereits hochgeladenen Konfiguration. Aufgrund der beschriebenen Probleme mit den Ansible-Modulen für Forti-OS (der Autor vermutet Unstimmigkeiten zwischen Ansible-Modulen und dem Python-Modul »fosapi«) verwendet das Play Ansibles URI-Methode, um die Firewall zu konfigurieren. Die Authentisierung für das API setzt einen Login-Prozess voraus. Er liefert dann ein Token zurück, das in den folgenden REST-Aufrufen zum Einsatz kommt.
Die Konfiguration besteht zunächst aus den Parametern für die Key-Exchange-Phasen 1 und 2. »Phase1« enthält das Passwort, Kryptoparameter sowie die IP-Adresse der Firewall in AWS. »Phase2« liefert ebenfalls Kryptoparameter sowie Daten zu den lokalen und entfernten Netzen. Außerdem liefert die Konfiguration eine Route (Zeile 61), die das Netz auf der AWS-Seite an den VPN-Tunnel übergibt, sowie zwei Firewall-Regeln, die den Traffic aus dem und in das private Netz auf AWS-Seite erlauben (Zeilen 70 und 84).
Listing 8
Fortsetzung von Listing 7
[...]
- name: Local Firewall Config
hosts: localhost
connection: local
gather_facts: no
vars:
localfw: 10.0.2.90
localadmin: admin
localpw: ""
vdom: root
lnet: 10.0.2.0/24
rnet: 10.100.0.0/17
remotefw: "{{ stackinfo.stack_outputs.FortiGatepubIp }}"
localinterface: port1
psk: "Passwort"
vpnname: elavpn
tasks:
- name: Get the token with uri
uri:
url: https://{{ localfw }}/logincheck
method: POST
validate_certs: no
body: "ajax=1&username={{ localadmin }}&password={{ localpw }}"
register: uriresult
tags: gettoken
- name: Get Token out
set_fact:
token: "{{
uriresult.cookies['ccsrftoken'] | regex_replace('\"', '') }}"
- debug: msg="{{ token }}"
- name: Phase1 old Style
uri:
url: https://{{ localfw }}/api/v2/cmdb/vpn.ipsec/phase1-interface
validate_certs: no
method: POST
headers:
X-CSRFTOKEN: "{{ token }}"
Cookie: "{{ uriresult.set_cookie }}"
body: "{{ lookup('template', 'forti-phase1.j2') }}"
body_format: json
register: answer
tags: phase1
- name: Phase2 old style
uri:
url: https://{{ localfw }}/api/v2/cmdb/vpn.ipsec/phase2-interface
validate_certs: no
method: POST
headers:
X-CSRFTOKEN: "{{ token }}"
Cookie: "{{ uriresult.set_cookie }}"
body: "{{ lookup('template', 'forti-phase2.j2') }}"
body_format: json
register: answer
tags: phase2
- name: Route old style
[...]
- name: Local Object Old Style
[...]
- name: Remote Object Old Stlye
[...]
- name: FW-Rule-In old style
uri:
url: https://{{ localfw }}/api/v2/cmdb/firewall/policy
validate_certs: no
method: POST
headers:
Cookie: "{{ uriresult.set_cookie }}"
X-CSRFTOKEN: "{{ token }}"
body:
[...]
body_format: json
register: answer
tags: rulein
- name: FW-Rule-out old style
uri:
url: https://{{ localfw }}/api/v2/cmdb/firewall/policy
validate_certs: no
method: POST
headers:
Cookie: "{{ uriresult.set_cookie }}"
X-CSRFTOKEN: "{{ token }}"
body:
[...]
body_format: json
register: answer
tags: ruleout
[...]
Ein Stück weiter unten (Listing 9) setzt das Playbook für die neuen Hosts den Parameter »do_ela« auf »1«, damit diese Rolle später auch Elasticsearch installiert. Als Wert für »masternode« verwendet es »0«, da es sich bei den neuen Hosts um Data Nodes handelt. Weil es in der Regel etwas dauert, bis die VPN-Verbindung einsatzbereit ist, wartet das Play nun auf dem Masternode des Elastic-Clusters, bis dieser einen neuen Knoten per SSH erreichen kann.
Das letzte Stück des Playbooks installiert schließlich Elasticsearch auf den neuen Knoten und passt deren Konfiguration so an, dass sie sich in den bestehenden Cluster einfügen. Die Rolle nimmt als Parameter etwa die Major-Version von Elasticsearch sowie einen Pfad, in dem der Elasticsearch-Server die Daten ablegen darf. Das erlaubt es, einen separaten Mount-Punkt auf einer reinen Datenfestplatte einzufügen.
Innerhalb von AWS sind zwar alle Systeme auf IPv6 vorbereitet, doch das gilt nicht für die im Artikel verwendete Konfiguration. Daher zwingt der erste Task zu einem Wechsel auf IPv4. Der zweite bringt die Konfiguration des Systems auf den aktuellen Stand. Im dritten Task installiert und konfiguriert die Elastic-Cluster-Rolle schließlich die Software.
Listing 9
Fortsetzung von Listing 8
[...]
- name: Set Facts for new hosts
hosts: newhosts
[...]
masternode: 0
do_ela: 1
- name: Wait For VPN Tunnel
hosts: 10.0.2.25
[...]
- name: Install elastic
hosts: elahosts
vars:
elaversion: 6
eladatapath: /elkdata
ansible_ssh_common_args: -o StrictHostKeyChecking=no
tasks:
- ini_file:
path: /etc/yum.conf
section: main
option: ip_resolve
value: 4
become: yes
become_method: sudo
when: do_ela == 1
name: Change yum.conf
- yum:
name: "*"
state: "latest"
name: RHUpdates
become: yes
become_method: sudo
when: do_ela == 1
- include_role:
name: matrix.centos-elasticcluster
vars:
clustername: matrixlog
elaversion: 6
when: do_ela == 1
- name: Set Permissions for data
file:
path: "{{ eladatapath }}"
owner: elasticsearch
group: elasticsearch
state: directory
mode: "4750"
become: yes
become_method: sudo
when: do_ela == 1
- systemd:
name: elasticsearch
state: restarted
become: yes
become_method: sudo
when: do_ela == 1
Da Ansible den Elasticsearch-Benutzer, dem der Ordner »elkdata/« gehören soll, erst im Laufe der Installation anlegt, muss das Skript auch noch die Permissions anpassen und Elasticsearch neu starten (ab Zeile 45). Damit ist der Cloud-Ausbau vollendet. Hat alles geklappt, bietet sich dem Admin in der Kibana-Konsole nach wenigen Augenblicken der Anblick aus Abbildung 3.
Großes Aufräumen
Will der Admin nach dem Ansturm die Erweiterung wieder abbauen, muss er die Knoten per API-Call aus dem Cluster entfernen. Das erledigt ein Aufruf wie in Listing 10. Er blockiert die weitere Zuweisung und veranlasst den Cluster dazu, alle Shards von diesem Knoten wegzubewegen. Nach der Aktion sind keine Shards mehr zugewiesen, und der Admin darf den Knoten einfach abschalten.
Listing 10
AWS-Teil des Clusters abbauen
curl -X PUT 10.0.2.25:9200/_cluster/settings \
-H 'Content-Type: application/json' \
-d '{"transient" : {"cluster.routing.allocation.exclude._ip":"10.100.68.139" } }'
Fazit
Die Hybrid-Cloud lebt davon, dass der Admin Skripte und Playbooks seiner eigenen Umgebung nahtlos auf die Cloud-Welt überträgt. Es gibt zwar höherwertige Cloud-Dienste (AWS besitzt auch Elasticsearch-as-a-Service), doch wie der Artikel zeigt, eignen diese sich in der Regel nur bedingt zum direkten Andocken. Um sie zu verwenden, muss der Admin in der Konfiguration auf die Eigenheiten des jeweiligen Cloud-Providers eingehen. Mit VMs in Microsoft Azure würde das hier gezeigte Beispiel hingegen direkt funktionieren, sodass der Anwender nur den CloudFormation-Teil durch ein Azure-Template ersetzen müsste. (kki)
Der Autor
Konstantin Agouros arbeitet als Head of Open Source & AWS Projects bei der Matrix Technology AG und berät dort mit seinem Team Kunden zu Open-Source- und Cloud-Themen. Sein Buch “Software Defined Networking: Praxis mit Controllern und OpenFlow” ist bei De Gruyter erschienen.
Infos
-
Azure: https://azure.microsoft.com
-
Google Cloud: https://cloud.google.com
-
Ansible: https://www.ansible.com
-
Puppet: https://puppet.com
-
Datensicherheit in der AWS-Cloud: Konstantin Agouros, “Schlüssel für Amazon”, LM 08/2019, S. 28, https://www.linux-magazin.de/43025
-
Fortinets Skriptvorlage: https://github.com/fortinet/aws-cloudformation-templates/tree/master/HA/6.0
-
Komplette Listings zum Artikel: http://www.linux-magazin.de/static/listings/magazin/2019/12/ansible/








