Docker 基础与实战指南(3)
2.2 数据卷:
容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部,我们要读写容器内的文件非常不方便。大家思考几个问题:
如果要升级 MySQL 版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
MySQL、Nginx 容器运行后,如果我要修改其中的某些配置该怎么办?
我想要让 Nginx 代理我的静态资源怎么办?
因此,容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦。
2.2.1 数据卷简介:
数据卷(volume)是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。
以 Nginx 为例,我们知道 Nginx 中有两个关键的目录:
html:放置一些静态资源
conf:放置配置文件
如果我们要让 Nginx 代理我们的静态资源,最好是放到html目录;如果我们要修改 Nginx 的配置,最好是找到conf下的nginx.conf文件。
但遗憾的是,容器运行的 Nginx 所有的文件都在容器内部。所以我们必须利用数据卷将两个目录与宿主机目录关联,方便我们操作。如图:
在上图中:
我们创建了两个数据卷:conf、html
Nginx 容器内部的conf目录和html目录分别与两个数据卷关联。
而数据卷 conf 和 html 分别指向了宿主机的/var/lib/docker/volumes/conf/_data目录和/var/lib/docker/volumes/html/_data目录
这样以来,容器内的conf和html目录就与宿主机的conf和html目录关联起来,我们称为挂载。此时,我们操作宿主机的/var/lib/docker/volumes/html/_data就是在操作容器内的/usr/share/nginx/html/_data目录。只要我们将静态资源放入宿主机对应目录,就可以被Nginx代理了。
/var/lib/docker/volumes这个目录就是默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为/数据卷名/_data。
2.2.2 数据卷命令:
数据卷的相关命令有:
# 1.首先创建容器并指定数据卷,注意通过 -v 参数来指定数据卷 docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx # 2.然后查看数据卷 docker volume ls # 结果 DRIVER VOLUME NAME local 29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f local html # 3.查看数据卷详情 docker volume inspect html # 结果 [ { "CreatedAt": "2024-05-17T19:57:08+08:00", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/html/_data", "Name": "html", "Options": null, "Scope": "local" } ] # 4.查看/var/lib/docker/volumes/html/_data目录 ll /var/lib/docker/volumes/html/_data # 可以看到与nginx的html目录内容一样,结果如下: 总用量 8 -rw-r--r--. 1 root root 497 12月 28 2021 50x.html -rw-r--r--. 1 root root 615 12月 28 2021 index.html # 5.进入该目录,并随意修改index.html内容 cd /var/lib/docker/volumes/html/_data vi index.html # 6.打开页面,查看效果 # 7.进入容器内部,查看/usr/share/nginx/html目录内的文件是否变化 docker exec -it nginx bash
# 1.查看MySQL容器详细信息 docker inspect mysql # 关注其中.Config.Volumes部分和.Mounts部分
{ "Config": { // ... 略 "Volumes": { "/var/lib/mysql": {} } // ... 略 } }
{ "Mounts": [ { "Type": "volume", "Name": "29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f", "Source": "/var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data", "Destination": "/var/lib/mysql", "Driver": "local", } ] }
可以发现,其中有几个关键属性:
Name:数据卷名称。由于定义容器未设置容器名,这里的就是匿名卷自动生成的名字,一串hash值。
Source:宿主机目录
Destination : 容器内的目录
上述配置是将容器内的/var/lib/mysql这个目录(这个目录主要用来放置 MYSQL 存储的数据,也就是说,我们平时的数据库表都是存放在这个目录下面),与数据卷29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f挂载。于是在宿主机中就有了/var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data这个目录。这就是匿名数据卷对应的目录,其使用方式与普通数据卷没有差别。
那么为什么 Mysql 要把存储的数据目录挂载出来呢?
这是因为,随着程序的运行,MYSQL 存储的数据越来越多,容器也会越来越大,将来如果要对数据进行迁移会非常的麻烦。如果把数据挂载出来,后续如果 MYSQL 需要升级,卸载了之后(数据卷不会被卸载掉),可以通过 Mysql 存储目录挂载数据卷,这样数据就都恢复了。
2.2.3 挂载本地目录或文件:
可以发现,数据卷的目录结构较深,如果我们去操作数据卷目录会不太方便。在很多情况下,我们会直接将容器目录与宿主机指定目录挂载。挂载语法与数据卷类似:
# 挂载本地目录 -v 本地目录:容器内目录 # 挂载本地文件 -v 本地文件:容器内文件
-v mysql:/var/lib/mysql # 会被识别为一个数据卷叫mysql,运行时会自动创建这个数据卷 -v ./mysql:/var/lib/mysql # 会被识别为当前目录下的mysql目录,运行时如果不存在会创建目录
2.3 镜像:
前面我们一直在使用别人准备好的镜像,那如果我要部署一个 Java 项目,把它打包为一个镜像该怎么做呢?
2.3.1 镜像结构:
要想自己构建镜像,必须先了解镜像的结构。
之前我们说过,镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。
因此,自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
举个例子,我们要从 0 部署一个 Java 应用,大概流程是这样:
准备一个linux服务(CentOS或者Ubuntu均可)
安装并配置JDK
上传Jar包
运行jar包
那因此,我们打包镜像也是分成这么几步:
准备 Linux 运行环境(java 项目并不需要完整的操作系统,仅仅是基础运行环境即可)
安装并配置 JDK
拷贝 jar 包
配置启动脚本
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer(层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
例如,第一步中需要的Linux运行环境,通用性就很强,所以Docker官方就制作了这样的只包含Linux运行环境的镜像。我们在制作java镜像时,就无需重复制作,直接使用Docker官方提供的CentOS或Ubuntu镜像作为基础镜像。然后再搭建其它层即可,这样逐层搭建,最终整个Java项目的镜像结构如图所示:
2.3.2 Dockerfile:
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可。
而这种记录镜像结构的文件就称为Dockerfile,其对应的语法可以参考官方文档:
https://docs.docker.com/reference/dockerfile/
其中的语法比较多,比较常用的有:
例如,要基于 Ubuntu 镜像来构建一个 Java 应用,其 Dockerfile 内容如下:
# 指定基础镜像 FROM ubuntu:16.04 # 配置环境变量,JDK的安装目录、容器内时区 ENV JAVA_DIR=/usr/local ENV TZ=Asia/Shanghai # 拷贝jdk和java项目的包 COPY ./jdk8.tar.gz $JAVA_DIR/ COPY ./docker-demo.jar /tmp/app.jar # 设定时区 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 安装JDK RUN cd $JAVA_DIR \ && tar -xf ./jdk8.tar.gz \ && mv ./jdk1.8.0_144 ./java8 # 配置环境变量 ENV JAVA_HOME=$JAVA_DIR/java8 ENV PATH=$PATH:$JAVA_HOME/bin # 指定项目监听的端口 EXPOSE 8080 # 入口,java项目的启动命令 ENTRYPOINT ["java", "-jar", "/app.jar"]
以后我们会有很多很多 java 项目需要打包为镜像,他们都需要 Linux 系统环境、JDK 环境这两层,只有上面的 3 层不同(因为 jar 包不同)。如果每次制作 java 镜像都重复制作前两层镜像,是不是很麻烦。
所以,就有人提供了基础的系统加 JDK 环境,我们在此基础上制作 java 镜像,就可以省去 JDK 的配置了(需要注意的是 openjdk:11.0-jre-buster 这个基础镜像需要自己提前下载,这里上传不了文件,所以只能友友自己去网上找了):
# 基础镜像 FROM openjdk:11.0-jre-buster # 设定时区 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 拷贝jar包 COPY docker-demo.jar /app.jar # 入口 ENTRYPOINT ["java", "-jar", "/app.jar"]
2.3.3 构建镜像:
# 进入镜像目录 cd /root/demo # 开始构建 docker build -t docker-demo:1.0 .
命令说明:
docker build : 就是构建一个 docker 镜像。
-t docker-demo:1.0 :-t参数是指定镜像的名称(repository和tag)
. : 最后的点所在位置是指,构建时 Dockerfile 所在路径。.代表当前目录,也可以直接指定 Dockerfile 目录。
# 直接指定Dockerfile目录 docker build -t docker-demo:1.0 /root/demo
本文系转载,版权归原作者所有,如若侵权请联系我们进行删除!
云掣基于多年在运维领域的丰富时间经验,编写了《云运维服务白皮书》,欢迎大家互相交流学习:
《云运维服务白皮书》下载地址:https://fs80.cn/v2kbbq
想了解更多大数据运维托管服务、数据库运维托管服务、应用系统运维托管服务的的客户,欢迎点击云掣官网沟通咨询:https://yunche.pro/?t=shequ