# Cost Optimization

Source: https://docs.ops0.com/docs/how-to/cost-optimization

Use ops0 cost estimation, policies, Kubernetes cost analysis, and discovery to control cloud spending before and after deployments.

---

## Before you start

- At least one cloud integration connected

- An IaC project with resources to manage (or plan to create one)

---

## Step 1: Understand Cost Estimation in Plans

Cost estimation runs automatically every time you trigger a plan. No setup required.

When you run a plan, ops0 calculates the estimated monthly cost of the planned changes using current cloud provider pricing:

```
Plan: 3 to add, 1 to change, 0 to destroy

Cost Estimation
─────────────────────────────────────────────────────────
│ Resource                   │ Type         │ Monthly    │
├────────────────────────────┼──────────────┼────────────┤
│ aws_instance.app_server    │ t3.xlarge    │ +$121.47   │
│ aws_db_instance.postgres   │ db.r5.large  │ +$172.80   │
│ aws_nat_gateway.main       │ per-hour     │  +$32.40   │
│ aws_instance.bastion       │ t3.micro     │   +$8.47   │
├────────────────────────────┼──────────────┼────────────┤
│ Total change               │              │ +$335.14   │
│ New monthly estimate       │              │ $1,850.97  │
─────────────────────────────────────────────────────────
```

**Estimates are based on on-demand pricing**

Cost estimates use on-demand prices from the cloud provider's public pricing API. Reserved instances, Savings Plans, committed use discounts, and spot pricing are not factored in — your actual bill may be lower.

### What cost estimation covers

| Included | Not included |
|---------|--------------|
| EC2/Compute instance hours | Data transfer costs |
| RDS/Cloud SQL/Azure SQL | Bandwidth charges |
| NAT Gateway hours | Support plan charges |
| Load balancer hours | Marketplace software fees |
| Storage (S3/GCS/Azure Blob) | Reserved/committed discounts |
| Kubernetes node costs | Spot/preemptible savings |

---

## Step 2: Set a Budget Alert

Set a monthly budget for your organization so ops0 notifies you when spending approaches the limit:

### Open Budget Settings
Go to **Settings → Budget**.

### Set Monthly Limit
Enter your monthly budget in USD. ops0 tracks estimated spending across all projects.

### Configure Alert Thresholds
Set alerts at 80% and 100% of budget. Notifications go to your configured Slack or email channels.

### Review Monthly
Check the budget dashboard monthly to track trends and identify which projects are driving the most spend.

---

## Step 3: Block Expensive Deployments with Policies

Policies can inspect cost estimates and block deployments before they execute. This prevents expensive mistakes from reaching production.

### Block deployments over a cost threshold

```rego
package terraform

# Block any deployment that increases monthly costs by more than $500
deny[msg] {
    cost_increase := input.cost_estimate.monthly_cost_increase
    cost_increase > 500

    msg := sprintf(
        "COST POLICY: This deployment increases monthly costs by $%.2f, which exceeds the $500 threshold. Contact your finance team for approval before deploying.",
        [cost_increase]
    )
}
```

### Restrict expensive instance types

```rego
package terraform

# Prevent use of oversized instances without approval
restricted_instance_types := {
    "m5.24xlarge",
    "r5.24xlarge",
    "c5.24xlarge",
    "x1.32xlarge",
    "x1e.32xlarge",
    "p3.16xlarge",
    "p4d.24xlarge",
}

deny[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_instance"
    instance_type := resource.change.after.instance_type
    restricted_instance_types[instance_type]

    msg := sprintf(
        "COST POLICY: Instance type '%s' is restricted. Use a smaller instance type or request an exception via your ops lead.",
        [instance_type]
    )
}
```

### Require cost allocation tags on new resources

```rego
package terraform

required_cost_tags := ["CostCenter", "Team", "Environment"]

deny[msg] {
    resource := input.resource_changes[_]
    resource.change.actions[_] == "create"
    tag := required_cost_tags[_]
    not resource.change.after.tags[tag]

    msg := sprintf(
        "COST POLICY: Resource %s is missing required tag '%s'. All new resources must have CostCenter, Team, and Environment tags for billing allocation.",
        [resource.address, tag]
    )
}
```

### Warn on storage without lifecycle rules

```rego
package terraform

warn[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_s3_bucket"
    resource.change.actions[_] == "create"

    # Check if there's a corresponding lifecycle config resource
    lifecycle_resources := [r | r := input.resource_changes[_]; r.type == "aws_s3_bucket_lifecycle_configuration"]
    count(lifecycle_resources) == 0

    msg := sprintf(
        "COST WARNING: S3 bucket %s has no lifecycle rule. Consider adding one to automatically transition or expire old objects.",
        [resource.address]
    )
}
```

