Docker的数据持久化、docker Compose


Docker的image是只读的,容器运行期间产生的数据是不会在写入到image里面,当容器被删除后,数据也随之被删除。如果想做到数据持久化,Docker使用Data volume或者Bind Mounting来持久化数据。

如果想要简化多容器的部署,则可以使用Docker Compose。

Docker的数据持久化

Data volume

Data volume除了可以持久化数据,还提供以下特性:

  • Data volume可以在容器之间共享和重用

  • 对Data volume的修改会立马生效

  • 对Data volume的更新,不会影响镜像

  • Data volume默认会一直存在,即使容器被删除

在Dockerfile中,可以使用VOLUME指令指定数据的挂载点。如下是mysql官方的Dockerfile,可以看到mysql的VOLUME挂载点:

VOLUME /var/lib/mysql

如下命令,运行一个mysql容器:

docker run -d -v mysql:/var/lib/mysql --name mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql:5.6

使用docker volume ls可以看到:

ubuntu@docker-node1:~$ docker volume ls
local               mysql

进入mysql的容器,创建一个数据库docker:

ubuntu@docker-node1:~$ docker exec -it mysql /bin/bash
root@937b2ceaf848:/# mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.6.42 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 |
+--------------------+
3 rows in set (0.00 sec)

mysql> CREATE DATABASE docker;
Query OK, 1 row affected (0.00 sec)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| docker             |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

mysql>

退出mysql的容器,并删除mysql容器:

ubuntu@docker-node1:~$ docker rm -f mysql
mysql

创建一个mysql-another的容器,并使用原来的mysql的VOLUME:

ubuntu@docker-node1:~$ docker run -d -v mysql:/var/lib/mysql --name mysql-another -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql:5.6

进入mysql-another的容器,可以看到数据库docker还存在:

ubuntu@docker-node1:~$ docker exec -it mysql-another /bin/bash
root@97ea74e7f222:/# mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.6.42 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 |
| docker             |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)

mysql>

Bind Mounting

Bind Mounting是将本地目录和容器目录进行映射,当本地目录中文件更改,对应的容器目录中的文件也会更改。

如下Dockerfile,是拉取一个nginx的image,并把当前目录下的index.html拷贝到容器的/usr/share/nginx/html:

FROM nginx:latest
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>

构建image:

ubuntu@docker-node1:~/nginx-demo$ docker build -t heqingliang/nginx-demo .

运行容器时,将本地目录挂载到容器的/usr/share/nginx/html目录:

ubuntu@docker-node1:~/nginx-demo$ docker run -d -v $(pwd):/usr/share/nginx/html -p 80:80 --name web heqingliang/nginx-demo
2d5dd44d42a6a9102ec66adb5e817270ef14b79b6c8755330a8903ffbaa43434
ubuntu@docker-node1:~/nginx-demo$ docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                NAMES
2d5dd44d42a6        heqingliang/nginx-demo   "nginx -g 'daemon ..."   3 seconds ago       Up 3 seconds        0.0.0.0:80->80/tcp   web

在浏览器中输入http://192.168.33.10/,可以看到如下结果:

nginx_hello_docker.png

进入web容器的/usr/share/nginx/html目录,创建一个test.txt文件:

ubuntu@docker-node1:~/nginx-demo$ docker exec -it web /bin/bash
root@2d5dd44d42a6:/usr/share/nginx/html# echo "abc" > test.txt

退出web容器,可以看到当前目录也创建了一个test.txt文件,内容和容器中是一样的:

ubuntu@docker-node1:~/nginx-demo$ ls
Dockerfile  index.html  test.txt
ubuntu@docker-node1:~/nginx-demo$ cat test.txt
abc

修改本地目录的下的index.html文件为:

<h1>Hello World! </h1>

在浏览器中输入http://192.168.33.10/,可以看到如下结果:

nginx_hello_world.png

Docker Compose

在多个容器的APP部署会存在以下的问题:

  • 要从Dockerfile build image或者从DockerHub中拉取镜像

  • 要创建多个container

  • 要管理这些container(启动、删除、停止)

Docker Compose是解决上面问题的”批处理”工具,这个工具可以通过一个yml文件定义多容器的docker应用,通过一条命令就可以根据yml文件的定义去创建或者管理多个容器。

在Docker Compose包含services、networks、volumes三大部分:

  • services:一个service代表一个container,这个container可以从DockerHub中拉取的image来创建,或者从本地的Dockerfile build出来的image来创建,service的启动类似docker run,可以指定network和volume,所以可以给service

  • networks:代表创建的docker网络

  • volumes:数据持久化

有如下yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vsersion: "3"

services:
db:
images: postgres:9.4
volumes:
- "db-data:/var/lib/postgresql/data"
networks:
- back-tier

