# Tortoise: Mercari's autoscaler that tunes HPA and VPA for you

> Tortoise is a Kubernetes controller that takes over autoscaling tuning for service teams, adjusting HPA and VPA parameters from historical utilisation data so that engineers configure a couple of fields rather than maintaining resource requests, limits and scaling parameters by hand. It is still described as an early-stage project that needs careful evaluation in a development environment first.

**mercari/tortoise** — Tortoise: Shell-Shockingly-Good Kubernetes Autoscaling

- Repository: https://github.com/mercari/tortoise
- Stars: 521 · Forks: 32
- Language: Go
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/mercari-tortoise

## The problem: a thousand deployments and nobody who knows autoscaling

The Motivation section explains why Mercari built this, and the explanation is organisational as much as technical.

At Mercari, the responsibilities of the Platform team and the service development teams are clearly distinguished, and not all service owners possess expert knowledge of Kubernetes. Mercari has embraced a microservices architecture and currently manages over 1000 Deployments, each with its dedicated development team.

To effectively drive FinOps across such a sprawling landscape, it is clear that the platform team cannot individually optimize all services. As a result, they provide a plethora of tools and guidelines to simplify the process of the Kubernetes optimization for service owners.

The specific pain is in the parameters. Manually optimizing various parameters across different resources, such as resource requests and limits, HPA parameters, and Golang runtime environment variables, presents a substantial challenge. And this optimization demands engineering efforts from each team constantly, because adjustments are necessary whenever there is a change impacting a resource usage, which can occur frequently. Changes in implementation can alter resource consumption patterns, and fluctuations in traffic volume are common.

So the alternatives as the README frames them are to mandate that all teams perpetually engage in complex manual optimization processes indefinitely, or until Mercari goes out of business.

That framing tells you what Tortoise is actually for. It is not a better autoscaler in the sense of cleverer scaling maths. It is a change of ownership: the optimization responsibility shifts from service owners to the platform team, with the platform team doing comprehensive tuning so that all Tortoises in the cluster adapt to each workload, while service owners configure only a minimal number of parameters to initiate autoscaling.

That is a governance tool as much as a technical one. The primary language is Go, the licence is MIT, the default branch is main, and the recent releases are v1.3.0 from 2026-10-07, v1.2.3 from 2026-02-10 and v1.2.2, a hotfix, from 2026-01-13. The last push was on 2026-10-07.

The project page links to both an English and a Japanese Mercari engineering blog post, and the Japanese title translates roughly as giving up on manual Kubernetes resource optimisation and the tortoise possibilities seen in it.

## Installing it: CRDs first, then the controller

The install section is short and there is a prerequisite that catches people.

```shell
# Install CRDs into the K8s cluster specified in ~/.kube/config.
make install
# Deploy controller to the K8s cluster specified in ~/.kube/config.
make deploy
```

Both commands act on the cluster in your ~/.kube/config, so the make targets are a convenience over kubectl apply rather than a separate deployment mechanism.

The prerequisite is stated plainly. You do not need a rearing cage, but need VPA in your Kubernetes cluster before installing it. Vertical Pod Autoscaler is not optional here, and that makes sense once you know what the controller does: it manages HPA and VPA under the hood, so it needs the VPA API to talk to.

The Makefile shows this is a standard kubebuilder-scaffolded controller project. There is a PROJECT file, a config/ directory for the kustomize configuration, manifests/ for the raw manifests, and a hack/ directory for the standard scaffolding scripts. The envtest version is pinned to 1.31.0 for the controller tests, and the image is built for ghcr.io/mercari/tortoise.

The Dockerfile is a two-stage build with a golang builder and a distroless static nonroot base image, running the manager binary as user 65532. The build is CGO_ENABLED=0, and the copy step is deliberately narrow, taking only cmd/main.go, api/, internal/controller/ and pkg/ rather than the whole tree, so the build cache is not invalidated by changes in docs or tests.

The dependency list is where the design becomes visible. The controller-runtime version is 0.19.0 with the Kubernetes libraries at 0.31.1, and the vertical-pod-autoscaler dependency is pinned to a specific commit rather than a release, v0.10.1-0.20240119175703-2bba2bac1635, with a comment in go.mod explaining that this commit is needed to use the controller fetcher. That is the VPA recommender interface that the controller uses to fetch recommendations, and pinning to a commit rather than a tag is a sign that the required interface is not in a released version.

