【Docker】高可用前后端分离项目集群部署

1,766 阅读14分钟

1 部署架构设计

在数据库集群中每一个 MySQL 都要发布到 Docker 虚拟机的实例中,即将每一个 MySQL 部署到一个独立的容器中,通过负载均衡分散数据库的请求。 前后端同样通过集群实现高可用,使用多个节点进行部署,一个节点宕机,其余节点仍能提供服务,中间件使用 Nginx。

未命名文件.jpg

2 实验环境准备

2.1 虚拟机

  1. 磁盘: ≥ 5 GB
  2. 内存: ≥ 2 GB
  3. 处理器:1 个 4 核 CPU
  4. cent OS的优势:
    • 跨平台的硬件支持
    • 可靠的安全性
    • 丰富的软件支持
    • 多用户多任务
    • 良好的稳定性
    • 完善的网络功能

2.2 后端项目

  1. 技术栈:SpringBoot + Shrio + SSM + JWT + Redis + Swagger
  2. Maven 环境
  3. MySQL 环境

2.3 前端项目

  1. 技术栈:Vue + ElementUI
  2. Node.js 环境

2.4 Docker虚拟机

Docker 创建的所有虚拟实例共用同一个 Linux 内核,对硬件占用小,属于轻量级虚拟机。容器是从镜像中创建出来的虚拟实例,容器是读写层,用来运行程序,镜像是只读层,用来安装程序。

  1. 更新 yum 软件管理器,安装 Docker
yum -y update
yum install -y docker
service docker start/stop/restart

【Error】[Errno 14] HTTP Error 404 - Not Found

【Solution】

yum clean all
rpm --rebuilddb
  1. Docker 虚拟机管理命令

未命名文件.jpg

  1. 配置 DaoCloud 的Docker加速器

  2. 在线安装 Java 镜像

docker search java
docker pull java

