{"id":34221925,"url":"https://github.com/containeroo/autovpa","last_synced_at":"2026-06-16T11:00:28.415Z","repository":{"id":328136269,"uuid":"1111071363","full_name":"containeroo/autovpa","owner":"containeroo","description":"Kubernetes operator that automatically keeps a matching VerticalPodAutoscaler in sync when workloads opt in via annotation.","archived":false,"fork":false,"pushed_at":"2026-06-12T20:33:59.000Z","size":752,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-12T22:20:55.537Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/containeroo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-06T07:57:03.000Z","updated_at":"2026-06-12T20:34:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/containeroo/autovpa","commit_stats":null,"previous_names":["containeroo/autovpa"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/containeroo/autovpa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/containeroo%2Fautovpa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/containeroo%2Fautovpa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/containeroo%2Fautovpa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/containeroo%2Fautovpa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/containeroo","download_url":"https://codeload.github.com/containeroo/autovpa/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/containeroo%2Fautovpa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34402663,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-15T23:26:15.109Z","updated_at":"2026-06-16T11:00:28.387Z","avatar_url":"https://github.com/containeroo.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AutoVPA\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/containeroo/autovpa?style=flat-square)](https://goreportcard.com/report/github.com/containeroo/autovpa)\n[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/containeroo/autovpa)\n[![Release](https://img.shields.io/github/release/containeroo/autovpa.svg?style=flat-square)](https://github.com/containeroo/autovpa/releases/latest)\n[![GitHub tag](https://img.shields.io/github/tag/containeroo/autovpa.svg?style=flat-square)](https://github.com/containeroo/autovpa/releases/latest)\n[![license](https://img.shields.io/github/license/containeroo/autovpa.svg?style=flat-square)](LICENSE)\n\nAutoVPA watches Deployments, StatefulSets, and DaemonSets and ensures a matching `VerticalPodAutoscaler` exists for each workload that opts in via an annotation. Profiles are defined once in a YAML file, and the operator renders the VPA spec with the selected profile.\n\n## Prerequisites\n\n- VPA CRDs installed in the cluster.\n- Config file mounted at the configured `--config`.\n\n## Quick Start\n\n```bash\n# Install (Helm)\nhelm upgrade --install autovpa ./deploy/kubernetes/chart/autovpa\n\n# Or apply kustomize manifests\nkubectl apply -k deploy/kubernetes\n```\n\nAnnotate a workload to opt in and AutoVPA creates/updates the matching VPA using your default template (`{{ .WorkloadName }}-{{ .Profile }}-vpa` by default):\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: api\n  namespace: demo\n  annotations:\n    autovpa.containeroo.ch/profile: default\nspec:\n  replicas: 2\n  template:\n    spec:\n      containers:\n        - name: api\n          image: ghcr.io/example/api:latest\n```\n\n## Installation and Usage\n\n- **Helm**: `helm upgrade --install autovpa ./deploy/kubernetes/chart/autovpa`\n- **Kustomize/manifests**: apply `deploy/kubernetes/kustomization.yaml` (or the rendered manifests) after setting image/tag/args.\n- **Profiles**: mount a YAML containing `defaultProfile` and `profiles` into the pod (default path `config.yaml`). Each profile may optionally set `nameTemplate` to override the default VPA name template.\n\n### Namespaced Mode\n\nBy default, `AutoVPA` watches all namespaces. To restrict it to specific namespaces, pass the `--watch-namespace` flag. This flag can be repeated or comma-separated to specify multiple namespaces. When set, `AutoVPA` will only monitor workloads (and create/update their VPAs) within those namespaces.\n\nIf running in namespaced mode, ensure the associated `Role` and `RoleBinding` are configured accordingly. You can use `deploy/manifests/role.template` and `deploy/manifests/rolebinding.template` as starting points for custom RBAC definitions.\n\n## Using AutoVPA\n\n- Add the annotation `autovpa.containeroo.ch/profile: \"\u003cprofile-name\u003e\"` to any Deployment, StatefulSet, or DaemonSet to enable VPA management.\n  Use `default` to apply the operator's default profile.\n\n- For each annotated workload, the operator automatically creates or updates a corresponding VPA:\n  - **Name** is rendered from the configured template\n    (default: `{{ .WorkloadName }}-{{ .Profile }}-vpa`, overridable globally or per profile).\n  - **Labels** on the VPA include:\n    - all labels from the workload,\n    - the managed label (default: `autovpa.containeroo.ch/managed=true`),\n    - the profile label (`autovpa.containeroo.ch/profile=\u003cprofile-name\u003e`).\n\n- **Profiles** are defined in a YAML file with:\n  - a `defaultProfile` key, and\n  - a `profiles` map containing per-profile settings (name template override, resource policy, etc.).\n    Any `targetRef` fields included in profile specs are ignored - AutoVPA always sets these automatically for you.\n\n### Profile file basics\n\n- `defaultProfile` must name one of the entries in `profiles`.\n- Profile specs are inline (no nested `spec:` key). `targetRef` is ignored and will be set automatically.\n- `nameTemplate` is optional per profile; otherwise the global `--vpa-name-template` is used.\n- `updatePolicy.updateMode` must be a string (`Off`, `Auto`, `Initial`, etc.); boolean `true`/`false` is tolerated and normalized to `Auto`/`Off`.\n\n## Profile file example (`config.yaml`)\n\n```yaml\n---\ndefaultProfile: default\nprofiles:\n  default:\n    # Note: updateMode must be a string (\"Off\", \"Auto\", \"Initial\", etc.).\n    updatePolicy:\n      updateMode: Off\n    resourcePolicy:\n      containerPolicies:\n        - containerName: \"*\"\n          controlledResources: [\"cpu\", \"memory\"]\n  safe:\n    # optional per-profile VPA name override\n    nameTemplate: \"{{ .WorkloadName }}-vpa\"\n    # Profiles are defined inline; do not wrap fields under a separate \"spec:\" key.\n    updatePolicy:\n      updateMode: Auto\n    resourcePolicy:\n      containerPolicies:\n        - containerName: \"*\"\n          controlledResources: [\"cpu\", \"memory\"]\n          minAllowed:\n            cpu: 20m\n            memory: 64Mi\n  aggressive:\n    updatePolicy:\n      updateMode: Auto\n    resourcePolicy:\n      containerPolicies:\n        - containerName: \"*\"\n          controlledResources: [\"cpu\", \"memory\"]\n          minAllowed:\n            cpu: 100m\n            memory: 128Mi\n```\n\n### Template hints\n\nFor the VPA template name, the following functions are available:\n\n- `toLower`: simple casing helpers. Since the name of the workload is used as the VPA name, this is useful to ensure the name is DNS-1123 compliant. \"ToUpp\" is not a valid DNS-1123 subdomain.\n  e.g.\n  `{{ toLower \"Hello\" }}` → `hello`\n- `replace`: replace all occurrences.\n  e.g.\n  `{{ replace \"api-dev\" \"-\" \".\" }}` → `api.dev`.\n- `trim`: strip surrounding whitespace.\n  e.g.\n  `{{ trim \" demo \" }}` → `demo`\n- `truncate`: keep the first N runes to cap length.\n  e.g.\n  `{{ truncate .WorkloadName 10 }}` → `myworkload` (first 10 runes)\n- `dnsLabel`: normalize to a DNS-safe label (lowercase, non-alnum to `-`).\n  e.g.\n  `{{ dnsLabel \"API_App\" }}` → `api-app`\n\nIt will be rendered with the following variables:\n\n- `.WorkloadName`: the name of the workload.\n- `.Namespace`: the namespace of the workload.\n- `.Kind`: the kind of the workload.\n- `.Profile`: the profile name.\n\n## Managed vs. Manual VPA Behavior\n\nAutoVPA treats the **workload** (Deployment, StatefulSet, DaemonSet) as the single source of truth.\n\nManaged VPAs are continuously reconciled against the workload’s desired state. Any drift detected on a managed VPA (labels, spec, or ownership) may trigger reconciliation of the owning workload, which restores the expected configuration.\n\n### If someone removes the managed label from a VPA\n\n- If the workload **still has** the profile annotation:\n  - AutoVPA reconciles the workload\n  - Re-adds the managed label and profile label\n  - Restores the VPA spec\n\n- If the workload **no longer has** the profile annotation:\n  - AutoVPA stops managing the VPA\n  - The VPA becomes manual and is left untouched\n\n### If someone changes the profile label or spec on a VPA\n\n- AutoVPA always derives the desired profile from the **workload annotation**.\n- Any manual changes to a **managed VPA** are treated as temporary:\n  - The next reconciliation restores labels and spec to match the workload.\n\n- Changes to the VPA **status** are ignored and never trigger reconciliation.\n\n**Rule of thumb**:\n\n- Edit the **workload** to make permanent changes.\n- Manual edits on **managed VPAs** will usually be reverted.\n\n## Workload example\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: api\n  namespace: demo\n  annotations:\n    autovpa.containeroo.ch/profile: aggressive # or \"default\" to use the default profile\nspec:\n  replicas: 2\n  template:\n    spec:\n      containers:\n        - name: api\n          image: ghcr.io/example/api:latest\n```\n\n## Start Parameters\n\n| Flag/Parameter                | Description                                                             | Default                                  | Env Var                              |\n| :---------------------------- | :---------------------------------------------------------------------- | :--------------------------------------- | :----------------------------------- |\n| `--config`                    | Path to the config file.                                                | `config.yaml`                            | `AUTO_VPA_CONFIG`                    |\n| `--disable-crd-check`         | Disable the check for the VPA CRD.                                      | `false`                                  | `AUTO_VPA_DISABLE_CRD_CHECK`         |\n| `--profile-annotation`        | Workload annotation key to select a profile.                            | `autovpa.containeroo.ch/profile`         | `AUTO_VPA_PROFILE_ANNOTATION`        |\n| `--managed-label`             | Label applied to managed VPAs.                                          | `autovpa.containeroo.ch/managed`         | `AUTO_VPA_MANAGED_LABEL`             |\n| `--vpa-name-template`         | Template for VPA names; per-profile `nameTemplate` can override. \\*     | `{{ .WorkloadName }}-{{ .Profile }}-vpa` | `AUTO_VPA_VPA_NAME_TEMPLATE`         |\n| `--watch-namespace`           | Namespaces to watch (repeatable/comma-separated). Watches all if unset. | (all)                                    | `AUTO_VPA_WATCH_NAMESPACE`           |\n| `--metrics-enabled`           | Enable/disable metrics endpoint.                                        | `true`                                   | `AUTO_VPA_METRICS_ENABLED`           |\n| `--metrics-bind-address`      | Metrics server address (e.g., `:8443`).                                 | `:8443`                                  | `AUTO_VPA_METRICS_BIND_ADDRESS`      |\n| `--metrics-secure`            | Serve metrics over HTTPS.                                               | `true`                                   | `AUTO_VPA_METRICS_SECURE`            |\n| `--enable-http2`              | Enable HTTP/2 for servers.                                              | `false`                                  | `AUTO_VPA_ENABLE_HTTP2`              |\n| `--health-probe-bind-address` | Health/readiness probe address.                                         | `:8081`                                  | `AUTO_VPA_HEALTH_PROBE_BIND_ADDRESS` |\n| `--leader-elect`              | Enable leader election.                                                 | `true`                                   | `AUTO_VPA_LEADER_ELECT`              |\n| `--log-encoder`               | Log format (`json`, `console`).                                         | `json`                                   | `AUTO_VPA_LOG_ENCODER`               |\n| `--log-stacktrace-level`      | Stacktrace log level (`info`, `error`, `panic`).                        | `panic`                                  | `AUTO_VPA_LOG_STACKTRACE_LEVEL`      |\n| `--log-devel`                 | Enable development mode logging.                                        | `false`                                  | `AUTO_VPA_LOG_DEVEL`                 |\n\n\\*) Variables are available in the template string: `.WorkloadName`, `.Namespace`, `.Kind`, `.Profile`.\nSee [Func hints](#func-hints) for template helper details.\n\n### Labels and annotations\n\n- Managed label (default) `autovpa.containeroo.ch/managed=true` marks VPAs the operator owns; override with `--managed-label`.\n- Profile annotation (default) `autovpa.containeroo.ch/profile=\u003cprofile\u003e` opts workloads in; override with `--profile-annotation`.\n- Keys must be unique; the operator will refuse to start if managed/profile keys collide.\n\n### Metrics and HTTP/2\n\n- Metrics are enabled by default on `:8443` with TLS. Toggle with `--metrics-enabled`, `--metrics-bind-address`, `--metrics-secure`.\n- HTTP/2 is disabled by default for compatibility; enable with `--enable-http2` if your ingress/stack requires it.\n\n## Prometheus Metrics\n\nAutoVPA exposes counters for the VPAs it creates, updates, or skips while reconciling workloads.\n\n### Available Metrics\n\n1. **VPAs Created**\n   - **Metric:** `autovpa_vpa_created_total`\n   - **Labels:** `namespace`, `name`, `kind`, `profile`\n2. **VPAs Updated**\n   - **Metric:** `autovpa_vpa_updated_total`\n   - **Labels:** `namespace`, `name`, `kind`, `profile`\n3. **Workloads Skipped**\n   - **Metric:** `autovpa_vpa_skipped_total`\n   - **Labels:** `namespace`, `name`, `kind`, `reason`\n4. **Managed VPAs Deleted (cleanup)**\n   - **Metrics:** `autovpa_vpa_deleted_obsolete_total`, `autovpa_vpa_deleted_opt_out_total`, `autovpa_vpa_deleted_workload_gone_total`, `autovpa_vpa_deleted_owner_gone_total`, `autovpa_vpa_deleted_orphaned_total`\n   - **Labels:** `namespace`, `kind` (or just `namespace` for orphaned)\n5. **Managed VPA Inventory**\n   - **Metric:** `autovpa_managed_vpa`\n   - **Labels:** `namespace`, `profile`\n6. **Reconcile Errors**\n   - **Metric:** `autovpa_reconcile_errors_total`\n   - **Labels:** `controller`, `kind`, `reason`\n\nAlerts for missing metrics and skip spikes are provided in `deploy/kubernetes/manifests/prometheusrule.yaml` and the Helm chart.\n\n## Running locally\n\n```bash\nGOCACHE=$(pwd)/.cache/go-build go run ./cmd/main.go \\\n  --config=deploy/kubernetes/manifests/config.yaml \\\n```\n\n## Testing\n\n- Unit tests: `GOCACHE=$(pwd)/.cache/go-build go test ./...` (or `make test` for fmt/vet/envtest + unit tests).\n- E2E: `make e2e` (uses an existing cluster; see `make kind`/`make delete-kind` for local Kind helper). Scope with `make e2e-generic`, `make e2e-namespaced`, or `make e2e-vpa`.\n- Lint: `make lint` or `make lint-fix`.\n\n## Troubleshooting\n\n- **VPA CRD missing**: startup fails unless `--disable-crd-check` is set. Install the VPA CRD or add the flag for environments where the CRD is not present yet.\n- **Annotation missing / profile not found**: AutoVPA logs and emits events but does not requeue aggressively. Add the profile annotation or fix the profile name in your config.\n- **Invalid name template**: the operator validates templates at startup; fix the template string or profile override before redeploying.\n\n## License\n\nThis project is licensed under the Apache 2.0 License. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontaineroo%2Fautovpa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcontaineroo%2Fautovpa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontaineroo%2Fautovpa/lists"}