📜 ⬆️ ⬇️

How to discover a million dollars in your AWS account

We recently talked about ways to save more than a million dollars on AWS annual maintenance . Although we talked in detail about various problems and solutions, the most popular question was still: “I know that I spend too much on AWS, but how can we really break up these expenses into understandable parts?”

At first glance, the problem seems rather simple.

You can easily split your AWS expenses by month and finish on it. Ten thousand dollars for EC2, one thousand for S3, five hundred dollars for network traffic, etc. But there is something important missing - the combination of which particular products and development groups accounts for the lion's share of the costs.
')
And note that you can change hundreds of instances and millions of containers. Soon, what at first seemed like a simple analytical problem becomes unimaginably complex.

In this continuation of the article, we would like to share information about the toolkit that we use ourselves. We hope to be able to come up with a few ideas on how to analyze your costs with AWS, regardless of whether you have a couple of instances or tens of thousands.

Grouping by "product lines"


If you are doing extensive operations on AWS, you probably have two problems already.

Firstly, it is difficult to notice if suddenly one of the development groups suddenly increases its budget.

Our AWS bill exceeds $ 100K per month, and the cost of each AWS component changes rapidly. In each specific week, we can roll out five new services, optimize the performance of DynamoDB and connect hundreds of new customers. In such a situation, it is easy to miss the attention that some one team spent on EC2 by $ 20,000 more than last month.

Secondly, it is difficult to predict how much the cost of servicing new customers will cost.

For clarity, our company Segment offers a single API that sends analytical data to any third-party tools, data warehouses, S3 or internal information systems of companies.

Although customers quite accurately predict how much traffic they will need and what products they prefer to use, but we constantly have problems with translating such a forecast into a specific dollar amount. Ideally, we would like to say: “1 million new API calls will cost us $ X, so we need to make sure we take at least $ Y from the client”.

The solution to this problem for us was the division of infrastructure into what we call “product lines”. In our case, these directions are vaguely formulated as follows:

  1. Integration (code that sends data from Segment to various analytics providers).
  2. API (a service that receives data in the Segment from client libraries).
  3. Data warehouses (a pipeline that loads Segment data into a user-defined data store ).
  4. Website and CDN.
  5. Internal systems (common logic and support systems for all of the above).

Analyzing the whole project, we came to the conclusion that it is almost impossible to measure everything . So instead, we set the task of tracking part of the costs in the account, say, 80%, and try to track these costs from beginning to end.

It is more useful for businesses to analyze 80% of the account than to aim at 100%, get bogged down at the data collection stage and never produce any result. Reaching 80% of the costs (the willingness to say “this is enough”) saved us again and again from futile data picking that does not give a dollar of savings.

Collection, then analysis


To split the costs by product, you need to download data for the billing system, that is, collect and subsequently combine the following data:

  1. AWS billing information in CSV format is a CSV that generates AWS with all cost items.
  2. AWS marked resources are resources that can be marked in billing CSV.
  3. Unlabeled resources are services like EBS and ECS, which need special data conveyors for marking resource consumption by “product lines”.

When we have determined the product directions for all this data, we can upload them for analysis to Redshift.

1. Billing CSV from AWS


Analyzing your costs starts with parsing a CSV file from AWS. You can activate the corresponding option on the billing portal - and Amazon will save a CSV file with detailed billing information every day to S3.

By detailed, I mean VERY detailed. Here is a typical line in the report:

  record_type |  LineItem
 record_id |  60280491644996957290021401
 product_name |  Amazon DynamoDB
 rate_id |  0123456
 subscription_id |  0123456
 pricing_plan_id |  0123456
 usage_type |  USW2-TimedStorage-ByteHrs
 operation |  StandardStorage
 availability_zone |  us-west-2
 reserved_instance |  N
 item_description |  $ 0.25 per free-of-month
 usage_start_date |  2017-02-07 03:00:00
 usage_end_date |  2017-02-07 04:00:00
 usage_quantity |  6e-08
 blended_rate |  0.24952229400
 blended_cost |  0.00000001000
 unblended_rate |  0.25000000000
 unblended_cost |  0.00000001000
 resource_id |  arn: aws: dynamodb: us-west-2: 012345: table / a-table
 statement_month |  2017-02-01 

This is due to an impressive amount of $ 0.00000001, that is, one millionth cent, for storage in the DynamoDB database of a single table on February 7 between 3:00 and 4:00 o'clock at night. On a typical day, our CSV contains approximately six million such records. ( Unfortunately, most of them are with larger amounts than one millionth cent .)

To transfer data from S3 to Redshift we use the awsdetailedbilling tool from Heroku. This is a good start, but we didn’t have a normal way to link specific AWS costs to our product lines (that is, whether a specific instance time was used for integrations or for data warehouses).

Moreover, approximately 60% of the cost comes from EC2. Although this is the lion's share of costs, it is absolutely impossible to understand the connection between EC2 instances and specific product lines only from the CSV that AWS generates.

There is an important reason why we couldn’t identify product lines simply by instance name. The fact is that instead of running one process at a host, we use ECS (Elastic Container Service) intensively to place hundreds of containers on a host and use resources much more intensively.



Unfortunately, Amazon accounts only contain EC2 instance costs, so we did not have any information about the costs of containers running on the instance: how many containers were running at the usual time, how much of the pool we used, how many CPU and memory units we used.

Worse, nowhere in the CSV is information about container autoscaling reflected. To obtain this data for analysis, I had to write my own toolkit for collecting information. In the following chapters, I will explain in more detail how this pipeline works.

However, the AWS CSV file provides very good detailed service usage data, which became the basis for the analysis. You just need to connect them with our grocery lines.

Note: This problem is also not going anywhere. Instance-hours billing will become more and more of a concern in terms of “What do I spend my money on?” Because more and more companies are launching a bunch of containers on a large number of instances using systems like ECS, Kubernetes and Mesos. There is some irony in the fact that Amazon itself has been experiencing the exact same problem for many years, because each EC2 instance is a Xen hypervisor that works in conjunction with other instances on the same physical server.

2. Cost data from AWS marked resources


The most important and ready-to-process data comes from AWS “tagged” resources.

By default, billing CSV does not contain any tags. Therefore, it is impossible to distinguish how one EC2 instance or bake behaves compared to another.

However, you can activate some labels that will appear next to your expenses for each unit using the cost allocation tags .

These tags are officially supported by many AWS resources, S3 buckets, DynamoDB tables, etc. To display the cost allocation tags in CSV, you can enable the corresponding parameter in the AWS billing console. After a day or so, the tag you selected (we selected product_area ) will begin to appear as a new column next to the corresponding resources in the detailed CSV report.

If you’ve not done any more optimizations, you can immediately start using the cost allocation tags for marking up your infrastructure. This is essentially a “free” service and does not require any infrastructure to operate.

After activating the function, we had two tasks: 1) marking the entire existing infrastructure; 2) checking that all new resources will be automatically tagged.

Layout of existing infrastructure


Tagging for existing infrastructure is pretty easy: for each specific AWS product, you request a list of the most expensive resources in Redshift - and slacken people in Slack until they tell you how to mark these resources. Finish the procedure when tagging 90% or more of the resources by value.

However, to ensure that new resources are flagged, some automation and tools are required.

