Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-03-20 01:46:02 -04:00
parent cdd93563f5
commit 99c2e615ee
8 changed files with 28 additions and 306 deletions

5
.gitignore vendored
View file

@ -49,7 +49,10 @@ cross/
coverage.txt
profile.out
*.tar
rootfs
config.json
.ip
*.tar
busybox
alpine

View file

@ -1,45 +1,31 @@
# Set an output prefix, which is the local directory if not specified
PREFIX?=$(shell pwd)
# Setup name variables for the package/tool
NAME := binctr
PKG := github.com/genuinetools/$(NAME)
# Set any default go build tags
BUILDTAGS := seccomp apparmor
# Set the build dir, where built cross-compiled binaries will be output
BUILDDIR := ${PREFIX}/cross
IMAGE := alpine
IMAGE_DATA_FILE := container/bindata.go
# Populate version variables
# Add to compile time flags
VERSION := $(shell cat VERSION.txt)
GITCOMMIT := $(shell git rev-parse --short HEAD)
GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no)
ifneq ($(GITUNTRACKEDCHANGES),)
GITCOMMIT := $(GITCOMMIT)-dirty
endif
CTIMEVAR=-X $(PKG)/version.GITCOMMIT=$(GITCOMMIT) -X $(PKG)/version.VERSION=$(VERSION)
GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)"
GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
GO_LDFLAGS_STATIC=-ldflags "-w -extldflags -static"
all: clean build fmt lint test staticcheck vet ## Runs a clean, build, fmt, lint, test, staticcheck, and vet
.PHONY: build
build: $(BUILDDIR)/$(notdir $(IMAGE)) ## Builds a static executable or package
build: alpine busybox ## Builds a static executable or package
$(BUILDDIR):
@mkdir -p $@
$(BUILDDIR)/$(notdir $(IMAGE)): $(BUILDDIR) $(IMAGE_DATA_FILE) *.go VERSION.txt
.PHONY: alpine
alpine: generate
@echo "+ $@"
CGO_ENABLED=1 go build \
-tags "$(BUILDTAGS) static_build" \
${GO_LDFLAGS_STATIC} -o $@ .
@echo "Static container for $(IMAGE) created at: $@"
${GO_LDFLAGS_STATIC} -o $@ ./examples/$@/...
@echo "Static container for alpine created at: ./$@"
.PHONY: busybox
busybox: generate
@echo "+ $@"
CGO_ENABLED=1 go build \
-tags "$(BUILDTAGS) static_build" \
${GO_LDFLAGS_STATIC} -o $@ ./examples/$@/...
@echo "Static container for alpine created at: ./$@"
.PHONY: fmt
fmt: ## Verifies all files have men `gofmt`ed
@ -95,15 +81,14 @@ tag: ## Create a new git tag to prepare to build a release
git tag -sa $(VERSION) -m "$(VERSION)"
@echo "Run git push origin $(VERSION) to push your new tag to GitHub and trigger a travis build."
.PHONY: $(IMAGE_DATA_FILE)
$(IMAGE_DATA_FILE):
GOMAXPROCS=1 go generate
.PHONY: generate
generate:
GOMAXPROCS=1 go generate ./...
.PHONY: clean
clean: ## Cleanup any build binaries or packages
@echo "+ $@"
$(RM) $(NAME)
$(RM) -r $(BUILDDIR)
$(RM) alpine busybox
@sudo $(RM) -r rootfs
.PHONY: help

View file

@ -17,11 +17,6 @@ got rootless containers into upstream! Woohoo!
Check out the original thread on the
[mailing list](https://groups.google.com/a/opencontainers.org/forum/#!topic/dev/yutVaSLcqWI).
**Nginx running with my user "jessie".**
![nginx.png](nginx.png)
### Building
You will need `libapparmor-dev` and `libseccomp-dev`.
@ -30,14 +25,13 @@ Most importantly you need userns in your kernel (`CONFIG_USER_NS=y`)
or else this won't even work.
```console
$ make build
Static container created at: ./bin/alpine
Run with ./bin/alpine
# building the alpine example
$ make alpine
Static container created at: ./alpine
# building a different base image
$ make build IMAGE=busybox
Static container created at: ./bin/busybox
Run with ./bin/busybox
# building the busybox example
$ make alpine
Static container created at: ./busybox
```
### Running
@ -47,42 +41,6 @@ $ ./alpine
$ ./busybox --read-only
```
### Usage
```console
_ _ _
| |__ (_)_ __ ___| |_ _ __
| '_ \| | '_ \ / __| __| '__|
| |_) | | | | | (__| |_| |
|_.__/|_|_| |_|\___|\__|_|
Fully static, self-contained container including the rootfs
that can be run by an unprivileged user.
Embedded Image: alpine - sha256:3fd9065eaf02feaf94d68376da52541925650b81698c53c6824d92ff63f98353
Version: 0.1.0
Build: 91b3ab5-dirty
-D run in debug mode
-console-socket string
path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal
-d detach from the container's process
-hook value
Hooks to prefill into spec file. (ex. --hook prestart:netns)
-id string
container ID
-pid-file string
specify the file to write the process id to
-read-only
make container filesystem readonly
-root string
root directory of container state, should be tmpfs (default "/tmp/binctr")
-t allocate a tty for the container (default true)
-v print version and exit (shorthand)
-version
print version and exit
```
## Cool things
The binary spawned does NOT need to oversee the container process if you

View file

@ -1 +0,0 @@
0.1.0

View file

@ -1,18 +0,0 @@
// +build ignore
package main
import (
"fmt"
"os"
"github.com/genuinetools/binctr/container"
)
// Pulls an image and saves the binary data in the container package bindata.go.
func main() {
if err := container.EmbedImage("alpine"); err != nil {
fmt.Fprintf(os.Stderr, "embed image failed: %v\n", err)
os.Exit(1)
}
}

198
main.go
View file

@ -1,198 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"github.com/genuinetools/binctr/container"
"github.com/genuinetools/binctr/version"
"github.com/opencontainers/runc/libcontainer"
_ "github.com/opencontainers/runc/libcontainer/nsenter"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
const (
// BANNER is what is printed for help/info output.
BANNER = ` _ _ _
| |__ (_)_ __ ___| |_ _ __
| '_ \| | '_ \ / __| __| '__|
| |_) | | | | | (__| |_| |
|_.__/|_|_| |_|\___|\__|_|
Fully static, self-contained container including the rootfs
that can be run by an unprivileged user.
Version: %s
Build: %s
`
defaultRoot = "/tmp/binctr"
defaultRootfsDir = "rootfs"
)
var (
containerID string
pidFile string
root string
allocateTty bool
consoleSocket string
detach bool
readonly bool
hooks specs.Hooks
hookflags stringSlice
debug bool
vrsn bool
)
// stringSlice is a slice of strings
type stringSlice []string
// implement the flag interface for stringSlice
func (s *stringSlice) String() string {
return fmt.Sprintf("%s", *s)
}
func (s *stringSlice) Set(value string) error {
*s = append(*s, value)
return nil
}
func (s stringSlice) ParseHooks() (hooks specs.Hooks, err error) {
for _, v := range s {
parts := strings.SplitN(v, ":", 2)
if len(parts) <= 1 {
return hooks, fmt.Errorf("parsing %s as hook_name:exec failed", v)
}
cmd := strings.Split(parts[1], " ")
exec, err := exec.LookPath(cmd[0])
if err != nil {
return hooks, fmt.Errorf("looking up exec path for %s failed: %v", cmd[0], err)
}
hook := specs.Hook{
Path: exec,
}
if len(cmd) > 1 {
hook.Args = cmd[:1]
}
switch parts[0] {
case "prestart":
hooks.Prestart = append(hooks.Prestart, hook)
case "poststart":
hooks.Poststart = append(hooks.Poststart, hook)
case "poststop":
hooks.Poststop = append(hooks.Poststop, hook)
default:
return hooks, fmt.Errorf("%s is not a valid hook, try 'prestart', 'poststart', or 'poststop'", parts[0])
}
}
return hooks, nil
}
func init() {
// Parse flags
flag.StringVar(&containerID, "id", "binctr", "container ID")
flag.StringVar(&pidFile, "pid-file", "", "specify the file to write the process id to")
flag.StringVar(&root, "root", defaultRoot, "root directory of container state, should be tmpfs")
flag.Var(&hookflags, "hook", "Hooks to prefill into spec file. (ex. --hook prestart:netns)")
flag.BoolVar(&allocateTty, "t", true, "allocate a tty for the container")
flag.StringVar(&consoleSocket, "console-socket", "", "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal")
flag.BoolVar(&detach, "d", false, "detach from the container's process")
flag.BoolVar(&readonly, "read-only", false, "make container filesystem readonly")
flag.BoolVar(&vrsn, "version", false, "print version and exit")
flag.BoolVar(&vrsn, "v", false, "print version and exit (shorthand)")
flag.BoolVar(&debug, "D", false, "run in debug mode")
flag.Usage = func() {
fmt.Fprint(os.Stderr, fmt.Sprintf(BANNER, version.VERSION, version.GITCOMMIT))
flag.PrintDefaults()
}
flag.Parse()
if vrsn {
fmt.Printf("%s, commit: %s", version.VERSION, version.GITCOMMIT)
os.Exit(0)
}
// Set log level.
if debug {
logrus.SetLevel(logrus.DebugLevel)
}
// Parse the hook flags.
var err error
hooks, err = hookflags.ParseHooks()
if err != nil {
logrus.Fatal(err)
}
}
//go:generate go run generate.go
func main() {
if len(os.Args) > 1 && os.Args[1] == "init" {
runInit()
return
}
// Create a new container spec with the following options.
opts := container.SpecOpts{
Rootless: true,
Readonly: readonly,
Terminal: allocateTty,
Hooks: &hooks,
}
spec := container.Spec(opts)
// Initialize the container object.
c := &container.Container{
ID: containerID,
Spec: spec,
PIDFile: pidFile,
ConsoleSocket: consoleSocket,
Root: root,
Detach: detach,
Rootless: true,
}
// Unpack the rootfs.
if err := c.UnpackRootfs(defaultRootfsDir, Asset); err != nil {
logrus.Fatal(err)
}
// Run the container.
status, err := c.Run()
if err != nil {
logrus.Fatal(err)
}
// Remove the rootfs after the container has exited.
if err := os.RemoveAll(defaultRootfsDir); err != nil {
logrus.Warnf("removing rootfs failed: %v", err)
}
// Exit with the container's exit status.
os.Exit(status)
}
func runInit() {
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
factory, _ := libcontainer.New("")
if err := factory.StartInitialization(); err != nil {
// as the error is sent back to the parent there is no need to log
// or write it to stderr because the parent process will handle this
os.Exit(1)
}
panic("libcontainer: container init failed to exec")
}

BIN
nginx.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -1,7 +0,0 @@
package version
// VERSION indicates which version of the binary is running.
var VERSION string
// GITCOMMIT indicates which git hash the binary was built off of
var GITCOMMIT string