1、最小镜像
(1)定义
hello-world是Docker官方提供的一个镜像,通常用来验证Docker是否安装成功
(2)Dockerfile
查看地址:https://github.com/docker-library/hello-world/blob/master/Dockerfile-linux.template
解释指令:
FROM scratch:此镜像是从白手起家,从0开始构建的
COPY hello /:将文件”hello”复制到镜像的根目录
CMD [“/hello”]:容器启动时,执行/hello
2、base镜像
(1)定义
base镜像提供的是最小安装的linux发行版。能称作base镜像的通常都是各种linux发行版的Docker镜像,比如Ubuntu、Debian、Centos等
(2)Dockerfile
查看地址:https://github.com/CentOS/sig-cloud-instance-images/blob/a77b36c6c55559b0db5bf9e74e61d32ea709a179/docker/Dockerfile
解释指令:
FROM、COPY、CMD这几个指令的作用跟hello-world镜像中的作用是一样的;
ADD指令添加到镜像的tar包就是Centos7的rootfs。在制作镜像时,这个tar包会自动解压到/目录下,生成/dev、/proc、/bin等目录
(3)含义
不依赖其他镜像,从scratch构建;
其他镜像可以以之为基础进行扩展
(4)以Cenots为例
下载镜像:docker pull centos
查看镜像信息:docker images centos(备注:平时我们安装一个Centos至少都有几个GB,这里只有200MB。这是因为内核空间kernel的文件系统是bootfs,linux刚启动时会加载bootfs文件系统,之后bootfs会被卸载掉。而用户空间的文件系统是rootfs,包含我们熟悉的/dev、/proc、/bin等目录。对于base镜像来说,底层用host的kernel,自己只需要提供rootfs就行。对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库等)
支持多种操作系统类型(备注:除Centos之外,base镜像可以模拟出多种操作系统环境。base镜像只是用户空间与发行版一致,kernel版本与发行版是不同的。因为所有容器共用host的kernel,因此在容器中无法对kernel升级。如果容器对kernel的版本有要求,比如说应用只能在某个kernel版本下运行,则不建议用容器,这种场景虚拟机可能更合适)
3、分层结构
(1)定义
通过扩展现有镜像,创建新的镜像(实际上,Docker Hub中99%的镜像都是通过在base镜像中安装和配置需要的软件构建出来的)
(2)Dockerfile
解释指令:
新镜像不再是从scratch开始,而是直接在Debian base镜像上构建;
安装emacs编辑器;
安装Apache2;
容器启动时运行bash
(3)好处:共享资源
比如,有多个镜像都从相同的base镜像构建而来,那么Docker Hub只需要在磁盘上保存一份base镜像即可;同时内存中也只需要加载一份base镜像,就可以为所有容器服务了
(4)copy-on-write特性
定义:当需要修改时,才复制一份数据
原理:当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被称作”容器层”,”容器层”之下都叫”镜像层”;
所有对容器的改动(无论是添加、删除或者是修改文件)都只会发生在容器层中;
只有容器层是可写的,容器层下面的所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。
(5)查看分层结构
docker history:会显示镜像的构建历史,也就是Dockerfile的执行过程。ubuntu-with-vi-dockerfile与ubuntu镜像相比,确实只是多了顶部的一层
备注:docker history输出中IMAGE列出现
4、构建镜像
(1)docker commit
步骤:
a、运行容器
-it参数的作用是以交互模式进入容器,并打开终端
b、确认vim确实没有安装
c、安装vim(如果没有找到vim软件包,先apt-get update一下)
d、打开一个新的窗口,查看当前运行的容器
silly_goldberg 是 Docker 为我们的容器随机分配的名字
e、执行docker commit命令将容器保存为镜像
新镜像命名为ubuntu-with-vi
f、查看新镜像属性
从size上看到镜像因为安装了软件而变大了
g、从新镜像启动容器,验证vi已经可以使用
总结:Docker并不建议用户通过这种方式构建镜像
原因:这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱;使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。
(2)Dockerfile构建文件
步骤:
a、新建一个Dockerfile文件,内容如下:
FROM ubuntu
RUN apt-get update && apt-get install -y vim
b、运行docker build命令
docker build -t ubuntu-with-vi-dockerfile .
命令解释:
-t将新镜像命名为ubuntu-with-vi-dockerfile;
命令末尾的点号(.),指明了build context为当前目录。目录下的所有文件和子目录都会被发送给Docker daemon。所以,不要将多余文件放到build context,特别不要把/、/usr作为build context,否则构建过程会相当缓慢甚至失败;
我们也可以通过-f参数指定Dockerfile的位置。
c、构建完成后,通过docker images查看镜像信息
5、缓存特性
(1)定义
缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建
(2)举例
在前面的Dockerfile中添加一点新内容,往镜像中复制一个文件
(3)不使用缓存
如果我们希望在构建镜像的时候不使用缓存,可以在docker build命令上加上–no-cache参数
(4)缓存失效
Dockerfile中每一个指令都会创建一个镜像层,上层是依赖下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。也就是说,如果我们改变Dockerfile指令的执行顺序,或者修改或添加指令,都会使缓存失效。比如,交换前面的RUN和COPY的顺序,也会导致缓存失效。
(5)使用场景
构建镜像 && 下载镜像
6、调试Dockerfile
(1)定义
如果Dockerfile由于某种原因执行到某个指令失败了,我们能够得到前一个指令成功执行构建出的镜像,这对调试Dockerfile非常有帮助
(2)举例
运行在第三步的时候报错,我们可以利用第二部创建的镜像ba6402b1298d进行调试,方式是通过 docker run -it 启动镜像的一个容器。手工执行 RUN 指令很容易定位失败的原因是 busybox 镜像中没有 bash。
7、Dockerfile常用指令
(1)FROM
指定base镜像
(2)MAINTAINER
设置镜像的作者,可以是任意字符串
(3)COPY
将文件从build context复制到镜像(build context默认是当前目录,也可以通过-f来指定)
COPY支持两种形式:
COPY src dest
COPY [“src”,”dest”]
备注:src只能指定build context中的文件或目录
(4)ADD
与COPY类似,从build context复制文件到镜像。不同的是,如果src是归档文件(tar、zip、tgz、xz等),文件会被自动解压到dest
(5)ENV
设置环境变量,环境变量可以被后面的指令使用
举例:
…
ENV MY_VERSION 1.3
RUN apt-get install -y mypackage=$MY_VERSION
…
(6)EXPOSE
指定容器中的进程会监听某个端口
(7)VOLUME
将文件或目录声明为volume
(8)WORKDIR
设置镜像中的当前工作目录
(9)RUN
在容器中运行指定的命令。通常用于安装应用和软件包
两种格式:
shell格式:RUN apt-get install python3
Exec格式:RUN [“apt-get”,”install”,”python3”]
举例:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
备注:
apt-get update和apt-get install被放在一个RUN指令中执行,这样能够保证每次安装的是最新的包。如果apt-get install在单独的RUN中执行,则会使用apt-get update创建的镜像层,而这一层可能是很久以前缓存的。
(10)CMD
设置容器启动时运行指定的指令,此命令会在容器启动且docker run没有指定其他指令时运行
备注:
如果docker run指定了其他命令,CMD指定的默认命令将会被忽略;
如果Dockerfile中有多个CMD指令,只有最后一个CMD有效
三种格式:
shell格式:CMD echo “hello world”
Exec格式:CMD [“/bin/echo”,”hello world”]
与ENTRYPOINT搭配使用:CMD [“param1”,”param2”] ,此时ENTRYPOINT必须使用Exec格式,其用途是为ENTRYPOINT设置默认的参数
举例:
Dockerfile 片段如下:
CMD echo “Hello world”
运行容器 docker run -it [image] 将输出:
Hello world
但当后面加上一个命令,比如 docker run -it [image] /bin/bash,CMD 会被忽略掉,命令 bash 将被执行
(11)ENTRYPOINT
设置容器启动时运行的指令,看上去与CMD很像
备注:
与CMD不同的地方在于,ENTRYPOINT不会被忽略,一定会被执行,即使运行docker run时指定了其他指令;
Dockerfile中有多个ENTRYPOINT命令,但是只有最后一个生效
两种格式:
shell格式:ENTRYPOINT echo “hello world”
Exec格式:ENTRYPOINT [“/bin/echo”,”hello world”]
举例:
Dockerfile 片段如下:
ENTRYPOINT [“/bin/echo”, “Hello”]
CMD [“world”]
当容器通过 docker run -it [image] 启动时,输出为:
Hello world
而如果通过 docker run -it [image] CloudMan 启动,则输出为:
Hello CloudMan
8、镜像命名
(1)组成
实际上一个特定镜像的名字由两部分组成:repository和tag。格式为:[imagename]=[reposity]:[tag]
默认如果docker build没有指定tag,会使用默认值latest
tag的作用:
常用于描述镜像的版本信息,比如httpd镜像
也可以是任意字符串,比如ubuntu镜像
打tag:
docker tag myimage-v1.9.2 myimage:1
docker tag myimage-v1.9.2 myimage:1.9
docker tag myimage-v1.9.2 myimage:1.9.2
docker tag myimage-v1.9.2 myimage:latest
9、搭建Registry
(1)搭建公共Registry
步骤:
a、首先在docker hub上面注册一个账号
b、命令行登录(用户名密码是你注册时提供的用户名和密码)
c、修改镜像的repository使之与docker hub账号匹配
d、通过docker push将镜像上传到Docker Hub
备注:Docker 会上传镜像的每一层。如果是在官方镜像的基础上做了一点修改而得到新的镜像,Docker Hub 上已经有了全部的镜像层,所以真正上传的数据很少。同样的,如果我们的镜像是基于 base 镜像的,也只有新增加的镜像层会被上传。如果想上传同一 repository 中所有镜像,省略 tag 部分就可以了,例如:
docker push liangzj/ubuntu-with-vi-dockerfile
e、登录 https://hub.docker.com,在repository就可以看到上传的镜像
f、这个镜像就可以被下载使用了
docker pull liangzj/ubuntu-with-vi-dockerfile:v1
(2)搭建本地Registry
why:Docker Hub虽然非常方便,但还是有些限制,比如,
需要Internet连接,而且上传和下载的速度慢;
上传到Docker Hub的镜像任何人都能够访问,虽然可以用私有repository,但不是免费的;
安全原因很多组织不允许将镜像放到外网;
解决方案就是搭建本地的Registry。
步骤:
a、启动registry容器
备注:
使用的镜像是 registry:2
-d是后台启动容器;
-p是将容器的5000端口映射到host的5000端口。5000 是 registry 服务端口。
-v是将容器的/var/lib/registry目录映射到host的/myregistry,用于存放镜像数据
b、通过docker tag重命名镜像,使之与registry匹配
备注:
localhost是文件/etc/hosts里面定义的主机名
我们在镜像的前面加上了运行 registry 的主机名称和端口。
c、通过docker push上传镜像
d、通过docker pull从本地registry下载镜像
10、常用操作子命令
(1)images:显示镜像列表
(2)history:显示镜像构建历史
(3)commit:从容器创建新镜像
(4)build:从Dockerfile构建镜像
(5)tag:给镜像打tag
(6)pull:从registry下载镜像
(7)push:将镜像上传到registry
(8)rmi:删除镜像(如果一个镜像对应了多个 tag,只有当最后一个 tag 被删除时,镜像才被真正删除)
(9)search:搜索docker hub中的镜像(search 让我们无需打开浏览器,在命令行中就可以搜索 Docker Hub 中的镜像)