Unleashing Skynet: Maximizing CI/CD Efficiency with ARC’s Self-hosted Runners Inspired by ‘The Terminator’

Adefemi Afuwpe
9 min readOct 4, 2023
Generated by Hackernoon

In the fictional universe of ‘The Terminator’, Skynet’s automated machines were developed with the overall goal of carrying out their tasks effectively. They continuously adapt, pick up new skills, and work. ARC’s self-hosted runners exhibit a similar attitude of unrelenting efficiency in the area of Continuous Integration and Continuous Delivery (CI/CD). Stick with me, despite the fact that the comparison might seem a bit far. Let’s take a journey through the analogies, investigating the universe of “The Terminator” and the potent automation tools offered by Actions Runner Controller (ARC), while also shining a light on how GitHub Actions operate.

via Giphy

Before diving deeper, let’s understand GitHub Actions, a platform that facilitates CI/CD. GitHub Actions allows developers to automate workflows directly in their GitHub repository. It can run tests, deploy code, and more. By default, it uses GitHub-hosted runners (virtual environments) to execute workflows. GitHub Action is based on events, which are based on what occurs in your repository (push or pull request); this then triggers runners, and your workflow gets executed by these runners. The workflow, of course, includes job(s), in which you can have one or more steps that, in turn, run your scripts or actions.

Event workflow from GitHub
Photo by GitHub

I have read about ARC: Actions Runner Controller from the Black In Tech Community, and the first thought that came to mind was the movie Terminator. While the tool itself does not wage war on humanity like Skynet, it serves a mission to streamline your CI/CD process Just like a terminator on a mission. ARC runners can be tailored for specific system requirements, ensuring that the CI/CD pipelines run swiftly and smoothly.

In this post, I will be walking you through how to install ARC into a local Kubernetes cluster.

You might be asking some questions, such as: Why use a self-hosted runner? Why not just use GitHub’s runner? Firstly, I will point out their pricing page. In short, you can choose to pay $56 for 5000 minutes for 2vCPU or pay less to rent a node and run your actions there. There is also the ability to select your own OS, increase your build time (having idle runners), and have less downtime as Github runner is hosted on Azure, which means your runner is dependent on Azure’s server health. Lastly, there’s better observability on your runner, and depending on your setup, you can also make your runner only accessible via VPN.

Requirements

To follow these installations, you need the following installed:

  1. Docker
  2. Kubernetes Cluster (I will be using Kind to run a local cluster)
  3. Helm
  4. Github Personal Access Token
via Giphy

Setting up a local Cluster

While there are various tools you can use to have a local Kubernetes cluster, I have a strong love for Kind because it is lightweight and, as the name implies, Kubernetes in Docker. This means you can have multiple nodes set up, all running as a docker container, so if you are wondering why I chose them Kindly read more about them (You see what I did there 💀).

I have written a script that can aid with creating a cluster using Kind. I am creating my cluster with two worker nodes and one control-plane. You can choose to use one worker node or more

kind: Cluster
name: arc-demo
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.22.17@sha256:f5b2e5698c6c9d6d0adc419c0deae21a425c07d81bbf3b6a6834042f25d4fba2
- role: worker
image: kindest/node:v1.22.17@sha256:f5b2e5698c6c9d6d0adc419c0deae21a425c07d81bbf3b6a6834042f25d4fba2
- role: worker
image: kindest/node:v1.22.17@sha256:f5b2e5698c6c9d6d0adc419c0deae21a425c07d81bbf3b6a6834042f25d4fba2

I am creating the above using:

kind create cluster --name arc --config kind.yaml 
cluster _reation

Cluster Running: what next?

via Giphy

ARC provides an easy method of installation. You can either use Kubectl or Helm. I will be using Helm, which is a package manager for Kubernetes.

We need to install two packages using Helm; one is the Controller and the other is the Runner scale set.

First, we need to add the chart repository, then update information of available charts locally from chart repositories, and lastly, install the chart.

#Add Repositories
helm repo add jetstack https://charts.jetstack.io
helm repo add actions-runner-controller https://actions-runner-controller.github.io/actions-runner-controller

#Update Chart repositories
helm repo update
add_repo
update_repo

The Action Runner Controller, when installed, creates a manager controller pod in the namespace specified. An AutoScalingRunnerSet resource is also deployed alongside; this controller calls GitHub’s APIs to fetch the runner group ID that the runner scale set will belong to. To install the controller, use the command below:

helm install arc-runner-set --namespace arc-runners --create-namespace -f runner-1/values.yaml oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set --version "0.4.0"

If you get an error related to the feature being marked as experimental, export HELM_EXPERIMENTAL_OCI=1 in your shell

controller_installation

