Terraform und Exoscale

Derzeit bin ich mich am Umschauen, welche Cloudprovider in der Schweiz vorhanden sind und welchen Service sie in Bezug auf Automatisierung anbieten. Einer dieser Provider ist exoscale. In diesem Artikel möchte ich kurz beschreiben, wie ich Compute-Instanzen in Exoscale in Kombination mit Terraform erzeugen und wieder löschen kann. Eine solche Aufgabe nicht via einem graphischen User Interface zu machen – das von Exoscale ist sehr gut – sondern in Source Code zu formulieren, macht die Verwaltung der Infrastruktur in einem Source Code Repository möglich – Infrastructure As Code.

Exoscale stellt eine API zur Verfügung um via HTTP diese Aufgaben automatisieren zu können. Das Schöne ist, dass sie zur Bereitstellung der Services Apache Cloudstack verwenden. D.h. alle Tools die Apache Cloudstack implementieren, können zusammen mit dem Service benutzt werden.

Da mich die Werkzeuge von HashiCorp immer wieder beeindrucken und ich sie aus verschieden Aspekten sehr gut finde, benutze ich zum verwalten meiner Test-Cloud Terraform. Als Einführung in Terraform kann ich die “Getting Startet“-Dokumentation empfehlen.

Hier der Inhalt meines Beispielprojektes:

.
+-- terraform.tfvars
+-- web
    +-- main.tf

In der Datei terraform.tfvars sind folgende Variablen deklariert.
exo_api_url         = "https://api.exoscale.ch/compute"
exo_api_key         = "your-api-key"
exo_secret_key      = "your-secret-key"
exo_keypair         = "ollin-es"

Den API- und Secret Key bekommt man, nach erfolgreicher Registrierung und Einloggen ins Portal, aus der Admin Konsole (Account -> Profile -> API Keys). Ein SSH Key Pair habe ich angelegt, damit ich nachher einfach via ssh auf den Rechner zugreifen kann. Hier wird nur der Name des Key Pairs angegeben, den ich für die zu erzeugende Instanz benutzen möchte.

Die Datei web/main.tf enthält die Deklaration, welcher Zustand erreicht werden soll.

variable "exo_api_url" {}
variable "exo_api_key" {}
variable "exo_secret_key" {}
variable "exo_keypair" {}

provider "cloudstack" {
  api_url = "${var.exo_api_url}"
  api_key = "${var.exo_api_key}"
  secret_key = "${var.exo_secret_key}"
  keypair = "${var.exo_keypair}"
}

resource "cloudstack_instance" "web" {
  count = 1

  name = "web-${count.index}"
  service_offering= "Micro"
  template = "Linux Ubuntu 16.04 LTS 64-bit"
  zone = "ch-dk-2"
}

Nach der Deklaration der Variablen, deren Werte zur Laufzeit von Terraform, aus der Datei terraform.tfvars gelesen werden, wird der Provider definiert. cloudstack ist nur ein Provider von vielen, die von Terraform unterstützt werden. Der “resource”-Block deklariert in meinem Fall eine Ressource die ich “web” genannt habe. Die Werte für service_offering, template und zone habe ich via Copy and Paste aus der Admin Console von exoscale übernommen. Der Wert von keypair entspricht dem Namen meines angelegten Keypairs.

Ich möchte eine Instanz (count = 1) von dieser Ressource haben und der Name der Instanz setzt sich aus dem String “web-” und der Nummer der Instanz (count.index) zusammen. Warum nicht einfach “web-1” wird später ersichtlich, wenn wir mehrere Instanzen erzeugen. Diese Interpolation von Werten in Strings ist sehr hilfreich.

Um zu sehen, was passieren würde, wenn man nun terraform ausführt, gibt es das terraform plan Kommando. Das Arbeitsverzeichnis in dem man die Kommandos ausführt ist das Root-Verzeichnis des Projektes.

07:42 $ terraform plan web
Refreshing Terraform state prior to plan...

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ cloudstack_instance.web
    display_name:     "" => "<computed>"
    expunge:          "" => "0"
    ipaddress:        "" => "<computed>"
    name:             "" => "web-0"
    service_offering: "" => "Micro"
    template:         "" => "Linux Ubuntu 16.04 LTS 64-bit"
    zone:             "" => "ch-dk-2"