volumes:
db-data:

networks:
back-tier:
driver: bridge

定义service相当于执行如下docker命令:

docker run -d --network back-tier -v db-data:/var/lib/postgresql/data postgres:9.4

定义volumes相当于执行如下docker命令:

docker volume create db-data

定义networks相当于执行如下docker命令:

docker network create -d bridge back-tier

安装

在linux系统中,安装docker并没有安装docker-compose,需要单独安装:

ubuntu@docker-node1:~/flask-redis$ sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
ubuntu@docker-node1:~$ sudo chmod +x /usr/local/bin/docker-compose

例子

有如下python程序,使用redis来记录每次访问web页面的次数,在部署时,可以把redis和应用程序部署在不同容器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
flask demo
"""

__author__ = 'heqingliang'



from flask import Flask
from redis import Redis
import os


app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)


@app.route('/', methods=['GET', 'POST'])
def hello():
redis.incr('hits')
return '<h1>Hello Container, I have been seen %s times.</h1>' % (redis.get('hits'))


def main():
app.run(host="0.0.0.0", port=5000, debug=True)


if __name__ == '__main__':
main()

如下是app.py程序的Dockerfile:

FROM python
LABEL maintainer="heqingliang_gzus@163.com"
RUN pip install flask redis
COPY app.py /app/
WORKDIR /app
EXPOSE 5000
CMD ["python", "app.py"]

定义如下docker-compose.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: "3"

services:
redis:
image: redis

web:
build:
context: .
dockerfile: Dockerfile
ports:
- 5000:5000
environment:
REDIS_HOST: redis
启动

执行下面的命令,就可以创建和启动容器:

ubuntu@docker-node1:~/flask-redis$ docker-compose up -d
Creating network "flaskredis_default" with the default driver
Creating flaskredis_web_1 ...
Creating flaskredis_redis_1 ...
Creating flaskredis_web_1
Creating flaskredis_redis_1 ... done

启动默认执行的是当前目录下的docer-compose.yml,如果想要执行指定的docer-compose.yml,可以使用-f参数指定:

docker-compose -f docker-compose.yml up -d

-d参数表示在后台执行,如果想要查看启动的信息,可以把-d参数去掉。

访问127.0.0.1:5000,可以看到如下结果:

ubuntu@docker-node1:~/flask-redis$ curl 127.0.0.1:5000
<h1>Hello Container, I have been seen b'1' times.</h1>
ubuntu@docker-node1:~/flask-redis$ curl 127.0.0.1:5000
<h1>Hello Container, I have been seen b'2' times.</h1>
ubuntu@docker-node1:~/flask-redis$ curl 127.0.0.1:5000
<h1>Hello Container, I have been seen b'3' times.</h1>

上面的docer-compose.yml并没有创建自定义的网络,但是也可以直接使用redis容器的名字,直接通信,那是因为docker-compose创建了一个默认的网络flaskredis_default

ubuntu@docker-node1:~/flask-redis$ docker network ls
NETWORK ID          NAME                 DRIVER              SCOPE
48a159571164        bridge               bridge              local
ff50d198b5dc        docker_gwbridge      bridge              local
182def1a98fa        flaskredis_default   bridge              local
65e22acf0beb        host                 host                local
c305a4b53c3b        none                 null                local
查看容器的运行状态
ubuntu@docker-node1:~/flask-redis$ docker-compose ps
   Name                   Command             State           Ports
--------------------------------------------------------------------------------
flaskredis_redis_1   docker-entrypoint.sh redis   Up      6379/tcp
                    ...
flaskredis_web_1     python app.py                Up      0.0.0.0:5000->5000/tcp
查看镜像
ubuntu@docker-node1:~/flask-redis$ docker-compose images
Container          Repository      Tag       Image Id      Size
---------------------------------------------------------------------
flaskredis_redis_1   redis            latest   415381a6cb81   90.5 MB
flaskredis_web_1     flaskredis_web   latest   0a1d5cbead89   889 MB
停止运行的容器
ubuntu@docker-node1:~/flask-redis$ docker-compose stop
Stopping flaskredis_redis_1 ... done
Stopping flaskredis_web_1   ... done
重新启动容器
ubuntu@docker-node1:~/flask-redis$ docker-compose start
Starting web   ... done
Starting redis ... done
进入到某个容器中
ubuntu@docker-node1:~/flask-redis$ docker-compose exec redis bash
root@3375d56db073:/data#
停止并删除容器
ubuntu@docker-node1:~/flask-redis$ docker-compose down
Stopping flaskredis_redis_1 ... done
Stopping flaskredis_web_1   ... done
Removing flaskredis_redis_1 ... done
Removing flaskredis_web_1   ... done
Removing network flaskredis_default