To już 3 post o tym, jak zrobić porządek w swojej subskrypcji. Na tym na chwilę poprzestaniemy. Nie możemy przecież ciągle czegoś kasować i oszczędzać. Czas na coś twórczego. Dlatego kolejny wpis będzie o przygodach z jeszcze jedną usługą PaaS.
Część z obiektów, które zaraz sobie usuniemy nic nas nie kosztuje. Ale… moim zdaniem pozostawianie śmieci wprowadza bałagan, które z czasem naprawdę przeszkadza oraz zwiększa ryzyko.
Zanim jednak do twórczych rzeczy, dziś w agendzie:
- wywalimy pozostawione Load Balancer’y
- usuniemy nie wykorzystywane Virtual Network Gateway (czyli to, czego używasz to Site2Site VPN, Point2Site VPN i Vnet2Vnet)
- pozbędziemy się serwerów SQL, które zawierają tylko jedną bazę danych (czyli master ;))
- sprawdzimy wszystkie konta Azure Cosmos DB i Azure Search
- usuniemy nieużywane CDN, zarówno te od Akamai jak i Verizon
- i na koniec perełka. Usuniemy wszystkie konta, które nie mają żadnych uprawnień do naszej subskrypcji, nie są Administratorami (wszystkie przypadki) i nie są tzw. Service Principal
Pamiętaj! Usuwasz na własną odpowiedzialność. Jeśli znajdziesz błędy, daj znać!
Load Balancer
#Get not attached Load Balancers $lbs=Get-AzureRmLoadBalancer | where {[string]::IsNullOrWhitespace($_.LoadBalancingRules) -OR [string]::IsNullOrWhitespace($_.Probes)} foreach ($lb in $lbs) { Write-Host $lb.Name 'will be deleted!' #Remove-AzureRmLoadBalancer -Name $lb.Name -ResourceGroupName $lb.ResourceGroupName -Force }
Pusty Load Balancer to taki, który nie ma żadnych reguł „rozrzucania ruchu” lub też nie ma żadnej metody do sprawdzania, czy podłączone „końcówki” żyją.
Virtual Network Gateway
#Get virtual network gateways $ngs=Find-AzureRmResource -ResourceType "Microsoft.Network/virtualNetworkGateways" foreach ($ng in $ngs) { $vng=Get-AzureRmVirtualNetworkGateway -ResourceGroupName $ng.ResourceGroupName $connections=Get-AzureRmVirtualNetworkGatewayConnection -ResourceGroupName $ng.ResourceGroupName if ($connections) { Write-Host $vng.Name 'will be deleted!' #Remove-AzureRmVirtualNetworkGateway -ResourceGroupName $vng.ResourceGroupName -Name $vng.Name -Force } }
Tej części kodu jestem najmniej pewny. Dla mnie pusty VirtualNetworkGateway to taki, który nie ma żadnego połączenia. Połączenie jednak nie zawiera relacje do Gateway i sprawa się komplikuje. Założyłem :), że jedno i drugie są w tej samej ResourceGroup i że taka bramka jest tylko jedna. W przyszłości do poprawy, dla mnie na dziś wystarczy.
Azure SQL Servers oraz SQL Databases
#Get Azure SQL Servers with no DB's $servers=Find-AzureRmResource -ResourceType "Microsoft.Sql/servers" foreach ($s in $servers) { $databases=Get-AzureRmSqlDatabase -ServerName $s.Name -ResourceGroupName $s.ResourceGroupName #every server contains master db if ($databases.Count -eq 1) { Write-Host $s.Name 'will be deleted!' #Remove-AzureRmResource -ResourceName $s.Name -ResourceGroupName $s.ResourceGroupName -Force } }
Koncepcja jest prosta. Sprawdzamy wszystkie serwery SQL i sprawdzamy, gdzie ilość baz wynosi 1. Należałoby jeszcze zrobić listę baz, których wielkość wskazuje na to, że są puste. Jest to bardzo proste, obiekt bazy ma parametr Size wyrażony w KB.
Azure Cosmos DB oraz Azure Search
Te dwa serwisy zostaną potraktowane razem, bo żaden z nich niestety nie ma swoich, dedykowanych poleceń w PowerShell. Natomiast to co można zrobić, to zobaczyć wszystkie usługi jakie mamy dla obu typów. Co więcej, jeśli z portalu pobierzesz sobie klucz API (jak j niżej) i wykorzystasz REST API, to możesz sprawdzić np. liczbę indexów w Azure Search czy liczbę baz lub kolekcji w Cosmos DB. Jeśli nie lubisz REST API, polecam SDK. Możesz też wykonać te polecenia, są super opisane w dokumentacji
#Get Cosmos DB accounts with no DB's created $docdbaccounts=Find-AzureRmResource -ResourceType "Microsoft.DocumentDb/databaseAccounts" #Get Azure Search with no collections $searchServices=Find-AzureRmResource -ResourceType "Microsoft.Search/searchServices" <# #with RestApi you can get the list of indexes for particular search service and then decide what to do $ResourceCard='https://mifurmsr.search.windows.net/indexes?api-version=2016-09-01' $ResHeaders = @{‘api-key’ = 'D0271A3D3A33B975CAF10892C17FA402'} $response=Invoke-RestMethod -Uri $ResourceCard -Headers $ResHeaders -ContentType ‘application/json’ #>
Azure CDN’s
Tutaj wprowadzenie jest zbędne. Pobieramy wszystkie profile i sprawdzamy, które z nich nie mają żadnych „endpointów”. Jeśli nie ma „końcówek” to CDN nie jest potrzebny.
#Get CDNs $cdns=Get-AzureRmCdnProfile foreach ($cdn in $cdns) { $endpoints=Get-AzureRmCdnEndpoint -ProfileName $cdn.Name -ResourceGroupName $cdn.ResourceGroupName if ($endpoints.Count -eq 0) { Write-Host $cdn.Name 'will be deleted.' #Remove-AzureRmCdnProfile -ProfileName $cdn.Name -ResourceGroupName $cdn.ResourceGroupName -Force } }
I na koniec najtrudniejsze. Proszę Państwa, Azure Active Directory.
Co robimy skrypt:
- pobiera użytkowników metodą Get-AzureRmAdUser
- pobiera wszystkich administratorów w ramach subskrypcji
- sprawdza następnie czy użytkownik aby nie jest adminem (i tych oszczędza) i czy ma jakieś przypisane uprawnienia do zasobów. Jeśli przypisań brak, użytkownik jest usuwany.
- Jeśli ma jednak jakieś prawa do zasobów, sprawdzamy czy to przypisanie występuje w subskrypcji, do której jesteśmy zalogowani. Stąd <SUBSCRIPTION ID> w kodzie poniżej. Jeśli są jakieś prawa, warto je wypisać i zobaczyć, czy faktycznie nadal są potrzebne.
#Get Active Directory accounts with no rights to subscrption $users=Get-AzureRmADUser $admins=Get-AzureRmRoleAssignment -IncludeClassicAdministrators | Where { ($_.RoleDefinitionName -like '*Administrator*')} | Select-Object -ExpandProperty SignInName foreach ($user in $users) { if ($user.UserPrincipalName) { if (($admins -inotcontains $user.UserPrincipalName) -and ($user.DisplayName -notlike '*On-Premises Directory Synchronization*')) { $assignment=Get-AzureRmRoleAssignment -SignInName $user.UserPrincipalName -ExpandPrincipalGroups if (!$assignment) { Write-Host $user.DisplayName $user.UserPrincipalName 'will be deleted!' #Remove-AzureRmADUser -UPNOrObjectId $user.UserPrincipalName -Force } else { $t=($assignment.Scope.Split('/'))[2] if ($t -eq '&lt;SUBSCRIPTION ID&gt;') { Write-Host $user.DisplayName $user.UserPrincipalName 'has some scopes!' Get-AzureRmRoleAssignment -SignInName $user.UserPrincipalName -ExpandPrincipalGroups | FL SignInName, RoleDefinitionName, Scope } } } } }
Kod przetestowany na dwóch subskrypcjach. Ale… testują go dla Ciebie dalej. Od dziś będzie chodził każdego wieczora w Azure Automation i usuwał moje radosne i nieudane próby. Jeśli Ty ma jakiś swój ulubiony skrypt, który skutecznie usuwa śmieci, daj znać!