From 003622c8b6587814744a9903f3286dc1b07554c2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 20:47:24 -0700 Subject: [PATCH 1/6] Check kernel version and display warning if too low --- runtime.go | 19 ++++++++++- utils.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index 72de9f8476..d8c6d4259c 100644 --- a/runtime.go +++ b/runtime.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/auth" "io" "io/ioutil" + "log" "os" "os/exec" "path" @@ -23,6 +24,7 @@ type Runtime struct { repositories *TagStore authConfig *auth.AuthConfig idIndex *TruncIndex + kernelVersion *KernelVersionInfo } var sysInitPath string @@ -282,7 +284,22 @@ func (runtime *Runtime) restore() error { // FIXME: harmonize with NewGraph() func NewRuntime() (*Runtime, error) { - return NewRuntimeFromDirectory("/var/lib/docker") + runtime, err := NewRuntimeFromDirectory("/var/lib/docker") + if err != nil { + return nil, err + } + + k, err := GetKernelVersion() + if err != nil { + return nil, err + } + runtime.kernelVersion = k + + if CompareKernelVersion(k, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 { + log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) + } + + return runtime, nil } func NewRuntimeFromDirectory(root string) (*Runtime, error) { diff --git a/utils.go b/utils.go index 68e12b20bd..8daf404481 100644 --- a/utils.go +++ b/utils.go @@ -13,8 +13,10 @@ import ( "os/exec" "path/filepath" "runtime" + "strconv" "strings" "sync" + "syscall" "time" ) @@ -384,3 +386,95 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) } return written, err } + +type KernelVersionInfo struct { + Kernel int + Major int + Minor int + Specific int +} + +func GetKernelVersion() (*KernelVersionInfo, error) { + var uts syscall.Utsname + + if err := syscall.Uname(&uts); err != nil { + return nil, err + } + + release := make([]byte, len(uts.Release)) + + i := 0 + for _, c := range uts.Release { + release[i] = byte(c) + i++ + } + + tmp := strings.SplitN(string(release), "-", 2) + if len(tmp) != 2 { + return nil, fmt.Errorf("Unrecognized kernel version") + } + tmp2 := strings.SplitN(tmp[0], ".", 3) + if len(tmp2) != 3 { + return nil, fmt.Errorf("Unrecognized kernel version") + } + + kernel, err := strconv.Atoi(tmp2[0]) + if err != nil { + return nil, err + } + + major, err := strconv.Atoi(tmp2[1]) + if err != nil { + return nil, err + } + + minor, err := strconv.Atoi(tmp2[2]) + if err != nil { + return nil, err + } + + specific, err := strconv.Atoi(strings.Split(tmp[1], "-")[0]) + if err != nil { + return nil, err + } + + return &KernelVersionInfo{ + Kernel: kernel, + Major: major, + Minor: minor, + Specific: specific, + }, nil +} + +func (k *KernelVersionInfo) String() string { + return fmt.Sprintf("%d.%d.%d-%d", k.Kernel, k.Major, k.Minor, k.Specific) +} + +// Compare two KernelVersionInfo struct. +// Returns -1 if a < b, = if a == b, 1 it a > b +func CompareKernelVersion(a, b *KernelVersionInfo) int { + if a.Kernel < b.Kernel { + return -1 + } else if a.Kernel > b.Kernel { + return 1 + } + + if a.Major < b.Major { + return -1 + } else if a.Major > b.Major { + return 1 + } + + if a.Minor < b.Minor { + return -1 + } else if a.Minor > b.Minor { + return 1 + } + + if a.Specific < b.Specific { + return -1 + } else if a.Specific > b.Specific { + return 1 + } + return 0 +} From 640efc2ed2244dc16e161cc954af59d20a5e6ed2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 20:55:41 -0700 Subject: [PATCH 2/6] Add capabilities check to allow docker to run on kernel that does not have all options --- commands.go | 2 +- container.go | 12 +++++++++--- runtime.go | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index ba501dd5e4..6ab164d647 100644 --- a/commands.go +++ b/commands.go @@ -907,7 +907,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) } func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - config, err := ParseRun(args, stdout) + config, err := ParseRun(args, stdout, srv.runtime.capabilities) if err != nil { return err } diff --git a/container.go b/container.go index 9f175e42aa..0d3427d9c0 100644 --- a/container.go +++ b/container.go @@ -66,7 +66,7 @@ type Config struct { Image string // Name of the image as it was passed by the operator (eg. could be symbolic) } -func ParseRun(args []string, stdout io.Writer) (*Config, error) { +func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) { cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { cmd.SetOutput(ioutil.Discard) @@ -81,8 +81,8 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) { flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") - if *flMemory > 0 && NO_MEMORY_LIMIT { - fmt.Fprintf(stdout, "WARNING: This version of docker has been compiled without memory limit support. Discarding -m.") + if *flMemory > 0 && !capabilities.MemoryLimit { + fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") *flMemory = 0 } @@ -135,6 +135,12 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) { Dns: flDns, Image: image, } + + if *flMemory > 0 && !capabilities.SwapLimit { + fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + config.MemorySwap = -1 + } + // When allocating stdin in attached mode, close stdin at client disconnect if config.OpenStdin && config.AttachStdin { config.StdinOnce = true diff --git a/runtime.go b/runtime.go index d8c6d4259c..43b1a7815e 100644 --- a/runtime.go +++ b/runtime.go @@ -15,6 +15,11 @@ import ( "time" ) +type Capabilities struct { + MemoryLimit bool + SwapLimit bool +} + type Runtime struct { root string repository string @@ -24,6 +29,7 @@ type Runtime struct { repositories *TagStore authConfig *auth.AuthConfig idIndex *TruncIndex + capabilities *Capabilities kernelVersion *KernelVersionInfo } @@ -299,6 +305,13 @@ func NewRuntime() (*Runtime, error) { log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) } + _, err1 := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes") + _, err2 := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.soft_limit_in_bytes") + runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil + + _, err = ioutil.ReadFile("/sys/fs/cgroup/memory/memeory.memsw.limit_in_bytes") + runtime.capabilities.SwapLimit = err == nil + return runtime, nil } @@ -338,6 +351,7 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) { repositories: repositories, authConfig: authConfig, idIndex: NewTruncIndex(), + capabilities: &Capabilities{}, } if err := runtime.restore(); err != nil { From f68d107a1368b3d4f3342456a2b0675659688354 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 21:08:20 -0700 Subject: [PATCH 3/6] Remove the NO_MEMORY_LIMIT constant --- Makefile | 5 +---- commands.go | 3 +-- container.go | 9 +++++++-- docker/docker.go | 7 +------ runtime_test.go | 2 -- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index c89f0f33b0..a6eb613830 100644 --- a/Makefile +++ b/Makefile @@ -13,10 +13,7 @@ endif GIT_COMMIT = $(shell git rev-parse --short HEAD) GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES") -NO_MEMORY_LIMIT ?= 0 -export NO_MEMORY_LIMIT - -BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS) -X main.NO_MEMORY_LIMIT $(NO_MEMORY_LIMIT)" +BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)" SRC_DIR := $(GOPATH)/src diff --git a/commands.go b/commands.go index 6ab164d647..274acf4992 100644 --- a/commands.go +++ b/commands.go @@ -21,8 +21,7 @@ import ( const VERSION = "0.1.6" var ( - GIT_COMMIT string - NO_MEMORY_LIMIT bool + GIT_COMMIT string ) func (srv *Server) Name() string { diff --git a/container.go b/container.go index 0d3427d9c0..c23578875d 100644 --- a/container.go +++ b/container.go @@ -373,10 +373,15 @@ func (container *Container) Start() error { return err } - if container.Config.Memory > 0 && NO_MEMORY_LIMIT { - log.Printf("WARNING: This version of docker has been compiled without memory limit support. Discarding the limit.") + // Make sure the config is compatible with the current kernel + if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit { + log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") container.Config.Memory = 0 } + if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit { + log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + container.Config.MemorySwap = -1 + } if err := container.generateLXCConfig(); err != nil { return err diff --git a/docker/docker.go b/docker/docker.go index 83c47c6f1e..411e4d0c96 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -14,8 +14,7 @@ import ( ) var ( - GIT_COMMIT string - NO_MEMORY_LIMIT string + GIT_COMMIT string ) func main() { @@ -39,15 +38,11 @@ func main() { os.Setenv("DEBUG", "1") } docker.GIT_COMMIT = GIT_COMMIT - docker.NO_MEMORY_LIMIT = NO_MEMORY_LIMIT == "1" if *flDaemon { if flag.NArg() != 0 { flag.Usage() return } - if NO_MEMORY_LIMIT == "1" { - log.Printf("WARNING: This version of docker has been compiled without memory limit support.") - } if err := daemon(*pidfile); err != nil { log.Fatal(err) } diff --git a/runtime_test.go b/runtime_test.go index 20e7ee140d..48786fd5be 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -46,8 +46,6 @@ func layerArchive(tarfile string) (io.Reader, error) { } func init() { - NO_MEMORY_LIMIT = os.Getenv("NO_MEMORY_LIMIT") == "1" - // Hack to run sys init during unit testing if SelfPath() == "/sbin/init" { SysInit() From 2d32ac8cffe08b9c5d562e6ea30c796ae32b8fe1 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 21:08:33 -0700 Subject: [PATCH 4/6] Improve the docker version output --- commands.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index 274acf4992..2a73da7f6c 100644 --- a/commands.go +++ b/commands.go @@ -183,10 +183,14 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string // 'docker version': show version information func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - fmt.Fprintf(stdout, "Version:%s\n", VERSION) - fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT) - if NO_MEMORY_LIMIT { - fmt.Fprintf(stdout, "Memory limit disabled\n") + fmt.Fprintf(stdout, "Version: %s\n", VERSION) + fmt.Fprintf(stdout, "Git Commit: %s\n", GIT_COMMIT) + fmt.Fprintf(stdout, "Kernel: %s\n", srv.runtime.kernelVersion) + if !srv.runtime.capabilities.MemoryLimit { + fmt.Fprintf(stdout, "WARNING: No memory limit support\n") + } + if !srv.runtime.capabilities.SwapLimit { + fmt.Fprintf(stdout, "WARNING: No swap limit support\n") } return nil } From c42a4179fc6954a2363b181969978641553955c4 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 21:34:34 -0700 Subject: [PATCH 5/6] Add unit tests for CompareKernelVersion --- utils_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/utils_test.go b/utils_test.go index c15084f61e..1ee223ee3c 100644 --- a/utils_test.go +++ b/utils_test.go @@ -228,3 +228,36 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult) } } + +func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) { + if r := CompareKernelVersion(a, b); r != result { + t.Fatalf("Unepected kernel version comparaison result. Found %d, expected %d", r, result) + } +} + +func TestCompareKernelVersion(t *testing.T) { + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + 0) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + -1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0, Specific: 0}, + 1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 16}, + -1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + 1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Specific: 25}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + -1) +} From f3e89fae287778cb8b7056e228170e0073c9a046 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 21:57:58 -0700 Subject: [PATCH 6/6] Use mount to determine the cgroup mountpoint --- runtime.go | 11 ++++++++--- utils.go | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/runtime.go b/runtime.go index 43b1a7815e..ca850d347c 100644 --- a/runtime.go +++ b/runtime.go @@ -305,11 +305,16 @@ func NewRuntime() (*Runtime, error) { log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) } - _, err1 := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes") - _, err2 := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.soft_limit_in_bytes") + cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory") + if err != nil { + return nil, err + } + + _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "/memory.limit_in_bytes")) + _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil - _, err = ioutil.ReadFile("/sys/fs/cgroup/memory/memeory.memsw.limit_in_bytes") + _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memeory.memsw.limit_in_bytes")) runtime.capabilities.SwapLimit = err == nil return runtime, nil diff --git a/utils.go b/utils.go index 8daf404481..8763a43933 100644 --- a/utils.go +++ b/utils.go @@ -12,6 +12,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" "strconv" "strings" @@ -478,3 +479,20 @@ func CompareKernelVersion(a, b *KernelVersionInfo) int { } return 0 } + +func FindCgroupMountpoint(cgroupType string) (string, error) { + output, err := exec.Command("mount").CombinedOutput() + if err != nil { + return "", err + } + + reg := regexp.MustCompile(`^cgroup on (.*) type cgroup \(.*` + cgroupType + `[,\)]`) + for _, line := range strings.Split(string(output), "\n") { + r := reg.FindStringSubmatch(line) + if len(r) == 2 { + return r[1], nil + } + fmt.Printf("line: %s (%d)\n", line, len(r)) + } + return "", fmt.Errorf("cgroup mountpoint not found") +}