Skip to main content

How to calculate the cost of CI/CD on top of Azure VSTS

In this post, we tackle the cost estimation of a CI/CD system that is hosted on top of Visual Studio Team Services (VSTS).

We are starting from an on-premises system that has a CI/CD system build on top of Jenkins, which deploys the web application on a local IIS. Beside web application, we have for each project an SQL Server Database that can reach up to 100GB. The source control repository is GitHub, and for task management and tracking, we have JIRA.
In the current environment, we have around 20 projects that need such a migration, but the cost estimation is done project base, allowing us to have a better forecast for each project. We don’t take into consideration any other dependencies that a specific project could have like Windows Services.
Most of the client is already using Azure to host their production environments for their web application. Going into the cloud is more than acceptable for them. Beside this remember that you have the code inside GitHub.
In some cases, we are required to keep the output of the build for 6-12 months. It needs to be taken into consideration because inside VSTS the build output is deleted after a specific period.

There are two main drivers that around this migration. The first one is related to on-premises hardware that needs to be upgraded. When you vision for the next 3-5 years is to be a cloud-first company you need to decide if you want to invest in new hardware of you externalize it.
The second driver is related to security and privacy. Clients require dedicated sandboxes where the CI/CD pipelines shall run and isolated testing environment. It is hard and expensive to offer using on-premises systems.
These two drivers make you look at a cloud offer, where you have already configured everything, you pay based on your need. Maintenance cost is lower, and you can grow or shrink depending on how well the business goes.

In the scope of the current cost estimation, we have only the CI/CD system and the environment where we deploy our web applications. The rest of the systems (GitHub and JIRA) are hosted outside the organization, and we don’t want to do any changes related to it.

High-level overview
The following Azure Services are required to be able to achieve our mission:

1. Build Machine
For the build machine, the initial phase is to with Microsoft-hosted CI/CD, where you can't change the tier, and you are running in a ‘shared resource pool’ if we can call in this way. The advantage is the cost per month that is flat and affordable (40$). When necessary, Self-Hosted CI/CD is a good option, but only when are required. You don’t want to manage the build agent by yourself.
Each project shall have it’s own Microsoft-hosted CI/CD, giving the flexibility to run their builds when necessary, without waiting for others. It enables us to be isolated complexity the access to each CI/CD per team and not per organization.

2. Website hosting
For web application hosting, there are two interesting locations where we could do this. The first one is inside Azure Web Apps, where we can deploy the web application. There is full control at the project level how we want to configure and setup.
Another approach would be Azure DevTest that support Web Apps. Even if you have more granular access, the lack of an Azure SQL Database option makes us, for now, to look at Web Apps and Azure SQL Database. We don’t want to add extra complexity by using multiple types of resources. In the next section, we will talk more about it.

3. DB Storage
The most appealing one at this moment is Azure SQL Database, where S0 tier is more than enough. There are some cases when Basic tier works as well, but this is more a cost optimization, and I don’t want to tackle this subject for now.
There is full control of the DB, with a small risk that there might be some features that are not supported inside Azure SQL Database.
Azure DevTest is another option, that is appealing. It’s a perfect location where we can run the DBs that are not compatible with Azure SQL Database. The main downside of it is the price, that is more expensive. Yes, even if you have multiple SQL Databases inside SQL Server Web Virtual Machines, it’s not appealing enough. We want to have a sandbox for each project and avoid resource sharing.
On long-run Azure DevTest will become the best place for this scenario, but for now, Azure SQL Databases and Azure Web Apps are the best options for the context presented at the beginning of the article. For now, another advantage is the cost monitoring, that can be made from only one location. If we combined Azure DevTest Labs with other resources, it would become a little more complex.

4. Artifacts History
To be able to store build output for a long period, the most simple solution is Azure Blob Storage, that can be used to store and archive any artifact. With the policy capability that is allowing us to delete automatically content after a specific period, gives us the perfect place to store the build output.
Remark: Once Azure DevTest Labs will have support for PaaS (more specific Azure SQL Database), the migration to Azure DevTest will be smooth and fast. From this perspective, it is just a matter of time.

