LOADING

Follow me

Docker问答录系列——Docker引擎相关问题(三)【zoues.com】
一月 4, 2017|DockerPaaS

Docker问答录系列——Docker引擎相关问题(三)【zoues.com】

Docker问答录系列——Docker引擎相关问题(三)【zoues.com】

本系列文章总结了一些初学Docker时比较常见问题的解决方法,解决思路大多遵循Docker官方的最佳实践的原则而进行的解答。文中内容绝对干货,强烈学习Docker的读者一读!本次主要介绍Docker引擎存储相关方面的一些问题。

  • 存储问题

容器磁盘可以限制配额么? 

对于 devicemapper, btrfs, zfs 来说,可以通过 –storage-opt size=100G 这种形式限制 rootfs 的大小。

docker create -it –storage-opt size=120G fedora /bin/bash

参考官网文档:https://docs.docker.com/engine/reference/commandline/run/#/set-storage-driver-options-per-container

容器内的数据该保存在镜像里还是物理机里? 

如果所谓数据是指运行时动态的数据,那么这部分数据文件不应该保存于镜像内。在运行时要保持容器基础文件不可变的特性,而变化部分使用挂载宿主目录,或者数据卷来解决。

建议看一下官网 docker volume 的文档:https://docs.docker.com/engine/tutorials/dockervolumes/

看到总说要保持容器无状态,那什么是无状态? 

这里说到的有两个层面的无状态:

  • 容器存储层的无状态 

这里提到的存储层是指用于存储镜像、容器各个层的存储,一般是 Union FS,如 AUFS,或者是使用块设备的一些机制(如 snapshot )进行模拟,如 devicemapper。

存储层不应该有任何文件变化,所有变化部分用卷进行持久化。由于卷的生存周期和容器不同,容器消亡重建,卷不会跟随消亡。所以容器可以随便删了重新run,而其挂载的卷则会保持之前的数据。

  • 服务层面的无状态 

使用卷持久化容器状态,虽然从存储层的角度看,是无状态的,但是从服务层面看,这个服务是有状态的。

从服务层面上说,也存在无状态服务。就是说服务本身不需要写入任何文件。比如前端 nginx,它不需要写入任何文件(日志走Docker日志驱动),中间的 php, node.js 等服务,可能也不需要本地存储,它们所需的数据都在 redis, mysql, mongodb 中了。这类服务,由于不需要卷,也不发生本地写操作,删除、重启、不保存自身状态,并不影响服务运行,它们都是无状态服务。这类服务由于不需要状态迁移,不需要分布式存储,因此它们的集群调度更方便。

之前没有 docker volume 的时候,有些人说 Docker 只可以支持无状态服务,原因就是只看到了存储层需求无状态,而没有 docker volume 的持久化解决方案。

现在这个说法已经不成立,服务可以有状态,状态持久化用 docker volume。

当服务可以有状态后,如果使用默认的 local 卷驱动,并且使用本地存储进行状态持久化的情况,单机服务、容器的再调度运行没有问题。但是顾名思义,使用本地存储的卷,只可以为当前主机提供持久化的存储,而无法跨主机。

但这只是使用默认的 local 驱动,并且使用 本地存储 而已。使用分布式/共享存储就可以解决跨主机的问题。docker volume 自然支持很多分布式存储的驱动,比如 flocker、glusterfs、ceph、ipfs 等等。常用的插件列表可以参考官方文档:https://docs.docker.com/engine/extend/legacy_plugins/#/volume-plugins

数据容器、数据卷、命名卷、匿名卷、挂载目录这些都有什么区别? 

首先,挂载分为挂载本地宿主目录,或者挂载数据卷,而数据卷又分为匿名数据卷和命名数据卷。

绑定宿主目录的概念很容易理解,就是将宿主目录绑定到容器中的某个目录位置。这样容器可以直接访问宿主目录的文件。其形式是

docker run -d -v /var/www:/app nginx

这里注意到 -v 的参数中,前半部分是绝对路径。在 docker run 中必须是绝对路径,而在 docker-compose 中,可以是相对路径。

另一种形式是使用 Docker Volume,也就是数据卷。这是很多看古董书的人不了解的概念,不要跟数据容器(Data Container)弄混。数据卷是 Docker 引擎维护的存储方式,使用 docker volume create 命令创建,可以利用卷驱动支持多种存储方案。其默认的驱动为 local,也就是本地卷驱动。本地驱动支持命名卷和匿名卷。

顾名思义,命名卷就是有名字的卷,使用 docker volume create –name xxx 形式创建并命名的卷;而匿名卷就是没名字的卷,一般是 docker run -v /data 这种不指定卷名的时候所产生,或者 Dockerfile 里面的定义直接使用的。

有名字的卷,在用过一次后,以后挂载容器的时候还可以使用,因为有名字可以指定。所以一般需要保存的数据使用命名卷保存。

而匿名卷则是随着容器建立而建立,随着容器消亡而淹没于卷列表中(对于 docker run 匿名卷不会被自动删除)。对于二代 Swarm 服务而言,匿名卷会随着服务删除而自动删除。 因此匿名卷只存放无关紧要的临时数据,随着容器消亡,这些数据将失去存在的意义。

此外,还有一个叫数据容器 (Data Volume) 的概念,也就是使用 –volumes-from 的东西。这早就不用了,如果看了书还在说这种方式,那说明书已经过时了。按照今天的理解,这类数据容器,无非就是挂了个匿名卷的容器罢了。

在 Dockerfile 中定义的挂载,是指 匿名数据卷。Dockerfile 中指定 VOLUME 的目的,只是为了将某个路径确定为卷。

我们知道,按照最佳实践的要求,不应该在容器存储层内进行数据写入操作,所有写入应该使用卷。如果定制镜像的时候,就可以确定某些目录会发生频繁大量的读写操作,那么为了避免在运行时由于用户疏忽而忘记指定卷,导致容器发生存储层写入的问题,就可以在 Dockerfile 中使用 VOLUME 来指定某些目录为匿名卷。这样即使用户忘记了指定卷,也不会产生不良的后果。

这个设置可以在运行时覆盖。通过 docker run 的 -v 参数或者 docker-compose.yml 的 volumes 指定。使用命名卷的好处是可以复用,其它容器可以通过这个命名数据卷的名字来指定挂载,共享其内容(不过要注意并发访问的竞争问题)。

比如,Dockerfile 中说 VOLUME /data,那么如果直接 docker run,其 /data 就会被挂载为匿名卷,向 /data 写入的操作不会写入到容器存储层,而是写入到了匿名卷中。但是如果运行时 docker run -v mydata:/data,这就覆盖了 /data 的挂载设置,要求将 /data 挂载到名为 mydata 的命名卷中。所以说 Dockerfile 中的 VOLUME 实际上是一层保险,确保镜像运行可以更好的遵循最佳实践,不向容器存储层内进行写入操作。

数据卷默认可能会保存于 /var/lib/docker/volumes,不过一般不需要、也不应该访问这个位置。

卷和挂载目录有什么区别? 

卷 (Docker Volume) 是受控存储,是由 Docker 引擎进行管理维护的。因此使用卷,你可以不必处理 uid、SELinux 等各种权限问题,Docker 引擎在建立卷时会自动添加安全规则,以及根据挂载点调整权限。并且可以统一列表、添加、删除。另外,除了本地卷外,还支持网络卷、分布式卷。

而挂载目录而挂载目录那就没人管了,属于用户自行维护。你就必须手动处理所有权限问题。特别是在 CentOS 上,很多人碰到 Permission Denied,就是因为没有使用卷,而是挂载目录,而且还对 SELinux 安全权限一无所知导致。

多个 Docker 容器之间共享数据怎么办?NFS ? 

如果是同一个宿主,那么可以绑定同一个数据卷,当然,程序上要处理好并发问题。

如果是不同宿主,则可以使用分布式数据卷驱动,让分布在不同宿主的容器都可以访问到的分布式存储的位置。如S3之类:

https://docs.docker.com/engine/extend/plugins/#volume-plugins

既然一个容器一个应用,那么我想在该容器中用计划任务 cron 怎么办? 

cron 其实是另一个服务了,所以应该另起一个容器来进行,如需访问该应用的数据文件,那么可以共享该应用的数据卷即可。而 cron 的容器中,cron 以前台运行即可。

比如,我们希望有个 python 脚本可以定时执行。那么可以这样构建这个容器。

首先基于 python 的镜像定制:

FROM python:3.5.2

ENV TZ=Asia/Shanghai

RUN apt-get update /

    && apt-get install -y cron /

    && apt-get autoremove -y

COPY ./cronpy /etc/cron.d/cronpy

CMD [“cron”, “-f”]

其中所提及的 cronpy 就是我们需要计划执行的 cron 脚本。

* * * * * root /app/task.py >> /var/log/task.log 2>&1

在这个计划中,我们希望定时执行 /app/task.py 文件,日志记录在 /var/log/task.log 中。这个 task.py 是一个非常简单的文件,其内容只是输出个时间而已。

#!/usr/local/bin/python

from datetime import datetime

print(“Cron job has run at {0} with environment variable “.format(str(datetime.now())))

这 task.py 可以在构建镜像时放进去,也可以挂载宿主目录。在这里,我以挂载宿主目录举例。

# 构建镜像

docker build -t cronjob:latest .

# 运行镜像

docker run /

    –name cronjob /

    -d /

    -v $(pwd)/task.py:/app/task.py /

    -v $(pwd)/log/:/var/log/ /

    cronjob:latest

需要注意的是,应该在构建主机上赋予 task.py 文件可执行权限。

如何初始化卷? 

卷(Volume),是用于动态数据持久化的。因此其内存储的都是动态数据,运行时会变化。如果这里面需要初始化里面的数据,需要在运行时进行。或者在镜像里加入初始化的脚本,比如 mysql 镜像中的初始化目录中的脚本;或者自己单独制作纯粹用于初始化卷用的镜像,单独一次性运行以将初始化数据灌入卷中。

举个例子来说,假设你需要个卷 mydata,然后里面需要有个 hello.txt 文件是必须存在的,否则容器运行就要出大事儿了……(这需求很傻我知道……😅好吧,假设如此)。

当然,我们得先有这个卷。

docker volume create –name mydata

那怎么把这个超重要的 hello.txt 文件放入卷中呢?有几种办法。

正常挂载该 mydata 卷,然后 docker cp 进去 

这是个很傻的办法,不过如果容器运行并不依赖于 hello.txt 的话,这样做是可以的。

$ docker run -d –name web -v mydata:/data nginx

$ docker cp ./hello.txt web:/data/

这样是先让容器启动,启动后,再把所需数据导入卷里面去。以后容器就可以使用 /data/hello.txt 文件了。

但是,如果容器是严重依赖于这个 hello.txt 文件的话,这样做就会出问题。容器会因为 hello.txt 文件不存在,而报错退出,导致根本没有 docker cp 的机会。

这种情况,我们可以变通一下。

$ docker run –rm /

    -v $PWD:/source /

    -v mydata:/data /

    busybox /

    cp /source/hello.txt /data/

$ docker run -d –name web -v mydata:/data nginx

这里我们先启动了一个 busybox 容器,分别挂载要复制的源以及目标的 mydata 卷,然后用 cp 命令将 hello.txt 复制到 mydata 中去。数据导入结束后,我们再正式挂载 mydata 卷到正式的容器上并启动。这个时候严重依赖 /data/hello.txt 的这个容器就可以顺利运行了。

专门制作初始化镜像 

手动的去执行 docker cp,或者 docker run … cp … 并不是很正规。可以写个脚本让一切都标准化,但是,除了流程外,还需要确保当前环境中的初始化数据的版本必须是所期望的,否则初始化了错误的数据,也会让运行时状态达不到预期的效果。

因此,另一种办法是专门制作一个初始化卷的镜像,这样的做法也比较方便在 CI/CD 流程中对初始化数据的过程进行测试确认。

FROM busybox

COPY hello.txt /source/

VOLUME /data

CMD [“cp”, “/source/hello.txt”, “/data/”]

