Allocate AWS Costs with Resource Tags
Tags: aws, devops, tutorial, serverless
Published: 2019-11-20 16:51:32

---
title: Allocate AWS Costs with Resource Tags 
published: true
cover_image: https://thepracticaldev.s3.amazonaws.com/i/wfz1e3f4o4jyaifkldjc.png
description: some tips about using tags to aggregate AWS resources and have a better overview in cost reports
tags: #aws, #devops, #tutorial, #serverless
---

When you start having many projects - each with different environments - in your AWS account, it **is very important to have an overview of the costs by project (and/or environment).**

Under AWS Cost Management / Cost Explorer is possible to view reports and aggregate costs by Services and you can set up many different filters to have a more granular view. 

![Cost Reports](https://thepracticaldev.s3.amazonaws.com/i/e7ibgok9tcbhpkizm4ou.png)


The one filter I find more useful is [by Tag](https://docs.aws.amazon.com/en_us/awsaccountbilling/latest/aboutv2/cost-alloc-tags.html).

Assuming you are deploying your serverless infrastructure via CloudFormation ( directly, or through AWS SAM or Serverless framework) you are immediately able to Filter costs by Tag using the auto-generated `Cloudformation:StackName` ( which is the name you assign to the service in the yml file).

Often though this is not enough, because maybe your infrastructure is split into multiple separate stacks.

In some of our recent projects for example we have :
- a **React frontend** which we deploy to **S3** and serve with a **CloudFront Distribution** ( which handles caching for us ) + **Route53** for the domain name.
- a **RestAPI** which relies on **API Gateway** and many different **Lambdas** which read and write to **DynamoDB**, and make use of **SQS**.
- a **Cognito User Pool** to handle the Authentication to the FrontEnd and allows it to make calls to the RestAPI.
- some additional **CloudFront distributions** (+ **Route53** ) to expose some specific endpoints of the RestAPI to a 3rd party ( not the Cognito Authorized frontend ) and deal with caching based on query parameters.

![AWS Infrastructure Components](https://thepracticaldev.s3.amazonaws.com/i/vjsqxb4az00cwjdwqt2q.jpg)

That means that these 4 components of the Architecture are described with different cloud formation stacks (in some case we don't even have a stack because we created the resources via AWS CLI or CDK, or forgive me, directly with the AWS UI Console).

The simple filter by stack name, therefore, is not enough.
This does not mean you can not monitor the costs of the entire Application/Project.

> Many AWS services support user-generated tags, therefore it is possible to assign the same tag to resources from different services to indicate that the resources are related. For example, you could assign the same tag to an API Gateway stage that you assign to a CloudWatch Events rule.


[User-generated tags](https://docs.aws.amazon.com/en_us/awsaccountbilling/latest/aboutv2/custom-tags.html) are simple key-value pairs assigned to the resource you are creating ( or have already created ).

As usual, the quickest and easy approach could be adding them for each resource directly in the UIConsole:

![UI Console Tags CognitoUserPool](https://thepracticaldev.s3.amazonaws.com/i/nkgbxm2irb5o1aodijnr.png)

![UI Console Tags Lambda](https://thepracticaldev.s3.amazonaws.com/i/2j63xa05tc9yroofmt3v.png)

but this is definitely something you don't want to do for anything else than a quick test or prototype project.

Also adding tags with the AWS CLI is quick and easy, just check the docs for the CLI for your resource. (**unfortunately, the right way of passing the tags is slightly different from a resource to another**).


For example to add tags for a Cognito User Pool is just:
```
 aws cognito-idp tag-resource --resource-arn YOUR_USER_POOL_ARN --tags Project=PROJECT_NAME,Environment=YOUR_ENV
```
but for S3 you need to pass a TagSet:

```
     # add cost allocation tags
    export tagSet="TagSet=[{Key=Project,Value=$PROJECT},{Key=Environment,Value=$YOUR_ENV}]"
    aws s3api put-bucket-tagging --bucket $S3_BUCKET_NAME --tagging=$tagSet 
```

and for CloudFront the tags are defined in an Array called Items. You can, therefore, create a JSON file and use that.

```
# in tags.json
{
  "Items": [
    { 
     "Key": "Project",
     "Value":"cap"
    },
    {
     "Key": "Environment",
     "Value": "dev"
    }
  ]
}

aws cloudfront tag-resource --resource arn:aws:cloudfront::Q1W2E3R4T5:distribution/Q1W2E3R4T5Y6 --tags file://tags.json
tags.json 
```
 
And the examples and differences could go on.
As you see, a better and more maintainable approach is defining your tags along with the resource in your **Infrastructure As Code** ( be it Terraform, SAM or Serverless).


With **serverless framework** it is as simple as adding this snippet to the provider section of your serverless.yml:

```
  stackTags:
    Project: PROJECT_NAME
    Environment: YOUR_STAGE/ENV
```

Everything will work out of the box for your Lambdas and for DynamoDb, but again, you might need some adjustments for Cognito, SQS, S3 and API Gateway.

For example for Cognito you need to specify them in the Cognito User Pool properties:

```
 UserPoolTags: {'Project': '${self:provider.stackTags.Project}', "Environment": '${self:provider.stackTags.Environment}'}
```

For SQS is instead you have to specify key-value pairs under the properties of your Queue.

```
Properties: 
 Tags: 
   - Key: Project
      Value: ${self:service}
    - Key: Environment
      Value: ${self:provider.stage}
```

If you have many queues in your serverless.yml it could make sense to create a Custom Variable and refer to it in each queue.

```
custom:
  sqsTags:
    - Key: Project
      Value: ${self:service}
    - Key: Environment
      Value: ${self:provider.stage}

// and then in SQS block:
 Properties
  Tags: ${self:custom.sqsTags}
```

As soon as we did that, with [just one more google search](https://dev.to/dvddpl/often-your-solution-is-one-more-google-search-away-574a) we found out that there is a [plugin](https://www.npmjs.com/package/serverless-tag-sqs) to simplify the script.


This has brought us to another [plugin needed to tag the ApiGateway](https://www.npmjs.com/package/serverless-tag-api-gateway) (which as you can see by the [source code](https://github.com/gfragoso/serverless-tag-api-gateway/blob/master/index.js), I guess it's clearly forked by the SQS one).

In the serverless world, things change fast and it seems that at least for the API Gateway that plugin is not necessary anymore and we can just use the `stackTags` that [come with the framework](https://serverless.com/framework/docs/providers/aws/events/apigateway#tags--stack-tags) (alone or together with another node `tags` that allow customizing even more the API Resources.  Honestly, though, I haven't yet figure out what is the difference between using stackTags and tags.
It seems that the tags added with the `tags` node are added to the APIS/Stages/Configure Tags (together with the stackTags), while the stackTags are added only to APIS/Settings/Configure Tags. If you are interested in more granularity in that tagging you might want to check out the [API Gateway Resources That Can Be Tagged](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-tagging-supported-resources.html)


### AWS Resource Groups  and Tag Editor

Something that I really found useful while working on Cost Allocation Tags is the Tag Editor and Resource Group section of the UI Console.

![Add Tags to Resources via Tag Editor](https://thepracticaldev.s3.amazonaws.com/i/lb6uaqgonu6zytvjeje0.png)

Tag editor allows you to select all the resources you have in your account, filter them and edit directly their tags. 

![Tag based Resource group](https://thepracticaldev.s3.amazonaws.com/i/kpe64wcz0u6wmnwuivhg.png)

Resource groups allows you to have an aggregated view of all the resources under specific tags. 

These 2 tools are very handy to have an overview of the tags that you have applied and make sure all the different resources from different stacks will belong nicely together when it comes to the Cost Reports.

Unfortunately, **CloudFront distributions are not supported( yet ) in the Resource Groups (outside us-east-1)** but you still can add tags directly to them. They will not be shown in the Tagged Resource Groups, but they will in the Cost Reports.

 
## Err on the side of using too many tags rather than too few.

As you saw you can play around a lot and customize the tagging for each service.  You can set the Tags at the provider level, or at the resource level. And of course, you can do both.
One of the suggestions from AWS in regards to tagging is to be as precise and granular as you like, it is better to have too many tags than too few and this is why they set a rather high **limitation of 50 tags for each resource**, you will have plenty of room to customize and be specific your tags!


---

As with many things AWS as soon as you start working on something, you realize there is an enourmous amout of stuff that you do NOT know ( and that's never good for your [Imposter Syndrome](https://dev.to/dvddpl/what-s-worse-than-being-an-imposter-pip).. ).
In the end Tagging resources for Costs Allocation is very simple, but it is only one aspect, because for example you could use tags to grant access to resources ( constraining IAM permissions by specific tags).  If you want to know more you can start reading this [page](https://aws.amazon.com/answers/account-management/aws-tagging-strategies/)


---

Something else you might want to check out is the version of AWS CLI you have installed on your machine, recently I wasted good 20 minutes trying to figure out why 
`aws cognito-identity tag-resource --resource-arn arn:aws:cognito-identity:my_region:my_account:identitypool/my_region:my_identity_pool_id --tags MyTagKey=MyTagValue`
wasn't working: I was getting the error response `Argument Operation: Invalid Choice` despite apparently everything being exactly as described [here](https://docs.aws.amazon.com/cognito/latest/developerguide/tagging.html)

I then run `pip3 install --upgrade awscli` and notice that my aws cli was updated from  1.16.70 to 1.16.283.  (quite a big jump of patches) and then magically `list-tags-for-resource` and `tag-resource` where finally available.                              



---
Photo by [David Carboni](https://unsplash.com/@davidcarboni?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash