This repository is a part of '100 commitów' challenge. I will work on a project for 100 days in this challenge. This project aims to have a simple solution for starting and stopping virtual machines in Azure based on the schedule. The schedule will be defined in resource tags.
We can save money by turning virtual machines on/off in Azure. We can turn off VMs during the night, weekends, or holidays. We can also turn on VMs during working hours. This solution can be helpful for development, testing, and staging environments.
The cost of the Azure environment can be calculated with Azure Pricing Calculator. Of course, the end cost depends on many factors, like networking configuration, storage, etc.
Let's assume that we have a VM with the following configuration:
- VM size: Standard D2 v5, 2 vCPUs, 8 GiB memory, 0 GiB temporary storage
- OS: Windows
- Region: Poland Central
- We are not using Azure Hybrid Benefit
- We are not using Reserved Instances.
Do some calculations:
- Running VM cost: €147.37/month
- If we are running the VM only 10 hours per day, 7 days per week (300h per month), the cost will be: €60.56/month - it means that we can save €86.81/month (58.8%).
- If we are running the VM only 10 hours per day, 5 days per week (220h per month), the cost will be: €44.01/month - it means that we can save €103,36/month (70%).
The solution needs to turn VMs on and off based on schedule within tags on the VM or RG levels. The tag on the RG level means that all VMs in the RG should be turned on/off simultaneously.
- We would avoid administrative overhead. We want to prevent secret management/rotation, manual authentication, etc.
- The solution should be monitored to see if it is working. If any problem will appear, we would like to be notified
- Email notification
- Slack/Discord notification
- A single solution should cover different subscriptions.
- The solution should work in 15-minute time frames.
We have two options to achieve this goal with the existing Azure services:
In the 5th day of the challenge, I have created a diagram of the infrastructure on AzureDiagrams.com. The diagram is available at the following link: VM Start/Stop diagram on AzureDiagrams.com
The tool is handy, but it has some limitations. I will use it for the initial design, but I will use another tool for the final version.
The raw version of the diagram is stored in the file: docs/assets/vm-start-stop.drawio
.
To allow the function app to switch VMs, the service principal (or managed identity as in our case) should have the following permissions:
- Reader,
- Virtual Machine Contributor.
Thanks to this, the function app can read subscriptions, and resource groups, look for VMs, and start/stop them. To avoid the manual assignment of permissions everywhere, it is recommended to use management groups.
It is an excellent practice to have a naming convention for resources. The naming convention can help identify the resource's purpose, environment, owner, etc. It supports proper resource management and cost allocation and helps to determine the resources in the logs, monitor them, etc.
Useful links:
- Define your naming convention
- Naming rules and restrictions for Azure resources
- Develop your naming and tagging strategy for Azure resources
- Azure Naming Tool wiki
- Abbreviation examples for Azure resources
The tool installation can be found in the documentation. The preferred way is to run it as a Docker container.
The global configuration file for the Azure Naming Tool is placed here: src/naming-convention/globalconfig.json
.
The components configuration file for the Azure Naming Tool is placed here: src/naming-convention/componentsconfig.json
.
Deployments into Azure are done with Powershell commands and scripts stored in the src/scripts
folder.
The authorization of the GitHub Actions workflow is done with Identity Federation how it is described in the documentation: Quickstart: Deploy Bicep files by using GitHub Actions
The deployment workflows are using:
- Azure/powershell GitHub Action,
- Azure/login GitHub Action.
The workflow environments were configured separately to deploy resources into the production and development environments. Also, it was required to allow connections based on federated identity from different branches. More info in the documentation Configure a federated identity credential on an app: GitHub Actions (point 4.).
In the workflow, we have also added conditional deployments to ensure that the resources are only deployed to the production environment from the main branch. More info Using conditions to control job execution.
The splatting mechanism is a Powershell syntax that allows you to simplify runs of cmdlets with a significant number of parameters.
Approved Verbs for PowerShell Commands
Role assignments with Bicep are described in the documentation: Create Azure RBAC resources by using Bicep.
The principalId
value is an object ID of the Enterprise Application related to the service principal.
The 'Complete' mode deployment ensures that deployments manage all resources on the resource group level. More information:
- Azure Resource Manager deployment modes
- Using linked and nested templates when deploying Azure resources
- Deletion of Azure resources for complete mode deployments
To check the deployment before the actual deployment, the WhatIf
parameter can be used. More information: ARM template deployment what-if operation
The Architecture Decision Records (ADR) will keep a history of architectural changes. More information about ADR can be found:
- Architecture decision record (ADR) on GitHub,
- Architectural Decision Records: Homepage of the ADR GitHub organization.
Template of ADRs: Decision record template by Michael Nygard.
ACCEPTED
During the function app deployment, there was an error:
Requested features are not supported in region. Please try another region. (Target: /subscriptions/88a99f8e-abc3-4f87-b5d1-6582ecf72501/resourceGroups/eit-vms-plc-dev-rg-1/providers/Microsoft.Web/serverfarms/eit-vms-plc-dev-plan-1)
After checking the Products available by region documentation, it was found that the Azure Functions Consumption plan on Linux is not available in the Poland Central region.
The Azure Functions Consumption plan on Windows will be deployed in the Central region of Poland.
It is expected to double-check if the code that runs on other platforms (like Linux) will work on Windows.
ACCEPTED
During the function app deployment, there was an error:
10:14:29 - The deployment 'vms_20240325101322' failed with error(s).
| Showing 1 out of 1 error(s). Status Message: The subscription has
| reached its limit of 'configurationStores' resources with the 'free'
| SKU. (Code:SkuPerSubscriptionLimitReached) CorrelationId:
| beefdcbe-5a3c-489b-bb14-a9a3795a2673
The error message indicates that the subscription has reached its limit of 'configurationStores' resources with the 'free' SKU. Based on the documentation, each subscription has a limit of 1 free configuration store. The App Configuration service was added to store the configuration data. Source: Which App Configuration tier should I use?.
The App Configuration service in the Standard tier costs about 33 Euros per month.
As the App Configuration service is not required for the current solution and the application's configuration can be handled on the function app level, it will be removed.
The application's configuration have to be handled on the function app level.
The tag can be defined at the subscription, resource group, or VM level. The tag key is the same for all levels.
The tag key is: VM-START-STOP-SCHEDULE
.
The tag value will be created in the following way:
<ON/OFF>;HH:MM-HH:MM;<TIMEZONE>;<MONDAY/TUESDAY/WEDNESDAY/FRIDAY/SATURDAY/SUNDAY/WORKWEEK/WEEK/WEEKEND>
The denominator character is: ';
'.
The first part defines whether the tag should apply to the scope. The value can be ON
or OFF
. If the value is ON
, the tag should be evaluated. If the value is OFF
, the tag should be ignored.
The second part defines the time range. The time range is described in the format HH:MM-HH:MM
. The time is in the 24-hour format. The - character separates the time range. The time range defines when the VM should be turned on.
The third part defines the timezone. The timezone should be defined in the IANA configuration: IANA timezone database. The summertime should be applied automatically.
The fourth part defines the days when the VM should be turned on. The value can be: MONDAY/TUESDAY/WEDNESDAY/FRIDAY/SATURDAY/SUNDAY/WORKWEEK/WEEK/WEEKEND
. The values can be connected by comma, for example: WEEKEND,MONDAY,TUESDAY
.
There is an Easter egg for you. But this is about resting, spending time with your family, and taking care of yourself. If you are thinking about how to be a better developer, engineer, and so on, remember that you need to take care of yourself first. So, take a break, go outside, and enjoy the time with your family and friends.
- Event storming
.gitignore
&.git/info/exclude
- OpenID Connect
- GitHub Actions: About security hardening with OpenID Connect
- Workload identity federation
- Workflow syntax for GitHub Actions
- App Registration vs Enterprise Applications
- Application and service principal objects in Microsoft Entra ID
- Products available by region
- Storage considerations for Azure Functions
- What is an Azure landing zone?
- Multiple function projects
- REST Client VS Code extension
- Quickstart: Create a C# function in Azure from the command line
- Azure Funcitions: Continuous delivery by using GitHub Actions
- Azure SDK for .NET overview
- Azure Functions - Part 2 - Unit and Integration Testing
- Unit testing C# with NUnit and .NET Core
- Guide for running C# Azure Functions in the isolated worker model
- Names of Classes, Structs, and Interfaces
- Testing with C# Dev Kit
- Design the infrastructure persistence layer
- Pagination with the Azure SDK for .NET
- VirtualMachineCollection.GetAll Method
- Services in DDD finally explained
- MediatR
- Configuration in .NET
- String interpolation in .NET
- Overview of Service Bus dead-letter queues
- Make HTTP requests with the HttpClient class
- Azure Service Bus trigger for Azure Functions
- Use Key Vault references as app settings in Azure App Service and Azure Functions
- GitHub Action: Variables
- Parameters file function for Bicep
- TimeOnly.CompareTo Method
- Parse date and time strings in .NET
- How to use the DateOnly and TimeOnly structures
- NUnit: TestCase
- Selection statements - if, if-else, and switch
- Using type dynamic
- Quickstart: Send and receive messages from an Azure Service Bus queue (.NET)
- Azure Functions HTTP trigger
- out (C# Reference)
- Guide for running C# Azure Functions in the isolated worker model
- The time zone ID 'Pacific Standard Time' was not found on the local computer
- TimeZoneConverter
- C# DateTime Conversion With Specific TimeZone
- IANA timezones
- Time Zone Conversion APIs
- .NET globalization and ICU
- Espanso - Text expander