b2c信息网

您现在的位置是:首页 > 明日新闻 > 正文

明日新闻

导入docker源码(docker基础教程)

hacker2022-06-10 23:22:23明日新闻97
本文目录一览:1、怎样将Docker容器中的文件导入到主机

本文目录一览:

怎样将Docker容器中的文件导入到主机

从Docker容器内拷贝文件到主机上

docker cp :/file/path/within/container /host/path/target

从主机上拷贝文件到容器内

参考自:

1.用-v挂载主机数据卷到容器内

docker run -v /path/to/hostdir:/mnt $container

在容器内拷贝

cp /mnt/sourcefile /path/to/destfile

2.直接在主机上拷贝到容器物理存储系统

A. 获取容器名称或者id :

$ docker ps

B. 获取整个容器的id

$ docker inspect -f '{{.Id}}' 步骤A获取的名称或者id

C. 在主机上拷贝文件:

$ sudo cp path-file-host /var/lib/docker/aufs/mnt/FULL_CONTAINER_ID/PATH-NEW-FILE

或者

$ sudo cp path-file-host /var/lib/docker/devicemapper/mnt/123abc/rootfs/root

例子:

$ docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

d8e703d7e303 solidleon/ssh:latest /usr/sbin/sshd -D cranky_pare

$ docker inspect -f '{{.Id}}' cranky_pare

or

$ docker inspect -f '{{.Id}}' d8e703d7e303

d8e703d7e3039a6df6d01bd7fb58d1882e592a85059eb16c4b83cf91847f88e5

