From 5a8134410d44a521dffb81e334251035f0f90abf Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Tue, 11 Apr 2023 10:58:12 +0800 Subject: [PATCH] Run as a container (#8) including Docker-in-Docker. (#84) This adds a very simple Dockerfile and run script for running `act_runner` as a container. It also allows setting `Privileged` and `ContainerOptions` flags via the new config file when spawning task containers. The combination makes it possible to use Docker-in-Docker (which requires `privileged` mode) as well as pass any other options child Docker containers may require. For example, if Gitea is running in Docker on the same machine, for the `checkout` action to behave as expected from a task container launched by `act_runner`, it might be necessary to map the hostname via something like: ``` container: network_mode: bridge privileged: true options: --add-host=my.gitea.hostname:host-gateway ``` > NOTE: Description updated to reflect latest code. > NOTE: Description updated to reflect latest code (again). Reviewed-on: https://gitea.com/gitea/act_runner/pulls/84 Reviewed-by: Lunny Xiao Reviewed-by: Jason Song Co-authored-by: Thomas E Lackey Co-committed-by: Thomas E Lackey --- Dockerfile | 17 ++++++++++ internal/app/cmd/exec.go | 3 ++ internal/app/run/runner.go | 2 ++ internal/pkg/config/config.example.yaml | 4 +++ internal/pkg/config/config.go | 4 ++- run.sh | 45 +++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100755 run.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..37c9354 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM golang:alpine as builder +RUN apk add --update-cache make git + +COPY . /opt/src/act_runner +WORKDIR /opt/src/act_runner + +RUN make clean && make build + +FROM alpine as runner +RUN apk add --update-cache \ + git bash \ + && rm -rf /var/cache/apk/* + +COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner +COPY run.sh /opt/act/run.sh + +ENTRYPOINT ["/opt/act/run.sh"] diff --git a/internal/app/cmd/exec.go b/internal/app/cmd/exec.go index 6cb87de..b6f27f7 100644 --- a/internal/app/cmd/exec.go +++ b/internal/app/cmd/exec.go @@ -48,6 +48,7 @@ type executeArgs struct { useGitIgnore bool containerCapAdd []string containerCapDrop []string + containerOptions string artifactServerPath string artifactServerAddr string artifactServerPort string @@ -375,6 +376,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command // GitHubInstance: t.client.Address(), ContainerCapAdd: execArgs.containerCapAdd, ContainerCapDrop: execArgs.containerCapDrop, + ContainerOptions: execArgs.containerOptions, AutoRemove: true, ArtifactServerPath: execArgs.artifactServerPath, ArtifactServerPort: execArgs.artifactServerPort, @@ -456,6 +458,7 @@ func loadExecCmd(ctx context.Context) *cobra.Command { execCmd.Flags().BoolVar(&execArg.useGitIgnore, "use-gitignore", true, "Controls whether paths specified in .gitignore should be copied into container") execCmd.Flags().StringArrayVarP(&execArg.containerCapAdd, "container-cap-add", "", []string{}, "kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)") execCmd.Flags().StringArrayVarP(&execArg.containerCapDrop, "container-cap-drop", "", []string{}, "kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)") + execCmd.Flags().StringVarP(&execArg.containerOptions, "container-opts", "", "", "container options") execCmd.PersistentFlags().StringVarP(&execArg.artifactServerPath, "artifact-server-path", "", ".", "Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.") execCmd.PersistentFlags().StringVarP(&execArg.artifactServerPort, "artifact-server-port", "", "34567", "Defines the port where the artifact server listens (will only bind to localhost).") execCmd.PersistentFlags().StringVarP(&execArg.defaultActionsUrl, "default-actions-url", "", "https://gitea.com", "Defines the default url of action instance.") diff --git a/internal/app/run/runner.go b/internal/app/run/runner.go index 1d9c91a..e228b9b 100644 --- a/internal/app/run/runner.go +++ b/internal/app/run/runner.go @@ -188,6 +188,8 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report. ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id), ContainerMaxLifetime: maxLifetime, ContainerNetworkMode: r.cfg.Container.NetworkMode, + ContainerOptions: r.cfg.Container.Options, + Privileged: r.cfg.Container.Privileged, DefaultActionInstance: taskContext["gitea_default_actions_url"].GetStringValue(), PlatformPicker: r.labels.PickPlatform, } diff --git a/internal/pkg/config/config.example.yaml b/internal/pkg/config/config.example.yaml index 38ffaff..12e69f8 100644 --- a/internal/pkg/config/config.example.yaml +++ b/internal/pkg/config/config.example.yaml @@ -44,3 +44,7 @@ cache: container: # Which network to use for the job containers. Could be bridge, host, none, or the name of a custom network. network_mode: bridge + # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker). + privileged: false + # And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway). + options: diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 4c3380a..30e33d2 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -35,7 +35,9 @@ type Config struct { } `yaml:"cache"` Container struct { NetworkMode string `yaml:"network_mode"` - } + Privileged bool `yaml:"privileged"` + Options string `yaml:"options"` + } `yaml:"container"` } // LoadDefault returns the default configuration. diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..aa8f456 --- /dev/null +++ b/run.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +if [[ ! -d /data ]]; then + mkdir -p /data +fi + +cd /data + +CONFIG_ARG="" +if [[ ! -z "${CONFIG_FILE}" ]]; then + CONFIG_ARG="--config ${CONFIG_FILE}" +fi + +# Use the same ENV variable names as https://github.com/vegardit/docker-gitea-act-runner + +if [[ ! -s .runner ]]; then + try=$((try + 1)) + success=0 + + # The point of this loop is to make it simple, when running both act_runner and gitea in docker, + # for the act_runner to wait a moment for gitea to become available before erroring out. Within + # the context of a single docker-compose, something similar could be done via healthchecks, but + # this is more flexible. + while [[ $success -eq 0 ]] && [[ $try -lt ${GITEA_MAX_REG_ATTEMPTS:-10} ]]; do + act_runner register \ + --instance "${GITEA_INSTANCE_URL}" \ + --token "${GITEA_RUNNER_REGISTRATION_TOKEN}" \ + --name "${GITEA_RUNNER_NAME:-`hostname`}" \ + --labels "${GITEA_RUNNER_LABELS}" \ + ${CONFIG_ARG} --no-interactive > /tmp/reg.log 2>&1 + + cat /tmp/reg.log + + cat /tmp/reg.log | grep 'Runner registered successfully' > /dev/null + if [[ $? -eq 0 ]]; then + echo "SUCCESS" + success=1 + else + echo "Waiting to retry ..." + sleep 5 + fi + done +fi + +act_runner daemon ${CONFIG_ARG}