# MySQL 主从复制
# 新建主服务器容器实例 3307
docker run -p 3307:3306 --name mysql-master \ | |
-v /mydata/mysql-master/log:/var/log/mysql \ | |
-v /mydata/mysql-master/data:/var/lib/mysql \ | |
-v /mydata/mysql-master/conf:/etc/mysql \ | |
-e MYSQL_ROOT_PASSWORD=root \ | |
-d mysql:5.7 |
# 新建 my.cnf 文件
进入 /mydata/mysql-master/conf 目录下新建 my.cnf
vim my.cnf |
添加配置文件内容
[mysqld] | |
# 配置 server_id,同一局域网中需要唯一 | |
server_id=101 | |
# 配置指定不需要的数据库名称 | |
binlog-ignore-db=mysql | |
# 开启二进制日志功能 | |
log-bin=mail-mysql-slave1-bin | |
# 设置二进制日志使用内存大小(事务) | |
binlog_cache_size=1M | |
# 设置使用的二进制日志格式(mixed,statement,row) | |
binlog_format=mixed | |
# 二进制日志过期清理时间,默认值为 0,表示不自动清理 | |
expire_logs_days=7 | |
# 跳过主从复制中遇到的所有错误或指定类型的错误,避免 slave 端复制中断 | |
slave_skip_errors=1062 |
# 重启 master 实例
docker restart mysql-master |
# 进入 mysql-master 容器
docker exec -it mysql-master /bin/bash |
mysql -uroot -proot |
# 创建数据同步用户
# 创建用户
create user 'slave'@'%' identified by '123456';
# 授予权限
grant replication slave, replication client on *.* to 'slave'@'%';
# 新建从服务器 3308
docker run -p 3308:3306 --name mysql-slave \ | |
-v /mydata/mysql-slave/log:/var/log/mysql \ | |
-v /mydata/mysql-slave/data:/var/lib/mysql \ | |
-v /mydata/mysql-slave/conf:/etc/mysql \ | |
-e MYSQL_ROOT_PASSWORD=root \ | |
-d mysql:5.7 |
# 新建 my.cnf 文件
进入 /mydata/mysql-slave/conf 目录下新建 my.cnf
vim my.cnf |
添加配置文件内容
[mysqld] | |
# 配置 server_id,同一局域网中需要唯一 | |
server_id=102 | |
# 配置指定不需要的数据库名称 | |
binlog-ignore-db=mysql | |
# 开启二进制日志功能 | |
log-bin=mail-mysql-slave1-bin | |
# 设置二进制日志使用内存大小(事务) | |
binlog_cache_size=1M | |
# 设置使用的二进制日志格式(mixed,statement,row) | |
binlog_format=mixed | |
# 二进制日志过期清理时间,默认值为 0,表示不自动清理 | |
expire_logs_days=7 | |
# 跳过主从复制中遇到的所有错误或指定类型的错误,避免 slave 端复制中断 | |
slave_skip_errors=1062 | |
# reply_log 配置中继日志 | |
relay_log=mail-mysql-relay-bin | |
# log_slave_updates 表示将复制事件写进自己的二进制日志 | |
log_slave_updates=1 | |
# slave 设置为只读(具有 super 权限的用户除外) | |
read_only=1 |
# 重启 slave 实例
docker restart mysql-slave |
# 在主数据库查看主从同步状态
show master status; |
# 进入 mysql-slave 容器
docker exec -it mysql-slave /bin/bash |
mysql -uroot -p123456 |
# 在从数据库中配置主从复制
change master to master_host='192.168.116.129', master_user='slave', master_password='123456', master_port=3307, master_log_file='mail_mysql_bin.000001', master_log_pos=617, master_connect_retry=30; |
# 在从数据库中查看主从同步状态
show slave status \G; |
# 在从数据库中开启主从同步
start slave; |
# 查看从数据库状态发现已经同步
show slave status \G; |
# 分布式存储
# 哈希取余分区算法
# 介绍
每次进行取余操作,计算出哈希值,用来决定数据映射到哪一个节点上
# 优点
简单粗暴,直接有效,只需要预估好数据规划好节点,就能保证一段时间的数据支撑,使用 Hash 算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡 + 分而治之的作用
# 缺点
某个 redis 机器宕机了,由于台数数量变化,会导致 hash 取余全部数据重新洗牌
# 一致性哈希算法
# 算法构建一致性哈希环
一致性哈希算法必然有个 hash 函数并按照算法产生 hash 值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个 hash 空间,这个是一个线性空间,但在算法中,我们通过适当的逻辑控制将它首位连接,这样就让它逻辑上形成一个环形空间。
# 服务器 IP 节点映射
将集群中的 IP 节点映射到环上的某一个位置
将各个服务器使用 Hash 进行一个哈希,具体可以选择服务器的 IP 或主机名作为关键字进行哈希,这样每台机器就能确定哈希环上的位置,假如 4 个节点 NodeA,B,C,D,经过 IP 地址的哈希函数计算 (hash (ip)),使用 IP 地址哈希后的环空间的位置如下:
# key 落到服务器的落键规则
当我们需要存储一个 kv 键值对时,首先计算 key 的 hash 值,hash (key),将这个 key 使用相同的函数 Hash 计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针行走,第一台遇到的服务器就是其应该定位到的服务器,并将键值对存储在该节点上
假设我们有 ObjectA、ObjectB、ObjectC、ObjectD 四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性 Hash 算法,数据 A 会定为 Node A 上,B 会定为 Node B 上,C 会定为 Node C 上,D 会定为 Node D 上。
# 优点
# 一致性哈希算法的容错性
加入 Node C 宕机,可以看到此对象 A、B、C、D 不会受到影响,只有 C 对象被定位到 Node D,一般的,在一致性 Hash 算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走的低的第一台服务器)之间的数据,其它不会受到影响,简单说,就是 C 挂了,受到影响的是 B、C 之间的数据,并且这些数据会转移到 D 进行存储
# 一致性哈希算法的扩张性
数据量增加了,需要增加一台节点 Node X,X 的位置在 A 和 B 之间,那受到影响的也就是 A 到 X 之间的数据,重新把 A 到 X 的数据录入到 X 上即可,不会导致 hash 取余全部数据重新洗牌
# 缺点
# Hash 环数据倾斜问题
一致性 Hash 算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题
# 哈希槽分区算法
# 哈希槽简介
在数据和节点之间加入了一层,这层称为哈希槽(slot),用于管理数据与节点之间的关系,现在就相当于节点上放的是槽,槽里面放的是数据
# 哈希槽计算
Redis 集群内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同节点,当需要 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 取余,这样每个 key 就会对应一个编号在 0 - 16383 之间的哈希槽,也就是映射到某个对应的节点上
# 主 3 从 redis 集群配置
# 创建 6 个 redis 容器
docker run -d --name redis-node-1 --net host --privileged=true -v /data/redis/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381 | |
docker run -d --name redis-node-2 --net host --privileged=true -v /data/redis/share/redis-node-2:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6382 | |
docker run -d --name redis-node-3 --net host --privileged=true -v /data/redis/share/redis-node-3:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6383 | |
docker run -d --name redis-node-4 --net host --privileged=true -v /data/redis/share/redis-node-4:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6384 | |
docker run -d --name redis-node-5 --net host --privileged=true -v /data/redis/share/redis-node-5:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6385 | |
docker run -d --name redis-node-6 --net host --privileged=true -v /data/redis/share/redis-node-6:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6386 |
# 进入容器
docker exec -it redis-node-1 /bin/bash |
# 构建主从关系
redis-cli --cluster create 192.168.116.129:6381 192.168.116.129:6382 192.168.116.129:6383 192.168.116.129:6384 192.168.116.129:6385 192.168.116.129:6386 --cluster-replicas 1 |
# 查看集群状态
redis-cli -p 6381 |
cluster info |
cluster nodes |
# redis 集群读写
# error 说明
# 解决办法
使用集群方式连接
redis-cli -p 6381 -c |
# 查看集群路由状态
redis-cli --cluster check 192.168.116.129:6381 |
# 主从容错切换迁移
- 主 redis 节点宕机,对应的从 redis 节点升为 master
- 之前的主 redis 节点恢复,现在恢复的 redis 节点为从 redis 节点
# 主从扩容
# 新建 6387、6388 两个节点
docker run -d --name redis-node-7 --net host --privileged=true -v /data/redis/share/redis-node-7:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6387 | |
docker run -d --name redis-node-8 --net host --privileged=true -v /data/redis/share/redis-node-8:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6388 |
# 进入 6387 容器实例内部
docker exec -it redis-node-7 /bin/bash |
# 将新增的 6387 系欸但作为 master 节点加入原集群
redis-cli --cluster add-node 192.168.116.129:6387 192.168.116.129:6381 |
# 重新分配槽号
redis-cli --cluster reshard 192.168.116.129:6381 |
# 将 6388 作为从节点挂载到 6387 下面
redis-cli --cluster add-node 192.168.116.129:6388 192.168.116.126:6387 --cluster-slave --cluster-master-id e2e097252f20727da3df32b05eabc23bac879ca2 |
# 主从缩容
# 检查集群情况获取 6388 节点 ID
redis-cli --cluster check 192.168.116.129:6382 |
# 将从节点 6388 清除
redis-cli --cluster del-node 192.168.116.129:6387 e2e097252f20727da3df32b05eabc23bac879ca2 |
# 重新分配槽号
# 清空 6387 槽位分配给 6381
redis-cli --cluster reshard 192.168.116.129:6381 |
# 将 6387 从集群中删除
redis-cli --cluster del-node 192.168.116.129:6387 e2e097252f20727da3df32b05eabc23bac879ca2 |
# Dockerfile
# 介绍
Dockerfile 是用来构建 Docker 镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本
# 构建三步骤
- 编写 Dockerfile 文件
- docker build 命令构建镜
- docker run 依镜像运行容器实例
# Dockerfile 构建过程解析
# Dockerfile 基础知识
- 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
- #表示注解
- 每条指令都会创建一个新的镜像层并对镜像进行提交
# Docker 执行 Dockerfile 的大致流程
- docker 从基础镜像运行一个容器
- 执行一条指令并对容器做出修改
- 执行类似 docker commit 的操作提交一个新的镜像层
- docker 再基于刚提交的镜像运行一个新的容器
- 执行 dockerfile 中的下一条指令直到所有指令都要执行完成
# Dockerfile 常用保留字
# FROM
基础镜像,当前新的镜像是基于哪个镜像,指定一个已经存在的镜像作为模板,第一条必须是 from
# MAINTAINER
镜像维护者的姓名和邮箱
# RUN
容器构建时需要运行的命令,Run 命令是在 docker build 时运行的
# EXPOSE
当前容器对外暴露出的端口
# WORKDIR
指定创建容器后,中断默认登录进来的工作目录,一个落脚点
# USER
指定该镜像以什么用户去执行,如果不指定,默认是 root
# ENV
用来在构建镜像过程中设置环境变量
# ADD
将宿主机目录下的文件拷贝进镜像且会自动处理 URL 和解压 tar 压缩包
# COPY
拷贝文件和目录到镜像中
# VOLUME
容器数据卷,用于数据保存和持久化工作
# CMD
- 指定容器启动后要干的事情
- Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
- 可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
- CMD 在 docker run 时运行
- RUN 在 docker build 时运行
# ENTRYPOINT
- 指定一个容器启动时要运行的命令
- 类似于 CMD 命令,但是 ENTRYPOINT 不会被 docker run 后面的命令覆盖,而且这些命令参数会被当前参数传给 ENTRYPOINT 指令指定的程序
- ENTRYPOINT 和 CMD 一起用,一般变参才会使用 CMD,这里的 CMD 等于是在给 ENTRYPOINT 传参
- 当指定了 ENTRYPOINT 后,CMD 的含义就发生了变化,不再是运行命令,而是将 CMD 的内容作为参数传递给 ENTRYPOINT 指令
FROM nginx | |
ENTRYPOINT ["nginx", "-c"] | |
CMD ["/etc/nginx/nginx.conf"] |
# 虚悬镜像
# 介绍
仓库名、标签都是 none 的镜像,俗称 dangline image
# 删除
docker image prune |
# 微服务工程 Docker 部署
# 编写 Dockerfile
# Dockerfile 内容
# 基础镜像使用 java8 | |
FROM java:8 | |
# 作者 | |
MAINTAINER baozi | |
# VOLUME 指定临时文件目录为 /tmp | |
VOLUME /tmp | |
# 将 jar 包添加到容器中并更名 | |
ADD docker_boot-0.0.1-SNAPSHOT.jar baozi_docker.jar | |
# 运行 jar 包 | |
RUN bash -c 'touch /baozi_docker.jar' | |
ENTRYPOINT ["java","-jar","/baozi_docker.jar"] | |
# 暴露 6001 端口作为微服务 | |
EXPOSE 6001 |
# 目录存放
将微服务 jar 包和 Dockerfile 文件上传到统一目录下
# 构建镜像
docker build -t baozi_docker:1.6 . |
# 运行容器
docker run -d -p 6001:6001 baozi_docker:1.6 |
# network
# 介绍
docker 启动之后,会产生一个名为 docker0 的虚拟网桥。就是通过这个网桥与宿主机之间,以及容器与容器之间进行连接
# 常用命令
# ls
查看所有的 docker 网络模式命令
docker network ls |
# COMMAND
docker network COMMAND |
# create
创建一个 network
docker network create name__network |
# rm
删除一个 network
docker network rm name_network |
# inspect
查看网络详情
docker network inspect bridge |
# 作用
- 容器间的互联和通信以及端口映射
- 容器 IP 变动的时候可以通过服务名直接网络通信而不受到影响
# 网络模式分类
网络模式 | 简介 |
---|---|
bridge | 为每一个容器分配、设置 IP,并将容器连接到一个 docker,虚拟网桥,默认为该模式 |
host | 容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口 |
none | 容器有独立的 Network namespace,但并没有对其进行任何网络设置,如分配 veth pair 和网桥连接,IP 等 |
container | 新创建的容器不会创建自己的网卡和配置自己的 IP,而是和一个指定的容器共享 IP,端口范围等 |
# bridge 模式
# 简介
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),该桥接网络的名称为 docker0,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络,Docker 默认指定了 docker0 接口的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信
# 查看 bridge 网络详细信息
docker network inspect bridge | grep name |
# 底层连接模式
- 整个宿主机的网桥模式都是 docker0,类似于一个交换机有一堆接口,每个接口叫 veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫 veth pair)
- 每一个容器实例内部也有一块网卡,每个接口叫 eth0
- docker0 上面的每个 veth 匹配某个容器实例内部的 eth0,两两配对,一一配对
# host 模式
# 介绍
直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行 NAT 转换
# 底层连接模式
容器将不再获得一个独立的 Network Namespace,而是和宿主机共用一个 Network Namespace,容器将不会虚拟出自己的网卡而是使用宿主机的 IP 和端口
# none 模式
# 介绍
禁用网络功能,只有 lo 标识(就是 127.0.0.1 表示本地回环)
# 底层连接模式
- 在 none 模式下,并不为 Docker 容器进行任何网络配置
- 也就是说,这个 Docker 容器没有网卡、IP、路由等信息,只有一个 lo
- 需要我们自己为 Docker 容器添加网卡、配置 IP 等
# container 模式
# 介绍
新建的容器和已经存在的一个容器共享一个网络 ip 配置而不是和宿主机共享,新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等,同样,这两个容器除了网络方面,其它的如文件系统、进程列表等还是隔离的
# 自定义网络
# 介绍
自定义网络本身就维护好了主机名和 IP 的对应关系(ip 和域名都能通)
# Compose
# 介绍
- Docker-Compose 是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排
- Compose 是 Docker 公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用,你需要定义一个 YAML 文件格式的配置文件 docker-compose.yml,写好多个容器之间的调用关系,然后,只要一个命令,就能同时启动 / 关闭这些容器
# 作用
- Compose 允许用户通过一个单独的 docker-compose.yml 模板文件来定义一组相关联的应用容器为一个项目(project)
- 可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装应用的所有依赖,完成构建,Docker-Compose 解决了容器与容器之间如何管理编排的问题
# 核心概念
# 一个文件
docker-compose.yml 文件
# 两个要素
# 服务(service)
一个应用容器实例,比如订单微服务、库存微服务、mysql 容器
# 工程(project)
由一组关联的应用容器组成一个完整的业务单元,在 docker-compose.yml 文件中定义
# Compose 使用的三个步骤
- 编写 Dockerfile 定义各个微服务应用并构建出对应的镜像文件
- 使用 docker-compose.yml 定义一个完整的业务单元,安排好整体应用中的各个容器服务
- 最后,执行
docker-compose up
命令,来启动并运行整个应用程序,完成一键部署
# Compose 常用命令
命令 | 说明 |
---|---|
docker-compose -h |
查看帮助 |
docker-compose up |
启动所有 docker-compose 服务 |
docker-compose up -d |
启动所有 docker-compose 服务并后台运行 |
docker-compose down |
停止并删除容器、网络、卷、镜像 |
docker-compose exec yml里面服务的id |
进入容器实例内部 |
docker-compose ps |
展示当前 docker-compose 编排过的运行所有容器 |
docker-compose top |
展示当前 docker-compose 编排过的容器进程 |
docker-compose logs yml里面的服务id |
查看容器输出日志 |
docker-compose config |
检查配置 |
docker-compose config -q |
检查配置,有问题才有输出 |
docker-compose restart |
重启服务 |
docker-compose start |
启动服务 |
docker-compose stop |
停止服务 |
# Portainer
# 启动 Portainer 实例
docker run -d -p 8000:8000 -p 9000:9000 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest |
# GIC 容器重量级监控系统
# 介绍
CAdvisor 监控收集 + InfluxDB 存储数据 + Granfana 展示图表
# CAdvisor
# 介绍
CAdvisor 是一个容器资源监控工具,包括容器的内存 CPU,网络 IO,磁盘 IO 监控,同时提供一个 WEB 页面用于查看容器的实时运行状态,CAdvisor 默认存储是 2 分钟的数据,而且只是针对单物理机,不过,CAvisor 提供了很多数据集成接口,支持 InfluxDB,Redis,Kafka,Elasticsearch 等集成,可以加上对应配置将监控数据发往这些数据库存储起来
# 功能
- 展示 host 和容器两个层次的监控数据
- 展示历史变化的数据
# InfluxDB
# 介绍
InfluxDB 是用 Go 语言编写的开源分布式时序、事件和指标数据库,无需外部依赖
CAdvisor 默认只是在本机保存最近两分钟的数据,为了持久化存储数据和统一收集展示监控数据,需要将数据存储到 InfluxDB 中,InfluxDB 是一个时序数据库,专门用于存储时序相关数据,很适合存储 CAdvisor 的数据,而且,CAdvisor 本身已经提供了 InfluxDB 的集成方法,丰启动容器时指定配置即可
# 功能
- 基于时间排序,支持与时间有关的相关函数
- 可度量性,你可以实时对大量数据进行计算
- 基于事件,它支持任意的事件数据
# Granfana
# 介绍
Granfana 是一个开源的数据监控分析可视化平台,支持多种数据源配置和丰富的插件及模板功能,支持图表权限控制与报警
# 功能
- 灵活丰富的图形化选项
- 可以混合多种风格
- 支持白天和黑夜模式
- 多个数据源
# 使用 compose 部署 GIC
# 配置 docker-compose.yml 文件
version: '3.1' | |
volumes: | |
grafana_data: {} | |
services: | |
influxdb: | |
image: tutum/influxdb:0.9 | |
restart: always | |
environment: | |
- PRE_CREATE_DB=cadvisor | |
ports: | |
- "8083:8083" | |
- "8086:8086" | |
volumes: | |
- ./data/influxdb:/data | |
cadvisor: | |
image: google/cadvisor | |
links: | |
- influxdb:influxsrv | |
command: -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086 | |
restart: always | |
ports: | |
- "8080:8080" | |
volumes: | |
- /:/rootfs:ro | |
- /var/run:/var/run:rw | |
- /sys:/sys:ro | |
- /var/lib/docker/:/var/lib/docker:ro | |
grafana: | |
user: "104" | |
image: grafana/grafana | |
user: "104" | |
restart: always | |
links: | |
- influxdb:influxsrv | |
ports: | |
- "3000:3000" | |
volumes: | |
- grafana_data:/var/lib/grafana | |
environment: | |
- HTTP_USER=admin | |
- HTTP_PASS=admin | |
- INFLUXDB_HOST=influxsrv | |
- INFLUXDB_PORT=8066 | |
- INFLUXDB_NAME=cadvisor | |
- INFLUXDB_USER=root | |
- INFLUXDB_PASS=root |
# 检查配置文件
docker-compose config -q |
# 启动 docker-compose 文件
docker-compose up |
# 验证容器启动情况
http://ip:8080/ 浏览 cAdvisor
http://ip:8083/ 浏览 influxdb 存储服务
http://ip:3000/ 浏览 grafana 展现服务