Inspiration

With an agentic tool that finally was up to following my plans, I decided to build an idea I've had for a long time. One that would need skills that I don't have and a deep understanding of metaprogramming:

Build infrastructure configuration in typescript that runs inside the kubernetes cluster's reconciliation loop. I was inspired to build this at the start of this year after seeing awslabs release KRO, a resource orchestrator which does this with YAML, but nobody likes YAML.

What it does

I used Kiro to build TypeKro. A way to define declarative and dynamic infrastructure configuration that bridges the gap between the developer and the kubernetes control plane.

Consider this code snippet:

import { type } from 'arktype';
import { kubernetesComposition } from 'typekro';
import { Deployment, Service } from 'typekro/simple';

const webapp = kubernetesComposition(
  {
    name: 'webapp',
    apiVersion: 'example.com/v1',
    kind: 'WebApp',
    spec: type({ replicas: 'number' }),
    status: type({ ready: 'boolean' })
  },
  (spec) => {
    const deployment = Deployment({
      name: 'webapp',
      image: 'nginx',
      replicas: spec.replicas
    });

    const service = Service({
      name: 'webapp-service',
      selector: { app: 'webapp' },
      ports: [{ port: 80 }]
    });

    return {
      // ✨ Natural JavaScript - automatically converted to CEL
      ready: deployment.status.readyReplicas > 0
    };  
  }
);

await webapp.factory('direct').deploy({ replicas: 3 });

You can adjust the factory options to stream kubernetes control plane logs associated with the composition into your terminal, to wait for resources to stabilize, and to hydrate the status fields so you can consume the dynamic values resolved by your kubernetes control plane.

How we built it

I worked for hours on Kiro specs, and then let Kiro do most of the heavy lifting. To see the architecture, review the architecture document in the kiro steering docs, and to see the specs, see the kiro specs. There's a lot there.

I first built a declarative resource graph API with a ES proxy that turns unknown values into references that can be resolved at runtime. Then I built a serializer that serializes this into KRO YAML.

Then I and Kiro built an imperative wrapper for better developer experience.

Then I and Kiro built a CelExpression parser so that you can have the typekro runtime manage the dependencies on clusters that KRO is not installed on.

Then I and Kiro changed the API to deploy resource graphs to follow the factory pattern, with deployment options so that the same compositions could be deployed in multiple modes.

Then I and Kiro built a event monitor to stream kubernetes events associated with a resource graph back to typekro during deployments.

Then I and Kiro built a javascript to cel compilation engine so regular javascript syntax can be used to specify logic that is executed within the kro controller or typekro runtime depending on deployment options.

Then I and Kiro built support for Helm and Yaml as part of kubernetes compositions so you can easily integrate with third party kubernetes tooling.

Then I and Kiro built a composition to deploy CI/CD components KRO and FluxCD necessary to work with Helm and TypeKro resources requiring continuous reconciliation.

Then I built out a docs site with Kiro and started releasing to production.

Then I started building out integrations with third party kubernetes tools like externalDNS, Cilium, ApiSix, Cert Manager, and used these integrations to work on finding edgecases in the platform and resolving them.

Challenges we ran into

I ran into a lot of challenges along the way and spent a lot of time optimizing my steering docs to address them.

The largest challenge was that the es proxy and javascript to cel conversion features created discrepancy between the runtime types of the TypeKro runtime and the compile time types the compiler was reporting. Kiro kept on trying to break the abstractions. I spent a good amount of time writing steering docs, tests, and comments to keep Kiro from creating regressions in my type system. But with the right mix of docs, tests, and comments, Kiro understood the expectations well enough.

Aside from this, I ran into some issues where Kiro would change the assertions in the tests rather than the behavior of the app. Which I dealt with with steering docs too.

Steering docs and comments together with mature specs were essential in getting Kiro to execute effectively.

Accomplishments that we're proud of

I have a 26 star open source project on github, where I only know 3 of the stargazers. I also have a really cool open source project that I think provides an unparalleled kubernetes development experience. I've tried a bunch of them, and TypeKro has really improved my development experience.

What we learned

I didn't believe it was possible to build difficult software so clearly. I think iterating on specs and providing clear steering and clear comments really enables you to focus on software architecture and get to market way faster.

This is aside from what the Kiro agent taught me about about typescript meta programming and web programming. It's been a super fun experience, and I've deeply appreciated it.

What's next for TypeKro

I'm going to try turn TypeKro into the best Kubernetes development tool out there. I'm going after the Kubernetes ecosystem, aiming to provide easy installation of core Kubernetes components. We've got cert manager, apisix, cilium, helm, and external dns already decently supported. Next up is postgres, temporal, ory, aws and other cloud controllers, and way more.

Statically typed, easily tested Kubernetes is where we go next.

Built With

Share this project:

Updates