The controller is also a Prometheus client, with client_golang as a dependency, which is what lets it expose the utilisation history it collects.

## The Tortoise custom resource and what it actually manages

The CRD is deliberately small, and the README is upfront about the division of configuration between the two audiences.

As described in the Motivation section, Tortoise exposes many global parameters to a cluster admin, while it exposes few parameters in the Tortoise resource.

Here is the whole user-facing interface.

```yaml
apiVersion: autoscaling.mercari.com/v1beta3
kind: Tortoise
metadata:
  name: lovely-tortoise
  namespace: zoo
spec:
  updateMode: Auto 
  targetRefs:
    scaleTargetRef:
      kind: Deployment
      name: sample
```

Then, Tortoise creates HPA and VPA under the hood.

So a service team creates a Tortoise naming the workload they want tuned, sets the update mode, and the controller creates and maintains both the horizontal and vertical autoscaler objects for them. Everything else, the tuning of the HPA's target metrics and scaling behaviour and the VPA's requests and limits, is derived from data the controller collects rather than configured by hand.

The README's description of what the resource hides is worth quoting for its framing. Despite its simple appearance, each tortoise stores a rich collection of historical data on resource utilization beneath its shell, and cleverly utilizes them to manage parameters in autoscalers.

That historical data is the mechanism, and it is what distinguishes Tortoise from a static configuration generator. The controller watches utilisation over time and uses the history to decide what the HPA and VPA should be set to, which means the parameters move as the workload's profile changes rather than being pinned at whatever they were when the service was deployed.

The updateMode field is the switch between that automatic behaviour and a manual one, and the Global Disable Mode and Emergency mode documents exist because a controller that writes autoscaler parameters on its own needs an off switch and an override. Those two documents are worth reading before deploying, since they are the two ways to stop the controller acting.

The documentation set is thorough for an early-stage project: a user guide, an admin guide, the global disable mode, the emergency mode, a horizontal scaling document, a vertical scaling document, an internal technical document mostly for contributors, and a contributor guide. The API definition is a single Go file at api/v1beta3/tortoise_types.go.

## What it supports today, and what it does not

Two limitations are stated in the Notes section and both are worth checking against your cluster before you plan a rollout.

The first is scope of workloads. Tortoise only supports Deployment and Argo Rollouts' Rollout at the moment. In the future, there is a linked issue to support all resources supporting scale subresources, and the user guide has an Argo Rollouts section.

That is a narrower surface than it might first appear. StatefulSets, ReplicaSets and custom resources with a scale subresource are all outside the supported set today, so a platform with any of those in scope cannot adopt this as the default path.

The second is language coverage, and Mercari is candid about it. In Mercari, Tortoise has been evaluated with many Golang microservices, while there are a few services implemented in other languages using Tortoise, and any contributions to enhance the recommendation for other languages would be welcome.

That sentence is doing real work. The controller's recommendations are derived from observed resource utilisation, and the relationship between a language's runtime behaviour and its resource profile is not uniform. A Go service's GC behaviour and memory growth pattern are different from a Node service's event loop and heap behaviour, and a recommendation tuned on Go workloads is a starting point for others rather than a finished answer.

There is also a project-stage statement that belongs in any evaluation. Tortoise is still in the early stage and the README recommends evaluating its behaviour with your services in your development environment carefully.

That is a direct instruction rather than a hedge, and it is paired with the existence of the Global Disable Mode and Emergency mode documents. A controller that writes autoscaler parameters automatically is the kind of component that needs a rehearsed way to be turned off, and the project has clearly thought about that.

The Contribution section adds one more constraint for anyone wanting to modify it. Before implementing any feature changes as pull requests, you should raise an issue and discuss what you propose with maintainers, and a major change may have to be proposed through the proposals/ process. There is also a CLA, and by submitting a contribution you are deemed to accept and be bound by its terms, which is worth knowing before you open a pull request.

## Where Tortoise is the wrong choice

There are three limits, and the first is about when a platform-wide autoscaler is the wrong shape.

