GVKun编程网logo

Docker 持久存储介绍(十三)(docker持久化存储)

18

如果您对Docker持久存储介绍(十三)和docker持久化存储感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解Docker持久存储介绍(十三)的各种细节,并对docker持久化存储进行深入的分

如果您对Docker 持久存储介绍(十三)docker持久化存储感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解Docker 持久存储介绍(十三)的各种细节,并对docker持久化存储进行深入的分析,此外还有关于167 docker docker构建nginx容器系列问题 docker registry docker run docker toolbo、18、docker的持久化存储和数据共享、Docker (十三)-Docker save and load 镜像保存、Docker in Docker(实际上是 Docker outside Docker): /var/run/docker.sock的实用技巧。

本文目录一览:

Docker 持久存储介绍(十三)(docker持久化存储)

Docker 持久存储介绍(十三)(docker持久化存储)

[TOC]

一、Docker 数据存储

我们都知道 Docker 的数据可以存在容器的可写层,但是也存在以下几点不足:

  1. 当该容器不再运行时,数据将不会持久存储,如果另一个进程需要它,就很难将数据从容器中取出。
  2. 容器的可写层与 Docker Host 在容器运行时紧密耦合,你不能轻易地把数据移到别的地方。
  3. 写入容器的可写层需要一个 storage driver 来管理。storage driver 使用 Linux 内核提供一个文件系统。与使用直接写入宿主文件系统的 volume 相比,这种额外的抽象降低了性能。

Docker 提供了三种不同的方式将数据从 Docker Host 挂载到 Docker 容器,并实现数据的读取和存储:volumes、bind mounts、tmpfs 。

无论我们选择使用哪种类型的挂载,数据从容器中看起来都一样的,它在容器的文件系统中作为目录或单个文件展示。

我们可以通过数据存储在 Docker Host 的方式来简单的了解这三种挂载方式的不同,如下图:

  • Volumes 存储在 Docker Host 文件系统的一个路径下,这个路径是由 Docker 来进行管理,路径默认是 /var/lib/docker/volumes/,非 Docker 的进程不能去修改这个路径下面的文件,所以说 Volumes 是持久存储数据最好的一种方式。
  • Bind mounts 可以存储在 Docker Host 文件系统的任何位置,它们甚至可能是重要的系统文件或目录,非 Docker 的进程或者 Docker 容器可能随时对其进行修改,存在潜在的安全风险。
  • Tmpfs 只存储在 Docker Host 的系统内存中,不会写入到系统的文件系统中,不会持久存储。

所有说使用 volumes 是我们非常推荐的一种方式。

二、Bind mount

1、详细介绍

相对于 volume,bind mount 具有有限的功能。我们使用 bind mount 时,host 上的文件或目录被挂载到容器中。挂载时需要我们指定文件或目录在 host 上的完整路径。

bind mount 是非常高效的,但它依赖 host 的文件系统的目录结构。如果打算部署新的 Docker 应用,我们可以考虑使用 volume 而命名,不然你不能使用 Docker CLI 命令直接管理 bind mount。

使用 bind mount 的一个缺点是,我们可以通过在容器中运行的进程更改 host 的文件系统,包括创建、修改或删除重要的系统文件或目录。这会严重影响系统的安全,甚至影响 host 上面非 Docker 的进程。

2、如何使用

之前我们使用 bind mount 可以使用-v或者--volume,这个参数在单容器的情况下使用,在 swarm 集群中使用--mount,从 Docker 17.06 之后,我们可以统一使用参数--mount

对于新接触 Docker 的我们来说建议使用--mount,老司机可以继续使用-v,但是我们还是建议使用--mount

-v or --volume 语法

它有三部分组成,使用:进行分割,这些字段必须以正确的顺序排列,并且每个字段的含义不明显。

  • 第一个字段是 Docker Host 上的一个文件或者目录。
  • 第二个字段是将要挂载到容器上的一个文件或者目录。
  • 第三个字段是可选的,用来增加一些附加选项,比如 ro,consistent,delegated,cached,z,and Z。

--mount 语法

它由一组键值对组成,由,进行分割,每个值为 <key>=<value>

Key Value
type bind、volume、tmpfs,如不指定,默认是 volume
source/src Docker Host 上的一个文件或者目录
destination/dst/target 被挂载容器上的一个文件或者目录
readonly 没有参数,只写这个词即可
bind-propagation rprivate、private、rshared、shared、rslave、slave
consistency consistent、delegated、cached,只在 Mac 系统上生效

两者区别

使用-v的时候,如果在 Docker Host 不存在要挂载的文件或者目录,Docker 将会自动进行创建,通常是一个目录。 使用--mount的时候,如果在 Docker Host 不存在要挂载的文件或者目录,Docker 不会自动创建目录,并生成一个错误。

3、使用场景

  1. 把 host 中的配置文件共享给 host 上面的容器。容器为什么自带 DNS 解析呢,那是因为默认情况下 host 把 /etc/resolv.conf 挂载到它上面的容器里面。
  2. 在 Docker Host 上面的开发环境和容器直接共享程序的源代码或者构建要素。例如,你可以挂载一个 Maven 目录到一个容器中,每当你在 Docker Host 重新建立 maven 项目,容器都可以直接获取你重新构建的 maven 项目。
  3. 我们可以将源代码目录 mount 到容器中,在 host 中修改代码就能看到应用的实时效果。
  4. 将 mysql 容器的数据放在 bind mount 里,这样 host 可以方便地备份和迁移数据。
  5. 只需要向容器添加文件,不希望覆盖整个目录。

4、使用案例

存在目录 bind mount

比如我们想把 Docker Host 的目录source/html/挂载到 nginx 容器的/usr/share/nginx/html/,我们对 html 目录的更改希望可以立刻在容器 html 目录生效,这样我们就可以非常方便的修改网页的文件了。

docker run -d \
  -it \
  --name devtest \
  -p 80:80 \
  --mount type=bind,source="$(pwd)"/html,target=/usr/share/nginx/html/ \
  nginx:latest

使用命令docker inspect devtest来查看挂载是否正确挂载。

        "Mounts": [
            {
                "Type": "bind",
                "Source": "/root/sample/html",
                "Destination": "/usr/share/nginx/html",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

从里面可以出这是一个 bind mount,并且是只读挂载。

我们在 html 目录下创建一个 index.html,并且写入内容为“This is a bind mount test!”。并且访问本地的 80 端口查看结果。

从这里例子之中我们可以看出如果我们挂载到容器的目录中有文件,文件会被我们的源地址文件进行覆盖。

我们把容器销毁掉,查看一下我们创建的文件是否还存在。

docker stop devtest
docker rm devtest

我们发现文件还是存在的,可见,即使容器没有了,bind mount 也还在。这也合理,bind mount 是 host 文件系统中的数据,只是借给容器用用,哪能随便就删了啊。

只读挂载

另外,bind mount 时还可以指定数据的读写权限,默认是可读可写,可指定为只读:

docker run -d \
  -it \
  --name devtest \
  -p 80:80 \
  --mount type=bind,source="$(pwd)"/html,target=/usr/share/nginx/html/,readonly\
  nginx:latest

查看挂载详情,看看是不是只读模式。

        "Mounts": [
            {
                "Type": "bind",
                "Source": "/root/sample/html",
                "Destination": "/usr/share/nginx/html",
                "Mode": "",
                "RW": false,
                "Propagation": "rprivate"
            }
        ],

我们命令docker exec -ti devtest bash进入到容器内部,修改文件测试一下。

readonly 设置了只读权限,在容器中是无法对 bind mount 数据进行修改的。只有 host 有权修改数据,提高了安全性。

单文件挂载

除了制定目录外,我们也可以指定单个文件进行覆盖,如下:

docker run -d \
  -it \
  --name devtest \
  -p 80:80 \
  --mount type=bind,source="$(pwd)"/html/index.html,target=/usr/share/nginx/html/index.html \
  nginx:latest

三、Volume

1、详细介绍

Volume 完全由 Docker 来进行管理,比如 volume 的创建,我们可以使用命令 docker volume create 来简单的创建一个 volume,当容器或者服务创建的时候,Docker 也可以自动的创建一个 volume。

当我们创建了一个 volume,它存储在 Docker Host 的存储目录下。当我们把 volume 挂载入容器时,此目录就是挂载到容器中的目录。这类似于 bind mount 的工作方式,不同的是 volume 是由 Docker 来管理并且和 Docker Host 的核心功能进行隔离。

一个给定的 volume 可以同时挂载到多个容器中。当没有容器的使用 volume 时, volume 对 Docker 仍然是可用的并且不会被自动删除。我们可以使用命令docker volume prune来删除一个已经不使用的 volume。

我们在挂载 volume 时,可以对其命名,也可以是默认随机生成的名字。如果我们没有指定名称,当 volume 第一次挂载到一个容器时,Docker 会用一个随机字符串对其进行命名,这样可以保证 volume 在 Docker Host 的唯一性。

Volume 还支持使用 volume drivers,它允许您将数据存储挂载到远程主机或云提供商上等。

Volumes 对比 bind mounts 具备以下几点有点:

  • Volumes 的备份和迁移更加容易。
  • 可以使用 Docker CLI 或者 Docker API 管理 volumes。
  • Volumes 既可以在 Linux 的容器中使用,也可以在 Windows 的容器中使用。
  • Volumes 在多容器中共享更加的安全。
  • Volume drivers 允许我们把数据存储在远程主或云提供商。

2、使用语法

我们推荐使用--mount,所有这里我们只写它的使用方法。

Key Value
type bind、volume、tmpfs ,如不指定,默认是 volume
source/src Docker Host 上的一个文件或者目录
destination/dst/target 被挂载容器上的一个文件或者目录
readonly 没有参数,只写这个词即可
volume-opt 可以指定更多的附加参数

3、使用场景

使用容器技术,volume 是最推荐的一种持久存储数据的方式。volume 的一些使用场景如下:

  • 当我们需要在多个正在运行的容器之间共享数据时,我们需要volume 。如果我们没有明确指定创建它,那么它第一次装入容器时就会创建一个 volume。当容器停止或删除掉,volume 仍然存在。多个容器可以同时读写一个 volume。只有当我们明确指定要删除某个 volume 时,它才会被删除。
  • 当我们需要把容器的数据永久存储在一个远程主机或者一个云服务器上,我们需要 volume。
  • 当我们的 Docker Host 无法保证可以提供一个目录或者文件来作为数据存储时,我们也需要 volume,它可以减少我们对配置文件的依赖。
  • 当我们需要备份数据,或者恢复数据,以及需要把数据从一个 Docker Host 迁移到另外一个 Docker Host 的时候,volume 是我们最好的一个选择,我们可以停掉正在使用 volume 的容器,然后把 volume 的目录备份下来即可,volume 的目录一般在 /var/lib/docker/volumes/<volume-name>下。

4、使用案例

不像 bind mount,我们首先需要创建一个 volume。

docker volume create my_vol

列出 volumes:

root@ubuntu:~# docker volume ls
DRIVER              VOLUME NAME
local               my_vol

查看指定卷的详细信息:

root@ubuntu:~# docker volume inspect my_vol
[
    {
        "CreatedAt": "2017-12-07T10:27:26+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my_vol/_data",
        "Name": "my_vol",
        "Options": {},
        "Scope": "local"
    }
]

删除卷:

docker volume rm my_vol

使用无数据 volume 启动容器

我们查看一下刚刚创建的 volume 里面是否有数据

root@ubuntu:~# ll /var/lib/docker/volumes/my_vol/_data
total 8
drwxr-xr-x 2 root root 4096 Dec  7 10:27 ./
drwxr-xr-x 3 root root 4096 Dec  7 10:27 ../

我们看到里面并没有数据,那我们启动容器查看一下。

docker run -d \
  -it \
  -p 80:80 \
  --name devtest \
  --mount source=my_vol,target=/usr/share/nginx/html \
  nginx:latest

使用命令docker inspect devtest查看一下挂载详情。

        "Mounts": [
            {
                "Type": "volume",
                "Name": "my_vol",
                "Source": "/var/lib/docker/volumes/my_vol/_data",
                "Destination": "/usr/share/nginx/html",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],

我们再次查看一下 Source 目录。

root@ubuntu:~# ll /var/lib/docker/volumes/my_vol/_data
total 16
drwxr-xr-x 2 root root 4096 Dec  7 09:33 ./
drwxr-xr-x 3 root root 4096 Dec  6 20:18 ../
-rw-r--r-- 1 root root  537 Nov 21 22:28 50x.html
-rw-r--r-- 1 root root  612 Nov 21 22:28 index.html

我们可以看到 volume 的内容跟容器原有 /usr/share/nginx/html 完全一样,因为我们挂载的 volume 是刚刚创建没有数据的,容器原有的数据会被复制到 volume 中,我们同样的可以对其进行修改操作,直接反映到容器中。

我们删掉容器查看一下 volume 的数据是否被删除。

docker stop devtest
docker rm devtest

再次进行查看。

root@ubuntu:~# ll /var/lib/docker/volumes/my_vol/_data
total 16
drwxr-xr-x 2 root root 4096 Dec  7 10:13 ./
drwxr-xr-x 3 root root 4096 Dec  6 20:18 ../
-rw-r--r-- 1 root root  537 Nov 21 22:28 50x.html
-rw-r--r-- 1 root root   31 Dec  7 10:13 index.html

我们可以看到,数据没有被删除。

使用有数据 volume 启动容器

我们接着上个 volume 进行测试,我们知道里面已经存在文件 index.html,我们更改里面的内容,并且把 50x.html 删掉。

root@ubuntu:~# ll /var/lib/docker/volumes/my_vol/_data
total 12
drwxr-xr-x 2 root root 4096 Dec  7 10:16 ./
drwxr-xr-x 3 root root 4096 Dec  6 20:18 ../
-rw-r--r-- 1 root root   31 Dec  7 10:13 index.html
root@ubuntu:~# cat /var/lib/docker/volumes/my_vol/_data/index.html   
This is a volume mount test!

确认好 volume 之后,我们启动容器。

docker run -d \
  -it \
  -p 80:80 \
  --name devtest \
  --mount source=my_vol,target=/usr/share/nginx/html \
  nginx:latest

然后访问一下容器。

root@ubuntu:~# curl localhost
This is a volume mount test!

我们可以看到,当我们的 volume 里面有数据的时候,容器内的数据就被 volume 覆盖了,同样的,当我们删除容器之后,volume 里面的数据会依然存在的。

不提前创建 volume 启动容器

之前的情况都说我们提前创建好 volume 进行挂载的,这次我们不提前创建,直接指定,看看会出现什么情况。

docker run -d \
  -it \
  -p 80:80 \
  --name devtest \
  --mount source=my_vol2,target=/usr/share/nginx/html \
  nginx:latest

我们可以看到,也创建成功了,这次我们对 volume 的命名为 my_vol2。

我们使用名称查看一下 volume 的情况。

root@ubuntu:~# docker volume  ls
DRIVER              VOLUME NAME
local               15a731acddee296080b56ddd5faf27748bdfbc422ce2e6b9574ca755e878f434
local               aaf128a2672ffe8994bd080c83bcd4540796a47fd404a8c85e2e1f48f3086855
local               e157aff9c4db4bcf935484b99f119dbe8faeac2de6408197f25b6f1ea798975c
local               my_vol
local               my_vol2

我们可以看到 Docker 给我们自动创建了一个 volume,那我们使用docker inspect devtest查看一下挂载详情。

        "Mounts": [
            {
                "Type": "volume",
                "Name": "my_vol2",
                "Source": "/var/lib/docker/volumes/my_vol2/_data",
                "Destination": "/usr/share/nginx/html",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],

我们猜测,这种情况应该和第一种空 volume 挂载类似,volume 里面的内容应该是容器复制过来的,我们查看一下是否这样的。

root@ubuntu:~# ll /var/lib/docker/volumes/my_vol2/_data
total 16
drwxr-xr-x 2 root root 4096 Dec  7 10:38 ./
drwxr-xr-x 3 root root 4096 Dec  7 10:38 ../
-rw-r--r-- 1 root root  537 Nov 21 22:28 50x.html
-rw-r--r-- 1 root root  612 Nov 21 22:28 index.html

情况确实如我们所说,从这里我们可以看出,如果使用 bind mount,我们的源目录必须存在,不然docker 会报错,然而我们使用 volume,如果源不存在,docker 会为我们进行创建。

这是因为 bind mount 挂载的路径并不是 docker 进行管理的,他没有权限随便创建目录,然后 volume 是 docker 进行管理的,它可以在自己的存储目录下面创建 volume。

当我们想把容器内的数据导出来时,使用这种方式非常方便。

只读模式挂载 volume

在某些情况下,我们使用多容器进行挂载的时候,我们不允许容器对 volume 里面的数据进行修改,这样可以保证所有的容器挂载的是相同的 volume。

docker run -d \
  -it \
  -p 80:80 \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
  nginx:latest

使用命令docker inspect nginxtest查看一下挂载情况。

        "Mounts": [
            {
                "Type": "volume",
                "Name": "nginx-vol",
                "Source": "/var/lib/docker/volumes/nginx-vol/_data",
                "Destination": "/usr/share/nginx/html",
                "Driver": "local",
                "Mode": "z",
                "RW": false,
                "Propagation": ""
            }
        ],

同样的我们可以看到容器的数据被复制到了 volume 里面,我们进入容器,修改文件看看。

root@ubuntu:~# docker exec -ti nginxtest bash
root@28f1d32e08be:/# echo "nginxtest" > /usr/share/nginx/html/index.html 
bash: /usr/share/nginx/html/index.html: Read-only file system

可以看到,容器内部是不能修改 volume 里面的数据的。

到这里我们简单对比一下 bind mount 和 volume 的不同点。

不同点 bind mount volume
Source位置 可以任意指定 /var/lib/docker/volumes/...
空 source 覆盖掉容器的内容 容器内数据复制到 volume
是否支持单个文件 支持 不支持,只能是目录
权限控制 读写或者只读 读写或者只读
移植性 弱,与 host path 绑定 强,无需指定 host 目录

四、tmpfs

1、详细介绍

tmpfs 不在磁盘上持久存储,也不在 Docker Host 容器里面存储,他存储在 host 的内存中,它可以在容器的整个生命周期内被容器所使用。

2、使用场景

当你不需要持久保留数据在 host 或容器内。这可能是出于安全原因,或者是提升容器的性能,比如我们的程序需要写入很多不需要存储的状态数据时,我们就会使用 tmpfs。

3、使用语法

同样的,我们可以在单容器的情况下使用--tmpfs,并且不能指定参数,在集群的情况下使用--mount,可以指定一些参数,具体如下:

Key Value
type bind、volume、tmpfs,如不指定,默认为 volume
destination/dst/target 容器中的路径
tmpfs-type/tmpfs-mode 一些附加参数

4、使用案例

使用 tmpfs 启动容器。

docker run -d \
  -it \
  -p 80:80 \
  --name tmptest \
  --mount type=tmpfs,destination=/usr/share/nginx/html \
  nginx:latest

容器对目录所有的读写操作都在内存中。

我们也可以指定 tmpfs 的权限情况。

docker run -d \
  -it \
  -p 80:80 \
  --name tmptest \
  --mount type=tmpfs,destination=/usr/share/nginx/html,tmpfs-mode=1770 \
  nginx:latest

参考文档:https://docs.docker.com/engine/admin/volumes/

167 docker docker构建nginx容器系列问题 docker registry docker run docker toolbo

167 docker docker构建nginx容器系列问题 docker registry docker run docker toolbo

docker构建nginx容器系列问题


background : 最近为小伙伴们筹划docker系列的技术分享,研究了一会docker相关技术, 在此记录一下构建nginx容器时候的坑

1.nginx服务器根目录问题

docker 官方镜像提供的nginx基于debian/jessie平台,其文件结构和ubuntu中的nginx中并不相同

eg:

run一个niginx容器

<span>//80端口被占用,so...</span>
$ sudo docker run <span>-it</span><span>-p</span><span>800</span>:<span>800</span> nginx
$ sudo docker ps 

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                   NAMES
<span>1801</span>a32aab54        nginx               <span>"nginx -g ''daemon off"</span><span>2</span> minutes ago       Up <span>2</span> minutes        <span>80</span>/tcp, <span>443</span>/tcp, <span>0.0</span><span>.0</span><span>.0</span>:<span>800</span><span>-&gt;</span><span>800</span>/tcp   berserk_kare
登录后复制

进入容器内部

<span>$ </span>sudo docker exec -it <span>1801</span>a32aab54 /bin/bash
root<span>@1801a32aab54</span><span>:/</span><span># </span>
登录后复制

查看nginx目录

<span># cd /etc/nginx/</span>
conf<span>.d</span>/         koi-utf         mime<span>.types</span>      nginx<span>.conf</span>      uwsgi_params    
fastcgi_params  koi-win         modules/        scgi_params     win-utf  
登录后复制

可以看到不仅没有熟悉的 /sites-available,也没有 /sites-enabled

继续查看nginx配置

<span># cat /conf.d/default.conf</span><span>server</span> {
    listen       <span>80</span>;
    server_name  localhost;

    <span>#charset koi8-r;</span><span>#access_log  /var/log/nginx/log/host.access.log  main;</span>    location / {
        root   /usr/share/nginx/html;
        <span>index</span><span>index</span>.html <span>index</span>.htm;
    }

    <span>#error_page  404              /404.html;</span><span># redirect server error pages to the static page /50x.html</span><span>#</span>
    error_page   <span>500</span><span>502</span><span>503</span><span>504</span>  /<span>50</span>x.html;
    location = /<span>50</span>x.html {
        root   /usr/share/nginx/html;
    }
    <span>#...省略php-fpm配置,好长..</span>
}
登录后复制

根目录配置: root /usr/share/nginx/html;

测试

<span># cd /usr/share/nginx/html</span><span># touch index.html</span><span># echo "test nginx in docker" &gt;index.html</span>
登录后复制

php-fpm配置相关

'').addClass(''pre-numbering'').hide(); $(this).addClass(''has-numbering'').parent().append($numbering); for (i = 1; i '').text(i)); }; $numbering.fadeIn(1700); }); });

