Cloud Cost Management
Cloud cost management encompasses the activities required to track, analyse, and optimise cloud expenditure. This task establishes the foundational controls and ongoing practices that prevent uncontrolled spending while ensuring resources remain available for mission delivery. Organisations that complete these procedures gain visibility into where money goes, receive alerts before budgets exhaust, and systematically reduce waste.
Prerequisites
| Requirement | Detail |
|---|---|
| Access | Billing administrator or cost management role in cloud platform |
| Permissions | Create and modify budgets, access billing data, modify resource tags |
| Tools | Cloud provider CLI installed and authenticated; spreadsheet for analysis |
| Information | Chart of accounts or cost centre structure; list of projects and responsible owners |
| Baseline | Minimum 30 days of cloud usage data for meaningful analysis |
| Time | Initial setup: 4-6 hours; monthly review: 2-3 hours |
Verify CLI access to billing data before proceeding. The examples use AWS, Azure, and Google Cloud CLIs; adapt commands to your platform.
For AWS:
aws ce get-cost-and-usage \ --time-period Start=2024-11-01,End=2024-11-30 \ --granularity MONTHLY \ --metrics UnblendedCostExpected output confirms access:
{ "ResultsByTime": [ { "TimePeriod": {"Start": "2024-11-01", "End": "2024-12-01"}, "Total": {"UnblendedCost": {"Amount": "1234.56", "Unit": "USD"}} } ]}For Azure:
az consumption usage list \ --start-date 2024-11-01 \ --end-date 2024-11-30 \ --query "[0:1]"For Google Cloud:
gcloud billing accounts listbq query --use_legacy_sql=false \ 'SELECT SUM(cost) FROM `project.dataset.gcp_billing_export_v1_*` WHERE DATE(_PARTITIONTIME) >= "2024-11-01"'If commands fail with permission errors, request the following roles:
| Platform | Required role |
|---|---|
| AWS | Billing IAM policy or ViewBilling permission |
| Azure | Cost Management Reader at subscription or management group level |
| Google Cloud | Billing Account Viewer and BigQuery access to billing export |
Procedure
Establishing cost visibility
Cost visibility requires two components: a tagging taxonomy that categorises resources, and cost allocation rules that assign shared costs to appropriate owners.
Define your tagging taxonomy based on how your organisation needs to slice costs. A minimal taxonomy requires three tags; larger organisations benefit from additional dimensions.
Create a tagging policy document specifying required tags:
required_tags: - key: CostCentre description: Financial cost centre code valid_values: ["HQ-IT", "PROG-WASH", "PROG-HEALTH", "PROG-EDUC", "FIELD-KE", "FIELD-UG"]
- key: Project description: Project or grant code valid_values_pattern: "^[A-Z]{2,4}-[0-9]{4,6}$"
- key: Environment description: Deployment environment valid_values: ["production", "staging", "development", "sandbox"]
recommended_tags: - key: Owner description: Email of responsible person
- key: DataClassification description: Data sensitivity level valid_values: ["public", "internal", "confidential", "restricted"]Apply tags to existing resources. Start with the highest-cost resources to maximise visibility quickly.
For AWS, list untagged resources consuming significant cost:
aws ce get-cost-and-usage \ --time-period Start=2024-11-01,End=2024-11-30 \ --granularity MONTHLY \ --metrics UnblendedCost \ --group-by Type=TAG,Key=CostCentre \ --filter '{"Not": {"Tags": {"Key": "CostCentre", "Values": []}}}' \ | jq '.ResultsByTime[0].Groups[] | select(.Keys[0] == "CostCentre$")'Resources appearing under the empty tag key lack the CostCentre tag. Tag them using:
aws ec2 create-tags \ --resources i-0abc123def456 \ --tags Key=CostCentre,Value=PROG-WASH Key=Project,Value=WS-2024-001 Key=Environment,Value=productionEnable cost allocation tags in your billing console. Tagging resources does not automatically make tags available in cost reports; you must activate them.
For AWS, navigate to Billing → Cost Allocation Tags → Activate the tags defined in your taxonomy. Tags become available in cost reports within 24 hours.
For Azure, no activation is required; tags appear automatically in Cost Management after resources are tagged.
For Google Cloud, configure billing export to BigQuery if not already enabled:
gcloud beta billing accounts describe BILLING_ACCOUNT_ID \ --format='value(name)'
# Enable detailed usage cost export gcloud billing budgets create \ --billing-account=BILLING_ACCOUNT_ID \ --display-name="Cost Export" \ --budget-amount=1000000USDConfigure cost allocation rules for shared resources. Some costs cannot be attributed through tagging because they apply across multiple projects or the entire organisation.
Identify shared cost categories:
Shared cost type Allocation method Example Support plans Proportional to total spend AWS Enterprise Support Reserved capacity To consuming workloads Reserved Instances Data transfer By source workload Cross-region transfer Shared services Fixed percentage or per-user Identity platform, monitoring For AWS, create a cost category to allocate support costs proportionally:
aws ce create-cost-category \ --name "SupportCostAllocation" \ --rule-version "CostCategoryExpression.v1" \ --rules '[ { "Value": "Allocated", "Rule": { "Tags": { "Key": "CostCentre", "Values": ["HQ-IT", "PROG-WASH", "PROG-HEALTH"], "MatchOptions": ["EQUALS"] } } } ]'- Verify tagging coverage by generating a compliance report. Target 90% tagging coverage within 60 days of implementing the taxonomy.
# AWS: Calculate tagging coverage percentage TOTAL=$(aws ce get-cost-and-usage \ --time-period Start=2024-11-01,End=2024-11-30 \ --granularity MONTHLY \ --metrics UnblendedCost \ | jq -r '.ResultsByTime[0].Total.UnblendedCost.Amount')
TAGGED=$(aws ce get-cost-and-usage \ --time-period Start=2024-11-01,End=2024-11-30 \ --granularity MONTHLY \ --metrics UnblendedCost \ --filter '{"Tags": {"Key": "CostCentre", "Values": [], "MatchOptions": ["ABSENT"]}}' \ | jq -r '.ResultsByTime[0].Total.UnblendedCost.Amount')
COVERAGE=$(echo "scale=2; (1 - $TAGGED / $TOTAL) * 100" | bc) echo "Tagging coverage: ${COVERAGE}%"Expected output for a well-tagged environment:
Tagging coverage: 94.3%Configuring budgets and alerts
Budgets establish spending thresholds that trigger alerts before costs exceed planned amounts. Configure budgets at multiple levels: organisation-wide to protect against runaway spending, and per-project to give owners visibility into their consumption.
- Calculate baseline monthly spend from historical data. Use the previous three months to establish a representative baseline, excluding any anomalous months.
aws ce get-cost-and-usage \ --time-period Start=2024-09-01,End=2024-12-01 \ --granularity MONTHLY \ --metrics UnblendedCost \ | jq '.ResultsByTime[] | {month: .TimePeriod.Start, cost: .Total.UnblendedCost.Amount}'Sample output:
{"month": "2024-09-01", "cost": "4521.34"} {"month": "2024-10-01", "cost": "4789.12"} {"month": "2024-11-01", "cost": "5102.87"}Average: $4,804.44. Set initial budget at 110% of average: $5,285.
Create an organisation-wide budget with alerts at 50%, 80%, and 100% thresholds.
For AWS:
aws budgets create-budget \ --account-id 123456789012 \ --budget '{ "BudgetName": "Monthly-Organisation-Budget", "BudgetLimit": {"Amount": "5285", "Unit": "USD"}, "BudgetType": "COST", "TimeUnit": "MONTHLY", "CostFilters": {} }' \ --notifications-with-subscribers '[ { "Notification": { "NotificationType": "ACTUAL", "ComparisonOperator": "GREATER_THAN", "Threshold": 50, "ThresholdType": "PERCENTAGE" }, "Subscribers": [ {"SubscriptionType": "EMAIL", "Address": "it-finance@example.org"} ] }, { "Notification": { "NotificationType": "ACTUAL", "ComparisonOperator": "GREATER_THAN", "Threshold": 80, "ThresholdType": "PERCENTAGE" }, "Subscribers": [ {"SubscriptionType": "EMAIL", "Address": "it-finance@example.org"}, {"SubscriptionType": "EMAIL", "Address": "it-manager@example.org"} ] }, { "Notification": { "NotificationType": "ACTUAL", "ComparisonOperator": "GREATER_THAN", "Threshold": 100, "ThresholdType": "PERCENTAGE" }, "Subscribers": [ {"SubscriptionType": "EMAIL", "Address": "it-director@example.org"}, {"SubscriptionType": "SNS", "Address": "arn:aws:sns:eu-west-1:123456789012:budget-alerts"} ] } ]'For Azure:
az consumption budget create \ --budget-name "Monthly-Organisation-Budget" \ --amount 5285 \ --category Cost \ --time-grain Monthly \ --start-date 2024-12-01 \ --end-date 2025-12-01 \ --resource-group "" \ --notifications '{ "Actual_GreaterThan_50_Percent": { "enabled": true, "operator": "GreaterThan", "threshold": 50, "contactEmails": ["it-finance@example.org"] } }'Create per-project budgets filtered by the Project tag. Calculate each project’s budget from its historical spend or its approved grant budget.
For a project with $1,200 monthly budget:
aws budgets create-budget \ --account-id 123456789012 \ --budget '{ "BudgetName": "Project-WS-2024-001", "BudgetLimit": {"Amount": "1200", "Unit": "USD"}, "BudgetType": "COST", "TimeUnit": "MONTHLY", "CostFilters": { "TagKeyValue": ["user:Project$WS-2024-001"] } }' \ --notifications-with-subscribers '[ { "Notification": { "NotificationType": "ACTUAL", "ComparisonOperator": "GREATER_THAN", "Threshold": 80, "ThresholdType": "PERCENTAGE" }, "Subscribers": [ {"SubscriptionType": "EMAIL", "Address": "wash-pm@example.org"} ] } ]'- Configure forecasted spend alerts to receive warnings before month-end based on current trajectory.
aws budgets create-budget \ --account-id 123456789012 \ --budget '{ "BudgetName": "Monthly-Forecast-Alert", "BudgetLimit": {"Amount": "5285", "Unit": "USD"}, "BudgetType": "COST", "TimeUnit": "MONTHLY" }' \ --notifications-with-subscribers '[ { "Notification": { "NotificationType": "FORECASTED", "ComparisonOperator": "GREATER_THAN", "Threshold": 100, "ThresholdType": "PERCENTAGE" }, "Subscribers": [ {"SubscriptionType": "EMAIL", "Address": "it-finance@example.org"} ] } ]'- Test alert delivery by temporarily lowering a budget threshold below current spend.
aws budgets update-budget \ --account-id 123456789012 \ --new-budget '{ "BudgetName": "Monthly-Organisation-Budget", "BudgetLimit": {"Amount": "100", "Unit": "USD"}, "BudgetType": "COST", "TimeUnit": "MONTHLY" }'Verify email receipt within 15 minutes, then restore the correct budget amount.
Analysing spend patterns
Regular analysis identifies trends, anomalies, and optimisation opportunities. Conduct monthly reviews using the following procedure.
- Generate a month-over-month comparison by service to identify significant changes.
aws ce get-cost-and-usage \ --time-period Start=2024-10-01,End=2024-12-01 \ --granularity MONTHLY \ --metrics UnblendedCost \ --group-by Type=DIMENSION,Key=SERVICE \ | jq -r '.ResultsByTime[] as $period | $period.Groups[] | [$period.TimePeriod.Start, .Keys[0], .Metrics.UnblendedCost.Amount] | @csv' \ | sort -t',' -k2,2 -k1,1Sample output identifying EC2 growth:
"2024-10-01","Amazon Elastic Compute Cloud","1823.45" "2024-11-01","Amazon Elastic Compute Cloud","2341.67" "2024-10-01","Amazon Simple Storage Service","456.78" "2024-11-01","Amazon Simple Storage Service","478.23"EC2 increased 28% ($518.22) while S3 increased 4.7% ($21.45). The EC2 increase warrants investigation.
- Drill into the high-growth service by resource to find the cause.
aws ce get-cost-and-usage \ --time-period Start=2024-11-01,End=2024-12-01 \ --granularity MONTHLY \ --metrics UnblendedCost \ --filter '{ "Dimensions": { "Key": "SERVICE", "Values": ["Amazon Elastic Compute Cloud"] } }' \ --group-by Type=DIMENSION,Key=RESOURCE_ID \ | jq -r '.ResultsByTime[0].Groups | sort_by(.Metrics.UnblendedCost.Amount | tonumber) | reverse | .[0:10][] | [.Keys[0], .Metrics.UnblendedCost.Amount] | @csv'Identify unused or underutilised resources using utilisation metrics.
For EC2, query CloudWatch for CPU utilisation below 10% over 14 days:
for instance in $(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --output text); do avg_cpu=$(aws cloudwatch get-metric-statistics \ --namespace AWS/EC2 \ --metric-name CPUUtilization \ --dimensions Name=InstanceId,Value=$instance \ --start-time $(date -d '14 days ago' --iso-8601=seconds) \ --end-time $(date --iso-8601=seconds) \ --period 86400 \ --statistics Average \ --query 'Datapoints[].Average | [0]' \ --output text 2>/dev/null)
if [[ $(echo "$avg_cpu < 10" | bc -l 2>/dev/null) -eq 1 ]]; then echo "Low utilisation: $instance (${avg_cpu}% avg CPU)" fi done- Check for unattached storage volumes incurring ongoing charges.
aws ec2 describe-volumes \ --filters Name=status,Values=available \ --query 'Volumes[].{ID:VolumeId,Size:Size,Created:CreateTime}' \ --output tableVolumes in available state are not attached to any instance but continue to incur storage charges.
- Analyse data transfer costs, which often grow unexpectedly.
aws ce get-cost-and-usage \ --time-period Start=2024-11-01,End=2024-12-01 \ --granularity MONTHLY \ --metrics UnblendedCost \ --filter '{ "Dimensions": { "Key": "USAGE_TYPE_GROUP", "Values": ["EC2: Data Transfer - Internet (Out)", "EC2: Data Transfer - Region to Region (Out)"] } }' \ --group-by Type=DIMENSION,Key=USAGE_TYPE- Document findings in a monthly cost review report. Include:
- Total spend versus budget
- Month-over-month change by cost centre
- Top 5 cost increases with explanations
- Identified optimisation opportunities
- Actions taken since last review
Implementing optimisations
Act on analysis findings to reduce waste and improve cost efficiency. The following optimisations are listed in order of typical impact and implementation complexity.
Terminate or stop unused resources identified in analysis. For development and staging environments, implement schedules to stop instances outside working hours.
Create a Lambda function to stop tagged instances at 19:00 and start them at 07:00:
import boto3
def lambda_handler(event, context): ec2 = boto3.client('ec2')
# Find instances tagged for scheduling response = ec2.describe_instances( Filters=[ {'Name': 'tag:Schedule', 'Values': ['office-hours']}, {'Name': 'instance-state-name', 'Values': ['running']} ] )
instance_ids = [] for reservation in response['Reservations']: for instance in reservation['Instances']: instance_ids.append(instance['InstanceId'])
if instance_ids: ec2.stop_instances(InstanceIds=instance_ids) return f"Stopped {len(instance_ids)} instances" return "No instances to stop"Schedule with EventBridge:
aws events put-rule \ --name "StopDevInstances" \ --schedule-expression "cron(0 19 ? * MON-FRI *)" \ --state ENABLED
aws events put-rule \ --name "StartDevInstances" \ --schedule-expression "cron(0 7 ? * MON-FRI *)" \ --state ENABLEDPotential savings: A t3.large instance running 24/7 costs approximately $60/month; running only during office hours (12 hours × 22 days) reduces this to $22/month, a 63% reduction.
Right-size over-provisioned instances based on utilisation data.
Use AWS Compute Optimizer or equivalent to get recommendations:
aws compute-optimizer get-ec2-instance-recommendations \ --instance-arns arn:aws:ec2:eu-west-1:123456789012:instance/i-0abc123def456 \ --query 'instanceRecommendations[].{ Current: currentInstanceType, Recommended: recommendationOptions[0].instanceType, MonthlySavings: recommendationOptions[0].projectedUtilizationMetrics }'Sample recommendation:
{ "Current": "m5.xlarge", "Recommended": "m5.large", "MonthlySavings": "Estimated $70/month (50% reduction)" }Before resizing, verify the application can function with reduced resources in staging.
- Delete unattached volumes and obsolete snapshots.
# List unattached volumes older than 30 days aws ec2 describe-volumes \ --filters Name=status,Values=available \ --query 'Volumes[?CreateTime<=`2024-11-01`].{ID:VolumeId,Size:Size,Cost:"$0.10/GB/month"}' \ --output table
# Delete after confirmation (no undo) aws ec2 delete-volume --volume-id vol-0abc123def456For snapshots, identify those not associated with any AMI:
aws ec2 describe-snapshots \ --owner-ids self \ --query 'Snapshots[?StartTime<=`2024-06-01`].{ID:SnapshotId,Size:VolumeSize,Created:StartTime}'- Implement S3 lifecycle policies to transition infrequently accessed data to cheaper storage tiers.
aws s3api put-bucket-lifecycle-configuration \ --bucket project-data-bucket \ --lifecycle-configuration '{ "Rules": [ { "ID": "ArchiveOldData", "Status": "Enabled", "Filter": {"Prefix": "reports/"}, "Transitions": [ {"Days": 90, "StorageClass": "STANDARD_IA"}, {"Days": 180, "StorageClass": "GLACIER"} ], "Expiration": {"Days": 2555} } ] }'Storage class cost comparison for 1 TB:
| Storage class | Monthly cost | Access cost |
|---|---|---|
| S3 Standard | $23.00 | $0.0004/request |
| S3 Standard-IA | $12.50 | $0.01/request |
| S3 Glacier | $4.00 | $0.03/request + retrieval time |
| S3 Glacier Deep Archive | $1.00 | $0.02/request + 12hr retrieval |
Purchase reserved capacity for stable workloads. Only commit to reservations for resources that have run continuously for 6 or more months and will continue for at least another 12 months.
Calculate potential savings:
aws ce get-reservation-purchase-recommendation \ --service "Amazon Elastic Compute Cloud - Compute" \ --term-in-years ONE_YEAR \ --payment-option NO_UPFRONT \ --query 'Recommendations[].RecommendationDetails[].{ Instance: InstanceDetails.EC2InstanceDetails.InstanceType, MonthlySavings: EstimatedMonthlySavingsAmount, BreakEven: EstimatedBreakEvenInMonths }'Sample recommendation:
{ "Instance": "t3.large", "MonthlySavings": "24.50", "BreakEven": "7 months" }A 1-year no-upfront reserved instance for t3.large saves approximately 30% compared to on-demand pricing.
- Enable S3 Intelligent-Tiering for buckets with unpredictable access patterns rather than manually managing lifecycle transitions.
aws s3api put-bucket-intelligent-tiering-configuration \ --bucket project-data-bucket \ --id "AutoTiering" \ --intelligent-tiering-configuration '{ "Id": "AutoTiering", "Status": "Enabled", "Tierings": [ {"Days": 90, "AccessTier": "ARCHIVE_ACCESS"}, {"Days": 180, "AccessTier": "DEEP_ARCHIVE_ACCESS"} ] }'Establishing ongoing governance
Sustaining cost efficiency requires regular review cadence and clear accountability.
Schedule monthly cost review meetings with budget owners. The agenda should cover:
- Actual versus budgeted spend by cost centre
- Significant variances (over 10%) requiring explanation
- Forecast for remainder of budget period
- Optimisation opportunities and actions
- New projects requiring budget allocation
Create a cost dashboard for self-service visibility. Use cloud-native tools or export to BI platforms.
For AWS Cost Explorer saved reports:
aws ce create-cost-category \ --name "Programme-View" \ --rule-version "CostCategoryExpression.v1" \ --rules '[ {"Value": "WASH", "Rule": {"Tags": {"Key": "CostCentre", "Values": ["PROG-WASH"]}}}, {"Value": "Health", "Rule": {"Tags": {"Key": "CostCentre", "Values": ["PROG-HEALTH"]}}}, {"Value": "Education", "Rule": {"Tags": {"Key": "CostCentre", "Values": ["PROG-EDUC"]}}}, {"Value": "Shared", "Rule": {"Tags": {"Key": "CostCentre", "Values": ["HQ-IT"]}}} ]'- Implement anomaly detection to alert on unexpected spend patterns.
aws ce create-anomaly-monitor \ --anomaly-monitor '{ "MonitorName": "ServiceAnomaly", "MonitorType": "DIMENSIONAL", "MonitorDimension": "SERVICE" }'
aws ce create-anomaly-subscription \ --anomaly-subscription '{ "SubscriptionName": "CostAnomalyAlert", "MonitorArnList": ["arn:aws:ce::123456789012:anomalymonitor/abc123"], "Subscribers": [ {"Type": "EMAIL", "Address": "it-finance@example.org"} ], "Threshold": 100, "Frequency": "DAILY" }'This alerts when any service cost deviates by more than $100 from predicted spend.
Document your cost allocation model for grant compliance. Many funders require demonstration that cloud costs are allocated fairly to funded activities.
Create a cost allocation report template:
Cost Allocation Report Period: [Month Year] Grant: [Grant Code]
Direct Costs (tagged to grant): - Compute: $X,XXX.XX - Storage: $XXX.XX - Data transfer: $XX.XX
Allocated Shared Costs: - Platform support (X% of $Y,YYY): $ZZZ.ZZ - Security services (X% of $Y,YYY): $ZZZ.ZZ
Total Cloud Costs for Grant: $X,XXX.XX
Allocation Methodology: [Description of how percentages derived]Verification
Confirm cost management controls function correctly:
Tagging coverage: Run the coverage calculation from step 5 of “Establishing cost visibility.” Coverage should exceed 90%.
# Expected outputTagging coverage: 94.3%Budget alerts: Verify budgets exist and have notifications configured.
aws budgets describe-budgets --account-id 123456789012 \ --query 'Budgets[].{Name:BudgetName,Limit:BudgetLimit.Amount,Alerts:length(notifications)}' \ --output tableExpected output shows budgets with alert counts:
-------------------------------------------| DescribeBudgets |+---------+---------------------------+---+| Alerts | Name |Limit|+---------+---------------------------+---+| 3 | Monthly-Organisation-Budget| 5285|| 1 | Project-WS-2024-001 | 1200|| 1 | Monthly-Forecast-Alert | 5285|+---------+---------------------------+---+Anomaly detection: Confirm monitor and subscription are active.
aws ce get-anomaly-monitors \ --query 'AnomalyMonitors[].{Name:MonitorName,Status:MonitorSpecification}'Scheduled actions: Verify instance scheduling Lambda functions execute.
aws lambda list-functions \ --query 'Functions[?contains(FunctionName, `Instance`)].{Name:FunctionName,Last:LastModified}'Check CloudWatch Logs for recent executions:
aws logs filter-log-events \ --log-group-name /aws/lambda/StopDevInstances \ --start-time $(date -d '24 hours ago' +%s000) \ --query 'events[].message'Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
| Tags not appearing in Cost Explorer | Cost allocation tags not activated | Navigate to Billing → Cost Allocation Tags → Activate required tags; wait 24 hours |
| Budget alerts not received | SNS topic missing permissions or email not confirmed | Check SNS topic policy allows budgets.amazonaws.com; verify email subscription confirmed |
| Anomaly detection triggers excessive alerts | Threshold set too low for variable workloads | Increase threshold; use percentage rather than absolute amount for variable services |
| Cost allocation shows large “untagged” category | Resources created without tags; inherited resources from before taxonomy | Run compliance report to identify untagged resources; implement preventive controls |
| Reserved instance savings not materialising | Reservations purchased for wrong instance family or region | Verify reservation attributes match running instance attributes exactly |
| Scheduled Lambda not stopping instances | Instances missing Schedule tag; Lambda lacking ec2:StopInstances permission | Verify tag key matches filter exactly; check IAM role attached to Lambda |
| Cost data missing for recent days | Cost and Usage Report has 24-48 hour delay | Wait for data propagation; use real-time billing dashboard for current day |
| Cannot create budget with tag filter | Tag key format incorrect; tag not activated | Use format user:TagKey$TagValue for AWS; verify tag activated in Cost Allocation Tags |
| Compute Optimizer shows no recommendations | Insufficient data (requires 14 days) or instance types not supported | Wait for data collection; Compute Optimizer covers EC2, EBS, Lambda, and ECS on Fargate |
| S3 lifecycle policy not transitioning objects | Objects smaller than 128KB; policy filter excluding objects | Intelligent-Tiering has 128KB minimum; verify prefix filter matches object paths |
| Cost anomaly detected but cause unclear | Anomaly detection identifies deviation but not root cause | Use Cost Explorer to drill into the flagged service by resource, usage type, and tag |
| Chargeback report totals do not match invoice | Shared costs not allocated; credits/refunds not included | Verify all cost categories sum to invoice total; include credits in allocation methodology |
Common untagged resource sources:
Resources frequently created without tags include: Auto Scaling launched instances (configure launch template with tags), CloudFormation stacks missing tag propagation (set PropagateAtLaunch: true), and resources created via console without tag entry (implement Service Control Policies requiring tags).
Prevent future untagged resources with an SCP:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "RequireCostCentreTag", "Effect": "Deny", "Action": [ "ec2:RunInstances", "ec2:CreateVolume" ], "Resource": "*", "Condition": { "Null": { "aws:RequestTag/CostCentre": "true" } } } ]}