微服务部署之Maven插件构建Docker镜像

微服务架构下,微服务在带来良好的设计和架构理念的同时,也带来了运维上的额外复杂性,尤其是在服务部署和服务监控上。单体应用是集中式的,就一个单体跑在一起,部署和管理的时候非常简单,而微服务是一个网状分布的,有很多服务需要维护和管理,对它进行部署和维护的时候则比较复杂。

下面从Dev的角度来看一下Ops的工作。从Dev提交代码,到完成集成测试的一系列步骤如下:

  • 首先是开发人员把程序代码更新后上传到Git,然后其他的事情都将由Jenkins自动完成。
  • 然后Git在接收到用户更新的代码后,会把消息和任务传递给Jenkins,然后Jenkins会自动构建一个任务,下载Maven相关的软件包。下载完成后,就开始利用Maven Build新的项目包,然后重建Maven容器,构建新的Image并Push到Docker私有库中。
  • 最后删除正在运行的Docker容器,再基于新的镜像重新把Docker容器启动,自动完成集成测试。

整个过程都是自动的,这样就简化了原本复杂的集成工作,一天可以集成一次,甚至是多次。

未分类

本文主要关注的第二步,作为Dev使用Maven插件构建Docker镜像。

一、过程步骤

1、环境

笔者的电脑系统是MacOS,在进行下面的步骤之前,先具备一下条件:

  • Docker Registry
  • Maven(3.5.0)
  • JDK(1.8.0_131)
  • Docker for Mac (17.09.0-ce-mac35)