以上就介绍了167 docker docker构建nginx容器系列问题,包括了docker,nginx方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

18、docker的持久化存储和数据共享

18、docker的持久化存储和数据共享

docker volume

18.1 Data Volume

Docker持久化数据方案

  • 基于本地文件系统的Volume

  可以在执行docker create或者docker run的时候,通过-v参数将主机的目录作为容器的数据卷。这部分功能便是基于本地文件系统的Volume管理。

  • 基于plugin的Volume

  支持第三方的存储方案,比如NAS、AWS等。

Data Volume 类型

  • 受管理的data volume,由docker后台自动创建
  • 绑定挂载的volume,具体挂载位置可以由用户指定

通过例子来查看

  以MySQL为例,可以查看官方的Dockerfile,当中用到了Volume。

# 查看volume
[root@docker ~]# docker volume ls
[root@docker ~]# 
# 创建一个MySQL的容器
[root@docker ~]# docker run -d --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql

# 查看MySQL容器跑起来之后的volume
[root@docker ~]# docker volume ls
DRIVER              VOLUME NAME
local               e3c54bb7b620d86524b6cebc4c28568a2a81a104871a5baf41e22c02bd8d7bac
[root@docker ~]# 
# 查看这个volume的想象信息
[root@docker ~]# docker volume inspect e3c54bb7b620d86524b6cebc4c28568a2a81a104871a5baf41e22c02bd8d7bac
[
    {
        "CreatedAt": "2018-06-07T09:13:24Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/e3c54bb7b620d86524b6cebc4c28568a2a81a104871a5baf41e22c02bd8d7bac/_data",
        "Name": "e3c54bb7b620d86524b6cebc4c28568a2a81a104871a5baf41e22c02bd8d7bac",
        "Options": null,
        "Scope": "local"
    }
]
[root@docker ~]# 

  可以看到,这个volume并不在容器当中,而是挂载到了宿主机的/var/lib/docker/volumes/e3c54bb7b620d86524b6cebc4c28568a2a81a104871a5baf41e22c02bd8d7bac/_data目录下。

  再创建一个mysql2的容器:

[root@docker ~]# docker run -d --name mysql2 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql

[root@docker ~]# docker volume ls
DRIVER              VOLUME NAME
local               a3c730f24ca254cabbd328b4c5a6e7fa1822d7e1b2e54c9ac9104839295e5225
local               e3c54bb7b620d86524b6cebc4c28568a2a81a104871a5baf41e22c02bd8d7bac
[root@docker ~]# docker volume inspect a3c730f24ca254cabbd328b4c5a6e7fa1822d7e1b2e54c9ac9104839295e5225
[
    {
        "CreatedAt": "2018-06-07T09:22:18Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/a3c730f24ca254cabbd328b4c5a6e7fa1822d7e1b2e54c9ac9104839295e5225/_data",
        "Name": "a3c730f24ca254cabbd328b4c5a6e7fa1822d7e1b2e54c9ac9104839295e5225",
        "Options": null,
        "Scope": "local"
    }
]
[root@docker ~]# 

  删除这两个容器:

[root@docker ~]# docker stop mysql1 mysql2
[root@docker ~]# docker rm mysql1 mysql2
[root@docker ~]# docker volume ls
DRIVER              VOLUME NAME
local               a3c730f24ca254cabbd328b4c5a6e7fa1822d7e1b2e54c9ac9104839295e5225
local               e3c54bb7b620d86524b6cebc4c28568a2a81a104871a5baf41e22c02bd8d7bac
[root@docker ~]# 

  可以发现,volume在容器删除之后也不会被删除,可以防止容器删除之后数据也不存在了的问题。但是volume的名字非常不友好,不方便我们使用,但是我们可以在创建或者启动容器的时候给volume设置别名来方便我们使用。

给volume起别名

  重新创建mysql1容器,在启动的时候给volume设置别名

[root@docker ~]# sudo docker run -d -v mysql:/var/lib/mysql --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql
[root@docker ~]# docker volume ls
DRIVER              VOLUME NAME
local               mysql
[root@docker ~]# docker volume inspect mysql
[
    {
        "CreatedAt": "2018-06-07T09:30:33Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/mysql/_data",
        "Name": "mysql",
        "Options": null,
        "Scope": "local"
    }
]
[root@docker ~]# 

  验证一下这个mysql的volume是否已经被使用:进入mysql1容器,创建一个数据库

[root@docker ~]# docker exec -it mysql1 /bin/bash
root@5aa496b309cc:/# mysql -uroot
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.11 MySQL Community Server - GPL

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

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.01 sec)

mysql> create database docker;
Query OK, 1 row affected (0.07 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| docker             |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> \q
Bye
root@5aa496b309cc:/# exit
[root@docker ~]# 停止mysql1容器,并删除mysql1容器
[root@docker ~]# docker stop mysql1
[root@docker ~]# docker rm mysql1
[root@docker ~]# 创建一个新的mysql2的容器,急需使用mysql这个volume
[root@docker ~]# sudo docker run -d -v mysql:/var/lib/mysql --name mysql2 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql
[root@docker ~]# 进入mysql2查看是否存在之前的数据
[root@docker ~]# docker exec -it mysql2 /bin/bash
root@cf1cc3ad331e:/# mysql -uroot
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.11 MySQL Community Server - GPL

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

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           |
+--------------------+
| docker             |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> \q
Bye
root@cf1cc3ad331e:/# exit

  可以发现,volume中的数据并不会因为容器的删除二消失,实现了数据持久化的目标。但是这种方式的volume需要在Dockerfile中使用VOLUME来预先指定容器中的数据存放路径。

18.2 Bind Mounting

  Bind Mounting跟上面的方式不一样,可以动态的指定容器内文件存放路径和宿主机上的数据库卷目录。

  构建一个docker-nginx的镜像:

# Dockerfile
[root@docker docker-nginx]# cat Dockerfile
# this same shows how we can extend/change an existing official image from Docker Hub

FROM nginx:latest
# highly recommend you always pin versions for anything beyond dev/learn

WORKDIR /usr/share/nginx/html
# change working directory to root of nginx webhost
# using WORKDIR is prefered to using ''RUN cd /some/path''

COPY index.html index.html

# I don''t have to specify EXPOSE or CMD because they''re in my FROM
[root@docker docker-nginx]# cat index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">

  <title>hello</title>

</head>

<body>
  <h1>Hello Docker! </h1>
</body>
</html>
[root@docker docker-nginx]# 构建镜像
[root@docker docker-nginx]# docker build -t staryjie/docker-nginx .
[root@docker docker-nginx]# 创建容器
[root@docker docker-nginx]# docker run -d -p 80:80 --name web staryjie/docker-nginx
[root@docker docker-nginx]# 本地访问
[root@docker docker-nginx]# curl 127.0.0.1
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">

  <title>hello</title>

</head>

<body>
  <h1>Hello Docker! </h1>
</body>
</html>
[root@docker docker-nginx]# 

  但是index.html文件是无法更改的,如果要更改必须要重新构建镜像,这样非常不便。

[root@docker docker-nginx]# 强制删除web容器
[root@docker docker-nginx]# docker rm -f web
[root@docker docker-nginx]# 重新创建一个容器,指定宿主机上index.html的目录到容器中nginx的html目录
[root@docker docker-nginx]# docker exec -it web1 /bin/bash
root@18af473954f1:/usr/share/nginx/html# ls
Dockerfile  index.html
root@18af473954f1:/usr/share/nginx/html# touch test.txt
root@18af473954f1:/usr/share/nginx/html# exit
[root@docker docker-nginx]# ls
Dockerfile  index.html  test.txt
[root@docker docker-nginx]# curl 127.0.0.1
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">

  <title>hello</title>

</head>

<body>
  <h1>Hello Docker! </h1>
</body>
</html>
[root@docker docker-nginx]# 修改index.html
[root@docker docker-nginx]# cat index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">

  <title>hello</title>

</head>

<body>
  <h1>Hello Docker! </h1>
  <h1>Hello, I have changed this file! </h1>
</body>
</html>
[root@docker docker-nginx]# curl 127.0.0.1
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">

  <title>hello</title>

</head>

<body>
  <h1>Hello Docker! </h1>
  <h1>Hello, I have changed this file! </h1>
</body>
</html>
[root@docker docker-nginx]# 

  采用Bind Mounting的方式实现volume的话,容器内外的数据是同步的,只需要修改一个地方,容器内或者容器外都会同步修改,非常的方便快捷。

  采用Bind Mounting的方式,将docker作为开发环境可以使我们的开发环境和生产环境保持一致,这也是实现DevOps的第一步。(很多开发使用的都是Windows的系统,但是服务器一般都是Linux的,无法保持环境的一致性,影响开发效率。)

  利用docker搭建一个与生产环境统一的开发环境

Docker (十三)-Docker save and load 镜像保存

Docker (十三)-Docker save and load 镜像保存

持久化 docker 的镜像或容器的方法

Docker 的镜像和容器可以有两种方式来导出

  • docker save #ID or #Name
  • docker export #ID or #Name

docker save 和 docker export 的区别

  • 对于 Docker Save 方法,会保存该镜像的所有历史记录
  • 对于 Docker Export 方法,不会保留历史记录,即没有 commit 历史
  • docker save 保存的是镜像(image),docker export 保存的是容器(container);
  • docker load 用来载入镜像包,docker import 用来载入容器包,但两者都会恢复为镜像;
  • docker load 不能对载入的镜像重命名,而 docker import 可以为镜像指定新名称。

save 命令 


docker save [options] images [images...]

示例 
docker save -o nginx.tar nginx:latest 
或 
docker save > nginx.tar nginx:latest 
其中 - o 和 > 表示输出到文件,nginx.tar 为目标文件,nginx:latest 是源镜像名(name:tag)

load 命令


docker load [options]

示例
docker load -i nginx.tar

docker load < nginx.tar
其中 - i 和 < 表示从文件输入。会成功导入镜像及相关元数据,包括 tag 信息

export 命令


docker export [options] container

示例
docker export -o nginx-test.tar nginx-test

#导出为 tar

docker export #ID or #Name > /home/export.tar

其中 - o 表示输出到文件,nginx-test.tar 为目标文件,nginx-test 是源容器名(name)

import 命令


docker import [options] file|URL|- [REPOSITORY[:TAG]]

示例
docker import nginx-test.tar nginx:imp

cat nginx-test.tar | docker import - nginx:imp



Docker in Docker(实际上是 Docker outside Docker): /var/run/docker.sock

Docker in Docker(实际上是 Docker outside Docker): /var/run/docker.sock

在 Docker 容器里面使用 docker run/docker build

Docker 容器技术目前是微服务/持续集成/持续交付领域的第一选择。而在 DevOps 中,我们需要将各种后端/前端的测试/构建环境打包成 Docker 镜像,然后在需要的时候,Jenkins 会使用这些镜像启动容器以执行 Jenkins 任务。

为了方便维护,我们的 CI 系统如 Jenkins,也会使用 Docker 方式部署。 Jenkins 任务中有些任务需要将微服务构建成 Docker 镜像,然后推送到 Harbor 私有仓库中。 或者我们所有的 Jenkins Master 镜像和 Jenkins Slave 镜像本身都不包含任何额外的构建环境,执行任务时都需要启动包含对应环境的镜像来执行任务。

我们的 Jenkins Master、Jenkins Slaves 都是跑在容器里面的,该如何在这些容器里面调用 docker run 命令启动包含 CI 环境的镜像呢? 在这些 CI 镜像里面,我们从源码编译完成后,又如何通过 docker build 将编译结果打包成 Docker 镜像,然后推送到内网仓库呢?

答案下面揭晓。

一、原理说明:/var/run/docker.sock

Docker 采取的是 Client/Server 架构,我们常用的 docker xxx 命令工具,只是 docker 的 client,我们通过该命令行执行命令时,实际上是在通过 client 与 docker engine 通信。

我们通过 apt/yum 安装 docker-ce 时,会自动生成一个 systemd 的 service,所以安装完成后,需要通过 sudo systemctl enable docker.service 来启用该服务。 这个 Docker 服务启动的,就是 docker engine,查看 /usr/lib/systemd/system/docker.service,能看到有这样一条语句:

ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

默认情况下,Docker守护进程会生成一个 socket(/var/run/docker.sock)文件来进行本地进程通信,因此只能在本地使用 docker 客户端或者使用 Docker API 进行操作。 sock 文件是 UNIX 域套接字,它可以通过文件系统(而非网络地址)进行寻址和访问。

因此只要以数据卷的形式将 docker 客户端和上述 socket 套接字挂载到容器内部,就能实现 "Docker in Docker",在容器内使用 docker 命令了。具体的命令见后面的「示例」部分。

要记住的是,真正执行我们的 docker 命令的是 docker engine,而这个 engine 跑在宿主机上。所以这并不是真正的 "Docker in Docker".

二、示例

在容器内部使用宿主机的 docker,方法有二:

  1. 命令行方式:将 /usr/bin/docker 映射进容器内部,然后直接在容器内部使用这个命令行工具 docker
    • 需要的时候,也可以将 /etc/docker 文件夹映射到容器内,这样容器内的 docker 命令行工具也会使用与宿主机同样的配置。
  2. 编程方式:在容器内部以编程的方式使用 docker
    • 通过 python 使用 docker: 在 Dockerfile 中通过 pip install docker 将 docker client 安装到镜像中来使用

容器的启动方式也有两种,如下:

1. 直接通过 docker 命令启动

示例命令如下:

docker run --name <name> \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /usr/bin/docker:/usr/bin/docker \
    --user root \
    <image-name>:<tag>

**必须以 root 用户启动!(或者其他有权限读写 /var/run/docker.sock 的用户)**然后,在容器内就能正常使用 docker 命令,或者访问宿主机的 docker api 了。

2. 使用 docker-compose 启动

docker-compose.yml 文件内容如下:

version: ''3.3''
services:
  jenkins-master:
    image: jenkinsci/blueocean:latest
    container_name: jenkins-master
    environment:
      - TZ=Asia/Shanghai  # 时区
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - ./jenkins_home:/var/jenkins_home  # 将容器中的数据映射到宿主机
      - /usr/bin/docker:/usr/bin/docker  # 为容器内部提供 docker 命令行工具(这个随意)
      - /var/run/docker.sock:/var/run/docker.sock  # 容器内部通过 unix socket 使用宿主机 docker engine
    user: root  # 必须确保容器以 root 用户启动!(这样它才有权限读写 docker.socket)
    restart: always

然后通过 docker-compose up -d 即可后台启动容器。

Docker 中的 uid 与 gid

通过上面的操作,我们在容器内执行 docker ps 时,还是很可能会遇到一个问题:权限问题

如果你容器的默认用户是 root,那么你不会遇到这个问题,因为 /var/run/docker.sock 的 onwer 就是 root.

但是一般来说,为了限制用户的权限,容器的默认用户一般都是 uid 和 gid 都是 1000 的普通用户。这样我们就没有权限访问 /var/run/docker.sock 了。

解决办法:

方法一(不一定有效):在构建镜像时,最后一层添加如下内容:

# docker 用户组的 id,通常都是 999
RUN groupadd -g 999 docker \
    && usermod -aG docker <your_user_name>

这样我们的默认用户,就能使用 docker 命令了。

P.S. 999 不一定是 docker 用户组,所以上述方法某些情况下可能失效。这时还是老老实实通过 docker run -u root 启动容器吧。(或者在 docker-compose.yml 中添加 user: root 属性)

参考

  • Docker in Docker - 王柏元

关于Docker 持久存储介绍(十三)docker持久化存储的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于167 docker docker构建nginx容器系列问题 docker registry docker run docker toolbo、18、docker的持久化存储和数据共享、Docker (十三)-Docker save and load 镜像保存、Docker in Docker(实际上是 Docker outside Docker): /var/run/docker.sock的相关知识,请在本站寻找。

本文标签: