在前面的文章中我们就说过,镜像可以打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时库、环境变量和配置文件。我们在使用过程中通常会直接拉取官方为我们制作好的镜像,那么我们能不能自己制作镜像呢?
答案是肯定的。使用到的就是Dockerfile。
Dockerfile是用来构建Docker镜像的构建文件,是由一系列命令和参数构成的脚本。
Dockerfile中记录了镜像制作过程中的每一个详细的步骤,我们到官网上看看一个centos的镜像是怎么做出来的。
https://hub.docker.com/_/centos
这是官网上centos镜像的Dockerfile,可以看到一个centos镜像里面都包含了哪些内容。
我们也可以仿照此制作自己的镜像。
构建镜像一般有以下几个步骤:
基础知识:
流程:
说明:
从应用软件的角度来看,Dockerfile,docker镜像与docker容器分别代表软件的三个不同阶段。
Dockerfile 面向开发,Docker镜像成为交付标准,Docker容器则涉及部署与运维,三者缺一不可!
Dockerfile:需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。
Docker镜像:在Dockerfile 定义了一个文件之后,Docker build 时会产生一个Docker镜像,当运行Docker 镜像时,会真正开始提供服务;
关键字
上面我们看了制作centos镜像的Dockerfile,里面就是一些指令,我们这里将一些常用指令进行汇总。
FROM # 基础镜像,当前新镜像是基于哪个镜像的
MAINTAINER # 镜像维护者的姓名和邮箱地址
RUN # 容器构建时需要运行的命令
EXPOSE # 当前容器对外保留出的端口
WORKDIR # 指定在创建容器后,终端默认登录的进来工作目录,一个落脚点
ENV # 用来在构建镜像过程中设置环境变量
ADD # 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包
COPY # 类似ADD,拷贝文件和目录到镜像中!
VOLUME # 容器数据卷,用于数据保存和持久化工作
CMD # 指定一个容器启动时要运行的命令,Dockerfile中可以有多个CMD指令,但只有最后一个生效!
ENTRYPOINT # 指定一个容器启动时要运行的命令!和CMD一样
ONBUILD # 当构建一个被继承的Dockerfile时运行命令,父镜像在被子镜像继承后,父镜像的ONBUILD被触发
这里注意ADD和COPY的区别,经常使用
看指令不太好理解,我们通过一张图片来给大家说明。
我们从centos的Dockerfile中可以看出,centos镜像来源于一个base镜像(Scratch),Docker Hub中绝大多数镜像都是通过此基础镜像安装和配置需要的软件构建。
当我们使用官网提供的centos时会发现很多问题,比如没有vim
命令编辑器,查看网络配置ifconfig等等,我们现在做一个镜像,能够使用这些命令。
[root@jiangnan centos]# pwd
/home/centos
[root@jiangnan centos]# vim Dockerfile-centos
[root@jiangnan centos]# cat Dockerfile-centos
FROM centos:7
MAINTAINER jiangnan<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum install -y vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "----------end--------"
CMD /bin/bash
[root@jiangnan centos]#
说明:我们这里以centos7为基础镜像。
docker build -f /home/centos/Dockerfile-centos -t mycentos:1.0 .
-f:指定Dockerfile文件。-t:给镜像命名。后面有个点不要忘了。
[root@jiangnan centos]# docker build -f /home/centos/Dockerfile-centos -t mycentos:1.0 .
Sending build context to Docker daemon 2.048kB
Step 1/10 : FROM centos:7
---> eeb6ee3f44bd
Step 2/10 : MAINTAINER jiangnan<[email protected]>
---> Running in 6fd0d509a785
Removing intermediate container 6fd0d509a785
---> 9945ff2fb154
Step 3/10 : ENV MYPATH /usr/local
........
---> a4b55ee0c621
Successfully built a4b55ee0c621
Successfully tagged mycentos:1.0
[root@jiangnan centos]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mycentos 1.0 a4b55ee0c621 About a minute ago 570MB
centos 7 eeb6ee3f44bd 5 months ago 204MB
[root@jiangnan centos]#
注意:构建命令最后有个
.
[root@jiangnan centos]# docker run -it mycentos:1.0 /bin/bash
[root@669ce95e0e0b local]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)
RX packets 7 bytes 586 (586.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@669ce95e0e0b local]# vim hello.txt
[root@669ce95e0e0b local]# cat hello.txt
hello,Docker
[root@669ce95e0e0b local]#
可以看到我们要的效果达到了。
我们前面也说了,Dockerfile中记录了镜像制作过程中的每一个详细的步骤,那我们怎么看呢?
docker history 镜像名ID
[root@jiangnan centos]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mycentos 1.0 a4b55ee0c621 About an hour ago 570MB
centos 7 eeb6ee3f44bd 5 months ago 204MB
[root@jiangnan centos]# docker history a4b55ee0c621
IMAGE CREATED CREATED BY SIZE COMMENT
a4b55ee0c621 About an hour ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin… 0B
c7d422106fd9 About an hour ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B
07b9b6147049 About an hour ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B
20704538d7c4 About an hour ago /bin/sh -c #(nop) EXPOSE 80 0B
9c58a0593f09 About an hour ago /bin/sh -c yum -y install net-tools 156MB
341ac0e40b31 About an hour ago /bin/sh -c yum install -y vim 211MB
52a117dd18a2 About an hour ago /bin/sh -c #(nop) WORKDIR /usr/local 0B
746a15b8b0fa About an hour ago /bin/sh -c #(nop) ENV MYPATH=/usr/local 0B
9945ff2fb154 About an hour ago /bin/sh -c #(nop) MAINTAINER jiangnan
eeb6ee3f44bd 5 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 5 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 5 months ago /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4… 204MB
[root@jiangnan centos]#
说明:从这里可以看出我们在Dockerfile中定义每一个步骤都会有记录。而且我们还发现了一个很重要的规律,Dockerfile是由上往下写的,但是镜像是从下往上的,这就验证了我们之前说的镜像是一层一层叠加起来的。
我们知道tomcat镜像运行需要jdk环境,那么这次我们将jdk连同tomcat一同打包成镜像。
# 设置基础镜像
FROM centos:7
# 设置作者
MAINTAINER jiangnan<[email protected]>
# 将readme.txt复制到容器中
COPY readme.txt /usr/local
# 将安装包复制到容器中并配置环境变量
ADD jdk-8u161-linux-x64.tar.gz /usr/local
ENV JAVA_HOME /usr/local/jdk1.8.0_161
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ADD apache-tomcat-9.0.58.tar.gz /usr/local
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.58
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.58
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
# 删除不需要的安装包
RUN rm -f /usr/local/*.tar.gz
# 设置工作目录
WORKDIR /usr/local/apache-tomcat-9.0.58
# 暴露端口
EXPOSE 8080
# 启动tomcat
ENTRYPOINT ["./bin/catalina.sh", "run"]
[root@jiangnan tomcat]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mytomcat 1.0 2733bd702ea7 8 seconds ago 604MB
centos 7 eeb6ee3f44bd 5 months ago 204MB
[root@jiangnan tomcat]#
[root@jiangnan tomcat]# docker run -dit --name=divtomcat -p 9090:8080 -v /home/tomcat/webapps/:/usr/local/apache-tomcat-9.0.58/webapps mytomcat:1.0
962898ed75522850347bca325ff213f6ce869d2d6a762c0710e61ca8b86ff6da
[root@jiangnan tomcat]#
[root@jiangnan tomcat]# cp -rf music webapps/
[root@jiangnan tomcat]# cd webapps/music/
[root@jiangnan music]# ll
total 24
drwxr-xr-x 2 root root 4096 Feb 26 19:15 css
drwxr-xr-x 2 root root 4096 Feb 26 19:15 images
-rw-r--r-- 1 root root 2707 Feb 26 19:15 index.html
drwxr-xr-x 2 root root 4096 Feb 26 19:15 js
[root@jiangnan music]#
/home/tomcat/webapps/和/usr/local/apache-tomcat-9.0.58/webapps进行了挂载。
我们之前说过,两个命令都是指定一个容器启动时要运行的命令。
CMD:Dockerfile 中可以有多个CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换!
ENTRYPOINT: docker run 之后的参数会被当做参数传递给 ENTRYPOINT,之后形成新的命令组合!
测试:
CMD
[root@jiangnan cmd]# pwd
/home/cmd
[root@jiangnan cmd]# ll
total 4
-rw-r--r-- 1 root root 32 Feb 26 14:53 Dockerfile
[root@jiangnan cmd]# cat Dockerfile
FROM centos
CMD [ "ls", "-a" ]
[root@jiangnan cmd]#
[root@jiangnan cmd]# docker build -f Dockerfile -t cmdtest .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM centos
latest: Pulling from library/centos
a1d0c7532777: Pull complete
Digest: sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177
Status: Downloaded newer image for centos:latest
---> 5d0da3dc9764
Step 2/2 : CMD [ "ls", "-a" ]
---> Running in bd1a6bba4aee
Removing intermediate container bd1a6bba4aee
---> 29a1a1704d9a
Successfully built 29a1a1704d9a
Successfully tagged cmdtest:latest
[root@jiangnan cmd]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
cmdtest latest 94e05d54db92 About a minute ago 231MB
tomcat latest fb5657adc892 2 months ago 680MB
centos latest 5d0da3dc9764 5 months ago 231MB
[root@jiangnan cmd]# docker run 94e05d54db92
.
..
.dockerenv
bin
dev
etc
......
usr
var
[root@jiangnan cmd]#
当我们运行容器,实际上执行了ls -a
的命令,就看到了3中的效果,如果我们想执行的是ls -al
该怎么做呢?理论上后面加上参数就可以,比如
[root@jiangnan cmd]# docker run cmd -l
docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "-l": executable file not found in $PATH: unknown.
[root@jiangnan cmd]#
问题:我们可以看到可执行文件找不到的报错,executable file not found。
之前我们说过,跟在镜像名后面的是 command,运行时会替换 CMD 的默认值。
因此这里的 -l 替换了原来的 CMD,而不是添加在原来的 ls -a 后面。而 -l 根本不是命令,所以自然找不到。
那么如果我们希望加入 -l 这参数,我们就必须重新完整的输入这个命令:
[root@jiangnan cmd]# docker run cmd ls -al
total 56
drwxr-xr-x 1 root root 4096 Feb 26 07:06 .
drwxr-xr-x 1 root root 4096 Feb 26 07:06 ..
-rwxr-xr-x 1 root root 0 Feb 26 07:06 .dockerenv
lrwxrwxrwx 1 root root 7 Nov 3 2020 bin -> usr/bin
..........
drwxr-xr-x 12 root root 4096 Sep 15 14:17 usr
drwxr-xr-x 20 root root 4096 Sep 15 14:17 var
[root@jiangnan cmd]#
ENTRYPOINT
[root@jiangnan entrypoint]# pwd
/home/entrypoint
[root@jiangnan entrypoint]# touch Dockerfile
[root@jiangnan entrypoint]# vim Dockerfile
[root@jiangnan entrypoint]# cat Dockerfile
FROM centos
ENTRYPOINT [ "ls", "-a" ]
[root@jiangnan entrypoint]#
[root@jiangnan entrypoint]# docker build -f Dockerfile -t entrypoint .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM centos
---> 5d0da3dc9764
Step 2/2 : ENTRYPOINT [ "ls", "-a" ]
---> Running in 07ae62691ce1
Removing intermediate container 07ae62691ce1
---> 7a01d22d4b6b
Successfully built 7a01d22d4b6b
Successfully tagged entrypoint:latest
[root@jiangnan entrypoint]#
[root@jiangnan entrypoint]# docker run entrypoint
.
..
.dockerenv
bin
dev
etc
......
tmp
usr
var
[root@jiangnan entrypoint]#
[root@jiangnan entrypoint]# docker run entrypoint -l
total 56
drwxr-xr-x 1 root root 4096 Feb 26 07:12 .
drwxr-xr-x 1 root root 4096 Feb 26 07:12 ..
-rwxr-xr-x 1 root root 0 Feb 26 07:12 .dockerenv
.......
drwxr-xr-x 20 root root 4096 Sep 15 14:17 var
[root@jiangnan entrypoint]#
和cmd中的测试一样,我们直接跟了
-l
,但是cmd报错,而ENTRYPOINT成功了。
上面的四步做完,只能算是单机版玩耍,而要实现多个环境使用,需要镜像发布。
我们这里演示将镜像发布到阿里云镜像仓库,首先要开通阿里云账号,创建命名空间和镜像仓库。
按照步骤做即可。
[root@jiangnan entrypoint]# docker login --username=xxxxxx registry.cn-beijing.aliyuncs.com
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
[root@jiangnan entrypoint]#
看到Login Succeeded说明登录成功了。
[root@jiangnan entrypoint]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
entrypoint latest 7a01d22d4b6b 18 minutes ago 231MB
cmdtest latest 94e05d54db92 30 minutes ago 231MB
tomcat latest fb5657adc892 2 months ago 680MB
centos latest 5d0da3dc9764 5 months ago 231MB
[root@jiangnan entrypoint]# docker tag 7a01d22d4b6b registry.cn-beijing.aliyuncs.com/xxc-study/mybuild:1.0
[root@jiangnan entrypoint]# docker push registry.cn-beijing.aliyuncs.com/xxc-study/mybuild:1.0
The push refers to repository [registry.cn-beijing.aliyuncs.com/xxc-study/mybuild]
74ddd0ec08fa: Pushed
1.0: digest: sha256:29a034073345d4e389b407042b84d4a6fd2392716a80196ed1d5a44526c67d73 size: 529
[root@jiangnan entrypoint]#