Using helm library charts to improve DevEx with Tyk Operator

Our clients over at Move.com have written another top blog! – this time about how they used helm library charts to improve developer experience with Tyk Operator. Take it away, friends!

Let’s start with a hot take 🔥. Documentation is not as important as you think it is. Before you put this article down and write me off, hear me out…it gets better. I am not saying documentation isn’t important; I am just saying that it might not be the most efficient way to improve developer experience.

As programmers, we don’t relish the thought of writing documentation, but we love to tell people to RTFM (read The f’ing manual). What if I told you there was a way to write less documentation, but also have fewer user support questions coming your way? Sounds pretty good, right? So, what is it?

The answer is good design. A good design can be worth a thousand words…in documentation.

I love the way that this is summed up in the following meme:

 

Based on the original from Fernando Villalba.

Don’t get me wrong, I think documentation is critical, but I think that we often rely too heavily on it. If you work with Tyk, you are likely in a DevOps or Platform Engineering role. As engineers in this discipline, it is our job to reduce the cognitive load on our domain teams, not add to it.

If we can learn to make our interfaces simple and intuitive, our users will be happier because they understand how to use our products, and our own teams will be happier because there is less support work distracting from adding sweet – sweet, new features!

What is good design?

So what is good design? Unfortunately, it is easier to say than it is to do. It is more art than science, but we can give some general guidelines. Typically, good design is intuitive, meaning that the purpose of the product is plain to see without requiring specialized knowledge.

Users can intuit what it is meant to do and how to use it. Further, simplicity is often a hallmark of good design. Einstein is often quoted as saying, “Everything should be made as simple as possible, but not simpler”.

Design applied to Tyk APIDefinitions

But you came here to talk about API Gateways…why am I ranting on about design? The answer is that these are closely related. At Realtor.com, we strive to improve our developer platform and make API Gateway functionality self-service.

As discussed in the previous article in this series, we do this by leveraging Tyk Operator, but that’s only part of the story. We don’t just use the Operator CRDs out of the box; we work to make their design fit our environment.

The Tyk APIDefinition is awesome! It provides a ton of functionality, but all those features require a good deal of upfront investment in learning the APIDefinition spec and all associated variables you can tweak and tune. This is very powerful but can also overwhelm developers who do not work directly on the platform team.

In the DevOps space, we should constantly remind ourselves that every bit of cognitive burden we add to our developer’s experience comes at the cost of reduced velocity for their feature work. We should be judicious when we ask users to learn new things like the Tyk APIDefinition spec and try to minimize our asks on our client teams. It is likely that the majority of our clients don’t need to understand the full APIDefinition spec, and just need to use a few specific features for their use case.

How can we provide this? For us, the answer has been helm library charts.

What is a helm library chart?

You can think of a helm library chart as like a software library. It is a chart that provides reusable functionality to its users, thereby simplifying the creation of Kubernetes resources. Like most things, it is best explained by example. So, let’s back up and talk about our use case at Realtor.com to demonstrate how helm library charts make this simpler.

At Realtor, we have multiple teams making use of the Tyk Gateway. We have the typical use case of APIDefinitions fronting our backend APIs. For example, we have a GraphQL route and several routes in front of our Authentication endpoints, but, additionally, we use Tyk as an ingress controller to proxy traffic into our Kubernetes clusters.

For us, these functions are sufficiently different that separating them into separate shards makes sense. We call the shard fronting our APIs the API shard and the shard that handles the web shard because it typically handles ingress to NextJS backend servers that handle server-side page rendering.

The teams providing APIs and web apps can self-serve their own routing within our Tyk Gateway shards, which requires them to add either an `API` or a `web` tag to their APIDefinition.

We have several other differing configurations between these two use cases as well. For example, the domains can differ; some use cases require authentication, and others don’t.

So, how can we ensure that teams create the right APIDefinition for their use case? Should they need to know about Tyk Gateway shards? JWT Token Validation? The underlying details for host-based routing? We could rely on documentation to convey this information, but this would require a lot of additional time from our domain teams which, as we discussed earlier, we’d have to pay for in reduced productivity. It would be better if we could provide them with an intuitive design. This is exactly what helm library charts allow us to do.

What is so good about helm library charts?

I’ve teased this for a while, so let’s get down to it…what makes helm library charts so great? For us, they can simplify the API exposed to domain teams. With a library chart, you can reduce the number of variables domain teams need to know about.

Let’s walk through an example of this with keyless APIDefinitions. We aim to make it as simple as possible for teams to create a keyless APIDefinition. This will require the following steps:

1. Write the helm templates that will be supplied to clients.

2. Publish the chart to a helm repository.

3. Use the library chart in a client application.

Writing helm templates for a library chart

A library chart will need its own Git Repository, which at its simplest, could have the following structure.

│   ── library-chart