After installing the controller, you will also need to install the Runner scale set. This resource creates a pod in which the listener applications connect to the GitHub Action Service to authenticate and establish an HTTPS long-poll connection. The listener stays idle until it receives a message from the GitHub Actions Service.

helm install "arc-runner-set" --namespace "arc-runners" --create-namespace --set githubConfigUrl="<REPO_URL>" --set githubConfigSecret.github_token="PAT" --set containerMode.type="dind" --set maxRunners="3" --set minRunners="1" oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set --version "0.4.0"
install_runner_scale_set

You will notice that in the command above I am setting some additional variables; these variables can be seen in ARC’s chart values.yaml file. I mentioned faster build time when I was referring to why you should create a self-hosted runner. With some of the attributes I passed, you can have a set of minimum runners that are idle and waiting for jobs, so when you kickstart your workflow, a new runner does not have to come online since you already have idle runners. You will also notice I am using dind as my container mode type; this is the default mode type from the ARC chart but you can change this to use Kubernetes, and I will speak more about that in my conclusion. Lastly, you will notice the namespace in which I am creating my runner set is different from my controller. You should not have them in the same namespace, as your runner set will fail to start.

Before moving forward, you will notice that I passed some GitHub configuration to the command as well. You should create a repo to test the runner and also create a GitHub Personal Access Token. While creating the token, some permissions are needed depending on the organization level (if you are creating the runner for your GitHub organization) and the repository level (if you are targeting just the repository).

org_repo_access

With all these setups done, let us check our cluster to see how many pods we have running.

# Check pod on arc-system namespace
kubectl get po -n arc-systems

#check pod on arc runner namespace
kubectl get po -n arc-runners
pod_status

Note: If you have only one pod running, inspect the controller logs and see what the issue is. Mostly, the logs from the pod let you know if you have some misconfiguration. You can then update your runner-scale-set with the correct values. Speaking from experience, most of the time the issue always comes from token misconfiguration.

Use the command below to inspect the logs, and you should have an output close to mine:

kubectl logs -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller
controller_pod_log_output

Set Up Done

via Giphy

When you have a successful setup, the next step will be to test this but before we test, let us see if we have our runners available in the GitHub repo we have specified

runner_available

For this test, I have created a simple Typescript application that does basic arithmetic (addition, subtraction, division, and multiplication). I am also writing a simple test using Jest. The code can be found in this repo.
I have also created a simple workflow that does the installation and runs the test.

name: Test Arc
on:
workflow_dispatch:
inputs:
delay:
description: 'Delay in seconds before starting the runner'
required: true
default: '2'

jobs:
test-job:
runs-on: arc-runner-set
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Use Node 17.x
uses: actions/setup-node@v3
with:
node-version: '17.x'

- name: Install dependencies
run: npm ci

- name: Test
run: npm test

For my workflow, I have set up a workflow dispatch to allow me to run this job myself and also added a timer in seconds for the action to start. Let me draw your attention to the runs-on line. You will see here that I am using the same name as what I described when installing the runner scale set. This name should be the same. When I trigger this pipeline, you will notice that your runner, which was in an idle state, will then become available and your workflow will be executed.

triggering_d_pipeline
job_completion

Before concluding, I want to draw our attention to the number of pods that were available in the arc-runners namespace; this should be one if you follow the same setup as I did. The available pod is the one in which the workflow is running. If we describe this pod, you will notice that it has an init container and two more containers (Github Actions runner image and dind image). This is because we are using dind (Docker in Docker), and if you inspect the pod in which the runner is executing the action, you will also notice that this pod does a clean-up by itself. This means that once your action is successfully executed, the pod cleans up and gets ready to be picked up by a new action.

To check the content or describe a pod, you can use this command:

kubectl describe arc-runner-set-m6p84-runner-wjjll -n arc-runners
images_running_in_the_pod

I used the below command to get the logs command; you can also use tools such as k9s:

kubectl logs arc-runner-set-m6p84-runner-wjjll -n arc-runners
runnerLogs

Conclusion

via Giphy

In summary, ARC’s self-hosted runners are the epitome of automation, effectiveness, and adaptability, much like the Terminators.

A recap of what was covered:

  1. We looked into running and configuring a local cluster
  2. Running a self-hosted Runner using ARC’s Helm chart
  3. Debugging the pods running in the runner (I bet you have been waiting for this🥹).
  4. Creating a simple workflow on GitHub Actions

I mentioned earlier that you can change the container type to Kubernetes, and the reason why you will want to use this is if your workflow includes building a Docker image. If you use the type dind, your workflow will fail, and this is because of the architecture of ARC.

If you want me to write about using Kubernetes as the container type, let me know in the comments.

via Giphy

--

--