Compare commits

..

35 Commits

Author SHA1 Message Date
callum-tait-pbx
3b2d2c052e chore: adding Helm app version back (#412)
* chore: adding Helm app version back

* chore: removing redundant values entry

* chore: bumping to newer version

* chore: bumping app version to latest

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2021-04-18 13:58:54 +09:00
Manuel Jurado
37c2a62fa8 Allow to configure runner volume size limit (#436)
Enable the user to set a limit size on the volume of the runner to avoid some runner pod affecting other resources of the same cluster

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2021-04-18 13:56:59 +09:00
callum-tait-pbx
2eeb56d1c8 docs: removing superfluous title reference (#459) 2021-04-18 09:45:28 +09:00
ToMe25
a612b38f9b Cache docker images in acceptance test (#463)
* Cache docker images locally

Cache dind, runner, and kube-rbac-proxy docker image on the host and copy onto the kind node instead of downloading it to the node directly.

* Also cache certmanager docker images
2021-04-18 09:44:59 +09:00
callum-tait-pbx
1c67ea65d9 ci: fix latest tag push logic (#462)
* ci: fix latest tag push logic

* ci: use better job names
2021-04-18 09:41:22 +09:00
ToMe25
c26fb5ad5f Make acceptance use local docker image (#448)
load the local docker image to the kind cluster instead of pushing it to dockerhub and pulling it from there
2021-04-17 17:13:47 +09:00
callum-tait-pbx
325c2cc385 docs: correct and simplify example (#450)
* docs: correct and simplify example

* docs: removing alternatives

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2021-04-17 17:08:57 +09:00
Agoney Garcia-Deniz
2e551c9d0a Add hostAliases to the runner spec (#456) 2021-04-17 17:04:52 +09:00
asoldino
7b44454d01 Add documentation of dockerVolumeMount (#453) 2021-04-17 17:04:38 +09:00
callum-tait-pbx
f2680b2f2d Bumping runner to Ubuntu 20.04 (#438)
Images for `actions-runner:v${VERSION}` and `actions-runner:latest` tags are upgraded to Ubuntu 20.04.

If you would like not to upgrade Ubuntu in the runner image in the future, migrate to new tags suffixed with `-ubuntu-20.04` like`actions-runner:v${VERSION}-ubuntu-20.04`.

We also keep publishing the existing Ubuntu 18.04 images with new `actions-runner:v${VERSION}-ubuntu-18.04` tags. Please use it when it turned out that you had workflows dependent on Ubuntu 18.04.

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2021-04-17 17:02:03 +09:00
asoldino
b42b8406a2 Add dockerVolumeMounts (#439)
Resolves #435
2021-04-06 10:10:10 +09:00
Javi Polo
3c125e2191 Fix helm webhook ingress error: spec.rules[0].http.paths[0].backend: Required value: port name or number is required (#437) 2021-04-02 06:34:45 +09:00
Christoph Brand
9ed245c85e feature(controller): remove dockerd executable (#432) 2021-04-01 08:50:48 +09:00
Florian Braun
5b7807d54b Quote vars in entrypoint.sh to prevent unwanted argument split (#420)
Prevents arguments from being split when e.g. the RUNNER_GROUP variable contains spaces (which is legit. One can create such groups in GitHub).

I've seen that all workers with group names that contain no spaces can register successfully, while all workers with groups that contain spaces will not register.

Furthermore, I suppose also other chars can be used here to inject arbitrary commands in an unsupported way via e.g. pipe symbol.

Quoting the vars correctly should prevent that and allow for e.g. group names and runner labels with spaces and other bash reserved characters.
2021-03-31 10:09:08 +09:00
Yusuke Kuoka
156e2c1987 Fix MTU configuration for dockerd (#421)
Resolves #393
2021-03-31 09:29:21 +09:00
Yusuke Kuoka
da4dfb3fdf Add make target test-with-deps to ease setting up dependent binaries (#426) 2021-03-31 09:23:16 +09:00
Gabriel Dantas Gomes
0783ffe989 some readme typos (#423) 2021-03-29 10:08:21 +09:00
Yusuke Kuoka
374105c1f3 Fix dindWithinRunnerContainer not to crash-loop runner pods (#419)
Apparently #253 broke dindWithinRunnerContainer completely due to the difference in how /runner volume is set up.
2021-03-25 10:23:36 +09:00
Yusuke Kuoka
bc6e499e4f Make logging more concise (#410)
This makes logging more concise by changing logger names to something like `controllers.Runner` to `actions-runner-controller.runner` after the standard `controller-rutime.controller` and reducing redundant logs by removing unnecessary requeues. I have also tweaked log messages so that their style is more consistent, which will also help readability. Also, runnerreplicaset-controller lacked useful logs so I have enhanced it.
2021-03-20 07:34:25 +09:00
Yusuke Kuoka
07f822bb08 Do include Runner controller in integration test (#409)
So that we could catch bugs in runner controller like seen in #398, #404, and #407.

Ref #400
2021-03-19 16:14:15 +09:00
Hidetake Iwata
3a0332dfdc Add metrics of RunnerDeployment and HRA (#408)
* Add metrics of RunnerDeployment and HRA

* Use kube-state-metrics-style label names
2021-03-19 16:14:02 +09:00
Yusuke Kuoka
f6ab66c55b Do not delay min/maxReplicas propagation from HRA to RD due to caching (#406)
As part of #282, I have introduced some caching mechanism to avoid excessive GitHub API calls due to the autoscaling calculation involving GitHub API calls is executed on each Webhook event.

Apparently, it was saving the wrong value in the cache- The value was one after applying `HRA.Spec.{Max,Min}Replicas` so manual changes to {Max,Min}Replicas doesn't affect RunnerDeployment.Spec.Replicas until the cache expires. This isn't what I had wanted.

This patch fixes that, by changing the value being cached to one before applying {Min,Max}Replicas.

Additionally, I've also updated logging so that you observe which number was fetched from cache, and what number was suggested by either TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy, and what was the final number used as the desired-replicas(after applying {Min,Max}Replicas).

Follow-up for #282
2021-03-19 12:58:02 +09:00
Yusuke Kuoka
d874a5cfda Fix status.lastRegistrationCheckTime in body must be of type string: \"null\" errors (#407)
Follow-up for #398 and #404
2021-03-19 11:15:35 +09:00
Yusuke Kuoka
c424215044 Do recheck runner registration timely (#405)
Since #392, the runner controller could have taken unexpectedly long time until it finally notices that the runner has been registered to GitHub. This patch fixes the issue, so that the controller will notice the successful registration in approximately 1 minute(hard-coded).

More concretely, let's say you had configured a long sync-period of like 10m, the runner controller could have taken approx 10m to notice the successful registration. The original expectation was 1m, because it was intended to recheck every 1m as implemented in #392. It wasn't working as such due to my misunderstanding in how requeueing work.
2021-03-19 11:02:47 +09:00
Yusuke Kuoka
c5fdfd63db Merge pull request #404 from summerwind/fix-reg-update-runner-status-err
Fix `Failed to update runner status for Registration` errors
2021-03-19 08:56:20 +09:00
Yusuke Kuoka
23a45eaf87 Bump chart version 2021-03-19 08:37:17 +09:00
Yusuke Kuoka
dee997b44e Fix Failed to update runner status for Registration errors
Fixes #400
2021-03-19 07:02:00 +09:00
Yusuke Kuoka
2929a739e3 Merge pull request #398 from summerwind/fix-status-last-reg-check-time-type-err
Fix `status.lastRegistrationCheckTime in body must be of type string: \"null\"` error
2021-03-18 10:36:44 +09:00
Yusuke Kuoka
3cccca8d09 Do patch runner status instead of update to reduce conflicts and avoid future bugs
Ref https://github.com/summerwind/actions-runner-controller/pull/398#issuecomment-801548375
2021-03-18 10:31:17 +09:00
Yusuke Kuoka
7a7086e7aa Make error logs more helpful 2021-03-18 10:26:21 +09:00
Yusuke Kuoka
565b14a148 Fix status.lastRegistrationCheckTime in body must be of type string: \"null\" error
Follow-up for #392
2021-03-18 10:20:49 +09:00
Yusuke Kuoka
ecc441de3f Bump chart version 2021-03-18 07:36:22 +09:00
Manabu Sakai
25335bb3c3 Fix typo in certificate.yaml (#396) 2021-03-18 07:33:34 +09:00
Yusuke Kuoka
9b871567b1 Fix wildcard in middle of actionsglob/scaleUpTrigger.githubEvent.checkRun.names not working (#395)
actionsglob patterns like `foo-*-bar` was not correctly working. Tests and the implementation was enhanced to correctly support it.
2021-03-17 06:46:48 +09:00
Balazs Gyurak
264cf494e3 Fix "pole" typo in README (#394)
I think these should be "poll".
2021-03-17 06:34:01 +09:00
48 changed files with 1386 additions and 371 deletions

View File

@@ -13,21 +13,27 @@ on:
paths: paths:
- runner/patched/* - runner/patched/*
- runner/Dockerfile - runner/Dockerfile
- runner/dindrunner.Dockerfile - runner/Dockerfile.ubuntu.1804
- runner/Dockerfile.dindrunner
- runner/entrypoint.sh - runner/entrypoint.sh
- .github/workflows/build-and-release-runners.yml - .github/workflows/build-and-release-runners.yml
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Build ${{ matrix.name }} name: Build ${{ matrix.name }}-ubuntu-${{ matrix.os-version }}
strategy: strategy:
matrix: matrix:
include: include:
- name: actions-runner - name: actions-runner
os-version: 20.04
dockerfile: Dockerfile dockerfile: Dockerfile
- name: actions-runner
os-version: 18.04
dockerfile: Dockerfile.ubuntu.1804
- name: actions-runner-dind - name: actions-runner-dind
dockerfile: dindrunner.Dockerfile os-version: 20.04
dockerfile: Dockerfile.dindrunner
env: env:
RUNNER_VERSION: 2.277.1 RUNNER_VERSION: 2.277.1
DOCKER_VERSION: 19.03.12 DOCKER_VERSION: 19.03.12
@@ -55,7 +61,55 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Build and Push - name: Build and Push Versioned Tags
uses: docker/build-push-action@v2
with:
context: ./runner
file: ./runner/${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
build-args: |
RUNNER_VERSION=${{ env.RUNNER_VERSION }}
DOCKER_VERSION=${{ env.DOCKER_VERSION }}
tags: |
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-ubuntu-${{ matrix.os-version }}
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-ubuntu-${{ matrix.os-version }}-${{ steps.vars.outputs.sha_short }}
latest-tags:
runs-on: ubuntu-latest
name: Build ${{ matrix.name }}-latest
strategy:
matrix:
include:
- name: actions-runner
dockerfile: Dockerfile
- name: actions-runner-dind
dockerfile: Dockerfile.dindrunner
env:
RUNNER_VERSION: 2.277.1
DOCKER_VERSION: 19.03.12
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
- name: Login to DockerHub
uses: docker/login-action@v1
if: ${{ github.event_name == 'push' || github.event_name == 'release' }}
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Build and Push Latest Tag
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: ./runner context: ./runner
@@ -66,6 +120,4 @@ jobs:
RUNNER_VERSION=${{ env.RUNNER_VERSION }} RUNNER_VERSION=${{ env.RUNNER_VERSION }}
DOCKER_VERSION=${{ env.DOCKER_VERSION }} DOCKER_VERSION=${{ env.DOCKER_VERSION }}
tags: | tags: |
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-${{ steps.vars.outputs.sha_short }}
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:latest ${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:latest

8
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,8 @@
# Contributing
### Helm Verison Bumps
**Chart Version :** When bumping the chart version follow semantic versioning https://semver.org/<br />
**App Version :** When bumping the app version you will also need to bump the chart verison too. Again, follow semantic verisoning when bumping the chart.
To determine if you need tp bump the MAJOR, MINOR or PATCH versions you will need to review the changes between the previous app version and the new app verison and / or ask for a maintainer to advise.

View File

@@ -14,6 +14,8 @@ else
GOBIN=$(shell go env GOBIN) GOBIN=$(shell go env GOBIN)
endif endif
TEST_ASSETS=$(PWD)/test-assets
# default list of platforms for which multiarch image is built # default list of platforms for which multiarch image is built
ifeq (${PLATFORMS}, ) ifeq (${PLATFORMS}, )
export PLATFORMS="linux/amd64,linux/arm64" export PLATFORMS="linux/amd64,linux/arm64"
@@ -37,6 +39,13 @@ all: manager
test: generate fmt vet manifests test: generate fmt vet manifests
go test ./... -coverprofile cover.out go test ./... -coverprofile cover.out
test-with-deps: kube-apiserver etcd kubectl
# See https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/envtest#pkg-constants
TEST_ASSET_KUBE_APISERVER=$(KUBE_APISERVER_BIN) \
TEST_ASSET_ETCD=$(ETCD_BIN) \
TEST_ASSET_KUBECTL=$(KUBECTL_BIN) \
make test
# Build manager binary # Build manager binary
manager: generate fmt vet manager: generate fmt vet
go build -o bin/manager main.go go build -o bin/manager main.go
@@ -126,7 +135,8 @@ release/clean:
rm -rf release rm -rf release
.PHONY: acceptance .PHONY: acceptance
acceptance: release/clean docker-build docker-push release acceptance: release/clean docker-build release
make acceptance/pull
ACCEPTANCE_TEST_SECRET_TYPE=token make acceptance/kind acceptance/setup acceptance/tests acceptance/teardown ACCEPTANCE_TEST_SECRET_TYPE=token make acceptance/kind acceptance/setup acceptance/tests acceptance/teardown
ACCEPTANCE_TEST_SECRET_TYPE=app make acceptance/kind acceptance/setup acceptance/tests acceptance/teardown ACCEPTANCE_TEST_SECRET_TYPE=app make acceptance/kind acceptance/setup acceptance/tests acceptance/teardown
ACCEPTANCE_TEST_DEPLOYMENT_TOOL=helm ACCEPTANCE_TEST_SECRET_TYPE=token make acceptance/kind acceptance/setup acceptance/tests acceptance/teardown ACCEPTANCE_TEST_DEPLOYMENT_TOOL=helm ACCEPTANCE_TEST_SECRET_TYPE=token make acceptance/kind acceptance/setup acceptance/tests acceptance/teardown
@@ -134,8 +144,23 @@ acceptance: release/clean docker-build docker-push release
acceptance/kind: acceptance/kind:
kind create cluster --name acceptance kind create cluster --name acceptance
kind load docker-image ${NAME}:${VERSION} --name acceptance
kind load docker-image quay.io/brancz/kube-rbac-proxy:v0.8.0 --name acceptance
kind load docker-image summerwind/actions-runner:latest --name acceptance
kind load docker-image docker:dind --name acceptance
kind load docker-image quay.io/jetstack/cert-manager-controller:v1.0.4 --name acceptance
kind load docker-image quay.io/jetstack/cert-manager-cainjector:v1.0.4 --name acceptance
kind load docker-image quay.io/jetstack/cert-manager-webhook:v1.0.4 --name acceptance
kubectl cluster-info --context kind-acceptance kubectl cluster-info --context kind-acceptance
acceptance/pull:
docker pull quay.io/brancz/kube-rbac-proxy:v0.8.0
docker pull summerwind/actions-runner:latest
docker pull docker:dind
docker pull quay.io/jetstack/cert-manager-controller:v1.0.4
docker pull quay.io/jetstack/cert-manager-cainjector:v1.0.4
docker pull quay.io/jetstack/cert-manager-webhook:v1.0.4
acceptance/setup: acceptance/setup:
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.0.4/cert-manager.yaml #kubectl create namespace actions-runner-system kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.0.4/cert-manager.yaml #kubectl create namespace actions-runner-system
kubectl -n cert-manager wait deploy/cert-manager-cainjector --for condition=available --timeout 60s kubectl -n cert-manager wait deploy/cert-manager-cainjector --for condition=available --timeout 60s
@@ -191,3 +216,66 @@ ifeq (, $(wildcard $(GOBIN)/yq))
} }
endif endif
YQ=$(GOBIN)/yq YQ=$(GOBIN)/yq
OS_NAME := $(shell uname -s | tr A-Z a-z)
# find or download etcd
etcd:
ifeq (, $(wildcard $(TEST_ASSETS)/etcd))
@{ \
set -xe ;\
INSTALL_TMP_DIR=$$(mktemp -d) ;\
cd $$INSTALL_TMP_DIR ;\
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.2/kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\
mkdir -p $(TEST_ASSETS) ;\
tar zxvf kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/etcd $(TEST_ASSETS)/etcd ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kube-apiserver $(TEST_ASSETS)/kube-apiserver ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kubectl $(TEST_ASSETS)/kubectl ;\
rm -rf $$INSTALL_TMP_DIR ;\
}
ETCD_BIN=$(TEST_ASSETS)/etcd
else
ETCD_BIN=$(TEST_ASSETS)/etcd
endif
# find or download kube-apiserver
kube-apiserver:
ifeq (, $(wildcard $(TEST_ASSETS)/kube-apiserver))
@{ \
set -xe ;\
INSTALL_TMP_DIR=$$(mktemp -d) ;\
cd $$INSTALL_TMP_DIR ;\
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.2/kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\
mkdir -p $(TEST_ASSETS) ;\
tar zxvf kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/etcd $(TEST_ASSETS)/etcd ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kube-apiserver $(TEST_ASSETS)/kube-apiserver ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kubectl $(TEST_ASSETS)/kubectl ;\
rm -rf $$INSTALL_TMP_DIR ;\
}
KUBE_APISERVER_BIN=$(TEST_ASSETS)/kube-apiserver
else
KUBE_APISERVER_BIN=$(TEST_ASSETS)/kube-apiserver
endif
# find or download kubectl
kubectl:
ifeq (, $(wildcard $(TEST_ASSETS)/kubectl))
@{ \
set -xe ;\
INSTALL_TMP_DIR=$$(mktemp -d) ;\
cd $$INSTALL_TMP_DIR ;\
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.2/kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\
mkdir -p $(TEST_ASSETS) ;\
tar zxvf kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/etcd $(TEST_ASSETS)/etcd ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kube-apiserver $(TEST_ASSETS)/kube-apiserver ;\
mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kubectl $(TEST_ASSETS)/kubectl ;\
rm -rf $$INSTALL_TMP_DIR ;\
}
KUBECTL_BIN=$(TEST_ASSETS)/kubectl
else
KUBECTL_BIN=$(TEST_ASSETS)/kubectl
endif

View File

@@ -25,8 +25,7 @@ ToC:
- [Using EKS IAM role for service accounts](#using-eks-iam-role-for-service-accounts) - [Using EKS IAM role for service accounts](#using-eks-iam-role-for-service-accounts)
- [Software installed in the runner image](#software-installed-in-the-runner-image) - [Software installed in the runner image](#software-installed-in-the-runner-image)
- [Common errors](#common-errors) - [Common errors](#common-errors)
- [Developing](#developing) - [Contributing](#contributing)
- [Alternatives](#alternatives)
## Motivation ## Motivation
@@ -45,8 +44,8 @@ Install the custom resource and actions-runner-controller with `kubectl` or `hel
`kubectl`: `kubectl`:
```shell ```shell
# REPLACE "v0.17.0" with the version you wish to deploy # REPLACE "v0.18.2" with the version you wish to deploy
kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/download/v0.17.0/actions-runner-controller.yaml kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/download/v0.18.2/actions-runner-controller.yaml
``` ```
`helm`: `helm`:
@@ -61,7 +60,7 @@ helm upgrade --install -n actions-runner-system actions-runner-controller/action
If you use either Github Enterprise Cloud or Server, you can use **actions-runner-controller** with those, too. If you use either Github Enterprise Cloud or Server, you can use **actions-runner-controller** with those, too.
Authentication works same way as with public Github (repo and organization level). Authentication works same way as with public Github (repo and organization level).
The minimum version of Github Enterprise Server is 3.0.0 (or rc1/rc2). The minimum version of Github Enterprise Server is 3.0.0 (or rc1/rc2).
__**NOTE : The maintainers do not have an Enterprise environment to be able to test changes and so are reliant on the community for testing, support is a best endeavors basis only and is community driven**__ __**NOTE : The maintainers do not have an Enterprise environment to be able to test changes and so this feature is community driven. Support is on a best endeavors basis.**__
```shell ```shell
kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL> --namespace actions-runner-system kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL> --namespace actions-runner-system
@@ -88,7 +87,6 @@ spec:
template: template:
spec: spec:
enterprise: your-enterprise-name enterprise: your-enterprise-name
dockerdWithinRunnerContainer: true
resources: resources:
limits: limits:
cpu: "4000m" cpu: "4000m"
@@ -96,12 +94,6 @@ spec:
requests: requests:
cpu: "200m" cpu: "200m"
memory: "200Mi" memory: "200Mi"
volumeMounts:
- mountPath: /runner
name: runner
volumes:
- name: runner
emptyDir: {}
``` ```
@@ -163,7 +155,7 @@ Log-in to a GitHub account that has `admin` privileges for the repository, and [
* repo (Full control) * repo (Full control)
**Scopes for a Organisation Runner** **Scopes for a Organization Runner**
* repo (Full control) * repo (Full control)
* admin:org (Full control) * admin:org (Full control)
@@ -292,7 +284,7 @@ A `RunnerDeployment` can scale the number of runners between `minReplicas` and `
**TotalNumberOfQueuedAndInProgressWorkflowRuns** **TotalNumberOfQueuedAndInProgressWorkflowRuns**
In the below example, `actions-runner` will pole GitHub for all pending workflows with the pole period defined by the sync period configuration. It will then scale to e.g. 3 if there're 3 pending jobs at sync time. In the below example, `actions-runner` will poll GitHub for all pending workflows with the poll period defined by the sync period configuration. It will then scale to e.g. 3 if there're 3 pending jobs at sync time.
With this scaling metric we are required to define a list of repositories within our metric. With this scaling metric we are required to define a list of repositories within our metric.
The scale out performance is controlled via the manager containers startup `--sync-period` argument. The default value is set to 10 minutes to prevent default deployments rate limiting themselves from the GitHub API. The scale out performance is controlled via the manager containers startup `--sync-period` argument. The default value is set to 10 minutes to prevent default deployments rate limiting themselves from the GitHub API.
@@ -349,7 +341,7 @@ spec:
**PercentageRunnersBusy** **PercentageRunnersBusy**
The `HorizontalRunnerAutoscaler` will pole GitHub based on the configuration sync period for the number of busy runners which live in the RunnerDeployment's namespace and scale based on the settings The `HorizontalRunnerAutoscaler` will poll GitHub based on the configuration sync period for the number of busy runners which live in the RunnerDeployment's namespace and scale based on the settings
**Kustomize Config :** The period can be customised in the `config/default/manager_auth_proxy_patch.yaml` patch<br /> **Kustomize Config :** The period can be customised in the `config/default/manager_auth_proxy_patch.yaml` patch<br />
**Helm Config :** `syncPeriod` **Helm Config :** `syncPeriod`
@@ -419,11 +411,11 @@ spec:
> Please get prepared to put some time and effort to learn and leverage this feature! > Please get prepared to put some time and effort to learn and leverage this feature!
`actions-runner-controller` has an optional Webhook server that receives GitHub Webhook events and scale `actions-runner-controller` has an optional Webhook server that receives GitHub Webhook events and scale
[`RunnerDeployment`s](#runnerdeployments) by updating corresponding [`HorizontalRunnerAutoscaler`s](#autoscaling). [`RunnerDeployments`](#runnerdeployments) by updating corresponding [`HorizontalRunnerAutoscalers`](#autoscaling).
Today, the Webhook server can be configured to respond GitHub `check_run`, `pull_request`, and `push` events Today, the Webhook server can be configured to respond GitHub `check_run`, `pull_request`, and `push` events
by scaling up the matching `HorizontalRunnerAutoscaler` by N replica(s), where `N` is configurable within by scaling up the matching `HorizontalRunnerAutoscaler` by N replica(s), where `N` is configurable within
`HorizontalRunerAutoscaler`'s `Spec`. `HorizontalRunerAutoscaler's` `Spec`.
More concretely, you can configure the targeted GitHub event types and the `N` in More concretely, you can configure the targeted GitHub event types and the `N` in
`scaleUpTriggers`: `scaleUpTriggers`:
@@ -613,6 +605,17 @@ spec:
# You can customise this setting allowing you to change the default working directory location # You can customise this setting allowing you to change the default working directory location
# for example, the below setting is the same as on the ubuntu-18.04 image # for example, the below setting is the same as on the ubuntu-18.04 image
workDir: /home/runner/work workDir: /home/runner/work
# You can mount some of the shared volumes to the dind container using dockerVolumeMounts, like any other volume mounting.
# NOTE: in case you want to use an hostPath like the following example, make sure that Kubernetes doesn't schedule more than one runner
# per physical host. You can achieve that by setting pod anti-affinity rules and/or resource requests/limits.
volumes:
- name: docker-extra
hostPath:
path: /mnt/docker-extra
type: DirectoryOrCreate
dockerVolumeMounts:
- mountPath: /var/lib/docker
name: docker-extra
``` ```
### Runner labels ### Runner labels
@@ -745,8 +748,11 @@ Your base64'ed PAT token has a new line at the end, it needs to be created witho
* `echo -n $TOKEN | base64` * `echo -n $TOKEN | base64`
* Create the secret as described in the docs using the shell and documeneted flags * Create the secret as described in the docs using the shell and documeneted flags
# Developing # Contributing
For more details about any requirements or process, please check out [Getting Started with Contributing](CONTRIBUTING.md).
**The Controller**<br />
If you'd like to modify the controller to fork or contribute, I'd suggest using the following snippet for running If you'd like to modify the controller to fork or contribute, I'd suggest using the following snippet for running
the acceptance test: the acceptance test:
@@ -759,7 +765,7 @@ NAME=$DOCKER_USER/actions-runner-controller \
APP_ID=*** \ APP_ID=*** \
PRIVATE_KEY_FILE_PATH=path/to/pem/file \ PRIVATE_KEY_FILE_PATH=path/to/pem/file \
INSTALLATION_ID=*** \ INSTALLATION_ID=*** \
make docker-build docker-push acceptance make docker-build acceptance
``` ```
Please follow the instructions explained in [Using Personal Access Token](#using-personal-access-token) to obtain Please follow the instructions explained in [Using Personal Access Token](#using-personal-access-token) to obtain
@@ -780,19 +786,9 @@ NAME=$DOCKER_USER/actions-runner-controller \
PRIVATE_KEY_FILE_PATH=path/to/pem/file \ PRIVATE_KEY_FILE_PATH=path/to/pem/file \
INSTALLATION_ID=*** \ INSTALLATION_ID=*** \
ACCEPTANCE_TEST_SECRET_TYPE=token \ ACCEPTANCE_TEST_SECRET_TYPE=token \
make docker-build docker-push \ make docker-build acceptance/setup \
acceptance/setup acceptance/tests acceptance/tests
``` ```
# Alternatives
The following is a list of alternative solutions that may better fit you depending on your use-case: **Runner Tests**<br />
A set of example pipelines (./acceptance/pipelines) are provided in this repository which you can use to validate your runners are working as expected. When raising a PR please run the relevant suites to prove your change hasn't broken anything.
- <https://github.com/evryfs/github-actions-runner-operator/>
- <https://github.com/philips-labs/terraform-aws-github-runner/>
Although the situation can change over time, as of writing this sentence, the benefits of using `actions-runner-controller` over the alternatives are:
- `actions-runner-controller` has the ability to autoscale runners based on number of pending/progressing jobs (#99)
- `actions-runner-controller` is able to gracefully stop runners (#103)
- `actions-runner-controller` has ARM support
- `actions-runner-controller` has GitHub Enterprise support (see [GitHub Enterprise support](#github-enterprise-support) section for caveats)

View File

@@ -12,6 +12,9 @@ done
echo Found runner ${runner_name}. echo Found runner ${runner_name}.
# Wait a bit to make sure the runner pod is created before looking for it.
sleep 2
pod_name= pod_name=
while [ -z "${pod_name}" ]; do while [ -z "${pod_name}" ]; do
@@ -24,6 +27,6 @@ echo Found pod ${pod_name}.
echo Waiting for pod ${runner_name} to become ready... 1>&2 echo Waiting for pod ${runner_name} to become ready... 1>&2
kubectl wait pod/${runner_name} --for condition=ready --timeout 180s kubectl wait pod/${runner_name} --for condition=ready --timeout 270s
echo All tests passed. 1>&2 echo All tests passed. 1>&2

View File

@@ -26,13 +26,14 @@ if [ "${tool}" == "helm" ]; then
charts/actions-runner-controller \ charts/actions-runner-controller \
-n actions-runner-system \ -n actions-runner-system \
--create-namespace \ --create-namespace \
--set syncPeriod=5m --set syncPeriod=5m \
kubectl -n actions-runner-system wait deploy/actions-runner-controller --for condition=available --set authSecret.create=false
kubectl -n actions-runner-system wait deploy/actions-runner-controller --for condition=available --timeout 60s
else else
kubectl apply \ kubectl apply \
-n actions-runner-system \ -n actions-runner-system \
-f release/actions-runner-controller.yaml -f release/actions-runner-controller.yaml
kubectl -n actions-runner-system wait deploy/controller-manager --for condition=available --timeout 60s kubectl -n actions-runner-system wait deploy/controller-manager --for condition=available --timeout 120s
fi fi
# Adhocly wait for some time until actions-runner-controller's admission webhook gets ready # Adhocly wait for some time until actions-runner-controller's admission webhook gets ready

View File

@@ -0,0 +1,36 @@
name: EKS Integration Tests
on:
workflow_dispatch:
env:
IRSA_ROLE_ARN:
ASSUME_ROLE_ARN:
AWS_REGION:
jobs:
assume-role-in-runner-test:
runs-on: ['self-hosted', 'Linux']
steps:
- name: Test aws-actions/configure-aws-credentials Action
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ env.AWS_REGION }}
role-to-assume: ${{ env.ASSUME_ROLE_ARN }}
role-duration-seconds: 900
assume-role-in-container-test:
runs-on: ['self-hosted', 'Linux']
container:
image: amazon/aws-cli
env:
AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
AWS_ROLE_ARN: ${{ env.IRSA_ROLE_ARN }}
volumes:
- /var/run/secrets/eks.amazonaws.com/serviceaccount/token:/var/run/secrets/eks.amazonaws.com/serviceaccount/token
steps:
- name: Test aws-actions/configure-aws-credentials Action in container
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ env.AWS_REGION }}
role-to-assume: ${{ env.ASSUME_ROLE_ARN }}
role-duration-seconds: 900

View File

@@ -0,0 +1,83 @@
name: Runner Integration Tests
on:
workflow_dispatch:
env:
ImageOS: ubuntu18 # Used by ruby/setup-ruby action | Update me for the runner OS version you are testing against
jobs:
run-step-in-container-test:
runs-on: ['self-hosted', 'Linux']
container:
image: alpine
steps:
- name: Test we are working in the container
run: |
if [[ $(sed -n '2p' < /etc/os-release | cut -d "=" -f2) != "alpine" ]]; then
echo "::error ::Failed OS detection test, could not match /etc/os-release with alpine. Are we really running in the container?"
echo "/etc/os-release below:"
cat /etc/os-release
exit 1
fi
setup-python-test:
runs-on: ['self-hosted', 'Linux']
steps:
- name: Print native Python environment
run: |
which python
python --version
- uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Test actions/setup-python works
run: |
VERSION=$(python --version 2>&1 | cut -d ' ' -f2 | cut -d '.' -f1-2)
if [[ $VERSION != '3.9' ]]; then
echo "Python version detected : $(python --version 2>&1)"
echo "::error ::Detected python failed setup version test, could not match version with version specified in the setup action"
exit 1
else
echo "Python version detected : $(python --version 2>&1)"
fi
setup-node-test:
runs-on: ['self-hosted', 'Linux']
steps:
- uses: actions/setup-node@v2
with:
node-version: '12'
- name: Test actions/setup-node works
run: |
VERSION=$(node --version | cut -c 2- | cut -d '.' -f1)
if [[ $VERSION != '12' ]]; then
echo "Node version detected : $(node --version 2>&1)"
echo "::error ::Detected node failed setup version test, could not match version with version specified in the setup action"
exit 1
else
echo "Node version detected : $(node --version 2>&1)"
fi
setup-ruby-test:
runs-on: ['self-hosted', 'Linux']
steps:
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0
bundler-cache: true
- name: Test ruby/setup-ruby works
run: |
VERSION=$(ruby --version | cut -d ' ' -f2 | cut -d '.' -f1-2)
if [[ $VERSION != '3.0' ]]; then
echo "Ruby version detected : $(ruby --version 2>&1)"
echo "::error ::Detected ruby failed setup version test, could not match version with version specified in the setup action"
exit 1
else
echo "Ruby version detected : $(ruby --version 2>&1)"
fi
python-shell-test:
runs-on: ['self-hosted', 'Linux']
steps:
- name: Test Python shell works
run: |
import os
print(os.environ['PATH'])
shell: python

View File

@@ -7,3 +7,14 @@ spec:
template: template:
spec: spec:
repository: mumoshu/actions-runner-controller-ci repository: mumoshu/actions-runner-controller-ci
#
# dockerd within runner container
#
## Replace `mumoshu/actions-runner-dind:dev` with your dind image
#dockerdWithinRunnerContainer: true
#image: mumoshu/actions-runner-dind:dev
#
# Set the MTU used by dockerd-managed network interfaces (including docker-build-ubuntu)
#
#dockerMTU: 1450

View File

@@ -156,6 +156,7 @@ type HorizontalRunnerAutoscalerStatus struct {
DesiredReplicas *int `json:"desiredReplicas,omitempty"` DesiredReplicas *int `json:"desiredReplicas,omitempty"`
// +optional // +optional
// +nullable
LastSuccessfulScaleOutTime *metav1.Time `json:"lastSuccessfulScaleOutTime,omitempty"` LastSuccessfulScaleOutTime *metav1.Time `json:"lastSuccessfulScaleOutTime,omitempty"`
// +optional // +optional

View File

@@ -18,6 +18,7 @@ package v1alpha1
import ( import (
"errors" "errors"
"k8s.io/apimachinery/pkg/api/resource"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -48,6 +49,8 @@ type RunnerSpec struct {
// +optional // +optional
DockerdContainerResources corev1.ResourceRequirements `json:"dockerdContainerResources,omitempty"` DockerdContainerResources corev1.ResourceRequirements `json:"dockerdContainerResources,omitempty"`
// +optional // +optional
DockerVolumeMounts []corev1.VolumeMount `json:"dockerVolumeMounts,omitempty"`
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty"` Resources corev1.ResourceRequirements `json:"resources,omitempty"`
// +optional // +optional
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
@@ -94,6 +97,10 @@ type RunnerSpec struct {
DockerEnabled *bool `json:"dockerEnabled,omitempty"` DockerEnabled *bool `json:"dockerEnabled,omitempty"`
// +optional // +optional
DockerMTU *int64 `json:"dockerMTU,omitempty"` DockerMTU *int64 `json:"dockerMTU,omitempty"`
// +optional
HostAliases []corev1.HostAlias `json:"hostAliases,omitempty"`
// +optional
VolumeSizeLimit *resource.Quantity `json:"volumeSizeLimit,omitempty"`
} }
// ValidateRepository validates repository field. // ValidateRepository validates repository field.
@@ -121,13 +128,17 @@ func (rs *RunnerSpec) ValidateRepository() error {
// RunnerStatus defines the observed state of Runner // RunnerStatus defines the observed state of Runner
type RunnerStatus struct { type RunnerStatus struct {
// +optional
Registration RunnerStatusRegistration `json:"registration"` Registration RunnerStatusRegistration `json:"registration"`
Phase string `json:"phase"` // +optional
Reason string `json:"reason"` Phase string `json:"phase,omitempty"`
Message string `json:"message"` // +optional
Reason string `json:"reason,omitempty"`
//+optional // +optional
LastRegistrationCheckTime *metav1.Time `json:"lastRegistrationCheckTime"` Message string `json:"message,omitempty"`
// +optional
// +nullable
LastRegistrationCheckTime *metav1.Time `json:"lastRegistrationCheckTime,omitempty"`
} }
// RunnerStatusRegistration contains runner registration status // RunnerStatusRegistration contains runner registration status

View File

@@ -595,6 +595,13 @@ func (in *RunnerSpec) DeepCopyInto(out *RunnerSpec) {
} }
} }
in.DockerdContainerResources.DeepCopyInto(&out.DockerdContainerResources) in.DockerdContainerResources.DeepCopyInto(&out.DockerdContainerResources)
if in.DockerVolumeMounts != nil {
in, out := &in.DockerVolumeMounts, &out.DockerVolumeMounts
*out = make([]v1.VolumeMount, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Resources.DeepCopyInto(&out.Resources) in.Resources.DeepCopyInto(&out.Resources)
if in.VolumeMounts != nil { if in.VolumeMounts != nil {
in, out := &in.VolumeMounts, &out.VolumeMounts in, out := &in.VolumeMounts, &out.VolumeMounts
@@ -699,6 +706,18 @@ func (in *RunnerSpec) DeepCopyInto(out *RunnerSpec) {
*out = new(int64) *out = new(int64)
**out = **in **out = **in
} }
if in.HostAliases != nil {
in, out := &in.HostAliases, &out.HostAliases
*out = make([]v1.HostAlias, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.VolumeSizeLimit != nil {
in, out := &in.VolumeSizeLimit, &out.VolumeSizeLimit
x := (*in).DeepCopy()
*out = &x
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerSpec.

View File

@@ -15,7 +15,10 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.10.0 version: 0.11.0
# Used as the default manager tag value when no tag property is provided in the values.yaml
appVersion: 0.18.2
home: https://github.com/summerwind/actions-runner-controller home: https://github.com/summerwind/actions-runner-controller

View File

@@ -207,6 +207,7 @@ spec:
type: integer type: integer
lastSuccessfulScaleOutTime: lastSuccessfulScaleOutTime:
format: date-time format: date-time
nullable: true
type: string type: string
observedGeneration: observedGeneration:
description: ObservedGeneration is the most recent generation observed description: ObservedGeneration is the most recent generation observed

View File

@@ -436,6 +436,33 @@ spec:
dockerMTU: dockerMTU:
format: int64 format: int64
type: integer type: integer
dockerVolumeMounts:
items:
description: VolumeMount describes a mounting of a Volume within a container.
properties:
mountPath:
description: Path within the container at which the volume should be mounted. Must not contain ':'.
type: string
mountPropagation:
description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10.
type: string
name:
description: This must match the Name of a Volume.
type: string
readOnly:
description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.
type: boolean
subPath:
description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root).
type: string
subPathExpr:
description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. This field is beta in 1.15.
type: string
required:
- mountPath
- name
type: object
type: array
dockerdContainerResources: dockerdContainerResources:
description: ResourceRequirements describes the compute resource requirements. description: ResourceRequirements describes the compute resource requirements.
properties: properties:
@@ -580,6 +607,20 @@ spec:
type: array type: array
group: group:
type: string type: string
hostAliases:
items:
description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.
properties:
hostnames:
description: Hostnames for the above IP address.
items:
type: string
type: array
ip:
description: IP address of the host file entry.
type: string
type: object
type: array
image: image:
type: string type: string
imagePullPolicy: imagePullPolicy:
@@ -768,6 +809,12 @@ spec:
- name - name
type: object type: object
type: array type: array
volumeSizeLimit:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
volumes: volumes:
items: items:
description: Volume represents a named volume in a pod that may be accessed by any container in the pod. description: Volume represents a named volume in a pod that may be accessed by any container in the pod.

View File

@@ -436,6 +436,33 @@ spec:
dockerMTU: dockerMTU:
format: int64 format: int64
type: integer type: integer
dockerVolumeMounts:
items:
description: VolumeMount describes a mounting of a Volume within a container.
properties:
mountPath:
description: Path within the container at which the volume should be mounted. Must not contain ':'.
type: string
mountPropagation:
description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10.
type: string
name:
description: This must match the Name of a Volume.
type: string
readOnly:
description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.
type: boolean
subPath:
description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root).
type: string
subPathExpr:
description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. This field is beta in 1.15.
type: string
required:
- mountPath
- name
type: object
type: array
dockerdContainerResources: dockerdContainerResources:
description: ResourceRequirements describes the compute resource requirements. description: ResourceRequirements describes the compute resource requirements.
properties: properties:
@@ -580,6 +607,20 @@ spec:
type: array type: array
group: group:
type: string type: string
hostAliases:
items:
description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.
properties:
hostnames:
description: Hostnames for the above IP address.
items:
type: string
type: array
ip:
description: IP address of the host file entry.
type: string
type: object
type: array
image: image:
type: string type: string
imagePullPolicy: imagePullPolicy:
@@ -768,6 +809,12 @@ spec:
- name - name
type: object type: object
type: array type: array
volumeSizeLimit:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
volumes: volumes:
items: items:
description: Volume represents a named volume in a pod that may be accessed by any container in the pod. description: Volume represents a named volume in a pod that may be accessed by any container in the pod.

View File

@@ -401,6 +401,33 @@ spec:
dockerMTU: dockerMTU:
format: int64 format: int64
type: integer type: integer
dockerVolumeMounts:
items:
description: VolumeMount describes a mounting of a Volume within a container.
properties:
mountPath:
description: Path within the container at which the volume should be mounted. Must not contain ':'.
type: string
mountPropagation:
description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10.
type: string
name:
description: This must match the Name of a Volume.
type: string
readOnly:
description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.
type: boolean
subPath:
description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root).
type: string
subPathExpr:
description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. This field is beta in 1.15.
type: string
required:
- mountPath
- name
type: object
type: array
dockerdContainerResources: dockerdContainerResources:
description: ResourceRequirements describes the compute resource requirements. description: ResourceRequirements describes the compute resource requirements.
properties: properties:
@@ -545,6 +572,20 @@ spec:
type: array type: array
group: group:
type: string type: string
hostAliases:
items:
description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.
properties:
hostnames:
description: Hostnames for the above IP address.
items:
type: string
type: array
ip:
description: IP address of the host file entry.
type: string
type: object
type: array
image: image:
type: string type: string
imagePullPolicy: imagePullPolicy:
@@ -733,6 +774,12 @@ spec:
- name - name
type: object type: object
type: array type: array
volumeSizeLimit:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
volumes: volumes:
items: items:
description: Volume represents a named volume in a pod that may be accessed by any container in the pod. description: Volume represents a named volume in a pod that may be accessed by any container in the pod.
@@ -1543,6 +1590,7 @@ spec:
properties: properties:
lastRegistrationCheckTime: lastRegistrationCheckTime:
format: date-time format: date-time
nullable: true
type: string type: string
message: message:
type: string type: string
@@ -1572,11 +1620,6 @@ spec:
- expiresAt - expiresAt
- token - token
type: object type: object
required:
- message
- phase
- reason
- registration
type: object type: object
type: object type: object
version: v1alpha1 version: v1alpha1

View File

@@ -5,7 +5,7 @@ apiVersion: cert-manager.io/v1
kind: Issuer kind: Issuer
metadata: metadata:
name: {{ include "actions-runner-controller.selfsignedIssuerName" . }} name: {{ include "actions-runner-controller.selfsignedIssuerName" . }}
namespace: {{ .Namespace }} namespace: {{ .Release.Namespace }}
spec: spec:
selfSigned: {} selfSigned: {}
--- ---
@@ -13,7 +13,7 @@ apiVersion: cert-manager.io/v1
kind: Certificate kind: Certificate
metadata: metadata:
name: {{ include "actions-runner-controller.servingCertName" . }} name: {{ include "actions-runner-controller.servingCertName" . }}
namespace: {{ .Namespace }} namespace: {{ .Release.Namespace }}
spec: spec:
dnsNames: dnsNames:
- {{ include "actions-runner-controller.webhookServiceName" . }}.{{ .Release.Namespace }}.svc - {{ include "actions-runner-controller.webhookServiceName" . }}.{{ .Release.Namespace }}.svc

View File

@@ -65,7 +65,7 @@ spec:
- name: {{ $key }} - name: {{ $key }}
value: {{ $val | quote }} value: {{ $val | quote }}
{{- end }} {{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}"
name: manager name: manager
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: ports:

View File

@@ -47,7 +47,7 @@ spec:
- name: {{ $key }} - name: {{ $key }}
value: {{ $val | quote }} value: {{ $val | quote }}
{{- end }} {{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}"
name: github-webhook-server name: github-webhook-server
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: ports:

View File

@@ -1,6 +1,6 @@
{{- if .Values.githubWebhookServer.ingress.enabled -}} {{- if .Values.githubWebhookServer.ingress.enabled -}}
{{- $fullName := include "actions-runner-controller-github-webhook-server.fullname" . -}} {{- $fullName := include "actions-runner-controller-github-webhook-server.fullname" . -}}
{{- $svcPort := .Values.githubWebhookServer.service.port -}} {{- $svcPort := (index .Values.githubWebhookServer.service.ports 0).port -}}
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1 apiVersion: networking.k8s.io/v1beta1
{{- else -}} {{- else -}}

View File

@@ -22,7 +22,6 @@ authSecret:
image: image:
repository: summerwind/actions-runner-controller repository: summerwind/actions-runner-controller
tag: "v0.17.0"
dindSidecarRepositoryAndTag: "docker:dind" dindSidecarRepositoryAndTag: "docker:dind"
pullPolicy: IfNotPresent pullPolicy: IfNotPresent

View File

@@ -207,6 +207,7 @@ spec:
type: integer type: integer
lastSuccessfulScaleOutTime: lastSuccessfulScaleOutTime:
format: date-time format: date-time
nullable: true
type: string type: string
observedGeneration: observedGeneration:
description: ObservedGeneration is the most recent generation observed description: ObservedGeneration is the most recent generation observed

View File

@@ -436,6 +436,33 @@ spec:
dockerMTU: dockerMTU:
format: int64 format: int64
type: integer type: integer
dockerVolumeMounts:
items:
description: VolumeMount describes a mounting of a Volume within a container.
properties:
mountPath:
description: Path within the container at which the volume should be mounted. Must not contain ':'.
type: string
mountPropagation:
description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10.
type: string
name:
description: This must match the Name of a Volume.
type: string
readOnly:
description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.
type: boolean
subPath:
description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root).
type: string
subPathExpr:
description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. This field is beta in 1.15.
type: string
required:
- mountPath
- name
type: object
type: array
dockerdContainerResources: dockerdContainerResources:
description: ResourceRequirements describes the compute resource requirements. description: ResourceRequirements describes the compute resource requirements.
properties: properties:
@@ -580,6 +607,20 @@ spec:
type: array type: array
group: group:
type: string type: string
hostAliases:
items:
description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.
properties:
hostnames:
description: Hostnames for the above IP address.
items:
type: string
type: array
ip:
description: IP address of the host file entry.
type: string
type: object
type: array
image: image:
type: string type: string
imagePullPolicy: imagePullPolicy:
@@ -768,6 +809,12 @@ spec:
- name - name
type: object type: object
type: array type: array
volumeSizeLimit:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
volumes: volumes:
items: items:
description: Volume represents a named volume in a pod that may be accessed by any container in the pod. description: Volume represents a named volume in a pod that may be accessed by any container in the pod.

View File

@@ -436,6 +436,33 @@ spec:
dockerMTU: dockerMTU:
format: int64 format: int64
type: integer type: integer
dockerVolumeMounts:
items:
description: VolumeMount describes a mounting of a Volume within a container.
properties:
mountPath:
description: Path within the container at which the volume should be mounted. Must not contain ':'.
type: string
mountPropagation:
description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10.
type: string
name:
description: This must match the Name of a Volume.
type: string
readOnly:
description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.
type: boolean
subPath:
description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root).
type: string
subPathExpr:
description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. This field is beta in 1.15.
type: string
required:
- mountPath
- name
type: object
type: array
dockerdContainerResources: dockerdContainerResources:
description: ResourceRequirements describes the compute resource requirements. description: ResourceRequirements describes the compute resource requirements.
properties: properties:
@@ -580,6 +607,20 @@ spec:
type: array type: array
group: group:
type: string type: string
hostAliases:
items:
description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.
properties:
hostnames:
description: Hostnames for the above IP address.
items:
type: string
type: array
ip:
description: IP address of the host file entry.
type: string
type: object
type: array
image: image:
type: string type: string
imagePullPolicy: imagePullPolicy:
@@ -768,6 +809,12 @@ spec:
- name - name
type: object type: object
type: array type: array
volumeSizeLimit:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
volumes: volumes:
items: items:
description: Volume represents a named volume in a pod that may be accessed by any container in the pod. description: Volume represents a named volume in a pod that may be accessed by any container in the pod.

View File

@@ -401,6 +401,33 @@ spec:
dockerMTU: dockerMTU:
format: int64 format: int64
type: integer type: integer
dockerVolumeMounts:
items:
description: VolumeMount describes a mounting of a Volume within a container.
properties:
mountPath:
description: Path within the container at which the volume should be mounted. Must not contain ':'.
type: string
mountPropagation:
description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10.
type: string
name:
description: This must match the Name of a Volume.
type: string
readOnly:
description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.
type: boolean
subPath:
description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root).
type: string
subPathExpr:
description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. This field is beta in 1.15.
type: string
required:
- mountPath
- name
type: object
type: array
dockerdContainerResources: dockerdContainerResources:
description: ResourceRequirements describes the compute resource requirements. description: ResourceRequirements describes the compute resource requirements.
properties: properties:
@@ -545,6 +572,20 @@ spec:
type: array type: array
group: group:
type: string type: string
hostAliases:
items:
description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.
properties:
hostnames:
description: Hostnames for the above IP address.
items:
type: string
type: array
ip:
description: IP address of the host file entry.
type: string
type: object
type: array
image: image:
type: string type: string
imagePullPolicy: imagePullPolicy:
@@ -733,6 +774,12 @@ spec:
- name - name
type: object type: object
type: array type: array
volumeSizeLimit:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
volumes: volumes:
items: items:
description: Volume represents a named volume in a pod that may be accessed by any container in the pod. description: Volume represents a named volume in a pod that may be accessed by any container in the pod.
@@ -1543,6 +1590,7 @@ spec:
properties: properties:
lastRegistrationCheckTime: lastRegistrationCheckTime:
format: date-time format: date-time
nullable: true
type: string type: string
message: message:
type: string type: string
@@ -1572,11 +1620,6 @@ spec:
- expiresAt - expiresAt
- token - token
type: object type: object
required:
- message
- phase
- reason
- registration
type: object type: object
type: object type: object
version: v1alpha1 version: v1alpha1

View File

@@ -34,7 +34,7 @@ func getValueAvailableAt(now time.Time, from, to *time.Time, reservedValue int)
return &reservedValue return &reservedValue
} }
func (r *HorizontalRunnerAutoscalerReconciler) getDesiredReplicasFromCache(hra v1alpha1.HorizontalRunnerAutoscaler) *int { func (r *HorizontalRunnerAutoscalerReconciler) fetchSuggestedReplicasFromCache(hra v1alpha1.HorizontalRunnerAutoscaler) *int {
var entry *v1alpha1.CacheEntry var entry *v1alpha1.CacheEntry
for i := range hra.Status.CacheEntries { for i := range hra.Status.CacheEntries {
@@ -63,7 +63,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) getDesiredReplicasFromCache(hra v
return nil return nil
} }
func (r *HorizontalRunnerAutoscalerReconciler) determineDesiredReplicas(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) { func (r *HorizontalRunnerAutoscalerReconciler) suggestDesiredReplicas(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) {
if hra.Spec.MinReplicas == nil { if hra.Spec.MinReplicas == nil {
return nil, fmt.Errorf("horizontalrunnerautoscaler %s/%s is missing minReplicas", hra.Namespace, hra.Name) return nil, fmt.Errorf("horizontalrunnerautoscaler %s/%s is missing minReplicas", hra.Namespace, hra.Name)
} else if hra.Spec.MaxReplicas == nil { } else if hra.Spec.MaxReplicas == nil {
@@ -73,20 +73,20 @@ func (r *HorizontalRunnerAutoscalerReconciler) determineDesiredReplicas(rd v1alp
metrics := hra.Spec.Metrics metrics := hra.Spec.Metrics
if len(metrics) == 0 { if len(metrics) == 0 {
if len(hra.Spec.ScaleUpTriggers) == 0 { if len(hra.Spec.ScaleUpTriggers) == 0 {
return r.calculateReplicasByQueuedAndInProgressWorkflowRuns(rd, hra) return r.suggestReplicasByQueuedAndInProgressWorkflowRuns(rd, hra)
} }
return hra.Spec.MinReplicas, nil return nil, nil
} else if metrics[0].Type == v1alpha1.AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns { } else if metrics[0].Type == v1alpha1.AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns {
return r.calculateReplicasByQueuedAndInProgressWorkflowRuns(rd, hra) return r.suggestReplicasByQueuedAndInProgressWorkflowRuns(rd, hra)
} else if metrics[0].Type == v1alpha1.AutoscalingMetricTypePercentageRunnersBusy { } else if metrics[0].Type == v1alpha1.AutoscalingMetricTypePercentageRunnersBusy {
return r.calculateReplicasByPercentageRunnersBusy(rd, hra) return r.suggestReplicasByPercentageRunnersBusy(rd, hra)
} else { } else {
return nil, fmt.Errorf("validting autoscaling metrics: unsupported metric type %q", metrics[0].Type) return nil, fmt.Errorf("validting autoscaling metrics: unsupported metric type %q", metrics[0].Type)
} }
} }
func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByQueuedAndInProgressWorkflowRuns(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) { func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgressWorkflowRuns(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) {
var repos [][]string var repos [][]string
metrics := hra.Spec.Metrics metrics := hra.Spec.Metrics
@@ -101,7 +101,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByQueuedAndInPro
// we assume that the desired replicas should always be `minReplicas + capacityReservedThroughWebhook`. // we assume that the desired replicas should always be `minReplicas + capacityReservedThroughWebhook`.
// See https://github.com/summerwind/actions-runner-controller/issues/377#issuecomment-793372693 // See https://github.com/summerwind/actions-runner-controller/issues/377#issuecomment-793372693
if len(metrics) == 0 { if len(metrics) == 0 {
return hra.Spec.MinReplicas, nil return nil, nil
} }
if len(metrics[0].RepositoryNames) == 0 { if len(metrics[0].RepositoryNames) == 0 {
@@ -178,28 +178,10 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByQueuedAndInPro
} }
} }
minReplicas := *hra.Spec.MinReplicas
maxReplicas := *hra.Spec.MaxReplicas
necessaryReplicas := queued + inProgress necessaryReplicas := queued + inProgress
var desiredReplicas int
if necessaryReplicas < minReplicas {
desiredReplicas = minReplicas
} else if necessaryReplicas > maxReplicas {
desiredReplicas = maxReplicas
} else {
desiredReplicas = necessaryReplicas
}
rd.Status.Replicas = &desiredReplicas
replicas := desiredReplicas
r.Log.V(1).Info( r.Log.V(1).Info(
"Calculated desired replicas", fmt.Sprintf("Suggested desired replicas of %d by TotalNumberOfQueuedAndInProgressWorkflowRuns", necessaryReplicas),
"computed_replicas_desired", desiredReplicas,
"spec_replicas_min", minReplicas,
"spec_replicas_max", maxReplicas,
"workflow_runs_completed", completed, "workflow_runs_completed", completed,
"workflow_runs_in_progress", inProgress, "workflow_runs_in_progress", inProgress,
"workflow_runs_queued", queued, "workflow_runs_queued", queued,
@@ -209,13 +191,11 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByQueuedAndInPro
"horizontal_runner_autoscaler", hra.Name, "horizontal_runner_autoscaler", hra.Name,
) )
return &replicas, nil return &necessaryReplicas, nil
} }
func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByPercentageRunnersBusy(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) { func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByPercentageRunnersBusy(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) {
ctx := context.Background() ctx := context.Background()
minReplicas := *hra.Spec.MinReplicas
maxReplicas := *hra.Spec.MaxReplicas
metrics := hra.Spec.Metrics[0] metrics := hra.Spec.Metrics[0]
scaleUpThreshold := defaultScaleUpThreshold scaleUpThreshold := defaultScaleUpThreshold
scaleDownThreshold := defaultScaleDownThreshold scaleDownThreshold := defaultScaleDownThreshold
@@ -363,21 +343,13 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByPercentageRunn
desiredReplicas = *rd.Spec.Replicas desiredReplicas = *rd.Spec.Replicas
} }
if desiredReplicas < minReplicas {
desiredReplicas = minReplicas
} else if desiredReplicas > maxReplicas {
desiredReplicas = maxReplicas
}
// NOTES for operators: // NOTES for operators:
// //
// - num_runners can be as twice as large as replicas_desired_before while // - num_runners can be as twice as large as replicas_desired_before while
// the runnerdeployment controller is replacing RunnerReplicaSet for runner update. // the runnerdeployment controller is replacing RunnerReplicaSet for runner update.
r.Log.V(1).Info( r.Log.V(1).Info(
"Calculated desired replicas", fmt.Sprintf("Suggested desired replicas of %d by PercentageRunnersBusy", desiredReplicas),
"replicas_min", minReplicas,
"replicas_max", maxReplicas,
"replicas_desired_before", desiredReplicasBefore, "replicas_desired_before", desiredReplicasBefore,
"replicas_desired", desiredReplicas, "replicas_desired", desiredReplicas,
"num_runners", numRunners, "num_runners", numRunners,
@@ -391,8 +363,5 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByPercentageRunn
"repository", repository, "repository", repository,
) )
rd.Status.Replicas = &desiredReplicas return &desiredReplicas, nil
replicas := desiredReplicas
return &replicas, nil
} }

View File

@@ -224,7 +224,7 @@ func TestDetermineDesiredReplicas_RepositoryRunner(t *testing.T) {
}, },
} }
got, err := h.computeReplicas(rd, hra) got, _, _, err := h.computeReplicasWithCache(log, metav1Now.Time, rd, hra)
if err != nil { if err != nil {
if tc.err == "" { if tc.err == "" {
t.Fatalf("unexpected error: expected none, got %v", err) t.Fatalf("unexpected error: expected none, got %v", err)
@@ -234,12 +234,8 @@ func TestDetermineDesiredReplicas_RepositoryRunner(t *testing.T) {
return return
} }
if got == nil { if got != tc.want {
t.Fatalf("unexpected value of rs.Spec.Replicas: nil") t.Errorf("%d: incorrect desired replicas: want %d, got %d", i, tc.want, got)
}
if *got != tc.want {
t.Errorf("%d: incorrect desired replicas: want %d, got %d", i, tc.want, *got)
} }
}) })
} }
@@ -424,6 +420,8 @@ func TestDetermineDesiredReplicas_OrganizationalRunner(t *testing.T) {
_ = v1alpha1.AddToScheme(scheme) _ = v1alpha1.AddToScheme(scheme)
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
t.Helper()
server := fake.NewServer( server := fake.NewServer(
fake.WithListRepositoryWorkflowRunsResponse(200, tc.workflowRuns, tc.workflowRuns_queued, tc.workflowRuns_in_progress), fake.WithListRepositoryWorkflowRunsResponse(200, tc.workflowRuns, tc.workflowRuns_queued, tc.workflowRuns_in_progress),
fake.WithListWorkflowJobsResponse(200, tc.workflowJobs), fake.WithListWorkflowJobsResponse(200, tc.workflowJobs),
@@ -485,7 +483,7 @@ func TestDetermineDesiredReplicas_OrganizationalRunner(t *testing.T) {
}, },
} }
got, err := h.computeReplicas(rd, hra) got, _, _, err := h.computeReplicasWithCache(log, metav1Now.Time, rd, hra)
if err != nil { if err != nil {
if tc.err == "" { if tc.err == "" {
t.Fatalf("unexpected error: expected none, got %v", err) t.Fatalf("unexpected error: expected none, got %v", err)
@@ -495,12 +493,8 @@ func TestDetermineDesiredReplicas_OrganizationalRunner(t *testing.T) {
return return
} }
if got == nil { if got != tc.want {
t.Fatalf("unexpected value of rs.Spec.Replicas: nil, wanted %v", tc.want) t.Errorf("%d: incorrect desired replicas: want %d, got %d", i, tc.want, got)
}
if *got != tc.want {
t.Errorf("%d: incorrect desired replicas: want %d, got %d", i, tc.want, *got)
} }
}) })
} }

View File

@@ -19,6 +19,7 @@ package controllers
import ( import (
"context" "context"
"fmt" "fmt"
corev1 "k8s.io/api/core/v1"
"time" "time"
"github.com/summerwind/actions-runner-controller/github" "github.com/summerwind/actions-runner-controller/github"
@@ -30,10 +31,10 @@ import (
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/summerwind/actions-runner-controller/api/v1alpha1" "github.com/summerwind/actions-runner-controller/api/v1alpha1"
"github.com/summerwind/actions-runner-controller/controllers/metrics"
) )
const ( const (
@@ -52,6 +53,8 @@ type HorizontalRunnerAutoscalerReconciler struct {
Name string Name string
} }
const defaultReplicas = 1
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnerdeployments,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnerdeployments,verbs=get;list;watch;update;patch
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=horizontalrunnerautoscalers,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=actions.summerwind.dev,resources=horizontalrunnerautoscalers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=horizontalrunnerautoscalers/finalizers,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=actions.summerwind.dev,resources=horizontalrunnerautoscalers/finalizers,verbs=get;list;watch;create;update;patch;delete
@@ -71,6 +74,8 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
metrics.SetHorizontalRunnerAutoscalerSpec(hra.ObjectMeta, hra.Spec)
var rd v1alpha1.RunnerDeployment var rd v1alpha1.RunnerDeployment
if err := r.Get(ctx, types.NamespacedName{ if err := r.Get(ctx, types.NamespacedName{
Namespace: req.Namespace, Namespace: req.Namespace,
@@ -83,41 +88,18 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
var replicas *int
replicasFromCache := r.getDesiredReplicasFromCache(hra)
if replicasFromCache != nil {
replicas = replicasFromCache
} else {
var err error
replicas, err = r.computeReplicas(rd, hra)
if err != nil {
r.Recorder.Event(&hra, corev1.EventTypeNormal, "RunnerAutoscalingFailure", err.Error())
log.Error(err, "Could not compute replicas")
return ctrl.Result{}, err
}
}
const defaultReplicas = 1
currentDesiredReplicas := getIntOrDefault(rd.Spec.Replicas, defaultReplicas)
newDesiredReplicas := getIntOrDefault(replicas, defaultReplicas)
now := time.Now() now := time.Now()
for _, reservation := range hra.Spec.CapacityReservations { newDesiredReplicas, computedReplicas, computedReplicasFromCache, err := r.computeReplicasWithCache(log, now, rd, hra)
if reservation.ExpirationTime.Time.After(now) { if err != nil {
newDesiredReplicas += reservation.Replicas r.Recorder.Event(&hra, corev1.EventTypeNormal, "RunnerAutoscalingFailure", err.Error())
}
log.Error(err, "Could not compute replicas")
return ctrl.Result{}, err
} }
if hra.Spec.MaxReplicas != nil && *hra.Spec.MaxReplicas < newDesiredReplicas { currentDesiredReplicas := getIntOrDefault(rd.Spec.Replicas, defaultReplicas)
newDesiredReplicas = *hra.Spec.MaxReplicas
}
// Please add more conditions that we can in-place update the newest runnerreplicaset without disruption // Please add more conditions that we can in-place update the newest runnerreplicaset without disruption
if currentDesiredReplicas != newDesiredReplicas { if currentDesiredReplicas != newDesiredReplicas {
@@ -143,7 +125,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
updated.Status.DesiredReplicas = &newDesiredReplicas updated.Status.DesiredReplicas = &newDesiredReplicas
} }
if replicasFromCache == nil { if computedReplicasFromCache == nil {
if updated == nil { if updated == nil {
updated = hra.DeepCopy() updated = hra.DeepCopy()
} }
@@ -160,12 +142,14 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
updated.Status.CacheEntries = append(cacheEntries, v1alpha1.CacheEntry{ updated.Status.CacheEntries = append(cacheEntries, v1alpha1.CacheEntry{
Key: v1alpha1.CacheEntryKeyDesiredReplicas, Key: v1alpha1.CacheEntryKeyDesiredReplicas,
Value: *replicas, Value: computedReplicas,
ExpirationTime: metav1.Time{Time: time.Now().Add(cacheDuration)}, ExpirationTime: metav1.Time{Time: time.Now().Add(cacheDuration)},
}) })
} }
if updated != nil { if updated != nil {
metrics.SetHorizontalRunnerAutoscalerStatus(updated.ObjectMeta, updated.Status)
if err := r.Status().Patch(ctx, updated, client.MergeFrom(&hra)); err != nil { if err := r.Status().Patch(ctx, updated, client.MergeFrom(&hra)); err != nil {
return ctrl.Result{}, fmt.Errorf("patching horizontalrunnerautoscaler status to add cache entry: %w", err) return ctrl.Result{}, fmt.Errorf("patching horizontalrunnerautoscaler status to add cache entry: %w", err)
} }
@@ -200,14 +184,59 @@ func (r *HorizontalRunnerAutoscalerReconciler) SetupWithManager(mgr ctrl.Manager
Complete(r) Complete(r)
} }
func (r *HorizontalRunnerAutoscalerReconciler) computeReplicas(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) { func (r *HorizontalRunnerAutoscalerReconciler) computeReplicasWithCache(log logr.Logger, now time.Time, rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (int, int, *int, error) {
var computedReplicas *int minReplicas := defaultReplicas
if hra.Spec.MinReplicas != nil && *hra.Spec.MinReplicas > 0 {
replicas, err := r.determineDesiredReplicas(rd, hra) minReplicas = *hra.Spec.MinReplicas
if err != nil {
return nil, err
} }
var suggestedReplicas int
suggestedReplicasFromCache := r.fetchSuggestedReplicasFromCache(hra)
var cached *int
if suggestedReplicasFromCache != nil {
cached = suggestedReplicasFromCache
if cached == nil {
suggestedReplicas = minReplicas
} else {
suggestedReplicas = *cached
}
} else {
v, err := r.suggestDesiredReplicas(rd, hra)
if err != nil {
return 0, 0, nil, err
}
if v == nil {
suggestedReplicas = minReplicas
} else {
suggestedReplicas = *v
}
}
var reserved int
for _, reservation := range hra.Spec.CapacityReservations {
if reservation.ExpirationTime.Time.After(now) {
reserved += reservation.Replicas
}
}
newDesiredReplicas := suggestedReplicas + reserved
if newDesiredReplicas < minReplicas {
newDesiredReplicas = minReplicas
} else if hra.Spec.MaxReplicas != nil && newDesiredReplicas > *hra.Spec.MaxReplicas {
newDesiredReplicas = *hra.Spec.MaxReplicas
}
//
// Delay scaling-down for ScaleDownDelaySecondsAfterScaleUp or DefaultScaleDownDelay
//
var scaleDownDelay time.Duration var scaleDownDelay time.Duration
if hra.Spec.ScaleDownDelaySecondsAfterScaleUp != nil { if hra.Spec.ScaleDownDelaySecondsAfterScaleUp != nil {
@@ -216,17 +245,50 @@ func (r *HorizontalRunnerAutoscalerReconciler) computeReplicas(rd v1alpha1.Runne
scaleDownDelay = DefaultScaleDownDelay scaleDownDelay = DefaultScaleDownDelay
} }
now := time.Now() var scaleDownDelayUntil *time.Time
if hra.Status.DesiredReplicas == nil || if hra.Status.DesiredReplicas == nil ||
*hra.Status.DesiredReplicas < *replicas || *hra.Status.DesiredReplicas < newDesiredReplicas ||
hra.Status.LastSuccessfulScaleOutTime == nil || hra.Status.LastSuccessfulScaleOutTime == nil {
hra.Status.LastSuccessfulScaleOutTime.Add(scaleDownDelay).Before(now) {
computedReplicas = replicas } else if hra.Status.LastSuccessfulScaleOutTime != nil {
t := hra.Status.LastSuccessfulScaleOutTime.Add(scaleDownDelay)
// ScaleDownDelay is not passed
if t.After(now) {
scaleDownDelayUntil = &t
newDesiredReplicas = *hra.Status.DesiredReplicas
}
} else { } else {
computedReplicas = hra.Status.DesiredReplicas newDesiredReplicas = *hra.Status.DesiredReplicas
} }
return computedReplicas, nil //
// Logs various numbers for monitoring and debugging purpose
//
kvs := []interface{}{
"suggested", suggestedReplicas,
"reserved", reserved,
"min", minReplicas,
}
if cached != nil {
kvs = append(kvs, "cached", *cached)
}
if scaleDownDelayUntil != nil {
kvs = append(kvs, "last_scale_up_time", *hra.Status.LastSuccessfulScaleOutTime)
kvs = append(kvs, "scale_down_delay_until", scaleDownDelayUntil)
}
if maxReplicas := hra.Spec.MaxReplicas; maxReplicas != nil {
kvs = append(kvs, "max", *maxReplicas)
}
log.V(1).Info(fmt.Sprintf("Calculated desired replicas of %d", newDesiredReplicas),
kvs...,
)
return newDesiredReplicas, suggestedReplicas, suggestedReplicasFromCache, nil
} }

View File

@@ -71,7 +71,9 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
err := k8sClient.Create(ctx, ns) err := k8sClient.Create(ctx, ns)
Expect(err).NotTo(HaveOccurred(), "failed to create test namespace") Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
mgr, err := ctrl.NewManager(cfg, ctrl.Options{}) mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Namespace: ns.Name,
})
Expect(err).NotTo(HaveOccurred(), "failed to create manager") Expect(err).NotTo(HaveOccurred(), "failed to create manager")
responses := &fake.FixedResponses{} responses := &fake.FixedResponses{}
@@ -97,6 +99,21 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
return fmt.Sprintf("%s%s", ns.Name, name) return fmt.Sprintf("%s%s", ns.Name, name)
} }
runnerController := &RunnerReconciler{
Client: mgr.GetClient(),
Scheme: scheme.Scheme,
Log: logf.Log,
Recorder: mgr.GetEventRecorderFor("runnerreplicaset-controller"),
GitHubClient: env.ghClient,
RunnerImage: "example/runner:test",
DockerImage: "example/docker:test",
Name: controllerName("runner"),
RegistrationRecheckInterval: time.Millisecond,
RegistrationRecheckJitter: time.Millisecond,
}
err = runnerController.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup runner controller")
replicasetController := &RunnerReplicaSetReconciler{ replicasetController := &RunnerReplicaSetReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Scheme: scheme.Scheme, Scheme: scheme.Scheme,
@@ -106,7 +123,7 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
Name: controllerName("runnerreplicaset"), Name: controllerName("runnerreplicaset"),
} }
err = replicasetController.SetupWithManager(mgr) err = replicasetController.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller") Expect(err).NotTo(HaveOccurred(), "failed to setup runnerreplicaset controller")
deploymentsController := &RunnerDeploymentReconciler{ deploymentsController := &RunnerDeploymentReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
@@ -116,7 +133,7 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
Name: controllerName("runnnerdeployment"), Name: controllerName("runnnerdeployment"),
} }
err = deploymentsController.SetupWithManager(mgr) err = deploymentsController.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller") Expect(err).NotTo(HaveOccurred(), "failed to setup runnerdeployment controller")
autoscalerController := &HorizontalRunnerAutoscalerReconciler{ autoscalerController := &HorizontalRunnerAutoscalerReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
@@ -128,7 +145,7 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
Name: controllerName("horizontalrunnerautoscaler"), Name: controllerName("horizontalrunnerautoscaler"),
} }
err = autoscalerController.SetupWithManager(mgr) err = autoscalerController.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller") Expect(err).NotTo(HaveOccurred(), "failed to setup autoscaler controller")
autoscalerWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{ autoscalerWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{
Client: mgr.GetClient(), Client: mgr.GetClient(),
@@ -475,10 +492,9 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() {
ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1)
ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3) ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3)
}
{
env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners")
env.SyncRunnerRegistrations()
ExpectRunnerCountEventuallyEquals(ctx, ns.Name, 3)
} }
// Scale-up to 4 replicas on first check_run create webhook event // Scale-up to 4 replicas on first check_run create webhook event
@@ -486,19 +502,19 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() {
env.SendOrgCheckRunEvent("test", "valid", "pending", "created") env.SendOrgCheckRunEvent("test", "valid", "pending", "created")
ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook")
ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 4, "runners after first webhook event") ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 4, "runners after first webhook event")
}
{
env.ExpectRegisteredNumberCountEventuallyEquals(4, "count of fake list runners") env.ExpectRegisteredNumberCountEventuallyEquals(4, "count of fake list runners")
env.SyncRunnerRegistrations()
ExpectRunnerCountEventuallyEquals(ctx, ns.Name, 4)
} }
// Scale-up to 5 replicas on second check_run create webhook event // Scale-up to 5 replicas on second check_run create webhook event
{ {
env.SendOrgCheckRunEvent("test", "valid", "pending", "created") env.SendOrgCheckRunEvent("test", "valid", "pending", "created")
ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 5, "runners after second webhook event") ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 5, "runners after second webhook event")
env.ExpectRegisteredNumberCountEventuallyEquals(5, "count of fake list runners")
env.SyncRunnerRegistrations()
ExpectRunnerCountEventuallyEquals(ctx, ns.Name, 5)
} }
env.ExpectRegisteredNumberCountEventuallyEquals(5, "count of fake list runners")
}) })
It("should create and scale organization's repository runners only on check_run event", func() { It("should create and scale organization's repository runners only on check_run event", func() {
@@ -1228,6 +1244,44 @@ func ExpectRunnerSetsCountEventuallyEquals(ctx context.Context, ns string, count
time.Second*10, time.Millisecond*500).Should(BeEquivalentTo(count), optionalDescription...) time.Second*10, time.Millisecond*500).Should(BeEquivalentTo(count), optionalDescription...)
} }
func ExpectRunnerCountEventuallyEquals(ctx context.Context, ns string, count int, optionalDescription ...interface{}) {
runners := actionsv1alpha1.RunnerList{Items: []actionsv1alpha1.Runner{}}
EventuallyWithOffset(
1,
func() int {
err := k8sClient.List(ctx, &runners, client.InNamespace(ns))
if err != nil {
logf.Log.Error(err, "list runner sets")
}
var running int
for _, r := range runners.Items {
if r.Status.Phase == string(corev1.PodRunning) {
running++
} else {
var pod corev1.Pod
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: r.Name}, &pod); err != nil {
logf.Log.Error(err, "simulating pod controller")
continue
}
copy := pod.DeepCopy()
copy.Status.Phase = corev1.PodRunning
if err := k8sClient.Status().Patch(ctx, copy, client.MergeFrom(&pod)); err != nil {
logf.Log.Error(err, "simulating pod controller")
continue
}
}
}
return running
},
time.Second*10, time.Millisecond*500).Should(BeEquivalentTo(count), optionalDescription...)
}
func ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx context.Context, ns string, count int, optionalDescription ...interface{}) { func ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx context.Context, ns string, count int, optionalDescription ...interface{}) {
runnerSets := actionsv1alpha1.RunnerReplicaSetList{Items: []actionsv1alpha1.RunnerReplicaSet{}} runnerSets := actionsv1alpha1.RunnerReplicaSetList{Items: []actionsv1alpha1.RunnerReplicaSet{}}

View File

@@ -0,0 +1,67 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
hraName = "horizontalrunnerautoscaler"
hraNamespace = "namespace"
)
var (
horizontalRunnerAutoscalerMetrics = []prometheus.Collector{
horizontalRunnerAutoscalerMinReplicas,
horizontalRunnerAutoscalerMaxReplicas,
horizontalRunnerAutoscalerDesiredReplicas,
}
)
var (
horizontalRunnerAutoscalerMinReplicas = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "horizontalrunnerautoscaler_spec_min_replicas",
Help: "minReplicas of HorizontalRunnerAutoscaler",
},
[]string{hraName, hraNamespace},
)
horizontalRunnerAutoscalerMaxReplicas = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "horizontalrunnerautoscaler_spec_max_replicas",
Help: "maxReplicas of HorizontalRunnerAutoscaler",
},
[]string{hraName, hraNamespace},
)
horizontalRunnerAutoscalerDesiredReplicas = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "horizontalrunnerautoscaler_status_desired_replicas",
Help: "desiredReplicas of HorizontalRunnerAutoscaler",
},
[]string{hraName, hraNamespace},
)
)
func SetHorizontalRunnerAutoscalerSpec(o metav1.ObjectMeta, spec v1alpha1.HorizontalRunnerAutoscalerSpec) {
labels := prometheus.Labels{
hraName: o.Name,
hraNamespace: o.Namespace,
}
if spec.MaxReplicas != nil {
horizontalRunnerAutoscalerMaxReplicas.With(labels).Set(float64(*spec.MaxReplicas))
}
if spec.MinReplicas != nil {
horizontalRunnerAutoscalerMinReplicas.With(labels).Set(float64(*spec.MinReplicas))
}
}
func SetHorizontalRunnerAutoscalerStatus(o metav1.ObjectMeta, status v1alpha1.HorizontalRunnerAutoscalerStatus) {
labels := prometheus.Labels{
hraName: o.Name,
hraNamespace: o.Namespace,
}
if status.DesiredReplicas != nil {
horizontalRunnerAutoscalerDesiredReplicas.With(labels).Set(float64(*status.DesiredReplicas))
}
}

View File

@@ -0,0 +1,14 @@
// Package metrics provides the metrics of custom resources such as HRA.
//
// This depends on the metrics exporter of kubebuilder.
// See https://book.kubebuilder.io/reference/metrics.html for details.
package metrics
import (
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
func init() {
metrics.Registry.MustRegister(runnerDeploymentMetrics...)
metrics.Registry.MustRegister(horizontalRunnerAutoscalerMetrics...)
}

View File

@@ -0,0 +1,37 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
)
const (
rdName = "runnerdeployment"
rdNamespace = "namespace"
)
var (
runnerDeploymentMetrics = []prometheus.Collector{
runnerDeploymentReplicas,
}
)
var (
runnerDeploymentReplicas = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "runnerdeployment_spec_replicas",
Help: "replicas of RunnerDeployment",
},
[]string{rdName, rdNamespace},
)
)
func SetRunnerDeployment(rd v1alpha1.RunnerDeployment) {
labels := prometheus.Labels{
rdName: rd.Name,
rdNamespace: rd.Namespace,
}
if rd.Spec.Replicas != nil {
runnerDeploymentReplicas.With(labels).Set(float64(*rd.Spec.Replicas))
}
}

View File

@@ -20,11 +20,12 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"time"
gogithub "github.com/google/go-github/v33/github" gogithub "github.com/google/go-github/v33/github"
"github.com/summerwind/actions-runner-controller/hash" "github.com/summerwind/actions-runner-controller/hash"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"strings"
"time"
"github.com/go-logr/logr" "github.com/go-logr/logr"
kerrors "k8s.io/apimachinery/pkg/api/errors" kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -52,12 +53,15 @@ const (
// RunnerReconciler reconciles a Runner object // RunnerReconciler reconciles a Runner object
type RunnerReconciler struct { type RunnerReconciler struct {
client.Client client.Client
Log logr.Logger Log logr.Logger
Recorder record.EventRecorder Recorder record.EventRecorder
Scheme *runtime.Scheme Scheme *runtime.Scheme
GitHubClient *github.Client GitHubClient *github.Client
RunnerImage string RunnerImage string
DockerImage string DockerImage string
Name string
RegistrationRecheckInterval time.Duration
RegistrationRecheckJitter time.Duration
} }
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners,verbs=get;list;watch;create;update;patch;delete
@@ -164,9 +168,11 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Gracefully handle pod-already-exists errors due to informer cache delay. // Gracefully handle pod-already-exists errors due to informer cache delay.
// Without this we got a few errors like the below on new runner pod: // Without this we got a few errors like the below on new runner pod:
// 2021-03-16T00:23:10.116Z ERROR controller-runtime.controller Reconciler error {"controller": "runner-controller", "request": "default/example-runnerdeploy-b2g2g-j4mcp", "error": "pods \"example-runnerdeploy-b2g2g-j4mcp\" already exists"} // 2021-03-16T00:23:10.116Z ERROR controller-runtime.controller Reconciler error {"controller": "runner-controller", "request": "default/example-runnerdeploy-b2g2g-j4mcp", "error": "pods \"example-runnerdeploy-b2g2g-j4mcp\" already exists"}
log.Info("Runner pod already exists. Probably this pod has been already created in previous reconcilation but the new pod is not yet cached.") log.Info(
"Failed to create pod due to AlreadyExists error. Probably this pod has been already created in previous reconcilation but is still not in the informer cache. Will retry on pod created. If it doesn't repeat, there's no problem",
)
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil return ctrl.Result{}, nil
} }
log.Error(err, "Failed to create pod resource") log.Error(err, "Failed to create pod resource")
@@ -184,7 +190,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if deletionDidTimeout { if deletionDidTimeout {
log.Info( log.Info(
"Pod failed to delete itself in a timely manner. "+ fmt.Sprintf("Failed to delete pod within %s. ", deletionTimeout)+
"This is typically the case when a Kubernetes node became unreachable "+ "This is typically the case when a Kubernetes node became unreachable "+
"and the kube controller started evicting nodes. Forcefully deleting the pod to not get stuck.", "and the kube controller started evicting nodes. Forcefully deleting the pod to not get stuck.",
"podDeletionTimestamp", pod.DeletionTimestamp, "podDeletionTimestamp", pod.DeletionTimestamp,
@@ -248,6 +254,9 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// saving API calls and scary{ log messages // saving API calls and scary{ log messages
if !restart { if !restart {
registrationCheckInterval := time.Minute registrationCheckInterval := time.Minute
if r.RegistrationRecheckInterval > 0 {
registrationCheckInterval = r.RegistrationRecheckInterval
}
// We want to call ListRunners GitHub Actions API only once per runner per minute. // We want to call ListRunners GitHub Actions API only once per runner per minute.
// This if block, in conjunction with: // This if block, in conjunction with:
@@ -255,15 +264,28 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// achieves that. // achieves that.
if lastCheckTime := runner.Status.LastRegistrationCheckTime; lastCheckTime != nil { if lastCheckTime := runner.Status.LastRegistrationCheckTime; lastCheckTime != nil {
nextCheckTime := lastCheckTime.Add(registrationCheckInterval) nextCheckTime := lastCheckTime.Add(registrationCheckInterval)
if nextCheckTime.After(time.Now()) { now := time.Now()
// Requeue scheduled by RequeueAfter can happen a bit earlier (like dozens of milliseconds)
// so to avoid excessive, in-effective retry, we heuristically ignore the remaining delay in case it is
// shorter than 1s
requeueAfter := nextCheckTime.Sub(now) - time.Second
if requeueAfter > 0 {
log.Info( log.Info(
fmt.Sprintf("Skipping registration check because it's deferred until %s", nextCheckTime), fmt.Sprintf("Skipped registration check because it's deferred until %s. Retrying in %s at latest", nextCheckTime, requeueAfter),
"lastRegistrationCheckTime", lastCheckTime,
"registrationCheckInterval", registrationCheckInterval,
) )
// Note that we don't need to explicitly requeue on this reconcilation because // Without RequeueAfter, the controller may not retry on scheduled. Instead, it must wait until the
// the requeue should have been already scheduled previsouly // next sync period passes, which can be too much later than nextCheckTime.
// (with `return ctrl.Result{RequeueAfter: registrationRecheckDelay}, nil` as noted above and coded below) //
return ctrl.Result{}, nil // We need to requeue on this reconcilation even though we have already scheduled the initial
// requeue previously with `return ctrl.Result{RequeueAfter: registrationRecheckDelay}, nil`.
// Apparently, the workqueue used by controller-runtime seems to deduplicate and resets the delay on
// other requeues- so the initial scheduled requeue may have been reset due to requeue on
// spec/status change.
return ctrl.Result{RequeueAfter: requeueAfter}, nil
} }
} }
@@ -354,7 +376,12 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
} }
if (notFound || offline) && !registrationDidTimeout { if (notFound || offline) && !registrationDidTimeout {
registrationRecheckDelay = registrationCheckInterval + wait.Jitter(10*time.Second, 0.1) registrationRecheckJitter := 10 * time.Second
if r.RegistrationRecheckJitter > 0 {
registrationRecheckJitter = r.RegistrationRecheckJitter
}
registrationRecheckDelay = registrationCheckInterval + wait.Jitter(registrationRecheckJitter, 0.1)
} }
} }
@@ -369,7 +396,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
updated.Status.LastRegistrationCheckTime = &metav1.Time{Time: time.Now()} updated.Status.LastRegistrationCheckTime = &metav1.Time{Time: time.Now()}
if err := r.Status().Patch(ctx, updated, client.MergeFrom(&runner)); err != nil { if err := r.Status().Patch(ctx, updated, client.MergeFrom(&runner)); err != nil {
log.Error(err, "Failed to update runner status") log.Error(err, "Failed to update runner status for LastRegistrationCheckTime")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
@@ -391,7 +418,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
updated.Status.Message = pod.Status.Message updated.Status.Message = pod.Status.Message
if err := r.Status().Patch(ctx, updated, client.MergeFrom(&runner)); err != nil { if err := r.Status().Patch(ctx, updated, client.MergeFrom(&runner)); err != nil {
log.Error(err, "Failed to update runner status") log.Error(err, "Failed to update runner status for Phase/Reason/Message")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
} }
@@ -463,8 +490,8 @@ func (r *RunnerReconciler) updateRegistrationToken(ctx context.Context, runner v
ExpiresAt: metav1.NewTime(rt.GetExpiresAt().Time), ExpiresAt: metav1.NewTime(rt.GetExpiresAt().Time),
} }
if err := r.Status().Update(ctx, updated); err != nil { if err := r.Status().Patch(ctx, updated, client.MergeFrom(&runner)); err != nil {
log.Error(err, "Failed to update runner status") log.Error(err, "Failed to update runner status for Registration")
return false, err return false, err
} }
@@ -608,45 +635,63 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
}...) }...)
} }
if !dockerdInRunner && dockerEnabled { //
runnerVolumeName := "runner" // /runner must be generated on runtime from /runnertmp embedded in the container image.
runnerVolumeMountPath := "/runner" //
// When you're NOT using dindWithinRunner=true,
// it must also be shared with the dind container as it seems like required to run docker steps.
//
pod.Spec.Volumes = []corev1.Volume{ runnerVolumeName := "runner"
{ runnerVolumeMountPath := "/runner"
runnerVolumeEmptyDir := &corev1.EmptyDirVolumeSource{}
if runner.Spec.VolumeSizeLimit != nil {
runnerVolumeEmptyDir.SizeLimit = runner.Spec.VolumeSizeLimit
}
pod.Spec.Volumes = append(pod.Spec.Volumes,
corev1.Volume{
Name: runnerVolumeName,
VolumeSource: corev1.VolumeSource{
EmptyDir: runnerVolumeEmptyDir,
},
},
)
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts,
corev1.VolumeMount{
Name: runnerVolumeName,
MountPath: runnerVolumeMountPath,
},
)
if !dockerdInRunner && dockerEnabled {
pod.Spec.Volumes = append(pod.Spec.Volumes,
corev1.Volume{
Name: "work", Name: "work",
VolumeSource: corev1.VolumeSource{ VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{}, EmptyDir: &corev1.EmptyDirVolumeSource{},
}, },
}, },
{ corev1.Volume{
Name: runnerVolumeName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
{
Name: "certs-client", Name: "certs-client",
VolumeSource: corev1.VolumeSource{ VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{}, EmptyDir: &corev1.EmptyDirVolumeSource{},
}, },
}, },
} )
pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{ pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts,
{ corev1.VolumeMount{
Name: "work", Name: "work",
MountPath: workDir, MountPath: workDir,
}, },
{ corev1.VolumeMount{
Name: runnerVolumeName,
MountPath: runnerVolumeMountPath,
},
{
Name: "certs-client", Name: "certs-client",
MountPath: "/certs/client", MountPath: "/certs/client",
ReadOnly: true, ReadOnly: true,
}, },
} )
pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []corev1.EnvVar{ pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []corev1.EnvVar{
{ {
Name: "DOCKER_HOST", Name: "DOCKER_HOST",
@@ -661,23 +706,31 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
Value: "/certs/client", Value: "/certs/client",
}, },
}...) }...)
pod.Spec.Containers = append(pod.Spec.Containers, corev1.Container{
Name: "docker", // Determine the volume mounts assigned to the docker sidecar. In case extra mounts are included in the RunnerSpec, append them to the standard
Image: r.DockerImage, // set of mounts. See https://github.com/summerwind/actions-runner-controller/issues/435 for context.
VolumeMounts: []corev1.VolumeMount{ dockerVolumeMounts := []corev1.VolumeMount{
{ {
Name: "work", Name: "work",
MountPath: workDir, MountPath: workDir,
},
{
Name: runnerVolumeName,
MountPath: runnerVolumeMountPath,
},
{
Name: "certs-client",
MountPath: "/certs/client",
},
}, },
{
Name: runnerVolumeName,
MountPath: runnerVolumeMountPath,
},
{
Name: "certs-client",
MountPath: "/certs/client",
},
}
if extraDockerVolumeMounts := runner.Spec.DockerVolumeMounts; extraDockerVolumeMounts != nil {
dockerVolumeMounts = append(dockerVolumeMounts, extraDockerVolumeMounts...)
}
pod.Spec.Containers = append(pod.Spec.Containers, corev1.Container{
Name: "docker",
Image: r.DockerImage,
VolumeMounts: dockerVolumeMounts,
Env: []corev1.EnvVar{ Env: []corev1.EnvVar{
{ {
Name: "DOCKER_TLS_CERTDIR", Name: "DOCKER_TLS_CERTDIR",
@@ -692,11 +745,17 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
if mtu := runner.Spec.DockerMTU; mtu != nil { if mtu := runner.Spec.DockerMTU; mtu != nil {
pod.Spec.Containers[1].Env = append(pod.Spec.Containers[1].Env, []corev1.EnvVar{ pod.Spec.Containers[1].Env = append(pod.Spec.Containers[1].Env, []corev1.EnvVar{
// See https://docs.docker.com/engine/security/rootless/
{ {
Name: "DOCKERD_ROOTLESS_ROOTLESSKIT_MTU", Name: "DOCKERD_ROOTLESS_ROOTLESSKIT_MTU",
Value: fmt.Sprintf("%d", *runner.Spec.DockerMTU), Value: fmt.Sprintf("%d", *runner.Spec.DockerMTU),
}, },
}...) }...)
pod.Spec.Containers[1].Args = append(pod.Spec.Containers[1].Args,
"--mtu",
fmt.Sprintf("%d", *runner.Spec.DockerMTU),
)
} }
} }
@@ -759,6 +818,10 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
pod.Spec.TerminationGracePeriodSeconds = runner.Spec.TerminationGracePeriodSeconds pod.Spec.TerminationGracePeriodSeconds = runner.Spec.TerminationGracePeriodSeconds
} }
if len(runner.Spec.HostAliases) != 0 {
pod.Spec.HostAliases = runner.Spec.HostAliases
}
if err := ctrl.SetControllerReference(&runner, &pod, r.Scheme); err != nil { if err := ctrl.SetControllerReference(&runner, &pod, r.Scheme); err != nil {
return pod, err return pod, err
} }
@@ -768,6 +831,9 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
func (r *RunnerReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *RunnerReconciler) SetupWithManager(mgr ctrl.Manager) error {
name := "runner-controller" name := "runner-controller"
if r.Name != "" {
name = r.Name
}
r.Recorder = mgr.GetEventRecorderFor(name) r.Recorder = mgr.GetEventRecorderFor(name)

View File

@@ -38,6 +38,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/summerwind/actions-runner-controller/api/v1alpha1" "github.com/summerwind/actions-runner-controller/api/v1alpha1"
"github.com/summerwind/actions-runner-controller/controllers/metrics"
) )
const ( const (
@@ -77,6 +78,8 @@ func (r *RunnerDeploymentReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
metrics.SetRunnerDeployment(rd)
var myRunnerReplicaSetList v1alpha1.RunnerReplicaSetList var myRunnerReplicaSetList v1alpha1.RunnerReplicaSetList
if err := r.List(ctx, &myRunnerReplicaSetList, client.InNamespace(req.Namespace), client.MatchingFields{runnerSetOwnerKey: req.Name}); err != nil { if err := r.List(ctx, &myRunnerReplicaSetList, client.InNamespace(req.Namespace), client.MatchingFields{runnerSetOwnerKey: req.Name}); err != nil {
return ctrl.Result{}, err return ctrl.Result{}, err
@@ -189,17 +192,28 @@ func (r *RunnerDeploymentReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
if len(oldSets) > 0 { if len(oldSets) > 0 {
readyReplicas := newestSet.Status.ReadyReplicas readyReplicas := newestSet.Status.ReadyReplicas
if readyReplicas < currentDesiredReplicas { oldSetsCount := len(oldSets)
log.WithValues("runnerreplicaset", types.NamespacedName{
logWithDebugInfo := log.WithValues(
"newest_runnerreplicaset", types.NamespacedName{
Namespace: newestSet.Namespace, Namespace: newestSet.Namespace,
Name: newestSet.Name, Name: newestSet.Name,
}). },
Info("Waiting until the newest runner replica set to be 100% available", "newest_runnerreplicaset_replicas_ready", readyReplicas,
"ready", readyReplicas, "newest_runnerreplicaset_replicas_desired", currentDesiredReplicas,
"desired", currentDesiredReplicas, "old_runnerreplicasets_count", oldSetsCount,
) )
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil if readyReplicas < currentDesiredReplicas {
logWithDebugInfo.
Info("Waiting until the newest runnerreplicaset to be 100% available")
return ctrl.Result{}, nil
}
if oldSetsCount > 0 {
logWithDebugInfo.
Info("The newest runnerreplicaset is 100% available. Deleting old runnerreplicasets")
} }
for i := range oldSets { for i := range oldSets {

View File

@@ -139,7 +139,9 @@ func SetupDeploymentTest(ctx context.Context) *corev1.Namespace {
err := k8sClient.Create(ctx, ns) err := k8sClient.Create(ctx, ns)
Expect(err).NotTo(HaveOccurred(), "failed to create test namespace") Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
mgr, err := ctrl.NewManager(cfg, ctrl.Options{}) mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Namespace: ns.Name,
})
Expect(err).NotTo(HaveOccurred(), "failed to create manager") Expect(err).NotTo(HaveOccurred(), "failed to create manager")
controller := &RunnerDeploymentReconciler{ controller := &RunnerDeploymentReconciler{
@@ -199,7 +201,7 @@ var _ = Context("Inside of a new namespace", func() {
}, },
}, },
Spec: actionsv1alpha1.RunnerSpec{ Spec: actionsv1alpha1.RunnerSpec{
Repository: "foo/bar", Repository: "test/valid",
Image: "bar", Image: "bar",
Env: []corev1.EnvVar{ Env: []corev1.EnvVar{
{Name: "FOO", Value: "FOOVALUE"}, {Name: "FOO", Value: "FOOVALUE"},
@@ -295,7 +297,7 @@ var _ = Context("Inside of a new namespace", func() {
Replicas: intPtr(1), Replicas: intPtr(1),
Template: actionsv1alpha1.RunnerTemplate{ Template: actionsv1alpha1.RunnerTemplate{
Spec: actionsv1alpha1.RunnerSpec{ Spec: actionsv1alpha1.RunnerSpec{
Repository: "foo/bar", Repository: "test/valid",
Image: "bar", Image: "bar",
Env: []corev1.EnvVar{ Env: []corev1.EnvVar{
{Name: "FOO", Value: "FOOVALUE"}, {Name: "FOO", Value: "FOOVALUE"},
@@ -391,7 +393,7 @@ var _ = Context("Inside of a new namespace", func() {
Replicas: intPtr(1), Replicas: intPtr(1),
Template: actionsv1alpha1.RunnerTemplate{ Template: actionsv1alpha1.RunnerTemplate{
Spec: actionsv1alpha1.RunnerSpec{ Spec: actionsv1alpha1.RunnerSpec{
Repository: "foo/bar", Repository: "test/valid",
Image: "bar", Image: "bar",
Env: []corev1.EnvVar{ Env: []corev1.EnvVar{
{Name: "FOO", Value: "FOOVALUE"}, {Name: "FOO", Value: "FOOVALUE"},

View File

@@ -114,13 +114,14 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
desired = 1 desired = 1
} }
log.V(0).Info("debug", "desired", desired, "available", available)
if available > desired { if available > desired {
n := available - desired n := available - desired
// get runners that are currently not busy log.V(0).Info(fmt.Sprintf("Deleting %d runners", n), "desired", desired, "available", available, "ready", ready)
var notBusy []v1alpha1.Runner
// get runners that are currently offline/not busy/timed-out to register
var deletionCandidates []v1alpha1.Runner
for _, runner := range allRunners.Items { for _, runner := range allRunners.Items {
busy, err := r.GitHubClient.IsRunnerBusy(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name) busy, err := r.GitHubClient.IsRunnerBusy(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
@@ -168,35 +169,37 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
"configuredRegistrationTimeout", registrationTimeout, "configuredRegistrationTimeout", registrationTimeout,
) )
notBusy = append(notBusy, runner) deletionCandidates = append(deletionCandidates, runner)
} }
// offline runners should always be a great target for scale down // offline runners should always be a great target for scale down
if offline { if offline {
notBusy = append(notBusy, runner) deletionCandidates = append(deletionCandidates, runner)
} }
} else if !busy { } else if !busy {
notBusy = append(notBusy, runner) deletionCandidates = append(deletionCandidates, runner)
} }
} }
if len(notBusy) < n { if len(deletionCandidates) < n {
n = len(notBusy) n = len(deletionCandidates)
} }
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if err := r.Client.Delete(ctx, &notBusy[i]); client.IgnoreNotFound(err) != nil { if err := r.Client.Delete(ctx, &deletionCandidates[i]); client.IgnoreNotFound(err) != nil {
log.Error(err, "Failed to delete runner resource") log.Error(err, "Failed to delete runner resource")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
r.Recorder.Event(&rs, corev1.EventTypeNormal, "RunnerDeleted", fmt.Sprintf("Deleted runner '%s'", notBusy[i].Name)) r.Recorder.Event(&rs, corev1.EventTypeNormal, "RunnerDeleted", fmt.Sprintf("Deleted runner '%s'", deletionCandidates[i].Name))
log.Info("Deleted runner", "runnerreplicaset", rs.ObjectMeta.Name) log.Info("Deleted runner")
} }
} else if desired > available { } else if desired > available {
n := desired - available n := desired - available
log.V(0).Info(fmt.Sprintf("Creating %d runner(s)", n), "desired", desired, "available", available, "ready", ready)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
newRunner, err := r.newRunner(rs) newRunner, err := r.newRunner(rs)
if err != nil { if err != nil {

View File

@@ -47,7 +47,9 @@ func SetupTest(ctx context.Context) *corev1.Namespace {
err := k8sClient.Create(ctx, ns) err := k8sClient.Create(ctx, ns)
Expect(err).NotTo(HaveOccurred(), "failed to create test namespace") Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
mgr, err := ctrl.NewManager(cfg, ctrl.Options{}) mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Namespace: ns.Name,
})
Expect(err).NotTo(HaveOccurred(), "failed to create manager") Expect(err).NotTo(HaveOccurred(), "failed to create manager")
runnersList = fake.NewRunnersList() runnersList = fake.NewRunnersList()
@@ -127,7 +129,7 @@ var _ = Context("Inside of a new namespace", func() {
}, },
}, },
Spec: actionsv1alpha1.RunnerSpec{ Spec: actionsv1alpha1.RunnerSpec{
Repository: "foo/bar", Repository: "test/valid",
Image: "bar", Image: "bar",
Env: []corev1.EnvVar{ Env: []corev1.EnvVar{
{Name: "FOO", Value: "FOOVALUE"}, {Name: "FOO", Value: "FOOVALUE"},

View File

@@ -55,9 +55,17 @@ func TestAPIs(t *testing.T) {
var _ = BeforeSuite(func(done Done) { var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
var apiServerFlags []string
apiServerFlags = append(apiServerFlags, envtest.DefaultKubeAPIServerFlags...)
// Avoids the following error:
// 2021-03-19T15:14:11.673+0900 ERROR controller-runtime.controller Reconciler error {"controller": "testns-tvjzjrunner", "request": "testns-gdnyx/example-runnerdeploy-zps4z-j5562", "error": "Pod \"example-runnerdeploy-zps4z-j5562\" is invalid: [spec.containers[1].image: Required value, spec.containers[1].securityContext.privileged: Forbidden: disallowed by cluster policy]"}
apiServerFlags = append(apiServerFlags, "--allow-privileged=true")
By("bootstrapping test environment") By("bootstrapping test environment")
testEnv = &envtest.Environment{ testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
KubeAPIServerFlags: apiServerFlags,
} }
var err error var err error

32
main.go
View File

@@ -41,8 +41,8 @@ const (
) )
var ( var (
scheme = runtime.NewScheme() scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup") log = ctrl.Log.WithName("actions-runner-controller")
) )
func init() { func init() {
@@ -109,13 +109,13 @@ func main() {
Namespace: namespace, Namespace: namespace,
}) })
if err != nil { if err != nil {
setupLog.Error(err, "unable to start manager") log.Error(err, "unable to start manager")
os.Exit(1) os.Exit(1)
} }
runnerReconciler := &controllers.RunnerReconciler{ runnerReconciler := &controllers.RunnerReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Runner"), Log: log.WithName("runner"),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
GitHubClient: ghClient, GitHubClient: ghClient,
RunnerImage: runnerImage, RunnerImage: runnerImage,
@@ -123,64 +123,64 @@ func main() {
} }
if err = runnerReconciler.SetupWithManager(mgr); err != nil { if err = runnerReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Runner") log.Error(err, "unable to create controller", "controller", "Runner")
os.Exit(1) os.Exit(1)
} }
runnerSetReconciler := &controllers.RunnerReplicaSetReconciler{ runnerSetReconciler := &controllers.RunnerReplicaSetReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("RunnerReplicaSet"), Log: log.WithName("runnerreplicaset"),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
GitHubClient: ghClient, GitHubClient: ghClient,
} }
if err = runnerSetReconciler.SetupWithManager(mgr); err != nil { if err = runnerSetReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "RunnerReplicaSet") log.Error(err, "unable to create controller", "controller", "RunnerReplicaSet")
os.Exit(1) os.Exit(1)
} }
runnerDeploymentReconciler := &controllers.RunnerDeploymentReconciler{ runnerDeploymentReconciler := &controllers.RunnerDeploymentReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("RunnerDeployment"), Log: log.WithName("runnerdeployment"),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
CommonRunnerLabels: commonRunnerLabels, CommonRunnerLabels: commonRunnerLabels,
} }
if err = runnerDeploymentReconciler.SetupWithManager(mgr); err != nil { if err = runnerDeploymentReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "RunnerDeployment") log.Error(err, "unable to create controller", "controller", "RunnerDeployment")
os.Exit(1) os.Exit(1)
} }
horizontalRunnerAutoscaler := &controllers.HorizontalRunnerAutoscalerReconciler{ horizontalRunnerAutoscaler := &controllers.HorizontalRunnerAutoscalerReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("HorizontalRunnerAutoscaler"), Log: log.WithName("horizontalrunnerautoscaler"),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
GitHubClient: ghClient, GitHubClient: ghClient,
CacheDuration: syncPeriod - 10*time.Second, CacheDuration: syncPeriod - 10*time.Second,
} }
if err = horizontalRunnerAutoscaler.SetupWithManager(mgr); err != nil { if err = horizontalRunnerAutoscaler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "HorizontalRunnerAutoscaler") log.Error(err, "unable to create controller", "controller", "HorizontalRunnerAutoscaler")
os.Exit(1) os.Exit(1)
} }
if err = (&actionsv1alpha1.Runner{}).SetupWebhookWithManager(mgr); err != nil { if err = (&actionsv1alpha1.Runner{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "Runner") log.Error(err, "unable to create webhook", "webhook", "Runner")
os.Exit(1) os.Exit(1)
} }
if err = (&actionsv1alpha1.RunnerDeployment{}).SetupWebhookWithManager(mgr); err != nil { if err = (&actionsv1alpha1.RunnerDeployment{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "RunnerDeployment") log.Error(err, "unable to create webhook", "webhook", "RunnerDeployment")
os.Exit(1) os.Exit(1)
} }
if err = (&actionsv1alpha1.RunnerReplicaSet{}).SetupWebhookWithManager(mgr); err != nil { if err = (&actionsv1alpha1.RunnerReplicaSet{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "RunnerReplicaSet") log.Error(err, "unable to create webhook", "webhook", "RunnerReplicaSet")
os.Exit(1) os.Exit(1)
} }
// +kubebuilder:scaffold:builder // +kubebuilder:scaffold:builder
setupLog.Info("starting manager") log.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager") log.Error(err, "problem running manager")
os.Exit(1) os.Exit(1)
} }
} }

View File

@@ -65,7 +65,7 @@ func Match(pat string, s string) bool {
s = subs[1] s = subs[1]
wildcardInHead = false wildcardInHead = wildcardInTail
} }
r := s == "" r := s == ""

View File

@@ -195,4 +195,20 @@ func TestMatch(t *testing.T) {
Want: false, Want: false,
}) })
}) })
t.Run("actions-*-metrics == actions-workflow-metrics", func(t *testing.T) {
run(t, testcase{
Pattern: "actions-*-metrics",
Target: "actions-workflow-metrics",
Want: true,
})
})
t.Run("!actions-*-metrics == actions-workflow-metrics", func(t *testing.T) {
run(t, testcase{
Pattern: "!actions-*-metrics",
Target: "actions-workflow-metrics",
Want: false,
})
})
} }

View File

@@ -1,4 +1,4 @@
FROM ubuntu:18.04 FROM ubuntu:20.04
ARG TARGETPLATFORM ARG TARGETPLATFORM
ARG RUNNER_VERSION=2.274.2 ARG RUNNER_VERSION=2.274.2
@@ -8,37 +8,37 @@ RUN test -n "$TARGETPLATFORM" || (echo "TARGETPLATFORM must be set" && false)
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt update -y \ RUN apt update -y \
&& apt install -y software-properties-common \ && apt install -y software-properties-common \
&& add-apt-repository -y ppa:git-core/ppa \ && add-apt-repository -y ppa:git-core/ppa \
&& apt update -y \ && apt update -y \
&& apt install -y --no-install-recommends \ && apt install -y --no-install-recommends \
build-essential \ build-essential \
curl \ curl \
ca-certificates \ ca-certificates \
dnsutils \ dnsutils \
ftp \ ftp \
git \ git \
iproute2 \ iproute2 \
iputils-ping \ iputils-ping \
jq \ jq \
libunwind8 \ libunwind8 \
locales \ locales \
netcat \ netcat \
openssh-client \ openssh-client \
parallel \ parallel \
rsync \ rsync \
shellcheck \ shellcheck \
sudo \ sudo \
telnet \ telnet \
time \ time \
tzdata \ tzdata \
unzip \ unzip \
upx \ upx \
wget \ wget \
zip \ zip \
zstd \ zstd \
&& cd /usr/bin && ln -sf python3 python \ python-is-python3 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_${ARCH} \ && curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_${ARCH} \
@@ -46,18 +46,18 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
# Docker download supports arm64 as aarch64 & amd64 as x86_64 # Docker download supports arm64 as aarch64 & amd64 as x86_64
RUN set -vx; \ RUN set -vx; \
export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x86_64 ; fi \ && if [ "$ARCH" = "amd64" ]; then export ARCH=x86_64 ; fi \
&& curl -L -o docker.tgz https://download.docker.com/linux/static/stable/${ARCH}/docker-${DOCKER_VERSION}.tgz \ && curl -L -o docker.tgz https://download.docker.com/linux/static/stable/${ARCH}/docker-${DOCKER_VERSION}.tgz \
&& tar zxvf docker.tgz \ && tar zxvf docker.tgz \
&& install -o root -g root -m 755 docker/docker /usr/local/bin/docker \ && install -o root -g root -m 755 docker/docker /usr/local/bin/docker \
&& rm -rf docker docker.tgz \ && rm -rf docker docker.tgz \
&& adduser --disabled-password --gecos "" --uid 1000 runner \ && adduser --disabled-password --gecos "" --uid 1000 runner \
&& groupadd docker \ && groupadd docker \
&& usermod -aG sudo runner \ && usermod -aG sudo runner \
&& usermod -aG docker runner \ && usermod -aG docker runner \
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers && echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers
ENV RUNNER_ASSETS_DIR=/runnertmp ENV RUNNER_ASSETS_DIR=/runnertmp
@@ -67,21 +67,21 @@ ENV RUNNER_ASSETS_DIR=/runnertmp
# It is installed after installdependencies.sh and before removing /var/lib/apt/lists # It is installed after installdependencies.sh and before removing /var/lib/apt/lists
# to avoid rerunning apt-update on its own. # to avoid rerunning apt-update on its own.
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \ && if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \
&& mkdir -p "$RUNNER_ASSETS_DIR" \ && mkdir -p "$RUNNER_ASSETS_DIR" \
&& cd "$RUNNER_ASSETS_DIR" \ && cd "$RUNNER_ASSETS_DIR" \
&& curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ && curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \
&& tar xzf ./runner.tar.gz \ && tar xzf ./runner.tar.gz \
&& rm runner.tar.gz \ && rm runner.tar.gz \
&& ./bin/installdependencies.sh \ && ./bin/installdependencies.sh \
&& mv ./externals ./externalstmp \ && mv ./externals ./externalstmp \
&& apt-get install -y libyaml-dev \ && apt-get install -y libyaml-dev \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > .env \ RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > .env \
&& mkdir /opt/hostedtoolcache \ && mkdir /opt/hostedtoolcache \
&& chgrp docker /opt/hostedtoolcache \ && chgrp docker /opt/hostedtoolcache \
&& chmod g+rwx /opt/hostedtoolcache && chmod g+rwx /opt/hostedtoolcache
COPY entrypoint.sh / COPY entrypoint.sh /
COPY --chown=runner:docker patched $RUNNER_ASSETS_DIR/patched COPY --chown=runner:docker patched $RUNNER_ASSETS_DIR/patched

View File

@@ -1,11 +1,12 @@
FROM ubuntu:20.04 FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
# Dev + DinD dependencies RUN apt update -y \
RUN apt update \
&& apt install -y software-properties-common \ && apt install -y software-properties-common \
&& add-apt-repository -y ppa:git-core/ppa \ && add-apt-repository -y ppa:git-core/ppa \
&& apt install -y \ && apt update -y \
&& apt install -y --no-install-recommends \
software-properties-common \
build-essential \ build-essential \
curl \ curl \
ca-certificates \ ca-certificates \
@@ -13,7 +14,6 @@ RUN apt update \
ftp \ ftp \
git \ git \
iproute2 \ iproute2 \
iptables \
iputils-ping \ iputils-ping \
jq \ jq \
libunwind8 \ libunwind8 \
@@ -21,11 +21,9 @@ RUN apt update \
netcat \ netcat \
openssh-client \ openssh-client \
parallel \ parallel \
python-is-python3 \
rsync \ rsync \
shellcheck \ shellcheck \
sudo \ sudo \
supervisor \
telnet \ telnet \
time \ time \
tzdata \ tzdata \
@@ -34,6 +32,9 @@ RUN apt update \
wget \ wget \
zip \ zip \
zstd \ zstd \
python-is-python3 \
iptables \
supervisor \
&& rm -rf /var/lib/apt/list/* && rm -rf /var/lib/apt/list/*
# Runner user # Runner user
@@ -79,7 +80,7 @@ ENV RUNNER_ASSETS_DIR=/runnertmp
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \ && if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \
&& mkdir -p "$RUNNER_ASSETS_DIR" \ && mkdir -p "$RUNNER_ASSETS_DIR" \
&& cd "$RUNNER_ASSETS_DIR" \ && cd "$RUNNER_ASSETS_DIR" \
&& curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ && curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \
&& tar xzf ./runner.tar.gz \ && tar xzf ./runner.tar.gz \
&& rm runner.tar.gz \ && rm runner.tar.gz \
@@ -88,9 +89,9 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > /runner.env \ RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > /runner.env \
&& mkdir /opt/hostedtoolcache \ && mkdir /opt/hostedtoolcache \
&& chgrp docker /opt/hostedtoolcache \ && chgrp docker /opt/hostedtoolcache \
&& chmod g+rwx /opt/hostedtoolcache && chmod g+rwx /opt/hostedtoolcache
COPY modprobe startup.sh /usr/local/bin/ COPY modprobe startup.sh /usr/local/bin/
COPY supervisor/ /etc/supervisor/conf.d/ COPY supervisor/ /etc/supervisor/conf.d/

View File

@@ -0,0 +1,91 @@
FROM ubuntu:18.04
ARG TARGETPLATFORM
ARG RUNNER_VERSION=2.274.2
ARG DOCKER_VERSION=19.03.12
RUN test -n "$TARGETPLATFORM" || (echo "TARGETPLATFORM must be set" && false)
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update -y \
&& apt install -y software-properties-common \
&& add-apt-repository -y ppa:git-core/ppa \
&& apt update -y \
&& apt install -y --no-install-recommends \
build-essential \
curl \
ca-certificates \
dnsutils \
ftp \
git \
iproute2 \
iputils-ping \
jq \
libunwind8 \
locales \
netcat \
openssh-client \
parallel \
rsync \
shellcheck \
sudo \
telnet \
time \
tzdata \
unzip \
upx \
wget \
zip \
zstd \
&& cd /usr/bin && ln -sf python3 python \
&& rm -rf /var/lib/apt/lists/*
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_${ARCH} \
&& chmod +x /usr/local/bin/dumb-init
# Docker download supports arm64 as aarch64 & amd64 as x86_64
RUN set -vx; \
export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x86_64 ; fi \
&& curl -L -o docker.tgz https://download.docker.com/linux/static/stable/${ARCH}/docker-${DOCKER_VERSION}.tgz \
&& tar zxvf docker.tgz \
&& install -o root -g root -m 755 docker/docker /usr/local/bin/docker \
&& rm -rf docker docker.tgz \
&& adduser --disabled-password --gecos "" --uid 1000 runner \
&& groupadd docker \
&& usermod -aG sudo runner \
&& usermod -aG docker runner \
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers
ENV RUNNER_ASSETS_DIR=/runnertmp
# Runner download supports amd64 as x64. Externalstmp is needed for making mount points work inside DinD.
#
# libyaml-dev is required for ruby/setup-ruby action.
# It is installed after installdependencies.sh and before removing /var/lib/apt/lists
# to avoid rerunning apt-update on its own.
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \
&& mkdir -p "$RUNNER_ASSETS_DIR" \
&& cd "$RUNNER_ASSETS_DIR" \
&& curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \
&& tar xzf ./runner.tar.gz \
&& rm runner.tar.gz \
&& ./bin/installdependencies.sh \
&& mv ./externals ./externalstmp \
&& apt-get install -y libyaml-dev \
&& rm -rf /var/lib/apt/lists/*
RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > .env \
&& mkdir /opt/hostedtoolcache \
&& chgrp docker /opt/hostedtoolcache \
&& chmod g+rwx /opt/hostedtoolcache
COPY entrypoint.sh /
COPY --chown=runner:docker patched $RUNNER_ASSETS_DIR/patched
USER runner
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD ["/entrypoint.sh"]

View File

@@ -2,7 +2,7 @@ NAME ?= summerwind/actions-runner
DIND_RUNNER_NAME ?= ${NAME}-dind DIND_RUNNER_NAME ?= ${NAME}-dind
TAG ?= latest TAG ?= latest
RUNNER_VERSION ?= 2.274.2 RUNNER_VERSION ?= 2.277.1
DOCKER_VERSION ?= 19.03.12 DOCKER_VERSION ?= 19.03.12
# default list of platforms for which multiarch image is built # default list of platforms for which multiarch image is built
@@ -22,16 +22,15 @@ else
export PUSH_ARG="--push" export PUSH_ARG="--push"
endif endif
docker-build: docker-build-ubuntu:
docker build --build-arg TARGETPLATFORM=amd64 --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${NAME}:${TAG} . docker build --build-arg TARGETPLATFORM=amd64 --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${NAME}:${TAG} .
docker build --build-arg TARGETPLATFORM=amd64 --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${DIND_RUNNER_NAME}:${TAG} -f dindrunner.Dockerfile . docker build --build-arg TARGETPLATFORM=amd64 --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${DIND_RUNNER_NAME}:${TAG} -f Dockerfile.dindrunner .
docker-push-ubuntu:
docker-push:
docker push ${NAME}:${TAG} docker push ${NAME}:${TAG}
docker push ${DIND_RUNNER_NAME}:${TAG} docker push ${DIND_RUNNER_NAME}:${TAG}
docker-buildx: docker-buildx-ubuntu:
export DOCKER_CLI_EXPERIMENTAL=enabled export DOCKER_CLI_EXPERIMENTAL=enabled
@if ! docker buildx ls | grep -q container-builder; then\ @if ! docker buildx ls | grep -q container-builder; then\
docker buildx create --platform ${PLATFORMS} --name container-builder --use;\ docker buildx create --platform ${PLATFORMS} --name container-builder --use;\
@@ -46,5 +45,5 @@ docker-buildx:
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \ --build-arg RUNNER_VERSION=${RUNNER_VERSION} \
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \ --build-arg DOCKER_VERSION=${DOCKER_VERSION} \
-t "${DIND_RUNNER_NAME}:latest" \ -t "${DIND_RUNNER_NAME}:latest" \
-f dindrunner.Dockerfile \ -f Dockerfile.dindrunner \
. ${PUSH_ARG} . ${PUSH_ARG}

View File

@@ -29,21 +29,13 @@ else
exit 1 exit 1
fi fi
if [ -n "${RUNNER_WORKDIR}" ]; then
WORKDIR_ARG="--work ${RUNNER_WORKDIR}"
fi
if [ -n "${RUNNER_LABELS}" ]; then
LABEL_ARG="--labels ${RUNNER_LABELS}"
fi
if [ -z "${RUNNER_TOKEN}" ]; then if [ -z "${RUNNER_TOKEN}" ]; then
echo "RUNNER_TOKEN must be set" 1>&2 echo "RUNNER_TOKEN must be set" 1>&2
exit 1 exit 1
fi fi
if [ -z "${RUNNER_REPO}" ] && [ -n "${RUNNER_GROUP}" ];then if [ -z "${RUNNER_REPO}" ] && [ -n "${RUNNER_GROUP}" ];then
RUNNER_GROUP_ARG="--runnergroup ${RUNNER_GROUP}" RUNNER_GROUPS=${RUNNER_GROUP}
fi fi
# Hack due to https://github.com/summerwind/actions-runner-controller/issues/252#issuecomment-758338483 # Hack due to https://github.com/summerwind/actions-runner-controller/issues/252#issuecomment-758338483
@@ -56,7 +48,14 @@ sudo chown -R runner:docker /runner
mv /runnertmp/* /runner/ mv /runnertmp/* /runner/
cd /runner cd /runner
./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "${GITHUB_URL}${ATTACH}" --token "${RUNNER_TOKEN}" ${RUNNER_GROUP_ARG} ${LABEL_ARG} ${WORKDIR_ARG} ./config.sh --unattended --replace \
--name "${RUNNER_NAME}" \
--url "${GITHUB_URL}${ATTACH}" \
--token "${RUNNER_TOKEN}" \
--runnergroup "${RUNNER_GROUPS}" \
--labels "${RUNNER_LABELS}" \
--work "${RUNNER_WORKDIR}"
mkdir ./externals mkdir ./externals
# Hack due to the DinD volumes # Hack due to the DinD volumes
mv ./externalstmp/* ./externals/ mv ./externalstmp/* ./externals/

View File

@@ -17,6 +17,34 @@ function wait_for_process () {
return 0 return 0
} }
sudo /bin/bash <<SCRIPT
mkdir -p /etc/docker
cat <<EOS > /etc/docker/daemon.json
{
EOS
if [ -n "${MTU}" ]; then
cat <<EOS >> /etc/docker/daemon.json
"mtu": ${MTU}
EOS
# See https://docs.docker.com/engine/security/rootless/
echo "environment=DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=${MTU}" >> /etc/supervisor/conf.d/dockerd.conf
fi
cat <<EOS >> /etc/docker/daemon.json
}
EOS
SCRIPT
INFO "Using /etc/docker/daemon.json with the following content"
cat /etc/docker/daemon.json
INFO "Using /etc/supervisor/conf.d/dockerd.conf with the following content"
cat /etc/supervisor/conf.d/dockerd.conf
INFO "Starting supervisor" INFO "Starting supervisor"
sudo /usr/bin/supervisord -n >> /dev/null 2>&1 & sudo /usr/bin/supervisord -n >> /dev/null 2>&1 &
@@ -27,6 +55,8 @@ for process in "${processes[@]}"; do
wait_for_process "$process" wait_for_process "$process"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
ERROR "$process is not running after max time" ERROR "$process is not running after max time"
ERROR "Dumping /var/log/dockerd.err.log to help investigation"
cat /var/log/dockerd.err.log
exit 1 exit 1
else else
INFO "$process is running" INFO "$process is running"