美文网首页k8s in action实践笔记
10.4 在Statefulset中发现伙伴节点

10.4 在Statefulset中发现伙伴节点

作者: 众神开挂 | 来源:发表于2021-07-31 21:50 被阅读0次

    10.4 在Statefulset中发现伙伴节点

    我们仍然需要弄清楚一件很重要的事情。集群应用中很重要的一个需求是伙伴节点彼此能发现——这样才可以找到集群中的其他成员。一个Statefulset中的成员需要很容易地找到其他的所有成员。当然它可以通过与API服务器通信来获取,但是Kubernetes的一个目标是设计功能来帮助应用完全感觉不到Kubernetes的存在。因此让应用与API服务器通信的设计是不允许的。

    那如何使得一个pod可以不通过API与其他伙伴通信呢?是否有已知的广泛存在的技术来帮助你达到目的呢?那使用域名系统(DNS)如何?这依赖于你对DNS系统有多熟悉,你可能理解什么是A、CNAME或MX记录的用处是什么。DNS记录里还有其他一些不是那么知名的类型,SRV记录就是其中的一个。

    介绍SRV记录

    SRV记录用来指向提供指定服务的服务器的主机名和端口号。Kubernetes通过一个headless service创建SRV记录来指向pod的主机名。

    可以在一个临时pod里运行DNS查询工具——dig命令,列出你的有状态pod的SRV记录。示例命令如下:

    $ kubectl run -it srvlookup --image=tutum/dnsutils --rm --restart=Never -- dig SRV kubia.custom.svc.cluster.local
    

    上面的命令运行一个名为srvlookup的一次性pod(--restart=Never),它会关联控制台(-it)并且在终止后立即删除(--rm)。这个pod依据tutum/dnsutils镜像启动单独的容器,然后运行下面的命令:

    dig SRV kubia.default.svc.cluster.local
    

    下面的代码清单显示了这个命令的输出结果。

    代码清单10.8 列出你的headless Service的DNS SRV记录

    ....
    ;; ANSWER SECTION:
    kubia.custom.svc.cluster.local. 30 IN   SRV     0 50 80 kubia-1.kubia.custom.svc.cluster.local.
    kubia.custom.svc.cluster.local. 30 IN   SRV     0 50 80 kubia-0.kubia.custom.svc.cluster.local.
    
    ;; ADDITIONAL SECTION:
    kubia-1.kubia.custom.svc.cluster.local. 30 IN A 172.18.0.2
    kubia-0.kubia.custom.svc.cluster.local. 30 IN A 172.18.0.4
    

    上面的ANSWER SECTION显示了两条指向后台headless service的SRV记录。同时如ADDITIONAL SECTION所示,每个pod都拥有独自的一条记录。

    当一个pod要获取一个Statefulset里的其他pod列表时,你需要做的就是触发一次SRV DNS查询。例如,在Node.js中查询命令为:

    dns.resolveSrv("kubia.custom.svc.cluster.local", callBackFunction);
    

    可以在你的应用中使用上述命令让每个pod发现它的伙伴pod。

    注意 返回的SRV记录顺序是随机的,因为它们拥有相同的优先级。所以不要期望总是看到kubia-0会排在kubia-1前面。

    10.4.1 通过DNS实现伙伴间彼此发现

    原始的数据存储服务还不是集群级别的,每个数据存储节点都是完全独立于其他节点的——它们彼此之间没有通信。下一步你要做的就是让它们彼此通信。

    客户端通过kubia-public Service连接你的数据存储服务,并且会到达集群里随机的一个节点。集群可以存储多条数据项,但是客户端当前却不能看到所有的数据项。因为服务把请求随机地送达一个pod,所以若客户端想获取所有pod的数据,必须发送很多次请求,一直到它的请求发送到所有的pod为止。

    可以通过让节点返回所有集群节点数据的方式来改进这个行为。为了达到目的,节点需要能找到它所有的伙伴节点。可以使用之前学习到的Statefulset和SRV记录来实现这个功能。

    可以如下面的代码清单所示修改你的应用源码(完整的代码在本书的代码附件中,这里仅展示其中重要的一段)。

    代码清单10.9 在简单应用中发现伙伴节点:kubia-pet-peers-image/app.js

    const http = require('http');
    const os = require('os');
    const fs = require('fs');
    const dns = require('dns');
    
    const dataFile = "/var/data/kubia.txt";
    const serviceName = "kubia.default.svc.cluster.local";
    const port = 8080;
    
    
    function fileExists(file) {
      try {
        fs.statSync(file);
        return true;
      } catch (e) {
        return false;
      }
    }
    
    function httpGet(reqOptions, callback) {
      return http.get(reqOptions, function(response) {
        var body = '';
        response.on('data', function(d) { body += d; });
        response.on('end', function() { callback(body); });
      }).on('error', function(e) {
        callback("Error: " + e.message);
      });
    }
    
    var handler = function(request, response) {
      if (request.method == 'POST') {
        var file = fs.createWriteStream(dataFile);
        file.on('open', function (fd) {
          request.pipe(file);
          response.writeHead(200);
          response.end("Data stored on pod " + os.hostname() + "\n");
        });
      } else {
        response.writeHead(200);
        if (request.url == '/data') {
          var data = fileExists(dataFile) ? fs.readFileSync(dataFile, 'utf8') : "No data posted yet";
          response.end(data);
        } else {
          response.write("You've hit " + os.hostname() + "\n");
          response.write("Data stored in the cluster:\n");
          dns.resolveSrv(serviceName, function (err, addresses) {  # 通过DNS查询SRV记录
            if (err) {
              response.end("Could not look up DNS SRV records: " + err);
              return;
            }
            var numResponses = 0;
            if (addresses.length == 0) {
              response.end("No peers discovered.");
            } else {
              addresses.forEach(function (item) { #与每个SRV记录的pod通信
                var requestOptions = {
                  host: item.name,
                  port: port,
                  path: '/data'
                };
                httpGet(requestOptions, function (returnedData) {
                  numResponses++;
                  response.write("- " + item.name + ": " + returnedData + "\n");
                  if (numResponses == addresses.length) {
                    response.end();
                  }
                });
              });
            }
          });
        }
      }
    };
    
    var www = http.createServer(handler);
    www.listen(port);
    

    图10.12展示了一个GET请求到达你的应用后的处理过程。首先收到请求的服务器会触发一次headless kubia服务的SRV记录查询,然后发送GET请求到服务背后的每一个pod(也会发送给自己,虽说没有必要,这里只是为了保证代码简单易懂),然后返回所有节点和它们的数据信息的列表。

    [图片上传失败...(image-7c7606-1627739427880)]

    图10.12 简单的分布式数据存储服务的操作流程

    包含最新版本内容的应用对应的容器镜像链接为:docker.io/luksa/kubia-petpeers

    10.4.2 更新Statefulset

    现在你的Statefulset已经运行起来,那让我们看一下如何更新它的pod模板,让它使用新的镜像。同时你也会修改副本数为3。通常会使用kubectl edit命令来更新Statefulset(另一个选择是patch命令)。

    $ kubectl edit statefulset kubia
    

    上面的命令会使用默认的编辑器打开Statefulset的定义。在定义中,修改 spec.replicas 为3,修改 spec.template.spec.containers.image 属性指向新的镜像(使用luksa/kubia-pet-peers替换 luksa/kubia-pet)。然后保存文件并退出,Statefulset就会更新。

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: kubia
    spec:
      serviceName: kubia
      replicas: 3
      selector:
        matchLabels:
          app: kubia
      template:
        metadata:
          labels:
            app: kubia
        spec:
          containers:
          - name: kubia
            image: luksa/kubia-pet-peers
            ports:
            - name: http
              containerPort: 8080
            volumeMounts:
            - name: data
              mountPath: /var/data
      volumeClaimTemplates:
      - metadata:
          name: data
        spec:
          resources:
            requests:
              storage: 1Mi
          accessModes:
          - ReadWriteOnce
    

    之前Statefulset有两个副本,现在应该可以看到一个新的名叫kubia-2的副本启动了。通过下面的代码列出pod来确认:

    $ kubectl get po
    

    新的pod实例会使用新的镜像运行,那已经存在的两个副本呢?Statefulset支持与 Deployment和DaemonSet一样的滚动升级。 Statefulset会依据新的模板重新调度启动它们。

    $ watch kubectl get pod
    

    10.4.3 尝试集群数据存储

    当两个pod都启动后,即可测试你的崭新的新石器时代的数据存储是否按预期一样工作了。如下面的代码清单所示,发送一些请求到集群。

    代码清单10.10 通过service往集群数据存储中写入数据

    $ curl -X POST -d "The sun is shining" \localhost:8001/api/v1/namespaces/custom/services/kubia-public/proxy/$ curl -X POST -d "The weather is sweet" \localhost:8001/api/v1/namespaces/custom/services/kubia-public/proxy
    

    现在,读取存储的数据,如下面的代码清单所示。

    代码清单10.11 从数据存储中读取数据

    $ curl localhost:8001/api/v1/namespaces/custom/services/kubia-public/proxy/
    

    非常棒!当一个客户端请求到达集群中任意一个节点后,它会发现它的所有伙伴节点,然后通过它们收集数据,然后把收集到的所有数据返回给客户端。即使你扩容或缩容Statefulset,服务于客户端请求的pod都会找到所有的伙伴节点。

    这个应用本身也许没太多用处,但笔者希望你觉得这是一种有趣的方式,一个多副本Statefulset应用的实例如何发现它的伙伴,并且随需求做到横向扩展。

    相关文章

      网友评论

        本文标题:10.4 在Statefulset中发现伙伴节点

        本文链接:https://www.haomeiwen.com/subject/ilvnmltx.html