Cost Inputs
To be able to do a cost estimation as close a possible to reality we need to collect the right data that can affect our monthly cost. The things that I identify and I consider that is relevant in this discussion are the following:

  • Average build duration
  • Number of builds per day
  • Build output size
  • Build storage duration
  • Database size
  • Web app complexity

Each of this item needs to be considered for each environment type of pipeline.

Cost calculation
To simplify the calculation, we are doing the cost calculation only for the development pipeline that deploys the solution to a development environment (Web Application with an Azure SQL Database). The total cost per month of a CI/CD that includes the Development environment would be 63$. If we remove the development environment and focus only on the CI/CD cost, the cost per month would be around 41$ per month. The difference is coming from the Web App instance and Azure SQL Database instance. 
The following requirements were used to calculate the cost:
with the following requirements:

  • Average build duration: 5 minutes
  • Number of builds per day: 50 builds
  • Build output size: 200 MB
  • Build storage duration: 90 days
  • Database size: 5 GB
  • Web app complexity: Low (Web App with Basic tier)

The total number of build minutes per month is around 9.000; this means that we can have a dedicated concurrent job, which cost 40$ per month.
You can easily add 2-3 additional environments or create multiple pipelines when needed. You can even share the same Microsoft-hosted CI/CD job cross pipelines as long as are under the same VSTS project.
It means that if you have 4-5 projects hosted inside GitHub, you could have the same job shared between them and dedicated pipelines for each of them. In this way, you could drastically reduce the cost.

As you can see, the cost per month of CI/CD inside Microsoft Azure it’s more than acceptable. Based on what you want to achieve, the price can go up or down. One of the most important capabilities that Azure is offering around it is the possibility to extend and add complexity to your system as much as you need.


Popular posts from this blog

ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded

Today blog post will be started with the following error when running DB tests on the CI machine:
threw exception: System.InvalidOperationException: The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer' registered in the application config file for the ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded. Make sure that the assembly-qualified name is used and that the assembly is available to the running application. See for more information. at System.Data.Entity.Infrastructure.DependencyResolution.ProviderServicesFactory.GetInstance(String providerTypeName, String providerInvariantName) This error happened only on the Continuous Integration machine. On the devs machines, everything has fine. The classic problem – on my machine it’s working. The CI has the following configuration:

TeamCity.NET 4.51EF 6.0.2VS2013
It seems that there …

Entity Framework (EF) TransactionScope vs Database.BeginTransaction

In today blog post we will talk a little about a new feature that is available on EF6+ related to Transactions.
Until now, when we had to use transaction we used ‘TransactionScope’. It works great and I would say that is something that is now in our blood.
using (var scope = new TransactionScope(TransactionScopeOption.Required)) { using (SqlConnection conn = new SqlConnection("...")) { conn.Open(); SqlCommand sqlCommand = new SqlCommand(); sqlCommand.Connection = conn; sqlCommand.CommandText = ... sqlCommand.ExecuteNonQuery(); ... } scope.Complete(); } Starting with EF6.0 we have a new way to work with transactions. The new approach is based on Database.BeginTransaction(), Database.Rollback(), Database.Commit(). Yes, no more TransactionScope.
In the followi…

GET call of REST API that contains '/'-slash character in the value of a parameter

Let’s assume that we have the following scenario: I have a public HTTP endpoint and I need to post some content using GET command. One of the parameters contains special characters like “\” and “/”. If the endpoint is an ApiController than you may have problems if you encode the parameter using the http encoder.
using (var httpClient = new HttpClient()) { httpClient.BaseAddress = baseUrl; Task<HttpResponseMessage> response = httpClient.GetAsync(string.Format("api/foo/{0}", "qwert/qwerqwer"))); response.Wait(); response.Result.EnsureSuccessStatusCode(); } One possible solution would be to encode the query parameter using UrlTokenEncode method of HttpServerUtility class and GetBytes method ofUTF8. In this way you would get the array of bytes of the parameter and encode them as a url token.
The following code show to you how you could write the encode and decode methods.