本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

使用 Kubernetes 和 Docker 进行简单的领导者选举

概述

Kubernetes 简化了集群上运行服务的部署和操作管理。然而,它也简化了这些服务的开发。在这篇文章中,我们将看到如何使用 Kubernetes 轻松地在分布式应用程序中执行领导者选举。分布式应用程序通常为了可靠性和可伸缩性而复制服务任务,但通常需要指定其中一个副本作为领导者,负责所有副本之间的协调。

通常在领导者选举中,会确定一组候选者。这些候选者都竞相声明自己是领导者。其中一位候选者获胜并成为领导者。一旦选举获胜,领导者会持续“心跳”以更新其领导者位置,而其他候选者则定期尝试成为新的领导者。这确保了如果当前领导者因某种原因失败,可以迅速识别新的领导者。

实现领导者选举通常需要部署 ZooKeeper、etcd 或 Consul 等软件并将其用于共识,或者自行实现共识算法。我们将在下面看到,Kubernetes 使在应用程序中使用领导者选举的过程大大简化。

在 Kubernetes 中实现领导者选举

领导者选举的第一个要求是指定成为领导者的候选者集合。Kubernetes 已经使用 _Endpoints_ 来表示由服务组成的副本 Pod 集合,因此我们将重用这个相同的对象。(附注:您可能认为我们会使用 _ReplicationControllers_,但它们与特定的二进制文件绑定,通常即使您正在执行滚动更新,您也希望只有一个领导者)

为了执行领导者选举,我们使用所有 Kubernetes API 对象的两个属性:

  • ResourceVersions - 每个 API 对象都有一个唯一的 ResourceVersion,您可以使用这些版本对 Kubernetes 对象执行比较并交换操作
  • Annotations - 每个 API 对象都可以使用任意键/值对进行注释,供客户端使用。

有了这些原语,使用主选举的代码相对简单,您可以在此处找到它。让我们自己运行一下。

$ kubectl run leader-elector --image=gcr.io/google_containers/leader-elector:0.4 --replicas=3 -- --election=example

这将创建一个包含 3 个副本的领导者选举集

$ kubectl get pods
NAME                   READY     STATUS    RESTARTS   AGE
leader-elector-inmr1   1/1       Running   0          13s
leader-elector-qkq00   1/1       Running   0          13s
leader-elector-sgwcq   1/1       Running   0          13s

要查看哪个 Pod 被选为领导者,您可以访问其中一个 Pod 的日志,将您自己的 Pod 名称替换掉

${pod_name}, (e.g. leader-elector-inmr1 from the above)

$ kubectl logs -f ${name}
leader is (leader-pod-name)

... 另外,您也可以直接检查端点对象

'example' 是上述 kubectl run … 命令中的候选集名称。

$ kubectl get endpoints example -o yaml

现在要验证领导者选举是否确实有效,请在另一个终端中运行

$ kubectl delete pods (leader-pod-name)

这将删除现有的领导者。由于 Pod 集由副本控制器管理,因此一个新的 Pod 将替换被删除的 Pod,从而确保副本集的大小仍然是三个。通过领导者选举,这三个 Pod 中的一个被选为新的领导者,您应该会看到领导者故障转移到另一个 Pod。由于 Kubernetes 中的 Pod 在终止前有一个 _宽限期_,这可能需要 30-40 秒。

领导者选举容器提供了一个简单的 Web 服务器,可以在任何地址(例如 https://:4040)上提供服务。您可以通过删除现有领导者选举组并创建一个新的组来测试此功能,在新组中,您还需要向 leader-elector 镜像传递 --http=(host):(port) 参数。这会使集合中的每个成员通过 Webhook 提供有关领导者的信息。

# delete the old leader elector group
$ kubectl delete rc leader-elector

# create the new group, note the --http=localhost:4040 flag
$ kubectl run leader-elector --image=gcr.io/google_containers/leader-elector:0.4 --replicas=3 -- --election=example --http=0.0.0.0:4040

# create a proxy to your Kubernetes api server
$ kubectl proxy

然后您可以访问

https://:8001/api/v1/proxy/namespaces/default/pods/(leader-pod-name):4040/

您将看到

{"name":"(name-of-leader-here)"}

使用 Sidecar 的领导者选举

好的,太棒了,您可以进行领导者选举并通过 HTTP 找出领导者,但是如何在您自己的应用程序中使用它呢?这就是 sidecar 概念的用武之地。在 Kubernetes 中,Pod 由一个或多个容器组成。通常,这意味着您将 sidecar 容器添加到您的主应用程序中以组成一个 Pod。(有关此主题的更详细处理,请参阅我之前的博客文章)。

领导者选举容器可以作为 sidecar,您可以在自己的应用程序中使用它。Pod 中任何对当前主节点感兴趣的容器都可以简单地访问 https://:4040,它们将返回一个包含当前主节点名称的简单 JSON 对象。由于 Pod 中的所有容器共享相同的网络命名空间,因此无需服务发现!

例如,这是一个简单的 Node.js 应用程序,它连接到领导者选举 sidecar 并打印出它当前是否是主节点。领导者选举 sidecar 默认将其标识符设置为 hostname

var http = require('http');
// This will hold info about the current master
var master = {};

  // The web handler for our nodejs application
  var handleRequest = function(request, response) {
    response.writeHead(200);
    response.end("Master is " + master.name);
  };

  // A callback that is used for our outgoing client requests to the sidecar
  var cb = function(response) {
    var data = '';
    response.on('data', function(piece) { data = data + piece; });
    response.on('end', function() { master = JSON.parse(data); });
  };

  // Make an async request to the sidecar at https://:4040
  var updateMaster = function() {
    var req = http.get({host: 'localhost', path: '/', port: 4040}, cb);
    req.on('error', function(e) { console.log('problem with request: ' + e.message); });
    req.end();
  };

  / / Set up regular updates
  updateMaster();
  setInterval(updateMaster, 5000);

  // set up the web server
  var www = http.createServer(handleRequest);
  www.listen(8080);

当然,您可以从您选择的任何支持 HTTP 和 JSON 的语言中使用此 sidecar。

结论

希望我已经向您展示了使用 Kubernetes 为分布式应用程序构建领导者选举是多么容易。在未来的文章中,我们将向您展示 Kubernetes 如何让构建分布式系统变得更加简单。与此同时,请前往Google Container Enginekubernetes.io开始使用 Kubernetes。