Эволюция Docker. Часть 2.3

от автора

Вступление

Данная статья является четвертой в цикле (1, 2, 3), посвященном изучению исходного кода Docker и прямым продолжением предыдущей статьи, которую мне пришлось преждевременно завершить в виду зависания редактора хабра. В этой статье мы закончим изучать код первого публичного релиза Docker v0.1.0. Будут рассмотрены оставшиеся команды по управлению контейнерами, сетевой стек, а также создание и запуск образа.

Управление контейнерами

Start

Запускает остановленный контейнер вызовом метода container.Start, код которого был представлен в конце предыдущей статьи:

CmdStart
func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "start", "[OPTIONS] NAME", "Start a stopped container") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() < 1 { cmd.Usage() return nil } for _, name := range cmd.Args() { if container := srv.runtime.Get(name); container != nil { if err := container.Start(); err != nil { return err } fmt.Fprintln(stdout, container.Id) } else { return errors.New("No such container: " + name) } } return nil }

Stop

Останавливает контейнер вызовом метода container.Stop:

CmdStop
func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() < 1 { cmd.Usage() return nil } for _, name := range cmd.Args() { if container := srv.runtime.Get(name); container != nil { if err := container.Stop(); err != nil { return err } fmt.Fprintln(stdout, container.Id) } else { return errors.New("No such container: " + name) } } return nil }  func (container *Container) Stop() error { if !container.State.Running { return nil }  // 1. Send a SIGTERM if output, err := exec.Command("/usr/bin/lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { log.Printf(string(output)) log.Printf("Failed to send SIGTERM to the process, force killing") if err := container.Kill(); err != nil { return err } }  // 2. Wait for the process to exit on its own if err := container.WaitTimeout(10 * time.Second); err != nil { log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id) if err := container.Kill(); err != nil { return err } } return nil }

В начале метода производится попытка остановки утилитой lxc-kill, если по прошествии 10 секунд контейнер не остановлен, тогда вызывается метод container.Kill, который убивает процесс:

container.Kill
func (container *Container) Kill() error { if !container.State.Running { return nil } return container.kill() }  func (container *Container) kill() error { if err := container.cmd.Process.Kill(); err != nil { return err } // Wait for the container to be actually stopped container.Wait() return nil }

Restart

Последовательно вызывает методы container.Stop и container.Start:

CmdRestart
func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "restart", "[OPTIONS] NAME", "Restart a running container") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() < 1 { cmd.Usage() return nil } for _, name := range cmd.Args() { if container := srv.runtime.Get(name); container != nil { if err := container.Restart(); err != nil { return err } fmt.Fprintln(stdout, container.Id) } else { return errors.New("No such container: " + name) } } return nil }  func (container *Container) Restart() error { if err := container.Stop(); err != nil { return err } if err := container.Start(); err != nil { return err } return nil }

Wait

Ждет завершения работы контейнера, вызывая метод container.Wait:

CmdWait
// 'docker wait': block until a container stops func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() < 1 { cmd.Usage() return nil } for _, name := range cmd.Args() { if container := srv.runtime.Get(name); container != nil { fmt.Fprintln(stdout, container.Wait()) } else { return errors.New("No such container: " + name) } } return nil }  // Wait blocks until the container stops running, then returns its exit code. func (container *Container) Wait() int {  for container.State.Running { container.State.wait() } return container.State.ExitCode }

Kill

Убивает процесс контейнера методом container.Kill, который уже был рассмотрен выше:

CmdKill
// 'docker kill NAME' kills a running container func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container") if err := cmd.Parse(args); err != nil { return nil } for _, name := range cmd.Args() { container := srv.runtime.Get(name) if container == nil { return errors.New("No such container: " + name) } if err := container.Kill(); err != nil { fmt.Fprintln(stdout, "Error killing container "+name+": "+err.Error()) } } return nil }

Rm

Вызывает метод runtime.Destroy, который останавливает контейнер, при необходимости размонтирует файловую систему и удаляет директорию контейнера:

CmdRm
func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container") if err := cmd.Parse(args); err != nil { return nil } for _, name := range cmd.Args() { container := srv.runtime.Get(name) if container == nil { return errors.New("No such container: " + name) } if err := srv.runtime.Destroy(container); err != nil { fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error()) } } return nil }  func (runtime *Runtime) Destroy(container *Container) error { element := runtime.getContainerElement(container.Id) if element == nil { return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id) }  if err := container.Stop(); err != nil { return err } if mounted, err := container.Mounted(); err != nil { return err } else if mounted { if err := container.Unmount(); err != nil { return fmt.Errorf("Unable to unmount container %v: %v", container.Id, err) } } // Deregister the container before removing its directory, to avoid race conditions runtime.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 }

Ps

Получает весь список существующих контейнеров и отображает их в таблице в зависимости от переданных параметров:

CmdPs
func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "ps", "[OPTIONS]", "List containers") quiet := cmd.Bool("q", false, "Only display numeric IDs") fl_all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") fl_full := cmd.Bool("notrunc", false, "Don't truncate output") if err := cmd.Parse(args); err != nil { return nil } w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0) if !*quiet { fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\n") } for _, container := range srv.runtime.List() { if !container.State.Running && !*fl_all { continue } if !*quiet { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) if !*fl_full { command = Trunc(command, 20) } for idx, field := range []string{ /* ID */ container.Id, /* IMAGE */ srv.runtime.repositories.ImageName(container.Image), /* COMMAND */ command, /* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago", /* STATUS */ container.State.String(), /* COMMENT */ "", } { if idx == 0 { w.Write([]byte(field)) } else { w.Write([]byte("\t" + field)) } } w.Write([]byte{'\n'}) } else { stdout.Write([]byte(container.Id + "\n")) } } if !*quiet { w.Flush() } return nil }

Diff

Показывает diff между образом контейнера и его текущей файловой системой, полученный из метода container.Changes:

CmdDiff
func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "diff", "CONTAINER [OPTIONS]", "Inspect changes on a container's filesystem") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() < 1 { return errors.New("Not enough arguments") } if container := srv.runtime.Get(cmd.Arg(0)); container == nil { return errors.New("No such container") } else { changes, err := container.Changes() if err != nil { return err } for _, change := range changes { fmt.Fprintln(stdout, change.String()) } } return nil }  func (container *Container) Changes() ([]Change, error) { image, err := container.GetImage() if err != nil { return nil, err } return image.Changes(container.rwPath()) }  func (image *Image) Changes(rw string) ([]Change, error) { layers, err := image.layers() if err != nil { return nil, err } return Changes(layers, rw) }

Changes является главной функцией, в которой и происходит вся работа по вычислению изменений, ее код находится в файле changes.go:

