5-Docker镜像构建
5-Docker镜像构建
docker commit
docker run -it ubuntu # -it 参数的作用是以交互模式进入容器,并打开终
exit #退出容器
docker ps -a #列出所有的container
docker ps -l #列出最后一次启动的container
NAMES 是docker为容器随机分配的名字
docker commit brave_einstein ubuntu-with-vi #将容器brave_einstein保存为镜像 镜像名字为 ubuntu-with-vi
Dockerfile
在当前目录创建文件#vim dockerfile 文件内容如下
FROM centos
RUN cd /etc/yum.repos.d/
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
RUN yum update -y
RUN yum clean all
RUN yum makecache
EXPOSE 80
RUN yum -y install vim
RUN yum -y install net-tools
自2022年1月31日起,CentOS团队从官方镜像中移除CentOS 8的所有包,但软件包仍在官方镜像上保留一段时间。现在被转移到https://vault.centos.org。如需继续运行旧CentOS 8,可以在/etc/yum.repos中更新repos.d,使用vault.centos.org代替mirror.centos.org
sudo sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
sudo sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
运行 docker build 命令,-t 将新镜像命名为 centos-with-test-dockerfile
docker history centos-with-test-dockerfile . #-t重命名 .表示当前目录
Sending build context to Docker daemon 32.26 kB ④ #docker将 build context 发送给daemon
#中间就是按照写的顺序执行
Step 10/10 : RUN yum -y install net-tools
---> Running in 49da877d6e87
#启动 ID 为 49da877d6e87 的临时容器,在容器中通过 apt-get 安装net-tools
Complete!
Removing intermediate container 49da877d6e87 #删除临时容器
---> 7657afa4abf3
Successfully built 7657afa4abf3 安装 成功其 ID 为 7657afa4abf3
创建过程中出现的第一个id为基础镜像id 第二个为执行命令的临时容器id 最后一个为创建成功的容器id
可以通过docker history+镜像名字 查看创建过程
docker history 也向我们展示了镜像的分层结构,每一层由上至下排列。
docker history centos-with-test-dockerfile
镜像分层结构
Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的
FROM debain #1
RUN apt-get install emacs #2
RUN apt-get install apache2 #3
CMD [“/bin/bash”] #4
新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
安装 emacs 编辑器。
安装 apache2。
容器启动时运行 bash。
分层最大的好处就是共享资源 当容器运行的时候 内存中只需要加载一份 base镜像就可以为所有容器服务
磁盘中也只保存一份base镜像就可以了
每个容器内的修改会限制在容器内 当一个容器修改了/etc文件时 其他容器的不会更改
Copy-on-Write 特性
当容器启动时,一个新的可写层被加载到镜像的顶部。
这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。
对镜像所有的修改都只会发生在新添加的容器层内
- 只有容器层是可写的,容器层下面的所有镜像层都是只读的
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
- 添加文件
在容器中创建文件时,新文件被添加到容器层中
- 读取文件
在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,打开并读入内存。
- 修改文件
在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
- 删除文件
在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改 同时镜像可以被多个容器共享
镜像的缓存技术
镜像的缓存特性
Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。
比如上一个镜像是 centos-with-test-dockerfile
RFOM centos
...
本地镜像中已经有了centos镜像 就不会再去下载而是直接使用,在centos镜像的基础上在执行后面的内容
RFOM centos
RUN touch test
COPY testfile /
[root@MyMachine ~]# docker build -t centos-docker-test3 .
Sending build context to Docker daemon 18.43kB
Step 1/3 : FROM centos
---> 5d0da3dc9764
Step 2/3 : RUN touch test
---> Running in 039c08b73f93
Removing intermediate container 039c08b73f93
---> 8103cb5fd082
Step 3/3 : COPY testfile /
COPY failed: file not found in build context or excluded by .dockerignore: stat testfile: file does not exist
[root@MyMachine ~]#
#docker build –no-cache 构建的时候不使用缓存
Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的
只要某一层发生变化 其上面所有层缓存都失效
即使改变dockerfile文档里面语句的顺序 缓存也会失效。
除了构建时候需要缓存 docker下载镜像的时候同样需要
docker pull 命令输出显示第一层(base 镜像)已经存在,不需要下载。
会显示 already exitsts
也可以通过docker history验证这个信息
dockerfile命令
FROM #基础镜像,一切从这里开始
MAINTAINER #镜像是谁写的,姓名+邮箱
RUN #镜像构建的时候需要执行的命令
ADD #添加内容,
WORKDIR #镜像的工作目录
VOLUME #卷挂载的目录
EXPOSE #暴漏端口配置,与docker run -p 宿主机端口:容器内端口 效果一样
CMD #指定这个容器启动的时候要运行的命令,只有最后一个会生效
ENTRYPOINT #指令这个容器启动的时候执行的命令
ONBUILD #当构建一个被继承的DockerFile时,会运行ONBUILD的指令,触发指令
COPY #类似于ADD,将我们文件拷贝到镜像中
ENV #构建的时候设置环境变量
初始DockerFile
构建步骤
1. 编写一个dockerFile文件
2.docker build 构建成为一个镜像
3. docker run 运行镜像
4. docker push 发布镜像(DockerHub、阿里云镜像)
#
每个保留关键字(指令)都是必须大写字母
执行从上到下顺序执行
# 表示注释
每个指令都会创建提交一个新的镜像层,并提交!
# 创建一个dockerfile文件, 名字可以随机
# 文件的内容 指定(大写) 参数
FROM centos
VOLUME ["volume01", "volume02"] #匿名挂载
CMD echo "----end----"
CMD /bin/bash
# 这里的每一个命令都是镜像的一层!
[root@MyMachine ~]# docker build -f dockerfile -t test/centos1.0 .
Sending build context to Docker daemon 87.55kB
Step 1/4 : FROM centos
---> 5d0da3dc9764
Step 2/4 : VOLUME ["volume01", "volume02"]
---> Running in 078200b78112
Removing intermediate container 078200b78112
---> 06e2b87f5512
Step 3/4 : CMD echo "----end----"
---> Running in 3791edc49723
Removing intermediate container 3791edc49723
---> 8e3b48e75e5c
Step 4/4 : CMD /bin/bash
---> Running in 9e04335f2ad5
Removing intermediate container 9e04335f2ad5
---> 63f2cfec005a
Successfully built 63f2cfec005a
Successfully tagged test/centos1.0:latest
[root@MyMachine ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/centos1.0 latest 63f2cfec005a 48 seconds ago 231MB
启动自己的容器
[root@MyMachine ~]# docker run -it test/centos1.0 /bin/bash
[root@c9b1c83be32c /]# ls -a
. bin home lost+found opt run sys var
.. dev lib media proc sbin tmp volume01
.dockerenv etc lib64 mnt root srv usr volume02
[root@c9b1c83be32c /]#
volume01 volume02 是挂载的目录
[root@MyMachine /]# docker inspect c9b1c83be32c #"Mounts" 会显示挂载目录的宿主机目录路径
"Mounts": [
{
"Type": "volume",
"Name": "5f75cc516a695fda504a3cd73b5c457d90ef720101b70e4c0f47de9eae007b70",
"Source": "/var/lib/docker/volumes/5f75cc516a695fda504a3cd73b5c457d90ef720101b70e4c0f47de9eae007b70/_data",
"Destination": "volume01",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "d015280192a1fb9eed4aff30f138efb8bc2fd40973b9942b6fe7ce90a43fe886",
"Source": "/var/lib/docker/volumes/d015280192a1fb9eed4aff30f138efb8bc2fd40973b9942b6fe7ce90a43fe886/_data",
"Destination": "volume02",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
# 创建一个dockerfile文件,名字可以随机,建议 dockerfile
[root@localhost docker-test-volume]# vim dockerfile
# 文件中的内容:指令(大写) 参数
FROM centos
VOLUME ["volume01","volume02"] #匿名挂载
CMD echo"----end----"
CMD /bin/bash
# 这里的每个命令,就是镜像的一层!
注意:我们这里的 dockerfile 是我们编写的文件名
这两个卷和外部一定有两个同步的目录!
查看一下卷挂载在主机上的路径
docker inspect 容器id
这种方式我们未来使用十分的多,因为我们通常会构建自己的镜像!
假设构建镜像的时候没有挂在卷,要手动镜像挂载即可: (参考上文具名和匿名挂载)
-v 卷名:容器内路径
数据卷容器
多个mysql同步数据
在docker03下创建docker03文件后,进入docker01发现也依旧会同步过来:
# 测试1:删除docker01后,docker02和docker03是否还可以访问原来docker01下创建的的文件 依旧可以访问
# 测试2:删除docker01后,docker02和docker03之间依然 可以完成同步
数据共享
docker run -it --name docker01 test/centos1.0
[root@MyMachine ~]# docker run -it --name docker01 test/centos1.0
[root@85c027f10258 /]# ls
bin home lost+found opt run sys var
dev lib media proc sbin tmp volume01
etc lib64 mnt root srv usr volume02
[root@85c027f10258 /]# echo "hahaha" >volume01/test #共享目录中写入一个文件
[root@85c027f10258 /]#
docker run -it --name docker02 --volumes-from docker01 test/centos1.0 #与docker01共享目录
[root@MyMachine /]# docker run -it --name docker02 --volumes-from docker01 test/centos1.0
[root@e47d2ca07444 /]# ls
bin home lost+found opt run sys var
dev lib media proc sbin tmp volume01
etc lib64 mnt root srv usr volume02
[root@e47d2ca07444 /]# cat volume01/test #可以直接看到docker01写入的内容
hahaha
[root@e47d2ca07444 /]#
多个mysql实现数据共享
[root@MyMachine ~]# docker run -d -p 3344:3306 -v /etc/mysql/conf.d -v /var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
[root@MyMachine ~]# docker run -d -p 3344:3306 -v /etc/mysql/conf.d -v /var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7 #from实现与上一个数据同步
FROM # 基础镜像,一切从这里开始构建
MAINTAINER # 镜像是谁写的, 姓名+邮箱
RUN # 镜像构建的时候需要运行的命令
ADD # 步骤, tomcat镜像, 这个tomcat压缩包!添加内容
WORKDIR # 镜像的工作目录
VOLUME # 挂载的目录
EXPOSE # 保留端口配置
CMD # 指定这个容器启动的时候要运行的命令,只有最后一个会生效可被替代
ENTRYPOINT # 指定这个容器启动的时候要运行的命令, 可以追加命令
ONBUILD # 当构建一个被继承DockerFile 这个时候就会运行 ONBUILD 的指令,触发指令
COPY # 类似ADD, 将我们文件拷贝到镜像中
ENV # 构建的时候设置环境变量!
容器之间的配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止。
但是 持久化到了本地,本地的数据是不会删除的
编写一个centos dockerfile
基础知识:**
1、每个保留关键字(指令)都是必须是大写字母
2、执行从上到下顺序
3、# 表示注释
4、每一个指令都会创建提交一个新的镜像层,并提交
dockerfile
FROM # 基础镜像,一切从这里开始构建
MAINTAINER # 镜像是谁写的,姓名+邮箱
RUN # 镜像构建的时候需要运行的命令
ADD # 步骤:tomcat镜像,这个tomcat压缩包! 添加内容
WORKDIR # 镜像的工作目录
VOLUME # 挂载的目录
EXPOSE # 暴露端口配置,跟 -p 是一个道理
CMD # 指定这个容器启动时要执行的命令,只有最后一个命令会生效,可悲替代
ENTRYPOINT # 指定这个容器启动的时候要执行的命令,可以追加命令
ONBUILD # 当构建一个被继承DockerFile 这个时候就会运行ONBUILD的指令。触发指令
COPY # 类似ADD,将我们文件拷贝到镜像中
ENV # 构建的时候设置环境变量,跟 -e 是一个意思
# CMD 和 ENTRYPOINT 的区别说明:(后面也会介绍)
# 若CMD 和 ENTRYPOINT 后跟的都是 ls -a 这个命令,当docker run 一个容器时,添加了 -l 选项,则CMD里的ls -a 命令就会被替换成-l;而ENTRYPOINT中的 ls -a会追加-l变成 ls -a -l
创建一个自己的centos7dockerfile
# 1. 编写Dockerfile的文件
[root@MyMachine ~]# cat mydockerfile-centos
FROM centos:7
MAINTAINER xiaohong<7758991@qq.com>
ENV MYPATH /usr/local #定义变量
WORKDIR $MYPATH # 镜像的工作目录 /usr/local
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash
# 2. 通过这个文件构建镜像
# 命令 docker build -f dockerfile文件路径 -t 镜像名:[tag] .
[root@MyMachine ~]# docker build -f mydockerfile-centos -t mycentos:0.1 .
Successfully built d2d9f0ea8cb2
Successfully tagged mycentos:0.1
对比:
之前的原生的centos
我们增加之后的镜像
我们可以列出本地镜像的变更历史:
调试dockerfile
RUN、CMD 和 ENTRYPOINT 这三个 Dockerfile 指令看上去很类似
-
RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
-
CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参数替换。如果docker镜像启动成容器时有命令 CMD这个命令会被覆盖掉 run后面必须是完整的命令 命令参数无法直接直接传递给CMD
-
ENTRYPOINT 配置容器启动时运行的命令 也就是构建好的镜像 镜像启动成容器时执行的命令 可以在启动容器时追加命令参数到dockerfile中的命令
相当于 会 把 docker run 后 面 的 参 数 当 作 传 递 给
ENTRYPOINT 指令的参数。
测试CMD
# 编写dockerfile文件
$ vim dockerfile-test-cmd
FROM centos
CMD ["ls","-a"]
# 构建镜像
$ docker build -f dockerfile-test-cmd -t cmd-test:0.1 .
# 运行镜像
$ docker run cmd-test:0.1
.
..
.dockerenv
bin
dev
# 想追加一个命令 -l 成为ls -al
$ docker run cmd-test:0.1 -l #这里-l报错
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\":
executable file not found in $PATH": unknown.
ERRO[0000] error waiting for container: context canceled
# cmd的情况下 -l 替换了CMD["ls","-l"]。 -l 不是命令,所以报错
测试ENTRYPOINT
# 编写dockerfile文件
$ vim dockerfile-test-entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]
$ docker run entrypoint-test:0.1
.
..
.dockerenv
bin
dev
etc
home
lib
lib64
lost+found ...
# 我们的命令,是直接拼接在我们的ENTRYPOINT命令后面的
$ docker run entrypoint-test:0.1 -l #这里-l不报错
total 56
drwxr-xr-x 1 root root 4096 May 16 06:32 .
drwxr-xr-x 1 root root 4096 May 16 06:32 ..
-rwxr-xr-x 1 root root 0 May 16 06:32 .dockerenv
lrwxrwxrwx 1 root root 7 May 11 2019 bin -> usr/bin
drwxr-xr-x 5 root root 340 May 16 06:32 dev
drwxr-xr-x 1 root root 4096 May 16 06:32 etc
drwxr-xr-x 2 root root 4096 May 11 2019 home
lrwxrwxrwx 1 root root 7 May 11 2019 lib -> usr/lib
lrwxrwxrwx 1 root root 9 May 11 2019 lib64 -> usr/lib64 ....
- 编写Dockerfile文件,官方命名: Dockerfile ,build会自动寻找这个文件,就不要 -f 指定了
FROM centos:7
MAINTAINER JIYUANQIAOHE<123456@qq.com>
COPY readme.txt /usr/local/readme.txt#宿主机目录需要先下 touch readme.txt
ADD jdk-8u161-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-8.0.53.tar.gz /usr/local
RUN yum -y install vim
ENV MYPATH /usr/local
WORKDIR $MYPATH
ENV JAVA_HOME /usr/local/jdk1.8.0_161
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-8.0.53
ENV CATALINA_BASH /usr/local/apache-tomcat-8.0.53
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
EXPOSE 8080
CMD /usr/local/apache-tomcat-8.0.53/bin/startup.sh && tail -F /usr/local/apache-tomcat-8.0.53/bin/logs/catalina.out
-
构建镜像
# docker build -t diytomcat . diytomcat是定义的镜像名
-
启动镜像,创建容
# docker run -d -p 9090:8080 --name kuangshentomcat02
-v /home/build/tomcat/test:/usr/local/apache-tomcat-8.0.53/webapps/test
-v /home/build/tomcat/tomcatlogs/:/usr/local/apache-tomcat-8.0.53/logs diytomcat
- 发布项目(由于做了卷挂载,我们就可以直接在本地发布项目了)
在/home/build/tomcat/test目录下创建WEB-INF目录,在里面创建web.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
</web-app>
#在回到test目录,添加一个index.jsp页面:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello kuangshen</title>vim
</head>
<body>
Hello World!<br/>
<%
System.out.println("---my test web logs---");
%>
</body>
</html>
进入/home/build/tomcat/tomcatlogs/目录下就可以看到日志信息了:
[root@localhost tomcatlogs]# cat catalina.out
shell格式和exec格式
dockfile文件,以shell格式编写
Shell格式底层会调用/bin/sh -c来执行命令,可以解析变量
RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"
当指令执行时,shell 格式 底层会调用 /bin/sh -c <command>。
执行下面片段
ENV name heyman
ENTRYPOINT echo "Hello, $name"
执行 docker run <image> 将输出:
Hello, heyman
环境变量 name 已经被值 heyman 替换。
dockfile文件,以exec格式编写
exec格式需要指定使用什么脚本来执行 不会直接解析变量
FROM centos:7
CMD [ "echo", "$HOME" ]
#以上会输出$HOME
FROM centos:7
CMD ["sh", "-c", "echo $HOME"]
#以上会输出当前目录 如果是root用户登录的 会输出/root
*CMD 和 ENTRYPOINT 推荐使用 Exec 格式*,因为指令可读性更强,更容易理解。RUN 则两种格式都可以。