Heute geht es um’s Geld… Naja, darum Geld zu sparen.
Azure bietet „out of the Box“ die Option, VMs zu gewissen Zeitpunkten herunterzufahren. Es gibt sogar die Option mittels Automation Accounts die VMs auch zu gewissen Zeiten zu starten. Jedoch entweder pro ResourceGroup oder auf die ganze Subscription. Ich brauchte hier mehr Dynamik in Form eines „Self-Services“. Wie funktioniert es?
Der Contributor der die Verantwortung über eine VM hat, kann in den Tags der VM div. Attribute setzen. Diese definieren den Status der VM:
- Immer AN
- Immer AUS
- Automatisch zu gewissen Zeiten und Tagen starten oder stoppen
Die Optionen hierfür sind:
- Beliebige Tage (Mon,Tue,Wed,Thu,Fri,Sat,Sun)
- Start Zeit (24h-Format)
- Stopp Zeit (24h-Format)
- UTC Offset der Zeitzone (mit Plus oder Minus davor, ggf. 0)
Dafür brauchen wir:
- Einen Azure Automation Account mit Run As Account
- Das Skript in diesem Post
- 10 Minuten Zeit
- Kollegen die ihre Tags pflegen…
Legen wir also los!
1. Azure Automation Account inkl. Run As Account erstellen
Ohne Account geht es nicht. Es kann natürlich auch ein vorhandener benutzt werden.
Da wir im Skript dieses Module brauchen, bitte sicherstellen, dass es verfügbar ist.
2. Sicherstellen, dass die Azure Module verfügbar sind
3. Alles einmal updaten
In diesem Beispiel ist es ein neuer Account der für noch nichts anderes genutzt wird. Darum kann ich hier problemlos updaten.
Solltet ihr einen vorhandenen Account nehmen, bitte vorischt! Nicht, dass ihr in anderen Runbooks legacy Module braucht und ihr euch was kaputt macht.
4. Beispiel Runbooks löschen
Ich bin kein Fan von Müll. Weg mit den Beispielen.
Das mit allen Runbooks machen
5. Neues PowerShell Runbook erstellen
6. Skript einfügen, speichern und publishen
# Retrieve constants from Automation Account's variables $connectionName = Get-AutomationVariable connectionName $DefaultStartTime = Get-AutomationVariable DefaultStartTime $DefaultStopTime = Get-AutomationVariable DefaultStopTime $DefaultUtcOffset = Get-AutomationVariable DefaultUtcOffset $DefaultOnlineDays = Get-AutomationVariable DefaultOnlineDays function OnlineToday ($OnlineDays){ if ($OnlineDays){ $TodayValue = $CurrentDateUTC0.DayOfWeek.value__ $OnlineDays = $OnlineDays.Replace("Mon","1") $OnlineDays = $OnlineDays.Replace("Tue","2") $OnlineDays = $OnlineDays.Replace("Wed","3") $OnlineDays = $OnlineDays.Replace("Thu","4") $OnlineDays = $OnlineDays.Replace("Fri","5") $OnlineDays = $OnlineDays.Replace("Sat","6") $OnlineDays = $OnlineDays.Replace("Sun","7") if ($OnlineDays -like "*$TodayValue*"){ return $true } else{ return $false } } else{ return $true } } function OnlineNow ($OnlineStartUTC0, $OnlineStopUTC0){ if (($CurrentDateUTC0 -gt $OnlineStartUTC0) -and ($CurrentDateUTC0 -lt $OnlineStopUTC0)){ # VM should be online return $true } else{ # VM shoould be offline return $false } } function StartAzureVM ($VM){ if (!($VM.PowerState -eq "VM running")){ Write-Output "VM is starting." $VM | Start-AzureRmVM -AsJob } else{ Write-Output "VM is already running." } } function StopAzureVM ($VM){ if (!($VM.PowerState -eq "VM deallocated")){ Write-Output "VM shutting down." $VM | Stop-AzureRmVM -AsJob -Force } else{ Write-Output "VM is already deallocated." } } function TriggerAutoStartStop ($VM){ if(!$VM.Tags.StartTime){ $StartTime = $DefaultStartTime } else{ $StartTime = $VM.Tags.StartTime } if(!$VM.Tags.StopTime){ $StopTime = $DefaultStopTime } else{ $StopTime = $VM.Tags.StopTime } if(!$VM.Tags.UtcOffset){ $UtcOffset = $DefaultUtcOffset } else{ $UtcOffset = $VM.Tags.UtcOffset } if(!$VM.Tags.OnlineDays){ $OnlineDays = $DefaultOnlineDays } else{ $OnlineDays = $VM.Tags.OnlineDays } $OnlineStartUTC0 = (Get-Date $StartTime).AddHours(-$UtcOffset) $OnlineStopUTC0 = (Get-Date $StopTime).AddHours(-$UtcOffset) $CurrentDateUTC0 = (Get-Date).AddHours(-(Get-TimeZone).BaseUtcOffset.Hours) Write-Output "VM: $($VM.Name)" Write-Output "Auto online/offline desired." Write-Output "OnlineDays: $OnlineDays" Write-Output "OnlineStartUTC0: $OnlineStartUTC0" Write-Output "OnlineStopUTC0: $OnlineStopUTC0" Write-Output "CurrentDateUTC0: $CurrentDateUTC0" if (OnlineToday -OnlineDays $OnlineDays){ Write-Output "Not generally offline today. Now checking if it is in an offline or online timeframe." if (OnlineNow -OnlineStartUTC0 $OnlineStartUTC0 -OnlineStopUTC0 $OnlineStopUTC0){ Write-Output "VM needed right now." Write-Output "Starting VM if not already running." StartAzureVM -VM $VM } else{ Write-Output "VM not needed. Shut it down." Write-Output "Shutting VM down if not already shut down." StopAzureVM -VM $VM } } else{ Write-Output "Shut down VM. No need to have it for the whole." Write-Output "Shutting VM down if not already shut down." StopAzureVM -VM $VM } } function ProcessVM ($VM){ $AutoStartStop = $VM.Tags.AutoStartStop if($VM.Tags.AutoStartStop -eq "Auto"){ TriggerAutoStartStop -VM $VM } elseif($VM.Tags.AutoStartStop -eq "Online"){ Write-Output "VM should ALWAYS be running." Write-Output "Maybe check for reserved instances...?" } elseif($VM.Tags.AutoStartStop -eq "Offline"){ Write-Output "VM should be OFFLINE." Write-Output "Maybe check for reserved instances...?" StopAzureVM -VM $VM }else{ Write-Output "- Nothing -" # Maybe enforce it one day... # --> TriggerAutoStartStop with default values # Simply change the first elseif to if # and the original if to this else. } } function Login { 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 } } } function StartCycle{ # All VMs Login $AllVMs = Get-AzureRmVM -Status foreach ($VM in $AllVMs){ Write-Output "Processing $($VM.Name)" ProcessVM -VM $VM Write-Output "----------------------" Write-Output "" } } StartCycle exit
7. Connection Name kopieren
Den brauchen wir gleich in den Variablen.
8. Variablen extern füllen
Im Skript verweise ich auf ein paar Konstanten, welche ich lieber extern, als im Skript habe. Diese sind im Blade für die Variablen definiert. Die Namen sind entweder 1 zu 1 zu übernehmen, oder im Kopf des Skripts entsprechend anzupassen.
Alle Werte wie hier zur sehen eintragen.
9. Zeitplan erstellen und mit Runbook verknüpfen
Um das Ganze zu automatisieren, braucht es einen Trigger. Ich habe mich für alle 15 Minuten entschieden. Bitte für eure Bedarfe anpassen.
In meinem Fall, 4 mal wiederholen. Anschließend verknpüfen.
Entsprechend alle schedules verknüpfen.
10. Tags auf den VMs pflegen
Das wichtigste Tag ist „AutoStartStop“! Folgende Werte werden ausgewertet:
- Auto – VM wir nach vorgegebenem Schema automatisch herunter- oder hochgefahren.
- Online – VM soll immer online sein. Hier würden reserved Instances Sinn machen…
- Offline – VM soll immer offline sein. Hier machen reserved Instances natürlich keinen Sinn.
Ist das Feld nicht gepflegt, passiert NICHTS! Keine Angst 🙂
Sollte nun also „Auto“ eingetragen sein, kann man folgende Felder zusätzlich definieren:
- StartTime – Ab welcher Uhrzeit soll die VM an sein. ACHTUNG: Nur 24h-Format!
- StopTime – Ab welcher Uhrzeit soll die VM aus sein. ACHTUNG: Nur 24h-Format!
- UtcOffset – Über welche Utc Zeitzone reden wir? Hier wird sichergestellt, dass es im global Kontext nicht zu verwirrungen kommt.
- OnlineDays – An welchen Tagen ist die VM an. Wenn ein Tag nicht mit drin ist, ist die VM an diesem Tag pauschal aus!
Sollte nicht definiert sein, wird der Default-Wert aus den Variablen des Automation Accounts genommen.
11. Testen
Nun sollte man auch mal schauen ob das Ganze so funktioniert. Dafür einmal das Runbook editieren und testen.
Ändern wir die Werte in den Tags, ist das Verhalten auch ein anderes.
Abschließende Worte
Das Skript hat den Nachteil, dass es nicht über einen Tag hinweg agieren kann. Es kann auch nicht mehrer Trigger über einen Tag haben. Also Freitag 20:00 bis Samstag 04:00 geht zum Beispiel nicht (, außer über Tricks mit dem UtcOffset). Oder Mon-Fri 08:00 – 17:00, Sa-So aber von 08:00 – 12:00.
Aber ganz ehrlich… Ich will erstmal sehen welcher unserer Fachbereiche das überhaupt nutzt. Und ein paar hundert mal 50 Euro statt 80 Euro die Woche gespart, ist besser als gar nichts gespart.