Łukasz Kołodziej
DevOps i Cloud Architect
15 grudnia 2024
Jak stworzyć uniwersalne moduły w Terraform na przykładzie Azure AKS?
Tworzenie uniwersalnych modułów Terraform wymaga elastyczności, aby mogły być one łatwo wykorzystane w różnych scenariuszach. W tym artykule przedstawię podejście oparte na przykładzie modułu Azure Kubernetes Service (AKS), aby pokazać, jak budować moduły, które są zarówno elastyczne, jak i maksymalnie uniwersalne. Dzięki temu zyskasz możliwość prostego zarządzania infrastrukturą, niezależnie od środowiska, w którym pracujesz.
Skalowalność i Elastyczność dzięki for_each
Jednym z kluczowych elementów naszego modułu jest `for_each`, który pozwala definiować wiele zasobów na podstawie przekazanych zmiennych. Dzięki niemu możemy tworzyć zasoby w sposób dynamiczny i skalowalny, dopasowując ich liczbę do potrzeb.
Na przykład, aby utworzyć wiele klastrów AKS, używamy for_each do iteracji przez wszystkie definicje klastrów, które znajdują się w zmiennej var.kubernetes_clusters. To podejście pozwala zautomatyzować tworzenie zasobów bez konieczności ręcznego ich definiowania, co jest szczególnie przydatne przy dużych infrastrukturach.
resource "azurerm_kubernetes_cluster" "aks" { for_each = var.kubernetes_clusters name = each.value.custom_name != null ? each.value.custom_name : "${var.prefix}-${each.value.name}-aks-${var.environment}-${var.region_suffix}" # Pozostałe właściwości... }
Dzięki for_each, proces definiowania zasobów staje się nie tylko prostszy, ale także bardziej zautomatyzowany, co z kolei pozwala na większą skalowalność i elastyczność.
—
Parametryzacja Nazw Zasobów
Parametryzacja nazw to kolejny sposób, który pomaga nam tworzyć elastyczne moduły. W naszym przykładzie AKS używamy wyrażeń warunkowych, aby dynamicznie generować nazwy zasobów w zależności od wartości zmiennych. Pozwala to na większą elastyczność i spójność w zarządzaniu infrastrukturą.
name = each.value.custom_name != null ? each.value.custom_name : "${var.prefix}-${each.value.name}-aks-${var.environment}-${var.region_suffix}"
Dzięki temu moduł można łatwo dostosować do różnych środowisk (np. dev, test, prod) oraz regionów chmurowych, co znacząco ułatwia zarządzanie. Tego rodzaju elastyczność umożliwia dostosowanie zasobów do specyficznych potrzeb każdego środowiska.
Wykorzystywanie Lokalnych Tagów
Dodawanie tagów do zasobów to świetny sposób na lepszą organizację i zarządzanie infrastrukturą. W naszym module używamy lokalnych tagów, aby wzbogacić zasoby o dodatkowe informacje, takie jak nazwa modułu (`tf_module_name`) i jego wersja (`tf_module_ver`).
locals { tags = merge(each.value.tags, { tf_module_name = "aks", tf_module_ver = "1.0.0" }) } resource "azurerm_kubernetes_cluster" "aks" { tags = merge(each.value.tags, local.tags) }
Dzięki temu łatwiej jest zidentyfikować zasoby w panelu zarządzania chmurą i prowadzić audyt. Tagi pomagają w standaryzacji zasobów, co z kolei ułatwia ich monitorowanie oraz analizę kosztów.
Zabezpieczenia i Walidacje Danych Wejściowych
Kolejnym kluczowym aspektem naszego modułu jest walidacja danych wejściowych. W Terraform używamy null_resource wraz z for_each i provisioner „local-exec” do sprawdzenia, czy użytkownik przekazał wszystkie niezbędne parametry dla każdego klastra AKS.
resource "null_resource" "validate_aks_names" { for_each = var.kubernetes_clusters count = (each.value.name == null && lookup(each.value, "custom_name", null) == null) ? 1 : 0 provisioner "local-exec" { command = "echo 'Error: Either custom_name or name must be defined for each AKS cluster.' && exit 1" } }
Dzięki temu zapewniamy, że każdy klaster jest prawidłowo skonfigurowany, co zmniejsza ryzyko błędów. Walidacja pomaga w upewnieniu się, że każda konfiguracja jest zgodna ze standardami oraz że zasoby są tworzone zgodnie z oczekiwaniami.
Finalny Moduł – Elastyczność w Działaniu
W naszej konfiguracji tworzymy różne zasoby wspierające działanie AKS, takie jak dodatkowe pule węzłów, diagnostyka monitorowania oraz prywatne połączenia sieciowe. Taka architektura pozwala na kompleksowe zarządzanie klastrami, zapewniając jednocześnie elastyczność i możliwość rozszerzania modułu.
locals { tags = merge(each.value.tags, { tf_module_name = "aks", tf_module_ver = "1.0.0" }) } resource "null_resource" "validate_aks_names" { for_each = var.kubernetes_clusters count = (each.value.name == null && lookup(each.value, "custom_name", null) == null) ? 1 : 0 provisioner "local-exec" { command = "echo 'Error: Either custom_name or name must be defined for each AKS cluster.' && exit 1" } } resource "azurerm_kubernetes_cluster" "aks" { for_each = var.kubernetes_clusters name = each.value.custom_name != null ? each.value.custom_name : "${var.prefix}-${each.value.name}-aks-${var.environment}-${var.region_suffix}" location = each.value.location resource_group_name = each.value.custom_rg_name != null ? each.value.custom_rg_name : "${var.prefix}-${each.value.name}-aks-rg-${var.environment}-${var.region_suffix}" kubernetes_version = each.value.kubernetes_version dns_prefix = "${each.value.name}-dns" default_node_pool { name = each.value.default_node_pool.custom_name != null ? each.value.default_node_pool.custom_name : "${var.prefix}-${each.value.default_node_pool.name}-pool${var.environment}-${var.region_suffix}" vm_size = each.value.default_node_pool.vm_size node_count = each.value.default_node_pool.node_count enable_auto_scaling = each.value.default_node_pool.enable_auto_scaling min_count = each.value.default_node_pool.min_count max_count = each.value.default_node_pool.max_count } identity { type = each.value.identity_type } network_profile { network_plugin = each.value.network_profile.network_plugin dns_service_ip = each.value.network_profile.dns_service_ip # docker_bridge_cidr = each.value.network_profile.docker_bridge_cidr service_cidr = each.value.network_profile.service_cidr } tags = merge(each.value.tags, local.tags) } resource "azurerm_monitor_diagnostic_setting" "aks_monitoring" { for_each = var.monitoring_settings name = each.value.custom_diagnostic_name != null ? each.value.custom_diagnostic_name : "${var.prefix}-${each.value.cluster_name}-diagnostic${var.environment}-${var.region_suffix}" target_resource_id = azurerm_kubernetes_cluster.aks[each.key].id log_analytics_workspace_id = each.value.log_analytics_workspace_id metric { category = "AllMetrics" enabled = true } } resource "azurerm_kubernetes_cluster_node_pool" "additional_node_pools" { for_each = var.additional_node_pools kubernetes_cluster_id = azurerm_kubernetes_cluster.aks[each.value.cluster_key].id name = each.value.custom_node_pool_name != null ? each.value.custom_node_pool_name : "${var.prefix}-${each.key}-nodepool-${var.environment}-${var.region_suffix}" vm_size = each.value.vm_size node_count = each.value.node_count enable_auto_scaling = each.value.enable_auto_scaling min_count = each.value.min_count max_count = each.value.max_count } resource "azurerm_subnet" "aks_subnet" { for_each = { for key, value in var.kubernetes_clusters : key => value if lookup(value, "create_private_endpoint", false) } name = "${var.prefix}-${each.value.name}-aks-subnet${var.environment}-${var.region_suffix}" resource_group_name = each.value.vnet_resource_group_name virtual_network_name = each.value.virtual_network_name address_prefixes = each.value.subnet_address_prefixes } resource "azurerm_private_endpoint" "aks_private_endpoint" { for_each = { for key, value in var.kubernetes_clusters : key => value if lookup(value, "create_private_endpoint", false) } name = each.value.custom_private_endpoint_name != null ? each.value.custom_private_endpoint_name : "${var.prefix}-${each.value.name}-aks-private-endpoint" location = each.value.location resource_group_name = each.value.custom_rg_name != null ? each.value.custom_rg_name : "${var.prefix}-${each.value.name}-rg-${var.environment}-${var.region_suffix}" subnet_id = azurerm_subnet.aks_subnet[each.key].id private_service_connection { name = each.value.custom_pe_name != null ? each.value.custom_pe_name : "${var.prefix}-aks-pe-${each.value.name}-${var.environment}-${var.region_suffix}" private_connection_resource_id = azurerm_key_vault.aks[each.key].id is_manual_connection = each.value.private_endpoint.create_manual_connection subresource_names = ["aks"] } tags = merge(each.value.tags, local.tags) }
Podsumowanie – Tworzenie Elastycznego Modułu AKS
Dzięki powyższym elementom – używaniu for_each, parametryzacji nazw, tagom i walidacjom – nasz moduł AKS jest maksymalnie uniwersalny i elastyczny. Może być wykorzystany w różnych środowiskach, z różnymi konfiguracjami, zachowując pełną kontrolę nad infrastrukturą.
Dodatkowo, taka konfiguracja znacznie ułatwia import już istniejących zasobów i pozwala na ich elastyczny rozwój bez konieczności tworzenia nowych zasobów. Dzięki temu można zaoszczędzić czas oraz uniknąć ryzyka związanego z tworzeniem i konfiguracją nowych komponentów od podstaw.
Tworzenie modułów Terraform, które są elastyczne i uniwersalne, wymaga odpowiedniego planowania, ale efekty w postaci automatyzacji i minimalizacji ryzyka błędów zdecydowanie są tego warte. Elastyczność takich modułów sprawia, że mogą one być wielokrotnie wykorzystywane, co przynosi oszczędności zarówno w czasie, jak i kosztach.
Chcesz dowiedzieć się więcej o budowaniu elastycznych modułów w Terraform? Podziel się swoimi doświadczeniami lub pytaniami w komentarzach!
Dodaj komentarz