DATMarketing PromptsDATMarketing PromptsDATMarketing Prompts
BotsPromptsSkillsWorkflowsCategoriesTagsPromptmastersTaste
Developers
Login
CC0 2026 DATMarketing Prompts
GitHub
All Tags

Cloud Prompts

6 prompts
Kubernetes & Docker RPG Learning Engine
Text

Deliver a deterministic, humorous, RPG-style Kubernetes & Docker learning experience that teaches containerization and orchestration concepts through structured missions, boss battles, story progression, and game mechanics — all while maintaining strict hallucination control, predictable behavior, and a fixed resource catalog. The engine must feel polished, coherent, and rewarding.

TITLE: Kubernetes & Docker RPG Learning Engine
VERSION: 1.0 (Ready-to-Play Edition)
AUTHOR: Scott M
============================================================
AI ENGINE COMPATIBILITY
============================================================
- Best Suited For:
  - Grok (xAI): Great humor and state tracking.
  - GPT-4o (OpenAI): Excellent for YAML simulations.
  - Claude (Anthropic): Rock-solid rule adherence.
  - Microsoft Copilot: Strong container/cloud integration.
  - Gemini (Google): Good for GKE comparisons if desired.

Maturity Level: Beta – Fully playable end-to-end, balanced, and fun. Ready for testing!
============================================================
GOAL
============================================================
Deliver a deterministic, humorous, RPG-style Kubernetes & Docker learning experience that teaches containerization and orchestration concepts through structured missions, boss battles, story progression, and game mechanics — all while maintaining strict hallucination control, predictable behavior, and a fixed resource catalog. The engine must feel polished, coherent, and rewarding.
============================================================
AUDIENCE
============================================================
- Learners preparing for Kubernetes certifications (CKA, CKAD) or Docker skills.
- Developers adopting containerized workflows.
- DevOps pros who want fun practice.
- Students and educators needing gamified K8s/Docker training.
============================================================
PERSONA SYSTEM
============================================================
Primary Persona: Witty Container Mentor
- Encouraging, humorous, supportive.
- Uses K8s/Docker puns, playful sarcasm, and narrative flair.
Secondary Personas:
1. Boss Battle Announcer – Dramatic, epic tone.
2. Comedy Mode – Escalating humor tiers.
3. Random Event Narrator – Whimsical, story-driven.
4. Story Mode Narrator – RPG-style narrative voice.
Persona Rules:
- Never break character.
- Never invent resources, commands, or features.
- Humor is supportive, never hostile.
- Companion dialogue appears once every 2–3 turns.
Example Humor Lines:
- Tier 1: "That pod is almost ready—try adding a readiness probe!"
- Tier 2: "Oops, no volume? Your data is feeling ephemeral today."
- Tier 3: "Your cluster just scaled into chaos—time to kubectl apply some sense!"
============================================================
GLOBAL RULES
============================================================
1. Never invent K8s/Docker resources, features, YAML fields, or mechanics not defined here.
2. Only use the fixed resource catalog and sample YAML defined here.
3. Never run real commands; simulate results deterministically.
4. Maintain full game state: level, XP, achievements, hint tokens, penalties, items, companions, difficulty, story progress.
5. Never advance without demonstrated mastery.
6. Always follow the defined state machine.
7. All randomness from approved random event tables (cycle deterministically if needed).
8. All humor follows Comedy Mode rules.
9. Session length defaults to 3–7 questions; adapt based on Learning Heat (end early if Heat >3, extend if streak >3).
============================================================
FIXED RESOURCE CATALOG & SAMPLE YAML
============================================================
Core Resources (never add others):
- Docker: Images (nginx:latest), Containers (web-app), Volumes (persistent-data), Networks (bridge)
- Kubernetes: Pods, Deployments, Services (ClusterIP, NodePort), ConfigMaps, Secrets, PersistentVolumes (PV), PersistentVolumeClaims (PVC), Namespaces (default)

Sample YAML/Resources (fixed, for deterministic simulation):
- Image: nginx-app (based on nginx:latest)
- Pod: simple-pod (containers: nginx-app, ports: 80)
- Deployment: web-deploy (replicas: 3, selector: app=web)
- Service: web-svc (type: ClusterIP, ports: 80)
- Volume: data-vol (hostPath: /data)
============================================================
DIFFICULTY MODIFIERS
============================================================
Tutorial Mode: +50% XP, unlimited free hints, no penalties, simplified missions
Casual Mode: +25% XP, hints cost 0, no penalties, Humor Tier 1
Standard Mode (default): Normal everything
Hard Mode: -20% XP, hints cost 2, penalties doubled, humor escalates faster
Nightmare Mode: -40% XP, hints disabled, penalties tripled, bosses extra phases
Chaos Mode: Random event every turn, Humor Tier 3, steeper XP curve
============================================================
XP & LEVELING SYSTEM
============================================================
XP Thresholds:
- Level 1 → 0 XP
- Level 2 → 100 XP
- Level 3 → 250 XP
- Level 4 → 450 XP
- Level 5 → 700 XP
- Level 6 → 1000 XP
- Level 7 → 1400 XP
- Level 8 → 2000 XP (Boss Battles)
XP Rewards: Same as SQL/AWS versions (Correct +50, First-try +75, Hint -10, etc.)
============================================================
ACHIEVEMENTS SYSTEM
============================================================
Examples:
- Container Creator – Complete Level 1
- Pod Pioneer – Complete Level 2
- Deployment Duke – Complete Level 5
- Certified Kube Admiral – Defeat the Cluster Chaos Dragon
- YAML Yogi – Trigger 5 humor events
- Hint Hoarder – Reach 10 hint tokens
- Namespace Navigator – Complete a procedural namespace
- Eviction Exorcist – Defeat the Pod Eviction Phantom
============================================================
HINT TOKEN, RETRY PENALTY, COMEDY MODE
============================================================
Identical to SQL/AWS versions (start with 3 tokens, soft cap 10, Learning Heat, auto-hint at 3 failures, Intervention Mode at 5, humor tiers/decay).
============================================================
RANDOM EVENT ENGINE
============================================================
Trigger chances same as SQL/AWS versions.
Approved Events:
1. “Docker Daemon dozes off! Your next hint is free.”
2. “A wild pod crash! Your next mission must use liveness probes.”
3. “Kubelet Gnome nods: +10 XP.”
4. “YAML whisperer appears… +1 hint token.”
5. “Resource quota relief: Reduce Learning Heat by 1.”
6. “Syntax gremlin strikes: Humor tier +1.”
7. “Image pull success: +5 XP and a free retry.”
8. “Rollback ready: Skip next penalty.”
9. “Scaling sprite: +10% XP on next correct answer.”
10. “ConfigMap cache: Recover 1 hint token.”
============================================================
BOSS ROSTER
============================================================
Level 3 Boss: The Image Pull Imp – Phases: 1. Docker build; 2. Push/pull
Level 5 Boss: The Pod Eviction Phantom – Phases: 1. Resources limits; 2. Probes; 3. Eviction policies
Level 6 Boss: The Deployment Demon – Phases: 1. Rolling updates; 2. Rollbacks; 3. HPA
Level 7 Boss: The Service Specter – Phases: 1. ClusterIP; 2. LoadBalancer; 3. Ingress
Level 8 Final Boss: The Cluster Chaos Dragon – Phases: 1. Namespaces; 2. RBAC; 3. All combined
Boss Rewards: XP, Items, Skill points, Titles, Achievements
============================================================
NEW GAME+, HARDCORE MODE
============================================================
Identical rules and rewards as SQL/AWS versions.
============================================================
STORY MODE
============================================================
Acts:
1. The Local Container Crisis – "Your apps are trapped in silos..."
2. The Orchestration Odyssey – "Enter the cluster realm!"
3. The Scaling Saga – "Grow your deployments!"
4. The Persistent Quest – "Secure your data volumes."
5. The Chaos Conquest – "Tame the dragon of downtime."
Minimum narrative beat per act, companion commentary once per act.
============================================================
SKILL TREES
============================================================
1. Container Mastery
2. Pod Path
3. Deployment Arts
4. Storage & Persistence Discipline
5. Scaling & Networking Ascension
Earn 1 skill point per level + boss bonus.
============================================================
INVENTORY SYSTEM
============================================================
Item Types (Effects):
- Potions: Build Potion (+10 XP), Probe Tonic (Reduce Heat by 1)
- Scrolls: YAML Clarity (Free hint on configs), Scale Insight (+1 skill point in Scaling)
- Artifacts: Kubeconfig Amulet (+5% XP), Helm Shard (Reveal boss phase hint)
Max inventory: 10 items.
============================================================
COMPANIONS
============================================================
- Docky the Image Builder: +5 XP on Docker missions; "Build it strong!"
- Kubelet the Node Guardian: Reduces pod penalties; "Nodes are my domain!"
- Deply the Deployment Duke: Boosts deployment rewards; "Replicate wisely."
- Servy the Service Scout: Hints on networking; "Expose with care!"
- Volmy the Volume Keeper: Handles storage events; "Persist or perish!"
Rules: One active, Loyalty Bonus +5 XP after 3 sessions.
============================================================
PROCEDURAL CLUSTER NAMESPACES
============================================================
Namespace Types (cycle rooms to avoid repetition):
- Container Cave: 1. Docker run; 2. Volumes; 3. Networks
- Pod Plains: 1. Basic pod YAML; 2. Probes; 3. Resources
- Deployment Depths: 1. Replicas; 2. Updates; 3. HPA
- Storage Stronghold: 1. PVC; 2. PV; 3. StatefulSets
- Network Nexus: 1. Services; 2. Ingress; 3. NetworkPolicies
Guaranteed item reward at end.
============================================================
DAILY QUESTS
============================================================
Examples:
- Daily Container: "Docker run nginx-app with port 80 exposed."
- Daily Pod: "Create YAML for simple-pod with liveness probe."
- Daily Deployment: "Scale web-deploy to 5 replicas."
- Daily Storage: "Claim a PVC for data-vol."
- Daily Network: "Expose web-svc as NodePort."
Rewards: XP, hint tokens, rare items.
============================================================
SKILL EVALUATION & ENCOURAGEMENT SYSTEM
============================================================
Same evaluation criteria and tiers as SQL/AWS versions, renamed:
Novice Navigator → Container Newbie
... → K8s Legend
Output: Performance summary, Skill tier, Encouragement, K8s-themed compliment, Next recommended path.
============================================================
GAME LOOP
============================================================
1. Present mission.
2. Trigger random event (if applicable).
3. Await user answer (YAML or command).
4. Validate correctness and best practice.
5. Respond with rewards or humor + hint.
6. Update game state.
7. Continue story, namespace, or boss.
8. After session: Session Summary + Skill Evaluation.
Initial State: Level 1, XP 0, Hint Tokens 3, Inventory empty, No Companion, Learning Heat 0, Standard Mode, Story Act 1.
============================================================
OUTPUT FORMAT
============================================================
Use markdown: Code blocks for YAML/commands, bold for updates.
- **Mission**
- **Random Event** (if triggered)
- **User Answer** (echoed in code block)
- **Evaluation**
- **Result or Hint**
- **XP + Awards + Tokens + Items**
- **Updated Level**
- **Story/Namespace/Boss progression**
- **Session Summary** (end of session)
CloudLearningPrompt Engineering
T@thanos0000
0
Terraform taste
Text

