1. Answers
  2. Scaling Self-hosted GitHub Action Runners With Kubernetes

Scaling Self-Hosted GitHub Action Runners With Kubernetes

Introduction

This guide will walk you through the process of scaling self-hosted GitHub Action Runners using Kubernetes. By leveraging Kubernetes, you can efficiently manage and scale your GitHub Action Runners to meet the demands of your CI/CD workflows. The key services involved in this setup include Kubernetes for orchestration, GitHub Actions for CI/CD, and Pulumi for infrastructure as code.

Step-by-Step Explanation

Prerequisites

  1. Ensure you have a Kubernetes cluster up and running. You can use a managed Kubernetes service like Amazon EKS, Google GKE, or Azure AKS, or set up your own cluster.
  2. Install Pulumi CLI and configure it to work with your cloud provider.
  3. Ensure you have the necessary permissions to create resources in your Kubernetes cluster.
  4. Obtain a GitHub Personal Access Token (PAT) with the necessary permissions to manage self-hosted runners.

Steps

  1. Set up Pulumi Project: Initialize a new Pulumi project and configure it to use TypeScript.

  2. Define Kubernetes Namespace: Create a namespace in your Kubernetes cluster to isolate the resources for your GitHub Action Runners.

  3. Create a Deployment for GitHub Action Runners: Define a Kubernetes Deployment that specifies the container image for the GitHub Action Runner and the necessary environment variables, including the GitHub PAT and repository information.

  4. Configure Autoscaling: Set up Horizontal Pod Autoscaler (HPA) to automatically scale the number of runner pods based on CPU utilization or other metrics.

  5. Apply the Configuration: Use Pulumi to deploy the resources to your Kubernetes cluster.

Example Code

import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";

// Create a Kubernetes namespace
const namespace = new k8s.core.v1.Namespace("github-runners", {
    metadata: { name: "github-runners" },
});

// Define the GitHub Action Runner deployment
const runnerDeployment = new k8s.apps.v1.Deployment("github-runner-deployment", {
    metadata: { namespace: namespace.metadata.name },
    spec: {
        replicas: 1,
        selector: { matchLabels: { app: "github-runner" } },
        template: {
            metadata: { labels: { app: "github-runner" } },
            spec: {
                containers: [{
                    name: "runner",
                    image: "myoung34/github-runner:latest",
                    env: [
                        { name: "RUNNER_NAME", value: "self-hosted-runner" },
                        { name: "RUNNER_WORKDIR", value: "/runner" },
                        { name: "RUNNER_TOKEN", value: "<YOUR_GITHUB_PAT>" },
                        { name: "RUNNER_REPOSITORY_URL", value: "https://github.com/your/repo" },
                    ],
                }],
            },
        },
    },
});

// Configure Horizontal Pod Autoscaler
const hpa = new k8s.autoscaling.v1.HorizontalPodAutoscaler("github-runner-hpa", {
    metadata: { namespace: namespace.metadata.name },
    spec: {
        scaleTargetRef: {
            apiVersion: "apps/v1",
            kind: "Deployment",
            name: runnerDeployment.metadata.name,
        },
        minReplicas: 1,
        maxReplicas: 10,
        targetCPUUtilizationPercentage: 80,
    },
});

export const namespaceName = namespace.metadata.name;
export const deploymentName = runnerDeployment.metadata.name;
export const hpaName = hpa.metadata.name;

Conclusion

By following this guide, you have successfully set up a scalable self-hosted GitHub Action Runner environment using Kubernetes and Pulumi. This setup ensures that your CI/CD workflows can handle varying loads efficiently by automatically scaling the runners based on demand. For further customization, you can adjust the deployment and autoscaling configurations to better suit your specific requirements.

Full Code Example

import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";

// Create a Kubernetes namespace
const namespace = new k8s.core.v1.Namespace("github-runners", {
    metadata: { name: "github-runners" },
});

// Define the GitHub Action Runner deployment
const runnerDeployment = new k8s.apps.v1.Deployment("github-runner-deployment", {
    metadata: { namespace: namespace.metadata.name },
    spec: {
        replicas: 1,
        selector: { matchLabels: { app: "github-runner" } },
        template: {
            metadata: { labels: { app: "github-runner" } },
            spec: {
                containers: [{
                    name: "runner",
                    image: "myoung34/github-runner:latest",
                    env: [
                        { name: "RUNNER_NAME", value: "self-hosted-runner" },
                        { name: "RUNNER_WORKDIR", value: "/runner" },
                        { name: "RUNNER_TOKEN", value: "<YOUR_GITHUB_PAT>" },
                        { name: "RUNNER_REPOSITORY_URL", value: "https://github.com/your/repo" },
                    ],
                }],
            },
        },
    },
});

// Configure Horizontal Pod Autoscaler
const hpa = new k8s.autoscaling.v1.HorizontalPodAutoscaler("github-runner-hpa", {
    metadata: { namespace: namespace.metadata.name },
    spec: {
        scaleTargetRef: {
            apiVersion: "apps/v1",
            kind: "Deployment",
            name: runnerDeployment.metadata.name,
        },
        minReplicas: 1,
        maxReplicas: 10,
        targetCPUUtilizationPercentage: 80,
    },
});

export const namespaceName = namespace.metadata.name;
export const deploymentName = runnerDeployment.metadata.name;
export const hpaName = hpa.metadata.name;

Deploy this code

Want to deploy this code? Sign up for a free Pulumi account to deploy in a few clicks.

Sign up

New to Pulumi?

Want to deploy this code? Sign up with Pulumi to deploy in a few clicks.

Sign up