Plan: 1 to add, 0 to change, 0 to destroy.

Mit terraform apply web wird nun der Plan ausgeführt und wo zuvor keine Instanz war ist danach eine Instanz vorhanden.

exoscale-01-empty

$ terraform apply web
cloudstack_instance.web: Creating...
  display_name:     "" => "<computed>"
  expunge:          "" => "0"
  ipaddress:        "" => "<computed>"
  keypair:          "" => "ollin-es"
  name:             "" => "web-0"
  service_offering: "" => "Micro"
  template:         "" => "Linux Ubuntu 16.04 LTS 64-bit"
  zone:             "" => "ch-dk-2"
cloudstack_instance.web: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

exoscale-02-web-0

Wenn man nun den count der Instanzen auf 5 ändert und nochmals terraform apply ausführt, so bekommt man 4 zusätzliche Instanzen.

$ cat web/main.tf 
variable "exo_api_url" {}
variable "exo_api_key" {}
variable "exo_secret_key" {}
variable "exo_keypair" {}

provider "cloudstack" {
  api_url = "${var.exo_api_url}"
  api_key = "${var.exo_api_key}"
  secret_key = "${var.exo_secret_key}"
}

resource "cloudstack_instance" "web" {
  count = 5

  name = "web-${count.index}"
  service_offering= "Micro"
  template = "Linux Ubuntu 16.04 LTS 64-bit"
  zone = "ch-dk-2"
  keypair = "${var.exo_keypair}"
}
$ terraform apply web
cloudstack_instance.web.0: Refreshing state... (ID: ebfacb67-2a12-44e4-8d39-928e9dc5287d)
cloudstack_instance.web.3: Creating...
  display_name:     "" => "<computed>"
  expunge:          "" => "0"
  ipaddress:        "" => "<computed>"
  keypair:          "" => "ollin-es"
  name:             "" => "web-3"
  service_offering: "" => "Micro"
  template:         "" => "Linux Ubuntu 16.04 LTS 64-bit"
  zone:             "" => "ch-dk-2"
cloudstack_instance.web.1: Creating...
  display_name:     "" => "<computed>"
  expunge:          "" => "0"
  ipaddress:        "" => "<computed>"
  keypair:          "" => "ollin-es"
  name:             "" => "web-1"
  service_offering: "" => "Micro"
  template:         "" => "Linux Ubuntu 16.04 LTS 64-bit"
  zone:             "" => "ch-dk-2"
cloudstack_instance.web.4: Creating...
  display_name:     "" => "<computed>"
  expunge:          "" => "0"
  ipaddress:        "" => "<computed>"
  keypair:          "" => "ollin-es"
  name:             "" => "web-4"
  service_offering: "" => "Micro"
  template:         "" => "Linux Ubuntu 16.04 LTS 64-bit"
  zone:             "" => "ch-dk-2"
cloudstack_instance.web.2: Creating...
  display_name:     "" => "<computed>"
  expunge:          "" => "0"
  ipaddress:        "" => "<computed>"
  keypair:          "" => "ollin-es"
  name:             "" => "web-2"
  service_offering: "" => "Micro"
  template:         "" => "Linux Ubuntu 16.04 LTS 64-bit"
  zone:             "" => "ch-dk-2"
cloudstack_instance.web.0: Modifying...
  network: "defaultGuestNetwork" => ""
cloudstack_instance.web.0: Modifications complete
cloudstack_instance.web.3: Creation complete
cloudstack_instance.web.4: Creation complete
cloudstack_instance.web.2: Creation complete
cloudstack_instance.web.1: Creation complete

Apply complete! Resources: 4 added, 1 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

exoscale-02-web-1-5

Werden die zusätzlichen Instanzen nicht mehr benötigt, so setzt man den count wieder auf 1 und führt terraform erneut aus.

 ~/dev/src/github.com/ollin/learning/exoscale 
$ cat web/main.tf 
variable "exo_api_url" {}
variable "exo_api_key" {}
variable "exo_secret_key" {}
variable "exo_keypair" {}

provider "cloudstack" {
  api_url = "${var.exo_api_url}"
  api_key = "${var.exo_api_key}"
  secret_key = "${var.exo_secret_key}"
}

