Autor: aircloud

  • Monitorowanie i logowanie w środowisku DevOps: najlepsze praktyki i narzędzia

    Monitorowanie i logowanie w środowisku DevOps: najlepsze praktyki i narzędzia

    Łukasz Kołodziej

    DevOps i Cloud Architect

    6 kwietnia 2023

    Monitorowanie i logowanie są kluczowymi elementami środowiska DevOps, które pozwalają na utrzymanie wysokiej jakości usług, szybkie wykrywanie błędów i ciągłe doskonalenie aplikacji. W artykule przedstawimy najlepsze praktyki oraz narzędzia związane z monitorowaniem i logowaniem w środowisku DevOps.

    1. Najlepsze praktyki monitorowania i logowania

    Wdrożenie efektywnego monitorowania i logowania w środowisku DevOps opiera się na kilku kluczowych praktykach:

    • Centralizacja danych: zbieraj logi, metryki i inne dane z różnych komponentów aplikacji i infrastruktury w jednym miejscu, aby ułatwić analizę i wykrywanie problemów.
    • Automatyzacja: korzystaj z narzędzi do automatycznego zbierania, przetwarzania i analizy danych, aby zoptymalizować proces monitorowania i reagowania na problemy.
    • Wczesne wykrywanie: ustaw alerty na podstawie istotnych metryk i progów, aby szybko wykryć problemy i podjąć odpowiednie działania.
    • Integracja z procesem wdrażania: łącz dane monitorowania i logowania z informacjami o wdrożeniach, aby łatwo identyfikować wpływ zmian na jakość usług.
    • Wizualizacja danych: wykorzystuj narzędzia do wizualizacji danych, aby łatwiej zrozumieć stan aplikacji i infrastruktury, a także zidentyfikować trendy i problemy.

    2. Narzędzia monitorowania i logowania

    Istnieje wiele narzędzi monitorowania i logowania, które mogą wspierać praktyki DevOps. Oto kilka popularnych rozwiązań:

    • Elasticsearch, Logstash i Kibana (ELK Stack): open-source’owy stos technologiczny, który umożliwia centralizację, przetwarzanie, wyszukiwanie i wizualizację logów.
    • Grafana: narzędzie do wizualizacji danych, które pozwala na tworzenie atrakcyjnych i interaktywnych dashboardów z różnych źródeł danych, takich jak Prometheus, InfluxDB czy Elasticsearch.
    • Prometheus: open-source’owy system monitorowania i alertowania, który zbiera metryki z aplikacji i infrastruktury, oferując wydajne zapytania i wizualizacje danych.
    • Datadog: platforma monitorowania i logowania w chmurze, która integruje się z wieloma usługami i technologiami, oferując zaawansowane funkcje analizy i wizualizacji danych.
    • Splunk: rozwiązanie do zbierania, analizy i wizualizacji danych z różnych źródeł, które oferuje potężne możliwości wyszukiwania, analizy i generowania raportów.
    • Graylog: open-source’owa platforma do zarządzania logami, która oferuje funkcje przetwarzania, wyszukiwania i analizy logów, a także możliwość definiowania alertów.
    • New Relic: usługa monitorowania aplikacji (APM) i infrastruktury, która oferuje szczegółowe informacje o wydajności, błędach i transakcjach, wspierając różne języki programowania i technologie.
    • Zabbix: open-source’owy system monitorowania, który pozwala na zbieranie danych z różnych źródeł, generowanie alertów i wizualizowanie danych za pomocą prostych dashboardów

    3. Wybór odpowiednich narzędzi

    Wybór odpowiednich narzędzi monitorowania i logowania zależy od wielu czynników, takich jak wymagania projektu, zasoby, technologie i preferencje zespołu. Oto kilka kryteriów, które warto wziąć pod uwagę podczas wyboru narzędzi:

    • Skalowalność: wybierz narzędzia, które są w stanie obsłużyć rosnącą ilość danych i zasobów w miarę rozwoju aplikacji i infrastruktury.
    • Integracja: upewnij się, że wybrane narzędzia współpracują z istniejącymi technologiami, usługami i procesami w Twoim środowisku DevOps.
    • Elastyczność: daj pierwszeństwo narzędziom, które oferują konfigurowalność i możliwość dostosowania do Twoich potrzeb, tak aby mogły ewoluować wraz z Twoim projektem.
    • Koszt: weź pod uwagę koszty związane z zakupem, wdrożeniem i utrzymaniem narzędzi, a także koszty szkolenia personelu w ich obsłudze.

    Monitorowanie i logowanie są niezbędnymi elementami środowiska DevOps, które pozwalają na utrzymanie wysokiej jakości usług, szybkie wykrywanie błędów i ciągłe doskonalenie aplikacji. Wybór odpowiednich praktyk i narzędzi może znacząco wpłynąć na efektywność procesów monitorowania i logowania. Dlatego warto poświęcić czas na analizę dostępnych rozwiązań oraz na testowanie ich w praktyce, aby znaleźć najlepsze narzędzia, które spełnią wymagania Twojego projektu i zespołu.

  • Zarządzanie Agile w zespole DevOps

    Zarządzanie Agile w zespole DevOps

    Łukasz Kołodziej

    DevOps i Cloud Architect

    8 kwietnia 2023

    Zarządzanie Agile jest podejściem do prowadzenia projektów, które opiera się na elastyczności, współpracy i ciągłym doskonaleniu. W świecie DevOps, gdzie szybkość i adaptacja do zmian są kluczowe, zarządzanie Agile staje się coraz bardziej popularne. W niniejszym artykule przedstawimy, jak zastosować zasady Agile w zespołach DevOps, omówimy różne metodyki oraz praktyki zarządzania, które można wykorzystać w celu osiągnięcia lepszych rezultatów.

    1. Agile w DevOps: połączenie dwóch światów

    Zarówno Agile, jak i DevOps mają na celu zwiększenie efektywności i szybkości dostarczania wartości dla klienta poprzez ciągłą współpracę i usprawnienie procesów. W zespołach DevOps, Agile może zostać zastosowane na różnych etapach cyklu życia produktu, począwszy od analizy wymagań, przez rozwój, testowanie, aż po wdrożenie i utrzymanie aplikacji.

    2. Metodyki Agile w zespołach DevOps

    W zarządzaniu Agile w zespole DevOps można wykorzystać różne metodyki, które oferują elastyczność i adaptację do zmieniających się potrzeb. Do najpopularniejszych z nich należą:

    • Scrum: iteracyjna i inkrementalna metodyka zarządzania projektem, która dzieli pracę na tzw. „sprinty” (krótkie okresy czasu) oraz promuje planowanie, przegląd i retrospekcję w ramach każdego sprintu.
    • Kanban: metodyka oparta na wizualizacji pracy, która pozwala na usprawnienie procesów i zredukowanie ilości pracy w trakcie realizacji. Kanban pomaga w identyfikacji wąskich gardeł i pozwala na ich eliminację, dzięki czemu zespół może się skupić na wartościowych zadaniach.
    • Extreme Programming (XP): metodyka skupiająca się na ciągłej poprawie jakości kodu, testowaniu i komunikacji między członkami zespołu. W XP stosuje się praktyki takie jak programowanie w parach, integracja ciągła, czy przeglądy kodu.

    3. Praktyki zarządzania Agile w zespole DevOps

    • Planowanie i priorytetyzacja: w zarządzaniu Agile konieczne jest regularne planowanie i ustalanie priorytetów zadań, tak aby zespół mógł skupić się na najważniejszych celach i dostarczyć wartość dla klienta.
    • Komunikacja i współpraca: ciągła komunikacja między członkami zespołu, jak również między zespołem DevOps a innymi zespołami i interesariuszami, jest kluczowa dla sukcesu projektów opartych na Agile. Współpraca pomaga w identyfikacji problemów, podejmowaniu decyzji i osiąganiu wspólnych celów.
    • Retrospekcja i ciągłe doskonalenie: po każdym zakończonym sprincie lub etapie projektu, zespół powinien przeprowadzić retrospekcję, aby zidentyfikować obszary wymagające poprawy, uczestniczyć w wymianie wiedzy i uczestniczyć w procesie ciągłego doskonalenia.
    • Automatyzacja i integracja ciągła: praktyki Agile w DevOps zachęcają do automatyzacji procesów, takich jak testowanie, wdrożenie czy monitorowanie, co pozwala na szybsze dostarczenie wartości dla klienta i lepszą kontrolę nad jakością kodu.
    • Adaptacja do zmian: zarządzanie Agile w zespole DevOps zakłada, że zmiany są nieuniknione i stanowią źródło wartości dla klienta. Dlatego zespoły powinny być gotowe do elastycznego reagowania na zmieniające się wymagania, potrzeby rynku i sytuacje biznesowe.

    4. Korzyści wynikające z zarządzania Agile w zespole DevOps

    • Wdrożenie praktyk Agile w zespole DevOps może przynieść wiele korzyści, takich jak:
    • Szybsze dostarczenie wartości dla klienta: Agile pozwala na efektywniejsze zarządzanie zadaniami, co przekłada się na szybsze wdrożenie nowych funkcji i poprawek.
    • Wyższa jakość kodu: dzięki ciągłemu doskonaleniu i praktykom takim jak przeglądy kodu, zespoły DevOps są w stanie utrzymać wysoką jakość kodu i unikać błędów.
    • Lepsza komunikacja i współpraca: praktyki Agile promują otwartość i współpracę między członkami zespołu oraz z innymi zespołami i interesariuszami, co prowadzi do lepszych rezultatów.
    • Większa elastyczność i adaptacja do zmian: zespoły DevOps stosujące zarządzanie Agile są w stanie szybciej dostosowywać się do zmieniających się potrzeb rynku i klientów, co daje im przewagę konkurencyjną.

    W pewnej dynamicznie rozwijającej się firmie technologicznej, zespół DevOps o nazwie „Ninja Team” przyjął metodykę Agile, aby sprostać rosnącym wymaganiom rynku i szybko dostarczać wartość dla klientów. W skład zespołu wchodzili: Katarzyna (Scrum Master), Jakub (DevOps Engineer), Piotr (Backend Developer), Marta (Frontend Developer) i Michał (QA Engineer). W miarę jak liczba projektów i zadań związanych z utrzymaniem infrastruktury zaczęła rosnąć, zespół napotkał na trudności związane z komunikacją i koordynacją działań.

    Aby zaradzić temu problemowi, „Ninja Team” zdecydował się na eksperyment, polegający na wprowadzeniu praktyk Scruma, takich jak sprinty, planowanie, przeglądy i retrospekcje. Wprowadzenie Scruma miało na celu usprawnienie procesów i zwiększenie przejrzystości pracy zespołu.

    W pierwszym sprincie zespół zdecydował się skupić na automatyzacji procesów wdrożenia nowych wersji oprogramowania. Zespół podzielił pracę na mniejsze zadania, które miały być realizowane w ciągu dwutygodniowego sprintu. Przed rozpoczęciem sprintu, zespół przeprowadził spotkanie planistyczne, na którym każdy członek zespołu miał możliwość zgłoszenia swoich pomysłów i sugestii dotyczących zadań do realizacji.

    W trakcie sprintu „Ninja Team” zetknął się z problemem związanym z jednym z istniejących skryptów automatyzujących wdrożenie. Okazało się, że skrypt generował błędy podczas pracy z jednym z modułów oprogramowania. Członkowie zespołu: Katarzyna, Jakub, Piotr, Marta i Michał zdecydowali się na wspólne zidentyfikowanie przyczyny problemu i opracowanie rozwiązania. Dzięki bliskiej współpracy i komunikacji, udało się naprawić błąd i dokończyć zadanie.

    Po zakończonym sprincie zespół przeprowadził przegląd, podczas którego przedstawiono wyniki pracy, a także omówiono trudności, które napotkano w trakcie realizacji zadań. Następnie odbyła się retrospekcja, w trakcie której zespół zidentyfikował obszary wymagające poprawy i ustalił plan działania na kolejny sprint.

    Eksperyment z wprowadzeniem Scruma okazał się sukcesem. „Ninja Team” zauważył znaczną poprawę w komunikacji, większą efektywność w realizacji zadań oraz lepsze zarządzanie czasem. Przez zastosowanie praktyk Agile w DevOps, udało się usprawnić procesy, zwiększyć przejrzystość pracy zespołu oraz przyspieszyć dostarczanie wartości dla klientów. Ta pozytywna zmiana stała się impulsem do dalszego stosowania metodyki Agile w codziennej pracy „Ninja Team”.

    W kolejnych sprintach zespół kontynuował swoją pracę nad automatyzacją, monitorowaniem i optymalizacją różnych aspektów wdrożeń. W miarę jak „Ninja Team” coraz bardziej opanowywał metodykę Agile, zaczęli także świadomie dzielić się swoimi doświadczeniami z innymi zespołami w firmie. Dzięki temu, również inne zespoły zaczęły wprowadzać praktyki Agile do swojej pracy, co przyczyniło się do poprawy efektywności i komunikacji w całej organizacji.

    Jednym z wyjątkowych osiągnięć „Ninja Team” było opracowanie innowacyjnego narzędzia do monitorowania infrastruktury, które pozwoliło na wykrywanie potencjalnych problemów z wydajnością i bezpieczeństwem przed ich wystąpieniem. Ten sukces był możliwy dzięki współpracy wszystkich członków zespołu, którzy wspólnie opracowali koncepcję, zaprojektowali architekturę i wdrożyli rozwiązanie. To osiągnięcie stało się źródłem dumy dla „Ninja Team” i przyczyniło się do dalszego wzrostu zaufania między członkami zespołu.

    Kiedy firma zdecydowała się na ekspansję na nowe rynki, zespół „Ninja Team” był w stanie elastycznie dostosować się do rosnących wymagań i nowych wyzwań. Dzięki praktykom Agile, takim jak regularne planowanie, adaptacja do zmian oraz ciągłe doskonalenie, „Ninja Team” stał się wzorem dla innych zespołów w organizacji i wyraźnym dowodem na wartość wprowadzenia zarządzania Agile w zespołach DevOps.

    Zarządzanie Agile w zespole DevOps pozwala na efektywniejsze realizowanie projektów, dostarczanie wartości dla klienta i osiąganie sukcesów biznesowych. Przez stosowanie metodyk takich jak Scrum, Kanban czy Extreme Programming oraz praktyk zarządzania, takich jak planowanie, komunikacja, retrospekcja czy automatyzacja, zespoły DevOps mogą lepiej dostosować się do zmieniających się potrzeb rynku i klientów. Wdrożenie zarządzania Agile

  • Implementing CI/CD Pipelines in Azure DevOps: A Practical Approach Using Terraform

    Implementing CI/CD Pipelines in Azure DevOps: A Practical Approach Using Terraform

    Łukasz Kołodziej

    DevOps i Cloud Architect

    11 grudnia 2024

    Introduction

    Azure DevOps is a comprehensive platform that facilitates application lifecycle management, offering tools for version control, requirement management, build automation, testing, and release management. In the DevOps context, the primary goal is to automate and streamline the deployment process to deliver new features faster and with higher quality. In this article, I will discuss the practical implementation of CI/CD pipelines in Azure DevOps, and how Terraform supports these processes through an Infrastructure as Code (IaC) approach.

    What is Azure DevOps and Why Should You Use It?

    Azure DevOps is a set of tools that integrate various stages of software development into a single, automated process. CI/CD pipelines form the backbone of DevOps, enabling the automated building, testing, and deployment of code to testing, production, and other environments. The key aspects of Azure DevOps include:

    Continuous Integration (CI): Integrating code changes into a shared repository continuously, allowing for quick detection of issues.

    Continuous Delivery (CD): Automating the deployment process to ensure quick and reliable delivery to users.

    Creating a Pipeline in Azure DevOps

    The foundation of a CI/CD pipeline in Azure DevOps is a YAML file, which defines the steps for building, testing, and deploying the application. Below, I outline the main steps and elements that should be included in the pipeline:

    Version Control Definition: Azure DevOps provides version control via GIT, which allows tracking of changes in both code and infrastructure configurations.

    Automated Build: The build process can include steps such as compilation and running unit and integration tests.

    Testing: Testing is critical in the CI/CD process. Unit, integration, and functional tests can be automatically run during the pipeline.

    Deployment (Release): The release pipeline is responsible for deploying the application to production or other environments. Azure DevOps supports deployments to platforms like Azure App Services, Azure Kubernetes Service (AKS), and others.

    Terraform and Azure DevOps: Infrastructure as Code (IaC)

    Terraform is a tool for managing infrastructure using an IaC model, which allows the automation of creating and managing environments. Using Terraform in Azure DevOps enables:

    Infrastructure Management: Defining infrastructure in configuration files (e.g., main.tf) that can be versioned and shared within the team. Resources such as VMs, App Services, or Kubernetes can be defined here.

    Simplified Workflow: Terraform plans, creates, and updates resources according to the defined state (`terraform plan`, terraform apply). This helps quickly detect discrepancies between the actual and desired state.

    Modularity and Reusability: Terraform supports modules, enabling you to create resource sets that can easily be reused across environments. For example, you can create a module for deploying an Azure App Service and use it in multiple projects.

    Implementing CI/CD Pipelines in Azure DevOps with Terraform: A Practical Approach\

    General Outline of the Pipeline

    The CI/CD pipeline described here consists of two stages: Plan and Deploy. The pipeline combines best practices for code versioning, validation, and infrastructure deployment through Terraform.

    The pipeline is structured in a YAML file, making it easy to edit, version, and reuse. Below is a detailed description of each stage.

    Pipeline Parameters\

    In the parameters section, we specify the environment selection (e.g., DEV, TEST, PRE, PROD) and an optional Terraform deployment (`boolean deployTerraform`). These parameters allow for dynamic pipeline adjustment, making it more versatile and easier to use across different environments.

    parameters:
    - name: environment
      displayName: "Select an environment"
      type: string
      default: "DEV"
      values:
      - "DEV"
      - "TEST"
      - "PRE"
      - "PROD"
    - name: deployTerraform
      displayName: "Deploy Terraform?"
      type: boolean
      default: false

    Stage 1: Planning (Plan Stage)

    The first stage, called „Validate and Plan Terraform,” consists of several key steps. The goal of this stage is to prepare a Terraform action plan, which will be approved before moving to the deployment stage.

    Install Terraform: In this step, the pipeline installs the latest version of Terraform.

    Create „: Next, the pipeline generates a backend.tf file, which contains the backend configuration for Terraform (`backend „azurerm”`). Values such as resource_group_name, storage_account_name, and subscription_id are drawn from the variable definitions.

    Validation and Planning: Finally, the pipeline runs terraform init and terraform plan commands to create a detailed deployment plan for the selected environment.

    stages:
    - stage: Plan
      displayName: 'Validate and Plan Terraform'
      jobs:
      - job: Plan
        displayName: 'Plan Terraform'
        steps:
        - checkout: self
        - task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
          displayName: 'Install Terraform'
          inputs:
            terraformVersion: 'latest'
        - task: Bash@3
          displayName: 'Run Terraform Plan'
          inputs:
            targetType: 'inline'
            script: |
              cat > backend.tf <<EOL
              terraform {
                backend "azurerm" {
                  resource_group_name  = "$(backendAzureRmResourceGroupName)"
                  storage_account_name = "$(backendAzureRmStorageAccountName)"
                  container_name       = "$(backendAzureRmContainerName)"
                  key                  = "$(backendAzureRmKey)"
                  subscription_id      = "$(backendAzureRMSubscriptionID)"
                  tenant_id            = "$(terraform_app_tenant_id)"
                }
              }
              EOL
              terraform init && terraform plan -var-file=environments/${{parameters.environment}}.tfvars

    Stage 2: Deployment (Deploy Stage)

    The second stage, called „Deploy Terraform,” is a conditional stage that can be run if the deployTerraform parameter is set to true. In this stage, manual validation of the previous step’s results is also required before proceeding.

    Manual Validation: Before moving to the actual deployment, manual validation is required. Stakeholders must confirm that the plan results align with expectations.

    Deploy Terraform Configuration: After approval, the pipeline continues by installing Terraform and running terraform apply to implement the prepared infrastructure.

    - stage: Deploy
      displayName: 'Deploy Terraform'
      condition: and(succeeded(), eq('${{parameters.deployTerraform}}', true))
      jobs:
      - job: WaitForApproval
        displayName: 'Wait for validation approval'
        pool: server
        steps:
        - task: ManualValidation@0
          displayName: 'Wait for validation'
          inputs:
            notifyUsers: '$(PipelineValidators)'
            instructions: 'Please review the output of the plan stage before proceeding.'
      - job: Deploy
        displayName: 'Deploy Terraform Configurations'
        dependsOn: WaitForApproval
        steps:
        - task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
          displayName: 'Install Terraform'
          inputs:
            terraformVersion: 'latest'
        - task: Bash@3
          displayName: 'Run Terraform Apply'
          inputs:
            targetType: 'inline'
            script: 'terraform init && terraform apply -auto-approve'

    This CI/CD pipeline in Azure DevOps, using Terraform, is a powerful tool for managing infrastructure as code. It automates planning, validation, and deployment of infrastructure changes, ensuring consistency, repeatability, and high-quality deployments.

    The parameterization and manual validation add flexibility and control over the deployment process, which is especially important in production projects where every change can have significant consequences. Terraform as an infrastructure management tool is an excellent choice, allowing not only resource definition but also versioning and easy scaling.

    Benefits of Using Azure DevOps and Terraform

    Automation and Repeatability: Both Terraform and Azure DevOps support automation, allowing for consistent, repeatable deployment of changes in infrastructure and applications.

    Version Control: Terraform supports version control, enabling precise tracking of changes in infrastructure configuration and easy rollback if needed.

    Scalability and Modularity: With Terraform modules, managing complex infrastructure is easier, which increases flexibility when creating and maintaining resources.

    Challenges and Best Practices

    Security: Terraform stores the state of the infrastructure, which may contain sensitive information. It is recommended to store state in secure locations, such as Azure Storage with access controls.

    Module Versioning: Using modules helps with reusability but also requires proper versioning to avoid compatibility issues.

    Monitoring and Alerts: In the DevOps context, monitoring the infrastructure and setting appropriate alerts is key. Tools like Coralogix and Prometheus can be used to monitor deployments.

    Conclusion: Azure DevOps and Terraform are powerful tools that enable the full automation of both application and infrastructure deployments. The integration of these tools allows development teams to make changes more quickly and respond rapidly to user needs. The key to success in implementing CI/CD pipelines is to follow best practices, such as modularity, versioning, and appropriate security management.

  • Tworzenie Elastycznych Modułów Terraform na Przykładzie Azure AKS

    Ł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!

  • Importowanie istniejących zasobów do Terraform 🌍 – Praktyczny Przewodnik 🚀

    Importowanie istniejących zasobów do Terraform 🌍 – Praktyczny Przewodnik 🚀

    Łukasz Kołodziej

    DevOps i Cloud Architect

    16 grudnia 2024

    W zarządzaniu infrastrukturą kluczową rolę odgrywa automatyzacja i standaryzacja. Terraform umożliwia importowanie istniejących zasobów, które zostały utworzone poza jego konfiguracją, dzięki czemu można je w pełni objąć zarządzaniem jako kodem (IaC). To podejście pozwala na łatwiejsze aktualizacje, śledzenie zmian i lepszą współpracę w zespole. 🤝


    Dlaczego warto importować zasoby do Terraform? 🛠️

    Importowanie zasobów do Terraform pozwala na: ✅ Standaryzację zarządzania infrastrukturą – wszystkie zasoby są opisane jako kod. ✅ Lepszą automatyzację – konfiguracje można aktualizować i wdrażać za pomocą Terraform. ✅ Transparentność – zmiany w infrastrukturze są śledzone w repozytorium, co zwiększa kontrolę i zmniejsza ryzyko błędów.


    Jak działa proces importu? ⚙️

    Importowanie zasobów wymaga wykonania dwóch kroków:

    1️⃣ Zdefiniowanie zasobów w konfiguracji Terraform Tworzymy odpowiednie bloki konfiguracyjne w plikach .tf odpowiadające zasobom, które chcemy zaimportować.

    2️⃣ Dodanie bloków import Każdy importowany zasób wymaga:

    • to – ścieżki do definiowanego zasobu Terraform.
    • id – unikalnego identyfikatora zasobu, który pochodzi z platformy chmurowej (np. Azure, AWS).

    Przykład: Importowanie zasobów Azure EventHub ☁️

    Rozważmy import zasobów Azure EventHub. Zdefiniujemy je w module Terraform, a następnie zaimportujemy do niego istniejące zasoby.

    Konfiguracja Terraform w pliku main.tf:

    module "eventhub" {
      source              = "git::https://example-repo.com/terraform/modules/eventhub?ref=1.0.0"
      for_each            = var.eventhub
      name                = each.key
      eventhubs           = each.value.eventhubs
      eventhub_sku        = each.value.eventhub_sku
      resource_group_name = lookup(each.value, "resource_group_name", module.resource_group.name)
      location            = local.location
      environment         = local.environment
      tags                = module.resource_group.tags
    } 

    Dodanie bloków import do pliku konfiguracyjnego:

    import {
      to = module.eventhub["example-eventhub-ns"].azurerm_eventhub_namespace.eventhub
      id = "/subscriptions/XXXX/resourceGroups/example-rg/providers/Microsoft.EventHub/namespaces/example-eventhub-ns"
    }
    
    import {
      to = module.eventhub["example-eventhub-ns"].azurerm_eventhub.eventhub["example-hub"]
      id = "/subscriptions/XXXX/resourceGroups/example-rg/providers/Microsoft.EventHub/namespaces/example-eventhub-ns/eventhubs/example-hub"
    }
    
    import {
      to = module.eventhub["example-eventhub-ns"].azurerm_eventhub_consumer_group.consumer_groups["example-consumer"]
      id = "/subscriptions/XXXX/resourceGroups/example-rg/providers/Microsoft.EventHub/namespaces/example-eventhub-ns/eventhubs/example-hub/consumerGroups/example-consumer"
    } 

    Powyższe bloki importują namespace, konkretny EventHub o nazwie example-hub, oraz grupę konsumentów. 🛠️

    Zdefiniowanie zmiennych w pliku prod.tfvars:

    eventhub = {
      example-eventhub-ns = {
        resource_group_name = "example-rg"
        eventhub_sku = "Standard"
        eventhubs = {
          example-hub = {
            name              = "example-hub"
            partition_count   = 1
            message_retention = 7
            consumer_groups   = ["example-consumer"]
          }
        }
      }
    } 

    Powyższa konfiguracja określa parametry namespace’u, takie jak SKU, oraz szczegóły konkretnego EventHub, w tym liczbę partycji i grupy konsumentów.


    Najważniejsze wskazówki 📝

    💡 Zweryfikuj zgodność zasobów: Po dodaniu zasobów uruchom terraform plan, aby upewnić się, że konfiguracja jest poprawna i nie spowoduje nieoczekiwanych zmian.

    💡 Unikaj modyfikacji w locie: Importuj zasoby dokładnie tak, jak istnieją w infrastrukturze. Jakiekolwiek niezgodności mogą spowodować problemy podczas aktualizacji.

    💡 Dokumentuj zaimportowane zasoby: Opisuj w plikach README lub dokumentacji, jakie zasoby zostały zaimportowane i w jaki sposób są używane – ułatwi to przyszłe zmiany.


    Podsumowanie 📌

    Importowanie istniejących zasobów do Terraform to potężne narzędzie, które pozwala na pełne zarządzanie infrastrukturą jako kodem. Dzięki temu możesz: 🎯 Usprawnić automatyzację. 🎯 Zyskać lepszą kontrolę nad zmianami. 🎯 Przygotować swoją infrastrukturę na przyszły rozwój.

    Jeśli jeszcze nie korzystałeś z funkcji importu Terraform, spróbuj zaimportować kilka zasobów i zobacz, jak bardzo ułatwi to Twoją pracę! 😊

    Masz swoje doświadczenia z importem zasobów? Podziel się nimi w komentarzach! 👇

  • Automating Virtual Machine Management in Azure with Ansible and Azure DevOps

    Automating Virtual Machine Management in Azure with Ansible and Azure DevOps

    Łukasz Kołodziej

    DevOps i Cloud Architect

    12 grudnia 2024

    Managing a fleet of virtual machines (VMs) across multiple Azure regions can quickly become a logistical challenge. As the person responsible for streamlining this process, I developed an Azure DevOps pipeline that automates much of the heavy lifting using Ansible. I’d like to share my experience creating this pipeline, highlight its key features, and provide insights for anyone looking to build a similar solution.


    The Problem to Solve

    When managing hundreds of VMs across numerous regions, I faced several recurring challenges:

    • Keeping VM inventories updated dynamically.
    • Consistently applying configurations and updates.
    • Targeting specific hosts or regions for specialized tasks.
    • Avoiding manual errors while speeding up deployment.

    This was the inspiration behind automating the entire process. My goal was to create a pipeline that required minimal manual intervention while maintaining security and flexibility.


    The Solution: My Azure DevOps Pipeline

    I built a YAML-based Azure DevOps pipeline that integrates with Ansible to:

    1. Automatically update inventories of VMs based on Azure tags.
    2. Run Ansible playbooks dynamically across selected regions or specific hosts.
    3. Leverage Git for inventory version control and collaboration.

    The result was a highly flexible pipeline that addressed all my pain points.


    Key Features of the Pipeline

    1. Parameterized Flexibility

    The pipeline is fully parameterized, allowing me to choose the playbooks to execute, the regions to target, and whether to apply changes to all hosts or a single host.

    Here’s a snippet of the parameters:

    parameters:
      - name: selected_playbooks
        displayName: 'Select Playbooks to Run'
        type: string
        default: 'playbooks/ping.yaml'
        values:
          - 'playbooks/install_basic.yaml'
          - 'playbooks/update_ubuntu.yaml'
          # Additional playbooks...
    
      - name: selected_regions
        displayName: 'Select Regions to Target'
        type: string
        default: '*'
        values:
          - 'ase'
          - 'aue'
          # Other regions... 

    This flexibility allows me to quickly adapt the pipeline to different scenarios, such as deploying monitoring agents or running health checks.


    2. Dynamic Inventory Updates

    One of the most critical components is the update_inventory.sh script. It dynamically discovers VMs based on Azure tags and updates Ansible inventory files by region.

    Here’s the core logic:

    #!/bin/bash
    
    # Capture the start time
    start_time=$(date +%s)
    
    # Define the inventory directory
    INVENTORY_DIR="./inventories"
    
    # Define the tag key and value to filter VMs
    TAG_KEY="your_tag"
    TAG_VALUE="your_tag_value"
    
    # Temporary file to store results for found VMs
    TEMP_FILE=$(mktemp)
    
    # Get the list of VMs with the specified tag
    vms=$(az vm list --query "[?tags.$TAG_KEY=='$TAG_VALUE'].{name:name, resource_group:resourceGroup}" -o json)
    
    # Function to process each VM
    process_vm() {
        vm=$1
        _jq() {
            echo ${vm} | base64 --decode | jq -r ${1}
        }
    
        vm_name=$(_jq '.name')
        resource_group=$(_jq '.resource_group')
    
        # Extract the region suffix from the VM name
        region_suffix=$(echo "$vm_name" | awk -F'-' '{print $NF}')
    
        # Define the path to the corresponding inventory.ini file
        inventory_file="$INVENTORY_DIR/$region_suffix/inventory.ini"
    
        # Quick check: If the VM is already in the inventory, skip further processing
        if grep -q "^$vm_name ansible_host=" "$inventory_file"; then
            echo "$vm_name already exists in $inventory_file. Skipping..."
            return
        fi
    
        # Get the private IP address for the VM
        private_ip=$(az vm list-ip-addresses -g "$resource_group" -n "$vm_name" --query "[].virtualMachine.network.privateIpAddresses[0]" -o tsv)
    
        # Check if the private IP was retrieved
        if [[ -z "$private_ip" ]]; then
            echo "Warning: No private IP found for VM $vm_name. Skipping..."
            return
        fi
    
        # Define the new entry for the VM
        new_entry="$vm_name ansible_host=$private_ip ansible_user=<user> ansible_become_pass=<password>!"
    
        # Check if the inventory file exists, create it if not
        if [[ ! -f "$inventory_file" ]]; then
            echo "[servers]" > "$inventory_file"  # Initialize with the group header
        fi
    
        # Add the VM entry to the inventory file
        echo "$new_entry" >> "$inventory_file"
        echo "Added $vm_name to $inventory_file"
    
        # Add the VM details to the temporary file
        echo "$vm_name (IP: $private_ip)" >> "$TEMP_FILE"
    }
    
    # Iterate over the VMs and process them in parallel
    for vm in $(echo "${vms}" | jq -r '.[] | @base64'); do
        process_vm "$vm" &
    done
    
    # Wait for all background processes to finish
    wait
    
    # Capture the end time
    end_time=$(date +%s)
    
    # Calculate the duration in seconds
    duration=$((end_time - start_time))
    
    # Convert duration to minutes and seconds
    minutes=$((duration / 60))
    seconds=$((duration % 60))
    
    # Display the duration of the script
    echo -e "\nScript execution took $minutes minutes and $seconds seconds."
    
    # Display the count and the list of found VMs
    found_vms_count=$(wc -l < "$TEMP_FILE")
    echo "Found $found_vms_count VMs with tag '$TAG_VALUE':"
    cat "$TEMP_FILE"
    rm "$TEMP_FILE"  # Clean up the temporary file 

    The script:

    • Filters VMs based on a specific tag (your_tag).
    • Retrieves private IPs and dynamically generates Ansible inventory files for each region.

    Tip: Make sure to enable the „Shell tasks arguments validation” option in Azure DevOps pipeline settings to avoid validation errors during script execution. This is a small but critical detail that I learned the hard way!


    3. Git Integration for Inventory Management

    One aspect I’m particularly proud of is how I integrated Git into the pipeline. All inventory updates are pushed to a Git repository, ensuring version control and collaboration.

    steps:
      - bash: |
          git config --global user.email "$GIT_EMAIL"
          git config --global user.name "$GIT_USERNAME"
          echo "https://$GIT_USERNAME:$(System.AccessToken)@orgnization.visualstudio.com" > ~/.git-credentials
    
          git remote add origin $(GIT_REPO_URL) || echo "Remote 'origin' already exists."
          git fetch --all
    
          git checkout -b $(SOURCE_BRANCH) || git checkout $(SOURCE_BRANCH)
          git add -A :!*.pem :!update_inventory.sh
          git commit -m "Updated inventory on $(date '+%Y-%m-%d %H:%M:%S')" || echo "No changes to commit."
          git push origin $(SOURCE_BRANCH) || echo "Failed to push changes."
        displayName: "Push Inventory Updates to Git" 

    This step ensures:

    • Every change to the inventory is tracked and versioned.
    • Team members can easily review and collaborate on updates.

    4. Running Ansible Playbooks Dynamically

    With the inventories updated, the pipeline executes selected playbooks across the targeted regions or specific hosts. Here’s how I handled this:

    steps:
      - bash: |
          playbooks=(${SELECTED_PLAYBOOKS//,/ })
          regions=(${ALL_REGIONS//,/ })
    
          for playbook in "${playbooks[@]}"; do
            for region in "${regions[@]}"; do
              inventory_path="inventories/$region/inventory.ini"
    
              if [ -f "$inventory_path" ]; then
                ansible-playbook $playbook -i $inventory_path --private-key $(ANSIBLE_PRIVATE_KEY_PATH)
              else
                echo "Inventory file for $region not found. Skipping."
              fi
            done
          done
        displayName: "Run Selected Playbooks" 

    This logic:

    • Iterates over selected playbooks and regions.
    • Dynamically adjusts the inventory path for each region.
    • Gracefully handles missing inventory files.

    Lessons Learned

    1. Dynamic Updates Save Time: Automating the inventory process eliminated hours of manual effort and ensured accuracy.
    2. Parameterization is Key: Building flexibility into the pipeline allowed me to reuse it across different scenarios without additional modifications.
    3. Git is a Lifesaver: By pushing inventory updates to Git, I ensured traceability and easy collaboration with the team.
    4. Attention to Pipeline Settings: Enabling „Shell tasks arguments validation” was essential to ensure my scripts ran smoothly.

    Final Thoughts

    Building this pipeline was a rewarding experience. It’s not just about automation—it’s about creating a robust system that scales with your needs and reduces operational overhead. For anyone managing a large-scale Azure environment, I encourage you to explore similar solutions.

    If you’re tackling similar challenges or have questions about this approach, I’d love to connect and discuss! 🚀


    Disclaimer: Sensitive credentials and specifics have been anonymized for security.

    Full pipeline:

    parameters:
      - name: selected_playbooks
        displayName: 'Select Playbooks to Run'
        type: string
        default: 'playbooks/sample-playbook.yaml'
        values:
          - 'playbooks/install_agent.yml'
          - 'playbooks/update_service.yml'
          - 'playbooks/sample-playbook.yaml'
    
      - name: selected_regions
        displayName: 'Select Regions to Target'
        type: string
        default: '*'
        values:
          - 'region1'
          - 'region2'
          - 'region3'
          - '*'
    
      - name: target_scope
        displayName: 'Select Target Scope'
        type: string
        default: 'all_hosts'
        values:
          - 'all_hosts'
          - 'single_host'
    
      - name: single_host
        displayName: 'Enter Single Host to Target (Required if targeting a single host)'
        type: string
        default: 'sample-host-prod-region1'
    
    variables:
      AZURE_CLIENT_ID: '********-****-****-****-************'
      AZURE_CLIENT_SECRET: '*********'
      AZURE_TENANT_ID: '********-****-****-****-************'
      AZURE_SUBSCRIPTION: 'Your Azure Subscription Name'
      GIT_EMAIL: 'devops@example.com'
      GIT_USERNAME: 'DevOps'
      GIT_REPO_URL: 'https://your-org.visualstudio.com/YourProject/_git/ansible-repo'
      SOURCE_BRANCH: 'feature-ansible-updates'
      ANSIBLE_PRIVATE_KEY_PATH: 'ssh/private-key.pem'
      ANSIBLE_HOST_KEY_CHECKING: 'False'
      SELECTED_PLAYBOOKS: "${{ parameters.selected_playbooks }}"
      SELECTED_REGIONS: "${{ parameters.selected_regions }}"
      SINGLE_HOST: "${{ parameters.single_host }}"
      TARGET_SCOPE: "${{ parameters.target_scope }}"
      ALL_REGIONS: 'region1,region2,region3'
    
    stages:
      - stage: CheckCommitAuthor
        displayName: "Check Commit Author"
        jobs:
          - job: CheckAuthor
            displayName: "Check Commit Author"
            pool:
              vmImage: "ubuntu-latest"
    
            steps:
              - checkout: self
                displayName: "Checkout Repository"
    
              - bash: |
                  COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an')
                  echo "Commit Author: $COMMIT_AUTHOR"
                  if [ "$COMMIT_AUTHOR" == "$GIT_USERNAME" ]; then
                    echo "Commit made by DevOps, skipping pipeline."
                    echo "##vso[task.complete result=Succeeded;]DONE"
                    exit 0
                  else
                    echo "Commit made by another user, proceeding with pipeline."
                  fi
                displayName: "Check Commit Author"
    
      - stage: InventoryUpdate
        displayName: "Inventory Update Stage"
        jobs:
          - job: UpdateInventory
            displayName: "Update Inventory with New VM List"
            pool: ubuntu-k8s
    
            steps:
              - checkout: self
                displayName: "Checkout Repository"
    
              - bash: |
                  sed -i -e 's|http://archive.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' \
                         -e 's|http://security.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' /etc/apt/sources.list
    
                  apt-get update -y
                  ACCEPT_EULA=Y apt-get install -y unixodbc jq msodbcsql18 mssql-tools18 unixodbc-dev
                  apt-get install -y python3 python3-venv python3-pip jq
    
                  python3 -m venv ansible_env
                  source ansible_env/bin/activate
                  pip install --upgrade pip --break-system-packages
                  pip install ansible[azure]
                  ansible-galaxy collection install azure.azcollection
                displayName: "Install Dependencies and Ansible"
    
              - task: AzureCLI@2
                inputs:
                  azureSubscription: $(AZURE_SUBSCRIPTION)
                  scriptType: bash
                  scriptLocation: inlineScript
                  inlineScript: |
                    az login --service-principal -u $(AZURE_CLIENT_ID) -p $(AZURE_CLIENT_SECRET) --tenant $(AZURE_TENANT_ID)
                    chmod +x update_inventory.sh
                    ./update_inventory.sh   
                  workingDirectory: $(System.DefaultWorkingDirectory)
                displayName: "Azure CLI Authentication"
    
              - bash: |
                  git config --global user.email "$GIT_EMAIL"
                  git config --global user.name "$GIT_USERNAME"
                  echo "https://$GIT_USERNAME:$(System.AccessToken)@your-org.visualstudio.com" > ~/.git-credentials
                  git config --global credential.helper store
    
                  git remote add origin $(GIT_REPO_URL) || echo "Remote 'origin' already exists, skipping..."
                  git fetch --all
    
                  if git show-ref --verify --quiet refs/heads/$(SOURCE_BRANCH); then
                      git checkout $(SOURCE_BRANCH)
                  else
                      git checkout -b $(SOURCE_BRANCH)
                  fi
                  git add -A :!ansible_env :!*.pem :!update_inventory.sh
                  git commit -m "Updated inventory on $(date '+%Y-%m-%d %H:%M:%S')" || echo "No changes to commit."
                  git push origin $(SOURCE_BRANCH) || echo "Failed to push changes."
                displayName: "Push Inventory Updates to Git"
    
              - bash: |
                  echo "---------------------------------------" >> $(System.DefaultWorkingDirectory)/outputs.md
                displayName: "Initialize outputs.md file"
    
              - bash: |
                  playbooks=(${SELECTED_PLAYBOOKS//,/ })
                  chmod 600 $(ANSIBLE_PRIVATE_KEY_PATH)
    
                  if [ "${SELECTED_REGIONS}" == "*" ]; then
                    regions=(${ALL_REGIONS//,/ })
                  else
                    regions=(${SELECTED_REGIONS//,/ })
                  fi
    
                  for playbook in "${playbooks[@]}"; do
                    for region in "${regions[@]}"; do
                      inventory_path="inventories/$region/inventory.ini"
    
                      if [ -f "$inventory_path" ] && [ -s "$inventory_path" ]; then
                        if [ "$TARGET_SCOPE" == "all_hosts" ]; then
                          echo "Running playbook $playbook for all hosts in region $region"
                          source ansible_env/bin/activate && ansible-playbook $playbook -i $inventory_path --private-key $(ANSIBLE_PRIVATE_KEY_PATH) || true
                        elif [ "$TARGET_SCOPE" == "single_host" ]; then
                          if [ -n "$SINGLE_HOST" ]; then
                            if grep -q "^$SINGLE_HOST" "$inventory_path"; then
                              echo "Running playbook $playbook for single host $SINGLE_HOST in region $region"
                              source ansible_env/bin/activate && ansible-playbook $playbook -i $inventory_path --limit "$SINGLE_HOST" --private-key $(ANSIBLE_PRIVATE_KEY_PATH) || true
                            else
                              echo "Host $SINGLE_HOST does not exist in region $region's inventory. Skipping."
                            fi
                          else
                            echo "No single host specified for region $region. Skipping."
                          fi
                        else
                          echo "Skipping playbook $playbook for region $region as no valid host selection is made."
                        fi
                      else
                        echo "Skipping region $region as inventory file is missing or empty"
                      fi
                    done
                  done
                displayName: "Run Selected Playbooks $(SELECTED_PLAYBOOKS) for Selected Regions $(SELECTED_REGIONS)"
                env:
                  SELECTED_PLAYBOOKS: "$(SELECTED_PLAYBOOKS)"
                  SELECTED_REGIONS: "$(SELECTED_REGIONS)"
                  SINGLE_HOST: "$(SINGLE_HOST)"
                  TARGET_SCOPE: "$(TARGET_SCOPE)"
                  ANSIBLE_HOST_KEY_CHECKING: "$(ANSIBLE_HOST_KEY_CHECKING)"
                  ANSIBLE_PRIVATE_KEY_PATH: "$(ANSIBLE_PRIVATE_KEY_PATH)"
                  ALL_REGIONS: "$(ALL_REGIONS)"
    
              - bash: |
                  echo "Displaying the contents of outputs.md:"
                  ls -l $(System.DefaultWorkingDirectory)  
                  cat $(System.DefaultWorkingDirectory)/outputs.md
                displayName: "Display outputs.md file"