[root@localhost ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
java         latest    d23bdf5b1b1b   4 years ago   643MB 

# 将镜像保存为文件
$ docker save docker.io/java > /home/java.tar.gz
# 移除镜像
$ docker rmi docker.io/java
# 将文件导入为镜像
$ docker load < /home/java.tar.gz
  1. 启动容器:启动镜像会创建出一个运行状态的容器
$ docker run -it --name myjava java bash
$ docker run -it --name myjava -p 9000:8080 -p 9001:8085 java bash
$ docker run -it -p 9000:8000 -p 9001:8085 -v /home/project:/soft --privileged --name myjava docker.io/java bash

【Error】docker: Error response from daemon: Conflict. The container name "/myjava" is already in use by container "8301ced0145f6f10223f7403c222630118791de905eb9c119445495e67c7d7fd". You have to remove (or rename) that container to be able to reuse that name. 【Solution】通过docker ps -a查看容器运行状态,通过docker rm id/name移除容器。

  1. 暂停和停止容器
[root@localhost/] docker pause myjava
[root@localhost/] docker uppause myjava
[root@localhost/] docker stop myjava
# 进入容器
[root@localhost/] docker start -i myjava

3 搭建数据库集群

3.1 单节点数据库的缺陷

  1. 大型互联网程序用户群体庞大,单节点数据库无法满足性能要求
  2. 单节点数据库没有冗余设计,无法满足高可用

3.2 常见的 MySQL 集群方案

方案特点适用场景
Replication速度快、弱一致性、低价值日志、新闻、帖子
PXC速度慢、强一致性、高价值订单、账户、财务

PXC 原理

PXC 的全称是 Percona XtraDB Cluster,是基于 Galera 技术实现的集群,任何一个数据库的节点都是可读可写的

使用的数据库实例可以是原生的 MySQL,但建议 PXC 使用 PerconaServer,PerconaServer 是 MySQL 的改进版,具有很好的性能提升。 tu1.jpg

PXC 和 Replication 方案对比

PXC 方案:PXC 具有数据强一致性,通过同步复制,事务在所有节点上要么同时提交,要么都不提交。 tu1.jpg

Replication 方案:Replication 采用异步复制,无法保证数据的一致性。 tu1.jpg

3.3 PXC集群安装

Docker 的镜像仓库中包含了 PXC 数据库的镜像,可以通过docker pull docker.io/percona/percona-xtradb-cluster直接下载。

处于安全考虑,需要给 PXC 集群实例创建 Docker 内部网络

# 创建网段
docker network create net1
# 查看网段相关信息
docker network inspect net1
# 删除网段
docker network rm net1
[root@localhost ~]# docker network create --subnet=172.18.0.0/24 net19322b84026c901b5501d2e680d388e8dfcf6cd0fa54cbeeb38de82e3fa095859
[root@localhost ~]# docker inspect net1
[
    {
        "Name": "net1",
        "Id": "9322b84026c901b5501d2e680d388e8dfcf6cd0fa54cbeeb38de82e3fa095859",
        "Created": "2021-04-11T08:24:17.009369885Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/24"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

3.4 创建 Docker 卷

容器中的 PXC 节点映射数据目录的解决方法

[root@localhost ~]#  docker volume create v1
v1
[root@localhost ~]# docker inspect v1
[
    {
        "CreatedAt": "2021-04-11T08:29:20Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/v1/_data",
        "Name": "v1",
        "Options": {},
        "Scope": "local"
    }
]

3.5 创建 PXC 容器

向 PXC 镜像传入运行参数,创建出 PXC 容器。

相关参数:

  • -d 创建出的实例后台运行
  • -p 指定端口( port1 宿主机端口: port2 容器端口)
  • -v 路径映射(数据卷中的文件目录)
  • -e 启动参数

启动5个容器:

docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -v v1:/var/lib/mysql --privileged --name=node1 --net=net1 --ip 172.18.0.2 pxc
docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=node1 -v v2:/var/lib/mysql --privileged --name=node2 --net=net1 --ip 172.18.0.3 pxc

docker run -d -p 3308:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=node1 -v v3:/var/lib/mysql --privileged --name=node3 --net=net1 --ip 172.18.0.4 pxc

docker run -d -p 3309:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=node1 -v v4:/var/lib/mysql --privileged --name=node4 --net=net1 --ip 172.18.0.5 pxc

docker run -d -p 3310:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -e CLUSTER_JOIN=node1 -v v5:/var/lib/mysql --privileged --name=node5 --net=net1 --ip 172.18.0.6 pxc

【Error】docker 使用 pxc 创建 mysql 集群节点启动自动退出

【Solution】使用指定版本的 PXC,docker pull percona/percona-xtradb-cluster:5.7.21, 每个节点创建之后,因为要执行pxc的初始化和加入集群等工作,耐心等待一点时间再用客户端连接MySQL,客户端连接成功后,再去创建node2节点。设置volume目录读写权限,chmod -R 777 /var/lib/docker/volumes/v1/_data

完成5个节点的启动:

image.png

3.5 数据库负载均衡

在搭建集群的同时,还需要使用数据库负载均衡,否则单节点处理所有请求,负载高,性能差。可以使用 Haroxy 等中间件做负载均衡,请求会被均匀分发给各个节点,单节点负载低,性能好。 tu1.jpg

中间件产品免费虚拟机HTTP协议TCP/IP协议插件性能
Haproxy支持支持支持不支持
Nginx支持支持支持支持
Apache支持支持不支持不支持一般
LVS不支持支持支持不支持最好

拉取Haproxydocker pull haproxy

创建Haproxy配置文件touch /home/soft/haproxy.cfg

global
    #工作目录
    chroot /usr/local/etc/haproxy
    #日志文件,使用rsyslog服务中local5日志设备(/var/log/local5),等级info
    log 127.0.0.1 local5 info
    #守护进程运行
    daemon

defaults
    log    global
    mode    http
    #日志格式
    option    httplog
    #日志中不记录负载均衡的心跳检测记录
    option    dontlognull
    #连接超时(毫秒)
    timeout connect 5000
    #客户端超时(毫秒)
    timeout client  50000
    #服务器超时(毫秒)
    timeout server  50000

#监控界面    
listen  admin_stats
    #监控界面的访问的IP和端口
    bind  0.0.0.0:8888
    #访问协议
    mode        http
    #URI相对地址
    stats uri   /dbs
    #统计报告格式
    stats realm     Global\ statistics
    #登陆帐户信息
    stats auth  admin:abc123456
#数据库负载均衡
listen  proxy-mysql
    #访问的IP和端口
    bind  0.0.0.0:3306  
    #网络协议
    mode  tcp
    #负载均衡算法(轮询算法)
    #轮询算法:roundrobin
    #权重算法:static-rr
    #最少连接算法:leastconn
    #请求源IP算法:source 
    balance  roundrobin
    #日志格式
    option  tcplog
    #在MySQL中创建一个没有权限的haproxy用户,密码为空。Haproxy使用这个账户对MySQL数据库心跳检测
    option  mysql-check user haproxy
    server  MySQL_1 172.18.0.2:3306 check weight 1 maxconn 2000  
    server  MySQL_2 172.18.0.3:3306 check weight 1 maxconn 2000  
    server  MySQL_3 172.18.0.4:3306 check weight 1 maxconn 2000 
    server  MySQL_4 172.18.0.5:3306 check weight 1 maxconn 2000
    server  MySQL_5 172.18.0.6:3306 check weight 1 maxconn 2000
    #使用keepalive检测死链
    option  tcpka

创建Haproxy容器

# 后台监控端口是8888 和数据库实例在一个网段
docker run -it -d -p 4001:8888 -p 4002:3306 -v /home/soft/haproxy:/usr/local/etc/haproxy --name h1 --privileged --net=net1 --ip 172.18.0.7 haproxy bash

进入Haproxy容器docker exec -it h1 bash

【Error】Error response from daemon: Container dd729aa0c8bdbbee428ceda7af5802dbedf454ee7c4fbee7a796ebedf36165c9 is not running

【Solution】通过docker ps -a发现 h1 处于退出状态,在上面的 run 的执行语句中少加了bash,每次 run 完 2s 后就退出了,加上 bash 后,持续处于 up 状态。

加载配置文件haproxy -f /usr/local/etc/haproxy/haproxy.cfg

【Error】文件不存在

【Solution】将文件从宿主机拷贝到容器中:docker cp /home/soft/haproxy.cfg h1:/usr/local/etc/haproxy/

在MySQL中创建一个 haproxy 账号create user 'haproxy'@'%' identified by ''

访问DBShttp://192.168.3.113:4001/dbs 输入配置文件中定义的用户名和密码

image.png

4 双机热备

单节点Haproxy不具有高可用性,必须要有冗余设计。

Linux可以在一个网卡中定义多个虚拟IP,可以将IP分配给不同的应用程序。

4.1 利用keepalived实现双机热备

Keepalived用来抢占虚拟IP,争抢到虚拟IP的作为主服务器,等待虚拟IP的作为备用服务器,两个服务器之间存在心跳检测,如果心跳检测没有响应,说明主服务器可能出现了故障,备用服务器可以去争抢虚拟IP。

tu1.jpg

4.2 Haproxy双机热备方案

通过上述步骤创建数据库集群,里面存在多个 PXC 实例,通过负载均衡转发请求。 创建两个容器分别部署 Haproxy,内部安装 Keepalived,这样一来,任意一个容器宕机之后,还有另一个容器可以使用。两个 Keepalived 抢占 172.18.0.X 虚拟 IP。Docker 内的虚拟 IP 不可以被外网使用,所以需要借助宿主机 Keepalived 映射为外网可以访问的虚拟 IP。

tu1.jpg

4.3 安装Keepalived

  1. Keepalived 必须安装在 Haproxy 所在的容器之内
# 先进入h1
[root@localhost ~]# docker exec -it h1 bash
# 在h1中安装
root@a5dfcd63c066:/# apt-get update
root@a5dfcd63c066:/# apt-get install keepalived
  1. 编辑keepalived的配置文件/etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
    # keepalived的身份(MASTER/BACKUP)主服务器抢占虚拟IP 备用服务器不会抢占
    state MASTER
    # 网卡设备
    interface eth1
    # 虚拟路由标识,主备的虚拟路由标识必须一致(0~255)
    virtual_router_id 51
    # MASTER权重高于BACKUP 数字越大优先级越高
    priority 100
    # 心跳检测的间隔
    
    
    时间,单位为s
    advert_int 1
    # 主服务器验证方式,主备必须使用相同的密码才能正常通信
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    # 虚拟IP地址,可以设置多个虚拟IP,每行一个
    virtual_ipaddress {
        172.18.0.201
    }
}
  1. 启动 Keepalived:service keepalived start
  2. 宿主机可以 ping 通虚拟 IP。 【Error】keepalived 绑定虚拟IP失败。

vrrp_instance VI_1 { state MASTER interface eth1 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 172.18.0.201 } }

【Error】apt-get 换源的问题

W: GPG 错误:http://mirrors.ustc.edu.cn/ubuntu trusty Release: 由于没有公钥,无法验证下列签名: NO_PUBKEY 40976EAF437D05B5 NO_PUBKEY 3B4FE6ACC0B21F32
E: 仓库 “http://mirrors.ustc.edu.cn/ubuntu trusty Release” 没有数字签名。

【Solution】

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 # 最后这部分就是上面那个NO_PUBKEY 后面的一坨

4.4 热备份数据

冷备份是关闭数据库时的备份方式,通常做法是拷贝数据文件。冷备份是最 简单、最安全的一种备份方式,大型网站无法关闭业务备份数据,所以冷备份不是好的选择。

热备份是在系统运行的状态下备份数据,也是难度最大的备份。MySQL 常见的热备份有 LVM 和 XtraBackup 两种方案。

LVM 是 Linux 自带的备份方式,Linux 对某个分区创建快照,实现对这个分区的备份,缺点是备份数据库的时候需要对数据库加锁,数据库只能读数据不能写数据。

XtraBackup 是一款基于 InnoDB 的在线热备工具,开源免费、支持在线热备,占用磁盘空间小,可以快速的备份和恢复数据库,XtraBackup 不需要锁表就可以备份,备份时不会打断正在执行的事务,基于压缩等功能节约磁盘空间和流量,所以建议使用 XtraBackup。

XtraBackup 分为全量备份和增量备份,全量备份是备份全部数据,备份时间长,占用空间大。增量备份时只备份变化的那部分的数据,备份时间短,占用空间小。一般一周进行一次全量备份,一天进行一次增量备份。

4.5 PXC 全量备份

PXC 容器中安装 XtraBackup,并执行备份。在 node1 中添加 backup,删除node1,重新创建 node1。

docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e CLUSTER_NAME=PXC -e XTRABACKUP_PASSWORD=123456 -v v1:/var/lib/mysql -v backup:/data --privileged -e CLUSTER_JOIN=node2 --name=node1 --net=net1 --ip 172.18.0.2 pxc
# 进入 node1
docker exec -it node1 bash
apt-get update
apt-get install percona-xtrabackup-24
# 数据库用户名和密码 存储位置
innobackupex --user=root --password=123456 /data/backup/full

完成备份: image.png

4.6 PXC全量恢复

数据库可以热备份,但是不能热还原,为了避免恢复过程中的数据同步,我们采用空白的 MySQL 还原数据,然后再建立 PXC 集群。

还原数据前要将未提交的事务回滚,还原数据之后,重启 MySQL。

rm -rf /var/lib/mysql/*
innobackupex --user=root --password=123456 --apply-back /data/backup/2020-4-12_02-58-43/
innobackupex --user=root --password=123456 --copy-back /data/backup/2020-4-12_02-58-43/

5 Redis集群搭建

Redis 是 Wmvare开发的开源免费的 key-value 的 NoSQL 缓存产品,Redis 具有很好的性能,最多可以提供 10W 次/秒的读写。

Redis目前的集群方案包括:

  • RedisCluster:官方推荐,没有中心节点,客户端和 Redis 节点直连,不需要中间代理层。数据可以被分片存储。管理方便,后续可以新增和删除节点。
  • Codis:中间件产品,存在中心节点
  • Twemproxy:中间件产品,存在中心节点

5.1 Redis 主从同步

Redis 集群中的数据库复制是通过主从同步实现的,主节点把数据分发给从节点,主从同步的优点是高可用,Redis 节点有冗余设计。

Redis 集群中应该包含奇数个 Master,至少有3个 Master,Redis 有选举机制,超过半数节点宕机,无法执行选举。且每个 Master 都应该有 Slave。1 tu1.jpg

5.2 获取redis镜像

5.2.1 创建redis docker基础镜像

  1. 下载redis安装包:wget http://download.redis.io/releases/redis-4.0.1.tar.gz
  2. 解压:tar zxvf redis-4.0.1.tar.gz
  3. 进入redis文件夹,编译:make
  4. 修改redis配置:vi /home/soft/docker_redis/redis-4.0.1/redis.conf
daemonize yes #以后台进程运行
cluster-enabled yes # 开启集群
cluster-config-file nodes.conf # 集群配置文件
cluster-node-timeout 15000 # 超时时间
appendonly yes # 开启AOF模式
  1. 进入/home/soft/docker_redis,制作镜像:vi Dockerfile
# Redis
# Version 4.0.1

FROM centos:7
ENV REDIS_HOME /usr/local
ADD redis-4.0.1.tar.gz / # 本地的redis源码包复制到镜像的根路径下,ADD命令会在复制过后自动解包。被复制的对象必须处于Dockerfile同一路径,且ADD后面必须使用相对路径
RUN mkdir -p $REDIS_HOME/redis # 创建安装目录
ADD redis-4.0.1/redis.conf $REDIS_HOME/redis/  # 将一开始编译产生并修改后的配置复制到安装目录

RUN yum -y update  # 更新yum源
RUN yum install -y gcc make # 安装编译需要的工具

WORKDIR /redis-4.0.1
RUN make
RUN mv /redis-4.0.1/src/redis-server  $REDIS_HOME/redis/   # 编译后,容器中只需要可执行文件redis-server

WORKDIR /
RUN rm -rf /redis-4.0.1          # 删除解压文件

RUN yum remove -y gcc make   # 安装编译完成之后,可以删除多余的gcc跟make

VOLUME ["/var/log/redis"]  # 添加数据卷

EXPOSE 6379   # 暴露6379端口,也可以暴露多个端口,这里不需要如此
  1. 构建镜像:docker build -t xd1998/cluster-redis

5.2.2 拉取官方 Redis

这里有一点需要注意,官方的 Redis 镜像没有 Redis 的配置文件,所以需要操作一下。

  1. redis 官网下载压缩包,拿到 redis.conf
  2. 修改 redis.conf
# bind 127.0.0.1
daemonize no # 用守护线程的方式启动
appendonly yes # redis持久化
tcp-keepalive 300 # 防止出现远程主机强迫关闭了一个现有的连接的错误 默认是300
  1. 创建本地存放redis的配置:mkdir /data/redis/data
  2. 复制:cp -p redis.conf /data/redis/
  3. 启动docker:docker run -p 6379:6379 --name redis -v /data/redis/redis.conf:/etc/redis/redis.conf -v /data/redis/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes

5.3 安装 redis-trib.rb

redis-trib 是基于 Ruby 的 Redis 集群命令工具。

cp /usr/redis/src/redis-trib.rb /usr/redis/cluster/
cd /usr/redis/cluster
apt-get install ruby
apt-get install rubygems
gem install redis

利用 redis-trib.rb 创建 Redis 集群:./redis-trib.rb create --replicas 1 172.19.0.2:6379 172.19.0.3:6379 172.19.0.4:6379 172.19.0.5:6379 172.19.0.6:6379 172.19.0.7:6379