FinOps - IaC pipeline cost control

Posted by Nikos Tsirmirakis on 2023-10-03

With a wide cloud adoption and cloud-first approach, every company is using cloud resources now. Due to its nature resources are available on demand and IaC (Infrastructure as a Code) can be provisioned whenever it is required. This flexibility comes with a cost and has to be managed to provide a balance between speed/cost of development and cost-efficient architecture. FinOps is a practice of managing costs in the cloud and introducing finance to the DevOps development cycle. In this post, I will demonstrate how to implement an infrastructure cost check and guardrails with the use of infracost tool. In this scenario, we will implement infracost steps in a build pipeline for the AKS test environment to check environment costs, check cost differences in the pull request and validate costs for the new version of the environment against cost policy.

We will cover the following areas in our test environment (Azure DevOps pipeline and GitHub repository)

  • Registration, installation and run
  • Infracost Variables
  • Cost breakdown
  • Cost difference calculation
  • Cost policy

Sample Azure DevOps pipeline and OPA (Open Policy Agent) cost policy are available in DBAinTheCloud GitHub repository.

Registration, install and run

Before we use infracost we have to register and generate an API key which is required to obtain the latest costs for resources. The entire princess is covered in infracode documentation. Depending on preference we can use a pipeline addon/extension, or download a cli application.

- bash: |
    curl -O -L https://github.com/infracost/infracost/releases/latest/download/infracost-linux-amd64.tar.gz
    tar zxvf infracost-linux-amd64.tar.gz && rm infracost-linux-amd64.tar.gz
    mv infracost-linux-amd64 /usr/local/bin/infracost
    infracost --version
    displayName: Install infracost    

Or use a docker image.

- bash: |
    docker run -e INFRACOST_API_KEY=$INFRACOST_API_KEY \
    -e INFRACOST_CURRENCY=GBP \
    -e INFRACOST_AZURE_OVERRIDE_REGION=westeu \
    --volume $(pwd):/terraform_iac \
    infracost/infracost:ci-latest \
    breakdown \
    --path /terraform_iac \
    --terraform-var-file /terraform_iac/env/dev.tfvars
    env:
        INFRACOST_API_KEY: $(infracost_api_key) 
    workingDirectory: $(System.DefaultWorkingDirectory)/$(folder_name)
    displayName: 'infracost'    

It very much depends on your preference, in this scenario, we will use the docker image to check the environment’s total cost and the CLI application to check a cost difference.

Variables

Infacost uses server environment variables, all list is available here. In our scenario, we will use the following variables:

  • API key (INFRACOST_API_KEY) - it is a required parameter to obtain current cost data and we have to generate it beforehand.
  • Currency (INFRACOST_CURRENCY) - The default currency is USD however if you prefer a different currency you can specify here (we are using GBP in this scenario)
  • Region (INFRACOST_AZURE_OVERRIDE_REGION) - cost may vary between regions and we can use this parameter to specify our preferred location (we are using West EU in this scenario)

Cost breakdown

Cost breakout is a handy feature, it scans out terraform code and calculates our estimated monthly cost for environment and separate components. There are several scan options including scanning a terraform plan output as well, all details can be found in infracost documentation.

Cost difference calculation

Cost difference calculation is beneficial in a DevOps environment where configuration can often change due to agile development. To calculate the difference we need a baseline, we can either use a fixed baseline or a current configuration. In our scenario, we will calculate a difference during the pull request, and compare the costs of configuration from the current (feature) branch and destination branch.

We will use an Azure DevOps feature to clone the repository from both branches to generate a baseline and calculate the cost difference for changes in pull requests.

- bash: |
    infracost diff \
    --path $(current_git_repo_folder)/$(folder_name) \
    --terraform-var-file env/dev.tfvars \
    --compare-to dev-infracost-base.json \
    --format json \
    --out-file dev-infracost.json
    env:
        INFRACOST_API_KEY: $(infracost_api_key)
        INFRACOST_AZURE_OVERRIDE_REGION: westeu
        INFRACOST_CURRENCY: GBP
    workingDirectory: $(System.DefaultWorkingDirectory)
    displayName: infracost - current cost differance (PR branch vs. PR destination branch)    

Cost policy

To automate our DevOps pipeline we configure a cost policy to check the total cost for the environment and validate if it is below our threshold.

package infracost

deny[out] {
	maxTotalMonthlyCost = 450.0

	msg := sprintf(
		"Max total monthly estimated cost must be less than £ %.2f (actual max estimated cost is £ %.2f)",
		[maxTotalMonthlyCost, to_number(input.totalMonthlyCost)],
	)

	out := {
		"msg": msg,
		"failed": to_number(input.totalMonthlyCost) >= maxTotalMonthlyCost,
	}
}

Pull request comment

The pull request comment is a very handy feature allowing you to add all details as a pull request including output of costs policy validation. This step will streamline the pull request review process.

- bash: |
    infracost comment github \
    --path dev-infracost.json \
    --repo=$BUILD_REPOSITORY_NAME \
    --pull-request=$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER \
    --github-token=$GITHUB_TOKEN \
    --policy-path=$(current_git_repo_folder)/$(folder_name)/cost_policy.rego \
    --behavior=new
    env:
        INFRACOST_API_KEY: $(infracost_api_key)
        GITHUB_TOKEN: $(github_token)
    workingDirectory: $(System.DefaultWorkingDirectory)
    displayName: infracost - add PR comment    

Depending on the output of policy validation, the pipeline can be stopped, like in this case when we have exceeded the budget of £ 400.00.

Or allowed, in this scenario, we have to increase the budget to the new threshold to £ 450.00.

Conclusion

Cost management/FinOps is a fundamental part of DevOps, when the dust of excitement of new technology will settle someone will have to pick up the check. It is a good practice for the Head of DevOps or DevOps Lead to establish cost guardrails from the beginning to allow agile development and avoid sour surprises with costs out of control.

Upfront cost estimation is always prone to error especially when we are using resources charged per throughput, it is based on assumption or load test results but but still might not be 100% accurate. In my next post, I will describe how to implement a budget and alerting on resource group. It will allow us to monitor actual costs and alert us when we get close to the budget threshold.

If you need help with implementing FinOps guardrails or you would like to save on your cloud spending get in touch.