docker进阶02
1.数据持久化
在容器层的 UnionFS (联合文件系统) 中对文件/目录的任何修改,无论是手工修改还是容器在运行过程中的修改,在该容器丢失或被删除后这些修改将全部丢失,也就是说这些修改是无法保存下来的,若要保存下来这些修改,通常有两种方式:
- 定制镜像持久化:将这个修改过的容器生成一个新的镜像,让这些修改变为只读的镜像。
- 数据卷持久化:将这些修改通过数据卷同步到宿主机。
1.1 定制镜像持久化
1.1.1 案例
我们这里要实现的功能是:为 tomcat:10.0 镜像修改其 webapps 目录。原本该目录内容是空的,用户访问 tomcat 页面会报 404,而真正的内容是在 webapps.dist 中。现要将原webapps 目录删除,然后重命名 webapps.dist 目录为webapps,以使用户可以看到 tomcat页面。
# 运行容器
docker run --name mytom -d -p 8080:8080 tomcat:8.5.50
# 进入容器,修改webapps目录
docker exec -it mytom /bin/bash
# 把容器提交成新镜像
docker commit -a "tom" -m "modify webapps" mytom mytomcat:8.5.50
# 用新镜像运行容器
docker run --name mytom2 -d -p 8081:8080 mytomcat:8.5.50
这种方式的问题:如果容器中在部署了网站,但是容器被误删,会导致部署的网站丢失。虽然也可以用 docker cp 做数据同步,但是不能实时同步,数据并不安全。
1.2 数据卷持久化
1.2.1 简介
Docker 提供了三种实时同步 (宿主机与容器FS间数据的同步)方式:数据卷、bind mounts (绑定挂载)、tmpfs (临时文件系统)。但其中使用最广泛的是数据卷。
数据卷是宿主机中的一个特殊的文件/目录,这个文件/目录与容器中的另一个文件/目录进行了直接关联,在任何一端对文件/目录的写操作,在另一端都会同时发生相应变化。在宿主中的这个文件/目录就称为数据卷,而容器中的这个关联文件/目录则称为该数据卷在该容器中的挂载点。
数据卷的设计目的就是为了实现数据持久化,其完全独立于容器的生命周期,属于宿主机文件系统,但不属于 UnionFS。因此,容器被删除时,不会删除其挂载的数据卷。
数据卷的底层实现方式:硬链接。
数据卷具备以下明显特性:
- 数据卷在容器启动时初始化,如果容器启动后容器本身已经包含了数据,那么这些据会在容器启动后直接出现在数据卷中,反之亦然。
- 可以对数据卷或挂载点中的内容直接修改,修改后对方立即可看到。
- 数据卷会一直存在,即使挂载数据卷的容器已经被删除。
- 数据卷可以在容器之间共享和重用。
数据卷是在使用 docker run 启动容器时指定的,语法为:
# -v, --volume list:Bind mount a volume
docker run -v /宿主机目录的绝对路径:/容器内目录的绝对路径 镜像名:TAG
注:无论是宿主机中的数据卷还是容器中的挂载点,如果指定的目录不存在,那么 docker 引擎都会自动创建(包括多级目录)。
案例1.centos7
在宿主机中的 /host/log/ 目录与 centos7容器的 /opt/log/ 目录间建立关联,即宿主机中的 /host/log/ 目录作为数据卷,而centos7容器的 /opt/log/ 目录作为挂载点。
docker run --name mycent -it -v /host/log:/opt/log centos:7
# 检查目录、测试新建文件、测试追加文件等
用 docker inspect mycent 检查容器:
案例2.只读数据卷
只读数据卷指的是容器对挂载点的操作权限是只读的,而宿主机对数据卷的操作权限始终是读写的。
有些情况下,为了防止容器在运行过程中对文件产生修改,就需要创建只读数据卷(比如配置文件问题)。
语法:
# 注意 :ro
docker run -v /宿主机目录的绝对路径:/容器内目录的绝对路径:ro 镜像名:TAG
还是用centos7测试:
docker run --name mycent -it -v /host/log:/opt/log:ro centos:7
此时在容器内进行写操作会有以下提示:
案例3.数据卷共享
当一个容器与另一个容器使用相同的数据卷时,就称这两个容器实现了“数据卷共享”。
数据卷容器是实现数据卷共享的一种非常有效的方案:
当一个容器A启动运行时创建并挂载了数据卷,若其它容器也需要共享该容器A挂载的数据卷,这些容器只需在 docker run 启动时通过 ”–volumes-from 容器A“ 选项即可实现数据卷共享,此时容器A就称为数据卷容器。
案例:准备3个centos7容器,第一个使用 -v 创建数据卷,其他两个使用 --volumes-from 方式使用数据卷容器。
# 创建容器,其他测试略
docker run --name mycent1 -it -v /host/log:/opt/log centos:7
docker run --name mycent2 -it --volumes-from mycent1 centos:7
docker run --name mycent3 -it --volumes-from mycent1 centos:7
注1:这些容器可以来自于不同的镜像,如 centos7 和 tomcat。
注2:如果删除 mycent1 ,其他不受影响。
1.3 Dockerfile持久化
VOLUME 指令可以在容器中创建可以挂载数据卷的挂载点,其参数可以是字符串数组也可以是使用空格隔开的多个纯字符串。
例如:VOLUME [“/opt/log”,“/opt/db”] 或 VOLUME /opt/log /opt/db
1.3.1 案例
编写Dockerfile:
构建镜像:
运行容器:
进入某个挂载点,测试新增文件:
问题:宿主机的数据卷在哪?刚才的 a.log 是否同步到了宿主机?
docker inspect 查看容器信息:
2.docker网络
2.1 引入
使用 busybox 构建2个镜像做测试
busybox 在 github 的 Dockerfile:
创建2个容器,按 ctrl + p + q 退出容器,让容器在后台继续运行
docker run --name bb1 -it busybox
docker run --name bb2 -it busybox
使用 docker exec 执行以下命令测试
2.2 网络命名空间
Docker 网络中的相关命令非常少,但需要掌握的底层原理相对较多。
Docker 网络的底层原理是 Linux 的 Network Namespace,所以对于 Linux Network Namespace 的理解对 Docker 网络底层原理的理解非常重要。
Network Namespace 是 Linux 内核提供的用于实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,每个独立的网络空间内的防火墙、网卡、路由表、邻居表、协议栈都是独立的。不管是虚拟机还是容器,当运行在独立的命名空间时,就像是一台单独的主机一样。
下面要通过手工方式创建两个 Network Namespace,并最终让它们相互连通,即可以通过 ping 命令测试成功,用此案例来理解 Docker 网络的底层原理。
创建2个 net namespace
ip netns add ns1
ip netns add ns2
查看命名空间中的 ip信息,目前只有 lo
ip netns exec ns1 ip a
ip netns exec ns2 ip a
如果要让两个命名空间连通,则需要用到虚拟设备接口技术 veth pair,该技术需要一对网络接口分别置于两个命名空间中,
以下命令用于创建一对网络接口(网卡) veth-ns1 与 veth-ns2
ip link add veth-ns1 type veth peer name veth-ns2
把这两个网络接口分配给命名空间
ip link set veth-ns1 netns ns1
ip link set veth-ns2 netns ns2
设置ip
ip netns exec ns1 ip addr add 192.168.1.1/24 dev veth-ns1
ip netns exec ns2 ip addr add 192.168.1.2/24 dev veth-ns2
启动网卡
ip netns exec ns1 ip link set dev veth-ns1 up
ip netns exec ns2 ip link set dev veth-ns2 up
ping测试
ip netns exec ns1 ping 192.168.1.2
ip netns exec ns2 ping 192.168.1.1
2.3 CNM 和 Libnetwork
CNM,Container Network Model,容器网络模型,其是一种网络连接的解决方案,是一种设计规范、设计标准,其规定了 Docker 网络的基础组成要素。
CNM 中定义了三个基本要素:沙盒 Sandbox,终端 Endpoint 与网络 Network。
- 沙盒:一个独立的网络栈,其中包括以太网接口、端口号、路由表、DNS 配置等。Linux Network Namespace 是沙盒的标准实现。
- 终端:虚拟网络接口,主要负责创建连接,即将沙盒连接到网络上。一个终端只能接入某一个网络。
- 网络:802.1d网桥的软件实现,是需要交互的终端的集合。
CNM 是设计规范,而 Libnetwork是开源的、由 Go 语言编写的、跨平台的 CNM 的标准实现。
Libnetwork 除了实现了CNM 的三个组件,还实现了本地服务发现、容器负载均衡,以及网络控制层与管理层功能。
docker inspect bb1:
2.4 Driver
每种不同的网络类型都有对应的不同的底层 Driver,这些 Driver 负责在主机上真正实现需要的网络功能,例如创建 veth pair 设备等。
不过,无论哪种网络类型,其工作方式都是类似的。
通过调用 Docker 引擎的 API发出请求,然后由 Libnetwork 做出框架性的处理,然后将请求转发给相应的Driver。
通过 docker network ls 命令可以査看当前主机所连接的网络及网络类型。
2.5 bridge
bridge 网络,也称为单机桥接网络是 Docker 默认的网络模式,该网络模式只能存在于单个 Docker 主机上,其只能用于连接所在 Docker 主机上的容器。
在 Linux 主机上,Docker 的 bridge 网络由 bridge 驱动创建,其在创建时会创建一个默认的网桥 docker0。容器与网桥间是通过 veth pair 技术实现的连接,网桥与外网间是通过 NAT 实现的连接,即将通信的数据包中的内网地址转换为外网地址。bidge 驱动的底层是基于 Linux 内核的 Linux Bridge 技术,该技术已经经历了近 20 年的考验,这就意味着该模式是高性能且非常稳定的。
查看名称为 bridge 的网络信息 (docker network inspect bridge)
使用 ip a 查看
也能看到 veth pair
在宿主机中用 ip a 看到的 11:vethc1c8084 这种名字,是虚拟出来的,查看本机的网桥信息(yum -y install bridge-utils):
关系图:
其他:
- none 网络:即没有网络,属于此网络的容器仍是一个独立的 Network Namespace,但没有网络接口没有 ip。
(docker run --name bb4 --network none -it busybox) - host 网络:即与宿主机 host 共用一个 Network Namespace,该网络类型的容器没有独立的网络空间,没有独立的 ip,全部与 host 共用。(docker run --name bb5 --network host -it busybox)
2.6 网络相关命令
通过以下案例学习网络相关命令
创建 bridge2 网络,并查看:
通过 ip a 也可以查看:
brctl show 查看到该网络中并无网卡:
创建容器 bb3 ,让其属于 bridge2 网络:
docker inspect bb3 查看信息:
再用 brctl show 查看:
ip a :
此时查看 bridge2 的信息(docker network inspect bridge2),其中的容器只有 bb3:
把 bb2容器也加入到 bridge2:
# Usage: docker network connect [OPTIONS] NETWORK CONTAINER
docker network connect bridge2 bb2
此时查看 bridge2 的信息(docker network inspect bridge2),其中的容器有 bb2 和 bb3:
brctl show:
查看bb2的ip,同时 bb2 和 bb3 可以ping通了
2.7 容器名与ip问题
在自定义容器中容器名和ip会自动解析
如果希望在默认的 docker0 中也这样
3.mysql案例
3.1 mysql 安装
3.1.1 官方安装
# 参考:https://hubgw.docker.com/_/mysql
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
以5.7为例
可以先拉取镜像:
docker pull mysql:5.7
运行容器:
docker run --name mysql57 -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
进入容器、登录mysql、测试建库、建表、插入数据,略…
用 navicat 操作时,对中文字符报错:
中文报错原因:
要解决此问题需要配置 my.cnf 。
此外数据库文件、日志文件都是在容器中的,如果容器出现问题无法运行,则这些文件都无法挽回,所以需要用数据卷来解决这些问题。
这些文件在容器中的位置一般都约定为:
- my.cnf 配置文件:/etc/mysql/conf.d/
- 数据文件:/var/lib/mysql/
3.1.2 生产安装
运行容器:
在数据卷中创建配置文件 my.cnf:
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
port = 3306
datadir=/var/lib/mysql
character-set-server=utf8
pid-file=/var/run/mysqld/mysqld.pid
bind-address = 0.0.0.0
max_connections=2048
default-storage-engine=INNODB
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_AUTO_VALUE_ON_ZERO,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,PIPES_AS_CONCAT,ANSI_QUOTES
重启容器:
docker restart mysql57
测试建库、建表、插入数据,再次进入容器查看字符集信息,略…
也可以测试删除容器,再次创建新容器(看看数据库还在吗)。
3.2 一主一从
单机版的 MySQL存在单点问题,且在高并发场景下性能会急剧下降。所以,生产中对于 MVSQL 都是使用读写分离的主从集群。既保证了数据的安全性,又提升了性能。下面要使用 Docker 搭建一个“一主一从”的 MySQL 读写分离集群。
master 和 slave 注意点:
- 有各自的数据卷
- 端口不能一样
步骤1.master
# 在宿主机创建用来挂载mysql配置文件的目录和存放数据的目录
mkdir -p /db/mysql/master/conf
mkdir -p /db/mysql/master/data
# 授权
cd /db/mysql/master
chmod 777 * -R
# 进入conf目录,创建mysql配置文件:my.cnf,写入以下内容
cd conf/
vim my.cnf
####################################################################################################
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
server-id=1
log-bin=mysql-bin
binlog-ignore-db=mysql
port = 3306
datadir=/var/lib/mysql
character-set-server=utf8
pid-file=/var/run/mysqld/mysqld.pid
bind-address = 0.0.0.0
max_connections=2048
default-storage-engine=INNODB
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_AUTO_VALUE_ON_ZERO,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,PIPES_AS_CONCAT,ANSI_QUOTES
####################################################################################################
# 创建容器
docker run --name mysql-master -v /db/mysql/master/data:/var/lib/mysql -v /db/mysql/master/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 -d -p 3306:3306 mysql:5.7
进入容器或用navicat 连接master,创建用户,查看主机信息
# 创建用户,并授予主从复制权限
mysql> create user 'my'@'%' identified with mysql_native_password by '123456';
mysql> grant replication slave on *.* to 'my'@'%';
# 查看主机上的binlog信息,如下图
mysql> show master status;
步骤2.slave
# 在宿主机创建用来挂载mysql配置文件的目录和存放数据的目录
mkdir -p /db/mysql/slave/conf
mkdir -p /db/mysql/slave/data
# 授权
cd /db/mysql/slave
chmod 777 * -R
# 进入conf目录,创建mysql配置文件:my.cnf,写入以下内容
cd conf/
vim my.cnf
####################################################################################################
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
server-id=2
log-bin=mysql-bin
port = 3306
datadir=/var/lib/mysql
character-set-server=utf8
pid-file=/var/run/mysqld/mysqld.pid
bind-address = 0.0.0.0
max_connections=2048
default-storage-engine=INNODB
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_AUTO_VALUE_ON_ZERO,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,PIPES_AS_CONCAT,ANSI_QUOTES
####################################################################################################
# 创建容器
docker run --name mysql-slave -v /db/mysql/slave/data:/var/lib/mysql -v /db/mysql/slave/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 -d -p 3307:3306 mysql:5.7
进入容器或用navicat 连接 slave,指定主机信息等
# 在从机上设置主机相关信息
mysql> CHANGE MASTER TO
master_host='172.17.0.2',
master_user='my',
master_password='123456',
master_port=3306,
master_log_file='mysql-bin.000003',
master_log_pos=591;
# 开启从机
mysql> START SLAVE;
# 查看状态,如下图
mysql> SHOW SLAVE STATUS\G;
建库建表测试即可。
3.3 优化网络
使用自定义网络优化一主一从的配置
# 测试之前,先删除宿主机数据卷中data目录的信息
# 创建网络
docker network create --subnet 172.18.0.0/16 --gateway 172.18.0.1 mysqlnet
# 创建mysql主机master的容器,注意 --net 的使用,后续操作省略...
docker run -dp 3306:3306 -v /db/mysql/master/data:/var/lib/mysql -v /db/mysql/master/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --net mysqlnet --name mysql-master mysql:5.7
# 创建mysql从机slave的容器,注意 --net 的使用,后续操作省略...
docker run -dp 3307:3306 -v /db/mysql/slave/data:/var/lib/mysql -v /db/mysql/slave/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --net mysqlnet --name mysql-slave mysql:5.7
其他配置略,注意在 slave 中配置主机信息时,只要指定容器名称即可(代替 ip地址)
4. 发布项目
4.1 说明
项目构成:
- 前端:vue+elementUI,部署在 nginx
- 后端:springboot,需要 jdk
- 数据库:mysql 5.7
部署要求:
- 使用Dockerfile为前、后端项目分别构建镜像
- 使用docker网络技术
- 使用docker数据卷技术
4.2 步骤
略…
ata:/var/lib/mysql -v /db/mysql/master/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --net mysqlnet --name mysql-master mysql:5.7
创建mysql从机slave的容器,注意 --net 的使用,后续操作省略…
docker run -dp 3307:3306 -v /db/mysql/slave/data:/var/lib/mysql -v /db/mysql/slave/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --net mysqlnet --name mysql-slave mysql:5.7
其他配置略,注意在 slave 中配置主机信息时,只要指定容器名称即可(代替 ip地址)
[外链图片转存中...(img-iSWvY0ul-1740033529970)]
# 4. 发布项目
## 4.1 说明
项目构成:
- 前端:vue+elementUI,部署在 nginx
- 后端:springboot,需要 jdk
- 数据库:mysql 5.7
部署要求:
- 使用Dockerfile为前、后端项目分别构建镜像
- 使用docker网络技术
- 使用docker数据卷技术
## 4.2 步骤
略...