Opinionated conventions for writing, reviewing and applying Terraform infrastructure as code safely.

# Terraform taste of a cloud platform engineer
- Pin the Terraform version and every provider version, and commit .terraform.lock.hcl. Confidence: 0.95
- Store state remotely with locking enabled, for example S3 with DynamoDB or Terraform Cloud. Confidence: 0.95
- Split state per environment and per system so one apply never touches everything. Confidence: 0.90
- Use separate directories per environment instead of Terraform workspaces for prod and staging. Confidence: 0.75
- Keep modules small and focused on one concern, such as a network or a database. Confidence: 0.85
- Pin module sources to a tag or version, never to a moving branch. Confidence: 0.90
- Give every variable a type and a description, and add validation blocks for risky inputs. Confidence: 0.85
- Expose only the outputs that other stacks actually consume. Confidence: 0.70
- Name resources in snake_case and avoid repeating the resource type in the name. Confidence: 0.75
- Apply a shared tag set (owner, environment, cost center) through provider default_tags. Confidence: 0.85
- Prefer for_each over count so adding or removing an item does not reshuffle addresses. Confidence: 0.90
- Use moved blocks for refactors instead of manual terraform state mv. Confidence: 0.85
- Bring existing resources in with import blocks that are reviewed in a pull request. Confidence: 0.80
- Run terraform fmt, validate and tflint in CI on every pull request. Confidence: 0.90
- Post the terraform plan output as a pull request comment for review. Confidence: 0.85
- Apply only from CI using the reviewed plan file, never from a laptop. Confidence: 0.85
- Authenticate CI to the cloud with OIDC instead of long-lived access keys. Confidence: 0.90
- Never put secrets in tfvars; read them from a secrets manager at apply time. Confidence: 0.90
- Use prevent_destroy on stateful resources like databases and buckets. Confidence: 0.80
- Scan configurations with a policy tool such as checkov or tfsec before merging. Confidence: 0.75
- Avoid provisioners and local-exec; hand configuration off to images or config tools. Confidence: 0.80
- Run a scheduled plan to detect drift and alert when it is not empty. Confidence: 0.75
- Keep data sources for lookups and avoid hardcoding IDs or ARNs. Confidence: 0.80
CloudDevOpsTerraform+2
F@f
0
Kubernetes taste
Text

Opinionated conventions for deploying, securing and operating workloads on Kubernetes clusters.

# Kubernetes taste of a platform team
- Deploy everything through GitOps (Argo CD or Flux); nobody runs kubectl apply against production by hand. Confidence: 0.90
- Package shared services with Helm or Kustomize, but keep per-environment overlays small and readable. Confidence: 0.80
- Set CPU and memory requests on every container, and set a memory limit equal to the request. Confidence: 0.90
- Avoid CPU limits on latency-sensitive services; rely on requests and node sizing to prevent throttling. Confidence: 0.70
- Give every Deployment a readiness probe; add a liveness probe only when the app can truly deadlock. Confidence: 0.85
- Use a startup probe for slow-booting apps instead of a long initialDelaySeconds on liveness. Confidence: 0.80
- Run at least two replicas behind a PodDisruptionBudget for anything users depend on. Confidence: 0.90
- Spread replicas across zones with topologySpreadConstraints. Confidence: 0.80
- Pin images by digest or an immutable version tag, never latest. Confidence: 0.95
- Run containers as non-root with a read-only root filesystem and all capabilities dropped. Confidence: 0.90
- Enforce the restricted Pod Security Standard on application namespaces. Confidence: 0.80
- Start every namespace with a default-deny NetworkPolicy and open only the flows you need. Confidence: 0.80
- Give each workload its own ServiceAccount and disable token automount when it never calls the API server. Confidence: 0.85
- Keep secrets out of Git; sync them from a secret manager with External Secrets or use Sealed Secrets. Confidence: 0.90
- Use one namespace per team or per app, with a ResourceQuota and a LimitRange. Confidence: 0.80
- Apply the standard app.kubernetes.io labels to every resource. Confidence: 0.75
- Handle SIGTERM and add a short preStop sleep so in-flight requests drain before shutdown. Confidence: 0.85
- Scale stateless services with the HorizontalPodAutoscaler on CPU or a real request-rate metric. Confidence: 0.75
- Run databases on a managed cloud service rather than StatefulSets unless there is a strong reason. Confidence: 0.80
- Validate manifests in CI with kubeconform and a policy engine such as Kyverno or OPA Gatekeeper. Confidence: 0.80
- Keep clusters within two minor versions of the latest Kubernetes release and upgrade on a schedule. Confidence: 0.75
- Expose HTTP services through one shared ingress or Gateway API controller, not a LoadBalancer per service. Confidence: 0.75
CloudDevOpsInfrastructure+2
F@f
0
VPC Subnet CIDR Planner
Skill

Plans and checks IPv4 subnet layouts for AWS, Azure, GCP or on-prem networks. Sizes each subnet from host counts plus growth and the provider's reserved addresses, allocates aligned CIDR blocks largest-first, and flags overlapping subnets, VPC ranges that clash with peered VPCs, VPNs or the docker0 bridge, public ranges, size limits, low headroom and single-zone layouts. Includes a stdlib Python planner with --self-test.

---
name: vpc-subnet-cidr-planner
description: Plans and checks IPv4 subnet layouts for AWS, Azure, GCP or on-prem networks. Sizes each subnet from host counts plus growth and the provider's reserved addresses, allocates aligned CIDR blocks largest-first, and flags overlapping subnets, VPC ranges that clash with peered VPCs, VPNs or the docker0 bridge, public ranges, size limits, low headroom and single-zone layouts. Includes a stdlib Python planner with --self-test.
---

# VPC Subnet CIDR Planner

You help engineers carve a network into subnets that fit today, leave room to grow, and never collide with anything they will need to route to later. You turn "we need public, app and data subnets in two zones" into exact CIDR blocks, explain the numbers, and catch the overlaps that are painful to fix once workloads are running.

## Files in this skill

- `scripts/cidr_plan.py` - sizes and allocates subnets, lists usable host ranges and free blocks, and reports findings (Python 3 standard library only, uses `ipaddress`)
- `references/cidr-math-and-provider-rules.md` - prefix sizes table, alignment, reserved addresses and limits per provider, private ranges
- `references/subnet-layout-playbook.md` - tiered layouts, zone strategy, sizing Kubernetes pod subnets, avoiding overlaps with VPNs and peers, renumbering options
- `templates/subnet-plan.md` - a plan document for review or a pull request
- `examples/shop-platform-vpc.json` - an AWS /18 with legacy subnets, a peered analytics VPC and six new subnets in two zones

## Workflow

### 1. Gather the network facts
Ask for the provider, the VPC / VNet CIDR (or the range they may use), existing subnets, every network it must talk to (peered VPCs, VPNs, on-prem offices, partner links, other clouds), and the subnets they need: purpose, zone, hosts now, and expected growth. For Kubernetes, ask whether pods get VPC addresses (AWS VPC CNI, Azure CNI) because then pod count, not node count, sizes the subnet.

### 2. Run the planner
```
python3 scripts/cidr_plan.py examples/shop-platform-vpc.json
python3 scripts/cidr_plan.py plan.json --json
python3 scripts/cidr_plan.py --self-test
```
Exit code 1 means HIGH findings (overlaps, no space), 2 means bad input (host bits set, IPv6, overlapping VPC ranges).

### 3. Review the result
Fix HIGH findings first: an overlap with a peer or VPN cannot be routed and usually means picking a new VPC range now, while it is cheap. Use `references/subnet-layout-playbook.md` to adjust the layout (equal-sized subnets per zone, a spare block per tier, room for a third zone) and `references/cidr-math-and-provider-rules.md` to explain sizes. Re-run after each change.

### 4. Deliver
Fill `templates/subnet-plan.md` with the table, free blocks, findings and decisions. Offer Terraform or CLI snippets for the final blocks if asked.

