Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.
Craig Jellick edited this page Mar 15, 2023 · 19 revisions

Welcome to Acorn's Developer Wiki!

This wiki's purpose is to document the information need to develop or contribute to the acorn project itself. If you are an acorn user looking for documentation on USING acorn, you are in the wrong place. Go here: https://docs.acorn.io

This wiki assumes working knowledge of Kubernetes and familiarity with the functionality of acorn.

Getting Started

To work on Acorn you're going to need the following:

  • A local Kubernetes cluster like Docker Desktop or Rancher Desktop
  • The Go SDK installed on your workstation
  • A Go IDE - @ibuildthecloud and @cjellick use GoLand, but take your pick

High level architecture

Acorn can be broken down into the following high-level components or areas:

  • cli
  • api-server
  • controller
  • Acornfile and the language that supports it
  • buildkit and internal regsitry

Acorn's integration into Kubernetes as an api-server and controller is powered by this custom framework:

It integrates with the following systems/services that are external to the main acorn project

  • acorn-dns
  • Let's Encrypt
  • Registries such as Docker Hub or GitHub Container Registry

A deeper-dive of the architecture decisions is available here.

Building

To build a new acorn, run: make build. But you usually will need to get that new binary into the acorn image so that you can run it. The following set of commands will build acorn, an image containing it, and launch acorn using that image. You can adapt to your own needs:

make build
docker build -t cjellick/acorn:dev .
docker push cjellick/acorn:dev
acorn install --image cjellick/acorn:dev

Running components locally

To speed development, you can run components locally from your IDE. This let's you quickly relaunch after making changes and even set debug breakpoints. Here's a quick breakdown on how to do that:

Controller

First, launch acorn with the controller scaled to zero:

acorn install --image cjellick/acorn:dev --controller-replicas=0

The image is still specified there in case the changes also affects the api-server, which is still running in-cluster.

Then, just create a debug/run configuration for your development environment that launches the controller. The locally running controller will know how to talk to your cluster's K8s api-server based on your kubeconfig. Integrating with your development environment's debugger will vary but there are two main ones that the Acorn maintainers use at this moment.

You will also want to ensure that the BAAAH_DEV_MODE environment variable is set to something (like "true"). This ensures that certain configuration is set for debugging and breakpoints.

Goland

Here's what the debug config can look like.

Screenshot 2023-02-01 at 13 58 24

Import Settings (Preferences > Editor > Code Style > Go > Import) image

Visual Studio Code

Here's a good starting point for a .vscode/launch.json that will get you integrated with the debugger.

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run Controller",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${workspaceRoot}",
            "args": ["controller"],
            "env": {
                "BAAAH_DEV_MODE": "true",
            }
        },
        {
            "name": "Run Acornfile",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${workspaceRoot}",
            "args": ["run", "."], 
        }
    ]
}

Api-server

If you want to run the api-server locally, the command is api-server. This will launch the the server on port 7443.

To get your acorn cli to hit the locally running server, you need to have a modified kubeconfig that points at the locally running server. You need to change the port to 7443 and tell the client to ignore certs. You make these modifications in the "cluster" object. For Rancher Desktop, it should look like this:

apiVersion: v1
clusters:
- cluster:
    server: https://127.0.0.1:7443
    insecure-skip-tls-verify: true
  name: rancher-desktop
contexts:
- context:
    cluster: rancher-desktop
    user: rancher-desktop
  name: rancher-desktop
current-context: rancher-desktop
kind: Config
preferences: {}
users:
- name: rancher-desktop
  user:
    client-certificate-data: ...

You should copy your ~/.kube/config file, delete unnecessary and extraneous entries related to other clusters, and make the above modifications. Note that this new kubeconfig will only be good for acorn requests. You cannot make general kubectl commands against it. So, you shouldn't set this as your primary kubeconfig, just set it in your current terminal session, via:

export KUBECONFIG=./config
acorn ps

When you run the api-server like this, you don't really need to scale the api-server running in-cluster to zero via --api-server-replicas 0. Both can be running at the same time. Indeed, it is sometimes useful to be able to run the same command against both and see the difference.

Running api server locally within the same kubeconfig

You can also run acorn custom api-server locally, while having kubectl be able to see custom resource and k8s resource. To do so, follow the steps:

  1. Delete your existing acorn-api service in acorn-system. This removes the reference to point k8s api server to acorn api service running in the pod.
  2. Create a new Headless service and endpoint that points to your localhost:7443(where your local acorn api-server is running). In rancher-desktop, you can point it to IP 192.168.5.2 as this is qemu gateway IP that points to your localhost. For other container runtime you will just have to modify that accordingly.
apiVersion: v1
kind: Service
metadata:
  name: acorn-api
  namespace: acorn-system
spec:
  ports:
    - protocol: TCP
      port: 7443
      targetPort: 7443
---
apiVersion: v1
kind: Endpoints
metadata:
  name: acorn-api
  namespace: acorn-system
subsets:
- addresses:
  - ip: 192.168.5.2
  ports:
  - name: ""
    port: 7443
    protocol: TCP

Other components/commands