Maven 和JDK 就不用过多多了,必须具有的。Docker Registry是私有的hub,mac上装好docker之后,配置一下Docker Registry的地址,配置如下:

  "debug" : true,
  "experimental" : false,
  "insecure-registries" : [
    "192.168.1.202"

未分类

2、pom文件

pom文件中需要引入相应的插件。docker-maven-plugin有三款:spotify、fabric8io和bibryam。其中第一款最为流行,资料也多,所以毫不犹豫选择第一款。
插件有两种使用方式,一种是在直接在pom配置中指定baseImage和entryPoint。另一种适合于复杂的构建,使用dockerfile,只需要在配置中指定dockerfile的位置。前一种比较简单,此处略过,主要讲下第二种的配置。

<plugin>
             <groupId>com.spotify</groupId>
             <artifactId>docker-maven-plugin</artifactId>
             <version>${maven.docker.version}</version>
             <!--插件绑定到phase-->
             <executions>
                 <execution>
                     <phase>install</phase>
                     <goals>
                         <goal>build</goal>
                     </goals>
                 </execution>
             </executions>
             <configuration>
             <!--配置变量,包括是否build、imageName、imageTag,非常灵活-->
                 <skipDocker>${docker.skip.build}</skipDocker>
                 <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
                 <!--最后镜像产生了两个tag,版本和和最新的-->
                 <imageTags>
                     <imageTag>${project.version}</imageTag>
                     <imageTag>latest</imageTag>
                 </imageTags>
                 <forceTags>true</forceTags>                 
                 <env>
                     <TZ>Asia/Shanghai</TZ>
                 </env>
                 <!--时区配置-->
                 <runs>
                     <run>ln -snf /usr/share/zoneinfo/$TZ /etc/localtime</run>
                     <run>echo $TZ > /etc/timezone</run>                      
                 </runs>
                 <dockerDirectory>${project.basedir}</dockerDirectory>
                 <resources>
                     <resource>
                         <targetPath>/</targetPath>
                         <directory>${project.build.directory}</directory>
                         <include>${project.build.finalName}.jar</include>
                     </resource>
                 </resources>
                 <!--push到私有的hub-->
                 <serverId>docker-registry</serverId>
             </configuration>
         </plugin>

${maven.docker.version}、${docker.skip.build}、${docker.image.prefix}都是可配置的变量。${project.basedir}、${project.build.directory}、${project.build.finalName}、${project.version}分别对应项目根目录、构建目录、打包后生成的结果名称、项目版本号。

上面的pom插件配置,指定了dockerfile的位置和镜像的命名规则。并将docker的build目标,绑定在install这个phase上。

3、dockerfile

FROM 192.168.1.202/library/basejava
VOLUME /tmp
ADD ./target/cloud-api-gateway-1.2.0.RELEASE.jar app.jar
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

dockerfile 写的很简单,将jar包ADD进去,提供ENTRYPOINT。

4、setting.xml

在pom插件中,还有一个serverId的配置。这个配置是必要的,对于需要将image上传到私有hub上,在如上配置之后,只需要加上-DpushImage即可实现。serverId是与maven的配置文件setting.xml相对应,setting.xml中增加的配置:

<server>
  <id>docker-registry</id>
  <username>用户名</username>
  <password>密码</password>
  <configuration>
    <email>邮箱</email>
  </configuration>
</server>

5、结果

未分类

上图是执行mvn clean install -DpushImage成功的结果。mvn首先是打包,将生产的文件拷贝到target下的docker目录,然后执行dockerfile中的步骤,将打成的镜像进行tag,最后上传到私有hub上。

未分类

上图是VMware Harbor中的截图,可以看到,我们已经成功将镜像上传,其tag有两个:1.2.0.RELEASE和latest。

本文属于工程实践类文章,比较简单。开头由背景介绍了Dev到继承测试的一系列步骤,本文主要关注的是第二步,作为Dev使用Maven插件构建Docker镜像。正文部分主要讲了实践的环境,然后讲了docker-maven-plugin插件的使用方式,重点介绍了使用dockerfile的方式,对于涉及到的配置进行了解释。

本文的源码地址:

GitHub:https://github.com/keets2012/snowflake-id-generator
码云: https://gitee.com/keets/snowflake-id-generator

使用docker搭建自动化规则测试环境

一、问题场景

自动化规则测试环境,使用python2编写,依赖于redis-py-cluster,pykafka,futures,mysql-python。在本地搭建测试环境,python安装 没有出现问题,但是在安装python插件时,遇到问题较多,解决花费时间较长,且未能完全解决,导致测试环境迟迟不能运行。所以决定采用docker来 搭建测试环境。

二、搭建步骤

以下为搭建环境的步骤

1、安装docker环境不提

2、创建基本python环境,并安装插件

  • 创建Dockerfile文件
  • 在Dockerfile中输入如下代码 “` FROM centos:7 MAINTAINER Augustus “[email protected]
RUN yum -y update RUN yum -y install epel-release RUN yum -y install python-pip RUN yum -y install mysql-devel RUN yum -y install python-devel RUN yum -y install MySQL-python RUN pip install –upgrade pip RUN pip install pykafka RUN pip install mysql RUN pip install redis-py-cluster RUN pip install futures

build 基本镜像,执行命令

docker build py_auto:v1 .

3、创建支持java命令环境

创建Dockerfile

mkdir v2 && cd v2 &&  vi Dockerfile ``` * 从oracle下载linux-jdk8到当前目录 * 在Dockerfile中输入如下命令 ``` FROM py_auto_base:v2 MAINTAINER Augustus "[email protected]"
ADD ./jdk-8u151-linux-x64.rpm /opt/ RUN rpm -ivh /opt/jdk-8u151-linux-x64.rpm RUN rm -f /opt/jdk-8u151-linux-x64.rpm

执行如下命令

docker build py_auto:v2 .

4、创建测试环境

在项目目录下创建Dockerfile

在Dickerfile中输入如下命令

FROM py_auto_base:v2 MAINTAINER Augustus “[email protected]”

RUN mkdir /py_auto VOLUME [“/py_auto”]

CMD [“/bin/bash”]

运行命令

docker build auto_test:v1 .

三、运行并执行测试

如果上面一切顺利,那我们这边已经成功构建了auto_test:v1镜像。下面我们来启动镜像并运行测试环境。

启动镜像

docker run -v /Users/augustus/asiawork/pytest/py-autotest-frame:/py_auto –name auto_test -d -it auto_test:v1

运行测试

docker exec auto_test python /py_auto/pyautotestframe/pyautotestframe.py -m sample-test

这样成功运行了测试实例。

四、一点心得

采用docker,快速的搭建了测试环境,在新增人员的情况下,可以节省不少时间在环境搭建上。就是对于开发 者也可以避免很多搭建环境的问题。

本示例采用的是每次一执行,所以使用docker exec,在有些情况下,需要启动镜像便运行环境的时候,可以 在Dockerfile中使用ENTRTPOINT来以使镜像启动便运行。

Centos7修改docker的Data Space Total大小

记得备份你的容器

此处就不多做介绍了

–storage-opts 参数

devicemapper文档:https://github.com/moby/moby/tree/master/daemon/graphdriver/devmapper
docker官方文档:https://docs.docker.com/engine/reference/commandline/dockerd/#options-per-storage-driver

修改docker的启动参数

停止docker,修改配置,重新加载配置

<code><span class="hljs-built_in">sudo</span> systemctl stop docker
<span class="hljs-built_in">sudo</span> vi /lib/systemd/system/docker.service</code>

找到 ExecStart=/usr/bin/dockerd
在这一行后面加上
--storage-opt dm.loopdatasize=8G --storage-opt dm.loopmetadatasize=4G --storage-opt dm.basesize=8G 即可,意思是为,设置devicemapper的data为8G,metadata为4G,镜像的大小不能大于8G

记得还要抹去现有的空间,请确保你已经完成了第一步

<code>sudo rm -rf /var/lib/docker
sudo mkdir -p /var/lib/docker/devicemapper/devicemapper/
sudo dd <span class="hljs-keyword">if</span>=<span class="hljs-regexp">/dev/zero</span> of=<span class="hljs-regexp">/var/lib</span><span class="hljs-regexp">/docker/devicemapper</span><span class="hljs-regexp">/devicemapper/data</span> bs=<span class="hljs-number">1</span>M count=<span class="hljs-number">0</span> seek=<span class="hljs-number">8192</span>
sudo dd <span class="hljs-keyword">if</span>=<span class="hljs-regexp">/dev/zero</span> of=<span class="hljs-regexp">/var/lib</span><span class="hljs-regexp">/docker/devicemapper</span><span class="hljs-regexp">/devicemapper/metadata</span> bs=<span class="hljs-number">1</span>M count=<span class="hljs-number">0</span> seek=<span class="hljs-number">4096</span></code>

完成后运行

<code><span class="hljs-built_in">sudo</span> systemctl daemon-reload
<span class="hljs-built_in">sudo</span> systemctl start docker
docker info</code>

查看是否设置正确,貌似比预设的大了一点点。

未分类

让docker处于ufw防火墙的控制下

最近在使用docker时遇到了一个大坑:运行一个实例时,将宿主机的一个端口映射到docker的一个实例,也就是使用-p 8080:80命令运行后,端口呢是能在宿主机上直接访问了,但是我突然发现我在本地居然也能访问服务器上的8080端口号,这就不科学了,因为我通过ufw把8080禁止的呀!

查资料可知,dockerz直接在iptables上进行修改,ufw的设置对它没有作用。下面是解决办法。

修改ufw默认的配置

vim /etc/default/ufw

#把DEFAULT_FORWARD_POLICY修改为下面
DEFAULT_FORWARD_POLICY="ACCEPT"

修改docker的默认配置

取消注释DOCKER_OPTS这行,在参数后添加添加-iptables=false

vim /etc/default/docker

#修改文件
DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 -iptables=false"

修改/etc/ufw/before.rules

vim /etc/ufw/before.rules

在`*filter`前面添加下面内容
###########我是分割线############
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE
COMMIT
###########我是分割线############

新增文件/etc/docker/daemon.json

vim /etc/docker/daemon.json

###########我是分割线############
{
"iptables": false
}
###########我是分割线############

重启系统

reboot

docker maven nexus svn 搭建

0. 准备工作

需要一个 image curiousby/centos-ssh-root-java-tomcat-maven-nexus:original

1. 运行该 镜像

Java代码

docker run -d -p 8080:8080   -p 8081:8081  -v /docker/nexus/data/:/usr/local/nexus/sonatype-work -v /docker/tomcat2/logs/:/usr/local/tomcat/logs  -v /docker/maven/repos/:/docker/maven/repos/ curiousby/centos-ssh-root-java-tomcat-maven-nexus:original   

2. 进入该镜像

Java代码

docker exec -it f7f bash  

3. 使用 yum 命令安装 svn

Java代码

yum install -y subversion  

Java代码

mkdir -p /data/svn/    
svnadmin create /data/svn/repo    
修改配置    
vim /data/svn/repo/conf/svnserve.conf    

[general]    
anon-access=none #匿名访问的权限,可以是read,write,none,默认为read    
auth-access=write #使授权用户有写权限     
password-db=passwd #密码数据库的路径     
authz-db=authz #访问控制文件     
realm=/data/svn/repo #认证命名空间,subversion会在认证提示里显示,并且作为凭证缓存的关键字     

vim /data/svn/repo/conf/passwd    
[users]    
baoyou=123456    

vim /data/svn/repo/conf/authz    
[/]    
baoyou = rw    
启动    
ps -ef|grep svn     
svnserve -d -r /data/svn/repo  --listen-port=3690      

4. 提交镜像

Java代码

docker commit  f7f  curiousby/centos-ssh-root-java-tomcat-maven-nexus-svn:original  

5. 启动运行镜像

Java代码

docker run -d -p 8080:8080  -p 3690:3690  -p 8081:8081  -v  /docker/svn/data:/data/svn/ -v /docker/nexus/data/:/usr/local/nexus/sonatype-work -v /docker/tomcat/server/:/usr/local/tomcat/webapps -v /docker/tomcat/logs/:/usr/local/tomcat/logs  -v /docker/maven/repos/:/docker/maven/repos/ curiousby/centos-ssh-root-java-tomcat-maven-nexus-svn:original   

6. 进入镜像打开 svn 访问权限

Java代码

svnadmin create /data/svn/repo  
svnserve -d -r /data/svn/repo  --listen-port=3690  

7. 测试

未分类

未分类

未分类

未分类

docker使用笔记

部署公司信息安全平台包含了乌云知识库、NESSUS、AWVS11、巡风资产扫描系统、CTFd、XSS平台,对巡风信息安全平台巡风、CTFd、漏洞靶机均采用docker运行,用到的命令记录下来。

0x01 在线安装docker

//Docker 安装
$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 
$ sudo bash -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list" 
sudo apt-get update 
sudo apt-get install lxc-docker 
sudo docker -v

0x02 拉取部署巡风

//巡风github 
https://github.com/ysrc/xunfeng/blob/master/docs/install/Docker.md

//拉取巡风Docker镜像
$ docker pull ysrc/xunfeng
//速度比较快
https://www.daocloud.io/mirror#accelerator-doc
//更新Docker镜像仓库地址到国内源
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://93cd7dbb.m.daocloud.io
拉取完后:
创建启动容器
docker run -d -p 8000:80 -v /opt/data:/data ysrc/xunfeng:latest

//巡风Docker 镜像信息
类型  用户名 密码
Web账号   admin   xunfeng321
mongodb 数据库 scan    scanlol66
mongodb 端口  27017   
巡风物理路径  /opt/xunfeng    
MASSCAN 路径  /opt/xunfeng/masscan/linux_64/masscan   

0x03 快速搭建各种漏洞环境

//拉取s2-052镜像:
docker pull medicean/vulapps:s_struts2_s2-052
//启动:
docker run -d -p 80:8080 medicean/vulapps:s_struts2_s2-052

//拉取dvwa镜像
docker pull vulnerables/web-dvwa
//启动
docker run --rm -it -p 8081:80 vulnerables/web-dvwa

0x04 启动信息安全中心容器

启动信息安全中心容器

docker run -itd -p80:80 20495bb5bcec /bin/bash
进入信息安全中心容器
docker exec -it b9fdbfa12be7 /bin/bash  进入容器打开一个sh
执行:
mysql启动
service mysqld start
apache启动
service httpd start
访问IP地址即可显示信息安全中心平台

启动巡风容器
docker run -itd -p8000:80 c91fd1a54b93 /bin/bash

0x04 docker基础命令

docker images  查看镜像
docker ps      查看正在运行的镜像
docker ps -a   显示所有的容器
docker rmi ID  停止一个容器后,删除这个镜像
docker info    查看docker信息
docker ps –l   显示最后一次创建的container,包括未运行的
docker pull ${IMAGE} 安装镜像
docker images --no-trunc 显示已经安装镜像的详细内容
docker stop ${CID} 停止指定容器
docker stop 
docker ps -q 停止所有正在运行的容器
docker rm ${CID} 删除指定容器
docker rm -f $(docker ps -a | grep Exit | awk '{ print $1 }') 删除所有退出的容器
docker rm $(docker ps -aq) 删除所有的容器
docker attach ${CID} 
进入容器docker exec -it ${CID} bash 进入容器打开一个shell
docker ps | grep wildfly | awk '{print $1}' 通过正则表达式查找容器的镜像ID
docker inspect ID 可以使用docker inspect来查看该容器的详细信息。
docker exec -it 775c7c9ee1e1 /bin/bash     使用docker exec进入运行Docker容器
docker commit [container-id] custom/centos_httpd 把所做的改变提交到一个新的容器:容器成功提交后,执行 docker images ,会看到刚才提交的容器
删除旧容器:
docker rm [container-id]
删除旧镜像:
docker rmi [image-id]
docker rmi [image-name][:image-tag]

0x05 docker打包迁移镜像

docker打包迁移镜像
[root@oegw1 soft]# docker save  8236fe4c8304 > /root/srcwww.img  
[root@oegw1 soft]# scp postgres.img 192.168.157.134:/opt/soft/  
Address 192.168.157.134 maps to localhost, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!  
[email protected]'s password:   
postgres.img                        
[root@oegw2 soft]# docker load < postgres.img  
[root@oegw2 soft]# docker images  
[root@oegw2 soft]# docker tag eb9a804ae885 postgres:postgres  
[root@oegw2 soft]# docker images

0x06 Docker镜像导入导出

导出容器快照到本地文件
$ sudo docker ps -a
$ sudo docker export 7691a814370e > ubuntu.tar
导入容器快照docker import
从容器快照文件中再导入为镜像
$ cat ubuntu.tar | sudo docker import - test/ubuntu:v1.0
$ sudo docker images
拷被完成后启动容器
docker run -itd -p80:80 20495bb5bcec /bin/bash

0x07 效果图

未分类

使用InfluxDB+cAdvisor+Grafana配置Docker监控

文档来源

  • 文档来源:How to setup Docker Monitoring
  • 由garyond翻译、校正及整理

Docker监控简介

我们提供的Docker主机和容器越来越多,对Docker服务器和容器的监控越来越必要。本文将引导你了解如何通过多个不同组件的配置和协作,以实现Docker监控。

Docker监控组件

首先, 我们假设在您的主机上已经安装、配置和运行Docker Engine。请确保您可以通过Web浏览器在本地或者通过网络IP连接到Docker主机。以下我们将详细介绍如何创建我们的Docker监控方案。

  • cAdvisor: Google开源的用于监控基础设施应用的工具,它是一个强大的监控工具,不需要任何配置就可以通过运行在Docker主机上的容器来监控Docker容器,而且可以监控Docker主机。更多详细操作和配置选项可以查看Github上的cAdvisor项目文档。

  • influxDB: 它是一个分布式时间序列数据库。cAdvisor仅仅显示实时信息,但是不存储监视数据。因此,我们需要提供时序数据库用于存储cAdvisor组件所提供的监控信息,以便显示除实时信息之外的时序数据。

  • Grafana: Grafana可视化展示平台允许我们可视化地展示监控信息。它允许我们对influxDB进行查询,并通过强大的图表功能进行可视化展示。

本文配置组件版本信息如下:

  • InfluxDB: V 1.0.0
  • cAdvisior: V 0.24.1
  • Grafana: V 4.0.2

Docker监控安装与配置

下面,我们将根据上述的Docker监控组件进行安装和配置,形成统一的Docker监控平台。

我们将首先先安装influxDB, 以便于它能够与cAdvisor进行协作。

1. 安装influxDB

下载influxDB镜像

docker pull tutum/influxdb

运行influxdb容器

docker run -d 
   -p 8083:8083 
   -p 8086:8086 
   --expose 8090 
   --expose 8099 
   --name influxsrv 
   tutum/influxdb

influxdb容器运行成功后,通过Web浏览器访问http://docker-host-ip:8083访问influxdb后台管理,并登录后台管理系统(默认用户名:root, 默认密码:root)。

未分类

influxdb_connection_settings

2. 创建cAdvisor应用数据库

在登录influxdb后台数据库管理平台后, 创建cAdvisor数据库, 用于存储cAdvisor应用所获取的实时监控数据。在influxDB管理界面中的Querie s输入框中创建数据库和用户:

未分类

create_database
# 创建数据库
create database 'cadvisor'; 

# 创建用户
CREATE USER 'cadvisor' WITH PASSWORD 'cadvisor'

# 用户授权
grant all privileges on 'cadvisor' to 'cadvisor'

# 授予读写权限
grant WRITE on 'cadvisor' to 'cadvisor'
grant READ on 'cadvisor' to 'cadvisor'

3. 运行cAdvisor应用容器并与influxDB容器进行互联

启动cAdvisor容器

docker run 
  --volume=/:/rootfs:ro 
  --volume=/var/run:/var/run:rw 
  --volume=/sys:/sys:ro 
  --volume=/var/lib/docker/:/var/lib/docker:ro 
  -p 8080:8080 
  --detach=true  
  --link influxsrv:influxsrv 
  --name=cadvisor 
  google/cadvisor:latest 
  -storage_driver=influxdb 
  -storage_driver_db=cadvisor 
  -storage_driver_host=influxsrv:8086

cAdvisor应用容器启动成功后,通过Web浏览器访问地址http://docker-host-ip:8080,便可以查看cAdvisor监控工具所收集到的Docker主机和容器的资源统计信息。

未分类

cAdvisor WebUI

4. 运行Grafana可视化平台并与influxDB容器进行互联

docker run -d 
  -p 3000:3000 
  -e INFLUXDB_HOST=localhost 
  -e INFLUXDB_PORT=8086 
  -e INFLUXDB_NAME=cadvisor 
  -e INFLUXDB_USER=root  
  -e INFLUXDB_PASS=root 
  --link influxsrv:influxsrv 
  --name grafana 
  grafana/grafana

5. 登录Grafana管理平台

通过Web浏览器访问地址http://docker-host-ip:3000登录Grafana管理平台。

  • 用户名:admin
  • 密 码:admin

未分类

Grafana_Login

6. 配置Grafana与influxDB数据库连接

(1). 配置Granfana数据源

在Grafana管理平台中,单击“添加数据源”对数据源进行配置。如下图所示。

未分类

Add_Datasource

  • 基本信息

Name:influxdb
Type:influxDB
Default: checked

  • Http settings

Url:http://influxsrv:8086
Access:proxy

  • HTTP Auth

Basic Auth: checked

  • Basic Auth Details

User: admin
Password: admin

  • InfluxDB Details

Database:cadvisor
User:cadvisor
Password:cadvisor

配置完成后, 我们已经建立与influxDB的连接,下面我们将其进行测试。

(2). 添加Dashboard

下面我们将使用Grafana配置我们的第一个Dashboard,并可视化来自cAdvisor的监控数据。

1). 点击“Grafana”图标, 打开Dashboard菜单,选择新建Dashboard;

未分类

New Dashboard

2). 在Dashboard面板中选择相应的组件并拖动到相关的位置;

未分类

Add_Component

3). 点击面板的标题,会弹出相应的选项”view”、”edit”、”Duplicates”、”share”和删除按扭;

未分类

Edit Component

4). 选择”edit”选项,出现相应的配置项,在“Metrics”选项卡中配置相应的数据表和数据源信息;

未分类

Edit_Configure

例如: 配置内存使用率信息

未分类

Edit Memory Usage

在Metric选项卡中, 配置数据表来源为:SELECT mean(“value”) FROM “memory_usage” WHERE $timeFilter GROUP BY time(10s) fill(0), 配置数据源Panel Datasource为:cadvisor;

在Axes选项卡中配置相关的显示单位。

未分类

Config Axies

5). 配置完相关的选项后,点击Dashboard导航栏的保存按钮,便可完成Dashboard的配置。

未分类

Save Config

至此, 所有配置步骤完成, 我们能够看到Grafana显示相关的监控图表信息。

完整PDF文档可以到Linux公社资源站下载:

——————————————分割线——————————————

免费下载地址在 http://linux.linuxidc.com/

用户名与密码都是www.linuxidc.com

具体下载目录在 /2017年资料/11月/7日/使用InfluxDB+cAdvisor+Grafana配置Docker监控/

下载方法见 http://www.linuxidc.com/Linux/2013-07/87684.htm

——————————————分割线——————————————

Redis Cluster部署、管理和测试

背景

  Redis 3.0之后支持了Cluster,大大增强了Redis水平扩展的能力。Redis Cluster是Redis官方的集群实现方案,在此之前已经有第三方Redis集群解决方案,如Twenproxy、Codis,与其不同的是:Redis Cluster并非使用Porxy的模式来连接集群节点,而是使用无中心节点的模式来组建集群。在Cluster出现之前,只有Sentinel保证了Redis的高可用性。

  Redis Cluster实现在多个节点之间进行数据共享,即使部分节点失效或者无法进行通讯时,Cluster仍然可以继续处理请求。若每个主节点都有一个从节点支持,在主节点下线或者无法与集群的大多数节点进行通讯的情况下, 从节点提升为主节点,并提供服务,保证Cluster正常运行,Redis Cluster的节点分片是通过哈希槽(hash slot)实现的,每个键都属于这 16384(0~16383) 个哈希槽的其中一个,每个节点负责处理一部分哈希槽。

环境

Ubuntu 14.04
Redis 3.2.8
主节点:192.168.100.134/135/136:17021
从节点:192.168.100.134/135/136:17022

对应主从节点:

   主        从 
134:17021  135:17022
135:17021  136:17022
136:17021  134:17022

手动部署

①:安装

按照Redis之Sentinel高可用安装部署文章中的说明,装好Redis。只需要修改一下Cluster相关的配置参数:

################################ REDIS CLUSTER ###############################
#集群开关,默认是不开启集群模式。
cluster-enabled yes

#集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系统中配置文件名称不冲突
cluster-config-file nodes-7021.conf

#节点互连超时的阀值。集群节点超时毫秒数
cluster-node-timeout 30000

#在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升>为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是:
#比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period
#如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移
#可能出现由于某主节点失联却没有从节点能顶上的情况,从而导致集群不能正常工作,在这种情况下,只有等到原来的主节点重新回归到集群,集群才恢复运作
#如果设置成0,则无论从节点与主节点失联多久,从节点都会尝试升级成主节
cluster-slave-validity-factor 10

#master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节>点会尝试迁移。
#主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移。
# cluster-migration-barrier 1

#默认情况下,集群全部的slot有节点分配,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会
造成分区的时候,小分区的master一直在接受写请求,而造成很长时间数据不一致。
#在部分key所在的节点不可用时,如果此参数设置为”yes”(默认值), 则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的key提供读>操作
cluster-require-full-coverage yes

安装好之后开启Redis:均运行在集群模式下

root@redis-cluster1:~# ps -ef | grep redis
redis      4292      1  0 00:33 ?        00:00:03 /usr/local/bin/redis-server 192.168.100.134:17021 [cluster]
redis      4327      1  0 01:58 ?        00:00:00 /usr/local/bin/redis-server 192.168.100.134:17022 [cluster]

②:配置主节点

添加节点: cluster meet ip port

进入其中任意17021端口的实例,进入集群模式需要参数-c:
~# redis-cli -h 192.168.100.134 -p 17021 -c
192.168.100.134:17021> cluster meet 192.168.100.135 17021
OK
192.168.100.134:17021> cluster meet 192.168.100.136 17021
OK
节点添加成功

查看集群状态:cluster info

192.168.100.134:17021> cluster info
cluster_state:fail                        #集群状态
cluster_slots_assigned:0                  #被分配的槽位数
cluster_slots_ok:0                        #正确分配的槽位             
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3                     #当前3个节点
cluster_size:0
cluster_current_epoch:2                  
cluster_my_epoch:1
cluster_stats_messages_sent:83
cluster_stats_messages_received:83

上面看到集群状态是失败的,原因是槽位没有分配,而且需要一次性把16384个槽位完全分配了,集群才可用。接着开始分配槽位:需要登入到各个节点,进行槽位的分配,如:

node1分配:0~5461
node2分配:5462~10922
node3分配:10923~16383

分配槽位:cluster addslots 槽位,一个槽位只能分配一个节点,16384个槽位必须分配完,不同节点不能冲突。

192.168.100.134:17021> cluster addslots 0
OK
192.168.100.135:17021> cluster addslots 0   #冲突
(error) ERR Slot 0 is already busy

目前还没有支持区间范围的添加槽位操作,所以添加16384个槽位的需要写一个批量脚本(addslots.sh):

node1:
#!/bin/bash
n=0
for ((i=n;i<=5461;i++))
do
   /usr/local/bin/redis-cli -h 192.168.100.134 -p 17021 -a dxy CLUSTER ADDSLOTS $i
done

node2:
#!/bin/bash
n=5462
for ((i=n;i<=10922;i++))
do
   /usr/local/bin/redis-cli -h 192.168.100.135 -p 17021 -a dxy CLUSTER ADDSLOTS $i
done

node3:
#!/bin/bash
n=10923
for ((i=n;i<=16383;i++))
do
   /usr/local/bin/redis-cli -h 192.168.100.136 -p 17021 -a dxy CLUSTER ADDSLOTS $i
done

连接3个节点分别执行:bash addslots.sh。所有槽位得到分配之后,在看下集群状态:

192.168.100.134:17021> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:3
cluster_current_epoch:2
cluster_my_epoch:1
cluster_stats_messages_sent:4193
cluster_stats_messages_received:4193

看到集群已经成功,那移除一个槽位看看集群会怎么样:cluster delslots 槽位

192.168.100.134:17021> cluster delslots 0
OK
192.168.100.134:17021> cluster info
cluster_state:fail
cluster_slots_assigned:16383
cluster_slots_ok:16383
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:3
cluster_current_epoch:2
cluster_my_epoch:1
cluster_stats_messages_sent:4482
cluster_stats_messages_received:4482

看到16384个槽位如果没有分配完全,集群是不成功的。 到这里为止,一个简单的Redis Cluster已经搭建完成,这里每个节点都是一个单点,若出现一个节点不可用,会导致整个集群的不可用,如何保证各个节点的高可用呢?这可以对每个主节点再建一个从节点来保证。

添加从节点(集群复制): 复制的原理和单机的Redis复制原理一样,区别是:集群下的从节点也需要运行在cluster模式下,要先添加到集群里面,再做复制。

①:添加从节点到集群中

192.168.100.134:17021> cluster meet 192.168.100.134 17022
OK
192.168.100.134:17021> cluster meet 192.168.100.135 17022
OK
192.168.100.134:17021> cluster meet 192.168.100.136 17022
OK
192.168.100.134:17021> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6     #当前集群下的所有节点,包括主从节点
cluster_size:3            #当前集群下的有槽位分配的节点,即主节点
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_sent:13438
cluster_stats_messages_received:13438

②:创建从节点 cluster replicate node_id ,通过cluster nodes得到node_id,需要在要成为的从节点的Redis(17022)上执行。

192.168.100.134:17022> cluster nodes #查看节点信息
7438368ca8f8a27fdf2da52940bb50098a78c6fc 192.168.100.136:17022 master - 0 1488255023528 5 connected
e1b78bb74970d0353832b2913e9b35eba74a2a1a 192.168.100.134:17022 myself,master - 0 0 0 connected
05e72d06edec6a920dd91b050c7a315937fddb66 192.168.100.136:17021 master - 0 1488255022526 2 connected 10923-16383
b461a30fde28409c38ee6c32db1cd267a6cfd125 192.168.100.135:17021 master - 0 1488255026533 3 connected 5462-10922
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 master - 0 1488255025531 1 connected 0-5461
2b8b518324de0990ca587b47f6316e5f07b1df59 192.168.100.135:17022 master - 0 1488255024530 4 connected

#成为135:17021的从节点
192.168.100.134:17022> cluster replicate b461a30fde28409c38ee6c32db1cd267a6cfd125
OK

处理其他2个节点:

#成为136:17021的从节点
192.168.100.135:17022> cluster replicate 05e72d06edec6a920dd91b050c7a315937fddb66
OK
#成为134:17021的从节点
192.168.100.136:17022> cluster replicate 11f9169577352c33d85ad0d1ca5f5bf0deba3209
OK

查看节点状态:cluster nodes

2b8b518324de0990ca587b47f6316e5f07b1df59 192.168.100.135:17022 slave 05e72d06edec6a920dd91b050c7a315937fddb66 0 1488255859347 4 connected
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 myself,master - 0 0 1 connected 0-5461
05e72d06edec6a920dd91b050c7a315937fddb66 192.168.100.136:17021 master - 0 1488255860348 2 connected 10923-16383
e1b78bb74970d0353832b2913e9b35eba74a2a1a 192.168.100.134:17022 slave b461a30fde28409c38ee6c32db1cd267a6cfd125 0 1488255858344 3 connected
7438368ca8f8a27fdf2da52940bb50098a78c6fc 192.168.100.136:17022 slave 11f9169577352c33d85ad0d1ca5f5bf0deba3209 0 1488255856341 5 connected
b461a30fde28409c38ee6c32db1cd267a6cfd125 192.168.100.135:17021 master - 0 1488255857343 3 connected 5462-10922

可以通过查看slave对应的node_id找出它的master节点,如以上操作遇到问题可以查看/var/log/redis/目录下的日志。到此Redis Cluster分片、高可用部署完成,接着继续说明一下集群的相关管理命令。

管理:cluster xxx

上面已经介绍了一部分Cluster相关的命令,现在对所有的命令所以下说明。

CLUSTER info:打印集群的信息。
CLUSTER nodes:列出集群当前已知的所有节点(node)的相关信息。
CLUSTER meet <ip> <port>:将ip和port所指定的节点添加到集群当中。
CLUSTER addslots <slot> [slot ...]:将一个或多个槽(slot)指派(assign)给当前节点。
CLUSTER delslots <slot> [slot ...]:移除一个或多个槽对当前节点的指派。
CLUSTER slots:列出槽位、节点信息。
CLUSTER slaves <node_id>:列出指定节点下面的从节点信息。
CLUSTER replicate <node_id>:将当前节点设置为指定节点的从节点。
CLUSTER saveconfig:手动执行命令保存保存集群的配置文件,集群默认在配置修改的时候会自动保存配置文件。
CLUSTER keyslot <key>:列出key被放置在哪个槽上。
CLUSTER flushslots:移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
CLUSTER countkeysinslot <slot>:返回槽目前包含的键值对数量。
CLUSTER getkeysinslot <slot> <count>:返回count个槽中的键。

CLUSTER setslot <slot> node <node_id> 将槽指派给指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽,然后再进行指派。  
CLUSTER setslot <slot> migrating <node_id> 将本节点的槽迁移到指定的节点中。  
CLUSTER setslot <slot> importing <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。  
CLUSTER setslot <slot> stable 取消对槽 slot 的导入(import)或者迁移(migrate)。 

CLUSTER failover:手动进行故障转移。
CLUSTER forget <node_id>:从集群中移除指定的节点,这样就无法完成握手,过期时为60s,60s后两节点又会继续完成握手。
CLUSTER reset [HARD|SOFT]:重置集群信息,soft是清空其他节点的信息,但不修改自己的id,hard还会修改自己的id,不传该参数则使用soft方式。

CLUSTER count-failure-reports <node_id>:列出某个节点的故障报告的长度。
CLUSTER SET-CONFIG-EPOCH:设置节点epoch,只有在节点加入集群前才能设置。

为了更好的展示上面命令,先为这个新集群插入一些数据:通过脚本插入:

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import redis
import time
import random
import sys


from rediscluster import StrictRedisCluster

redis_nodes =  [{'host':'192.168.100.134','port':7021},
                {'host':'192.168.100.135','port':7021},
                {'host':'192.168.100.136','port':7021},
                {'host':'192.168.100.134','port':7022},
                {'host':'192.168.100.135','port':7022},
                {'host':'192.168.100.136','port':7022}
                ]

try:
    r = StrictRedisCluster(startup_nodes=redis_nodes,password='dxy')
except Exception,e:
    print "Connect Error!"
    sys.exit()

#使得一个主从节点全部挂了,其他节点也支持数据处理
r.config_set('cluster-require-full-coverage','yes')

max_long = 9223372036854775807
set_index = max_long
post_index = max_long
count = 0
num_sets = 300000
set_size = 1

for i in xrange(0, num_sets):
    for j in xrange(0, set_size):
        r.zadd("%s" % (set_index), time.time() * (random.random() + 1),post_index)
        post_index = max_long - random.randint(1, 10000000000)
    set_index -= 1

这里说明一下上面没有介绍过的管理命令:

①:cluster slots 列出槽位和对应节点的信息

192.168.100.134:17021> cluster slots
1) 1) (integer) 0
   2) (integer) 5461
   3) 1) "192.168.100.134"
      2) (integer) 17021
      3) "11f9169577352c33d85ad0d1ca5f5bf0deba3209"
   4) 1) "192.168.100.136"
      2) (integer) 17022
      3) "7438368ca8f8a27fdf2da52940bb50098a78c6fc"
2) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "192.168.100.136"
      2) (integer) 17021
      3) "05e72d06edec6a920dd91b050c7a315937fddb66"
   4) 1) "192.168.100.135"
      2) (integer) 17022
      3) "2b8b518324de0990ca587b47f6316e5f07b1df59"
3) 1) (integer) 5462
   2) (integer) 10922
   3) 1) "192.168.100.135"
      2) (integer) 17021
      3) "b461a30fde28409c38ee6c32db1cd267a6cfd125"
   4) 1) "192.168.100.134"
      2) (integer) 17022
      3) "e1b78bb74970d0353832b2913e9b35eba74a2a1a"

②:cluster slaves:列出指定节点的从节点

192.168.100.134:17021> cluster slaves 11f9169577352c33d85ad0d1ca5f5bf0deba3209
1) "7438368ca8f8a27fdf2da52940bb50098a78c6fc 192.168.100.136:17022 slave 11f9169577352c33d85ad0d1ca5f5bf0deba3209 0 1488274385311 5 connected"

③:cluster keyslot:列出key放在那个槽上

192.168.100.134:17021> cluster keyslot 9223372036854742675
(integer) 10310

④:cluster countkeysinslot:列出指定槽位的key数量

192.168.100.134:17021> cluster countkeysinslot 1
(integer) 19

⑤:cluster getkeysinslot :列出指定槽位中的指定数量的key

192.168.100.134:17021> cluster getkeysinslot 1 3
1) "9223372036854493093"
2) "9223372036854511387"
3) "9223372036854522344"

⑥:cluster setslot …:手动迁移192.168.100.134:17021的0槽位到192.168.100.135:17021

1:首先查看各节点的槽位
192.168.100.134:17021> cluster nodes
2b8b518324de0990ca587b47f6316e5f07b1df59 192.168.100.135:17022 slave 05e72d06edec6a920dd91b050c7a315937fddb66 0 1488295105089 4 connected
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 myself,master - 0 0 7 connected 0-5461
05e72d06edec6a920dd91b050c7a315937fddb66 192.168.100.136:17021 master - 0 1488295107092 2 connected 10923-16383
e1b78bb74970d0353832b2913e9b35eba74a2a1a 192.168.100.134:17022 slave b461a30fde28409c38ee6c32db1cd267a6cfd125 0 1488295106090 6 connected
7438368ca8f8a27fdf2da52940bb50098a78c6fc 192.168.100.136:17022 slave 11f9169577352c33d85ad0d1ca5f5bf0deba3209 0 1488295104086 7 connected
b461a30fde28409c38ee6c32db1cd267a6cfd125 192.168.100.135:17021 master - 0 1488295094073 6 connected 5462-10922

2:查看要迁移槽位的key
192.168.100.134:17021> cluster getkeysinslot 0 100
1) "9223372012094975807"
2) "9223372031034975807"

3:到目标节点执行导入操作
192.168.100.135:17021> cluster setslot 0 importing 11f9169577352c33d85ad0d1ca5f5bf0deba3209
OK
192.168.100.135:17021> cluster nodes
...
b461a30fde28409c38ee6c32db1cd267a6cfd125 192.168.100.135:17021 myself,master - 0 0 6 connected 5462-10922 [0-<-11f9169577352c33d85ad0d1ca5f5bf0deba3209]
...

4:到源节点进行迁移操作
192.168.100.134:17021> cluster setslot 0 migrating b461a30fde28409c38ee6c32db1cd267a6cfd125
OK
192.168.100.134:17021> cluster nodes
...
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 myself,master - 0 0 7 connected 0-5461 [0->-b461a30fde28409c38ee6c32db1cd267a6cfd125]
...

5:在源节点迁移槽位中的key到目标节点:MIGRATE host port key destination-db timeout [COPY] [REPLACE]
192.168.100.134:17021> migrate 192.168.100.135 17021 9223372031034975807 0 5000 replace
OK
192.168.100.134:17021> migrate 192.168.100.135 17021 9223372012094975807 0 5000 replace
OK
192.168.100.134:17021> cluster getkeysinslot 0 100     #key迁移完之后,才能进行下一步
(empty list or set)

6:最后设置槽位到指定节点,命令将会广播给集群其他节点,已经将Slot转移到目标节点
192.168.100.135:17021> cluster setslot 0 node b461a30fde28409c38ee6c32db1cd267a6cfd125
OK
192.168.100.134:17021> cluster setslot 0 node b461a30fde28409c38ee6c32db1cd267a6cfd125
OK

7:验证是否迁移成功:
192.168.100.134:17021> cluster nodes
...
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 myself,master - 0 0 9 connected 1-5461 #变了
...
b461a30fde28409c38ee6c32db1cd267a6cfd125 192.168.100.135:17021 master - 0 1488300965322 10 connected 0 5462-10922

查看槽位信息:
192.168.100.134:17021> cluster slots
1) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "192.168.100.136"
      2) (integer) 17021
      3) "05e72d06edec6a920dd91b050c7a315937fddb66"
2) 1) (integer) 1
   2) (integer) 5461
   3) 1) "192.168.100.134"
      2) (integer) 17021
      3) "11f9169577352c33d85ad0d1ca5f5bf0deba3209"
3) 1) (integer) 0
   2) (integer) 0
   3) 1) "192.168.100.135"
      2) (integer) 17021
      3) "b461a30fde28409c38ee6c32db1cd267a6cfd125"
4) 1) (integer) 5462
   2) (integer) 10922
   3) 1) "192.168.100.135"
      2) (integer) 17021
      3) "b461a30fde28409c38ee6c32db1cd267a6cfd125"

查看数据是否迁移成功:
192.168.100.134:17021> cluster getkeysinslot 0 100
(empty list or set)
192.168.100.135:17021> cluster getkeysinslot 0 100
1) "9223372012094975807"
2) "9223372031034975807"

对于大量slot要迁移,而且slot里也有大量的key的话,可以按照上面的步骤写个脚本处理,或则用后面脚本部署里介绍的处理。

大致的迁移slot的步骤如下:

1,在目标节点上声明将从源节点上迁入Slot CLUSTER SETSLOT <slot> IMPORTING <source_node_id>
2,在源节点上声明将往目标节点迁出Slot CLUSTER SETSLOT <slot> migrating <target_node_id>
3,批量从源节点获取KEY CLUSTER GETKEYSINSLOT <slot> <count>
4,将获取的Key迁移到目标节点 MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>
重复步骤3,4直到所有数据迁移完毕,MIGRATE命令会将所有的指定的key通过RESTORE key ttl serialized-value REPLACE迁移给target
5,分别向双方节点发送 CLUSTER SETSLOT <slot> NODE <target_node_id>,该命令将会广播给集群其他节点,取消importing和migrating。
6,等待集群状态变为OK CLUSTER INFO 中的 cluster_state = ok

注意:这里在操作migrate的时候,若各节点有认证,执行的时候会出现:

(error) ERR Target instance replied with error: NOAUTH Authentication required.

若确定执行的迁移,本文中是把所有节点的masterauth和requirepass注释掉之后进行的,等进行完之后再开启认证。

⑦:cluster forget:从集群中移除指定的节点,这样就无法完成握手,过期时为60s,60s后两节点又会继续完成握手。

192.168.100.134:17021> cluster nodes
05e72d06edec6a920dd91b050c7a315937fddb66 192.168.100.136:17021 master - 0 1488302330582 2 connected 10923-16383
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 myself,master - 0 0 9 connected 1-5461
b461a30fde28409c38ee6c32db1cd267a6cfd125 192.168.100.135:17021 master - 0 1488302328576 10 connected 0 5462-10922
...

192.168.100.134:17021> cluster forget 05e72d06edec6a920dd91b050c7a315937fddb66
OK
192.168.100.134:17021> cluster nodes
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 myself,master - 0 0 9 connected 1-5461
b461a30fde28409c38ee6c32db1cd267a6cfd125 192.168.100.135:17021 master - 0 1488302376718 10 connected 0 5462-10922
...

一分钟之后:
192.168.100.134:17021> cluster nodes
05e72d06edec6a920dd91b050c7a315937fddb66 192.168.100.136:17021 master - 0 1488302490107 2 connected 10923-16383
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 myself,master - 0 0 9 connected 1-5461
b461a30fde28409c38ee6c32db1cd267a6cfd125 192.168.100.135:17021 master - 0 1488302492115 10 connected 0 5462-10922

⑧:cluster failover:手动进行故障转移,在下一节会详解。需要注意的是在需要故障转移的节点上执行,必须在slave节点上执行,否则报错:

(error) ERR You should send CLUSTER FAILOVER to a slave

⑨:cluster flushslots:需要在没有key的节点执行,移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点,该节点所有数据丢失。

192.168.100.136:17022> cluster nodes
05e72d06edec6a920dd91b050c7a315937fddb66 192.168.100.136:17021 master - 0 1488255398859 2 connected 10923-16383
...

192.168.100.136:17021> cluster flushslots
OK

192.168.100.136:17021> cluster nodes
05e72d06edec6a920dd91b050c7a315937fddb66 192.168.100.136:17021 myself,master - 0 0 2 connected
...

⑩:cluster reset :需要在没有key的节点执行,重置集群信息。

192.168.100.134:17021> cluster reset
OK
192.168.100.134:17021> cluster nodes
11f9169577352c33d85ad0d1ca5f5bf0deba3209 192.168.100.134:17021 myself,master - 0 0 9 connected

脚本部署(redis-trib.rb)

Redis Cluster有一套管理脚本,如:创建集群、迁移节点、增删槽位等,这些脚本都存放在源码包里,都是用ruby编写的。现在测试用下脚本完成集群的部署。

①:按照需求创建Redis实例,6个实例(3主3从)。

②:安全需要ruby模块:

apt-get install ruby
gem install redis

③:脚本redis-trib.rb(/usr/local/src/redis-3.2.8/src)

./redis-trib.rb help
Usage: redis-trib <command> <options> <arguments ...>

#创建集群
create          host1:port1 ... hostN:portN  
                  --replicas <arg> #带上该参数表示是否有从,arg表示从的数量
#检查集群
check           host:port
#查看集群信息
info            host:port
#修复集群
fix             host:port
                  --timeout <arg>
#在线迁移slot  
reshard         host:port       #个是必传参数,用来从一个节点获取整个集群信息,相当于获取集群信息的入口
                  --from <arg>  #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id,还可以直接传递--from all,这样源节点就是集群的所有节点,不传递该参数的话,则会在迁移过程中提示用户输入
                  --to <arg>    #slot需要迁移的目的节点的node id,目的节点只能填写一个,不传递该参数的话,则会在迁移过程中提示用户输入。
                  --slots <arg> #需要迁移的slot数量,不传递该参数的话,则会在迁移过程中提示用户输入。
                  --yes         #设置该参数,可以在打印执行reshard计划的时候,提示用户输入yes确认后再执行reshard
                  --timeout <arg>  #设置migrate命令的超时时间。
                  --pipeline <arg> #定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10。
#平衡集群节点slot数量  
rebalance       host:port
                  --weight <arg>
                  --auto-weights
                  --use-empty-masters
                  --timeout <arg>
                  --simulate
                  --pipeline <arg>
                  --threshold <arg>
#将新节点加入集群 
add-node        new_host:new_port existing_host:existing_port
                  --slave
                  --master-id <arg>
#从集群中删除节点
del-node        host:port node_id
#设置集群节点间心跳连接的超时时间
set-timeout     host:port milliseconds
#在集群全部节点上执行命令
call            host:port command arg arg .. arg
#将外部redis数据导入集群
import          host:port
                  --from <arg>
                  --copy
                  --replace
#帮助
help            (show this help)

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

1)创建集群 cretate :6个节点,每个节点一个从库,这里有个问题是不能指定那个从库属于哪个主库,不过可以先添加3个主库,通过新增节点(add-node)来添加从库到指定主库。

./redis-trib.rb create --replicas 1 192.168.100.134:17021 192.168.100.135:17021 192.168.100.136:17021 192.168.100.134:17022 192.168.100.135:17022 192.168.100.136:17022
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.100.134:7021
192.168.100.135:7021
192.168.100.136:7021
Adding replica 192.168.100.135:7022 to 192.168.100.134:7021
Adding replica 192.168.100.134:7022 to 192.168.100.135:7021
Adding replica 192.168.100.136:7022 to 192.168.100.136:7021
M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:7021
   slots:0-5460 (5461 slots) master
M: 51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:7021
   slots:5461-10922 (5462 slots) master
M: 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:7021
   slots:10923-16383 (5461 slots) master
S: 5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:7022
   replicates 51bf103f7cf6b5ede6e009ce489fdeec14961be8
S: 77d02fef656265c9c421fef425527c510e4cfcb8 192.168.100.135:7022
   replicates 7fa64d250b595d8ac21a42477af5ac8c07c35d83
S: 140c72a443eb1c7b87b9cdd06b7f71cd583b2e1d 192.168.100.136:7022
   replicates 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join..
>>> Performing Cluster Check (using node 192.168.100.134:7021)
M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:7021
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
S: 77d02fef656265c9c421fef425527c510e4cfcb8 192.168.100.135:7022
   slots: (0 slots) slave
   replicates 7fa64d250b595d8ac21a42477af5ac8c07c35d83
S: 5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:7022
   slots: (0 slots) slave
   replicates 51bf103f7cf6b5ede6e009ce489fdeec14961be8
M: 51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:7021
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
M: 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:7021
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
S: 140c72a443eb1c7b87b9cdd06b7f71cd583b2e1d 192.168.100.136:7022
   slots: (0 slots) slave
   replicates 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

2)测试集群 check ip:port:测试集群是否分配完了slot

./redis-trib.rb check 192.168.100.134:17021
>>> Performing Cluster Check (using node 192.168.100.134:7021)
M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:7021
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
S: 77d02fef656265c9c421fef425527c510e4cfcb8 192.168.100.135:7022
   slots: (0 slots) slave
   replicates 7fa64d250b595d8ac21a42477af5ac8c07c35d83
S: 5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:7022
   slots: (0 slots) slave
   replicates 51bf103f7cf6b5ede6e009ce489fdeec14961be8
M: 51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:7021
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
M: 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:7021
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
S: 140c72a443eb1c7b87b9cdd06b7f71cd583b2e1d 192.168.100.136:7022
   slots: (0 slots) slave
   replicates 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

3)查看集群信息 info ip:port:查看集群信息:包括slot、slave、和key的数量分布

./redis-trib.rb info 192.168.100.134:17021
192.168.100.134:7021 (7fa64d25...) -> 58086 keys | 5461 slots | 1 slaves.
192.168.100.135:7021 (51bf103f...) -> 58148 keys | 5462 slots | 1 slaves.
192.168.100.136:7021 (0191a8b5...) -> 58051 keys | 5461 slots | 1 slaves.
[OK] 174285 keys in 3 masters.
10.64 keys per slot on average.

4)平衡节点的slot数量 rebalance ip:port:平均各个节点的slot数量

./redis-trib.rb rebalance 192.168.100.134:17021

流程:

1、load_cluster_info_from_node方法先加载集群信息。
2、计算每个master的权重,根据参数--weight <arg>,为每个设置的节点分配权重,没有设置的节点,则权重默认为1。
3、根据每个master的权重,以及总的权重,计算自己期望被分配多少个slot。计算的方式为:总slot数量 * (自己的权重 / 总权重)。
4、计算每个master期望分配的slot是否超过设置的阈值,即--threshold <arg>设置的阈值或者默认的阈值。计算的方式为:先计算期望移动节点的阈值,算法为:(100-(100.0*expected/n.slots.length)).abs,如果计算出的阈值没有超出设置阈值,则不需要为该节点移动slot。只要有一个master的移动节点超过阈值,就会触发rebalance操作。
5、如果触发了rebalance操作。那么就开始执行rebalance操作,先将每个节点当前分配的slots数量减去期望分配的slot数量获得balance值。将每个节点的balance从小到大进行排序获得sn数组。
6、用dst_idx和src_idx游标分别从sn数组的头部和尾部开始遍历。目的是为了把尾部节点的slot分配给头部节点。sn数组保存的balance列表排序后,负数在前面,正数在后面。负数表示需要有slot迁入,所以使用dst_idx游标,正数表示需要有slot迁出,所以使用src_idx游标。理论上sn数组各节点的balance值加起来应该为0,不过由于在计算期望分配的slot的时候只是使用直接取整的方式,所以可能出现balance值之和不为0的情况,balance值之和不为0即为节点不平衡的slot数量,由于slot总数有16384个,不平衡数量相对于总数,基数很小,所以对rebalance流程影响不大。

5)删除集群节点 del-node ip:port :只能删除没有分配slot的节点,从集群中删出之后直接关闭实例

./redis-trib.rb del-node 192.168.100.135:17022 77d02fef656265c9c421fef425527c510e4cfcb8
#删除成功
>>> Removing node 77d02fef656265c9c421fef425527c510e4cfcb8 from cluster 192.168.100.135:7022
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

#删除失败
>>> Removing node 51bf103f7cf6b5ede6e009ce489fdeec14961be8 from cluster 192.168.100.135:7021
[ERR] Node 192.168.100.135:7021 is not empty! Reshard data away and try again.

流程:

1、通过load_cluster_info_from_node方法转载集群信息。
2、根据传入的node id获取节点,如果节点没找到,则直接提示错误并退出。
3、如果节点分配的slot不为空,则直接提示错误并退出。
4、遍历集群内的其他节点,执行cluster forget命令,从每个节点中去除该节点。如果删除的节点是master,而且它有slave的话,这些slave会去复制其他master,调用的方法是get_master_with_least_replicas,与add-node没设置--master-id寻找master的方法一样。
5、然后关闭该节点

6)添加集群节点 add-node :新节点加入集群,节点可以为master,也可以为某个master节点的slave。

添加一个主节点:134:17022 加入到134:17021的集群当中

./redis-trib.rb add-node 192.168.100.134:17022 192.168.100.134:17021
>>> Adding node 192.168.100.134:7022 to cluster 192.168.100.134:7021
>>> Performing Cluster Check (using node 192.168.100.134:7021)
M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:7021
   slots:0-5460 (5461 slots) master
   0 additional replica(s)
M: 51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:7021
   slots:5461-10922 (5462 slots) master
   0 additional replica(s)
M: 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:7021
   slots:10923-16383 (5461 slots) master
   0 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 192.168.100.134:7022 to make it join the cluster.
[OK] New node added correctly.

添加一个从节点:135:17022加入到134:17021的集群当中,并且作为指定的从库

./redis-trib.rb add-node --slave --master-id 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.135:17022 192.168.100.134:17021
>>> Adding node 192.168.100.135:7022 to cluster 192.168.100.134:7021
>>> Performing Cluster Check (using node 192.168.100.134:7021)
M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:7021
   slots:0-5460 (5461 slots) master
   0 additional replica(s)
M: 5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:7022
   slots: (0 slots) master
   0 additional replica(s)
M: 51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:7021
   slots:5461-10922 (5462 slots) master
   0 additional replica(s)
M: 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:7021
   slots:10923-16383 (5461 slots) master
   0 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 192.168.100.135:7022 to make it join the cluster.
Waiting for the cluster to join.
>>> Configure node as replica of 192.168.100.134:7021.
[OK] New node added correctly.

最后集群的信息:

192.168.100.134:17021> cluster nodes
77d02fef656265c9c421fef425527c510e4cfcb8 192.168.100.135:17022 slave 7fa64d250b595d8ac21a42477af5ac8c07c35d83 0 1488346523944 5 connected
5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:17022 master - 0 1488346525949 4 connected
7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:17021 myself,master - 0 0 1 connected 0-5460
51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:17021 master - 0 1488346522942 2 connected 5461-10922
0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:17021 master - 0 1488346524948 3 connected 10923-16383

流程:

1、通过load_cluster_info_from_node方法转载集群信息,check_cluster方法检查集群是否健康。
2、如果设置了--slave,则需要为该节点寻找master节点。设置了--master-id,则以该节点作为新节点的master,如果没有设置--master-id,则调用get_master_with_least_replicas方法,寻找slave数量最少的master节点。如果slave数量一致,则选取load_cluster_info_from_node顺序发现的第一个节点。load_cluster_info_from_node顺序的第一个节点是add-node设置的existing_host:existing_port节点,后面的顺序根据在该节点执行cluster nodes返回的结果返回的节点顺序。
3、连接新的节点并与集群第一个节点握手。
4、如果没设置–slave就直接返回ok,设置了–slave,则需要等待确认新节点加入集群,然后执行cluster replicate命令复制master节点。
5、至此,完成了全部的增加节点的流程。

7)在线迁移slot reshard :在线把集群的一些slot从集群原来slot节点迁移到新的节点,即可以完成集群的在线横向扩容和缩容。

提示执行:迁移134:17021集群

./redis-trib.rb reshard 192.168.100.134:17021
>>> Performing Cluster Check (using node 192.168.100.134:17021)
M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:17021
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
S: 77d02fef656265c9c421fef425527c510e4cfcb8 192.168.100.135:17022
   slots: (0 slots) slave
   replicates 7fa64d250b595d8ac21a42477af5ac8c07c35d83
M: 5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:17022
   slots: (0 slots) master
   0 additional replica(s)
M: 51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:17021
   slots:5461-10922 (5462 slots) master
   0 additional replica(s)
M: 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:17021
   slots:10923-16383 (5461 slots) master
   0 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
#迁移几个槽位?
How many slots do you want to move (from 1 to 16384)? 1 
#迁移到那个node_id?
What is the receiving node ID? 5476787f31fa375fda6bb32676a969c8b8adfbc2
#从哪些node_id迁移?
Please enter all the source node IDs.
#输入all,集群里的所有节点
  Type 'all' to use all the nodes as source nodes for the hash slots.
#输入源节点,回车后再输入done开始迁移
  Type 'done' once you entered all the source nodes IDs.
Source node #1:7fa64d250b595d8ac21a42477af5ac8c07c35d83
Source node #2:done

Ready to move 1 slots.
  Source nodes:
    M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:17021
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
  Destination node:
    M: 5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:17022
   slots: (0 slots) master
   0 additional replica(s)
  Resharding plan:
    Moving slot 0 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
#是否看迁移计划?
Do you want to proceed with the proposed reshard plan (yes/no)? yes 
Moving slot 0 from 192.168.100.134:17021 to 192.168.100.134:17022: ..........

参数执行:从from指定的node迁移10个slots到to指定的节点

./redis-trib.rb reshard --from 7fa64d250b595d8ac21a42477af5ac8c07c35d83 --to 5476787f31fa375fda6bb32676a969c8b8adfbc2 --slots 10 192.168.100.134:17021
>>> Performing Cluster Check (using node 192.168.100.134:17021)
M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:17021
   slots:2-5460 (5459 slots) master
   1 additional replica(s)
S: 77d02fef656265c9c421fef425527c510e4cfcb8 192.168.100.135:17022
   slots: (0 slots) slave
   replicates 7fa64d250b595d8ac21a42477af5ac8c07c35d83
M: 5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:17022
   slots:0-1 (2 slots) master
   0 additional replica(s)
M: 51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:17021
   slots:5461-10922 (5462 slots) master
   0 additional replica(s)
M: 0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:17021
   slots:10923-16383 (5461 slots) master
   0 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

Ready to move 10 slots.
  Source nodes:
    M: 7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:17021
   slots:2-5460 (5459 slots) master
   1 additional replica(s)
  Destination node:
    M: 5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:17022
   slots:0-1 (2 slots) master
   0 additional replica(s)
  Resharding plan:
    Moving slot 2 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 3 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 4 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 5 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 6 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 7 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 8 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 9 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 10 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
    Moving slot 11 from 7fa64d250b595d8ac21a42477af5ac8c07c35d83
Do you want to proceed with the proposed reshard plan (yes/no)? yes
Moving slot 2 from 192.168.100.134:17021 to 192.168.100.134:17022: ....................
Moving slot 3 from 192.168.100.134:17021 to 192.168.100.134:17022: ..........
Moving slot 4 from 192.168.100.134:17021 to 192.168.100.134:17022: ..................
Moving slot 5 from 192.168.100.134:17021 to 192.168.100.134:17022: ..
Moving slot 6 from 192.168.100.134:17021 to 192.168.100.134:17022: ..
Moving slot 7 from 192.168.100.134:17021 to 192.168.100.134:17022: ...............................
Moving slot 8 from 192.168.100.134:17021 to 192.168.100.134:17022: ..........
Moving slot 9 from 192.168.100.134:17021 to 192.168.100.134:17022: ..........................
Moving slot 10 from 192.168.100.134:17021 to 192.168.100.134:17022: ........................................
Moving slot 11 from 192.168.100.134:17021 to 192.168.100.134:17022: ..........

流程:

1、通过load_cluster_info_from_node方法装载集群信息。
2、执行check_cluster方法检查集群是否健康。只有健康的集群才能进行迁移。
3、获取需要迁移的slot数量,用户没传递--slots参数,则提示用户手动输入。
4、获取迁移的目的节点,用户没传递--to参数,则提示用户手动输入。此处会检查目的节点必须为master节点。
5、获取迁移的源节点,用户没传递--from参数,则提示用户手动输入。此处会检查源节点必须为master节点。--from all 的话,源节点就是除了目的节点外的全部master节点。这里为了保证集群slot分配的平均,建议传递--from all。
6、执行compute_reshard_table方法,计算需要迁移的slot数量如何分配到源节点列表,采用的算法是按照节点负责slot数量由多到少排序,计算每个节点需要迁移的slot的方法为:迁移slot数量 * (该源节点负责的slot数量 / 源节点列表负责的slot总数)。这样算出的数量可能不为整数,这里代码用了下面的方式处理:

n = (numslots/source_tot_slots*s.slots.length)
if i == 0
    n = n.ceil
else
    n = n.floor
这样的处理方式会带来最终分配的slot与请求迁移的slot数量不一致,这个BUG已经在github上提给作者,https://github.com/antirez/redis/issues/2990。

7、打印出reshard计划,如果用户没传--yes,就提示用户确认计划。
8、根据reshard计划,一个个slot的迁移到新节点上,迁移使用move_slot方法,该方法被很多命令使用,具体可以参见下面的迁移流程。move_slot方法传递dots为true和pipeline数量。
9、至此,就完成了全部的迁移任务。

迁移后的slots分布:

192.168.100.135:17021> cluster nodes
5476787f31fa375fda6bb32676a969c8b8adfbc2 192.168.100.134:17022 master - 0 1488349695628 7 connected 0-11
7fa64d250b595d8ac21a42477af5ac8c07c35d83 192.168.100.134:17021 master - 0 1488349698634 1 connected 12-5460
51bf103f7cf6b5ede6e009ce489fdeec14961be8 192.168.100.135:17021 myself,master - 0 0 2 connected 5461-10922
77d02fef656265c9c421fef425527c510e4cfcb8 192.168.100.135:17022 slave 7fa64d250b595d8ac21a42477af5ac8c07c35d83 0 1488349697631 1 connected
0191a8b52646fb5c45323ab0c1a1a79dc8f3aea2 192.168.100.136:17021 master - 0 1488349696631 3 connected 10923-16383

新增的节点,slot分布不均匀,可以通过上面说的rebalance进行平衡slot。

这里需要注意的是:要是Redis Server 配置了认证,需要密码登入,这个脚本就不能执行了,脚本执行的Server之间都是无密码。若确定需要登陆,则:可以暂时修改成无认证状态:

192.168.100.134:17022> config set masterauth ""  
OK
192.168.100.134:17022> config set requirepass ""
OK
#正常来讲是没有权限写入的。
#192.168.100.134:17022> config rewrite  

等到处理完毕之后,可以再把密码设置回去。到此,通过脚本部署也介绍完了,通过手动和脚本部署发现在数据迁移的时候服务器都不能设置密码,否则认证失败。在设置了认证的服务器上操作时,需要注意一下。

故障检测和转移

在上面管理中介绍过failover的命令,现在可以用这个命令模拟故障检测转移,当然也可以stop掉Redis Server来实现模拟。进行failover节点必须是slave节点,查看集群里各个节点和slave的信息:

192.168.100.134:17021> cluster nodes
93a030d6f1d1248c1182114c7044b204aa0ee022 192.168.100.136:17021 master - 0 1488378411940 4 connected 10923-16383
b836dc49206ac8895be7a0c4b8ba571dffa1e1c4 192.168.100.135:17022 slave 23c2bb6fc906b55fb59a051d1f9528f5b4bc40d4 0 1488378410938 1 connected
5980546e3b19ff5210057612656681b505723da4 192.168.100.134:17022 slave 93a030d6f1d1248c1182114c7044b204aa0ee022 0 1488378408935 4 connected
23c2bb6fc906b55fb59a051d1f9528f5b4bc40d4 192.168.100.134:17021 myself,master - 0 0 1 connected 0-5461
526d99b679229c8003b0504e27ae7aee4e9c9c3a 192.168.100.135:17021 master - 0 1488378412941 2 connected 5462-10922
39bf42b321a588dcd93efc4b4cc9cb3b496cacb6 192.168.100.136:17022 slave 526d99b679229c8003b0504e27ae7aee4e9c9c3a 0 1488378413942 5 connected
192.168.100.134:17021> cluster slaves 23c2bb6fc906b55fb59a051d1f9528f5b4bc40d4
1) "b836dc49206ac8895be7a0c4b8ba571dffa1e1c4 192.168.100.135:17022 slave 23c2bb6fc906b55fb59a051d1f9528f5b4bc40d4 0 1488378414945 1 connected"

在134:17021上模拟故障,要到该节点的从节点135:17022上执行failover,通过日志看如何进行故障转移

192.168.100.135:17022> cluster failover
OK
192.168.100.135:17022> cluster nodes
39bf42b321a588dcd93efc4b4cc9cb3b496cacb6 192.168.100.136:17022 slave 526d99b679229c8003b0504e27ae7aee4e9c9c3a 0 1488378807681 5 connected
23c2bb6fc906b55fb59a051d1f9528f5b4bc40d4 192.168.100.134:17021 slave b836dc49206ac8895be7a0c4b8ba571dffa1e1c4 0 1488378804675 6 connected
526d99b679229c8003b0504e27ae7aee4e9c9c3a 192.168.100.135:17021 master - 0 1488378806679 2 connected 5462-10922
5980546e3b19ff5210057612656681b505723da4 192.168.100.134:17022 slave 93a030d6f1d1248c1182114c7044b204aa0ee022 0 1488378808682 4 connected
b836dc49206ac8895be7a0c4b8ba571dffa1e1c4 192.168.100.135:17022 myself,master - 0 0 6 connected 0-5461
93a030d6f1d1248c1182114c7044b204aa0ee022 192.168.100.136:17021 master - 0 1488378809684 4 connected 10923-16383

通过上面结果看到从库已经提升变成了主库,而老的主库起来之后变成了从库。在日志里也可以看到这2个节点同步的过程。当然有兴趣的可以模拟一下stop的过程。

整个集群的部署、管理和测试到这里全部结束,下面附上几个生成数据的测试脚本:

①:操作集群(cluster_write_test.py)

#!/usr/bin/python
# -*- encoding: utf-8 -*-
import redis
import time
import random
import sys

from rediscluster import StrictRedisCluster

redis_nodes =  [{'host':'192.168.100.134','port':7021},
                {'host':'192.168.100.135','port':7021},
                {'host':'192.168.100.136','port':7021},
                {'host':'192.168.100.134','port':7022},
                {'host':'192.168.100.135','port':7022},
                {'host':'192.168.100.136','port':7022}
                ]

try:
    r = StrictRedisCluster(startup_nodes=redis_nodes,password='123')
#    r = StrictRedisCluster(startup_nodes=redis_nodes)
except Exception,e:
    print "Connect Error!"
    sys.exit()

#使得一个主从节点全部挂了,其他节点也支持数据处理
r.config_set('cluster-require-full-coverage','yes')

max_long = 9223372036854775807
set_index = max_long
post_index = max_long
num_sets = 300000
set_size = 1

for i in xrange(0, num_sets):
    for j in xrange(0, set_size):
        r.zadd("%s" % (set_index), time.time() * (random.random() + 1),post_index)
        post_index = max_long - random.randint(1, 10000000000)
    set_index -= 100000

②:pipeline操作集群(cluster_write_pipe_test.py)

#!/usr/bin/python
# -*- encoding: utf-8 -*-
import redis
import time
import random

from rediscluster import StrictRedisCluster

redis_nodes =  [{'host':'192.168.100.134','port':7021},
                {'host':'192.168.100.135','port':7021},
                {'host':'192.168.100.136','port':7021},
                {'host':'192.168.100.134','port':7022},
                {'host':'192.168.100.135','port':7022},
                {'host':'192.168.100.136','port':7022}
                ]

try:
    r = StrictRedisCluster(startup_nodes=redis_nodes,password='123')
#    r = StrictRedisCluster(startup_nodes=redis_nodes)
    pipe  = r.pipeline()
except Exception,e:
    print "Connect Error!"
    sys.exit()

max_long = 9223372036854775807
set_index = max_long
post_index = max_long
num_sets = 300000
set_size = 1

for i in xrange(0, num_sets):
    for j in xrange(0, set_size):
        r.zadd("%s" % (set_index), time.time() * (random.random() + 1),post_index)
        post_index = max_long - random.randint(1, 10000000000)
    set_index -= 1

③:操作单例(single_write_test.py)

#!/usr/bin/python
# -*- encoding: utf-8 -*-
import redis
import time
import random


r = redis.Redis(host='192.168.200.24', port=22001, db=0, password='dxy')

max_long = 9223372036854775807
set_index = max_long
post_index = max_long
count = 0

start = time.time()
num_sets = 1000
set_size = 1000
r.flushall()
initial_size = r.dbsize()
initial_info = r.info()

for i in xrange(0, num_sets):
    for j in xrange(0, set_size):
        r.zadd("%s" % (set_index), post_index,time.time() * (random.random() + 1))
        post_index = max_long - random.randint(1, 10000000000)
    set_index -= 1
    count += 1
    if count >= 1000 and count % 1000 == 0:
        print "Keys: %s => %s" % (initial_size, r.dbsize())
        print "Memory: %s => %s" % (initial_info['used_memory_human'],r.info()['used_memory_human'])


final_size = r.dbsize()
final_info = r.info()

print "For %s sets with %s values." % (num_sets, set_size)
print "Keys: %s => %s" % (initial_size, final_size)
print "Memory: %s => %s" % (initial_info['used_memory_human'],final_info['used_memory_human'])
print "Cost Time : %s "%(time.time() - start)
print "request per second : %s" % (1000 * 1000 / (time.time() - start))

④:pipeline操作单例(single_write_pipe_test.py)

#!/usr/bin/python
# -*- encoding: utf-8 -*-
import redis
import time
import random


r     = redis.Redis(host='192.168.200.24', port=22001, db=0, password='dxy')
pipe  = r.pipeline()

max_long = 9223372036854775807
set_index = max_long
post_index = max_long
count = 0

start = time.time()
num_sets = 1000
set_size = 1000
r.flushall()
initial_size = r.dbsize()
initial_info = r.info()

for i in xrange(0, num_sets):
    for j in xrange(0, set_size):
        pipe.zadd("%s" % (set_index), post_index,time.time() * (random.random() + 1))
        post_index = max_long - random.randint(1, 10000000000)
    set_index -= 1
#    if i%30 == 0:
    pipe.execute()

final_size = r.dbsize()
final_info = r.info()

print "For %s sets with %s values." % (num_sets, set_size)
print "Keys: %s => %s" % (initial_size, final_size)
print "Memory: %s => %s" % (initial_info['used_memory_human'],final_info['used_memory_human'])
print "Cost Time : %s "%(time.time() - start)
print "request per second : %s" % (1000 * 1000 / (time.time() - start))

总结

Redis Cluster采用无中心节点方式实现,无需proxy代理,客户端直接与redis集群的每个节点连接,根据同样的hash算法计算出key对应的slot,然后直接在slot对应的Redis上执行命令。从CAP定理来看,Cluster支持了AP(Availability&Partition-Tolerancy),这样让Redis从一个单纯的NoSQL内存数据库变成了分布式NoSQL数据库。

Docker 给运行中的容器设置端口映射的方法

一、概念

Docker 端口映射即映射容器内应用的服务端口到本机宿主机器。

二、实现

当容器中运行一些网络应用,要让外部访问这些应用时,可以通过 -P 或 -p 参数两种方式来指定端口映射。

1. 随机映射

使用 -P 参数时,Docker 会随机映射一个端口到内部容器开放的网络端口,如下开启一个 nginx 服务:

$ docker run -d -P nginx
e93349d539119dc48dc841e117f6388d6afa6a6065b75a5b4aedaf5fb2a051fc
$ 
$ docker ps 
CONTAINER ID    IMAGE        COMMAND         CREATED       STATUS       PORTS          NAMES
e93349d53911    nginx        "nginx -g 'daemon ..."  11 seconds ago   Up 9 seconds    0.0.0.0:32769->80/tcp  zen_kirch

使用 docker ps 看到,本地主机的 32769 端口被映射到了容器的 80 端口,这时我们通过本机浏览器访问 http://localhost:32769 就会出现 nginx 欢迎页面。

2. 指定端口

使用 -p 参数时,可以指定要映射的端口,并且在一个指定的端口上只可以绑定一个容器。支持的格式有:

  • IP:HostPort:ContainerPort
  • IP:ContainerPort
  • HostPort:ContainerPort

下面开启一个 nginx 服务,将本机 8080 端口映射到容器的 80 端口:

$ docker run -d -p 8080:80 nginx
23e725098712d061a1382f33d6fe54da23ae37597a62f8debdd3731b5f9cc4b9
$ 
$ docker ps
CONTAINER ID    IMAGE        COMMAND         CREATED       STATUS       PORTS          NAMES
23e725098712    nginx        "nginx -g 'daemon ..."  8 seconds ago    Up 6 seconds    0.0.0.0:8080->80/tcp  frosty_ptolemy

使用 docker ps 看到,本地主机的 8080 端口被映射到了容器的 80 端口,这时我们通过本机浏览器访问 http://localhost:8080 就会出现 nginx 欢迎页面。

三、查看映射端口

使用 docker port 命令来查看当前映射的端口配置,也可以查看到绑定的地址。命令格式如下:

$ docker port CONTAINER [PRIVATE_PORT[/PROTO]]

容器有自己的内部网络和 IP 地址,可以使用 docker inspect + 容器ID 获取容器的具体信息。

DOCKER 给运行中的容器添加映射端口

方法1

1、获得容器IP

将container_name 换成实际环境中的容器名

docker inspect `container_name` | grep IPAddress

2、iptable转发端口

将容器的8000端口映射到docker主机的8001端口

代码如下:

iptables -t nat -A  DOCKER -p tcp --dport 8001 -j DNAT --to-destination 172.17.0.19:8000

方法2

  • 提交一个运行中的容器为镜像
docker commit containerid foo/live
  • 运行镜像并添加端口
docker run -d -p 8000:80 foo/live /bin/bash

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

关于docker的虚拟网络划分方案

由于一台主机上不可能只跑一个容器,一台主机上的容器也不可能都是一个功能或者一个用户群体,不同用户群体之间信息的性质不同,对信息安全性的要求不同,因此划分虚拟网络的需求的客观的。划分虚拟网络的方法有很多,本文将提出划分docker虚拟网络的一个方案,即使用Docker的macvlan为容器提供桥接网络及跨主机通讯来划分网络。

macvlan的原理是在宿主机物理网卡上虚拟出多个子网卡,通过不同的MAC地址在数据链路层(Data Link Layer)进行网络数据转发的,它是比较新的网络虚拟化技术,需要较新的内核支持(Linux kernel v3.9–3.19 and 4.0+)。

使用mvcvlan

docker network create -d macvlan 
--subnet=172.17.0.0/24 
--gateway=172.17.0.1  
-o parent=eth0 mcv

首先,需要创建一个macvlan网络

然后指定要桥接的网络地址此处填docker0的IP地址,docker0的IP地址可以通过ifconfig进行查询。

网关地址与网络地址应该相互匹配

设置要在宿主机的哪块网卡上建立虚拟子网卡,这里我选择的是eth0,给它编号为mcv,方便之后使用

docker  run --net=mcv --ip=101.132.161.25 -itd alpine /bin/sh
docker  run --net=mcv --ip=101.132.161.67 -itd alpine /bin/sh

运行容器,指定刚建好的macvlan网络,并指定IP地址

之后通过docker attach 命令进入容器内部进行ping命令,这一轮操作就算完成了。

提示:创建第二块子网卡时可以将eth0改成eth0.1,将网桥修改为172.17.0.2

结果展示:

(注:101.132.161.67和101.132.161.25均在mcv网卡下,101.132.161.50在mcv1网卡下)

未分类

未分类

这样虚拟网络划分就完成了。