目录
- 1. 什么是存储卷?
- 2. 存储卷分类
- 3. 管理卷
- 3.1 方式一:Volume 命令操作
- 3.1.1 docker volume create命令
- 3.1.2 docker volume inspect命令
- 3.1.3 docker volume ls命令
- 3.1.4 docker volume rm命令
- 3.1.5 docker volume prune命令
- 3.2 方式二:-v 或者--mount 指定
- 3.2.1 -v 参数
- 3.2.2 --mount 参数
- 3.3 方式三:Dockerfile 匿名卷
- 3.4 操作案例
- 3.4.1 Docker 命令创建管理卷
- 3.4.2 Docker -v 创建管理卷
- 3.4.3 Docker mount 创建管理卷
- 3.4.4 Docker 卷的生命周期
- 3.4.5 Docker 卷的共享
- 4. 绑定卷
- 4.1 -v 参数创建卷
- 4.2 --mount 参数创建绑定卷
- 4.3 操作案例
- 4.3.1 mount 创建绑定卷
- 4.3.2 -v 创建绑定卷
- 4.3.3 绑定卷共享
- 5. 临时卷
- 5.1 方式一:指定--tmpfs 创建
- 5.2 方式二: --mount 指定参数创建
- 5.3 操作案例
- 5.3.1 tmpfs 参数创建临时卷
- 5.3.2 mount 创建临时卷
- 5.3.3 tmpfs 失踪了
- 6. 综合实战 -- MySQL 灾难恢复
- 7. 常见问题
- 8. 扩展思考:存储卷在实际研发中带来了哪些问题。
1. 什么是存储卷?
(1)存储卷就是将宿主机的本地文件系统中存在的某个目录直接与容器内部的文件系统上的某一目录建立绑定关系。这就意味着,当我们在容器中的这个目录下写入数据时,容器会将其内容直接写入到宿主机上与此容器建立了绑定关系的目录。
(2)在宿主机上的这个与容器形成绑定关系的目录被称作存储卷。卷的本质是文件或者目录,它可以绕过默认的联合文件系统,直接以文件或目录的形式存在于宿主机上。
(3)宿主机的/data/web 目录与容器中的/container/data/web 目录绑定关系,然后容器中的进程向这个目录中写数据时,是直接写在宿主机的目录上的,绕过容器文件统与宿主机的文件系统建立关联关系,使得可以在宿主机和容器内共享数据库内容,让容器直接访问宿主机中的内容,也可以宿主机向容器写入内容, 容器和宿主机的数据读写是同步的。
(4)生活例子来立即存储卷:
- 存储卷就相当于租了个地下室,建立对应的映射就相当于有了这个地下室的钥匙,能够使用房子以外的空间。
- 如果有一天龙卷风来了,房子被吹毁了,但是地下室依然安然无恙,就相当于容器销毁了我们的这个地下室依然没有影响。
(5)为什么需要存储卷?
-
数据丢失问题。容器按照业务类型,总体可以分为两类:
-
无状态的(数据不需要被持久化)。
-
有状态的(数据需要被持久化)。
-
显然,容器更擅长无状态应用。因为未持久化数据的容器根目录的生命周期与容器的生命周期一样,容器文件系统的本质是在镜像层上面创建的读写层,运行中的容器对任何文件的修改都存在于该读写层,当容器被删除时,容器中的读写层也会随之消失。
-
虽然容器希望所有的业务都尽量保持无状态,这样容器就可以开箱即用,并且可以任意调度,但实际业务总是有各种需要数据持久化的场景,比如 MySQL、 Kafka 等有状态的业务。因此为了解决有状态业务的需求, Docker 提出了卷(Volume)的概念。
-
-
性能问题:UnionFS 对于修改删除等,一般效率非常低,如果对一于 I/O 要求比较高的应用,如redis 在实现持化存储时,是在底层存储时的性能要求比较高。
-
宿主机和容器互访不方便:宿主机访问容器,或者容器访问要通过 docker cp 来完成,应用很难操作。
-
容器和容器共享不方便。
2. 存储卷分类
(1)目前 Docker 提供了三种方式将数据从宿主机挂载到容器中:
- volume docker 管理卷:默认映射到宿主机的/var/lib/docker/volumes 目录下, 只需要在容器内指定容器的挂载点是什么,而被绑定宿主机下的那个目录,是由容器引擎 daemon 自行创建一个空的目录,或者使用一个已经存在的目录,与存储卷建立存储关系,这种方式极大解脱用户在使用卷时的耦合关系,缺陷是用户无法指定那些使用目录,临时存储比较适合;
- bind mount 绑定数据卷:映射到宿主机指定路径下,在宿主机上的路径要人工的指定一个特定的路径, 在容器中也需要指定一个特定的路径, 两个已知的路径建立关联关系
- tmpfs mount 临时数据卷:映射到于宿主机内存中,一旦容器停止运行, tmpfsmounts 会被移除,数据就会丢失,用于高性能的临时数据存储。
3. 管理卷
创建卷存储卷可以通过命令方式创建,也可以在创建容器的时候通过 -v and --mount 指定。
3.1 方式一:Volume 命令操作
(1)命令清单如下:
命令 | 别名 | 功能 |
---|---|---|
docker volume create | 创建存储卷 | |
docker volume inspect | 显示存储卷详细信息 | |
docker volume ls | docker volume list | 列出存储卷 |
docker volume prune | 清理所有无用数据卷 | |
docker volume rm | 删除卷,使用中的无法删除 |
3.1.1 docker volume create命令
(1)功能:
- 创建存储卷。
(2)语法:
docker volume create [OPTIONS] [VOLUME]
(3)关键参数:
- -d, --driver:指定驱动,默认是 local。
- –label:指定元数据。
(4)样例:
docker volume create my-vol
3.1.2 docker volume inspect命令
(1)功能:
- 查看卷详细信息。
(2)语法:
docker volume inspect [OPTIONS] VOLUME [VOLUME...]
(3)关键参数:
- -f:指定相应个格式,如 json。
(4)样例:
docker volume inspect my-vol
3.1.3 docker volume ls命令
(1)功能:
- 列出卷。
(2)语法:
docker volume ls [OPTIONS]
(3)关键参数:
- –format:指定相应个格式,如 json、table。
- –filter,-f:过滤。
- -q:仅显示名称。
(4)样例:
docker volume ls
3.1.4 docker volume rm命令
(1)功能:
- 删除卷,需要容器不使用。
(2)语法:
docker volume rm [OPTIONS] VOLUME [VOLUME...]
(3)关键参数:
- -f,–force:强制删除。
(4)样例:
docker volume rm hello
3.1.5 docker volume prune命令
(1)功能:
- 删除不使用的本地卷
(2)语法:
docker volume prune [OPTIONS]
(3)关键参数:
- –filter:过滤。
- -f, --force:不提示是否删除。
(4)样例:
docker volume prune
3.2 方式二:-v 或者–mount 指定
3.2.1 -v 参数
(1)功能:
- 完成目录映射。
(2)语法:
docker run -v name:directory[:options] .........
(3)参数:
- 第一个参数:卷名称。
- 第二个参数:卷映射到容器的目录。
- 第三个参数:选项,如 ro 表示 readonly。
(4)样例:
docker run -d \
--name devtest \
-v myvol2:/app \
nginx:latest
(5)通过 docker inspect 可以看到:
"Mounts": [{"Type": "volume","Name": "myvol2","Source": "/var/lib/docker/volumes/myvol2/_data","Destination": "/app","Driver": "local","Mode": "","RW": true,"Propagation": ""}
],
3.2.2 --mount 参数
(1)功能:
- 完成目录映射
(2)语法:
--mount '<key>=<value>,<key>=<value>'
(3)关键参数:
- type:类型表示 bind, volume, or tmpfs。
- source,src:对于命名卷,这是卷的名称。对于匿名卷,省略此字段。
- destination,dst,target:文件或目录挂载在容器中的路径。
- ro,readonly:只读方式挂载完成目录映射。
(4)样例:采用 mount 创建 volume
docker run -d \
--name devtest \
--mount source=myvol2,target=/app \
nginx:latest
(5)通过 docker inspect 可以看到:
"Mounts": [{"Type": "volume","Name": "myvol2","Source": "/var/lib/docker/volumes/myvol2/_data","Destination": "/app","Driver": "local","Mode": "","RW": true,"Propagation": ""}
],
3.3 方式三:Dockerfile 匿名卷
(1)通过 Dockerfile 的 VOLUME 可以创建 docker 管理卷。这个我们在 Dockerfile中详细讲解。可以见博客《未完成》。
(2)我们也可以通过 dockerfile 的 VOLUME 指令在镜像中创建 Data Volume,这样只要通过该镜像创建的容器都会存在挂载点,但值得注意的是通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,而是由 docker 随机生成的。
3.4 操作案例
3.4.1 Docker 命令创建管理卷
(1)命令创建管理卷:
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker volume create test_volume
test_volume
docker container run --name nginx1 -d -p 80:80 -vtest_volume:/usr/share/nginx/html nginx:1.22.1
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker container run --name nginx1 -d -p 82:80 -vtest_volume:/usr/share/nginx/html nginx:1.22.1
cf56ecd6a4876a7be7ba09170c01698dc51ceb887d4faf423cf6cc4800b8ef65
(2)查看我们的 docker 管理卷:
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker volume ls
(3)我们查看其中一个管理卷,可以看到对应的宿主机目录:
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker volume inspect test_volume
(4)我们进入宿主机目录,可以放到容器里面的内容已经放到该目录了:
[xiaomaker@xiaomaker-virtual-machine:fs]$ cd /data/var/lib/docker/volumes/test_volume/_data
(5)查看容器内容,可以看到我们的具体的 mount 信息:
root@139-159-150-152:~# docker inspect nginx1
(6)我们修改 index.html:
cd /data/var/lib/docker/volumes/test_volume/_data
echo "Hello" > index.html
(7)通过浏览器查看:
可以看到宿主机和容器是同步的。
(8)停止容器释放空间:
docker stop nginx1
docker rm nginx1
(9)结论:宿主机和容器之间数据是同步的。
3.4.2 Docker -v 创建管理卷
(1)-v 创建管理卷,并且启动容器:
docker container run --name nginx2 -d -p 80:80 -vtest_volume2:/usr/share/nginx/html:ro nginx:1.22.1
(2)进入卷目录:
[xiaomaker@xiaomaker-virtual-machine:_data]$ docker inspect test_volume2
(3)修改 index.html:
[xiaomaker@xiaomaker-virtual-machine:_data]$ echo "Hello
from test_volume2" > index.html
root@139-159-150-[xiaomaker@xiaomaker-virtual-machine:_data]$ cat index.html Hello from test_volume2
(4)浏览器查看:
(5)容器中修改提示无法修改:
[xiaomaker@xiaomaker-virtual-machine:_data]$ docker exec -it nginx2 bash
(6)清理释放空间:
docker stop nginx2
docker rm nginx2
(7)结论:指定 ro 的话宿主机可以修改,但是容器里面无法修改
3.4.3 Docker mount 创建管理卷
(1)mount 创建管理卷,并且启动容器:
docker container run --name nginx3 -d -p 8081:80 --mount type=volume,source=test_volume3,target=/usr/share/nginx/html nginx:1.22.1
(2)查看卷创建成功:
[xiaomaker@xiaomaker-virtual-machine:~]$ docker volume ls
(3)查看卷挂载点:
[xiaomaker@xiaomaker-virtual-machine:~]$ docker volume inspect test_volume3
(4)浏览器查看:
(5)宿主机上修改文件内容:
[xiaomaker@xiaomaker-virtual-machine:_data]$ echo "Hello
bit volume by mount" > index.html
[xiaomaker@xiaomaker-virtual-machine:_data]$ cat index.html
Hello volume by mount
(6)查看页面:
(7)清理释放空间:
docker stop nginx3
docker rm nginx3
(8)结论: mount 创建的卷数据也会完成同步。
3.4.4 Docker 卷的生命周期
(1)-v 创建管理卷,并且启动容器:
docker container run --name nginx2 -d -p 80:80 -vtest_volume2:/usr/share/nginx/html nginx:1.22.1
(2)进入卷目录, 注意此时可以看到容器里面的内容自动的放到了宿主机里面,也就是说宿主机上没有容器会拷贝过去:
[xiaomaker@xiaomaker-virtual-machine:_data]$ docker inspect test_volume2
(3)修改 index.html:
[xiaomaker@xiaomaker-virtual-machine:_data]$ echo "Hello from test_volume2" > index.html
[xiaomaker@xiaomaker-virtual-machine:_data]$ cat index.html
Hello from test_volume2
(4)浏览器查看:
(5)清理释放空间:
docker stop nginx2
docker rm nginx2
(6)进入卷目录查看可以看到文件并没有被删除:
[xiaomaker@xiaomaker-virtual-machine:_data]$ cd /data/var/lib/docker/volumes/test_volume2/_data
[xiaomaker@xiaomaker-virtual-machine:_data]$ cat index.html
Hello from test_volume2
(7)删除卷,可以看到我们的卷被删除掉了:
[xiaomaker@xiaomaker-virtual-machine:_data]$ docker volume rm test_volume2
test_volume2
[xiaomaker@xiaomaker-virtual-machine:_data]$ ls /data/var/lib/docker/volumes/test_volume2/_data
ls: cannot access '/data/var/lib/docker/volumes/test_volume2/_data': No such file or directory
3.4.5 Docker 卷的共享
(1)-v 创建管理卷,并且启动 2 容器,指定同一个卷:
docker container run --name nginx2 -d -p 80:80 -vtest_volume2:/usr/share/nginx/html nginx:1.22.1
docker container run --name nginx3 -d -p 8080:80 -vtest_volume2:/usr/share/nginx/html nginx:1.22.1
(2)进入卷目录:
[xiaomaker@xiaomaker-virtual-machine:_data]$ docker inspect test_volume2
(3)修改 index.html:
[xiaomaker@xiaomaker-virtual-machine:_data]$ echo "Hello from share volume" > index.html
[xiaomaker@xiaomaker-virtual-machine:_data]$ cat index.html
Hello from share volume
(4)浏览器查看,可以看到两个都是一样的首页:
(5)清理释放空间:
docker stop nginx2 nginx3
docker rm nginx2 nginx3
4. 绑定卷
4.1 -v 参数创建卷
(1)功能:
- 完成卷映射。
(2)语法:
docker run -v name:directory[:options] .........
(3)参数:
- 第一个参数: 宿主机目录,这个和管理卷是不一样的。
- 第二个参数:卷映射到容器的目录。
- 第三个参数:选项,如 ro 表示 readonly。
(4)样例:
docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app \
nginx:latest
4.2 --mount 参数创建绑定卷
(1)功能:
- 完成目录映射
(2)语法:
--mount '<key>=<value>,<key>=<value>'
(3)关键参数:
- type:类型表示 bind, volume, or tmpfs
- source,src:宿主机目录,这个和管理卷是不一样的。
- destination,dst,target:文件或目录挂载在容器中的路径
- ro,readonly:只读方式挂载
(4)样例:
docker run -d \
-it \
--name devtest \
--mount type=bind,source="$(pwd)"/target,target=/app \
nginx:latest
4.3 操作案例
4.3.1 mount 创建绑定卷
(1)使用-mount 方式创建容器: 创建 nginx 容器,并将宿主机/webapp1 目录挂载至容器/usr/share/nginx/html 目录,注意如果 webapp1 目录不存在会启动报错:
#创建目录
[xiaomaker@xiaomaker-virtual-machine:fs]$ mkdir -p /data/myworkdir/fs/webapp1
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker run -d -p 80:80 --name bind1 --mount type=bind,source=/data/myworkdir/fs/webapp1,target=/usr/share/nginx/html nginx:1.22.1
8acce86ee0ae8e397e6824521113f1084de7890364efa2f1806d600c926a7a65
[xiaomaker@xiaomaker-virtual-machine:_data]$ docker run -d -p 80:80 --name bind1 --mount type=bind,source=/data/myworkdir/fs/webapp1_new,target=/usr/share/nginx/html nginx:1.22.1
docker: Error response from daemon: invalid mount config for type
"bind": bind source path does not exist:
/data/myworkdir/fs/webapp1_new.
See 'docker run --help'.
(2)查看挂载信息:
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker inspect bind1
(3)进入容器的终端,查看挂载点目录,和在宿主机上查看里面都是没有文件:
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker exec -it bind1 ls /usr/share/nginx/html
可以发现容器该目录本身存在的文件消失不见, 这是 bind mount 模式和 volume 模式最大的不同点。
(4)我们在宿主机上添加 index.html:
[xiaomaker@xiaomaker-virtual-machine:fs]$ cd /data/myworkdir/fs/webapp1
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ echo "Hello bind mount" >> index.html
(5)通过浏览器访问,可以看到,容器已经读取到宿主机的共享内容了。
(6)删除容器,然后查看宿主机上的文件,还是存在,说明容器删除并不影响 bind 映射。
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker stop bind1
bind1
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker rm bind1
bind1
(7)释放空间:
docker stop bind2
docker rm bind2
4.3.2 -v 创建绑定卷
(1)使用-v 方式创建容器: 创建 nginx 容器,并将宿主机/webapp2 目录挂载至容器/usr/share/nginx/html 目录,注意如果 webapp2 目录不存在,启动不会报错,这是-v 和–mount 方式的区别:
docker run -d -p 8080:80 --name bind2 -v /data/myworkdir/fs/webapp2:/usr/share/nginx/html nginx:1.22.1
(2)查看挂载信息:
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker inspect bind2
(3)进入容器的终端,查看挂载点目录,和在宿主机上查看里面都是没有文件:
[xiaomaker@xiaomaker-virtual-machine:fs]$ docker exec -it bind2 ls /usr/share/nginx/html
可以发现容器该目录本身存在的文件消失不见, 这是 bind mount 模式和 volume 模式最大的不同点
(4)我们在宿主机上添加 index.html:
[xiaomaker@xiaomaker-virtual-machine:fs]$ cd /data/myworkdir/fs/webapp2
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ echo "Hello bind2 mount" > index.html
(5)通过浏览器访问,可以看到,容器已经读取到宿主机的共享内容了。
(6)删除容器,然后查看宿主机上的文件,还是存在,说明容器删除并不影响 bind 映射。
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ docker stop bind2
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ docker rm bind2
4.3.3 绑定卷共享
(1)我们启动两个绑定卷,都绑定到宿主机的同一个目录:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker run -d -p 80:80 --name bind3 -v /data/myworkdir/fs/webapp1:/usr/share/nginx/html nginx:1.22.1
31e0ddec8d99b65a22f70c63ab6bd08effe345ad298cbadd886b8e432f8d3711
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker run -d -p 81:80 --name bind4 -v /data/myworkdir/fs/webapp1:/usr/share/nginx/html nginx:1.22.1
6b9676266200f77aa2a763449a0f27e394d2426c17458063c50e0ecdfdbcdd86
(2)我们访问者 2 个页面可以看到相应内容一样:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ curl 127.0.0.1:80 Hello bit bind mount
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ curl 127.0.0.1:81 Hello bit bind mount
(3)我们修改 index.html:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ echo "bind mount after edit" > index.html
(4)再次访问两个页面,可以看到我们实现了容器间的数据共享。
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ curl 127.0.0.1:80 bind mount after edit
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ curl 127.0.0.1:81 bind mount after edit
(5)清理空间:
docker stop bind3
docker rm bind3
docker stop bind4
docker rm bind4
5. 临时卷
(1)临时卷数据位于内存中,在容器和宿主机之外。临时卷(tmpfs)局限性:
- 不同于卷和绑定挂载,不能在容器之间共享 tmpfs 挂载。
- 这个功能只有在 Linux 上运行 Docker 时才可用。
5.1 方式一:指定–tmpfs 创建
(1)功能:
- 完成临时卷映射
(2)语法:
--tmpfs /app
(3)样例:
docker run -d \
-it \
--name tmptest \
--tmpfs /app \
nginx:1.22.1
5.2 方式二: --mount 指定参数创建
(1)功能:
- 完成目录映射
(2)语法:
--mount '<key>=<value>,<key>=<value>'
(3)关键参数:
- type:类型表示 bind, volume, or tmpfs
- destination,dst,target:挂载在容器中的路径
- tmpfs-size:tmpfs 挂载的大小(以字节为单位)。默认无限制。
- tmpfs-mode:tmpfs 的八进制文件模式。例如, 700 或 0770。默认为 1777或全局可写。
(4)样例:
docker run -d \
-it \
--name tmptest \
--mount type=tmpfs,destination=/app \
nginx:latest
5.3 操作案例
5.3.1 tmpfs 参数创建临时卷
(1)创建临时卷并启动容器:
docker container run --name tmpfs1 -d -p 80:80 --tmpfs /usr/share/nginx/html nginx:1.22.1
(2)进入容器可以看到 nginx 里面的文件被覆盖了,也就是说 tmpfs 也会覆盖容器里面的文件:
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ docker exec -it tmpfs1 bash
(3)添加一个首页:
[xiaomaker@xiaomaker-virtual-machine:html]$ echo "Hello from tmpfs" > index.html
[xiaomaker@xiaomaker-virtual-machine:html]$ cat index.html Hello from tmpfs
(4)浏览器查看:
(5)停止容器:
docker stop tmpfs1
(6)启动容器 再次进入,可以看到 tmpfs 的内容完全消失了,也就是说内容是存在内存里面的。
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ docker start tmpfs1
tmpfs1
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ docker exec -it tmpfs1 bash
root@1ed9f248b659:/# ls /usr/share/nginx/html/
root@1ed9f248b659:/#
(7)清理空间:
docker stop tmpfs1
docker rm tmpfs1
5.3.2 mount 创建临时卷
(1)创建临时卷并启动容器:
docker container run --name tmpfs1 -d -p 80:80 --mount type=tmpfs,destination=/usr/share/nginx/html,tmpfs-size=1m nginx:1.22.1
(2)进入容器可以看到 nginx 里面的文件被覆盖了,也就是说 tmpfs 也会覆盖容器里面的文件:
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ docker exec -it tmpfs1 bash
(3)添加一个首页:
[xiaomaker@xiaomaker-virtual-machine:html]$ echo "Hello from tmpfs" > index.html
[xiaomaker@xiaomaker-virtual-machine:html]$ cat index.html Hello from tmpfs
(4)浏览器查看:
(5)拷贝一个大文件到容器里面,dockerdemo-1.0-SNAPSHOT.jar 约 17M:
docker cp dockerdemo-1.0-SNAPSHOT.jar tmpfs1:/
(6)拷贝文件到我们的卷目录, 超过了限制,空间限制为了 1m,会提示没有空间。
[xiaomaker@xiaomaker-virtual-machine:myapp]$ docker exec -it tmpfs1 bash
(7)停止容器:
docker stop tmpfs1
(8)启动容器 再次进入,可以看到 tmpfs 的内容完全消失了,也就是说内容是存在内存里面的。
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ docker start tmpfs1
tmpfs1
[xiaomaker@xiaomaker-virtual-machine:webapp2]$ docker exec -it tmpfs1 bash
root@1ed9f248b659:/# ls /usr/share/nginx/html/
root@1ed9f248b659:/#
5.3.3 tmpfs 失踪了
(1)创建一个普通的容器:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker run -d -it --name tmptest nginx:1.22.1
d3a0da7d9881d9bf8f409e9241c96c9ece826379eda94c3f5cc46ba633356ec3
(2)在容器里面写入一个文件 mylabel.txt:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker exec -it tmptest bash
root@d3a0da7d9881:/# mkdir -p /app
root@d3a0da7d9881:/# echo 1 > /app/mylabel.txt
root@d3a0da7d9881:/# exit
exit
(3)我们在宿主机上查找文件,文件被找到了,是因为他在容器的可写层:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ find / -name mylabel.txt
/data/var/lib/docker/overlay2/09ff8b4eb1604296a6e67c80918639eb8f67
e7098bd1456551cb2cf858b647a8/merged/app/mylabel.txt
/data/var/lib/docker/overlay2/09ff8b4eb1604296a6e67c80918639eb8f67
e7098bd1456551cb2cf858b647a8/diff/app/mylabel.txt
(4)我们创建个临时卷:
docker run -d --name tmptest2 --tmpfs /app nginx:1.22.1
(5)进入容器在/app 目录下创建 mynewlabel.txt:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker exec -it tmptest2 bash
root@409c2f09b485:/# ls app
root@409c2f09b485:/# echo 222 > /app/mynewlabel.txt
root@409c2f09b485:/# ls /app
mynewlabel.txt
root@409c2f09b485:/# exit
exit
(6)在宿主机上查找 mynewlabel.txt,可以发现, 文件找不到:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ find / -name mynewlabel.txt
root@139-159-150-152:/data/myworkdir/fs/webapp1#
所以 tmpfs 的内容不是存储在我们的容器的可写层里面的。
(7)释放资源:
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker stop tmptest
tmptest
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker rm tmptest
tmptest
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker stop tmptest2
tmptest2
[xiaomaker@xiaomaker-virtual-machine:webapp1]$ docker rm tmptest2
tmptest2
6. 综合实战 – MySQL 灾难恢复
(1)使用 MySQL 5.7 的镜像创建容器并创建一个普通数据卷 mysql-data 用来保存容器中产生的数据。需要在容器中连接 MySQL 服务, 并创建数据库 test, 并在在该数据库中创建一个简单的表并插入一些数据进来。准备镜像:
[xiaomaker@xiaomaker-virtual-machine:~]$ docker pull mysql:5.7
(2)创建容器:
[xiaomaker@xiaomaker-virtual-machine:~]$ docker container run --name mysql-demo -e MYSQL_ROOT_PASSWORD=bi -itd -v /data/myworkdir/mysqldata:/var/lib/mysql mysql:5.7
c0d8fb5266b554661270cec0145389550a2455e4848023d531ad90bb4d670a04
-e 选项通过参数 MYSQL_ROOT_PASSWORD 来传递 MySQL 密码。
(3)查看容器挂载信息:
[xiaomaker@xiaomaker-virtual-machine:~]$ docker container inspect mysql-demo | grep "Mounts" -A 10
(4)连接 MySQL 的 shell, 创建数据库, 密码是 bi:
[xiaomaker@xiaomaker-virtual-machine:~]$ docker container exec -it mysql-demo /bin/bash
bash-4.2# mysql -u root -p
Enter password:
ERROR 1045 (28000): Access denied for user 'root'@'localhost'
(using password: YES)
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.41 MySQL Community Server (GPL)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current
input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
mysql> create database user;
Query OK, 1 row affected (0.00 sec)
mysql> use user
Database changed
mysql> create table student(sno char(3), sname varchar(10));
Query OK, 0 rows affected (0.01 sec)
mysql> insert into student values('1', 'zs'),('2', 'ls');
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> select * from student;
+------+-------+
| sno | sname |
+------+-------+
| 1 | zs |
| 2 | ls |
+------+-------+
2 rows in set (0.00 sec)
mysql> exit
Bye
创建了一个 user 数据库,并在该数据库中创建一个 student 表,在表中插入了两条数据。
(5)在宿主机中查看 volume:
[xiaomaker@xiaomaker-virtual-machine:~]$ ll /data/myworkdir/mysql-data/
我们可以看到容器中 MySQL 创建的数据库和表数据以及持久化到宿主机挂载的目录下了。
(6)有一天莫名其妙停电了, 然后服务器重启了,这个时候 Mysql 没有起来,然后有个哥们看磁盘空间不多了,把所有停止的容器都删除了。结果我们的 Mysql 也没有了。
docker stop mysql-demo
docker rm mysql-demo
(7)幸好我们的数据还在,这个时候怎么恢复呢,我们再次启动我们的运行命令,确保目录映射一致就能找回我们的数据了。
[xiaomaker@xiaomaker-virtual-machine:user]$ docker container run --name mysql-demo-new -e MYSQL_ROOT_PASSWORD=bite -itd -v /data/myworkdir/mysql-data:/var/lib/mysql mysql:5.7
59f87837af7815bd679f06efc95d1ad7420c260442a7318c994c103dee8e5d01
(8)我们通过 mysql 的客户端进去,再次查看我们的数据:
[xiaomaker@xiaomaker-virtual-machine:user]$ docker exec -it mysql-demo-new bash
bash-4.2# msyql
bash: msyql: command not found
bash-4.2# msyql -p
bash: msyql: command not found
bash-4.2# msyql -uroot -p
bash: msyql: command not found
bash-4.2# mysql -u root -p
Enter password:
...
验证可以知道我们的数据还是存在的!!!!
(9)释放空间:
docker stop mysql-demo-new
docker rm mysql-demo-new
7. 常见问题
(1)什么时候用 Volume,什么时候用 bind、 tmpfs?
- volume:volume 是 docker 的宿主机文件系统一部分,用于不需要规划具体目录的场景。
- bind: bind mount 完全是依赖于主机的目录结构和操作系统,用于目录需要提前规划,比如 mysql 的目录需要个空间大的,其他服务有不占用的时候,用 volume 就不太合适了。
- tmpfs:用于敏感文件存储,文件不想存储的宿主机和容器的可写层之中
8. 扩展思考:存储卷在实际研发中带来了哪些问题。
(1)跨主机使用:
- docker 存储卷是使用其所在的宿主机上的本地文件系统目录,也就是宿主机有一块磁盘,这块磁盘并没有共享给其他的 docker 主机,容器在这宿主机上停止或删除,是可以重新再创建的,但是不能调度到其他的主机上,这也是 docker 本身没有解决的问题,所以 docker 存储卷默认就是 docker 所在主机的本地,但是自己搭建一个共享的 NFS来存储 docker 存储的数据,也可以实现, 但是这个过程强依赖于运维人员的能力。
- 所以未来应用的存储和数据往往分离,越来越多的分布式存储方案出现,如 s3 系列,nfs 等。
(2)启动参数未知:
- 容器有一个问题,一般与进程的启动不太一样,就是容器启动时选项比较多,如果下次再启动时,很容器会忘记它启动时的选项,所以最好有一个文件来保存容器的启动,这就是容器编排工具的作用。
- 一般情况下,是使用命令来启动操作 docker,但是可以通过文件来读,也就读文件来启动,读所需要的存储卷等,但是它也只是操作一个容器,如果要几十上百个容器操作,就需要专业的容器编排工具这种一般像开源的 k8s,各个云厂商也有自己的企业版编排软件。
(3)复杂场景仍然需要运维:
- 对于有状态要持久的集群化组件,如 mysql 的主从。部署维护一个 Mysql 主从需要运维知识、经验整合进去才能实现所谓的部署,扩展或缩容,出现问题后修复,必须要了解集群的规模有多大,有多少个主节点,有多少个从节点,主节点上有多少个库,这些都要一清二楚,才能修复故障,这些就强依赖于运维经验这种复杂的场景往往还是需要人力,很难有完美的工具出现。