$ sudo cp file.txt /var/lib/docker/aufs/mnt/**d8e703d7e3039a6df6d01bd7fb58d1882e592a85059eb16c4b83cf91847f88e5

3.用输入输出符

docker run -i Ubuntu /bin/bash -c 'cat /path/to/container/file' /path/to/host/file/

或者

docker exec -it bash -c 'cat /path/to/container/file' /path/to/host/file/

如何进行Docker源码调试

经过研究docker的官方编译脚步,发现本地编译也很简单,只需要在docker源码的目录下执行如下命令即可:

./hack/make.sh binary

上面这条命令就只会生成docker的二进制文件,不过肯定不会这么顺利的,执行这个命令你就会发现错误。如果第一次执行报的错误应该是找不到相应的go依赖包。那么现在就开始解决第一个问题,go依赖包。

解决go依赖包最直接的方法就一个一个去github或者其他地方去下载到本地,但是这样做很麻烦,docker依赖的go语言包很多,然后依赖包可能又依赖其他包。这里有一个简单实用的办法,也是go语言管理项目的方便之处。通过go get命令来自动下载,例如发现报错的是docker某一个目录下的依赖包,那么可以如下执行:

go get -v ./src/github.com/docker/docker/...

这条命令执行以后整个docker目录下源文件依赖的包都会被自动下载。如果发现其他目录下源文件也报同样的错误,可以按照次方法解决。不过这里需要强调一点, 这些下载都是会下载最新的包,如果编译老的docker肯定会出问题 ,如果编译最新的docker代码肯定不会有问题,因为官方的编译是这种方式。

上面执行的命令都是建立在go语言环境建立成功的基础上,我安装的go遇到是1.3.3版本的,采用源码方式安装。安装在/export/servers/go下面,然后所有的go语言工程源码目录放在 /export/servers/gopath。然后配置环境变量在用户的根目录下的.bashrc文件里面如下:

export GOPATH=/export/servers/gopath

export GOROOT=/export/servers/go

export GOARCH=amd64

export GOOS=linux

如何从Docker Registry中导出镜像

一、目录结构

Registry的配置文件中可以指定registry的运行目录(实验用本地文件系统作为后端存储),registry会在这个目录中建立相应的目录结构,我在本地启动一个registry服务,然后只push一个centos镜像上去。镜像名称是localhost:5000/library/centos:latest,然后registry在本地创建了如图1所示的目录。

图 1 registry目录

为了显示方便,我只截取了64位ID的前一部分。可以看到,目录大体分为两个:一个是blobs,一个是repositories。blobs中主要存放数据文件,可以看出都是经过sha256计算后的ID。repositories目录中放镜像的描述信息,记录了一个镜像有哪些layer,tag对应的manifest文件,link文件是一个文本文件,内容是一个形如“sha256:cf34a09a90b54c…”的64位ID,这个ID对应在blob中的文件其实就是这个image的manifest文件。

二、Manifest文件

manifest文件描述了一个镜像的元信息,包括了layer的数据ID,layer的配置等,文件格式是json形式的文本文件。

docker镜像可以分为V1和V2,在1.9以后镜像格式有一些变化。为了向前兼容V1版本的docker,docekr

registryV2使用的manifest也对应地分为Schema1和Schema2,两者可以通过官方对于manifest的解释可以参考[1]和[2]。这里实验都是在schema1上做的。

Schema1主要包含如下信息:

name:image的仓库(repository)名,比如localhost:5000/library/centos:latest这个镜像的repository name是library/centos

tag:该镜像的tag

architecture:指该镜像的宿主机的操作系统架构,如“amd64”

fsLayers:该字段是一个数组,数组中的元素分别指明了各层对应的数据文件的sha256ID,数组的第1个就是镜像的最顶层,第2个是次顶层…以此类推,值得注意的是,不同层的fsLayer

ID

有可能一样,是因为有些层是空的,只有一些配置信息。当执行了一个不涉及文件操作的命令,这时候就会形成空fsLayer,空fsLayer计算出来的sha256ID也都是一样的了。镜像的一个layer,是由文件系统(比如新增的文件)fsLayer以及配置信息构成的,layer在docker的代码层面又被称为image,因为任意一个layer都可以作为顶层layer,被docker

image信息引用,从而成为一个image。所以需要区分fsLayer与layer。

history:该字段也是一个数组,是为了兼容v1而设置的,指明了每个layer的配置信息,数组第一项对应的是镜像的最顶层,与fslayer一起构成了一个layer。数组元素是一个json格式的map对象,key为“v1Compatibility”,值为一个字符串,该字符串就是layer的配置信息,可以直接用json.Unmrashal成为一个V1Image结构体(定义可以参考代码github.com/docker/docker/image/image.go

L31)

schemaVersion:该manifest的版本,一个int型,如 1。

三、Tar包形式的镜像

Docker中有个save和load命令。save命令可以将一个docker镜像导出,把这个镜像从最顶层到最底层的所有layer一起导出到一个tar包中,然后就可以随意拷贝、发送这个tar包到别的机器,最后可以用load命令把这个镜像重新加载进docker。

如果我们把一个镜像从registry里拿出来,按照save成的tar包格式来组织,然后使用load命令加载,这样就实现了不通过docker pull命令来下载镜像,可以根据这个原理做第三方镜像下载工具。

所以我们先来看一下镜像的tar包形式是什么样的,我使用save命令导出了centos镜像,解压后目录结构如图2所示:

图 2 镜像tar包解压后的目录

下面对各文件进行解释:

根目录下的repositories文件,描述了这个镜像的名字,tage,还有顶层layer的id

不同的文件夹代表了不同的layer。

json:layer的配置信息,如创建时间,执行命令等。

layer.tar:layer中包含的文件,如果是空layer,layer.tar解压后就是空的。

VERSION: 版本信息。

四、从registry导出镜像

我们对比tar包中的文件和registry中的文件,不难发现其中的对应关系,json、VERSION还有repositories文件都是可以从manifest中导出。

json文件其实就是之前提到的history字段中v1Compatibilitiy,不同的是manifest中的这个字段中有很多转义符,我们需要去掉这些转义符,方法是先Unmarshal成为一个V1Image结构体,然后在json.Marshal转回字符串就好。

layer.tar其实就是blobs中对应的data文件,直接复制出来然后改个名字就可以。

VERSION是manifest中的schemaVersion。

repositories文件内容很简单,格式是{“imageName”:{“tag”:”topLayerID”}},所以按照这个格式从manifest中找到对应的数据填进去就ok。

这些文件都准备好了以后,就可以准备打包成tar包了,直接使用linux中自带的tar命令,这里需要注意的一点是,应该使用“只打包不压缩”的选项。生成tar包后就可以直接使用docker load命令导入了。

我这么做了,是可以成功地导入一个镜像。但是发现存在一个问题:使用上述方式导入的镜像,每个layer的ID和我直接用docker

pull命令下载下的不一样,而且docker

pull得到的ID从未在manifest和registry中的任何地方出现过。而且不管我使用新的机器还是重新pull,得到的ID都是一样的。经过阅读docker的代码我才发现,layerID不是随机生成的,也不是manifest中写道的id,而是算出来的。下面就说一下计算过程。

我们最终需要的layerID在docker源码中叫做StrongID,StrongID是把一个byte数组做Hash后得到的,这个byte数组的生成需要三个对象:v1Compatibility,blobSum(manifest中的fsLayerID),parent(父layer的StrongID),数组生成方法参考image.go中的MakeImageConfig方法(docker

1.9),基本操作就是把一个json对象转成字节数组。因为有parent字段的存在,需要从最底层的layer开始计算,逐步迭代,最终的到top

layer的ID。最后要做的工作就是替换json文件中的id字段和parent字段成为新计算出来的ID即可。同样地,文件夹名也要做相应改变。

增量导入:如果本地已经存在某些layer的情况,我们只用打包新的layer即可,因为导入时候docker会检测这个layer是否存在,而且有parent信息来保证layer之间的关系。

怎样进入docker镜像导入文件

只能先使用以后的镜像创建容器,然后进入运行的容器后,进行相应的修改,在使用修改后的容器进行镜像的生成,这样,生成后的镜像与之前那个镜像的区别就是,你的修改内容。

如何编译Docker源码

本文根据docker官方给出的docker代码编译环境搭建指南做更深入的分析。官方给出的指导比较简单,但是由于国内的网络问题经常会编译失败,了解了编译步骤后,也可以结合自身遇到的网络问题进行“规避”。

docker的编译环境实际上是创建一个docker容器,在容器中对代码进行编译。 如果想快速的查看编译环境搭建指导,而不关注环境搭建的机制和细节,可以直接跳到最后一章“总结”。

前提

机器上已经安装了docker,因为编译环境是个docker容器,所以要事先有docker(daemon),后面会创建个编译环境容器,在容器里面编译代码。本文中使用物理机,物理机上运行着docker (daemon)。

机器(物理机)上安装了git 。 后续使用git下载docker源码

机器(物理机)上安装了make。

下载ubuntu 14.04的docker镜像

下载docker源码

git clone

会把代码下载到当前目录下,后面会把代码拷贝到容器中。

编译前分析

官方给的编译方法是make build 和 make binary等。下面先分析Makefile,看懂Makefile后,编译环境的准备流程就比较清楚了。

Makefile

在下载的docker源码中可以看到它的Makefile,Makefile中比较关键的几个参数:

DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)") DOCKER_MOUNT 表示创建容器时的mount参数。因为编译环境是一个容器,在后续的步骤中启动容器时使用DOCKER_MOUNT参数,会将物理机上的目录mount给容器容器,容器中该目录是编译生成docker二进制文件的目录。

DOCKER_FLAGS := docker run --rm -i --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) 这是后面创建docker容器时的命令行的一部分,其中包含了前面的DOCKER_MOUNT参数。

DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH)) 这是docker image参数,镜像的名字是docker-dev,以当前git中docker版本作为tag名。这个镜像是在make build一步做出来的。

DOCKER_RUN_DOCKER := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)" 创建docker容器的命令行,组合了前面的DOCKER_FLAGS 和 DOCKER_IMAGE 。 从命令行中可以看出,启动容器使用的参数有 --rm -i --privileged,使用了一些环境变量,还有使用了-v参数把物理机上目录mount给容器,在容器中编译好二进制文件后放到该目录中,在物理机上就能获得docker二进制文件。启动的的docker 容器镜像名字是docker-dev。下文会介绍docker-dev镜像是怎么来的。

由于官方给出的“构建编译环境”的方法是执行 make build,下面在Makefile中看到build分支是这样的:

make build时会调用 docker build -t "$(DOCKER_IMAGE)" . 去制作一个叫做DOCKER_IMAGE的镜像。

进行源码编译的方式是执行 make binary来编译代码,在Makefile中make binary的分支如下:

make binary除了进行 make build以外,会执行$(DOCKER_RUN_DOCKER),即上文提到的docker run命令行。由于执行过了build,会build出来docker-dev镜像,所以在docker run时直接使用前面build出来的镜像。docker run时的命令行参数是hack/make.sh binary。make binary的过程实际上是创建一个容器,在容器中执行hack/make.sh binary脚本。接下来会详细介绍make build和make binary所做的内容。

make build

根据官方的指导,先执行make build来搭建编译环境。上面分析了,make build实际上是制作了一个镜像,这个镜像里会包含编译代码所需的环境。下面来介绍下这个镜像。

Dockerfile

在和Makefile相同的目录下(源码的根目录),有Dockerfile。执行make build 相当于调用docker build,使用的就是该Dockerfile。Dockerfile中的几个主要步骤(有些步骤这里略过):

FROM ubuntu:14.04 使用ubuntu 14.04作为基础镜像;在宿主机上,要事先下载好ubuntu 14.04镜像。

安装一些编译需要的软件;

用git下载lvm2源码,并编译安装;

下载并安装GO 1.5.1;

安装GO相关的tools 可以做code coverage test 、 go lint等代码检查

安装registry和notary server;

安装docker-py 后面跑集成测试用的

将物理机的contrib/download-frozen-image.sh 脚本拷贝到镜像中/go/src/github.com/docker/docker/contrib/

运行contrib/download-frozen-image.sh 制作镜像 实际上这一步只是下载了3个镜像的tar文件。注意:docker build相当于创建一个临时的容器(在临时的容器中执行Dockerfile中的每一步,最后在保存成镜像),“运行contrib/download-frozen-image.sh 制作镜像”这个动作出现在Dockerfile中,相当于在docker build所创建的临时的容器中下载docker镜像,有docker-in-docker容器嵌套的概念。下一小节会对download-frozen-image.sh脚本做详细分析。

ENTRYPOINT ["hack/dind"] 做出来的镜像,使用它启动的容器可以自动运行源码目录中的hack/dind脚本。 dind这个脚本是a wrapper script which allows docker to be run inside a docker container 。后面的小节会对hack/dind脚本做详细的分析。

COPY . /go/src/github.com/docker/docker 把物理机上的docker源码文件打入到镜像中

download-frozen-image.sh脚本

上一小节里提到,在Dockerfile中,有一步会调用contrib/download-frozen-image.sh ,它主要作用是下载3个镜像的tar包,供后续docker load。在Dockerfile中的调用方式如下:

download-frozen-image.sh脚本中会依次解析参数,其中/docker-frozen-images作为base dir,后面下载的东西全放到这里。之后的3个参数是镜像,里面包含了镜像名(例如busybox)、镜像tag(例如latest)、镜像id(例如d7057cb020844f245031d27b76cb18af05db1cc3a96a29fa7777af75f5ac91a3),后面会在循环中依次下载这3个镜像的tar文件。

download-frozen-image.sh脚本中会通过curl从registry上获取如下信息:

token:获取token,后面curl获取的其他信息时都需要使用token。例如本例中 token='signature=9088f0552b1b147364e07bdd48857dd77c0d94ee,repository="library/busybox",access=read'

ancestryJson:把镜像相关联的历史层次的id也都获取到,因为每一层的tar都需要下载。本例中 ancestryJson='["d7057cb020844f245031d27b76cb18af05db1cc3a96a29fa7777af75f5ac91a3", "cfa753dfea5e68a24366dfba16e6edf573daa447abf65bc11619c1a98a3aff54"]'

这里可以看到这个镜像只有2层,两层的id这里都列了出来。 每个镜像包含的层数不同,例如。第三个镜像jess/unshare共有10层。

VERSION、json、tar: 每一层镜像id的目录下,都下载这3个文件,其中VERSION文件内容目前都是“1.0”,json文件是该层镜像的json文件,tar文件是该层镜像的真正内容,以.tar保存。

下载好的各层镜像目录结构如下:

$ls

$tree

hack/dind脚本

在Dockerfile中,ENTRYPOINT ["hack/dind"] ,表示在镜像启动后,运行该脚本,下面分析一下这个脚本的功能。

脚本在代码根目录下的hack目录中,作者对脚本的描述是 DinD: a wrapper script which allows docker to be run inside a docker container.

就是可以在docker容器中创建docker容器。它就做了一个事,那就是在容器中创建好cgroup目录,并把各个cgroup子系统mount上来。

为了方便理解,我们可以先看看物理机。在宿主机上如果创建docker容器,需要宿主机上必须事先mount cgroup子系统,因为cgroup是docker容器的一个依赖。同理docker-in-docker也要求外层的docker容器中有cgroup子系统,dind脚本在容器启动后,先去/proc/1/cgroup中获取cgroup子系统,然后依次使用mount命令,将cgroup mount上来,例如mount -n -t cgroup -o "cpuset" cgroup "/cgroup/cpuset"

最终在运行make build后,会制作出一个叫docker-dev的镜像。

make binary

执行make binary 就可以编译出docker二进制文件。编译出来的二进制文件在源码目录下的bundles/1.10.0-dev/binary/docker-1.10.0-dev ,其中还包含md5和sha256文件。

Makefile中的binary

Makefile中关于make binary流程是

先执行build,即上一节介绍的,制作docker-dev编译环境镜像。

再执行DOCKER_RUN_DOCKER,创建容器,DOCKER_RUN_DOCKER就是执行docker run,使用docker-dev镜像启动容器,并且会mount -v 将容器生成二进制文件的路径与宿主机共享。DOCKER_RUN_DOCKER在“编译前分析”一章中有介绍。启动的容器运行的命令行是 hack/make.sh binary 。docker run完整的形式如下:

docker run --rm -i --privileged -e BUILDFLAGS -e DOCKER_CLIENTONLY -e DOCKER_DEBUG -e DOCKER_EXECDRIVER -e DOCKER_EXPERIMENTAL -e DOCKER_REMAP_ROOT -e DOCKER_GRAPHDRIVER -e DOCKER_STORAGE_OPTS -e DOCKER_USERLANDPROXY -e TESTDIRS -e TESTFLAGS -e TIMEOUT -v "/home/mubai/src/docker/docker/bundles:/go/src/github.com/docker/docker/bundles" -t "docker-dev:master" hack/make.sh binary

hack/make.sh脚本

上一节提到的make binary中创建的容器启动命令是hack/make.sh binary,运行容器中的(docker源码目录下的)hack/make.sh脚本,参数为binary。

make.sh中根据传入的参数组装后续编译用的flags(BUILDFLAGS),最后根据传入的参数依次调用 hack/make/目录下对应的脚本。例如我们的操作中传入的参数只有一个binary。那么在make.sh的最后,会调用hack/make/binary脚本。

hack/make/binary脚本中,就是直接调用go build进行编译了,其中会使用BUILDFLAGS LDFLAGS LDFLAGS_STATIC_DOCKER等编译选项。

如果最终生成的docker二进制文件不在bundles/1.10.0-dev/binary/目录下,那么可能是编译参数BINDDIR设置的不正确,可以在执行make binary时增加BINDDIR参数,例如

make BINDDIR=. binary , 将BINDDIR设置为当前目录。

总结

编译步骤总结:

1、编译前在物理机上安装好make、git,并下载好docker代码。下载好ubuntu:14.04镜像

2、执行make build 。这步执行完会在物理机上创建出一个docker-dev的镜像。

3、执行make binary 。 这步会使用docker-dev镜像启动一个容器,在容器中编译docker代码。编译完成后在物理机上直接可以看到二进制文件。默认二进制文件在 bundles/1.10.0-dev/binary/目录下

4、docker代码里有很多test,可以使用此套编译环境执行test,例如 make test 。 更多参数可以看Makefile

搭建环境心得:

1、在make build时,使用Dockerfile创建制作镜像,这个镜像有40多层,其中一层失败就会导致整个build过程失败。由于Dockerfile中很多步骤是要连到国外的网站去下载东西,很容易失败。好在docker build有cache机制,如果前面的层成功了,下次重新build时会使用cache跳过,节省了很多时间。所以如果make build中途失败(一般是由于国内连国外的网络原因),只要重新执行make build就会在上次失败的地方继续,多试几次可以成功。

2、如果其他人已经build出了docker-dev镜像,可以把它下载到自己的环境上。这样在自己make build时,会跳过那些已经在本地存在的层,可以节省时间。

3、每一次编译会自动删除掉前面已经生成的二进制文件,所以不用担心二进制文件不是最新的问题。

发表评论

评论列表

  • 余安辞慾(2022-06-11 09:06:18)回复取消回复

    ,把这个镜像从最顶层到最底层的所有layer一起导出到一个tar包中,然后就可以随意拷贝、发送这个tar包到别的机器,最后可以用load命令把这个镜像重新加载进docker。如果我们把一个镜像从registry里拿出来,按照save成的tar包格

  • 鸠骨辙弃(2022-06-11 10:55:37)回复取消回复

    f88e53.用输入输出符docker run -i Ubuntu /bin/bash -c 'cat /path/to/container/file' /path/to/host/file