架构

kubernetes 初探——在 Master 节点部署 Pod

虽然 K8S 本身不建议在 Master 节点中部署 Pod,但是实际上也是可以的。

“`
kubectl taint nodes –all node-role.kubernetes.io/master-
“`

这行命令的意思是移除所有节点的 master taint,这样就可以在 master 节点部署 pod 啦~

# 原理解释——taint 和 toleration

1. https://www.ibm.com/support/knowledgecenter/en/SSCKRH_1.0.1/platform/install_pod_scheduling.html
2. https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/

故障排查记录

Case 1:

CPU 和内存都被打满了,同时发现硬盘 IO 也很高。

“`
ps -eo pid,ppid,cmd,%mem,mem,%cpu –sort=-%mem | head -n 50
“`

发现 kswapd0 偶尔会占用很高的 CPU,而这个进程是在内存满的时候负责在内存和 swap 之间交换。也就是问题的根源是内存满了,而 kswapd0 开始在内存和 swap 之间反复读写文件,导致 CPU 和 IO 也涨了起来,这时候应该找到内存暴涨的根源,进而解决问题。

[1] https://askubuntu.com/questions/259739/kswapd0-is-taking-a-lot-of-cpu

kubernetes 初探——服务治理

服务治理有两种方式:

– 一种是直接在服务中集成熔断降级等操作
– 一种是使用 sidecar 模式,有公共组件来处理

两种模式的区别如图:

![](https://ws1.sinaimg.cn/large/006tNc79ly1fzbu3a41c3j30gw09jabr.jpg)

# 参考资料

1. [Service Mesh 的本质、价值和应用探索 ](https://mp.weixin.qq.com/s/1zAxecTzeZToaWFymeY-sw)
2. [Istio, K8S 的微服务支持](https://www.kubernetes.org.cn/2350.html)
3. [微服务之熔断、降级、限流](https://blog.csdn.net/aa1215018028/article/details/81700796)
4. [微服务化之服务拆分与服务发现](https://mp.weixin.qq.com/s?__biz=MzI1NzYzODk4OQ==&mid=2247484925&idx=1&sn=5c15ba98fb03a2a0d9c823136f34e162)

使用 Nomad 编排服务

2019-01-02 更新:相对于 Kubernetes 来说,Nomad 还是太简陋了,弃坑

Nomad 是 HashiCorp 出品的一个容器编排服务,相较于重量级的 Kubernetes 来说,Nomad 的特点在于

1. 轻量级,只有一个二进制文件。K8s 的安装可能就要花上半天,在国内还有万恶的防火墙问题。
2. 概念也比较清晰,专注于任务的调度的编排,而不像 Kubernetes 一样引入了各种五花八门的概念。
3. 除了编排容器之外,Nomad 还可以直接编排普通应用,使用 cgroups 安全运行应用。

# 安装

从官网下载二进制文件,复制到 /usr/local/bin 就好了,不再赘述

# 使用

“`
$ sudo nomad agent -dev

$ nomad node status
ID DC Name Class Drain Eligibility Status
171a583b dc1 nomad false eligible ready

$ nomad server members
Name Address Port Status Leader Protocol Build Datacenter Region
nomad.global 127.0.0.1 4648 alive true 2 0.7.0 dc1 global
“`

# Job

Nomad 的调度单元称作 Job,Job 分为了三种类型:

1. Batch,也就是一次批处理,程序运行之后就结束了。不过也可以通过 cron 字段指定任务定期运行
2. Service,程序是一个常驻内存的服务,如果退出之后,Nomad 会按照给定的策略重启
3. System,在每一个 Nomad 节点上都需要运行的服务

Job 可以使用 HCL 文件来定义,HCL 文件在语义上和 JSON 是等价的,只不过是省去了一些多余的引号逗号之类的。也可以使用 JSON 文件来定义。

## 创建一个新的 Job

创建一个空白的 job 文件

“`
$ nomad job init
Example job file written to example.nomad
“`

打开生成的 example.nomad 文件,我们看到生成了一大推配置,默认定义了一个 redis 服务器的 job。Job 中包含了 Group,Group 中包含了 Task,task 可以认为是我们最终需要运行服务的那个命令。比如这里就是定义了运行 redis:3.2 这个 docker 镜像。

“`
task “redis” {
# The “driver” parameter specifies the task driver that should be used to
# run the task.
driver = “docker”

# The “config” stanza specifies the driver configuration, which is passed
# directly to the driver to start the task. The details of configurations
# are specific to each driver, so please see specific driver
# documentation for more information.
config {
image = “redis:3.2”
port_map {
db = 6379
}
}
“`

我们可以运行一下这个 job

“`
-> % nomad job run example.nomad
==> Monitoring evaluation “4f5559e0”
Evaluation triggered by job “example”
Allocation “98959767” created: node “ecf9f7cd”, group “cache”
Evaluation within deployment: “e66e0957”
Evaluation status changed: “pending” -> “complete”
==> Evaluation “4f5559e0” finished with status “complete”
“`

然后查看一下 job 的运行状态:

“`
$ nomad status example

Allocations
ID Node ID Task Group Version Desired Status Created Modified
8ba85cef 171a583b cache 0 run running 5m ago 5m ago
“`

在最下面一行我们可以看到 Allocation 的状态。Allocation 可以理解为一个 Job 的一个实例化。

我们可以再查看这个 Alloc 的状态:

“`
$ nomad alloc status 8ba85cef

Recent Events:
Time Type Description
10/31/17 22:58:49 UTC Started Task started by client
10/31/17 22:58:40 UTC Driver Downloading image redis:3.2
10/31/17 22:58:40 UTC Task Setup Building Task Directory
10/31/17 22:58:40 UTC Received Task received by client
“`

查看 Alloc 的日志

“`
$ nomad alloc logs 8ba85cef redis
“`

## 修改 Job

比如说,我们可以把这个 Job 中 cache task group 需要运行的副本数量改为 3

“`
count = 3
“`

使用 nomad job plan 来 dry run 一下。

“`
$ nomad job plan example.nomad

+/- Job: “example”
+/- Task Group: “cache” (2 create, 1 in-place update)
+/- Count: “1” => “3” (forces create)
Task: “redis”

Job Modify Index: 7
To submit the job with version verification run:

nomad job run -check-index 7 example.nomad

“`

注意到其中返回了一个 check-index 这个是为了避免同时更改同一个 job 造成冲突。

“`
$ nomad job run -check-index 7 example.nomad
“`

# 集群

在生产环境中,我们当然应该使用集群模式,而不是单机。nomad 可以直接利用 consul 来实现 bootstrap 集群。

服务端配置:

“`
# /etc/nomad.d/server.hcl

data_dir = “/etc/nomad.d”

server {
enabled = true
bootstrap_expect = 3
}
“`

启动:

“`
$ nomad agent -config=/etc/nomad.d/server.hcl
“`

客户端配置:

“`
# /etc/nomad.d/client.hcl

datacenter = “dc1”
data_dir = “/etc/nomad.d”

client {
enabled = true
}
“`

启动:

“`
$ nomad agent -config=/etc/nomad.d/client.hcl
“`

kubernetes 初探——使用 helm 部署服务

前几篇写了一写 k8s 部署方面的东西, 后来发现阿里云的 k8s 集群还很好用…何苦自己研究几天部署呢…?

——分隔线——

在 k8s 上部署一个应用还是有些复杂的, 自己的应用当然需要自己一步一步部署, 但是对于一些通用的应用, 比如说 mysql/grafana 这种就没必要自己手工一步一步部署了. 这时候就有了 helm, 通俗的来说他就是 kubernetes 上的 AppStore, 常见的应用都已经在了, 而且你也可以按照他的格式打包自己的应用部署.

安装

在 helm 的 release 页面下载, 然后拷贝到本地的 /usr/local/bin/ 目录就好了. helm 运行的时候会使用 ~/.kube/config 文件, 所以本地 kubectl 可以使用就好了.

helm 分为两部分, helm 是本地命令, tiller 是服务端, 作为一个 pod 运行在, 两者的版本需要保持一致. 理论上来说, 安装好 helm 之后, 只需要执行 helm init 就可以自动在 k8s 集群中安装 tiller 了, 但是由于一些众所周知的原因, 在中国大陆地区并不能安装成功, 需要使用阿里云或者中科大的镜像:

helm init --stable-repo-url http://mirror.azure.cn/kubernetes/charts/

如果已经安装好了,

使用

概念

  • Chart, 大概相当于 package 的意思
  • Repository, Helm 的中心仓库
  • Release, 每次运行一个 Chart 都会生成一个 Release, 每个 release 之间是独立的. Chart/Release 的关系就好比 Docker 的 Image/Container 一样.
  • Revision, 每次更新 Release 都会产生一个新的版本, 可以回滚

基础命令

helm search 查找相关的软件包(chart), 现在 stable 仓库中大概有 200 多个包可以安装.

helm install –name NAME PACKAGE 会安装对应的 chart. 如果不指定 name, 会自动生成一个.

helm status NAME 可以查看对应的包的信息, 一般包括了如何连接使用这个包等信息, 可以当做帮助来用.

在安装包之前更改配置

每个 helm chart 都定义了一些默认的配置, 可以在安装之前查看并修改这些值.

helm inspect values stable/mysql 查看 mysql 的默认值. 或者去 GitHub 上看这个仓库的 readme.

把需要覆盖的变量写到 OVERRIDE.yml 文件中, helm install -f OVERRIDE.yml stable/mysql 就可以使用自己的配置了

更新 release

如果需要更新一个 release, 可以使用 helm upgrade -f OVERRIDE.yml RELEASE_NAME 命令更新相关的配置. 这时就会创建一个新的版本.

使用 helm list 可以查看当前部署的 release, 这时候我们可以看到部署的版本变成了 2 (最初是1).

-> % helm ls
NAME    REVISION        UPDATED                         STATUS          CHART           NAMESPACE
mysql   1               Sat Oct  6 15:44:25 2018        DEPLOYED        mysql-0.3.0     default

如果当前的更新有误, 可以回退到之前的版本, 语法是 helm rollback [RELEASE] [REVISION]

管理 repo

helm repo list 列出当前添加的 repo

$ helm repo list
NAME            URL
stable          https://kubernetes-charts.storage.googleapis.com
local           http://localhost:8879/charts
mumoshu         https://mumoshu.github.io/charts

添加新的 repo

$ helm repo add dev https://example.com/dev-charts

参考文献

  1. https://docs.helm.sh/
  2. https://ezmo.me/2017/09/24/helm-quick-toturial/
  3. https://help.aliyun.com/document_detail/58587.html
  4. https://johng.cn/helm-brief/

kubernetes 初探——部署有状态服务

为了部署我们自己的应用, 首先需要把基础设施部署上去, 其中数据库就占了大头. 有人说数据库等应用不是和容器化部署, 但是也有人认为所有的应用都适合容器化部署. 在这里我们不讨论这些观点,仅以部署 MySQL 为例说明一下如何在 K8S 上部署有状态服务。

# 相关概念

– PersistentVolume(PV) 是集群之中的一块网络存储。跟 Node 一样,也是集群的资源。PV 跟 Volume 类似,不过会有独立于 Pod 的生命周期。这一 API 对象包含了存储的实现细节,例如 NFS、iSCSI 或者其他的云提供商的存储系统。
– PersistentVolumeClaim (PVC) 是用户的一个请求。他跟 Pod 类似。Pod 消费 Node 的资源,PVC 消费 PV 的资源。Pod 能够申请特定的资源(CPU 和 内存);Claim 能够请求特定的尺寸和访问模式(例如可以加载一个读写,以及多个只读实例)
– Stateful Set. Deployment 是无状态的服务,而 StatefulSets 旨在与有状态的应用及分布式系统一起使用。
– ConfigMap 用来保存非密码的配置. configmap 可以以配置文件或者环境变量等方式挂在到 pod 中
– Secret 用来保存密码等私密数据
– Init Container 用于初始化的容器. 有点类似于 docker build 的过程

# 动态 PV vs 静态 PV

# 使用 Deployment PVC 还是 Stateful Set

可以看出我们即可以使用普通的 Deployment + PVC 来部署 MySQL, 也可以使用 Stateful Set 来部署, 那么哪种方式更好呢?

个人理解:

– 对于需要使用挂载一定资源的,使用 PVC 就好了,甚至只需要只读挂载就好。
– 对于强状态依赖的服务,比如数据库,肯定要使用 PVC

Stack Overflow 上的一个问题[2]也很值得参考.

# MySQL 主从集群

本文中我们要部署一个一主多从的 MySQL 集群. 关于一主多从的优点不是本文的重点, 这里就不多说了, 可以参考下面:

> 1. 扩容解决方案:在多个slave之间扩展负载以提高性能。在这种模式下,所有的写入和更新操作都必须在主服务器上进行。然而,读取操作通过slave镜像。该模型可以提高写入操作的性能,同时,也能够通过增加slave的节点数量,从而显著地提升读取速度。
> 2. 数据安全:数据从master被复制到slave,并且slave可以暂停复制过程。因此,可以在不损坏master的情况下,在slave上运行备份服务。
> 3. 分析:现场数据可以在master上创建,而对信息的分析可以在slave进行,而不影响master的性能。
> 4. 远程数据分发:可以使用复制为远程站点创建本地数据的副本,而不必一直通过访问master。

# 参考

1. [使用 PVC 和 Deployment 部署单实例 MySQL 集群](https://blog.csdn.net/sweatOtt/article/details/81092484)
2. https://stackoverflow.com/questions/41732819/why-statefulsets-cant-a-stateless-pod-use-persistent-volumes
3. [使用普通 Deployment 部署单节点 mysql](https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application/)
4. https://kubernetes.io/cn/docs/tutorials/stateful-application/basic-stateful-set/
5. [如何使用 ConfigMap](https://www.cnblogs.com/zhenyuyaodidiao/p/6594410.html)
6. [Secret 文档](https://kubernetes.io/cn/docs/concepts/configuration/secret/)
7. https://stackoverflow.com/questions/41583672/kubernetes-deployments-vs-statefulsets
8. [阿里云挂载 OSS Volume](https://yq.aliyun.com/articles/640212)
9. https://jimmysong.io/posts/kubernetes-persistent-volume/
10. https://kubernetes.io/docs/concepts/storage/volumes/

kubernetes 初探——部署无状态应用

kubernetes 架构图

![](https://ws2.sinaimg.cn/large/006tNbRwly1fvxa7izqb8j30mb0gcmzk.jpg)

* Master. 用于控制整个集群部署的机器, 为了高可用, 可以使用多台,一般至少三台为宜。
* Node. 工作节点, 用于部署服务. 一台机器可以既是 Master 也是 Worker,当然最好 Master 不要做 Worker。
* Pod. k8s 部署的最小单元, 一个 Pod 中可能包含多个 container. Pod 随时可能挂掉,也可能被替换。
* Label. Pod 的标签, 可以通过这些标签(组合)来选择对应的 Pod。
* Replica Set. 作为一个高可用的系统, 每个服务一般来说可能有多个 Pod. Replication Set 用来创建并保证有足够的 Pod 副本。RS 的名字总是 格式的。
* Deployment. 用户一般来说不会直接创建 Pod, 而是创建一个 Deployment 来部署服务. (在老版本中是创建 RC)
* Namespace. 命名空间, 默认情况下会有 kube-system 和 default 两个命名空间, kube-system 存放的是 k8s 系统的 pod 等资源, 而用户部署的资源会在 default 命名空间中.
* PersistendVolume. 如果把 Node 理解为抽象的 CPU 和内存资源,那么 PV 就可以理解为抽象的硬盘资源。我们通过 Pod 来使用 Node,因此我们也不直接使用 PV,而是通过 PersistentVolumeClaim 来使用 PV。
* PersistentVolumeClaim. 存储声明,用来声明需要使用的存储资源。
* Stateful Set. Deployment 对应的部署语义上没有状态的,而StatefulSet会充分运用 PV 和 PVC 实现 Pod 重启之间的状态保持。典型应用场景是数据库。
* Label 和 Selector. K8S 中的资源全部都通过 Label 来标识的选择。

# deployment

deployment 是使用 k8s 部署服务直接操作的概念。其他的概念往往都是**通过 deployment 来间接使用**的,因此理解 deployment 至关重要。

一个典型的 deployment 配置文件如下:

“`
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
– name: my-nginx
image: nginx
ports:
– containerPort: 80

“`

deployment 的配置可以发生改变,如果只是 replica 的数目发生了改变,那么这只是一个简单的扩容或者缩容操作,k8s只会简单的打开或者杀死新的 Pod。如果镜像、命令等参数发生了改变,那么 K8S 会把这次操作视为升级,也就是开始一个 RollOut 操作,创建新的 ReplicaSet。在这个过程中,如果 deployment 中的 spec 指定了保留历史 revision 的次数大于零,那么原有的 ReplicaSet 不会被删除,只是会被 Scale 到 0 而已,方便回滚。

文档:

> Note: a Deployment’s rollout is triggered if and only if the Deployment’s pod template (i.e. .spec.template) is changed, e.g. updating labels or container images of the template. Other updates, such as scaling the Deployment, will not trigger a rollout.

> https://stackoverflow.com/questions/42561791/right-way-to-update-deployments-on-kubernetes

# 服务

## 相关概念

* Service. 如果一个 Deployment 对外(pod 外)提供服务的话,可以暴露为 Service。它是服务的抽象, 通过 kube-proxy 和 DNS 等提供负载均衡给后端 RC 定义的 Pod。
* clusterIP. 服务暴露在集群内部的一个虚拟 IP,声明周期和服务相同
* nodePort. 暴露在 Node 上的服务端口,不建议在生产环境使用。
* Ingress Controller. Service 只是对集群内部暴露了服务,ingress controller 用于把 Service 再对外暴露。

如果一个 deployment 需要对集群内部或者是外部提供服务的话,可以使用 service。

这时将创建一个 clusterIP,需要特别注意的是,这个 clusterIP 也是虚拟的,并没有和任何 pod 绑定,而是绑定到了服务上,可以理解为绑定到了这个服务对应的内部负载均衡上,并且是不变的。即使你的 RC 中指定了多个副本,那么也只有这一个 clusterIP,pod 的创建和销毁也都不会影响到 clusterIP。

# kubectl 使用

## kubectl get

常用参数 `-o wide` 用来现实更详细信息. 用来获取集群的各种信息:

* `kubectl get pod` 显示所有 pod 信息
* `kubectl get deployment` 显示所有部署信息
* `kubectl get node` 显示所有节点信息

## kubectl create & apply

用来创建 pod, 部署等. 一般情况下都是使用 `-f` 参数来制定配置文件

“`
kubectl create -f file.yml
“`

和 kubectl create 还有一个类似的概念,kubectl apply 也可以用于创建资源。这两个的区别有以下几点:

– kubectl create 是过程性的,重点在于“创建”这个操作;而 kubectl apply 是声明性的,重点在于达成“应用”这个结果
– kubectl create 在创建重复资源的时候会报错,而 kubectl apply 可以用于更新。

## kubectl run

类似于 docker run, 但是由 kubernetes 接管, 直接运行在集群上. 比如运行 hello world

## kubectl delete

用来删除节点上的 pod, deployment 等信息

## kubectl logs

类似于 docker logs, 用来显示打印到 stdout 的日志

1. [deployment 和 pod 的区别](https://stackoverflow.com/questions/41325087/in-kubernetes-what-is-the-difference-between-a-pod-and-a-deployment)
2. [Kubernetes 基础概念](http://dockone.io/article/932)
3. [客户端和服务端服务发现](https://www.jianshu.com/p/1bf9a46efe7a)
4. [kubernetes 命令表](http://docs.kubernetes.org.cn/683.html)
5. [Kubernetes之kubectl常用命令使用指南:1:创建和删除](https://blog.csdn.net/liumiaocn/article/details/73913597)
6. [Kubernetes之kubectl常用命令](https://blog.csdn.net/xingwangc2014/article/details/51204224)
7. [Kubernetes基本概念以及术语](https://blog.csdn.net/u010209217/article/details/78782353)
8. [kubectl create 和 apply 的区别](https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create)
9. https://stackoverflow.com/questions/42561791/right-way-to-update-deployments-on-kubernetes
10. https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
11. [K8S YAML 文件基础](https://blog.csdn.net/phantom_111/article/details/79427144)
12. [NodePort/LB/Ingress 三种方式的对比](https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0)
13. https://kubernetes.io/docs/tasks/access-application-cluster/service-access-application-cluster/

kubernetes 初探——部署集群

随着 docker cloud 的关闭,容器的编排工具之争似乎已经结束了,Docker Swarm 上算是完了,Kubernetes 笑到了最后。然而 k8s 的组件众多,因此部署起来也很麻烦。为此,网上有不少的网上有不少的部署教程和脚本,包括但不限于:

– kubesaz
– minikube
– kubespray
– rke
– kubernetes: the hard way

有的出于教学目的的单机版本,有的是可以在生产环境部署的版本。研究了一番,发现 rke 这个工具应该是最简单的了。不过 rke 还不太稳定,目前看来,kubespray 声明了可以在生产环境使用

本文基于 ubuntu 16.04. CentOS 上好多默认设置都需要修改,因此建议大家基于 Ubuntu 部署 k8s。

# 使用 kubespray 安装 k8s

(零) 假设我们要在三台机器上安装, 另外一台机器作为控制节点. 其中每台都作为工作节点, 两台作为 master 节点.

IP | 角色
————|———————–
10.4.17.165 | 控制节点, 不参与 k8s 集群
10.4.17.167 | master, node
10.4.17.168 | master, node
10.4.17.169 | node

(一) 下载 kubespray 安装包, 这里我们使用最新版(2018.10), 可能需要安装 python3, 如果没有安装的话

“`
VERSION=2.7.0

# download kubespray
wget https://github.com/kubernetes-incubator/kubespray/archive/v${VERSION}.tar.gz
tar xzf v${VERSION}.tar.gz

# install dependencies
pip3 install -r kubespray-${VERSION}/requirements.txt
“`

(二) 生成部署的 hosts.ini

kubespray 中有一个脚本叫做 inventory_builder 用来生成部署的 hosts.ini

“`
cd kubespray-v${VERSION}
cp -r inventory/sample inventory/mycluster
declare -a IPS=(10.4.17.167 10.4.17.168 10.4.17.169)
CONFIG_FILE=inventory/mycluster/hosts.ini python3 contrib/inventory_builder/inventory.py ${IPS[@]}
“`
生成之后, 可以查看生成的文件

“`
cat inventory/mycluster/host.ini

[k8s-cluster:children]
kube-master·▸ ·
kube-node·▸ ·

[all]
node1 ▸ ansible_host=10.4.17.167 ip=10.4.17.167
node2 ▸ ansible_host=10.4.17.168 ip=10.4.17.168
node3 ▸ ansible_host=10.4.17.169 ip=10.4.17.169

[kube-master]
node1·▸ ·
node2·▸ ·

[kube-node]
node1·▸ ·
node2·▸ ·
node3·▸ ·

[etcd]
node1·▸ ·
node2·▸ ·
node3·▸ ·

[calico-rr]

[vault]
node1·▸ ·
node2·▸ ·
node3·▸ ·
“`

(三) 修改一些配置

代理:

由于众所周知的原因, k8s 依赖的 gcr.io 在中国大陆范围内无法访问, 我们可以使用代理访问, 关于如何搭建代理超出了本文的范围.
假设我们的代理是 http://proxy.com:10086, 修改 `inventory/mycluster/group_vars/all/all.yml` 文件, 设置 http_proxy 和 https_proxy 两个变量.

下载 kubectl 到本机:

设置 kubectl_localhost 和 kubeconfig_localhost 两个变量为 true. 安装完成后会在本机安装 kubectl, 并且可以使用 inventory/mycluster/artifacts/admin.conf 使用 kubectl.

(四) 部署

这里我们需要使用 tiger 用户, 这个用户需要满足如下条件:

1. 可以无密码远程登录
2. 在远程主机上具有无密码 sudo 权限

“`
ansible-playbook -i inventory/mycluster/hosts.ini cluster.yml –become –user tiger -v
“`

大概过十几分钟就部署好了

(五) 验证

“`
cd inventory/mycluster/artifacts
./kubectl.sh get nodes

NAME STATUS ROLES AGE VERSION
node1 Ready master,node 1d v1.11.3
node2 Ready master,node 1d v1.11.3
node3 Ready node 1d v1.11.3
“`

参考:

1. [HN 上关于 Docker Cloud 关闭的讨论](https://news.ycombinator.com/item?id=16665130)
2. [使用Kubespray部署生产可用的Kubernetes集群](http://www.itmuch.com/docker/kubernetes-deploy-by-kubespray/)

图解一致性哈希

# 起源

比如你有 N 个 cache 服务器(后面简称 cache ),那么如何将一个对象 object 映射到 N 个 cache 上呢,你很可能会采用类似下面的通用方法计算 object 的 hash 值,然后均匀的映射到到 N 个 cache ;

“`
hash(object) % N
“`

一切都运行正常,再考虑如下的两种情况;

1. 一个 cache 服务器 m down 掉了(在实际应用中必须要考虑这种情况),这样所有映射到 cache m 的对象都会失效,怎么办,需要把 cache m 从 cache 中移除,这时候 cache 是 N-1 台,映射公式变成了 `hash(object) % (N-1)` ;

2. 由于访问加重,需要添加 cache ,这时候 cache 是 N+1 台,映射公式变成了 `hash(object) % (N+1)` ;

1 和 2 意味着什么?这意味着突然之间几乎所有的 cache 都失效了。对于服务器而言,这是一场灾难,洪水般的访问都会直接冲向后台服务器;

再来考虑第三个问题,由于硬件能力越来越强,你可能想让后面添加的节点多做点活,显然上面的 hash 算法也做不到。

有什么方法可以改变这个状况呢,这就是 consistent hashing…

# 一致性哈希

一致性哈希把哈希值想象成一个环,比如说在 0 ~ 2^32-1 这个范围内,然后将节点(名字、IP等)求哈希之后分不到环上。当有访问请求时,把请求信息求哈希之后,寻找小于该哈希值的下一个节点。

当有节点宕机的时候,请求会依次查找下一个节点,从而不让所有节点的缓存都失效。

当加入新节点的时候,只会影响一个区间内的请求,也不会影响其他区间。

如下图所示:

![](https://ws1.sinaimg.cn/large/006tKfTcly1ftvggxvlwfj315o0djq52.jpg)

# 虚拟节点

以上虽然解决了大部分问题,但是还有三个问题:

1. 节点有可能在分布不均匀。
2. 当一个节点因为负载过重宕机以后,所有请求会落到下一台主机,这样就有可能使下一台主机也宕机,这就是雪崩问题。
3. 不同主机处理能力不同如何配置不同的量。

这时候可以引入虚拟节点。原始的一致性哈希中,每个节点通过哈希之后在环上占有一个位置,可以通过对每个节点多次计算哈希来获得过个虚拟节点。

比如说,本来我们通过节点的 IP 来计算哈希

“`
hash(‘10.1.1.1’) => n1
hash(‘10.1.1.2’) => n2
hash(‘10.1.1.3’) => n3
“`

现在引入两倍的虚拟节点之后

“`
hash(‘10.1.1.1-1’) => n1-1
hash(‘10.1.1.1-2’) => n1-2
hash(‘10.1.1.2-1’) => n2-1
hash(‘10.1.1.2-2’) => n2-2
hash(‘10.1.1.3-1’) => n3-1
hash(‘10.1.1.3-2’) => n3-2
“`

如图所示

![](https://ws2.sinaimg.cn/large/006tKfTcly1ftvggbzovij315o0en40x.jpg)

引入虚拟节点之后:

1. 平衡性得到了直接改善
2. 主机是交替出现的,所以当一个节点宕机后,所有流量会随机分配给剩余节点
3. 可以给处理能力强的节点配置更多地虚拟节点。

最后,一致性哈希可以用跳表或者平衡二叉树来实现

参考文档:

1. https://blog.csdn.net/MBuger/article/details/76189561
2. https://www.cnblogs.com/23lalala/p/3588553.html
3. https://crossoverjie.top/2018/01/08/Consistent-Hash/