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 <xiaolunwen@gmail.com>
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Thomas E Lackey <telackey@bozemanpass.com>
Co-committed-by: Thomas E Lackey <telackey@bozemanpass.com>
This commit is contained in:
Thomas E Lackey 2023-04-11 10:58:12 +08:00 committed by Jason Song
parent b79c3aa1a3
commit 5a8134410d
6 changed files with 74 additions and 1 deletions

17
Dockerfile Normal file
View file

@ -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"]

View file

@ -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.")

View file

@ -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,
}

View file

@ -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:

View file

@ -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.

45
run.sh Executable file
View file

@ -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}