温馨提示:所有文章中所提及的Docker相关的案例的安装包和工具,都可以在我的网盘中直接下载并跟着实操:
链接:pan.baidu.com/s/1pgboR8O5...
提取码:30in
10. Dockerfile自定义镜像
Dockerfile 是一个文本格式的配置文件, 用户可以使用 Dockerfile 来快速创建自定义的镜像,Dockerfile文件是由一行行命令语句组成,用来设置镜像的构建流程,基于这些命令即可以构建一个镜像
10.1 基本结构
一般的来说,Dockerfile 主体内容分为四部分:基础镜像信息 、维护者/作者信息 、镜像操作指令 、容器启动时执行指令 比如下面就是一个Dockefile文件样例:
bash
#选择基础镜像
FROM centos:7
#指定维护者/作者信息
MAINTAINER gucaini
#设置环境变量
ENV JAVA_HOME=/usr/local/java
#tomcat文件添加到镜像的 /usr/local/目录下,ADD指令会自动解压文件
ADD apache-tomcat-9.0.64.tar.gz /usr/local/
#在当前镜像基础上执行指定命令,并并提交为新的镜像层
RUN mv apache-tomcat-9.0.64 tomcat9
#声明镜像内服务监听的端口,供互联系统使用,在启动容器时需要通过-p映射端口
EXPOSE 8080
#指令用来指定启动容器时默认执行的命令
CMD ["catalina.sh", "run"]
10.2 常用指令说明
Dockerfile 中指令包括配置指令 和操作指令,这里我们列举一些常用指令进行说明:
分类 | 指令 | 说明 |
---|---|---|
配置指令 | FROM | 指定所创建镜像的基础镜像 |
配置指令 | MAINTAINER | 指定维护者/作者信息 |
配置指令 | LABEL | 为生成的镜像添加源数据标签信息 |
配置指令 | EXPOSE | 声明镜像内服务监听的端口 |
配置指令 | ENV | 指定环境变量 |
配置指令 | ENTRYPOINT | 指定镜像的默认入口命令 |
配置指令 | VOLUME | 创建一个数据卷挂载点 |
配置指令 | WORKDIR | 配置工作目录 |
配置指令 | ONBUILD | 创建子镜像时指定自动执行的操作指令 |
操作指令 | RUN | 运行指定命令 |
操作指令 | CMD | 启动容器时指定默认执行的命令 |
操作指令 | ADD | 添加内容到镜像 |
操作指令 | COPY | 复制内容到镜像 |
-
FROM:
指定所创建镜像的基础镜像,是最重要的指令之一,格式为:
FROM 镜像名:镜像标签 [AS 别名]
,任何 Dockerfile 中第一条指令必须为FROM 指令。 并且, 如果在同一个Dockerfile 中创建多个镜像时, 可以使用多个FROM 指令vbnet#默认在构建镜像时会把此镜像最为基础镜像,如果本地没有此镜像,会到远程仓库中拉取 FROM centos:7 #取上别名 FROM centos:7 AS my_centos
-
MAINTAINER:
指定维护者/作者信息,Dockerfile不限制MAINTAINER出现的位置,但是推荐放到FROM指令之后,格式为:
MAINTAINER 作者名
bash#指定作者名字 MAINTAINER gucaini
-
LABEL:
可以为生成的镜像添加元数据标签信息。一个Dockerfile可以写多个LABEL,但是不推荐这么做,Dockerfile每一条指令都会生成一层镜像,如果LABEL太长可 以使用\符号换行。格式为:
LABEL key1=value1 key2=value2...
ini#指定镜像的源数据信息,包括版本,创建时间,作者等 LABEL version=1.0 date="2023-01-01" \ author="gucaini"
-
EXPOSE:
声明镜像内服务监听的端口,注意该指令只是起到声明作用, 并不会自动完成端口映射,可以一次性指定多个端口。格式为:
EXPOSE 端口1 端口2...
yaml#声明容器内开放的端口,方便做端口映射 EXPOSE 8080 9090
-
ENV:
指定环境变量,在镜像启动的容器中也会存在,格式为:
ENV key=value
ini#指定JDK环境变量 ENV JAVA_HOME=/usr/local/java ENV PATH=$JAVA_HOME/bin:$PATH
-
ENTRYPOINT:
指定镜像的默认入口命令, 该入口命令会在启动容器时作为根命令执行, 所有传人值作为该命令的参数。格式为:
ENTRYPOINT ["命令","参数1","参数2"...]
bash#容器启动时,执行echo这个命令,打印hello docker ENTRYPOINT ["/bin/echo","hello docker"]
-
VOLUME:
创建一个数据卷挂载点,可以挂载宿主机上的卷或者其他容器上的卷,格式为:
VOLUME [挂载目录]
css#容器启动时,自动挂载容器内部的/var/lib/mysql数据目录 VOLUME ["/var/lib/mysql"]
-
WORKDIR:
为后续的RUN、CMD、ENTRYPOINT指令配置工作目录,只会影响当前WORKDIR之后的指令。如不指定,默认的工作目录是在容器镜像中的根目录
/
中执行的,可以使用多个WORKDIR,命令格式为:WORKDIR 绝对路径
bash#将容器的工作目录切换到/a目录中,并运行pwd命令 WORKDIR /a RUN pwd
-
ONBUILD:
指定当基于所生成镜像创建子镜像时,自动执行的操作指令,命令格式为:
ONBUILD 指令
bash#使用如下的Dockerfile创建父镜像ParentImag,指定ONBUILD指令 ..... ONBUILD RUN ls ONBUILD RUN pwd .... #当创建子镜像ChildImage时,会先执行ParentImag镜像中配置的ONBUILD指令 FROM ParentImag .... #等价于在ChildImage的Dockerfile中添加了如下指令 RUN ls RUN pwd
-
RUN:
运行指定命令,命令格式为:
RUN 命令 或 RUN ["执行脚本","参数1","参数2"]
,注意后者指令会被解析为 JSON 数组,因此必须用双引号、前者默认将在 shell 终端中运行命令,即/bin/sh -c 后者则使用 exec 执行,不会启动 shell 环境。bash#使用shell终端运行pwd命令 RUN pwd #使用其他类型终端执行命令 RUN ["/bin/bash","-c","echo hello"]
-
CMD:
用来指定启动容器时默认执行的命令,每个Dockerfile只能有一条CMD命令,如果指定了多条命令,只有最后一条会被执行,命令格式为:
CMD ["执行脚本","参数1","参数2"] 或 CMD 命令 参数1 参数2 或 CMD ["参数1","参数2"]
bash#容器启动时,执行Tomcat的启动脚本 CMD ["catalina.sh", "run"] #容器启动时,执行Tomcat的启动脚本 CMD /usr/local/apache-tomcat-9.0.64/bin/catalina.sh run #如果dokcerfile存在ENTRYPOINT指令也存在CMD指令,则CMD指令是提供给ENTRYPOINT指令的默认参数 ENTRYPOINT ["/bin/echo"] CMD ["hello docker"]
-
ADD:
添加内容到镜像,命令格式为:
ADD 宿主机文件或目录路径[URL] 镜像内的文件或目录路径
,宿主机文件或目录路径可以是当前Dockerfile文件所在的相对路径或绝对路径,也可以是一个URL远程资源地址。镜像内的文件或目录路径可以是镜像内绝对路径,也可以是相对于WORKDIR
的相对路径sql#tomcat文件添加到镜像的/usr/local/目录下,ADD指令会自动解压文件 ADD apache-tomcat-9.0.64.tar.gz /usr/local/
-
COPY:
复制内容到镜像,命令格式:
COPY 宿主机文件或目录路径 镜像内的文件或目录路径
,这个命令和ADD指令比较相似,算是一迷你版的ADD
指令,不同的是COPY
指令只能复制宿主机的文件或目录,不能从URL远程资源地址中复制,同时,ADD
指令会自动对归档压缩文件进行解压,而COPY
指令则不会sql#tomcat目录复制到镜像的/usr/local/目录下 COPY apache-tomcat-9.0.64 /usr/local/
10.3 自定义jdk镜像
我们来尝试自己制作一个Centos7的镜像,里面包含了jdk
-
在/mydata目录下建立docker/jdk文件夹,并将jdk压缩包上传至该文件夹
shellmkdir -p /mydata/docker/jdk
-
创建并编写Dockerfile文件
shell#进入/mydata/dokcer/jdk目录 cd /mydata/dokcer/jdk #创建Dockerfile vim Dockerfile #编写内容 FROM centos:7 MAINTAINER gucaini ADD jdk-8u121-linux-x64.tar.gz /usr/local/java ENV JAVA_HOME=/usr/local/java/jdk1.8.0_121 ENV PATH=$JAVA_HOME/bin:$PATH CMD ["java","-version"]
-
构建镜像
语法:
docker build [选项] Dockerfile路径
shell#在Dockerfile所在的目录下执行以下命令,注命令末尾的"."不可省略 docker build -t my_jdk . #以上命令解释 docker build:构建镜像 -t my_jdk选项:指定构建镜像的tag名字 .:当前Dockerfile所在的路径
-
运行镜像
shelldocker run my_jdk
10.4 自定义tomcat镜像
我们在我们制作的jdk镜像的基础上来制作tomcat镜像
-
在/mydata目录下建立docker/tomcat文件夹,并将tomcat压缩包上传至该文件夹
shellmkdir -p /mydata/docker/tomcat
-
创建并编写Dockerfile文件
shell#进入/mydata/dokcer/tomcat目录 cd /mydata/dokcer/jdk #创建Dockerfile vim Dockerfile #编写内容 FROM my_jdk MAINTAINER gucaini ADD apache-tomcat-9.0.64.tar.gz /usr/local/ ENV CATALINA_HOME=/usr/local/apache-tomcat-9.0.64 ENV PATH=$CATALINA_HOME/bin:$PATH EXPOSE 8080 CMD ["catalina.sh","run"]
-
构建镜像
shell#在Dockerfile所在的目录下执行以下命令,注命令末尾的"."不可省略 docker build -t my_tomcat:9 . #以上命令解释 docker build:构建镜像 -t my_tomcat:9选项:指定构建镜像的tag名字 .:当前Dockerfile所在的路径
-
运行镜像
shelldocker run -d -p 8080:8080 my_tomcat:9
-
在浏览器访问以下地址
urlhttp://192.168.177.128:8080/
10.5 多个镜像构建
Dockerfile文件支持在一个文件中,指定多个镜像的构建指令,他们以FROM
指令来区分,例如:我们可以在一个Dockerfile文件中即构建jdk镜像,又构建Tomcat镜像
-
删除jdk和tomcat自定义镜像
shelldocker rmi my_jdk docker rmi my_tomcat:9
-
在/mydata/docker目录下编写Dockerfile文件
shell#进入/mydata/dokcer目录 cd /mydata/dokcer #创建Dockerfile vim Dockerfile #编写内容,我们可以在一个Dockerfile文件中编写多个自定义镜像的构建,通过在FROM指令后面加上AS来区分各个镜像,并为它们加上别名 FROM centos:7 AS centos7 MAINTAINER gucaini ADD jdk/jdk-8u121-linux-x64.tar.gz /usr/local/java ENV JAVA_HOME=/usr/local/java/jdk1.8.0_121 ENV PATH=$JAVA_HOME/bin:$PATH CMD ["java","-version"] FROM my_jdk as jdk8 MAINTAINER gucaini ADD tomcat/apache-tomcat-9.0.64.tar.gz /usr/local/ ENV CATALINA_HOME=/usr/local/apache-tomcat-9.0.64 ENV PATH=$CATALINA_HOME/bin:$PATH EXPOSE 8080 CMD ["catalina.sh","run"]
-
构建镜像
shell#在Dockerfile所在的目录下执行以下命令,注命令末尾的"."不可省略 docker build -t my_jdk --target centos7 . docker build -t my_tomcat:9 --target jdk8 . #以上命令解释 docker build:构建镜像 -t 选项:指定构建镜像的tag名字 --target 选项:指定要当前要构建的是Dockerfile文件中的哪一个自定义镜像的别名 .:当前Dockerfile所在的路径
-
运行镜像
shelldocker run -d -p 8080:8080 my_tomcat:9
-
在浏览器访问以下地址
urlhttp://192.168.177.128:8080/
10.6 部署SpringBoot项目
我们在学习了Dockerfile之后,可以尝试部署一个单体SpringBoot项目到Docker容器中,项目的源码和打包后的文件都附在了资料中,首先我们需要将SpringBoot项目打包,然后将打包好的SpringBoot项目制作Docker镜像,镜像制作完毕之后,启动MySQL数据库容器,将SQL脚本导入,最后启动SpringBoot容器
-
将项目进行打包(也可以使用打包好的jar包)
将项目pom.xml文件中的打包方式修改为jar
pom<groupId>com.java</groupId> <artifactId>JXC</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging>
将项目的数据库连接进行修改,此处url的数据库的连接地址修改为你的虚拟机ip地址
点击右上角
Maven
按钮,选择Lifecycle->package
进行打包,出现BUILD SUCCESS
即为打包成功 -
上传打包好的jar包
在
target
目录下找到打包好的JXC.jar
,鼠标右键,选择Open In->Explorer
就可以找到我们磁盘上的JXC.jar
包了 -
上传JXC.jar到宿主机中
创建
/mydata/docker/springboot
文件夹,并将JXC.jar上传到文件夹中shell#创建文件夹 mkdir -p /mydata/docker/springboot
-
创建并编写Dockerfile文件
shell#进入/mydata/dokcer/spirngboot目录 cd /mydata/dokcer/springboot #创建Dockerfile vim Dockerfile #编写内容 FROM my_jdk MAINTAINER gucaini COPY JXC.jar /usr/local/ WORKDIR /usr/local/ RUN chmod +x /usr/local/JXC.jar EXPOSE 80 CMD ["java","-jar","JXC.jar"]
-
构建镜像
shell#在Dockerfile所在的目录下执行以下命令,注命令末尾的"."不可省略 docker build -t springboot_jxc .
-
启动MySQL容器
shell#启动MySQL容器 docker run --name=my_mysql --privileged=true --restart=always \ -v /mydata/mysql/data:/var/lib/mysql \ -v /mydata/mysql/conf:/etc/mysql/conf.d \ -v /mydata/mysql/logs:/var/log/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ -d -p 3306:3306 mysql #以上命令解析 --name:容器的名称 --privileged=true:为了防止出现Centos7安全Selinux禁止了一些安全权限 --restart=always:开机自动重启MySQL容器 -v:目录挂载 -v /mydata/mysql/data:/var/lib/mysql 将宿主机的/mydata/mysql/data目录挂载到容器中的/var/lib/mysql目录中(这是MySQL容器默认存储数据文件的目录) -v /mydata/mysql/conf:/etc/mysql/conf.d 将宿主机的/mydata/mysql/conf目录挂载到容器中的/etc/mysql/conf.d目录(这是MySQL容器默认的配置文件目录) -v /mydata/mysql/logs:/var/log/mysql 将宿主机的/mydata/mysql/conf目录挂载到容器中的/etc/mysql/conf.d目录(这是MySQL容器默认的配置文件目录) -e:指定环境变量,其中MYSQL_ROOT_PASSWORD指定数据库的密码 -d:后台启动 -p:端口映射
-
导入数据库数据
在Windows中使用SQLyog连接数据库,并将
jxc.sql
文件中的数据库语句进行导入,出现数据库db_jxc
和具体的表即导入成功 -
启动SpringBoot容器
shelldocker run --name=jxc -d -p 80:80 springboot_jxc
-
进行访问测试
在Windows浏览器中访问如下地址,访问后如出现如下登录页面,则部署成功,登录账号:
admin
,登录密码:admin123
urlhttp://192.168.177.128:80/login.html
11. Docker网络
容器和容器之前是相互隔离的,外界无法直接访问,那么Docker是如何做到容器和宿主机,容器和容器之前的网络通信呢?首先我们先来看一下宿主机的网络情况,执行ifconfig
,可以得到下图内容:
- 其中ens33是我们宿主机的网卡
- lo是本机回环地址
- virbr0是我们在创建CentOS7虚拟机时如果在安装过程中选择过虚拟化的服务安装,启动网卡时就会帮我们创建这个网卡,它的作用是做虚拟机网桥使用的,其作用是让我们的虚拟机可以访问互联网
- 我们着重要关注的是docker0 ,Docker默认启动时会在宿主机上建立一个虚拟网桥docker0,当容器启动的时候,每一个容器会分配一个虚拟IP地址,宿主机通过虚拟网桥转发容器进行通信,容器也通过虚拟网桥转发宿主机进行通信
11.1 Docker的网络模式
我们学习Docker网络,实际就是学习docker0 这个虚拟网桥相关的知识,Docker一般来说有5种网络模式,它们分别是:bridge模式、host模式、none模式、container模式、自定义模式 。Docker默认提供了3种网络模式,我们可以通过docker network ls
来查看,生成容器时不指定网络模式下默认使用bridge模式
11.2 bridger模式
我们知道Docker服务启动时会首先在主机上自动创建一个 docker0虚拟网桥, 网桥可以理解为一个交换机, 负责挂载其上的接口之间进行包转发。同时,Docker随机分配一个本地未占用的私有网段地址给 docker0接口,一般为172.17.0.0/16 网段, 掩码为 255.255.0.0 。 此后每启动一个新的容器,就会为容器自动分配一个该网段的地址,同时会创建了一对 veth pair 互联接口。 当向任一个接口发送包时, 另外一个接口自动收到相同的包。 互联接口的一端位于容器内, 即 eth0 另一端在本地并被挂载到 docker0网桥, 名称以 veth 开头(例如 vethAQI2QT) 。 通过这种方式,主机可以与容器通信, 容器之间也可以相互通信。 如此一来, Docker就创建了在主机和所有容器之间一个虚拟共享网络。这种模式就是bridger模式,也是默认的模式
-
我们在启动容器时可以添加
--network
参数来指定容器以何种网络模式运行,如果我们不写--network
那么默认就是bridger模式。可以通过-P
或-p
参数来指定端口映射,我们可以启动一个tomcat容器来观察shell#启动tomcat01容器 docker run --name tomcat01 -d -p 8081:8080 my_tomcat:9
-
启动容器后,我们再次使用
ifconfig
查看网络情况,如下图,可以看到在我们的网络信息中,多了一个veth19884e7这么一个网卡,这就是每个容器的互连接口,在宿主机的网络信息中以 veth 开头
-
我们再查看一下Docker容器中的网络情况,可以看到容器内Docker为我们分配了在172.17.0.0/16 网段的一个地址做为容器内部的IP地址,容器内部的互连接口就是eth0
shelldocker inspect tomcat01
-
在宿主机中,我们尝试ping一下容器的ip,发现是可以进行访问的,说明docker0虚拟网桥打通了宿主机和容器内部的网络
shellping 172.17.0.2
-
我们再启动一个新的tomcat,看一看容器与容器之间是否可以连通
shell#启动tomcat02容器 docker run --name tomcat02 -d -p 8082:8080 my_tomcat:9 #进入tomcat02容器 docker exec -it tomcat02 bash #容器内部默认是没有安装ping命令的,我们先安装一下 yum install -y iputils-ping #从tomcat02容器访问tomcat01容器 ping 172.17.0.2
- 这就是bridger网络模式,其特点是网络隔离性好,会占用宿主机端口,只占用一个真实ip,适用于大多数的场景,也是我们用的最多的网络模式
11.3 host模式
如果启动容器的时候使用--network=host
,就开启了host模式,那么这个容器将不会获得一个独立的Network Namespace,即不要创建容器内的网络,而是和宿主机共用一个本地网络,容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口,如下图,容器1将不在通过docker0,而是直接共用宿主机的网卡。
-
我们再以host模式来启动一个tomcat容器,需要注意的是,当使用host模式时,将不用指定
-P
或-p
端口映射shell#启动tomcat03容器 docker run --name tomcat03 --network=host -d my_tomcat:9
-
我们查看一下容器的网络情况,网络模式切换为了host,并且容器内的ip和网关都为空了
shelldocker inspect tomcat03
-
我们进入到容器中,查看容器中的网络情况,我们发现目前容器中显示的网络信息和宿主机中一致
shell#进入tomcat03容器 docker exec -it tomcat03 bash #容器内部默认是没有安装ifconfig命令的,我们先安装一下 yum install -y net-tools #查看网络情况 ifconfig
-
在浏览器中访问8080端口,可以正常访问到tomcat,说明容器确实使用的是宿主机的IP和端口号
urlhttp://192.168.177.128:8080
- 这就是host网络模式,网络隔离性较差,会占用宿主机端口,只占用一个真实ip,会出现端口冲突,性能最好。能确保所有容器端口不冲突且都需要对外暴露时可以使用
11.4 none模式
如果启动容器的时候使用--network=none
,就开启了none模式,这种模式下Docker容器拥有自己的NetWork Namespace,但是并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、ip、路由等信息,需要我们自己为Docker容器添加网卡、配置ip等。
-
我们再以none模式来启动一个tomcat容器
shell#启动tomcat04容器 docker run --name tomcat04 --network=none -d my_tomcat:9
-
我们查看一下容器的网络情况,网络模式切换为了none,并且容器内的ip和网关都为空了
shelldocker inspect tomcat04
-
我们进入到容器中,无法安装ifconfig命令,因为容器处于无网状态
shell#进入tomcat04容器 docker exec -it tomcat04 bash #容器内部默认是没有安装ifconfig命令的,执行时命令时出错,容器无网 yum install -y net-tools
-
我们知道在Linux系统中安装软件的方式还有一种是使用
npm
的方式,默认tomcat容器中的Linux系统中是包含npm
命令的,所以我们可以通过在宿主机中下载好一个net-tools
的npm
包,拷贝到容器中,然后进行ifconfig
命令的安装shell#上传net-tools-2.0-0.25.20131004git.el7.x86_64.rpm到/root目录下,并拷贝到容器中/usr/local中 docker cp /root/net-tools-2.0-0.25.20131004git.el7.x86_64.rpm tomcat04:/usr/local #进入tomcat04容器 docker exec -it tomcat04 bash #执行安装 rpm -ivh net-tools-2.0-0.25.20131004git.el7.x86_64.rpm
-
在容器中再次查看网络情况,此时我们发现只有lo是本机回环地址
shellifconfig
- 这就是none网络模式,默认创建的容器是一个无网容器,其网卡、ip、路由等信息,一切由运维人员来自定义。例如使用Open vSwitch 来自定义网络,或是借助Openstack、Kubenetes等方式来自动化配置网络。
11.5 container模式
如果启动容器的时候使用--network=container:容器名
,就开启了container模式,这种模式下新创建的Docker容器不会创建自己的网卡、配置自己的IP等。而是和一个已经存在的容器共享一个Network Namespace,而不再和宿主机共享,是和已存在的容器共享网卡、端口、IP等。两个容器之间除了网络方面,其他的例如文件、进程列表等还是相互隔离的。
-
我们先以bridge模式来启动一个redis容器,并暴露其端口号为8080
shell#启动centos7容器,并暴露端口为8080 docker run --name redis -p 8080:8080 -d redis:7.0.5
-
我们查看一下redis容器的网络情况,网络模式为bridge,容器IP为172.17.0.2
shelldocker inspect redis
-
我们再以container模式来启动一个tomcat容器,绑定到redis容器
shell#启动tomcat05容器 docker run --name tomcat05 --network=container:redis -d my_tomcat:9
-
我们查看一下tomcat容器的网络情况,网络信息都是空白
shelldocker inspect tomcat05
-
然后我们在浏览器中访问8080端口,可以正常访问到tomcat,说明容器确实使用的redis容器的IP和端口号等
urlhttp://192.168.177.128:8080
- 这就是container网络模式,网络隔离性较好,会占用容器端口,会出现端口冲突,性能差。可能只有在开发类似网关应用时可以考虑使用
11.6 自定义模式
之前在bridger模式中,我们启动了两个tomcat容器,tomcat01和tomcat02。之后我们进入tomcat02容器中安装ping命令来尝试访问tomcat01容器的IP地址,发现是可以互通的,但是会有一些问题。在说明自定义模式之前,我们先来看下面一个现象
-
我们先重新启动bridger模式中的两个tomcat容器
shelldocker start tomcat01 docker start tomcat02 #如果容器已经删除,可以使用一下命令快速创建 docker run --name tomcat01 -d -p 8081:8080 my_tomcat:9 docker run --name tomcat02 -d -p 8082:8080 my_tomcat:9
-
然后我们分别查看两个容器的ip地址
shelldocker inspect tomcat01 docker inspect tomcat02
-
进入tomcat02容器,并尝试访问tomcat01容器的ip
shell#进入tomcat02容器 docker exec -it tomcat02 bash #从tomcat02容器访问tomcat01容器 ping 172.17.0.2 #如果是新创建的两个容器,没有ping命令的话,就安装一下 yum install -y iputils-ping
-
然后我们停止tomcat01容器,再重新启动一个tomcat06容器,并查看网络信息
shell#停止tomcat01容器 docker stop tomcat01 #启动tomcat06容器 docker run --name tomcat06 -d -p 8083:8080 my_tomcat:9 #查看tomcat06容器的网络信息 docker inspect tomcat06
-
重启tomcat01容器,并查看网络信息
shell#启动tomcat01容器 docker start tomcat01 #查看tomcat01容器的网络信息 docker inspect tomcat01
通过以上现象,我们得出一个结论:在默认的bridger桥接模式下,容器可以通过容器ip进行互联通信,但是容器被分配的ip地址会随着容器的启动顺序而变化,也就是说,容器启动后的ip是不固定的。那么这就会有问题了,假设我们现在部署了一个SpringBoot程序,该程序是用docker进行部署的,并且需要连接同样由docker启动的MySQL数据库,如果我们在SpringBoot程序中使用容器ip地址进行连接,那么当MySQL数据库容器的ip发生变化时,就连接不上了!
这就好比在微服务部署的场景下,注册中心是使用服务名来识别微服务的,而我们上线部署的时候微服务对应的ip地址可能会改动,所以我们需要使用容器名来配置容器间的网络连接,而不是使用容器ip连接
官方推荐我们使用自定义模式来创建网络,使用docker network create
命令创建一个自定义桥接网络,在启动容器的时候将容器指定到新创建的桥接网络中,这样同一桥接网络中的容器就可以通过容器名来相互访问了。
-
首先,我们先进入tomcat02容器中,看看能否直接用容器名来访问
shell#进入tomcat02容器 docker exec -it tomcat02 bash #使用容器名从tomcat02容器访问tomcat01容器 ping tomcat01
-
创建自定义桥接网络,语法:
docker network create 网络名 或者 docker network create 网络名 --subnet=172.18.0.0/24 指定网段
shell#创建自定义桥接网络 docker network create my_network #查看网络 dokcer network ls
-
通过自定义网络重新创建tomcat01和tomcat02容器
shell#删掉之前的容器 docker rm -f tomcat01 docker rm -f tomcat02 #通过自定义网络创建容器 docker run --name tomcat01 --network my_network -d -p 8081:8080 my_tomcat:9 docker run --name tomcat02 --network my_network -d -p 8082:8080 my_tomcat:9
-
查看两个容器的网络信息,目前使用的网络是我们自定义的网络名
shelldocker inspect tomcat01 docker inspect tomcat02
-
我们进入tomcat02容器,通过容器名来尝试访问tomcat01容器
shell#进入tomcat02容器 docker exec -it tomcat02 bash #使用容器名从tomcat02容器访问tomcat01容器 ping tomcat01 #如果是新创建的两个容器,没有ping命令的话,就安装一下 yum install -y iputils-ping
- 这就是自定义网络模式,默认使用的是bridger网桥模式,只不过是我们自己创建和定义的网络,其特点在相同网桥下的容器可以通过容器名来互相访问,共享网络,我们无需再去担心容器启动时IP地址等信息发生变化,自定义模式本身就维护好了主机名和IP的对应关系。
11.7 同宿主机网络通信的方案
在了解了Docker网络后,在同一台宿主机中,容器之间的网络通信方案就有以下几种:
-
通过容器本身的ip相互直接访问对方(不推荐)
例如:Tomcat互访、Redis互访、MySQL互访,这种方式会导致ip地址的硬编码写死,不方便迁移,容器重启后,ip可能会发生变化,容器启动时是随机生成的一个ip,通过容器ip访问不是一个好的方案,不推荐使用
-
通过宿主机的ip:port访问(可以使用) 通过宿主机的ip:port访问,需要将宿主机的端口与容器的端口进行映射
-
通过link建立连接(docker老版本使用,官方已经不推荐使用)
运行容器时,指定参数link,使得源容器与被链接的容器可以进行相互通信,并且接受的容器可以获得源容器的一些数据,比如:环境变量。缺点是:通过link建立连接的容器,被链接的容器能ping通源容器,反过来不行
-
通过自定义网络通信(官方推荐)
使用
docker network
命令创建一个自定义桥接网络,在docker run
的时候将容器指定到新创建的桥接网络中,这样同一桥接网络中的容器就可以相互访问
11.8 跨宿主机网络通信的方案
从Docker 1.7.0 版本开始,Docker官方正式把网络的功能实现以插件化的形式剥离出来,允许用户通过指令来选择不同的后端实现,剥离出来的独立容器网络项目即为libnetwork项目,Docker 希望将来能为不同类型的容器定义统一规范的网络层标准,支持多种操作系统平台,这也是Docker希望构建强大容器生态系统的积极的尝试。简单来说,就是Docker官方把多台宿主机的网络支持剥离出来,交给了用户自己来定义,你可以使用Docker官方为我们提供的原生网络方案,也可以使用一些优秀的第三方开源产品来实现网络方案。
libnetwork容器网络模型( Container Networking Model, CNM )十分简洁和抽象,可以让其上层使用网络功能的容器不关心底层的网络实现,网络模型包括三种基本元素
- 沙盒(Sandbox):代表一个具体的容器中的网络命名空间
- 接入点(Endpoint):代表网络上可以挂载容器的接口,会分配IP地址
- 网络(Network):可以连通多个接入点的一个子网
跨宿主机通信目前有如下几种主流方法:
-
使用Overlay覆盖模式打通网路
Overlay覆盖网络会在多个宿主机之间创建一个分布式的网络。这个网络会覆盖宿主机特有的网络,并允许容器连接它(包括集群服务中的容器)来安全通信。这是Docker官方提供的一个原生方案
-
使用Open vSwitch(OVS) 虚拟交换机实现打通网络
Open vSwitch是第三方产品,是一个虚拟交换软件,主要用于虚拟机环境,提供网络的虚拟化
-
使用Flannel来打通网络
Flannel是一个网络规划服务软件,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。从而使得不同节点上的容器能够获得"同属一个内网"且"不重复的"IP地址,并让属于不同节点上的容器能够直接通过内网IP通信
-
借助Openstack 、Kubenetes等方式打通网络
Openstack 和Kubenetes无疑是当今云计算领域的两大巨头,它们都是开源管理技术。其内部提供了完善的虚拟技术的网络解决方案
我们以Docker官方的原生方案Overlay 来实现跨宿主机网络通信,Overlay 在IP地址可以互相访问的多个宿主机之间搭建隧道,从而让容器可以互相访。要实现Overlay网络我们需要满足以下条件:
- 保证Docker的版本在1.9.0以上,Overlay方案是在Docker 1.9.0之后才推出的方案
- 需要有一个key-value式的键值数据库将各个宿主机的中的容器网络信息进行注册,从而实现跨主机的通信。Docker支持Consul、Etcd、ZooKeeper等服务注册发现工具,官方推荐我们使用Consul
- 每台宿主机必须具有唯一的主机名,因为key-value式的键值数据库使用主机名来标识每台宿主机成员
- Linux内核的版本大于3.12 ,因为overlay模式底层是基于VXLAN这种技术来实现的,而在Linux内核3.12以后才开始提供,如果低于这个版本,可能会出现无法通信的情况
Overlay网络原理:当我们使用Overlay网络的时候,每台宿主机会将自己的网络信息注册到Consul之中,当我们在某一台机器创建Overlay网络的时候,会将网络信息也同步到Consul之中,加入到Consul管理的其他宿主机将同步这个网络。同时,当创建Overlay网络的时候,Docker会帮我们创建2个网桥,overlay是我们自己创建的覆盖网络网桥,docker_gwbridge是docker帮我们创建的网桥,它会在这种模式中,代替docker0,用于容器与当前宿主机通信。当容器1向容器2通信时,会通过容器的eth0发送数据包到node1宿主机的overlay网桥中,overlay网桥使用VETP将数据包进行封装,然后将封装好的VXLAN格式的数据包交给node1宿主机的eth0,随后传递给node2宿主机的eth0。node2宿主机接收到该数据后同样使用overlay网桥的VETP将数据解析,然后发送给容器2的eth0从而实现跨主机通信
下面我们以两台分别以两台虚拟机node1和node2为例来演示这个案例:
要确保node1和node2两台虚拟机的网络是互通的 node1:192.168.177.128 node2:192.168.177.129
-
配置每台虚拟机的主机名
shell#在192.168.177.128虚拟机执行 vim /etc/hostname #输入以下内容,并保存 node1 #在192.168.177.129虚拟机执行 vim /etc/hostname #输入以下内容,并保存 node2
-
配置每台虚拟机的hosts映射
shell#在192.168.177.128虚拟机执行 vim /etc/hosts #输入以下内容,并保存 192.168.177.128 node1 192.168.177.128 node2 #在192.168.177.129虚拟机执行 vim /etc/hosts #输入以下内容,并保存 192.168.177.128 node1 192.168.177.128 node2 #操作完毕之后分别重启两台虚拟机 reboot
-
分别查看每台虚拟机的主机名
shellhostname -f
-
查看Linux内核,并配置内核升级仓库(如果内核版本高于3.12则不用执行4-6步骤)
shell#查看Linux的内核版本 uname -r #最新的Linux内核版本地址 https://www.kernel.org/ #配置ELRepo仓库,用于升级内核 rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm #仓库启用后,可以使用下面的命令列出可用的内核相关包 yum --disablerepo="*" --enablerepo="elrepo-kernel" list available
-
安装内核
在列出的内核相关包中lt为长期维护版本,ml为稳定版本,从上图可知,目前最新的稳定版本内核是
kerner-ml.x86_64
shell#安装kernel-ml.x86_64和kernel-ml-devel.x86_64 yum -y --enablerepo=elrepo-kernel install kernel-ml.x86_64 kernel-ml-devel.x86_64
-
设置 GRUB 默认的内核版本
我们再次查看一下Linux的内核版本,发现内核的版本还是之前的3.10的版本,其原因是Linux启动的时候还是默认选择了老的内核,这里需要配置一下GRUB,重新设置一下启动时的默认内核
shell#查看内核 uname -r #查看当前都有哪些启动项 cat /boot/grub2/grub.cfg |grep menuentry #设置默认选项 grub2-set-default 'CentOS Linux (6.1.9-1.el7.elrepo.x86_64) 7 (Core)' #重启Linux reboot
至此,我们的内核升级就完成了,注意将node1和node2宿主机节点都完成内核升级
-
在node1中安装Consul注册中心
shell#拉取Consul注册中心镜像 docker pull consul #启动Consul容器 docker run --name consul --restart=always -p 8500:8500 -d consul
-
访问consul,看是否启动成功
urlhttp://192.168.177.128:8500
-
配置node1的daemon.json文件
shell#打开node1的daemon.json文件 vim /etc/docker/daemon.json #在该文件中,添加如下两行配置 { "cluster-store":"consul://192.168.177.128:8500", "cluster-advertise":"192.168.177.128:2376" } #以上配置中cluster-store是配置Consul注册中心集群的地址 #cluster-advertise,是广播通信地址和端口 #重启daemon systemctl daemon-reload #重启docker服务 systemctl restart docker
-
配置node2的daemon.json文件
shell#打开node2的daemon.json文件 vim /etc/docker/daemon.json #在该文件中,添加如下两行配置 { "cluster-store":"consul://192.168.177.128:8500", "cluster-advertise":"192.168.177.129:2376" } #注意,这里的cluster-advertise是暴露129这台机器的广播IP地址,需要修改IP为129节点地址 #重启daemon systemctl daemon-reload #重启docker服务 systemctl restart docker
-
使用自定义模式创建网络
前面介绍过Docker中的自定义网络模式,可以使用
docker network create
命令创建,默认创建出的自定义网络的模式是bridger桥接模式。实际上,在自定义模式中,我们还可以指定创建的自定网络模式为overlay覆盖模式,在node1中创建自定义overlay网络shell#创建自定义网络,--driver overlay指定创建模式为覆盖模式,如果不指定,默认就是bridger桥接 docker network create --driver overlay my_overlay_work #同时在node1和node2查看网络,可以看到新网络my_overlay_work docker network ls
-
在node1中创建tomcat01容器
shell#删除掉之前的tomcat01容器 docker rm -f tomcat01 #创建tomcat01容器,并连接到my_overlay_work网络上 docker run --name tomcat01 --network my_overlay_work -d -p 8081:8080 my_tomcat:9
-
在node2中创建tomcat02容器
shell#删除掉之前的tomcat02容器 docker rm -f tomcat02 #创建tomcat02容器,并连接到my_overlay_work网络上 docker run --name tomcat02 --network my_overlay_work -d -p 8082:8080 my_tomcat:9
-
进入node2中的tomcat02容器,通过容器名来尝试访问node1中的tomcat01容器
shell#进入node2中的tomcat02容器 docker exec -it tomcat02 bash #使用容器名从node2中的tomcat02容器访问node1的tomcat01容器 ping tomcat01 #如果是新创建的两个容器,没有ping命令的话,就安装一下 yum install -y iputils-ping
至此,我们完成了跨宿主机的网络通信
12. 常用软件高可用安装
前面,我们对常用软件的基础安装做了了解和学习,现在我们对常用软件的高可用安装来进行学习。在日常企业工作中,我们所部署的大量基础软件为避免出现单点故障问题 都需要进行高可用来保证程序正常对外提供服务。通常的做法是在同一台宿主机中启动多态实例,或在多台宿主机中部署多个实例来达到集群和分布式的效果。这里我们以一台宿主机为例,以伪分布式(伪集群) 的方式来构建常用软件的高可用方式,仅方便学习。
12.1 MySQL主从复制搭建
所谓MySQL主从,就是建立两个完全一样的数据库,一个是主库,一个是从库,主库对外提供读写的操作,从库对外提供读的操作 ,大部分系统的访问模型是读多写少,读写请求量的差距可能比较大,所以我们可以通过一主多从的方式,主库只负责写入和部分核心逻辑的查询,多个从库只负责查询,提升查询性能,降低主库压力 ,当主库宕机时,从库可以切成主库,保证服务的高可用,然后从库也可以做数据的容灾备份,总结下有这些优势:读写分离 、高可用 、数据备份
下图是MySQL主从复制的原理图:
MySQL的主从复制将经过如下步骤:
- 当Master 主服务器上的数据发生改变时(UPDATE、INSERT、DELETE ),则将其改变写入二进制事件日志文件Binary log (binlog)中
- Slave 从服务器会在一定时间间隔内对Master 主服务器上的二进制日志进行探测,探测其是否发生过改变,如果探测到Master 主服务器的二进制事件日志发生了改变,则开始一个I/O thread 线程请求Master二进制事件日志
- 同时Master 主服务器为每个I/O thread 线程启动一个Log dump thread线程,用于向其发送二进制事件日志
- Slave 从服务器将接收到的二进制事件日志保存至自己本地的中继日志文件Relay log中
- Slave 从服务器将启动SQL thread线程从中继日志中读取二进制日志,在本地重放(回放),使得其数据和主服务器保持一致
- 最后I/O thread 和SQL thread将进入睡眠状态,等待下一次被唤醒
下面我们以一主一从 、伪集群的方式来搭建数据库来进行演示,即在一台宿主机中即安装主库,也安装从库实现主从复制。其中主库的端口号为3306,从库的端口号为3307,这里我们以bridger网络摸式来进行演示
-
在宿主机中分别启动两个数据库容器
shell#启动MySQL主库容器 docker run --name=mysql_master --privileged=true --restart=always \ -v /mydata/mysql/master/data:/var/lib/mysql \ -v /mydata/mysql/master/conf:/etc/mysql/conf.d \ -v /mydata/mysql/master/logs:/var/log/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ -d -p 3306:3306 mysql #启动MySQL从库容器 docker run --name=mysql_slave --privileged=true --restart=always \ -v /mydata/mysql/slave/data:/var/lib/mysql \ -v /mydata/mysql/slave/conf:/etc/mysql/conf.d \ -v /mydata/mysql/slave/logs:/var/log/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ -d -p 3307:3306 mysql #注意两个库的数据目录的挂载路径:主库在/mydata/mysql/master/目录下,从库在/mydata/mysql/slave/目录下
-
添加MySQL主库的自定义配置
shell#在宿主机中切换到/mydata/mysql/master/conf目录下 cd /mydata/mysql/master/conf #添加my.cnf数据库核心配置文件 vim my.cnf #添加以下内容 [client] default-character-set=utf8mb4 [mysqld] character-set-server=utf8mb4 autocommit=1 server-id=3306 log-bin=mysql-bin #[clinet]代表配置MySQL客户端连接时的相关配置 default-character-set:设置MySQL客户端连接时的字符集 #[mysqld]代表MySQL服务端的相关配置 character-set-server:设置服务端的字符集 autocommit:开启自动提交 server-id:表示server编号,编号要唯一 log-bin:表示启用二进制日志,并设置日志文件的名字,可以用于主从服务器之间的数据同步 #配置完毕后重启容器 docker restart mysql_master
-
添加MySQL从库的自定义配置
shell#在宿主机中切换到/mydata/mysql/slave/conf目录下 cd /mydata/mysql/slave/conf #添加my.cnf数据库核心配置文件 vim my.cnf #添加以下内容 [client] default-character-set=utf8mb4 [mysqld] character-set-server=utf8mb4 autocommit=1 server-id=3307 log-bin=mysq-slave-bin read_only=1 #[clinet]代表配置MySQL客户端连接时的相关配置 default-character-set:设置MySQL客户端连接时的字符集 #[mysqld]代表MySQL服务端的相关配置 character-set-server:设置服务端的字符集 autocommit:开启自动提交 server-id:表示server编号,编号要唯一 log-bin:表示启用二进制日志,并设置日志文件的名字,以防Slave作为其他数据库实例的Master时使用 read_only:表示只读 #配置完毕后重启容器 docker restart mysql_slave
-
分别进入主从MySQL数据库进行授权远程登录访问
shell#进入容器 docker exec -it mysql_master bash docker exec -it mysql_slave bash #进入容器后输入MySQL客户端连接命令,其中-u是数据库用户名,默认为root,-p是数据库密码 mysql -uroot -p123456 #修改远程用户访问密码,其中root@'%'中的root表示用户名,%表示任意ip地址 alter user 'root'@'%' identified with mysql_native_password by '123456'; #授权访问,其中*.* 的第一个*表示所有数据库名,第二个*表示所有的数据库表 grant all privileges on *.* to 'root'@'%'; #刷新权限 flush privileges;
-
在主服务器上创建复制数据的账号并授权
shell#进入容器 docker exec -it mysql_master bash #进入容器后输入MySQL客户端连接命令,其中-u是数据库用户名,默认为root,-p是数据库密码 mysql -uroot -p123456 #在主服务器上创建复制数据的账号和本地访问密码 create user 'copy'@'%' identified by '123456'; #修改复制数据账号的远程访问密码,其中copy@'%'中的copy表示用户名,%表示任意ip地址 alter user 'copy'@'%' identified with mysql_native_password by '123456'; #在主服务器上对复制数据账号授权 grant replication slave on *.* to 'copy'@'%'; #刷新权限 flush privileges; #在主服务器上执行命令,查看主服务器状态 show master status; #如果主服务状态不是初始状态,建议重置状态,初始状态:日志文件是:mysql-bin.000001,偏移坐标:156 reset master;
-
在从服务器设置主库
shell#进入容器 docker exec -it mysql_slave bash #进入容器后输入MySQL客户端连接命令,其中-u是数据库用户名,默认为root,-p是数据库密码 mysql -uroot -p123456 #查看从服务器状态 show slave status; #如果从服务器不是初始状态,重置一下(什么也没有就是初始状态) stop slave; --停止复制 reset slave; --重置从机器状态 #设置master change master to master_host='192.168.177.128',master_user='copy',master_port=3306,master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=156; #执行开始复制命令 start slave;
-
检查从服务器复制功能状态
sql#如果Slave_IO_Running和Slave_SQL_Running均为YES,则表示主从关系正常 show slave status \G
-
在主服务器上创建数据库、表、数据,然后在从服务器上查看是否已经复制
在主库执行以下数据库语句:
sql#创建一个数据库 create database docker; #切换数据库 use docker; #创建一张表 create table t_user( id int primary key, user_name varchar(20), passsword varchar(50) ); #插入数据 insert into t_user values(1,'Tom','123456'); #查询表 select * from t_user;
在从库执行以下数据库语句:
sql#查看数据库是否复制 show databases; #切换数据库 use docker; #查看所有表 show tables; #查询表 select * from t_user;
12.2 Redis哨兵集群搭建
Redis也提供了一主多从架构,主库对外提供读写的操作,从库对外提供读的操作 。主库把写入的数据异步复制到从机器,当主库宕机时,从库可以切成主库,保证服务的高可用,然后从库也可以做数据的容灾备份。但是当主库发生故障 时,需将其中一台从库手动切换成主库 ,这种处理也叫冷处理(人工处理) 。这种处理方式并不智能,要实现自动化处理,这就需要Sentinel哨兵实现故障自动转移。Sentinel哨兵是Redis官方提供的高可用方案,使用Sentinel哨兵可以监控多个Redis服务实例的运行情况。它的基本工作流程如下:
- Sentinel哨兵用来监视Redis的主从服务器,它会不断检查主库和从库是否正常
- 但是如果Sentinel挂了,也无法监控,所以需要多个哨兵组成Sentinel网络,监控同一个Redis主从集群的各个Sentinel哨兵会相互通信,组成一个分布式的Sentinel哨兵网络,互相交换彼此关于被监控Redis服务器的信息
- 当一个Sentinel哨兵认为被监控的Redis服务器出现故障时,它会向网络中的其它Sentinel哨兵进行确认,判断该服务器是否真的已故障
- 如果故障的Redis为主库 ,那么Sentinel哨兵网络将对故障的主库服务器进行自动故障转移,将某个Redis从库提升为新的主库,并让其它Redis从库转移到新的主库下,以此来让整个主从模式重新回到正常状态
- 待出现故障旧的主库重新启动上线时,Sentinel哨兵会让它变成一个Redis从库,并挂到新的Redis主库,所以哨兵是自动实现故障转移,不需要人工干预,是一种高可用的集群方案
通过了解,我们得知,Redis的哨兵集群模式=Redis主从复制+哨兵集群组成 ,所以实际上,我们如果要搭建哨兵集群就要分为两个方面来搭建,首先是搭建Redis主从复制部分,我们以一主两从 、伪集群 的方式来搭建。其次是Sentinel哨兵集群部分,我们以三个哨兵、伪集群的方式来搭建,其中主库的端口号为6379,两个从库的端口号分别为6380,6381。三个Sentinel的端口号分别为26379,26380,26381。这里以host网络模式进行演示
-
分别创建Redis主库和从库的数据持久化目录和配置文件目录
shell#创建三台Redis的数据持久化目录 mkdir -p /mydata/redis/6379/data /mydata/redis/6380/data /mydata/redis/6381/data #创建三台Redis的配置文件目录 mkdir -p /mydata/redis/6379/config /mydata/redis/6380/config /mydata/redis/6381/config
-
将核心配置文件
redis.conf
分别放入三台Redis的配置文件目录,并修改shell#在/mydata/redis/6379/config放入redis.conf配置文件并修改如下内容 protected-mode no (改为不保护,否则远程访问不了) bind 127.0.0.1 (注释掉,否则只能本机ip访问) requirepass 设置密码 配置文件搜索SNAPSHOTTING部分,设置RDB的自动备份方式,配置格式:save <seconds> <changes> save 900 1 save 300 10 save 60 10000 配置文件搜索APPEND ONLY MODE部分,开启aof appendonly (默认是no,改成yes即开启了aof持久化) #在/mydata/redis/6380/config和/mydata/redis/6381/config放入redis.conf配置文件并修改如下内容 protected-mode no (改为不保护,否则远程访问不了) bind 127.0.0.1 (注释掉,否则只能本机ip访问) requirepass 设置密码 port 6380/6381 (修改从库端口号,否则在使用host模式时端口会冲突) 配置文件搜索SNAPSHOTTING部分,设置RDB的自动备份方式,配置格式:save <seconds> <changes> save 900 1 save 300 10 save 60 10000 配置文件搜索APPEND ONLY MODE部分,开启aof appendonly yes (默认是no,改成yes即开启了aof持久化) 配置文件搜索REPLICATION部分,配置主从复制 replicaof 192.168.177.128 6379 (主库的地址) masterauth 主库密码
-
创建Redis主库和两个从库容器
shell#创建6379容器 docker run --name redis6379 --restart=always \ -v /mydata/redis/6379/data:/data \ -v /mydata/redis/6379/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf #创建6380容器 docker run --name redis6380 --restart=always \ -v /mydata/redis/6380/data:/data \ -v /mydata/redis/6380/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf #创建6381容器 docker run --name redis6381 --restart=always \ -v /mydata/redis/6381/data:/data \ -v /mydata/redis/6381/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf
-
查看三个redis服务的角色
shell#登录6379主库redis客户端 docker exec -it redis6379 redis-cli -p 6379 #输入密码 auth 123456 #查看角色 info replication #登录6380从库redis客户端 docker exec -it redis6380 redis-cli -p 6380 #输入密码 auth 123456 #查看角色 info replication #登录6381从库redis客户端 docker exec -it redis6381 redis-cli -p 6381 #输入密码 auth 123456 #查看角色 info replication
-
测试主从复制,至此主从复制搭建完毕
shell#在6379主库写入数据 set test 1 #在6380从库查看数据 get test
-
开始搭建哨兵,首先将
sentinel.conf
哨兵配置文件分别放入三台Redis的配置文件目录,并修改shell#在/mydata/redis/6379/config放入sentinel.conf配置文件并修改如下内容 sentinel monitor mymaster 192.168.177.128 6379 2(设置监控的主库地址,其中末尾的2代表的是投票数) sentinel auth-pass mymaster 123456(由于redis设置过密码,所以这里需要配置下) #在/mydata/redis/6380/config放入sentinel.conf配置文件并修改如下内容 port 26380 (修改端口号,默认为26379) sentinel monitor mymaster 192.168.177.128 6379 2(设置监控的主库地址,其中末尾的2代表的是投票数) sentinel auth-pass mymaster 123456(由于redis设置过密码,所以这里需要配置下) #在/mydata/redis/6381/config放入sentinel.conf配置文件并修改如下内容 port 26381 (修改端口号,默认为26379) sentinel monitor mymaster 192.168.177.128 6379 2(设置监控的主库地址,其中末尾的2代表的是投票数) sentinel auth-pass mymaster 123456(由于redis设置过密码,所以这里需要配置下)
-
创建三个哨兵容器
哨兵的创建仍然是使用redis镜像,只是在镜像启动后执行的脚本改为
redis-sentinel
,指向的配置文件也指向sentinel.conf
shell#创建26379哨兵容器 docker run --name sentinel26379 --restart=always \ -v /mydata/redis/6379/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-sentinel /usr/local/etc/redis/sentinel.conf #创建26380哨兵容器 docker run --name sentinel26380 --restart=always \ -v /mydata/redis/6380/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-sentinel /usr/local/etc/redis/sentinel.conf #创建26381哨兵容器 docker run --name sentinel26381 --restart=always \ -v /mydata/redis/6381/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-sentinel /usr/local/etc/redis/sentinel.conf
-
进入任意一个哨兵容器,查看它监控的Redis主从容器和其他哨兵容器
shell#进入容器命令行 docker exec -it sentinel26379 redis-cli -p 26379 #查看主容器 sentinel master mymaster #查看从容器 sentinel slaves mymaster #查看其他哨兵容器 sentinel sentinels mymaster
-
停止Redis主库,观察主库重新选举日志
shell#停止主库 docker stop redis6379 #在哨兵日志中查看选举切换日志,默认为30秒 docker logs sentinel26379
-
查看6380和6381容器的角色变化
shell#登录6380客户端 docker exec -it redis6380 redis-cli -p 6380 #输入密码 auth 123456 #查看角色 info replication #登录6381客户端 docker exec -it redis6381 redis-cli -p 6381 #输入密码 auth 123456 #查看角色 info replication
-
重新启动6379容器,不会把6379容器切换成主库,而是作为从库
shell#重启6379容器 docker start redis6379 #登录6379客户端 docker exec -it redis6379 redis-cli -p 6379 #输入密码 auth 123456 #查看角色 info replication
12.3 Redis分片集群搭建
使用哨兵模式可以达到Redis高可用目的,但是此时每个Redis库存有集群中的所有数据,从而导致集群的总数据存储量受限于可用存储内存最小的节点,形成了木桶效应 。因此在Redis3.0版本开始提供了Redis Cluster
集群功能,集群的特点在于拥有和单机实例一样的性能,同时提供了数据分片存储 的支持,以及在主Redis数据库故障后自动故障转移恢复的支持。 哨兵集群和分片集群是两个独立的功能,当不需要对数据进行分片使用哨兵就够了,如果要进行水平扩容Redis Cluster
是一个比较好的方式,它的基本拓扑结构如下:
-
Redis的数据分区:
一个Redis Cluster由多个Redis节点构成,不同节点组的Redis数据没有交集,也就是每个组节组对应数据的一个分片,节点组内部分为主备两类节点,对应Master和Slave节点,两者数据准实时一致,通过异步化的主备复制机制来保证。一个节点组有且只有一个Master节点,同时可以有0个到多个Slave节点,在这个节点组中只有Master节点对用户提供些服务,Slave节点用于备份数据。
-
Redis Cluster数据分片概念:
Redis Cluster提出哈希槽slot的概念,来实现数据分片,每个存入Redis的key通过CRC16函数(一种哈希函数)后对16384取模来决定将key放置哪个槽,集群的每个节点负责一部分哈希槽,key的槽位计算公式为:slot number = crc16(key) % 16384,举个例子说明:比如当前集群有3个节点组,那么节点组 A 包含 0 到 5500号哈希槽,节点组 B 包含5501 到 11000 号哈希槽,节点组 C 包含11001 到 16383号哈希槽,假设我们现在有一个key想存入分片集群,那么会首先进行计算:235 = crc16 (k1) % 16384,此时结果为235,那么就会将数据存入节点组A。
-
哈希槽的不变性:
哈希槽的个数固定是2的14次方,也就是
16384
,它是不可改变的。这种结构很容易添加或者删除节点组,比如:如果我想新添加一个节点组D, 我需要从节点组 A, B, C中分得部分哈希槽到D上。如果我想移除节点A,需要将A中的槽移到B和C节点组上,然后将没有任何槽的A节点组从集群中移除即可。由于从一个节点组将哈希槽移动到另一个节点组并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。这非常方便的进行了内存空间的横向扩容,由于哈希槽恒定不变,当节点组的数量增加,每个节点组所分配的哈希槽范围就缩小了,能够存储当前哈希槽范围内的数据变相的就增多了。 -
高可用性:
通常情况下,一个主节点下至少部署一个从节点,比如有A,B,C三个节点的集群,在没有从节点复制模型的情况下,如果节点B故障了,那么整个集群就会因为缺少5501-11000这个范围的哈希槽而不可用。如果在集群创建的时候我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个Master节点和三个Slave节点组成,这样在节点B故障后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用,不过当B和B1都失败后,集群是不可用的。
我们以三主三从 、伪集群的方式来搭建Redis分片集群。其中3个节点组的Master节点端口号分别为:7000、7001、7002。Slave节点端口号分别为:7003、7004、7005。这里以host网络模式进行演示
-
分别创建Redis各节点的数据持久化目录和配置文件目录
shell#创建六台Redis的数据持久化目录 mkdir -p /mydata/redis/7000/data /mydata/redis/7001/data /mydata/redis/7002/data /mydata/redis/7003/data /mydata/redis/7004/data /mydata/redis/7005/data #创建六台Redis的配置文件目录 mkdir -p /mydata/redis/7000/config /mydata/redis/7001/config /mydata/redis/7002/config /mydata/redis/7003/config /mydata/redis/7004/config /mydata/redis/7005/config
-
将核心配置文件
redis.conf
分别放入六台Redis的配置文件目录,并修改shellport 7000/7001/7002/7003/7004/7005 (端口号修改) protected-mode no (改为不保护,否则远程访问不了) bind 127.0.0.1 (注释掉,否则只能本机ip访问) cluster-enabled yes (启用集群模式) cluster-config-file nodes-7000.conf/nodes-7001.conf/nodes-7002.conf/nodes-7003.conf/nodes-7004.conf/nodes-7005.conf(它由Redis节点自行创建的集群文件,确保没有重叠的集群配置文件名) cluster-node-timeout 15000 (集群的故障超时时间) 配置文件搜索SNAPSHOTTING部分,设置RDB的自动备份方式,配置格式:save <seconds> <changes> save 900 1 save 300 10 save 60 10000 配置文件搜索APPEND ONLY MODE部分,开启aof appendonly yes (默认是no,改成yes即开启了aof持久化)
-
启动六台Redis容器
shell#创建7000容器 docker run --name redis7000 --restart=always \ -v /mydata/redis/7000/data:/data \ -v /mydata/redis/7000/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf #创建7001容器 docker run --name redis7001 --restart=always \ -v /mydata/redis/7001/data:/data \ -v /mydata/redis/7001/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf #创建7002容器 docker run --name redis7002 --restart=always \ -v /mydata/redis/7002/data:/data \ -v /mydata/redis/7002/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf #创建7003容器 docker run --name redis7003 --restart=always \ -v /mydata/redis/7003/data:/data \ -v /mydata/redis/7003/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf #创建7004容器 docker run --name redis7004 --restart=always \ -v /mydata/redis/7004/data:/data \ -v /mydata/redis/7004/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf #创建7005容器 docker run --name redis7005 --restart=always \ -v /mydata/redis/7005/data:/data \ -v /mydata/redis/7005/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf
-
执行命令配置集群,进入容器执行集群配置命令,任意一台即可,这里以端口号为7000的为例
shelldocker exec -it redis7000 redis-cli --cluster create \ 192.168.177.128:7000 192.168.177.128:7001 \ 192.168.177.128:7002 192.168.177.128:7003 \ 192.168.177.128:7004 192.168.177.128:7005 \ --cluster-replicas 1 #以上参数解释 --cluster create 代表指定创建集群的redis容器 --cluster-replicas 1 代表副本的数量为1,即6台服务器最后会变为3主3从
-
配置信息确认
redis-cli会输出一份预想中的配置信息需要确认,包含分配的slots哈希槽,可以输入yes,redis-cli就会将这份配置应用到集群当中,让各个节点开始互相通讯。当显示
All 16384 slots covered
这表示集群中的16384个槽都有至少一个主节点在处理,每个主下面有一个备用的从节点,集群运作正常 -
查看集群信息
shell#使用redis-cli连接任意一台Redis,这里以7000为例,-c表示以集群模式连接 docker exec -it redis7000 redis-cli -c -p 7000 #查看集群信息 cluster info #查看集群节点 cluster nodes #查看槽位分配 docker exec -it redis7000 redis-cli --cluster check 192.168.177.128:7000
-
测试集群
shell#使用redis-cli连接任意一台Redis,这里以7000为例 docker exec -it redis7000 redis-cli -c -p 7000 #分别执行以下命令观察结果 set a a set b b set c c
当对某个key进行存储时,会自动进行哈希槽计算得出对应的slot槽位,然后重定向给对应的Master节点进行处理。同样的,当对某个key进行数据访问的时候,也会先计算key所对应的slot槽位,然后重定向到对应的Mater节点进行访问
-
故障测试
停掉一个Master主节点后,该节点组下的Slave节点会提升为Master主节点,再次启动原来的Master主节点,它将沦为Slave从节点,这里我们把7000停掉来进行测试
shell#停止Redis7000容器 docker stop redis7000 #再次启动Redis7000容器 docker start redis7000 #使用redis-cli连接任意一台Redis,这里以7000为例,-c表示以集群模式连接 docker exec -it redis7000 redis-cli -c -p 7000 #查看集群节点,7000成了slave,它的master是7004 cluster nodes
-
密码的设置
在搭建分片集群时,不要在一开始就为每个Redis节点设置密码,而是需要在集群搭建完毕之后,再统一为整个集群设置访问密码
shell#使用redis-cli连接任意一台Redis,这里以7000为例,-c表示以集群模式连接 docker exec -it redis7000 redis-cli -c -p 7000 #设置密码 config set requirepass '123456' #设置从节点连接主节点的密码 config set masterauth '123456' #退出客户端,重新连接 docker exec -it redis7000 redis-cli -c -p 7000 #此时需要输入密码 auth 123456
-
添加新的主节点,添加一个7006的新节点,加入到整个集群中
创建数据持久化目录和配置文件
shell#创建数据持久化目录和配置文件目录 mkdir -p /mydata/redis/7006/data mkdir -p /mydata/redis/7006/config #拷贝一份7000节点的redis.conf文件到/mydata/redis/7006/config目录下 cp /mydata/redis/7000/config/redis.conf /mydata/redis/7006/config/redis.conf #修改/mydata/redis/7006/config/redis.conf如下内容 vim /mydata/redis/7006/config/redis.conf port 7006 (端口号修改) cluster-config-file nodes-7006.conf(它由Redis节点自行创建的集群文件,确保没有重叠的集群配置文件名)
启动新的容器
shell#启动7006容器 docker run --name redis7006 --restart=always \ -v /mydata/redis/7006/data:/data \ -v /mydata/redis/7006/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf
加入集群
shell#使用redis-cli连接任意一台节点客户端,这里以7000为例,添加7006节点到集群,第一个参数是要添加的节点,第二个参数是集群中任意一个主节点 docker exec -it redis7000 redis-cli --cluster add-node 192.168.177.128:7006 192.168.177.128:7002
查看集群情况
shell#使用redis-cli连接任意一台Redis,这里以7000为例,-c表示以集群模式连接 docker exec -it redis7000 redis-cli -c -p 7000 #查看集群信息 cluster info #查看集群节点 cluster nodes #查看槽位分配 docker exec -it redis7000 redis-cli --cluster check 192.168.177.128:7006
新节点7006现在已经加入了集群,成为集群的一份子,可以对客户端的命令请求进行重定向,且新添加的节点是一个主节点,但是和其他主节点相比:1.新节点7006没有包含任何哈希槽;2.集群需要将某个从节点升级为新的主节点时,这个新节点不会被选中;3.将集群中的某些哈希槽移动到新节点中,新节点才会成为真正的主节点
-
集群重新分片
重新分片操作本质上就是将某些节点上的哈希槽移动到另外一些节点上面,和创建集群一样, 重新分片也可以使用
redis-cli
程序来执行,执行以下命令可以开始一次重新分片操作shell#192.168.72.128:7002是集群中任意一个主节点 docker exec -it redis7000 redis-cli --cluster reshard 192.168.177.128:7002
提示移动多少个槽( 从0 到 16384)?我们原来是3个Master,后面添加一个M,aster 7006,就是四个Master了。16384个槽4个节点分,是
4096
个,我们这里输入4096
表示想移动的槽的个数提示要把移动的槽给哪个节点,要求输入节点ID,7006节点的ID是414ab04c83bc4d9fd7837b004fc5cfe6f330fffb拷贝粘贴过来
提示从哪些节点分哈希槽过来,输入
all
表示全部节点提示是否确定执行拟定的重新分片计划 ,输入
yes
在重新分片结束后可检查集群情况,至此完成了对进群的重新分片操作
shell#使用redis-cli连接任意一台Redis,这里以7000为例,-c表示以集群模式连接 docker exec -it redis7000 redis-cli -c -p 7000 #查看集群信息 cluster info #查看集群节点 cluster nodes #查看槽位分配 docker exec -it redis7000 redis-cli --cluster check 192.168.177.128:7006
-
添加新的从节点,添加一个7007的新节点,加入到整个集群中,并作为7006主节点的从节点
创建数据持久化目录和配置文件
shell#创建数据持久化目录和配置文件目录 mkdir -p /mydata/redis/7007/data mkdir -p /mydata/redis/7007/config #拷贝一份7000节点的redis.conf文件到/mydata/redis/7007/config目录下 cp /mydata/redis/7000/config/redis.conf /mydata/redis/7007/config/redis.conf #修改/mydata/redis/7007/config/redis.conf如下内容 vim /mydata/redis/7007/config/redis.conf port 7007 (端口号修改) cluster-config-file nodes-7007.conf(它由Redis节点自行创建的集群文件,确保没有重叠的集群配置文件名)
启动新的节点
ruby#启动7007容器 docker run --name redis7007 --restart=always \ -v /mydata/redis/7007/data:/data \ -v /mydata/redis/7007/config/:/usr/local/etc/redis \ -d --network=host \ redis:7.0.5 redis-server /usr/local/etc/redis/redis.conf
加入集群,并设置挂载主节点
shell#使用redis-cli连接任意一台节点客户端,这里以7000为例,添加7006节点到集群,第一个参数是要添加的节点,第二个参数是集群中任意一个主节点,414ab04c83bc4d9fd7837b004fc5cfe6f330fffb是要挂载的主节点7006的节点ID docker exec -it redis7000 redis-cli --cluster add-node 192.168.177.128:7007 192.168.177.128:7002 --cluster-slave --cluster-master-id 414ab04c83bc4d9fd7837b004fc5cfe6f330fffb
检查集群情况
shell#使用redis-cli连接任意一台Redis,这里以7000为例,-c表示以集群模式连接 docker exec -it redis7000 redis-cli -c -p 7000 #查看集群信息 cluster info #查看集群节点 cluster nodes #查看槽位分配 docker exec -it redis7000 redis-cli --cluster check 192.168.177.128:7007
12.4 RabbitMQ默认集群
RabbitMQ的集群分两种模式,一种是默认集群模式 ,一种是镜像集群模式 。默认集群模式也叫普通集群模式 或者内置集群模式 ,在RabbitMQ集群中所有的节点被归为两类:磁盘节点 和内存节点,磁盘节点会把集群的所有信息(比如交换机、绑定关系、队列等信息)持久化到磁盘中,而内存节点只会将这些信息保存到内存中,如果该节点宕机或重启,内存节点的数据会全部丢失,而磁盘节点的数据不会丢失。RabbitMQ默认集群模式,只会把交换机、队列、虚拟主机等元数据信息在各个节点同步,而具体队列中的消息内容不会在各个节点中同步,如下图:
- 当用户访问其中任何一个RabbitMQ节点时,查询到的队列、交换机、虚拟主机、绑定关系等信息都是相同的
- 集群中队列的具体信息数据只在队列的拥有者节点保存,其他节点只知道队列的元数据和指向该节点的指针,所以当消费者消费时连接了另一个节点并访问不属于该节点队列的消息时,会通过元数据定位到队列数据所在的节点然后访问队列拉取数据发送给消费者
- 默认集群可以提高RabbitMQ的消息吞吐能力、节省存储空间、提高性能,如果消息需要复制到集群中每个节点,网络开销不可避免,持久化消息还需要写磁盘,占用磁盘空间等
- 默认集群无法保证高可用,因为一旦一个RabbitMQ节点挂了,消息就没法访问了,如果当前节点是磁盘节点 (做了持久化),那么等 RabbitMQ 实例恢复后,就可以继续访问了。如果当前节点是内存节点没做持久化,那么消息就丢了
- 默认集群要求至少有一个磁盘节点 ,其他节点可以是内存节点 。当节点加入或者离开集群时,必须要将变更通知到至少一个磁盘节点 。如果集群中唯一的一个磁盘节点挂掉的话,集群仍然可以保持运行,但是无法进行其他操作,直到磁盘节点 恢复。为了确保集群信息的可靠性,或者在不确定使用磁盘节点还是内存节点的时候,建议直接使用磁盘节点
接下来我们就在同一宿主机中通过Docker搭建3个节点的RabbitMQ的默认集群,并全部采用磁盘节点。端口分别为5672、5673、5674。需要值得注意的是,在同一宿主机创建RabbitMQ集群,不能使用Docker默认的bridger 网络模式,默认的bridger 网络模式只支持暴露端口通过ip和端口号进行通讯,而RabbitMQ节点则是通过hostname
节点名来加入集群,所以这里我们应当使用自定义网络模式,从而让各个节点可以通过节点名来进行访问(这一点我们在搭建单体RabbitMQ时也说明了此参数用于来指定节点名,从而才能进行持久化)
-
创建自定义网络
shelldocker network create rabbit_network
-
创建第5672、5673、5674节点容器
shell#创建5672节点 docker run --name my_rabbitmq1 --restart=always \ --hostname=rabbit1 \ --network rabbit_network \ -v /mydata/rabbitmq-cluster:/var/lib/rabbitmq \ -p 5672:5672 \ -p 15672:15672 \ -e RABBITMQ_DEFAULT_USER=admin \ -e RABBITMQ_DEFAULT_PASS=123456 \ -d rabbitmq:management #创建5673节点 docker run --name my_rabbitmq2 --restart=always \ --hostname=rabbit2 \ --network rabbit_network \ -v /mydata/rabbitmq-cluster:/var/lib/rabbitmq \ -p 5673:5672 \ -p 15673:15672 \ -e RABBITMQ_DEFAULT_USER=admin \ -e RABBITMQ_DEFAULT_PASS=123456 \ -d rabbitmq:management #创建5674节点 docker run --name my_rabbitmq3 --restart=always \ --hostname=rabbit3 \ --network rabbit_network \ -v /mydata/rabbitmq-cluster:/var/lib/rabbitmq \ -p 5674:5672 \ -p 15674:15672 \ -e RABBITMQ_DEFAULT_USER=admin \ -e RABBITMQ_DEFAULT_PASS=123456 \ -d rabbitmq:management #这里有几点需要注意: --hostname (必须要指定) -v /mydata/rabbitmq-cluster:/var/lib/rabbitmq(三个挂载的目录都是一致的,这是因为RabbitMQ底层是通过 Erlang语言来实现的,所以会使用Erlang系统连接RabbitMQ节点,在连接过程中需要正确的Erlang Cookie和节点名称,Erlang节点通过交换Erlang Cookie以获得认证,erlang.cookie是erlang实现分布式的必要文件,erlang分布式的每个节点上要保持相同的erlang.cookie文件)
-
分别进入5673、5674节点容器,加入5672节点形成集群
shell#将第5673节点加入到集群中 docker exec -it my_rabbitmq2 bash rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl join_cluster rabbit@rabbit1 rabbitmqctl start_app #将第5674节点加入到集群中 docker exec -it my_rabbitmq3 bash rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl join_cluster rabbit@rabbit1 rabbitmqctl start_app #以上命令中: rabbitmqctl stop_app (停止RabbitMQ应用) rabbitmqctl reset (用于清空节点的元数据和状态信息,以便于同步集群中的数据信息) rabbitmqctl start_app(启动RabbitMQ应用) rabbitmqctl join_cluster rabbit@rabbit1(加入到指定节点中形成集群,默认为磁盘节点,如果希望作为内存节点加入,可以使用 join_cluster --ram rabbit@rabbit1)
-
通过Web控制台页面查看集群状况
在Windows浏览器中,访问如下地址,用户名
admin
,密码123456
,查看集群情况,3个节点信息都出现就证明默认集群搭建完毕urlhttp://192.168.177.128:15672
-
测试集群消息发送
前面我们在创建RabbitMQ容器的时候分别都创建了Web控制台,其中15672对应5672,15673对应5673,15674对应5674。接下来,我们在15672中创建一个
test
队列然后创建交换机
testExchange
并绑定关系分别查看15673、15674也有了对应的队列和交换机的元数据信息
向15672控制台的交互机发送一条消息
test message
每个节点的
test
队列都能收到消息我们可以在任何节点消费此消息,例如我们在15673控制台手动消费消息,此时由于消息是存储在5672节点的,15673控制台对应的是5673节点,并没有该消息,5673节点的RabbitMQ会主动向5672节点进行消息的拉取并消费,当消息被消费时,其他节点将不会存在该消息
-
测试集群宕机
下面我们再通过15672控制台发送一条消息
test message2
确保15673和15674都有了消息之后,将15672所对应的5672节点容器手动停止掉,再使用15673控制台进行消息的消费观察现象
shelldocker stop my_rabbitmq1
此时消息直到5672节点恢复之前,消息都将无法被消费
默认集群中,如果每个节点都是磁盘节点的话,只能保证消息的可靠性,也就是消息不丢失。但无法保证集群的高可用
12.5 RabbitMQ镜像集群
由于默认集群无法保证高可用,所以可以采用镜像集群 ,镜像集群 是基于默认集群加上一定的配置得来的。镜像集群是把所有的队列数据完全同步,包括元数据信息和消息数据信息,当然这对性能肯定会有一定影响,但能保证数据的可靠性和集群的高可用,实现镜像集群也非常简单,它是在默认集群基础之上搭建而成的
-
镜像集群只需要进入默认集群中的某一个节点容器内部执行命令就可以进行配置
命令格式:
rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
set_policy:设置集群策略
-p Vhost:可选参数,针对指定vhost下的queue进行设置,可以不填,也可以默认填个'/'
Name:策略的名称,名称可以随意拟定
Pattern:队列的匹配模式,可以使用正则表达式,满足正则表达式的队列才会被设置上当前的策略
Definition:定义镜像队列的规则,是json字符串,分为三个部分:
ha-mode
,ha-params
,ha-sync-mode
- ha-mode:指定镜像队列的模式,可选值为
all/exactly/nodes
,其中all
表示在集群中所有的节点上进行镜像,exactly
表示在指定个数的节点上进行镜像,nodes
表示在指定的节点上进行镜像 - ha-params:ha-mode中
exactly
和nodes
模式的参数,表示节点数或节点名称 - ha-sync-mode:进行队列中消息的同步方式,有效值为automatic(自动)和manual(手动)
Priority:可选参数,策略的优先级,可以不填
- ha-mode:指定镜像队列的模式,可选值为
-
进入5672节点容器,进行配置,配置所有队列都进行镜像配置
shell#进入5672节点容器内 docker exec -it my_rabbitmq1 bash #设置策略匹配所有名称的队列都进行镜像配置 rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}' #如果要配置指定名称的队列进行镜像配置,可采用策略匹配所有名称是amp开头的队列都存储在2个节点上的命令如下 rabbitmqctl set_policy ha-amp "^amp*" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}' #查询策略 rabbitmqctl list_policies #删除策略 rabbitmqctl clear_policy 策略名
-
配置完毕之后,我们可以通过15672的控制台来查看一下情况
交换机已经全部同步为
ha-all
我们指定的镜像策略队列也已经全部同步为
ha-all
我们指定的镜像策略 -
测试集群宕机
下面我们通过15672控制台发送一条消息
test message3
确保15673和15674都有了消息之后,将15672所对应的5672节点容器手动停止掉,再使用15673控制台进行消息的消费观察现象
shelldocker stop my_rabbitmq1
此时5673和5674节点仍然可以进行消息的消费
再次重启5672节点,消息已经被消费了,队列不再有信息
12.6 ElasticSearch集群
假设有一个索引有10亿个文档,占用1TB的磁盘空间,而任意一个节点都没有这样大的磁盘空间,或者单个节点处理搜索请求响应太慢了,为了解决这个问题,ElasticSearch提供了将索引划分为多份的功能,每一份就叫做一个分片 。当建立一个索引的时候,可以指定想要分配的分片数量,每个分片本身也是一个功能完善并且独立的索引 ,这个索引 可以被放置到集群中的任何节点上,有了分片,一方面允许你水平分割/扩展你的内容容量,另一方面允许你在分片之上进行分布式并行操作,从而提高性能和吞吐量。当某个分片节点出现故障,该分片上的数据应该在另一个节点上有备份 ,这样才能保证不会出现找不到数据的情况,因此Elasticsearch允许你创建分片的一份或者多份拷贝,这些拷贝叫做分片的副本,副本的存在提高了节点出现故障时的集群高可用性。因此,副本分片不会与主分片在同一个节点上,如下图:
-
一个索引可以被分为多个分片,可以被复制0~N次,分片数和副本数可以在索引创建的时候指定,在索引创建之后,副本数可以改变,但分片数不能再改变
-
创建索引时,可以指定主分片数,默认主分片数是5个(可以修改),默认副分片数是1个(可以修改),需要注意:
- 主分片设置过少,比如只有一个分片,当数据增长比较快时,无法水平扩展
- 主分片设置过多,那么单个分片容量小,导致一个节点上有太多分片,影响性能
- 副本分片数过多,会降低集群写入性能
-
当我们创建文档时,ElasticSearch是根据这个公式决定的:shard = hash(文档id) % 主分片数 ,为了保证文档能正确路由,主分片个数不能随便修改,因为如果主分片个数发生变化了,那么所有之前路由值都会无效,文档也找不到了
-
每个节点都能接收请求,每个节点接收到请求后都能把该请求路由到有对应数据的其他节点上,接收原始请求的节点负责采集数据并返回给客户端
接下来,我们在一台宿主机中搭建3个节点的ElasticSearch伪集群,端口号分别为9200,9201,9202,并安装head插件查看集群情况,安装Kibana操作ElasticSearch,在安装之前需要确保单台宿主机的内存不低于2G
-
准备持久化目录
我们需要自己去创建一下挂载目录,这样做的目的是为了手动更改一下目录的访问权限,在ES的官方中提到,如果我们在Docker中尝试以挂载目录的方式来持久化数据,那么需要对持久化目录的访问权限进行授权,因为Docker中的ES默认是以elasticsearch用户身份 运行的,如果宿主机目录是以root用户身份创建的,那么就会导致权限不足,从而启动失败
shell#在宿主机中分别创建9200、9201、9202持久化目录,设置权限为777最大权限 mkdir -p /mydata/es-cluster/9200/plugins /mydata/es-cluster/9201/plugins /mydata/es-cluster/9202/plugins mkdir -p /mydata/es-cluster/9200/data /mydata/es-cluster/9201/data /mydata/es-cluster/9202/data #设置目录权限为777 chmod -R 777 /mydata/es-cluster/ #目录解释 plugins:用于存储es的插件 data:用于存储es的持久化数据
-
创建自定义模式的网络
在ElasticSearch集群中,各个节点之间的通信是通过节点名字来进行通信的,为了保证可以节点名称访问具体的容器网络,这里就必须使用自定义模式网络
shelldocker network create es_network
-
调整
vm.max_map_count
大小vm.max_map_count
参数,是一个Linux系统的调优参数,该参数可以限制一个进程可以拥有的VMA(虚拟内存区域)的数量,默认值是65530~65536这个区间。由于我们现在是在一台宿主机中搭建3个节点的ElasticSearch容器节点,ElasticSearch本身是需要占用较大的虚拟内存。为了防止出现类似如下的错误,我们需要将默认Linux中的虚拟内存的大小调大,建议是调整为默认大小的4倍shellmax virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
查看当前
vm.max_map_count
的大小shellsysctl -a | grep vm.max_map_count
调整
vm.max_map_count
的大小shell#打开系统配置文件 vim /etc/sysctl.conf #添加如下内容 vm.max_map_count=262144 #执行命令,让其生效 sysctl -p
-
分别创建3个ElasticSearch节点的Docker容器
shell#9200容器 docker run --name es1 --restart=always \ --network es_network \ -e ES_JAVA_OPTS="-Xms256m -Xmx256m" \ -e cluster.name=es-cluster \ -e node.name=es1 \ -e node.master=true \ -e network.host=0.0.0.0 \ -e discovery.seed_hosts=es1,es2,es3 \ -e cluster.initial_master_nodes=es1 \ -v /mydata/es-cluster/9200/plugins:/usr/share/elasticsearch/plugins \ -v /mydata/es-cluster/9200/data:/usr/share/elasticsearch/data \ -p 9200:9200 \ -p 9300:9300 \ -d elasticsearch:7.17.6 #9201容器 docker run --name es2 --restart=always \ --network es_network \ -e ES_JAVA_OPTS="-Xms256m -Xmx256m" \ -e cluster.name=es-cluster \ -e node.name=es2 \ -e node.master=true \ -e network.host=0.0.0.0 \ -e discovery.seed_hosts=es1,es2,es3 \ -e cluster.initial_master_nodes=es1 \ -v /mydata/es-cluster/9201/plugins:/usr/share/elasticsearch/plugins \ -v /mydata/es-cluster/9201/data:/usr/share/elasticsearch/data \ -p 9201:9200 \ -p 9301:9300 \ -d elasticsearch:7.17.6 #9202容器 docker run --name es3 --restart=always \ --network es_network \ -e ES_JAVA_OPTS="-Xms256m -Xmx256m" \ -e cluster.name=es-cluster \ -e node.name=es3 \ -e node.master=true \ -e network.host=0.0.0.0 \ -e discovery.seed_hosts=es1,es2,es3 \ -e cluster.initial_master_nodes=es1 \ -v /mydata/es-cluster/9202/plugins:/usr/share/elasticsearch/plugins \ -v /mydata/es-cluster/9202/data:/usr/share/elasticsearch/data \ -p 9202:9200 \ -p 9302:9300 \ -d elasticsearch:7.17.6 #以上环境变量解释命令解析 -e ES_JAVA_OPTS="-Xms256m -Xmx256m":es容器默认设置的JVM内存大小是"-Xms512m -Xmx512m",我们可以指定环境变量来调整JVM的内存大小 -e cluster.name=es-cluster:集群名字,同一个集群该值必须相同 -e node.name=es1:该节点的名字 -e node.master=true:该节点是否有机会成为master节点 -e network.host=0.0.0.0:打开网络,让其他主机可访问 -e discovery.seed_hosts=es1,es2,es3:集群中所有节点的地址列表 -e cluster.initial_master_nodes=es1:集群初始时的候选主节点
-
查看集群启动结果
集群启动之后,需要等待一小会儿时间,ES的启动会比较慢,确保全部启动完毕之后,可以在Windows浏览器中通过如下地址观察每个节点的情况和集群情况
url查看每个节点的启动情况 http://192.168.177.128:9200 http://192.168.177.128:9201 http://192.168.177.128:9202 #查看集群的情况 http://192.168.177.128:9200/_cat/nodes
-
安装
elasticsearch-head
插件elasticsearch-head
项目提供了一个直观的界面,可以很方便地查看集群、分片、数据等等。elasticsearch-head本身是一个由JS开发的前端项目,被托管在GitHub上:github.com/mobz/elasti...,它的最简单的安装方式是通过下载插件直接安装在谷歌浏览器
上,插件被存放在源码中的crx
目录中,直接下载即可,下载后的文件为es-head.crx
,将此文件后缀改为.zip
,即es-head.zip
,然后就可以解压此文件了。紧接着在谷歌浏览器中选择"更多工具"--"扩展程序"
确保右上角的开发者模式处于"打开状态",然后点击"加载已解压的扩展程序",选择我们前面解压的插件目录即安装完毕
在浏览器中点击 elasticsearch-head 插件打开 head 界面,并连接 http://192.168.177.181:9200/
-
访问
elasticsearch-head
页面插件安装完毕后,点击谷歌浏览器扩展程序图片,即可看到elasticsearch-head插件,点击插件即可进入页面
在页面中填写如下地址连接ES集群
urlhttp://192.168.177.128/9200
标有★符号的是Master主节点,标有●的是从节点
被粗实体边框包裹的分片代表的是主分片,被细实体边框包裹的分片代表的是副本分片,数字代表的是当前分片编号
-
安装Kibana
在集群中,安装Kibana有少许不同,首先要确保Kibana也处于ES集群的自定义网络中,其次在配置连接的时候我们需要指定3个节点的地址
shelldocker run --name my_kibana --restart=always \ --network es_network \ -e ELASTICSEARCH_HOSTS='["http://es1:9200","http://es2:9200","http://es3:9200"]' \ -p 5601:5601 \ -d kibana:7.17.6
-
访问Kibana
在Windows浏览器中输入一下地址对Kibana的可视化界面进行访问
urlhttp://192.168.177.128:5601/app/kibana
-
测试分片
json<!-- 创建索引,命名为phone--> PUT /phone { "settings": { "number_of_shards": 3, "number_of_replicas": 1 } } /* 以上参数解释: number_of_shards:分片数量,默认值是5 number_of_replicas:副本数量,默认值是1 我们有三个节点,可以在每个节点上都创建一个主分片,每个分片都创建一个副本分片 */
es1节点为主节点,其中2号分片为主分片,1号分片为副本分片
es2节点为从节点,其中的0号分片为主分片,2号分片为副本分片
es3节点为从节点,其中的1号分片为主分片,0号分片为副本分片
ES集群会保证主分片和副本分片不会在同一个节点中,主分片可以在同一个节点中
-
测试集群宕机
我们停掉es1节点,观察情况
shelldocker stop es1
通过Head插件观察集群情况,我们发现原本es1节点是主节点,宕机之后es2被选举为了新的主节点
原本es1节点中中2号分片是主分片,此时宕机之后,将es2节点中的2号副本分片提升为了主分片
同时健康值变为yellow黄色(集群本身是Green绿色,表示健康状态 ),这种情况代表的是亚健康状态,指主分4.片全部可用,部分副本分片不可用
如果只有1个主分片不设置副本分片的话,有主分片所在的节点宕机之后,整个集群会出现Red红色,表示不健康状态,指的就是部分主分片不可用了
然后我们恢复es1节点,观察情况
shelldocker start es1
通过Head插件观察集群情况,我们发现原本es1节点现在是从节点
es1节点中的两个分片现在都是副本分片,集群情况变为Green绿色,表示健康状态
集群目前满足主分片和副本分片不在同一个节点中,允许多个主分片在同一个节点,在一些特定情况,如果不满足这个情况,ES集群内部会重新分片
-
安装ik分词器
前面单机模式中我们已经了解过ik分词器,它是为了对中文分词的支持,本例中我们为3台节点分别也安装一下ik分词器
shell#分别在各节点plugins目录下创建ik分词器目录 mkdir /mydata/es-cluster/9200/plugins/ik /mydata/es-cluster/9201/plugins/ik /mydata/es-cluster/9202/plugins/ik #将之前单机版/mydata/es/plugins/ik目录下的elasticsearch-analysis-ik-7.17.6.zip分别拷贝到三个节点ik目录中 cp /mydata/es/plugins/ik/elasticsearch-analysis-ik-7.17.6.zip /mydata/es-cluster/9200/plugins/ik cp /mydata/es/plugins/ik/elasticsearch-analysis-ik-7.17.6.zip /mydata/es-cluster/9201/plugins/ik cp /mydata/es/plugins/ik/elasticsearch-analysis-ik-7.17.6.zip /mydata/es-cluster/9202/plugins/ik #分别并解压 cd /mydata/es-cluster/9200/plugins/ik unzip elasticsearch-analysis-ik-7.17.6.zip cd /mydata/es-cluster/9201/plugins/ik unzip elasticsearch-analysis-ik-7.17.6.zip cd /mydata/es-cluster/9202/plugins/ik unzip elasticsearch-analysis-ik-7.17.6.zip #重启ES容器 docker restart es1 es2 es3
-
测试分词
json<!-- 细粒度分割策略 --> GET _analyze { "analyzer": "ik_max_word", "text": ["中华人民共和国国歌"] } <!-- 粗粒度度分割策略 --> GET _analyze { "analyzer": "ik_smart", "text": ["中华人民共和国国歌"] }