Docker镜像和仓库
什么是Docker镜像
docker 镜像是由文件系统系统叠加而成
, 最底层是一个引导文件系统,即 bootfs
和 linux 引导文件系统很像,用户是不会和引导文件系统有什么交互,当一个容器启动后,它将会被移到内存中,docker看起来很像一个典型的 linux虚拟化栈,
docker 镜像第二层是root文件系统 rootfs
,它位于引导文件系统之上,rootfs
可以是一种或多张操作系统,root
文件系统永远是能是只读状态,并且 docker 利用 联合加载技术
又会在 root
文件系统层上加载更多的只读文件系统,联合加载
是指一次同时加载多个文件系统,但是在外层看起来只能看到一个文件系统,联合文件系统会将各层文件系统叠加到一起,这样最终的文件系统会包含所有底层的文件和目录。
docker 将这样的文件系统称之为镜像,一个镜像可以放到另外一个镜像顶部,位于下面的镜像称之为父镜像
,直到镜像栈的最底部,最底部的镜像称之为基础镜像
,当启动容器时,Docker 会在该镜像的最顶层加载一个读写文件系统,我们运行的程序就是在就是在读写层中执行
当docker 第一次启动一个容器时
,初始读写层是空的,当文件系统发生变化时,这些变化都会应用到这一层上
,当修改一个文件时,这个文件首先会从该读写层下面的只读层复制到该读写层
,只读版本依然存在
,但是已经被读写层中的该文件副本所隐藏
,这种机制称为写时复制
,每个只读镜像层都是只读的,并且以后永远不会变化,创建新容器时, docker会构建初一个镜像栈,并在栈的最顶端添加一个读写层,这个读写层加上其下面的镜像层以及一些配置数据,就构成一个容器。
列出镜像
想要知道本机中有哪些镜像,可以使用 docker images
命令查看,本地镜像都保存在 Docker 宿主机的 /var/lib/docker
目录下。
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu latest c4ff7513909d 6 days ago 225.4 MB
docker 从仓库中下载下来, 镜像保存在仓库中, 而仓库位于 Registry
中,默认的Registry
是由Docker
公司运营的公共 Registry
服务,Docker 提供一种标签功能(tag)
来区别镜像,这样可以使得同一个仓库中可以存储多个镜像,同一个镜像可以有多个标签
,在构建镜像的时候,建议指定标签,这样更容易来区分镜像
拉取镜像
启动容器时, 如果镜像不在本地,Docker会从Docker hub 拉去镜像,没有指定镜像标签,会自动下载 latest标签的镜像。也可以使用 docker pull
拉取镜像到本地,然后再启动容器,拉取指定标签的镜像为docker pull 镜像名:标签名
查找镜像
可以通过 docker search
命令来查找所有 Docker hub 上公共可用的镜像
$ sudo docker search puppet
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
wfarr/puppet-module...
jamtur01/puppetmaster
. . .
查找镜像会返回该镜像的详细信息,仓库名
, 镜像描述
, 用户评价(stars)
, 是否官方(official)-由上游开发这管理的镜像
, 自动构建(Automated)-表示镜像是由 Docker Hub 自动构建
构建镜像
在使用过程中我们需要修改,更新和管理这些镜像,可以自动构建镜像,构建镜像的方法有两种,docker commit
命令和 Dockerfile
文文件构建,在实际开发中,不推荐使用 docker commit
方式构建,应该使用更灵活
,更强大
,的Dockerfile
来构建镜像
docker commit
docker commit
方式构建镜像,需要我们先创建一个容器,并在容器內做出修改,最后将修改提交为一个新的镜像
$ sudo docker run -i -t ubuntu /bin/bash
root@4aab3ce3cb76:/#
root@4aab3ce3cb76:/# apt-get -yqq update
. . .
root@4aab3ce3cb76:/# apt-get -y install nginx
. . .
$ sudo docker commit 4aab3ce3cb76 jamtur01/apache2
8ce0ea7a1528
docker commit 指定要提交的修改过的容器ID, 以及一个目标镜像仓库,和镜像名,docker commit 提交的两个镜像的差异部分,使得构建镜像非常的轻量化
$ sudo docker commit -m"A new custom image" -a"James Turnbull" \
4aab3ce3cb76 jamtur01/apache2:webserver
f99ebb6fed1f559258840505a0f5d5b6173177623946815366f3e3acff01adef
还可以指定 -m
来指定新创建的镜像的提交信息,-a
来指定该镜像的作者信息
Dockerfile
并不推荐 docker commit
来构建镜像,推荐使用 Dockerfile
定义文件,和 docker build
命令来构建镜像,使用 Dockerfile
的好处是构建镜像更具可重复性,透明性等好处。
Dockerfile初体验
编写Dockerfile文件
$ mkdir static_web
$ cd static_web
$ touch Dockerfile
创建的目录
就是我们的构建环境
, Docker 称此环境为上下文
,或者构建上下文
,Docker 会在构建镜像时,将构建上下文和该上下文中的文件和目录上传到 Docker 守护进程
, 这样 Docker 守护进程就能直接访问用户想要在镜像中存储的任何代码,文件,或者其他数据
。
# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
>/usr/share/nginx/html/index.html
EXPOSE 80
Dockerfile 由一系列指令和参数组成
, 指令都是大写字母
,指令后面跟随一个参数, Dockerfile会按照顺序,从上到下执行
,在书写Dockerfile时,需要合理安排指令的顺序。
每条指令都会创建一个新的镜像层并对镜像进行提交
, 大致执行流程如下
- Docker 从基础镜像运行一个容器
- 执行一条指令,对容器作出修改
- 执行类似 docker commit 操作,提交一个新的镜像层
- Docker 再基于刚提交的镜像运行一个新容器,
- 执行 Dockerfile 中的下一条指令,直到所有指令都执行完毕
如果用户的Dockerfile
由于某些原因(指令失败)
, 没有正常结束,那么用户将得到一个可以使用的镜像
,方便用户调试,用户可以基于该镜像运行一个具备交互功能的容器,进行 失败指令调试。
Dockerfile 也支持注视, 已 #
开始。
每个Dockerfile第一条指令必须是 FROM
, FROM 指定一个与存在的镜像,后续指令都将基于该镜像进行,这个镜像被称基础镜像
MAINTAINER
指令,指定该镜像的作者是谁,以及作者的电子邮件嘻,联系方式
RUN
指令, 会在当前镜像中运行指定的命令,每条 RUN
指令都会创建一个新的镜像层,如果指令成功,就会将此镜像层提交,之后继续执行 Dockerfile中的吓一条指令, 默认情况下,RUN
指令会在 shell 中使用命令包装器, /bin/sh -c 来执行,如果在一个不支持 shell的平台上运行,或者不希望在shell中运行,可以使用 exec 格式的run 命令
RUN [ "apt-get", " install", "-y", "nginx" ]
EXPOSE
指令, 告诉 Docker 该容器內额应用程序将会使用容器的指定端口,并不意味这可以自动访问任意容器运行中服务的端口,出于安全原因,Docker 并不会自动打开该端口,而是需要用户在使用 docker run 运行容器是来指定需要打开那些端口,可以指定多个 EXPOST 指令向外部公开多个端口
Dockerfile构建新镜像
Dockerfile 编写完毕,需要使用 docker build
来构建镜像,执行docker build
命令时, Dockefile中所有的指令都会被执行,并且提交,并且在该命令成功结束后返回一个新镜像
$ cd static_web
$ sudo docker build -t="jamtur01/static_web" .
Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> ba5877dc9bec
Step 1 : MAINTAINER James Turnbull "james@example.com"
---> Running in b8ffa06f9274
---> 4c66c9dcee35
Removing intermediate container b8ffa06f9274
Step 2 : RUN apt-get update
---> Running in f331636c84f7
---> 9d938b9e0090
Removing intermediate container f331636c84f7
Step 3 : RUN apt-get install -y nginx
---> Running in 4b989d4730dd
---> 93fb180f3bc9
Removing intermediate container 4b989d4730dd
Step 4 : RUN echo 'Hi, I am in your container' >/usr/share/
nginx/html/index.html
---> Running in b51bacc46eb9
---> b584f4ac1def
Removing intermediate container b51bacc46eb9
Step 5 : EXPOSE 80
---> Running in 7ff423bd1f4d
---> 22d47c8cb6e5
Successfully built 22d47c8cb6e5
-t
参数,为新镜像设置了仓库和名称, 也可以为过程中镜像设置一个标签,镜像名:标签
$ sudo docker build -t="jamtur01/static_web:v1" .
自Docker 1.5.0
开始,-f
标志指定一个区别与标准Dockerfile
的构建源的位置,这个文件可以不必命名为Dockerfile
但是必须要位于构建上下文中
docker build –t="jamtur01/static_web" -f path/to/file
构建失败
在构建过程中,指令失败,也会也会得到一个镜像,我们可以使用 docker run
命令来基于这个构建到目前为止已经成功的最后一步创建一个容器,方便调试指令
Dockerfile和构建缓存
每一步构建过程都会将结果提交为镜像,Docker 会讲之前的镜像层看作缓存, 构建起来额速度非常块,也可以在确保构建过程中不实用缓存,可以使用 --no-cache
标志
$ sudo docker build --no-cache -t="jamtur01/static_web" .
查找新镜像
可以使用 docker images
可以查看到刚刚构建出来的镜像,也可以使用docker history
命令来查看镜像是如何构建出来的
$ sudo docker history 22d47c8cb6e5
IMAGE CREATED CREATED BY SIZE
22d47c8cb6e5 6 minutes ago /bin/sh -c #(nop) EXPOSE map[80/tcp:{}] 0 B
b584f4ac1def 6 minutes ago /bin/sh -c echo 'Hi, I am in your container' 27 B
93fb180f3bc9 6 minutes ago /bin/sh -c apt-get install -y nginx 18.46 MB
9d938b9e0090 6 minutes ago /bin/sh -c apt-get update 20.02 MB
4c66c9dcee35 6 minutes ago /bin/sh -c #(nop) MAINTAINER James Turnbull " 0 B
. . .
新镜像启动容器
我们可以使用新镜像,检查构建镜像是否一切正常
$ sudo docker run -d -p 80 --name static_web jamtur01/static_web \
nginx -g "daemon off;"
6751b94bb5c001a650c918e9a7f9683985c3eb2b026c2f1776e61190669494a8
-p
该标志用来控制Docker
在运行是应该公开那些网络端口给外部(宿主机),Docker
可以在宿主机随机选择一个位于 32768-61000 的一个较大的端口来映射到容器的80端口上, 可以在宿主机指定一个具体的端口来映射到容器的 80 端口上
Docker 提供了一个更简单的方式, 即 -P
参数, 该参数可以用来对外公开在Dockerfile中通过 EXPOSE 指令公开的所有端口
$ sudo docker run -d -P --name static_web jamtur01/static_web \
nginx -g "daemon off;"
该命令会将容器內的80端口对本地宿主机公开
,并且绑定到宿主机的一个随机端口上
, 该命令会将用来构建该镜像的Dockerfile
文件中的EXPOST
指令指定的其他端口也一并公开
Dockerfile指令
1.CMD
CMD
指令用于指定一个容器启动是要运行的命令
,和RUN
命令很相似,两者的区别是,RUN
指令是指定镜像被构建时要运行的命令
,而CMD
只指定容器被创建时要运行的命令
$ sudo docker run -i -t jamtur01/static_web /bin/true
CMD ["/bin/true"]
需要注意的要运行的命令是存放在一个数组接口中
, 使用docker run 命令可以覆盖CMD指令
,如果我们在Dockerfile中指定了CMD
指令,而同时在docker run
命令行中也指定了要运行的命令,命令行中指定的命令覆盖Dockerfile中的CMD指令,在 Dockerfile 中只能指定一条CMD指令,如果指定多条CMD指定
,也只有最后一条指令会被执行
。
2. ENTRYPOINT
ENTRYPOINT
指令与 CMD
指令非常类似,两者的区别是,docker run
命令行中指令会覆盖CMD
指令,而ENTRYPOINT
指令提供的命令则不容器在启动时被覆盖,实际上 docker run 命令行中指定的任何参数都会被当作参数再次传递给ENTTRYPOINT
指令中指定的命令
3. WORKDIR
WORKDIR
指令用来在创建一个新容器时, 在容器内部设置一个工作目录,ENTRYPOINT
或CMD
指定的程序会在这个目录下执行, 我们可以使用该指令为 Dockerfile中后续的一系列指令设置工作目录,也可以为最终的容器设置工作目录
ORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT [ "rackup" ]
4. ENV
ENV
指令用来在镜像构建称中设置环境变量
ENV RVM_PATH /home/rvm/
这些环境变量会被持久保存知道从我们的惊喜那个创建的任何容器中, 也可以使用 docker run
命令行的 -e
标志来传递环境变量,这些环境变量只在运行时有效
5. USER
USER
指令用来指定该镜像会以什么样的用户去运行
USER nginx
如果不通过USER
指令指定用户,默认用户为 root,也可以使用docker run
命令行中 通过 -u
标志来覆盖该指令指定的值
6. VOLUME
VOLUME
指令 用来向基于镜像创建的容器添加卷
,一个卷是一个存在与一个或者多个容器內特定的目录
,这个目录可以绕过联合文件系统
,并提供如下共享数据
,或者对数据进行持久化的功能
- 卷可以在容器间共享和重用
- 一个容器可以不是必须和其他容器共享卷
- 对卷的修改是立即生效
- 对卷的修改不会对更新镜像产生影响
- 卷会一直存在直到没有任何容器使用它
卷功能让我们可以将数据(源代码)
,数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中
,并且允许我们在多个容器间共享这些内容。我们可以利用此功能来测试容器和内部的应用程序代码,管理日志,或者处理容器内部的数据库
VOLUME ["/opt/project"]
这条指令将会为基于此镜像创建的任何容器创建一个名为/opt/project
的挂载点,也可以挂载多个卷
VOLUME ["/opt/project", "/data" ]
7. ADD
ADD
指令用来将构建环境下的文件和目录复制到镜像中,指向源文件
的位置参数可以是一个 URL
, 或者构建上下文或环境中文件名
,或者目录
,不能对构建目录或者上下文之外的文件进行ADD操作
。
ADD software.lic /opt/application/software.lic
ADD 在处理本地归档文件时,如果将一个归档文件(gzip, bzip2, xz) 指定为源文件, Docker 会自动将归档文件解开。
8. COPY
COPY
指令非常类似于 ADD
他们根本的不同是 COPY
只关心在构建上下文中复制本地文件,而不会去做文件提取 和解压的工作。
COPY conf.d/ /etc/apache2/
文件源路径必须是一个与当前构建环境相对的文件或者目录
,本地文件都放在和Dockerfile
统一个目录下, 不能复制该目录之外的任何文件
,因为构建环境将会上传到Docker守护进程
,而复制是在 Docker守护进程中进行的
,任何位于构建环境之外的东西都是不可用的,COPY
指令的目的位置则必须是容器内部的一个绝对路径
9. LABEL
LABEL
指令用于为 Docker
镜像添加元数据, 元数据以键值对的形式展现
LABEL version="1.0"
LABEL location="New York" type="Data Center" role="Web Server"
LABEL version="1.0"
LABEL location="New York" type="Data Center" role="Web Server"
LABEL
指令以 label="value" 的形式出现,可以在每一条指令中指定一个元数据,或者指定多个元数据,不同的的元数据之间用空格分割
,推荐将所有的元数据,都放到一条LABEL
指令中,以防止不同的元数据指令创建过多的镜像层。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。