docker run 过程解析

2023-05-16

以运行 busybox容器为线索,跟踪docekr启动容器的过程,

vito@caas:~$ docker run -it busybox /bin/sh

这里写图片描述

1、docker 客户端解析

Docker client主要的工作是通过解析用户所提供的一系列参数后,docker的入口函数main,在入口函数中处理传入的参数,并把参数转化为cobra的command类型,最后通过cobra调用相应的方法。
/docker/api/client/container/run.go

// NewRunCommand create a new `docker run` command
func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command {
    var opts runOptions
    var copts *runconfigopts.ContainerOptions

    cmd := &cobra.Command{
        Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
        Short: "Run a command in a new container",
        Args:  cli.RequiresMinArgs(1),
        RunE: func(cmd *cobra.Command, args []string) error {
            copts.Image = args[0]
            if len(args) > 1 {
                copts.Args = args[1:]
            }
            //run 命令对应的客户端方法
            return runRun(dockerCli, cmd.Flags(), &opts, copts)
        },
    }

客户端中预准备的步骤:

func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error {
    stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
    //实例化一个客户端,用于发送run命令请求
    client := dockerCli.Client()

    // TODO: pass this as an argument
    cmdPath := "run"

    //定义错误信息
    var (
        flAttach                              *opttypes.ListOpts
        ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
        ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
        ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
    )

    //解析命令和参数
    //config :主要是与主机无关的配置数据,比如hostname,user;默认omitempty设置,如果为空置则忽略字段。
    //hostConfig : 与主机相关的配置。
    //networkingConfig :网络相关的配置。
    config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
    fmt.Println('-----------print config:/n',config, hostConfig, networkingConfig)

分析:
config文件的结构定义在container包中,具体路径如下:
docker/vendor/github.com/docker/engine-api/types/container/config.go
docker/vendor/github.com/docker/engine-api/types/container/host_config.go

  1. container.Config :包含着容器的配置数据,主要是与主机无关的配置数据,比如hostname,user;默认omitempty设置,如果为空置则忽略字段。
type Config struct {
    Hostname   string // Hostname 容器内的主机名
    Domainname string // Domainname 域名服务器名称
    //容器内用户名,用于运行CMD命令
    User string // User that will run the command(s) inside the container

    AttachStdin  bool                  // Attach the standard input, makes possible user interaction
    AttachStdout bool                  // Attach the standard output 是否附加标准输出
    AttachStderr bool                  // Attach the standard error
    ExposedPorts map[nat.Port]struct{} `json:",omitempty"` // List of exposed ports 容器内暴露的端口号
    Tty          bool                  // Attach standard streams to a tty, including stdin if it is not closed.是否分配一个伪终端
    OpenStdin    bool                  // Open stdin 在没有附加标准输入是,是否依然打开标准输入
    //如该为真,用户关闭标准输入,容器的标准输入关闭
    StdinOnce       bool                // If true, close stdin after the 1 attached client disconnects.
    Env             []string            // List of environment variable to set in the container 环境变量
    Cmd             strslice.StrSlice   // Command to run when starting the container 容器内运行的指令
    Healthcheck     *HealthConfig       `json:",omitempty"` // Healthcheck describes how to check the container is healthy
    ArgsEscaped     bool                `json:",omitempty"` // True if command is already escaped (Windows specific)
    Image           string              // Name of the image as it was passed by the operator (eg. could be symbolic) 镜像名称
    Volumes         map[string]struct{} // List of volumes (mounts) used for the container 挂载目录
    WorkingDir      string              // Current directory (PWD) in the command will be launched 容器内,进程指定的工作目录
    Entrypoint      strslice.StrSlice   // Entrypoint to run when starting the container 覆盖镜像中默认的entrypoint
    NetworkDisabled bool                `json:",omitempty"` // Is network disabled 是否关闭容器网络功能
    MacAddress      string              `json:",omitempty"` // Mac Address of the container MAC地址
    //在dockerfile中定义的,指定的命令在容器构建时不执行,而是在镜像构建完成之后被出发执行
    OnBuild     []string          // ONBUILD metadata that were defined on the image Dockerfile
    Labels      map[string]string // List of labels set to this container 容器中的labels
    StopSignal  string            `json:",omitempty"` // Signal to stop a container
    StopTimeout *int              `json:",omitempty"` // Timeout (in seconds) to stop a container
    Shell       strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT
}
  1. container.HostConfig: 与主机相关的配置信息。
type HostConfig struct {
    // Applicable to all platforms
    //从宿主机上绑定到容器的volume
    Binds []string // List of volume bindings for this container
    //用于写入容器ID的文件名
    ContainerIDFile string // File (path) where the containerId is written
    //配置容器的日志
    LogConfig LogConfig // Configuration of the logs for this container
    //容器的网络模式
    NetworkMode NetworkMode // Network mode to use for the container
    //容器绑定到宿主及的端口
    PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
    //容器退出是采取的重启策略
    RestartPolicy RestartPolicy // Restart policy to be used for the container
    //容器退出时是否自动移除容器
    AutoRemove   bool     // Automatically remove container when it exits
    VolumeDriver string   // Name of the volume driver used to mount volumes
    VolumesFrom  []string // List of volumes to take from other container

    // Applicable to UNIX platforms
    CapAdd          strslice.StrSlice // List of kernel capabilities to add to the container
    CapDrop         strslice.StrSlice // List of kernel capabilities to remove from the container
    DNS             []string          `json:"Dns"`        // List of DNS server to lookup
    DNSOptions      []string          `json:"DnsOptions"` // List of DNSOption to look for
    DNSSearch       []string          `json:"DnsSearch"`  // List of DNSSearch to look for
    ExtraHosts      []string          // List of extra hosts
    GroupAdd        []string          // List of additional groups that the container process will run as
    IpcMode         IpcMode           // IPC namespace to use for the container
    Cgroup          CgroupSpec        // Cgroup to use for the container
    Links           []string          // List of links (in the name:alias form)
    OomScoreAdj     int               // Container preference for OOM-killing
    PidMode         PidMode           // PID namespace to use for the container
    Privileged      bool              // Is the container in privileged mode
    PublishAllPorts bool              // Should docker publish all exposed port for the container
    ReadonlyRootfs  bool              // Is the container root filesystem in read-only
    SecurityOpt     []string          // List of string values to customize labels for MLS systems, such as SELinux.
    StorageOpt      map[string]string `json:",omitempty"` // Storage driver options per container.
    Tmpfs           map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container
    UTSMode         UTSMode           // UTS namespace to use for the container
    UsernsMode      UsernsMode        // The user namespace to use for the container
    ShmSize         int64             // Total shm memory usage
    Sysctls         map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container
    Runtime         string            `json:",omitempty"` // Runtime to use with this container

    // Applicable to Windows
    ConsoleSize [2]int    // Initial console size
    Isolation   Isolation // Isolation technology of the container (eg default, hyperv)

    // Contains container's resources (cgroups, ulimits)
    Resources
}
  1. networktypes.NetworkingConfig:网络相关的配置。
  2. 其他的一些配置校验
    其中最关键的两个步骤:create、start
    创建容器的函数:
    //创建容器
    createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
    if err != nil {
        reportError(stderr, cmdPath, err.Error(), true)
        return runStartContainerErr(err)
    }

运行容器的函数:

    //启动容器
    if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
        // If we have holdHijackedConnection, we should notify
        // holdHijackedConnection we are going to exit and wait
        // to avoid the terminal are not restored.
        if attach {
            cancelFun()
            <-errCh
        }

通过上面的分析我们可以看出,docker run 命令主要执行了两个操作,一个是docker create、另一个是docker start,
查看route,找到相应的方法对应的方法实现:
container 相关的 route地址:docker/api/server/router/container/container.go

func (r *containerRouter) initRoutes() {
    r.routes = []router.Route{
        // HEAD
        router.NewHeadRoute("/containers/{name:.*}/archive", r.headContainersArchive),
        // GET
        router.NewGetRoute("/containers/json", r.getContainersJSON),
        router.NewGetRoute("/containers/{name:.*}/export", r.getContainersExport),
        router.NewGetRoute("/containers/{name:.*}/changes", r.getContainersChanges),
        router.NewGetRoute("/containers/{name:.*}/json", r.getContainersByName),
        router.NewGetRoute("/containers/{name:.*}/top", r.getContainersTop),
        router.Cancellable(router.NewGetRoute("/containers/{name:.*}/logs", r.getContainersLogs)),
        router.Cancellable(router.NewGetRoute("/containers/{name:.*}/stats", r.getContainersStats)),
        router.NewGetRoute("/containers/{name:.*}/attach/ws", r.wsContainersAttach),
        router.NewGetRoute("/exec/{id:.*}/json", r.getExecByID),
        router.NewGetRoute("/containers/{name:.*}/archive", r.getContainersArchive),
        // POST
        router.NewPostRoute("/containers/create", r.postContainersCreate),
        router.NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),
        router.NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),
        router.NewPostRoute("/containers/{name:.*}/unpause", r.postContainersUnpause),
        router.NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart),
        router.NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
        router.NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop),
        router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
        router.NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
        router.NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
        router.NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy), // Deprecated since 1.8, Errors out since 1.12
        router.NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
        router.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
        router.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
        router.NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
        router.NewPostRoute("/containers/{name:.*}/update", r.postContainerUpdate),
        // PUT
        router.NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
        // DELETE
        router.NewDeleteRoute("/containers/{name:.*}", r.deleteContainers),
    }