---

## Step 4: Cost Estimation in GitOps PR Comments

If you've set up GitHub sync, cost estimates automatically appear as comments on pull requests:

ops0 — Terraform Plan

Plan: 3 to add, 1 to change, 0 to destroy

💰 **Cost impact: +$335.14/month**

New monthly estimate: $1,850.97

aws_instance.app_server (t3.xlarge) +$121.47

aws_db_instance.postgres (db.r5.large) +$172.80

aws_nat_gateway.main +$32.40

aws_instance.bastion (t3.micro) +$8.47

This gives reviewers the cost impact before approving and merging the PR — no surprises on the next cloud bill.

---

## Step 5: Identify Expensive Resources with Discovery

After deploying infrastructure, use Discovery to find resources that are costing money but may no longer be needed:

### Run a Discovery scan
Go to **Discovery → New Scan** and scan your cloud account.

### Filter for expensive resource types
After the scan, filter by resource type. Common cost culprits:
- Unattached EBS volumes (paying for storage with no instance using it)
- Unused Elastic IPs (billed whether attached or not)
- Idle NAT Gateways (charged per hour)
- Stopped EC2 instances (EBS storage and Elastic IPs still billed)
- Unused RDS snapshots (storage costs accumulate)

### Import and manage or delete
For resources you want to keep, import them into an IaC project. For orphaned resources, use ops0 to generate destroy Terraform that safely removes them.

---

## Step 6: Kubernetes Cost Analysis

For teams running Kubernetes clusters, ops0 provides per-namespace cost analysis via the ops0 agent:

### Connect your cluster
Go to **Kubernetes → Add Cluster** and deploy the ops0 agent.

### Open Cost Analysis
Navigate to **Kubernetes → [your cluster] → Cost Analysis**.

### Review per-namespace breakdown
ops0 breaks down CPU, memory, GPU, and persistent volume costs per namespace. Sort by cost to identify which teams or applications are driving the most spend.

### Identify over-provisioned workloads
Compare requested resources vs actual usage. A pod requesting 4 CPU cores but using 0.2 is a candidate for right-sizing.

### Example cost breakdown

```
Kubernetes Cost Analysis — production cluster
─────────────────────────────────────────────────────────
Namespace           CPU     Memory   PV       Total/mo
─────────────────────────────────────────────────────────
ml-training         $420    $180     $45      $645
api-services        $210    $95      $20      $325
monitoring          $85     $140     $60      $285
frontend            $45     $30      $5       $80
dev-sandbox         $290    $120     $0       $410  ← review
─────────────────────────────────────────────────────────
Total               $1,050  $565     $130     $1,745
─────────────────────────────────────────────────────────
```

The `dev-sandbox` namespace in this example costs $410/month but has no persistent volumes — it's using a lot of CPU and memory that may not be needed outside business hours.

---

## Cost Optimization Checklist

Use this checklist periodically to keep costs under control:

Right-size compute instances

Check CPU and memory utilization in your monitoring tool. If an EC2 instance is averaging 15% CPU, consider dropping from t3.xlarge to t3.medium — ~60% cost reduction.

Schedule non-production environments

Use ops0 Workflows to stop dev/staging EC2 instances and RDS clusters outside business hours (e.g., 6pm–8am). For a t3.large running 24/7, this saves ~65% of compute cost.

Add S3 lifecycle rules

Objects not accessed for 90 days can transition to S3 Glacier Instant Retrieval (68% cheaper than Standard). Objects not accessed for 180 days can go to Glacier Deep Archive (95% cheaper).

Delete orphaned resources

Run Discovery monthly and look for unattached EBS volumes ($0.10/GB/month), unused Elastic IPs ($0.005/hour each), and old snapshots. These accumulate silently.

Right-size Kubernetes pods

Use ops0 Kubernetes Cost Analysis to find namespaces where requested resources significantly exceed actual usage. Update resource requests/limits in your deployment manifests.

---

## Next Steps

- [Creating Policies](https://docs.ops0.com/docs/policies/creating-policies): Write OPA/Rego policies that enforce cost controls automatically.

- [Workflows](https://docs.ops0.com/docs/workflows/overview): Schedule resource start/stop on a cron to cut non-production costs.

- [Kubernetes Cost Analysis](https://docs.ops0.com/docs/kubernetes/cost-analysis): Per-namespace cost breakdown for your Kubernetes clusters.

- [Discovery](https://docs.ops0.com/docs/discovery/overview): Find orphaned and unmanaged resources wasting money.
