{"id":400851,"date":"2024-06-29T15:30:24","date_gmt":"2024-06-29T15:30:24","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=400851"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=400851","title":{"rendered":"<span>\u042d\u0432\u043e\u043b\u044e\u0446\u0438\u044f Docker. \u0427\u0430\u0441\u0442\u044c 2.3<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h2>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0414\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u043e\u0439 \u0432 \u0446\u0438\u043a\u043b\u0435 (<a href=\"https:\/\/habr.com\/ru\/post\/573828\/\" rel=\"noopener noreferrer nofollow\">1<\/a>, <a href=\"https:\/\/habr.com\/ru\/post\/574750\/\" rel=\"noopener noreferrer nofollow\">2<\/a>, <a href=\"https:\/\/habr.com\/ru\/post\/575256\/\" rel=\"noopener noreferrer nofollow\">3<\/a>), \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u043e\u043c \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044e \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 Docker \u0438 \u043f\u0440\u044f\u043c\u044b\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435\u043c <a href=\"https:\/\/habr.com\/ru\/post\/575256\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438<\/a>, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043f\u0440\u0435\u0436\u0434\u0435\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0432 \u0432\u0438\u0434\u0443 \u0437\u0430\u0432\u0438\u0441\u0430\u043d\u0438\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430 \u0445\u0430\u0431\u0440\u0430. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043c \u0438\u0437\u0443\u0447\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u0440\u0435\u043b\u0438\u0437\u0430 Docker v0.1.0. \u0411\u0443\u0434\u0443\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u044b \u043e\u0441\u0442\u0430\u0432\u0448\u0438\u0435\u0441\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u043c\u0438, \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0441\u0442\u0435\u043a, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a \u043e\u0431\u0440\u0430\u0437\u0430.<\/p>\n<h3>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u043c\u0438<\/h3>\n<h4>Start<\/h4>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0430 container.Start, \u043a\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u044b\u043b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0432 \u043a\u043e\u043d\u0446\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>CmdStart<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Stop<\/h4>\n<p>\u041e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0430 container.Stop:<\/p>\n<details class=\"spoiler\">\n<summary>CmdStop<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 \u043d\u0430\u0447\u0430\u043b\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0443\u0442\u0438\u043b\u0438\u0442\u043e\u0439 lxc-kill, \u0435\u0441\u043b\u0438 \u043f\u043e \u043f\u0440\u043e\u0448\u0435\u0441\u0442\u0432\u0438\u0438 10 \u0441\u0435\u043a\u0443\u043d\u0434 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043d\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d, \u0442\u043e\u0433\u0434\u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 container.Kill, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0431\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441:<\/p>\n<details class=\"spoiler\">\n<summary>container.Kill<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Restart<\/h4>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434\u044b container.Stop \u0438 container.Start:<\/p>\n<details class=\"spoiler\">\n<summary>CmdRestart<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Wait<\/h4>\n<p>\u0416\u0434\u0435\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430, \u0432\u044b\u0437\u044b\u0432\u0430\u044f \u043c\u0435\u0442\u043e\u0434 container.Wait:<\/p>\n<details class=\"spoiler\">\n<summary>CmdWait<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ '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() &lt; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Kill<\/h4>\n<p>\u0423\u0431\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u043c container.Kill, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0431\u044b\u043b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d \u0432\u044b\u0448\u0435:<\/p>\n<details class=\"spoiler\">\n<summary>CmdKill<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ '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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Rm<\/h4>\n<p>\u0412\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 runtime.Destroy, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0440\u0430\u0437\u043c\u043e\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438 \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdRm<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Ps<\/h4>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0432\u0435\u0441\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432 \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u0438\u0445 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<details class=\"spoiler\">\n<summary>CmdPs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 &amp;&amp; !*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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Diff<\/h4>\n<p>\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 diff \u043c\u0435\u0436\u0434\u0443 \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0438 \u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0438\u0437 \u043c\u0435\u0442\u043e\u0434\u0430 container.Changes:<\/p>\n<details class=\"spoiler\">\n<summary>CmdDiff<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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) }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>Changes \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0438 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432\u0441\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u043f\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439, \u0435\u0435 \u043a\u043e\u0434 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 changes.go:<\/p>\n<details class=\"spoiler\">\n<summary>changes.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"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 &amp;&amp; !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() &amp;&amp; f.IsDir() { if f.Size() == stat.Size() &amp;&amp; f.Mode() == stat.Mode() &amp;&amp; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043e\u0431\u0445\u043e\u0434\u0438\u0442 \u0432\u0441\u0435 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0432 rw, \u0435\u0441\u043b\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0438\u043c\u0435\u0435\u0442 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 .wh., \u0442\u043e \u044d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u043e\u043d \u0431\u044b\u043b \u0443\u0434\u0430\u043b\u0435\u043d (\u0442\u0430\u043a Aufs \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435). \u0412 \u0438\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u0445\u043e\u0434 \u0441\u043b\u043e\u0435\u0432 \u043e\u0431\u0440\u0430\u0437\u0430 \u0432 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043d\u0430\u0439\u0442\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0432 \u043d\u0438\u0445. \u0415\u0441\u043b\u0438 \u043e\u043d \u043d\u0435 \u0431\u044b\u043b \u043d\u0430\u0439\u0434\u0435\u043d \u043d\u0438 \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0441\u043b\u043e\u0435\u0432, \u0437\u043d\u0430\u0447\u0438\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u0441\u043e\u0437\u0434\u0430\u043d.\u00a0 \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0432 \u0441\u043b\u043e\u044f\u0445, \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u0447\u0438\u0442\u0430\u0435\u0442, \u0447\u0442\u043e \u044d\u0442\u043e \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f. \u041a\u0430\u0436\u0434\u043e\u0435 \u0438\u0437 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u0431\u0443\u043a\u0432\u043e\u0439 (D A C).<\/p>\n<h3>\u041e\u0431\u0449\u0438\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b<\/h3>\n<h4>Info<\/h4>\n<p>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u0432\u0435\u0440\u0441\u0438\u044e, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0437\u043e\u0432:<\/p>\n<details class=\"spoiler\">\n<summary>CmdInfo<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ '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 } <\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Inspect<\/h4>\n<p>\u041f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0438\u043b\u0438 \u043e\u0431\u0440\u0430\u0437\u0430 \u043f\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u0438\u043c\u0435\u043d\u0438, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0435\u0435 \u0432 json \u0444\u043e\u0440\u043c\u0430\u0442\u0435, \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u043e\u0442\u0441\u0442\u0443\u043f\u044b \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>CmdInspect<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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 &amp;&amp; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Logs<\/h4>\n<p>\u0427\u0438\u0442\u0430\u0435\u0442 \u0438 \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0432 stdout, stderr \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043b\u043e\u0433 \u0444\u0430\u0439\u043b\u043e\u0432 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdLogs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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)) }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Attach<\/h4>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043a \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0443 \u0438 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u0442\u043e\u043a\u0438 stdin, stdout, stderr:<\/p>\n<details class=\"spoiler\">\n<summary>CmdAttach<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Port<\/h4>\n<p>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0438\u0437 \u0445\u0435\u0448 \u0442\u0430\u0431\u043b\u0438\u0446\u044b PortMapping, \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u043e\u0439 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435. \u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u043e \u043d\u0438\u0436\u0435, \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u043e \u0441\u0435\u0442\u0435\u0432\u043e\u043c\u0443 \u0441\u0442\u0435\u043a\u0443:<\/p>\n<details class=\"spoiler\">\n<summary>CmdPort<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u0421\u0435\u0442\u0435\u0432\u043e\u0439 \u0441\u0442\u0435\u043a<\/h3>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u0434 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 network.go. \u041c\u044b \u043d\u0430\u0447\u043d\u0435\u043c \u0440\u0430\u0437\u0431\u043e\u0440 \u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 newNetworkManager, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 NetworkManager:<\/p>\n<details class=\"spoiler\">\n<summary>network.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"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 := &amp;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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>newNetworkManager \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 getIfaceAddr \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 net.IPNet \u0441 \u0430\u0434\u0440\u0435\u0441\u043e\u043c, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u043c \u043d\u0430 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 networkBridgeIface (lxcbr0). \u0414\u0430\u043d\u043d\u044b\u0439 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u043c\u043e\u0441\u0442 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 lxc. \u0415\u0441\u043b\u0438 \u043d\u0430 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e ip \u0430\u0434\u0440\u0435\u0441\u043e\u0432, \u0442\u043e \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u044b\u0439 \u0438\u0437 \u043d\u0438\u0445. \u0414\u0430\u043b\u0435\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044f newIPAllocator \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 IPAllocator, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c,\u00a0 \u0430\u043b\u043b\u043e\u0446\u0438\u0440\u0443\u0435\u0442 \u0430\u0434\u0440\u0435\u0441\u0430 \u0434\u043b\u044f \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432 \u0432 \u043f\u043e\u0434\u0441\u0435\u0442\u0438 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u0439 lxcbr0.<\/p>\n<details class=\"spoiler\">\n<summary>IPAllocator<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ 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 := &amp;IPAllocator{ network: network, } if err := alloc.populate(); err != nil { return nil, err } return alloc, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041c\u0435\u0442\u043e\u0434 populate \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u043f\u043e\u0434\u0441\u0435\u0442\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 ip \u0438 \u043c\u0430\u0441\u043a\u0438, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043a\u0430\u043d\u0430\u043b \u043e\u0447\u0435\u0440\u0435\u0434\u0438. \u0414\u043b\u044f \u0430\u043b\u043b\u043e\u043a\u0430\u0446\u0438\u0438 \u043d\u043e\u0432\u044b\u0445 \u0430\u0434\u0440\u0435\u0441\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442. \u0425\u0435\u043b\u043f\u0435\u0440 \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f networkRange, networkSize, intToIp, ipToInt \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b \u0432 \u0442\u043e\u043c \u0436\u0435 \u0444\u0430\u0439\u043b\u0435:<\/p>\n<details class=\"spoiler\">\n<summary>alloc.populate<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 &lt; 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 &lt;- 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 &lt; 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, &amp;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(&amp;buf, binary.BigEndian, &amp;n); err != nil { return net.IP{}, err } ip := net.IPv4(0, 0, 0, 0).To4() for i := 0; i &lt; 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 &lt; net.IPv4len; i++ { m[i] = ^mask[i] } buf := bytes.NewBuffer(m) var n int32 if err := binary.Read(buf, binary.BigEndian, &amp;n); err != nil { return 0, err } return n + 1, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0410\u043b\u043b\u043e\u043a\u0430\u0446\u0438\u044f \u043f\u043e\u0440\u0442\u043e\u0432 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0430 \u043d\u0430 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e\u043c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435 \u0441 \u043a\u0430\u043d\u0430\u043b\u043e\u043c \u043e\u0447\u0435\u0440\u0435\u0434\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>PortAllocator<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ 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 &lt; end; port++ { alloc.ports &lt;- port } }  func (alloc *PortAllocator) Acquire() (int, error) { select { case port := &lt;-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 &lt;- port: return nil default: return errors.New(\"Too many ports have been released\") } return nil }  func newPortAllocator(start, end int) (*PortAllocator, error) { allocator := &amp;PortAllocator{} allocator.populate(start, end) return allocator, nil }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u043b\u044f \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430 \u043f\u043e\u0440\u0442\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f \u0443\u0442\u0438\u043b\u0438\u0442\u0430 iptables:<\/p>\n<details class=\"spoiler\">\n<summary>iptables<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ 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 := &amp;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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f newPortMapper \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 PortMapper, \u0432 \u0445\u0435\u0448 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c\u0441\u044f \u0432\u0441\u0435 \u043f\u0440\u043e\u0431\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u0440\u0442\u044b. \u0412 \u043c\u0435\u0442\u043e\u0434\u0430\u0445 setup \u0438 cleanup \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0438 \u0443\u0434\u0430\u043b\u044f\u044e\u0442\u0441\u044f NAT \u0446\u0435\u043f\u043e\u0447\u043a\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>PortMapper<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412\u0435\u0441\u044c \u043c\u0430\u043f\u043f\u0438\u043d\u0433 \u043f\u043e\u0440\u0442\u043e\u0432 \u043e\u0441\u043d\u043e\u0432\u0430\u043d \u043d\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043f\u0440\u0430\u0432\u0438\u043b \u0432 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0432\u044b\u0437\u043e\u0432\u0430 iptables:<\/p>\n<details class=\"spoiler\">\n<summary>iptablesForward<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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))) }<\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0430 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430<\/h3>\n<p>\u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0437\u0430 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u0442\u0438\u043b\u0438\u0442\u0443 debootstrap, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043a\u0430\u0447\u0430\u0435\u0442 \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442 \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0434\u0438\u0441\u0442\u0440\u0438\u0431\u0443\u0442\u0438\u0432\u0430 Ubuntu.<\/p>\n<pre><code class=\"bash\">cd \/tmp &amp;&amp; sudo debootstrap trusty trusty &amp;&amp; sudo chroot \/tmp\/trusty\/ apt-get install iptables -y<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0438\u043c\u043b\u0438\u043d\u043a \u043d\u0430 \/etc\/resolv.conf \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0443\u0441\u0442\u043e\u0439 \u0444\u0430\u0439\u043b \u0434\u043b\u044f \u0442\u043e\u0447\u043a\u0438 \u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0442\u0430\u043a \u043a\u0430\u043a \u0431\u0435\u0437 \u044d\u0442\u043e\u0433\u043e lxc \u0432\u044b\u0434\u0430\u0441\u0442 \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<pre><code class=\"bash\">sudo rm \/tmp\/trusty\/etc\/resolv.conf &amp;&amp; sudo touch \/tmp\/trusty\/etc\/resolv.conf<\/code><\/pre>\n<p>\u0412\u043c\u0435\u0441\u0442\u043e \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0438 systemd-resolved, \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0433\u0443\u0433\u043b\u043e\u0432\u044b\u0439 dns:<\/p>\n<pre><code class=\"bash\">sudo rm \/etc\/resolv.conf &amp;&amp; echo \"nameserver 8.8.8.8\" | sudo tee \/etc\/resolv.conf<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432\u0435\u0440\u043d\u0435\u043c\u0441\u044f \u0432 \u043f\u0430\u043f\u043a\u0443 \u0441 \u0434\u043e\u043a\u0435\u0440\u043e\u043c \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u0440\u0430\u0437.<\/p>\n<pre><code class=\"bash\">cd \/home\/vagrant\/engine\/docker &amp;&amp; sudo tar -C \/tmp\/trusty -c . | sudo .\/docker import - ubuntu docker import - ubuntu 10834b53c26eb579<\/code><\/pre>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0447\u0442\u043e \u043e\u0431\u0440\u0430\u0437 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043b\u0441\u044f:<\/p>\n<pre><code class=\"bash\">vagrant@ubuntu-focal:~\/engine\/docker$ sudo .\/docker images docker images REPOSITORY          TAG                 ID                  CREATED             PARENT ubuntu              latest              10834b53c26eb579    12 seconds ago<\/code><\/pre>\n<p>\u0412 \u0432\u0438\u0434\u0443 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u043c \u0444\u0430\u0439\u043b\u0435 \u043d\u043e\u0432\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u0439 LXC \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0430\u0442\u0447:<\/p>\n<details class=\"spoiler\">\n<summary>docker.patch<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"diff\">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 <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0421\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u043c \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440:<\/p>\n<pre><code class=\"bash\">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<\/code><\/pre>\n<h3>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h3>\n<p>\u042d\u0442\u043e \u0431\u044b\u043b \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u043b\u043d\u044b\u0439 \u043e\u0431\u0437\u043e\u0440 \u043a\u043e\u0434\u0430 \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 Docker. \u0421 \u0442\u0435\u0445 \u043f\u043e\u0440 \u043f\u0440\u043e\u0448\u043b\u043e \u0443\u0436\u0435 \u043c\u043d\u043e\u0433\u043e \u043b\u0435\u0442 \u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u043a\u043e\u0434 Docker \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043e\u0442\u0435\u043d \u0444\u0430\u0439\u043b\u043e\u0432, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0431\u0435\u0437 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438. \u041d\u043e, \u043a\u0430\u043a \u043c\u044b \u0443\u0432\u0438\u0434\u0435\u043b\u0438, \u043d\u0430\u0447\u0438\u043d\u0430\u043b\u0441\u044f \u043e\u043d \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0438 \u043f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u0435 \u043a\u043e\u0434\u0430, \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u044f \u043f\u043e \u0432\u0435\u0440\u0441\u0438\u044f\u043c \u0432 git \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438. \u041b\u0438\u0447\u043d\u043e \u043c\u043d\u0435 \u0442\u0430\u043a\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043f\u043e\u043d\u044f\u0442\u044c \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f, \u043a\u0430\u043a \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u0438 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u043b\u0441\u044f \u0442\u043e\u0442 \u0438\u043b\u0438 \u0438\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b. \u0415\u0441\u043b\u0438 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u0432\u0440\u0435\u043c\u044f, \u0442\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e libcontainer, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0435\u0440\u0435\u0448\u0435\u043b Docker \u043f\u043e\u0441\u043b\u0435 LXC.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/575296\/\"> https:\/\/habr.com\/ru\/articles\/575296\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h2>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0414\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u043e\u0439 \u0432 \u0446\u0438\u043a\u043b\u0435 (<a href=\"https:\/\/habr.com\/ru\/post\/573828\/\" rel=\"noopener noreferrer nofollow\">1<\/a>, <a href=\"https:\/\/habr.com\/ru\/post\/574750\/\" rel=\"noopener noreferrer nofollow\">2<\/a>, <a href=\"https:\/\/habr.com\/ru\/post\/575256\/\" rel=\"noopener noreferrer nofollow\">3<\/a>), \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u043e\u043c \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044e \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 Docker \u0438 \u043f\u0440\u044f\u043c\u044b\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435\u043c <a href=\"https:\/\/habr.com\/ru\/post\/575256\/\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438<\/a>, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043f\u0440\u0435\u0436\u0434\u0435\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0432 \u0432\u0438\u0434\u0443 \u0437\u0430\u0432\u0438\u0441\u0430\u043d\u0438\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0430 \u0445\u0430\u0431\u0440\u0430. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043c \u0438\u0437\u0443\u0447\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u0440\u0435\u043b\u0438\u0437\u0430 Docker v0.1.0. \u0411\u0443\u0434\u0443\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u044b \u043e\u0441\u0442\u0430\u0432\u0448\u0438\u0435\u0441\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u043c\u0438, \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0441\u0442\u0435\u043a, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a \u043e\u0431\u0440\u0430\u0437\u0430.<\/p>\n<h3>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u043c\u0438<\/h3>\n<h4>Start<\/h4>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0430 container.Start, \u043a\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u044b\u043b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0432 \u043a\u043e\u043d\u0446\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>CmdStart<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Stop<\/h4>\n<p>\u041e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0430 container.Stop:<\/p>\n<details class=\"spoiler\">\n<summary>CmdStop<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 \u043d\u0430\u0447\u0430\u043b\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0443\u0442\u0438\u043b\u0438\u0442\u043e\u0439 lxc-kill, \u0435\u0441\u043b\u0438 \u043f\u043e \u043f\u0440\u043e\u0448\u0435\u0441\u0442\u0432\u0438\u0438 10 \u0441\u0435\u043a\u0443\u043d\u0434 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043d\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d, \u0442\u043e\u0433\u0434\u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 container.Kill, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0431\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441:<\/p>\n<details class=\"spoiler\">\n<summary>container.Kill<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Restart<\/h4>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434\u044b container.Stop \u0438 container.Start:<\/p>\n<details class=\"spoiler\">\n<summary>CmdRestart<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Wait<\/h4>\n<p>\u0416\u0434\u0435\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430, \u0432\u044b\u0437\u044b\u0432\u0430\u044f \u043c\u0435\u0442\u043e\u0434 container.Wait:<\/p>\n<details class=\"spoiler\">\n<summary>CmdWait<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ '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() &lt; 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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Kill<\/h4>\n<p>\u0423\u0431\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u043c container.Kill, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0431\u044b\u043b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d \u0432\u044b\u0448\u0435:<\/p>\n<details class=\"spoiler\">\n<summary>CmdKill<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">\/\/ '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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Rm<\/h4>\n<p>\u0412\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 runtime.Destroy, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0440\u0430\u0437\u043c\u043e\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u0444\u0430\u0439\u043b\u043e\u0432\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438 \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>CmdRm<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Ps<\/h4>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0432\u0435\u0441\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432 \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u0438\u0445 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<details class=\"spoiler\">\n<summary>CmdPs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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 &amp;&amp; !*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 }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>Diff<\/h4>\n<p>\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 diff \u043c\u0435\u0436\u0434\u0443 \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0438 \u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0438\u0437 \u043c\u0435\u0442\u043e\u0434\u0430 container.Changes:<\/p>\n<details class=\"spoiler\">\n<summary>CmdDiff<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">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() &lt; 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) }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>Changes \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0438 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432\u0441\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u043f\u043e \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439, \u0435\u0435 \u043a\u043e\u0434 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 changes.go:<\/p>\n<details class=\"spoiler\">\n<summary>changes.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"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 :=<\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-400851","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/400851","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=400851"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/400851\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=400851"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=400851"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=400851"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}