зеркало из https://github.com/microsoft/docker.git
Merge branch 'master' into load-profile
Conflicts: daemon/execdriver/native/create.go daemon/execdriver/native/driver.go Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes <guillaume@charmes.net> (github: creack)
This commit is contained in:
Коммит
813cebc64f
|
@ -82,6 +82,9 @@ RUN go get code.google.com/p/go.tools/cmd/cover
|
|||
# TODO replace FPM with some very minimal debhelper stuff
|
||||
RUN gem install --no-rdoc --no-ri fpm --version 1.0.2
|
||||
|
||||
# Get the "busybox" image source so we can build locally instead of pulling
|
||||
RUN git clone https://github.com/jpetazzo/docker-busybox.git /docker-busybox
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg
|
||||
|
||||
|
|
15
Makefile
15
Makefile
|
@ -1,4 +1,4 @@
|
|||
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-integration test-integration-cli
|
||||
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-integration test-integration-cli validate
|
||||
|
||||
# to allow `make BINDDIR=. shell` or `make BINDDIR= test`
|
||||
BINDDIR := bundles
|
||||
|
@ -11,7 +11,8 @@ DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
|||
DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/dotcloud/docker/$(BINDDIR)")
|
||||
|
||||
DOCKER_RUN_DOCKER := docker run --rm -it --privileged -e TESTFLAGS -e DOCKER_GRAPHDRIVER -e DOCKER_EXECDRIVER $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
|
||||
DOCKER_RUN_DOCS := docker run --rm -it -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)"
|
||||
# to allow `make DOCSDIR=docs docs-shell`
|
||||
DOCKER_RUN_DOCS := docker run --rm -it -p $(if $(DOCSPORT),$(DOCSPORT):)8000 $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR)) -e AWS_S3_BUCKET
|
||||
|
||||
default: binary
|
||||
|
||||
|
@ -25,10 +26,13 @@ cross: build
|
|||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
||||
|
||||
docs: docs-build
|
||||
$(DOCKER_RUN_DOCS)
|
||||
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" mkdocs serve
|
||||
|
||||
docs-shell: docs-build
|
||||
$(DOCKER_RUN_DOCS) bash
|
||||
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" bash
|
||||
|
||||
docs-release: docs-build
|
||||
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./release.sh
|
||||
|
||||
test: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary test test-integration test-integration-cli
|
||||
|
@ -39,6 +43,9 @@ test-integration: build
|
|||
test-integration-cli: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-integration-cli
|
||||
|
||||
validate: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco
|
||||
|
||||
shell: build
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ package builtins
|
|||
|
||||
import (
|
||||
api "github.com/dotcloud/docker/api/server"
|
||||
"github.com/dotcloud/docker/daemon/networkdriver/bridge"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/runtime/networkdriver/bridge"
|
||||
"github.com/dotcloud/docker/server"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver/devmapper"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver/devmapper"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
[ $(id -u) -eq 0 ] || {
|
||||
printf >&2 '%s requires root\n' "$0"
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
printf >&2 '%s: [-r release] [-m mirror] [-s]\n' "$0"
|
||||
exit 1
|
||||
}
|
||||
|
||||
tmp() {
|
||||
TMP=$(mktemp -d /tmp/alpine-docker-XXXXXXXXXX)
|
||||
ROOTFS=$(mktemp -d /tmp/alpine-docker-rootfs-XXXXXXXXXX)
|
||||
trap "rm -rf $TMP $ROOTFS" EXIT TERM INT
|
||||
}
|
||||
|
||||
apkv() {
|
||||
curl -s $REPO/$ARCH/APKINDEX.tar.gz | tar -Oxz |
|
||||
grep '^P:apk-tools-static$' -A1 | tail -n1 | cut -d: -f2
|
||||
}
|
||||
|
||||
getapk() {
|
||||
curl -s $REPO/$ARCH/apk-tools-static-$(apkv).apk |
|
||||
tar -xz -C $TMP sbin/apk.static
|
||||
}
|
||||
|
||||
mkbase() {
|
||||
$TMP/sbin/apk.static --repository $REPO --update-cache --allow-untrusted \
|
||||
--root $ROOTFS --initdb add alpine-base
|
||||
}
|
||||
|
||||
conf() {
|
||||
printf '%s\n' $REPO > $ROOTFS/etc/apk/repositories
|
||||
}
|
||||
|
||||
pack() {
|
||||
local id
|
||||
id=$(tar --numeric-owner -C $ROOTFS -c . | docker import - alpine:$REL)
|
||||
|
||||
docker tag $id alpine:latest
|
||||
docker run -i -t alpine printf 'alpine:%s with id=%s created!\n' $REL $id
|
||||
}
|
||||
|
||||
save() {
|
||||
[ $SAVE -eq 1 ] || return
|
||||
|
||||
tar --numeric-owner -C $ROOTFS -c . | xz > rootfs.tar.xz
|
||||
}
|
||||
|
||||
while getopts "hr:m:s" opt; do
|
||||
case $opt in
|
||||
r)
|
||||
REL=$OPTARG
|
||||
;;
|
||||
m)
|
||||
MIRROR=$OPTARG
|
||||
;;
|
||||
s)
|
||||
SAVE=1
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
REL=${REL:-edge}
|
||||
MIRROR=${MIRROR:-http://nl.alpinelinux.org/alpine}
|
||||
SAVE=${SAVE:-0}
|
||||
REPO=$MIRROR/$REL/main
|
||||
ARCH=$(uname -m)
|
||||
|
||||
tmp
|
||||
getapk
|
||||
mkbase
|
||||
conf
|
||||
pack
|
||||
save
|
|
@ -57,6 +57,7 @@ mknod -m 666 $DEV/tty0 c 4 0
|
|||
mknod -m 666 $DEV/full c 1 7
|
||||
mknod -m 600 $DEV/initctl p
|
||||
mknod -m 666 $DEV/ptmx c 5 2
|
||||
ln -sf /proc/self/fd $DEV/fd
|
||||
|
||||
tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux
|
||||
docker run -i -t archlinux echo Success.
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/image"
|
||||
"github.com/dotcloud/docker/links"
|
||||
"github.com/dotcloud/docker/nat"
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -64,7 +64,7 @@ type Container struct {
|
|||
stdin io.ReadCloser
|
||||
stdinPipe io.WriteCloser
|
||||
|
||||
runtime *Runtime
|
||||
daemon *Daemon
|
||||
|
||||
waitLock chan struct{}
|
||||
Volumes map[string]string
|
||||
|
@ -76,42 +76,6 @@ type Container struct {
|
|||
activeLinks map[string]*links.Link
|
||||
}
|
||||
|
||||
// FIXME: move deprecated port stuff to nat to clean up the core.
|
||||
type PortMapping map[string]string // Deprecated
|
||||
|
||||
type NetworkSettings struct {
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping // Deprecated
|
||||
Ports nat.PortMap
|
||||
}
|
||||
|
||||
func (settings *NetworkSettings) PortMappingAPI() *engine.Table {
|
||||
var outs = engine.NewTable("", 0)
|
||||
for port, bindings := range settings.Ports {
|
||||
p, _ := nat.ParsePort(port.Port())
|
||||
if len(bindings) == 0 {
|
||||
out := &engine.Env{}
|
||||
out.SetInt("PublicPort", p)
|
||||
out.Set("Type", port.Proto())
|
||||
outs.Add(out)
|
||||
continue
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
out := &engine.Env{}
|
||||
h, _ := nat.ParsePort(binding.HostPort)
|
||||
out.SetInt("PrivatePort", p)
|
||||
out.SetInt("PublicPort", h)
|
||||
out.Set("Type", port.Proto())
|
||||
out.Set("IP", binding.HostIp)
|
||||
outs.Add(out)
|
||||
}
|
||||
}
|
||||
return outs
|
||||
}
|
||||
|
||||
// Inject the io.Reader at the given path. Note: do not close the reader
|
||||
func (container *Container) Inject(file io.Reader, pth string) error {
|
||||
if err := container.Mount(); err != nil {
|
||||
|
@ -148,10 +112,6 @@ func (container *Container) Inject(file io.Reader, pth string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) When() time.Time {
|
||||
return container.Created
|
||||
}
|
||||
|
||||
func (container *Container) FromDisk() error {
|
||||
data, err := ioutil.ReadFile(container.jsonPath())
|
||||
if err != nil {
|
||||
|
@ -358,14 +318,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
|||
})
|
||||
}
|
||||
|
||||
func populateCommand(c *Container) {
|
||||
func populateCommand(c *Container, env []string) {
|
||||
var (
|
||||
en *execdriver.Network
|
||||
driverConfig = make(map[string][]string)
|
||||
)
|
||||
|
||||
en = &execdriver.Network{
|
||||
Mtu: c.runtime.config.Mtu,
|
||||
Mtu: c.daemon.config.Mtu,
|
||||
Interface: nil,
|
||||
}
|
||||
|
||||
|
@ -402,18 +362,7 @@ func populateCommand(c *Container) {
|
|||
Resources: resources,
|
||||
}
|
||||
c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
}
|
||||
|
||||
func (container *Container) ArgsAsString() string {
|
||||
var args []string
|
||||
for _, arg := range container.Args {
|
||||
if strings.Contains(arg, " ") {
|
||||
args = append(args, fmt.Sprintf("'%s'", arg))
|
||||
} else {
|
||||
args = append(args, arg)
|
||||
}
|
||||
}
|
||||
return strings.Join(args, " ")
|
||||
c.command.Env = env
|
||||
}
|
||||
|
||||
func (container *Container) Start() (err error) {
|
||||
|
@ -423,186 +372,50 @@ func (container *Container) Start() (err error) {
|
|||
if container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we encounter and error during start we need to ensure that any other
|
||||
// setup has been cleaned up properly
|
||||
defer func() {
|
||||
if err != nil {
|
||||
container.cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
if container.ResolvConfPath == "" {
|
||||
if err := container.setupContainerDns(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.setupContainerDns(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := container.Mount(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if container.runtime.config.DisableNetwork {
|
||||
container.Config.NetworkDisabled = true
|
||||
container.buildHostnameAndHostsFiles("127.0.1.1")
|
||||
} else {
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
|
||||
if err := container.initializeNetworking(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure the config is compatible with the current kernel
|
||||
if container.Config.Memory > 0 && !container.runtime.sysInfo.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.sysInfo.SwapLimit {
|
||||
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
container.Config.MemorySwap = -1
|
||||
}
|
||||
|
||||
if container.runtime.sysInfo.IPv4ForwardingDisabled {
|
||||
log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work")
|
||||
}
|
||||
|
||||
container.verifyDaemonSettings()
|
||||
if err := prepareVolumesForContainer(container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup environment
|
||||
env := []string{
|
||||
"HOME=/",
|
||||
"PATH=" + DefaultPathEnv,
|
||||
"HOSTNAME=" + container.Config.Hostname,
|
||||
}
|
||||
|
||||
if container.Config.Tty {
|
||||
env = append(env, "TERM=xterm")
|
||||
}
|
||||
|
||||
// Init any links between the parent and children
|
||||
runtime := container.runtime
|
||||
|
||||
children, err := runtime.Children(container.Name)
|
||||
linkedEnv, err := container.setupLinkedContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(children) > 0 {
|
||||
container.activeLinks = make(map[string]*links.Link, len(children))
|
||||
|
||||
// If we encounter an error make sure that we rollback any network
|
||||
// config and ip table changes
|
||||
rollback := func() {
|
||||
for _, link := range container.activeLinks {
|
||||
link.Disable()
|
||||
}
|
||||
container.activeLinks = nil
|
||||
}
|
||||
|
||||
for linkAlias, child := range children {
|
||||
if !child.State.IsRunning() {
|
||||
return fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias)
|
||||
}
|
||||
|
||||
link, err := links.NewLink(
|
||||
container.NetworkSettings.IPAddress,
|
||||
child.NetworkSettings.IPAddress,
|
||||
linkAlias,
|
||||
child.Config.Env,
|
||||
child.Config.ExposedPorts,
|
||||
runtime.eng)
|
||||
|
||||
if err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
container.activeLinks[link.Alias()] = link
|
||||
if err := link.Enable(); err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
for _, envVar := range link.ToEnv() {
|
||||
env = append(env, envVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// because the env on the container can override certain default values
|
||||
// we need to replace the 'env' keys where they match and append anything
|
||||
// else.
|
||||
env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env)
|
||||
env := container.createDaemonEnvironment(linkedEnv)
|
||||
// TODO: This is only needed for lxc so we should look for a way to
|
||||
// remove this dep
|
||||
if err := container.generateEnvConfig(env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if container.Config.WorkingDir != "" {
|
||||
container.Config.WorkingDir = path.Clean(container.Config.WorkingDir)
|
||||
|
||||
pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if pthInfo != nil && !pthInfo.IsDir() {
|
||||
return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
|
||||
}
|
||||
}
|
||||
|
||||
envPath, err := container.EnvConfigPath()
|
||||
if err != nil {
|
||||
if err := container.setupWorkingDirectory(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
populateCommand(container)
|
||||
container.command.Env = env
|
||||
|
||||
if err := setupMountsForContainer(container, envPath); err != nil {
|
||||
populateCommand(container, env)
|
||||
if err := setupMountsForContainer(container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil {
|
||||
if err := container.startLoggingToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.waitLock = make(chan struct{})
|
||||
|
||||
callbackLock := make(chan struct{})
|
||||
callback := func(command *execdriver.Command) {
|
||||
container.State.SetRunning(command.Pid())
|
||||
if command.Tty {
|
||||
// The callback is called after the process Start()
|
||||
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace
|
||||
// which we close here.
|
||||
if c, ok := command.Stdout.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
if err := container.ToDisk(); err != nil {
|
||||
utils.Debugf("%s", err)
|
||||
}
|
||||
close(callbackLock)
|
||||
}
|
||||
|
||||
// We use a callback here instead of a goroutine and an chan for
|
||||
// syncronization purposes
|
||||
cErr := utils.Go(func() error { return container.monitor(callback) })
|
||||
|
||||
// Start should not return until the process is actually running
|
||||
select {
|
||||
case <-callbackLock:
|
||||
case err := <-cErr:
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return container.waitForStart()
|
||||
}
|
||||
|
||||
func (container *Container) Run() error {
|
||||
|
@ -683,42 +496,18 @@ func (container *Container) allocateNetwork() error {
|
|||
var (
|
||||
env *engine.Env
|
||||
err error
|
||||
eng = container.runtime.eng
|
||||
eng = container.daemon.eng
|
||||
)
|
||||
|
||||
if container.State.IsGhost() {
|
||||
if container.runtime.config.DisableNetwork {
|
||||
env = &engine.Env{}
|
||||
} else {
|
||||
currentIP := container.NetworkSettings.IPAddress
|
||||
|
||||
job := eng.Job("allocate_interface", container.ID)
|
||||
if currentIP != "" {
|
||||
job.Setenv("RequestIP", currentIP)
|
||||
}
|
||||
|
||||
env, err = job.Stdout.AddEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
job := eng.Job("allocate_interface", container.ID)
|
||||
env, err = job.Stdout.AddEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
job := eng.Job("allocate_interface", container.ID)
|
||||
if env, err = job.Stdout.AddEnv(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if container.Config.PortSpecs != nil {
|
||||
utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
|
||||
if err := migratePortMappings(container.Config, container.hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -733,58 +522,23 @@ func (container *Container) allocateNetwork() error {
|
|||
bindings = make(nat.PortMap)
|
||||
)
|
||||
|
||||
if !container.State.IsGhost() {
|
||||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
if container.hostConfig.PortBindings != nil {
|
||||
bindings = container.hostConfig.PortBindings
|
||||
}
|
||||
} else {
|
||||
if container.NetworkSettings.Ports != nil {
|
||||
for port, binding := range container.NetworkSettings.Ports {
|
||||
portSpecs[port] = struct{}{}
|
||||
bindings[port] = binding
|
||||
}
|
||||
}
|
||||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
if container.hostConfig.PortBindings != nil {
|
||||
bindings = container.hostConfig.PortBindings
|
||||
}
|
||||
|
||||
container.NetworkSettings.PortMapping = nil
|
||||
|
||||
for port := range portSpecs {
|
||||
binding := bindings[port]
|
||||
if container.hostConfig.PublishAllPorts && len(binding) == 0 {
|
||||
binding = append(binding, nat.PortBinding{})
|
||||
if err := container.allocatePort(eng, port, bindings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < len(binding); i++ {
|
||||
b := binding[i]
|
||||
|
||||
portJob := eng.Job("allocate_port", container.ID)
|
||||
portJob.Setenv("HostIP", b.HostIp)
|
||||
portJob.Setenv("HostPort", b.HostPort)
|
||||
portJob.Setenv("Proto", port.Proto())
|
||||
portJob.Setenv("ContainerPort", port.Port())
|
||||
|
||||
portEnv, err := portJob.Stdout.AddEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := portJob.Run(); err != nil {
|
||||
eng.Job("release_interface", container.ID).Run()
|
||||
return err
|
||||
}
|
||||
b.HostIp = portEnv.Get("HostIP")
|
||||
b.HostPort = portEnv.Get("HostPort")
|
||||
|
||||
binding[i] = b
|
||||
}
|
||||
bindings[port] = binding
|
||||
}
|
||||
container.WriteHostConfig()
|
||||
|
||||
container.NetworkSettings.Ports = bindings
|
||||
|
||||
container.NetworkSettings.Bridge = env.Get("Bridge")
|
||||
container.NetworkSettings.IPAddress = env.Get("IP")
|
||||
container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen")
|
||||
|
@ -797,7 +551,7 @@ func (container *Container) releaseNetwork() {
|
|||
if container.Config.NetworkDisabled {
|
||||
return
|
||||
}
|
||||
eng := container.runtime.eng
|
||||
eng := container.daemon.eng
|
||||
|
||||
eng.Job("release_interface", container.ID).Run()
|
||||
container.NetworkSettings = &NetworkSettings{}
|
||||
|
@ -810,12 +564,12 @@ func (container *Container) monitor(callback execdriver.StartCallback) error {
|
|||
)
|
||||
|
||||
pipes := execdriver.NewPipes(container.stdin, container.stdout, container.stderr, container.Config.OpenStdin)
|
||||
exitCode, err = container.runtime.Run(container, pipes, callback)
|
||||
exitCode, err = container.daemon.Run(container, pipes, callback)
|
||||
if err != nil {
|
||||
utils.Errorf("Error running container: %s", err)
|
||||
}
|
||||
|
||||
if container.runtime != nil && container.runtime.srv != nil && container.runtime.srv.IsRunning() {
|
||||
if container.daemon != nil && container.daemon.srv != nil && container.daemon.srv.IsRunning() {
|
||||
container.State.SetStopped(exitCode)
|
||||
|
||||
// FIXME: there is a race condition here which causes this to fail during the unit tests.
|
||||
|
@ -838,8 +592,8 @@ func (container *Container) monitor(callback execdriver.StartCallback) error {
|
|||
container.stdin, container.stdinPipe = io.Pipe()
|
||||
}
|
||||
|
||||
if container.runtime != nil && container.runtime.srv != nil {
|
||||
container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image))
|
||||
if container.daemon != nil && container.daemon.srv != nil {
|
||||
container.daemon.srv.LogEvent("die", container.ID, container.daemon.repositories.ImageName(container.Image))
|
||||
}
|
||||
|
||||
close(container.waitLock)
|
||||
|
@ -885,7 +639,7 @@ func (container *Container) KillSig(sig int) error {
|
|||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
return container.runtime.Kill(container, sig)
|
||||
return container.daemon.Kill(container, sig)
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
|
@ -962,10 +716,10 @@ func (container *Container) ExportRw() (archive.Archive, error) {
|
|||
if err := container.Mount(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if container.runtime == nil {
|
||||
if container.daemon == nil {
|
||||
return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID)
|
||||
}
|
||||
archive, err := container.runtime.Diff(container)
|
||||
archive, err := container.daemon.Diff(container)
|
||||
if err != nil {
|
||||
container.Unmount()
|
||||
return nil, err
|
||||
|
@ -1012,22 +766,22 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
|
|||
}
|
||||
|
||||
func (container *Container) Mount() error {
|
||||
return container.runtime.Mount(container)
|
||||
return container.daemon.Mount(container)
|
||||
}
|
||||
|
||||
func (container *Container) Changes() ([]archive.Change, error) {
|
||||
return container.runtime.Changes(container)
|
||||
return container.daemon.Changes(container)
|
||||
}
|
||||
|
||||
func (container *Container) GetImage() (*image.Image, error) {
|
||||
if container.runtime == nil {
|
||||
if container.daemon == nil {
|
||||
return nil, fmt.Errorf("Can't get image of unregistered container")
|
||||
}
|
||||
return container.runtime.graph.Get(container.Image)
|
||||
return container.daemon.graph.Get(container.Image)
|
||||
}
|
||||
|
||||
func (container *Container) Unmount() error {
|
||||
return container.runtime.Unmount(container)
|
||||
return container.daemon.Unmount(container)
|
||||
}
|
||||
|
||||
func (container *Container) logPath(name string) string {
|
||||
|
@ -1080,7 +834,7 @@ func (container *Container) GetSize() (int64, int64) {
|
|||
var (
|
||||
sizeRw, sizeRootfs int64
|
||||
err error
|
||||
driver = container.runtime.driver
|
||||
driver = container.daemon.driver
|
||||
)
|
||||
|
||||
if err := container.Mount(); err != nil {
|
||||
|
@ -1089,7 +843,7 @@ func (container *Container) GetSize() (int64, int64) {
|
|||
}
|
||||
defer container.Unmount()
|
||||
|
||||
if differ, ok := container.runtime.driver.(graphdriver.Differ); ok {
|
||||
if differ, ok := container.daemon.driver.(graphdriver.Differ); ok {
|
||||
sizeRw, err = differ.DiffSize(container.ID)
|
||||
if err != nil {
|
||||
utils.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
|
||||
|
@ -1182,29 +936,32 @@ func (container *Container) DisableLink(name string) {
|
|||
}
|
||||
|
||||
func (container *Container) setupContainerDns() error {
|
||||
if container.ResolvConfPath != "" {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
config = container.hostConfig
|
||||
runtime = container.runtime
|
||||
config = container.hostConfig
|
||||
daemon = container.daemon
|
||||
)
|
||||
resolvConf, err := utils.GetResolvConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If custom dns exists, then create a resolv.conf for the container
|
||||
if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(runtime.config.DnsSearch) > 0 {
|
||||
if len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0 {
|
||||
var (
|
||||
dns = utils.GetNameservers(resolvConf)
|
||||
dnsSearch = utils.GetSearchDomains(resolvConf)
|
||||
)
|
||||
if len(config.Dns) > 0 {
|
||||
dns = config.Dns
|
||||
} else if len(runtime.config.Dns) > 0 {
|
||||
dns = runtime.config.Dns
|
||||
} else if len(daemon.config.Dns) > 0 {
|
||||
dns = daemon.config.Dns
|
||||
}
|
||||
if len(config.DnsSearch) > 0 {
|
||||
dnsSearch = config.DnsSearch
|
||||
} else if len(runtime.config.DnsSearch) > 0 {
|
||||
dnsSearch = runtime.config.DnsSearch
|
||||
} else if len(daemon.config.DnsSearch) > 0 {
|
||||
dnsSearch = daemon.config.DnsSearch
|
||||
}
|
||||
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
|
||||
f, err := os.Create(container.ResolvConfPath)
|
||||
|
@ -1227,3 +984,198 @@ func (container *Container) setupContainerDns() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) initializeNetworking() error {
|
||||
if container.daemon.config.DisableNetwork {
|
||||
container.Config.NetworkDisabled = true
|
||||
container.buildHostnameAndHostsFiles("127.0.1.1")
|
||||
} else {
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure the config is compatible with the current kernel
|
||||
func (container *Container) verifyDaemonSettings() {
|
||||
if container.Config.Memory > 0 && !container.daemon.sysInfo.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.daemon.sysInfo.SwapLimit {
|
||||
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
container.Config.MemorySwap = -1
|
||||
}
|
||||
if container.daemon.sysInfo.IPv4ForwardingDisabled {
|
||||
log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work")
|
||||
}
|
||||
}
|
||||
|
||||
func (container *Container) setupLinkedContainers() ([]string, error) {
|
||||
var (
|
||||
env []string
|
||||
daemon = container.daemon
|
||||
)
|
||||
children, err := daemon.Children(container.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(children) > 0 {
|
||||
container.activeLinks = make(map[string]*links.Link, len(children))
|
||||
|
||||
// If we encounter an error make sure that we rollback any network
|
||||
// config and ip table changes
|
||||
rollback := func() {
|
||||
for _, link := range container.activeLinks {
|
||||
link.Disable()
|
||||
}
|
||||
container.activeLinks = nil
|
||||
}
|
||||
|
||||
for linkAlias, child := range children {
|
||||
if !child.State.IsRunning() {
|
||||
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias)
|
||||
}
|
||||
|
||||
link, err := links.NewLink(
|
||||
container.NetworkSettings.IPAddress,
|
||||
child.NetworkSettings.IPAddress,
|
||||
linkAlias,
|
||||
child.Config.Env,
|
||||
child.Config.ExposedPorts,
|
||||
daemon.eng)
|
||||
|
||||
if err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container.activeLinks[link.Alias()] = link
|
||||
if err := link.Enable(); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, envVar := range link.ToEnv() {
|
||||
env = append(env, envVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func (container *Container) createDaemonEnvironment(linkedEnv []string) []string {
|
||||
// Setup environment
|
||||
env := []string{
|
||||
"HOME=/",
|
||||
"PATH=" + DefaultPathEnv,
|
||||
"HOSTNAME=" + container.Config.Hostname,
|
||||
}
|
||||
if container.Config.Tty {
|
||||
env = append(env, "TERM=xterm")
|
||||
}
|
||||
env = append(env, linkedEnv...)
|
||||
// because the env on the container can override certain default values
|
||||
// we need to replace the 'env' keys where they match and append anything
|
||||
// else.
|
||||
env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env)
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func (container *Container) setupWorkingDirectory() error {
|
||||
if container.Config.WorkingDir != "" {
|
||||
container.Config.WorkingDir = path.Clean(container.Config.WorkingDir)
|
||||
|
||||
pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if pthInfo != nil && !pthInfo.IsDir() {
|
||||
return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) startLoggingToDisk() error {
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if err := container.daemon.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.daemon.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) waitForStart() error {
|
||||
callbackLock := make(chan struct{})
|
||||
callback := func(command *execdriver.Command) {
|
||||
container.State.SetRunning(command.Pid())
|
||||
if command.Tty {
|
||||
// The callback is called after the process Start()
|
||||
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace
|
||||
// which we close here.
|
||||
if c, ok := command.Stdout.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
if err := container.ToDisk(); err != nil {
|
||||
utils.Debugf("%s", err)
|
||||
}
|
||||
close(callbackLock)
|
||||
}
|
||||
|
||||
// We use a callback here instead of a goroutine and an chan for
|
||||
// syncronization purposes
|
||||
cErr := utils.Go(func() error { return container.monitor(callback) })
|
||||
|
||||
// Start should not return until the process is actually running
|
||||
select {
|
||||
case <-callbackLock:
|
||||
case err := <-cErr:
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bindings nat.PortMap) error {
|
||||
binding := bindings[port]
|
||||
if container.hostConfig.PublishAllPorts && len(binding) == 0 {
|
||||
binding = append(binding, nat.PortBinding{})
|
||||
}
|
||||
|
||||
for i := 0; i < len(binding); i++ {
|
||||
b := binding[i]
|
||||
|
||||
job := eng.Job("allocate_port", container.ID)
|
||||
job.Setenv("HostIP", b.HostIp)
|
||||
job.Setenv("HostPort", b.HostPort)
|
||||
job.Setenv("Proto", port.Proto())
|
||||
job.Setenv("ContainerPort", port.Port())
|
||||
|
||||
portEnv, err := job.Stdout.AddEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
eng.Job("release_interface", container.ID).Run()
|
||||
return err
|
||||
}
|
||||
b.HostIp = portEnv.Get("HostIP")
|
||||
b.HostPort = portEnv.Get("HostPort")
|
||||
|
||||
binding[i] = b
|
||||
}
|
||||
bindings[port] = binding
|
||||
return nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/nat"
|
|
@ -1,9 +1,16 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/execdrivers"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/lxc"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
_ "github.com/dotcloud/docker/daemon/graphdriver/vfs"
|
||||
_ "github.com/dotcloud/docker/daemon/networkdriver/bridge"
|
||||
"github.com/dotcloud/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/dotcloud/docker/daemonconfig"
|
||||
"github.com/dotcloud/docker/dockerversion"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
|
@ -14,13 +21,6 @@ import (
|
|||
"github.com/dotcloud/docker/pkg/selinux"
|
||||
"github.com/dotcloud/docker/pkg/sysinfo"
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/dotcloud/docker/runtime/execdriver/execdrivers"
|
||||
"github.com/dotcloud/docker/runtime/execdriver/lxc"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
_ "github.com/dotcloud/docker/runtime/graphdriver/vfs"
|
||||
_ "github.com/dotcloud/docker/runtime/networkdriver/bridge"
|
||||
"github.com/dotcloud/docker/runtime/networkdriver/portallocator"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -44,7 +44,7 @@ var (
|
|||
validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
|
||||
)
|
||||
|
||||
type Runtime struct {
|
||||
type Daemon struct {
|
||||
repository string
|
||||
sysInitPath string
|
||||
containers *list.List
|
||||
|
@ -76,17 +76,17 @@ func remountPrivate(mountPoint string) error {
|
|||
return mount.ForceMount("", mountPoint, "none", "private")
|
||||
}
|
||||
|
||||
// List returns an array of all containers registered in the runtime.
|
||||
func (runtime *Runtime) List() []*Container {
|
||||
// List returns an array of all containers registered in the daemon.
|
||||
func (daemon *Daemon) List() []*Container {
|
||||
containers := new(History)
|
||||
for e := runtime.containers.Front(); e != nil; e = e.Next() {
|
||||
for e := daemon.containers.Front(); e != nil; e = e.Next() {
|
||||
containers.Add(e.Value.(*Container))
|
||||
}
|
||||
return *containers
|
||||
}
|
||||
|
||||
func (runtime *Runtime) getContainerElement(id string) *list.Element {
|
||||
for e := runtime.containers.Front(); e != nil; e = e.Next() {
|
||||
func (daemon *Daemon) getContainerElement(id string) *list.Element {
|
||||
for e := daemon.containers.Front(); e != nil; e = e.Next() {
|
||||
container := e.Value.(*Container)
|
||||
if container.ID == id {
|
||||
return e
|
||||
|
@ -97,17 +97,17 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element {
|
|||
|
||||
// Get looks for a container by the specified ID or name, and returns it.
|
||||
// If the container is not found, or if an error occurs, nil is returned.
|
||||
func (runtime *Runtime) Get(name string) *Container {
|
||||
if c, _ := runtime.GetByName(name); c != nil {
|
||||
func (daemon *Daemon) Get(name string) *Container {
|
||||
if c, _ := daemon.GetByName(name); c != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
id, err := runtime.idIndex.Get(name)
|
||||
id, err := daemon.idIndex.Get(name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e := runtime.getContainerElement(id)
|
||||
e := daemon.getContainerElement(id)
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -116,18 +116,18 @@ func (runtime *Runtime) Get(name string) *Container {
|
|||
|
||||
// Exists returns a true if a container of the specified ID or name exists,
|
||||
// false otherwise.
|
||||
func (runtime *Runtime) Exists(id string) bool {
|
||||
return runtime.Get(id) != nil
|
||||
func (daemon *Daemon) Exists(id string) bool {
|
||||
return daemon.Get(id) != nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) containerRoot(id string) string {
|
||||
return path.Join(runtime.repository, id)
|
||||
func (daemon *Daemon) containerRoot(id string) string {
|
||||
return path.Join(daemon.repository, id)
|
||||
}
|
||||
|
||||
// Load reads the contents of a container from disk
|
||||
// This is typically done at startup.
|
||||
func (runtime *Runtime) load(id string) (*Container, error) {
|
||||
container := &Container{root: runtime.containerRoot(id)}
|
||||
func (daemon *Daemon) load(id string) (*Container, error) {
|
||||
container := &Container{root: daemon.containerRoot(id)}
|
||||
if err := container.FromDisk(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -140,19 +140,19 @@ func (runtime *Runtime) load(id string) (*Container, error) {
|
|||
return container, nil
|
||||
}
|
||||
|
||||
// Register makes a container object usable by the runtime as <container.ID>
|
||||
func (runtime *Runtime) Register(container *Container) error {
|
||||
if container.runtime != nil || runtime.Exists(container.ID) {
|
||||
// Register makes a container object usable by the daemon as <container.ID>
|
||||
func (daemon *Daemon) Register(container *Container) error {
|
||||
if container.daemon != nil || daemon.Exists(container.ID) {
|
||||
return fmt.Errorf("Container is already loaded")
|
||||
}
|
||||
if err := validateID(container.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runtime.ensureName(container); err != nil {
|
||||
if err := daemon.ensureName(container); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container.runtime = runtime
|
||||
container.daemon = daemon
|
||||
|
||||
// Attach to stdout and stderr
|
||||
container.stderr = utils.NewWriteBroadcaster()
|
||||
|
@ -164,8 +164,8 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
container.stdinPipe = utils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||
}
|
||||
// done
|
||||
runtime.containers.PushBack(container)
|
||||
runtime.idIndex.Add(container.ID)
|
||||
daemon.containers.PushBack(container)
|
||||
daemon.idIndex.Add(container.ID)
|
||||
|
||||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// if so, then we need to restart monitor and init a new lock
|
||||
|
@ -192,7 +192,7 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
if err != nil {
|
||||
utils.Debugf("cannot find existing process for %d", existingPid)
|
||||
}
|
||||
runtime.execDriver.Terminate(cmd)
|
||||
daemon.execDriver.Terminate(cmd)
|
||||
}
|
||||
if err := container.Unmount(); err != nil {
|
||||
utils.Debugf("ghost unmount error %s", err)
|
||||
|
@ -202,10 +202,10 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
}
|
||||
}
|
||||
|
||||
info := runtime.execDriver.Info(container.ID)
|
||||
info := daemon.execDriver.Info(container.ID)
|
||||
if !info.IsRunning() {
|
||||
utils.Debugf("Container %s was supposed to be running but is not.", container.ID)
|
||||
if runtime.config.AutoRestart {
|
||||
if daemon.config.AutoRestart {
|
||||
utils.Debugf("Restarting")
|
||||
if err := container.Unmount(); err != nil {
|
||||
utils.Debugf("restart unmount error %s", err)
|
||||
|
@ -234,9 +234,9 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) ensureName(container *Container) error {
|
||||
func (daemon *Daemon) ensureName(container *Container) error {
|
||||
if container.Name == "" {
|
||||
name, err := generateRandomName(runtime)
|
||||
name, err := generateRandomName(daemon)
|
||||
if err != nil {
|
||||
name = utils.TruncateID(container.ID)
|
||||
}
|
||||
|
@ -245,8 +245,8 @@ func (runtime *Runtime) ensureName(container *Container) error {
|
|||
if err := container.ToDisk(); err != nil {
|
||||
utils.Debugf("Error saving container name %s", err)
|
||||
}
|
||||
if !runtime.containerGraph.Exists(name) {
|
||||
if _, err := runtime.containerGraph.Set(name, container.ID); err != nil {
|
||||
if !daemon.containerGraph.Exists(name) {
|
||||
if _, err := daemon.containerGraph.Set(name, container.ID); err != nil {
|
||||
utils.Debugf("Setting default id - %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ func (runtime *Runtime) ensureName(container *Container) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst, stream string) error {
|
||||
func (daemon *Daemon) LogToDisk(src *utils.WriteBroadcaster, dst, stream string) error {
|
||||
log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -263,13 +263,13 @@ func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst, stream strin
|
|||
return nil
|
||||
}
|
||||
|
||||
// Destroy unregisters a container from the runtime and cleanly removes its contents from the filesystem.
|
||||
func (runtime *Runtime) Destroy(container *Container) error {
|
||||
// Destroy unregisters a container from the daemon and cleanly removes its contents from the filesystem.
|
||||
func (daemon *Daemon) Destroy(container *Container) error {
|
||||
if container == nil {
|
||||
return fmt.Errorf("The given container is <nil>")
|
||||
}
|
||||
|
||||
element := runtime.getContainerElement(container.ID)
|
||||
element := daemon.getContainerElement(container.ID)
|
||||
if element == nil {
|
||||
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID)
|
||||
}
|
||||
|
@ -278,42 +278,42 @@ func (runtime *Runtime) Destroy(container *Container) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := runtime.driver.Remove(container.ID); err != nil {
|
||||
return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", runtime.driver, container.ID, err)
|
||||
if err := daemon.driver.Remove(container.ID); err != nil {
|
||||
return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", daemon.driver, container.ID, err)
|
||||
}
|
||||
|
||||
initID := fmt.Sprintf("%s-init", container.ID)
|
||||
if err := runtime.driver.Remove(initID); err != nil {
|
||||
return fmt.Errorf("Driver %s failed to remove init filesystem %s: %s", runtime.driver, initID, err)
|
||||
if err := daemon.driver.Remove(initID); err != nil {
|
||||
return fmt.Errorf("Driver %s failed to remove init filesystem %s: %s", daemon.driver, initID, err)
|
||||
}
|
||||
|
||||
if _, err := runtime.containerGraph.Purge(container.ID); err != nil {
|
||||
if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
|
||||
utils.Debugf("Unable to remove container from link graph: %s", err)
|
||||
}
|
||||
|
||||
// Deregister the container before removing its directory, to avoid race conditions
|
||||
runtime.idIndex.Delete(container.ID)
|
||||
runtime.containers.Remove(element)
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Remove(element)
|
||||
if err := os.RemoveAll(container.root); err != nil {
|
||||
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) restore() error {
|
||||
func (daemon *Daemon) restore() error {
|
||||
if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
|
||||
fmt.Printf("Loading containers: ")
|
||||
}
|
||||
dir, err := ioutil.ReadDir(runtime.repository)
|
||||
dir, err := ioutil.ReadDir(daemon.repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containers := make(map[string]*Container)
|
||||
currentDriver := runtime.driver.String()
|
||||
currentDriver := daemon.driver.String()
|
||||
|
||||
for _, v := range dir {
|
||||
id := v.Name()
|
||||
container, err := runtime.load(id)
|
||||
container, err := daemon.load(id)
|
||||
if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
|
||||
fmt.Print(".")
|
||||
}
|
||||
|
@ -332,12 +332,12 @@ func (runtime *Runtime) restore() error {
|
|||
}
|
||||
|
||||
register := func(container *Container) {
|
||||
if err := runtime.Register(container); err != nil {
|
||||
if err := daemon.Register(container); err != nil {
|
||||
utils.Debugf("Failed to register container %s: %s", container.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if entities := runtime.containerGraph.List("/", -1); entities != nil {
|
||||
if entities := daemon.containerGraph.List("/", -1); entities != nil {
|
||||
for _, p := range entities.Paths() {
|
||||
if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
|
||||
fmt.Print(".")
|
||||
|
@ -353,12 +353,12 @@ func (runtime *Runtime) restore() error {
|
|||
// Any containers that are left over do not exist in the graph
|
||||
for _, container := range containers {
|
||||
// Try to set the default name for a container if it exists prior to links
|
||||
container.Name, err = generateRandomName(runtime)
|
||||
container.Name, err = generateRandomName(daemon)
|
||||
if err != nil {
|
||||
container.Name = utils.TruncateID(container.ID)
|
||||
}
|
||||
|
||||
if _, err := runtime.containerGraph.Set(container.Name, container.ID); err != nil {
|
||||
if _, err := daemon.containerGraph.Set(container.Name, container.ID); err != nil {
|
||||
utils.Debugf("Setting default id - %s", err)
|
||||
}
|
||||
register(container)
|
||||
|
@ -372,38 +372,38 @@ func (runtime *Runtime) restore() error {
|
|||
}
|
||||
|
||||
// Create creates a new container from the given configuration with a given name.
|
||||
func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Container, []string, error) {
|
||||
func (daemon *Daemon) Create(config *runconfig.Config, name string) (*Container, []string, error) {
|
||||
var (
|
||||
container *Container
|
||||
warnings []string
|
||||
)
|
||||
|
||||
img, err := runtime.repositories.LookupImage(config.Image)
|
||||
img, err := daemon.repositories.LookupImage(config.Image)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := runtime.checkImageDepth(img); err != nil {
|
||||
if err := daemon.checkImageDepth(img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if warnings, err = runtime.mergeAndVerifyConfig(config, img); err != nil {
|
||||
if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if container, err = runtime.newContainer(name, config, img); err != nil {
|
||||
if container, err = daemon.newContainer(name, config, img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := runtime.createRootfs(container, img); err != nil {
|
||||
if err := daemon.createRootfs(container, img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := runtime.Register(container); err != nil {
|
||||
if err := daemon.Register(container); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return container, warnings, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) checkImageDepth(img *image.Image) error {
|
||||
func (daemon *Daemon) checkImageDepth(img *image.Image) error {
|
||||
// We add 2 layers to the depth because the container's rw and
|
||||
// init layer add to the restriction
|
||||
depth, err := img.Depth()
|
||||
|
@ -416,7 +416,7 @@ func (runtime *Runtime) checkImageDepth(img *image.Image) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) checkDeprecatedExpose(config *runconfig.Config) bool {
|
||||
func (daemon *Daemon) checkDeprecatedExpose(config *runconfig.Config) bool {
|
||||
if config != nil {
|
||||
if config.PortSpecs != nil {
|
||||
for _, p := range config.PortSpecs {
|
||||
|
@ -429,9 +429,9 @@ func (runtime *Runtime) checkDeprecatedExpose(config *runconfig.Config) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (runtime *Runtime) mergeAndVerifyConfig(config *runconfig.Config, img *image.Image) ([]string, error) {
|
||||
func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image.Image) ([]string, error) {
|
||||
warnings := []string{}
|
||||
if runtime.checkDeprecatedExpose(img.Config) || runtime.checkDeprecatedExpose(config) {
|
||||
if daemon.checkDeprecatedExpose(img.Config) || daemon.checkDeprecatedExpose(config) {
|
||||
warnings = append(warnings, "The mapping to public ports on your host via Dockerfile EXPOSE (host:port:port) has been deprecated. Use -p to publish the ports.")
|
||||
}
|
||||
if img.Config != nil {
|
||||
|
@ -445,14 +445,14 @@ func (runtime *Runtime) mergeAndVerifyConfig(config *runconfig.Config, img *imag
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) generateIdAndName(name string) (string, string, error) {
|
||||
func (daemon *Daemon) generateIdAndName(name string) (string, string, error) {
|
||||
var (
|
||||
err error
|
||||
id = utils.GenerateRandomID()
|
||||
)
|
||||
|
||||
if name == "" {
|
||||
name, err = generateRandomName(runtime)
|
||||
name, err = generateRandomName(daemon)
|
||||
if err != nil {
|
||||
name = utils.TruncateID(id)
|
||||
}
|
||||
|
@ -465,19 +465,19 @@ func (runtime *Runtime) generateIdAndName(name string) (string, string, error) {
|
|||
name = "/" + name
|
||||
}
|
||||
// Set the enitity in the graph using the default name specified
|
||||
if _, err := runtime.containerGraph.Set(name, id); err != nil {
|
||||
if _, err := daemon.containerGraph.Set(name, id); err != nil {
|
||||
if !graphdb.IsNonUniqueNameError(err) {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
conflictingContainer, err := runtime.GetByName(name)
|
||||
conflictingContainer, err := daemon.GetByName(name)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "Could not find entity") {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Remove name and continue starting the container
|
||||
if err := runtime.containerGraph.Delete(name); err != nil {
|
||||
if err := daemon.containerGraph.Delete(name); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
} else {
|
||||
|
@ -490,7 +490,7 @@ func (runtime *Runtime) generateIdAndName(name string) (string, string, error) {
|
|||
return id, name, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) generateHostname(id string, config *runconfig.Config) {
|
||||
func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
|
||||
// Generate default hostname
|
||||
// FIXME: the lxc template no longer needs to set a default hostname
|
||||
if config.Hostname == "" {
|
||||
|
@ -498,7 +498,7 @@ func (runtime *Runtime) generateHostname(id string, config *runconfig.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
func (runtime *Runtime) getEntrypointAndArgs(config *runconfig.Config) (string, []string) {
|
||||
func (daemon *Daemon) getEntrypointAndArgs(config *runconfig.Config) (string, []string) {
|
||||
var (
|
||||
entrypoint string
|
||||
args []string
|
||||
|
@ -513,18 +513,18 @@ func (runtime *Runtime) getEntrypointAndArgs(config *runconfig.Config) (string,
|
|||
return entrypoint, args
|
||||
}
|
||||
|
||||
func (runtime *Runtime) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) {
|
||||
func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) {
|
||||
var (
|
||||
id string
|
||||
err error
|
||||
)
|
||||
id, name, err = runtime.generateIdAndName(name)
|
||||
id, name, err = daemon.generateIdAndName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime.generateHostname(id, config)
|
||||
entrypoint, args := runtime.getEntrypointAndArgs(config)
|
||||
daemon.generateHostname(id, config)
|
||||
entrypoint, args := daemon.getEntrypointAndArgs(config)
|
||||
|
||||
container := &Container{
|
||||
// FIXME: we should generate the ID here instead of receiving it as an argument
|
||||
|
@ -537,34 +537,34 @@ func (runtime *Runtime) newContainer(name string, config *runconfig.Config, img
|
|||
Image: img.ID, // Always use the resolved image id
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
Name: name,
|
||||
Driver: runtime.driver.String(),
|
||||
ExecDriver: runtime.execDriver.Name(),
|
||||
Driver: daemon.driver.String(),
|
||||
ExecDriver: daemon.execDriver.Name(),
|
||||
}
|
||||
container.root = runtime.containerRoot(container.ID)
|
||||
container.root = daemon.containerRoot(container.ID)
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) createRootfs(container *Container, img *image.Image) error {
|
||||
func (daemon *Daemon) createRootfs(container *Container, img *image.Image) error {
|
||||
// Step 1: create the container directory.
|
||||
// This doubles as a barrier to avoid race conditions.
|
||||
if err := os.Mkdir(container.root, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
initID := fmt.Sprintf("%s-init", container.ID)
|
||||
if err := runtime.driver.Create(initID, img.ID, ""); err != nil {
|
||||
if err := daemon.driver.Create(initID, img.ID, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
initPath, err := runtime.driver.Get(initID)
|
||||
initPath, err := daemon.driver.Get(initID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer runtime.driver.Put(initID)
|
||||
defer daemon.driver.Put(initID)
|
||||
|
||||
if err := graph.SetupInitLayer(initPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := runtime.driver.Create(container.ID, initID, ""); err != nil {
|
||||
if err := daemon.driver.Create(container.ID, initID, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -572,7 +572,7 @@ func (runtime *Runtime) createRootfs(container *Container, img *image.Image) err
|
|||
|
||||
// Commit creates a new filesystem image from the current state of a container.
|
||||
// The image can optionally be tagged into a repository
|
||||
func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*image.Image, error) {
|
||||
func (daemon *Daemon) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*image.Image, error) {
|
||||
// FIXME: freeze the container before copying it to avoid data corruption?
|
||||
if err := container.Mount(); err != nil {
|
||||
return nil, err
|
||||
|
@ -595,13 +595,13 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a
|
|||
containerImage = container.Image
|
||||
containerConfig = container.Config
|
||||
}
|
||||
img, err := runtime.graph.Create(rwTar, containerID, containerImage, comment, author, containerConfig, config)
|
||||
img, err := daemon.graph.Create(rwTar, containerID, containerImage, comment, author, containerConfig, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Register the image if needed
|
||||
if repository != "" {
|
||||
if err := runtime.repositories.Set(repository, tag, img.ID, true); err != nil {
|
||||
if err := daemon.repositories.Set(repository, tag, img.ID, true); err != nil {
|
||||
return img, err
|
||||
}
|
||||
}
|
||||
|
@ -618,31 +618,31 @@ func GetFullContainerName(name string) (string, error) {
|
|||
return name, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) GetByName(name string) (*Container, error) {
|
||||
func (daemon *Daemon) GetByName(name string) (*Container, error) {
|
||||
fullName, err := GetFullContainerName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entity := runtime.containerGraph.Get(fullName)
|
||||
entity := daemon.containerGraph.Get(fullName)
|
||||
if entity == nil {
|
||||
return nil, fmt.Errorf("Could not find entity for %s", name)
|
||||
}
|
||||
e := runtime.getContainerElement(entity.ID())
|
||||
e := daemon.getContainerElement(entity.ID())
|
||||
if e == nil {
|
||||
return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID())
|
||||
}
|
||||
return e.Value.(*Container), nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
|
||||
func (daemon *Daemon) Children(name string) (map[string]*Container, error) {
|
||||
name, err := GetFullContainerName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
children := make(map[string]*Container)
|
||||
|
||||
err = runtime.containerGraph.Walk(name, func(p string, e *graphdb.Entity) error {
|
||||
c := runtime.Get(e.ID())
|
||||
err = daemon.containerGraph.Walk(name, func(p string, e *graphdb.Entity) error {
|
||||
c := daemon.Get(e.ID())
|
||||
if c == nil {
|
||||
return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p)
|
||||
}
|
||||
|
@ -656,25 +656,25 @@ func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
|
|||
return children, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) RegisterLink(parent, child *Container, alias string) error {
|
||||
func (daemon *Daemon) RegisterLink(parent, child *Container, alias string) error {
|
||||
fullName := path.Join(parent.Name, alias)
|
||||
if !runtime.containerGraph.Exists(fullName) {
|
||||
_, err := runtime.containerGraph.Set(fullName, child.ID)
|
||||
if !daemon.containerGraph.Exists(fullName) {
|
||||
_, err := daemon.containerGraph.Set(fullName, child.ID)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME: harmonize with NewGraph()
|
||||
func NewRuntime(config *daemonconfig.Config, eng *engine.Engine) (*Runtime, error) {
|
||||
runtime, err := NewRuntimeFromDirectory(config, eng)
|
||||
func NewDaemon(config *daemonconfig.Config, eng *engine.Engine) (*Daemon, error) {
|
||||
daemon, err := NewDaemonFromDirectory(config, eng)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return runtime, nil
|
||||
return daemon, nil
|
||||
}
|
||||
|
||||
func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*Runtime, error) {
|
||||
func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*Daemon, error) {
|
||||
if !config.EnableSelinuxSupport {
|
||||
selinux.SetDisabled()
|
||||
}
|
||||
|
@ -693,9 +693,9 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*
|
|||
return nil, err
|
||||
}
|
||||
|
||||
runtimeRepo := path.Join(config.Root, "containers")
|
||||
daemonRepo := path.Join(config.Root, "containers")
|
||||
|
||||
if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
|
||||
if err := os.MkdirAll(daemonRepo, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -774,8 +774,8 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*
|
|||
return nil, err
|
||||
}
|
||||
|
||||
runtime := &Runtime{
|
||||
repository: runtimeRepo,
|
||||
daemon := &Daemon{
|
||||
repository: daemonRepo,
|
||||
containers: list.New(),
|
||||
graph: g,
|
||||
repositories: repositories,
|
||||
|
@ -790,19 +790,19 @@ func NewRuntimeFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*
|
|||
eng: eng,
|
||||
}
|
||||
|
||||
if err := runtime.checkLocaldns(); err != nil {
|
||||
if err := daemon.checkLocaldns(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := runtime.restore(); err != nil {
|
||||
if err := daemon.restore(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return runtime, nil
|
||||
return daemon, nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) shutdown() error {
|
||||
func (daemon *Daemon) shutdown() error {
|
||||
group := sync.WaitGroup{}
|
||||
utils.Debugf("starting clean shutdown of all containers...")
|
||||
for _, container := range runtime.List() {
|
||||
for _, container := range daemon.List() {
|
||||
c := container
|
||||
if c.State.IsRunning() {
|
||||
utils.Debugf("stopping %s", c.ID)
|
||||
|
@ -823,22 +823,22 @@ func (runtime *Runtime) shutdown() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Close() error {
|
||||
func (daemon *Daemon) Close() error {
|
||||
errorsStrings := []string{}
|
||||
if err := runtime.shutdown(); err != nil {
|
||||
utils.Errorf("runtime.shutdown(): %s", err)
|
||||
if err := daemon.shutdown(); err != nil {
|
||||
utils.Errorf("daemon.shutdown(): %s", err)
|
||||
errorsStrings = append(errorsStrings, err.Error())
|
||||
}
|
||||
if err := portallocator.ReleaseAll(); err != nil {
|
||||
utils.Errorf("portallocator.ReleaseAll(): %s", err)
|
||||
errorsStrings = append(errorsStrings, err.Error())
|
||||
}
|
||||
if err := runtime.driver.Cleanup(); err != nil {
|
||||
utils.Errorf("runtime.driver.Cleanup(): %s", err.Error())
|
||||
if err := daemon.driver.Cleanup(); err != nil {
|
||||
utils.Errorf("daemon.driver.Cleanup(): %s", err.Error())
|
||||
errorsStrings = append(errorsStrings, err.Error())
|
||||
}
|
||||
if err := runtime.containerGraph.Close(); err != nil {
|
||||
utils.Errorf("runtime.containerGraph.Close(): %s", err.Error())
|
||||
if err := daemon.containerGraph.Close(); err != nil {
|
||||
utils.Errorf("daemon.containerGraph.Close(): %s", err.Error())
|
||||
errorsStrings = append(errorsStrings, err.Error())
|
||||
}
|
||||
if len(errorsStrings) > 0 {
|
||||
|
@ -847,55 +847,55 @@ func (runtime *Runtime) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Mount(container *Container) error {
|
||||
dir, err := runtime.driver.Get(container.ID)
|
||||
func (daemon *Daemon) Mount(container *Container) error {
|
||||
dir, err := daemon.driver.Get(container.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting container %s from driver %s: %s", container.ID, runtime.driver, err)
|
||||
return fmt.Errorf("Error getting container %s from driver %s: %s", container.ID, daemon.driver, err)
|
||||
}
|
||||
if container.basefs == "" {
|
||||
container.basefs = dir
|
||||
} else if container.basefs != dir {
|
||||
return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
|
||||
runtime.driver, container.ID, container.basefs, dir)
|
||||
daemon.driver, container.ID, container.basefs, dir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Unmount(container *Container) error {
|
||||
runtime.driver.Put(container.ID)
|
||||
func (daemon *Daemon) Unmount(container *Container) error {
|
||||
daemon.driver.Put(container.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) {
|
||||
if differ, ok := runtime.driver.(graphdriver.Differ); ok {
|
||||
func (daemon *Daemon) Changes(container *Container) ([]archive.Change, error) {
|
||||
if differ, ok := daemon.driver.(graphdriver.Differ); ok {
|
||||
return differ.Changes(container.ID)
|
||||
}
|
||||
cDir, err := runtime.driver.Get(container.ID)
|
||||
cDir, err := daemon.driver.Get(container.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
|
||||
return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.daemon.driver, err)
|
||||
}
|
||||
defer runtime.driver.Put(container.ID)
|
||||
initDir, err := runtime.driver.Get(container.ID + "-init")
|
||||
defer daemon.driver.Put(container.ID)
|
||||
initDir, err := daemon.driver.Get(container.ID + "-init")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
|
||||
return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.daemon.driver, err)
|
||||
}
|
||||
defer runtime.driver.Put(container.ID + "-init")
|
||||
defer daemon.driver.Put(container.ID + "-init")
|
||||
return archive.ChangesDirs(cDir, initDir)
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) {
|
||||
if differ, ok := runtime.driver.(graphdriver.Differ); ok {
|
||||
func (daemon *Daemon) Diff(container *Container) (archive.Archive, error) {
|
||||
if differ, ok := daemon.driver.(graphdriver.Differ); ok {
|
||||
return differ.Diff(container.ID)
|
||||
}
|
||||
|
||||
changes, err := runtime.Changes(container)
|
||||
changes, err := daemon.Changes(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cDir, err := runtime.driver.Get(container.ID)
|
||||
cDir, err := daemon.driver.Get(container.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
|
||||
return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.daemon.driver, err)
|
||||
}
|
||||
|
||||
archive, err := archive.ExportChanges(cDir, changes)
|
||||
|
@ -904,26 +904,26 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) {
|
|||
}
|
||||
return utils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
runtime.driver.Put(container.ID)
|
||||
daemon.driver.Put(container.ID)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
return runtime.execDriver.Run(c.command, pipes, startCallback)
|
||||
func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
return daemon.execDriver.Run(c.command, pipes, startCallback)
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Kill(c *Container, sig int) error {
|
||||
return runtime.execDriver.Kill(c.command, sig)
|
||||
func (daemon *Daemon) Kill(c *Container, sig int) error {
|
||||
return daemon.execDriver.Kill(c.command, sig)
|
||||
}
|
||||
|
||||
// Nuke kills all containers then removes all content
|
||||
// from the content root, including images, volumes and
|
||||
// container filesystems.
|
||||
// Again: this will remove your entire docker runtime!
|
||||
func (runtime *Runtime) Nuke() error {
|
||||
// Again: this will remove your entire docker daemon!
|
||||
func (daemon *Daemon) Nuke() error {
|
||||
var wg sync.WaitGroup
|
||||
for _, container := range runtime.List() {
|
||||
for _, container := range daemon.List() {
|
||||
wg.Add(1)
|
||||
go func(c *Container) {
|
||||
c.Kill()
|
||||
|
@ -931,63 +931,63 @@ func (runtime *Runtime) Nuke() error {
|
|||
}(container)
|
||||
}
|
||||
wg.Wait()
|
||||
runtime.Close()
|
||||
daemon.Close()
|
||||
|
||||
return os.RemoveAll(runtime.config.Root)
|
||||
return os.RemoveAll(daemon.config.Root)
|
||||
}
|
||||
|
||||
// FIXME: this is a convenience function for integration tests
|
||||
// which need direct access to runtime.graph.
|
||||
// which need direct access to daemon.graph.
|
||||
// Once the tests switch to using engine and jobs, this method
|
||||
// can go away.
|
||||
func (runtime *Runtime) Graph() *graph.Graph {
|
||||
return runtime.graph
|
||||
func (daemon *Daemon) Graph() *graph.Graph {
|
||||
return daemon.graph
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Repositories() *graph.TagStore {
|
||||
return runtime.repositories
|
||||
func (daemon *Daemon) Repositories() *graph.TagStore {
|
||||
return daemon.repositories
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Config() *daemonconfig.Config {
|
||||
return runtime.config
|
||||
func (daemon *Daemon) Config() *daemonconfig.Config {
|
||||
return daemon.config
|
||||
}
|
||||
|
||||
func (runtime *Runtime) SystemConfig() *sysinfo.SysInfo {
|
||||
return runtime.sysInfo
|
||||
func (daemon *Daemon) SystemConfig() *sysinfo.SysInfo {
|
||||
return daemon.sysInfo
|
||||
}
|
||||
|
||||
func (runtime *Runtime) SystemInitPath() string {
|
||||
return runtime.sysInitPath
|
||||
func (daemon *Daemon) SystemInitPath() string {
|
||||
return daemon.sysInitPath
|
||||
}
|
||||
|
||||
func (runtime *Runtime) GraphDriver() graphdriver.Driver {
|
||||
return runtime.driver
|
||||
func (daemon *Daemon) GraphDriver() graphdriver.Driver {
|
||||
return daemon.driver
|
||||
}
|
||||
|
||||
func (runtime *Runtime) ExecutionDriver() execdriver.Driver {
|
||||
return runtime.execDriver
|
||||
func (daemon *Daemon) ExecutionDriver() execdriver.Driver {
|
||||
return daemon.execDriver
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Volumes() *graph.Graph {
|
||||
return runtime.volumes
|
||||
func (daemon *Daemon) Volumes() *graph.Graph {
|
||||
return daemon.volumes
|
||||
}
|
||||
|
||||
func (runtime *Runtime) ContainerGraph() *graphdb.Database {
|
||||
return runtime.containerGraph
|
||||
func (daemon *Daemon) ContainerGraph() *graphdb.Database {
|
||||
return daemon.containerGraph
|
||||
}
|
||||
|
||||
func (runtime *Runtime) SetServer(server Server) {
|
||||
runtime.srv = server
|
||||
func (daemon *Daemon) SetServer(server Server) {
|
||||
daemon.srv = server
|
||||
}
|
||||
|
||||
func (runtime *Runtime) checkLocaldns() error {
|
||||
func (daemon *Daemon) checkLocaldns() error {
|
||||
resolvConf, err := utils.GetResolvConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
if len(daemon.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
log.Printf("Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v\n", DefaultDns)
|
||||
runtime.config.Dns = DefaultDns
|
||||
daemon.config.Dns = DefaultDns
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
// +build !exclude_graphdriver_aufs
|
||||
|
||||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver/aufs"
|
||||
"github.com/dotcloud/docker/graph"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver/aufs"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// +build !exclude_graphdriver_btrfs
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
_ "github.com/dotcloud/docker/daemon/graphdriver/btrfs"
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
// +build !exclude_graphdriver_devicemapper
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
_ "github.com/dotcloud/docker/daemon/graphdriver/devmapper"
|
||||
)
|
|
@ -1,9 +1,9 @@
|
|||
// +build exclude_graphdriver_aufs
|
||||
|
||||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
)
|
||||
|
||||
func migrateIfAufs(driver graphdriver.Driver, root string) error {
|
|
@ -2,10 +2,10 @@ package execdrivers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/lxc"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native"
|
||||
"github.com/dotcloud/docker/pkg/sysinfo"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/dotcloud/docker/runtime/execdriver/lxc"
|
||||
"github.com/dotcloud/docker/runtime/execdriver/native"
|
||||
"path"
|
||||
)
|
||||
|
|
@ -2,9 +2,9 @@ package lxc
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/pkg/cgroups"
|
||||
"github.com/dotcloud/docker/pkg/label"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
|
@ -3,9 +3,9 @@ package lxc
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/pkg/netlink"
|
||||
"github.com/dotcloud/docker/pkg/user"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
"io/ioutil"
|
||||
"net"
|
|
@ -1,8 +1,8 @@
|
|||
package lxc
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/pkg/label"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
|
@ -3,7 +3,7 @@ package lxc
|
|||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
|
@ -1,7 +1,7 @@
|
|||
package configuration
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/runtime/execdriver/native/template"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native/template"
|
||||
"testing"
|
||||
)
|
||||
|
|
@ -4,12 +4,12 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native/configuration"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native/template"
|
||||
"github.com/dotcloud/docker/pkg/apparmor"
|
||||
"github.com/dotcloud/docker/pkg/label"
|
||||
"github.com/dotcloud/docker/pkg/libcontainer"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/dotcloud/docker/runtime/execdriver/native/configuration"
|
||||
"github.com/dotcloud/docker/runtime/execdriver/native/template"
|
||||
)
|
||||
|
||||
// createContainer populates and configures the container type with the
|
|
@ -3,12 +3,6 @@ package native
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/pkg/apparmor"
|
||||
"github.com/dotcloud/docker/pkg/cgroups"
|
||||
"github.com/dotcloud/docker/pkg/libcontainer"
|
||||
"github.com/dotcloud/docker/pkg/libcontainer/nsinit"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
@ -18,6 +12,13 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/pkg/apparmor"
|
||||
"github.com/dotcloud/docker/pkg/cgroups"
|
||||
"github.com/dotcloud/docker/pkg/libcontainer"
|
||||
"github.com/dotcloud/docker/pkg/libcontainer/nsinit"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
)
|
||||
|
||||
const (
|
|
@ -5,7 +5,7 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
|
@ -24,8 +24,8 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
mountpk "github.com/dotcloud/docker/pkg/mount"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"os"
|
||||
"os/exec"
|
|
@ -5,7 +5,7 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
|
@ -11,7 +11,7 @@ import "C"
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
|
@ -4,7 +4,7 @@ package devmapper
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"os"
|
|
@ -4,7 +4,7 @@ package devmapper
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"runtime"
|
|
@ -2,7 +2,7 @@ package vfs
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/runtime/graphdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
|
@ -1,4 +1,4 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
@ -14,7 +14,7 @@ func (history *History) Len() int {
|
|||
|
||||
func (history *History) Less(i, j int) bool {
|
||||
containers := *history
|
||||
return containers[j].When().Before(containers[i].When())
|
||||
return containers[j].Created.Before(containers[i].Created)
|
||||
}
|
||||
|
||||
func (history *History) Swap(i, j int) {
|
|
@ -0,0 +1,42 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/nat"
|
||||
)
|
||||
|
||||
// FIXME: move deprecated port stuff to nat to clean up the core.
|
||||
type PortMapping map[string]string // Deprecated
|
||||
|
||||
type NetworkSettings struct {
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping // Deprecated
|
||||
Ports nat.PortMap
|
||||
}
|
||||
|
||||
func (settings *NetworkSettings) PortMappingAPI() *engine.Table {
|
||||
var outs = engine.NewTable("", 0)
|
||||
for port, bindings := range settings.Ports {
|
||||
p, _ := nat.ParsePort(port.Port())
|
||||
if len(bindings) == 0 {
|
||||
out := &engine.Env{}
|
||||
out.SetInt("PublicPort", p)
|
||||
out.Set("Type", port.Proto())
|
||||
outs.Add(out)
|
||||
continue
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
out := &engine.Env{}
|
||||
h, _ := nat.ParsePort(binding.HostPort)
|
||||
out.SetInt("PrivatePort", p)
|
||||
out.SetInt("PublicPort", h)
|
||||
out.Set("Type", port.Proto())
|
||||
out.Set("IP", binding.HostIp)
|
||||
outs.Add(out)
|
||||
}
|
||||
}
|
||||
return outs
|
||||
}
|
|
@ -2,13 +2,13 @@ package bridge
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/daemon/networkdriver"
|
||||
"github.com/dotcloud/docker/daemon/networkdriver/ipallocator"
|
||||
"github.com/dotcloud/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/dotcloud/docker/daemon/networkdriver/portmapper"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/pkg/iptables"
|
||||
"github.com/dotcloud/docker/pkg/netlink"
|
||||
"github.com/dotcloud/docker/runtime/networkdriver"
|
||||
"github.com/dotcloud/docker/runtime/networkdriver/ipallocator"
|
||||
"github.com/dotcloud/docker/runtime/networkdriver/portallocator"
|
||||
"github.com/dotcloud/docker/runtime/networkdriver/portmapper"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
@ -32,7 +32,7 @@ var (
|
|||
// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
|
||||
// In theory this shouldn't matter - in practice there's bound to be a few scripts relying
|
||||
// on the internal addressing or other stupid things like that.
|
||||
// The shouldn't, but hey, let's not break them unless we really have to.
|
||||
// They shouldn't, but hey, let's not break them unless we really have to.
|
||||
"172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23
|
||||
"10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive
|
||||
"10.1.42.1/16",
|
|
@ -3,8 +3,8 @@ package ipallocator
|
|||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/dotcloud/docker/daemon/networkdriver"
|
||||
"github.com/dotcloud/docker/pkg/collections"
|
||||
"github.com/dotcloud/docker/runtime/networkdriver"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/utils"
|
|
@ -1,4 +1,4 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import "sort"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,4 +1,4 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -51,14 +51,14 @@ func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig, driverConfig map[
|
|||
}
|
||||
|
||||
type checker struct {
|
||||
runtime *Runtime
|
||||
daemon *Daemon
|
||||
}
|
||||
|
||||
func (c *checker) Exists(name string) bool {
|
||||
return c.runtime.containerGraph.Exists("/" + name)
|
||||
return c.daemon.containerGraph.Exists("/" + name)
|
||||
}
|
||||
|
||||
// Generate a random and unique name
|
||||
func generateRandomName(runtime *Runtime) (string, error) {
|
||||
return namesgenerator.GenerateRandomName(&checker{runtime})
|
||||
func generateRandomName(daemon *Daemon) (string, error) {
|
||||
return namesgenerator.GenerateRandomName(&checker{daemon})
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -1,9 +1,9 @@
|
|||
package runtime
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/runtime/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -33,9 +33,14 @@ func prepareVolumesForContainer(container *Container) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setupMountsForContainer(container *Container, envPath string) error {
|
||||
func setupMountsForContainer(container *Container) error {
|
||||
envPath, err := container.EnvConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mounts := []execdriver.Mount{
|
||||
{container.runtime.sysInitPath, "/.dockerinit", false, true},
|
||||
{container.daemon.sysInitPath, "/.dockerinit", false, true},
|
||||
{envPath, "/.dockerenv", false, true},
|
||||
{container.ResolvConfPath, "/etc/resolv.conf", false, true},
|
||||
}
|
||||
|
@ -80,7 +85,7 @@ func applyVolumesFrom(container *Container) error {
|
|||
}
|
||||
}
|
||||
|
||||
c := container.runtime.Get(specParts[0])
|
||||
c := container.daemon.Get(specParts[0])
|
||||
if c == nil {
|
||||
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0])
|
||||
}
|
||||
|
@ -162,7 +167,7 @@ func createVolumes(container *Container) error {
|
|||
return err
|
||||
}
|
||||
|
||||
volumesDriver := container.runtime.volumes.Driver()
|
||||
volumesDriver := container.daemon.volumes.Driver()
|
||||
// Create the requested volumes if they don't exist
|
||||
for volPath := range container.Config.Volumes {
|
||||
volPath = filepath.Clean(volPath)
|
||||
|
@ -195,7 +200,7 @@ func createVolumes(container *Container) error {
|
|||
// Do not pass a container as the parameter for the volume creation.
|
||||
// The graph driver using the container's information ( Image ) to
|
||||
// create the parent.
|
||||
c, err := container.runtime.volumes.Create(nil, "", "", "", "", nil, nil)
|
||||
c, err := container.daemon.volumes.Create(nil, "", "", "", "", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package daemonconfig
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/daemon/networkdriver"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/runtime/networkdriver"
|
||||
"net"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,19 +1,45 @@
|
|||
FROM ubuntu:12.04
|
||||
MAINTAINER Nick Stinemates
|
||||
#
|
||||
# docker build -t docker:docs . && docker run -p 8000:8000 docker:docs
|
||||
# See the top level Makefile in https://github.com/dotcloud/docker for usage.
|
||||
#
|
||||
FROM debian:jessie
|
||||
MAINTAINER Sven Dowideit <SvenDowideit@docker.com> (@SvenDowideit)
|
||||
|
||||
# TODO switch to http://packages.ubuntu.com/trusty/python-sphinxcontrib-httpdomain once trusty is released
|
||||
RUN apt-get update && apt-get install -yq make python-pip python-setuptools vim-tiny git pandoc
|
||||
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq make python-pip python-setuptools
|
||||
RUN pip install mkdocs
|
||||
|
||||
# installing sphinx for the rst->md conversion only - will be removed after May release
|
||||
# pip installs from docs/requirements.txt, but here to increase cacheability
|
||||
RUN pip install Sphinx==1.2.1
|
||||
RUN pip install sphinxcontrib-httpdomain==1.2.0
|
||||
ADD . /docs
|
||||
RUN make -C /docs clean docs
|
||||
RUN pip install Sphinx==1.2.1
|
||||
RUN pip install sphinxcontrib-httpdomain==1.2.0
|
||||
|
||||
# add MarkdownTools to get transclusion
|
||||
# (future development)
|
||||
#RUN easy_install -U setuptools
|
||||
#RUN pip install MarkdownTools2
|
||||
|
||||
# this week I seem to need the latest dev release of awscli too
|
||||
# awscli 1.3.6 does --error-document correctly
|
||||
# https://github.com/aws/aws-cli/commit/edc2290e173dfaedc70b48cfa3624d58c533c6c3
|
||||
RUN pip install awscli
|
||||
|
||||
# get my sitemap.xml branch of mkdocs and use that for now
|
||||
RUN git clone https://github.com/SvenDowideit/mkdocs &&\
|
||||
cd mkdocs/ &&\
|
||||
git checkout docker-markdown-merge &&\
|
||||
./setup.py install
|
||||
|
||||
ADD . /docs
|
||||
ADD MAINTAINERS /docs/sources/humans.txt
|
||||
WORKDIR /docs
|
||||
|
||||
#build the sphinx html
|
||||
#RUN make -C /docs clean docs
|
||||
|
||||
#convert to markdown
|
||||
#RUN ./convert.sh
|
||||
|
||||
WORKDIR /docs/_build/html
|
||||
CMD ["python", "-m", "SimpleHTTPServer"]
|
||||
# note, EXPOSE is only last because of https://github.com/dotcloud/docker/issues/3525
|
||||
EXPOSE 8000
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["mkdocs", "serve"]
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
James Turnbull <james@lovedthanlost.net> (@jamtur01)
|
||||
Sven Dowideit <SvenDowideit@fosiki.com> (@SvenDowideit)
|
||||
O.S. Tezer <ostezer@gmail.com> (@OSTezer)
|
||||
|
|
|
@ -4,77 +4,49 @@ Docker Documentation
|
|||
Overview
|
||||
--------
|
||||
|
||||
The source for Docker documentation is here under ``sources/`` in the
|
||||
form of .rst files. These files use
|
||||
[reStructuredText](http://docutils.sourceforge.net/rst.html)
|
||||
formatting with [Sphinx](http://sphinx-doc.org/) extensions for
|
||||
structure, cross-linking and indexing.
|
||||
The source for Docker documentation is here under ``sources/`` and uses
|
||||
extended Markdown, as implemented by [mkdocs](http://mkdocs.org).
|
||||
|
||||
The HTML files are built and hosted on
|
||||
[readthedocs.org](https://readthedocs.org/projects/docker/), appearing
|
||||
via proxy on https://docs.docker.io. The HTML files update
|
||||
The HTML files are built and hosted on https://docs.docker.io, and update
|
||||
automatically after each change to the master or release branch of the
|
||||
[docker files on GitHub](https://github.com/dotcloud/docker) thanks to
|
||||
post-commit hooks. The "release" branch maps to the "latest"
|
||||
documentation and the "master" branch maps to the "master"
|
||||
documentation and the "master" (unreleased development) branch maps to the "master"
|
||||
documentation.
|
||||
|
||||
## Branches
|
||||
|
||||
**There are two branches related to editing docs**: ``master`` and a
|
||||
``doc*`` branch (currently ``doc0.8.1``). You should normally edit
|
||||
docs on the ``master`` branch. That way your fixes will automatically
|
||||
get included in later releases, and docs maintainers can easily
|
||||
cherry-pick your changes to bring over to the current docs branch. In
|
||||
the rare case where your change is not forward-compatible, then you
|
||||
could base your change on the appropriate ``doc*`` branch.
|
||||
``docs`` branch. You should always edit
|
||||
docs on a local branch of the ``master`` branch, and send a PR against ``master``.
|
||||
That way your fixes
|
||||
will automatically get included in later releases, and docs maintainers
|
||||
can easily cherry-pick your changes into the ``docs`` release branch.
|
||||
In the rare case where your change is not forward-compatible,
|
||||
you may need to base your changes on the ``docs`` branch.
|
||||
|
||||
Now that we have a ``doc*`` branch, we can keep the ``latest`` docs
|
||||
Now that we have a ``docs`` branch, we can keep the [http://docs.docker.io](http://docs.docker.io) docs
|
||||
up to date with any bugs found between ``docker`` code releases.
|
||||
|
||||
**Warning**: When *reading* the docs, the ``master`` documentation may
|
||||
**Warning**: When *reading* the docs, the [http://beta-docs.docker.io](http://beta-docs.docker.io) documentation may
|
||||
include features not yet part of any official docker
|
||||
release. ``Master`` docs should be used only for understanding
|
||||
bleeding-edge development and ``latest`` (which points to the ``doc*``
|
||||
release. The ``beta-docs`` site should be used only for understanding
|
||||
bleeding-edge development and ``docs.docker.io`` (which points to the ``docs``
|
||||
branch``) should be used for the latest official release.
|
||||
|
||||
If you need to manually trigger a build of an existing branch, then
|
||||
you can do that through the [readthedocs
|
||||
interface](https://readthedocs.org/builds/docker/). If you would like
|
||||
to add new build targets, including new branches or tags, then you
|
||||
must contact one of the existing maintainers and get your
|
||||
readthedocs.org account added to the maintainers list, or just file an
|
||||
issue on GitHub describing the branch/tag and why it needs to be added
|
||||
to the docs, and one of the maintainers will add it for you.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
To edit and test the docs, you'll need to install the Sphinx tool and
|
||||
its dependencies. There are two main ways to install this tool:
|
||||
|
||||
### Native Installation
|
||||
|
||||
Install dependencies from `requirements.txt` file in your `docker/docs`
|
||||
directory:
|
||||
|
||||
* Linux: `pip install -r docs/requirements.txt`
|
||||
|
||||
* Mac OS X: `[sudo] pip-2.7 install -r docs/requirements.txt`
|
||||
|
||||
### Alternative Installation: Docker Container
|
||||
|
||||
If you're running ``docker`` on your development machine then you may
|
||||
find it easier and cleaner to use the docs Dockerfile. This installs Sphinx
|
||||
in a container, adds the local ``docs/`` directory and builds the HTML
|
||||
docs inside the container, even starting a simple HTTP server on port
|
||||
8000 so that you can connect and see your changes.
|
||||
Docker documentation builds are done in a docker container, which installs all
|
||||
the required tools, adds the local ``docs/`` directory and builds the HTML
|
||||
docs. It then starts a HTTP server on port 8000 so that you can connect
|
||||
and see your changes.
|
||||
|
||||
In the ``docker`` source directory, run:
|
||||
```make docs```
|
||||
|
||||
This is the equivalent to ``make clean server`` since each container
|
||||
starts clean.
|
||||
If you have any issues you need to debug, you can use ``make docs-shell`` and
|
||||
then run ``mkdocs serve``
|
||||
|
||||
# Contributing
|
||||
|
||||
|
@ -84,8 +56,8 @@ starts clean.
|
|||
``../CONTRIBUTING.md``](../CONTRIBUTING.md)).
|
||||
* [Remember to sign your work!](../CONTRIBUTING.md#sign-your-work)
|
||||
* Work in your own fork of the code, we accept pull requests.
|
||||
* Change the ``.rst`` files with your favorite editor -- try to keep the
|
||||
lines short and respect RST and Sphinx conventions.
|
||||
* Change the ``.md`` files with your favorite editor -- try to keep the
|
||||
lines short (80 chars) and respect Markdown conventions.
|
||||
* Run ``make clean docs`` to clean up old files and generate new ones,
|
||||
or just ``make docs`` to update after small changes.
|
||||
* Your static website can now be found in the ``_build`` directory.
|
||||
|
@ -94,27 +66,13 @@ starts clean.
|
|||
|
||||
``make clean docs`` must complete without any warnings or errors.
|
||||
|
||||
## Special Case for RST Newbies:
|
||||
|
||||
If you want to write a new doc or make substantial changes to an
|
||||
existing doc, but **you don't know RST syntax**, we will accept pull
|
||||
requests in Markdown and plain text formats. We really want to
|
||||
encourage people to share their knowledge and don't want the markup
|
||||
syntax to be the obstacle. So when you make the Pull Request, please
|
||||
note in your comment that you need RST markup assistance, and we'll
|
||||
make the changes for you, and then we will make a pull request to your
|
||||
pull request so that you can get all the changes and learn about the
|
||||
markup. You still need to follow the
|
||||
[``CONTRIBUTING``](../CONTRIBUTING) guidelines, so please sign your
|
||||
commits.
|
||||
|
||||
Working using GitHub's file editor
|
||||
----------------------------------
|
||||
|
||||
Alternatively, for small changes and typos you might want to use
|
||||
GitHub's built in file editor. It allows you to preview your changes
|
||||
right online (though there can be some differences between GitHub
|
||||
markdown and Sphinx RST). Just be careful not to create many commits.
|
||||
Markdown and mkdocs Markdown). Just be careful not to create many commits.
|
||||
And you must still [sign your work!](../CONTRIBUTING.md#sign-your-work)
|
||||
|
||||
Images
|
||||
|
@ -122,62 +80,25 @@ Images
|
|||
|
||||
When you need to add images, try to make them as small as possible
|
||||
(e.g. as gif). Usually images should go in the same directory as the
|
||||
.rst file which references them, or in a subdirectory if one already
|
||||
.md file which references them, or in a subdirectory if one already
|
||||
exists.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Publishing Documentation
|
||||
------------------------
|
||||
|
||||
* For the template the css is compiled from less. When changes are
|
||||
needed they can be compiled using
|
||||
To publish a copy of the documentation you need a ``docs/awsconfig``
|
||||
file containing AWS settings to deploy to. The release script will
|
||||
create an s3 if needed, and will then push the files to it.
|
||||
|
||||
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
|
||||
```
|
||||
[profile dowideit-docs]
|
||||
aws_access_key_id = IHOIUAHSIDH234rwf....
|
||||
aws_secret_access_key = OIUYSADJHLKUHQWIUHE......
|
||||
region = ap-southeast-2
|
||||
```
|
||||
|
||||
Guides on using sphinx
|
||||
----------------------
|
||||
* To make links to certain sections create a link target like so:
|
||||
The ``profile`` name must be the same as the name of the bucket you are
|
||||
deploying to - which you call from the docker directory:
|
||||
|
||||
```
|
||||
.. _hello_world:
|
||||
|
||||
Hello world
|
||||
===========
|
||||
|
||||
This is a reference to :ref:`hello_world` and will work even if we
|
||||
move the target to another file or change the title of the section.
|
||||
```
|
||||
|
||||
The ``_hello_world:`` will make it possible to link to this position
|
||||
(page and section heading) from all other pages. See the [Sphinx
|
||||
docs](http://sphinx-doc.org/markup/inline.html#role-ref) for more
|
||||
information and examples.
|
||||
|
||||
* Notes, warnings and alarms
|
||||
|
||||
```
|
||||
# a note (use when something is important)
|
||||
.. note::
|
||||
|
||||
# a warning (orange)
|
||||
.. warning::
|
||||
|
||||
# danger (red, use sparsely)
|
||||
.. danger::
|
||||
|
||||
* Code examples
|
||||
|
||||
* Start typed commands with ``$ `` (dollar space) so that they
|
||||
are easily differentiated from program output.
|
||||
* Use "sudo" with docker to ensure that your command is runnable
|
||||
even if they haven't [used the *docker*
|
||||
group](http://docs.docker.io/en/latest/use/basics/#why-sudo).
|
||||
|
||||
Manpages
|
||||
--------
|
||||
|
||||
* To make the manpages, run ``make man``. Please note there is a bug
|
||||
in Sphinx 1.1.3 which makes this fail. Upgrade to the latest version
|
||||
of Sphinx.
|
||||
* Then preview the manpage by running ``man _build/man/docker.1``,
|
||||
where ``_build/man/docker.1`` is the path to the generated manfile
|
||||
``make AWS_S3_BUCKET=dowideit-docs docs-release``
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
diff --git a/docs/sources/examples/hello_world.md b/docs/sources/examples/hello_world.md
|
||||
index 6e072f6..5a4537d 100644
|
||||
--- a/docs/sources/examples/hello_world.md
|
||||
+++ b/docs/sources/examples/hello_world.md
|
||||
@@ -59,6 +59,9 @@ standard out.
|
||||
|
||||
See the example in action
|
||||
|
||||
+<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/7658.js"id="asciicast-7658" async></script></body>"></iframe>
|
||||
+
|
||||
+
|
||||
## Hello World Daemon
|
||||
|
||||
Note
|
||||
@@ -142,6 +145,8 @@ Make sure it is really stopped.
|
||||
|
||||
See the example in action
|
||||
|
||||
+<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/2562.js"id="asciicast-2562" async></script></body>"></iframe>
|
||||
+
|
||||
The next example in the series is a [*Node.js Web
|
||||
App*](../nodejs_web_app/#nodejs-web-app) example, or you could skip to
|
||||
any of the other examples:
|
||||
diff --git a/docs/asciinema.patch b/docs/asciinema.patch
|
||||
index e240bf3..e69de29 100644
|
||||
--- a/docs/asciinema.patch
|
||||
+++ b/docs/asciinema.patch
|
||||
@@ -1,23 +0,0 @@
|
||||
-diff --git a/docs/sources/examples/hello_world.md b/docs/sources/examples/hello_world.md
|
||||
-index 6e072f6..5a4537d 100644
|
||||
---- a/docs/sources/examples/hello_world.md
|
||||
-+++ b/docs/sources/examples/hello_world.md
|
||||
-@@ -59,6 +59,9 @@ standard out.
|
||||
-
|
||||
- See the example in action
|
||||
-
|
||||
-+<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/7658.js"id="asciicast-7658" async></script></body>"></iframe>
|
||||
-+
|
||||
-+
|
||||
- ## Hello World Daemon
|
||||
-
|
||||
- Note
|
||||
-@@ -142,6 +145,8 @@ Make sure it is really stopped.
|
||||
-
|
||||
- See the example in action
|
||||
-
|
||||
-+<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/2562.js"id="asciicast-2562" async></script></body>"></iframe>
|
||||
-+
|
||||
- The next example in the series is a [*Node.js Web
|
||||
- App*](../nodejs_web_app/#nodejs-web-app) example, or you could skip to
|
||||
- any of the other examples:
|
||||
diff --git a/docs/sources/examples/hello_world.md b/docs/sources/examples/hello_world.md
|
||||
index 6e072f6..c277f38 100644
|
||||
--- a/docs/sources/examples/hello_world.md
|
||||
+++ b/docs/sources/examples/hello_world.md
|
||||
@@ -59,6 +59,8 @@ standard out.
|
||||
|
||||
See the example in action
|
||||
|
||||
+<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/7658.js"id="asciicast-7658" async></script></body>"></iframe>
|
||||
+
|
||||
## Hello World Daemon
|
||||
|
||||
Note
|
||||
@@ -142,6 +144,8 @@ Make sure it is really stopped.
|
||||
|
||||
See the example in action
|
||||
|
||||
+<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/2562.js"id="asciicast-2562" async></script></body>"></iframe>
|
||||
+
|
||||
The next example in the series is a [*Node.js Web
|
||||
App*](../nodejs_web_app/#nodejs-web-app) example, or you could skip to
|
||||
any of the other examples:
|
||||
diff --git a/docs/sources/use/workingwithrepository.md b/docs/sources/use/workingwithrepository.md
|
||||
index 2122b8d..49edbc8 100644
|
||||
--- a/docs/sources/use/workingwithrepository.md
|
||||
+++ b/docs/sources/use/workingwithrepository.md
|
||||
@@ -199,6 +199,8 @@ searchable (or indexed at all) in the Central Index, and there will be
|
||||
no user name checking performed. Your registry will function completely
|
||||
independently from the Central Index.
|
||||
|
||||
+<iframe width="640" height="360" src="//www.youtube.com/embed/CAewZCBT4PI?rel=0" frameborder="0" allowfullscreen></iframe>
|
||||
+
|
||||
See also
|
||||
|
||||
[Docker Blog: How to use your own
|
|
@ -0,0 +1,53 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd /
|
||||
|
||||
#run the sphinx build first
|
||||
make -C /docs clean docs
|
||||
|
||||
cd /docs
|
||||
|
||||
#find sources -name '*.md*' -exec rm '{}' \;
|
||||
|
||||
# convert from rst to md for mkdocs.org
|
||||
# TODO: we're using a sphinx specific rst thing to do between docs links, which we then need to convert to mkdocs specific markup (and pandoc loses it when converting to html / md)
|
||||
HTML_FILES=$(find _build -name '*.html' | sed 's/_build\/html\/\(.*\)\/index.html/\1/')
|
||||
|
||||
for name in ${HTML_FILES}
|
||||
do
|
||||
echo $name
|
||||
# lets not use gratuitious unicode quotes that cause terrible copy and paste issues
|
||||
sed -i 's/“/"/g' _build/html/${name}/index.html
|
||||
sed -i 's/”/"/g' _build/html/${name}/index.html
|
||||
pandoc -f html -t markdown --atx-headers -o sources/${name}.md1 _build/html/${name}/index.html
|
||||
|
||||
#add the meta-data from the rst
|
||||
egrep ':(title|description|keywords):' sources/${name}.rst | sed 's/^:/page_/' > sources/${name}.md
|
||||
echo >> sources/${name}.md
|
||||
#cat sources/${name}.md1 >> sources/${name}.md
|
||||
# remove the paragraph links from the source
|
||||
cat sources/${name}.md1 | sed 's/\[..\](#.*)//' >> sources/${name}.md
|
||||
|
||||
rm sources/${name}.md1
|
||||
|
||||
sed -i 's/{.docutils .literal}//g' sources/${name}.md
|
||||
sed -i 's/{.docutils$//g' sources/${name}.md
|
||||
sed -i 's/^.literal} //g' sources/${name}.md
|
||||
sed -i 's/`{.descname}`//g' sources/${name}.md
|
||||
sed -i 's/{.descname}//g' sources/${name}.md
|
||||
sed -i 's/{.xref}//g' sources/${name}.md
|
||||
sed -i 's/{.xref .doc .docutils .literal}//g' sources/${name}.md
|
||||
sed -i 's/{.xref .http .http-post .docutils$//g' sources/${name}.md
|
||||
sed -i 's/^ .literal}//g' sources/${name}.md
|
||||
|
||||
sed -i 's/\\\$container\\_id/\$container_id/' sources/examples/hello_world.md
|
||||
sed -i 's/\\\$TESTFLAGS/\$TESTFLAGS/' sources/contributing/devenvironment.md
|
||||
sed -i 's/\\\$MYVAR1/\$MYVAR1/g' sources/reference/commandline/cli.md
|
||||
|
||||
# git it all so we can test
|
||||
# git add ${name}.md
|
||||
done
|
||||
|
||||
#annoyingly, there are lots of failures
|
||||
patch --fuzz 50 -t -p2 < pr4923.patch || true
|
||||
patch --fuzz 50 -t -p2 < asciinema.patch || true
|
|
@ -0,0 +1,197 @@
|
|||
diff --git a/docs/Dockerfile b/docs/Dockerfile
|
||||
index bc2b73b..b9808b2 100644
|
||||
--- a/docs/Dockerfile
|
||||
+++ b/docs/Dockerfile
|
||||
@@ -4,14 +4,24 @@ MAINTAINER SvenDowideit@docker.com
|
||||
# docker build -t docker:docs . && docker run -p 8000:8000 docker:docs
|
||||
#
|
||||
|
||||
-RUN apt-get update && apt-get install -yq make python-pip python-setuptools
|
||||
-
|
||||
+RUN apt-get update && apt-get install -yq make python-pip python-setuptools
|
||||
RUN pip install mkdocs
|
||||
|
||||
+RUN apt-get install -yq vim-tiny git pandoc
|
||||
+
|
||||
+# pip installs from docs/requirements.txt, but here to increase cacheability
|
||||
+RUN pip install Sphinx==1.2.1
|
||||
+RUN pip install sphinxcontrib-httpdomain==1.2.0
|
||||
+
|
||||
ADD . /docs
|
||||
+
|
||||
+#build the sphinx html
|
||||
+RUN make -C /docs clean docs
|
||||
+
|
||||
WORKDIR /docs
|
||||
|
||||
-CMD ["mkdocs", "serve"]
|
||||
+#CMD ["mkdocs", "serve"]
|
||||
+CMD bash
|
||||
|
||||
# note, EXPOSE is only last because of https://github.com/dotcloud/docker/issues/3525
|
||||
EXPOSE 8000
|
||||
diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html
|
||||
index 7d78fb9..0dac9e0 100755
|
||||
--- a/docs/theme/docker/layout.html
|
||||
+++ b/docs/theme/docker/layout.html
|
||||
@@ -63,48 +63,6 @@
|
||||
|
||||
<body>
|
||||
|
||||
-<div id="wrap">
|
||||
-<div class="navbar navbar-static-top navbar-inner navbar-fixed-top ">
|
||||
- <div class="navbar-dotcloud">
|
||||
- <div class="container">
|
||||
-
|
||||
- <div style="float: right" class="pull-right">
|
||||
- <ul class="nav">
|
||||
- <li id="nav-introduction"><a href="http://www.docker.io/" title="Docker Homepage">Home</a></li>
|
||||
- <li id="nav-about"><a href="http://www.docker.io/about/" title="About">About</a></li>
|
||||
- <li id="nav-gettingstarted"><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
|
||||
- <li id="nav-community"><a href="http://www.docker.io/community/" title="Community">Community</a></li>
|
||||
- <li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
||||
- <li id="nav-blog"><a href="http://blog.docker.io/" title="Docker Blog">Blog</a></li>
|
||||
- <li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" alt="link to external site" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
|
||||
- </ul>
|
||||
- </div>
|
||||
-
|
||||
- <div class="brand-logo">
|
||||
- <a href="http://www.docker.io" title="Docker Homepage"><img src="{{ pathto('_static/img/docker-top-logo.png', 1) }}" alt="Docker logo"></a>
|
||||
- </div>
|
||||
- </div>
|
||||
- </div>
|
||||
-</div>
|
||||
-
|
||||
-<div class="container-fluid">
|
||||
-
|
||||
- <!-- Docs nav
|
||||
- ================================================== -->
|
||||
- <div class="row-fluid main-row">
|
||||
-
|
||||
- <div class="sidebar bs-docs-sidebar">
|
||||
- <div class="page-title" >
|
||||
- <h4>DOCUMENTATION</h4>
|
||||
- </div>
|
||||
-
|
||||
- {{ toctree(collapse=False, maxdepth=3) }}
|
||||
- <form>
|
||||
- <input type="text" id="st-search-input" class="st-search-input span3" placeholder="search in documentation" style="width:210px;" />
|
||||
- <div id="st-results-container"></div>
|
||||
- </form>
|
||||
- </div>
|
||||
-
|
||||
<!-- body block -->
|
||||
<div class="main-content">
|
||||
|
||||
@@ -114,111 +72,7 @@
|
||||
{% block body %}{% endblock %}
|
||||
</section>
|
||||
|
||||
- <div class="pull-right"><a href="https://github.com/dotcloud/docker/blob/{{ github_tag }}/docs/sources/{{ pagename }}.rst" title="edit this article">Edit this article on GitHub</a></div>
|
||||
</div>
|
||||
- </div>
|
||||
-</div>
|
||||
-
|
||||
-<div id="push-the-footer"></div>
|
||||
-</div> <!-- end wrap for pushing footer -->
|
||||
-
|
||||
-<div id="footer">
|
||||
- <div class="footer-landscape">
|
||||
- <div class="footer-landscape-image">
|
||||
- <!-- footer -->
|
||||
- <div class="container">
|
||||
- <div class="row footer">
|
||||
- <div class="span12 tbox">
|
||||
- <div class="tbox">
|
||||
- <p>Docker is an open source project, sponsored by <a href="https://www.docker.com">Docker Inc.</a>, under the <a href="https://github.com/dotcloud/docker/blob/master/LICENSE" title="Docker licence, hosted in the Github repository">apache 2.0 licence</a></p>
|
||||
- <p>Documentation proudly hosted by <a href="http://www.readthedocs.org">Read the Docs</a></p>
|
||||
- </div>
|
||||
-
|
||||
- <div class="social links">
|
||||
- <a title="Docker on Twitter" class="twitter" href="http://twitter.com/docker">Twitter</a>
|
||||
- <a title="Docker on GitHub" class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
- <a title="Docker on Reddit" class="reddit" href="http://www.reddit.com/r/Docker/">Reddit</a>
|
||||
- <a title="Docker on Google+" class="googleplus" href="https://plus.google.com/u/0/b/100381662757235514581/communities/108146856671494713993">Google+</a>
|
||||
- <a title="Docker on Facebook" class="facebook" href="https://www.facebook.com/docker.run">Facebook</a>
|
||||
- <a title="Docker on SlideShare" class="slideshare" href="http://www.slideshare.net/dotCloud">Slideshare</a>
|
||||
- <a title="Docker on Youtube" class="youtube" href="http://www.youtube.com/user/dockerrun/">Youtube</a>
|
||||
- <a title="Docker on Flickr" class="flickr" href="http://www.flickr.com/photos/99741659@N08/">Flickr</a>
|
||||
- <a title="Docker on LinkedIn" class="linkedin" href="http://www.linkedin.com/company/dotcloud">LinkedIn</a>
|
||||
- </div>
|
||||
-
|
||||
- <div class="tbox version-flyer ">
|
||||
- <div class="content">
|
||||
- <p class="version-note">Note: You are currently browsing the development documentation. The current release may work differently.</p>
|
||||
-
|
||||
- <small>Available versions:</small>
|
||||
- <ul class="inline">
|
||||
- {% for slug, url in versions %}
|
||||
- <li class="alternative"><a href="{{ url }}{%- for word in pagename.split('/') -%}
|
||||
- {%- if word != 'index' -%}
|
||||
- {%- if word != '' -%}
|
||||
- {{ word }}/
|
||||
- {%- endif -%}
|
||||
- {%- endif -%}
|
||||
- {%- endfor -%}"
|
||||
- title="Switch to {{ slug }}">{{ slug }}</a></li>
|
||||
- {% endfor %}
|
||||
- </ul>
|
||||
- </div>
|
||||
- </div>
|
||||
-
|
||||
-
|
||||
- </div>
|
||||
- </div>
|
||||
- </div>
|
||||
- </div>
|
||||
- <!-- end of footer -->
|
||||
- </div>
|
||||
-
|
||||
-</div>
|
||||
-
|
||||
-
|
||||
-<script type="text/javascript" src="{{ pathto('_static/js/docs.js', 1) }}"></script>
|
||||
-
|
||||
-<!-- Swiftype search -->
|
||||
-
|
||||
-<script type="text/javascript">
|
||||
- var Swiftype = window.Swiftype || {};
|
||||
- (function() {
|
||||
- Swiftype.key = 'pWPnnyvwcfpcrw1o51Sz';
|
||||
- Swiftype.inputElement = '#st-search-input';
|
||||
- Swiftype.resultContainingElement = '#st-results-container';
|
||||
- Swiftype.attachElement = '#st-search-input';
|
||||
- Swiftype.renderStyle = "overlay";
|
||||
- // from https://swiftype.com/questions/how-can-i-make-more-popular-content-rank-higher
|
||||
- // Use "page" for now -- they don't subgroup by document type yet.
|
||||
- Swiftype.searchFunctionalBoosts = {"page": {"popularity": "linear"}};
|
||||
-
|
||||
- var script = document.createElement('script');
|
||||
- script.type = 'text/javascript';
|
||||
- script.async = true;
|
||||
- script.src = "//swiftype.com/embed.js";
|
||||
- var entry = document.getElementsByTagName('script')[0];
|
||||
- entry.parentNode.insertBefore(script, entry);
|
||||
- }());
|
||||
-</script>
|
||||
-
|
||||
-
|
||||
-<!-- Google analytics -->
|
||||
-<script type="text/javascript">
|
||||
-
|
||||
- var _gaq = _gaq || [];
|
||||
- _gaq.push(['_setAccount', 'UA-6096819-11']);
|
||||
- _gaq.push(['_setDomainName', 'docker.io']);
|
||||
- _gaq.push(['_setAllowLinker', true]);
|
||||
- _gaq.push(['_trackPageview']);
|
||||
-
|
||||
- (function() {
|
||||
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
- })();
|
||||
-
|
||||
-</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,126 @@
|
|||
site_name: Docker Documentation
|
||||
#site_url: http://docs.docker.io/
|
||||
site_url: /
|
||||
site_description: Documentation for fast and lightweight Docker container based virtualization framework.
|
||||
site_favicon: img/favicon.png
|
||||
|
||||
dev_addr: '0.0.0.0:8000'
|
||||
|
||||
repo_url: https://github.com/dotcloud/docker/
|
||||
|
||||
docs_dir: sources
|
||||
|
||||
include_search: true
|
||||
|
||||
use_absolute_urls: true
|
||||
|
||||
# theme: docker
|
||||
theme_dir: ./theme/mkdocs/
|
||||
theme_center_lead: false
|
||||
include_search: true
|
||||
|
||||
copyright: Copyright © 2014, Docker, Inc.
|
||||
google_analytics: ['UA-6096819-11', 'docker.io']
|
||||
|
||||
pages:
|
||||
|
||||
# Introduction:
|
||||
- ['index.md', 'About', 'Docker']
|
||||
- ['introduction/index.md', '**HIDDEN**']
|
||||
- ['introduction/understanding-docker.md', 'About', 'Understanding Docker']
|
||||
- ['introduction/technology.md', 'About', 'The Technology']
|
||||
- ['introduction/working-with-docker.md', 'About', 'Working with Docker']
|
||||
- ['introduction/get-docker.md', 'About', 'Get Docker']
|
||||
|
||||
# Installation:
|
||||
- ['installation/index.md', '**HIDDEN**']
|
||||
- ['installation/mac.md', 'Installation', 'Mac OS X']
|
||||
- ['installation/ubuntulinux.md', 'Installation', 'Ubuntu']
|
||||
- ['installation/rhel.md', 'Installation', 'Red Hat Enterprise Linux']
|
||||
- ['installation/gentoolinux.md', 'Installation', 'Gentoo']
|
||||
- ['installation/google.md', 'Installation', 'Google Cloud Platform']
|
||||
- ['installation/rackspace.md', 'Installation', 'Rackspace Cloud']
|
||||
- ['installation/amazon.md', 'Installation', 'Amazon EC2']
|
||||
- ['installation/softlayer.md', 'Installation', 'IBM Softlayer']
|
||||
- ['installation/archlinux.md', 'Installation', 'Arch Linux']
|
||||
- ['installation/frugalware.md', 'Installation', 'FrugalWare']
|
||||
- ['installation/fedora.md', 'Installation', 'Fedora']
|
||||
- ['installation/openSUSE.md', 'Installation', 'openSUSE']
|
||||
- ['installation/cruxlinux.md', 'Installation', 'CRUX Linux']
|
||||
- ['installation/windows.md', 'Installation', 'Microsoft Windows']
|
||||
- ['installation/binaries.md', 'Installation', 'Binaries']
|
||||
|
||||
# Examples:
|
||||
- ['use/index.md', '**HIDDEN**']
|
||||
- ['use/basics.md', 'Examples', 'First steps with Docker']
|
||||
- ['examples/index.md', '**HIDDEN**']
|
||||
- ['examples/hello_world.md', 'Examples', 'Hello World']
|
||||
- ['examples/nodejs_web_app.md', 'Examples', 'Node.js web application']
|
||||
- ['examples/python_web_app.md', 'Examples', 'Python web application']
|
||||
- ['examples/mongodb.md', 'Examples', 'MongoDB service']
|
||||
- ['examples/running_redis_service.md', 'Examples', 'Redis service']
|
||||
- ['examples/postgresql_service.md', 'Examples', 'PostgreSQL service']
|
||||
- ['examples/running_riak_service.md', 'Examples', 'Running a Riak service']
|
||||
- ['examples/running_ssh_service.md', 'Examples', 'Running an SSH service']
|
||||
- ['examples/couchdb_data_volumes.md', 'Examples', 'CouchDB service']
|
||||
- ['examples/apt-cacher-ng.md', 'Examples', 'Apt-Cacher-ng service']
|
||||
- ['examples/https.md', 'Examples', 'Running Docker with HTTPS']
|
||||
- ['examples/using_supervisord.md', 'Examples', 'Using Supervisor']
|
||||
- ['examples/cfengine_process_management.md', 'Examples', 'Process management with CFEngine']
|
||||
- ['use/working_with_links_names.md', 'Examples', 'Linking containers together']
|
||||
- ['use/working_with_volumes.md', 'Examples', 'Sharing Directories using volumes']
|
||||
- ['use/puppet.md', 'Examples', 'Using Puppet']
|
||||
- ['use/chef.md', 'Examples', 'Using Chef']
|
||||
- ['use/workingwithrepository.md', 'Examples', 'Working with a Docker Repository']
|
||||
- ['use/port_redirection.md', 'Examples', 'Redirect ports']
|
||||
- ['use/ambassador_pattern_linking.md', 'Examples', 'Cross-Host linking using Ambassador Containers']
|
||||
- ['use/host_integration.md', 'Examples', 'Automatically starting Containers']
|
||||
|
||||
#- ['user-guide/index.md', '**HIDDEN**']
|
||||
# - ['user-guide/writing-your-docs.md', 'User Guide', 'Writing your docs']
|
||||
# - ['user-guide/styling-your-docs.md', 'User Guide', 'Styling your docs']
|
||||
# - ['user-guide/configuration.md', 'User Guide', 'Configuration']
|
||||
# ./faq.md
|
||||
|
||||
# Docker Index docs:
|
||||
- ['index/index.md', '**HIDDEN**']
|
||||
# - ['index/home.md', 'Docker Index', 'Help']
|
||||
- ['index/accounts.md', 'Docker Index', 'Accounts']
|
||||
- ['index/repos.md', 'Docker Index', 'Repositories']
|
||||
- ['index/builds.md', 'Docker Index', 'Trusted Builds']
|
||||
|
||||
# Reference
|
||||
- ['reference/index.md', '**HIDDEN**']
|
||||
- ['reference/commandline/cli.md', 'Reference', 'Command line']
|
||||
- ['reference/builder.md', 'Reference', 'Dockerfile']
|
||||
- ['reference/run.md', 'Reference', 'Run Reference']
|
||||
- ['articles/index.md', '**HIDDEN**']
|
||||
- ['articles/runmetrics.md', 'Reference', 'Runtime metrics']
|
||||
- ['articles/security.md', 'Reference', 'Security']
|
||||
- ['articles/baseimages.md', 'Reference', 'Creating a Base Image']
|
||||
- ['use/networking.md', 'Reference', 'Advanced networking']
|
||||
- ['reference/api/index_api.md', 'Reference', 'Docker Index API']
|
||||
- ['reference/api/registry_api.md', 'Reference', 'Docker Registry API']
|
||||
- ['reference/api/registry_index_spec.md', 'Reference', 'Registry & Index Spec']
|
||||
- ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API']
|
||||
- ['reference/api/docker_remote_api_v1.10.md', 'Reference', 'Docker Remote API v1.10']
|
||||
- ['reference/api/docker_remote_api_v1.9.md', 'Reference', 'Docker Remote API v1.9']
|
||||
- ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API Client Libraries']
|
||||
|
||||
# Contribute:
|
||||
- ['contributing/index.md', '**HIDDEN**']
|
||||
- ['contributing/contributing.md', 'Contribute', 'Contributing']
|
||||
- ['contributing/devenvironment.md', 'Contribute', 'Development environment']
|
||||
# - ['about/license.md', 'About', 'License']
|
||||
|
||||
- ['jsearch.md', '**HIDDEN**']
|
||||
|
||||
# - ['static_files/README.md', 'static_files', 'README']
|
||||
#- ['terms/index.md', '**HIDDEN**']
|
||||
# - ['terms/layer.md', 'terms', 'layer']
|
||||
# - ['terms/index.md', 'terms', 'Home']
|
||||
# - ['terms/registry.md', 'terms', 'registry']
|
||||
# - ['terms/container.md', 'terms', 'container']
|
||||
# - ['terms/repository.md', 'terms', 'repository']
|
||||
# - ['terms/filesystem.md', 'terms', 'filesystem']
|
||||
# - ['terms/image.md', 'terms', 'image']
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
set -o pipefail
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
To publish the Docker documentation you need to set your access_key and secret_key in the docs/awsconfig file
|
||||
(with the keys in a [profile $AWS_S3_BUCKET] section - so you can have more than one set of keys in your file)
|
||||
and set the AWS_S3_BUCKET env var to the name of your bucket.
|
||||
|
||||
make AWS_S3_BUCKET=beta-docs.docker.io docs-release
|
||||
|
||||
will then push the documentation site to your s3 bucket.
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ "$AWS_S3_BUCKET" ] || usage
|
||||
|
||||
#VERSION=$(cat VERSION)
|
||||
BUCKET=$AWS_S3_BUCKET
|
||||
|
||||
export AWS_CONFIG_FILE=$(pwd)/awsconfig
|
||||
[ -e "$AWS_CONFIG_FILE" ] || usage
|
||||
export AWS_DEFAULT_PROFILE=$BUCKET
|
||||
|
||||
echo "cfg file: $AWS_CONFIG_FILE ; profile: $AWS_DEFAULT_PROFILE"
|
||||
|
||||
setup_s3() {
|
||||
echo "Create $BUCKET"
|
||||
# Try creating the bucket. Ignore errors (it might already exist).
|
||||
aws s3 mb s3://$BUCKET 2>/dev/null || true
|
||||
# Check access to the bucket.
|
||||
echo "test $BUCKET exists"
|
||||
aws s3 ls s3://$BUCKET
|
||||
# Make the bucket accessible through website endpoints.
|
||||
echo "make $BUCKET accessible as a website"
|
||||
#aws s3 website s3://$BUCKET --index-document index.html --error-document jsearch/index.html
|
||||
s3conf=$(cat s3_website.json)
|
||||
aws s3api put-bucket-website --bucket $BUCKET --website-configuration "$s3conf"
|
||||
}
|
||||
|
||||
build_current_documentation() {
|
||||
mkdocs build
|
||||
}
|
||||
|
||||
upload_current_documentation() {
|
||||
src=site/
|
||||
dst=s3://$BUCKET
|
||||
|
||||
echo
|
||||
echo "Uploading $src"
|
||||
echo " to $dst"
|
||||
echo
|
||||
#s3cmd --recursive --follow-symlinks --preserve --acl-public sync "$src" "$dst"
|
||||
aws s3 sync --acl public-read --exclude "*.rej" --exclude "*.rst" --exclude "*.orig" --exclude "*.py" "$src" "$dst"
|
||||
}
|
||||
|
||||
setup_s3
|
||||
build_current_documentation
|
||||
upload_current_documentation
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"ErrorDocument": {
|
||||
"Key": "jsearch/index.html"
|
||||
},
|
||||
"IndexDocument": {
|
||||
"Suffix": "index.html"
|
||||
},
|
||||
"RoutingRules": [
|
||||
{ "Condition": { "KeyPrefixEquals": "en/latest/" }, "Redirect": { "ReplaceKeyPrefixWith": "" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "en/master/" }, "Redirect": { "ReplaceKeyPrefixWith": "" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "en/v0.6.3/" }, "Redirect": { "ReplaceKeyPrefixWith": "" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "jsearch/index.html" }, "Redirect": { "ReplaceKeyPrefixWith": "jsearch/" } }
|
||||
]
|
||||
}
|
||||
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
После Ширина: | Высота: | Размер: 8.2 KiB |
|
@ -0,0 +1,8 @@
|
|||
# Articles
|
||||
|
||||
## Contents:
|
||||
|
||||
- [Docker Security](security/)
|
||||
- [Create a Base Image](baseimages/)
|
||||
- [Runtime Metrics](runmetrics/)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
page_title: Create a Base Image
|
||||
page_description: How to create base images
|
||||
page_keywords: Examples, Usage, base image, docker, documentation, examples
|
||||
|
||||
# Create a Base Image
|
||||
|
||||
So you want to create your own [*Base
|
||||
Image*](../../terms/image/#base-image-def)? Great!
|
||||
|
||||
The specific process will depend heavily on the Linux distribution you
|
||||
want to package. We have some examples below, and you are encouraged to
|
||||
submit pull requests to contribute new ones.
|
||||
|
||||
## Create a full image using tar
|
||||
|
||||
In general, you’ll want to start with a working machine that is running
|
||||
the distribution you’d like to package as a base image, though that is
|
||||
not required for some tools like Debian’s
|
||||
[Debootstrap](https://wiki.debian.org/Debootstrap), which you can also
|
||||
use to build Ubuntu images.
|
||||
|
||||
It can be as simple as this to create an Ubuntu base image:
|
||||
|
||||
$ sudo debootstrap raring raring > /dev/null
|
||||
$ sudo tar -C raring -c . | sudo docker import - raring
|
||||
a29c15f1bf7a
|
||||
$ sudo docker run raring cat /etc/lsb-release
|
||||
DISTRIB_ID=Ubuntu
|
||||
DISTRIB_RELEASE=13.04
|
||||
DISTRIB_CODENAME=raring
|
||||
DISTRIB_DESCRIPTION="Ubuntu 13.04"
|
||||
|
||||
There are more example scripts for creating base images in the Docker
|
||||
GitHub Repo:
|
||||
|
||||
- [BusyBox](https://github.com/dotcloud/docker/blob/master/contrib/mkimage-busybox.sh)
|
||||
- CentOS / Scientific Linux CERN (SLC) [on
|
||||
Debian/Ubuntu](https://github.com/dotcloud/docker/blob/master/contrib/mkimage-rinse.sh)
|
||||
or [on
|
||||
CentOS/RHEL/SLC/etc.](https://github.com/dotcloud/docker/blob/master/contrib/mkimage-yum.sh)
|
||||
- [Debian /
|
||||
Ubuntu](https://github.com/dotcloud/docker/blob/master/contrib/mkimage-debootstrap.sh)
|
||||
|
||||
## Creating a simple base image using `scratch`
|
||||
|
||||
There is a special repository in the Docker registry called
|
||||
`scratch`, which was created using an empty tar
|
||||
file:
|
||||
|
||||
$ tar cv --files-from /dev/null | docker import - scratch
|
||||
|
||||
which you can `docker pull`. You can then use that
|
||||
image to base your new minimal containers `FROM`:
|
||||
|
||||
FROM scratch
|
||||
ADD true-asm /true
|
||||
CMD ["/true"]
|
||||
|
||||
The Dockerfile above is from extremely minimal image -
|
||||
[tianon/true](https://github.com/tianon/dockerfiles/tree/master/true).
|
|
@ -0,0 +1,434 @@
|
|||
page_title: Runtime Metrics
|
||||
page_description: Measure the behavior of running containers
|
||||
page_keywords: docker, metrics, CPU, memory, disk, IO, run, runtime
|
||||
|
||||
# Runtime Metrics
|
||||
|
||||
Linux Containers rely on [control
|
||||
groups](https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt)
|
||||
which not only track groups of processes, but also expose metrics about
|
||||
CPU, memory, and block I/O usage. You can access those metrics and
|
||||
obtain network usage metrics as well. This is relevant for "pure" LXC
|
||||
containers, as well as for Docker containers.
|
||||
|
||||
## Control Groups
|
||||
|
||||
Control groups are exposed through a pseudo-filesystem. In recent
|
||||
distros, you should find this filesystem under
|
||||
`/sys/fs/cgroup`. Under that directory, you will see
|
||||
multiple sub-directories, called devices, freezer, blkio, etc.; each
|
||||
sub-directory actually corresponds to a different cgroup hierarchy.
|
||||
|
||||
On older systems, the control groups might be mounted on
|
||||
`/cgroup`, without distinct hierarchies. In that
|
||||
case, instead of seeing the sub-directories, you will see a bunch of
|
||||
files in that directory, and possibly some directories corresponding to
|
||||
existing containers.
|
||||
|
||||
To figure out where your control groups are mounted, you can run:
|
||||
|
||||
grep cgroup /proc/mounts
|
||||
|
||||
## Enumerating Cgroups
|
||||
|
||||
You can look into `/proc/cgroups` to see the
|
||||
different control group subsystems known to the system, the hierarchy
|
||||
they belong to, and how many groups they contain.
|
||||
|
||||
You can also look at `/proc/<pid>/cgroup` to see
|
||||
which control groups a process belongs to. The control group will be
|
||||
shown as a path relative to the root of the hierarchy mountpoint; e.g.
|
||||
`/` means “this process has not been assigned into a
|
||||
particular group”, while `/lxc/pumpkin` means that
|
||||
the process is likely to be a member of a container named
|
||||
`pumpkin`.
|
||||
|
||||
## Finding the Cgroup for a Given Container
|
||||
|
||||
For each container, one cgroup will be created in each hierarchy. On
|
||||
older systems with older versions of the LXC userland tools, the name of
|
||||
the cgroup will be the name of the container. With more recent versions
|
||||
of the LXC tools, the cgroup will be `lxc/<container_name>.`
|
||||
|
||||
For Docker containers using cgroups, the container name will be the full
|
||||
ID or long ID of the container. If a container shows up as ae836c95b4c3
|
||||
in `docker ps`, its long ID might be something like
|
||||
`ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79`. You can look it up with `docker inspect`
|
||||
or `docker ps -notrunc`.
|
||||
|
||||
Putting everything together to look at the memory metrics for a Docker
|
||||
container, take a look at
|
||||
`/sys/fs/cgroup/memory/lxc/<longid>/`.
|
||||
|
||||
## Metrics from Cgroups: Memory, CPU, Block IO
|
||||
|
||||
For each subsystem (memory, CPU, and block I/O), you will find one or
|
||||
more pseudo-files containing statistics.
|
||||
|
||||
### Memory Metrics: `memory.stat`
|
||||
|
||||
Memory metrics are found in the "memory" cgroup. Note that the memory
|
||||
control group adds a little overhead, because it does very fine-grained
|
||||
accounting of the memory usage on your host. Therefore, many distros
|
||||
chose to not enable it by default. Generally, to enable it, all you have
|
||||
to do is to add some kernel command-line parameters:
|
||||
`cgroup_enable=memory swapaccount=1`.
|
||||
|
||||
The metrics are in the pseudo-file `memory.stat`.
|
||||
Here is what it will look like:
|
||||
|
||||
cache 11492564992
|
||||
rss 1930993664
|
||||
mapped_file 306728960
|
||||
pgpgin 406632648
|
||||
pgpgout 403355412
|
||||
swap 0
|
||||
pgfault 728281223
|
||||
pgmajfault 1724
|
||||
inactive_anon 46608384
|
||||
active_anon 1884520448
|
||||
inactive_file 7003344896
|
||||
active_file 4489052160
|
||||
unevictable 32768
|
||||
hierarchical_memory_limit 9223372036854775807
|
||||
hierarchical_memsw_limit 9223372036854775807
|
||||
total_cache 11492564992
|
||||
total_rss 1930993664
|
||||
total_mapped_file 306728960
|
||||
total_pgpgin 406632648
|
||||
total_pgpgout 403355412
|
||||
total_swap 0
|
||||
total_pgfault 728281223
|
||||
total_pgmajfault 1724
|
||||
total_inactive_anon 46608384
|
||||
total_active_anon 1884520448
|
||||
total_inactive_file 7003344896
|
||||
total_active_file 4489052160
|
||||
total_unevictable 32768
|
||||
|
||||
The first half (without the `total_` prefix)
|
||||
contains statistics relevant to the processes within the cgroup,
|
||||
excluding sub-cgroups. The second half (with the `total_`
|
||||
prefix) includes sub-cgroups as well.
|
||||
|
||||
Some metrics are "gauges", i.e. values that can increase or decrease
|
||||
(e.g. swap, the amount of swap space used by the members of the cgroup).
|
||||
Some others are "counters", i.e. values that can only go up, because
|
||||
they represent occurrences of a specific event (e.g. pgfault, which
|
||||
indicates the number of page faults which happened since the creation of
|
||||
the cgroup; this number can never decrease).
|
||||
|
||||
cache
|
||||
: the amount of memory used by the processes of this control group
|
||||
that can be associated precisely with a block on a block device.
|
||||
When you read from and write to files on disk, this amount will
|
||||
increase. This will be the case if you use "conventional" I/O
|
||||
(`open`, `read`,
|
||||
`write` syscalls) as well as mapped files (with
|
||||
`mmap`). It also accounts for the memory used by
|
||||
`tmpfs` mounts, though the reasons are unclear.
|
||||
rss
|
||||
: the amount of memory that *doesn’t* correspond to anything on disk:
|
||||
stacks, heaps, and anonymous memory maps.
|
||||
mapped\_file
|
||||
: indicates the amount of memory mapped by the processes in the
|
||||
control group. It doesn’t give you information about *how much*
|
||||
memory is used; it rather tells you *how* it is used.
|
||||
pgfault and pgmajfault
|
||||
: indicate the number of times that a process of the cgroup triggered
|
||||
a "page fault" and a "major fault", respectively. A page fault
|
||||
happens when a process accesses a part of its virtual memory space
|
||||
which is nonexistent or protected. The former can happen if the
|
||||
process is buggy and tries to access an invalid address (it will
|
||||
then be sent a `SIGSEGV` signal, typically
|
||||
killing it with the famous `Segmentation fault`
|
||||
message). The latter can happen when the process reads from a memory
|
||||
zone which has been swapped out, or which corresponds to a mapped
|
||||
file: in that case, the kernel will load the page from disk, and let
|
||||
the CPU complete the memory access. It can also happen when the
|
||||
process writes to a copy-on-write memory zone: likewise, the kernel
|
||||
will preempt the process, duplicate the memory page, and resume the
|
||||
write operation on the process’ own copy of the page. "Major" faults
|
||||
happen when the kernel actually has to read the data from disk. When
|
||||
it just has to duplicate an existing page, or allocate an empty
|
||||
page, it’s a regular (or "minor") fault.
|
||||
swap
|
||||
: the amount of swap currently used by the processes in this cgroup.
|
||||
active\_anon and inactive\_anon
|
||||
: the amount of *anonymous* memory that has been identified has
|
||||
respectively *active* and *inactive* by the kernel. "Anonymous"
|
||||
memory is the memory that is *not* linked to disk pages. In other
|
||||
words, that’s the equivalent of the rss counter described above. In
|
||||
fact, the very definition of the rss counter is **active\_anon** +
|
||||
**inactive\_anon** - **tmpfs** (where tmpfs is the amount of memory
|
||||
used up by `tmpfs` filesystems mounted by this
|
||||
control group). Now, what’s the difference between "active" and
|
||||
"inactive"? Pages are initially "active"; and at regular intervals,
|
||||
the kernel sweeps over the memory, and tags some pages as
|
||||
"inactive". Whenever they are accessed again, they are immediately
|
||||
retagged "active". When the kernel is almost out of memory, and time
|
||||
comes to swap out to disk, the kernel will swap "inactive" pages.
|
||||
active\_file and inactive\_file
|
||||
: cache memory, with *active* and *inactive* similar to the *anon*
|
||||
memory above. The exact formula is cache = **active\_file** +
|
||||
**inactive\_file** + **tmpfs**. The exact rules used by the kernel
|
||||
to move memory pages between active and inactive sets are different
|
||||
from the ones used for anonymous memory, but the general principle
|
||||
is the same. Note that when the kernel needs to reclaim memory, it
|
||||
is cheaper to reclaim a clean (=non modified) page from this pool,
|
||||
since it can be reclaimed immediately (while anonymous pages and
|
||||
dirty/modified pages have to be written to disk first).
|
||||
unevictable
|
||||
: the amount of memory that cannot be reclaimed; generally, it will
|
||||
account for memory that has been "locked" with `mlock`
|
||||
. It is often used by crypto frameworks to make sure that
|
||||
secret keys and other sensitive material never gets swapped out to
|
||||
disk.
|
||||
memory and memsw limits
|
||||
: These are not really metrics, but a reminder of the limits applied
|
||||
to this cgroup. The first one indicates the maximum amount of
|
||||
physical memory that can be used by the processes of this control
|
||||
group; the second one indicates the maximum amount of RAM+swap.
|
||||
|
||||
Accounting for memory in the page cache is very complex. If two
|
||||
processes in different control groups both read the same file
|
||||
(ultimately relying on the same blocks on disk), the corresponding
|
||||
memory charge will be split between the control groups. It’s nice, but
|
||||
it also means that when a cgroup is terminated, it could increase the
|
||||
memory usage of another cgroup, because they are not splitting the cost
|
||||
anymore for those memory pages.
|
||||
|
||||
### CPU metrics: `cpuacct.stat`
|
||||
|
||||
Now that we’ve covered memory metrics, everything else will look very
|
||||
simple in comparison. CPU metrics will be found in the
|
||||
`cpuacct` controller.
|
||||
|
||||
For each container, you will find a pseudo-file `cpuacct.stat`,
|
||||
containing the CPU usage accumulated by the processes of the container,
|
||||
broken down between `user` and `system` time. If you’re not familiar
|
||||
with the distinction, `user` is the time during which the processes were
|
||||
in direct control of the CPU (i.e. executing process code), and `system`
|
||||
is the time during which the CPU was executing system calls on behalf of
|
||||
those processes.
|
||||
|
||||
Those times are expressed in ticks of 1/100th of a second. Actually,
|
||||
they are expressed in "user jiffies". There are `USER_HZ`
|
||||
*"jiffies"* per second, and on x86 systems,
|
||||
`USER_HZ` is 100. This used to map exactly to the
|
||||
number of scheduler "ticks" per second; but with the advent of higher
|
||||
frequency scheduling, as well as [tickless
|
||||
kernels](http://lwn.net/Articles/549580/), the number of kernel ticks
|
||||
wasn’t relevant anymore. It stuck around anyway, mainly for legacy and
|
||||
compatibility reasons.
|
||||
|
||||
### Block I/O metrics
|
||||
|
||||
Block I/O is accounted in the `blkio` controller.
|
||||
Different metrics are scattered across different files. While you can
|
||||
find in-depth details in the
|
||||
[blkio-controller](https://www.kernel.org/doc/Documentation/cgroups/blkio-controller.txt)
|
||||
file in the kernel documentation, here is a short list of the most
|
||||
relevant ones:
|
||||
|
||||
blkio.sectors
|
||||
: contain the number of 512-bytes sectors read and written by the
|
||||
processes member of the cgroup, device by device. Reads and writes
|
||||
are merged in a single counter.
|
||||
blkio.io\_service\_bytes
|
||||
: indicates the number of bytes read and written by the cgroup. It has
|
||||
4 counters per device, because for each device, it differentiates
|
||||
between synchronous vs. asynchronous I/O, and reads vs. writes.
|
||||
blkio.io\_serviced
|
||||
: the number of I/O operations performed, regardless of their size. It
|
||||
also has 4 counters per device.
|
||||
blkio.io\_queued
|
||||
: indicates the number of I/O operations currently queued for this
|
||||
cgroup. In other words, if the cgroup isn’t doing any I/O, this will
|
||||
be zero. Note that the opposite is not true. In other words, if
|
||||
there is no I/O queued, it does not mean that the cgroup is idle
|
||||
(I/O-wise). It could be doing purely synchronous reads on an
|
||||
otherwise quiescent device, which is therefore able to handle them
|
||||
immediately, without queuing. Also, while it is helpful to figure
|
||||
out which cgroup is putting stress on the I/O subsystem, keep in
|
||||
mind that is is a relative quantity. Even if a process group does
|
||||
not perform more I/O, its queue size can increase just because the
|
||||
device load increases because of other devices.
|
||||
|
||||
## Network Metrics
|
||||
|
||||
Network metrics are not exposed directly by control groups. There is a
|
||||
good explanation for that: network interfaces exist within the context
|
||||
of *network namespaces*. The kernel could probably accumulate metrics
|
||||
about packets and bytes sent and received by a group of processes, but
|
||||
those metrics wouldn’t be very useful. You want per-interface metrics
|
||||
(because traffic happening on the local `lo`
|
||||
interface doesn’t really count). But since processes in a single cgroup
|
||||
can belong to multiple network namespaces, those metrics would be harder
|
||||
to interpret: multiple network namespaces means multiple `lo`
|
||||
interfaces, potentially multiple `eth0`
|
||||
interfaces, etc.; so this is why there is no easy way to gather network
|
||||
metrics with control groups.
|
||||
|
||||
Instead we can gather network metrics from other sources:
|
||||
|
||||
### IPtables
|
||||
|
||||
IPtables (or rather, the netfilter framework for which iptables is just
|
||||
an interface) can do some serious accounting.
|
||||
|
||||
For instance, you can setup a rule to account for the outbound HTTP
|
||||
traffic on a web server:
|
||||
|
||||
iptables -I OUTPUT -p tcp --sport 80
|
||||
|
||||
There is no `-j` or `-g` flag,
|
||||
so the rule will just count matched packets and go to the following
|
||||
rule.
|
||||
|
||||
Later, you can check the values of the counters, with:
|
||||
|
||||
iptables -nxvL OUTPUT
|
||||
|
||||
Technically, `-n` is not required, but it will
|
||||
prevent iptables from doing DNS reverse lookups, which are probably
|
||||
useless in this scenario.
|
||||
|
||||
Counters include packets and bytes. If you want to setup metrics for
|
||||
container traffic like this, you could execute a `for`
|
||||
loop to add two `iptables` rules per
|
||||
container IP address (one in each direction), in the `FORWARD`
|
||||
chain. This will only meter traffic going through the NAT
|
||||
layer; you will also have to add traffic going through the userland
|
||||
proxy.
|
||||
|
||||
Then, you will need to check those counters on a regular basis. If you
|
||||
happen to use `collectd`, there is a nice plugin to
|
||||
automate iptables counters collection.
|
||||
|
||||
### Interface-level counters
|
||||
|
||||
Since each container has a virtual Ethernet interface, you might want to
|
||||
check directly the TX and RX counters of this interface. You will notice
|
||||
that each container is associated to a virtual Ethernet interface in
|
||||
your host, with a name like `vethKk8Zqi`. Figuring
|
||||
out which interface corresponds to which container is, unfortunately,
|
||||
difficult.
|
||||
|
||||
But for now, the best way is to check the metrics *from within the
|
||||
containers*. To accomplish this, you can run an executable from the host
|
||||
environment within the network namespace of a container using **ip-netns
|
||||
magic**.
|
||||
|
||||
The `ip-netns exec` command will let you execute any
|
||||
program (present in the host system) within any network namespace
|
||||
visible to the current process. This means that your host will be able
|
||||
to enter the network namespace of your containers, but your containers
|
||||
won’t be able to access the host, nor their sibling containers.
|
||||
Containers will be able to “see” and affect their sub-containers,
|
||||
though.
|
||||
|
||||
The exact format of the command is:
|
||||
|
||||
ip netns exec <nsname> <command...>
|
||||
|
||||
For example:
|
||||
|
||||
ip netns exec mycontainer netstat -i
|
||||
|
||||
`ip netns` finds the "mycontainer" container by
|
||||
using namespaces pseudo-files. Each process belongs to one network
|
||||
namespace, one PID namespace, one `mnt` namespace,
|
||||
etc., and those namespaces are materialized under
|
||||
`/proc/<pid>/ns/`. For example, the network
|
||||
namespace of PID 42 is materialized by the pseudo-file
|
||||
`/proc/42/ns/net`.
|
||||
|
||||
When you run `ip netns exec mycontainer ...`, it
|
||||
expects `/var/run/netns/mycontainer` to be one of
|
||||
those pseudo-files. (Symlinks are accepted.)
|
||||
|
||||
In other words, to execute a command within the network namespace of a
|
||||
container, we need to:
|
||||
|
||||
- Find out the PID of any process within the container that we want to
|
||||
investigate;
|
||||
- Create a symlink from `/var/run/netns/<somename>`
|
||||
to `/proc/<thepid>/ns/net`
|
||||
- Execute `ip netns exec <somename> ....`
|
||||
|
||||
Please review [*Enumerating Cgroups*](#enumerating-cgroups) to learn how to find
|
||||
the cgroup of a pprocess running in the container of which you want to
|
||||
measure network usage. From there, you can examine the pseudo-file named
|
||||
`tasks`, which containes the PIDs that are in the
|
||||
control group (i.e. in the container). Pick any one of them.
|
||||
|
||||
Putting everything together, if the "short ID" of a container is held in
|
||||
the environment variable `$CID`, then you can do
|
||||
this:
|
||||
|
||||
TASKS=/sys/fs/cgroup/devices/$CID*/tasks
|
||||
PID=$(head -n 1 $TASKS)
|
||||
mkdir -p /var/run/netns
|
||||
ln -sf /proc/$PID/ns/net /var/run/netns/$CID
|
||||
ip netns exec $CID netstat -i
|
||||
|
||||
## Tips for high-performance metric collection
|
||||
|
||||
Note that running a new process each time you want to update metrics is
|
||||
(relatively) expensive. If you want to collect metrics at high
|
||||
resolutions, and/or over a large number of containers (think 1000
|
||||
containers on a single host), you do not want to fork a new process each
|
||||
time.
|
||||
|
||||
Here is how to collect metrics from a single process. You will have to
|
||||
write your metric collector in C (or any language that lets you do
|
||||
low-level system calls). You need to use a special system call,
|
||||
`setns()`, which lets the current process enter any
|
||||
arbitrary namespace. It requires, however, an open file descriptor to
|
||||
the namespace pseudo-file (remember: that’s the pseudo-file in
|
||||
`/proc/<pid>/ns/net`).
|
||||
|
||||
However, there is a catch: you must not keep this file descriptor open.
|
||||
If you do, when the last process of the control group exits, the
|
||||
namespace will not be destroyed, and its network resources (like the
|
||||
virtual interface of the container) will stay around for ever (or until
|
||||
you close that file descriptor).
|
||||
|
||||
The right approach would be to keep track of the first PID of each
|
||||
container, and re-open the namespace pseudo-file each time.
|
||||
|
||||
## Collecting metrics when a container exits
|
||||
|
||||
Sometimes, you do not care about real time metric collection, but when a
|
||||
container exits, you want to know how much CPU, memory, etc. it has
|
||||
used.
|
||||
|
||||
Docker makes this difficult because it relies on `lxc-start`, which
|
||||
carefully cleans up after itself, but it is still possible. It is
|
||||
usually easier to collect metrics at regular intervals (e.g. every
|
||||
minute, with the collectd LXC plugin) and rely on that instead.
|
||||
|
||||
But, if you’d still like to gather the stats when a container stops,
|
||||
here is how:
|
||||
|
||||
For each container, start a collection process, and move it to the
|
||||
control groups that you want to monitor by writing its PID to the tasks
|
||||
file of the cgroup. The collection process should periodically re-read
|
||||
the tasks file to check if it’s the last process of the control group.
|
||||
(If you also want to collect network statistics as explained in the
|
||||
previous section, you should also move the process to the appropriate
|
||||
network namespace.)
|
||||
|
||||
When the container exits, `lxc-start` will try to
|
||||
delete the control groups. It will fail, since the control group is
|
||||
still in use; but that’s fine. You process should now detect that it is
|
||||
the only one remaining in the group. Now is the right time to collect
|
||||
all the metrics you need!
|
||||
|
||||
Finally, your process should move itself back to the root control group,
|
||||
and remove the container control group. To remove a control group, just
|
||||
`rmdir` its directory. It’s counter-intuitive to
|
||||
`rmdir` a directory as it still contains files; but
|
||||
remember that this is a pseudo-filesystem, so usual rules don’t apply.
|
||||
After the cleanup is done, the collection process can exit safely.
|
|
@ -0,0 +1,258 @@
|
|||
page_title: Docker Security
|
||||
page_description: Review of the Docker Daemon attack surface
|
||||
page_keywords: Docker, Docker documentation, security
|
||||
|
||||
# Docker Security
|
||||
|
||||
> *Adapted from* [Containers & Docker: How Secure are
|
||||
> They?](http://blog.docker.io/2013/08/containers-docker-how-secure-are-they/)
|
||||
|
||||
There are three major areas to consider when reviewing Docker security:
|
||||
|
||||
- the intrinsic security of containers, as implemented by kernel
|
||||
namespaces and cgroups;
|
||||
- the attack surface of the Docker daemon itself;
|
||||
- the "hardening" security features of the kernel and how they
|
||||
interact with containers.
|
||||
|
||||
## Kernel Namespaces
|
||||
|
||||
Docker containers are essentially LXC containers, and they come with the
|
||||
same security features. When you start a container with
|
||||
`docker run`, behind the scenes Docker uses
|
||||
`lxc-start` to execute the Docker container. This
|
||||
creates a set of namespaces and control groups for the container. Those
|
||||
namespaces and control groups are not created by Docker itself, but by
|
||||
`lxc-start`. This means that as the LXC userland
|
||||
tools evolve (and provide additional namespaces and isolation features),
|
||||
Docker will automatically make use of them.
|
||||
|
||||
**Namespaces provide the first and most straightforward form of
|
||||
isolation**: processes running within a container cannot see, and even
|
||||
less affect, processes running in another container, or in the host
|
||||
system.
|
||||
|
||||
**Each container also gets its own network stack**, meaning that a
|
||||
container doesn’t get a privileged access to the sockets or interfaces
|
||||
of another container. Of course, if the host system is setup
|
||||
accordingly, containers can interact with each other through their
|
||||
respective network interfaces — just like they can interact with
|
||||
external hosts. When you specify public ports for your containers or use
|
||||
[*links*](../../use/working_with_links_names/#working-with-links-names)
|
||||
then IP traffic is allowed between containers. They can ping each other,
|
||||
send/receive UDP packets, and establish TCP connections, but that can be
|
||||
restricted if necessary. From a network architecture point of view, all
|
||||
containers on a given Docker host are sitting on bridge interfaces. This
|
||||
means that they are just like physical machines connected through a
|
||||
common Ethernet switch; no more, no less.
|
||||
|
||||
How mature is the code providing kernel namespaces and private
|
||||
networking? Kernel namespaces were introduced [between kernel version
|
||||
2.6.15 and
|
||||
2.6.26](http://lxc.sourceforge.net/index.php/about/kernel-namespaces/).
|
||||
This means that since July 2008 (date of the 2.6.26 release, now 5 years
|
||||
ago), namespace code has been exercised and scrutinized on a large
|
||||
number of production systems. And there is more: the design and
|
||||
inspiration for the namespaces code are even older. Namespaces are
|
||||
actually an effort to reimplement the features of
|
||||
[OpenVZ](http://en.wikipedia.org/wiki/OpenVZ) in such a way that they
|
||||
could be merged within the mainstream kernel. And OpenVZ was initially
|
||||
released in 2005, so both the design and the implementation are pretty
|
||||
mature.
|
||||
|
||||
## Control Groups
|
||||
|
||||
Control Groups are the other key component of Linux Containers. They
|
||||
implement resource accounting and limiting. They provide a lot of very
|
||||
useful metrics, but they also help to ensure that each container gets
|
||||
its fair share of memory, CPU, disk I/O; and, more importantly, that a
|
||||
single container cannot bring the system down by exhausting one of those
|
||||
resources.
|
||||
|
||||
So while they do not play a role in preventing one container from
|
||||
accessing or affecting the data and processes of another container, they
|
||||
are essential to fend off some denial-of-service attacks. They are
|
||||
particularly important on multi-tenant platforms, like public and
|
||||
private PaaS, to guarantee a consistent uptime (and performance) even
|
||||
when some applications start to misbehave.
|
||||
|
||||
Control Groups have been around for a while as well: the code was
|
||||
started in 2006, and initially merged in kernel 2.6.24.
|
||||
|
||||
## Docker Daemon Attack Surface
|
||||
|
||||
Running containers (and applications) with Docker implies running the
|
||||
Docker daemon. This daemon currently requires root privileges, and you
|
||||
should therefore be aware of some important details.
|
||||
|
||||
First of all, **only trusted users should be allowed to control your
|
||||
Docker daemon**. This is a direct consequence of some powerful Docker
|
||||
features. Specifically, Docker allows you to share a directory between
|
||||
the Docker host and a guest container; and it allows you to do so
|
||||
without limiting the access rights of the container. This means that you
|
||||
can start a container where the `/host` directory
|
||||
will be the `/` directory on your host; and the
|
||||
container will be able to alter your host filesystem without any
|
||||
restriction. This sounds crazy? Well, you have to know that **all
|
||||
virtualization systems allowing filesystem resource sharing behave the
|
||||
same way**. Nothing prevents you from sharing your root filesystem (or
|
||||
even your root block device) with a virtual machine.
|
||||
|
||||
This has a strong security implication: if you instrument Docker from
|
||||
e.g. a web server to provision containers through an API, you should be
|
||||
even more careful than usual with parameter checking, to make sure that
|
||||
a malicious user cannot pass crafted parameters causing Docker to create
|
||||
arbitrary containers.
|
||||
|
||||
For this reason, the REST API endpoint (used by the Docker CLI to
|
||||
communicate with the Docker daemon) changed in Docker 0.5.2, and now
|
||||
uses a UNIX socket instead of a TCP socket bound on 127.0.0.1 (the
|
||||
latter being prone to cross-site-scripting attacks if you happen to run
|
||||
Docker directly on your local machine, outside of a VM). You can then
|
||||
use traditional UNIX permission checks to limit access to the control
|
||||
socket.
|
||||
|
||||
You can also expose the REST API over HTTP if you explicitly decide so.
|
||||
However, if you do that, being aware of the abovementioned security
|
||||
implication, you should ensure that it will be reachable only from a
|
||||
trusted network or VPN; or protected with e.g. `stunnel`
|
||||
and client SSL certificates.
|
||||
|
||||
Recent improvements in Linux namespaces will soon allow to run
|
||||
full-featured containers without root privileges, thanks to the new user
|
||||
namespace. This is covered in detail
|
||||
[here](http://s3hh.wordpress.com/2013/07/19/creating-and-using-containers-without-privilege/).
|
||||
Moreover, this will solve the problem caused by sharing filesystems
|
||||
between host and guest, since the user namespace allows users within
|
||||
containers (including the root user) to be mapped to other users in the
|
||||
host system.
|
||||
|
||||
The end goal for Docker is therefore to implement two additional
|
||||
security improvements:
|
||||
|
||||
- map the root user of a container to a non-root user of the Docker
|
||||
host, to mitigate the effects of a container-to-host privilege
|
||||
escalation;
|
||||
- allow the Docker daemon to run without root privileges, and delegate
|
||||
operations requiring those privileges to well-audited sub-processes,
|
||||
each with its own (very limited) scope: virtual network setup,
|
||||
filesystem management, etc.
|
||||
|
||||
Finally, if you run Docker on a server, it is recommended to run
|
||||
exclusively Docker in the server, and move all other services within
|
||||
containers controlled by Docker. Of course, it is fine to keep your
|
||||
favorite admin tools (probably at least an SSH server), as well as
|
||||
existing monitoring/supervision processes (e.g. NRPE, collectd, etc).
|
||||
|
||||
## Linux Kernel Capabilities
|
||||
|
||||
By default, Docker starts containers with a very restricted set of
|
||||
capabilities. What does that mean?
|
||||
|
||||
Capabilities turn the binary "root/non-root" dichotomy into a
|
||||
fine-grained access control system. Processes (like web servers) that
|
||||
just need to bind on a port below 1024 do not have to run as root: they
|
||||
can just be granted the `net_bind_service`
|
||||
capability instead. And there are many other capabilities, for almost
|
||||
all the specific areas where root privileges are usually needed.
|
||||
|
||||
This means a lot for container security; let’s see why!
|
||||
|
||||
Your average server (bare metal or virtual machine) needs to run a bunch
|
||||
of processes as root. Those typically include SSH, cron, syslogd;
|
||||
hardware management tools (to e.g. load modules), network configuration
|
||||
tools (to handle e.g. DHCP, WPA, or VPNs), and much more. A container is
|
||||
very different, because almost all of those tasks are handled by the
|
||||
infrastructure around the container:
|
||||
|
||||
- SSH access will typically be managed by a single server running in
|
||||
the Docker host;
|
||||
- `cron`, when necessary, should run as a user
|
||||
process, dedicated and tailored for the app that needs its
|
||||
scheduling service, rather than as a platform-wide facility;
|
||||
- log management will also typically be handed to Docker, or by
|
||||
third-party services like Loggly or Splunk;
|
||||
- hardware management is irrelevant, meaning that you never need to
|
||||
run `udevd` or equivalent daemons within
|
||||
containers;
|
||||
- network management happens outside of the containers, enforcing
|
||||
separation of concerns as much as possible, meaning that a container
|
||||
should never need to perform `ifconfig`,
|
||||
`route`, or ip commands (except when a container
|
||||
is specifically engineered to behave like a router or firewall, of
|
||||
course).
|
||||
|
||||
This means that in most cases, containers will not need "real" root
|
||||
privileges *at all*. And therefore, containers can run with a reduced
|
||||
capability set; meaning that "root" within a container has much less
|
||||
privileges than the real "root". For instance, it is possible to:
|
||||
|
||||
- deny all "mount" operations;
|
||||
- deny access to raw sockets (to prevent packet spoofing);
|
||||
- deny access to some filesystem operations, like creating new device
|
||||
nodes, changing the owner of files, or altering attributes
|
||||
(including the immutable flag);
|
||||
- deny module loading;
|
||||
- and many others.
|
||||
|
||||
This means that even if an intruder manages to escalate to root within a
|
||||
container, it will be much harder to do serious damage, or to escalate
|
||||
to the host.
|
||||
|
||||
This won’t affect regular web apps; but malicious users will find that
|
||||
the arsenal at their disposal has shrunk considerably! You can see [the
|
||||
list of dropped capabilities in the Docker
|
||||
code](https://github.com/dotcloud/docker/blob/v0.5.0/lxc_template.go#L97),
|
||||
and a full list of available capabilities in [Linux
|
||||
manpages](http://man7.org/linux/man-pages/man7/capabilities.7.html).
|
||||
|
||||
Of course, you can always enable extra capabilities if you really need
|
||||
them (for instance, if you want to use a FUSE-based filesystem), but by
|
||||
default, Docker containers will be locked down to ensure maximum safety.
|
||||
|
||||
## Other Kernel Security Features
|
||||
|
||||
Capabilities are just one of the many security features provided by
|
||||
modern Linux kernels. It is also possible to leverage existing,
|
||||
well-known systems like TOMOYO, AppArmor, SELinux, GRSEC, etc. with
|
||||
Docker.
|
||||
|
||||
While Docker currently only enables capabilities, it doesn’t interfere
|
||||
with the other systems. This means that there are many different ways to
|
||||
harden a Docker host. Here are a few examples.
|
||||
|
||||
- You can run a kernel with GRSEC and PAX. This will add many safety
|
||||
checks, both at compile-time and run-time; it will also defeat many
|
||||
exploits, thanks to techniques like address randomization. It
|
||||
doesn’t require Docker-specific configuration, since those security
|
||||
features apply system-wide, independently of containers.
|
||||
- If your distribution comes with security model templates for LXC
|
||||
containers, you can use them out of the box. For instance, Ubuntu
|
||||
comes with AppArmor templates for LXC, and those templates provide
|
||||
an extra safety net (even though it overlaps greatly with
|
||||
capabilities).
|
||||
- You can define your own policies using your favorite access control
|
||||
mechanism. Since Docker containers are standard LXC containers,
|
||||
there is nothing “magic” or specific to Docker.
|
||||
|
||||
Just like there are many third-party tools to augment Docker containers
|
||||
with e.g. special network topologies or shared filesystems, you can
|
||||
expect to see tools to harden existing Docker containers without
|
||||
affecting Docker’s core.
|
||||
|
||||
## Conclusions
|
||||
|
||||
Docker containers are, by default, quite secure; especially if you take
|
||||
care of running your processes inside the containers as non-privileged
|
||||
users (i.e. non root).
|
||||
|
||||
You can add an extra layer of safety by enabling Apparmor, SELinux,
|
||||
GRSEC, or your favorite hardening solution.
|
||||
|
||||
Last but not least, if you see interesting security features in other
|
||||
containerization systems, you will be able to implement them as well
|
||||
with Docker, since everything is provided by the kernel anyway.
|
||||
|
||||
For more context and especially for comparisons with VMs and other
|
||||
container systems, please also see the [original blog
|
||||
post](http://blog.docker.io/2013/08/containers-docker-how-secure-are-they/).
|
|
@ -0,0 +1,7 @@
|
|||
# Contributing
|
||||
|
||||
## Contents:
|
||||
|
||||
- [Contributing to Docker](contributing/)
|
||||
- [Setting Up a Dev Environment](devenvironment/)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
page_title: Contribution Guidelines
|
||||
page_description: Contribution guidelines: create issues, conventions, pull requests
|
||||
page_keywords: contributing, docker, documentation, help, guideline
|
||||
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on Docker? Awesome!
|
||||
|
||||
The repository includes [all the instructions you need to get
|
||||
started](https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md).
|
||||
|
||||
The [developer environment
|
||||
Dockerfile](https://github.com/dotcloud/docker/blob/master/Dockerfile)
|
||||
specifies the tools and versions used to test and build Docker.
|
||||
|
||||
If you’re making changes to the documentation, see the
|
||||
[README.md](https://github.com/dotcloud/docker/blob/master/docs/README.md).
|
||||
|
||||
The [documentation environment
|
||||
Dockerfile](https://github.com/dotcloud/docker/blob/master/docs/Dockerfile)
|
||||
specifies the tools and versions used to build the Documentation.
|
||||
|
||||
Further interesting details can be found in the [Packaging
|
||||
hints](https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md).
|
|
@ -0,0 +1,147 @@
|
|||
page_title: Setting Up a Dev Environment
|
||||
page_description: Guides on how to contribute to docker
|
||||
page_keywords: Docker, documentation, developers, contributing, dev environment
|
||||
|
||||
# Setting Up a Dev Environment
|
||||
|
||||
To make it easier to contribute to Docker, we provide a standard
|
||||
development environment. It is important that the same environment be
|
||||
used for all tests, builds and releases. The standard development
|
||||
environment defines all build dependencies: system libraries and
|
||||
binaries, go environment, go dependencies, etc.
|
||||
|
||||
## Install Docker
|
||||
|
||||
Docker’s build environment itself is a Docker container, so the first
|
||||
step is to install Docker on your system.
|
||||
|
||||
You can follow the [install instructions most relevant to your
|
||||
system](https://docs.docker.io/en/latest/installation/). Make sure you
|
||||
have a working, up-to-date docker installation, then continue to the
|
||||
next step.
|
||||
|
||||
## Install tools used for this tutorial
|
||||
|
||||
Install `git`; honest, it’s very good. You can use
|
||||
other ways to get the Docker source, but they’re not anywhere near as
|
||||
easy.
|
||||
|
||||
Install `make`. This tutorial uses our base Makefile
|
||||
to kick off the docker containers in a repeatable and consistent way.
|
||||
Again, you can do it in other ways but you need to do more work.
|
||||
|
||||
## Check out the Source
|
||||
|
||||
git clone http://git@github.com/dotcloud/docker
|
||||
cd docker
|
||||
|
||||
To checkout a different revision just use `git checkout`
|
||||
with the name of branch or revision number.
|
||||
|
||||
## Build the Environment
|
||||
|
||||
This following command will build a development environment using the
|
||||
Dockerfile in the current directory. Essentially, it will install all
|
||||
the build and runtime dependencies necessary to build and test Docker.
|
||||
This command will take some time to complete when you first execute it.
|
||||
|
||||
sudo make build
|
||||
|
||||
If the build is successful, congratulations! You have produced a clean
|
||||
build of docker, neatly encapsulated in a standard build environment.
|
||||
|
||||
## Build the Docker Binary
|
||||
|
||||
To create the Docker binary, run this command:
|
||||
|
||||
sudo make binary
|
||||
|
||||
This will create the Docker binary in
|
||||
`./bundles/<version>-dev/binary/`
|
||||
|
||||
### Using your built Docker binary
|
||||
|
||||
The binary is available outside the container in the directory
|
||||
`./bundles/<version>-dev/binary/`. You can swap your
|
||||
host docker executable with this binary for live testing - for example,
|
||||
on ubuntu:
|
||||
|
||||
sudo service docker stop ; sudo cp $(which docker) $(which docker)_ ; sudo cp ./bundles/<version>-dev/binary/docker-<version>-dev $(which docker);sudo service docker start
|
||||
|
||||
> **Note**:
|
||||
> Its safer to run the tests below before swapping your hosts docker binary.
|
||||
|
||||
## Run the Tests
|
||||
|
||||
To execute the test cases, run this command:
|
||||
|
||||
sudo make test
|
||||
|
||||
If the test are successful then the tail of the output should look
|
||||
something like this
|
||||
|
||||
--- PASS: TestWriteBroadcaster (0.00 seconds)
|
||||
=== RUN TestRaceWriteBroadcaster
|
||||
--- PASS: TestRaceWriteBroadcaster (0.00 seconds)
|
||||
=== RUN TestTruncIndex
|
||||
--- PASS: TestTruncIndex (0.00 seconds)
|
||||
=== RUN TestCompareKernelVersion
|
||||
--- PASS: TestCompareKernelVersion (0.00 seconds)
|
||||
=== RUN TestHumanSize
|
||||
--- PASS: TestHumanSize (0.00 seconds)
|
||||
=== RUN TestParseHost
|
||||
--- PASS: TestParseHost (0.00 seconds)
|
||||
=== RUN TestParseRepositoryTag
|
||||
--- PASS: TestParseRepositoryTag (0.00 seconds)
|
||||
=== RUN TestGetResolvConf
|
||||
--- PASS: TestGetResolvConf (0.00 seconds)
|
||||
=== RUN TestCheckLocalDns
|
||||
--- PASS: TestCheckLocalDns (0.00 seconds)
|
||||
=== RUN TestParseRelease
|
||||
--- PASS: TestParseRelease (0.00 seconds)
|
||||
=== RUN TestDependencyGraphCircular
|
||||
--- PASS: TestDependencyGraphCircular (0.00 seconds)
|
||||
=== RUN TestDependencyGraph
|
||||
--- PASS: TestDependencyGraph (0.00 seconds)
|
||||
PASS
|
||||
ok github.com/dotcloud/docker/utils 0.017s
|
||||
|
||||
If $TESTFLAGS is set in the environment, it is passed as extra
|
||||
arguments to ‘go test’. You can use this to select certain tests to run,
|
||||
eg.
|
||||
|
||||
> TESTFLAGS=’-run \^TestBuild\$’ make test
|
||||
|
||||
If the output indicates "FAIL" and you see errors like this:
|
||||
|
||||
server.go:1302 Error: Insertion failed because database is full: database or disk is full
|
||||
|
||||
utils_test.go:179: Error copy: exit status 1 (cp: writing '/tmp/docker-testd5c9-[...]': No space left on device
|
||||
|
||||
Then you likely don’t have enough memory available the test suite. 2GB
|
||||
is recommended.
|
||||
|
||||
## Use Docker
|
||||
|
||||
You can run an interactive session in the newly built container:
|
||||
|
||||
sudo make shell
|
||||
|
||||
# type 'exit' or Ctrl-D to exit
|
||||
|
||||
## Build And View The Documentation
|
||||
|
||||
If you want to read the documentation from a local website, or are
|
||||
making changes to it, you can build the documentation and then serve it
|
||||
by:
|
||||
|
||||
sudo make docs
|
||||
# when its done, you can point your browser to http://yourdockerhost:8000
|
||||
# type Ctrl-C to exit
|
||||
|
||||
**Need More Help?**
|
||||
|
||||
If you need more help then hop on to the [\#docker-dev IRC
|
||||
channel](irc://chat.freenode.net#docker-dev) or post a message on the
|
||||
[Docker developer mailing
|
||||
list](https://groups.google.com/d/forum/docker-dev).
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
# Examples
|
||||
|
||||
## Introduction:
|
||||
|
||||
Here are some examples of how to use Docker to create running processes,
|
||||
starting from a very simple *Hello World* and progressing to more
|
||||
substantial services like those which you might find in production.
|
||||
|
||||
## Contents:
|
||||
|
||||
- [Check your Docker install](hello_world/)
|
||||
- [Hello World](hello_world/#hello-world)
|
||||
- [Hello World Daemon](hello_world/#hello-world-daemon)
|
||||
- [Node.js Web App](nodejs_web_app/)
|
||||
- [Redis Service](running_redis_service/)
|
||||
- [SSH Daemon Service](running_ssh_service/)
|
||||
- [CouchDB Service](couchdb_data_volumes/)
|
||||
- [PostgreSQL Service](postgresql_service/)
|
||||
- [Building an Image with MongoDB](mongodb/)
|
||||
- [Riak Service](running_riak_service/)
|
||||
- [Using Supervisor with Docker](using_supervisord/)
|
||||
- [Process Management with CFEngine](cfengine_process_management/)
|
||||
- [Python Web App](python_web_app/)
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
page_title: Running an apt-cacher-ng service
|
||||
page_description: Installing and running an apt-cacher-ng service
|
||||
page_keywords: docker, example, package installation, networking, debian, ubuntu
|
||||
|
||||
# Apt-Cacher-ng Service
|
||||
|
||||
> **Note**:
|
||||
>
|
||||
> - This example assumes you have Docker running in daemon mode. For
|
||||
> more information please see [*Check your Docker
|
||||
> install*](../hello_world/#running-examples).
|
||||
> - **If you don’t like sudo** then see [*Giving non-root
|
||||
> access*](../../installation/binaries/#dockergroup).
|
||||
> - **If you’re using OS X or docker via TCP** then you shouldn’t use
|
||||
> sudo.
|
||||
|
||||
When you have multiple Docker servers, or build unrelated Docker
|
||||
containers which can’t make use of the Docker build cache, it can be
|
||||
useful to have a caching proxy for your packages. This container makes
|
||||
the second download of any package almost instant.
|
||||
|
||||
Use the following Dockerfile:
|
||||
|
||||
#
|
||||
# Build: docker build -t apt-cacher .
|
||||
# Run: docker run -d -p 3142:3142 --name apt-cacher-run apt-cacher
|
||||
#
|
||||
# and then you can run containers with:
|
||||
# docker run -t -i --rm -e http_proxy http://dockerhost:3142/ debian bash
|
||||
#
|
||||
FROM ubuntu
|
||||
MAINTAINER SvenDowideit@docker.com
|
||||
|
||||
VOLUME ["/var/cache/apt-cacher-ng"]
|
||||
RUN apt-get update ; apt-get install -yq apt-cacher-ng
|
||||
|
||||
EXPOSE 3142
|
||||
CMD chmod 777 /var/cache/apt-cacher-ng ; /etc/init.d/apt-cacher-ng start ; tail -f /var/log/apt-cacher-ng/*
|
||||
|
||||
To build the image using:
|
||||
|
||||
$ sudo docker build -t eg_apt_cacher_ng .
|
||||
|
||||
Then run it, mapping the exposed port to one on the host
|
||||
|
||||
$ sudo docker run -d -p 3142:3142 --name test_apt_cacher_ng eg_apt_cacher_ng
|
||||
|
||||
To see the logfiles that are ‘tailed’ in the default command, you can
|
||||
use:
|
||||
|
||||
$ sudo docker logs -f test_apt_cacher_ng
|
||||
|
||||
To get your Debian-based containers to use the proxy, you can do one of
|
||||
three things
|
||||
|
||||
1. Add an apt Proxy setting
|
||||
`echo 'Acquire::http { Proxy "http://dockerhost:3142"; };' >> /etc/apt/conf.d/01proxy`
|
||||
|
||||
2. Set an environment variable:
|
||||
`http_proxy=http://dockerhost:3142/`
|
||||
3. Change your `sources.list` entries to start with
|
||||
`http://dockerhost:3142/`
|
||||
|
||||
**Option 1** injects the settings safely into your apt configuration in
|
||||
a local version of a common base:
|
||||
|
||||
FROM ubuntu
|
||||
RUN echo 'Acquire::http { Proxy "http://dockerhost:3142"; };' >> /etc/apt/apt.conf.d/01proxy
|
||||
RUN apt-get update ; apt-get install vim git
|
||||
|
||||
# docker build -t my_ubuntu .
|
||||
|
||||
**Option 2** is good for testing, but will break other HTTP clients
|
||||
which obey `http_proxy`, such as `curl`, `wget` and others:
|
||||
|
||||
$ sudo docker run --rm -t -i -e http_proxy=http://dockerhost:3142/ debian bash
|
||||
|
||||
**Option 3** is the least portable, but there will be times when you
|
||||
might need to do it and you can do it from your `Dockerfile`
|
||||
too.
|
||||
|
||||
Apt-cacher-ng has some tools that allow you to manage the repository,
|
||||
and they can be used by leveraging the `VOLUME`
|
||||
instruction, and the image we built to run the service:
|
||||
|
||||
$ sudo docker run --rm -t -i --volumes-from test_apt_cacher_ng eg_apt_cacher_ng bash
|
||||
|
||||
$$ /usr/lib/apt-cacher-ng/distkill.pl
|
||||
Scanning /var/cache/apt-cacher-ng, please wait...
|
||||
Found distributions:
|
||||
bla, taggedcount: 0
|
||||
1. precise-security (36 index files)
|
||||
2. wheezy (25 index files)
|
||||
3. precise-updates (36 index files)
|
||||
4. precise (36 index files)
|
||||
5. wheezy-updates (18 index files)
|
||||
|
||||
Found architectures:
|
||||
6. amd64 (36 index files)
|
||||
7. i386 (24 index files)
|
||||
|
||||
WARNING: The removal action may wipe out whole directories containing
|
||||
index files. Select d to see detailed list.
|
||||
|
||||
(Number nn: tag distribution or architecture nn; 0: exit; d: show details; r: remove tagged; q: quit): q
|
||||
|
||||
Finally, clean up after your test by stopping and removing the
|
||||
container, and then removing the image.
|
||||
|
||||
$ sudo docker stop test_apt_cacher_ng
|
||||
$ sudo docker rm test_apt_cacher_ng
|
||||
$ sudo docker rmi eg_apt_cacher_ng
|
|
@ -0,0 +1,152 @@
|
|||
page_title: Process Management with CFEngine
|
||||
page_description: Managing containerized processes with CFEngine
|
||||
page_keywords: cfengine, process, management, usage, docker, documentation
|
||||
|
||||
# Process Management with CFEngine
|
||||
|
||||
Create Docker containers with managed processes.
|
||||
|
||||
Docker monitors one process in each running container and the container
|
||||
lives or dies with that process. By introducing CFEngine inside Docker
|
||||
containers, we can alleviate a few of the issues that may arise:
|
||||
|
||||
- It is possible to easily start multiple processes within a
|
||||
container, all of which will be managed automatically, with the
|
||||
normal `docker run` command.
|
||||
- If a managed process dies or crashes, CFEngine will start it again
|
||||
within 1 minute.
|
||||
- The container itself will live as long as the CFEngine scheduling
|
||||
daemon (cf-execd) lives. With CFEngine, we are able to decouple the
|
||||
life of the container from the uptime of the service it provides.
|
||||
|
||||
## How it works
|
||||
|
||||
CFEngine, together with the cfe-docker integration policies, are
|
||||
installed as part of the Dockerfile. This builds CFEngine into our
|
||||
Docker image.
|
||||
|
||||
The Dockerfile’s `ENTRYPOINT` takes an arbitrary
|
||||
amount of commands (with any desired arguments) as parameters. When we
|
||||
run the Docker container these parameters get written to CFEngine
|
||||
policies and CFEngine takes over to ensure that the desired processes
|
||||
are running in the container.
|
||||
|
||||
CFEngine scans the process table for the `basename`
|
||||
of the commands given to the `ENTRYPOINT` and runs
|
||||
the command to start the process if the `basename`
|
||||
is not found. For example, if we start the container with
|
||||
`docker run "/path/to/my/application parameters"`,
|
||||
CFEngine will look for a process named `application`
|
||||
and run the command. If an entry for `application`
|
||||
is not found in the process table at any point in time, CFEngine will
|
||||
execute `/path/to/my/application parameters` to
|
||||
start the application once again. The check on the process table happens
|
||||
every minute.
|
||||
|
||||
Note that it is therefore important that the command to start your
|
||||
application leaves a process with the basename of the command. This can
|
||||
be made more flexible by making some minor adjustments to the CFEngine
|
||||
policies, if desired.
|
||||
|
||||
## Usage
|
||||
|
||||
This example assumes you have Docker installed and working. We will
|
||||
install and manage `apache2` and `sshd`
|
||||
in a single container.
|
||||
|
||||
There are three steps:
|
||||
|
||||
1. Install CFEngine into the container.
|
||||
2. Copy the CFEngine Docker process management policy into the
|
||||
containerized CFEngine installation.
|
||||
3. Start your application processes as part of the
|
||||
`docker run` command.
|
||||
|
||||
### Building the container image
|
||||
|
||||
The first two steps can be done as part of a Dockerfile, as follows.
|
||||
|
||||
FROM ubuntu
|
||||
MAINTAINER Eystein Måløy Stenberg <eytein.stenberg@gmail.com>
|
||||
|
||||
RUN apt-get -y install wget lsb-release unzip ca-certificates
|
||||
|
||||
# install latest CFEngine
|
||||
RUN wget -qO- http://cfengine.com/pub/gpg.key | apt-key add -
|
||||
RUN echo "deb http://cfengine.com/pub/apt $(lsb_release -cs) main" > /etc/apt/sources.list.d/cfengine-community.list
|
||||
RUN apt-get update
|
||||
RUN apt-get install cfengine-community
|
||||
|
||||
# install cfe-docker process management policy
|
||||
RUN wget https://github.com/estenberg/cfe-docker/archive/master.zip -P /tmp/ && unzip /tmp/master.zip -d /tmp/
|
||||
RUN cp /tmp/cfe-docker-master/cfengine/bin/* /var/cfengine/bin/
|
||||
RUN cp /tmp/cfe-docker-master/cfengine/inputs/* /var/cfengine/inputs/
|
||||
RUN rm -rf /tmp/cfe-docker-master /tmp/master.zip
|
||||
|
||||
# apache2 and openssh are just for testing purposes, install your own apps here
|
||||
RUN apt-get -y install openssh-server apache2
|
||||
RUN mkdir -p /var/run/sshd
|
||||
RUN echo "root:password" | chpasswd # need a password for ssh
|
||||
|
||||
ENTRYPOINT ["/var/cfengine/bin/docker_processes_run.sh"]
|
||||
|
||||
By saving this file as `Dockerfile` to a working
|
||||
directory, you can then build your container with the docker build
|
||||
command, e.g. `docker build -t managed_image`.
|
||||
|
||||
### Testing the container
|
||||
|
||||
Start the container with `apache2` and
|
||||
`sshd` running and managed, forwarding a port to our
|
||||
SSH instance:
|
||||
|
||||
docker run -p 127.0.0.1:222:22 -d managed_image "/usr/sbin/sshd" "/etc/init.d/apache2 start"
|
||||
|
||||
We now clearly see one of the benefits of the cfe-docker integration: it
|
||||
allows to start several processes as part of a normal
|
||||
`docker run` command.
|
||||
|
||||
We can now log in to our new container and see that both
|
||||
`apache2` and `sshd` are
|
||||
running. We have set the root password to "password" in the Dockerfile
|
||||
above and can use that to log in with ssh:
|
||||
|
||||
ssh -p222 root@127.0.0.1
|
||||
|
||||
ps -ef
|
||||
UID PID PPID C STIME TTY TIME CMD
|
||||
root 1 0 0 07:48 ? 00:00:00 /bin/bash /var/cfengine/bin/docker_processes_run.sh /usr/sbin/sshd /etc/init.d/apache2 start
|
||||
root 18 1 0 07:48 ? 00:00:00 /var/cfengine/bin/cf-execd -F
|
||||
root 20 1 0 07:48 ? 00:00:00 /usr/sbin/sshd
|
||||
root 32 1 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 34 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 35 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
www-data 36 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
|
||||
root 93 20 0 07:48 ? 00:00:00 sshd: root@pts/0
|
||||
root 105 93 0 07:48 pts/0 00:00:00 -bash
|
||||
root 112 105 0 07:49 pts/0 00:00:00 ps -ef
|
||||
|
||||
If we stop apache2, it will be started again within a minute by
|
||||
CFEngine.
|
||||
|
||||
service apache2 status
|
||||
Apache2 is running (pid 32).
|
||||
service apache2 stop
|
||||
* Stopping web server apache2 ... waiting [ OK ]
|
||||
service apache2 status
|
||||
Apache2 is NOT running.
|
||||
# ... wait up to 1 minute...
|
||||
service apache2 status
|
||||
Apache2 is running (pid 173).
|
||||
|
||||
## Adapting to your applications
|
||||
|
||||
To make sure your applications get managed in the same manner, there are
|
||||
just two things you need to adjust from the above example:
|
||||
|
||||
- In the Dockerfile used above, install your applications instead of
|
||||
`apache2` and `sshd`.
|
||||
- When you start the container with `docker run`,
|
||||
specify the command line arguments to your applications rather than
|
||||
`apache2` and `sshd`.
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
page_title: Sharing data between 2 couchdb databases
|
||||
page_description: Sharing data between 2 couchdb databases
|
||||
page_keywords: docker, example, package installation, networking, couchdb, data volumes
|
||||
|
||||
# CouchDB Service
|
||||
|
||||
> **Note**:
|
||||
>
|
||||
> - This example assumes you have Docker running in daemon mode. For
|
||||
> more information please see [*Check your Docker
|
||||
> install*](../hello_world/#running-examples).
|
||||
> - **If you don’t like sudo** then see [*Giving non-root
|
||||
> access*](../../installation/binaries/#dockergroup)
|
||||
|
||||
Here’s an example of using data volumes to share the same data between
|
||||
two CouchDB containers. This could be used for hot upgrades, testing
|
||||
different versions of CouchDB on the same data, etc.
|
||||
|
||||
## Create first database
|
||||
|
||||
Note that we’re marking `/var/lib/couchdb` as a data
|
||||
volume.
|
||||
|
||||
COUCH1=$(sudo docker run -d -p 5984 -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
|
||||
## Add data to the first database
|
||||
|
||||
We’re assuming your Docker host is reachable at `localhost`. If not,
|
||||
replace `localhost` with the public IP of your Docker host.
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(sudo docker port $COUCH1 5984 | grep -Po '\d+$')/_utils/"
|
||||
echo "Navigate to $URL in your browser, and use the couch interface to add data"
|
||||
|
||||
## Create second database
|
||||
|
||||
This time, we’re requesting shared access to `$COUCH1`'s volumes.
|
||||
|
||||
COUCH2=$(sudo docker run -d -p 5984 --volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||
|
||||
## Browse data on the second database
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(sudo docker port $COUCH2 5984 | grep -Po '\d+$')/_utils/"
|
||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
|
||||
|
||||
Congratulations, you are now running two Couchdb containers, completely
|
||||
isolated from each other *except* for their data.
|
|
@ -0,0 +1,166 @@
|
|||
page_title: Hello world example
|
||||
page_description: A simple hello world example with Docker
|
||||
page_keywords: docker, example, hello world
|
||||
|
||||
# Check your Docker installation
|
||||
|
||||
This guide assumes you have a working installation of Docker. To check
|
||||
your Docker install, run the following command:
|
||||
|
||||
# Check that you have a working install
|
||||
$ sudo docker info
|
||||
|
||||
If you get `docker: command not found` or something
|
||||
like `/var/lib/docker/repositories: permission denied`
|
||||
you may have an incomplete Docker installation or insufficient
|
||||
privileges to access docker on your machine.
|
||||
|
||||
Please refer to [*Installation*](../../installation/)
|
||||
for installation instructions.
|
||||
|
||||
## Hello World
|
||||
|
||||
> **Note**:
|
||||
>
|
||||
> - This example assumes you have Docker running in daemon mode. For
|
||||
> more information please see [*Check your Docker
|
||||
> install*](#check-your-docker-installation).
|
||||
> - **If you don’t like sudo** then see [*Giving non-root
|
||||
> access*](../../installation/binaries/#dockergroup)
|
||||
|
||||
This is the most basic example available for using Docker.
|
||||
|
||||
Download the small base image named `busybox`:
|
||||
|
||||
# Download a busybox image
|
||||
$ sudo docker pull busybox
|
||||
|
||||
The `busybox` image is a minimal Linux system. You
|
||||
can do the same with any number of other images, such as
|
||||
`debian`, `ubuntu` or
|
||||
`centos`. The images can be found and retrieved
|
||||
using the [Docker index](http://index.docker.io).
|
||||
|
||||
$ sudo docker run busybox /bin/echo hello world
|
||||
|
||||
This command will run a simple `echo` command, that
|
||||
will echo `hello world` back to the console over
|
||||
standard out.
|
||||
|
||||
**Explanation:**
|
||||
|
||||
- **"sudo"** execute the following commands as user *root*
|
||||
- **"docker run"** run a command in a new container
|
||||
- **"busybox"** is the image we are running the command in.
|
||||
- **"/bin/echo"** is the command we want to run in the container
|
||||
- **"hello world"** is the input for the echo command
|
||||
|
||||
**Video:**
|
||||
|
||||
See the example in action
|
||||
|
||||
<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/7658.js"id="asciicast-7658" async></script></body>"></iframe>
|
||||
|
||||
|
||||
<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/7658.js"id="asciicast-7658" async></script></body>"></iframe>
|
||||
|
||||
## Hello World Daemon
|
||||
|
||||
> **Note**:
|
||||
>
|
||||
> - This example assumes you have Docker running in daemon mode. For
|
||||
> more information please see [*Check your Docker
|
||||
> install*](#check-your-docker-installation).
|
||||
> - **If you don’t like sudo** then see [*Giving non-root
|
||||
> access*](../../installation/binaries/#dockergroup)
|
||||
|
||||
And now for the most boring daemon ever written!
|
||||
|
||||
We will use the Ubuntu image to run a simple hello world daemon that
|
||||
will just print hello world to standard out every second. It will
|
||||
continue to do this until we stop it.
|
||||
|
||||
**Steps:**
|
||||
|
||||
CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done")
|
||||
|
||||
We are going to run a simple hello world daemon in a new container made
|
||||
from the `ubuntu` image.
|
||||
|
||||
- **"sudo docker run -d "** run a command in a new container. We pass
|
||||
"-d" so it runs as a daemon.
|
||||
- **"ubuntu"** is the image we want to run the command inside of.
|
||||
- **"/bin/sh -c"** is the command we want to run in the container
|
||||
- **"while true; do echo hello world; sleep 1; done"** is the mini
|
||||
script we want to run, that will just print hello world once a
|
||||
second until we stop it.
|
||||
- **$container_id** the output of the run command will return a
|
||||
container id, we can use in future commands to see what is going on
|
||||
with this process.
|
||||
|
||||
<!-- -->
|
||||
|
||||
sudo docker logs $container_id
|
||||
|
||||
Check the logs make sure it is working correctly.
|
||||
|
||||
- **"docker logs**" This will return the logs for a container
|
||||
- **$container_id** The Id of the container we want the logs for.
|
||||
|
||||
<!-- -->
|
||||
|
||||
sudo docker attach --sig-proxy=false $container_id
|
||||
|
||||
Attach to the container to see the results in real-time.
|
||||
|
||||
- **"docker attach**" This will allow us to attach to a background
|
||||
process to see what is going on.
|
||||
- **"–sig-proxy=false"** Do not forward signals to the container;
|
||||
allows us to exit the attachment using Control-C without stopping
|
||||
the container.
|
||||
- **$container_id** The Id of the container we want to attach to.
|
||||
|
||||
Exit from the container attachment by pressing Control-C.
|
||||
|
||||
sudo docker ps
|
||||
|
||||
Check the process list to make sure it is running.
|
||||
|
||||
- **"docker ps"** this shows all running process managed by docker
|
||||
|
||||
<!-- -->
|
||||
|
||||
sudo docker stop $container_id
|
||||
|
||||
Stop the container, since we don’t need it anymore.
|
||||
|
||||
- **"docker stop"** This stops a container
|
||||
- **$container_id** The Id of the container we want to stop.
|
||||
|
||||
<!-- -->
|
||||
|
||||
sudo docker ps
|
||||
|
||||
Make sure it is really stopped.
|
||||
|
||||
**Video:**
|
||||
|
||||
See the example in action
|
||||
|
||||
<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/2562.js"id="asciicast-2562" async></script></body>"></iframe>
|
||||
|
||||
<iframe width="640" height="480" frameborder="0" sandbox="allow-same-origin allow-scripts" srcdoc="<body><script type="text/javascript"src="https://asciinema.org/a/2562.js"id="asciicast-2562" async></script></body>"></iframe>
|
||||
|
||||
The next example in the series is a [*Node.js Web
|
||||
App*](../nodejs_web_app/#nodejs-web-app) example, or you could skip to
|
||||
any of the other examples:
|
||||
|
||||
- [*Node.js Web App*](../nodejs_web_app/#nodejs-web-app)
|
||||
- [*Redis Service*](../running_redis_service/#running-redis-service)
|
||||
- [*SSH Daemon Service*](../running_ssh_service/#running-ssh-service)
|
||||
- [*CouchDB
|
||||
Service*](../couchdb_data_volumes/#running-couchdb-service)
|
||||
- [*PostgreSQL Service*](../postgresql_service/#postgresql-service)
|
||||
- [*Building an Image with MongoDB*](../mongodb/#mongodb-image)
|
||||
- [*Python Web App*](../python_web_app/#python-web-app)
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
page_title: Docker HTTPS Setup
|
||||
page_description: How to setup docker with https
|
||||
page_keywords: docker, example, https, daemon
|
||||
|
||||
# Running Docker with https
|
||||
|
||||
By default, Docker runs via a non-networked Unix socket. It can also
|
||||
optionally communicate using a HTTP socket.
|
||||
|
||||
If you need Docker reachable via the network in a safe manner, you can
|
||||
enable TLS by specifying the tlsverify flag and pointing Docker’s
|
||||
tlscacert flag to a trusted CA certificate.
|
||||
|
||||
In daemon mode, it will only allow connections from clients
|
||||
authenticated by a certificate signed by that CA. In client mode, it
|
||||
will only connect to servers with a certificate signed by that CA.
|
||||
|
||||
> **Warning**:
|
||||
> Using TLS and managing a CA is an advanced topic. Please make you self
|
||||
> familiar with openssl, x509 and tls before using it in production.
|
||||
|
||||
## Create a CA, server and client keys with OpenSSL
|
||||
|
||||
First, initialize the CA serial file and generate CA private and public
|
||||
keys:
|
||||
|
||||
$ echo 01 > ca.srl
|
||||
$ openssl genrsa -des3 -out ca-key.pem
|
||||
$ openssl req -new -x509 -days 365 -key ca-key.pem -out ca.pem
|
||||
|
||||
Now that we have a CA, you can create a server key and certificate
|
||||
signing request. Make sure that "Common Name (e.g. server FQDN or YOUR
|
||||
name)" matches the hostname you will use to connect to Docker or just
|
||||
use ‘\*’ for a certificate valid for any hostname:
|
||||
|
||||
$ openssl genrsa -des3 -out server-key.pem
|
||||
$ openssl req -new -key server-key.pem -out server.csr
|
||||
|
||||
Next we’re going to sign the key with our CA:
|
||||
|
||||
$ openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem \
|
||||
-out server-cert.pem
|
||||
|
||||
For client authentication, create a client key and certificate signing
|
||||
request:
|
||||
|
||||
$ openssl genrsa -des3 -out client-key.pem
|
||||
$ openssl req -new -key client-key.pem -out client.csr
|
||||
|
||||
To make the key suitable for client authentication, create a extensions
|
||||
config file:
|
||||
|
||||
$ echo extendedKeyUsage = clientAuth > extfile.cnf
|
||||
|
||||
Now sign the key:
|
||||
|
||||
$ openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca-key.pem \
|
||||
-out client-cert.pem -extfile extfile.cnf
|
||||
|
||||
Finally you need to remove the passphrase from the client and server
|
||||
key:
|
||||
|
||||
$ openssl rsa -in server-key.pem -out server-key.pem
|
||||
$ openssl rsa -in client-key.pem -out client-key.pem
|
||||
|
||||
Now you can make the Docker daemon only accept connections from clients
|
||||
providing a certificate trusted by our CA:
|
||||
|
||||
$ sudo docker -d --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem \
|
||||
-H=0.0.0.0:4243
|
||||
|
||||
To be able to connect to Docker and validate its certificate, you now
|
||||
need to provide your client keys, certificates and trusted CA:
|
||||
|
||||
$ docker --tlsverify --tlscacert=ca.pem --tlscert=client-cert.pem --tlskey=client-key.pem \
|
||||
-H=dns-name-of-docker-host:4243
|
||||
|
||||
> **Warning**:
|
||||
> As shown in the example above, you don’t have to run the
|
||||
> `docker` client with `sudo` or
|
||||
> the `docker` group when you use certificate
|
||||
> authentication. That means anyone with the keys can give any
|
||||
> instructions to your Docker daemon, giving them root access to the
|
||||
> machine hosting the daemon. Guard these keys as you would a root
|
||||
> password!
|
||||
|
||||
## Other modes
|
||||
|
||||
If you don’t want to have complete two-way authentication, you can run
|
||||
Docker in various other modes by mixing the flags.
|
||||
|
||||
### Daemon modes
|
||||
|
||||
- tlsverify, tlscacert, tlscert, tlskey set: Authenticate clients
|
||||
- tls, tlscert, tlskey: Do not authenticate clients
|
||||
|
||||
### Client modes
|
||||
|
||||
- tls: Authenticate server based on public/default CA pool
|
||||
- tlsverify, tlscacert: Authenticate server based on given CA
|
||||
- tls, tlscert, tlskey: Authenticate with client certificate, do not
|
||||
authenticate server based on given CA
|
||||
- tlsverify, tlscacert, tlscert, tlskey: Authenticate with client
|
||||
certificate, authenticate server based on given CA
|
||||
|
||||
The client will send its client certificate if found, so you just need
|
||||
to drop your keys into \~/.docker/\<ca, cert or key\>.pem
|
|
@ -0,0 +1,89 @@
|
|||
page_title: Building a Docker Image with MongoDB
|
||||
page_description: How to build a Docker image with MongoDB pre-installed
|
||||
page_keywords: docker, example, package installation, networking, mongodb
|
||||
|
||||
# Building an Image with MongoDB
|
||||
|
||||
> **Note**:
|
||||
>
|
||||
> - This example assumes you have Docker running in daemon mode. For
|
||||
> more information please see [*Check your Docker
|
||||
> install*](../hello_world/#running-examples).
|
||||
> - **If you don’t like sudo** then see [*Giving non-root
|
||||
> access*](../../installation/binaries/#dockergroup)
|
||||
|
||||
The goal of this example is to show how you can build your own Docker
|
||||
images with MongoDB pre-installed. We will do that by constructing a
|
||||
`Dockerfile` that downloads a base image, adds an
|
||||
apt source and installs the database software on Ubuntu.
|
||||
|
||||
## Creating a `Dockerfile`
|
||||
|
||||
Create an empty file called `Dockerfile`:
|
||||
|
||||
touch Dockerfile
|
||||
|
||||
Next, define the parent image you want to use to build your own image on
|
||||
top of. Here, we’ll use [Ubuntu](https://index.docker.io/_/ubuntu/)
|
||||
(tag: `latest`) available on the [docker
|
||||
index](http://index.docker.io):
|
||||
|
||||
FROM ubuntu:latest
|
||||
|
||||
Since we want to be running the latest version of MongoDB we’ll need to
|
||||
add the 10gen repo to our apt sources list.
|
||||
|
||||
# Add 10gen official apt source to the sources list
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
|
||||
RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list
|
||||
|
||||
Then, we don’t want Ubuntu to complain about init not being available so
|
||||
we’ll divert `/sbin/initctl` to
|
||||
`/bin/true` so it thinks everything is working.
|
||||
|
||||
# Hack for initctl not being available in Ubuntu
|
||||
RUN dpkg-divert --local --rename --add /sbin/initctl
|
||||
RUN ln -s /bin/true /sbin/initctl
|
||||
|
||||
Afterwards we’ll be able to update our apt repositories and install
|
||||
MongoDB
|
||||
|
||||
# Install MongoDB
|
||||
RUN apt-get update
|
||||
RUN apt-get install mongodb-10gen
|
||||
|
||||
To run MongoDB we’ll have to create the default data directory (because
|
||||
we want it to run without needing to provide a special configuration
|
||||
file)
|
||||
|
||||
# Create the MongoDB data directory
|
||||
RUN mkdir -p /data/db
|
||||
|
||||
Finally, we’ll expose the standard port that MongoDB runs on, 27107, as
|
||||
well as define an `ENTRYPOINT` instruction for the
|
||||
container.
|
||||
|
||||
EXPOSE 27017
|
||||
ENTRYPOINT ["usr/bin/mongod"]
|
||||
|
||||
Now, lets build the image which will go through the
|
||||
`Dockerfile` we made and run all of the commands.
|
||||
|
||||
sudo docker build -t <yourname>/mongodb .
|
||||
|
||||
Now you should be able to run `mongod` as a daemon
|
||||
and be able to connect on the local port!
|
||||
|
||||
# Regular style
|
||||
MONGO_ID=$(sudo docker run -d <yourname>/mongodb)
|
||||
|
||||
# Lean and mean
|
||||
MONGO_ID=$(sudo docker run -d <yourname>/mongodb --noprealloc --smallfiles)
|
||||
|
||||
# Check the logs out
|
||||
sudo docker logs $MONGO_ID
|
||||
|
||||
# Connect and play around
|
||||
mongo --port <port you get from `docker ps`>
|
||||
|
||||
Sweet!
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче