├── .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 | [](https://travis-ci.com/iamwwc/wwcdocker)
4 |
5 |
6 |
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 | [](https://travis-ci.com/iamwwc/wwcdocker)
4 |
5 |
6 |
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
--------------------------------------------------------------------------------