In diesem Artikel gehe ich darauf ein, wie man die Ressourcen mit Terraform in Azure in einer Kadenz von 5 Minuten provisionieren und wieder abbauen kann. Dieses Szenario kann durchaus bei BDD-Test vorkommen, bei deinen Ad-Hoc eine Testinfrastruktur hochgefahren werden muss.
Währen der Provisionierung in der Pipeline, kann es immer wieder vokommen, dass bereits eine Ressource noch vorhanden sei, wenn diese rasch wieder erstellt werden muss. Dann sieht man folgende Fehlermeldung:
│ Error: waiting for creation/update of Server: (Name "sqldbserver" / Resource Group "rg-switzerland"): Code="NameAlreadyExists" Message="The name 'sqldbserver.database.windows.net' already exists. Choose a different name."
│
│ with azurerm_mssql_server.sqlsrv,
│ on main.tf line 52, in resource "azurerm_mssql_server" "sqlsrv":
│ 52: resource "azurerm_mssql_server" "sqlsrv" {
Ärgerlich, wenn man sich darauf verlassen möchte, dass die Test-Umgebung immer gleich aufgebaut werden soll.
Die Lösung
Um diesem Problem Herr zu werden, reicht es wenn man den Ressourcen einen zufälligen Namenszusatz vergibt. Dies kann mit dem Terraform Integer eine einfache Abhilfe geschaffen werden. Dazu braucht es im Terraform Script nur folgende Ressource:
resource "random_integer" "salt"{
min = 1
max = 99
}
Der Umstand, dass Zahlen zwischen 1 und 99 generiert werden, in einer Zufälligkeit, lässt die Wahrscheinlichkeit, dass eine Ressource bereits besteht und des zu einem Fehler kommt, minimieren.
Das Anlegen einer Ressource-Gruppe mit zufälligem Namenssuffix würde dann wie folgt aussehen.
# Create a resource group
resource "azurerm_resource_group" "rg" {
name = "rg-swiss-${random_integer.salt.result}"
location = "switzerlandnorth"
}
Mit einem Testscript dass fünf mal durchläuft mit einer realistischen Pause von 5min während den Ausführungen hat keinen Fehler ausgegeben.
Natürlich kann die Dauer mit der aktuellen Verfügbarkeit von Azure Dienste zusammenhängen. Bei der Vergabe der Namen für die Ressourcen ist es immer ratsam die Azure API der einzelnen Ressourcen zu konsultieren um keinen Fehler in der Länge des Namens zu generieren. Denn im Gegensatz zu früher, wo Namen noch wichtig waren, sind es heute nur noch Ressourcen, die nicht mehr für eine lange Existenz bestimmt sind, in Zeiten von DevOps Praktiken.
Fazit
Mit dieser Lösung kann sichergestellt werden, dass man sich Umgehungslösungen baut, die dann nur einen kleinen Zeitraum funktionieren. Ich hoffe der Artikel hat gefallen.
In diesem Artikel möchte ich die Schritte für das Veröffentlichen einer statischen Webseite, zum Beispiel einer "Landing Page" mit Terraform und Azure zeigen.
Hochladen der Landing Page in den Storage Account.
StorageAccount und statische WebApp erstellen
Im ersten Schritt wird ein StorageAccount erstellt.
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=3.0.0"
}
}
}
# Configure the Microsoft Azure Provider
provider "azurerm" {
features {}
subscription_id = "YOUR SUBSCRIPTION"
client_id = "YOU APPLICATION ID"
client_secret = "YOUR APPLICATION SECRET"
tenant_id = "YOUR TENANT ID"
}
resource "azurerm_resource_group" "rg" {
name = "terrfaform-playground"
# Westeurope da statische Webseiten in der Schweiz
# noch nicht verfügbar sind.
location = "westeurope"
}
resource "azurerm_storage_account" "storage" {
account_tier = "Standard"
account_kind = "StorageV2"
account_replication_type = "LRS"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
name = "schaedldstorage"
allow_nested_items_to_be_public = true
static_website {
index_document = "index.html"
}
}
Die Befehle terraform init, terraform plan -out sampleplan, terraform apply sampleplan und terraform destroy (In Produktion eher vorsichtig damit umgehen) ausgeführt. Diese sind durchgängig durch das ganze Beispiel immer wieder anzuwenden.
Terraform init
terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "3.0.0"...
- Installing hashicorp/azurerm v3.0.0...
- Installed hashicorp/azurerm v3.0.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Terraform plan
terraform plan -out simpleplan
Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.rg will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "westeurope"
+ name = "terrfaform-playground"
}
# azurerm_static_site.website will be created
+ resource "azurerm_static_site" "website" {
+ api_key = (known after apply)
+ default_host_name = (known after apply)
+ id = (known after apply)
+ location = "westeurope"
+ name = "sample-web-app"
+ resource_group_name = "terrfaform-playground"
+ sku_size = "Free"
+ sku_tier = "Free"
}
# azurerm_storage_account.storage will be created
+ resource "azurerm_storage_account" "storage" {
+ access_tier = (known after apply)
+ account_kind = "StorageV2"
+ account_replication_type = "LRS"
+ account_tier = "Standard"
+ allow_nested_items_to_be_public = true
+ enable_https_traffic_only = true
+ id = (known after apply)
+ infrastructure_encryption_enabled = false
+ is_hns_enabled = false
+ large_file_share_enabled = (known after apply)
+ location = "westeurope"
+ min_tls_version = "TLS1_2"
+ name = "schaedldstorage"
+ nfsv3_enabled = false
+ primary_access_key = (sensitive value)
+ primary_blob_connection_string = (sensitive value)
+ primary_blob_endpoint = (known after apply)
+ primary_blob_host = (known after apply)
+ primary_connection_string = (sensitive value)
+ primary_dfs_endpoint = (known after apply)
+ primary_dfs_host = (known after apply)
+ primary_file_endpoint = (known after apply)
+ primary_file_host = (known after apply)
+ primary_location = (known after apply)
+ primary_queue_endpoint = (known after apply)
+ primary_queue_host = (known after apply)
+ primary_table_endpoint = (known after apply)
+ primary_table_host = (known after apply)
+ primary_web_endpoint = (known after apply)
+ primary_web_host = (known after apply)
+ queue_encryption_key_type = "Service"
+ resource_group_name = "terrfaform-playground"
+ secondary_access_key = (sensitive value)
+ secondary_blob_connection_string = (sensitive value)
+ secondary_blob_endpoint = (known after apply)
+ secondary_blob_host = (known after apply)
+ secondary_connection_string = (sensitive value)
+ secondary_dfs_endpoint = (known after apply)
+ secondary_dfs_host = (known after apply)
+ secondary_file_endpoint = (known after apply)
+ secondary_file_host = (known after apply)
+ secondary_location = (known after apply)
+ secondary_queue_endpoint = (known after apply)
+ secondary_queue_host = (known after apply)
+ secondary_table_endpoint = (known after apply)
+ secondary_table_host = (known after apply)
+ secondary_web_endpoint = (known after apply)
+ secondary_web_host = (known after apply)
+ shared_access_key_enabled = true
+ table_encryption_key_type = "Service"
+ blob_properties {
+ change_feed_enabled = (known after apply)
+ default_service_version = (known after apply)
+ last_access_time_enabled = (known after apply)
+ versioning_enabled = (known after apply)
+ container_delete_retention_policy {
+ days = (known after apply)
}
+ cors_rule {
+ allowed_headers = (known after apply)
+ allowed_methods = (known after apply)
+ allowed_origins = (known after apply)
+ exposed_headers = (known after apply)
+ max_age_in_seconds = (known after apply)
}
+ delete_retention_policy {
+ days = (known after apply)
}
}
+ network_rules {
+ bypass = (known after apply)
+ default_action = (known after apply)
+ ip_rules = (known after apply)
+ virtual_network_subnet_ids = (known after apply)
+ private_link_access {
+ endpoint_resource_id = (known after apply)
+ endpoint_tenant_id = (known after apply)
}
}
+ queue_properties {
+ cors_rule {
+ allowed_headers = (known after apply)
+ allowed_methods = (known after apply)
+ allowed_origins = (known after apply)
+ exposed_headers = (known after apply)
+ max_age_in_seconds = (known after apply)
}
+ hour_metrics {
+ enabled = (known after apply)
+ include_apis = (known after apply)
+ retention_policy_days = (known after apply)
+ version = (known after apply)
}
+ logging {
+ delete = (known after apply)
+ read = (known after apply)
+ retention_policy_days = (known after apply)
+ version = (known after apply)
+ write = (known after apply)
}
+ minute_metrics {
+ enabled = (known after apply)
+ include_apis = (known after apply)
+ retention_policy_days = (known after apply)
+ version = (known after apply)
}
}
+ routing {
+ choice = (known after apply)
+ publish_internet_endpoints = (known after apply)
+ publish_microsoft_endpoints = (known after apply)
}
+ share_properties {
+ cors_rule {
+ allowed_headers = (known after apply)
+ allowed_methods = (known after apply)
+ allowed_origins = (known after apply)
+ exposed_headers = (known after apply)
+ max_age_in_seconds = (known after apply)
}
+ retention_policy {
+ days = (known after apply)
}
+ smb {
+ authentication_types = (known after apply)
+ channel_encryption_type = (known after apply)
+ kerberos_ticket_encryption_type = (known after apply)
+ versions = (known after apply)
}
}
}
Plan: 3 to add, 0 to change, 0 to destroy.
Terraform apply
terraform apply sampleplan
azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 0s [id=/subscriptions/YOUR SUBSCRIPTION/resourceGroups/terrfaform-playground]
azurerm_storage_account.storage: Creating...
azurerm_storage_account.storage: Still creating... [11s elapsed]
azurerm_storage_account.storage: Still creating... [21s elapsed]
azurerm_storage_account.storage: Creation complete after 22s [id=/subscriptions/YOUR SUBSCRIPTION/resourceGroups/terrfaform-playground/providers/Microsoft.Storage/storageAccounts/schaedldstorage]
azurerm_static_site.website: Creating...
azurerm_static_site.website: Creation complete after 3s [id=/subscriptions/YOUR SUBSCRIPTION/resourceGroups/terrfaform-playground/providers/Microsoft.Web/staticSites/sample-web-app]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
In Azure ist der StorageAccount erstellt worden.
Nun können die weiteren Elemente hinzugefügt werden. Gemäss der Anleitung für das Hosten von statischen Webseiten wird noch überprüft ob die Konfiguration von Terraform mit der dokumentierten übereinstimmt.
Hochladen der Landing Page
Azure bietet nicht die Möglichkeit ein Objekt direkt mit Terraform im StorageAccount zu erstellen, sodass ein anderer Weg zur Publizierung gewählt werden muss. Hierzu kann einer der drei dokumentierten Wege gewählt werden:
Beim Erstellen des StorageAccounts, wird ein Container automatisch mit dem Namen $web angelegt. Diesen kann man dann für das Hosten verwenden (das Script kopiert die Datei in diesen Container.)
Fazit
Mir nur wenig Aufwand, kann ein erster Kontaktpunkt zu einer neuen Firma auf Azure bereitgestellt werden. Dies ist nur ein Beispiel und hat noch keine Sicherheitsfunktionen aktiviert (vgl. Hosting a static Webseite in Azure Storage). Jedoch ist es weniger simple das Ganze, wie in AWS nur mit Terraform zu bewerkstelligen.
Um automatisiert Ressourcen auf Azure erstellen zu können, muss vorgängig eine App im Active Directory erstellt werden.
Im Azure Portal Active Directory, App Registrierung auswählen.
Nun wählt man neue Registrierung hinzufügen.
Anschliessend im Menü Zertifikate und Geheimnisse ein neuer Geheimer Clientschlüssel erstellen ein.
Nun ist es wichtig, beide Schlüssel zu notieren.
Nun muss noch die Client Id aufgeschrieben werden. Diese findet man in der App Registrierungs-Übersicht.
Diese können, wenn man sich mit den Azure Cli Tools einmal angemeldet hat mit folgendem Befehl herausgefunden werden:
az account list
Nun muss über das Abonnement und die Zugriffsteuerung der erstellten App eine Rolle zugewiesen werden, damit diese funktioniert. In diesem Beispiel ist die Rolle "Mitwirkender" verwendet worden (Hängt vom Anwendungsfall in der Firma ab, welche tatsächliche Rolle vergeben wird.) Dies geschieht über "Rollenzuweisung hinzufügen".
Anschliessend muss man die zuvor erstellte App hinzufügen. Diese kann mittels Suchfeld gesucht und hinzugefügt werden.
Verifizierung des Zugriffes mit den Azure Cli Tools
Als erstes müssen die Azure Cli Tools bereits installiert sein.
Sobald die Azure Cli Tools installiert sind, kann man sich mit dem Service Principal versuchen anzumelden.
Damit Terraform funktioniert, müssen die zuvor heruntergeladenen Schlüssel eingetragen werden. Diese Konfiguration sieht dann wie folgt aus:
# Configure the Microsoft Azure Provider
provider "azurerm" {
features {}
subscription_id = "ABONNEMENT ID"
client_id = "ZUVOR ERSTELLTE APPLIKATIONS ID"
client_secret = "ZUVOR ERSTELLTES GEHEIMNIS IN DER APP"
tenant_id = "TENANT ID"
}
Damit auch hier überprüft werden kann ob die Verbindung mit Azure funktioniert kann auch ein StorageAccount erstellt werden mit den Terraform Ressourcen.
terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Using previously-installed hashicorp/azurerm v3.0.0
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
terraform plan -out sampleplan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.rg will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "switzerlandnorth"
+ name = "terrfaform-playground"
}
# azurerm_storage_account.storage will be created
+ resource "azurerm_storage_account" "storage" {
+ access_tier = (known after apply)
+ account_kind = "StorageV2"
+ account_replication_type = "LRS"
+ account_tier = "Standard"
+ allow_nested_items_to_be_public = true
+ enable_https_traffic_only = true
+ id = (known after apply)
+ infrastructure_encryption_enabled = false
+ is_hns_enabled = false
+ large_file_share_enabled = (known after apply)
+ location = "switzerlandnorth"
+ min_tls_version = "TLS1_2"
+ name = "schaedldstorage"
+ nfsv3_enabled = false
+ primary_access_key = (sensitive value)
+ primary_blob_connection_string = (sensitive value)
+ primary_blob_endpoint = (known after apply)
+ primary_blob_host = (known after apply)
+ primary_connection_string = (sensitive value)
+ primary_dfs_endpoint = (known after apply)
+ primary_dfs_host = (known after apply)
+ primary_file_endpoint = (known after apply)
+ primary_file_host = (known after apply)
+ primary_location = (known after apply)
+ primary_queue_endpoint = (known after apply)
+ primary_queue_host = (known after apply)
+ primary_table_endpoint = (known after apply)
+ primary_table_host = (known after apply)
+ primary_web_endpoint = (known after apply)
+ primary_web_host = (known after apply)
+ queue_encryption_key_type = "Service"
+ resource_group_name = "terrfaform-playground"
+ secondary_access_key = (sensitive value)
+ secondary_blob_connection_string = (sensitive value)
+ secondary_blob_endpoint = (known after apply)
+ secondary_blob_host = (known after apply)
+ secondary_connection_string = (sensitive value)
+ secondary_dfs_endpoint = (known after apply)
+ secondary_dfs_host = (known after apply)
+ secondary_file_endpoint = (known after apply)
+ secondary_file_host = (known after apply)
+ secondary_location = (known after apply)
+ secondary_queue_endpoint = (known after apply)
+ secondary_queue_host = (known after apply)
+ secondary_table_endpoint = (known after apply)
+ secondary_table_host = (known after apply)
+ secondary_web_endpoint = (known after apply)
+ secondary_web_host = (known after apply)
+ shared_access_key_enabled = true
+ table_encryption_key_type = "Service"
+ blob_properties {
+ change_feed_enabled = (known after apply)
+ default_service_version = (known after apply)
+ last_access_time_enabled = (known after apply)
+ versioning_enabled = (known after apply)
+ container_delete_retention_policy {
+ days = (known after apply)
}
+ cors_rule {
+ allowed_headers = (known after apply)
+ allowed_methods = (known after apply)
+ allowed_origins = (known after apply)
+ exposed_headers = (known after apply)
+ max_age_in_seconds = (known after apply)
}
+ delete_retention_policy {
+ days = (known after apply)
}
}
+ network_rules {
+ bypass = (known after apply)
+ default_action = (known after apply)
+ ip_rules = (known after apply)
+ virtual_network_subnet_ids = (known after apply)
+ private_link_access {
+ endpoint_resource_id = (known after apply)
+ endpoint_tenant_id = (known after apply)
}
}
+ queue_properties {
+ cors_rule {
+ allowed_headers = (known after apply)
+ allowed_methods = (known after apply)
+ allowed_origins = (known after apply)
+ exposed_headers = (known after apply)
+ max_age_in_seconds = (known after apply)
}
+ hour_metrics {
+ enabled = (known after apply)
+ include_apis = (known after apply)
+ retention_policy_days = (known after apply)
+ version = (known after apply)
}
+ logging {
+ delete = (known after apply)
+ read = (known after apply)
+ retention_policy_days = (known after apply)
+ version = (known after apply)
+ write = (known after apply)
}
+ minute_metrics {
+ enabled = (known after apply)
+ include_apis = (known after apply)
+ retention_policy_days = (known after apply)
+ version = (known after apply)
}
}
+ routing {
+ choice = (known after apply)
+ publish_internet_endpoints = (known after apply)
+ publish_microsoft_endpoints = (known after apply)
}
+ share_properties {
+ cors_rule {
+ allowed_headers = (known after apply)
+ allowed_methods = (known after apply)
+ allowed_origins = (known after apply)
+ exposed_headers = (known after apply)
+ max_age_in_seconds = (known after apply)
}
+ retention_policy {
+ days = (known after apply)
}
+ smb {
+ authentication_types = (known after apply)
+ channel_encryption_type = (known after apply)
+ kerberos_ticket_encryption_type = (known after apply)
+ versions = (known after apply)
}
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: sampleplan
To perform exactly these actions, run the following command to apply:
terraform apply "sampleplan"
terraform apply simpleplan
azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 1s [id=/subscriptions/YOURSUBSCRIPTION/resourceGroups/terrfaform-playground]
azurerm_storage_account.storage: Creating...
azurerm_storage_account.storage: Still creating... [10s elapsed]
azurerm_storage_account.storage: Still creating... [20s elapsed]
azurerm_storage_account.storage: Creation complete after 21s [id=/subscriptions/YOURSUBSCRIPTION/resourceGroups/terrfaform-playground/providers/Microsoft.Storage/storageAccounts/schaedldstorage]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Nach der Kontrolle im Azure Portal sieht man das Ergebnis.
Die Ressource Gruppe ist erstellt und wenn man diese auswählt, sieht man den darin erstellten StorageAccount.
as Abräumen der Ressource kann dann wie folgt geschehen:
Nach dessen Ausführung ist dann auch im Azure-Portal nichts mehr zu sehen.
Fazit
Ein einfacher Weg Infrastruktur auch in Azure zu erstellen, ohne die dauernde Anmeldung und der Möglichkeit, Terraform automatisiert in einer CI/CD Pipeline laufen zu lassen.
In diesem Artikel möchte ich die Schritte für das Veröffentlichen einer statischen Webseite, zum Beispiel einer "Landing Page" mit Terraform und AWS zeigen.
Die Befehle terraform init, terraform plan -out sampleplan, terraform apply sampleplan und terraform destroy (In Produktion eher vorsichtig damit umgehen) ausgeführt. Diese sind durchgängig durch das ganze Beispiel immer wieder anzuwenden.
Terraform init
terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 3.0"...
- Installing hashicorp/aws v3.75.1...
- Installed hashicorp/aws v3.75.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
Terraform plan
terraform plan -out sampleplan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.webapp will be created
+ resource "aws_s3_bucket" "webapp" {
+ acceleration_status = (known after apply)
+ acl = "private"
+ arn = (known after apply)
+ bucket = "schaedld-webapp"
+ bucket_domain_name = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = false
+ region = (known after apply)
+ request_payer = (known after apply)
+ tags_all = (known after apply)
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
+ object_lock_configuration {
+ object_lock_enabled = (known after apply)
+ rule {
+ default_retention {
+ days = (known after apply)
+ mode = (known after apply)
+ years = (known after apply)
}
}
}
+ versioning {
+ enabled = (known after apply)
+ mfa_delete = (known after apply)
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: sampleplan
To perform exactly these actions, run the following command to apply:
terraform apply "sampleplan"
Nun können die weiteren Elemente hinzugefügt werden.
Erstellen der Webseiten Konfiguration
Damit das Bucket auch als Webseite für die Auslieferung von statischem Inhalt funktioniert, muss eine Webseitenkonfigurationselement in Terraform hinzugefügt werden. (Aus Platzgründen habe ich die vorherigen Schritte weggelassen.)
Klickt man im Portal auf das Bucket Objekt, gelangt man in die Verwaltungsseite des Objektes.
Wählt man die Option Eigenschaften, so gelangt man in die Einstellungen des Buckets. Navigiert man ans untere Ende der Seite, ist folgender Punkt zu sehen:
Hier zu sehen, ist dass diese Option aktiviert ist. Wenn man nun den bereitgestellten Link anklickt, gelangt man auf eine Seite, die einem den Zugriff verwehrt, da noch kein Objekt für einen öffentlichen Lesezugriff vorhanden ist.
Nun kann mit dem nächsten Schritt, dem erstellen eines Objekts für das Bucket fortgefahren werden.
Nun kann mit dem nächsten Schritt, dem erstellen eines Objekts für das Bucket fortgefahren werden.
Erstellen eines Objektes im Bucket
Als letztes Puzzle-Teilchen, ist das Objekt für das Bucket hinzuzufügen. In diesem Beispiel ist es ein einfaches Index.html, dass als "Landing Page" verwendet werden könnte, wenn man frischer Besitzer einer Domain ist und gerade die Webseite aufbaut.
Die Kontrolle im AWS Portal, des Buckets offenbar, dass diese Datei angelegt worden ist.
Wir der Link, in den Eigenschaften des Buckets unter "Hosten einer statischen Webseite" angeklickt so ist nicht mehr die Access Denied Meldung zu sehen, sondern die bereitgestellte Webseite.
Die komplette Terraform Konfiguration sieht dann wie folgt aus:
Mir nur wenig Aufwand, kann ein erster Kontaktpunkt zu einer neuen Firma auf AWS bereitgestellt werden. Dies ist nur ein Beispiel und hat noch keine Sicherheitsfunktionen aktiviert (vgl. Hosting a static Webseite using Amazon S3).
Um automatisiert Ressourcen auf AWS erstellen zu können, muss vorgängig ein sogenannter IAM-Benutzer erstellt werden. Hier kann wie folgt vorgegangen werden.
Im AWS Konto auf die Identity und Accessmanagement navigieren. Man sollte nun schon auf der richtigen Maske landen.
Mit dem drücken des Knopfes "neuer Benutzer" gelangt man die nachfolgende Ansicht für die Parametrisierung des Benutzers.
Wichtig hierbei ist, dass die der Haken bei den CLI Tools gesetzt wird, damit man später mit Terraform darauf zugreifen kann.
Nun können die notwendigen Berechtigungen hinzugefügt werden.
Wenn der Benutzer erstellt worden ist, kopiert euch den Access Key und den private Key oder ladet diesen als CSV herunter, damit diese in den nächsten Schritten weiter verwendet werden können.
AWS Extension Visual Studio Code
Als erstes muss im Marketplace von Visual Studio Code nach der Extension für AWS gesucht werden um diese dann installieren zu können.
Nun sind die Erweiterungen für AWS installiert. Diese müssen nun konfiguriert werden. Dies kann mit Hilfe der AWS-Erweiterung durchgeführt werden die durch den SetUp Prozess führt. Hierbei ist es wichtig, dass der Access Key und der private Schlüssel notiert worden sind.
Um zu testen ob eine Verbindung mit den Tools auf AWS gemacht werden kann, reicht es nach deren Konfiguration einfach in der Menüleiste eine Ressource zu erstellen um zu sehen ob die Verbindung geklappt hat.
Ich habe mit einem S3 Bucket getestet und bin wie folgt vorgegangen.
Bucket Option in den Erweiterungen auswählen.
Anschliessend muss nur noch ein Name eingegeben werden, der eineindeutig sein muss.
Hat man Erfolg und einen eineindeutigen Namen erwischt so kann ein Bucket erstellt werden und man erhält eine Erfolgsmeldung.
Nun sind alle Schritte gemacht und die Verbindung zu AWS funktioniert.
Terraform SetUp
Damit Terraform funktioniert, müssen die zuvor heruntergeladenen Schlüssel eingetragen werden. Diese Konfiguration sieht dann wie folgt aus:
terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 3.0"...
- Installing hashicorp/aws v3.75.1...
- Installed hashicorp/aws v3.75.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
terraform plan -out sampleplan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated
with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.samplebucket will be created
+ resource "aws_s3_bucket" "samplebucket" {
+ acceleration_status = (known after apply)
+ acl = "private"
+ arn = (known after apply)
+ bucket = "schaedlds-sample-bucket"
+ bucket_domain_name = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = false
+ region = (known after apply)
+ request_payer = (known after apply)
+ tags_all = (known after apply)
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
+ object_lock_configuration {
+ object_lock_enabled = (known after apply)
+ rule {
+ default_retention {
+ days = (known after apply)
+ mode = (known after apply)
+ years = (known after apply)
}
}
}
+ versioning {
+ enabled = (known after apply)
+ mfa_delete = (known after apply)
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: simpleplan
Eine .NET Anwendung, die sowohl auf einem Windows, wie auch interaktiv im GitLab Runner gestartet werden kann.
Leider hatte ich dann immer folgende Fehlermeldung, als die Applikation im GitLab Runner gestartet worden ist:
/src/Sample.Cli\plantUml//bin//java.exe' with working directory '/builds/Sample/src/Sample.Cli\plantUml//bin'. No such file or directory
Detailbetrachtung
Um dem Ganzen ein wenig weiter auf die Spur zu gehen, habe ich mir eine Beispiel-Applikation geschrieben um das Verhalten auf beiden System zu betrachten.
static void Main(string[] args)
{
var environmentVariable = Environment.ExpandEnvironmentVariables("tmp");
var tempPath = Path.GetTempPath();
Console.WriteLine($"Value for Environment Variable {environmentVariable}");
Console.WriteLine($"Value for {nameof(Path.GetTempPath)} {tempPath}");
Console.ReadKey();
}
Lässt man dann das Ganze auf einem Windows System mit dotnet wie folgt laufen, sieht das Ergebnis dann so aus:
PS C:\Users\schae> dotnet run --project D:\_Development_Projects\Repos\ConsoleApp1\ConsoleApp1\ConsoleApp1.csproj
Value for Environment Variable tmp
Value for GetTempPath C:\Users\schae\AppData\Local\Temp\
Das Ergebnis ist wie gewünscht. Nun schauen wir uns das auf der WSL2 an.
root@GAMER-001:~/.dotnet# ./dotnet run --project /mnt/d/_Development_Projects/Repos/ConsoleApp1/ConsoleApp1/ConsoleApp1.
csproj
Value for Environment Variable tmp
Value for GetTempPath /tmp/
Schauen wir doch nun ob der Pfad auch existiert:
root@GAMER-001:~/.dotnet# cd /tmp/
root@GAMER-001:/tmp#
Und das ist der erhoffte Pfad.
Die Lösung
Nach ein wenig Recherchieren in der Dokumentation von Microsoft bin ich auf diesen Artikel DirectorySeperatorChar gestossen. Nicht dass er mir mit dieser Methode geholfen hätte, sondern vielmehr mit dem Auszug
The following example displays Path field values on Windows and on Unix-based systems. Note that Windows supports either the forward slash (which is returned by the AltDirectorySeparatorChar field) or the backslash (which is returned by the DirectorySeparatorChar field) as path separator characters, while Unix-based systems support only the forward slash
dass Windows auch Forward-Slashes unterstützt. Manchem wird das sicherlich schon bekannt gewesen sein aber ich selber werde wohl meine Arbeit mit Pfaden, auch in Windows in Zukunft nur noch mit Forwar-Slashes machen.
Nun habe ich das natürlich auch getestet und zwar in der powershell core.
PS C:\Temp> cd C:/Users
PS C:\Users>
Interessant ist der Umstand, dass wenn der Pfad bekannt ist, man den Tabulator betätigt, Windows automatisch Backslashes macht.
Nun sind überall wo Pfade verwendet werden, die Backward-Slashes durch Forward-Slashes zu ersetzen. Die appsettings.json sieht dann nun so aus:
Die Durchführung lässt sich in folgende Schritte gliedern:
Verbinden auf die Azure Webseite mit den Azure Cli Tools
Vorbereitung des Certbots local
Dateien und Ordner in der Webseite erstellen
Weiterfahren mit Cerbot
Konvertierung des Zertifikates
Installation des Zertifikates auf Azure
Verbindung auf die Azure Webseite herstellen
Die Verbindung zu Azure und der Webseite geschieht wie folgt.
az login --use-device-code
az subscription --set <%subscriptionId%>
az webapp ssh -n <%webseiten-name%> -g <%resourcegruppenname%>
Nach erfolgtem einloggen sieht man folgenden Azure Willkommensbilschirm:
Last login: Wed Apr 6 18:38:26 2022 from 169.254.130.3
_____
/ _ \ __________ _________ ____
/ /_\ \___ / | \_ __ \_/ __ \
/ | \/ /| | /| | \/\ ___/
\____|__ /_____ \____/ |__| \___ >
\/ \/ \/
A P P S E R V I C E O N L I N U X
Documentation: http://aka.ms/webapp-linux
Dotnet quickstart: https://aka.ms/dotnet-qs
ASP .NETCore Version: 6.0.0
Note: Any data outside '/home' is not persisted
root@bcd2c665073e:~/site/wwwroot# ^C
root@bcd2c665073e:~/site/wwwroot#
Nun muss in folgenden Ordner navigiert werden:
cd /home/wwwroot/wwwroot
Nun weiter mit dem nächsten Schritt.
### Vorbereitung des Certbots local
Sollte der Certbot noch nicht installiert sein, so kann dies mittels folgendem Befehl durchgeführt werden:
```bash
sudo apt-get install certbot
Ist der Certbot installiert, kann dieser gestartet werden, wie nachfolgen beschrieben:
Der Certbot startet und man sieht folgende Meldungen:
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for www.dnug-bern.ch
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.
Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o:
Mit Y bestätigen und man erhält die Instruktionen, wie weiter vorzugehen ist.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create a file containing just this data:
JxOqa2wKiSbAq5R_o66Gs5_sEE9xhuDwyPbv6pfEOJ8.RN326tu-yly1wbWDsnoT5mbba-NazH6fhba6WeEfA2s
And make it available on your web server at this URL:
http://www.dnug-bern.ch/.well-known/acme-challenge/JxOqa2wKiSbAq5R_o66Gs5_sEE9xhuDwyPbv6pfEOJ8
Achtung: Hier nicht bestätigen, ansonsten wird die Validierung fehlschlagen
Dateien und Ordner in der Webseite erstellen
Nun können wir in der bereits geöffneten Webseite in Azure in dem Ordner weiterfahren, in welchem wir vorher schon navigiert sind.
Nun wird der Ordner erstellt, den Certbot verlangt. Um dies zu bewerkstelligen muss wie folgt vorgegangen werden:
mkdir .well-known
mkdir acme-challenge
Nun muss die Datei erstellt werden. In unserem Fall soll die Datei so heissen: JxOqa2wKiSbAq5R_o66Gs5_sEE9xhuDwyPbv6pfEOJ8
Um dies zu erreichen muss zuerst vim gestartet werden. Hier muss dann die folgende Zeile eingefügt werden: JxOqa2wKiSbAq5R_o66Gs5_sEE9xhuDwyPbv6pfEOJ8.RN326tu-yly1wbWDsnoT5mbba-NazH6fhba6WeEfA2s. Anschliessend ist die Datei und dem folgenden Namen zu speicher (mit :w in vim) JxOqa2wKiSbAq5R_o66Gs5_sEE9xhuDwyPbv6pfEOJ8
Anschliessend kann die Webseite für das Testen einmal mit der URL die der Certbot angegeben hat, aufgerufen werden. Ist dies erfolgreich, so wird die Datei aufgerufen und man sieht die erfasste Zeichenfolge.
Weiterfahren mit Certbot
Da der Certbot noch darauf wartet eine Bestätigung für die Validierung zu erhalten, drücken wir nun im noch geöffneten Dialog die ENTER-Taste. Ist alles in Ordnung, so erhält man eine Erfolgsmeldung, dass die Validierung erfolgreich war.
Konvertierung des Zertifikates
Anschliessend muss die PEM Datei in eine PFX-Datei umgewandelt werden. Dies erfolgt wie nachfolgend beschrieben:
Anschliessend muss nach nach folgender Anleitung das Zertifikat in Azure hochgeladen werden.
Fazit
So kann in einfachen Schritten das Zertifikat mit Certbot, zwar manuel aktualisiert werden und es entstehen keine weiteren Kosten. Der Nachteil ist, dass in kurzen Intervallen die Aktualisierung durchgeführt werden muss. Ich hoffe Dir hat Dieser Blogbeitrag gefallen.
In diesem Artikel ist die Vorgehensweise beschrieben wie man eine Terraform Umgebung für Tests auf- und wieder abbauen kann. Hierzu habe ich Microsoft Azure verwendet. Diese ist im Rahmen der dotnet user group Seite geschehen um so die Tests mit Browserstack auf einer virtuellen Umgebung durchführen zu können.
Das Ziel soll sein, dass eine Testumgebung bestehend aus folgenden Komponenten
SQL Server
App Service hochfährt, die automatisierten Tests durchläuft und danach gelöscht wird.
Vorgehensweise
Die nachfolgenden Schritte beschreiben die Vorgehensweise in 3 Schritten.
Identifizieren der benötigten Ressourcen
Identifizieren der Terraform Ressourcen
Zusammenführen
Identifizieren der Terraform Ressourcen
Wirf man ein Auge auf die Azure Automation, so sticht die sehr grosse JSON-Datei ins Auge. Sie kann als Anhaltspunkt verwendet werden, muss aber nicht. Schaut man sich das so an, dann sind das eine Menge an Ressourcen, die da Verwendung finden. Nun stellt Terraform unter folgendem Link, die aktuellen Ressourcen für Azure zur Verfügung. Folgende Ressourcen können dafür Verwendung finden:
Nun muss das Ganze in einer Terraform Datei zusammengeführt werden. Hierzu wird ein Ordner erstellt, zum Beispiel dotnet-usergroup-bern-terraform-configuration. In diesem Ordner ist dann eine main.tf Datei zu erstellen, die die ganze Terraform Konfiguration beinhaltet. Diese Datei sieht dann wie so aus:
Natürlich sind hier noch nich alle definitiven Werte eingetragen, sodass während der Build-Zeit diese Konfiguration erstellt wird. Diesen Punkt werde ich in Zukunft behandeln.
Erstellung der Ressourcen Nun ist es an der Zeit, zu sehen ob die Konfiguration, die da entstanden ist, auch ohne Fehler angewendet werden kann. Hierzu bin ich wie folgt vorgegangen: 1. Anmelden in Azure (Diese Methode habe ich gewählt, weil bei mir der Browser oder die Browser ein wenig gezickt haben.)
az login --use-device-code
Nach erfolgter Anmeldung, sind die Azure-Abos ersichtlich.
ausgeführt werden. Wobei der Out Parameter optional ist. Bei der Ausführung speichert Terraform mit diesem Parameter den Plan als Datei im aktuellen Verzeichnis oder in dem angegeben im out-Parameter, ab. Verlief alles nach Plan, so zeigt Terraform den Plan an. Die + Zeichen deuten darauf hin, dass diese Ressourcen erstellt werden. Der nachfolgende Plan ist der Übersicht halber symbolisch dargestellt. In Wirklichkeit ist dieser um einiges länger.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# azurerm_app_service.bddtest will be created
+ resource "azurerm_app_service" "bddtest" {
+ app_service_plan_id = (known after apply)
+ app_settings = {
+ "SOME_KEY" = "some-value"
}
+ client_affinity_enabled = false
+ client_cert_enabled = false
+ default_site_hostname = (known after apply)
+ enabled = true
+ https_only = false
+ id = (known after apply)
+ location = "switzerlandnorth"
+ name = "bddtest-dnug-bern"
+ outbound_ip_addresses = (known after apply)
+ possible_outbound_ip_addresses = (known after apply)
+ resource_group_name = "bddtest-dotnet-usergroup"
+ site_credential = (known after apply)
+ auth_settings {
+ additional_login_params = (known after apply)
+ allowed_external_redirect_urls = (known after apply)
+ default_provider = (known after apply)
+ enabled = (known after apply)
+ issuer = (known after apply)
+ runtime_version = (known after apply)
+ token_refresh_extension_hours = (known after apply)
+ token_store_enabled = (known after apply)
+ unauthenticated_client_action = (known after apply)
+ active_directory {
+ allowed_audiences = (known after apply)
+ client_id = (known after apply)
+ client_secret = (sensitive value)
}
....
Plan: 7 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
This plan was saved to: .\MyPlan
To perform exactly these actions, run the following command to apply:
terraform apply ".\\MyPlan"
Nun wird der Plan mit
terraform apply
zur Ausführung gebracht.
Eine kurze Rechenphase und der Plan der ausgeführt werden wird, wird angezeigt. Die Bestätigung mit „Yes“ lässt Terraform nun seine Arbeit verrichten.
Sobald die Bestätigung mit „YES“ erfolgte, schreitet Terraform zur Tat. Die Fortschritte sind dann wie folgt:
Wenn alles richtig gemacht worden ist, dann zeigt Terraform den Status der Operation an.
Hier noch die erstellten Ressourcen im Azure Portal.
Löschen der erstellten Ressourcen
Der Aufbau einer Umgebung ist das Eine. Das Abbauen das Andere. In der Dotnet User Group Bern, sollt diese Umgebung für die BDD (Behavior Driven Tests) hochgefahren und dann wieder abgebaut werden. Der Abbau geht dann wie nachfolgend beschrieben, sehr schnell.
Mit dem Befehl
terraform.exe destroy
werden die Ressourcen dann abgebaut.
2. Nach einer kurzen Verarbeitungszeit auf dem ausführenden Client sieht man dann Ausführungsplan des oben bereits dargestellten Ausführungsplanes. Der Unterschied hier, bei jeder Resource ist nun ein – vorangestellt, dass angibt das die Ressource gelöscht werden wird. Auch hier muss mit YES die Aktion bestätigt werden.
3. Sind alle Aktionen erfolgreich verlaufen so teilt terraform mit, dass die Ressourcen zerstört sind.
Auch im Azure Portal sind die Ressourcen nicht mehr zu finden.
Fazit
Mit Terrform ist es relativ einfach eine automatisierte Infrastruktur zu instantiieren und so den Prozess der Bereitstellung zu Beschleunigen. Wenn Dir dieser Artiekl gefallen hat, dann würde ich mich über ein Like freuen und nehme gerne Verbesserungsvorschläge an. Meine Reise mit Terraform ist noch nicht zu Ende und das hier gezeigt, einfache Beispiel, kann noch weiter verbessert werden. Weiter will ich zudem die Möglichkeiten erkunden, diese Lösung auch onPremise einzusetzen, da nicht in jeder Umgebung die Cloud als definierte Zielplattform gewünscht ist.
In meinem Unternehmen setzen wir IBM Urban Code Deploy ein für die Provisionierung der Server. Nun da dies einen manuellen Mehraufwand bedeutet muss dieses Vorgehen effizienter gestaltet werden. Aus diesem Grund habe ich mit für die Konfiguration mittels Desired State Configuration (DSC) entschieden.
Für die Durchführung orientieren wir uns an den gesteckten Zielen.
Ist JAVA instaliert?
Hat man JAVA installiert, so wird dies meistens in ein Verzeichnis wie JDK oder JAVA gemacht und im Pfad wird das entsprechende angegeben. Die Konfiguration wird nicht gestartet, sollte JAVA nicht installiert sein. Dies kann man auf einfache Art und Weise überprüfen.
Nun gut, wenn alles korrekt verläuft dann kann die Konfiguration beginnen.
Installation des Agenten
Der Agent wird mittels XCOPY Installation auf dem System installiert. Das hat den Vorteil, dass der Agent bereits als Vorlage "gezipped" zur Verfügung steht und dann noch an die richtige Stelle kopiert wird. Um diesen Vorgang auszuführen braucht es die Resource Archive, die bereits standardmässig von Windows zur Verfügung steht.
Die Resource sieht wie folgt aus:
Die Destination ist das Ziel in welche die binären Dateien extrahiert werden. Der Path wird durch das Aufrufen des Konfigurationsscriptes mitgegeben.
Konfiguration des Dienstes für den Agenten
Für die Konfiguration des Agenten ist die Service Resource verwendet worden, die wie folgt konfiguriert wird.
# Preconfigure the properties for the service
$agentName = ("ucd-agent-{0}" -f $env:COMPUTERNAME)
$servicePathArguments = "//RS//{0}" -f $agentName
$servicePath = Join-Path -Path $env:ProgramFiles -ChildPath "ibm-ucd-agent\native\x64\winservice.exe"
Service UrbanCodeAgentService
{
Ensure = "Present"
Name = $agentName
StartupType = "Manual"
Path = ("`"{0}`" {1}" -f $servicePath, $servicePathArguments)
Description = "IBM Urban Code Agent"
DisplayName = $agentName
State = "Stopped"
}
Vor der Ressource sind die notwendigen Parameter vorkonfiguriert worden, da dieser JAVA Agent über einen Windows-Dienst Wrappter mit Argumenten gestartet wird.
Der Dienst ist nun konfiguriert nur noch nicht so wie gewünscht.
Konfiguration des Dienstes für den Agent
Die Dienst Resource lässt nur zu einen Benutzer-Account mit Passwort zu verknüpfen. Da kein Object PSCredential Objekt ohne Passwort erstellt werden kann und das Passwort für den Group Managed Service Account unbekannt ist, muss man sich mittels WMI den Dienst wie gewünscht konfigurieren. Dies geschieht mittels der Script Resource
# Third Set the Service with a script Resource
Script ServiceScript{
DependsOn = "[Service]UrbanCodeAgentService"
GetScript = {
return Get-WmiObject -Class win32_Service | Where-Object -Property Name -like ("*{0}*" -f $using:agentName)
}
TestScript = {
$service = [scriptblock]::Create($GetScript).Invoke()
if($service){
return ($service.StartName -eq $using:groupManagedServiceAccount)
}
return $false
}
SetScript = {
$service = [scriptblock]::Create($GetScript).Invoke()
if($service){
$service.Change($null, $null, $null, $null, $null, $null, $using:groupManagedServiceAccount, $null, $null, $null, $null)
}
}
}
Wichtig an dieser Stelle ist zu erwähnen, dass diese Script-Resource mit dem DependsOn versehen ist. Das bedeutet, dass diese Ressource erst durchgeführt wird, wenn die angegeben Ressource erfolgreich appliziert werden konnte.
Hier noch das ganze Script
param([Parameter(Mandatory=$true,HelpMessage="The full path to the template ucd agent zip")]
[ValidateNotNullOrEmpty()]
[string]$agentSource="C:\Temp\ibm-ucd-agent.zip",
[Parameter(Mandatory=$true,HelpMessage="The Group Managed Account that is used as service account.")]
[ValidateNotNullOrEmpty()]
[string]$groupManagedServiceAccount="IFC1\srvgp-ucd-r$"
)
Configuration UrbanCodeAgentConfiguration{
Import-DscResource -ModuleName PSDesiredStateConfiguration
if(($env:Path.Split(';') | Where-Object { $_ -like "*JDK*" -or $_ -like "*JAVA*" } )){
Node $env:COMPUTERNAME
{
# First Extract the service binaries to the Destination
Archive UrbanCodeBinaries
{
Destination = "C:\Program Files"
Ensure = "Present"
Path = $agentSource
Force = $true
}
# Preconfigure the properties for the service
$agentName = ("ucd-agent-{0}" -f $env:COMPUTERNAME)
$servicePathArguments = "//RS//{0}" -f $agentName
$servicePath = Join-Path -Path $env:ProgramFiles -ChildPath "ibm-ucd-agent\native\x64\winservice.exe"
# Second configure the service
Service UrbanCodeAgentService
{
Ensure = "Present"
Name = $agentName
StartupType = "Manual"
Path = ("`"{0}`" {1}" -f $servicePath, $servicePathArguments)
Description = "IBM Urban Code Agent"
DisplayName = $agentName
State = "Stopped"
}
# Third Set the Service with a script Resource
Script ServiceScript{
DependsOn = "[Service]UrbanCodeAgentService"
GetScript = {
return Get-WmiObject -Class win32_Service | Where-Object -Property Name -like ("*{0}*" -f $using:agentName)
}
TestScript = {
$service = [scriptblock]::Create($GetScript).Invoke()
if($service){
return ($service.StartName -eq $using:groupManagedServiceAccount)
}
return $false
}
SetScript = {
$service = [scriptblock]::Create($GetScript).Invoke()
if($service){
$service.Change($null, $null, $null, $null, $null, $null, $using:groupManagedServiceAccount, $null, $null, $null, $null)
}
}
}
}
}else {
Write-Host "Java is not installed"
}
}
UrbanCodeAgentConfiguration
Eine Bemerkung zum Node: Started man die Desired State Configuration, so ist eine MOF-Datei das Resultat. Will man diese archivieren, so würde diese standardmässig "localhost" heissen, was nicht hilfreich wäre. Aus diesem Grund verwende ich immer die Variable $env:COMPUTERNAME um so eine sprechende MOF-Datei zu erhalten.
Fazit
Nicht alles was bereits standardmässig zur Verüfung steht, kann gleich für jeden Anwendungsfall verwendet werden. Mit der Script-Ressource ist es möglich eine gewisse Flexibilität gegeben und man kann auch ohne dedizierte Scripts Aktionen ausführen, die von keiner Ressource bereitgestellt werden.
Für Fragen und Anregungen bin ich offen und wenn Dir der Artikel gefallen hat, dann freue ich mich über ein like
In diesem Blog-Post soll gezeigt werden, wie man einfach einen Dienst mittels DSC (Desired State Configuration) installiert und wieder deinstalliert. Die meisten Beispiele zeigen nur kleine Schnipsel an Code, sodass kein Praxisnahes Beispiel herangezogen werden kann.
Ziel
Die Installation eines Windows Dienstes mittels DSC auf einem Windows Server 2019 Core
Durchführung
Um die Voraussetzungen zu erfüllen, um DSC verwenden zu können, kann hier nachgelesen werden.
Schritte zur Durchführung
Die Quelldateien des Dienstes müssen von einem Quellverzeichnis in das Zielverzeichnis des Dienstes kopiert werden.
Der Dienst muss dann mittels DSC installiert werden können.
Für diese Operation sind die zwei Windows Resourcen notwendig, die bereits fixfertig vom Betriebssystem geliefert werden.
Die Variable $env:COMPUTERNAME wird verwendet, damit die generierte MOF-Datei den Namen des Servers aufweist und die Konfiguration so zu anderen unterschieden werden kann.
Die ServiceDirectoryCopy Aktion führt das Kopieren in rekursiver Form der Dienst Dateien aus.
Die Operation DemoService konfiguriert und installiert den Dienst.
Nun muss mittels Aufruf der Scriptes und den Parametern die MOF-Datei erzeugt werden. In diesem Beispiel sieht der Aufruf wie folgt aus:
so sehen wir alle konfigurierten Werte, dieses Dienstes und sehen, dass die Konfiguration angewendet worden ist.
Fazit
Mit einfachen Mitteln lassen sich so ohne Komplexe Scripts gewünschte Zustände eines Systems festlegen und die Konfiguration ist einfacher zu lesen und zu interpretieren. Ein weiterer Vorteil liegt in der Idempotenz.
Ein Nachteil dieser Methode ist, dass die Anmeldeinformationen nicht mitgegeben werden können und diese anschliessend konfiguriert werden müssen. Eine Alternative hierfür könnnte sein, dass man selber eine Resource schreibt und dies implementiert oder wie bereits erwähnt über Set-CimInstance, die entsprechende Eigenschaft des Dienst-Objektes setzt.
Falls der Artikel Gefallen gefunden hat, freue ich mich über einen Like und bin für Rückmeldungen offen.