diff --git a/Vagrantfile b/Vagrantfile index dcc3e50bdb..c823a39581 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,11 +7,11 @@ Vagrant::Config.run do |config| # please see the online documentation at vagrantup.com. # Every Vagrant virtual environment requires a box to build off of. - config.vm.box = "quantal64" + config.vm.box = "quantal64_3.5.0-25" # The url from where the 'config.vm.box' box will be fetched if it # doesn't already exist on the user's system. - config.vm.box_url = "http://unworkable.org/~niallo/quantal64.box" + config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box" # Boot with a GUI so you can see the screen. (Default is headless) # config.vm.boot_mode = :gui diff --git a/client/client.go b/client/client.go index a277a4b18f..073fe02b0b 100644 --- a/client/client.go +++ b/client/client.go @@ -111,6 +111,7 @@ func InteractiveMode(scripts ...string) error { if err != nil { return err } + defer os.Remove(rcfile.Name()) io.WriteString(rcfile, "enable -n help\n") os.Setenv("PATH", tmp+":"+os.Getenv("PATH")) os.Setenv("PS1", "\\h docker> ") diff --git a/container.go b/container.go index 7a3970353d..cda114907d 100644 --- a/container.go +++ b/container.go @@ -53,12 +53,13 @@ type Container struct { } type Config struct { - Hostname string - User string - Ram int64 - Ports []int - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin + Hostname string + User string + Memory int64 // Memory limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap + Ports []int + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin } type NetworkSettings struct { diff --git a/container_test.go b/container_test.go index 8801b0e764..d896553be8 100644 --- a/container_test.go +++ b/container_test.go @@ -2,9 +2,12 @@ package docker import ( "./fs" + "bufio" "fmt" "io" "io/ioutil" + "math/rand" + "os" "sort" "strings" "testing" @@ -23,7 +26,7 @@ func TestCommitRun(t *testing.T) { []string{"-c", "echo hello > /world"}, GetTestImage(docker), &Config{ - Ram: 33554432, + Memory: 33554432, }, ) if err != nil { @@ -65,7 +68,7 @@ func TestCommitRun(t *testing.T) { []string{"/world"}, img, &Config{ - Ram: 33554432, + Memory: 33554432, }, ) if err != nil { @@ -100,7 +103,7 @@ func TestRun(t *testing.T) { []string{"-al"}, GetTestImage(docker), &Config{ - Ram: 33554432, + Memory: 33554432, }, ) if err != nil { @@ -349,7 +352,7 @@ func TestUser(t *testing.T) { // Set a username container, err = docker.Create( "user_root", - "/bin/id", + "id", []string{}, GetTestImage(docker), &Config{ @@ -393,7 +396,7 @@ func TestUser(t *testing.T) { // Set a different user by uid container, err = docker.Create( "user_uid1", - "/usr/bin/id", + "id", []string{}, GetTestImage(docker), &Config{ @@ -417,7 +420,7 @@ func TestUser(t *testing.T) { // Set a different user by username container, err = docker.Create( "user_daemon", - "/usr/bin/id", + "id", []string{}, GetTestImage(docker), &Config{ @@ -616,6 +619,59 @@ func TestEnv(t *testing.T) { } } +func grepFile(t *testing.T, path string, pattern string) { + f, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + r := bufio.NewReader(f) + var ( + line string + ) + err = nil + for err == nil { + line, err = r.ReadString('\n') + if strings.Contains(line, pattern) == true { + return + } + } + t.Fatalf("grepFile: pattern \"%s\" not found in \"%s\"", pattern, path) +} + +func TestLXCConfig(t *testing.T) { + docker, err := newTestDocker() + if err != nil { + t.Fatal(err) + } + defer nuke(docker) + // Memory is allocated randomly for testing + rand.Seed(time.Now().UTC().UnixNano()) + memMin := 33554432 + memMax := 536870912 + mem := memMin + rand.Intn(memMax-memMin) + container, err := docker.Create( + "config_test", + "/bin/true", + []string{}, + GetTestImage(docker), + &Config{ + Hostname: "foobar", + Memory: int64(mem), + }, + ) + if err != nil { + t.Fatal(err) + } + defer docker.Destroy(container) + container.generateLXCConfig() + grepFile(t, container.lxcConfigPath, "lxc.utsname = foobar") + grepFile(t, container.lxcConfigPath, + fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem)) + grepFile(t, container.lxcConfigPath, + fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2)) +} + func BenchmarkRunSequencial(b *testing.B) { docker, err := newTestDocker() if err != nil { diff --git a/install.sh b/install.sh index af5c214d78..0cfba4ddc3 100644 --- a/install.sh +++ b/install.sh @@ -1,20 +1,46 @@ -# This script is meant for quick & easy install via 'curl URL-OF-SCRIPPT | bash' -# Courtesy of Jeff Lindsay +#!/bin/sh +# This script is meant for quick & easy install via 'curl URL-OF-SCRIPT | sh' +# Original version by Jeff Lindsay +# Revamped by Jerome Petazzoni +# +# This script canonical location is http://get.docker.io/; to update it, run: +# s3cmd put -m text/x-shellscript -P install.sh s3://get.docker.io/index -cd /tmp +echo "Ensuring basic dependencies are installed..." +apt-get -qq update +apt-get -qq install lxc wget bsdtar -echo "Ensuring dependencies are installed..." -apt-get --yes install lxc wget bsdtar 2>&1 > /dev/null +echo "Looking in /proc/filesystems to see if we have AUFS support..." +if grep -q aufs /proc/filesystems +then + echo "Found." +else + echo "Ahem, it looks like the current kernel does not support AUFS." + echo "Let's see if we can load the AUFS module with modprobe..." + if modprobe aufs + then + echo "Module loaded." + else + echo "Ahem, things didn't turn out as expected." + KPKG=linux-image-extra-$(uname -r) + echo "Trying to install $KPKG..." + if apt-get -qq install $KPKG + then + echo "Installed." + else + echo "Oops, we couldn't install the -extra kernel." + echo "Are you sure you are running a supported version of Ubuntu?" + echo "Proceeding anyway, but Docker will probably NOT WORK!" + fi + fi +fi -echo "Downloading docker binary..." -wget -q https://dl.dropbox.com/u/20637798/docker.tar.gz 2>&1 > /dev/null -tar -xf docker.tar.gz 2>&1 > /dev/null +echo "Downloading docker binary and uncompressing into /usr/local/bin..." +curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz | +tar -C /usr/local/bin --strip-components=1 -zxf- \ +docker-master/docker docker-master/dockerd -echo "Installing into /usr/local/bin..." -mv docker/docker /usr/local/bin -mv dockerd/dockerd /usr/local/bin - -if [[ -f /etc/init/dockerd.conf ]] +if [ -f /etc/init/dockerd.conf ] then echo "Upstart script already exists." else @@ -22,13 +48,8 @@ else echo "exec /usr/local/bin/dockerd" > /etc/init/dockerd.conf fi -echo "Restarting dockerd..." -restart dockerd > /dev/null +echo "Starting dockerd..." +start dockerd > /dev/null -echo "Cleaning up..." -rmdir docker -rmdir dockerd -rm docker.tar.gz - -echo "Finished!" +echo "Done." echo diff --git a/lxc_template.go b/lxc_template.go index d9eb1f48db..7ae50bce85 100755 --- a/lxc_template.go +++ b/lxc_template.go @@ -85,16 +85,32 @@ lxc.mount.entry = /etc/resolv.conf {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0 lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod net_raw setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config # limits -{{if .Config.Ram}} -lxc.cgroup.memory.limit_in_bytes = {{.Config.Ram}} +{{if .Config.Memory}} +lxc.cgroup.memory.limit_in_bytes = {{.Config.Memory}} +lxc.cgroup.memory.soft_limit_in_bytes = {{.Config.Memory}} +{{with $memSwap := getMemorySwap .Config}} +lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}} +{{end}} {{end}} ` var LxcTemplateCompiled *template.Template +func getMemorySwap(config *Config) int64 { + // By default, MemorySwap is set to twice the size of RAM. + // If you want to omit MemorySwap, set it to `-1'. + if config.MemorySwap < 0 { + return 0 + } + return config.Memory * 2 +} + func init() { var err error - LxcTemplateCompiled, err = template.New("lxc").Parse(LxcTemplate) + funcMap := template.FuncMap{ + "getMemorySwap": getMemorySwap, + } + LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate) if err != nil { panic(err) } diff --git a/puppet/modules/docker/manifests/init.pp b/puppet/modules/docker/manifests/init.pp index 2923b302fc..38b40a8e39 100644 --- a/puppet/modules/docker/manifests/init.pp +++ b/puppet/modules/docker/manifests/init.pp @@ -1,14 +1,19 @@ class docker { # update this with latest docker binary distro - $docker_url = "https://dl.dropbox.com/u/20637798/docker.tar.gz" + $docker_url = "http://docker.io.s3.amazonaws.com/builds/$kernel/$hardwaremodel/docker-master.tgz" # update this with latest go binary distry $go_url = "http://go.googlecode.com/files/go1.0.3.linux-amd64.tar.gz" - Package { ensure => "installed" } - package { ["lxc", "debootstrap", "wget", "bsdtar"]: } + package { ["lxc", "debootstrap", "wget", "bsdtar", "git", + "linux-image-3.5.0-25-generic", + "linux-image-extra-3.5.0-25-generic", + "virtualbox-guest-utils", + "linux-headers-3.5.0-25-generic"]: } + + notify { "docker_url = $docker_url": withpath => true } exec { "debootstrap" : require => Package["debootstrap"], @@ -26,7 +31,7 @@ class docker { exec { "fetch-docker" : require => Package["wget"], command => "/usr/bin/wget -O - $docker_url | /bin/tar xz -C /home/vagrant", - creates => "/home/vagrant/docker/dockerd" + creates => "/home/vagrant/docker-master" } file { "/etc/init/dockerd.conf": @@ -39,10 +44,21 @@ class docker { exec { "copy-docker-bin" : require => Exec["fetch-docker"], - command => "/bin/cp /home/vagrant/docker/docker /usr/local/bin", + command => "/bin/cp /home/vagrant/docker-master/docker /usr/local/bin", creates => "/usr/local/bin/docker" } + exec { "copy-dockerd-bin" : + require => Exec["fetch-docker"], + command => "/bin/cp /home/vagrant/docker-master/dockerd /usr/local/bin", + creates => "/usr/local/bin/dockerd" + } + + exec { "vbox-add" : + require => Package["linux-headers-3.5.0-25-generic"], + command => "/etc/init.d/vboxadd setup", + } + service { "dockerd" : ensure => "running", start => "/sbin/initctl start dockerd", diff --git a/puppet/modules/docker/templates/dockerd.conf b/puppet/modules/docker/templates/dockerd.conf index a00d322bcb..c05f606391 100644 --- a/puppet/modules/docker/templates/dockerd.conf +++ b/puppet/modules/docker/templates/dockerd.conf @@ -7,5 +7,6 @@ start on runlevel [3] respawn script - /home/vagrant/dockerd/dockerd + test -f /etc/default/locale && . /etc/default/locale || true + LANG=$LANG LC_ALL=$LANG /usr/local/bin/dockerd end script diff --git a/server/server.go b/server/server.go index a2a2eada06..94a08a9bf4 100644 --- a/server/server.go +++ b/server/server.go @@ -37,28 +37,43 @@ func (srv *Server) Name() string { return "docker" } +// FIXME: Stop violating DRY by repeating usage here and in Subcmd declarations func (srv *Server) Help() string { help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n" for _, cmd := range [][]interface{}{ {"run", "Run a command in a container"}, {"ps", "Display a list of containers"}, {"import", "Create a new filesystem image from the contents of a tarball"}, - {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, - {"rm", "Remove containers"}, - {"kill", "Kill a running container"}, - {"wait", "Wait for the state of a container to change"}, - {"stop", "Stop a running container"}, - {"start", "Start a stopped container"}, - {"restart", "Restart a running container"}, - {"logs", "Fetch the logs of a container"}, + {"attach", "Attach to a running container"}, + {"cat", "Write the contents of a container's file to standard output"}, + {"commit", "Create a new image from a container's changes"}, + {"cp", "Create a copy of IMAGE and call it NAME"}, + {"debug", "(debug only) (No documentation available)"}, {"diff", "Inspect changes on a container's filesystem"}, - {"commit", "Save the state of a container"}, - {"attach", "Attach to the standard inputs and outputs of a running container"}, - {"wait", "Block until a container exits, then print its exit code"}, - {"info", "Display system-wide information"}, - {"tar", "Stream the contents of a container as a tar archive"}, - {"web", "Generate a web UI"}, {"images", "List images"}, + {"info", "Display system-wide information"}, + {"inspect", "Return low-level information on a container"}, + {"kill", "Kill a running container"}, + {"layers", "(debug only) List filesystem layers"}, + {"logs", "Fetch the logs of a container"}, + {"ls", "List the contents of a container's directory"}, + {"mirror", "(debug only) (No documentation available)"}, + {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, + {"ps", "List containers"}, + {"pull", "Download a new image from a remote location"}, + {"put", "Import a new image from a local archive"}, + {"reset", "Reset changes to a container's filesystem"}, + {"restart", "Restart a running container"}, + {"rm", "Remove a container"}, + {"rmimage", "Remove an image"}, + {"run", "Run a command in a new container"}, + {"start", "Start a stopped container"}, + {"stop", "Stop a running container"}, + {"tar", "Stream the contents of a container as a tar archive"}, + {"umount", "(debug only) Mount a container's filesystem"}, + {"wait", "Block until a container stops, then print its exit code"}, + {"web", "A web UI for docker"}, + {"write", "Write the contents of standard input to a container's file"}, } { help += fmt.Sprintf(" %-10.10s%s\n", cmd...) } @@ -69,7 +84,6 @@ func (srv *Server) Help() string { func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 1 { @@ -88,25 +102,24 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string // 'docker info': display system-wide information. func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - images, _ := srv.images.Images() - var imgcount int - if images == nil { - imgcount = 0 - } else { - imgcount = len(images) + cmd := rcli.Subcmd(stdout, "info", "", "Display system-wide information.") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() > 1 { + cmd.Usage() + return nil } - fmt.Fprintf(stdout, "containers: %d\nversion: %s\nimages: %d\n", len(srv.containers.List()), VERSION, - imgcount) + len(srv.images.ById)) return nil } func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 1 { @@ -129,7 +142,6 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "restart", "[OPTIONS] NAME", "Restart a running container") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 1 { @@ -152,7 +164,6 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "start", "[OPTIONS] NAME", "Start a stopped container") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 1 { @@ -175,7 +186,6 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin func (srv *Server) CmdUmount(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "umount", "[OPTIONS] NAME", "umount a container's filesystem (debug only)") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 1 { @@ -198,7 +208,6 @@ func (srv *Server) CmdUmount(stdin io.ReadCloser, stdout io.Writer, args ...stri func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "umount", "[OPTIONS] NAME", "mount a container's filesystem (debug only)") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 1 { @@ -221,7 +230,6 @@ func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...strin func (srv *Server) CmdCat(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "cat", "[OPTIONS] CONTAINER PATH", "write the contents of a container's file to standard output") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 2 { @@ -243,7 +251,6 @@ func (srv *Server) CmdCat(stdin io.ReadCloser, stdout io.Writer, args ...string) func (srv *Server) CmdWrite(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "write", "[OPTIONS] CONTAINER PATH", "write the contents of standard input to a container's file") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 2 { @@ -265,7 +272,6 @@ func (srv *Server) CmdWrite(stdin io.ReadCloser, stdout io.Writer, args ...strin func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "ls", "[OPTIONS] CONTAINER PATH", "List the contents of a container's directory") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 2 { @@ -289,7 +295,6 @@ func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string) func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() < 1 { @@ -322,7 +327,6 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") if err := cmd.Parse(args); err != nil { - cmd.Usage() return nil } if cmd.NArg() != 2 { @@ -459,7 +463,9 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images") limit := cmd.Int("l", 0, "Only show the N most recent versions of each image") quiet := cmd.Bool("q", false, "only show numeric IDs") - cmd.Parse(args) + if err := cmd.Parse(args); err != nil { + return nil + } if cmd.NArg() > 1 { cmd.Usage() return nil @@ -813,6 +819,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) fl_stdin := cmd.Bool("i", false, "Keep stdin open even if not attached") fl_tty := cmd.Bool("t", false, "Allocate a pseudo-tty") fl_comment := cmd.String("c", "", "Comment") + fl_memory := cmd.Int64("m", 0, "Memory limit (in bytes)") var fl_ports ports cmd.Var(&fl_ports, "p", "Map a network port to the container") if err := cmd.Parse(args); err != nil { @@ -842,7 +849,8 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) return errors.New("No such image: " + name) } // Create new container - container, err := srv.CreateContainer(img, fl_ports, *fl_user, *fl_tty, *fl_stdin, *fl_comment, cmdline[0], cmdline[1:]...) + container, err := srv.CreateContainer(img, fl_ports, *fl_user, *fl_tty, + *fl_stdin, *fl_memory, *fl_comment, cmdline[0], cmdline[1:]...) if err != nil { return errors.New("Error creating container: " + err.Error()) }