Docker使用手册(2)
四、Docker镜像讲解
1、镜像是什么?
镜像是一种轻量级、可执行的独立软件包。用来打包软件运行环境和基于环境开发的软件。它包括运行某个软件所需的所有内容包括代码,运行时库、环境变量和配置文件所有的应用直接打包Docker镜像,就可以直接跑起来。
如何得到镜像?
- 朋友给你
- 自己提交上传
- 镜像仓库下载
2、镜像的加载原理
UnionFS(联合文件系统)
联合文件系统是一种分层、轻量级并且高性能的文件系统。它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同的目录挂载到同一个虚拟文件系统下。联合文件系统是Docker镜像的基础,镜像可以通过分层来进行,继承基于基础镜像可以制作各种具体应用的镜像。
特性:一次同时加载多个文件系统,但从外面看起来只能看到一个文件系统,联合加载会把各种文件系统叠加起来,这样最终文件系统会包含所有底层的文件和目录。
Docker 镜像底层加载原理
docker的镜像实际上由一层一层的文件系统组成买这种层级的文件系统UnionFS。
bootfs(boot file system)主要包含bootloader的kernel,bootloader主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后,整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs(root file system),在bootfs之上。包含的就是典型Linux系统中的/dev, /proc, /bin, /etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等
平时我们安装虚拟机的CentOS都是好几个G,为什么docker这里才200M?
对于一个精简的OS,rootfs可以很小,只需要包含最基本的命令,工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的Linux发行版,bootfs会有差别,因此不同发行版可以用bootfs
3、分层理解
分层的镜像
我们可以去下载一个镜像,注意观察下载的日志输出,可以看到是一层一层的在下载
ubuntu@VM-0-13-ubuntu:/home$ sudo docker pull redis
Using default tag: latest
latest: Pulling from library/redis
6ec7b7d162b2: Already exists # 已经存在的就不在下载了
1f81a70aa4c8: Pull complete
968aa38ff012: Pull complete
884c313d5b0b: Pull complete
6e858785fea5: Pull complete
78bcc34f027b: Pull complete
Digest: sha256:0f724af268d0d3f5fb1d6b33fc22127ba5cbca2d58523b286ed3122db0dc5381
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest
为什么Docker镜像要采用这种分层的结构?
最大的好处,我觉得莫过于是资源共享了!比如有多个镜像都从相同的Base镜像构建而来,那么宿主机只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享
ubuntu@VM-0-13-ubuntu:/home$ sudo docker image inspect redis
[
{
#.....
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:87c8a1d8f54f3aa4e05569e8919397b65056aa71cdf48b7f061432c98475eee9",
"sha256:25075874ce886bd3adb3b75298622e6297c3f893e169f18703019e4abc8f13f0",
"sha256:caafc8119413c94f1e4b888128e2f337505fb57e217931a9b3a2cd7968340a9e",
"sha256:e5d940a579ec4a80b6ec8571cb0fecf640dba14ccfd6de352977fd379a254053",
"sha256:2a1c28c532d20c3b8af8634d72a4d276a67ce5acb6d186ac937c13bd6493c972",
"sha256:1540b8226044ed5ce19cc0fec7fbfb36a00bb15f4e882d6affbd147a48249574"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
理解:
所有的Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,假如基于Ubuntu Linux 16.04 创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加Python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。
该镜像当前已经包含了3个镜像,如下图所示(这知识一个用于演示的很简单的例子)
在添加 额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举一个简单的例子,每个镜像层包含3个文件,而镜像包含了来自两个镜像层的6个文件
上图中的镜像层个之前图中的略有区别,主要目的是便于展示文件。
下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有6个文件,这是因为最上层中的文件7是文件5的一个版本更新版本
这种情况下,上层镜像中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层到镜像当中。Docker通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为同意的文件系统。
Linux上可用的存储引擎有AUFS、Overlay2、Device Mapper、Btrfs以及ZFS。顾名思义,每种存储引擎都基于Linux中对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特点。
Docker在Windows上仅支持Windowsfilter一种存储引擎,该引擎基于NTFS文件系统之上实现了分层和CoW。
下图展示了与系统显示相同三层镜像。所有镜像层堆叠并合并,对外提供统一的视图
特点
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部!这一层就是我们通常说的容器层,容器之下都叫镜像层
4、commit 镜像
# 基于一个容器,来创建自己的一个镜像
docker commit -m="commit desc message" -a="image author name" <基于哪个容器id> 自定义镜像名称:[tags]
实战测试
# 拉取一个tomcat镜像
docker pull tomcat
# 发现这个tomcat默认是没有webapps的应用的(镜像的原因,官方默认webapps目录下是没有文件的)
[root@xiaoyequ ~] docker exec -it tomcat /bin/bash
oot@6ba1137fc95f:/usr/local/tomcat# ls
BUILDING.txt LICENSE README.md RUNNING.txt conf logs temp webapps.dist
CONTRIBUTING.md NOTICE RELEASE-NOTES bin lib native-jni-lib webapps work
root@6ba1137fc95f:/usr/local/tomcat# cp -r webapps.dist/* webapps # 复制文件到 webapps
root@6ba1137fc95f:/usr/local/tomcat# cd webapps
root@6ba1137fc95f:/usr/local/tomcat/webapps# ls
# 我自己拷贝进去了基本的文件
cp webapps.dist ./webapps
# 将我们操作过的容器commit为一个镜像,我们之后就使用修改过的
docker commmit -m "add webapps app" -a="lishanbiao" 7a23843156 tomcat01:1.0
# 测试
docker run -it -p 3344:8080 tomcat02:1.0
理解
如果你想要保存当前容器的状态,就可以通过commit命令来提交,相当于VM的快照
五、容器数据卷
1、什么是容器数据卷
问题1: 如果数据在容器中,那么我们容器删除之后,数据就会丢失;
需求1: 数据可以持久化;
问题2: 例如MySQL数据库容器化,当容器删除了,删库跑路,数据也就丢失了;
需求2: MySQL数据可以存储在本地;
宿主机和容器之间有一个数据共享的技术!Docker容器中产生的数据,同步到本地宿主机。
这就是卷技术,目录的挂载,将我们容器内的目录,挂载到Linux宿主机上面。
总结:容器的持久化和同步操作,容器间也是可以数据共享的。
2、使用数据卷
方式一:使用命令来挂载 -v
docker run -it -v 主机目录:容器目录
# 测试
docker run -it -v /home/ceshi:/home centos /bin/bash
# 启动后,我们通过 docker inspect 容器id 查看容器详细信息
docker inspect 824508c3232e
测试文件同步
总结:挂载后,主机与容器文件文件可以互相同步!
好处:我们以后修改文件信息,只需要在本地修改即可,文件内容会自动同步
3、实战:安装MySQL
docker pull mysql:5.7
docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/data -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
# 参数解析
-d:后台启动
-p:映射端口号
-v:挂载卷
--name:容器名字
我们创建完数据库后,假设我们把容器删除,我们的数据也不会有丢失,这就实现了数据的持久化功能!
4、具名挂载和匿名挂载
匿名挂载
# 匿名挂载 (-v 容器路径)
docker run -d -P --name nginx01 -v /etc/nginx nginx
# docker volumes ls
这里会发现这种没有指定主机路径的名字,成为匿名挂载。
具名挂载
# 匿名挂载 (-v 容器路径), ps:-v 后面不指定“/”的代表卷名
docker run -d -P --name nginx01 -v juming-nginx:/etc/nginx nginx
# docker volumes ls
DRIVER VOLUME NAME
local juming-nginx
# 查看一下这个卷在哪个位置
docker volumes inspect juming-nginx
这便是默认的挂载卷目录:
var/lib/docker/volumes/juming-nginx/_data
有时候我们会看到以下命令:docker run -d -😛 --name=nginx02 -v juming-nginx:/etc/nginx:ro nginx
或 docker run -d -😛 --name=nginx02 -v juming-nginx:/etc/nginx:rw nginx,这里的ro和rw代表读写权限,ro表示只读,rw表示读和写,对挂载出来的内容就进行了限制,ro表示只能通过宿主机进行改变,容器内无法操作。
总结:
- -v /宿主机路径:容器内路径 指定路径挂载
- -v 数据卷名:容器内路径 具名挂载
- -v 容器内路径 指定匿名挂载
5、数据卷容器
--volumes-from
结论:容器之间配置信息是同步互相双向自动传递的,数据卷容器的生命周期一直持续到没有容器使用为止。
6、初识Dockerfile
Dockerfile就是用来构建docker镜像的构建文件!命令脚本
通过下面这个脚本可以生成镜像,镜像是一层一层的,每个命令都是一层!
# 创建一个dockerfile文件,名字随机,建议Dockerfile
# 文件中的内容指令(大写) 参数
FORM centos
VOLUME ["volume01","volume02"]
CMD echo "---end---"
CMD /bin/bash
# 这里的每个命令,就是镜像的一层
测试使用Dockerfile
# 用dockerfile来构建镜像 docker build -f filePath -t 自定义镜像名:[tags] .
docker build -f /home/lishanbiao/test/dockerfile1 -t lishanbiao/centos:1.0 .
ps:最后有一个“.”,不能忘
六、DockerFile
构建步骤:
1、 编写一个dockerfile文件
2、 docker build 构建称为一个镜像
3、 docker run运行镜像
4、 docker push发布镜像(DockerHub 、阿里云仓库)
点击后跳到一个Dockerfile
很多官方镜像都是基础包,很多功能没有,我们通常会自己搭建自己的镜像
1、DockerFile构建过程
DockerFile基础知识
- 每个保留关键字(指令)都必须是大写字母。
- 执行顺序:从上到下顺序执行。
- # 表示注释
- 每一个指令都会创建提交一个新的镜像层,并提交。
DockerFile指令说明
- FROM 基础镜像,一切从这里开始构建
- MAINTAINER 镜像是谁写的,姓名+邮箱
- RUN 镜像构建的时候需要运行的命令
- ADD
- WORKDIR 镜像的工作目录
- VOLUME 挂载的目录
- EXPOSE 暴露端口配置
- CMD 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代。
- ENTRYPOINT 指定这个容器启动的时候要运行的命令,可以追加命令。
- ONBUILD 当构建一个被继承DockerFile,这个时候就会运行ONBUILD的指令,触发指令。
- COPY 类似ADD,将我们文件拷贝到镜像中。
- ENV 构建的时候设置环境变量。
2、centos测试
创建一个自己的centos
目标:docker官方提供的原生centos镜像是一个压缩版本的,里面甚至没有vim相关命令、ifconfig等net命令。因此我们要通过DockerFile来构建出自己的镜像。
vim mydockerfile-cntos
# 以下是mydockerfile-cntos内容:
FROM centos # 已原生镜像为基础
MAINTAINER lishanbiao<1336503209@qq.com> # 作者信息
ENV MYPATH /usr/local # 配置环境变量
WORKDIR $MYPATH # 指定工作目录
RUN yum -y install vim # 安装vim
RUN yum -y install net-tools # 安装net-tools
EXPOSE 80 # 暴露80端口
CMD echo $MYPATH
CMD echo "------end------"
CMD /bin/bash
# 编辑内容完毕,shift + :+ wq 退出后,构建自己的镜像
docker build -f mydockerfile-centos -t mycentos:0.1 .
测试运行
docker run -it mycentos /bin/bash
我们可以列出本地进行的变更历史
CMD 和 ENTRYPOINT区别
CMD # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代。
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
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
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中很多命令都十分的相似,我们需要了解它们的区别,我们最好的学习就是对比他们然后测试效果!
3、实战:Tomcat镜像
准备
tomcat 下载
wget -P ./ https://mirrors.cnnic.cn/apache/tomcat/tomcat-8/v8.5.64/bin/apache-tomcat-8.5.64.tar.gz
jdk 下载地址
wget -P ./ https://mirrors.cnnic.cn/AdoptOpenJDK/8/jdk/x64/linux/OpenJDK8U-jdk_x64_linux_hotspot_8u282b08.tar.gz
# 以上两个地址与我使用的版本不一样,勿抄
root@aliyunleo tomcat8source]# ll
total 191952
-rw-r--r-- 1 root root 11027411 Oct 15 11:44 apache-tomcat-9.0.33.tar.gz
-rw-r--r-- 1 root root 185516505 Oct 15 11:44 jdk-8u141-linux-x64.tar.gz
# 我们可以在当前目录下创建一个文件readme.txt
# 编辑后,查看自己的Dockerfile
[root@aliyunleo tomcat8source]# cat Dockerfile
FROM centos # 基础镜像
MAINTAINER leo<wei1986@126.com> # 作者信息
COPY readme.txt /usr/local/readme.txt # copy当前目录下的文件到容器
ADD jdk-8u141-linux-x64.tar.gz /usr/local/ # 加入文件,会自动解压
ADD apache-tomcat-9.0.33.tar.gz /usr/local/ # 加入文件,会自动解压
RUN yum -y install vim # 安装vim
ENV MYPATH /usr/local # 配置环境变量
WORKDIR $MYPATH # 指定工作目录
ENV JAVA_HOME /usr/local/jdk1.8.0_141 # 指定环境变量
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar # 指定环境变量
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.33 # 指定环境变量
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.33 # 指定环境变量
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin # 指定环境变量
EXPOSE 8080 # 暴露端口
CMD /usr/local/apache-tomcat-9.0.33/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.33/bin/logs/catalina.out # 执行命令(这里是启动tomcat,并查看日志)
执行build命令,构建镜像
# 这里不指定文件,回去当前目录下找到默认的DockerFile文件
docker build -t diytomcat:1.1 .
启动镜像
docker run -d -p 9090:8080 --name leotomcat -v /wwwroot/tomcat9/test:/usr/local/apache-tomcat-9.0.33/webapps/test -v /wwwroot/tomcat9/logs:/usr/local/apache-tomcat-9.0.33/logs diytomcat:1.1
# 命令行解析
# 后台启动
-d
# 主机9090映射docker的8080端口
-p 9090:8080
# 容器名
--name leotomcat
# 本地路径 /wwwroot/tomcat9/test 挂载到容器的 /usr/local/apache-tomcat-9.0.33/webapps/test
-v /wwwroot/tomcat9/test:/usr/local/apache-tomcat-9.0.33/webapps/test
# 镜像名
diytomcat
# 看到已经有容器启动了
[root@aliyunleo ~] docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
12d332ce5431 diytomcat "/bin/sh -c '/usr/lo…" 2 minutes ago Up 2 minutes 0.0.0.0:9090->8080/tcp leotomcat
进入镜像
docker exec -it 12d332ce5431 /bin/bash
[root@12d332ce5431 local]#
验证
curl localhost:9090
47.105.***.247:9090
由于做了卷挂载,在本地发布项目就可以了,不用到容器发布了
# 本地文件目录
[root@aliyunleo wwwroot]# tree
.
└── tomcat9
├── logs
│ ├── catalina.2021-03-11.log
└── test
├── index.jsp
└── WEB-INF
└── web.xml
[root@aliyunleo wwwroot]# pwd
/wwwroot
编写web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>FirstWebFontEnd</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
编写index.jsp
<html>
<head>
<title>第一个 JSP 程序</title>
</head>
<body>
<%
out.println("Hello World!");
%>
</body>
</html>
访问测试成功!
http://host:9090/test
4、发布自己的镜像
Docker Hub
- 地址:https://hub.docker.com/ 注册自己的账号
- 确定可以登陆
- 在我们的服务器上提交自己的镜像
docker push 自己的账户名/diytomcat:1.0 # 带上版本号,不然有可能被拒绝
为镜像打标签:
docker tag 镜像id 镜像名:[tags]
5、发布镜像到阿里云服务
参考官方文档,无脑又详细!