resource "cloudstack_instance" "web" {
  count = 1 

  name = "web-${count.index}"
  service_offering= "Micro"
  template = "Linux Ubuntu 16.04 LTS 64-bit"
  zone = "ch-dk-2"
  keypair = "${var.exo_keypair}"
}
 ~/dev/src/github.com/ollin/learning/exoscale 
$ terraform plan web
Refreshing Terraform state prior to plan...

cloudstack_instance.web.3: Refreshing state... (ID: 6005c354-7bdd-46e1-ba86-d8699572e5a0)
cloudstack_instance.web.4: Refreshing state... (ID: 4a709571-e5e5-4dac-adba-bd689580a787)
cloudstack_instance.web.1: Refreshing state... (ID: 4eedabea-ad98-439a-99ef-cf3cee108efb)
cloudstack_instance.web.2: Refreshing state... (ID: 695dfeeb-60e8-49cc-ae57-286b4afcd8ad)
cloudstack_instance.web: Refreshing state... (ID: ebfacb67-2a12-44e4-8d39-928e9dc5287d)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

~ cloudstack_instance.web
    network: "defaultGuestNetwork" => ""

- cloudstack_instance.web.1

- cloudstack_instance.web.2

- cloudstack_instance.web.3

- cloudstack_instance.web.4

Plan: 0 to add, 1 to change, 4 to destroy.
 ~/dev/src/github.com/ollin/learning/exoscale 
$ terraform apply web
cloudstack_instance.web.4: Refreshing state... (ID: 4a709571-e5e5-4dac-adba-bd689580a787)
cloudstack_instance.web.2: Refreshing state... (ID: 695dfeeb-60e8-49cc-ae57-286b4afcd8ad)
cloudstack_instance.web.1: Refreshing state... (ID: 4eedabea-ad98-439a-99ef-cf3cee108efb)
cloudstack_instance.web.3: Refreshing state... (ID: 6005c354-7bdd-46e1-ba86-d8699572e5a0)
cloudstack_instance.web: Refreshing state... (ID: ebfacb67-2a12-44e4-8d39-928e9dc5287d)
cloudstack_instance.web.1: Destroying...
cloudstack_instance.web.4: Destroying...
cloudstack_instance.web.3: Destroying...
cloudstack_instance.web.2: Destroying...
cloudstack_instance.web.2: Destruction complete
cloudstack_instance.web.4: Destruction complete
cloudstack_instance.web.1: Destruction complete
cloudstack_instance.web.3: Destruction complete
cloudstack_instance.web: Modifying...
  network: "defaultGuestNetwork" => ""
cloudstack_instance.web: Modifications complete

Apply complete! Resources: 0 added, 1 changed, 4 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

exoscale-03-web-1

Fazit

Der Service von Exoscale kann zusammen mit Terraform benutzt werden. Der Aufwand zur Erstellung der Skripte ist sehr klein. Angenehm ist, dass die Instanzen bei Exoscale sehr schnell verfügbar sind. Bei meinen Tests wurden die versprochenen 30 Sekunden komfortabel eingehalten.

3 thoughts on “Terraform und Exoscale

  1. ollin Post author

    Hallo jbx,

    Danke für den Kommentar. Es stimmt – es wird noch nicht alles unterstützt. Das mit den Security Groups muss ich mir mal anschauen. Bisher habe ich auch noch keine Lösung zum Einstellen der Harddisk gefunden. Sicher eine Frage der Zeit. Allerdings machen die Leute bei Hashicorp sehr viel und jedes mal wenn ich wieder in die Dokumentation schaue, bin ich erstaunt wie viel passiert ist. Eventuell bringt sich ja exoscale selber ins Projekt ein und trägt mit bei ( https://github.com/hashicorp/terraform ) .

    Gruss Oliver

  2. jbx

    Hallo,

    Vor einiger Zeit habe ich auch versucht mit dieser Kombination was aufzubauen. Leider kennt Terraform die “Security Group” Konstrukt von Exoscale nicht. Alle neue erstellte Instanzen landen im “Default” SG.

    Für ein einfaches Beispiel wie bei deinem mag es funktionieren. Sobald man meherere SGs braucht ist die fehlende Funktion von Terraform ein “pain in the @$$”

    Ich habe soweit die Erstellung der Instanzen mit CloudMonkey geskriptet..

Leave a Reply

Your email address will not be published. Required fields are marked *