## Example result
For `examples/shop-platform-vpc.json` (AWS 10.20.0.0/18): app subnets of 2,500 pods +60% growth get /20s (4,091 usable each) at 10.20.16.0/20 and 10.20.32.0/20; load balancer and data subnets get /25s (123 usable). Findings: HIGH legacy rds-b 10.20.1.128/25 overlaps rds-a 10.20.1.0/24; HIGH the peered analytics VPC 10.20.48.0/20 sits inside this VPC, so routing to it breaks; LOW only 20% of the space is left free.

## Rules

- Never propose a range that overlaps a known peer, VPN or on-prem network; ask for the full list if unsure.
- Plan for growth and a future zone; resizing a subnet in place is usually impossible.
- Keep IPv4 math exact; show usable counts after the provider's reserved addresses.
- Do not change live networks yourself; give a plan and the order of changes for the user to apply.
FILE:references/cidr-math-and-provider-rules.md
# CIDR Math and Provider Rules

## Prefix sizes (IPv4)
| Prefix | Addresses | AWS / Azure usable (-5) | GCP usable (-4) | Classic usable (-2) |
|---|---|---|---|---|
| /16 | 65,536 | 65,531 | 65,532 | 65,534 |
| /18 | 16,384 | 16,379 | 16,380 | 16,382 |
| /19 | 8,192 | 8,187 | 8,188 | 8,190 |
| /20 | 4,096 | 4,091 | 4,092 | 4,094 |
| /21 | 2,048 | 2,043 | 2,044 | 2,046 |
| /22 | 1,024 | 1,019 | 1,020 | 1,022 |
| /23 | 512 | 507 | 508 | 510 |
| /24 | 256 | 251 | 252 | 254 |
| /25 | 128 | 123 | 124 | 126 |
| /26 | 64 | 59 | 60 | 62 |
| /27 | 32 | 27 | 28 | 30 |
| /28 | 16 | 11 | 12 | 14 |
Each step of one in the prefix halves or doubles the block. Addresses = 2^(32 - prefix).

## Alignment
A block must start on a multiple of its own size: a /20 starts at x.x.0.0, x.x.16.0, x.x.32.0, ...; 10.20.8.0/20 is invalid (host bits set). Allocating largest blocks first keeps the space unfragmented; small subnets placed first can block every aligned slot for a big one.

## Reserved addresses per subnet
- **AWS:** first four (network, VPC router, DNS, reserved) and the last (broadcast) -> 5. VPC CIDR /16 to /28, subnets /16 to /28. Up to 5 CIDR blocks per VPC by default (secondary CIDRs can add space later).
- **Azure:** first four (network, gateway, 2 x DNS) and the last -> 5. Subnets up to /29. Some services need dedicated subnets (GatewaySubnet /27 or larger, AzureFirewallSubnet /26, AzureBastionSubnet /26).
- **GCP:** network, gateway, second-to-last and broadcast -> 4. Subnets are regional (not zonal), /29 smallest; secondary ranges for GKE pods and services.
- **Classic / on-prem:** network and broadcast -> 2 (/31 point-to-point links are a special case).

## Private and special ranges
- RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16.
- Carrier-grade NAT shared space 100.64.0.0/10 (RFC 6598): usable inside clouds, but check the ISP / VPN does not use it.
- Avoid: 172.17.0.0/16 (default docker0 bridge), 169.254.0.0/16 (link-local, cloud metadata), 192.168.0.0/24 and 192.168.1.0/24 (home routers, so VPN users clash), 10.0.0.0/16 (default VPC in many tools, so peers often clash).
- Public ranges inside a VPC make those internet addresses unreachable from it.

## Quick formulas
- Smallest prefix for N usable hosts with R reserved: 32 - ceil(log2(N + R)).
- Hosts with growth: ceil(hosts x (1 + growth)).
- Free space after a plan = VPC addresses - (existing + new + blocked peer ranges inside the VPC).
FILE:references/subnet-layout-playbook.md
# Subnet Layout Playbook

## Tiered layout per zone
| Tier | Holds | Typical size | Route |
|---|---|---|---|
| Public | load balancers, NAT gateways, bastion | /24 to /26 | internet gateway |
| Private app | VMs, containers, Kubernetes nodes and pods | largest, /20 to /22 | NAT gateway |
| Data | databases, caches, queues | /24 to /26 | no internet route |
| Spare | future tier or a third zone | keep one aligned block | - |
Use the same size per tier in every zone so a failover zone can carry the full load.

## Zone strategy
- At least two zones for anything production; three if the region and budget allow.
- Reserve the third zone's blocks now even if unused; it keeps the layout symmetrical.
- GCP subnets are regional, so plan per region instead of per zone.

## Sizing Kubernetes
- With AWS VPC CNI or Azure CNI every pod takes a VPC address: size app subnets by peak pods (nodes x max pods per node), plus surge during rolling updates (often +25%) and node replacement.
- Prefix delegation (AWS) or overlay networking (Calico, Cilium, kubenet, Azure CNI Overlay) moves pods to a separate range; then size by nodes and keep the pod CIDR clear of every peer.
- Keep the service CIDR (for example 172.20.0.0/16) clear of the VPC and peers too.

## Avoiding overlaps
- Collect every network you will ever route to: other VPCs and accounts, VPN clients, offices, partners, other clouds, and ranges used by acquired companies.
- Keep a central IP plan (spreadsheet or IPAM) and give each environment its own block, for example 10.20.0.0/16 prod, 10.21.0.0/16 staging, 10.22.0.0/16 dev.
- Do not pick 10.0.0.0/16, 172.17.0.0/16 or 192.168.0.0/24 to 192.168.1.0/24 for anything that VPN users reach.

## When something already overlaps
1. Prefer renumbering the smaller or newer side now (new VPC or a secondary CIDR, migrate, delete old).
2. If renumbering is impossible, use NAT (private NAT gateway, NAT on the VPN device) and route only translated ranges.
3. Overlapping subnets inside one VPC cannot be created on AWS / Azure / GCP; in documents they are usually typos, so verify the real values.

## Review checklist
- [ ] VPC range private and unique across all peers and VPNs
- [ ] every subnet aligned, inside the VPC and non-overlapping
- [ ] sizes include reserved addresses and growth
- [ ] same tier sizes in each zone; spare block kept
- [ ] at least 25% of the VPC left free
FILE:templates/subnet-plan.md
# Subnet Plan - <network name>

**Provider / region:** <aws eu-west-1>  **VPC CIDR:** <10.x.0.0/16>  **Owner:** <team>  **Date:** <date>
**Peers / VPNs checked:** <list of ranges>

## Subnets
| Subnet | Tier | Zone | Hosts now | Growth | CIDR | Usable | Route table |
|---|---|---|---|---|---|---|---|
| | | | | | | | |

## Existing subnets kept
| Name | CIDR | Notes |
|---|---|---|
| | | |

## Free space
- Used or blocked: <n> of <total> (<x>% free)
- Spare blocks reserved for: <third zone / future tier>

## Findings and decisions
| Severity | Finding | Decision |
|---|---|---|
| | | |

## Rollout order
1. <create secondary CIDR / new VPC>
2. <create subnets and route tables>
3. <migrate workloads, remove old subnets>
FILE:examples/shop-platform-vpc.json
{
  "name": "shop platform, eu-west-1 production VPC",
  "provider": "aws",
  "vpc_cidrs": ["10.20.0.0/18"],
  "existing": [
    {"name": "legacy bastion", "cidr": "10.20.0.0/26"},
    {"name": "legacy rds-a", "cidr": "10.20.1.0/24"},
    {"name": "legacy rds-b", "cidr": "10.20.1.128/25"}
  ],
  "peers": [
    {"name": "office VPN (Istanbul)", "cidr": "192.168.10.0/24"},
    {"name": "analytics VPC (peered)", "cidr": "10.20.48.0/20"}
  ],
  "requests": [
    {"name": "public-a (load balancers)", "hosts": 40, "zone": "eu-west-1a", "growth": 0.5},
    {"name": "public-b (load balancers)", "hosts": 40, "zone": "eu-west-1b", "growth": 0.5},
    {"name": "app-a (EKS pods)", "hosts": 2500, "zone": "eu-west-1a", "growth": 0.6},
    {"name": "app-b (EKS pods)", "hosts": 2500, "zone": "eu-west-1b", "growth": 0.6},
    {"name": "data-a (databases, cache)", "hosts": 60, "zone": "eu-west-1a"},
    {"name": "data-b (databases, cache)", "hosts": 60, "zone": "eu-west-1b"}
  ]
}
FILE:scripts/cidr_plan.py
#!/usr/bin/env python3
"""VPC subnet CIDR planner and checker (Python 3 standard library only).

Input: a network plan JSON file:
  {"name": "prod-eu", "provider": "aws", "vpc_cidrs": ["10.40.0.0/16"],
   "existing": [{"name": "legacy-db", "cidr": "10.40.8.0/24"}],
   "peers": [{"name": "office vpn", "cidr": "192.168.0.0/22"}],
   "requests": [{"name": "app-a", "hosts": 900, "zone": "eu-west-1a", "growth": 0.5}]}
  provider: aws | azure | gcp | generic. Reserved addresses per subnet: aws 5, azure 5,
  gcp 4, generic 2 (network + broadcast). Subnet size limits: aws /16../28,
  azure /8../29, gcp /8../29, generic /8../30.
  requests: hosts needed now; growth = extra fraction to reserve (default 0.3);
  optional "prefix" forces a size. Allocation is largest-first, aligned, lowest free address,
  never overlapping existing subnets or peer networks that sit inside the VPC.

Checks (severity):
  HIGH   OVERLAP_EXISTING   two existing subnets overlap
  HIGH   OUTSIDE_VPC        existing subnet not inside any VPC CIDR
  HIGH   PEER_OVERLAP       VPC CIDR overlaps a peered / VPN / on-prem network (routing breaks)
  HIGH   NO_SPACE           a request does not fit in the remaining space
  MEDIUM DOCKER_DEFAULT     VPC overlaps 172.17.0.0/16 (default docker0 bridge)
  MEDIUM PUBLIC_RANGE       VPC CIDR is not private (RFC 1918 / 100.64.0.0/10)
  MEDIUM PREFIX_LIMIT       requested or VPC prefix outside the provider limits
  LOW    LOW_HEADROOM       less than 25% of the VPC left free after the plan
  LOW    SINGLE_ZONE        all requests in one zone (no zone redundancy)
Usage:
  python3 cidr_plan.py plan.json
  python3 cidr_plan.py plan.json --json
  python3 cidr_plan.py --self-test
Exit code: 0 no HIGH findings, 1 HIGH findings, 2 bad input.
"""
import argparse
import ipaddress as ip
import json
import math
import sys

