Exclamation You have stumbled across the development site for xeiaso.net. Please do not share this link with others, don't be the reason I need something more robust than the honor system.

Slowly going mad with power using Tekton

Published on , 1228 words, 5 minutes to read

Starting the slow migration off of GitHub, one CI pipeline at a time

An image of
- Photo by Xe Iaso

GitHub has been the foundation of my career since before it began. I've been using it since 2010 (almost half my life, wow) and I have over 400 repos there from all stages of competence and my career. I'm not feeling good about its future given how unreliable it's been lately.

As part of the planning for a post-GitHub future, I've been looking at one of the hardest things to move: GitHub Actions. I've used GitHub Actions for most of my career and only have vague memories of Jenkins, Travis, and the other things I've used before. Most of my repos only have GitHub Actions (with a small fraction of them having Gitea Actions from when I experimented with that) and a lot of my assumptions around how CI works are centered around the implementation of GitHub actions.

Mara is hacker
Mara

CI/CD (Continuous Integration / Continuous Delivery) is a software practice that makes servers run tests and deploy code automatically as changes are committed. In practice, this means that you have something running tests and deploying software when changes are committed. The group of build/test/deploy steps is usually called a pipeline.

The other thing I'm gonna have a hard time getting over is all the basically free compute that GitHub Actions provides, which is significant when you look at how much Anubis does per commit. However, I do have a mildly absurd amount of compute laying around, so that may just even itself out I guess.

If I have to move my CI to a new solution that's going to require me to rewrite it all from scratch or adapt it over, I really want to settle on a vendor-neutral implementation that isn't beholden to a single platform. This would make me slightly more resilient against future enshittification (it's a matter of when, not if).

Mara is hacker
Mara

In this article, KubernetesTerms will be in JavaClassNameCase. If you're not sure what one of them is, search this in DuckDuckGo:

site:kubernetes.io KubernetesTerm

Tekton and you

Recently I discovered Tekton, a Kubernetes operator (read: plugin) that breaks CI/CD pipelines into Kubernetes objects. It's also as far as I can tell the only real vendor-neutral CI/CD pipeline solution. There's also a really neat bridge between Tangled.org and Tekton called Tack.

For the sake of this post, let's say that I'm going to settle on the TTT stack: Tangled, Tack, and Tekton.

Tekton in a nutshell

In order to help you understand where/why Tekton fits into the stack, let's think about it like this: Tekton is the layer under something like GitHub Actions. Tekton takes CI pipeline jobs and schedules them onto Kubernetes. Kubernetes handles scheduling pods across machines, durable storage between steps in the pipeline, networking between pods, logging, and other orchestration that you need in practice.

In Tekton, your CI/CD jobs are broken out into a few basic categories:

Here's an example Task:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: kubectl-apply
  labels:
    app.kubernetes.io/version: "0.1"
spec:
  description: |
    Run kubectl apply
  params:
    - name: image
      description: Docker image for kubectl
      default: "cgr.dev/chainguard/kubectl:latest"
    - name: args
      description: Args to pass to `kubectl apply`
      type: array
      default: []
    - name: subfolder
      description: subfolder that the repo is actually in
      default: "/repo"
  workspaces:
    - name: repo
      description: Repo to operate in
  steps:
    - name: chown-chmod
      image: $(params.image)
      workingDir: $(workspaces.source.path)$(params.subfolder)
      args:
        - apply
        - $(params.args[*])
      securityContext:
        runAsUser: 0

This looks like a lot, but that's mostly because Tekton is completely unopinionated. It places a lot of the assumptions that GitHub Actions or Travis makes out of the way so you build up your desired pipeline from scratch. I copied this Task out of my Tekton tasks repo in case you want to dig through the other ones I use.

Those Tasks are then chained together into Pipelines like this:

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: xeiaso-net-alrest-autoapply
  namespace: ci
spec:
  description: |
    Autoapply for xeiaso.net/alrest manifests.
  params:
    - name: repo-url
      type: string
      description: "Git repo to clone"
      default: "https://tangled.org/xeiaso.net/alrest"
    - name: "commit"
      type: string
      description: "Git revision to check out"
  workspaces:
    - name: repo
      description: |
        Cloned repo files.
  tasks:
    - name: fix-permissions
      taskRef:
        name: fix-permissions
      workspaces:
        - name: dir
          workspace: repo
    - name: clone-repo
      runAfter: ["fix-permissions"]
      taskRef:
        name: git-clone
      workspaces:
        - name: output
          workspace: repo
      params:
        - name: url
          value: $(params.repo-url)
        - name: revision
          value: $(params.commit)
    - name: kubectl-apply
      runAfter: ["clone-repo"]
      taskSpec:
        workspaces:
          - name: repo
        steps:
          - name: do-apply
            workingDir: $(workspaces.repo.path)/repo
            image: cgr.dev/chainguard/kubectl:latest
            args:
              - apply
              - -k
              - $(workspaces.repo.path)/repo

You would then invoke this Pipeline by creating a PipelineRun object:

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: tack-autoapply-yaml-7c5e518b6c60
  namespace: ci
spec:
  params:
    - name: commit
      value: 838e7e0e83df04d54c035b9c60b852ff56852b4a
  pipelineRef:
    name: xeiaso-net-alrest-autoapply
  taskRunTemplate:
    serviceAccountName: xeiaso-net-alrest-autoapply
  timeouts:
    pipeline: 1h0m0s
  workspaces:
    - name: repo
      volumeClaimTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 4Gi

You can see how this fits together, right? PipelineRuns bind values to Pipelines, which bind values to Tasks, which sequence running Pods in your cluster. This then gets turned into green and red commit statuses in the Tangled UI via Tack.

Walking through a single pipeline

From here I think it'd be useful to walk through a single CI pipeline, the one I use in kefka (a work-in-progress port of just-bash to Go, I plan to write more about it soon, I just need the energy/time). I had to make some compromises in my cluster because of how my storage system works, but you'll see more when it's time.


Facts and circumstances may have changed since publication. Please contact me before jumping to conclusions if something seems wrong or unclear.

Tags: