Docker的镜像是只读的,但是容器是可写的,我们可以将数据写入到容器,不过一旦容器删除数据将会丢失,那么有什么办法能将数据进行持久化存储呢?
一、Data Volume
在执行docker run 时,通过-v参数将主机目录作为容器的数据卷,这就是基于本地文件系统Volumn管理。
1、Volume类型
- 受管理的Volume,由docker后台自动创建
- 绑定挂载的Volume,具体挂载位置由用户指定
2、docker后台自动创建
以mysql镜像为例说明,进入到https://hub.docker.com/_/mysql查看详情。
-
启动docker服务
[root@docker-node1 ~]# systemctl start docker
-
拉取mysql镜像
[root@docker-node1 ~]# docker pull mysql Using default tag: latest latest: Pulling from library/mysql 619014d83c02: Pulling fs layer 9ced578c3a5f: Pulling fs layer
注意:如果出现Get https://registry-1.docker.io/v2/library/mysql/manifests/latest: dial tcp: lookup registry-1.docker.io on 8.8.8.8:53: read udp 192.168.0.109:41429->8.8.8.8:53: i/o timeout这种问题,可以尝试修改/etc/resolv.conf文件,加入nameserver 8.8.4.4

1[root@docker-node1 ~]# cat /etc/resolv.conf 2# Generated by NetworkManager 3nameserver 8.8.8.8 4nameserver 8.8.4.4
View Code
-
运行mysql镜像
[root@docker-node1 ~]# docker run -d --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql 9dd537e48f16fd924f19f95aa38d15e08bdfa254d65ee66f1e19331ed6e79ce3 [root@docker-node1 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9dd537e48f16 mysql "docker-entrypoint.s…" 20 seconds ago Up 11 seconds 3306/tcp, 33060/tcp mysql1
这样就启动了一个mysql的容器mysql1,但是显然我们并没有指定数据库中表指定放到什么地方,那么它给我们自动放到什么地方了呢?
我们可以看看mysql的Dockerfile:

1FROM debian:stretch-slim 2 3# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added 4RUN groupadd -r mysql && useradd -r -g mysql mysql 5 6RUN apt-get update && apt-get install -y --no-install-recommends gnupg dirmngr && rm -rf /var/lib/apt/lists/* 7 8# add gosu for easy step-down from root 9ENV GOSU_VERSION 1.7 10RUN set -x \ 11 && apt-get update && apt-get install -y --no-install-recommends ca-certificates wget && rm -rf /var/lib/apt/lists/* \ 12 && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" \ 13 && wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" \ 14 && export GNUPGHOME="$(mktemp -d)" \ 15 && gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \ 16 && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \ 17 && gpgconf --kill all \ 18 && rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc \ 19 && chmod +x /usr/local/bin/gosu \ 20 && gosu nobody true \ 21 && apt-get purge -y --auto-remove ca-certificates wget 22 23RUN mkdir /docker-entrypoint-initdb.d 24 25RUN apt-get update && apt-get install -y --no-install-recommends \ 26# for MYSQL_RANDOM_ROOT_PASSWORD 27 pwgen \ 28# for mysql_ssl_rsa_setup 29 openssl \ 30# FATAL ERROR: please install the following Perl modules before executing /usr/local/mysql/scripts/mysql_install_db: 31# File::Basename 32# File::Copy 33# Sys::Hostname 34# Data::Dumper 35 perl \ 36 && rm -rf /var/lib/apt/lists/* 37 38RUN set -ex; \ 39# gpg: key 5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported 40 key='A4A9406876FCBD3C456770C88C718D3B5072E1F5'; \ 41 export GNUPGHOME="$(mktemp -d)"; \ 42 gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ 43 gpg --batch --export "$key" > /etc/apt/trusted.gpg.d/mysql.gpg; \ 44 gpgconf --kill all; \ 45 rm -rf "$GNUPGHOME"; \ 46 apt-key list > /dev/null 47 48ENV MYSQL_MAJOR 8.0 49ENV MYSQL_VERSION 8.0.19-1debian9 50 51RUN echo "deb http://repo.mysql.com/apt/debian/ stretch mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list 52 53# the "/var/lib/mysql" stuff here is because the mysql-server postinst doesn't have an explicit way to disable the mysql_install_db codepath besides having a database already "configured" (ie, stuff in /var/lib/mysql/mysql) 54# also, we set debconf keys to make APT a little quieter 55RUN { \ 56 echo mysql-community-server mysql-community-server/data-dir select ''; \ 57 echo mysql-community-server mysql-community-server/root-pass password ''; \ 58 echo mysql-community-server mysql-community-server/re-root-pass password ''; \ 59 echo mysql-community-server mysql-community-server/remove-test-db select false; \ 60 } | debconf-set-selections \ 61 && apt-get update && apt-get install -y mysql-community-client="${MYSQL_VERSION}" mysql-community-server-core="${MYSQL_VERSION}" && rm -rf /var/lib/apt/lists/* \ 62 && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql /var/run/mysqld \ 63 && chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \ 64# ensure that /var/run/mysqld (used for socket and lock files) is writable regardless of the UID our mysqld instance ends up having at runtime 65 && chmod 777 /var/run/mysqld 66 67VOLUME /var/lib/mysql #持久化存储 68# Config files 69COPY config/ /etc/mysql/ 70COPY docker-entrypoint.sh /usr/local/bin/ 71RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat 72ENTRYPOINT ["docker-entrypoint.sh"] 73 74EXPOSE 3306 33060 75CMD ["mysqld"]
View Code
实际上已经帮我们持久化存储到主机的/var/lib/mysql目录中了。
另外可以看到主机上有一条volume的记录:
1[root@docker-node1 ~]# docker volume ls 2DRIVER VOLUME NAME 3local 5c4fbbc613e046baf9e148a301ef94ef09e4fc57312edd907885336e9ecd5bc1
可以看看这个存储卷记录的具体信息:
1[root@docker-node1 ~]# docker volume inspect 5c4fbbc613e046baf9e148a301ef94ef09e4fc57312edd907885336e9ecd5bc1 2[ 3 { 4 "CreatedAt": "2020-02-03T13:24:30+08:00", 5 "Driver": "local", 6 "Labels": null, 7 "Mountpoint": "/var/lib/docker/volumes/5c4fbbc613e046baf9e148a301ef94ef09e4fc57312edd907885336e9ecd5bc1/_data", 8 "Name": "5c4fbbc613e046baf9e148a301ef94ef09e4fc57312edd907885336e9ecd5bc1", 9 "Options": null, 10 "Scope": "local" 11 } 12]
- 持久化存储
如果我们讲这个容器删掉,这个存储卷还存在吗?可以尝试一下:
1[root@docker-node1 ~]# docker rm -f mysql1 #删掉mysql1容器 2mysql1 3[root@docker-node1 ~]# docker volume ls #存储卷仍旧存在 4DRIVER VOLUME NAME 5local 5c4fbbc613e046baf9e148a301ef94ef09e4fc57312edd907885336e9ecd5bc1
可以看到虽然mysql的容器不在了,但是它的存储卷依旧存在。
3、用户指定挂载位置
上面你也许已经看到了一些需要自定义的地方:其一,volume name的名字太长了,需要自己指定;其二,volume持久化的目录需要自己指定。
-
容器重命名以及指定挂载位置
[root@docker-node1 ~]# docker run -d -v /home/mysql:/var/lib/mysql --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql 0e560c5c9caf902c0d9647ef1d7995803d9dfde8e0db2c529d97f913f943bdf5
上面通过-v参数指定mysql的挂载位置(主机文件位置:容器文件位置[Dockerfile中已经指定]),通过--name指定容器的名称,如下所示:
1[root@docker-node1 ~]# docker ps 2CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 30e560c5c9caf mysql "docker-entrypoint.s…" 9 seconds ago Up 7 seconds 3306/tcp, 33060/tcp mysql1
- 测试持久化
现在,可以先进入到这个容器中,对mysql的数据库中做一些改变,然后删掉容器:
(1)进入容器
[root@docker-node1 ~]# docker exec -it mysql1 /bin/sh
(2)进入mysql
1# mysql -uroot 2Welcome to the MySQL monitor. Commands end with ; or \g. 3Your MySQL connection id is 8 4Server version: 8.0.19 MySQL Community Server - GPL 5 6Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. 7 8Oracle is a registered trademark of Oracle Corporation and/or its 9affiliates. Other names may be trademarks of their respective 10owners. 11 12Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
(3)创建test数据库
1mysql> create database test; 2Query OK, 1 row affected (0.36 sec) 3 4mysql> show databases; 5+--------------------+ 6| Database | 7+--------------------+ 8| information_schema | 9| mysql | 10| performance_schema | 11| sys | 12| test | 13+--------------------+ 145 rows in set (0.00 sec)
(4)删掉mysql1容器
1[root@docker-node1 ~]# docker rm -f mysql1 2mysql1
(5)新建mysql2容器
新建mysql2容器,并且指向mysql1容器的存储卷:
1[root@docker-node1 ~]# docker run -d -v /home/mysql:/var/lib/mysql --name mysql2 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql 29fc1aff1e798b0b6b4c01b26160877253c655f137a542734eb81b2b8984af66c
然后可以进入到mysql2容器中查看mysql中是否有mysql1容器创建的test数据库:
1[root@docker-node1 ~]# docker exec -it mysql2 /bin/sh #进入mysql2容器 2# mysql -uroot 3Welcome to the MySQL monitor. Commands end with ; or \g. 4Your MySQL connection id is 8 5Server version: 8.0.19 MySQL Community Server - GPL 6 7Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. 8 9Oracle is a registered trademark of Oracle Corporation and/or its 10affiliates. Other names may be trademarks of their respective 11owners. 12 13Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 14 15mysql> show databases; 16+--------------------+ 17| Database | 18+--------------------+ 19| information_schema | 20| mysql | 21| performance_schema | 22| sys | 23| test | 24+--------------------+ 255 rows in set (0.16 sec)
可以看到却是存在test数据库,这样也就实现了volume持久化。
二、Bind Mounting
1、bing mounting和data volume的区别
- data volume 需要在 Dockerfile文件中 定义 volume,这种方式较多的是将容器映射到本地,实现容器与外部程序共享数据。
- bind mounting 只需要 -v 指明 容器外部文件夹和容器映射文件夹的对应关系,将本地的文件映射到容器内。
2、bind mounting的应用
-
在bindmounting-test文件夹中新建两个文件
[root@docker-node1 bindmounting-test]# ls Dockerfile index.html
-
编写Dockerfile文件
FROM nginx #基础镜像 WORKDIR /usr/share/nginx/html #工作目录,容器内的目录 COPY index.html . #将本机当前目录下的index.html文件拷贝到上述工作目录下
-
编写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-node1 bindmounting-test]# docker build -t docker-nginx . Sending build context to Docker daemon 3.072kB Step 1/3 : FROM nginx ---> f68d6e55e065 Step 2/3 : WORKDIR /usr/share/nginx/html ---> Running in 3b784d71c4dc Removing intermediate container 3b784d71c4dc ---> 10cef7e9bbb9 Step 3/3 : COPY index.html . ---> f43b1a1b7bef Successfully built f43b1a1b7bef Successfully tagged docker-nginx:latest
-
运行docker-nginx镜像
[root@docker-node1 bindmounting-test]# docker run -d -p 80:80 --name web docker-nginx 22c28d4887d222785a3c60ca28c2fe79ff82ff9e630af0cdbb48232171732c90
上面通过-p进行了端口暴露,可通过外网访问:

但是难道每次修改index.html文件都需要重新生成镜像,然后运行吗?我们可以通过bing mounting进行映射,如果本地文件变了,容器内的就会改变。这样就不会重新生成镜像了。
-
使用bind mounting
[root@docker-node1 bindmounting-test]# ls Dockerfile index.html [root@docker-node1 bindmounting-test]# docker run -d -v $(pwd):/usr/share/nginx/html -p 80:80 --name web docker-nginx f4661067be3fdead6c627bc00a054244b92207d42baebafdc37b8c8a8fe6f3cb
可以看到上面我将$(pwd)这个当前路劲的文件,也就是bindmounting-test文件夹中内容映射到容器/usr/share/nginx/html目录下,如果本地bindmounting-test文件夹下有文件更改了,容器中自然会变化。
例如,修改本地index.html文件中的内容:
1[root@docker-node1 bindmounting-test]# vim index.html 2 3<!doctype html> 4<html lang="en"> 5<head> 6 <meta charset="utf-8"> 7 8 <title>hello</title> 9 10</head> 11 12<body> 13 <h1>Hello </h1> 14</body> 15</html>
此时,再刷新页面:

这样当在容器内或者容器外更改 映射文件夹内的内容时 两边都会改变,很利于开发者在linux进行应用的迭代更新部署。
三、总结
1、完成Data volume的步骤
- Dockerfile中指定volume参数,如 VOLUME /var/lib/mysql
- docker run -v /home/mysql:/var/lib/mysql
2、完成Bind Mounting的步骤
- docker run -v /home/mysql:/var/lib/mysql
上面的映射关系都是 主机:容器