Słowem wstępu

Polubiłem naukę na platformie Microsoft Learn, bo oferuje sandboxowe, tymczasowe subskrypcje. Dzięki temu nie muszę się martwić o koszty czy też późniejsze sprzątanie po ćwiczeniu - zostają one automatycznie usunięte po określonym czasie. Niestety przy niektórych ćwiczeniach konieczne jest użycie własnej subskrypcji i tutaj pojawiał się mały problem - trzeba własnoręcznie czyścić zasoby. Niby nic wielkiego, można pracować z jedną grupą zasobów i później ją usunąć ale czasami to nie wystarcza. Dodatkowo trzeba o tym pamiętać, a pozostawienie uruchomionych usług może sporo kosztować…

Można też ustawić alerty, które ostrzegą nas o przekroczeniu limitu. Jednak z alertami jest taki problem, że koszty z niektórych usług w billingu potrafią pojawiać się ze sporym opóźnieniem, czasami dochodzącym nawet do 24 godzin! Na chwilę obecną nie wiadomo mi o żadnym rozwiązaniu od Microsoftu, które gwarantowało by, że nie przekroczymy określonej kwoty w modelu Pay-As-You-Go.

Myśląc o tym wpadłem na pomysł wykorzystania usługi Azure Automation, w której możemy cyklicznie wykonywać skrypt usuwający zasoby. Rozwiązanie nie idealne ale na moje potrzeby wystarczające. 🙂


Mała uwaga - poniższy skrypt usuwa wszystkie zasoby w danej subskrypcji, więc chcąc go wykorzystać zabezpiecz obecne zasoby lub utwórz nową subskrypcję ☁


Jak się do tego zabrać?

1. Na początku warto utworzyć osobną subskrypcję i nazwać ją sandbox. Dzięki temu w łatwy sposób poznamy przeznaczenie subskrypcji. Oczywiście krok ten nie jest wymagany.

2. Tworzymy grupę zasobów rg-automation przeznaczoną tylko dla usługi Azure Automation.

azure sandbox subscription create resource group

3. W utworzonej grupie tworzymy Azure Automation Account.

azure sandbox subscription add automation account

4. Po utworzeniu usługi tworzymy runbook z naszym skryptem

azure sandbox subscription automation runbooks list

Warto tutaj wspomnieć o tym, że runbook powinien być typu PowerShell Workflow, umożliwi to równoległe wykonywanie pętli usuwającej grupy zasobów. Przydatne ze względu na to, że płacimy za czas wykonywania naszego skryptu.

azure sandbox subscription create a runbook

5. Przeklejamy poniższy skrypt, pamiętając aby nazwa workflow zgadzała się z nazwą runbooka.

workflow PeriodicallyDeleteResourceGroups
{
    Write-Output "---------Logging in...---------"
    $connectionName = "AzureRunAsConnection"
    try
    {
        # Get the connection "AzureRunAsConnection "
        $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName        
 
        "Logging in to Azure..."
        Add-AzureRmAccount `
            -ServicePrincipal `
            -TenantId $servicePrincipalConnection.TenantId `
            -ApplicationId $servicePrincipalConnection.ApplicationId `
            -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint 
    }
    catch {
        if (!$servicePrincipalConnection)
        {
            $ErrorMessage = "Connection $connectionName not found."
            throw $ErrorMessage
        } else{
            Write-Error -Message $_.Exception
            throw $_.Exception
        }
    }
    Write-Output "---------Logged in---------"
    Write-Output "---------Starting deleting...---------"
    $azrg = Get-AzureRmResourceGroup
    foreach -parallel ($rg in $azrg) 
    { 
        if($rg.Tags.count -eq 0 -Or ($rg.Tags.count -ne 0 -And $rg.Tags["Locked"] -ne "yes"))
        {
            Write-Output ("Removing resource group: `"" + $rg.ResourceGroupName + "`"")
            Remove-AzureRmResourceGroup -Name $rg.ResourceGroupName -Force
        }
    }
    Write-Output "---------Deleting finished---------"
}

Działanie powyższego skryptu jest proste:

  1. Logowanie/uwierzytelnienie się za pomocą Azure Run As account, które zostało utworzone podczas tworzenia Azure Azutomation.
  2. Pobranie wszystkich resource group.
  3. Usunięcie resource group, które nie posiadają taga Lock:yes.

6. Wstrzymujemy się z uruchomieniem skryptu! Jeśli go uruchomiliśmy 🤦‍♂️ to mamy właśnie okazję zobaczyć samodestrukcję usługi 🤭 oraz jak znikają wszystkie zasoby w subskrypcji. 😈

azure sandbox subscription disappearing resources

7. Dodajemy tag Locked:yes do naszej grupy zasobów.

azure sandbox subscription add locked tag to resource group

Uruchomienie skryptu w tym momencie będzie miało taki sam skutek jak poprzednio, nawet mając ustawiony tag na grupie zasobów. Dzieje się tak dlatego, że uruchamiając skrypt korzystamy z nieaktualnych modułów i warunek sprawdzający tagi nie zadziała prawidłowo.

8. Aktualizujemy moduły zgodnie z dokumentacją. W skrócie pobieramy skrypt z repozytorium na GitHubie, tworzymy nowy Powershell runbook z pobranym skryptem, zapisujemy go i publikujemy. Następnie uruchamiany z wymaganymi argumentami. Aktualizacja modułów może chwilę potrwać.

azure sandbox subscription start update automation modules input parameters

9. Przed uruchomieniem skryptu warto zabezpieczyć obecne zasoby za pomocą mechanizmu Lock resources, o którym pisałem we wcześniejszym poście o zabezpieczaniu zasobów w Azure przed usunięciem.

10. Mając zabezpieczone zasoby oraz zaktualizowane moduły jesteśmy gotowi do uruchomienia skryptu. Polecam się nim teraz pobawić dodając kilka grup zasobów, dodając do nich tagi, Lock resources, a nawet dodając do nich zasoby i przetestować zachowanie skryptu.

11. Pozostało już tylko ustawić harmonogram uruchamiania skryptu.

azure sandbox subscription schedule automation runbook

12. Cieszymy się subskrypcją, która będzie czyszczona według naszego harmonogramu!

Dodatkowo runbook możemy przypiąć do dashboardu, co ułatwi jego uruchamianie po skończonej zabawie z usługami. 🙂


Aktualizacja 02.12.2023

W dniu dzisiejszym zauważyłem, że skrypt przestał działać (utworzona wczoraj resource grupa nadal istniała). Powodem było wygaśnięcie certyfikatu dla app registration z którego korzysta Azure Automation, który był ważny do 1.12.2023.

expired app registration certificate

W związku z czym aby dalej cieszyć się czyszczonym środowiskiem musiałbym utworzyć nowy certifikat. Całe szczęście Azure Automation wspiera Managed Identity, dzięki czemu odpada konieczność generowania nowych certifikatów.

To co należało zrobić to:

1. Przejść do Automation Account > sekcja Account Settings > zakładka Identity i włączyć system assigned managed identity

automation identity

2. Nadać uprawnienia do subskrypcji, klikając w Azure role assignments

automation identity role assignments

3. Zaktualizować skrypt, aby korzystał z Managed Identity. Cały zaktualizowany kod przedstawiam poniżej

workflow PeriodicallyDeleteResourceGroups
{
    Write-Output "---------Logging in...---------"
    try
    {
        "Logging in to Azure..."
        Connect-AzureRMAccount –Identity
        Write-Output "---------Logged in---------"
    }
    catch {
        Write-Error -Message $_.Exception
        throw $_.Exception
    }

    Write-Output "---------Starting deleting...---------"
    $azrg = Get-AzureRmResourceGroup
    foreach -parallel ($rg in $azrg) 
    { 
        if($rg.Tags.count -eq 0 -Or ($rg.Tags.count -ne 0 -And $rg.Tags["Locked"] -ne "yes"))
        {
            Write-Output ("Removing resource group: `"" + $rg.ResourceGroupName + "`"")
            Remove-AzureRmResourceGroup -Name $rg.ResourceGroupName -Force
        }
    }
    Write-Output "---------Deleting finished---------"
}

4. Usunąć stare app registration.


Podsumowanie

W dość prosty sposób przygotowałem subskrypcję do zabaw z usługami, gdzie nie muszę się martwić o pozostawione usługi.
Skrypt ten służy mi już od ponad roku i mogę wyciągnąć kilka wniosków z jego używania:

  • pod koniec dnia nie muszę martwić się o to, że zapomniałem usunąć zasoby
  • mając przypięty runbook do dashboardu wystarczą tylko trzy kliknięcia aby posprzątać subskrypcję
  • mając w głowie myśl, że moje zasoby zostaną usunięte zacząłem częściej myśleć w podejściu IaC (Infrastructure as Code)
  • kilka razy zapomniałem o skrypcie, za co skrypt mi się odwdzięczył, powiadamiając mnie (w dość brutalny sposób) o późnej porze 🤷‍♂️

Rozwiązanie nie jest idealne i zawsze można je ulepszyć (np. wykorzystując Even Grid, aby reagować na dodanie nowych zasobów), jednak na chwilę obecną skrypt ten jest dla mnie w zupełności wystarczający. ☁