这样的镜像只有一个生存目的,就是挂载 mydata 卷,并且把数据导入进去。假设构建好的镜像名为 volume-prepare,只需要执行下面的命令就可以完成导入:

$ docker run –rm -v mydata:/data volume-prepare

在镜像的 Dockerfile 制作中,加入初始化部分 

在之前的问答中我们已经了解到,官方镜像 mysql 中可以使用 Dockerfile 来添加初始化脚本,并且会在运行时判断是否为第一次运行,如果确实需要初始化,则执行定制的初始化脚本。

我们也可以使用这种方法将 hello.txt 在初始化的时候加入到 mydata 卷中去。

首先我们需要写一个进入点的脚本,用以确保在容器执行的时候都会运行,而这个脚本将判断是否需要数据初始化,并且进行初始化操作。

#!/bin/bash

# entrypoint.sh

if [ ! -f “/data/hello.txt” ]; then

    cp /source/hello.txt /data/

fi

exec “$@”

名为 entrypoint.sh 的这个脚本很简单,判断一下 /data/hello.txt 是否存在,如果不存在就需要初始化。初始化行为也很简单,将实现准备好的 /source/hello.txt 复制到 /data/ 目录中去,以完成初始化。程序的最后,将执行送入的命令。

我们可以这样写 Dockerfile:

FROM nginx

COPY hello.txt /source/

COPY entrypoint.sh /

VOLUME /data

ENTRYPOINT [“/entrypoint.sh”]

CMD [“nginx”, “-g”, “daemon off;”]

当我们构建镜像、启动容器后,就会发现 /data 目录下已经存在了 hello.txt 文件了,初始化成功了。

为什么说数据库不适合放在 Docker 容器里运行? 

不为什么,因为这个说法不对,大部分认为数据库必须放到容器外运行的人根本不知道 Docker Volume 为何物。

在早年 Docker 没有 Docker Volume 的时候,其数据持久化是一个问题,但是这已经很多年过去了。现在有 Docker Volume 解决持久化问题,从本地目录绑定、受控存储空间、块设备、网络存储到分布式存储,Docker Volume 都支持,不存在数据读写类的服务不适于运行于容器内的说法。

Docker 不是虚拟机,使用数据卷是直接向宿主写入文件,不存在性能损耗。而且卷的生存周期独立于容器,容器消亡卷不消亡,重新运行容器可以挂载指定命名卷,数据依然存在,也不存在无法持久化的问题。

建议去阅读一下官方文档:

https://docs.docker.com/engine/tutorials/dockervolumes/

https://docs.docker.com/engine/reference/commandline/volume_create/

https://docs.docker.com/engine/extend/legacy_plugins/#/volume-plugins

如何列出容器和所使用的卷的关系? 

要感谢强大的 Go Template,可以使用下面的命令来显示:

docker inspect –format ‘{{.Name}} => {{with .Mounts}}{{range .}}

    {{.Name}},{{end}}{{end}}’ $(docker ps -aq)

注意这里的换行和空格是有意如此的,这样就可以再返回结果控制缩进格式。其结果将是如下形式:

$ docker inspect –format ‘{{.Name}} => {{with .Mounts}}{{range .}}

    {{.Name}}{{end}}{{end}}’ $(docker ps -aq)

/device_api_1 =>

/device_dashboard-debug_1 =>

/device_redis_1 =>

    device_redis-data

/device_mongo_1 =>

    device_mongo-data

    61453e46c3409f42e938324d7feffc6aeb6b7ce16d2080566e3b128c910c9570

/prometheus_prometheus_1 =>

    fc0185ed3fc637295de810efaff7333e8ff2f6050d7f9368a22e19fb2c1e3c3f

来源:大桥下的蜗牛

原文:http://t.cn/RI8whG6

题图:来自谷歌图片搜索

版权:本文版权归原作者所有

Docker问答录系列——Docker引擎相关问题(三)


更多精彩热文:



Docker问答录系列——Docker引擎相关问题(三)

Docker问答录系列——Docker引擎相关问题(三)

Docker问答录系列——Docker引擎相关问题(三)

no comments
Share

发表评论