changes.go
type ChangeType int  const ( ChangeModify = iota ChangeAdd ChangeDelete )  type Change struct { Path string Kind ChangeType }  func (change *Change) String() string { var kind string switch change.Kind { case ChangeModify: kind = "C" case ChangeAdd: kind = "A" case ChangeDelete: kind = "D" } return fmt.Sprintf("%s %s", kind, change.Path) }  func Changes(layers []string, rw string) ([]Change, error) { var changes []Change err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { if err != nil { return err }  // Rebase path path, err = filepath.Rel(rw, path) if err != nil { return err } path = filepath.Join("/", path)  // Skip root if path == "/" { return nil }  // Skip AUFS metadata if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched { return err }  change := Change{ Path: path, }  // Find out what kind of modification happened file := filepath.Base(path) // If there is a whiteout, then the file was removed if strings.HasPrefix(file, ".wh.") { originalFile := strings.TrimLeft(file, ".wh.") change.Path = filepath.Join(filepath.Dir(path), originalFile) change.Kind = ChangeDelete } else { // Otherwise, the file was added change.Kind = ChangeAdd  // ...Unless it already existed in a top layer, in which case, it's a modification for _, layer := range layers { stat, err := os.Stat(filepath.Join(layer, path)) if err != nil && !os.IsNotExist(err) { return err } if err == nil { // The file existed in the top layer, so that's a modification  // However, if it's a directory, maybe it wasn't actually modified. // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar if stat.IsDir() && f.IsDir() { if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() { // Both directories are the same, don't record the change return nil } } change.Kind = ChangeModify break } } }  // Record change changes = append(changes, change) return nil }) if err != nil { return nil, err } return changes, nil }

Функция обходит все директории в rw, если элемент имеет префикс .wh., то это означает, что он был удален (так Aufs регистрирует удаление). В ином случае происходит последовательный обход слоев образа в попытке найти элемент в них. Если он не был найден ни в одном из слоев, значит элемент был создан.  В случае обнаружения элемента в слоях, функция считает, что это модификация. Каждое из действий отображается соответствующей буквой (D A C).

Общие команды

Info

Отображает версию, а также количество контейнеров и образов:

CmdInfo
// 'docker info': display system-wide information. func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error { images, _ := srv.runtime.graph.All() var imgcount int if images == nil { imgcount = 0 } else { imgcount = len(images) } cmd := rcli.Subcmd(stdout, "info", "", "Display system-wide information.") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() > 0 { cmd.Usage() return nil } fmt.Fprintf(stdout, "containers: %d\nversion: %s\nimages: %d\n", len(srv.runtime.List()), VERSION, imgcount) return nil } 

Inspect

Пытается получить структуру контейнера или образа по переданному имени, после чего экспортирует и возвращает ее в json формате, предварительно добавив отступы для читаемости:

CmdInspect
func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() < 1 { cmd.Usage() return nil } name := cmd.Arg(0) var obj interface{} if container := srv.runtime.Get(name); container != nil { obj = container } else if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil { obj = image } else { // No output means the object does not exist // (easier to script since stdout and stderr are not differentiated atm) return nil } data, err := json.Marshal(obj) if err != nil { return err } indented := new(bytes.Buffer) if err = json.Indent(indented, data, "", "    "); err != nil { return err } if _, err := io.Copy(stdout, indented); err != nil { return err } stdout.Write([]byte{'\n'}) return nil }

Logs

Читает и копирует в stdout, stderr содержимое лог файлов контейнера:

CmdLogs
func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() != 1 { cmd.Usage() return nil } name := cmd.Arg(0) if container := srv.runtime.Get(name); container != nil { log_stdout, err := container.ReadLog("stdout") if err != nil { return err } log_stderr, err := container.ReadLog("stderr") if err != nil { return err } // FIXME: Interpolate stdout and stderr instead of concatenating them // FIXME: Differentiate stdout and stderr in the remote protocol if _, err := io.Copy(stdout, log_stdout); err != nil { return err } if _, err := io.Copy(stdout, log_stderr); err != nil { return err } return nil } return errors.New("No such container: " + cmd.Arg(0)) }

Attach

Подключается к контейнеру и перенаправляет потоки stdin, stdout, stderr:

CmdAttach
func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "attach", "[OPTIONS]", "Attach to a running container") fl_i := cmd.Bool("i", false, "Attach to stdin") fl_o := cmd.Bool("o", true, "Attach to stdout") fl_e := cmd.Bool("e", true, "Attach to stderr") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() != 1 { cmd.Usage() return nil } name := cmd.Arg(0) container := srv.runtime.Get(name) if container == nil { return errors.New("No such container: " + name) } var wg sync.WaitGroup if *fl_i { c_stdin, err := container.StdinPipe() if err != nil { return err } wg.Add(1) go func() { io.Copy(c_stdin, stdin); wg.Add(-1) }() } if *fl_o { c_stdout, err := container.StdoutPipe() if err != nil { return err } wg.Add(1) go func() { io.Copy(stdout, c_stdout); wg.Add(-1) }() } if *fl_e { c_stderr, err := container.StderrPipe() if err != nil { return err } wg.Add(1) go func() { io.Copy(stdout, c_stderr); wg.Add(-1) }() } wg.Wait() return nil }

Port

Отображает проброшенный порт контейнера из хеш таблицы PortMapping, заполняемой при запуске. Это будет подробно разобрано ниже, в разделе по сетевому стеку:

CmdPort
func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() != 2 { cmd.Usage() return nil } name := cmd.Arg(0) privatePort := cmd.Arg(1) if container := srv.runtime.Get(name); container == nil { return errors.New("No such container: " + name) } else { if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists { return fmt.Errorf("No private port '%s' allocated on %s", privatePort, name) } else { fmt.Fprintln(stdout, frontend) } } return nil }

Сетевой стек

Основной код находится в файле network.go. Мы начнем разбор с функции newNetworkManager, которая инициализирует и возвращает структуру NetworkManager:

network.go
// Network Manager manages a set of network interfaces // Only *one* manager per host machine should be used type NetworkManager struct { bridgeIface   string bridgeNetwork *net.IPNet  ipAllocator   *IPAllocator portAllocator *PortAllocator portMapper    *PortMapper }   func newNetworkManager(bridgeIface string) (*NetworkManager, error) { addr, err := getIfaceAddr(bridgeIface) if err != nil { return nil, err } network := addr.(*net.IPNet)  ipAllocator, err := newIPAllocator(network) if err != nil { return nil, err }  portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd) if err != nil { return nil, err }  portMapper, err := newPortMapper()  manager := &NetworkManager{ bridgeIface:   bridgeIface, bridgeNetwork: network, ipAllocator:   ipAllocator, portAllocator: portAllocator, portMapper:    portMapper, } return manager, nil }  // Return the IPv4 address of a network interface func getIfaceAddr(name string) (net.Addr, error) { iface, err := net.InterfaceByName(name) if err != nil { return nil, err } addrs, err := iface.Addrs() if err != nil { return nil, err } var addrs4 []net.Addr for _, addr := range addrs { ip := (addr.(*net.IPNet)).IP if ip4 := ip.To4(); len(ip4) == net.IPv4len { addrs4 = append(addrs4, addr) } } switch { case len(addrs4) == 0: return nil, fmt.Errorf("Interface %v has no IP addresses", name) case len(addrs4) > 1: fmt.Printf("Interface %v has more than 1 IPv4 address. Defaulting to using %v\n", name, (addrs4[0].(*net.IPNet)).IP) } return addrs4[0], nil }

newNetworkManager при помощи функции getIfaceAddr получает структуру net.IPNet с адресом, установленным на интерфейсе networkBridgeIface (lxcbr0). Данный сетевой мост создается при запуске lxc. Если на интерфейсе имеется несколько ip адресов, то выбирается первый из них. Далее функция newIPAllocator инициализирует и возвращает структуру IPAllocator, которая в свою очередь,  аллоцирует адреса для контейнеров в подсети назначенной lxcbr0.

IPAllocator
// IP allocator: Atomatically allocate and release networking ports type IPAllocator struct { network *net.IPNet queue   chan (net.IP) }  func newIPAllocator(network *net.IPNet) (*IPAllocator, error) { alloc := &IPAllocator{ network: network, } if err := alloc.populate(); err != nil { return nil, err } return alloc, nil }

Метод populate вычисляет диапазон подсети на основании ip и маски, после чего создает канал очереди. Для аллокации новых адресов используется простой инкремент. Хелпер методы для вычисления networkRange, networkSize, intToIp, ipToInt определенны в том же файле:

alloc.populate
func (alloc *IPAllocator) populate() error { firstIP, _ := networkRange(alloc.network) size, err := networkSize(alloc.network.Mask) if err != nil { return err } // The queue size should be the network size - 3 // -1 for the network address, -1 for the broadcast address and // -1 for the gateway address alloc.queue = make(chan net.IP, size-3) for i := int32(1); i < size-1; i++ { ipNum, err := ipToInt(firstIP) if err != nil { return err } ip, err := intToIp(ipNum + int32(i)) if err != nil { return err } // Discard the network IP (that's the host IP address) if ip.Equal(alloc.network.IP) { continue } alloc.queue <- ip } return nil }   // Calculates the first and last IP addresses in an IPNet func networkRange(network *net.IPNet) (net.IP, net.IP) { netIP := network.IP.To4() firstIP := netIP.Mask(network.Mask) lastIP := net.IPv4(0, 0, 0, 0).To4() for i := 0; i < len(lastIP); i++ { lastIP[i] = netIP[i] | ^network.Mask[i] } return firstIP, lastIP }  // Converts a 4 bytes IP into a 32 bit integer func ipToInt(ip net.IP) (int32, error) { buf := bytes.NewBuffer(ip.To4()) var n int32 if err := binary.Read(buf, binary.BigEndian, &n); err != nil { return 0, err } return n, nil }  // Converts 32 bit integer into a 4 bytes IP address func intToIp(n int32) (net.IP, error) { var buf bytes.Buffer if err := binary.Write(&buf, binary.BigEndian, &n); err != nil { return net.IP{}, err } ip := net.IPv4(0, 0, 0, 0).To4() for i := 0; i < net.IPv4len; i++ { ip[i] = buf.Bytes()[i] } return ip, nil }  // Given a netmask, calculates the number of available hosts func networkSize(mask net.IPMask) (int32, error) { m := net.IPv4Mask(0, 0, 0, 0) for i := 0; i < net.IPv4len; i++ { m[i] = ^mask[i] } buf := bytes.NewBuffer(m) var n int32 if err := binary.Read(buf, binary.BigEndian, &n); err != nil { return 0, err } return n + 1, nil }

Аллокация портов основана на аналогичном принципе с каналом очереди:

PortAllocator
// Port allocator: Atomatically allocate and release networking ports type PortAllocator struct { ports chan (int) }  func (alloc *PortAllocator) populate(start, end int) { alloc.ports = make(chan int, end-start) for port := start; port < end; port++ { alloc.ports <- port } }  func (alloc *PortAllocator) Acquire() (int, error) { select { case port := <-alloc.ports: return port, nil default: return -1, errors.New("No more ports available") } return -1, nil }  func (alloc *PortAllocator) Release(port int) error { select { case alloc.ports <- port: return nil default: return errors.New("Too many ports have been released") } return nil }  func newPortAllocator(start, end int) (*PortAllocator, error) { allocator := &PortAllocator{} allocator.populate(start, end) return allocator, nil }

Для маппинга портов используется стандартная утилита iptables:

iptables
// Wrapper around the iptables command func iptables(args ...string) error { if err := exec.Command("/sbin/iptables", args...).Run(); err != nil { return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " ")) } return nil }  func newPortMapper() (*PortMapper, error) { mapper := &PortMapper{} if err := mapper.cleanup(); err != nil { return nil, err } if err := mapper.setup(); err != nil { return nil, err } return mapper, nil }   // Port mapper takes care of mapping external ports to containers by setting // up iptables rules. // It keeps track of all mappings and is able to unmap at will type PortMapper struct { mapping map[int]net.TCPAddr }

Функция newPortMapper инициализирует структуру PortMapper, в хеш таблице которой будут сохраняться все проброшенные порты. В методах setup и cleanup соответственно создаются и удаляются NAT цепочки:

PortMapper
func (mapper *PortMapper) cleanup() error { // Ignore errors - This could mean the chains were never set up iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER") iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER") iptables("-t", "nat", "-F", "DOCKER") iptables("-t", "nat", "-X", "DOCKER") mapper.mapping = make(map[int]net.TCPAddr) return nil }  func (mapper *PortMapper) setup() error { if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil { return errors.New("Unable to setup port networking: Failed to create DOCKER chain") } if err := iptables("-t", "nat", "-A", "PREROUTING", "-j", "DOCKER"); err != nil { return errors.New("Unable to setup port networking: Failed to inject docker in PREROUTING chain") } if err := iptables("-t", "nat", "-A", "OUTPUT", "-j", "DOCKER"); err != nil { return errors.New("Unable to setup port networking: Failed to inject docker in OUTPUT chain") } return nil }

Весь маппинг портов основан на добавлении правил в созданные цепочки при помощи вызова iptables:

iptablesForward
func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error { if err := mapper.iptablesForward("-A", port, dest); err != nil { return err } mapper.mapping[port] = dest return nil }  func (mapper *PortMapper) Unmap(port int) error { dest, ok := mapper.mapping[port] if !ok { return errors.New("Port is not mapped") } if err := mapper.iptablesForward("-D", port, dest); err != nil { return err } delete(mapper.mapping, port) return nil }  func (mapper *PortMapper) iptablesForward(rule string, port int, dest net.TCPAddr) error { return iptables("-t", "nat", rule, "DOCKER", "-p", "tcp", "--dport", strconv.Itoa(port), "-j", "DNAT", "--to-destination", net.JoinHostPort(dest.IP.String(), strconv.Itoa(dest.Port))) }

Создание образа и запуск контейнера

Для создания образа будем использовать утилиту debootstrap, которая скачает и подготовит файловую систему дистрибутива Ubuntu.

cd /tmp && sudo debootstrap trusty trusty && sudo chroot /tmp/trusty/ apt-get install iptables -y

Далее нужно будет удалить симлинк на /etc/resolv.conf и создать пустой файл для точки монтирования, так как без этого lxc выдаст ошибку.

sudo rm /tmp/trusty/etc/resolv.conf && sudo touch /tmp/trusty/etc/resolv.conf

Вместо заглушки systemd-resolved, будем использовать гугловый dns:

sudo rm /etc/resolv.conf && echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf

Теперь вернемся в папку с докером и импортируем образ.

cd /home/vagrant/engine/docker && sudo tar -C /tmp/trusty -c . | sudo ./docker import - ubuntu docker import - ubuntu 10834b53c26eb579

Проверим, что образ импортировался:

vagrant@ubuntu-focal:~/engine/docker$ sudo ./docker images docker images REPOSITORY          TAG                 ID                  CREATED             PARENT ubuntu              latest              10834b53c26eb579    12 seconds ago

В виду изменений в конфигурационном файле новых версий LXC нам нужно применить патч:

docker.patch
diff --git a/container.go b/container.go index f900599d00..941cff0455 100644 --- a/container.go +++ b/container.go @@ -217,6 +217,7 @@ func (container *Container) Start() error {  params := []string{  "-n", container.Id,  "-f", container.lxcConfigPath(), +"-F",  "--",  "/sbin/init",  } diff --git a/lxc_template.go b/lxc_template.go index e3beb037f9..715522738c 100755 --- a/lxc_template.go +++ b/lxc_template.go @@ -7,33 +7,23 @@ import (  const LxcTemplate = `  # hostname  {{if .Config.Hostname}} -lxc.utsname = {{.Config.Hostname}} +lxc.uts.name = {{.Config.Hostname}}  {{else}} -lxc.utsname = {{.Id}} +lxc.uts.name = {{.Id}}  {{end}}  #lxc.aa_profile = unconfined    # network configuration -lxc.network.type = veth -lxc.network.flags = up -lxc.network.link = lxcbr0 -lxc.network.name = eth0 -lxc.network.mtu = 1500 -lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}} +lxc.net.0.type = veth +lxc.net.0.flags = up +lxc.net.0.link = lxcbr0 +lxc.net.0.name = eth0 +lxc.net.0.mtu = 1500 +lxc.net.0.ipv4.address = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}}    # root filesystem  {{$ROOTFS := .RootfsPath}} -lxc.rootfs = {{$ROOTFS}} - -# use a dedicated pts for the container (and limit the number of pseudo terminal -# available) -lxc.pts = 1024 - -# disable the main console -lxc.console = none - -# no controlling tty at all -lxc.tty = 1 +lxc.rootfs.path = {{$ROOTFS}}    # no implicit access to devices  lxc.cgroup.devices.deny = a 

Скомпилируем и запустим контейнер:

vagrant@ubuntu-focal:~/engine/docker$ sudo ./docker run ubuntu /bin/bash root@9aac67f055e3731a:/# cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=14.04 DISTRIB_CODENAME=trusty DISTRIB_DESCRIPTION="Ubuntu 14.04 LTS"  root@9aac67f055e3731a:/# cat /etc/resolv.conf nameserver 8.8.8.8  root@9aac67f055e3731a:/# ping google.com PING google.com (142.250.180.238) 56(84) bytes of data. 64 bytes from bud02s34-in-f14.1e100.net (142.250.180.238): icmp_seq=1 ttl=117 time=25.3 ms 64 bytes from bud02s34-in-f14.1e100.net (142.250.180.238): icmp_seq=2 ttl=117 time=27.5 ms 64 bytes from bud02s34-in-f14.1e100.net (142.250.180.238): icmp_seq=3 ttl=117 time=27.8 ms root@9aac67f055e3731a:/# exit exit  vagrant@ubuntu-focal:~/engine/docker$ sudo ./docker diff 9aac67f055e3731a docker diff 9aac67f055e3731a C /root A /root/.bash_history

Заключение

Это был практически полный обзор кода первой версии Docker. С тех пор прошло уже много лет и сейчас код Docker состоит из сотен файлов, в котором будет сложно разобраться без подготовки. Но, как мы увидели, начинался он достаточно просто и при желании можно продолжать прослеживать развитие кода, переходя по версиям в git репозитории. Лично мне такой способ помогает понять и разобраться, как появился и развивался тот или иной функционал. Если появится время, то следующая статья будет про libcontainer, на который перешел Docker после LXC.


ссылка на оригинал статьи https://habr.com/ru/articles/575296/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *