-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit eefbab4
Showing
6 changed files
with
387 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
copy.sh | ||
tc | ||
ts |
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 |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Creating a container from scratch | ||
|
||
This repository contains the demo files for how to create a Linux container from scratch. For detailed information about the files in this repo and how to use them go to http://hectorcorrea.com/blog/tiny-container | ||
|
||
## Quick rundown | ||
|
||
If you want to compile the code follow the following steps on a Linux machine with Go installed: | ||
|
||
``` | ||
$ git clone https://github.com/hectorcorrea/tiny-container.git | ||
$ cd tiny-container | ||
$ GOOS=linux go build -o tc tinyContainer.go | ||
$ GOOS=linux go build -o ts tinyShell.go | ||
$ ./tc -root=/root/tiny-container -shell=./ts | ||
Tiny shell started | ||
ts: _ | ||
``` | ||
|
||
## Quick rundown (without the source code) | ||
|
||
Download TinyContainer (`tc`) and TinyShell (`ts`) from https://github.com/hectorcorrea/tiny-container/releases and run | ||
|
||
``` | ||
$ pwd | ||
/root/tiny-container | ||
$ ./tc -root=/root/tiny-container -shell=./ts | ||
Tiny shell started | ||
ts: _ | ||
``` | ||
|
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 |
---|---|---|
@@ -0,0 +1,2 @@ | ||
GOOS=linux go build -o tc tinyContainer.go | ||
GOOS=linux go build -o ts tinyShell.go |
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 |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Downloads BusyBox and creates symlinks for each of the | ||
# commands that it supports so that they are available | ||
# by ther Linux common names (e.g. ls, hostname, et cetera) | ||
|
||
BUSYBOX_ROOT=./bb_root/bin | ||
|
||
echo "Creating BusyBox directory: $BUSYBOX_ROOT" | ||
mkdir -p $BUSYBOX_ROOT | ||
cd $BUSYBOX_ROOT | ||
|
||
echo "Downloading BusyBox..." | ||
curl https://www.busybox.net/downloads/binaries/1.30.0-i686/busybox > busybox | ||
chmod u+x busybox | ||
|
||
echo "Creating symlinks..." | ||
for i in $(busybox --list) | ||
do | ||
if [ "$i" != "busybox" ] | ||
then | ||
ln -s busybox $i | ||
fi | ||
done | ||
|
||
echo "Done" |
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 |
---|---|---|
@@ -0,0 +1,204 @@ | ||
// This program creates a Linux container using system calls | ||
// instead of a separate tool (like Docker). The idea is to | ||
// see what functionality Linux provides by itself. | ||
// | ||
// This program MUST BE RUN on a Linux machine. | ||
// | ||
// Sources: | ||
// "Linux Containers and Virtualization: A Kernel Perspective" by Shashank Mohan Jain (p.93-106) | ||
// https://medium.com/@ssttehrani/containers-from-scratch-with-golang-5276576f9909 | ||
// https://medium.com/@jain.sm/writing-your-own-linux-container-259054465bd1 | ||
// | ||
// To compile: | ||
// $ GOOS=linux go build -o tc tinyContainer.go | ||
// | ||
// You must compile for Linux (notice GOOS=linux), it will not compile | ||
// on Mac or Windows without it. | ||
// | ||
// Usage: | ||
// $ pwd | ||
// /root/tiny-container | ||
// $ ./tc -root=/root/tiny-container -shell=./ts | ||
// Creating container... | ||
// Tiny shell started | ||
// $ ls | ||
// ... | ||
// | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"syscall" | ||
) | ||
|
||
var root, shell, xaction string | ||
|
||
func init() { | ||
flag.StringVar(&root, "root", "", "Full path of directory to mount as root in the container. Required.") | ||
flag.StringVar(&shell, "shell", "", "Path to shell program to run (relative to root once mounted). Required.") | ||
flag.StringVar(&xaction, "x-action", "create", "Used internally. Please ignore.") | ||
flag.Parse() | ||
} | ||
|
||
func main() { | ||
|
||
// Root and shell are required args | ||
if root == "" || shell == "" { | ||
printHelp() | ||
os.Exit(1) | ||
} | ||
|
||
if xaction == "create" { | ||
// Create the wrapper for the container | ||
createContainer(root, shell) | ||
os.Exit(0) | ||
} | ||
|
||
if xaction == "launch-shell" { | ||
// This is used internally to lanch the shell inside | ||
// the container. Therefore this command must be exectuted | ||
// AFTER the container has been created. | ||
runShell(root, shell) | ||
os.Exit(0) | ||
} | ||
|
||
printHelp() | ||
os.Exit(1) | ||
} | ||
|
||
func createContainer(root string, shell string) { | ||
fmt.Printf("Creating container...\n") | ||
|
||
args := []string{"-root=" + root, "-shell=" + shell, "-x-action=launch-shell"} | ||
cmd := exec.Command("/proc/self/exe", args...) | ||
cmd.Stdin = os.Stdin | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
|
||
// These flags are what instruct Linux to create a new container | ||
// (notice NEWNS, NEWUTS, ...) as it runs the command. | ||
var flags uintptr | ||
flags = syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | | ||
syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | | ||
syscall.CLONE_NEWNET | syscall.CLONE_NEWUSER | ||
|
||
cmd.SysProcAttr = &syscall.SysProcAttr{ | ||
Cloneflags: flags, | ||
UidMappings: []syscall.SysProcIDMap{ | ||
{ | ||
ContainerID: 0, | ||
HostID: os.Getuid(), | ||
Size: 1, | ||
}, | ||
}, | ||
GidMappings: []syscall.SysProcIDMap{ | ||
{ | ||
ContainerID: 0, | ||
HostID: os.Getuid(), | ||
Size: 1, | ||
}, | ||
}, | ||
} | ||
if err := cmd.Run(); err != nil { | ||
fmt.Printf("Error running the /proc/self/exe container - %s\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
fmt.Printf("Exited container\n") | ||
} | ||
|
||
func runShell(root string, shell string) { | ||
fmt.Printf("Launching shell session...\n") | ||
fmt.Printf("\troot.: %s\n", root) | ||
fmt.Printf("\tshell: %s\n", shell) | ||
|
||
cmd := exec.Command(shell) | ||
|
||
cmd.Env = []string{"tiny_demo=something tiny"} | ||
cmd.Stdin = os.Stdin | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
|
||
// Set the hostname | ||
err := syscall.Sethostname([]byte("tinyhost")) | ||
if err != nil { | ||
fmt.Printf("Error setting hostname - %s\n", err) | ||
} | ||
|
||
// Pivot to our new root folder | ||
err = pivotRoot(root) | ||
if err != nil { | ||
fmt.Printf("Error running pivot_root - %s\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
// Launch the new shell session | ||
err = cmd.Run() | ||
if err != nil { | ||
fmt.Printf("Error running the shell %s - %s\n", shell, err) | ||
os.Exit(1) | ||
} | ||
|
||
fmt.Printf("Exited shell session\n") | ||
} | ||
|
||
func pivotRoot(newRoot string) error { | ||
putold := filepath.Join(newRoot, "/.pivot_root") | ||
|
||
// Bind mount `newroot` to itself. | ||
// This is a slight hack needed to satisfy the `pivot_root` | ||
// requirement that `newroot` and `putold` must not be on | ||
// the same filesystem as the current root | ||
err := syscall.Mount(newRoot, newRoot, "", syscall.MS_BIND|syscall.MS_REC, "") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// create putold directory | ||
err = os.MkdirAll(putold, 0700) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// call pivot_root | ||
err = syscall.PivotRoot(newRoot, putold) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// ensure current working directory is set to new root | ||
err = os.Chdir("/") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
//umount putold, which now lives at /.pivot_root | ||
putold = "/.pivot_root" | ||
err = syscall.Unmount(putold, syscall.MNT_DETACH) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// remove putold | ||
err = os.RemoveAll(putold) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func printHelp() { | ||
fmt.Println("tinyContainer (tc) parameters:") | ||
flag.PrintDefaults() | ||
fmt.Println("") | ||
fmt.Println("Example:") | ||
fmt.Println("") | ||
fmt.Println(" $ pwd") | ||
fmt.Println(" /root/tiny-container") | ||
fmt.Println(" $ ./tc -root=/root/tiny-container -shell=/ts") | ||
fmt.Println("") | ||
} |
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 |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Implements a tiny shell that we can use to run in our Linux container | ||
// if we don't want to import other Linux binaries. It emulates a few | ||
// basic Linux commands: `cat`, `cd`, `env`, `hostname`, `ls`, and `pwd`. | ||
// | ||
// Compile: | ||
// $ GOOS=linux go build -o ts tinyShell.go | ||
// Run: | ||
// $ ./ts | ||
// | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
func main() { | ||
fmt.Println("Tiny shell started") | ||
fmt.Println("Valid commands: cat, cd [dir], env, hostname, ls, pwd, quit") | ||
pwd, _ := filepath.Abs(".") | ||
home := pwd | ||
for true { | ||
cmd, arg := readCommand("ts: ") | ||
if cmd == "quit" || cmd == "exit" { | ||
break | ||
} | ||
|
||
switch { | ||
case cmd == "cat": | ||
cat(pwd, arg) | ||
case cmd == "env": | ||
env() | ||
case cmd == "hostname": | ||
hostname() | ||
case cmd == "ls": | ||
ls(pwd, arg) | ||
case cmd == "pwd": | ||
fmt.Printf("%s\n", pwd) | ||
case cmd == "cd": | ||
if arg == "" { | ||
pwd = cd(pwd, home) | ||
} else { | ||
pwd = cd(pwd, arg) | ||
} | ||
case cmd == "": | ||
// nothing to do | ||
default: | ||
fmt.Printf("Unknown command: %s\n", cmd) | ||
} | ||
} | ||
fmt.Println("Tiny shell ended") | ||
} | ||
|
||
func cat(pwd string, filename string) { | ||
fullname, _ := filepath.Abs(filepath.Join(pwd, filename)) | ||
bytes, err := ioutil.ReadFile(fullname) | ||
if err == nil { | ||
fmt.Printf("%s", string(bytes)) | ||
} else { | ||
fmt.Printf("Error reading %s: %s\n", fullname, err) | ||
} | ||
} | ||
|
||
func cd(pwd string, dir string) string { | ||
if filepath.IsAbs(dir) { | ||
return dir | ||
} | ||
newPwd, _ := filepath.Abs(filepath.Join(pwd, dir)) | ||
return newPwd | ||
} | ||
|
||
func env() { | ||
for _, env := range os.Environ() { | ||
fmt.Printf("%s\n", env) | ||
} | ||
} | ||
|
||
func hostname() { | ||
hostname, err := os.Hostname() | ||
if err != nil { | ||
fmt.Printf("Error: %s\n", err) | ||
} else { | ||
fmt.Printf("Hostname: %s\n", hostname) | ||
} | ||
} | ||
|
||
func ls(pwd string, dir string) { | ||
var path string | ||
if filepath.IsAbs(dir) { | ||
path = dir | ||
} else { | ||
path, _ = filepath.Abs(filepath.Join(pwd, dir)) | ||
} | ||
|
||
fmt.Printf("Files in: %s\n", path) | ||
files, err := ioutil.ReadDir(path) | ||
if err != nil { | ||
fmt.Printf("Error: %s\n", err) | ||
return | ||
} | ||
for _, f := range files { | ||
fmt.Println("\t" + f.Name()) | ||
} | ||
} | ||
|
||
func readCommand(prompt string) (string, string) { | ||
fmt.Printf("%s", prompt) | ||
reader := bufio.NewReader(os.Stdin) | ||
text, _ := reader.ReadString('\n') | ||
|
||
tokens := strings.Split(strings.TrimSpace(text), " ") | ||
cmd := tokens[0] | ||
arg := "" | ||
if len(tokens) == 2 { | ||
arg = tokens[1] | ||
} | ||
return cmd, arg | ||
} |