Wprowadzenie
W ostatnim czasie na blogu pojawiło się kilka wpisów z tematyki Terraform. Skupiałem się w nich głównie na używaniu Terraform do stawiania infrastruktury w Azure. W ostatnim wpisie z tej serii pokazałem przykładową aplikację wykorzystującą podstawowe elementy infrastruktury Azure. Dzisiaj przyszedł czas na automatyczne wykonywanie Terraform z poziomu Azure DevOps.
Backend
Do tej pory we wcześniejszych wpisach korzystaliśmy z Terraform w lokalnym środowisku. Takie rozwiązanie jest fajne podczas przygotowywania skryptu, czy zabawy samym Terraformem, ale już podczas wdrażania różnych środowisk nie jest to najlepszy pomysł. Szczególnie jeśli chcielibyśmy automatycznie zmieniać środowiska podczas procesu CI/CD.
Jak wspominałem we wcześniejszych wpisach, Terraform wykorzystuje plik stanu, w którym zapisuje informacje o utworzonych zasobach oraz ich właściwościach. Do tej pory plik stanu był zapisywany lokalnie na naszym komputerze, a jest on potrzebny do aktualizacji środowisk. Dlatego powinniśmy przechowywać go w jakiejś ogólnodostępnej lokalizacji (oczywiście tylko dla naszego zespołu).
Za przechowywanie plików stanu w Terraform odpowiedzialne są backendy. Gdy nie zdefiniujemy żadnego, to używany jest domyślny plik stanu przechowywany lokalnie. Mamy do dyspozycji sporą ich liczbę (https://www.terraform.io/language/settings/backends), a nas najbardziej będzie interesował azurerm (https://www.terraform.io/language/settings/backends/azurerm), który umożliwia przechowywanie pliku stanu w Azure Storage Account. W Azure DevOps mamy wsparcie dla tego backendu.
azurerm
Do głównego pliku Terraforma dodajemy konfigurację azurerm w sekcji terraform. Mamy różne sposoby uwierzytelniania się (po szczegóły zajrzyj do dokumentacji). Najbardziej podstawowa konfiguracja wygląda tak:
terraform { | |
backend "azurerm" { | |
resource_group_name = "StorageAccount-ResourceGroup" | |
storage_account_name = "tfstates" | |
container_name = "tfstates" | |
key = "prod.tfstate" | |
} | |
} |
W powyższej konfiguracji określamy:
- resource_group_name – nazwę grupy zasobów, w której znajduje się Azure Storage Account,
- storage_account_name – nazwę Azure Storage Account,
- container_name – nazwę kontenera, w którym będzie się znajdować plik stanu,
- key – nazwę pliku stanu.
W powyższej konfiguracji zostanie użyte konto, które tworzy nam zasoby w Azure i które skonfigurowaliśmy z użyciem komendy „az login”. Dlatego to konto powinno mieć dostęp do Azure Storage Account. Samo konto na ogół tworzymy ręcznie i przechowujemy w nim pliki stanu dla wszystkich środowisk wykorzystywanych w danym projekcie.
Po skonfigurowaniu azurerm plik stanu będzie przechowywany w Azure Storage Account, a nie w lokalnym folderze.
Azure DevOps – pipeline
Wiedząc już, czym jest backend w Terraform, możemy przejść do konfiguracji release w Azure DevOps. Konfiguracja azurerm będzie automatycznie ustawiana przez jeden z kroków w Azure DevOps, dlatego w samym repozytorium będziemy mieli pustą sekcję z konfiguracją backendu:
Pierwszą rzeczą, jaką musimy zrobić w Azure DevOps, jest skonfigurowanie pipeline. W przykładzie do tego wpisu będzie on bardzo prosty i sprowadzi się tylko do skopiowania plików Terraform do artefaktów:
Więcej ciekawszych rzeczy dzieje się w release.
Azure DevOps – release
Przykładowy release będzie odpowiedzialny tylko za postawienie infrastruktury z użyciem Terraform i będzie składał się z 4 kroków:
Poszczególne kroki to:
- Ustawienie wartości zmiennych w Terraform,
- Instalacja Terraform,
- Wykonanie komendy init,
- Wykonanie komendy validate and apply.
Ustawienie zmiennych
Tak jak opisywałem to we wpisie Terraform i Azure przykład, w Terraform możemy używać zmiennych, które parametryzują naszą infrastrukturę (na przykład wielkość/wydajność zasobów). W definicji zmiennych używaliśmy specjalnego wzorca dla domyślnej wartości zmiennej (format, który użyłem, to __{nazwa zmiennej}__):
variable "env" { | |
type = string | |
default = "__env__" | |
} |
Pierwszy krok w release będzie odpowiedzialny za ustawienie wartości dla zmiennych. Wartości będziemy brali ze zmiennych zdefiniowanych w release:
Natomiast sam krok będzie wyglądał tak:
Użyjemy tutaj krok typu Replace Tokens i ustawimy w nim kilka rzeczy:
- Nazwę kroku,
- Folder, w którym znajdują się pliki Terraform – jest to folder, który wrzuciliśmy do artefaktów w pipeline,
- Wzorzec dla plików, które mają być przeszukane pod względem wzorca tokenu,
- Wzorzec, który zostanie użyty do podmiany wartości zmiennych.
W wersji tekstowej ten krok wygląda tak:
steps: | |
- task: qetza.replacetokens.replacetokens-task.replacetokens@4 | |
displayName: 'Replace tokens in **/*.tf' | |
inputs: | |
rootDirectory: '$(System.DefaultWorkingDirectory)/_TerraformAzure/drop' | |
targetFiles: '**/*.tf' | |
tokenPattern: rm |
Instalacja Terraform
Drugi krok w release to instalacja Terraform:
Jest on dość prosty w konfiguracji i sprowadza się do określenie jego nazwy (punkt 1) oraz podania wersji Terraform, która ma zostać zainstalowana (punkt 2). Krok w wersji tekstowej:
steps: | |
- task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0 | |
displayName: 'Install Terraform' | |
inputs: | |
terraformVersion: 1.1.7 |
Terraform init
Kolejnym krokiem jest wykonanie komendy terraform init, który w Azure DevOps wygląda tak:
W kroku tym musimy określić:
- Jego nazwę,
- Providera, który ma zostać użyty – w tym przypadku azurerm,
- Komendę, która ma się wykonać – init,
- Folder, w którym znajdują się pliki Terraform – folder wrzucony do artefaktów w pipeline,
- Subskrypcję, w której znajduje się Azure Storage Account,
- Grupę zasobów, w której znajduje się Azure Storage Account – ten oraz kolejne elementy będą później ustawione bezpośrednio w konfiguracji azurerm (to co opisywałem na samym początku wpisu),
- Konto Azure Storage Account w określonej wcześniej grupie,
- Nazwę kontenera w Azure Storage Account,
- Nazwę pliku stanu.
W wersji tekstowej krok ten wygląda tak:
steps: | |
- task: ms-devlabs.custom-terraform-tasks.custom-terraform-release-task.TerraformTaskV2@2 | |
displayName: 'Terraform : azurerm init' | |
inputs: | |
workingDirectory: '$(System.DefaultWorkingDirectory)/_TerraformAzure/drop' | |
backendServiceArm: 'Subskrypcja platformy Azure' | |
backendAzureRmResourceGroupName: 'terraform-config' | |
backendAzureRmStorageAccountName: plawgoterraformazured | |
backendAzureRmContainerName: terraform | |
backendAzureRmKey: test.ftstate |
Terraform apply
Ostatnim krokiem jest wykonanie komendy apply, która wykona kod Terraforma i odpowiednio zmieni nam lub utworzy infrastrukturę w Azure. Konfiguracja tego kroku jest podobna do poprzedniego i wygląda tak:
W tym kroku określamy:
- Jego nazwę,
- Providera,
- Komendę – tutaj wybieramy validate and apply,
- Folder z plikami Terraforma,
- Dodatkową komendę (tutaj: -auto-approve) – domyślna komenda approve wyświetla to, co zostanie zmienione i musimy to zatwierdzić poprzez wpisanie „yes” w konsoli, czego nie chcemy w przypadku wykonania zmiany w Azure DevOps. Ten parametr spowoduje, że Terraform natychmiast wykona zmiany,
- Subskrypcję, w ramach której ma się wykonać kod Terraforma.
W wersji tekstowej ten krok wygląda tak:
steps: | |
- task: ms-devlabs.custom-terraform-tasks.custom-terraform-release-task.TerraformTaskV2@2 | |
displayName: 'Terraform : azurerm validate and apply' | |
inputs: | |
command: apply | |
workingDirectory: '$(System.DefaultWorkingDirectory)/_TerraformAzure/drop' | |
commandOptions: '-auto-approve' | |
environmentServiceNameAzureRM: 'Subskrypcja platformy Azure' | |
Po wykonaniu wszystkich powyższych kroków możemy cieszyć się automatycznym postawieniem infrastruktury w ramach procesu CI/CD w Azure DevOps.
Przykład
Tradycyjnie na githubie znajdziesz przykład do tego wpisu, w którym umieszczone są wszystkie wspomniane powyżej pliki Terraforma – https://github.com/danielplawgo/TerraformAzure
Podsumowanie
Jak widzisz, skonfigurowanie i wykonywanie kodu Terraform w Azure DevOps nie jest skomplikowane. A po wykonaniu odpowiednich kroków, możemy cieszyć się automatycznymi zmianami w infrastrukturze, bez potrzeby ich ręcznego wykonywania.