Tools: Setting Up Self-Hosted Runners As Pods

Tools: Setting Up Self-Hosted Runners As Pods

Prerequisites ### 🟣Kubectl ### 🟣Helm ### 🟣Kind ### 🟣Cert Manager ### 🟣Setting Up Actions Runner Controller (ARC) ### 🟣Configure GitHub Authentication ### Personal Access Token ### Github Apps ### 🟣Create a Secret ### Create a Runner Scale Set ### 🟣Create the workflow file to test your configurations ### Conclusion ### References Automation is a big part of modern technology, and for DevOps engineers, managing CI/CD pipelines is a key responsibility. Our goal is to make sure these pipelines run fast, cost-efficiently, and securely when deploying applications. Kubernetes pods can be used as self-hosted runners in CI/CD pipelines. Instead of relying entirely on shared runners or maintaining a dedicated server just for CI/CD tasks, this approach lets you control how build environments are created, scaled, and destroyed. This improves scalability, resource efficiency and also helps ensure our applications are deployed more securely. In this hands on project, ill be walking you through how to setup a self hosted runner as a pod on kubernetes using kind. This is a Kubernetes command-line tool, that allows you to run commands against Kubernetes clusters. You can use kubectl to deploy applications, inspect and manage cluster resources, and view logs. To install kubectl run this command on your terminal Helm is the package manager for Kubernetes, that helps you install applications using pre-packaged templates called charts. To install helm follow this step This is a tool for running local Kubernetes clusters using Docker container as nodes. It is used for testing kubernetes applications locally. You can install kind following the steps here This helps adds certificates and certificate issuers as resource types in Kubernetes clusters, and simplifies the process of obtaining, renewing and using those certificates. It supports issuing certificates from a variety of sources, including Let's Encrypt (ACME), HashiCorp Vault, and CyberArk Certificate Manager, as well as local in-cluster issuance. This is here because the runner controller uses admission webhooks, which requires valid TLS certificates for secure communication with the Kubernetes API server. cert-manager automatically generates and manages these certificates, ensuring the webhooks function properly without manual certificate management. We can install cert-manager on the cluster using the kubectl command This is a Kubernetes operator that orchestrates and scales self-hosted runners for GitHub Actions. With ARC, you can create runner scale sets that automatically scale based on the number of workflows running in your repository, organization, or enterprise. To set this up on kind, we'll use helm to install the chart on our local node To do this step, there are two options: you can either use a Personal Access Token or set up a GitHub App. Going the Personal Access Token route requires rotating the token regularly for security purposes, so using a GitHub App is generally recommended for production environments. In this hands-on guide, we will demonstrate both methods, starting with the Personal Access Token approach then Github Apps To set this up on your Github profile, go to Settings → Developer Settings → Personal access tokens → Tokens (classic), click on generate new tokens. Select the name of the token, For repository runners you usually need permissions like: repo; admin:repo_hook, then generate the token To configure this, go to GitHub Settings → Developer Settings, select GitHub Apps, create a new GitHub Apps. Fill the necessary information like, App name; arc-runner-controller, Homepage URL; in this case https://github.com/PreciousDipe/Self-Hosted-Runner-On-Pods, uncheck the active button for Webhook URL, under permissions select repository permissions and choose these; Actions → Read, Metadata → Read, Administration → Read & Write, Click on create Github App. After creation you will get: App ID, Client ID. To complete the step, generate a private key, This downloads a .pem file. That we will be used for authentication. Install the github app, ensure the app is installed The next thing we need to do is to use the credentials we've created in the previous step to create a secret in kubernetes. This tells Kubernetes how to spin up pods when jobs are queued, scale the number of runners up or down based on workload, and destroy pods once the jobs finish. we will be using helm to install the runner set from the gha-runner-scale-set helm chart Depending on the method you want to use, either it’s the PAT or GitHub Apps, just replace the runs-on with your Helm installation name. Here, we’re using the GitHub Apps method. looking at the image above we can see 2 pods running in the cluster, that is the runner-set-dlxqc-runner-vkm65 pod also know as the controller pod, this pod manages the runner lifecycle, while the other pod runner-set-github-actions-runner-controller-7bc8bd578xxlxf is the listener pod, and this pod helps to establish a connection to GitHub, watches for new jobs in your repo, and requests an ephemeral runner pods when jobs arrive. These pods are "always-on" components. We can see that the runners picked up the jobs on the pipeline and executed the workflow successfully. GitHub App configuration worked, Kubernetes spun up an ephemeral pod as a runner, which picked up the job from GitHub and executed it, achieving dynamic, secure CI/CD without relying on traditional self-hosted servers. Click here to check out the project on Github Templates let you quickly answer FAQs or store snippets for re-use. as well , this person and/or COMMAND_BLOCK:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" ## Install the binary file
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl ## To check the kubectl has been installed kubectl version --client COMMAND_BLOCK:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" ## Install the binary file
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl ## To check the kubectl has been installed kubectl version --client COMMAND_BLOCK:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" ## Install the binary file
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl ## To check the kubectl has been installed kubectl version --client COMMAND_BLOCK:
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 chmod 700 get_helm.sh ./get_helm.sh COMMAND_BLOCK:
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 chmod 700 get_helm.sh ./get_helm.sh COMMAND_BLOCK:
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 chmod 700 get_helm.sh ./get_helm.sh COMMAND_BLOCK:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.4/cert-manager.yaml COMMAND_BLOCK:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.4/cert-manager.yaml COMMAND_BLOCK:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.4/cert-manager.yaml CODE_BLOCK:
NAMESPACE="github-runner"
helm install arc \ --namespace "${NAMESPACE}" \ --create-namespace \ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller CODE_BLOCK:
NAMESPACE="github-runner"
helm install arc \ --namespace "${NAMESPACE}" \ --create-namespace \ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller CODE_BLOCK:
NAMESPACE="github-runner"
helm install arc \ --namespace "${NAMESPACE}" \ --create-namespace \ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller CODE_BLOCK:
RUNNER_NS="arc-runners"
GITHUB_PAT="Your_Generated_PAT" kubectl create secret generic arc-github-config \ --namespace ${RUNNER_NS} \ --from-literal=github_token="${GITHUB_PAT}" CODE_BLOCK:
RUNNER_NS="arc-runners"
GITHUB_PAT="Your_Generated_PAT" kubectl create secret generic arc-github-config \ --namespace ${RUNNER_NS} \ --from-literal=github_token="${GITHUB_PAT}" CODE_BLOCK:
RUNNER_NS="arc-runners"
GITHUB_PAT="Your_Generated_PAT" kubectl create secret generic arc-github-config \ --namespace ${RUNNER_NS} \ --from-literal=github_token="${GITHUB_PAT}" CODE_BLOCK:
RUNNER_NS="arc-runners"
APP_ID="Generated-App-ID"
INSTALLATION_ID="Generated-Installation-ID" (Click on your app on the installation page, and the installation ID will be the number at the end of the URL)
PATH_TO_PRIVATE_KEY=./path-to-pem-file kubectl create secret generic controller-manager \ --namespace ${NAMESPACE} \ --from-literal=github_app_id="${APP_ID}" \ --from-literal=github_app_installation_id="${INSTALLATION_ID}" \ --from-file=github_app_private_key="${PATH_TO_PRIVATE_KEY}" CODE_BLOCK:
RUNNER_NS="arc-runners"
APP_ID="Generated-App-ID"
INSTALLATION_ID="Generated-Installation-ID" (Click on your app on the installation page, and the installation ID will be the number at the end of the URL)
PATH_TO_PRIVATE_KEY=./path-to-pem-file kubectl create secret generic controller-manager \ --namespace ${NAMESPACE} \ --from-literal=github_app_id="${APP_ID}" \ --from-literal=github_app_installation_id="${INSTALLATION_ID}" \ --from-file=github_app_private_key="${PATH_TO_PRIVATE_KEY}" CODE_BLOCK:
RUNNER_NS="arc-runners"
APP_ID="Generated-App-ID"
INSTALLATION_ID="Generated-Installation-ID" (Click on your app on the installation page, and the installation ID will be the number at the end of the URL)
PATH_TO_PRIVATE_KEY=./path-to-pem-file kubectl create secret generic controller-manager \ --namespace ${NAMESPACE} \ --from-literal=github_app_id="${APP_ID}" \ --from-literal=github_app_installation_id="${INSTALLATION_ID}" \ --from-file=github_app_private_key="${PATH_TO_PRIVATE_KEY}" CODE_BLOCK:
INSTALLATION_NAME="runner-set" NAMESPACE="${RUNNER_NS}"
GITHUB_CONFIG_URL="https://github.com/PreciousDipe/Self-Hosted-Runner-On-Pods" helm install "${INSTALLATION_NAME}" \ --namespace "${NAMESPACE}" \ --create-namespace \ --set githubConfigUrl="${GITHUB_CONFIG_URL}" \ --set githubConfigSecret.github_token="${GITHUB_PAT}" \ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set CODE_BLOCK:
INSTALLATION_NAME="runner-set" NAMESPACE="${RUNNER_NS}"
GITHUB_CONFIG_URL="https://github.com/PreciousDipe/Self-Hosted-Runner-On-Pods" helm install "${INSTALLATION_NAME}" \ --namespace "${NAMESPACE}" \ --create-namespace \ --set githubConfigUrl="${GITHUB_CONFIG_URL}" \ --set githubConfigSecret.github_token="${GITHUB_PAT}" \ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set CODE_BLOCK:
INSTALLATION_NAME="runner-set" NAMESPACE="${RUNNER_NS}"
GITHUB_CONFIG_URL="https://github.com/PreciousDipe/Self-Hosted-Runner-On-Pods" helm install "${INSTALLATION_NAME}" \ --namespace "${NAMESPACE}" \ --create-namespace \ --set githubConfigUrl="${GITHUB_CONFIG_URL}" \ --set githubConfigSecret.github_token="${GITHUB_PAT}" \ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set CODE_BLOCK:
INSTALLATION_NAME="runner-set-github-apps" NAMESPACE="${RUNNER_NS}"
GITHUB_CONFIG_URL="https://github.com/PreciousDipe/Self-Hosted-Runner-On-Pods" helm repo add actions-runner-controller https://actions-runner-controller.github.io/actions-runner-controller
"actions-runner-controller" has been added to your repositories helm install "${INSTALLATION_NAME}" \ actions-runner-controller/actions-runner-controller \ --version 0.23.7 \ --namespace "${NAMESPACE}" \ --create-namespace \ --set githubConfigUrl="${GITHUB_CONFIG_URL}" \ --set githubConfigSecret.name=controller-manager CODE_BLOCK:
INSTALLATION_NAME="runner-set-github-apps" NAMESPACE="${RUNNER_NS}"
GITHUB_CONFIG_URL="https://github.com/PreciousDipe/Self-Hosted-Runner-On-Pods" helm repo add actions-runner-controller https://actions-runner-controller.github.io/actions-runner-controller
"actions-runner-controller" has been added to your repositories helm install "${INSTALLATION_NAME}" \ actions-runner-controller/actions-runner-controller \ --version 0.23.7 \ --namespace "${NAMESPACE}" \ --create-namespace \ --set githubConfigUrl="${GITHUB_CONFIG_URL}" \ --set githubConfigSecret.name=controller-manager CODE_BLOCK:
INSTALLATION_NAME="runner-set-github-apps" NAMESPACE="${RUNNER_NS}"
GITHUB_CONFIG_URL="https://github.com/PreciousDipe/Self-Hosted-Runner-On-Pods" helm repo add actions-runner-controller https://actions-runner-controller.github.io/actions-runner-controller
"actions-runner-controller" has been added to your repositories helm install "${INSTALLATION_NAME}" \ actions-runner-controller/actions-runner-controller \ --version 0.23.7 \ --namespace "${NAMESPACE}" \ --create-namespace \ --set githubConfigUrl="${GITHUB_CONFIG_URL}" \ --set githubConfigSecret.name=controller-manager CODE_BLOCK:
name: Self Hosted Runners on Pods on: push: branches: [ main ] jobs: build: runs-on: runner-set-github-apps steps: - name: Checkout uses: actions/checkout@v4 - name: Show runner info run: | echo "This job runs on a self-hosted runner managed by actions runner controller" hostname cat /etc/os-release || true CODE_BLOCK:
name: Self Hosted Runners on Pods on: push: branches: [ main ] jobs: build: runs-on: runner-set-github-apps steps: - name: Checkout uses: actions/checkout@v4 - name: Show runner info run: | echo "This job runs on a self-hosted runner managed by actions runner controller" hostname cat /etc/os-release || true CODE_BLOCK:
name: Self Hosted Runners on Pods on: push: branches: [ main ] jobs: build: runs-on: runner-set-github-apps steps: - name: Checkout uses: actions/checkout@v4 - name: Show runner info run: | echo "This job runs on a self-hosted runner managed by actions runner controller" hostname cat /etc/os-release || true - Github Repository
- Actions Runner Controller (ARC)
- Cert Manager - Github Apps Method - Github Apps Method