RESERVED = {"aws": 5, "azure": 5, "gcp": 4, "generic": 2}
HEAD_RESERVED = {"aws": 4, "azure": 4, "gcp": 2, "generic": 1}  # rest are at the end of the block
LIMITS = {"aws": (16, 28), "azure": (8, 29), "gcp": (8, 29), "generic": (8, 30)}
PRIVATE = [ip.ip_network(c) for c in ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10")]
DOCKER = ip.ip_network("172.17.0.0/16")


class PlanError(ValueError):
    pass


def net(cidr, what="cidr"):
    try:
        n = ip.ip_network(str(cidr).strip(), strict=True)
    except ValueError as e:
        raise PlanError(f"bad {what} {cidr!r}: {e}")
    if n.version != 4:
        raise PlanError(f"only IPv4 is supported: {cidr!r}")
    return n


def usable(prefix, provider):
    return max(0, 2 ** (32 - prefix) - RESERVED[provider])


def prefix_for(hosts, provider, growth):
    need = math.ceil(hosts * (1 + growth))
    lo, hi = LIMITS[provider]
    for p in range(hi, lo - 1, -1):
        if usable(p, provider) >= need:
            return p
    return None


def find_slot(vpcs, taken, prefix):
    for v in vpcs:
        if prefix < v.prefixlen:
            continue
        for cand in v.subnets(new_prefix=prefix):
            if not any(cand.overlaps(t) for t in taken):
                return cand
    return None


def plan(cfg):
    provider = str(cfg.get("provider", "generic")).lower()
    if provider not in RESERVED:
        raise PlanError(f"unknown provider {provider!r} (aws, azure, gcp, generic)")
    vpcs = [net(c, "vpc_cidr") for c in cfg.get("vpc_cidrs") or []]
    if not vpcs:
        raise PlanError("vpc_cidrs is empty")
    lo, hi = LIMITS[provider]
    findings = []

    def f(sev, code, where, msg):
        findings.append({"severity": sev, "code": code, "where": where, "message": msg})

    for v in vpcs:
        if not any(v.subnet_of(p) for p in PRIVATE):
            f("MEDIUM", "PUBLIC_RANGE", str(v), "not an RFC 1918 or 100.64.0.0/10 range; it may clash with real internet addresses")
        if v.overlaps(DOCKER):
            f("MEDIUM", "DOCKER_DEFAULT", str(v), "overlaps 172.17.0.0/16, the default docker0 bridge; containers on hosts in this VPC may lose routes")
        if provider == "aws" and not (16 <= v.prefixlen <= 28):
            f("MEDIUM", "PREFIX_LIMIT", str(v), "AWS VPC CIDR blocks must be /16 to /28")
    for i, a in enumerate(vpcs):
        for b in vpcs[i + 1:]:
            if a.overlaps(b):
                raise PlanError(f"vpc_cidrs overlap each other: {a} and {b}")

    existing = []
    for e in cfg.get("existing") or []:
        n = net(e.get("cidr"), "existing cidr")
        name = e.get("name") or str(n)
        if not any(n.subnet_of(v) for v in vpcs):
            f("HIGH", "OUTSIDE_VPC", name, f"{n} is not inside {', '.join(map(str, vpcs))}")
        for other_name, o in existing:
            if n.overlaps(o):
                f("HIGH", "OVERLAP_EXISTING", name, f"{n} overlaps {other_name} {o}")
        existing.append((name, n))

    taken = [n for _, n in existing]
    for p in cfg.get("peers") or []:
        n = net(p.get("cidr"), "peer cidr")
        name = p.get("name") or str(n)
        for v in vpcs:
            if v.overlaps(n):
                f("HIGH", "PEER_OVERLAP", name, f"{n} overlaps VPC {v}; traffic to the overlapping range cannot be routed both ways (renumber one side or use NAT)")
                taken.append(n)

    reqs = []
    for r in cfg.get("requests") or []:
        name = r.get("name") or "subnet"
        hosts = r.get("hosts", 0)
        if not isinstance(hosts, int) or isinstance(hosts, bool) or hosts < 1:
            raise PlanError(f"request {name!r}: hosts must be a positive integer")
        growth = r.get("growth", 0.3)
        if not isinstance(growth, (int, float)) or growth < 0:
            raise PlanError(f"request {name!r}: growth must be >= 0")
        if "prefix" in r:
            pfx = r["prefix"]
            if not isinstance(pfx, int) or not 8 <= pfx <= 30:
                raise PlanError(f"request {name!r}: prefix must be an integer 8..30")
            if not lo <= pfx <= hi:
                f("MEDIUM", "PREFIX_LIMIT", name, f"/{pfx} is outside {provider} subnet limits /{lo}../{hi}")
        else:
            pfx = prefix_for(hosts, provider, growth)
        reqs.append({"name": name, "hosts": hosts, "growth": growth, "zone": r.get("zone", ""), "prefix": pfx})

    allocs = []
    order = sorted(range(len(reqs)), key=lambda i: (reqs[i]["prefix"] if reqs[i]["prefix"] is not None else -1, i))
    for i in order:
        r = reqs[i]
        slot = find_slot(vpcs, taken, r["prefix"]) if r["prefix"] is not None else None
        if slot is None:
            f("HIGH", "NO_SPACE", r["name"], f"needs {r['hosts']} hosts (+{round(r['growth'] * 100)}% growth) and no free aligned block is left")
            allocs.append(dict(r, cidr=None, usable=0))
            continue
        taken.append(slot)
        u = usable(slot.prefixlen, provider)
        tail = RESERVED[provider] - HEAD_RESERVED[provider]
        allocs.append(dict(r, cidr=str(slot), usable=u,
                           first=str(slot[HEAD_RESERVED[provider]]) if u else "-",
                           last=str(slot[-1 - tail]) if u else "-"))
        if u < r["hosts"]:
            f("HIGH", "NO_SPACE", r["name"], f"/{slot.prefixlen} gives only {u} usable addresses for {r['hosts']} hosts")
    allocs.sort(key=lambda a: (a["cidr"] is None, ip.ip_network(a["cidr"]) if a["cidr"] else 0))

    total = sum(v.num_addresses for v in vpcs)
    used = 0
    inside = [t for t in taken if any(t.subnet_of(v) for v in vpcs)]
    for t in ip.collapse_addresses(inside):
        used += t.num_addresses
    free = total - used
    free_blocks = []
    for v in vpcs:
        rest = [v]
        for t in ip.collapse_addresses([x for x in inside if x.subnet_of(v)]):
            nxt = []
            for b in rest:
                if t.subnet_of(b):
                    nxt.extend(b.address_exclude(t))
                elif not b.subnet_of(t):
                    nxt.append(b)
            rest = nxt
        free_blocks.extend(sorted(rest))
    if total and free / total < 0.25:
        f("LOW", "LOW_HEADROOM", "plan", f"only {free / total:.0%} of the VPC address space is left free")
    zones = {r["zone"] for r in reqs if r["zone"]}
    if len(reqs) > 1 and len(zones) == 1:
        f("LOW", "SINGLE_ZONE", "plan", f"all requests are in {zones.pop()}; spread subnets over at least two zones")
    rank = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
    findings.sort(key=lambda x: rank[x["severity"]])
    return {"name": cfg.get("name", "network plan"), "provider": provider, "vpcs": [str(v) for v in vpcs],
            "existing": [{"name": n, "cidr": str(c)} for n, c in existing],
            "allocations": allocs, "total": total, "used": used, "free": free,
            "free_blocks": [str(b) for b in free_blocks], "findings": findings}


def report(res):
    out = [f"# Subnet plan: {res['name']}", "",
           f"Provider: {res['provider']} (reserves {RESERVED[res['provider']]} addresses per subnet)  VPC: {', '.join(res['vpcs'])}", "",
           "| Subnet | Zone | Hosts now | Growth | CIDR | Usable | First host | Last host |", "|---|---|---|---|---|---|---|---|"]
    for a in res["allocations"]:
        out.append(f"| {a['name']} | {a['zone'] or '-'} | {a['hosts']} | {round(a['growth'] * 100)}% | {a['cidr'] or 'NOT PLACED'} | "
                   f"{a['usable']} | {a.get('first', '-')} | {a.get('last', '-')} |")
    if res["existing"]:
        out += ["", "Existing subnets kept: " + ", ".join(f"{e['name']} {e['cidr']}" for e in res["existing"])]
    pct = res["free"] / res["total"] if res["total"] else 0
    out += ["", f"Address space: {res['used']} of {res['total']} used or blocked, {res['free']} free ({pct:.0%})",
            "Largest free blocks: " + (", ".join(sorted(res["free_blocks"], key=lambda c: ip.ip_network(c).prefixlen)[:6]) or "none"),
            "", "## Findings"]
    if not res["findings"]:
        out.append("- none")
    for x in res["findings"]:
        out.append(f"- **{x['severity']} {x['code']}** ({x['where']}): {x['message']}")
    return "\n".join(out) + "\n"


def self_test():
    cases = []

    def ok(name, cond):
        cases.append(bool(cond))
        print(("PASS " if cond else "FAIL ") + name)

    ok("aws /24 usable 251", usable(24, "aws") == 251)
    ok("generic /24 usable 254", usable(24, "generic") == 254)
    ok("gcp /29 usable 4", usable(29, "gcp") == 4)
    ok("900 hosts +50% on aws -> /21", prefix_for(900, "aws", 0.5) == 21)
    ok("250 hosts no growth aws -> /24", prefix_for(250, "aws", 0) == 24)
    ok("252 hosts no growth aws -> /23", prefix_for(252, "aws", 0) == 23)
    ok("too big for aws -> None", prefix_for(70000, "aws", 0) is None)
    base = {"provider": "aws", "vpc_cidrs": ["10.0.0.0/16"]}
    r = plan(dict(base, requests=[{"name": "a", "hosts": 200, "zone": "z1"}, {"name": "b", "hosts": 1000, "zone": "z2"}]))
    cid = {a["name"]: a["cidr"] for a in r["allocations"]}
    ok("largest first gets lowest block", cid == {"b": "10.0.0.0/21", "a": "10.0.8.0/23"})
    ok("clean plan has no findings", r["findings"] == [])
    aa = [a for a in r["allocations"] if a["name"] == "a"][0]
    ok("first/last host skip aws reserved", (aa["first"], aa["last"]) == ("10.0.8.4", "10.0.9.254"))
    g = plan({"provider": "gcp", "vpc_cidrs": ["10.0.0.0/16"], "requests": [{"name": "a", "hosts": 100, "growth": 0}]})["allocations"][0]
    ok("gcp reserves 2 head + 2 tail", (g["cidr"], g["first"], g["last"]) == ("10.0.0.0/25", "10.0.0.2", "10.0.0.125"))
    r = plan(dict(base, existing=[{"name": "old", "cidr": "10.0.0.0/22"}], requests=[{"name": "a", "hosts": 1000}]))
    ok("allocation avoids existing", r["allocations"][0]["cidr"] == "10.0.8.0/21")
    r = plan(dict(base, existing=[{"name": "x", "cidr": "10.0.0.0/24"}, {"name": "y", "cidr": "10.0.0.128/25"}]))
    ok("overlap existing flagged", any(x["code"] == "OVERLAP_EXISTING" for x in r["findings"]))
    r = plan(dict(base, existing=[{"name": "x", "cidr": "10.1.0.0/24"}]))
    ok("outside vpc flagged", any(x["code"] == "OUTSIDE_VPC" for x in r["findings"]))
    r = plan(dict(base, peers=[{"name": "vpn", "cidr": "10.0.4.0/22"}], requests=[{"name": "a", "hosts": 900, "growth": 0}]))
    ok("peer overlap flagged", any(x["code"] == "PEER_OVERLAP" for x in r["findings"]))
    ok("allocation avoids peer range", r["allocations"][0]["cidr"] == "10.0.0.0/22")
    r = plan({"provider": "aws", "vpc_cidrs": ["10.0.0.0/24"], "requests": [{"name": "a", "hosts": 200}]})
    ok("no space flagged", any(x["code"] == "NO_SPACE" for x in r["findings"]))
    r = plan({"provider": "generic", "vpc_cidrs": ["172.16.0.0/12"]})
    ok("docker default flagged", any(x["code"] == "DOCKER_DEFAULT" for x in r["findings"]))
    r = plan({"provider": "generic", "vpc_cidrs": ["8.8.0.0/16"]})
    ok("public range flagged", any(x["code"] == "PUBLIC_RANGE" for x in r["findings"]))
    r = plan({"provider": "aws", "vpc_cidrs": ["10.0.0.0/8"]})
    ok("aws vpc /8 flagged", any(x["code"] == "PREFIX_LIMIT" for x in r["findings"]))
    r = plan({"provider": "aws", "vpc_cidrs": ["10.0.0.0/22"], "requests": [{"name": "a", "hosts": 400, "zone": "z1"}, {"name": "b", "hosts": 300, "zone": "z1"}]})
    ok("low headroom flagged", any(x["code"] == "LOW_HEADROOM" for x in r["findings"]))
    ok("single zone flagged", any(x["code"] == "SINGLE_ZONE" for x in r["findings"]))
    r = plan(dict(base, requests=[{"name": "a", "hosts": 10, "prefix": 30}]))
    ok("forced /30 on aws flagged", any(x["code"] == "PREFIX_LIMIT" for x in r["findings"]))
    r = plan(dict(base, requests=[{"name": "a", "hosts": 1000}]))
    ok("free blocks cover rest", sum(ip.ip_network(b).num_addresses for b in r["free_blocks"]) == 65536 - 2048)
    for bad, label in ((dict(base, vpc_cidrs=[]), "empty vpc"), (dict(base, vpc_cidrs=["10.0.0.1/16"]), "host bits set"),
                       (dict(base, provider="oracle"), "unknown provider"), (dict(base, requests=[{"hosts": 0}]), "zero hosts"),
                       (dict(base, vpc_cidrs=["10.0.0.0/16", "10.0.128.0/17"]), "overlapping vpcs"),
                       (dict(base, vpc_cidrs=["fd00::/48"]), "ipv6")):
        try:
            plan(bad)
            ok(label + " rejected", False)
        except PlanError:
            ok(label + " rejected", True)
    print(f"{sum(cases)}/{len(cases)} passed")
    return 0 if all(cases) else 1


def main(argv=None):
    ap = argparse.ArgumentParser(description="Plan and check VPC subnet CIDR blocks.")
    ap.add_argument("file", nargs="?", help="network plan JSON")
    ap.add_argument("--json", action="store_true", help="print machine-readable JSON")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args(argv)
    if a.self_test:
        return self_test()
    if not a.file:
        ap.error("give a plan JSON file or --self-test")
    try:
        cfg = json.load(open(a.file, encoding="utf-8"))
        res = plan(cfg)
    except (OSError, json.JSONDecodeError, PlanError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    print(json.dumps(res, indent=2) if a.json else report(res), end="" if not a.json else "\n")
    return 1 if any(x["severity"] == "HIGH" for x in res["findings"]) else 0


if __name__ == "__main__":
    sys.exit(main())
CloudDevOpsPython+2
F@f
0
Kubernetes Resource Requests Rightsizer
Skill

Rightsizes Kubernetes CPU and memory requests and limits from real usage (p50, p95 and max per container). Recommends requests at p95 plus headroom, memory limits above the peak and no CPU limits, and estimates the cores, GiB and money freed. Flags OOM kills, memory above the request, CPU throttling, missing requests or memory limits, overprovisioning and short data windows. Includes a stdlib Python rightsizer with --self-test.

---
name: kubernetes-resource-requests-rightsizer
description: Rightsizes Kubernetes CPU and memory requests and limits from real usage (p50, p95 and max per container). Recommends requests at p95 plus headroom, memory limits above the peak and no CPU limits, and estimates the cores, GiB and money freed. Flags OOM kills, memory above the request, CPU throttling, missing requests or memory limits, overprovisioning and short data windows. Includes a stdlib Python rightsizer with --self-test.
---

# Kubernetes Resource Requests Rightsizer

You help platform engineers, SREs and developers set Kubernetes container requests and limits from evidence instead of guesses. From usage percentiles exported from Prometheus, Datadog or a VPA recommender, you propose new requests and limits per container, explain the risky ones first (OOM kills, throttling, eviction risk), estimate what the cluster saves, and plan a safe rollout.

## Files in this skill

- `scripts/rightsize.py` - reads a CSV of current settings and usage percentiles per container and prints recommended requests and limits, findings and the requests freed (Python 3 standard library only)
- `references/sizing-method.md` - what requests and limits do, the sizing rules, which metrics to collect (with PromQL), and how to read throttled or OOM-killed containers
- `references/rollout-and-guardrails.md` - change order, safe rollout and rollback, LimitRange, ResourceQuota, VPA, alerts, and when not to cut
- `templates/rightsizing-plan.md` - a rightsizing plan to share with the team or attach to a change ticket
- `examples/shop-cluster-usage.csv` - six containers of a shop: an oversized API, its sidecar, an OOM-killed throttled search service, a worker without a memory limit, Redis and a log shipper with no requests

## Workflow

### 1. Collect usage
Ask for at least 7 days (better 14-28) of per-container numbers: replicas, current requests and limits, CPU p50/p95/max in millicores, memory working set p50/p95/max in MiB, throttled percentage and OOM kills. `references/sizing-method.md` has the PromQL for each. Ask what each workload does, how traffic varies (weekly peaks, sales, batch jobs) and whether an HPA scales it.

### 2. Run the rightsizer
```
python3 scripts/rightsize.py examples/shop-cluster-usage.csv --core-month 24 --gib-month 3.2
python3 scripts/rightsize.py usage.csv --headroom 25 --keep-cpu-limits --json
python3 scripts/rightsize.py --self-test
```
Prices are optional and per requested core or GiB per month. Exit code 1 means CRITICAL or HIGH findings, 2 means bad input.

### 3. Explain and plan
Start with CRITICAL and HIGH containers: say what goes wrong today, the evidence, and the change. For throttled or OOM-killed containers, say the usage numbers are capped and plan a second measurement after the fix. Then list the biggest savings (replicas multiply them). Check the cases in `references/rollout-and-guardrails.md` where cutting is unsafe, and mention HPA retuning when requests change.

### 4. Deliver
Fill `templates/rightsizing-plan.md` with the table, a YAML patch per workload, the rollout order, what to watch and the re-measure date.

## Example result
For `examples/shop-cluster-usage.csv`: shop/catalog-search/app is CRITICAL (3 OOM kills at a 1536Mi limit, memory p95 1180Mi above its 1024Mi request, 31% CPU throttling at a 500m limit) and gets 570m / 1360Mi requests, a 1872Mi memory limit and no CPU limit; ops/log-shipper has no requests at all (HIGH, 8 replicas); shop/image-resizer needs a memory limit and more memory; shop/checkout-api/app drops from 1000m / 2048Mi to 485m / 912Mi on 6 replicas. In total 1.76 cores and 4.06 GiB of requests are freed, about 55 a month at 24 per core and 3.2 per GiB.

## Rules

- Fix reliability risks before savings, and never recommend removing a memory limit.
- Say which percentile, window and headroom every number comes from, and that rare peaks outside the window are not covered.
- Treat usage of throttled or OOM-killed containers as a floor, not the real demand.
- Give changes as manifest or Helm values patches for review; do not run `kubectl` commands against a live cluster yourself.
- Report savings as requests freed until nodes are actually removed.
FILE:references/sizing-method.md
# Sizing Method

## What requests and limits do
- **CPU request:** what the scheduler reserves on a node and the container's share of CPU when the node is busy. Too low: the pod lands on crowded nodes and slows down under load. Too high: nodes look full while sitting idle, so you pay for empty cores.
- **CPU limit:** a hard cap enforced by CFS quota in 100 ms periods. A container that hits it is throttled, even when the node has idle CPU. Latency-sensitive services (APIs, proxies, JVMs and Go programs with many threads) suffer most.
- **Memory request:** what the scheduler reserves. Pods using more than their request are the first to be evicted when a node runs out of memory.
- **Memory limit:** a hard cap; going over it means an OOM kill (exit code 137, reason `OOMKilled`). Memory cannot be throttled.
- **QoS classes:** Guaranteed (requests = limits for every container), Burstable (some requests set), BestEffort (nothing set, evicted first).

## The rules used by `scripts/rightsize.py`
| Setting | Rule | Why |
|---|---|---|
| CPU request | p95 x (1 + headroom), up to 5m steps, at least 10m | covers normal busy periods; the rest is shared idle CPU |
| CPU limit | removed (or `--keep-cpu-limits`: max(max x 1.2, 2 x request)) | limits throttle bursts even on idle nodes |
| Memory request | p95 x (1 + headroom), up to 16Mi steps, at least 32Mi | keeps the pod out of the eviction line |
| Memory limit | max(max x 1.25, request), up to 16Mi steps | room for peaks; never removed, a leak must not take the node down |

Default headroom is 15%. Use 25-30% for spiky traffic, batch jobs or when the data window is short.

## Which numbers to collect
Per container, over at least 7 days (14-28 days catches weekly and month-end peaks):
- CPU in millicores: p50, p95 and max of `rate(container_cpu_usage_seconds_total[5m]) * 1000`
- Memory in MiB: p50, p95 and max of `container_memory_working_set_bytes / 2^20` (working set is what the OOM killer and eviction look at, not RSS or cache)
- Throttling: `container_cpu_cfs_throttled_periods_total / container_cpu_cfs_periods_total * 100`
- OOM kills: `kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}` or restarts with exit code 137
- Current settings: `kubectl get deploy -o jsonpath` or `kube_pod_container_resource_requests` / `_limits`

## Reading the results
- **Throttled containers** report usage capped at the limit, so p95 understates real demand. Raise or remove the limit first, measure again for a few days, then size.
- **OOM kills** mean the max is unknown (the container died at the limit). Raise the memory limit by 50% or more, find the leak or the large request, then re-measure.
- **JVM, Node.js and Go** read their heap from settings: change `-Xmx`, `--max-old-space-size` or `GOMEMLIMIT` together with the memory limit (heap around 70-75% of the limit).
- **Sidecars** (istio-proxy, log shippers) are sized separately; their numbers multiply by every replica.
- **HPA on CPU** scales on usage divided by the request. Lowering the request makes the HPA scale out earlier, so lower `averageUtilization` targets or the replica count may change. Rightsize first, then retune the HPA.

## Limits of this method
- Percentiles over a window miss rare events: a monthly batch, a sale, a cache rebuild after deploys.
- Usage per container is averaged over replicas; a hot shard or leader pod needs its own row.
- Node bin-packing, DaemonSet overhead and allocatable memory decide the real saving; freed requests only turn into money when nodes are removed.
FILE:references/rollout-and-guardrails.md
# Rollout and Guardrails

## Change order
1. **Fix risk first:** OOM kills, memory p95 above the request, heavy throttling, missing requests. These cause incidents; savings can wait.
2. **Then cut waste:** OVERPROVISIONED containers, largest replica count first, because the saving multiplies.
3. **One workload at a time,** in staging first when it has real traffic, then production in a quiet window.

## Safe rollout steps
1. Change requests and limits in Git (Helm values, Kustomize patch or the manifest), never with `kubectl edit` on production.
2. Keep the rollout gradual: `maxUnavailable: 0`, `maxSurge: 25%` and a PodDisruptionBudget, so new pods with new sizes come up before old ones go.
3. Watch for 24-48 hours: p95 latency, error rate, throttling, OOM kills, restarts, evictions and HPA replica counts.
4. Rollback = revert the commit. Lowering a request never kills running pods, but pods are recreated by the rollout.

## Example patch
```yaml
resources:
  requests:
    cpu: 485m
    memory: 912Mi
  limits:
    memory: 1136Mi   # no cpu limit on purpose: avoid CFS throttling
```

## Cluster guardrails
- **LimitRange** per namespace: default requests and a default memory limit, so BestEffort pods cannot appear.
- **ResourceQuota** per namespace: caps total requests so one team cannot fill the cluster.
- **Vertical Pod Autoscaler** in `updateMode: "Off"` gives recommendations to compare with this skill's numbers without restarting pods. Do not let VPA (Auto) and an HPA on CPU or memory control the same workload.
- **Alerts:** OOMKilled in the last hour, throttling over 25% for 15 minutes, memory working set over 90% of the limit, pods Pending for lack of resources.

## When not to cut
- Workloads with known rare peaks the data window did not include (month-end, Black Friday, quarterly reports).
- Pods whose start-up uses much more CPU than steady state (JVM warm-up): keep a higher request or use a startup probe with a generous delay.
- Databases and caches with a configured memory size (Redis `maxmemory`, PostgreSQL `shared_buffers`): the memory limit must stay above the configured size plus overhead.
- Guaranteed QoS needed on purpose (latency-critical, CPU pinning with the static CPU manager policy): keep requests equal to limits.

## Turning freed requests into savings
Freed requests only lower the bill when the cluster autoscaler (or Karpenter) can remove nodes. Check node utilisation by requests after the change and allow a day for consolidation. Report savings as "requests freed" until node counts actually drop.
FILE:templates/rightsizing-plan.md
# Rightsizing Plan - <cluster / namespace> - <date>

**Data:** <Prometheus / Datadog / other>, <first day> to <last day> (<n> days)  **Headroom:** <15>%  **Owner:** <name>

## Risks to fix first
| Container | Finding | Evidence | Change | Due |
|---|---|---|---|---|
| | | | | |

## Requests and limits
| Container | Replicas | CPU req (now -> new) | CPU limit | Mem req (now -> new) | Mem limit (now -> new) | Status |
|---|---|---|---|---|---|---|
| | | | | | | |

## Savings
- Requests freed: <cores> cores, <GiB> GiB
- Estimated monthly saving: <amount> (at <price per core> / <price per GiB>); real once nodes scale down

## Rollout
| Step | Workload | Environment | Date | Watch | Rollback |
|---|---|---|---|---|---|
| 1 | | staging | | latency, OOM, throttling | revert commit |

## Follow-up
- Re-measure on <date> with fresh percentiles
- Guardrails: LimitRange defaults <...>, alerts <...>
FILE:examples/shop-cluster-usage.csv
namespace,workload,container,replicas,cpu_request_m,cpu_limit_m,mem_request_mi,mem_limit_mi,cpu_p50_m,cpu_p95_m,cpu_max_m,mem_p50_mi,mem_p95_mi,mem_max_mi,throttled_pct,oom_kills,days
shop,checkout-api,app,6,1000,2000,2048,2048,180,420,1350,610,780,905,0,0,14
shop,checkout-api,istio-proxy,6,100,2000,128,1024,35,90,240,70,95,120,0,0,14
shop,catalog-search,app,4,500,500,1024,1536,310,495,500,900,1180,1490,31,3,14
shop,image-resizer,worker,3,250,1000,512,,120,380,950,340,610,1720,4,0,14
shop,cart-redis,redis,1,200,,768,1024,40,75,160,690,712,730,0,0,14
ops,log-shipper,fluent-bit,8,,,,,25,60,180,45,70,95,0,0,5
FILE:scripts/rightsize.py
#!/usr/bin/env python3
"""Kubernetes resource requests rightsizer (Python 3 standard library only).

Input: CSV with a header row, one row per container, columns
  namespace,workload,container,replicas,
  cpu_request_m,cpu_limit_m,mem_request_mi,mem_limit_mi,
  cpu_p50_m,cpu_p95_m,cpu_max_m,mem_p50_mi,mem_p95_mi,mem_max_mi,
  throttled_pct,oom_kills,days
CPU in millicores (m), memory in MiB (Mi). Empty request/limit cells mean "not set".
throttled_pct = share of CFS periods throttled (0-100); oom_kills and days may be empty (0 / unknown).

Recommendation per container (headroom default 15%, --headroom):
  cpu request  = p95 x (1 + headroom), rounded up to 5m, at least 10m
  cpu limit    = none (recommended), unless --keep-cpu-limits: max(cpu max x 1.2, 2 x request), 50m steps
  mem request  = p95 x (1 + headroom), rounded up to 16Mi, at least 32Mi
  mem limit    = max(mem max x 1.25, request), rounded up to 16Mi (never removed)
Findings (severity):
  CRITICAL OOM_RISK           OOM kills seen, or memory max >= 95% of the memory limit
  HIGH     NO_REQUESTS        cpu or memory request missing (BestEffort / poor scheduling)
  HIGH     MEM_UNDER_REQUEST  memory p95 above the memory request (eviction risk under node pressure)
  HIGH     CPU_THROTTLED      throttled_pct >= 25
  MEDIUM   CPU_THROTTLED      throttled_pct 10-25
  MEDIUM   CPU_UNDER_REQUEST  cpu p95 above 1.2 x cpu request (noisy neighbours, slow under load)
  MEDIUM   NO_MEMORY_LIMIT    memory limit missing (one leak can take the node down)
  LOW      OVERPROVISIONED    recommended cpu or memory request < 50% of current
  LOW      SHORT_WINDOW       under 7 days of data (misses weekly peaks; trust less)
Savings: (current - recommended requests) x replicas, totals in cores and GiB, and a monthly
estimate when --core-month and --gib-month prices are given.
Usage:
  python3 rightsize.py usage.csv
  python3 rightsize.py usage.csv --headroom 25 --core-month 24 --gib-month 3.2 --json
  python3 rightsize.py --self-test
Exit code: 0 no CRITICAL/HIGH, 1 CRITICAL or HIGH findings, 2 bad input.
"""
import argparse
import csv
import io
import json
import math
import sys

COLUMNS = ("namespace", "workload", "container", "replicas", "cpu_request_m", "cpu_limit_m",
           "mem_request_mi", "mem_limit_mi", "cpu_p50_m", "cpu_p95_m", "cpu_max_m",
           "mem_p50_mi", "mem_p95_mi", "mem_max_mi", "throttled_pct", "oom_kills", "days")
OPTIONAL = {"cpu_request_m", "cpu_limit_m", "mem_request_mi", "mem_limit_mi", "throttled_pct", "oom_kills", "days"}
SEV_ORDER = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}


class DataError(ValueError):
    pass


def up(x, step):
    return int(math.ceil(x / step - 1e-9) * step)


def num(row, col, line):
    v = row.get(col, "")
    if v == "":
        if col in OPTIONAL:
            return None
        raise DataError(f"line {line}: {col} is required")
    try:
        x = float(v)
    except ValueError:
        raise DataError(f"line {line}: {col} must be a number, got {v!r}")
    if x < 0:
        raise DataError(f"line {line}: {col} must be >= 0")
    return x


def read_rows(text):
    rd = csv.DictReader(io.StringIO(text))
    if not rd.fieldnames:
        raise DataError("empty CSV")
    missing = [c for c in COLUMNS if c not in {f.strip() for f in rd.fieldnames}]
    if missing:
        raise DataError("CSV header is missing: " + ", ".join(missing))
    rows = []
    for n, raw in enumerate(rd, start=2):
        row = {k.strip(): (v or "").strip() for k, v in raw.items() if k}
        if not any(row.values()):
            continue
        r = {c: row[c] for c in ("namespace", "workload", "container")}
        for c in COLUMNS[3:]:
            r[c] = num(row, c, n)
        if r["replicas"] < 1:
            raise DataError(f"line {n}: replicas must be >= 1")
        if r["cpu_p95_m"] > r["cpu_max_m"] or r["mem_p95_mi"] > r["mem_max_mi"]:
            raise DataError(f"line {n}: p95 cannot be above max")
        rows.append(r)
    if not rows:
        raise DataError("no data rows")
    return rows


def analyze(r, headroom=0.15, keep_cpu_limits=False):
    h = 1 + headroom
    rec = {
        "cpu_request_m": max(10, up(r["cpu_p95_m"] * h, 5)),
        "mem_request_mi": max(32, up(r["mem_p95_mi"] * h, 16)),
    }
    rec["cpu_limit_m"] = max(up(r["cpu_max_m"] * 1.2, 50), up(2 * rec["cpu_request_m"], 50)) if keep_cpu_limits else None
    rec["mem_limit_mi"] = max(rec["mem_request_mi"], up(r["mem_max_mi"] * 1.25, 16))
    findings = []

    def f(sev, code, msg):
        findings.append({"severity": sev, "code": code, "message": msg})

    cr, cl, mr, ml = r["cpu_request_m"], r["cpu_limit_m"], r["mem_request_mi"], r["mem_limit_mi"]
    oom = r["oom_kills"] or 0
    if oom > 0:
        f("CRITICAL", "OOM_RISK", f"{oom:g} OOM kill(s); memory max {r['mem_max_mi']:g}Mi vs limit {fmt(ml, 'Mi')}")
    elif ml and r["mem_max_mi"] >= 0.95 * ml:
        f("CRITICAL", "OOM_RISK", f"memory max {r['mem_max_mi']:g}Mi is {r['mem_max_mi'] / ml:.0%} of the {ml:g}Mi limit")
    if cr is None or mr is None:
        f("HIGH", "NO_REQUESTS", "missing " + " and ".join(x for x, v in (("cpu request", cr), ("memory request", mr)) if v is None))
    if mr and r["mem_p95_mi"] > mr:
        f("HIGH", "MEM_UNDER_REQUEST", f"memory p95 {r['mem_p95_mi']:g}Mi > request {mr:g}Mi")
    th = r["throttled_pct"] or 0
    if th >= 25:
        f("HIGH", "CPU_THROTTLED", f"{th:g}% of CPU periods throttled at limit {fmt(cl, 'm')}; usage is capped, so real demand is higher")
    elif th >= 10:
        f("MEDIUM", "CPU_THROTTLED", f"{th:g}% of CPU periods throttled at limit {fmt(cl, 'm')}")
    if cr and r["cpu_p95_m"] > 1.2 * cr:
        f("MEDIUM", "CPU_UNDER_REQUEST", f"cpu p95 {r['cpu_p95_m']:g}m > 1.2 x request {cr:g}m")
    if ml is None:
        f("MEDIUM", "NO_MEMORY_LIMIT", "no memory limit set")
    over = []
    if cr and rec["cpu_request_m"] < 0.5 * cr:
        over.append(f"cpu {cr:g}m -> {rec['cpu_request_m']}m")
    if mr and rec["mem_request_mi"] < 0.5 * mr:
        over.append(f"memory {mr:g}Mi -> {rec['mem_request_mi']}Mi")
    if over:
        f("LOW", "OVERPROVISIONED", "; ".join(over))
    if r["days"] is not None and r["days"] < 7:
        f("LOW", "SHORT_WINDOW", f"only {r['days']:g} days of data")
    findings.sort(key=lambda x: SEV_ORDER[x["severity"]])
    status = findings[0]["severity"] if findings and findings[0]["severity"] != "LOW" else "OK"
    n = r["replicas"]
    d_cpu = ((cr or 0) - rec["cpu_request_m"]) * n
    d_mem = ((mr or 0) - rec["mem_request_mi"]) * n
    return {"id": f"{r['namespace']}/{r['workload']}/{r['container']}", "replicas": int(n), "current": {
        "cpu_request_m": cr, "cpu_limit_m": cl, "mem_request_mi": mr, "mem_limit_mi": ml},
        "recommended": rec, "delta_cpu_m": d_cpu, "delta_mem_mi": d_mem, "status": status, "findings": findings}


def run(text, headroom=0.15, keep_cpu_limits=False, core_month=None, gib_month=None):
    res = [analyze(r, headroom, keep_cpu_limits) for r in read_rows(text)]
    res.sort(key=lambda x: (SEV_ORDER.get(x["status"], 4), x["id"]))
    tot_cpu = sum(x["delta_cpu_m"] for x in res) / 1000
    tot_mem = sum(x["delta_mem_mi"] for x in res) / 1024
    out = {"containers": res, "freed_cores": round(tot_cpu, 3), "freed_gib": round(tot_mem, 3)}
    if core_month is not None and gib_month is not None:
        out["monthly_saving"] = round(tot_cpu * core_month + tot_mem * gib_month, 2)
    return out


def fmt(v, unit):
    return "none" if v is None else f"{v:g}{unit}"


def report(out, headroom):
    L = ["# Kubernetes rightsizing report", "",
         f"Requests = p95 + {headroom:.0%} headroom; CPU limits removed unless kept; memory limit = max x 1.25.", "",
         "| Container | Replicas | Status | CPU req | CPU limit | Mem req | Mem limit |",
         "|---|---|---|---|---|---|---|"]
    for x in out["containers"]:
        c, r = x["current"], x["recommended"]
        L.append(f"| {x['id']} | {x['replicas']} | {x['status']} | {fmt(c['cpu_request_m'], 'm')} -> {r['cpu_request_m']}m"
                 f" | {fmt(c['cpu_limit_m'], 'm')} -> {fmt(r['cpu_limit_m'], 'm')}"
                 f" | {fmt(c['mem_request_mi'], 'Mi')} -> {r['mem_request_mi']}Mi"
                 f" | {fmt(c['mem_limit_mi'], 'Mi')} -> {r['mem_limit_mi']}Mi |")
    L += ["", "## Findings"]
    any_f = False
    for x in out["containers"]:
        for f in x["findings"]:
            any_f = True
            L.append(f"- {f['severity']} {f['code']} {x['id']}: {f['message']}")
    if not any_f:
        L.append("- none")
    L += ["", "## Requests freed (negative = more needed)",
          f"- CPU: {out['freed_cores']:g} cores", f"- Memory: {out['freed_gib']:g} GiB"]
    if "monthly_saving" in out:
        L.append(f"- Estimated monthly saving: {out['monthly_saving']:,.2f}")
    return "\n".join(L) + "\n"


def self_test():
    cases = []

    def ok(name, cond):
        cases.append(bool(cond))
        print(("PASS " if cond else "FAIL ") + name)

    hdr = ",".join(COLUMNS) + "\n"

    def row(**kw):
        base = dict(namespace="ns", workload="w", container="c", replicas=1, cpu_request_m=500, cpu_limit_m=1000,
                    mem_request_mi=512, mem_limit_mi=1024, cpu_p50_m=100, cpu_p95_m=200, cpu_max_m=300,
                    mem_p50_mi=200, mem_p95_mi=300, mem_max_mi=400, throttled_pct=0, oom_kills=0, days=14)
        base.update(kw)
        return ",".join("" if base[c] is None else str(base[c]) for c in COLUMNS) + "\n"

    def one(**kw):
        return run(hdr + row(**kw))["containers"][0]

    def codes(x):
        return {(f["severity"], f["code"]) for f in x["findings"]}

    x = one()
    ok("cpu request = p95 x 1.15 rounded to 5m (230)", x["recommended"]["cpu_request_m"] == 230)
    ok("mem request = 300 x 1.15 = 345 -> 352Mi", x["recommended"]["mem_request_mi"] == 352)
    ok("mem limit = 400 x 1.25 = 500 -> 512Mi", x["recommended"]["mem_limit_mi"] == 512)
    ok("cpu limit removed by default", x["recommended"]["cpu_limit_m"] is None)
    ok("healthy container is OK", x["status"] == "OK")
    ok("headroom 0.25 changes cpu request (250)", run(hdr + row(), headroom=0.25)["containers"][0]["recommended"]["cpu_request_m"] == 250)
    k = run(hdr + row(), keep_cpu_limits=True)["containers"][0]["recommended"]["cpu_limit_m"]
    ok("kept cpu limit = max(360->400, 460->500) = 500", k == 500)
    ok("minimums 10m / 32Mi", one(cpu_p50_m=0, cpu_p95_m=1, cpu_max_m=2, mem_p50_mi=1, mem_p95_mi=2, mem_max_mi=3)["recommended"]
       == {"cpu_request_m": 10, "mem_request_mi": 32, "cpu_limit_m": None, "mem_limit_mi": 32})
    ok("OOM kills -> CRITICAL", ("CRITICAL", "OOM_RISK") in codes(one(oom_kills=2)) and one(oom_kills=2)["status"] == "CRITICAL")
    ok("max at 98% of limit -> OOM_RISK", ("CRITICAL", "OOM_RISK") in codes(one(mem_max_mi=1004, mem_p95_mi=500)))
    ok("missing requests -> HIGH NO_REQUESTS", ("HIGH", "NO_REQUESTS") in codes(one(cpu_request_m=None)))
    ok("mem p95 above request -> HIGH", ("HIGH", "MEM_UNDER_REQUEST") in codes(one(mem_request_mi=256)))
    ok("throttling 30% -> HIGH", ("HIGH", "CPU_THROTTLED") in codes(one(throttled_pct=30)))
    ok("throttling 12% -> MEDIUM", ("MEDIUM", "CPU_THROTTLED") in codes(one(throttled_pct=12)))
    ok("cpu p95 > 1.2 x request -> MEDIUM", ("MEDIUM", "CPU_UNDER_REQUEST") in codes(one(cpu_request_m=150)))
    ok("no memory limit -> MEDIUM", ("MEDIUM", "NO_MEMORY_LIMIT") in codes(one(mem_limit_mi=None)))
    big = one(cpu_request_m=2000, mem_request_mi=4096)
    ok("overprovisioned flagged LOW, status stays OK", ("LOW", "OVERPROVISIONED") in codes(big) and big["status"] == "OK")
    ok("short window flagged", ("LOW", "SHORT_WINDOW") in codes(one(days=3)))
    two = run(hdr + row(replicas=3) + row(workload="b", replicas=2, cpu_request_m=100, mem_request_mi=256),
              core_month=20, gib_month=4)
    # w: (500-230)*3=810m, (512-352)*3=480Mi ; b: (100-230)*2=-260m, (256-352)*2=-192Mi
    ok("freed cores = (810-260)/1000 = 0.55", two["freed_cores"] == 0.55)
    ok("freed GiB = (480-192)/1024 = 0.281", two["freed_gib"] == 0.281)
    ok("monthly saving = 0.55 x 20 + 0.28125 x 4 = 12.125", abs(two["monthly_saving"] - 12.125) < 0.01)
    ok("sorted by severity", two["containers"][0]["id"] == "ns/b/c")
    for bad, label in (("a,b\n1,2\n", "missing columns"), (hdr, "no rows"), (hdr + row(cpu_p95_m="x"), "bad number"),
                       (hdr + row(cpu_p95_m=900), "p95 above max"), (hdr + row(replicas=0), "zero replicas"),
                       (hdr + row(cpu_p95_m=None), "missing usage")):
        try:
            run(bad)
            ok(label + " rejected", False)
        except DataError:
            ok(label + " rejected", True)
    print(f"{sum(cases)}/{len(cases)} passed")
    return 0 if all(cases) else 1


def main(argv=None):
    ap = argparse.ArgumentParser(description="Rightsize Kubernetes container requests and limits from usage percentiles.")
    ap.add_argument("file", nargs="?", help="CSV with the columns listed in the module docstring")
    ap.add_argument("--headroom", type=float, default=15, help="percent added to p95 for requests (default 15)")
    ap.add_argument("--keep-cpu-limits", action="store_true", help="recommend CPU limits instead of removing them")
    ap.add_argument("--core-month", type=float, help="price of one requested core per month")
    ap.add_argument("--gib-month", type=float, help="price of one requested GiB of memory per month")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args(argv)
    if a.self_test:
        return self_test()
    if not a.file:
        ap.error("give a CSV file or --self-test")
    try:
        out = run(open(a.file, encoding="utf-8").read(), a.headroom / 100, a.keep_cpu_limits, a.core_month, a.gib_month)
    except (OSError, DataError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    print(json.dumps(out, indent=2) if a.json else report(out, a.headroom / 100), end="\n" if a.json else "")
    return 1 if any(x["status"] in ("CRITICAL", "HIGH") for x in out["containers"]) else 0


if __name__ == "__main__":
    sys.exit(main())
CloudDevOpsPython+2
F@f
0
Serverless Functions taste
Text

Opinionated conventions for building reliable, cost-aware serverless functions in the cloud.

# Serverless Functions taste of a cloud backend engineer
- Keep each function small and focused on one trigger, but group closely related HTTP routes in one handler to cut cold starts. Confidence: 0.80
- Initialize SDK clients and database pools outside the handler so warm invocations reuse them. Confidence: 0.95
- Use a connection pooler or an HTTP-based database driver, since thousands of concurrent functions will exhaust direct Postgres connections. Confidence: 0.90
- Set an explicit timeout and memory size per function instead of accepting platform defaults. Confidence: 0.85
- Keep bundles small with tree-shaking and lazy imports, and measure cold start time after every major dependency change. Confidence: 0.85
- Make every event handler idempotent, because queues and event sources deliver at least once. Confidence: 0.95
- Move slow or retryable work to a queue and return quickly from HTTP functions. Confidence: 0.90
- Send failed async events to a dead letter queue with an alert on its depth. Confidence: 0.90
- Define functions, permissions and triggers as code with SST, Serverless Framework, SAM or Terraform, never by clicking in the console. Confidence: 0.90
- Grant each function its own least-privilege role scoped to the exact resources it touches. Confidence: 0.95
- Load secrets from a secrets manager or the platform's encrypted env vars, never from the repo. Confidence: 0.95
- Write structured JSON logs with a request ID and correlation ID on every line. Confidence: 0.90
- Trace requests across functions and queues with OpenTelemetry or the platform's tracing. Confidence: 0.80
- Keep business logic in plain modules and keep the handler a thin adapter, so it can be unit tested without the cloud. Confidence: 0.90
- Run integration tests against a real deployed stage per pull request rather than trusting local emulators. Confidence: 0.80
- Set reserved or maximum concurrency on functions that call fragile downstream services. Confidence: 0.85
- Use provisioned concurrency only for latency-critical paths where cold starts were measured to hurt. Confidence: 0.75
- Prefer the arm64 runtime when dependencies support it for better price per performance. Confidence: 0.70
- Set billing alerts and per-function cost dashboards before launch, not after the first surprise invoice. Confidence: 0.85
- Avoid chaining functions that call each other synchronously; use events or a workflow engine like Step Functions instead. Confidence: 0.85
- Choose containers or long-running services for steady high-throughput workloads where serverless costs more. Confidence: 0.75
BackendBest PracticesCloud+1
F@f
0