{"id":1909,"date":"2025-03-31T08:00:00","date_gmt":"2025-03-31T05:00:00","guid":{"rendered":"https:\/\/upcloud.com\/global\/us\/resources\/tutorials\/supercharge-your-ci-cd-deploy-lightning-fast-github-actions-runners-on-upclouds-managed-kubernetes-part-2\/"},"modified":"2025-03-31T08:00:00","modified_gmt":"2025-03-31T05:00:00","slug":"supercharge-your-ci-cd-deploy-lightning-fast-github-actions-runners-on-upclouds-managed-kubernetes-part-2","status":"publish","type":"tutorial","link":"https:\/\/upcloud.com\/global\/resources\/tutorials\/supercharge-your-ci-cd-deploy-lightning-fast-github-actions-runners-on-upclouds-managed-kubernetes-part-2\/","title":{"rendered":"Supercharge Your CI\/CD: Deploy Lightning-Fast GitHub Actions Runners on UpCloud\u2019s Managed Kubernetes: Part 2"},"content":{"rendered":"\n\n\n<p class=\"wp-block-paragraph\">Welcome back to the four-part series on supercharging your CI\/CD pipelines by deploying lightning-fast GitHub Actions Runners on UpCloud\u2019s Managed Kubernetes! <a href=\"https:\/\/upcloud.com\/global\/resources\/tutorials\/supercharge-your-ci-cd\">In the previous installment<\/a>, you successfully set up a functional self-hosted runner on an UpCloud-based Kubernetes cluster, ready to execute your GitHub Actions workflows.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now, it\u2019s time to look closer into your runner deployment and learn some advanced configurations. In this part, we\u2019ll explore how to customize the runner deployment configuration, giving you fine-grained control over resources, tool installations, and standardization across your runner fleet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019ll also learn how to configure network policies for enhanced security and implement effective autoscaling policies to optimize runner costs. Let\u2019s get started on fine-tuning your GitHub Actions Runners!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Customizing Runner and Workflow Pods<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Customizing runner and workflow pods can help with optimizing the performance and functionality of your self-hosted GitHub Actions Runners. As you installed the Actions Runner Controller using Helm charts in the last part of this series, you use the values file for the chart to pass in customization options when creating the controller. <a href=\"https:\/\/web.archive.org\/web\/20250304011328mp_\/https:\/\/github.com\/actions\/actions-runner-controller\/blob\/master\/charts\/gha-runner-scale-set\/values.yaml\" target=\"_blank\" rel=\"noopener\">This template<\/a> for the values.yaml file for the GitHub Actions Runner Controller mentions all the options and customizations you can pass in through it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In some cases, you might also need to use configmaps and <a href=\"https:\/\/web.archive.org\/web\/20250304011328mp_\/https:\/\/github.com\/actions\/runner-container-hooks\/blob\/main\/docs\/adrs\/0134-hook-extensions.md\" target=\"_blank\" rel=\"noopener\">hook extensions<\/a> to pass in configurations and scripts to the runner pods. You will learn about these in this section as you need them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">While you might be used to directly modifying the values.yaml file of the helm chart or using other Kubernetes native methods to customize pods and other resources, you can not actually use those methods here.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s see how to use these methods to implement a few types of customizations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Specifying Resource Requests and Limits<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When running the Actions Runner controller on a shared cluster, resource consumption and limits are important to keep in mind. To ensure that your runner pods have the necessary resources to execute workflows efficiently (and also to ensure that they do not hog up more resources than they need), you can specify resource requests and limits. This is particularly important for workflows that require significant CPU or memory resources.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can implement this via the values file. To do that, create a new file named <code>values.yaml<\/code> in your current working directory and save the following contents in it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">template:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/actions\/actions-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        resources:\n          requests:\n            cpu: \"1000m\"\n            memory: \"2Gi\"\n          limits:\n            cpu: \"2000m\"\n            memory: \"4Gi\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This configuration sets a CPU request of 1 core and a memory request of 2 GB, with a CPU limit of 2 cores and a memory limit of 4 GB. The<code> $job<\/code> variable will be dynamically replaced by the runner controller.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now, apply the changes to the chart using the updated <code>values.yaml<\/code> file by running the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">INSTALLATION_NAME=\"arc-runner-set\"\nNAMESPACE=\"arc-runners\"\nGITHUB_CONFIG_URL=\"https:\/\/github.com\/&lt;github-username&gt;\/&lt;github-repo-name&gt;\"\nGITHUB_PAT=\"&lt;YOUR_GITHUB_PAT&gt;\"\n\nhelm upgrade --install \"${INSTALLATION_NAME}\" \\\n  --namespace \"${NAMESPACE}\" \\\n  --create-namespace \\\n  --set githubConfigUrl=\"${GITHUB_CONFIG_URL}\" \\\n  --set githubConfigSecret.github_token=\"${GITHUB_PAT}\" \\\n  -f values.yaml \\\n  oci:\/\/ghcr.io\/actions\/actions-runner-controller-charts\/gha-runner-scale-set<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s it! The workflow pods that are created now will have the resource limits applied to them as you specified in the values file above. You can try checking the configuration of the runner pods to confirm by running <code>kubectl get pod &lt;pod_name&gt; -o yaml -n arc-runners<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s what a typical pod created using these values might look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">$ kubectl get pod\/arc-runner-set-c48pp-runner-s5b5t -o yaml -n arc-runners\n\napiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    actions.github.com\/patch-id: \"2\"\n    # omitted other details...\nspec:\n  containers:\n  - command:\n    - \/home\/runner\/run.sh\n    env:\n    - name: ACTIONS_RUNNER_INPUT_JITCONFIG\n      valueFrom:\n        secretKeyRef:\n          key: jitToken\n          name: arc-runner-set-c48pp-runner-s5b5t\n    - name: GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT\n      value: actions-runner-controller\/0.10.1\n    image: ghcr.io\/actions\/actions-runner:latest\n    imagePullPolicy: Always\n    name: runner\n    # Here are the limits you had specified in the values file\n    resources:\n      limits:\n        cpu: \"2\"\n        memory: 4Gi\n      requests:\n        cpu: \"1\"\n        memory: 2Gi\n    terminationMessagePath: \/dev\/termination-log\n    terminationMessagePolicy: File\n    volumeMounts:\n    - mountPath: \/var\/run\/secrets\/kubernetes.io\/serviceaccount\n      name: kube-api-access-spnzt\n      readOnly: true\n  dnsPolicy: ClusterFirst\n  # omitted other details...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can fine-tune these limits based on your workload requirements and the other services running on the host cluster.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Installing Additional Tools<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">There are times when you create a runner type for particular use cases, such as running E2E tests on your app by building and hosting it on remote environments such as Kubernetes clusters. To be able to make these use cases work, you might need to install additional tools on the runner (such as kubectl for example).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is important to mention that you can always do this initial setup in your GitHub Actions workflow as well. However, if you have a large number of workflows following the same setup routine, it might make sense to design runners that implement that routine before picking up jobs to help keep the workflow scripts simple.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can do that by instructing the Actions Runner Controller to run your script before starting a job using the <code>ACTIONS_RUNNER_HOOK_JOB_STARTED<\/code> environment variable in the values file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, you will need to supply the script file to the runner pods before they can try running the script. To do that, you will use a ConfigMap mounted as a volume on the runner pod.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To start off, write your initialization script in a file named <code>install-additional-tools.yaml<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: init-script\n  namespace: arc-runners\ndata:\n  init.sh: |\n    #!\/bin\/bash\n    curl -LO \"https:\/\/dl.k8s.io\/release\/$(curl -L -s https:\/\/dl.k8s.io\/release\/stable.txt)\/bin\/linux\/amd64\/kubectl\"\n    sudo install -o root -g root -m 0755 kubectl \/usr\/local\/bin\/kubectl<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now, create the ConfigMap by applying this configuration to your cluster. Here\u2019s the command to do that:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">kubectl apply -f install-additional-tools.yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Next, update the values.yaml file to mount this ConfigMap as a volume and use the mounted address of the initialization script in the <code>ACTIONS_RUNNER_HOOK_JOB_STARTED<\/code> path. Here\u2019s what your <code>values.yaml<\/code> file should look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">template:\n    spec:\n      containers:\n      - name: runner\n        image: ghcr.io\/actions\/actions-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        env:\n          - name: ACTIONS_RUNNER_HOOK_JOB_STARTED\n            value: \/home\/runner\/init-script\/init.sh\n        volumeMounts:\n          - name: init-script\n            mountPath: \/home\/runner\/init-script\n      volumes:\n        - name: init-script\n          configMap:\n            name: init-script<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, apply the changes to the Actions Runner Controller by running the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">INSTALLATION_NAME=\"arc-runner-set\"\nNAMESPACE=\"arc-runners\"\nGITHUB_CONFIG_URL=\"https:\/\/github.com\/&lt;github-username&gt;\/&lt;github-repo-name&gt;\"\nGITHUB_PAT=\"&lt;YOUR_GITHUB_PAT&gt;\"\n\nhelm upgrade --install \"${INSTALLATION_NAME}\" \\\n  --namespace \"${NAMESPACE}\" \\\n  --set githubConfigUrl=\"${GITHUB_CONFIG_URL}\" \\\n  --set githubConfigSecret.github_token=\"${GITHUB_PAT}\" \\\n  -f values.yaml \\\n  oci:\/\/ghcr.io\/actions\/actions-runner-controller-charts\/gha-runner-scale-set<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And that\u2019s it! You can try creating a workflow such as this one and running it to see that kubectl has been set up before the job begins executing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">name: ARC Demo\non:\n  workflow_dispatch:\n\njobs:\n  Test-Runner:\n    runs-on: arc-runner-set\n    steps:\n      - run: kubectl version --client --output=yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s what the output of the run looks like:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-131-1024x520.png\" alt=\"-\" class=\"wp-image-49074\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019ll see logs from the initialization script under the <strong>Set up runner<\/strong> step and the expected output under the run steps.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you choose to allow running container jobs or container actions in your ARC runners, this method will not be able to supply initialization scripts to the containers created when jobs are run. This is because the containers created for the runner in those cases will be created via <a href=\"https:\/\/web.archive.org\/web\/20250304011328mp_\/https:\/\/github.com\/actions\/runner-container-hooks\" target=\"_blank\" rel=\"noopener\">runner-container-hooks<\/a>. To supply customizations (such as setting resource limits or providing initialization scripts) to these containers, you will need to use <a href=\"https:\/\/web.archive.org\/web\/20250304011328mp_\/https:\/\/docs.github.com\/en\/actions\/hosting-your-own-runners\/managing-self-hosted-runners-with-actions-runner-controller\/deploying-runner-scale-sets-with-actions-runner-controller#configuring-hook-extensions\" target=\"_blank\" rel=\"noopener\">container hook extensions<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To do that, you will need to create a ConfigMap that specifies the template that you would like to use with the containers that get created using the hooks. Here\u2019s what a typical ConfigMap would look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: hook-extension\n  namespace: arc-runners\ndata:\n  content: |\n    metadata:\n      annotations:\n        example: \"extension\"\n    spec:\n      containers:\n        - name: \"$job\" # Target the job container\n          env:\n            - name: ACTIONS_RUNNER_HOOK_JOB_STARTED # specify the init script path\n              value: \/home\/runner\/init-script\/init.sh\n            volumeMounts: # mount the init script\n              - name: init-script\n                mountPath: \/home\/runner\/init-script\n          resources: # set resource limits\n            requests:\n              cpu: \"1000m\"\n              memory: \"2Gi\"\n            limits:\n              cpu: \"2000m\"\n              memory: \"4Gi\"\n              \n      volumes: # set up the init script volume from its configmap\n        - name: init-script\n          configMap:\n            name: init-script<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now, you need to supply this ConfigMap in the env <code>ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE<\/code> in your values.yaml file. Here\u2019s what the file would look like (assuming you\u2019ve saved the ConfigMap above as <code>resource-limits-extension.yaml<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">template:\n    spec:\n      containers:\n      - name: runner\n        image: ghcr.io\/actions\/actions-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        env:\n          - name: ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE\n            value: \/home\/runner\/pod-template\/content\n        volumeMounts:\n          - name: pod-template\n            mountPath: \/home\/runner\/pod-template\n      volumes:\n        - name: pod-template\n          configMap:\n            name: resource-limits-extension<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The pods now created via the runner-container-hooks for container jobs and services and container actions will now have the customizations you\u2019ve passed above.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Using Runner ScaleSet Names<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When working with workloads that have different resource requirements, it might make sense to create multiple runners based on these resource requirements. This can help in cases like memory-heavy build processes which you only want to run on (potentially expensive) machinery, but scale it down right after it\u2019s done; all this while keeping a regular runner available for general use.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can deploy multiple runner scalesets using the <code>helm install<\/code> command, but you would need a way to pick which scaleset to run your workflow jobs on. Runner scaleset names help solve this problem. They act as the <em>label<\/em> for the runner scaleset, allowing you to define the possible runner options for a workflow using the<code> runs-on<\/code> node in the workflow configuration file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To try it out, simply deploy another runner scaleset with a different installation name, a different namespace, and your required resource limits in your values file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">INSTALLATION_NAME=\"arc-runner-set-heavy\"\nNAMESPACE=\"arc-runners-heavy\"\nGITHUB_CONFIG_URL=\"https:\/\/github.com\/&lt;github-username&gt;\/&lt;github-repo-name&gt;\"\nGITHUB_PAT=\"&lt;YOUR_GITHUB_PAT&gt;\"\n\nhelm upgrade --install \"${INSTALLATION_NAME}\" \\\n  --namespace \"${NAMESPACE}\" \\\n  --set githubConfigUrl=\"${GITHUB_CONFIG_URL}\" \\\n  --set githubConfigSecret.github_token=\"${GITHUB_PAT}\" \\\n  -f values.yaml \\\n  oci:\/\/ghcr.io\/actions\/actions-runner-controller-charts\/gha-runner-scale-set<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can now use the <code>arc-runner-set-heavy<\/code> label in your workflow configuration files, such as this one to run jobs specifically on this runner:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">name: ARC Demo\non:\n  workflow_dispatch:\n\njobs:\n  Test-Runner:\n    runs-on: arc-runner-set-heavy\n    steps:\n      - run: echo \"Heavy runner \ud83d\udcaa\"<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Network and Security<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When deploying self-hosted GitHub Actions Runners on a Kubernetes cluster, network security is an important aspect to keep in mind. By default, Kubernetes allows all pods to communicate freely with each other, which can pose significant security risks. In this section, we\u2019ll explore the issues with the default Kubernetes network policy, key principles of Kubernetes network policies, and how to implement these policies to enhance security.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Issues with the Default Kubernetes Network Policy<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Kubernetes operates on an \u201callow-any-any\u201d model by default, which means that all pods can communicate with each other freely. This flat network model simplifies cluster setup but lacks security, as it doesn\u2019t restrict traffic between pods. Without additional configuration, sensitive data could be exposed if a pod is compromised.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kubernetes network policies provide a way to control traffic flow within a cluster, enhancing security and helping adhere to compliance requirements. Here are a few key principles you should keep in mind for most CI\/CD setups on Kubernetes infrastructure:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Restricting Ingress and Egress Traffic<\/strong>: Network policies allow you to define rules for incoming (ingress) and outgoing (egress) traffic. By specifying which pods can communicate with each other, you can prevent unauthorized access and limit the attack surface.<\/li>\n\n\n\n<li><strong>Namespace Isolation<\/strong>: You can isolate pods within different namespaces, ensuring that applications running in separate namespaces cannot communicate unless explicitly allowed. This can help in ensuring that pods do not run in the same context as critical services on a Kubernetes cluster.<\/li>\n\n\n\n<li><strong>Zero-Trust Networking<\/strong>: Implementing a zero-trust model means that no pod is trusted by default. All traffic must be explicitly allowed, reducing the risk of lateral movement in case of a breach.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Creating and Applying Kubernetes Network Policies<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To implement these principles, you need to create and apply Kubernetes network policies. Here\u2019s an example network policy configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">apiVersion: networking.k8s.io\/v1\nkind: NetworkPolicy\nmetadata:\n  name: restrict-ingress\nspec:\n  podSelector:\n    matchLabels:\n      app.kubernetes.io\/name: arc-runner-set\n  policyTypes:\n  - Ingress\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          app: allowed-pod\n    ports:\n    - 80<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This policy restricts ingress traffic to pods named \u201carc-runner-set\u201d, allowing only traffic from pods labeled as allowed-pod on port 80.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can apply this policy by saving the configuration in a file (say config.yaml) and running the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">kubectl apply -f config.yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can then use <code>kubectl get networkpolicies <\/code>to check if the policy is applied correctly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Implementing Security Contexts for Runner Pods<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To further improve the security of your GitHub Actions Runner pods, you should consider implementing <a href=\"https:\/\/web.archive.org\/web\/20250304011328mp_\/https:\/\/kubernetes.io\/docs\/tasks\/configure-pod-container\/security-context\/\" target=\"_blank\" rel=\"noopener\">security contexts<\/a>. A security context defines privilege and access control settings for a pod or container, allowing you to specify settings such as running as a non-root user, disabling privilege escalation, and mounting the root filesystem as read-only.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can configure the security context using the values file you used earlier for setting the resource limits on your runner pods. Here\u2019s an example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">template:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/actions\/actions-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        resources:\n          securityContext:\n            readOnlyRootFilesystem: true\n            allowPrivilegeEscalation: false\n            capabilities:\n              add:\n                - NET_ADMIN<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Some key security measures you might want to implement this way could include:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Drop Unnecessary Linux Capabilities<\/strong>: Prevent privilege escalation within runner pods.<\/li>\n\n\n\n<li><strong>Run as Non-Root User<\/strong>: Avoid running processes as root to minimize risks.<\/li>\n\n\n\n<li><strong>Read-Only Filesystem<\/strong>: Restrict writes to prevent tampering.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s what the values file would look like for these settings:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">template:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/actions\/actions-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        resources:\n          securityContext:\n            runAsNonRoot: true\n            readOnlyRootFilesystem: true\n            capabilities:\n              drop:\n                - ALL<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Cost Management and Autoscaling<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As you continue to optimize your self-hosted Runners on UpCloud\u2019s Managed Kubernetes clusters, it is important to keep an eye on cost management and autoscaling. Efficiently managing resources not only helps reduce expenses but also ensures that your CI\/CD pipelines operate smoothly and reliably. In this section, we\u2019ll share some best practices for optimizing resource allocation, reducing idle runners, and implementing effective autoscaling policies.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Implementing Effective Autoscaling Policies<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The Actions Runner Controller provides a powerful tool for implementing autoscaling policies. The controller allows you to create rules that automatically adjust the number of runners based on the current workload.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/web.archive.org\/web\/20250304011328mp_\/https:\/\/docs.github.com\/en\/actions\/hosting-your-own-runners\/managing-self-hosted-runners-with-actions-runner-controller\/deploying-runner-scale-sets-with-actions-runner-controller#setting-the-maximum-and-minimum-number-of-runners\" target=\"_blank\" rel=\"noopener\">Set up autoscaling rules<\/a> through the values file to choose a maximum and minimum number of runners for autoscaling. The Actions Runner Controller will automatically take care of creating new runner instances to match demand and delete idle instances to match the minimum number of runners needed. This will ensure that your CI\/CD pipeline always has the necessary resources to run smoothly without wasting resources during idle times.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Reducing Idle Runners and Scaling Based on Actual Usage<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Idle runners can significantly increase costs without providing any benefits. To mitigate this, focus on scaling your runners based on actual usage patterns. This means dynamically adjusting the number of runners available to match the current demand from your workflows.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You should use monitoring tools (more on these in the next part!) to identify periods when runners are idle. This could be during off-peak hours or weekends when development activity is lower. And then set up automation to automatically scale down the number of idle runners during these periods. This ensures that you\u2019re not paying for resources that aren\u2019t being used.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is important to note that the GitHub Actions Runner Controller does not support scheduling scaling. You might need to make use of third-party cron or automation services to implement this.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Best Practices for Optimizing Resource Allocation<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Optimizing resource allocation involves ensuring that your runners are using the right amount of resources for the tasks they perform. This includes setting appropriate CPU and memory limits for each runner based on the types of workflows they execute. For instance, if your workflows primarily involve lightweight tasks like code compilation or testing, you might allocate fewer resources compared to workflows that require more intensive operations like large-scale data processing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As mentioned before, you should regularly monitor your runners\u2019 usage patterns to identify peak hours and idle times. This data will be crucial for setting up effective autoscaling policies. Also, ensure that each runner is allocated the right amount of resources based on the workload. Over-allocating resources can lead to unnecessary costs, while under-allocating can cause performance issues. Finally, you should use Kubernetes\u2019 resource requests and limits to ensure that your runners are allocated the necessary resources while preventing over-allocation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In this part, we looked into advanced configuration techniques for your self-hosted GitHub Actions Runners on UpCloud\u2019s Managed Kubernetes. You learned how to customize runner deployments for better resource management, tool installation, and standardization. Additionally, we covered configuring network policies for enhanced security, setting up monitoring for performance insights, and implementing cost-effective autoscaling strategies.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With these advanced configurations in place, your GitHub Actions workflow is now more robust and efficient. However, even the best setups can encounter issues. In Part 3, we\u2019ll shift focus to troubleshooting and handling common problems that may arise with your self-hosted runners. You\u2019ll discover practical tips and techniques to identify and resolve issues quickly, ensuring your workflows run smoothly and reliably. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/upcloud.com\/global\/resources\/tutorials\/deploy-lightning-fast-github-actions-runners-on-upclouds-managed-kubernetes-part-3\">Continue in the part 3 for expert advice on maintaining a seamless CI\/CD experience!<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"author":82,"featured_media":49081,"comment_status":"open","ping_status":"closed","template":"","community-category":[],"class_list":["post-1909","tutorial","type-tutorial","status-publish","has-post-thumbnail","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1909","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial"}],"about":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/types\/tutorial"}],"author":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/users\/82"}],"replies":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/comments?post=1909"}],"version-history":[{"count":0,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/1909\/revisions"}],"wp:attachment":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/media?parent=1909"}],"wp:term":[{"taxonomy":"community-category","embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/community-category?post=1909"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}