There's nothing stopping you from running other commands out of an IDE too. For example, you can debug the install command in just the same way you debug the controller

Testing

NOTE: You must make an effort to write tests for your changes. It might not always be practical, but the project is only maintainable if we put effort into writing tests as we develop.

There are two primary types of tests in acorn: Unit and Integration.

Integration tests have their own top-level package and require a running instance of acorn. They will read and write to the K8s api.

Unit tests are interspersed throughout the codebase. The largest collection of them is in this package . Note the testdata directory in that package. Our unit tests often use a custom framework that tests our handlers. The framework is based on supplying input k8s objects and asserting the objects outputted by the handler.

Generating a New Unit Test

Within the template directory there is a default template structure for a unit test.

  • input.yaml (input k8s object that utilizes handler under test)
  • existing.yaml (existing k8s objects that are necessary for tests such as secrets/app data)
  • expected.yaml.d (directory containing expected k8s objects yaml files that are outputted from handler under test)

Pull Request CI (and how to prep for it)

We use GitHub actions for our CI. You can see the actions here: https://github.com/acorn-io/acorn/tree/main/.github/workflows

The important things you need to worry about are covered in CONTRIBUTING.md.

Making an API change

If you are adding or changing a field to acorn, you have a long row to hoe. If it is user-facing, it will need exposed in Acornfile, potentially support overriding in the CLI, added to the API types, "business logic" added to the controller to handle it, and of course tests.

This PR added "labels and annotation" support to Acorn. It comprehensively hits all the above areas. Though, it is a bit overly complex because it adds labels and annotations to multiple levels. Nevertheless, it is a good example of all the areas you need to touch if you're adding a field.

Issue tracking & releases

All of our planning is driven from GitHub issues, projects, and milestones.

This is our project board: https://github.com/orgs/acorn-io/projects/1

The numbered milestones, like v0.4.0, map to upcoming releases. There are a few other milestones with special meaning:

  • v.Next - Issues in this milestone have been tentatively slated for the release after the one we are currently working on. Often, this will be more of a "wish list" of things we want to get done and won't necessarily reflect our resource constraints.
  • Backlog - The milestone is used for triaging features, enhancements, bugs, and other issues into different priorities. Issues generally go from having no milestone to being in this milestone, unless they are high priority and get dropped directly into a release milestone.

Some guiding principles on issue management:

  • Issue state is THE source of truth for conveying what you're working on. Keep it up-to-date. It is how we track and plan releases
  • You can add an issue to a milestone yourself (assuming you have permissions), but if you do, bring it up in standup at explain why you're adding it
  • Pull requests DON'T get added to milestones, just issues do
  • When you open a PR, reference the issue it is addressing Keep your issues up to
  • The columns in our project board should hopefully be self-explanatory. Move your issues along the board as you work on them to ensure they reflect reality

Pro-Tips

On choosing a local Kubernetes cluster

Rancher Desktop works really well. It comes with a ingress, storage class, and load balancer built-in. If you are using it with the 'dockerd (moby)', you don't even need to push your acorn:dev image, just building and tagging it is enough.

That said, you should get comfortable with all the major local k8s distributions: Docker Desktop, minikube, Rancher Desktop, and more distantly kind and k3d. You may have to make or test changes specific to those distributions.

Working with baaah

Need to test or change something in baaah? You can use go workspaces to make this easy.

If you have acorn and baaah as siblings of each other in your director structure, you can leverage your local baaah in acorn by putting this go.work file in your acorn directory (not it's parent, which might seem more logical):

go 1.19

use (
	.
	../baaah
)

Renovate PR's

Renovate helps us keep Acorn's dependencies up-to-date. It does this by automatically opening PR's that do the work necessary for the updates. Looking at one of these PR's to merge? Make sure you're considering the following:

  • Most of the PR's that are opened can be merged if CI is green.
  • For Kubernetes dependencies patch updates to Kubernetes are fine to go in with a green CI; However minor and (infrequently) major releases will require a deep-dive into the patch notes.
  • We should only merge a renovate PR if it is early enough in the release to do QA.
  • Pay special consideration for changes that renovate proposes to the Dockerfile, they can be dangerous.

Helpful k8s debug commands

One-liners to tail acorn controller and api-server logs

# controller
kubectl logs -l app=acorn-controller -n acorn-system -f

# api-server
kubectl logs -l app=acorn-api -n acorn-system -f

Locally deploying a dev version of acorn

When installing acorn, if your image tag starts with a v, the pull policy will be set to ifNotPresent. Otherwise it will be set to Always.

This is helpful to know for local dev because if you are using docker desktop (or rancher desktop with the docker backend), you can just build the image locally rather than having to push it to a registry and have k8s pull it. So, a local dev workflow might look like:

make build
docker build -t acorn:vdev .
acorn install --image acorn:vdev

# Make some changes to the code base, run the above commands to rebuild, and then delete the controller pod to have it relaunched with the newer image:
kubectl delete -l app=acorn-controller -n acorn-system pods

# Or the api-server, which uses the same image:
kubectl delete -l app=acorn-api -n acorn-system pods