├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── README-zh-CN.md ├── README.md ├── assert ├── assert.go └── assert_test.go ├── cgroups ├── cgroups.go └── subsystems │ ├── cpu.go │ ├── memory.go │ ├── subsystem.go │ ├── subsystem_test.go │ ├── util.go │ └── util_test.go ├── cmd ├── clean.go ├── cmd.go ├── init.go ├── run.go └── util.go ├── common ├── common.go ├── error.go ├── exec.go ├── file.go ├── file_test.go ├── mount.go ├── mount_test.go ├── must.go └── util.go ├── container ├── container.go ├── exec.go ├── process.go ├── process_test.go └── volume.go ├── go.mod ├── go.sum ├── images └── logo.jpg ├── install.sh ├── main.go ├── main_test.go └── scripts ├── clean.py └── clean.sh /.gitignore: -------------------------------------------------------------------------------- 1 | wwcdocker 2 | .idea/ 3 | info 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.12 5 | env: 6 | - GO111MODULE=on 7 | script: 8 | - go build . 9 | 10 | # build not trigger when I tag a commit, push it branch 11 | # remove branch.only 12 | # https://stackoverflow.com/questions/30156581/travis-ci-skipping-deployment-although-commit-is-tagged 13 | 14 | notifications: 15 | email: 16 | on_success: never 17 | on_failure: always 18 | 19 | before_deploy: 20 | - git config --local user.name "${GIT_USER_NAME}" 21 | - git config --local user.email "${GIT_USER_EMAIL}" 22 | - git tag 23 | 24 | deploy: 25 | provider: releases 26 | api_key: $GITHUB_TOKEN 27 | file: "wwcdocker" 28 | skip_cleanup: true 29 | on: 30 | tags: true -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "WWCDocker", 10 | "type": "go", 11 | "request": "launch", 12 | "mode": "auto", 13 | "program": "${workspaceFolder}", 14 | // "preLaunchTask": "echo preLaunchTask", 15 | "env": {}, 16 | "args": [ 17 | "run", 18 | "-ti", 19 | "busybox", 20 | "sh" 21 | ], 22 | "useApiV1": false, 23 | "dlvLoadConfig": { 24 | "followPointers": true, 25 | "maxVariableRecurse": 1, 26 | "maxStringLen": 9999, 27 | "maxArrayValues": 64, 28 | "maxStructFields": -1 29 | } 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/usr/bin/python3" 3 | } -------------------------------------------------------------------------------- /README-zh-CN.md: -------------------------------------------------------------------------------- 1 | # WWCDocker 2 | 3 | [![Build Status](https://travis-ci.com/iamwwc/wwcdocker.svg?branch=master)](https://travis-ci.com/iamwwc/wwcdocker) 4 | 5 |

6 | logo 7 |

8 | 9 | 10 | 这是一个简易的Docker实现 11 | 12 | - namespace进行资源隔离 13 | - cgroup 资源限制 14 | - aufs作为底层文件系统与镜像的实现 15 | 16 | ### 如何安装 17 | 18 | `source <(curl -s -L https://raw.githubusercontent.com/iamwwc/wwcdocker/master/install.sh)` 19 | 20 | 然后运行如下命令试玩 :) 21 | 22 | ``` 23 | wwcdocker run -ti busybox sh 24 | ``` 25 | 26 | ### 开发 27 | 默认在 `dev` 分支开发,开发完成,测试通过之后会发布至 `master` 分支,并构建 `release` 28 | 29 | **dev 分支不保证能通过 go build** 30 | 31 | **如果想尝试,请编译 master 分支,或者在 releases 下载最新稳定版本 😜** 32 | 33 | 现在只支持 busybox 镜像,而 busybox 只配备了 `sh`,并没有 `bash` 34 | 35 | 由于 wwcdocker 并没有实现类似 `docker pull` 的机制 36 | 所以全部的镜像都需要 `docker export` 来获得完整的 `rootfs` 37 | 38 | 你可以在下面找到支持的镜像 39 | 40 | `https://github.com/iamwwc/imageshub` 41 | 42 | 后续考虑实现 docker pull 43 | 更多细节你可以在这里看到 44 | 45 | https://github.com/iamwwc/wwcdocker/issues/2 46 | 47 | 后续会逐渐添加新的功能😀 48 | 49 | ### 开发工具 50 | 51 | 1. VSCode Remote Development -SSH 52 | 2. VMWare 53 | 54 | 宿主机是Windows,本地后台运行 `Ubuntu 18.04.2 LTS` 55 | 56 | SSH挂载目录远程开发 57 | 58 | ### TODO 59 | 60 | - [ ] docker exec 61 | - [ ] docker ps 62 | - [ ] docker container stop 63 | - [ ] docker container rm 64 | - [ ] docker run --network 65 | - [ ] docker network create | rm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WWCDocker 2 | 3 | [![Build Status](https://travis-ci.com/iamwwc/wwcdocker.svg?branch=master)](https://travis-ci.com/iamwwc/wwcdocker) 4 | 5 |

6 | logo 7 |

8 | 9 | ## [中文版本](./README-zh-CN.md) 10 | 11 | A simple docker implemention 12 | 13 | - namespace for resource isolation 14 | - cgroup for resource limit 15 | - aufs for file sytem 16 | 17 | ### How to install? 18 | 19 | `source <(curl -s -L https://raw.githubusercontent.com/iamwwc/wwcdocker/master/install.sh)` 20 | 21 | After installation 22 | 23 | Try following for fun! 24 | 25 | ``` 26 | wwcdocker run -ti busybox sh 27 | ``` 28 | 29 | ### Development 30 | 31 | We develop project in dev branch. After the test done, we will merge it to master ( which means stable verison ), and deploy a release according to git tag 32 | 33 | **Dev branches are not guaranteed to be compiled** 34 | 35 | **If you want give a try, please build master branch by your own, or download latest stable version from `releases` 😜** 36 | 37 | For now, we only support `busybox` image ( which has `sh` installed by default ). 38 | 39 | You can find all images we currently supported. 40 | 41 | https://github.com/iamwwc/imageshub 42 | 43 | So 44 | 45 | ``` 46 | wwcdocker run -ti ubuntu bash 47 | ``` 48 | 49 | maybe not working. 50 | 51 | `wwcdocker` don't implement `docker pull` mechanism. All images need to use `docker export` to export complete `rootfs` by hand. 52 | 53 | We will take it into account. 54 | 55 | You can find more related discussion from here 56 | 57 | https://github.com/iamwwc/wwcdocker/issues/2 58 | 59 | New features will come in future 😀 60 | 61 | Keep a eye on it! 62 | 63 | ### Development Tools 64 | 65 | 1. Vscode Remote Development - SSH 66 | 2. VMWare 67 | 68 | My host is `Windows`, using VMware connect into VM with SSH which running in backgroud 69 | 70 | ### TODO 71 | 72 | - [ ] docker exec 73 | - [ ] docker ps 74 | - [ ] docker container stop 75 | - [ ] docker container rm 76 | - [ ] docker run --network 77 | - [ ] docker network create | rm -------------------------------------------------------------------------------- /assert/assert.go: -------------------------------------------------------------------------------- 1 | // assert 包提供基础的断言能力 2 | // 后期考虑将整个包独立出来,加入我的通用工具包,避免重复开发 3 | package assert 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "reflect" 9 | "runtime" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | /* 15 | * Golang 不支持泛型,用接口实现很难看 16 | * 2.0草稿引入的 handle 与 check 只是一种 error 的处理方案。 17 | * 我所理解是的让代码不那么难看,但并未引入一种可以打断当前 control flow 的能力 18 | * 或者说, Go 只是让你处理 error 更容易,但却并未引入异常机制(可以直接打算当前 control flow,一直回溯到上层能够处理的位置) 19 | * 而新的 Try 提案也因为社区的反对而搁浅。 20 | * 听取社区的意见固然是好事,但在一些方面一定要果断。漫天的讨论没有意义 21 | */ 22 | 23 | type Matcher struct { 24 | method reflect.Value 25 | verb string 26 | } 27 | 28 | func (m *Matcher) call(value interface{}, expect ...interface{}) bool { 29 | input := make([]reflect.Value, m.method.Type().NumIn()) 30 | input[0] = reflect.ValueOf(value) 31 | for index, v := range expect { 32 | input[index+1] = reflect.ValueOf(v) 33 | } 34 | result := m.method.Call(input) 35 | return result[0].Bool() 36 | } 37 | 38 | var ( 39 | equals = make(map[reflect.Type]*Matcher) 40 | ) 41 | 42 | func createMatcher(fn interface{}, verb string) *Matcher { 43 | value := reflect.ValueOf(fn) 44 | if value.Kind() != reflect.Func { 45 | panic("fn is not a function") 46 | } 47 | return &Matcher{ 48 | method: value, 49 | } 50 | } 51 | 52 | func storeToTable(m map[reflect.Type]*Matcher, fn reflect.Value, matcher *Matcher) { 53 | t := fn.Type().In(0) 54 | m[t] = matcher 55 | } 56 | 57 | func registerEqualsMatcher(fn interface{}, verb string) { 58 | matcher := createMatcher(fn, verb) 59 | // todo reflect.ValueOf(fn) 60 | storeToTable(equals, reflect.ValueOf(fn), matcher) 61 | } 62 | 63 | func IsEquals(a, b interface{}) bool { 64 | t := reflect.TypeOf(b) 65 | return equals[t].call([]interface{}{a, b}) 66 | } 67 | 68 | func init() { 69 | registerEqualsMatcher(func(a, b int) bool { 70 | return a == b 71 | }, "equals to") 72 | registerEqualsMatcher(func(a, b string) bool { 73 | return a == b 74 | }, "equals to") 75 | } 76 | 77 | type Assert func(value interface{}, matcher *Matcher, expect ...interface{}) 78 | 79 | func With(t *testing.T) Assert { 80 | return func(value interface{}, matcher *Matcher, expect ...interface{}) { 81 | if !matcher.call(value, expect) { 82 | // 打印错误信息 83 | msg := []interface{}{"Not true that (", value, ")", matcher.verb, "( "} 84 | msg = append(msg, expect...) 85 | msg = append(msg, ")") 86 | fmt.Println(msg) 87 | t.FailNow() 88 | } 89 | } 90 | } 91 | 92 | // getCaller return file name and line number 93 | func getCaller() (string, int) { 94 | stackLevel := 1 95 | for { 96 | _, file, line, ok := runtime.Caller(stackLevel) 97 | if strings.Contains(file, "assert") { 98 | stackLevel++ 99 | } else { 100 | if ok { 101 | if index := strings.LastIndex(file, "/"); index >= 0 { 102 | file = file[index+1:] 103 | } else if index := strings.LastIndex(file, "\\"); index >= 0 { 104 | file = file[index+2:] 105 | } 106 | } else { 107 | file = "??" 108 | line = 1 109 | } 110 | return file, line 111 | } 112 | } 113 | } 114 | 115 | // decorate 格式化输出。 116 | func decorate(s string) string { 117 | file, line := getCaller() 118 | buf := new(bytes.Buffer) 119 | // Every line is indented at least one tab. 120 | buf.WriteString(" ") 121 | fmt.Fprintf(buf, "%s:%d: ", file, line) 122 | lines := strings.Split(s, "\n") 123 | if l := len(lines); l > 1 && lines[l-1] == "" { 124 | lines = lines[:l-1] 125 | } 126 | for i, line := range lines { 127 | if i > 0 { 128 | buf.WriteString("\n\t\t") 129 | } 130 | buf.WriteString(line) 131 | } 132 | buf.WriteByte('\n') 133 | return buf.String() 134 | } 135 | -------------------------------------------------------------------------------- /assert/assert_test.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestIntEquals(t *testing.T) { 9 | var a int 10 | var b int 11 | a = 1 12 | b = 1 13 | result := IsEquals(a, b) 14 | t.Log(result) 15 | s1 := "a" 16 | s2 := "a" 17 | result = IsEquals(s1, s2) 18 | a = 1 19 | } 20 | 21 | func TestTypeIsEquals(t *testing.T) { 22 | a := func(b interface{}) { 23 | 24 | } 25 | b := func(c int) { 26 | 27 | } 28 | 29 | t1 := reflect.TypeOf(a) 30 | t2 := reflect.TypeOf(b) 31 | r := t1 == t2 32 | t.Log(r) 33 | } 34 | -------------------------------------------------------------------------------- /cgroups/cgroups.go: -------------------------------------------------------------------------------- 1 | package cgroups 2 | 3 | import ( 4 | sub "github.com/iamwwc/wwcdocker/cgroups/subsystems" 5 | ) 6 | 7 | func CreateCgroup(id string, pid int) error { 8 | for _, sub := range sub.Subsystems { 9 | if err := sub.Apply(id, pid); err != nil { 10 | return err 11 | } 12 | } 13 | return nil 14 | } 15 | 16 | func RemoveFromCgroup(id string) error { 17 | for _, sub := range sub.Subsystems { 18 | if err := sub.Remove(id); err != nil { 19 | return err 20 | } 21 | } 22 | return nil 23 | } 24 | 25 | func CreateAndSetLimit(id string, pid int, config *sub.ResourceConfig) error { 26 | if err := CreateCgroup(id, pid); err != nil { 27 | return err 28 | } 29 | 30 | if err := SetResourceLimit(id, config); err != nil { 31 | return err 32 | } 33 | return nil 34 | } 35 | 36 | func SetResourceLimit(id string, config *sub.ResourceConfig) error { 37 | for _, sub := range sub.Subsystems { 38 | if err := sub.SetLimit(id, config); err != nil { 39 | return err 40 | } 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /cgroups/subsystems/cpu.go: -------------------------------------------------------------------------------- 1 | package subsystems 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | ) 8 | 9 | type cpuSubsystem struct { 10 | } 11 | 12 | func (c *cpuSubsystem) SetLimit(id string, config *ResourceConfig) error { 13 | cpath, err := GetCgroupPath(c.Name(), id) 14 | if err != nil { 15 | return err 16 | } 17 | limit := config.CPUShares 18 | if limit == "" { 19 | return nil 20 | } 21 | if err := ioutil.WriteFile(path.Join(cpath, "cpu.shares"), []byte(limit), 0644); err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | 27 | // Apply create a hierarchy 28 | // id is container id 29 | func (c *cpuSubsystem) Apply(id string, pid int) error { 30 | // cpath /sys/fs/cgroup/wwcdocker/1234/ 31 | cpath, err := GetCgroupPath(c.Name(), id) 32 | if err != nil { 33 | return err 34 | } 35 | return createCgroup(cpath, pid) 36 | } 37 | 38 | // Remove delete process from cgroup 39 | func (c *cpuSubsystem) Remove(id string) error { 40 | cpath, err := GetCgroupPath(c.Name(), id) 41 | if err != nil { 42 | return err 43 | } 44 | return os.RemoveAll(cpath) 45 | } 46 | 47 | func (c *cpuSubsystem) Name() string { 48 | return "cpu" 49 | } 50 | -------------------------------------------------------------------------------- /cgroups/subsystems/memory.go: -------------------------------------------------------------------------------- 1 | package subsystems 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | ) 8 | 9 | type memorySubsystem struct{} 10 | 11 | // Apply create new cgroup 12 | func (m *memorySubsystem) Apply(id string, pid int) error { 13 | cpath, err := GetCgroupPath(m.Name(), id) 14 | if err != nil { 15 | return err 16 | } 17 | return createCgroup(cpath, pid) 18 | } 19 | 20 | func (m *memorySubsystem) Remove(id string) error { 21 | cpath, err := GetCgroupPath(m.Name(), id) 22 | if err != nil { 23 | return err 24 | } 25 | return os.RemoveAll(cpath) 26 | } 27 | 28 | func (m *memorySubsystem) SetLimit(id string, config *ResourceConfig) error { 29 | cpath, err := GetCgroupPath(m.Name(), id) 30 | if err != nil { 31 | return err 32 | } 33 | limitInBytes := config.MemLimit 34 | if limitInBytes == "" { 35 | return nil 36 | } 37 | if err := ioutil.WriteFile(path.Join(cpath, "memory.limit_in_bytes"), []byte(limitInBytes),0644); err != nil { 38 | return err 39 | } 40 | return nil 41 | } 42 | 43 | func (m *memorySubsystem) Name() string { 44 | return "memory" 45 | } 46 | -------------------------------------------------------------------------------- /cgroups/subsystems/subsystem.go: -------------------------------------------------------------------------------- 1 | package subsystems 2 | 3 | 4 | var ( 5 | Subsystems = []subsystem { 6 | &cpuSubsystem{}, 7 | &memorySubsystem{}, 8 | } 9 | ) 10 | 11 | type subsystem interface{ 12 | // Name returns name of cgroup 13 | Name() string 14 | // Set sets resource limits 15 | SetLimit(id string, c *ResourceConfig) error 16 | // Apply create a cgroup 17 | Apply(id string, pid int) error 18 | // Remove delete process from this cgroup 19 | Remove(id string) error 20 | } 21 | 22 | type ResourceConfig struct { 23 | CPUSet string 24 | CPUShares string 25 | MemLimit string 26 | } 27 | -------------------------------------------------------------------------------- /cgroups/subsystems/subsystem_test.go: -------------------------------------------------------------------------------- 1 | package subsystems 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "strconv" 8 | "testing" 9 | ) 10 | 11 | func TestSubsystems(t *testing.T) { 12 | p := "/sys/fs/cgroup/memory/test1" 13 | f := "tasks" 14 | 15 | err := os.MkdirAll(p, 0644) 16 | 17 | if err != nil { 18 | t.Log(err.Error()) 19 | } 20 | 21 | pid := os.Getpid() 22 | 23 | if err := ioutil.WriteFile(path.Join(p, f), []byte(strconv.Itoa(pid)), 0644); err != nil { 24 | t.Failed() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cgroups/subsystems/util.go: -------------------------------------------------------------------------------- 1 | package subsystems 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | const ( 17 | subSystem = "/([\\w|,]+?)/wwcdocker/" 18 | ) 19 | 20 | func createCgroup(cpath string, pid int) error { 21 | if err := os.MkdirAll(cpath, 0644); err != nil { 22 | return fmt.Errorf("Failed to create cgroup %s, error: %v", cpath, err) 23 | } 24 | r := regexp.MustCompile(subSystem) 25 | subSystemName := r.FindStringSubmatch(cpath)[1] 26 | 27 | taskpath := path.Join(cpath, "tasks") 28 | if err := ioutil.WriteFile(taskpath,[]byte(strconv.Itoa(pid)),0644); err != nil { 29 | log.Error(err) 30 | return err 31 | } 32 | log.Debugf("Limit Resource in %s", subSystemName) 33 | return nil 34 | } 35 | 36 | func GetCgroupPath(subSysName string, containerId string) (string, error) { 37 | cgroupDir := FindCgroupMountPoint(subSysName) 38 | return path.Join(cgroupDir, "wwcdocker", containerId), nil 39 | } 40 | 41 | func FindCgroupMountPoint(subsystem string) string { 42 | file, err := os.Open("/proc/self/mountinfo") 43 | if err != nil { 44 | return "" 45 | } 46 | defer file.Close() 47 | scanner := bufio.NewScanner(file) 48 | 49 | // / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,memory 50 | for scanner.Scan() { 51 | text := scanner.Text() 52 | fields := strings.Split(text, " ") 53 | for _, opt := range strings.Split(fields[len(fields)-1], ",") { 54 | if opt == subsystem { 55 | return fields[4] 56 | } 57 | } 58 | } 59 | if err := scanner.Err(); err != nil { 60 | return "" 61 | } 62 | return "" 63 | } 64 | -------------------------------------------------------------------------------- /cgroups/subsystems/util_test.go: -------------------------------------------------------------------------------- 1 | package subsystems 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | ) 7 | 8 | func TestFindCgroupMountPoint(t *testing.T) { 9 | sub := "cpu" 10 | t.Logf("mount point is %s", FindCgroupMountPoint(sub)) 11 | } 12 | 13 | func TestRegexp(t *testing.T) { 14 | cpath := "/sys/fs/cgroup/cpu,cpuacct/wwcdocker/iwehjwqkenkwq" 15 | r := regexp.MustCompile(subSystem) 16 | subSystemName := r.FindStringSubmatch(cpath) 17 | t.Log(subSystemName) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/clean.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "syscall" 8 | 9 | "github.com/iamwwc/wwcdocker/common" 10 | log "github.com/sirupsen/logrus" 11 | "github.com/urfave/cli" 12 | ) 13 | 14 | var RemoveCommand = cli.Command{ 15 | Name: "remove", 16 | Usage: "Remove container, mount points, read layers, write layers", 17 | Action: func(context *cli.Context) error { 18 | // TOOD 19 | return nil 20 | }, 21 | } 22 | 23 | var funcs = map[string]removeFunc{ 24 | "m": removeAllMounts, 25 | "r": removeAllWriteLayers, 26 | "w": removeAllWriteLayers, 27 | } 28 | 29 | type removeFunc func() error 30 | 31 | func removeAll() error { 32 | return nil 33 | } 34 | 35 | func removeAllMounts() error { 36 | points, err := common.FindMountPoint() 37 | if err != nil { 38 | log.Error(err) 39 | } 40 | 41 | // 先解除挂载点 42 | for _, point := range points { 43 | if err := removeAMountPoint(point); err != nil { 44 | return err 45 | } 46 | } 47 | 48 | // 删除 mnt/ 文件夹 49 | root := common.ContainerMountRoot 50 | if common.NameExists(root) { 51 | return os.RemoveAll(root) 52 | } 53 | return nil 54 | } 55 | 56 | func removeAMountPoint(point string) error { 57 | if err := syscall.Unmount(point, syscall.MNT_DETACH); err != nil { 58 | log.Error(err) 59 | return err 60 | } 61 | return nil 62 | } 63 | 64 | func removeAllReadLayers() error { 65 | root := common.ContainerReadLayerRoot 66 | if !common.NameExists(root) { 67 | log.Debugf("Root read layers don't exist. %s", root) 68 | return nil 69 | } 70 | files, err := ioutil.ReadDir(root) 71 | if err != nil { 72 | log.Error(err) 73 | } 74 | for _, f := range files { 75 | p := path.Join(root, f.Name()) 76 | removeAMountPoint(p) 77 | } 78 | return nil 79 | } 80 | 81 | // 为什么哟了removeAll却出来一个removeA? 82 | // 后面添加删除单个container,所以这里包装一下,后面代码复用 83 | func removeAReadLayer(path string) error { 84 | return doRemove(path) 85 | } 86 | 87 | func removeAllWriteLayers() error { 88 | return doRemove(common.ContainerWriteLayerRoot) 89 | } 90 | 91 | func doRemove(n string) error { 92 | if common.NameExists(n) { 93 | return os.RemoveAll(n) 94 | } 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | "syscall" 9 | 10 | log "github.com/sirupsen/logrus" 11 | 12 | "github.com/iamwwc/wwcdocker/common" 13 | "github.com/iamwwc/wwcdocker/container" 14 | "github.com/urfave/cli" 15 | ) 16 | 17 | var InitCommand = cli.Command{ 18 | Name: "__DON'T__CALL__wwcdocker__init__", 19 | Usage: "Used in Container, User are forbidden to call this command", 20 | Action: func(ctx *cli.Context) error { 21 | log.Info("Init process started") 22 | // 注入的 fd 是3,不是4... 23 | // 就因为这个问题,相继引出了 24 | // https://stackoverflow.com/questions/57806908/the-task-is-removed-from-cgroup-after-the-exit 25 | // https://github.com/iamwwc/wwcdocker/issues/1 26 | // 可算搞明白僵尸进程了 27 | // 可算坑死我了 :( 28 | b, err := common.ReadFromFd(3) 29 | log.Infof("Read from parent process %s", b) 30 | if err != nil { 31 | log.Error(err) 32 | return err 33 | } 34 | 35 | setUpMount() 36 | 37 | cmdArrays := strings.Split(b, " ") 38 | absolutePath, err := exec.LookPath(cmdArrays[0]) 39 | args := cmdArrays[0:] 40 | args[0] = absolutePath 41 | log.Debugf("Found exec binary %s with cmd args %s", absolutePath, args) 42 | if err != nil { 43 | return fmt.Errorf("Fail to Lookup path %s. Error: %v", cmdArrays[0], err) 44 | } 45 | // env 在 容器里已经注入过了,这里 Environ 包含着 user 注入进来的 env 46 | // Linux execve syscall 文档上规定:http://man7.org/linux/man-pages/man2/execve.2.html 47 | // argv is an array of argument strings passed to the new program. By 48 | // convention, the first of these strings (i.e., argv[0]) should contain 49 | // the filename associated with the file being executed. 50 | // args 虽然名为参数,但 args[0] 应该是需要执行的 cmd 51 | if err := syscall.Exec(absolutePath, args, os.Environ()); err != nil { 52 | log.Error(err) 53 | return fmt.Errorf("Fail to Exec process in container. Error: %v", err) 54 | } 55 | return nil 56 | }, 57 | Hidden: true, 58 | HideHelp: true, 59 | } 60 | 61 | func setUpMount() error { 62 | pwd, err := os.Getwd() 63 | if err != nil { 64 | log.Errorf("Get current working directory error. %s", err) 65 | return err 66 | } 67 | // base := path.Dir(pwd) 68 | 69 | // syscall.Mount(base, base, "bind", syscall.MS_BIND | syscall.MS_REC, "") 70 | // if err := syscall.Mount("", base, "", syscall.MS_PRIVATE, ""); err != nil { 71 | // log.Error(err) 72 | // return err 73 | // } 74 | 75 | common.Exec("mount","--make-rprivate","/") 76 | 77 | // syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "") 78 | if err := container.PivotRoot(pwd); err != nil { 79 | log.Errorf("Error when call pivotRoot %v", err) 80 | return err 81 | } 82 | 83 | 84 | defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NODEV | syscall.MS_NOSUID 85 | if err := syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { 86 | return fmt.Errorf("Fail to mount /proc fs in container process. Error: %v", err) 87 | } 88 | return syscall.Mount("tmpfs", "/dev", "tmpfs", syscall.MS_NOSUID|syscall.MS_STRICTATIME, "mode=755") 89 | } 90 | -------------------------------------------------------------------------------- /cmd/run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/iamwwc/wwcdocker/common" 9 | "github.com/iamwwc/wwcdocker/container" 10 | "github.com/urfave/cli" 11 | ) 12 | 13 | var RunCommand = cli.Command{ 14 | Name: "run", 15 | Usage: "create a new container from given image", 16 | Action: func(ctx *cli.Context) error { 17 | if len(ctx.Args()) < 1 { 18 | return fmt.Errorf("missing container command") 19 | } 20 | 21 | var cmdArray []string 22 | // cmdArray[0] is image name 23 | // ubuntu bash 24 | // 0 1 25 | for _, arg := range ctx.Args() { 26 | cmdArray = append(cmdArray, arg) 27 | } 28 | imageName := cmdArray[0] 29 | enableTTY := ctx.Bool("ti") 30 | detachContainer := ctx.Bool("d") 31 | if enableTTY && detachContainer { 32 | return fmt.Errorf("ti and d args cannot both provided") 33 | } 34 | 35 | name := ctx.String("name") 36 | envs := ctx.StringSlice("env") 37 | id := common.GetRandomNumber() 38 | volumepoints := make(map[string]string) 39 | for _, point := range ctx.StringSlice("v") { 40 | p := strings.Split(point, ":") 41 | volumepoints[p[0]] = p[1] 42 | } 43 | info := &container.ContainerInfo{ 44 | Name: name, 45 | ID: id, 46 | Rm: ctx.Bool("rm"), 47 | EnableTTY: enableTTY, 48 | Detach: detachContainer, 49 | Env: append(os.Environ(), envs...), 50 | VolumePoints: volumepoints, 51 | InitCmd: cmdArray[1:], 52 | ImageName: imageName, 53 | ResourceLimit: parseResourceLimitFromcli(ctx), 54 | } 55 | return container.Run(info) 56 | }, 57 | Flags: []cli.Flag{ 58 | cli.BoolFlag{ 59 | Name: "ti", 60 | Usage: "enable tty", 61 | }, 62 | cli.BoolFlag{ 63 | Name: "d", 64 | Usage: "detach container", 65 | }, 66 | cli.StringFlag{ 67 | Name: "mem", 68 | Usage: "memery limit (mb)", 69 | }, 70 | cli.StringFlag{ 71 | Name: "cpushares", 72 | Usage: "cpu shares", 73 | }, 74 | cli.StringFlag{ 75 | Name: "name", 76 | Usage: "container name", 77 | }, 78 | cli.StringSliceFlag{ 79 | Name: "env", 80 | Usage: "environment variables", 81 | }, 82 | // cli.BoolFlag{ 83 | // Name: "rm", 84 | // Usage: "Remove container after container stopped", 85 | // } 86 | cli.StringSliceFlag{ 87 | Name: "v", 88 | Usage: "mount volume", 89 | }, 90 | }, 91 | } 92 | -------------------------------------------------------------------------------- /cmd/util.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/iamwwc/wwcdocker/cgroups/subsystems" 5 | "github.com/urfave/cli" 6 | ) 7 | 8 | func parseResourceLimitFromcli(ctx *cli.Context) (config *subsystems.ResourceConfig) { 9 | memLimits := ctx.String("mem") 10 | cpushares := ctx.String("cpushares") 11 | return &subsystems.ResourceConfig{ 12 | CPUShares: cpushares, 13 | MemLimit: memLimits, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | // WwcdockerRoot is container root 5 | WwcdockerRoot = "/var/lib/wwcdocker/" 6 | ContainerMountRoot = WwcdockerRoot + "mnt" 7 | ContainerWriteLayerRoot = WwcdockerRoot + "writelayers" 8 | ContainerReadLayerRoot = WwcdockerRoot + "readlayers" 9 | DefaultContainerLogLocation = WwcdockerRoot + "log" 10 | DefaultContainerInfoDir = WwcdockerRoot + "info" 11 | ) -------------------------------------------------------------------------------- /common/error.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | var instance = log.New() 11 | 12 | // TODO 13 | func LogAndErrorf(format string, a ...interface{}) error { 14 | instance.Formatter = &log.TextFormatter{ 15 | CallerPrettyfier: func(r *runtime.Frame) (string, string) { 16 | return "", "" 17 | }, 18 | } 19 | instance.Errorf(format, a) 20 | return fmt.Errorf("", a...) 21 | } 22 | -------------------------------------------------------------------------------- /common/exec.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "os/exec" 4 | 5 | // Exec run special cmd, block until cmd exits 6 | func Exec(cmd string, args ...string) ([]byte, error) { 7 | return exec.Command(cmd, args...).CombinedOutput() 8 | } 9 | -------------------------------------------------------------------------------- /common/file.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "io/ioutil" 5 | "strconv" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | // GetAllFdsOfProcess return all file descriptor of process 11 | func GetAllFdsOfProcess(path string) (fds []int) { 12 | result, err := ioutil.ReadDir(path) 13 | if err != nil { 14 | log.Error(err) 15 | } 16 | for _, fd := range result { 17 | f, err := strconv.ParseInt(fd.Name(), 10, 32) 18 | if err != nil { 19 | log.Errorf("Convert %s to int failed, Error Msg %v", fd.Name(), err) 20 | return 21 | } 22 | fds = append(fds, int(f)) 23 | } 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /common/file_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFile(t *testing.T) { 8 | path := "/proc/1/fd" 9 | _ = GetAllFdsOfProcess(path) 10 | } 11 | -------------------------------------------------------------------------------- /common/mount.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "syscall" 7 | 8 | ) 9 | 10 | var ( 11 | // Mount is shortcut of syscall.Mount 12 | Mount = syscall.Mount 13 | // Unmount is shortcut of syscall.Unmount 14 | Unmount = syscall.Unmount 15 | ) 16 | 17 | // FindMountPoint gets all wwcdocker mount point 18 | // like /var/run/wwcdocker/mnt/balabalabala 19 | func FindMountPoint() ([]string, error) { 20 | v := Must2(Exec("mount")) 21 | switch tp := v.(type) { 22 | case string: 23 | return parseMountInfo(tp),nil 24 | default: 25 | // TODO: 26 | // replace fmt.Errorf by LogAndErrorf 27 | return nil, fmt.Errorf("Unexpected type: %T", tp) 28 | } 29 | } 30 | 31 | func parseMountInfo(info string) (result []string) { 32 | arrays := strings.Split(info, "\n") 33 | root := WwcdockerRoot 34 | 35 | for _, value := range arrays { 36 | point := strings.Split(value, " ")[2] 37 | if index := strings.Index(point, root); index != -1 { 38 | result = append(result, point) 39 | } 40 | } 41 | return result 42 | } 43 | -------------------------------------------------------------------------------- /common/mount_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "testing" 4 | 5 | func TestFindMountPoint(t *testing.T) { 6 | result, err := FindMountPoint() 7 | Must(err) 8 | t.Logf("Mountinfo are %s",result) 9 | } 10 | -------------------------------------------------------------------------------- /common/must.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | 4 | // Must2 panics if the second parameter is not nil, Otherwise returns the first parameter 5 | func Must2(v interface{}, err error ) interface{}{ 6 | Must(err) 7 | return v 8 | } 9 | 10 | // Must panics if err is not nil 11 | func Must(err error) { 12 | if err != nil { 13 | panic(err) 14 | } 15 | } -------------------------------------------------------------------------------- /common/util.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | log "github.com/sirupsen/logrus" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | func GetRandomNumber() string { 12 | s, err := uuid.NewRandom() 13 | if err != nil { 14 | log.Warnf("UUID error. %v",err) 15 | } 16 | return s.String() 17 | } 18 | 19 | func NameExists(path string) bool { 20 | _, err := os.Stat(path) 21 | if err == nil { 22 | return true 23 | } 24 | if os.IsExist(err) { 25 | return true 26 | } 27 | return false 28 | } 29 | 30 | func ReadFromFd(fd uintptr) (string, error) { 31 | f := os.NewFile(fd, "cmdInit") 32 | b, err := ioutil.ReadAll(f) 33 | if err != nil { 34 | return "", fmt.Errorf("Failed to read from fd %d, error: %v", fd, err) 35 | } 36 | return string(b), nil 37 | } 38 | 39 | 40 | const ( 41 | quiet = "-q" 42 | directory = "-P" 43 | verbose = "-v" 44 | ) 45 | 46 | // Use wget download images 47 | func DownloadFromUrl(url string, savedDir string) { 48 | if _, err := Exec("wget",verbose,directory,savedDir,url); err != nil { 49 | log.Error(err) 50 | } 51 | } -------------------------------------------------------------------------------- /container/container.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "path" 5 | 6 | sub "github.com/iamwwc/wwcdocker/cgroups/subsystems" 7 | "github.com/iamwwc/wwcdocker/common" 8 | ) 9 | 10 | // ContainerInfo has all container information 11 | type ContainerInfo struct { 12 | Name string `json:"name"` 13 | ID string `json:"id"` 14 | Pid int `json:"pid"` 15 | ImageName string `json:"imageName"` 16 | Rm bool `json:"rm"` // Remove container after container stopped 17 | Env []string `json:"env"` 18 | VolumePoints map[string]string `json:"volumePoints"` 19 | InitCmd []string `json:"initCmd"` 20 | CreateTime string `json:"createTime"` 21 | ResourceLimit *sub.ResourceConfig `json:"resourceLimit"` 22 | EnableTTY bool `json:"enableTty"` 23 | Detach bool `json:"detach"` 24 | FilePath map[string]string `json:"filePath"` 25 | } 26 | 27 | func getCwdFromID(id string) string { 28 | return path.Join(common.ContainerMountRoot, id) 29 | } 30 | -------------------------------------------------------------------------------- /container/exec.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "os/exec" 7 | "path" 8 | "time" 9 | 10 | "github.com/iamwwc/wwcdocker/cgroups" 11 | "github.com/iamwwc/wwcdocker/common" 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | // ExecProcess exec container process and run it 16 | func ExecProcess(process *exec.Cmd, info *ContainerInfo) error { 17 | now := time.Now().Format("2019-09-03 16:36:05") 18 | info.CreateTime = now 19 | if err := process.Start(); err != nil { 20 | log.Errorf("Failed to Start Process, %v", err) 21 | return err 22 | } 23 | pid := process.Process.Pid 24 | info.Pid = pid 25 | 26 | return cgroups.CreateAndSetLimit(info.ID, pid, info.ResourceLimit) 27 | } 28 | 29 | func recordContainerInfo(info *ContainerInfo) error { 30 | base := path.Dir(common.DefaultContainerInfoDir) 31 | if err := os.MkdirAll(base, 0644); err != nil { 32 | return err 33 | } 34 | 35 | name := path.Base(common.DefaultContainerInfoDir) 36 | infoFile, err := os.Create(name) 37 | 38 | if err != nil { 39 | return err 40 | } 41 | 42 | i, err := json.Marshal(info) 43 | if err != nil { 44 | return err 45 | } 46 | content := string(i) 47 | _, err = infoFile.WriteString(content) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /container/process.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path" 8 | "strings" 9 | "syscall" 10 | 11 | "github.com/iamwwc/wwcdocker/common" 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | // Run start container run 16 | func Run(info *ContainerInfo) error { 17 | process, writePipe := GetContainerProcess(info) 18 | ExecProcess(process, info) 19 | _, err := writePipe.WriteString(strings.Join(info.InitCmd, " ")) 20 | if err != nil { 21 | log.Error(err) 22 | } 23 | writePipe.Close() 24 | err = recordContainerInfo(info) 25 | if info.EnableTTY { 26 | process.Wait() 27 | } 28 | return err 29 | } 30 | 31 | // GetContainerProcess returns cmd and writePipe. 32 | func GetContainerProcess(info *ContainerInfo) (*exec.Cmd, *os.File) { 33 | readPipe, writePipe, err := NewFilePipe() 34 | if err != nil { 35 | log.Errorln(err) 36 | return nil, nil 37 | } 38 | 39 | initCmd, err := os.Readlink("/proc/self/exe") 40 | 41 | cmd := exec.Command(initCmd, "__DON'T__CALL__wwcdocker__init__") 42 | cmd.SysProcAttr = &syscall.SysProcAttr{ 43 | Cloneflags: syscall.CLONE_NEWUTS | 44 | syscall.CLONE_NEWIPC | 45 | syscall.CLONE_NEWNET | 46 | syscall.CLONE_NEWPID | 47 | syscall.CLONE_NEWNS, 48 | } 49 | cmd.Env = info.Env 50 | cmd.ExtraFiles = []*os.File{readPipe} 51 | if info.EnableTTY { 52 | cmd.Stdin = os.Stdin 53 | cmd.Stdout = os.Stdout 54 | cmd.Stderr = os.Stderr 55 | } else { 56 | logDir := path.Join(common.DefaultContainerLogLocation, info.ID) 57 | // x 1 58 | // w 2 59 | // r 4 60 | if err := os.MkdirAll(logDir, 0622); err != nil { 61 | log.Errorf("Failed to create container %s Log folder, cause: [%s]", info.ID, err) 62 | return nil, nil 63 | } 64 | logFilePath := path.Join(logDir, info.ID) 65 | logFile, err := os.Create(logFilePath) 66 | if err != nil { 67 | log.Error(err) 68 | return nil, nil 69 | } 70 | cmd.Stdout = logFile 71 | info.FilePath["logFolder"] = logDir 72 | info.FilePath["logFile"] = logFilePath 73 | } 74 | cwd := NewWorkspace(common.ContainerMountRoot, info.ID, info.ImageName, info.VolumePoints) 75 | cmd.Dir = cwd 76 | return cmd, writePipe 77 | } 78 | 79 | // NewFilePipe Create a File Pipe, 80 | func NewFilePipe() (*os.File, *os.File, error) { 81 | reader, writer, err := os.Pipe() 82 | if err != nil { 83 | return nil, nil, err 84 | } 85 | return reader, writer, nil 86 | } 87 | 88 | func PivotRoot(rootfs string) error { 89 | // 可算被我找到了 90 | // https://github.com/torvalds/linux/blob/d41a3effbb53b1bcea41e328d16a4d046a508381/fs/namespace.c#L3582 91 | if err := syscall.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { 92 | log.Errorf("Mount %s to itself error, %v", rootfs, err) 93 | return err 94 | } 95 | 96 | putOld := path.Join(rootfs, ".pivotroot") 97 | if err := os.Mkdir(putOld, 0777); err != nil { 98 | log.Errorf("Failed to create putOld folder %s, error: %v", putOld, err) 99 | return err 100 | } 101 | if err := syscall.PivotRoot(rootfs, putOld); err != nil { 102 | log.Errorf("Failed to Pivot Rootfs %s, error: %v", rootfs, err) 103 | return err 104 | } 105 | log.Debug("PivotRoot done") 106 | 107 | if err := os.Chdir("/"); err != nil { 108 | return fmt.Errorf("chdir error %v", err) 109 | } 110 | 111 | old := path.Join("/", ".pivotroot") 112 | if err := syscall.Unmount(old, syscall.MNT_DETACH); err != nil { 113 | return fmt.Errorf("Unmount failed %v", err) 114 | } 115 | return os.Remove(old) 116 | } 117 | -------------------------------------------------------------------------------- /container/process_test.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import "testing" 4 | 5 | func TestPivotRoot(t *testing.T) { 6 | } 7 | -------------------------------------------------------------------------------- /container/volume.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path" 8 | "syscall" 9 | 10 | log "github.com/sirupsen/logrus" 11 | "github.com/iamwwc/wwcdocker/common" 12 | ) 13 | 14 | /** 15 | NewWorkSpace -> CreateWriteLayer -> CreateReadLayer 16 | -> CreateMountPoint -> MountVolume 17 | // mount volume into container special path 18 | */ 19 | 20 | // MountVolume mounts volume into container 21 | func MountVolume(parentPath, containerPath, id string) error { 22 | // 将 parent 挂载到 container 的工作目录下的 url中 23 | if err := os.MkdirAll(parentPath, 0777); err != nil { 24 | return err 25 | } 26 | cwd := getCwdFromID(id) 27 | mountPointInContainer := path.Join(cwd, containerPath) 28 | if err := os.MkdirAll(mountPointInContainer, 0700); err != nil { 29 | return fmt.Errorf("Failed to create volume in container. Error: %v", err) 30 | } 31 | dirs := "dirs=" + parentPath 32 | if err := syscall.Mount(parentPath, containerPath, "aufs", 0, dirs); err != nil { 33 | return err 34 | } 35 | return nil 36 | } 37 | 38 | // NewWorkspace create new container working directory 39 | func NewWorkspace(root, containerID,imageName string, volumes map[string]string) string { 40 | wlayerPath := path.Join(common.ContainerWriteLayerRoot, containerID) 41 | rlayerPath := path.Join(common.ContainerReadLayerRoot, containerID) 42 | createNewWriteLayer(wlayerPath) 43 | createNewReadLayer(rlayerPath,imageName) 44 | 45 | mountpath := getCwdFromID(containerID) 46 | // 将 write layer 与 read layer 组合挂载成aufs文件系统 47 | if err := createMountPoint(mountpath, wlayerPath, rlayerPath); err != nil { 48 | log.Errorf("Fail to mount writelayer and read layer. Error: %v", err) 49 | } 50 | if len(volumes) > 0 { 51 | for k, v := range volumes { 52 | if k != "" && v != "" { 53 | MountVolume(k, v, containerID) 54 | } 55 | log.Errorf("Invalid mount path %s:%s", k, v) 56 | continue 57 | } 58 | } 59 | return mountpath 60 | } 61 | 62 | func createNewWriteLayer(name string) error { 63 | if err := os.MkdirAll(name, 0777); err != nil { 64 | log.Error(err) 65 | return err 66 | } 67 | return nil 68 | } 69 | 70 | const ( 71 | imagesHub = "https://raw.githubusercontent.com/iamwwc/imageshub/master/" 72 | ) 73 | 74 | func findImages(imagePath, image string) string { 75 | if !common.NameExists(imagePath) { 76 | common.Must(os.MkdirAll(imagePath,0644)) 77 | } 78 | name := image + ".tar" 79 | absolutePath := path.Join(imagePath,name) 80 | if !common.NameExists(absolutePath) { 81 | common.DownloadFromUrl(imagesHub + name, imagePath) 82 | } 83 | return absolutePath 84 | } 85 | 86 | // createNewReadLayer create working folder from the given image. 87 | // root is container read layer folder 88 | // such as /var/lib/wwcdocker/readlayer/213kjassdqw/ 89 | func createNewReadLayer(root, imageLayer string) error { 90 | log.Debugf("Image: %s",imageLayer) 91 | imagePath := path.Join(common.WwcdockerRoot, "images") 92 | tarURL := findImages(imagePath,imageLayer) 93 | _, err := os.Stat(tarURL) 94 | if os.IsNotExist(err) { 95 | return fmt.Errorf("busybox.tar don't exist in %s",tarURL) 96 | } 97 | if err := os.MkdirAll(root,0644); err != nil { 98 | return err 99 | } 100 | if _, err := exec.Command("tar","-xvf",tarURL,"-C",root).CombinedOutput(); err != nil { 101 | return fmt.Errorf("untar error. %v", err) 102 | } 103 | return nil 104 | } 105 | func createMountPoint(mountpath, wlayerpath, rlayerpath string) error { 106 | // rlayerpath 就是 镜像的 只读文件夹 位置 107 | if err := os.MkdirAll(mountpath,0777); err != nil { 108 | return err 109 | } 110 | dirs := fmt.Sprintf("dirs=%s:%s", wlayerpath, rlayerpath) 111 | if _, err := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", mountpath).CombinedOutput(); err != nil { 112 | log.Error(err) 113 | return err 114 | } 115 | return nil 116 | } 117 | 118 | // DeleteWorkSpace deletes write layer, unmounts mountpoint 119 | func DeleteWorkSpace(containerID string) { 120 | deleteMountPoint(path.Join(common.ContainerMountRoot, containerID)) 121 | deleteWriteLayer(path.Join(common.ContainerWriteLayerRoot, containerID)) 122 | } 123 | 124 | func deleteMountPoint(mntpoint string) error { 125 | if err := syscall.Unmount(mntpoint,syscall.MNT_DETACH); err != nil { 126 | return err 127 | } 128 | _, err := os.Stat(mntpoint) 129 | if os.IsExist(err) { 130 | return os.RemoveAll(mntpoint) 131 | } 132 | return nil 133 | } 134 | 135 | // deleteWriteLayer deletes container write layer located on writelayer folder 136 | func deleteWriteLayer(path string) error { 137 | return os.RemoveAll(path) 138 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/iamwwc/wwcdocker 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/google/uuid v1.1.1 7 | github.com/sirupsen/logrus v1.4.2 8 | github.com/urfave/cli v1.21.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 5 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 6 | github.com/iamwwc/mydocker v0.0.0-20190828095931-7954a7c5e7ad h1:UFPAgbIm+UOb2oEokJzaHRsyh6H3Y7NaFQh9MJdOXTM= 7 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 8 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 12 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 13 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 15 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 16 | github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE= 17 | github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= 18 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 19 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 22 | -------------------------------------------------------------------------------- /images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taikulawo/wwcdocker/10ea9a3bfa42e2bc6a31d46782daac2c42340c70/images/logo.jpg -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Raw location 4 | # https://raw.githubusercontent.com/iamwwc/wwcdocker/master/install.sh 5 | 6 | # TO INSTALL this script, run following 7 | # source <(curl -s -L https://raw.githubusercontent.com/iamwwc/wwcdocker/master/install.sh) 8 | 9 | red='\033[0;31m' 10 | plain='\033[0m' 11 | 12 | [[ $EUID -ne 0 ]] && echo -e "[${red}Error${plain}] This script must be run as root!" && exit 1 13 | 14 | beforepath=$(pwd) 15 | 16 | tempdir="/tmp/wwcdockertempdir${RANDOM}" 17 | 18 | mkdir $tempdir -p 19 | 20 | cd $tempdir 21 | 22 | # download wwcdocker binary 23 | curl -s https://api.github.com/repos/iamwwc/wwcdocker/releases/latest \ 24 | | grep browser_download_url \ 25 | | cut -d '"' -f 4 \ 26 | | wget -i - 27 | 28 | cwd=$(pwd) 29 | chmod u+x wwcdocker 30 | #export to path 31 | export PATH=$PATH:${cwd} 32 | 33 | cd $beforepath 34 | 35 | green='\033[0;32m' 36 | echo -e "${green}Type wwcdocker for help :)${plain}" 37 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "runtime" 8 | "strings" 9 | 10 | "github.com/iamwwc/wwcdocker/cmd" 11 | log "github.com/sirupsen/logrus" 12 | "github.com/urfave/cli" 13 | ) 14 | 15 | func main() { 16 | app := cli.NewApp() 17 | app.Name = "wwcdocker" 18 | app.Commands = []cli.Command{ 19 | cmd.RunCommand, 20 | cmd.InitCommand, 21 | } 22 | app.Before = func(ctx *cli.Context) error { 23 | log.SetOutput(os.Stdout) 24 | log.SetReportCaller(true) 25 | log.SetLevel(log.DebugLevel) 26 | formatter := &log.TextFormatter{ 27 | CallerPrettyfier: func(f *runtime.Frame) (string, string) { 28 | filename := path.Base(f.File) 29 | filepath := strings.TrimPrefix(f.Function, "github.com/iamwwc/wwcdocker/") 30 | return fmt.Sprintf("%s()", filepath), fmt.Sprintf("%s:%d", filename, f.Line) 31 | }, 32 | } 33 | log.SetFormatter(formatter) 34 | // log.SetLevel(log.DebugLevel) 35 | return nil 36 | } 37 | if err := app.Run(os.Args); err != nil { 38 | fmt.Fprintln(os.Stdout, err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMain(t *testing.T) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /scripts/clean.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python3 2 | 3 | # 清理脚本,每次删除之前留下的cgroup,以及挂载点 4 | 5 | import re 6 | import subprocess 7 | 8 | 9 | def call(args): 10 | return subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout 11 | 12 | reg = "/var/run/wwcdocker/" 13 | 14 | points = [] 15 | if __name__ == "__main__": 16 | lines:bytes = call(["mount"]) 17 | for line in lines.decode("utf-8").split("\n"): 18 | mountpoint = line.split(" ")[2] 19 | if line == '': 20 | continue 21 | r = re.match(reg,mountpoint) 22 | if r is not None: 23 | points.append(mountpoint) -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 每次测试之前将之前的mountpoint全部卸载 4 | # 删除 wwcdocker 的cgroup目录 5 | 6 | # set -x 7 | 8 | root="/var/lib/wwcdocker/mnt" 9 | 10 | info=$(mount) 11 | 12 | # 以 wwcdocker 开头的挂载点 13 | mountpoints=() 14 | 15 | while read -r line;do 16 | point=$(echo $line | cut -d' ' -f3 -) 17 | if [[ $point =~ $root ]]; then 18 | mountpoints+=($point) 19 | fi 20 | done <<< $info 21 | 22 | # 解挂文件系统 23 | for i in "${mountpoints[@]}"; do 24 | umount $i 25 | done 26 | 27 | # 数组空格分离 28 | # 还是Python写起来方便... 29 | # 系统自带而且写起来还爽,适合当脚本写 30 | 31 | cgroups=() 32 | 33 | mountinfo=$(mount) 34 | 35 | # 获取全部的 cgroup 36 | while read -r line; do 37 | # here string 38 | # https://unix.stackexchange.com/questions/80362/what-does-mean 39 | type=$(cut -d ' ' -f1 <<< $line) 40 | path=$(cut -d ' ' -f3 <<< $line) 41 | if [[ $type == "cgroup" ]]; then 42 | sys=$(echo $path | rev | cut -d '/' -f1 | rev) 43 | cgroups+=($sys) 44 | fi 45 | done <<< $mountinfo 46 | 47 | 48 | # 删除每一个cgroup中的 wwcdocker 49 | for system in "${cgroups[@]}"; do 50 | target="/sys/fs/cgroup/${system}/wwcdocker" 51 | if [[ -d $target ]]; then 52 | printf "Removing ${system} system cgroup\n" 53 | cgdelete -r "${system}:wwcdocker" 54 | fi 55 | done --------------------------------------------------------------------------------