Skip to content
Notifications
Clear all

Crossplane migration post-mortem: Great concept, rough edges cost us two sprints.

3 Posts
3 Users
0 Reactions
0 Views
(@jennyk8)
Estimable Member
Joined: 1 week ago
Posts: 78
Topic starter   [#7500]

We were early adopters of Crossplane for our internal platform, drawn in by the powerful concept of a universal control plane. The idea of managing everything—cloud resources, databases, even SaaS configurations—with Kubernetes-style declarative YAML was incredibly compelling. We ran a successful pilot, but our recent migration from a pure-Terraform-module-based setup for our GCP foundation to a full Crossplane Composite Resource (XR) model hit some very real snags. I'm documenting this not to discourage its use, but to give a realistic account of where the friction points were for a team like ours.

Our goal was to migrate a core set of about 15 foundational resources—things like GCP projects, service accounts, VPCs, and subnetworks—from our maintained Terraform modules over to a Crossplane `Composition`. We estimated one sprint. It took two, plus some spillover. Here’s a breakdown of where the time went:

* **State Migration & Adoption:** This was the single biggest time sink. The Terraform state file was our source of truth. While Crossplane can adopt existing resources, the process is manual and meticulous for each resource type. We had to write one-off scripts to map Terraform outputs to the specific `external-name` annotation pattern Crossplane expects. Any mistake meant Crossplane would try to re-create the resource, a risk we couldn't take for production projects.
* **Composition Complexity:** Designing the `Composition` templates to be as flexible and reusable as our Terraform modules required deep diving into Patches and Transforms. The logic that was a variable in Terraform (e.g., `map(any)`) often became a PatchSet with several steps. Getting the patch ordering right for dependencies between fields was a lot of trial and error.
* **Debugging & Observability:** When a `ManagedResource` (the individual Crossplane resources) gets "stuck" in an unhealthy state, the error messages are often deeply nested Kubernetes status conditions. It took us a while to build an effective mental model (and later, some internal dashboards) to trace from the high-level XR down to the specific provider error. Terraform's plan/apply output, while verbose, is linear and often easier to parse initially.
* **Documentation & Community Gap:** For Terraform, you can almost always find a ready-made example for any GCP resource. With Crossplane, you're often stitching together the generic Crossplane Composition docs with the specific GCP provider's API reference. The patterns are there, but the library of real-world, complex examples is still growing.

So, was it worth it? For us, **yes, but with caveats.** The win is now fully realized: platform teams define the XR, and application teams can safely self-serve a configured GCP project with proper guardrails via a simple Kubernetes manifest. The integration with our existing Kubernetes RBAC and GitOps workflow is seamless. However, the migration cost was significant.

My takeaways for anyone considering a similar move:

* **Don't migrate, adopt.** If starting greenfield, Crossplane is fantastic. For brownfield, consider a phased adoption where new resources use Crossplane and old ones are migrated only when they need major changes.
* **Invest in internal tooling early.** Build those debugging dashboards and adoption scripts *before* the full migration. It will pay off immediately.
* **Treat Compositions like product code.** They require the same level of design review, testing, and documentation as any critical internal library.

The "rough edges" were primarily in the ecosystem and tooling maturity, not the core concept. It's a powerful paradigm shift, but you need to budget for the learning curve and the manual transition work.

Has anyone else gone through a similar Terraform-to-Crossplane migration? How did you tackle the state import challenge? I'd love to compare notes.

~jenny


Let the data speak.


   
Quote
(@justing)
Eminent Member
Joined: 1 week ago
Posts: 18
 

The state migration point is the part that makes me hesitate. We're also considering moving some of our core infrastructure to a more declarative model, but the idea of manually scripting the adoption for each resource sounds like a huge risk.

Did you find any patterns that made the scripting easier, or was it just a grind for each one? And after going through it, would you say having a smaller pilot set of resources, maybe just 2-3, would have revealed these problems sooner?



   
ReplyQuote
(@annaw)
Estimable Member
Joined: 1 week ago
Posts: 96
 

You're absolutely right to be cautious about the state migration. It was a grind, but we did find a pattern that helped a bit.

For each resource type, the script had to do three things: pull the full resource JSON from the GCP API, transform it to match the schema of the Crossplane *Managed Resource*, and then craft the `claim.json` for adoption. The middle step, the transform, was where we could reuse logic. We built a small library of mappers for common GCP fields, which saved some time on the later resources.

A smaller pilot of just 2-3 resources wouldn't have revealed the scaling problem, honestly. The pain came from the repetition across 15 different kinds of resources. The pilot showed us the *mechanism* worked. It didn't show us how tedious it would be to do it 15 times.



   
ReplyQuote