W poprzednich częściach cyklu przeszliśmy szybką drogę do stworzenia aplikacji mobilnej w oparciu o Azure App Service. Wszystkie przykłady przedstawiałem w oparciu o interfejs graficzny, którego intuicyjność pozwala poznać ogrom możliwości chmury. W pewnych okolicznościach wyklikiwanie wszystkiego może okazać się niepraktyczne, a co gorsza może prowadzić do poważnych błędów.
Terraform jest narzędziem pozwalającym wdrażać infrastrukturę jako kod (Infrastructure as a code). Opiera swoje działanie o ogromną ilość wtyczek (nazywanych provider’ami) do wszystkich popularnych dostawców chmurowych i nie tylko. W zasadzie każda usługa posiadająca swoje publiczne API może posiadać swojego provider’a. Niepełną ich listę można znaleźć pod adresem: https://www.terraform.io/docs/providers/index.html
Wszystko co chcemy zautomatyzować za pomocą terraform musi zostać zaimplementowane w języku HCL. Język ten ma charakter deklaratywny i ma wyrazić docelowy stan, a w momencie aplikacji zmian powoduje szereg wywołań API używanych providerów. Aplikacja zmian powoduje też odłożenie stanu jaki udało się osiągnąć np. w plikach terraform.tfstate, co pozwala na synchronizację i planowanie kolejnych zmian w zasobach.
Czy dałoby się jednak uniknąć przechowywania stanu? Cóż w pewnych scenariuszach byłoby to możliwe, jednak bardzo komplikowałoby to całą ideę szczególnie podczas operacji aktualizacji/zastąpienia zasobów, świadomości stworzonych metadanych i relacji między nimi oraz z powodów czysto wydajnościowych.
Praktyka
Spróbujmy przejść od tej suchej teorii do praktyki. Do utworzenia zasobów w Azure będzie nam potrzebny provider azurerm. Sprobujmy też przygotować dedykowaną grupę zasobów:
provider "azurerm" {
version = "~> 2.1.0"
features {}
}
resource "azurerm_resource_group" "example" {
name = "sgdevpl_blog_via_tf"
location = "westeurope"
}
Aby zaaplikować zmiany powinniśmy wykonać:
% terraform plan
% terraform apply
Przetworzenie całości trochę potrwa i powinniśmy być zalogowani za pomocą lokalnego klienta Azure dostępnego na wszystkie platformy (https://docs.microsoft.com/pl-pl/cli/azure/install-azure-cli). Do tak utworzonego pliku możemy przygotować definicję planu usługi określawjącą typ i rodzaj zasobów konsumowanych przez naszą aplikację:
resource "azurerm_app_service_plan" "test" {
name = "example-appserviceplan"
location = "${azurerm_resource_group.example.location}"
resource_group_name = "${azurerm_resource_group.example.name}"
kind = "Linux"
reserved = true
sku {
tier = "Free"
size = "F1"
}
}
Proszę zwróćcie uwagę w jaki sposób połączono lokalizację oraz grupę zasobów z uprzednio utworzonym planem.
Oczywiście po edycji pliku terraform ponownie wykonujemy polecenia plan i apply. W pliku stanu oraz w logach możemy podejrzeć addytywne zachowanie terraforma.
Po zdefiniowaniu planu przyszedł czas na przygotowanie usługi, która będzie z nego korzystać i pomieści naszą aplikację. Poniższe linijki mieszczą właściwie wszystkie ekrany związane z konfiguracją naszej aplikacji, a także musiały się w niej znaleźć zmienne środowiskowe przechowujące wszystkie sekrety. O tym w jaki sposób powinniśmy zarządzać sekretami opowiemy sobie w innym artykule tego cyklu.
resource "azurerm_app_service" "main" {
name = "sg-blog-appservice"
location = "${azurerm_resource_group.example.location}"
resource_group_name = "${azurerm_resource_group.example.name}"
app_service_plan_id = "${azurerm_app_service_plan.test.id}"
site_config {
app_command_line = ""
use_32_bit_worker_process = true
linux_fx_version = "DOCKER|registry.gitlab.com/sgdev_pl/marvelaggregator:latest"
}
app_settings = {
"WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
"DOCKER_REGISTRY_SERVER_URL" = "https://registry.gitlab.com"
"WEBSITES_CONTAINER_START_TIME_LIMIT" = "230"
"WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
"WEBSITES_PORT" = "8080"
"DOCKER_REGISTRY_SERVER_USERNAME" = "${var.docker_registry_server_username}"
"DOCKER_REGISTRY_SERVER_PASSWORD" = "${var.docker_registry_server_password}"
"DATABASE_URL" = "${var.database_url}"
"marvelclient_privateKey" = "${var.marvel_privateKey}"
"marvelclient_publicKey" = "${var.marvel_publicKey}"
"SPRING_DATASOURCE_URL" = "${var.spring_datasource_url}"
"SPRING_DATASOURCE_USERNAME" = "${var.spring_datasource_username}"
"SPRING_DATASOURCE_PASSWORD" = "${var.spring_datasource_password}"
}
}
Kiedy podejmiemy próbę aplikacji zmian okaże się że nasz kod jest niepoprawny:
Error: Reference to undeclared input variable
Oczywiście zmienne które zdefiniowaliśmy powinniśmy gdzieś zadeklarować. Utarło się że zmienne w terraform powinno umieścić się w plikach variables.tf lub vars.tf. Deklaracja wszystkich zmiennych wygląda w zbliżony sposób jak w innych językach programowania, a dobry opis ułatwia zarządzanie nimi.
variable "marvel_privateKey" {
description = "marvel_privateKey"
type = string
}
variable "marvel_publicKey" {
description = "marvel_publicKey"
type = string
}
variable "database_url" {
description = "DATABASE_URL as stated in Heroku"
type = string
}
variable "spring_datasource_url" {
description = "datasoure url as stated in Heroku"
type = string
}
variable "spring_datasource_username" {
description = "datasoure username as stated in Heroku"
type = string
}
variable "spring_datasource_password" {
description = "datasoure password as stated in Heroku"
type = string
}
variable "docker_registry_server_username" {
description = "docker registry server username"
type = string
}
variable "docker_registry_server_password" {
description = "docker registry server password"
type = string
}
Po dodaniu powyższych definicji przy próbie wykonania planu pojawi się ładne zapytanie o kolejne wartości:
% terraform plan
var.database_url
DATABASE_URL as stated in Heroku
Enter a value:
Takie zachowania można obejść na przykład poprzez zmienne środowiskowe. Muszą one przyjąć konkretny format tj. posiadać prefiks TF_VAR_. Jeśli uda nam się zestawić pełny zestaw zmiennych, mozolnie wpisując je w konsoli utworzenie aplikacji powinno udać się bez większych przeszkód.
Podsumowanie
W tym artykule udało nam się ująć definicję naszej aplikacji w dwóch plikach. Początkowo takie usprawnienie może wydawać się nadmiarowe, jednak zarządzając większą ilością usług możemy ujrzeć zalety automatyzacji i algorytmizacji naszej pracy.