-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft: LXC support for self-hosted runners #1682
Open
earl-warren
wants to merge
6
commits into
nektos:master
Choose a base branch
from
earl-warren:wip-github-lxc
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
c7f0532
add stopHostEnvironment to tear down the LXC container
earl-warren 9c95665
shell script to start the LXC container
earl-warren 3ee8456
create and destroy a LXC container
earl-warren fbad698
run commands with lxc-attach
earl-warren 643a462
expose additional devices for docker & libvirt to work
earl-warren 2ccb312
install node 16 & git for checkout to work
earl-warren File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,16 +25,18 @@ import ( | |
) | ||
|
||
type HostEnvironment struct { | ||
Name string | ||
Path string | ||
TmpDir string | ||
ToolCache string | ||
Workdir string | ||
ActPath string | ||
Root string | ||
CleanUp func() | ||
StdOut io.Writer | ||
} | ||
|
||
func (e *HostEnvironment) Create(capAdd []string, capDrop []string) common.Executor { | ||
func (e *HostEnvironment) Create(capAdd, capDrop []string) common.Executor { | ||
return func(ctx context.Context) error { | ||
return nil | ||
} | ||
|
@@ -60,7 +62,7 @@ func (e *HostEnvironment) Copy(destPath string, files ...*FileEntry) common.Exec | |
} | ||
} | ||
|
||
func (e *HostEnvironment) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor { | ||
func (e *HostEnvironment) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor { | ||
return func(ctx context.Context) error { | ||
logger := common.Logger(ctx) | ||
srcPrefix := filepath.Dir(srcPath) | ||
|
@@ -254,7 +256,7 @@ func getEnvListFromMap(env map[string]string) []string { | |
return envList | ||
} | ||
|
||
func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline string, env map[string]string, user, workdir string) error { | ||
func (e *HostEnvironment) exec(ctx context.Context, commandparam []string, cmdline string, env map[string]string, user, workdir string) error { | ||
envList := getEnvListFromMap(env) | ||
var wd string | ||
if workdir != "" { | ||
|
@@ -266,6 +268,15 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st | |
} else { | ||
wd = e.Path | ||
} | ||
command := make([]string, len(commandparam)) | ||
copy(command, commandparam) | ||
if user == "root" { | ||
command = append([]string{"/usr/bin/sudo"}, command...) | ||
} else { | ||
common.Logger(ctx).Debugf("lxc-attach --name %v %v", e.Name, command) | ||
command = append([]string{"/usr/bin/sudo", "--preserve-env", "--preserve-env=PATH", "/usr/bin/lxc-attach", "--keep-env", "--name", e.Name, "--"}, command...) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
} | ||
|
||
f, err := lookupPathHost(command[0], env, e.StdOut) | ||
if err != nil { | ||
return err | ||
|
@@ -308,7 +319,7 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st | |
} | ||
err = cmd.Run() | ||
if err != nil { | ||
return err | ||
return fmt.Errorf("RUN %w", err) | ||
} | ||
if tty != nil { | ||
writer.AutoStop = true | ||
|
@@ -361,6 +372,14 @@ func (e *HostEnvironment) ToContainerPath(path string) string { | |
return path | ||
} | ||
|
||
func (e *HostEnvironment) GetName() string { | ||
return e.Name | ||
} | ||
|
||
func (e *HostEnvironment) GetRoot() string { | ||
return e.Root | ||
} | ||
|
||
func (e *HostEnvironment) GetActPath() string { | ||
return e.ActPath | ||
} | ||
|
@@ -414,7 +433,7 @@ func (e *HostEnvironment) GetRunnerContext(ctx context.Context) map[string]inter | |
} | ||
} | ||
|
||
func (e *HostEnvironment) ReplaceLogWriter(stdout io.Writer, stderr io.Writer) (io.Writer, io.Writer) { | ||
func (e *HostEnvironment) ReplaceLogWriter(stdout, stderr io.Writer) (io.Writer, io.Writer) { | ||
org := e.StdOut | ||
e.StdOut = stdout | ||
return org, org | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ package runner | |
import ( | ||
"archive/tar" | ||
"bufio" | ||
"bytes" | ||
"context" | ||
"crypto/rand" | ||
"crypto/sha256" | ||
|
@@ -16,6 +17,7 @@ import ( | |
"regexp" | ||
"runtime" | ||
"strings" | ||
"text/template" | ||
|
||
"github.com/mitchellh/go-homedir" | ||
"github.com/opencontainers/selinux/go-selinux" | ||
|
@@ -139,6 +141,113 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) { | |
return binds, mounts | ||
} | ||
|
||
var startTemplate = template.Must(template.New("start").Parse(`#!/bin/sh -xe | ||
lxc-create --name="{{.Name}}" --template={{.Template}} -- --release {{.Release}} $packages | ||
tee -a /var/lib/lxc/{{.Name}}/config <<'EOF' | ||
security.nesting = true | ||
lxc.cap.drop = | ||
lxc.apparmor.profile = unconfined | ||
# | ||
# /dev/net (docker won't work without /dev/net/tun) | ||
# | ||
lxc.cgroup2.devices.allow = c 10:200 rwm | ||
lxc.mount.entry = /dev/net dev/net none bind,create=dir 0 0 | ||
# | ||
# /dev/kvm (libvirt / kvm won't work without /dev/kvm) | ||
# | ||
lxc.cgroup2.devices.allow = c 10:232 rwm | ||
lxc.mount.entry = /dev/kvm dev/kvm none bind,create=file 0 0 | ||
# | ||
# /dev/loop | ||
# | ||
lxc.cgroup2.devices.allow = c 10:237 rwm | ||
lxc.cgroup2.devices.allow = b 7:* rwm | ||
lxc.mount.entry = /dev/loop-control dev/loop-control none bind,create=file 0 0 | ||
# | ||
# /dev/mapper | ||
# | ||
lxc.cgroup2.devices.allow = c 10:236 rwm | ||
lxc.mount.entry = /dev/mapper dev/mapper none bind,create=dir 0 0 | ||
# | ||
# /dev/fuse | ||
# | ||
lxc.cgroup2.devices.allow = b 10:229 rwm | ||
lxc.mount.entry = /dev/fuse dev/fuse none bind,create=file 0 0 | ||
EOF | ||
|
||
mkdir -p /var/lib/lxc/{{.Name}}/rootfs/{{ .Root }} | ||
mount --bind {{ .Root }} /var/lib/lxc/{{.Name}}/rootfs/{{ .Root }} | ||
|
||
mkdir /var/lib/lxc/{{.Name}}/rootfs/tmpdir | ||
mount --bind {{.TmpDir}} /var/lib/lxc/{{.Name}}/rootfs/tmpdir | ||
|
||
lxc-start {{.Name}} | ||
lxc-wait --name {{.Name}} --state RUNNING | ||
|
||
# | ||
# Wait for the network to come up | ||
# | ||
cat > /var/lib/lxc/{{.Name}}/rootfs/tmpdir/networking.sh <<'EOF' | ||
#!/bin/sh -xe | ||
for d in $(seq 60); do | ||
getent hosts wikipedia.org > /dev/null && break | ||
sleep 1 | ||
done | ||
getent hosts wikipedia.org | ||
EOF | ||
chmod +x /var/lib/lxc/{{.Name}}/rootfs/tmpdir/networking.sh | ||
|
||
lxc-attach --name {{.Name}} -- /tmpdir/networking.sh | ||
|
||
cat > /var/lib/lxc/{{.Name}}/rootfs/tmpdir/node.sh <<'EOF' | ||
#!/bin/sh -xe | ||
# https://github.com/nodesource/distributions#debinstall | ||
apt-get install -y curl git | ||
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - | ||
apt-get install -y nodejs | ||
EOF | ||
chmod +x /var/lib/lxc/{{.Name}}/rootfs/tmpdir/node.sh | ||
|
||
lxc-attach --name {{.Name}} -- /tmpdir/node.sh | ||
|
||
`)) | ||
Comment on lines
+144
to
+213
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be separate file an embedded into go |
||
|
||
var stopTemplate = template.Must(template.New("stop").Parse(`#!/bin/sh -x | ||
lxc-ls -1 --filter="^{{.Name}}" | while read container ; do | ||
lxc-stop --kill --name="$container" | ||
umount "/var/lib/lxc/$container/rootfs/{{ .Root }}" | ||
umount "/var/lib/lxc/$container/rootfs/tmpdir" | ||
lxc-destroy --force --name="$container" | ||
done | ||
`)) | ||
Comment on lines
+215
to
+222
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
|
||
func (rc *RunContext) stopHostEnvironment() common.Executor { | ||
return func(ctx context.Context) error { | ||
logger := common.Logger(ctx) | ||
logger.Debugf("stopHostEnvironment") | ||
|
||
var stopScript bytes.Buffer | ||
if err := stopTemplate.Execute(&stopScript, struct { | ||
Name string | ||
Root string | ||
}{ | ||
Name: rc.JobContainer.GetName(), | ||
Root: rc.JobContainer.GetRoot(), | ||
}); err != nil { | ||
return err | ||
} | ||
|
||
return common.NewPipelineExecutor( | ||
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{ | ||
Name: "workflow/stop-lxc.sh", | ||
Mode: 0755, | ||
Body: stopScript.String(), | ||
}), | ||
rc.JobContainer.Exec([]string{rc.JobContainer.GetActPath() + "/workflow/stop-lxc.sh"}, map[string]string{}, "root", rc.Config.Workdir), | ||
)(ctx) | ||
} | ||
} | ||
|
||
func (rc *RunContext) startHostEnvironment() common.Executor { | ||
return func(ctx context.Context) error { | ||
logger := common.Logger(ctx) | ||
|
@@ -154,7 +263,8 @@ func (rc *RunContext) startHostEnvironment() common.Executor { | |
cacheDir := rc.ActionCacheDir() | ||
randBytes := make([]byte, 8) | ||
_, _ = rand.Read(randBytes) | ||
miscpath := filepath.Join(cacheDir, hex.EncodeToString(randBytes)) | ||
randName := hex.EncodeToString(randBytes) | ||
miscpath := filepath.Join(cacheDir, randName) | ||
actPath := filepath.Join(miscpath, "act") | ||
if err := os.MkdirAll(actPath, 0o777); err != nil { | ||
return err | ||
|
@@ -169,6 +279,8 @@ func (rc *RunContext) startHostEnvironment() common.Executor { | |
} | ||
toolCache := filepath.Join(cacheDir, "tool_cache") | ||
rc.JobContainer = &container.HostEnvironment{ | ||
Name: randName, | ||
Root: miscpath, | ||
Path: path, | ||
TmpDir: runnerTmp, | ||
ToolCache: toolCache, | ||
|
@@ -194,7 +306,34 @@ func (rc *RunContext) startHostEnvironment() common.Executor { | |
} | ||
} | ||
|
||
var startScript bytes.Buffer | ||
if err := startTemplate.Execute(&startScript, struct { | ||
Name string | ||
Template string | ||
Release string | ||
Repo string | ||
Root string | ||
TmpDir string | ||
Script string | ||
}{ | ||
Name: rc.JobContainer.GetName(), | ||
Template: "debian", | ||
Release: "bullseye", | ||
Repo: "", // step.Environment["CI_REPO"], | ||
Root: rc.JobContainer.GetRoot(), | ||
TmpDir: runnerTmp, | ||
Script: "", // "commands-" + step.Name, | ||
}); err != nil { | ||
return err | ||
} | ||
|
||
return common.NewPipelineExecutor( | ||
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{ | ||
Name: "workflow/start-lxc.sh", | ||
Mode: 0755, | ||
Body: startScript.String(), | ||
}), | ||
rc.JobContainer.Exec([]string{rc.JobContainer.GetActPath() + "/workflow/start-lxc.sh"}, map[string]string{}, "root", rc.Config.Workdir), | ||
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{ | ||
Name: "workflow/event.json", | ||
Mode: 0o644, | ||
|
@@ -397,12 +536,22 @@ func (rc *RunContext) IsHostEnv(ctx context.Context) bool { | |
} | ||
|
||
func (rc *RunContext) stopContainer() common.Executor { | ||
return rc.stopJobContainer() | ||
return func(ctx context.Context) error { | ||
image := rc.platformImage(ctx) | ||
if strings.EqualFold(image, "-self-hosted") { | ||
return rc.stopHostEnvironment()(ctx) | ||
} | ||
return rc.stopJobContainer()(ctx) | ||
} | ||
} | ||
|
||
func (rc *RunContext) closeContainer() common.Executor { | ||
return func(ctx context.Context) error { | ||
if rc.JobContainer != nil { | ||
image := rc.platformImage(ctx) | ||
if strings.EqualFold(image, "-self-hosted") { | ||
return rc.stopHostEnvironment()(ctx) | ||
} | ||
return rc.JobContainer.Close()(ctx) | ||
} | ||
return nil | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hardcoded
sudo