mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-11 03:57:01 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ece8fd8fe4 | ||
|
|
dcf8524b5c | ||
|
|
4eb45d3c7f | ||
|
|
1c30bdf35b | ||
|
|
3f335ca628 | ||
|
|
f2a2ab7ede | ||
|
|
40c5050978 | ||
|
|
99a53a6e79 | ||
|
|
6d78fb07b3 | ||
|
|
faaca10fba | ||
|
|
d16dfac0f8 | ||
|
|
af483d83da | ||
|
|
92920926fe | ||
|
|
7d0bfb77e3 | ||
|
|
c4074130e8 | ||
|
|
be2e61f209 | ||
|
|
da818a898a | ||
|
|
2d250d5e06 | ||
|
|
231cde1531 |
44
.github/workflows/build-runner.yml
vendored
44
.github/workflows/build-runner.yml
vendored
@@ -11,6 +11,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- runner/patched/*
|
- runner/patched/*
|
||||||
- runner/Dockerfile
|
- runner/Dockerfile
|
||||||
|
- runner/dindrunner.Dockerfile
|
||||||
- runner/entrypoint.sh
|
- runner/entrypoint.sh
|
||||||
- .github/workflows/build-runner.yml
|
- .github/workflows/build-runner.yml
|
||||||
name: Runner
|
name: Runner
|
||||||
@@ -21,6 +22,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RUNNER_VERSION: 2.273.5
|
RUNNER_VERSION: 2.273.5
|
||||||
DOCKER_VERSION: 19.03.12
|
DOCKER_VERSION: 19.03.12
|
||||||
|
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -33,19 +35,45 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Container Image
|
- name: Build Container Image
|
||||||
working-directory: runner
|
working-directory: runner
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
run: |
|
run: |
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \
|
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \
|
||||||
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \
|
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \
|
||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--tag summerwind/actions-runner:v${RUNNER_VERSION} \
|
--tag ${DOCKERHUB_USERNAME}/actions-runner:v${RUNNER_VERSION} \
|
||||||
|
--tag ${DOCKERHUB_USERNAME}/actions-runner:latest \
|
||||||
-f Dockerfile .
|
-f Dockerfile .
|
||||||
|
docker buildx build \
|
||||||
|
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \
|
||||||
|
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--tag ${DOCKERHUB_USERNAME}/actions-runner-dind:v${RUNNER_VERSION} \
|
||||||
|
--tag ${DOCKERHUB_USERNAME}/actions-runner-dind:latest \
|
||||||
|
-f dindrunner.Dockerfile .
|
||||||
|
|
||||||
- name: Push Container Image
|
- name: Login to GitHub Docker Registry
|
||||||
working-directory: runner
|
run: echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
|
||||||
run: |
|
|
||||||
docker login -u summerwind --password-stdin <<<${{ secrets.DOCKER_ACCESS_TOKEN }}
|
|
||||||
docker push summerwind/actions-runner:v${RUNNER_VERSION}
|
|
||||||
docker tag summerwind/actions-runner:v${RUNNER_VERSION} summerwind/actions-runner:latest
|
|
||||||
docker push summerwind/actions-runner:latest
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
||||||
|
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and Push Container Image
|
||||||
|
working-directory: runner
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \
|
||||||
|
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--tag ${DOCKERHUB_USERNAME}/actions-runner:v${RUNNER_VERSION} \
|
||||||
|
--tag ${DOCKERHUB_USERNAME}/actions-runner:latest \
|
||||||
|
-f Dockerfile . --push
|
||||||
|
docker buildx build \
|
||||||
|
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \
|
||||||
|
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--tag ${DOCKERHUB_USERNAME}/actions-runner-dind:v${RUNNER_VERSION} \
|
||||||
|
--tag ${DOCKERHUB_USERNAME}/actions-runner-dind:latest \
|
||||||
|
-f dindrunner.Dockerfile . --push
|
||||||
|
|||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -38,12 +38,14 @@ jobs:
|
|||||||
- name: Login to GitHub Docker Registry
|
- name: Login to GitHub Docker Registry
|
||||||
run: echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
|
run: echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
|
||||||
env:
|
env:
|
||||||
DOCKERHUB_USERNAME: summerwind
|
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
||||||
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
|
||||||
- name: Build Container Image
|
- name: Build Container Image
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
||||||
run: |
|
run: |
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--tag summerwind/actions-runner-controller:${{ env.VERSION }} \
|
--tag ${DOCKERHUB_USERNAME}/actions-runner-controller:${{ env.VERSION }} \
|
||||||
-f Dockerfile . --push
|
-f Dockerfile . --push
|
||||||
|
|||||||
35
.github/workflows/wip.yml
vendored
Normal file
35
.github/workflows/wip.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- "runner/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: release-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: crazy-max/ghaction-docker-buildx@v1
|
||||||
|
with:
|
||||||
|
buildx-version: latest
|
||||||
|
|
||||||
|
- name: Login to GitHub Docker Registry
|
||||||
|
run: echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
||||||
|
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Container Image
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--tag ${DOCKERHUB_USERNAME}/actions-runner-controller:latest \
|
||||||
|
-f Dockerfile . --push
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Build the manager binary
|
# Build the manager binary
|
||||||
FROM golang:1.13 as builder
|
FROM golang:1.15 as builder
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
|||||||
42
Makefile
42
Makefile
@@ -1,5 +1,8 @@
|
|||||||
NAME ?= summerwind/actions-runner-controller
|
NAME ?= summerwind/actions-runner-controller
|
||||||
VERSION ?= latest
|
VERSION ?= latest
|
||||||
|
# From https://github.com/VictoriaMetrics/operator/pull/44
|
||||||
|
YAML_DROP=$(YQ) delete --inplace
|
||||||
|
YAML_DROP_PREFIX=spec.validation.openAPIV3Schema.properties.spec.properties
|
||||||
|
|
||||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||||
CRD_OPTIONS ?= "crd:trivialVersions=true"
|
CRD_OPTIONS ?= "crd:trivialVersions=true"
|
||||||
@@ -56,7 +59,9 @@ deploy: manifests
|
|||||||
kustomize build config/default | kubectl apply -f -
|
kustomize build config/default | kubectl apply -f -
|
||||||
|
|
||||||
# Generate manifests e.g. CRD, RBAC etc.
|
# Generate manifests e.g. CRD, RBAC etc.
|
||||||
manifests: controller-gen
|
manifests: manifests-118 fix118
|
||||||
|
|
||||||
|
manifests-118: controller-gen
|
||||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||||
|
|
||||||
# Run go fmt against code
|
# Run go fmt against code
|
||||||
@@ -67,6 +72,22 @@ fmt:
|
|||||||
vet:
|
vet:
|
||||||
go vet ./...
|
go vet ./...
|
||||||
|
|
||||||
|
# workaround for CRD issue with k8s 1.18 & controller-gen
|
||||||
|
# ref: https://github.com/kubernetes/kubernetes/issues/91395
|
||||||
|
fix118: yq
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runnerreplicasets.yaml $(YAML_DROP_PREFIX).template.properties.spec.properties.containers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runnerreplicasets.yaml $(YAML_DROP_PREFIX).template.properties.spec.properties.initContainers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runnerreplicasets.yaml $(YAML_DROP_PREFIX).template.properties.spec.properties.sidecarContainers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runnerreplicasets.yaml $(YAML_DROP_PREFIX).template.properties.spec.properties.ephemeralContainers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runnerdeployments.yaml $(YAML_DROP_PREFIX).template.properties.spec.properties.containers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runnerdeployments.yaml $(YAML_DROP_PREFIX).template.properties.spec.properties.initContainers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runnerdeployments.yaml $(YAML_DROP_PREFIX).template.properties.spec.properties.sidecarContainers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runnerdeployments.yaml $(YAML_DROP_PREFIX).template.properties.spec.properties.ephemeralContainers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runners.yaml $(YAML_DROP_PREFIX).containers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runners.yaml $(YAML_DROP_PREFIX).initContainers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runners.yaml $(YAML_DROP_PREFIX).sidecarContainers.items.properties
|
||||||
|
$(YAML_DROP) config/crd/bases/actions.summerwind.dev_runners.yaml $(YAML_DROP_PREFIX).ephemeralContainers.items.properties
|
||||||
|
|
||||||
# Generate code
|
# Generate code
|
||||||
generate: controller-gen
|
generate: controller-gen
|
||||||
$(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths="./..."
|
$(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths="./..."
|
||||||
@@ -105,6 +126,7 @@ github-release: release
|
|||||||
# download controller-gen if necessary
|
# download controller-gen if necessary
|
||||||
controller-gen:
|
controller-gen:
|
||||||
ifeq (, $(shell which controller-gen))
|
ifeq (, $(shell which controller-gen))
|
||||||
|
ifeq (, $(wildcard $(GOBIN)/controller-gen))
|
||||||
@{ \
|
@{ \
|
||||||
set -e ;\
|
set -e ;\
|
||||||
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||||
@@ -113,7 +135,25 @@ ifeq (, $(shell which controller-gen))
|
|||||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0 ;\
|
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0 ;\
|
||||||
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
||||||
}
|
}
|
||||||
|
endif
|
||||||
CONTROLLER_GEN=$(GOBIN)/controller-gen
|
CONTROLLER_GEN=$(GOBIN)/controller-gen
|
||||||
else
|
else
|
||||||
CONTROLLER_GEN=$(shell which controller-gen)
|
CONTROLLER_GEN=$(shell which controller-gen)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# find or download yq
|
||||||
|
# download yq if necessary
|
||||||
|
# Use always go-version to get consistent line wraps etc.
|
||||||
|
yq:
|
||||||
|
ifeq (, $(wildcard $(GOBIN)/yq))
|
||||||
|
echo "Downloading yq"
|
||||||
|
@{ \
|
||||||
|
set -e ;\
|
||||||
|
YQ_TMP_DIR=$$(mktemp -d) ;\
|
||||||
|
cd $$YQ_TMP_DIR ;\
|
||||||
|
go mod init tmp ;\
|
||||||
|
go get github.com/mikefarah/yq/v3@3.4.0 ;\
|
||||||
|
rm -rf $$YQ_TMP_DIR ;\
|
||||||
|
}
|
||||||
|
endif
|
||||||
|
YQ=$(GOBIN)/yq
|
||||||
|
|||||||
112
README.md
112
README.md
@@ -17,9 +17,19 @@ actions-runner-controller uses [cert-manager](https://cert-manager.io/docs/insta
|
|||||||
Install the custom resource and actions-runner-controller itself. This will create actions-runner-system namespace in your Kubernetes and deploy the required resources.
|
Install the custom resource and actions-runner-controller itself. This will create actions-runner-system namespace in your Kubernetes and deploy the required resources.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/latest/download/actions-runner-controller.yaml
|
kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/latest/download/actions-runner-controller.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Github Enterprise support
|
||||||
|
|
||||||
|
If you use either Github Enterprise Cloud or Server (and have recent enought version supporting Actions), you can use **actions-runner-controller** with those, too. Authentication works same way as with public Github (repo and organization level).
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL>
|
||||||
|
```
|
||||||
|
|
||||||
|
[Enterprise level](https://docs.github.com/en/enterprise-server@2.22/actions/hosting-your-own-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-an-enterprise) runners are not working yet as there's no API definition for those.
|
||||||
|
|
||||||
## Setting up authentication with GitHub API
|
## Setting up authentication with GitHub API
|
||||||
|
|
||||||
There are two ways for actions-runner-controller to authenticate with the GitHub API:
|
There are two ways for actions-runner-controller to authenticate with the GitHub API:
|
||||||
@@ -58,7 +68,7 @@ When the installation is complete, you will be taken to a URL in one of the foll
|
|||||||
|
|
||||||
Finally, register the App ID (`APP_ID`), Installation ID (`INSTALLATION_ID`), and downloaded private key file (`PRIVATE_KEY_FILE_PATH`) to Kubernetes as Secret.
|
Finally, register the App ID (`APP_ID`), Installation ID (`INSTALLATION_ID`), and downloaded private key file (`PRIVATE_KEY_FILE_PATH`) to Kubernetes as Secret.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ kubectl create secret generic controller-manager \
|
$ kubectl create secret generic controller-manager \
|
||||||
-n actions-runner-system \
|
-n actions-runner-system \
|
||||||
--from-literal=github_app_id=${APP_ID} \
|
--from-literal=github_app_id=${APP_ID} \
|
||||||
@@ -80,8 +90,8 @@ Open the Create Token page from the following link, grant the `repo` and/or `adm
|
|||||||
|
|
||||||
Register the created token (`GITHUB_TOKEN`) as a Kubernetes secret.
|
Register the created token (`GITHUB_TOKEN`) as a Kubernetes secret.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ kubectl create secret generic controller-manager \
|
kubectl create secret generic controller-manager \
|
||||||
-n actions-runner-system \
|
-n actions-runner-system \
|
||||||
--from-literal=github_token=${GITHUB_TOKEN}
|
--from-literal=github_token=${GITHUB_TOKEN}
|
||||||
```
|
```
|
||||||
@@ -97,7 +107,7 @@ There are two ways to use this controller:
|
|||||||
|
|
||||||
To launch a single self-hosted runner, you need to create a manifest file includes *Runner* resource as follows. This example launches a self-hosted runner with name *example-runner* for the *summerwind/actions-runner-controller* repository.
|
To launch a single self-hosted runner, you need to create a manifest file includes *Runner* resource as follows. This example launches a self-hosted runner with name *example-runner* for the *summerwind/actions-runner-controller* repository.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
# runner.yaml
|
# runner.yaml
|
||||||
apiVersion: actions.summerwind.dev/v1alpha1
|
apiVersion: actions.summerwind.dev/v1alpha1
|
||||||
kind: Runner
|
kind: Runner
|
||||||
@@ -110,14 +120,14 @@ spec:
|
|||||||
|
|
||||||
Apply the created manifest file to your Kubernetes.
|
Apply the created manifest file to your Kubernetes.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ kubectl apply -f runner.yaml
|
$ kubectl apply -f runner.yaml
|
||||||
runner.actions.summerwind.dev/example-runner created
|
runner.actions.summerwind.dev/example-runner created
|
||||||
```
|
```
|
||||||
|
|
||||||
You can see that the Runner resource has been created.
|
You can see that the Runner resource has been created.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ kubectl get runners
|
$ kubectl get runners
|
||||||
NAME REPOSITORY STATUS
|
NAME REPOSITORY STATUS
|
||||||
example-runner summerwind/actions-runner-controller Running
|
example-runner summerwind/actions-runner-controller Running
|
||||||
@@ -125,7 +135,7 @@ example-runner summerwind/actions-runner-controller Running
|
|||||||
|
|
||||||
You can also see that the runner pod has been running.
|
You can also see that the runner pod has been running.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ kubectl get pods
|
$ kubectl get pods
|
||||||
NAME READY STATUS RESTARTS AGE
|
NAME READY STATUS RESTARTS AGE
|
||||||
example-runner 2/2 Running 0 1m
|
example-runner 2/2 Running 0 1m
|
||||||
@@ -141,7 +151,7 @@ Now you can use your self-hosted runner. See the [official documentation](https:
|
|||||||
|
|
||||||
To add the runner to an organization, you only need to replace the `repository` field with `organization`, so the runner will register itself to the organization.
|
To add the runner to an organization, you only need to replace the `repository` field with `organization`, so the runner will register itself to the organization.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
# runner.yaml
|
# runner.yaml
|
||||||
apiVersion: actions.summerwind.dev/v1alpha1
|
apiVersion: actions.summerwind.dev/v1alpha1
|
||||||
kind: Runner
|
kind: Runner
|
||||||
@@ -175,14 +185,14 @@ spec:
|
|||||||
|
|
||||||
Apply the manifest file to your cluster:
|
Apply the manifest file to your cluster:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ kubectl apply -f runner.yaml
|
$ kubectl apply -f runner.yaml
|
||||||
runnerdeployment.actions.summerwind.dev/example-runnerdeploy created
|
runnerdeployment.actions.summerwind.dev/example-runnerdeploy created
|
||||||
```
|
```
|
||||||
|
|
||||||
You can see that 2 runners have been created as specified by `replicas: 2`:
|
You can see that 2 runners have been created as specified by `replicas: 2`:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ kubectl get runners
|
$ kubectl get runners
|
||||||
NAME REPOSITORY STATUS
|
NAME REPOSITORY STATUS
|
||||||
example-runnerdeploy2475h595fr mumoshu/actions-runner-controller-ci Running
|
example-runnerdeploy2475h595fr mumoshu/actions-runner-controller-ci Running
|
||||||
@@ -195,7 +205,7 @@ example-runnerdeploy2475ht2qbr mumoshu/actions-runner-controller-ci Running
|
|||||||
|
|
||||||
In the below example, `actions-runner` checks for pending workflow runs for each sync period, and scale to e.g. 3 if there're 3 pending jobs at sync time.
|
In the below example, `actions-runner` checks for pending workflow runs for each sync period, and scale to e.g. 3 if there're 3 pending jobs at sync time.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
apiVersion: actions.summerwind.dev/v1alpha1
|
apiVersion: actions.summerwind.dev/v1alpha1
|
||||||
kind: RunnerDeployment
|
kind: RunnerDeployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -225,7 +235,7 @@ Please also note that the sync period is set to 10 minutes by default and it's c
|
|||||||
Additionally, the autoscaling feature has an anti-flapping option that prevents periodic loop of scaling up and down.
|
Additionally, the autoscaling feature has an anti-flapping option that prevents periodic loop of scaling up and down.
|
||||||
By default, it doesn't scale down until the grace period of 10 minutes passes after a scale up. The grace period can be configured by setting `scaleDownDelaySecondsAfterScaleUp`:
|
By default, it doesn't scale down until the grace period of 10 minutes passes after a scale up. The grace period can be configured by setting `scaleDownDelaySecondsAfterScaleUp`:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
apiVersion: actions.summerwind.dev/v1alpha1
|
apiVersion: actions.summerwind.dev/v1alpha1
|
||||||
kind: RunnerDeployment
|
kind: RunnerDeployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -251,6 +261,28 @@ spec:
|
|||||||
- summerwind/actions-runner-controller
|
- summerwind/actions-runner-controller
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Runner with DinD
|
||||||
|
|
||||||
|
When using default runner, runner pod starts up 2 containers: runner and DinD (Docker-in-Docker). This might create issues if there's `LimitRange` set to namespace.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# dindrunnerdeployment.yaml
|
||||||
|
apiVersion: actions.summerwind.dev/v1alpha1
|
||||||
|
kind: RunnerDeployment
|
||||||
|
metadata:
|
||||||
|
name: example-dindrunnerdeploy
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
image: summerwind/actions-runner-dind
|
||||||
|
dockerdWithinRunnerContainer: true
|
||||||
|
repository: mumoshu/actions-runner-controller-ci
|
||||||
|
env: []
|
||||||
|
```
|
||||||
|
|
||||||
|
This also helps with resources, as you don't need to give resources separately to docker and runner.
|
||||||
|
|
||||||
## Additional tweaks
|
## Additional tweaks
|
||||||
|
|
||||||
You can pass details through the spec selector. Here's an eg. of what you may like to do:
|
You can pass details through the spec selector. Here's an eg. of what you may like to do:
|
||||||
@@ -283,6 +315,17 @@ spec:
|
|||||||
requests:
|
requests:
|
||||||
cpu: "2.0"
|
cpu: "2.0"
|
||||||
memory: "4Gi"
|
memory: "4Gi"
|
||||||
|
# If set to true, runner pod container only 1 container that's expected to be able to run docker, too.
|
||||||
|
# image summerwind/actions-runner-dind or custom one should be used with true -value
|
||||||
|
dockerdWithinRunnerContainer: false
|
||||||
|
# Valid if dockerdWithinRunnerContainer is not true
|
||||||
|
dockerdContainerResources:
|
||||||
|
limits:
|
||||||
|
cpu: "4.0"
|
||||||
|
memory: "8Gi"
|
||||||
|
requests:
|
||||||
|
cpu: "2.0"
|
||||||
|
memory: "4Gi"
|
||||||
sidecarContainers:
|
sidecarContainers:
|
||||||
- name: mysql
|
- name: mysql
|
||||||
image: mysql:5.7
|
image: mysql:5.7
|
||||||
@@ -330,22 +373,41 @@ jobs:
|
|||||||
|
|
||||||
Note that if you specify `self-hosted` in your workflow, then this will run your job on _any_ self-hosted runner, regardless of the labels that they have.
|
Note that if you specify `self-hosted` in your workflow, then this will run your job on _any_ self-hosted runner, regardless of the labels that they have.
|
||||||
|
|
||||||
|
## Runner Groups
|
||||||
|
|
||||||
|
Runner groups can be used to limit which repositories are able to use the GitHub Runner at an Organisation level.
|
||||||
|
|
||||||
|
To add the runner to the group `NewGroup`, specify the group in your `Runner` or `RunnerDeployment` spec.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# runnerdeployment.yaml
|
||||||
|
apiVersion: actions.summerwind.dev/v1alpha1
|
||||||
|
kind: RunnerDeployment
|
||||||
|
metadata:
|
||||||
|
name: custom-runner
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
group: NewGroup
|
||||||
|
```
|
||||||
|
|
||||||
## Software installed in the runner image
|
## Software installed in the runner image
|
||||||
|
|
||||||
The GitHub hosted runners include a large amount of pre-installed software packages. For Ubuntu 18.04, this list can be found at https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-README.md
|
The GitHub hosted runners include a large amount of pre-installed software packages. For Ubuntu 18.04, this list can be found at <https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-README.md>
|
||||||
|
|
||||||
The container image is based on Ubuntu 18.04, but it does not contain all of the software installed on the GitHub runners. It contains the following subset of packages from the GitHub runners:
|
The container image is based on Ubuntu 18.04, but it does not contain all of the software installed on the GitHub runners. It contains the following subset of packages from the GitHub runners:
|
||||||
|
|
||||||
* Basic CLI packages
|
- Basic CLI packages
|
||||||
* git (2.26)
|
- git (2.26)
|
||||||
* docker
|
- docker
|
||||||
* build-essentials
|
- build-essentials
|
||||||
|
|
||||||
The virtual environments from GitHub contain a lot more software packages (different versions of Java, Node.js, Golang, .NET, etc) which are not provided in the runner image. Most of these have dedicated setup actions which allow the tools to be installed on-demand in a workflow, for example: `actions/setup-java` or `actions/setup-node`
|
The virtual environments from GitHub contain a lot more software packages (different versions of Java, Node.js, Golang, .NET, etc) which are not provided in the runner image. Most of these have dedicated setup actions which allow the tools to be installed on-demand in a workflow, for example: `actions/setup-java` or `actions/setup-node`
|
||||||
|
|
||||||
If there is a need to include packages in the runner image for which there is no setup action, then this can be achieved by building a custom container image for the runner. The easiest way is to start with the `summerwind/actions-runner` image and installing the extra dependencies directly in the docker image:
|
If there is a need to include packages in the runner image for which there is no setup action, then this can be achieved by building a custom container image for the runner. The easiest way is to start with the `summerwind/actions-runner` image and installing the extra dependencies directly in the docker image:
|
||||||
|
|
||||||
```yaml
|
```shell
|
||||||
FROM summerwind/actions-runner:v2.169.1
|
FROM summerwind/actions-runner:v2.169.1
|
||||||
|
|
||||||
RUN sudo apt update -y \
|
RUN sudo apt update -y \
|
||||||
@@ -364,3 +426,15 @@ spec:
|
|||||||
repository: summerwind/actions-runner-controller
|
repository: summerwind/actions-runner-controller
|
||||||
image: YOUR_CUSTOM_DOCKER_IMAGE
|
image: YOUR_CUSTOM_DOCKER_IMAGE
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Alternatives
|
||||||
|
|
||||||
|
The following is a list of alternative solutions that may better fit you depending on your use-case:
|
||||||
|
|
||||||
|
- <https://github.com/evryfs/github-actions-runner-operator/>
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -36,9 +36,14 @@ type RunnerSpec struct {
|
|||||||
// +optional
|
// +optional
|
||||||
Labels []string `json:"labels,omitempty"`
|
Labels []string `json:"labels,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
Containers []corev1.Container `json:"containers,omitempty"`
|
Containers []corev1.Container `json:"containers,omitempty"`
|
||||||
// +optional
|
// +optional
|
||||||
|
DockerdContainerResources corev1.ResourceRequirements `json:"dockerdContainerResources,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"`
|
||||||
@@ -77,6 +82,8 @@ type RunnerSpec struct {
|
|||||||
EphemeralContainers []corev1.EphemeralContainer `json:"ephemeralContainers,omitempty"`
|
EphemeralContainers []corev1.EphemeralContainer `json:"ephemeralContainers,omitempty"`
|
||||||
// +optional
|
// +optional
|
||||||
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"`
|
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"`
|
||||||
|
// +optional
|
||||||
|
DockerdWithinRunnerContainer *bool `json:"dockerdWithinRunnerContainer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateRepository validates repository field.
|
// ValidateRepository validates repository field.
|
||||||
|
|||||||
@@ -435,6 +435,7 @@ func (in *RunnerSpec) DeepCopyInto(out *RunnerSpec) {
|
|||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
in.DockerdContainerResources.DeepCopyInto(&out.DockerdContainerResources)
|
||||||
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
|
||||||
@@ -524,6 +525,11 @@ func (in *RunnerSpec) DeepCopyInto(out *RunnerSpec) {
|
|||||||
*out = new(int64)
|
*out = new(int64)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.DockerdWithinRunnerContainer != nil {
|
||||||
|
in, out := &in.DockerdWithinRunnerContainer, &out.DockerdWithinRunnerContainer
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func newGithubClient(server *httptest.Server) *github.Client {
|
func newGithubClient(server *httptest.Server) *github.Client {
|
||||||
client, err := github.NewClientWithAccessToken("token")
|
c := github.Config{
|
||||||
|
Token: "token",
|
||||||
|
}
|
||||||
|
client, err := c.NewClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ var _ = Context("Inside of a new namespace", func() {
|
|||||||
Spec: actionsv1alpha1.RunnerSpec{
|
Spec: actionsv1alpha1.RunnerSpec{
|
||||||
Repository: "test/valid",
|
Repository: "test/valid",
|
||||||
Image: "bar",
|
Image: "bar",
|
||||||
|
Group: "baz",
|
||||||
Env: []corev1.EnvVar{
|
Env: []corev1.EnvVar{
|
||||||
{Name: "FOO", Value: "FOOVALUE"},
|
{Name: "FOO", Value: "FOOVALUE"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -120,40 +120,13 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !runner.IsRegisterable() {
|
|
||||||
rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
|
||||||
if err != nil {
|
|
||||||
r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed")
|
|
||||||
log.Error(err, "Failed to get new registration token")
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updated := runner.DeepCopy()
|
|
||||||
updated.Status.Registration = v1alpha1.RunnerStatusRegistration{
|
|
||||||
Organization: runner.Spec.Organization,
|
|
||||||
Repository: runner.Spec.Repository,
|
|
||||||
Labels: runner.Spec.Labels,
|
|
||||||
Token: rt.GetToken(),
|
|
||||||
ExpiresAt: metav1.NewTime(rt.GetExpiresAt().Time),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.Status().Update(ctx, updated); err != nil {
|
|
||||||
log.Error(err, "Failed to update runner status")
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Recorder.Event(&runner, corev1.EventTypeNormal, "RegistrationTokenUpdated", "Successfully update registration token")
|
|
||||||
log.Info("Updated registration token", "repository", runner.Spec.Repository)
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var pod corev1.Pod
|
var pod corev1.Pod
|
||||||
if err := r.Get(ctx, req.NamespacedName, &pod); err != nil {
|
if err := r.Get(ctx, req.NamespacedName, &pod); err != nil {
|
||||||
if !errors.IsNotFound(err) {
|
if !errors.IsNotFound(err) {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
newPod, err := r.newPod(runner)
|
newPod, err := r.newPod(ctx, runner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "Could not create pod")
|
log.Error(err, "Could not create pod")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
@@ -167,7 +140,11 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
r.Recorder.Event(&runner, corev1.EventTypeNormal, "PodCreated", fmt.Sprintf("Created pod '%s'", newPod.Name))
|
r.Recorder.Event(&runner, corev1.EventTypeNormal, "PodCreated", fmt.Sprintf("Created pod '%s'", newPod.Name))
|
||||||
log.Info("Created runner pod", "repository", runner.Spec.Repository)
|
log.Info("Created runner pod", "repository", runner.Spec.Repository)
|
||||||
} else {
|
} else {
|
||||||
if runner.Status.Phase != string(pod.Status.Phase) {
|
// If pod has ended up succeeded we need to restart it
|
||||||
|
// Happens e.g. when dind is in runner and run completes
|
||||||
|
restart := pod.Status.Phase == corev1.PodSucceeded
|
||||||
|
|
||||||
|
if !restart && runner.Status.Phase != string(pod.Status.Phase) {
|
||||||
updated := runner.DeepCopy()
|
updated := runner.DeepCopy()
|
||||||
updated.Status.Phase = string(pod.Status.Phase)
|
updated.Status.Phase = string(pod.Status.Phase)
|
||||||
updated.Status.Reason = pod.Status.Reason
|
updated.Status.Reason = pod.Status.Reason
|
||||||
@@ -185,8 +162,6 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
restart := false
|
|
||||||
|
|
||||||
if pod.Status.Phase == corev1.PodRunning {
|
if pod.Status.Phase == corev1.PodRunning {
|
||||||
for _, status := range pod.Status.ContainerStatuses {
|
for _, status := range pod.Status.ContainerStatuses {
|
||||||
if status.Name != containerName {
|
if status.Name != containerName {
|
||||||
@@ -199,7 +174,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newPod, err := r.newPod(runner)
|
newPod, err := r.newPod(ctx, runner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "Could not create pod")
|
log.Error(err, "Could not create pod")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
@@ -274,12 +249,21 @@ func (r *RunnerReconciler) unregisterRunner(ctx context.Context, org, repo, name
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
func (r *RunnerReconciler) newPod(ctx context.Context, runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||||
var (
|
var (
|
||||||
privileged bool = true
|
privileged bool = true
|
||||||
group int64 = 0
|
dockerdInRunner bool = runner.Spec.DockerdWithinRunnerContainer != nil && *runner.Spec.DockerdWithinRunnerContainer
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
token := runner.Status.Registration.Token
|
||||||
|
if !runner.IsRegisterable() {
|
||||||
|
token, err = r.getRegistrationToken(ctx, runner)
|
||||||
|
if err != nil {
|
||||||
|
return corev1.Pod{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runnerImage := runner.Spec.Image
|
runnerImage := runner.Spec.Image
|
||||||
if runnerImage == "" {
|
if runnerImage == "" {
|
||||||
runnerImage = r.RunnerImage
|
runnerImage = r.RunnerImage
|
||||||
@@ -307,9 +291,21 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
|||||||
Name: "RUNNER_LABELS",
|
Name: "RUNNER_LABELS",
|
||||||
Value: strings.Join(runner.Spec.Labels, ","),
|
Value: strings.Join(runner.Spec.Labels, ","),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "RUNNER_GROUP",
|
||||||
|
Value: runner.Spec.Group,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "RUNNER_TOKEN",
|
Name: "RUNNER_TOKEN",
|
||||||
Value: runner.Status.Registration.Token,
|
Value: token,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "DOCKERD_IN_RUNNER",
|
||||||
|
Value: fmt.Sprintf("%v", dockerdInRunner),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "GITHUB_URL",
|
||||||
|
Value: r.GitHubClient.GithubBaseURL,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,58 +326,68 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
|||||||
ImagePullPolicy: runnerImagePullPolicy,
|
ImagePullPolicy: runnerImagePullPolicy,
|
||||||
Env: env,
|
Env: env,
|
||||||
EnvFrom: runner.Spec.EnvFrom,
|
EnvFrom: runner.Spec.EnvFrom,
|
||||||
VolumeMounts: []corev1.VolumeMount{
|
|
||||||
{
|
|
||||||
Name: "work",
|
|
||||||
MountPath: "/runner/_work",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "docker",
|
|
||||||
MountPath: "/var/run",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SecurityContext: &corev1.SecurityContext{
|
SecurityContext: &corev1.SecurityContext{
|
||||||
RunAsGroup: &group,
|
// Runner need to run privileged if it contains DinD
|
||||||
|
Privileged: runner.Spec.DockerdWithinRunnerContainer,
|
||||||
},
|
},
|
||||||
Resources: runner.Spec.Resources,
|
Resources: runner.Spec.Resources,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "docker",
|
|
||||||
Image: r.DockerImage,
|
|
||||||
VolumeMounts: []corev1.VolumeMount{
|
|
||||||
{
|
|
||||||
Name: "work",
|
|
||||||
MountPath: "/runner/_work",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "docker",
|
|
||||||
MountPath: "/var/run",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SecurityContext: &corev1.SecurityContext{
|
|
||||||
Privileged: &privileged,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Volumes: []corev1.Volume{
|
|
||||||
{
|
|
||||||
Name: "work",
|
|
||||||
VolumeSource: corev1.VolumeSource{
|
|
||||||
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "docker",
|
|
||||||
VolumeSource: corev1.VolumeSource{
|
|
||||||
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !dockerdInRunner {
|
||||||
|
pod.Spec.Volumes = []corev1.Volume{
|
||||||
|
{
|
||||||
|
Name: "work",
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "docker",
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "work",
|
||||||
|
MountPath: "/runner/_work",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "docker",
|
||||||
|
MountPath: "/var/run",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Containers = append(pod.Spec.Containers, corev1.Container{
|
||||||
|
Name: "docker",
|
||||||
|
Image: r.DockerImage,
|
||||||
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "work",
|
||||||
|
MountPath: "/runner/_work",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "docker",
|
||||||
|
MountPath: "/var/run",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SecurityContext: &corev1.SecurityContext{
|
||||||
|
Privileged: &privileged,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if len(runner.Spec.Containers) != 0 {
|
if len(runner.Spec.Containers) != 0 {
|
||||||
pod.Spec.Containers = runner.Spec.Containers
|
pod.Spec.Containers = runner.Spec.Containers
|
||||||
|
for i := 0; i < len(pod.Spec.Containers); i++ {
|
||||||
|
if pod.Spec.Containers[i].Name == containerName {
|
||||||
|
pod.Spec.Containers[i].Env = append(pod.Spec.Containers[i].Env, env...)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(runner.Spec.VolumeMounts) != 0 {
|
if len(runner.Spec.VolumeMounts) != 0 {
|
||||||
@@ -478,3 +484,20 @@ func removeFinalizer(finalizers []string) ([]string, bool) {
|
|||||||
|
|
||||||
return result, removed
|
return result, removed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RunnerReconciler) getRegistrationToken(ctx context.Context, runner v1alpha1.Runner) (string, error) {
|
||||||
|
log := r.Log.WithValues("runner", runner.Name)
|
||||||
|
if runner.IsRegisterable() {
|
||||||
|
return runner.Status.Registration.Token, nil
|
||||||
|
} else {
|
||||||
|
rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
||||||
|
if err != nil {
|
||||||
|
r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed")
|
||||||
|
log.Error(err, "Failed to get new registration token")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Updated registration token", "repository", runner.Spec.Repository)
|
||||||
|
return rt.GetToken(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,39 +14,65 @@ import (
|
|||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Config contains configuration for Github client
|
||||||
|
type Config struct {
|
||||||
|
EnterpriseURL string `split_words:"true"`
|
||||||
|
AppID int64 `split_words:"true"`
|
||||||
|
AppInstallationID int64 `split_words:"true"`
|
||||||
|
AppPrivateKey string `split_words:"true"`
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
// Client wraps GitHub client with some additional
|
// Client wraps GitHub client with some additional
|
||||||
type Client struct {
|
type Client struct {
|
||||||
*github.Client
|
*github.Client
|
||||||
regTokens map[string]*github.RegistrationToken
|
regTokens map[string]*github.RegistrationToken
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
// GithubBaseURL to Github without API suffix.
|
||||||
|
GithubBaseURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns a client authenticated as a GitHub App.
|
func (c *Config) NewClient() (*Client, error) {
|
||||||
func NewClient(appID, installationID int64, privateKeyPath string) (*Client, error) {
|
var (
|
||||||
tr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, appID, installationID, privateKeyPath)
|
httpClient *http.Client
|
||||||
if err != nil {
|
client *github.Client
|
||||||
return nil, fmt.Errorf("authentication failed: %v", err)
|
)
|
||||||
|
githubBaseURL := "https://github.com/"
|
||||||
|
if len(c.Token) > 0 {
|
||||||
|
httpClient = oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(
|
||||||
|
&oauth2.Token{AccessToken: c.Token},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
tr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, c.AppID, c.AppInstallationID, c.AppPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("authentication failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(c.EnterpriseURL) > 0 {
|
||||||
|
githubAPIURL, err := getEnterpriseApiUrl(c.EnterpriseURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("enterprise url incorrect: %v", err)
|
||||||
|
}
|
||||||
|
tr.BaseURL = githubAPIURL
|
||||||
|
}
|
||||||
|
httpClient = &http.Client{Transport: tr}
|
||||||
}
|
}
|
||||||
|
|
||||||
gh := github.NewClient(&http.Client{Transport: tr})
|
if len(c.EnterpriseURL) > 0 {
|
||||||
|
var err error
|
||||||
|
client, err = github.NewEnterpriseClient(c.EnterpriseURL, c.EnterpriseURL, httpClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("enterprise client creation failed: %v", err)
|
||||||
|
}
|
||||||
|
githubBaseURL = fmt.Sprintf("%s://%s%s", client.BaseURL.Scheme, client.BaseURL.Host, strings.TrimSuffix(client.BaseURL.Path, "api/v3/"))
|
||||||
|
} else {
|
||||||
|
client = github.NewClient(httpClient)
|
||||||
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
Client: gh,
|
Client: client,
|
||||||
regTokens: map[string]*github.RegistrationToken{},
|
regTokens: map[string]*github.RegistrationToken{},
|
||||||
mu: sync.Mutex{},
|
mu: sync.Mutex{},
|
||||||
}, nil
|
GithubBaseURL: githubBaseURL,
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientWithAccessToken returns a client authenticated with personal access token.
|
|
||||||
func NewClientWithAccessToken(token string) (*Client, error) {
|
|
||||||
tc := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(
|
|
||||||
&oauth2.Token{AccessToken: token},
|
|
||||||
))
|
|
||||||
|
|
||||||
return &Client{
|
|
||||||
Client: github.NewClient(tc),
|
|
||||||
regTokens: map[string]*github.RegistrationToken{},
|
|
||||||
mu: sync.Mutex{},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,3 +226,21 @@ func splitOwnerAndRepo(repo string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
return chunk[0], chunk[1], nil
|
return chunk[0], chunk[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEnterpriseApiUrl(baseURL string) (string, error) {
|
||||||
|
baseEndpoint, err := url.Parse(baseURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(baseEndpoint.Path, "/") {
|
||||||
|
baseEndpoint.Path += "/"
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(baseEndpoint.Path, "/api/v3/") &&
|
||||||
|
!strings.HasPrefix(baseEndpoint.Host, "api.") &&
|
||||||
|
!strings.Contains(baseEndpoint.Host, ".api.") {
|
||||||
|
baseEndpoint.Path += "api/v3/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim trailing slash, otherwise there's double slash added to token endpoint
|
||||||
|
return fmt.Sprintf("%s://%s%s", baseEndpoint.Scheme, baseEndpoint.Host, strings.TrimSuffix(baseEndpoint.Path, "/")), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import (
|
|||||||
var server *httptest.Server
|
var server *httptest.Server
|
||||||
|
|
||||||
func newTestClient() *Client {
|
func newTestClient() *Client {
|
||||||
client, err := NewClientWithAccessToken("token")
|
c := Config{
|
||||||
|
Token: "token",
|
||||||
|
}
|
||||||
|
client, err := c.NewClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/summerwind/actions-runner-controller
|
module github.com/summerwind/actions-runner-controller
|
||||||
|
|
||||||
go 1.13
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bradleyfalzon/ghinstallation v1.1.1
|
github.com/bradleyfalzon/ghinstallation v1.1.1
|
||||||
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04
|
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04
|
||||||
github.com/google/go-querystring v1.0.0
|
github.com/google/go-querystring v1.0.0
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
|
github.com/kelseyhightower/envconfig v1.4.0
|
||||||
github.com/onsi/ginkgo v1.8.0
|
github.com/onsi/ginkgo v1.8.0
|
||||||
github.com/onsi/gomega v1.5.0
|
github.com/onsi/gomega v1.5.0
|
||||||
github.com/stretchr/testify v1.4.0 // indirect
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -158,6 +158,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
|
|||||||
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||||
|
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
|||||||
73
main.go
73
main.go
@@ -20,9 +20,9 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kelseyhightower/envconfig"
|
||||||
actionsv1alpha1 "github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
actionsv1alpha1 "github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
||||||
"github.com/summerwind/actions-runner-controller/controllers"
|
"github.com/summerwind/actions-runner-controller/controllers"
|
||||||
"github.com/summerwind/actions-runner-controller/github"
|
"github.com/summerwind/actions-runner-controller/github"
|
||||||
@@ -62,74 +62,37 @@ func main() {
|
|||||||
|
|
||||||
runnerImage string
|
runnerImage string
|
||||||
dockerImage string
|
dockerImage string
|
||||||
|
|
||||||
ghToken string
|
|
||||||
ghAppID int64
|
|
||||||
ghAppInstallationID int64
|
|
||||||
ghAppPrivateKey string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var c github.Config
|
||||||
|
err = envconfig.Process("github", &c)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: Environment variable read failed.")
|
||||||
|
}
|
||||||
|
|
||||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||||
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
||||||
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
|
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
|
||||||
flag.StringVar(&runnerImage, "runner-image", defaultRunnerImage, "The image name of self-hosted runner container.")
|
flag.StringVar(&runnerImage, "runner-image", defaultRunnerImage, "The image name of self-hosted runner container.")
|
||||||
flag.StringVar(&dockerImage, "docker-image", defaultDockerImage, "The image name of docker sidecar container.")
|
flag.StringVar(&dockerImage, "docker-image", defaultDockerImage, "The image name of docker sidecar container.")
|
||||||
flag.StringVar(&ghToken, "github-token", "", "The personal access token of GitHub.")
|
flag.StringVar(&c.Token, "github-token", c.Token, "The personal access token of GitHub.")
|
||||||
flag.Int64Var(&ghAppID, "github-app-id", 0, "The application ID of GitHub App.")
|
flag.Int64Var(&c.AppID, "github-app-id", c.AppID, "The application ID of GitHub App.")
|
||||||
flag.Int64Var(&ghAppInstallationID, "github-app-installation-id", 0, "The installation ID of GitHub App.")
|
flag.Int64Var(&c.AppInstallationID, "github-app-installation-id", c.AppInstallationID, "The installation ID of GitHub App.")
|
||||||
flag.StringVar(&ghAppPrivateKey, "github-app-private-key", "", "The path of a private key file to authenticate as a GitHub App")
|
flag.StringVar(&c.AppPrivateKey, "github-app-private-key", c.AppPrivateKey, "The path of a private key file to authenticate as a GitHub App")
|
||||||
flag.DurationVar(&syncPeriod, "sync-period", 10*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled. When you use autoscaling, set to a lower value like 10 minute, because this corresponds to the minimum time to react on demand change")
|
flag.DurationVar(&syncPeriod, "sync-period", 10*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled. When you use autoscaling, set to a lower value like 10 minute, because this corresponds to the minimum time to react on demand change")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if ghToken == "" {
|
logger := zap.New(func(o *zap.Options) {
|
||||||
ghToken = os.Getenv("GITHUB_TOKEN")
|
o.Development = true
|
||||||
}
|
})
|
||||||
if ghAppID == 0 {
|
|
||||||
appID, err := strconv.ParseInt(os.Getenv("GITHUB_APP_ID"), 10, 64)
|
|
||||||
if err == nil {
|
|
||||||
ghAppID = appID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ghAppInstallationID == 0 {
|
|
||||||
appInstallationID, err := strconv.ParseInt(os.Getenv("GITHUB_APP_INSTALLATION_ID"), 10, 64)
|
|
||||||
if err == nil {
|
|
||||||
ghAppInstallationID = appInstallationID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ghAppPrivateKey == "" {
|
|
||||||
ghAppPrivateKey = os.Getenv("GITHUB_APP_PRIVATE_KEY")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ghAppID != 0 {
|
ghClient, err = c.NewClient()
|
||||||
if ghAppInstallationID == 0 {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Error: The installation ID must be specified.")
|
fmt.Fprintln(os.Stderr, "Error: Client creation failed.", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ghAppPrivateKey == "" {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: The path of a private key file must be specified.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ghClient, err = github.NewClient(ghAppID, ghAppInstallationID, ghAppPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error: Failed to create GitHub client: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
} else if ghToken != "" {
|
|
||||||
ghClient, err = github.NewClientWithAccessToken(ghToken)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error: Failed to create GitHub client: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: GitHub App credentials or personal access token must be specified.")
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrl.SetLogger(zap.New(func(o *zap.Options) {
|
ctrl.SetLogger(logger)
|
||||||
o.Development = true
|
|
||||||
}))
|
|
||||||
|
|
||||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
|
|||||||
&& 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 \
|
||||||
&& usermod -aG sudo runner \
|
&& usermod -aG sudo runner \
|
||||||
|
&& usermod -aG docker runner \
|
||||||
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers
|
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers
|
||||||
|
|
||||||
# Runner download supports amd64 as x64
|
# Runner download supports amd64 as x64
|
||||||
@@ -67,6 +69,6 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
|
|||||||
COPY entrypoint.sh /runner
|
COPY entrypoint.sh /runner
|
||||||
COPY patched /runner/patched
|
COPY patched /runner/patched
|
||||||
|
|
||||||
USER runner:runner
|
USER runner
|
||||||
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||||
CMD ["/runner/entrypoint.sh"]
|
CMD ["/runner/entrypoint.sh"]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
NAME ?= summerwind/actions-runner
|
NAME ?= summerwind/actions-runner
|
||||||
|
DIND_RUNNER_NAME ?= ${NAME}-dind
|
||||||
TAG ?= latest
|
TAG ?= latest
|
||||||
|
|
||||||
RUNNER_VERSION ?= 2.273.5
|
RUNNER_VERSION ?= 2.273.5
|
||||||
@@ -23,10 +24,14 @@ endif
|
|||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${NAME}:${TAG} -t ${NAME}:v${RUNNER_VERSION} .
|
docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${NAME}:${TAG} -t ${NAME}:v${RUNNER_VERSION} .
|
||||||
|
docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${DIND_RUNNER_NAME}:${TAG} -t ${DIND_RUNNER_NAME}:v${RUNNER_VERSION} -f dindrunner.Dockerfile .
|
||||||
|
|
||||||
|
|
||||||
docker-push:
|
docker-push:
|
||||||
docker push ${NAME}:${TAG}
|
docker push ${NAME}:${TAG}
|
||||||
docker push ${NAME}:v${RUNNER_VERSION}
|
docker push ${NAME}:v${RUNNER_VERSION}
|
||||||
|
docker push ${DIND_RUNNER_NAME}:${TAG}
|
||||||
|
docker push ${DIND_RUNNER_NAME}:v${RUNNER_VERSION}
|
||||||
|
|
||||||
docker-buildx:
|
docker-buildx:
|
||||||
export DOCKER_CLI_EXPERIMENTAL=enabled
|
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||||
@@ -39,3 +44,9 @@ docker-buildx:
|
|||||||
-t "${NAME}:latest" \
|
-t "${NAME}:latest" \
|
||||||
-f Dockerfile \
|
-f Dockerfile \
|
||||||
. ${PUSH_ARG}
|
. ${PUSH_ARG}
|
||||||
|
docker buildx build --platform ${PLATFORMS} \
|
||||||
|
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \
|
||||||
|
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \
|
||||||
|
-t "${DIND_RUNNER_NAME}:latest" \
|
||||||
|
-f dindrunner.Dockerfile \
|
||||||
|
. ${PUSH_ARG}
|
||||||
|
|||||||
100
runner/dindrunner.Dockerfile
Normal file
100
runner/dindrunner.Dockerfile
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
# Dev + DinD dependencies
|
||||||
|
RUN apt update \
|
||||||
|
&& apt install -y software-properties-common \
|
||||||
|
&& add-apt-repository -y ppa:git-core/ppa \
|
||||||
|
&& apt install -y \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
dnsutils \
|
||||||
|
ftp \
|
||||||
|
git \
|
||||||
|
iproute2 \
|
||||||
|
iptables \
|
||||||
|
iputils-ping \
|
||||||
|
jq \
|
||||||
|
libunwind8 \
|
||||||
|
locales \
|
||||||
|
netcat \
|
||||||
|
openssh-client \
|
||||||
|
parallel \
|
||||||
|
rsync \
|
||||||
|
shellcheck \
|
||||||
|
sudo \
|
||||||
|
supervisor \
|
||||||
|
telnet \
|
||||||
|
time \
|
||||||
|
tzdata \
|
||||||
|
unzip \
|
||||||
|
upx \
|
||||||
|
wget \
|
||||||
|
zip \
|
||||||
|
zstd \
|
||||||
|
&& rm -rf /var/lib/apt/list/*
|
||||||
|
|
||||||
|
# Runner user
|
||||||
|
RUN 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
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG RUNNER_VERSION=2.272.0
|
||||||
|
ARG DOCKER_CHANNEL=stable
|
||||||
|
ARG DOCKER_VERSION=19.03.13
|
||||||
|
ARG DEBUG=false
|
||||||
|
|
||||||
|
# Docker installation
|
||||||
|
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
|
||||||
|
&& if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \
|
||||||
|
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x86_64 ; fi \
|
||||||
|
&& if ! curl -L -o docker.tgz "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz"; then \
|
||||||
|
echo >&2 "error: failed to download 'docker-${DOCKER_VERSION}' from '${DOCKER_CHANNEL}' for '${ARCH}'"; \
|
||||||
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
echo "Downloaded Docker from https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz"; \
|
||||||
|
tar --extract \
|
||||||
|
--file docker.tgz \
|
||||||
|
--strip-components 1 \
|
||||||
|
--directory /usr/local/bin/ \
|
||||||
|
; \
|
||||||
|
rm docker.tgz; \
|
||||||
|
dockerd --version; \
|
||||||
|
docker --version
|
||||||
|
|
||||||
|
# Runner download supports amd64 as x64
|
||||||
|
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
|
||||||
|
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \
|
||||||
|
&& mkdir -p /runner \
|
||||||
|
&& cd /runner \
|
||||||
|
&& 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 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
|
COPY modprobe startup.sh /usr/local/bin/
|
||||||
|
COPY supervisor/ /etc/supervisor/conf.d/
|
||||||
|
COPY logger.sh /opt/bash-utils/logger.sh
|
||||||
|
COPY entrypoint.sh /usr/local/bin/
|
||||||
|
|
||||||
|
RUN chmod +x /usr/local/bin/startup.sh /usr/local/bin/entrypoint.sh /usr/local/bin/modprobe
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
VOLUME /var/lib/docker
|
||||||
|
|
||||||
|
COPY patched /runner/patched
|
||||||
|
|
||||||
|
# No group definition, as that makes it harder to run docker.
|
||||||
|
USER runner
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||||
|
CMD ["startup.sh"]
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -z "${GITHUB_URL}" ]; then
|
||||||
|
echo "Working with public GitHub" 1>&2
|
||||||
|
GITHUB_URL="https://github.com/"
|
||||||
|
else
|
||||||
|
length=${#GITHUB_URL}
|
||||||
|
last_char=${GITHUB_URL:length-1:1}
|
||||||
|
|
||||||
|
[[ $last_char != "/" ]] && GITHUB_URL="$GITHUB_URL/"; :
|
||||||
|
echo "Github endpoint URL ${GITHUB_URL}"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "${RUNNER_NAME}" ]; then
|
if [ -z "${RUNNER_NAME}" ]; then
|
||||||
echo "RUNNER_NAME must be set" 1>&2
|
echo "RUNNER_NAME must be set" 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "${RUNNER_ORG}" -a -n "${RUNNER_REPO}" ]; then
|
if [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_REPO}" ]; then
|
||||||
ATTACH="${RUNNER_ORG}/${RUNNER_REPO}"
|
ATTACH="${RUNNER_ORG}/${RUNNER_REPO}"
|
||||||
elif [ -n "${RUNNER_ORG}" ]; then
|
elif [ -n "${RUNNER_ORG}" ]; then
|
||||||
ATTACH="${RUNNER_ORG}"
|
ATTACH="${RUNNER_ORG}"
|
||||||
@@ -25,8 +36,12 @@ if [ -z "${RUNNER_TOKEN}" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -z "${RUNNER_REPO}" ] && [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_GROUP}" ];then
|
||||||
|
RUNNER_GROUP_ARG="--runnergroup ${RUNNER_GROUP}"
|
||||||
|
fi
|
||||||
|
|
||||||
cd /runner
|
cd /runner
|
||||||
./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "https://github.com/${ATTACH}" --token "${RUNNER_TOKEN}" ${LABEL_ARG}
|
./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "${GITHUB_URL}${ATTACH}" --token "${RUNNER_TOKEN}" "${RUNNER_GROUP_ARG}" "${LABEL_ARG}"
|
||||||
|
|
||||||
for f in runsvc.sh RunnerService.js; do
|
for f in runsvc.sh RunnerService.js; do
|
||||||
diff {bin,patched}/${f} || :
|
diff {bin,patched}/${f} || :
|
||||||
|
|||||||
24
runner/logger.sh
Normal file
24
runner/logger.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Logger from this post http://www.cubicrace.com/2016/03/log-tracing-mechnism-for-shell-scripts.html
|
||||||
|
|
||||||
|
function INFO(){
|
||||||
|
local function_name="${FUNCNAME[1]}"
|
||||||
|
local msg="$1"
|
||||||
|
timeAndDate=`date`
|
||||||
|
echo "[$timeAndDate] [INFO] [${0}] $msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DEBUG(){
|
||||||
|
local function_name="${FUNCNAME[1]}"
|
||||||
|
local msg="$1"
|
||||||
|
timeAndDate=`date`
|
||||||
|
echo "[$timeAndDate] [DEBUG] [${0}] $msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
function ERROR(){
|
||||||
|
local function_name="${FUNCNAME[1]}"
|
||||||
|
local msg="$1"
|
||||||
|
timeAndDate=`date`
|
||||||
|
echo "[$timeAndDate] [ERROR] $msg"
|
||||||
|
}
|
||||||
20
runner/modprobe
Normal file
20
runner/modprobe
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# "modprobe" without modprobe
|
||||||
|
# https://twitter.com/lucabruno/status/902934379835662336
|
||||||
|
|
||||||
|
# this isn't 100% fool-proof, but it'll have a much higher success rate than simply using the "real" modprobe
|
||||||
|
|
||||||
|
# Docker often uses "modprobe -va foo bar baz"
|
||||||
|
# so we ignore modules that start with "-"
|
||||||
|
for module; do
|
||||||
|
if [ "${module#-}" = "$module" ]; then
|
||||||
|
ip link show "$module" || true
|
||||||
|
lsmod | grep "$module" || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# remove /usr/local/... from PATH so we can exec the real modprobe as a last resort
|
||||||
|
export PATH='/usr/sbin:/usr/bin:/sbin:/bin'
|
||||||
|
exec modprobe "$@"
|
||||||
37
runner/startup.sh
Normal file
37
runner/startup.sh
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
source /opt/bash-utils/logger.sh
|
||||||
|
|
||||||
|
function wait_for_process () {
|
||||||
|
local max_time_wait=30
|
||||||
|
local process_name="$1"
|
||||||
|
local waited_sec=0
|
||||||
|
while ! pgrep "$process_name" >/dev/null && ((waited_sec < max_time_wait)); do
|
||||||
|
INFO "Process $process_name is not running yet. Retrying in 1 seconds"
|
||||||
|
INFO "Waited $waited_sec seconds of $max_time_wait seconds"
|
||||||
|
sleep 1
|
||||||
|
((waited_sec=waited_sec+1))
|
||||||
|
if ((waited_sec >= max_time_wait)); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO "Starting supervisor"
|
||||||
|
sudo /usr/bin/supervisord -n >> /dev/null 2>&1 &
|
||||||
|
|
||||||
|
INFO "Waiting for processes to be running"
|
||||||
|
processes=(dockerd)
|
||||||
|
|
||||||
|
for process in "${processes[@]}"; do
|
||||||
|
wait_for_process "$process"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
ERROR "$process is not running after max time"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
INFO "$process is running"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait processes to be running
|
||||||
|
entrypoint.sh
|
||||||
6
runner/supervisor/dockerd.conf
Normal file
6
runner/supervisor/dockerd.conf
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[program:dockerd]
|
||||||
|
command=/usr/local/bin/dockerd
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stderr_logfile=/var/log/dockerd.err.log
|
||||||
|
stdout_logfile=/var/log/dockerd.out.log
|
||||||
Reference in New Issue
Block a user