Mit allen verbunden Teil 2
This content is more than 4 years old and the cloud moves fast so some information may be slightly out of date.
Infrastrukturtest des Transit Gateway mit kitchen und Inspec
Transit Gateway (What is a Transit Gateway? – Amazon Virtual Private Cloud) ist ein neuer AWS Service, der VPC Peering komplett ablösen kann. Zusätzlich kann damit auch ein „transitives" Peering gebaut werden. Das heißt, anders als bei einer VPC Peering Verbindung, die immer nur zwei VPCs miteinander verbindet, werden hier alle Netze miteinander verbunden. Das lohnt es sich genau zu testen! Diesen Test will ich hier möglichst vollautomatisiert aufbauen.
Dafür brauche ich zwei BlogPosts.
Teil 1 war “Test von CloudFormation mit taskCat”.
Dies ist Teil 2: Test der Transit-Gateway Infrastruktur mit Inspec.
Der Versuchsaufbau
Zuerst baut kitchen aus dem Chef Framework für uns die Testinstanz auf. Dann definieren wir den Test selber mit inspec. Nach einer kurzen Betrachtung der testgetriebenen Entwicklung bauen wir letztendlich das Transit Gateway und testen die Infrastruktur.
Automatischer Aufbau von Testinstanzen mit kitchen
Kitchen ist der Test Controller vom Chef Framework. Die sprachliche Analogie vom (Küchen) Chef findet sich an vielen Stellen wieder, so auch hier. Andere Tools heißen z.B. “Knife” oder “foodcritic”.
Mehr zu Chef unter Chef.io.
Um zu Testen baut kitchen Instanzen auf (create), und bereitet diese auf den Test vor. Danach kann ich mit “verify” Test ausführen. Diese Test sind in einer eigenen DSL geschrieben, inspec genannt.
Doch zuerst zur Kücheneinrichtung:
Konfiguration von kitchen
In der Konfigurationsdatei “.kitchen.yml” konfiguriere ich, wie kitchen mit AWS zusammenspielt. Zuerst teile ich kitchen mit, was für eine Art von Instanz erstellt werden soll. Ich kann z.B. auch Vagrant oder docker statt des AWS Treibers verwenden, wenn ein lokaler Test ausreicht. Für den Test der AWS Infrastruktur verwende ich den kitchen-ec2
Treiber.
Driver
---
driver:
name: ec2
region: eu-central-1
Der driver ist also ec2
. Zusätzlich definiere ich die Region, hier Frankfurt. Der EC2 Driver hat ein eigenes Github Projekt. Zu Anfang definiere ich den Standard Driver. In der platform
Sektion kann ich einen anderen Treiber definieren. Hier verwende ich die Platform Daten, um weitere Konfigurationen vorzunehmen.
Provisioner
provisioner:
name: chef_zero
always_update_cookbooks: true
Der provisioner
wird nur benötigt, wenn ich Chef Rezepte teste. Daher wird er hier nicht verwendet. Da Chef normalerweise eine Client Server Infrastruktur verwendet, würde bei einem Test von Chef Konfigurationen bzw. Kochbüchern cookbooks
mit dem Provisioner chef_zero
der Server lokal simuliert werden.
Verifier
Der verifier führt die eigentlichen Tests aus.
verifier:
name: inspec
Für die verify
Phase wird inspec definiert.
Transport
transport:
username: ["ec2-user"]
Der Datentransport kann in Kitchen mit ssh oder winrm passieren. Hier definieren wir den richtigen Usernamen für das AMI.
Platform Definition
platforms:
- name: amazon
driver:
region: eu-central-1
image_id: ami-0cfbf4f6db41068ac
instance_type: t2.large
subnet_id: subnet-01cb511f85c1b2356
tags:
Name: "Kitchen Test Instance ec2"
Hier setze manuell ich die Subnet ID aus dem erzeugten VPC/Subnet A ein.
Testsuites
Hier können verschiedene Test in “Suites” zusammengefasst werden. Für dieses einfache Beispiel verwende ich nur die Standard suite.
suites:
- name: default
Ablauf des Test
Wenn ihr also den Test nachbauen wollt, muss Folgendes in .kitchen.yaml
angepasst werden:
- Die Region
- Die Subnet ID
Definition von Test mit der Inspec DSL
Für die Definition der Tests gibt es eine spezielle DSL Sprache, die viele Testarten vordefiniert hat. Hier verwende ich diese Sprache inspec
(Inspec Webseite). Genau genommen ist es in ruby geschrieben, baut aber eine eigene Sprache oben drauf.
Der Vorteil dabei ist, dass viele Resourcentypen bereits vordefiniert sind. Inspec definiert dabei auch viele AWS Resourcen, wie vpc, subnet, s3 usw. Zusammen mit kitchen werden die notwendigen Test Instanzen automatisch aufgebaut.
Da ich hier nur die Erreichbarkeit der Instanzen teste, kann ich die Resource http
verwenden. Ein einfaches Beispiel ist:
describe http('http://localhost') do
its('status') { should cmp 200 }
End
Der code erschließt sich so sehr einfach. Abgefragt wird die URL http://localhost
und die Antwort wird auf den Status geprüft. Natürlich kann ich viele andere Attribute prüfen. (Siehe Dokumentation inspec Resourcen) Für Netz A definiere ich diesen Test in test/integration/default/default_test.rb
:
describe http('http://10.0.1.16') do
its('status') { should cmp 200 }
its('body') { should eq 'Node A' }
end
Der Webserver auf der Instanz in VPC a wird auf Erreichbarkeit geprüft und der Inhalt wird auf den Text “Node A” geprüft. Dies korrespondiert mit der UserData der Instanz:
UserData:
Fn::Base64: |
#!/bin/bash -xe
yum update -y
yum install -y httpd24
service httpd start
chkconfig httpd on
echo "Node A" >/var/www/html/index.html
Der Webserver auf Instanz 10.0.1.16 in VPC A wird auf Port 80 innerhalb des VPC A getestet.
Ablauf beim Testen
Die grundsätzliche Philosophie des testgetriebenen Vorgehens ist, dass man sich vor der Erstellung der Funktionalität überlegt, was denn das Ergebnis der Funktion (/des Programms/der Infrastruktur) sein sollte.
Rot
Dann erstelle ich einen Test, der dann bei der ersten Ausführung fehlschlagen muss, also “Rot” ergibt. Das ist genau das erwartete Ergebnis, da die Funktionalität ja noch nicht vorhanden ist.
Grün
Jetzt baue ich die Funktionalität und teste immer wieder, bis die Funktion meinen Erwartungen entspricht, also “Grün” ist.
Umbau
Jetzt habe ich eine Funktion und einen automatisierten Test, dass diese Funktion ihren Zweck erfüllt. Jetzt kann ich unter der Haube umbauen und mit dem Test sicherstellen (verify Phase), dass die Funktionalität immer noch gegeben ist.
Lets Test
Alle Vorarbeiten und Vorüberlegungen sind abgeschlossen, wir können loslegen. Die meisten Schritte sind in einem Makefile
automatisiert.
Eine Übersicht der Schritte bekommt man mit make help
. Der Ablauf des Test in der Übersicht:
-
VPCs erstellen:
make deploy
-
Region und Subnet ID von VPC A in
.kitchen.yml
updaten -
Testumgebung aufbauen:
make start-kitchen
-
Test ausführen:
make test-kitchen
Ohne VPC Peering und Transit Gateway bekommen ich dann z.B. folgende Ausgabe vom Test:
http GET on http://10.0.1.16
✔ status should cmp == 200
✔ body should eq "Node A"
http GET on http://10.0.2.16
× status should cmp == 200
expected: 200
got: nil
(compared using `cmp` matcher)
× body should eq "Node B"
expected: "Node B"
got: nil
(compared using ==)
http GET on http://10.0.3.16
× status should cmp == 200
expected: 200
got: nil
(compared using `cmp` matcher)
× body should eq "Node C"
expected: "Node C"
got: nil
(compared using ==)
Test Summary: 2 successful, 4 failures, 0 skipped
Im lokalen Netz A, in dem auch die Testinstanz läuft ist der Zugriff auf 10.0.1.16
möglich, auf die anderen Netze noch nicht.
Nun endlich kann ich das Transit Gateway als Verbindung konfigurieren.
Transit Gateway
Dazu brauche ich folgende Komponenten:
- das Transit Gateway selber
- Pro VPC ein Transit Gateway Attachment
- Routen in den VPC zu den anderen VPCs
Das Transit Gateway (TGW)ist der Knotenpunkt, der Router der alles verbindet. Um eine Verbindung vom VPC zu dem TGW zu bekommen, muss ein VPC mit dem TGW verbunden werden. Diese Verbindung ist das Transit Gateway Attachment. Als letztes Element muss man im VPC die Subnets, die mit den anderen VPS verbunden werden sollen mit Routen versorgen. Die Routen Tabellen hängen am Subnet, nicht am VPC.
Ein Gesamtdurchlauf
Zusammen mit den VPCs aus Teil 1 können wir jetzt einen Gesamtdurchlauf machen:
Tools installieren
- ChefDK
- Clouds-aws
- Aws cli
VPCs aufbauen
Eingabe: make deploy
Dauer: ca. 1min
clouds -r eu-central-1 update -c --events demo-vpc-b &
clouds -r eu-central-1 update -c --events demo-vpc-c &
clouds -r eu-central-1 update -c --events demo-vpc-a
2018-12-30/15:17:08 CREATE_IN_PROGRESS AWS::CloudFormation::Stack demo-vpc-a User Initiated
2018-12-30/15:17:08 CREATE_IN_PROGRESS AWS::CloudFormation::Stack demo-vpc-b User Initiated
2018-12-30/15:17:08 CREATE_IN_PROGRESS AWS::CloudFormation::Stack demo-vpc-c User Initiated
2018-12-30/15:17:11 CREATE_IN_PROGRESS AWS::EC2::VPC VPC
2018-12-30/15:17:11 CREATE_IN_PROGRESS AWS::EC2::InternetGateway InternetGateway
2018-12-30/15:17:11 CREATE_IN_PROGRESS AWS::EC2::VPC
...
In der AWS Console sehe ich nun die drei VPCs:
Vom Subnet des VPCs A merke ich mir die ID:
Die ID trage ich in die .kitchen.yml
ein:
Kitchen Testinstanz aufbauen
Vorbereiten der Verwendung ChefDK auf Bash:
eval "$(chef shell-init bash)"
Eingabe: make start-kitchen
Dauer: ca. 1. Min.
kitchen create
-----> Starting Kitchen (v1.23.3)
WARN: Unresolved specs during Gem::Specification.reset:
concurrent-ruby (~> 1.0)
WARN: Clearing out unresolved specs.
Please report a bug if this causes problems.
-----> Creating <default-amazon>...
Detected platform: amazon version 2018.03. on x86_64. Instance Type: t2.large. Default username: ec2-user (default).
If you are not using an account that qualifies under the AWS
free-tier, you may be charged to run these suites. The charge
should be minimal, but neither Test Kitchen nor its maintainers
are responsible for your incurred costs.
Created automatic security group sg-0bcd26ac38ceea95c
Created automatic key pair kitchen-defaultamazon-silberkopf-GernotsMBPfritzbox-2018-12-30T15:23:20Z-u9w48qe6
Instance <i-09467b6ad93ddda02> requested.
Polling AWS for existence, attempt 0...
Attempting to tag the instance, 0 retries
EC2 instance <i-09467b6ad93ddda02> created.
Waited 0/300s for instance <i-09467b6ad93ddda02> volumes to be ready.
Waited 0/300s for instance <i-09467b6ad93ddda02> to become ready.
Waited 5/300s for instance <i-09467b6ad93ddda02> to become ready.
Waited 10/300s for instance <i-09467b6ad93ddda02> to become ready.
Waited 15/300s for instance <i-09467b6ad93ddda02> to become ready.
Waited 20/300s for instance <i-09467b6ad93ddda02> to become ready.
EC2 instance <i-09467b6ad93ddda02> ready (hostname: 35.159.15.219).
Waiting for SSH service on 35.159.15.219:22, retrying in 3 seconds
Waiting for SSH service on 35.159.15.219:22, retrying in 3 seconds
Waiting for SSH service on 35.159.15.219:22, retrying in 3 seconds
Waiting for SSH service on 35.159.15.219:22, retrying in 3 seconds
[SSH] Established
Finished creating <default-amazon> (0m41.87s).
-----> Kitchen is finished. (0m48.18s)
Erster Test
Eingabe: make test-kitchen
kitchen verify
-----> Starting Kitchen (v1.23.3)
WARN: Unresolved specs during Gem::Specification.reset:
concurrent-ruby (~> 1.0)
WARN: Clearing out unresolved specs.
Please report a bug if this causes problems.
-----> Converging <default-amazon>...
...
http GET on http://10.0.1.16
✔ status should cmp == 200
✔ body should eq "Node A"
http GET on http://10.0.2.16
× status should cmp == 200
...
Test Summary: 2 successful, 2 failures, 0 skipped
Da die kitchen Test instanz im VPC A erstellt wurde, kann sie auf den Webserver in VPC A zugreifen, nicht aber in B und C.
Transit Gateway erstellen
In der AWS Console:
- VPC-Transit Gateways (ganz unten links)
- Create Transit Gateway
Name Tag: “demo TGW” Description: “demo TGW” Alle anderen Einstellungen können im Standard bleiben. Jetzt dauert es ein paar Minuten, bis das TGW erstellt wurde.
Wie auch bei der EC2 Konsole, wenn man eine Instanz erstellt, kann Klick auf den Refresh Knopf ab und zu nicht schaden.
Transit Gateway Attachment A erstellen
In der AWS Console:
- VPC-Transit Gateways (ganz unten links)
- Create Transit Gateway Attachment
Transit Gateway ID: Eben erstelltes TGW wählen Attachment type: VPC Attachment name tag: Demo A VPC ID: VPC Demo A wählen.
- Create Attachment klicken.
Sobald das TGW Attachment in den Status “available” wechselt, erscheinen automatisch in der TGW Route Table unter “Associations”, “Propagations” und “Routes” Einträge.
Transit Gateway Attachment B erstellen
Das Gleiche wird jetzt für VPC B wiederholt: In der AWS Console:
- Service VPC wählen
- VPC-Transit Gateways (ganz unten links)
- Create Transit Gateway Attachment
Transit Gateway ID: Eben erstelltes TGW wählen Attachment type: VPC Attachment name tag: Demo B VPC ID: VPC Demo B wählen.
Test erneut ausführen
Da VPC A und B jetzt am TGW hängen, erwartet man ja irgendwie, dass die beiden sich jetzt unterhalten können… Die Ausführung des Kitchen Test make test-kitchen
belehrt uns eines Besseren: A und B sehen sich noch nicht!
Test Summary: 2 successful, 2 failures, 0 skipped
Was fehlt? Es fehlt der Rückweg! Oder der Hinweg, je nachdem wie man es betrachtet. Wir müssen noch in den Routentabellen von den Subnetzen in VPC A und B Routen für das TGW erstellen.
Route für VPC A erstellen
In der AWS Console:
- VPC-Route Tables (oben links)
- Demo_A Public Routes wählen
- Routes/Edit Routes
- Route hinzufügen: Destination: 10.0.0.0/16 Target: Eben erstelltes Transit Gateway
- Demo_A Public Routes wählen
Die Tabelle sieht danach in etwa so aus:
Auch jetzt ergibt der Test noch “Rot”. Erst wenn wir auch die Route vom VPC B zum TGW hinzufügen, klappt es:
http GET on http://10.0.1.16
✔ status should cmp == 200
✔ body should eq "Node A"
http GET on http://10.0.2.16
✔ status should cmp == 200
http GET on http://10.0.3.16
× status should cmp == 200
expected: 200
got:
(compared using `cmp` matcher)
Test Summary: 3 successful, 1 failure, 0 skipped
Und damit steht die Verbindung zwischen den VPCs! Durch das Testgetriebene Vorgehen konnten wir an jeder Stelle mit make test-kitchen
testen, ob die IP Verbindung steht und die Routen sowie die Security Groups usw. korrekt eingerichtet sind. Ohne Testen ist es so etwas wie ein Blindflug.
Deswegen gilt auch für Infrastrukturprojekte: Test first! U
nd um das Nachbauen einfacher zu machen, haben wir den Code auf github veröffentlicht:
Benötigen Sie Unterstützung bei Ihrem AWS oder Chef Projekt? Kontaktieren sie uns gerne hier.