For this we use Terraform . In most cases, the Terraform configuration supports adding the same cost allocation tags that are added through the AWS console. Here is an example Terraform configuration for S3 Bucket:

 resource "aws_s3_bucket" "staging_table" { bucket = "segment-tasks-to-redshift-staging-tables-prod" tags { product_area = "data-analysis" # this tag is what shows up in the billing CSV } } 

Although Terraform provides a basic configuration, we wanted to make sure that every time a new aws_s3_bucket resource is aws_s3_bucket to the Terraform file, the product_area tag is affixed.

Fortunately, Terraform configurations are written in HCL (Hashicorp Configuration Language), which has a configuration parser with comments saved. So we wrote a validation function that runs through all Terraform files and searches for markup resources without the product_area tag.

 func checkItemHasTag(item *ast.ObjectItem, resources map[string]bool) error { // looking for "resource" "aws_s3_bucket" or similar t := findS3BucketDeclaration(item) tags, ok := hclchecker.GetNodeForKey(t.List, "tags") if !ok { return fmt.Errorf("aws_s3_bucket resource has no tags", resource) } t2, ok := tags.(*ast.ObjectType) if !ok { return fmt.Errorf("expected 'tags' to be an ObjectType, got %#v", tags) } productNode, ok := hclchecker.GetNodeForKey(t2.List, "product_area") if !ok { return errors.New("Could not find a 'product_area' tag for S3 resource. Be sure to tag your resource with a product_area") } } 

We have established continuous integration for the repository with Terraform configs, and then added these checks, so tests will not pass if there is a resource to be markup without the product_area tag.

This is not ideal — tests are choosy, and people technically have the ability to create unlabeled resources directly in the AWS console, but the system works quite well for this stage. The easiest way to describe a new infrastructure is through Terraform.



Processing data from cost allocation tags


After resource markup, their accounting is a simple task.

  1. Find product_area tags for each resource to match resource identifiers with product_area tags.
  2. Add up the cost of all resources.
  3. Add the cost of product lines and record the result in a pivot table.

 SELECT sum(unblended_cost) FROM awsbilling.line_items WHERE statement_month = $1 AND product_name='Amazon DynamoDB'; 

You may want to break down the costs of AWS services - we have two separate tables, one for the product segments of Segment, and the other for AWS services.

With the usual AWS cost sharing tags, we managed to allocate about 35% of the costs.

Analysis of booked instances


This approach is good for marked up, accessible instances. But in some cases, AWS takes an advance payment for "booking." Booking guarantees the availability of a certain amount of resources in exchange for an advance payment at reduced rates.

In our case, it turns out that several large payments from last year’s December CSV account should be distributed over all months of the current year.

To correctly account for these costs, we decided to use data on separate (unblended) costs for the period. The request looks like this:

 select unblended_cost, usage_start_date, usage_end_date from awsbilling.line_items where start_date < '2017-04-01' and end_date > '2017-03-01' and product_name = 'Amazon DynamoDB' and resource_id = ''; 

Subscription costs are recorded in the form of "$ X0000 of DynamoDB", so that they can not be attributed to any resource or product direction.

Instead, we add up the cost of each resource by product line, and then we distribute the cost of the subscription in accordance with the percentage share. If 60% of the expenses of EC2 were spent on data storage, we assume that 60% of the subscription fee for these purposes also went away.

This is also not perfect. If most of your account is taken from an advance payment, this distribution strategy will be distorted by small changes in current expenditures for operating instances. In this case, you will want to distribute expenses based on information on the use of each resource, and it is more difficult to summarize than expenses.

3. Cost data from unlabeled AWS resources


Although the DynamoDB instance and table layout is great, other AWS resources do not support cost sharing tags. These resources required the creation of an abstruse workflow in the style of Ruba Goldberg in order to successfully obtain data on costs and transfer them to Redshift.

The two largest groups of unlabeled resources in our case are ECS and EBS.

ECS


The ECS system continuously increases and reduces the scale of our services, depending on the number of containers that each of them needs to work. She is also responsible for rebalancing and packaging in containers in a large number of individual instances.

ECS runs containers on hosts based on the number of "reserved CPUs and memory." Each service tells you how many CPU parts it needs, and ECS either places new containers on the host with enough resources, or scales the number of instances to add the necessary resources.

None of these ECS actions is directly reflected in the CSV billing report - but ECS is still responsible for running autoscaling for all of our instances.

Simply put, we wanted to understand what “part” of a particular machine each container uses, but the CSV report only gives us a breakdown of the “whole unit” by instance.

To determine the cost of a particular service, we developed our own algorithm:

  1. Set up a Cloudwatch subscription for each event when the ECS task starts or stops.
  2. Send relevant data of this event (service names, CPU / memory usage, start or stop, EC2 instance ID) to Kinesis Firehose (to accumulate individual events).
  3. Send data from Kinesis Firehose to Redshift.

As soon as all the start / stop / size data arrived at Redshift, we multiply the amount of time that this task worked in ECS (say, 120 seconds) by the number of CPU units it used on this machine (up to 4096 - this information is available in task description) to calculate the number of CPU-seconds for each service running on the instance.

The total costs of the instance from the account are then divided between the services in accordance with the number of used CPU-seconds.

This is also not an ideal method. EC2 instances do not run all the time at 100% capacity, while the surplus is currently distributed among all services that worked on this instance. This may or may not be the correct allocation of excess costs. But (and here you can find out the general theme of this article) that is enough.



In addition, we want to relate each ECS service to the corresponding product line. However, we cannot mark them in AWS, because ECS does not support cost sharing tags .

Instead, we add the product_area key to the Terraform module for each ECS service. This key does not lead to any metadata sent to AWS, but it fills in a script that reads product_area keys for all services.

This script then for each new data transfer publishes to the main branch for DynamoDB a map relating the names of services and product directions in base64 encoding.



Finally, then our tests check every new service that is tagged with the product line.

Ebs


Elastic Block Storage (EBS) also occupies a significant part of our account. EBS volumes are usually connected to EC2 instances, and it makes sense to consider the costs of EBS volumes together with the corresponding EC2 instances for accounting. However, the CSV billing from AWS does not show which EBS volume is connected to which instance.



To do this, we again used Cloudwatch - subscribed to events such as “connect volume” and “disconnect volume”, and then registered EBS = EC2 links in the DynamoDB table.

We then add the costs of the EBS volumes to the costs of the corresponding EC2 instances before we consider the costs of the ECS.

Merging data between accounts


So far, we have been discussing all our expenses in the context of a single AWS account. But in reality this does not reflect our real AWS configuration, which is distributed across different AWS physical accounts.



We use an operational account (Ops) not only to consolidate data and billing across all accounts, but also to provide a single point of access for engineers to make changes in production. We separate the Stage stage from the production - so you can check that the API call, for example, to delete the DynamoDB table, is safely handled with the appropriate checks.

Among these expense accounts, the Prod account dominates, but Stage account costs also account for a significant portion of the total AWS account.

Difficulties begin when you need to write data about ECS services from your Stage account to the Redshift cluster on production.

To be able to write “between accounts”, we must allow Cloudwatch subscription handlers to take on production roles for writing to Firehose (for ECS) or DynamoDB (for EBS). This is not easy to do, because you need to add the correct permissions of the correct function in the Stage account (sts.AssumeRole) and in the Prod account, and any error will lead to confusion permissions.



For us, this means that the code for accounting does not work on the Stage account, and all information is entered into the database on the Prod account.

Although a second service can be added to the Stage account, which is subscribed to the same data, but does not record it, we decided that in this case there is a chance to encounter random problems in the accounting code of the Stage.

Statistics issue


Finally, we have everything for proper data analysis:

  1. Marked resources in CSV.
  2. Data when each ECS event starts and stops.
  3. Binding the names of ECS services to the relevant product lines.
  4. Binding EBS volumes to connected instances.

To give it all to the analytic team, I broke the AWS data. For each AWS service, I summarized the Segment product lines and their costs for this AWS service.

These data are given in three different tables:

  1. Total costs for each ECS service in a given month.
  2. Total expenses for each product line in a given month.
  3. Total expenses for (AWS service, grocery segment Segment) a given month. For example, "Data Warehouse Direction spent $ 1,000 on DynamoDB last month."

The total costs for individual product lines look like this:

  month |  product_area |  cost_cents
   --------------------------------------
   2017-03-01 |  integrations |  500
   2017-03-01 |  warehouses |  783 

And the costs of AWS services in combination with the Segment product lines look like this:

  month |  product_area |  aws_product |  cost_cents
   -------------------------------------------------- -
   2017-03-01 |  integrations |  ec2 |  333
   2017-03-01 |  integrations |  dynamodb |  167
   2017-03-01 |  warehouses |  redshift |  783 

For each of these tables, we have a summary table with totals for each month and an additional pivot table that updates the data for the current month every day. The unique identifier in the pivot table matches each pass, so you can consolidate the AWS score by finding all the rows for that pass.

The resulting data effectively serves as our golden "source of truth", which is used for high-level metrics and reporting to management. Pivot tables are used to monitor current costs during the month.

Note: AWS issues a “final” account only a few days after the end of the month, so any logic that marks the billing records as final at the end of the month will be incorrect. You can find out the final Amazon score when an integer appears in the invoice_id field in the CSV file, not the word “Estimated”.

Some last tips


Before concluding, we realized that in the whole process there are some places where a little preparation and knowledge could save us a lot of time. Without any sorting here are these places:


Bottom line


Getting AWS visuals is not easy. This requires a lot of work — both for developing and configuring the toolkit, and for identifying expensive resources on AWS.

The biggest victory we have achieved is the possibility of simple continuous cost forecasting instead of periodic “one-time analyzes”.

To do this, we automated all data collection, implemented tag support in Terraform and our continuous integration system, and explained to all members of the development team how to tag their infrastructure correctly.

All our data is not dead weight in PDF, but is continuously updated in Redshift. If we want to answer new questions and generate new reports, we instantly get the results using a SQL query.

In addition, we exported all the data in Excel format, where we can accurately calculate how much a new client will cost us. And we can also see if a lot more money suddenly goes to some kind of service or product line - we see it before unplanned expenses affect the financial condition of the company.

, - , !

Source: https://habr.com/ru/post/342096/


All Articles