Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions images/actions-runner/v2.328.0-ubuntu-22.04-0/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2020 The actions-runner-controller authors.
# SPDX-FileCopyrightText: 2025 k0s authors

FROM docker.io/library/ubuntu:22.04

ARG TARGETPLATFORM
ARG RUNNER_VERSION=2.328.0
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
# Docker and Docker Compose arguments
ARG CHANNEL=stable
ARG DOCKER_VERSION=24.0.7
ARG DOCKER_COMPOSE_VERSION=v2.23.0
ARG RUNNER_USER_UID=1001
ARG DOCKER_GROUP_GID=121

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y \
&& apt-get install -y software-properties-common \
&& add-apt-repository -y ppa:git-core/ppa \
&& apt-get update -y \
&& apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
dumb-init \
git \
jq \
openssh-client \
sudo \
unzip \
zip \
&& rm -rf /var/lib/apt/lists/*

# Download latest git-lfs version
RUN curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \
apt-get install -y --no-install-recommends git-lfs

RUN adduser --disabled-password --gecos "" --uid $RUNNER_USER_UID runner \
&& groupadd docker --gid $DOCKER_GROUP_GID \
&& usermod -aG sudo runner \
&& usermod -aG docker runner \
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers \
&& echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers

ENV HOME=/home/runner

ENV RUNNER_ASSETS_DIR=/runnertmp
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x64 ; fi \
&& mkdir -p "$RUNNER_ASSETS_DIR" \
&& cd "$RUNNER_ASSETS_DIR" \
&& curl -fLo 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 \
# 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.
&& apt-get install -y libyaml-dev \
&& rm -rf /var/lib/apt/lists/*

ENV RUNNER_TOOL_CACHE=/opt/hostedtoolcache
RUN mkdir /opt/hostedtoolcache \
&& chgrp docker /opt/hostedtoolcache \
&& chmod g+rwx /opt/hostedtoolcache

RUN cd "$RUNNER_ASSETS_DIR" \
&& curl -fLo runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \
&& unzip ./runner-container-hooks.zip -d ./k8s \
&& rm -f runner-container-hooks.zip

RUN set -vx; \
export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \
&& if [ "$ARCH" = "arm" ]; then export ARCH=armhf ; fi \
&& if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \
&& curl -fLo docker.tgz https://download.docker.com/linux/static/${CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz \
&& tar zxvf docker.tgz \
&& install -o root -g root -m 755 docker/docker /usr/bin/docker \
&& rm -rf docker docker.tgz

RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \
&& if [ "$ARCH" = "arm" ]; then export ARCH=armv7 ; fi \
&& if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \
&& mkdir -p /usr/libexec/docker/cli-plugins \
&& curl -fLo /usr/libexec/docker/cli-plugins/docker-compose https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-${ARCH} \
&& chmod +x /usr/libexec/docker/cli-plugins/docker-compose \
&& ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/bin/docker-compose \
&& which docker-compose \
&& docker compose version

# We place the scripts in `/usr/bin` so that users who extend this image can
# override them with scripts of the same name placed in `/usr/local/bin`.
COPY entrypoint.sh startup.sh logger.sh graceful-stop.sh update-status /usr/bin/

# Copy the docker shim which propagates the docker MTU to underlying networks
# to replace the docker binary in the PATH.
COPY docker-shim.sh /usr/local/bin/docker

# Configure hooks folder structure.
COPY hooks /etc/arc/hooks/

# Add the Python "User Script Directory" to the PATH
ENV PATH="${PATH}:${HOME}/.local/bin/"
ENV ImageOS=ubuntu22

RUN echo "PATH=${PATH}" > /etc/environment \
&& echo "ImageOS=${ImageOS}" >> /etc/environment

USER runner

ENTRYPOINT ["/bin/bash", "-c"]
CMD ["entrypoint.sh"]
15 changes: 15 additions & 0 deletions images/actions-runner/v2.328.0-ubuntu-22.04-0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Ubuntu 24.04 GitHub Actions Runner image for k0sproject

This is a modified version of the official ARC Runner image based on commit
[0e006bb]. The following modifications were made:

- Can be built for ARMv7.
- Uses `dumb-init` from the APT repositories, so that it can be installed on
32-bit ARM.
- Add `build-essentials` to obtain a standard build environment. This includes
`make` and enables CGO usage.
- Install `openssh-client` to include the `ssh-keygen` executable.
- Setting the environment variable `DISABLE_RUNNER_DEFAULT_LABELS` allows for
skipping the addition of default labels by the runner.

[0e006bb]: https://github.com/actions/actions-runner-controller/tree/0e006bb0ff9094e54522cdf89c9bd5ab1806c4af/runner
17 changes: 17 additions & 0 deletions images/actions-runner/v2.328.0-ubuntu-22.04-0/docker-shim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

set -Eeuo pipefail

DOCKER=/usr/bin/docker
if [ ! -e $DOCKER ]; then
DOCKER=/home/runner/bin/docker
fi

if [[ ${ARC_DOCKER_MTU_PROPAGATION:-false} == true ]] &&
(($# >= 2)) && [[ $1 == network && $2 == create ]] &&
mtu=$($DOCKER network inspect bridge --format '{{index .Options "com.docker.network.driver.mtu"}}' 2>/dev/null); then
shift 2
set -- network create --opt com.docker.network.driver.mtu="$mtu" "$@"
fi

exec $DOCKER "$@"
30 changes: 30 additions & 0 deletions images/actions-runner/v2.328.0-ubuntu-22.04-0/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
source logger.sh
source graceful-stop.sh
trap graceful_stop TERM

dumb-init bash <<'SCRIPT' &
source logger.sh

startup.sh
SCRIPT

RUNNER_INIT_PID=$!
log.notice "Runner init started with pid $RUNNER_INIT_PID"
wait $RUNNER_INIT_PID
log.notice "Runner init exited. Exiting this process with code 0 so that the container and the pod is GC'ed Kubernetes soon."

if [ -f /runner/.runner ]; then
# If the runner failed with the following error:
# √ Connected to GitHub
# Failed to create a session. The runner registration has been deleted from the server, please re-configure.
# Runner listener exit with terminated error, stop the service, no retry needed.
# Exiting runner...
# It might have failed to delete the .runner file.
# We use the existence of the .runner file as the indicator that the runner agent has not stopped yet.
# Remove it by ourselves now, so that the dockerd sidecar prestop won't hang waiting for the .runner file to appear.
echo "Removing the .runner file"
rm -f /runner/.runner
fi

trap - TERM
99 changes: 99 additions & 0 deletions images/actions-runner/v2.328.0-ubuntu-22.04-0/graceful-stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/bin/bash

# This should be shorter enough than the terminationGracePeriodSeconds,
# so that the job is cancelled immediately, instead of hanging for 10 minutes or so and failing without any error message.
RUNNER_GRACEFUL_STOP_TIMEOUT=${RUNNER_GRACEFUL_STOP_TIMEOUT:-15}

graceful_stop() {
log.notice "Executing actions-runner-controller's SIGTERM handler."
log.notice "Note that if this takes more time than terminationGracePeriodSeconds, the runner will be forcefully terminated by Kubernetes, which may result in the in-progress workflow job, if any, to fail."

log.notice "Ensuring dockerd is still running."
if ! docker ps -a; then
log.warning "Detected configuration error: dockerd should be running but is already nowhere. This is wrong. Ensure that your init system to NOT pass SIGTERM directly to dockerd!"
fi

# The below procedure atomically removes the runner from GitHub Actions service,
# to ensure that the runner is not running any job.
# This is required to not terminate the actions runner agent while running the job.
# If we didn't do this atomically, we might end up with a rare race where
# the runner agent is terminated while it was about to start a job.

# `pushd`` is needed to run the config.sh successfully.
# Without this the author of this script ended up with errors like the below:
# Cannot connect to server, because config files are missing. Skipping removing runner from the server.
# Does not exist. Skipping Removing .credentials
# Does not exist. Skipping Removing .runner
if ! pushd /runner; then
log.error "Failed to pushd ${RUNNER_HOME}"
exit 1
fi

# We need to wait for the registration first.
# Otherwise a direct runner pod deletion triggered while the runner entrypoint.sh is about to register itself with
# config.sh can result in this graceful stop process to get skipped.
# In that case, the pod is eventually and forcefully terminated by ARC and K8s, resulting
# in the possible running workflow job after this graceful stop process failed might get cancelled prematurely.
log.notice "Waiting for the runner to register first."
while ! [ -f /runner/.runner ]; do
sleep 1
done
log.notice "Observed that the runner has been registered."

if ! /runner/config.sh remove --token "$RUNNER_TOKEN"; then
i=0
log.notice "Waiting for RUNNER_GRACEFUL_STOP_TIMEOUT=$RUNNER_GRACEFUL_STOP_TIMEOUT seconds until the runner agent to stop by itself."
while [[ $i -lt $RUNNER_GRACEFUL_STOP_TIMEOUT ]]; do
sleep 1
if ! pgrep Runner.Listener > /dev/null; then
log.notice "The runner agent stopped before RUNNER_GRACEFUL_STOP_TIMEOUT=$RUNNER_GRACEFUL_STOP_TIMEOUT"
break
fi
i=$((i+1))
done
fi

if ! popd; then
log.error "Failed to popd from ${RUNNER_HOME}"
exit 1
fi

if pgrep Runner.Listener > /dev/null; then
# The below procedure fixes the runner to correctly notify the Actions service for the cancellation of this runner.
# It enables you to see `Error: The operation was canceled.` in the worklow job log, in case a job was still running on this runner when the
# termination is requested.
#
# Note though, due to how Actions work, no all job steps gets `Error: The operation was canceled.` in the job step logs.
# Jobs that were still in the first `Stet up job` step` seem to get `Error: A task was canceled.`,
#
# Anyway, without this, a runer pod is "forcefully" killed by any other controller (like cluster-autoscaler) can result in the workflow job to
# hang for 10 minutes or so.
# After 10 minutes, the Actions UI just shows the failure icon for the step, without `Error: The operation was canceled.`,
# not even showing `Error: The operation was canceled.`, which is confusing.
runner_listener_pid=$(pgrep Runner.Listener)
log.notice "Sending SIGTERM to the actions runner agent ($runner_listener_pid)."
kill -TERM "$runner_listener_pid"

log.notice "SIGTERM sent. If the runner is still running a job, you'll probably see \"Error: The operation was canceled.\" in its log."
log.notice "Waiting for the actions runner agent to stop."
while pgrep Runner.Listener > /dev/null; do
sleep 1
done
fi

# This message is supposed to be output only after the runner agent output:
# 2022-08-27 02:04:37Z: Job test3 completed with result: Canceled
# because this graceful stopping logic is basically intended to let the runner agent have some time
# needed to "Cancel" it.
# At the times we didn't have this logic, the runner agent was even unable to output the Cancelled message hence
# unable to gracefully stop, hence the workflow job hanged like forever.
log.notice "The actions runner process exited."

if [ "$RUNNER_INIT_PID" != "" ]; then
log.notice "Holding on until runner init (pid $RUNNER_INIT_PID) exits, so that there will hopefully be no zombie processes remaining."
# We don't need to kill -TERM $RUNNER_INIT_PID as the init is supposed to exit by itself once the foreground process(=the runner agent) exists.
wait "$RUNNER_INIT_PID" || :
fi

log.notice "Graceful stop completed."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -u

exec update-status Idle
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -Eeuo pipefail

# shellcheck source=runner/logger.sh
source logger.sh

log.debug "Running ARC Job Completed Hooks"

for hook in /etc/arc/hooks/job-completed.d/*; do
log.debug "Running hook: $hook"
"$hook" "$@"
done
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -u

exec update-status Running "Run $GITHUB_RUN_ID from $GITHUB_REPOSITORY"
12 changes: 12 additions & 0 deletions images/actions-runner/v2.328.0-ubuntu-22.04-0/hooks/job-started.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -Eeuo pipefail

# shellcheck source=runner/logger.sh
source logger.sh

log.debug "Running ARC Job Started Hooks"

for hook in /etc/arc/hooks/job-started.d/*; do
log.debug "Running hook: $hook"
"$hook" "$@"
done
73 changes: 73 additions & 0 deletions images/actions-runner/v2.328.0-ubuntu-22.04-0/logger.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# We are not using `set -Eeuo pipefail` here because this file is sourced by
# other scripts that might not be ready for a strict Bash setup. The functions
# in this file do not require it, because they are not handling signals, have
# no external calls that can fail (printf as well as date failures are ignored),
# are not using any variables that need to be set, and are not using any pipes.

# This logger implementation can be replaced with another logger implementation
# by placing a script called `logger.sh` in `/usr/local/bin` of the image. The
# only requirement for the script is that it defines the following functions:
#
# - `log.debug`
# - `log.notice`
# - `log.warning`
# - `log.error`
# - `log.success`
#
# Each function **MUST** accept an arbitrary amount of arguments that make up
# the (unstructured) logging message.
#
# Additionally the following environment variables **SHOULD** be supported to
# disable their corresponding log entries, the value of the variables **MUST**
# not matter the mere fact that they are set is all that matters:
#
# - `LOG_DEBUG_DISABLED`
# - `LOG_NOTICE_DISABLED`
# - `LOG_WARNING_DISABLED`
# - `LOG_ERROR_DISABLED`
# - `LOG_SUCCESS_DISABLED`

# The log format is constructed in a way that it can easily be parsed with
# standard tools and simple string manipulations; pattern and example:
#
# YYYY-MM-DD hh:mm:ss.SSS $level --- $message
# 2022-03-19 10:01:23.172 NOTICE --- example message
#
# This function is an implementation detail and **MUST NOT** be called from
# outside this script (which is possible if the file is sourced).
__log() {
local color instant level

color=${1:?missing required <color> argument}
shift

level=${FUNCNAME[1]} # `main` if called from top-level
level=${level#log.} # substring after `log.`
level=${level^^} # UPPERCASE

if [[ ! -v "LOG_${level}_DISABLED" ]]; then
instant=$(date '+%F %T.%-3N' 2>/dev/null || :)

# https://no-color.org/
if [[ -v NO_COLOR ]]; then
printf -- '%s %s --- %s\n' "$instant" "$level" "$*" 1>&2 || :
else
printf -- '\033[0;%dm%s %s --- %s\033[0m\n' "$color" "$instant" "$level" "$*" 1>&2 || :
fi
fi
}

# To log with a dynamic level use standard Bash capabilities:
#
# level=notice
# command || level=error
# "log.$level" message
#
# @formatter:off
log.debug () { __log 37 "$@"; } # white
log.notice () { __log 34 "$@"; } # blue
log.warning () { __log 33 "$@"; } # yellow
log.error () { __log 31 "$@"; } # red
log.success () { __log 32 "$@"; } # green
# @formatter:on
Loading