每个Mater 都可以拥有多个slave.当Master掉线后,redis cluster集群会从多个Slave中选举出来一个新的Matser作为代替,而旧的Master重新上线后变成 Master 的Slave.
Statefulset
Service&depolyment
对于redis,mysql这种有状态的服务,我们使用statefulset方式为首选.我们这边主要就是介绍statefulset这种方式
ps: statefulset 的设计原理模型: 拓扑状态.应用的多个实例之间不是完全对等的关系,这个应用实例的启动必须按照某些顺序启动,比如应用的 主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个Pod删除掉,他们再次被创建出来是也必须严格按 照这个顺序才行,并且,新创建出来的Pod,必须和原来的Pod的网络标识一样,这样原先的访问者才能使用同样 的方法,访问到这个新的Pod 存储状态:应用的多个实例分别绑定了不同的存储数据.对于这些应用实例来说,Pod A第一次读取到的数据,和 隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间Pod A被重新创建过.一个数据库应用的多个 存储实例
无论是Master 还是 slave都作为statefulset的一个副本,通过pv/pvc进行持久化,对外暴露一个service 接受客户端请求
因为k8s上pod是飘忽不定的,所以我们肯定需要用一个共享存储来提供存储,这样不管pod漂移到哪个节点都能访问这个共享的数据卷.我这个地方先使用NFS
来做共享存储,后期可以 选择别的替换
yum -y install nfs-utils rpcbind vim /etc/exports /usr/local/kubernetes/redis/pv1 0.0.0.0/0(rw,all_squash) /usr/local/kubernetes/redis/pv2 0.0.0.0/0(rw,all_squash) /usr/local/kubernetes/redis/pv3 0.0.0.0/0(rw,all_squash) /usr/local/kubernetes/redis/pv4 0.0.0.0/0(rw,all_squash) /usr/local/kubernetes/redis/pv5 0.0.0.0/0(rw,all_squash) /usr/local/kubernetes/redis/pv6 0.0.0.0/0(rw,all_squash) mkdir -p /usr/local/kubernetes/redis/pv{1..6} chmod 777 /usr/local/kubernetes/redis/pv{1..6}
后期我们可以写成域名 通配符
启动服务 systemctl enable nfs systemctl enable rpcbind systemctl start nfs systemctl start rpcbind
创建6个pv 一会供pvc挂载使用
vim pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv1 spec: capacity: storage: 200M #磁盘大小200M accessModes: - ReadWriteMany #多客户可读写 nfs: server: NFS服务器地址 path: "/usr/local/kubernetes/redis/pv1" --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-vp2 spec: capacity: storage: 200M accessModes: - ReadWriteMany nfs: server: NFS服务器地址 path: "/usr/local/kubernetes/redis/pv2" --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv3 spec: capacity: storage: 200M accessModes: - ReadWriteMany nfs: server: NFS服务器地址 path: "/usr/local/kubernetes/redis/pv3" --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv4 spec: capacity: storage: 200M accessModes: - ReadWriteMany nfs: server: NFS服务器地址 path: "/usr/local/kubernetes/redis/pv4" --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv5 spec: capacity: storage: 200M accessModes: - ReadWriteMany nfs: server: NFS服务器地址 path: "/usr/local/kubernetes/redis/pv5" --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv6 spec: capacity: storage: 200M accessModes: - ReadWriteMany nfs: server: NFS服务器地址 path: "/usr/local/kubernetes/redis/pv6"
字段说明:
apiversion: api版本
kind: 这个yaml是生成pv的
metadata: 元数据
spec.capacity: 进行资源限制的
spec.accessmodes: 访问模式(读写模式)
spec.nfs: 这个pv卷名是通过nfs提供的
创建pv
kubectl create -f pv.yaml kubectl get pv #查看创建的pv
因为redis的配置文件里面可能会改变,所以我们使用configmap这种方式给配置文件弄出来,我们后期改的时候就不需要没改次配置文件就从新生成一个docker images包了
appendonly yes #开启Redis的AOF持久化cluster-enabled yes #集群模式打开cluster-config-file /var/lib/redis/nodes.conf #下面说明cluster-node-timeout 5000 #节点超时时间dir /var/lib/redis #AOF持久化文件存在的位置port 6379 #开启的端口
cluster-conf-file: 选项设定了保存节点配置文件的路径,如果这个配置文件不存在,每个节点在启动的时候都为他自身指定了一个新的ID存档到这个文件中,实例会一直使用同一个ID,在集群中保持一个独一无二的(Unique)名字.每个节点都是用ID而不是IP或者端口号来记录其他节点,因为在k8s中,IP地址是不固定的,而这个独一无二的标识符(Identifier)则会在节点的整个生命周期中一直保持不变,我们这个文件里面存放的是节点ID
创建名为redis-conf的Configmap:
kubectl create configmap redis-conf --from-file=redis.conf
查看:
[root@rke ~]# kubectl get cmNAME DATA AGE redis-conf 1 22h [root@rke ~]# kubectl describe cm redis-confName: redis-conf Namespace: default Labels: <none> Annotations: <none> Data ==== redis.conf: ---- appendonly yes cluster-enabled yes cluster-config-file /var/lib/redis/nodes.conf cluster-node-timeout 5000 dir /var/lib/redis port 6379 Events: <none>
Headless service是StatefulSet实现稳定网络标识的基础,我们需要提前创建。准备文件headless-service.yml如下:
apiVersion: v1 kind: Service metadata: name: redis-service labels: app: redis spec: ports: - name: redis-port port: 6379 clusterIP: None selector: app: redis appCluster: redis-cluster
创建:
kubectl create -f headless-service.yml
查看:
[root@k8s-node1 redis]# kubectl get svc redis-serviceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE redis-service ClusterIP None <none> 6379/TCP 53s
可以看到,服务名称为redis-service,其CLUSTER-IP为None,表示这是一个“无头”服务。
这是本文的核心内容,创建redis.yaml
文件
[root@rke ~]# cat /home/docker/redis/redis.yml apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: redis-app spec: serviceName: "redis-service" replicas: 6 template: metadata: labels: app: redis appCluster: redis-cluster spec: terminationGracePeriodSeconds: 20 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - redis topologyKey: kubernetes.io/hostname containers: - name: redis image: "redis" command: - "redis-server" #redis启动命令 args: - "/etc/redis/redis.conf" #redis-server后面跟的参数,换行代表空格 - "--protected-mode" #允许外网访问 - "no" # command: redis-server /etc/redis/redis.conf --protected-mode no resources: #资源 requests: #请求的资源 cpu: "100m" #m代表千分之,相当于0.1 个cpu资源 memory: "100Mi" #内存100m大小 ports: - name: redis containerPort: 6379 protocol: "TCP" - name: cluster containerPort: 16379 protocol: "TCP" volumeMounts: - name: "redis-conf" #挂载configmap生成的文件 mountPath: "/etc/redis" #挂载到哪个路径下 - name: "redis-data" #挂载持久卷的路径 mountPath: "/var/lib/redis" volumes: - name: "redis-conf" #引用configMap卷 configMap: name: "redis-conf" items: - key: "redis.conf" #创建configMap指定的名称 path: "redis.conf" #里面的那个文件--from-file参数后面的文件 volumeClaimTemplates: #进行pvc持久卷声明, - metadata: name: redis-data spec: accessModes: - ReadWriteMany resources: requests: storage: 200M
PodAntiAffinity
:表示反亲和性,其决定了某个pod不可以和哪些Pod部署在同一拓扑域,可以用于将一个服务的POD分散在不同的主机或者拓扑域中,提高服务本身的稳定性。matchExpressions
:规定了Redis Pod要尽量不要调度到包含app为redis的Node上,也即是说已经存在Redis的Node上尽量不要再分配Redis Pod了.
另外,根据StatefulSet的规则,我们生成的Redis的6个Pod的hostname会被依次命名为$(statefulset名称)-$(序号),如下图所示:
[root@rke ~]# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE redis-app-0 1/1 Running 0 40m 10.42.2.17 192.168.1.21 <none> redis-app-1 1/1 Running 0 40m 10.42.0.15 192.168.1.114 <none> redis-app-2 1/1 Running 0 40m 10.42.1.13 192.168.1.20 <none> redis-app-3 1/1 Running 0 40m 10.42.2.18 192.168.1.21 <none> redis-app-4 1/1 Running 0 40m 10.42.0.16 192.168.1.114 <none> redis-app-5 1/1 Running 0 40m 10.42.1.14 192.168.1.20 <none>
如上,可以看到这些Pods在部署时是以{0..N-1}的顺序依次创建的。注意,直到redis-app-0状态启动后达到Running状态之后,redis-app-1 才开始启动。
同时,每个Pod都会得到集群内的一个DNS域名,格式为$(podname).$(service name).$(namespace).svc.cluster.local
,也即是:
redis-app-0.redis-service.default.svc.cluster.local redis-app-1.redis-service.default.svc.cluster.local ...以此类推...
在K8S集群内部,这些Pod就可以利用该域名互相通信。我们可以使用busybox镜像的nslookup检验这些域名:
[root@k8s-node1 ~]# kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh If you don't see a command prompt, try pressing enter. / # nslookup redis-app-1.redis-service.default.svc.cluster.local Server: 10.43.0.10 Address: 10.43.0.10:53 Name: redis-app-1.redis-service.default.svc.cluster.local Address: 10.42.0.15 *** Can't find redis-app-1.redis-service.default.svc.cluster.local: No answer / # nslookup redis-app-0.redis-service.default.svc.cluster.localServer: 10.43.0.10 Address: 10.43.0.10:53 Name: redis-app-0.redis-service.default.svc.cluster.local Address: 10.42.2.17
可以看到, redis-app-0的IP为10.42.2.17。当然,若Redis Pod迁移或是重启(我们可以手动删除掉一个Redis Pod来测试),则IP是会改变的,但Pod的域名、SRV records、A record都不会改变。
另外可以发现,我们之前创建的pv都被成功绑定了:
[root@k8s-node1 ~]# kubectl get pvNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv1 200M RWX Retain Bound default/redis-data-redis-app-2 1h nfs-pv2 200M RWX Retain Bound default/redis-data-redis-app-3 1h nfs-pv3 200M RWX Retain Bound default/redis-data-redis-app-4 1h nfs-pv4 200M RWX Retain Bound default/redis-data-redis-app-5 1h nfs-pv5 200M RWX Retain Bound default/redis-data-redis-app-0 1h nfs-pv6 200M RWX Retain Bound default/redis-data-redis-app-1 1h
创建好6个Redis Pod后,我们还需要利用常用的Redis-tribe工具进行集群的初始化。
创建centos容器
由于Redis集群必须在所有节点启动后才能进行初始化,而如果将初始化逻辑写入Statefulset中,则是一件非常复杂而且低效的行为。这里,本人不得不称赞一下原项目作者的思路,值得学习。也就是说,我们可以在K8S上创建一个额外的容器,专门用于进行K8S集群内部某些服务的管理控制。
这里,我们专门启动一个Ubuntu的容器,可以在该容器中安装Redis-tribe,进而初始化Redis集群,执行:
kubectl run -i --tty centos --image=centos --restart=Never /bin/bash
成功后,我们可以进入centos容器中,原项目要求执行如下命令安装基本的软件环境:
cat >> /etc/yum.repo.d/epel.repo<<'EOF'[epel] name=Extra Packages for Enterprise Linux 7 - $basearchbaseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/7/$basearch#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearchfailovermethod=priority enabled=1 gpgcheck=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 EOF
初始化redis集群
首先,我们需要安装redis-trib(redis集群命令行工具):
yum -y install redis-trib.noarch bind-utils-9.9.4-72.el7.x86_64
然后创建一主一从的集群节点信息:
redis-trib create --replicas 1 \ `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 \ `dig +short redis-app-1.redis-service.default.svc.cluster.local`:6379 \ `dig +short redis-app-2.redis-service.default.svc.cluster.local`:6379 \ `dig +short redis-app-3.redis-service.default.svc.cluster.local`:6379 \ `dig +short redis-app-4.redis-service.default.svc.cluster.local`:6379 \ `dig +short redis-app-5.redis-service.default.svc.cluster.local`:6379#create: 创建一个新的集群#--replicas 1 : 创建的集群中每个主节点分配一个从节点,达到3主3从#后面跟的就是redis实例所在的位置
如上,命令dig +short redis-app-0.redis-service.default.svc.cluster.local
用于将Pod的域名转化为IP,这是因为redis-trib
不支持域名来创建集群。
执行完成后redis-trib
会打印一份预配置文件给你查看,如果没问题输入yes,redis-trib
就会把这份配置文件应用到集群中
>>> Creating cluster >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 10.42.2.17:6379 10.42.0.15:6379 10.42.1.13:6379 Adding replica 10.42.2.18:6379 to 10.42.2.17:6379 Adding replica 10.42.0.16:6379 to 10.42.0.15:6379 Adding replica 10.42.1.14:6379 to 10.42.1.13:6379 M: 4676f8913cdcd1e256db432531c80591ae6c5fc3 10.42.2.17:6379 slots:0-5460 (5461 slots) master M: 505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379 slots:5461-10922 (5462 slots) master M: 589b4f4f908a04f56d2ab9cd6fd0fd25ea14bb8f 10.42.1.13:6379 slots:10923-16383 (5461 slots) master S: 366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379 replicates 4676f8913cdcd1e256db432531c80591ae6c5fc3 S: cee3a27cc27635da54d94f16f6375cd4acfe6c30 10.42.0.16:6379 replicates 505f3e126882c0c5115885e54f9b361bc7e74b97 S: e9f1f704ff7c8f060d6b39e23be9cd8e55cb2e46 10.42.1.14:6379 replicates 589b4f4f908a04f56d2ab9cd6fd0fd25ea14bb8f 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 10.42.2.17:6379) M: 4676f8913cdcd1e256db432531c80591ae6c5fc3 10.42.2.17:6379 slots:0-5460 (5461 slots) master 1 additional replica(s) M: 589b4f4f908a04f56d2ab9cd6fd0fd25ea14bb8f 10.42.1.13:6379@16379 slots:10923-16383 (5461 slots) master 1 additional replica(s) S: e9f1f704ff7c8f060d6b39e23be9cd8e55cb2e46 10.42.1.14:6379@16379 slots: (0 slots) slave replicates 589b4f4f908a04f56d2ab9cd6fd0fd25ea14bb8f S: 366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379@16379 slots: (0 slots) slave replicates 4676f8913cdcd1e256db432531c80591ae6c5fc3 M: 505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379@16379 slots:5461-10922 (5462 slots) master 1 additional replica(s) S: cee3a27cc27635da54d94f16f6375cd4acfe6c30 10.42.0.16:6379@16379 slots: (0 slots) slave replicates 505f3e126882c0c5115885e54f9b361bc7e74b97 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
最后一句表示集群中的16384
个槽都有至少一个主节点在处理, 集群运作正常.
至此,我们的Redis集群就真正创建完毕了,连到任意一个Redis Pod中检验一下:
root@k8s-node1 ~]# kubectl exec -it redis-app-2 /bin/bashroot@redis-app-2:/data# /usr/local/bin/redis-cli -c127.0.0.1:6379> 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:6 cluster_my_epoch:1 cluster_stats_messages_ping_sent:186 cluster_stats_messages_pong_sent:199 cluster_stats_messages_sent:385 cluster_stats_messages_ping_received:194 cluster_stats_messages_pong_received:186 cluster_stats_messages_meet_received:5 cluster_stats_messages_received:385 127.0.0.1:6379> cluster nodes 589b4f4f908a04f56d2ab9cd6fd0fd25ea14bb8f 10.42.1.13:6379@16379 master - 0 1550555011000 3 connected 10923-16383 e9f1f704ff7c8f060d6b39e23be9cd8e55cb2e46 10.42.1.14:6379@16379 slave 589b4f4f908a04f56d2ab9cd6fd0fd25ea14bb8f 0 1550555011512 6 connected 366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379@16379 slave 4676f8913cdcd1e256db432531c80591ae6c5fc3 0 1550555010507 4 connected 505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379@16379 master - 0 1550555011000 2 connected 5461-10922 cee3a27cc27635da54d94f16f6375cd4acfe6c30 10.42.0.16:6379@16379 slave 505f3e126882c0c5115885e54f9b361bc7e74b97 0 1550555011713 5 connected 4676f8913cdcd1e256db432531c80591ae6c5fc3 10.42.2.17:6379@16379 myself,master - 0 1550555010000 1 connected 0-5460
另外,还可以在NFS上查看Redis挂载的数据:
[root@rke ~]# tree /usr/local/kubernetes/redis//usr/local/kubernetes/redis/ ├── pv1 │ ├── appendonly.aof │ ├── dump.rdb │ └── nodes.conf ├── pv2 │ ├── appendonly.aof │ ├── dump.rdb │ └── nodes.conf ├── pv3 │ ├── appendonly.aof │ ├── dump.rdb │ └── nodes.conf ├── pv4 │ ├── appendonly.aof │ ├── dump.rdb │ └── nodes.conf ├── pv5 │ ├── appendonly.aof │ ├── dump.rdb │ └── nodes.conf └── pv6 ├── appendonly.aof ├── dump.rdb └── nodes.conf 6 directories, 18 files
前面我们创建了用于实现statefulset的headless service,但该service没有cluster IP,因此不能用于外界访问.所以我们还需要创建一个service,专用于为Redis集群提供访问和负载均衡:
piVersion: v1 kind: Service metadata: name: redis-access-service labels: app: redis spec: ports: - name: redis-port protocol: "TCP" port: 6379 targetPort: 6379 selector: app: redis appCluster: redis-cluster
如上,该Service名称为 redis-access-service
,在K8S集群中暴露6379端口,并且会对labels name
为app: redis
或appCluster: redis-cluster
的pod进行负载均衡。
创建后查看:
[root@rke ~]# kubectl get svc redis-access-service -o wideNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR redis-access-service ClusterIP 10.43.40.62 <none> 6379/TCP 47m app=redis,appCluster=redis-cluster
如上,在k8s集群中,所有应用都可以通过10.43.40.62:6379
来访问redis集群,当然,为了方便测试,我们也可以为Service添加一个NodePort映射到物理机上,待测试。
在K8S上搭建完好Redis集群后,我们最关心的就是其原有的高可用机制是否正常。这里,我们可以任意挑选一个Master的Pod来测试集群的主从切换机制,如redis-app-2
:
[root@rke ~]# kubectl get pods redis-app-2 -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE redis-app-2 1/1 Running 0 1h 10.42.1.13 192.168.1.20 <none>
进入redis-app-2
查看:
[root@rke ~]# kubectl exec -it redis-app-2 /bin/bashroot@redis-app-2:/data# redis-cli127.0.0.1:6379> role 1) "master"2) (integer) 9478 3) 1) 1) "10.42.1.14" 2) "6379" 3) "9478"
如上可以看到,其为master,slave
为10.42.1.14
即redis-app-5
。
接着,我们手动删除redis-app-2
:
[root@rke ~]# kubectl delete pods redis-app-2pod "redis-app-2" deleted [root@rke ~]# kubectl get pods redis-app-2 -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE redis-app-2 1/1 Running 0 19s 10.42.1.15 192.168.1.20 <none>
如上,IP改变为10.42.1.15
。我们再进入redis-app-2
内部查看:
[root@rke ~]# kubectl exec -it redis-app-2 /bin/bashroot@redis-app-2:/data# redis-cli127.0.0.1:6379> ROLE 1) "slave"2) "10.42.1.14"3) (integer) 6379 4) "connected"5) (integer) 9688
如上,redis-app-2
变成了slave
,从属于它之前的从节点10.42.1.14
即redis-app-5
。
我们现在这个集群中有6个节点三主三从
,我现在添加两个pod节点,达到4主4从
cat >> /etc/exports <<'EOF'/usr/local/kubernetes/redis/pv7 192.168.0.0/16(rw,all_squash) /usr/local/kubernetes/redis/pv8 192.168.0.0/16(rw,all_squash) EOF systemctl restart nfs rpcbind [root@rke ~]# mkdir /usr/local/kubernetes/redis/pv{7..8}[root@rke ~]# chmod 777 /usr/local/kubernetes/redis/*
[root@rke redis]# cat pv_add.yml --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv7 spec: capacity: storage: 200M accessModes: - ReadWriteMany nfs: server: 192.168.1.253 path: "/usr/local/kubernetes/redis/pv7" --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv8 spec: capacity: storage: 200M accessModes: - ReadWriteMany nfs: server: 192.168.1.253 path: "/usr/local/kubernetes/redis/pv8"
创建查看pv:
[root@rke redis]# kubectl create -f pv_add.ymlpersistentvolume/nfs-pv7 created persistentvolume/nfs-pv8 created [root@rke redis]# kubectl get pvNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv1 200M RWX Retain Bound default/redis-data-redis-app-1 2h nfs-pv2 200M RWX Retain Bound default/redis-data-redis-app-2 2h nfs-pv3 200M RWX Retain Bound default/redis-data-redis-app-4 2h nfs-pv4 200M RWX Retain Bound default/redis-data-redis-app-5 2h nfs-pv5 200M RWX Retain Bound default/redis-data-redis-app-0 2h nfs-pv6 200M RWX Retain Bound default/redis-data-redis-app-3 2h nfs-pv7 200M RWX Retain Available 7s nfs-pv8 200M RWX Retain Available 7s
更改redis的yml文件里面的replicas:
字段,把这个字段改为8,然后升级运行
[root@rke redis]# kubectl apply -f redis.ymlWarning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply statefulset.apps/redis-app configured [root@rke redis]# kubectl get podsNAME READY STATUS RESTARTS AGE redis-app-0 1/1 Running 0 2h redis-app-1 1/1 Running 0 2h redis-app-2 1/1 Running 0 19m redis-app-3 1/1 Running 0 2h redis-app-4 1/1 Running 0 2h redis-app-5 1/1 Running 0 2h redis-app-6 1/1 Running 0 57s redis-app-7 1/1 Running 0 30s
[root@rke redis]#kubectl exec -it centos /bin/bash[root@centos /]# redis-trib add-node \`dig +short redis-app-6.redis-service.default.svc.cluster.local`:6379 \ `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 [root@centos /]# redis-trib add-node \`dig +short redis-app-7.redis-service.default.svc.cluster.local`:6379 \ `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379
add-node后面跟的是新节点的信息,后面是以前集群中的任意 一个节点
[root@rke redis]# kubectl exec -it redis-app-0 bashroot@redis-app-0:/data# redis-cli127.0.0.1:6379> cluster nodes 589b4f4f908a04f56d2ab9cd6fd0fd25ea14bb8f 10.42.1.15:6379@16379 slave e9f1f704ff7c8f060d6b39e23be9cd8e55cb2e46 0 1550564776000 7 connected e9f1f704ff7c8f060d6b39e23be9cd8e55cb2e46 10.42.1.14:6379@16379 master - 0 1550564776000 7 connected 10923-16383 366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379@16379 slave 4676f8913cdcd1e256db432531c80591ae6c5fc3 0 1550564777051 4 connected 505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379@16379 master - 0 1550564776851 2 connected 5461-10922 cee3a27cc27635da54d94f16f6375cd4acfe6c30 10.42.0.16:6379@16379 slave 505f3e126882c0c5115885e54f9b361bc7e74b97 0 1550564775000 5 connected e4697a7ba460ae2979692116b95fbe1f2c8be018 10.42.0.20:6379@16379 master - 0 1550564776549 0 connected 246c79682e6cc78b4c2c28d0e7166baf47ecb265 10.42.2.23:6379@16379 master - 0 1550564776548 8 connected 4676f8913cdcd1e256db432531c80591ae6c5fc3 10.42.2.17:6379@16379 myself,master - 0 1550564775000 1 connected 0-5460
redis-trib.rb reshard `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379## 输入要移动的哈希槽## 移动到哪个新的master节点(ID)## all 是从所有master节点上移动
127.0.0.1:6379> cluster nodes 589b4f4f908a04f56d2ab9cd6fd0fd25ea14bb8f 10.42.1.15:6379@16379 slave e9f1f704ff7c8f060d6b39e23be9cd8e55cb2e46 0 1550566162000 7 connected e9f1f704ff7c8f060d6b39e23be9cd8e55cb2e46 10.42.1.14:6379@16379 master - 0 1550566162909 7 connected 11377-16383 366abbba45d3200329a5c6305fbcec9e29b50c80 10.42.2.18:6379@16379 slave 4676f8913cdcd1e256db432531c80591ae6c5fc3 0 1550566161600 4 connected 505f3e126882c0c5115885e54f9b361bc7e74b97 10.42.0.15:6379@16379 master - 0 1550566161902 2 connected 5917-10922 cee3a27cc27635da54d94f16f6375cd4acfe6c30 10.42.0.16:6379@16379 slave 505f3e126882c0c5115885e54f9b361bc7e74b97 0 1550566162506 5 connected 246c79682e6cc78b4c2c28d0e7166baf47ecb265 10.42.2.23:6379@16379 master - 0 1550566161600 8 connected 0-453 5461-5916 10923-11376 4676f8913cdcd1e256db432531c80591ae6c5fc3 10.42.2.17:6379@16379 myself,master - 0 1550566162000 1 connected 454-5460
对redis的相关操作可以查看:http://redisdoc.com/topic/cluster-tutorial.html#id10
经过数次跳票之后,Let's Encrypt在2018年3月13日开始提供支持泛域名的SSL证书了,每次颁发证书的有效时间是3个月,因此Let's Encrypt提供了一个自动颁发和更新SSL证书的工具acme.sh,使用下来感觉比收费的还要方便。
目录 [显示]
acme.sh 实现了 acme
协议, 可以从 letsencrypt 生成免费的证书。下面我们以CentOS为例进行说明:
acme.sh需要curl、cron和socat的依赖支持,使用下面的任务进行安装:
yum update -y && yum -y install curl cron socat
对于官方不支持的CentOS版本(如5.x),可以手动下载和编译上述包进行安装。
PS:CentOS 5.x可以使用socat v1.7.2.4,安装方法可以参考:socat的安装与使用
可以使用下面的命令安装:
curl https://get.acme.sh | sh
安装脚本将所有的文件安装到 ~/.acme.sh/
目录下,并自动创建一个定时任务,每天0:00自动检测所有的证书,如果过期了就会自动更新证书。
生成证书的方式就两种:http方式和dns方式,相对来说我更喜欢dns方式,这种方式可以使用dns解析商的API自动进行域名的验证等操作,非常方便。目前支持 cloudflare, dnspod, cloudxns, godaddy 以及 ovh 等数十种解析商的自动集成,如果你的域名不是使用的这些解析商的话,智能使用http方式进行手动验证了。
这里以常用的dnspod来介绍如果使用dns方式来生成证书,首先在DNSPOD用户中心-安全设置中开启API Token,然后创建一个API Token,并记住ID和Token,执行下面的命令:
export DP_Id="<your_dnspod_id>" export DP_Key="<your_dnspod_token>" ~/.acme.sh/acme.sh --issue --dns dns_dp -d javatang.com -d *.javatang.com
上面的DP_Id和DP_Key是dnspod.cn API定义的变量名,--dns参数后面的dns_dp也指定了服务商为dnspod.cn,其他服务商的API名称见https://github.com/Neilpang/acme.sh/wiki/dnsapi。相同的ID和Key只要指定一次就可以了,acme.sh会自动将其保存在account.conf文件中。
后面-d参数用于生成证书的域名,如果想要生成泛域名的SSL证书必须按照上面的例子那样设定两次-d参数,第一次必须是主域名,不可以直接写泛域名的格式,后面一次是*.javatang.com泛域名的格式。如果不需要泛域名的SSL证书的话,只要指定一次-d参数就可以了。
执行上面的命令之后,acme.sh会自动校验域名的有效性并像Let's Encrypt请求SSL证书,成功之后会将证书放在~/.acme.sh/
目录下面,但一定要注意,不要在nginx/apache中直接使用此目录下面的证书文件,这是因为脚本升级之后此目录会发生变化,会造成引用错误。正确的做法是再执行复制并安装证书。
使用--installcert
命令可以将证书复制到固定的位置,并保证在更新证书之后自动重启nginx/apache,这里一nginx为例,执行的脚本如下:
~/.acme.sh/acme.sh --installcert -d javatang.com --key-file /etc/nginx/conf/cert/javatang.com.key --fullchain-file /etc/nginx/conf/cert/javatang.cer --reloadcmd "service nginx force-reload"
-d
参数表示需要复制的域名名称,如果是泛域名的话直接使用主域名。
--key-file
和--fullchain-file
参数分别表示所要复制的key和fullchain文件的位置和文件名,nginx/apache配置文件中所引用的SSL文件级为这里所设置的路径。
最后一个参数--reloadcmd
也非常重要,这里表示证书更新之后自动重启nginx/apache的命令,这样才能保证更新之后的证书有效。我一开始将这个参数设置错误了,导致证书到期更新之后没有应用于nginx。
如果因为误操作需要删除域名证书话,可以使用 --remove参数。
首先使用 ~/.acme.sh/acme.sh --list
查看当前的证书列表,然后使用 ~/.acme.sh/acme.sh --remove -d <Main_Domain>
删除对应的域名证书,最后可以再使用--list
参数查看是否删除成功。
PS:虽然acme.sh将所有的操作放在了 ~/.acme.sh/
目录下,但不建议直接删除该目录下的域名目录。
最后不要忘记了还要再nginx/apache中引用上面的SSL证书,这里以nginx为例,配置文件如下:
server { listen 443 ssl; server_name javatang.com *.javatang.com; ssl on; ssl_certificate cert/javatang.com.cer; ssl_certificate_key cert/javatang.com.key; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_prefer_server_ciphers on; }
再强调一下:这里引用的SSL证书一定不要是acme.sh原始下载的证书,而是使用--installcert
命令复制的证书。
使用acme.sh
还有一个好处就是不需要担心证书过期的问题,因为脚本会自动更新证书,非常方便。
最后可以通过https://crt.sh查询指定域名的证书详情。
在有的服务器中遇到了下面的错误提示:
Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 35
升级本地的openssl版本,可以使用下面的命令进行升级:
wget -c https://www.openssl.org/source/openssl-1.1.0-latest.tar.gz tar xzvf openssl-1.1.0-latest.tar.gz cd openssl-1.1.0* ./config --prefix=/usr/local/openssl make && make install mv /usr/bin/openssl /usr/bin/openssl.old -f mv /usr/lib64/openssl /usr/lib64/openssl.old -f mv /usr/lib64/libssl.so /usr/lib64/libssl.so.old -f mv /usr/include/openssl /usr/include/openssl.old -f ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl ln -s /usr/local/openssl/include/openssl /usr/include/openssl ln -s /usr/local/openssl/lib/libssl.so /usr/lib64/libssl.so echo "/usr/local/openssl/lib" >> /etc/ld.so.conf ldconfig -v
同一个主域名一周之内只能申请50个证书
每个账号下每个域名每小时申请验证失败的次数为5次
每周只能创建5个重复的证书,即使是通过不同的账号进行创建
每个账号同一个IP地址每3小时最多可以创建10个证书
每个多域名(SAN) SSL证书(不是通配符域名证书)最多只能包含100个子域
更新证书没有次数的限制,但是更新证书会受到上述重复证书的限制
对服务器资源和网络状况进行排除之后,在nginx的error.log文件中发现有很多类似下面的错误信息:
2019/03/22 17:28:42 [crit] 20807#0: *249015212 SSL_shutdown() failed (SSL: error:140E0197:SSL routines:SSL_shutdown:shutdown while in init) while closing request, client: 100.200.70.100, server: 0.0.0.0:443
查阅了相关的资料发现,是nginx和openssl的版本太低,需要保证nginx的版本在1.9.12以上,openssl的版本在1.1.0以上。
使用低版本的IE浏览器会无法访问https,在nginx的error.log文件中出现下面的错误信息:
SSL_do_handshake() failed (SSL: error:1417D18C:SSL routines:tls_process_client_hello:version too low) while SSL handshaking
通过命令wget --secure-protocol=SSLv3 -O - https://www.javatang.com/
进行测试,结果如下:
OpenSSL: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
Unable to establish SSL connection.
这是因为低版本的IE浏览器采用了SSLv3进行访问,而OpenSSL从1.1.0开始默认取消了SSLv3,即使在nginx的ssl_protocols配置项中增加SSLv3也是无效的,可以通过下面几种方法进行解决:
(1)编译OpenSSL v1.1.x的时候config的时候增加enable-ssl3 enable-ssl3-method
参数,编译Nginx的时候configure的时候增加--with-openssl-opt="enable-ssl3 enable-ssl3-method"
,然后重新编译OpenSSL和Nginx。不过不建议这样操作,因为SSLv3有安全漏洞,可以采用下面的方法。
(2)在Nginx中通常会采用80端口做301跳转到433端口,可以取消跳转同时保留80和433两个端口的访问,或者判断浏览器是IE或采用了SSLv3的时候进行跳转,设置如下:
server { listen 80; set $oldclient 0; if ($http_user_agent ~* "MSIE") { set $oldclient 1; } if ($ssl_protocol = SSLv3) { set $oldclient 1; } if ($oldclient = 0) { rewrite ^(.*) https://$host$1 permanent; break; } }
网站的托管环境如下:
OS:CentOS 7.6 阿里云
网站服务器:Nginx,使用 yum 安装,版本 1.12
提前配置好 Nginx,确保使用 HTTP 先可以访问到网站
注意:请使用 yum
命令安装 nginx,这样可以确保 nginx 安装在默认的位置,因为 certbot 会检测 /etc/nginx/
目录下的配置文件。
执行下面的步骤可以直接为你的网站配置 HTTPS 证书。
yum -y install yum-utils yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional yum install certbot python2-certbot-nginx
下图是在 Certbot 中选择服务器和操作系统的页面。
执行下面的命令,根据提示会自动配置 nginx。
certbot --nginx Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator nginx, Installer nginx Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org Which names would you like to activate HTTPS for? 1:servicemesher.com 2: www.servicemsher.com # 这里直接回车选择所有的域名 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - You have an existing certificate that contains a portion of the domains you requested (ref: /etc/letsencrypt/renewal/servicemesher.com.conf) It contains these names: servicemesher.com, www.servicemesher.com You requested these names for the new certificate: servicemesher.com, prow.servicemesher.com, www.servicemesher.com. Do you want to expand and replace this existing certificate with the new certificate? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (E)xpand/(C)ancel: E Renewing an existing certificate Performing the following challenges: http-01 challenge for prow.servicemesher.com Waiting for verification... Cleaning up challenges Deploying Certificate to VirtualHost /etc/nginx/nginx.conf Deploying Certificate to VirtualHost /etc/nginx/nginx.conf Deploying Certificate to VirtualHost /etc/nginx/nginx.conf Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate number [1-2] then [enter] (press 'c' to cancel): # 这里是为了扩展证书支持更多的域名,所有输入 2 回车 Traffic on port 80 already redirecting to ssl in /etc/nginx/nginx.conf Redirecting all traffic on port 80 to ssl in /etc/nginx/nginx.conf Traffic on port 80 already redirecting to ssl in /etc/nginx/nginx.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Your existing certificate has been successfully renewed, and the new certificate has been installed.
然后重新加载配置。
nginx -t;nginx -s reload
设置证书自动更新。
echo "0 0,12 * * * root python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew" | sudo tee -a /etc/crontab > /dev/null
好了现在访问你的网站就可以看到 https 头部加了 HTTPS 锁了。
Could not choose appropriate plugin: The manual plugin is not working; there may be problems with your existing configuration. The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.',). Skipping.
搜索后得知是因为验证域名所有者失败,没有指定 --manual-auth-hook 参数。
Let's Encrypt 有多种验证方式,常用的有 http 和 dns 方式,由于我是在 homestead 虚拟机里面申请的证书,无法使用 http 方式,所以之前申请证书用的是 dns 方式,也就是创建 TXT 记录。
我用的是 DNSPod,便根据 DNSPod 提供的 API 自己写了一个脚本,这里分享给大家。
$ wget https://raw.githubusercontent.com/al-one/certbot-auth-dnspod/master/certbot-auth-dnspod.sh $ chmod +x certbot-auth-dnspod.sh
echo "your dnspod token" > /etc/dnspod_token
add crontab
0 2 1 * * sh -c 'date "+\%Y-\%m-\%d \%H:\%M:\%S" && /usr/bin/certbot renew --manual-auth-hook /path/to/certbot-auth-dnspod.sh' >> /var/log/certbot-renew.log 2>&1
来自于157.0.111.176江苏省宿迁市 联通网友评分!
来自于42.119.148.32越南胡志明市网友评分!
来自于85.237.206.197英国英格兰伦敦网友评分!
来自于60.246.51.76澳门特别行政区网友评分!
来自于43.249.50.166印度网友评分!
来自于106.113.13.179河北省石家庄市 电信网友评分!
来自于101.94.224.43上海市上海市 电信网友评分!
来自于124.126.3.110北京市北京市 电信网友评分!
来自于106.87.116.73重庆市重庆市 电信网友评分!
来自于49.157.47.254菲律宾网友评分!
来自于3.112.41.223日本东京网友评分!
来自于106.87.116.73重庆市重庆市 电信网友评分!
来自于156.224.31.74香港特别行政区网友评分!
来自于118.150.135.228台湾省新北市网友评分!
来自于138.199.21.219欧洲网友评分!
来自于49.230.8.237泰国网友评分!
来自于1.200.36.188台湾省网友评分!
来自于94.66.59.128希腊网友评分!
来自于101.9.174.29台湾省网友评分!
来自于218.35.154.227台湾省新北市网友评分!
来自于111.55.11.245中国 移动网友评分!
来自于103.205.179.169巴基斯坦网友评分!
来自于183.200.16.191山西省太原市 移动网友评分!
来自于183.200.16.191山西省太原市 移动网友评分!
来自于176.97.73.32英国网友评分!
来自于46.232.121.89俄罗斯莫斯科网友评分!
来自于114.45.39.108台湾省台北市网友评分!
来自于164.155.132.208南非网友评分!
来自于14.192.208.96马来西亚吉隆坡网友评分!
来自于98.98.83.85美国佛罗里达网友评分!