目录
Docker容器存放数据的2种方法:第一种是由 storage driver 管理的镜像层和容器层;第二种是 Data Volume。
1、概念介绍
Storage Driver
Docker 支持多种 storage driver,有 AUFS、Device Mapper、Btrfs、OverlayFS、VFS 和 ZFS。它们都能实现分层的架构,同时又有各自的特性。
优先使用Linux 发行版默认的 storage driver。Docker 安装时会根据当前系统的配置选择默认的 driver。默认 driver 具有最好的稳定性,因为默认 driver 在发行版上经过了严格的测试。
# 查看 docker storage driver
docker info
无状态容器 | 有状态容器 |
对于某些容器,直接将数据放在由storage driver 维护的层中是很好的选择,比如那些无状态的应用。无状态意味着容器没有需要持久化的数据,随时可以从镜像直接创建。 | 有持久化数据的需求,容器启动时需要加载已有的数据,容器销毁时希望保留产生的新数据,这类容器是有状态的。有状态容器需要使用Data Volume存储数据 |
比如无状态容器 busybox,它是一个工具箱,我们启动 busybox 是为了执行wget,ping 之类的命令,不需要保存数据供以后使用,使用完直接退出,容器删除时存放在容器层中的工作数据也一起被删除。
Data Volume
Union File System
Docker 镜像是由多个文件系统(只读层)叠加而成。当我们启动一个容器的时候,Docker会加载只读镜像层并在其上(即镜像栈顶部)添加一个读写层。如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏。当删除Docker容器,并通过该镜像重新启动时,之前的更改将会丢失。在Docker中,只读层及在顶部的读写层的组合被称为Union File System(联合文件系统)。
Data Volume
为了能够保存(持久化)数据以及共享容器间的数据,Docker提出了Volume的概念, 简单来说,Volume就是目录或者文件,它可以绕过默认的联合文件系统,而以正常的文件或者目录的形式存在于宿主机上。Data Volume 本质上是 Docker Host 文件系统中的目录或文件,直接被 mount 到容器的文件系统中。
Data Volume 特点:
- Data Volume 是目录或文件,而非没有格式化的磁盘(块设备)。
- 容器可以读写 volume 中的数据。
- volume 数据可以被永久的保存,即使使用它的容器已经销毁。
docker 提供了两种类型的 volume:bind mount & docker managed volume
2、bind mount
bind mount 是将 host 上已存在的目录或文件 mount 到容器。类似于 Linux 下对目录进行 mount。
① 清空容器
# 清空容器
systemctl restart docker
docker rm -v $(docker ps -aq -f status=exited)
- 重启docker会关闭所有运行着的容器,除非添加--restart=always参数
② 挂载文件到容器
# 准备要挂载到容器的文件
mkdir /root/html
echo "hello apache" > html/index.html
# 把文件挂载到httpd容器网站根目录
docker run -p 80:80 \
-v /root/html/index.html:/usr/local/apache2/htdocs/index.html -d httpd
- -v /host/path:/container/path,如果容器上的路径不存在,目录自动在给定的路径中创建
③ 修改容器卷里的数据
echo "nihao123" >> /root/html/index.html
④ 删除容器,数据依然在宿主机目录中
# 删除容器
docker rm -f fff56a33bd5902
# 查看数据
cat /root/html/index.html
指定挂载文件只读权限
bind mount 时还可以指定数据的读写权限,默认是可读可写,可指定为只读
docker run -it -p 80:80 \
-v /root/html/index.html:/usr/local/apache2/htdocs/index.html:ro httpd /bin/bash
bind mount 挂载目录
docker run -d -p 80:80 -v /root/html:/usr/local/apache2/htdocs:ro httpd
3、docker manage volume
使用bind mount必须要知道宿主机的挂载目录,限制了容器的可移植性,当需要将容器迁移到其他host,而该 host 没有要 mount 的数据或者数据不在相同的路径时,操作会失败,移植性更好的方式是docker managed volume。
docker managed volume 与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明容器挂载点就行了。
# 使用docker managed volume
docker run -d -p 81:80 -v /usr/local/apache2/htdocs httpd
# 查看宿主机挂载目录
docker inspect -f {{.Mounts}} c64d72a996394a
每当容器申请mount docker managed volume 时,docker 在/var/lib/docker/volumes下生成一个目录,这个目录就是 mount 源。
# 修改文件内容
echo "hello linux" >> /var/lib/docker/volumes/d3d2fc39c2c249694bb68e34dbe5f61f1b0cff9b203cbe6c843e976a26be0ab7/_data//index.html
查看 volume
① 查看 volume:docker volume ls
docker volume inspect d3d2fc39c2c249694bb68e34dbe5f61f1b0cff9b203cbe6c843e976a26be0ab7
自定义 volume
语法:docker volume create --name volume_name
docker volume create --name data
# 使用自定义 volume
docker run -dit --name busybox1 -v data:/volume busybox
docker inspect -f {{.Mounts}} busybox1
使用 NFS 存储
① 服务端安装 nfs
yum install -y nfs-utils rpcbind
mkdir -p /data/nfs/docker
echo "/data/nfs *(rw,no_root_squash,sync)">>/etc/exports
systemctl start rpcbind nfs-server
systemctl enable rpcbind nfs-server
showmount -e localhost
② 客户端使用 nfs
yum install -y nfs-utils rpcbind
systemctl start rpcbind nfs-server
systemctl enable rpcbind nfs-server
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=192.168.137.110,rw \
--opt device=:/data/nfs \
volume-nfs
③ 容器使用 volume-nfs
docker run -dit --name busybox2 -v volume-nfs:/nfs busybox
docker inspect -f {{.Mounts}} busybox2
4、共享数据
容器与host共享数据 | 我们有两种类型的data volume,它们均可实现在容器与 host 之间共享数据,但方式有所区别。 对于bind mount :直接将要共享的目录 mount 到容器。 对于docker managed volume 就要麻烦点。由于 volume 位于 host 中的目录,是在容器启动时才生成,所以需要将共享数据拷贝到 volume 中。 |
容器之间共享数据 | 将共享数据放在bind mount 中,然后将其mount到多个容器 |
# 启动3个httpd挂载相同的volume
docker run -d -p 82:80 -v /root/html:/usr/local/apache2/htdocs httpd
docker run -d -p 83:80 -v /root/html:/usr/local/apache2/htdocs httpd
docker run -d -p 84:80 -v /root/html:/usr/local/apache2/htdocs httpd
volume container
volume container 是专门为其他容器提供 volume 的容器。它提供的卷可以是 bind mount,也可以是 docker managed volume。
① 创建 volume container
docker create --name vc_data \
-v /root/html:/usr/local/apache2/htdocs \
-v /data/tools busybox
docker inspect -f {{.Mounts}} vc_data
② 容器挂载 vc_data
docker run -d --name web1 -p 85:80 --volumes-from vc_data httpd
docker run -d --name web2 -p 86:80 --volumes-from vc_data httpd
docker run -d --name web3 -p 87:80 --volumes-from vc_data httpd
# 查看web1目录挂载
docker inspect -f {{.Mounts}} web1
③ 测试修改数据
data-packed volume container
volume container 的数据归根到底还是在host里, data-packed volume container可以将数据完全放到volume container 中,同时又能与其他容器共享。
① 使用 Dockerfile 构建镜像
cd /root/html/
vim Dockerfile
-----------------Dockerfile start ----------
FROM busybox
ADD index.html /usr/local/apache2/htdocs/
VOLUME /usr/local/apache2/htdocs/
-----------------Dockerfile end ------------
# 构建镜像
docker build -t datapacked .
② 创建 data-packed volume container
docker create --name vc_data1 datapacked
docker ps -a -f status=created
③ 启动 httpd 容器
docker run -p 88:80 --name web4 --volumes-from vc_data1 -d httpd
容器能够正确读取 volume 中的数据。data-packed volume container 是自包含的,不依赖 host 提供数据,具有很强的移植性,非常适合只使用静态数据的场景,比如应用的配置信息、web server 的静态文件等。
5、Volume 的声明周期
Data Volume 中存放的是重要的应用数据, volume 生命周期管理包含 volume 的创建、共享和使用、备份、恢复、迁移和销毁 volume。
备份 volume | 因为 volume 实际上是 host 文件系统中的目录和文件,所以 volume 的备份实际上是对文件系统的备份。 |
恢复 volume | volume 的恢复也很简单,如果数据损坏了,直接用之前备份的数据拷贝到对应目录就可以了。 |
迁移 volume | docker stop 当前容器 启动新版本容器并mount 原有volume,在启用新容器前要确保新版本的默认数据路径是否发生变化。 |
销毁 volume | 可以删除不再需要的 volume,但一定要确保知道自己正在做什么,volume 删除后数据是找不回来的。 docker不会销毁 bind mount,删除数据的工作只能由 host 负责。 对于 docker managed volume,在执行 docker rm 删除容器时可以带上-v参数,docker 会将容器使用到的 volume 一并删除,但前提是没有其他容器 mount 该 volume,目的是保护数据。 如果删除容器时没有带-v参数, 这样就会产生孤儿volume,可以使用docker volume rm进行删除 |
# 清空所有容器
systemctl restart docker
docker rm -v $(docker ps -aq -f status=exited)
docker rm -v $(docker ps -aq -f status=created)
# 查看孤儿volume
docker volume ls
# 批量删除孤儿 volume
docker volume rm $(docker volume ls -qf dangling=true)
6、Supervisor 进程管理工具
Docker 容器在启动的时候可以开启单个进程,比如,一个 ssh 或者 apache 的 daemon 服务。但我们经常也需要在一个容器上开启多个服务,这可以有很多方法,最简单的就是把多个启动命令放到一个启动脚本里面,启动的时候直接启动这个脚本。
① 编写 Dockerfile
mkdir httpd && cd httpd
vim Dockerfile
---------------------Dockerfile start ----------------------------------------
FROM centos:7
MAINTAINER from zhenglincong
#install supervisor
RUN yum install -y wget && wget -O /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo && yum install -y supervisor
#install ssh and apache
RUN yum install -q -y openssh-server httpd sudo
RUN useradd admin
RUN echo "admin:admin" | chpasswd
RUN echo "admin ALL=(ALL) ALL" >> /etc/sudoers
RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key
RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
RUN mkdir -p /var/run/sshd
RUN mkdir -p /home/admin/.ssh
RUN sed -ri 's/session reqired pam_loginuid.so/#session requied pam_loginuid.so/g' /etc/pam.d/sshd
RUN sed -i -r 's/^(.*pam_nologin.so)/#\1/' /etc/pam.d/sshd #记得添加
RUN sed -ri 's/#ServerName www.example.com:80/ServerName www.exaple.com/g' /etc/httpd/conf/httpd.conf
ADD authorized_keys /home/admin/.ssh/authorized_keys
COPY supervisord.conf /etc/supervisord.conf
EXPOSE 22 80
CMD ["/usr/bin/supervisord"]
---------------------Dockerfile end ----------------------------------------
② 生成ssh秘钥对,并创建authorized_keys文件
ssh-keygen -t rsa
cat /root/.ssh/id_rsa.pub > /root/httpd/authorized_keys
③ supervisor配置文件内容
vim /root/httpd/supervisord.conf
----------------------------supervisord.conf-------------------
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd
[program:httpd]
command=/usr/sbin/httpd
----------------------------supervisord.conf-------------------
- 第一段supervsord 配置软件本身,使用 nodaemon 参数来运行。nodaemon=true 如果是true,supervisord进程将在前台运行,默认为false,也就是后台以守护进程运行。
- 下面2段包含我们要控制的 2 个服务。每一段包含一个服务的目录和启动这个服务的命令
④ 构建镜像
docker build -t centos7.9_ssh_httpd_supervisord .
# 启动容器
docker run -d -p 1022:22 -p 80:80 centos7.9_ssh_httpd_supervisord
docker exec -it c6a21549f3b1 /bin/bash
ps -axf
①②③④⑤⑥⑦⑧⑨⑩