│   ├── Chart.yaml

│   ├── templates

│   │   ├── _template1.yaml

│   │   └── _template2.yaml

The Chart.yaml file will declare metadata related to this library chart. Here is an example of what this looks like:

---

apiVersion: v2

name: realtor-library-chart

type: library

version: 0.0.1

maintainers:

  - name: Developers Experience Team

    email: <your-email>

description: |-

  This library chart is a Helm Library Chart

  that provides a set of customizable definitions for

  common use cases.

Note that the type is specified as library, here.

In addition to the Chart.yaml file, we can include helm templates; this is where the magic happens. With these resources, we can provide a template that handles much of the underlying complexity and allows our domain teams to provide some basic configuration within their values files. For the use case we discussed above, we provide a template for APIDefinitions in the API shard and another in the Web shard.

The full APIDefiniton is too large to cover in depth here, but I’d like to give you a feel for the sorts of things you can provide your user with a template. Don’t focus too much on the syntax; you can learn more here. The goal is to show, by example, how you can move complexity away from your users and into a library chart.

One problem we face is communicating which URLs correspond to our internal and external endpoints. We provide a CloudFront endpoint for external clients and a URL for internal access to the Tyk Gateway. We could document this, but getting these addresses right is tricky and error-prone, and it means developers have to memorize or look up these details each time they want to add an API definition.

To move this complexity to the backend, we ask for an environment (dev, stage, or prod) and calculate the appropriate URL in our template. This code is placed in the template directory from the layout above and looks similar to the one below:

{{- define "microservices.keylessApiDefinitions" -}}

...

{{- $defaultDomains := dict "dev" (dict "external" "tyk.sandbox-dev.realtor.com" "internal" "tyk.dev.moveaws.com") "prod" (dict "external" "tyk.frontdoor.realtor.com" "internal" "tyk.prod.moveaws.com") "sandbox" (dict "external" "tyk.sandbox-dev.realtor.com" "internal" "tyk.dev.moveaws.com") "stag" (dict "external" "tyk.qa.realtor.com" "internal" "tyk.stag.moveaws.com") -}}

Another detail we want to hide from our users is whether their APIDefinition is deployed to our Web or API shard. To make this possible, we provide two templates, one for API and one for Web, and then set the appropriate tag based on the template chosen.

Finally, we wanted our users to be able to define caching for their endpoints easily, so we wrote a plugin that handles integration with Redis. We then have users specify a TTL for their APIDefinition and caching happens automatically.

These are just a few examples, but we have made several other choices guided by our philosophy that it is best to expose only what is necessary to our users and handle the details behind the scenes. In the end, users can configure an API by providing a few values to a helm chart. A typical example might look like this:

keylessApiDefinitions:

  example-modules:

    target_url: https://example-api.dev.moveaws.com/api/example-modules

    listen_path: /api/example-modules

    strip_listen_path: true

    enableInternalDomain: true

    enableExternalDomain: true

    frontdoor_cache:

      enabled: true

      ttl_seconds: 259200 # Linking modules TTL for 3 days

 

Once this is rendered into the full API spec, it might be something more like this:

 

apiVersion: tyk.tyk.io/v1alpha1

kind: ApiDefinition

metadata:

name: example-modules-keyless

namespace: rdc-seo

spec:

active: true

config_data:

  frontdoor_cache:

    cookies:

      - split_tcv

    enabled: true

    ttl_seconds: 259200

custom_middleware:

  driver: goplugin

  post:

    - name: FrontdoorCacheRequest

      path: >-

        /etc/tyk-gateway/efs-data/frontdoor-cache-plugin/release/frontdoor-cache-plugin-1.x.x.so

  response:

    - name: FrontdoorCacheResponse

      path: >-

      /etc/tyk-gateway/efs-data/frontdoor-cache-plugin/release/frontdoor-cache-plugin-1.x.x.so

domain: >-

  {subdomain:tyk.sandbox-dev.realtor.com|tyk.dev.moveaws.com}

name: example-modules-keyless

protocol: http

proxy:

  listen_path: /api/example-modules

  preserve_host_header: false

  strip_listen_path: true

  target_url: 'https://example-api.dev.moveaws.com/api/example-modules'

use_keyless: true

 

The platform developer’s best friend 

In revolutionizing our developer experience with Tyk Operator, helm library charts have emerged as a pivotal solution, allowing us to prioritize design over exhaustive documentation.

By encapsulating key functionalities and streamlining Kubernetes resource creation, these charts simplify Tyk APIDefinitions, reducing cognitive load for development teams.

Helm library charts have proven to be a platform developer’s best friend, significantly cutting code complexity and empowering teams to focus on core feature work. If you want to experience the efficiency and simplicity of helm library charts with Tyk Operator – the key to streamlined Tyk API Gateway workflows, check it out here.