The first is scale. Tortoise exists because of over 1000 Deployments across many teams. The overhead it introduces, a controller watching every workload's utilisation history, a CRD to manage, a global configuration surface, and a mechanism to disable it in an emergency, is only amortised over a large population of independently owned services. For a cluster with a dozen workloads owned by one team, a hand-tuned HPA and VPA is less machinery and more predictable, and the platform team that owns the cluster will know exactly why each workload is scaled the way it is.

The second is that it takes over control of something you configured. Once a Tortoise exists for a workload, the controller is writing the HPA and VPA parameters for it, and there is a mismatch risk between what the controller has derived from history and what a human set deliberately, for instance a service that should scale on a business signal rather than on CPU. The updateMode field exists to handle that, and the emergency mode exists for when the controller gets it wrong, but both are admission that this is a real risk rather than a theoretical one.

There is also the horizontal and vertical split to think about. The controller manages both, and they interact: the HPA decides how many pods, the VPA decides how big they are, and changing pod size changes the utilisation the HPA sees. Doing that automatically across a fleet is a control loop over a control loop, and it is why the project has horizontal scaling and vertical scaling documents rather than folding them into one.

The third is the maturity and the governance. The project says it is still in the early stage, the release line is 1.x with a hotfix in the recent history, and the contribution process requires an issue and maintainer discussion before any feature change, with major changes going through a proposals/ process. The code is MIT licensed and Mercari-branded, but the process signals a project run by a platform team with a specific internal context.

The honest alternative for most teams is to keep HPA and VPA tuning as a platform team's job by policy and tooling rather than by controller, for instance with a dashboard of recommended requests that engineers apply deliberately. That keeps the human in the loop, at the cost of the manual work the README is complaining about.

For the specific problem Mercari had, with over a thousand deployments and hundreds of engineers who do not know Kubernetes well, the controller is the right answer, because the alternative was mandating perpetual manual work. If your situation resembles that, Tortoise saves you a large recurring cost. If it does not, a controller rewriting your autoscaler parameters is a lot of machinery for a smaller problem.

## Conclusion

Adopt Tortoise if you run a Kubernetes platform with many service teams who each lack autoscaling expertise, since the controller centralises the tuning that would otherwise be duplicated across every team. Do not adopt it for a single workload, where a hand-tuned HPA and VPA is simpler. Before you install, note that Vertical Pod Autoscaler must already be running in the cluster, and read the early-stage warning, since the README asks you to evaluate its behaviour in a development environment carefully.

## FAQ

### What is Tortoise and what problem does it solve at Mercari?

It is a Kubernetes controller that automates resource optimization for service teams. Mercari manages over 1000 Deployments across many teams whose owners lack autoscaling expertise, and Tortoise shifts the tuning of resource requests, limits, HPA parameters and runtime variables from service owners to the platform team, which tunes each workload from collected utilisation history.

### How do I install Tortoise and what does it require?

Run make install to install the CRDs into the cluster in your ~/.kube/config and make deploy to deploy the controller. VPA must already be present in the Kubernetes cluster before installing it, and the README recommends evaluating Tortoise's behaviour with your services in a development environment carefully first.

### What does the Tortoise custom resource look like?

It is deliberately small: apiVersion autoscaling.mercari.com/v1beta3, kind Tortoise, with spec.updateMode and spec.targetRefs.scaleTargetRef naming the kind and name of the workload. Tortoise then creates the HPA and VPA under the hood, while global parameters are configured by the cluster admin rather than in the resource.

### Which workloads and languages does Tortoise support?

Tortoise only supports Deployment and Argo Rollouts' Rollout at the moment, with an open issue to support all resources that have scale subresources. At Mercari it has been evaluated with many Golang microservices while a few services in other languages use it, and contributions to improve recommendations for other languages are welcome.

### What happens if Tortoise's autoscaling behaviour is wrong?

The documentation set includes a Global Disable Mode page for testing scenarios and an Emergency mode page. The resource also carries an updateMode field, so automatic updating can be turned off. The README frames this as a reason to evaluate the behaviour in a development environment before relying on it.

## Sources

- [Issues](https://github.com/mercari/tortoise/issues)
- [License: MIT](https://github.com/mercari/tortoise/blob/main/LICENSE)
- [mercari/tortoise on GitHub](https://github.com/mercari/tortoise)
- [README](https://github.com/mercari/tortoise/blob/main/README.md)
- [Releases](https://github.com/mercari/tortoise/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mercari-tortoise