其中
create对应的handler:postContainersCreate
start对应的handler:postContainersStart
下面对两个过程逐个分析:

2、docker create 分析

这阶段Docker daemon的主要工作是对client提交的POST表单进行分析整理,获得config配置和hostconfig配置。然后daemon会调用daemon.newContainer函数来创建一个基本的container对象,并将config和hostconfig中保存的信息填写到container对象中。当然此时的container对象并不是一个具体的物理容器,它其中保存着所有用户指定的参数和Docker生成的一些默认的配置信息。最后,Docker会将container对象进行JSON编码,然后保存到其对应的状态文件中。

首先我们分析postContainersCreate方法,如下:

func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    if err := httputils.ParseForm(r); err != nil {
        return err
    }
    if err := httputils.CheckForJSON(r); err != nil {
        return err
    }
    name := r.Form.Get("name")
    config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
    //服务器端接收到的配置信息
    fmt.Println("服务器端接收到的配置信息 config:\n", config, "\n---------------hostConfig\n", hostConfig, "\n------------networkingConfig:\n", networkingConfig)
    if err != nil {
        return err
    }
    version := httputils.VersionFromContext(ctx)
    adjustCPUShares := versions.LessThan(version, "1.19")
    validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")

    //对应的接口ContainerCreate,在daemon文件夹中查找该接口的实现
    ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
        Name:             name,
        Config:           config,
        HostConfig:       hostConfig,
        NetworkingConfig: networkingConfig,
        AdjustCPUShares:  adjustCPUShares,
    }, validateHostname)
    if err != nil {
        return err
    }

    return httputils.WriteJSON(w, http.StatusCreated, ccr)
}

分析代码可知,每一类操作都是定义了接口,方法调用的时候直接使用的接口调用,然后在daemon包中再具体实现接口,container的接口定义文件:/docker/api/server/router/container/backend.go

type stateBackend interface {
    ContainerCreate(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error)
    ContainerKill(name string, sig uint64) error
    ContainerPause(name string) error
    ContainerRename(oldName, newName string) error
    ContainerResize(name string, height, width int) error
    ContainerRestart(name string, seconds int) error
    ContainerRm(name string, config *types.ContainerRmConfig) error
    ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool) error
    ContainerStop(name string, seconds int) error
    ContainerUnpause(name string) error
    ContainerUpdate(name string, hostConfig *container.HostConfig, validateHostname bool) ([]string, error)
    ContainerWait(name string, timeout time.Duration) (int, error)
}

在daemon包中找到ContainerCreate该接口的实现:
源代码路径:/docker/daemon/create.go

func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error) {
    return daemon.containerCreate(params, false, validateHostname)
}

我们分析containerCreate方法,经过几个配置确认工作,开始创建容器,创建容器方法中经过一系列的操作完成了容器的创建过程,总结下来,主要做的事情有以下几步:

  1. 获取镜像ID GetImage
  2. 合并容器配置
  3. 合并日志配置
  4. 创建容器对象 newContainer
  5. 设置安全选项
  6. 设置容器读写层
  7. 创建文件夹保存容器配置信息
  8. 容器网络配置
  9. 保存到硬盘
  10. 注册到daemon

首先是在创建容器以前的确认工作分别,对配置文件中定义的容器工作目录、容器端口、容器hostname、容器网络等信息进行了确认,如该没问题开始下面的创建工作:
下面逐个分析每一步做了那些工作:
1、 获取镜像ID GetImage
通过镜像的名字,获取完整的镜像ID,然后使用该镜像ID获得完整的镜像结构体,用于以后创建容器

    //通过image name 获取 image id
    if params.Config.Image != "" {
        img, err = daemon.GetImage(params.Config.Image)
        if err != nil {
            return nil, err
        }
        imgID = img.ID()
    }

2. 将用户指定的config参数与镜像中json文件中的config参数合并并验证
方法如下:

    if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
        return nil, err
    }

主要做的工作是,初始化容器的一些配置,比如环境变量,主机名称等,如果在启动的时候传入的参数包含这些配置,例如在启动的时候设置了环境变量,在这个方法中会合并启动命令中的参数和镜像里面的参数,把镜像中的默认配置写入到配置文件中,对比写入前后的数据,以下是用命令docker run -e aaa=bb -h hosttest busybox 命令的前后对比结果:

-----------------合并配置前params:
 &{hosttest   false false false map[] false false false [aaa=bbb] [] <nil> false busybox map[]  [] false  [] map[]  <nil> []} 
 &{[]  { map[]} default map[] {no 0} false  [] [] [] [] [] [] [] []   [] 0  false false false [] map[] map[]   67108864 map[] runc [0 0]  {0 0  0 [] [] [] [] [] 0 0   [] 0 0 0 0 0xc421096e60 0xc421096e6a 0 [] 0 0 0 0}} 
 &{map[]}
-----------------合并配置后params:
 &{hosttest   false false false map[] false false false [aaa=bbb PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin] [sh] <nil> true busybox map[]  [] false  [] map[]  <nil> []} 
 &{[]  {json-file map[]} default map[] {no 0} false  [] [] [] [] [] [] [] []   [] 0  false false false [] map[] map[]   67108864 map[] runc [0 0]  {0 0  0 [] [] [] [] [] 0 0   [] 0 0 0 0 0xc421096e60 0xc421096e6a 0 [] 0 0 0 0}} 
 &{map[]}

3. 将用户指定的log config参数与镜像中json文件中的config参数合并并验证 ??
如该容器的日志选项没有配置,该方法将会把daemon 中的默认日志配置合并到容器中,具体细节有待进一步深入分析

4. 创建新的container对象

初始化一个容器的结构体newContainer,包含基本的信息,比如容器名称、配置文件结构体、对应的镜像ID等:

    if container, err = daemon.newContainer(params.Name, params.Config, imgID, managed); err != nil {
        return nil, err
    }

同时设置一些安全选项和存储选项的配置到container结构体,setSecurityOptions、StorageOpt

5. 设置安全选项??

6. 创建容器的读写层:

    if err := daemon.setRWLayer(container); err != nil {
        return nil, err
    }

7. 创建文件夹,用于保存容器的配置信息
在/var/lib/docker/containers/id下:

获取rootUID, rootGID,并创建容器文件夹保存容器的配置信息:

    //创建文件夹,用于保存容器的配置信息,在/var/lib/docker/containers/id下,并赋予容器进程的读写权限
    if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil {
        return nil, err
    }

    //把配置文件保存到磁盘
    if err := daemon.setHostConfig(container, params.HostConfig); err != nil {
        return nil, err
    }

8. 更新容器网络配置,并保存到硬盘

    if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
        return nil, err
    }

9. 最后注册该容器到docker daemon
把容器的id加入到docker daemon 中,容器创建完成。

待研究问题:docker Security :/media/vito/code/golang/src/docker/daemon/daemon_unix.go

3、docker start 分析

还是从docker daemon 收到请求的route路由开始梳理:
/docker/api/server/router/container/container.go文件下,找到start容器的API路由,

router.NewPostRoute(“/exec/{name:.*}/start”, r.postContainerExecStart)

我们从postContainerExecStart开始追踪,我们从postContainerExecStart中能够找到start容器对应的接口是ContainerExecStart,下一步是在daemon中找到该接口的实现。

    if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname); err != nil {
        return err
    }

找到ContainerStart接口的daemon包实现:/docker/daemon/start.go

func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, validateHostname bool) error {
    container, err := daemon.GetContainer(name)
    if err != nil {
        return err
    }

然后检查一些配置和状态,例如是否运行、是否暂停等,确认hostconfig与当前系统配置是否一致。最后执行函数containerStart,开始启动容器,具体函数如下:

func (daemon *Daemon) containerStart(container *container.Container) (err error)

下面我们仔细分析容器启动过程,主要由两步来完成,第一步是创建container 实例;第二部是启动容器,我们逐步分析:
第一步,创建container实例过程:

1. 实例化容器对象
因为前面已经创建完成了container,所有这里使用查找方法查找container,查找方式可以是完整的container ID,完整的container 名称或者ID的前缀。返回一个container 对象。

    //1、通过container名称或者完整ID,或者ID前缀获取一个container实例
    container, err := daemon.GetContainer(name)
    if err != nil {
        return err
    }

2. 判断暂停状态

    //2、如果暂停的容器不能启动,先unpause再启动
    if container.IsPaused() {
        return fmt.Errorf("Cannot start a paused container, try unpause instead.")
    }

3. 判断是否运行

    //3、如果是运行的容器不用启动
    if container.IsRunning() {
        err := fmt.Errorf("Container already started")
        return errors.NewErrorWithStatusCode(err, http.StatusNotModified)
    }

4. 向后兼容设置
主要针对非windows系统

5. 确认hostconfig配置

    //5、确认hostconfig与当前系统是否一致
    if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false, validateHostname); err != nil {
        return err
    }
  1. 调整旧版容器设置
    //为老的容器调整的函数,老的容器在创建阶段没有机会调用的函数
    if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil {
        return err
    }
    linuxMinCPUShares = 2
    linuxMaxCPUShares = 262144
    platformSupported = true

主要是cpu、内存限制的校验和设置

7. 创建结束,返回container对象

8. 开始进入启动容器函数

  1. 容器对象加锁
    //1、container 对象加锁
    container.Lock()
    defer container.Unlock()
  1. 状态校验,如该已经运行,直接返回
    //2、容器状态判断
    if container.Running {
        return nil
    }
  1. 挂载读写层
    返回挂载的路径,一般是:
    设置container.BaseFS为该路径
    dir, err := container.RWLayer.Mount(container.GetMountLabel())
    if err != nil {
        return err
    }
  1. 初始化网络
    首先验证网络模型是否是正确的值,然后设置网络模式,保存到container对象中,最后设置hostname

  2. 创建RunC的spec对象

  3. containerd 调用runc

  4. 进入RunC启动容器

4、runc过程解析

这里写图片描述
看runc的源代码:

//初始化了一个APP,设置了APP都有那些command, 然后执行app.Run 看一下app.Run()函数:
func main() {
    app := cli.NewApp()
    app.Name = "runc"
    app.Usage = usage
...
...
    app.Commands = []cli.Command{
        checkpointCommand,
        createCommand,
        deleteCommand,
        eventsCommand,
        execCommand,
        initCommand,
        killCommand,
        listCommand,
        pauseCommand,
        psCommand,
        restoreCommand,
        resumeCommand,
        runCommand,
        specCommand,
        startCommand,
        stateCommand,
        updateCommand,
    }

接下来看一下app.Run()函数:
/runc/vendor/github.com/urfave/cli/app.go

func (a *App) Run(arguments []string) (err error) {
    a.Setup()

    // parse flags
    set := flagSet(a.Name, a.Flags)
    set.SetOutput(ioutil.Discard)
    err = set.Parse(arguments[1:])
    nerr := normalizeFlags(a.Flags, set)
    context := NewContext(a, set, nil)
    ...
    ...
    args := context.Args()
    if args.Present() {
        name := args.First()
        c := a.Command(name)
        if c != nil {
            return c.Run(context)
        }
    }

下面 name=args.First()获取到的就是create、restore、run等。接下来调用HandleAction(a.Action, context) 会调用到 create对应的cli.command的Action函数,我们先看一下cli.command 的createCommand函数的定义
/runc/utils_linux.go

//启动容器
func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
    id := context.Args().First()
    if id == "" {
        return -1, errEmptyID
    }

    notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
    if notifySocket != nil {
        notifySocket.setupSpec(context, spec)
    }
    //createContainer创建了一个container的对象内附参数
    container, err := createContainer(context, id, spec)
    if err != nil {
        return -1, err
    }

最终在以下函数中运行容器

func (r *runner) run(config *specs.Process) (int, error) {
    if err := r.checkTerminal(config); err != nil {
        r.destroy()
        return -1, err
    }
    process, err := newProcess(*config)
    if err != nil {
        r.destroy()
        return -1, err
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

docker run 过程解析 的相关文章

随机推荐

  • 关于海康摄像头OSD字幕叠加(.NET/C#/Formwork)

    刚接触摄像头代码的编写 xff0c 这里记录一下吧 xff01 记录一下我挨打的过程 xff01 xff01 在摄像头里面添加字符串 xff0c 困扰了很久 xff0c 资料也看了很多 xff0c 海康官网的文档看了也不是很懂 xff0c
  • 进程管理——进程实体

    一 进程的重要性 操作系统的基本功能是为了管理底层硬件资源 xff0c 在没有配置操作系统之前 xff0c 资源只属于当前运行的程序 这时的计算机只能运行一个程序 xff0c 并且是一个程序接着一个程序的运行 当计算机运行某一个程序时 xf
  • Effective C++读书笔记--Item 1:从四个语言层次理解C++

    可以将C 43 43 理解成由四个子语言组成 xff1a C Object Oriented C 43 43 Template C 43 43 STL C xff1a 代码块 语句 数组 指针 内置数据类型 预处理器 Object Orie
  • 设计师建筑师太难了,既要学BIM、无人机,还要学GIS!

    我 xff0c 一个平平无奇的城市规划专业 xff08 建筑专业 路桥专业 xff09 大学生 xff0c 还有一年要毕业 xff0c 很担心工作以后受到社会的毒打 xff0c 遂问导师和学长 xff0c 我要自学点什么技能和软件 xff1
  • 无人机航测是选择固定翼还是多旋翼?

    无人机测绘通过无人机低空摄影获取高清晰影像数据生成三维点云与模型 xff0c 实现地理信息的快速获取 效率高 xff0c 成本低 xff0c 数据准确 xff0c 操作灵活 xff0c 可以满足测绘行业的不同需求 大大地节省了测绘人员野外测
  • HAL库学习——串口中断

    一 介绍 串口的传输方式包括 xff1a 轮询 中断DMA xff0c 在此要介绍的是关于HAL库底层串口接收中断流程的讲解 xff0c 包括串口错误的处理 xff0c 中断回调函数以及错误中断回调函数的执行 二 配置流程 首先使用STM3
  • 嵌入式操作系统FreeRTOS的原理与实现

    URL http www eefocus com sensorwireless blog 08 03 144457 c9bd6 html 摘要 FreeRTOS是一个源码公开的免费的嵌入式实时操作系统 xff0c 通过研究其内核可以更好地理
  • 吃惊!难道Java也受美国出口管制?

    今天 xff0c 去翻看了一下Oracle Jdk的许可协议 xff0c 竟然是受美国出口管制 原文是这么说的 xff1a EXPORT REGULATIONS You agree that U S export control laws
  • 自己写出strcat函数

    通过指针和字符数组的结合写出strcat xff08 字符串拼接 源码如下 效果图 include lt stdio h gt include lt string h gt int main void char a 20 char b 20
  • 根据ttf文件 获取汉字点阵数据

    文件列表 untitled3 pro QT 61 gui CONFIG 43 61 c 43 43 11 console CONFIG 61 app bundle The following define makes your compil
  • nmap基本使用方法

    nmap基本使用方法 1 nmap简单扫描 nmap默认发送一个ARP的PING数据包 xff0c 来探测目标主机1 10000范围内所开放的所有端口 命令语法 xff1a nmap lt target ip address gt 其中 x
  • ROS学习之自定义msg类型

    1 创建msg文件 cd catkin ws src my package mkdir msg echo 34 string first name string last name uint8 age uint32 score 34 gt
  • 无人驾驶传感器之GPS和IMU

    GPS精度 xff1a GPS是由美国国防部牵头研制和维护的 xff0c 不可避免的牵扯到军事方面的因素 最早期因为害怕别的国家利用高精度的定位对美国进行打击 xff0c 他们甚至故意加大明勇定位的误差 xff0c 导致当时民用精度只能达到
  • 一步一步学CMake 之 VSCode+CMakeLists 调试 C++ 工程

    目录 1 插件推荐 2 文件准备 3 开始调试 一步一步学 CMake 系列文章 1 插件推荐 CMake CMake tools 2 文件准备 新建文件夹 xff1a TEST 新建文件 xff1a CMakeLists txt 内容如下
  • 记录下:ubuntu14.04安装xinetd服务

    1 先查看电脑是否已经安装xinetd sudo etc init d xinetd status 执行如上命令如果没有提示未知服务的话 xff0c 说明已经安装 2 更新apt get 资源列表 sudo apt get update 3
  • VS中Git使用教程

    现在的VS都自带Git插件 xff0c 用起来很方便 xff0c 能将VsCode前端和VS后端一起提交 xff0c 缺点 xff1a Word文档和Excel表没法协同处理冲突 基本上的常用操作都已经涵盖在内了 xff0c 能够满足日常开
  • Office365 - “The action can‘t be completed because the file is open in Microsoft OneDrive.“错误的解决方案

    今天收到user在 move OneDrive folder到machine local drive时弹出error The action can 39 t be completed because the file is open in
  • [学习笔记-SLAM篇]视觉SLAM十四讲ch3

    一鼓作气哈 还学了一点latex编写技巧 xff0c 技能max 注 xff1a 1 xff09 学习视频 xff1a 高翔 视觉SLAM十四讲 视觉SLAM十四讲 第3讲3 1 理论部分3 2 实践部分 第3讲 3 1 理论部分 这一部分
  • docker 开发编译环境搭建

    参与docker开源社区 xff0c 成为docker项目的contributor xff0c 首先要搭建docker的开发编译环境 xff0c 下面是docker官网介绍的编译环境的搭建 xff0c 这里做个笔记 docker的编译环境准
  • docker run 过程解析

    以运行 busybox容器为线索 xff0c 跟踪docekr启动容器的过程 vito 64 caas docker run it busybox bin sh 1 docker 客户端解析 Docker client主要的工作是通过解析用