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
- 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.
- Install Pulumi CLI and configure it to work with your cloud provider.
- Ensure you have the necessary permissions to create resources in your Kubernetes cluster.
- Obtain a GitHub Personal Access Token (PAT) with the necessary permissions to manage self-hosted runners.
Steps
Set up Pulumi Project: Initialize a new Pulumi project and configure it to use TypeScript.
Define Kubernetes Namespace: Create a namespace in your Kubernetes cluster to isolate the resources for your GitHub Action Runners.
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.
Configure Autoscaling: Set up Horizontal Pod Autoscaler (HPA) to automatically scale the number of runner pods based on CPU utilization or other metrics.
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 upNew to Pulumi?
Want to deploy this code? Sign up with Pulumi to deploy in a few clicks.
Sign upThank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.