S2:创建镜像
声明:所有的实验示例大部分来自《learn-docker-in-a-month-of-lunches》的作者Elton Stoneman,但运行结果并不都是照搬,大部分实验结果可能与原书不同
一、基础概念
- Q:什么是镜像?
A:镜像就是一系列只读的命令,这些命令被用来指导如何创建一个容器。一般来说,镜像一般会基于在别的镜像上,然后再此基础上添加用户的个性化内容。 - Q:什么是镜像层?
A:在Dockerfile中的每一行命令都会使得Docker创建一个对应的镜像层,所以镜像就相当一堆镜像层的集合。镜像实际上是一个个只读的文件,被存储在Docker的本地缓存中。Docker在根据Dockerfile创建镜像时,会自动识别可复用的镜像层,这样就避免了重复创建的开销,这也时为什么Docker如此便捷、轻量化并且快速的原因。 - Q:什么是Dokcerfile?
A:Dockerfile就是一个脚本,该脚本定义了如何打包你的应用,如何去生成一个对应的镜像。
二、编写Dockerfile
- 克隆Elton Stoneman在github上的代码(点颗星应该不算白嫖了吧,咳咳...)
$ git clone https://github.com/sixeyed/diamol
- 进入到项目目录下
$ cd diamol/ch03/exercises/web-ping
- 查看分析Dockerfile
- 查看项目目录下的Dockerfile
$ cat Dockerfile
- 分析Dockerfile
FROM diamol/node
基于diamol/node镜像
ENV TARGET="blog.sixeyed.com"
ENV METHOD="HEAD"
ENV INTERVAL="3000"
设置容器的环境变量
WORKDIR /web-ping
设置容器的文件系统上的项目文件夹
COPY app.js .
哪些数据需要从本地文件系统复制到容器的文件系统里,dot代表容器的项目目录
CMD ["node", "/web-ping/app.js"]
当Docker运行一个容器时,执行这个命令
三、分析镜像的创建过程
- 根据Dockerfile创建镜像(确保你自己在项目的目录下,即Dockerfile所在的目录下)
$ docker image build --tag web-ping .
注意最后面还有个dot!!!
- 分析镜像创建时输出的日志信息
Sending build context to Docker daemon 4.096kB
Step 1/7 : FROM diamol/node
---> 9dfa73010b19
step1/7说明执行的是Dockerfile里面的第1条命令“FROM diamol/node”,并且一共有7步
---> 9dfa73010b19说明执行完第一条命令之后生成的镜像id为9dfa73010b19
Step 2/7 : ENV TARGET="blog.sixeyed.com"
---> Running in caa76a3bbee5
Removing intermediate container caa76a3bbee5
---> 641e96bf92c8
Step 3/7 : ENV METHOD="HEAD"
---> Running in 72f20ca7c727
Removing intermediate container 72f20ca7c727
---> b638ad06534b
Step 4/7 : ENV INTERVAL="3000"
---> Running in db6d5653dfac
Removing intermediate container db6d5653dfac
---> e20424bc0a05
Step 5/7 : WORKDIR /web-ping
---> Running in 840065185c08
Removing intermediate container 840065185c08
---> 4591d6e9216f
---> Running in caa76a3bbee5
Removing intermediate container xxx
说明执行类的命令,会先运行一个临时的容器,然后根据更改后的容器创建一个新的镜像
Step 6/7 : COPY app.js .
---> 7fea8227d6f0
Step 7/7 : CMD ["node", "/web-ping/app.js"]
---> Running in d169d4bb485c
Removing intermediate container d169d4bb485c
---> df5c3f2e5d42
Successfully built df5c3f2e5d42
Successfully tagged web-ping:latest
Successfully built说明创建镜像成功,镜像id为df5c3f2e5d42
-
查看本地缓存的镜像
- 查看镜像
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE web-ping latest df5c3f2e5d42 23 minutes ago 75.3MB diamol/node latest 9dfa73010b19 7 months ago 75.3MB
- 查看所有的镜像,包括临时镜像(即上一步骤中的那些临时镜像)
$ docker image ls --all REPOSITORY TAG IMAGE ID CREATED SIZE web-ping latest df5c3f2e5d42 7 minutes ago 75.3MB <none> <none> 7fea8227d6f0 7 minutes ago 75.3MB <none> <none> 4591d6e9216f 7 minutes ago 75.3MB <none> <none> e20424bc0a05 7 minutes ago 75.3MB <none> <none> b638ad06534b 7 minutes ago 75.3MB <none> <none> 641e96bf92c8 7 minutes ago 75.3MB diamol/node latest 9dfa73010b19 7 months ago 75.3MB
- 模糊搜索本地的镜像
$ docker image ls w* REPOSITORY TAG IMAGE ID CREATED SIZE web-ping latest df5c3f2e5d42 31 minutes ago 75.3MB
-
根据这个镜像创建一个容器,并查看输出的日志
- 直接运行
$ docker container run web-ping ** web-ping ** Pinging: blog.sixeyed.com; method: HEAD; 3000ms intervals Making request number: 1; at 1578388826200 Got response status: 200 at 1578388828160; duration: 1960ms
ping的地址为blog.sixeyed.com ,方法为HEAD ,间隔为3s ,可以发现,这些都被设置在Dockerfile的环境变量里
- 自定义环境变量创建容器
$ docker container run --env TARGET=baidu.com --name wt1 web-ping ** web-ping ** Pinging: baidu.com; method: HEAD; 3000ms intervals Making request number: 1; at 1578389292540 Got response status: 302 at 1578389292703; duration: 163ms
--name指的是为该容器命个名,以后操作该容器就可以通过该名字了,不必通过容器ID了
可以看到ping的地址已经变成了baidu.com -
修改app.js,然后重新创建一个镜像,并分析创建过程的变化
- 重新创建一个镜像,命名为web-ping-modappjs
//随便修改一下app.js,加个空行也行 $ docker image build --tag web-ping-modappjs .
- 分析新镜像的创建过程
Step 1/7 : FROM diamol/node ---> 9dfa73010b19 Step 2/7 : ENV TARGET="blog.sixeyed.com" ---> Using cache ---> 641e96bf92c8 Step 3/7 : ENV METHOD="HEAD" ---> Using cache ---> b638ad06534b Step 4/7 : ENV INTERVAL="3000" ---> Using cache ---> e20424bc0a05 Step 5/7 : WORKDIR /web-ping ---> Using cache ---> 4591d6e9216f
---> Using cache说明Docker识别出这个镜像层可以被复用,所以会自动使用本地镜像缓存中已有的镜像
Step 6/7 : COPY app.js . ---> ef985b5a110e Step 7/7 : CMD ["node", "/web-ping/app.js"] ---> Running in 84847a3f59f2 Removing intermediate container 84847a3f59f2 ---> 5720186d593f Successfully built 5720186d593f Successfully tagged web-ping-modappjs:latest
可以看到第6步和第7步都没有使用镜像,因为第6步中的app.js确实被更改了,第七步没有使用缓存的镜像是因为第7步的镜像是建立在第6步的基础上的,如果之前某个镜像发生过变化,则接下来的所有镜像都会被强制重新生成,所以根据这个原理,优化Dockerfile就势在必行了
-
查看指定镜像的镜像层信息(镜像层大小、ID、创建时间、创建的命令)
$ docker image history web-ping-modappjs
IMAGE CREATED CREATED BY SIZE COMMENT
5720186d593f 23 minutes ago /bin/sh -c #(nop) CMD ["node" "/web-ping/ap… 0B
ef985b5a110e 23 minutes ago /bin/sh -c #(nop) COPY file:f56993489cc9d376… 766B
4591d6e9216f About an hour ago /bin/sh -c #(nop) WORKDIR /web-ping 0B
//...
- 查看本地镜像的占用的空间大小、数量、正在使用的镜像(不只包括正在运行的容器,还包括处于停止状态的容器)数量等信息
$ docker system df | grep "image"
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 10 0 933.2MB 933.2MB (100%)
注意:这里显示的镜像数量有10个是因为实验的机器上有许多原有的镜像,不过即便如此,细心的你也应该发现所有的镜像加起来的实际的占用容量和步骤三.3显示的不同,这正是因为镜像复用的原因docker container ls
显示的镜像大小是逻辑上的大小,包含所有镜像层的逻辑上的大小,并不是实际上的大小
四、Dockerfile的优化规则
- Dockerfile的优化规则
- 同一种类型的命令尽量写成一个命令
- 不经常变动的命令往前写
- 经常变动的命令往后写
- 修改本项目中的Dockerfile(修改后的结果在diamol/ch03/exercises/web-ping-optimized中),并重新创建镜像
- 修改后的Dockerfile
FROM diamol/node CMD ["node", "/web-ping/app.js"] ENV TARGET="blog.sixeyed.com" \ METHOD="HEAD" \ INTERVAL="3000" WORKDIR /web-ping COPY app.js .
这样创建步骤就只剩5步了,且就算修改了app.js,需要重新生成的镜像层也只有最后一步而已,
CMD
命令只需要放在FROM
后面就可以了,结果都是一样的。
五、通过容器创建镜像
- 其实Docker还可以通过容器来创建镜像,使用
docker container commit <容器ID> [镜像名]
命令可以把修改过后的容器作为一个新的镜像进行生成
$ docker container commit 0b7 c3lab
sha256:0be745068f061ab1812ee12712b515f8e18b40fce7aada731fe2c5472badbc80
$ docker image history c3lab
IMAGE CREATED CREATED BY SIZE COMMENT
0be745068f06 6 minutes ago /bin/sh 108B
c46f210501ad 6 months ago /bin/sh -c #(nop) COPY file:6a4b948f094c8555… 18B
<missing> 6 months ago /bin/sh -c #(nop) WORKDIR /diamol 0B
<missing> 8 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 8 months ago /bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6a… 5.53MB
c46f210501ad就是原来镜像的ID,0be745068f06就是根据容器重建后的镜像ID
参考文档:
[1] learn-docker-in-a-month-of-lunches
[2] 官方文档
附:
[1] Elton Stoneman的github项目