乐趣区

关于k8s:信也容器云揭秘04K8S集群稳定性保障

一、摘要

信也容器云平台基于 Kubernetes 和 Docker 开发,自 2018 年底上线以来,总共保护了 8 套 k8s 集群,生产接入超过 850 个利用,全环境实例数达到 9000。作为一家金融科技公司,服务稳定性必然是逃不开的话题,本次分享就以集群部署、高可用控制器、监控智能化解决三个局部,为大家介绍容器云在稳定性保障方面的一些教训和思考。

二、集群部署

以生产环境为例,咱们有两个机房,每个机房部署了两套 K8S 集群,同一机房的集群节点数相等。实例通过容器云平台公布,首先依据利用类型和重要水平抉择部署的机房,而后均匀分布于机房内的各个集群,同一集群的实例由 kube-scheduler 平均调度到各个节点。任何一个节点或者集群挂掉,其余节点和集群上的实例仍旧失常运行,不会影响服务的可用性,最大水平的保障了物理上的高可用。部署架构图如下:

因为存在局部利用发复数实例的状况,这些实例不能相对平均分配,长时间运行下来,会呈现同一机房的某个集群实例数略大于另一个集群的景象,如果不加干预,此集群实例数会越来越多,导致集群整体内存应用偏高,升高集群的稳定性,到前面更会因为集群内存不足而无奈调度。

基于以上起因,咱们开发了一个定时工作,通过 Prometheus 抓取集群的内存应用数据,判断出负载较低的集群,为该集群设置较高优先级。如此,下一轮的调度如果呈现复数的状况,多进去的一个实例将调度到高优先级的集群。这里算法的根本逻辑为,先判断利用已有实例的集群散布状况,待发布的实例优先调度到已有实例数较少的集群,如果存在多个集群已有实例数相等,优先选择高优先级(负载低)的集群,此算法对多于两个集群的状况同样无效。生产实践下来,该措施使得各个集群的实例数和负载处在同一程度,最大化利用了集群资源,无效进步了集群稳定性。

三、高可用控制器

为满足不同的业务场景和公布模式,以及平安上的固定 IP 需要,信也容器云平台抉择了以 Pod 为粒度进行实例部署,而没有应用常见的 Deployment 形式。因为 K8S 对单个 Pod 没有提供自愈机制,咱们在容器云平台中应用 Java 自研了一个高可用控制器,容器和物理机宕机后实例会主动复原。

在具体介绍高可用控制器之前,咱们先来回顾一下就绪探针(Readiness Probe)的概念。就绪探针用于判断容器是否启动实现,即 Pod 的 Condition 是否为 Ready,如果探测后果不胜利,则会将 Pod 从 Endpoint 中移除,直至下次判断胜利,再将 Pod 挂回到 Endpoint 上。

基于以上原理,咱们在容器里增加了一个衰弱检测接口作为就绪探针,容器云平台监听 Service 上面的 Endpoints 事件,一旦发现 Pod 状态变为 Not Ready,就会对 Pod 做一个删除和重新部署的操作,操作前后实例 IP 不变。如下图所示:

当高可用控制器检测到 Pod 状态变为 Ready 时,则会依据实例宕机前的流量状态做上线或疏忽解决。如下图所示:

控制器对 Endpoints 采取监听和轮询两种形式,避免监听时出现异常,确保所有实例状态变更后失去解决。示例代码如下:

public void startEventTrigger() {Config config = new ConfigBuilder().withMasterUrl(apiServer).build();
    KubernetesClient client = new DefaultKubernetesClient(config);

    client.endpoints().watch(new Watcher<Endpoints>() {
        @Override
        public void eventReceived(Action action, Endpoints endpoints) {for (EndpointSubset endpointSubset : endpoints.getSubsets()) {for (EndpointAddress endpointAddress : endpointSubset.getNotReadyAddresses()) {processNotReadyAddress(endpoints.getMetadata().getName(), endpointAddress);
                }

                for (EndpointAddress endpointAddress : endpointSubset.getAddresses()) {processReadyAddress(endpoints.getMetadata().getName(), endpointAddress);
                }
            }
        }

        @Override
        public void onClose(KubernetesClientException e) {log.error("事件监听时与 apiserver 产生链接中断, err=" + e.getMessage(), e);
        }
    });
}

public void startEventPolling() {Config config = new ConfigBuilder().withMasterUrl(apiServer).build();
    KubernetesClient client = new DefaultKubernetesClient(config);
    
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    scheduler.scheduleAtFixedRate(() -> {EndpointsList endpointsList = client.endpoints().list();
        for (Endpoints endpoints : endpointsList.getItems()) {for (EndpointSubset endpointSubset : endpoints.getSubsets()) {for (EndpointAddress endpointAddress : endpointSubset.getNotReadyAddresses()) {processNotReadyAddress(endpoints.getMetadata().getName(), endpointAddress);
                }

                for (EndpointAddress endpointAddress : endpointSubset.getAddresses()) {processReadyAddress(endpoints.getMetadata().getName(), endpointAddress);
                }
            }
        }
    }, 120, 120, TimeUnit.SECONDS);
}

依据下面的代码能够发现,咱们对 Not Ready 事件的解决放在了 processNotReadyAddress 办法中,而对 Ready 事件的解决则放到了 processReadyAddress 办法里,这两个办法蕴含了较多的疏忽逻辑,如实例有其余工作正在执行,实例处在静默期(用户刚操作完实例),利用未开启高可用,一段时间内的反复事件,等等,具体细节就不在此处开展了。

须要留神的是,processNotReadyAddress 办法里有一段重要的非凡逻辑,因为 Kubelet 进行后也会产生 Not Ready 事件,所以咱们会再次调用 Pod 的衰弱检测接口,避免因为 Kubelet 进行造成的误迁徙。当二次检测失败之后,咱们才对 Pod 做删除和重新部署操作。

这套高可用机制已在生产环境实现了屡次实例、物理机宕机的主动复原,体现良好,无效保障了集群的稳固运行。

四、监控智能化解决

下面的高可用控制器利用场景次要是实例、物理机宕机,这是一种预先解决形式,或者说是一种补救行为,那么有没有一种形式能让咱们事先感知到实例的异样从而在实例宕机前进行相应的解决呢?答案是有的,监控智能化解决,容器云平台联结谛听调用链监控平台、实时数据 Flink 计算平台共同完成了这一指标。架构如下:

简略介绍一下上图的流程,用户首先须要在谛听调用链监控平台配置利用的监控告警规定,监控的类型很多,涵盖了 JVM 监控、异样接口调用、主机监控、错误码、数据库指标等,规定能够配置多条,依据监控类型能够配置为堆内存使用量大于肯定值、每分钟异样数超过肯定值等简单组合,同时很重要的一点,配置智能触发行为,让容器重启或者下线。

接下来谛听通过 Cat、Prometheus 等工具从利用的容器中获取实时的监控数据,并将数据写入公司的 Kafka 集群,由实时数据团队的 Flink 计算平台生产数据进行实时运算,如果运算后果满足用户配置的监控告警规定,则将一个事件写入 kafka 集群,音讯被播送给谛听和容器云平台,谛听负责发送告警,容器云平台负责实例的重启和下线。

容器云示例代码如下,实例的解决逻辑在 processDitingEvent 办法里:

public void startDitingEventListener() {Properties props = new Properties();
    props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers);
    props.put(ConsumerConfig.GROUP_ID_CONFIG, "stargate");
    props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

    Consumer<String, String> ditingEventConsumer = new KafkaConsumer<>(props);
    ditingEventConsumer.subscribe(Collections.singletonList("diting_alerts"));

    ExecutorService ditingEventExecutor = Executors.newFixedThreadPool(1);
    ditingEventExecutor.submit(() -> {while (true) {ConsumerRecords<String, String> records = ditingEventConsumer.poll(1000);
            for (ConsumerRecord<String, String> record : records) {processDitingEvent(record.value());
            }
        }
    });
}

通过监控智能化解决机制,实例一旦出现异常,用户马上就能收到告警,同时实例做下线或重启操作,提前躲避危险,大大提高了服务的稳定性。

五、结语

很快乐为大家带来这次分享,置信通过上面对集群部署、高可用控制器、监控智能化解决的介绍,大家对信也容器云平台的稳定性保障机制有了肯定的理解。在这里打个小广告,信也容器云技术揭秘系列继续更新中,想理解更多容器云技术的小伙伴能够翻看一下历史文章,祝大家收货满满。

六、回馈社区

目前咱们已在 github 上开源了容器云平台的外围组件 Stargate、Dockeryard、Pauth、Atlas 以及 Macvlan 插件,地址如下:

https://github.com/ppdaicorp

大家感兴趣的话能够扫描下方二维码退出微信群进行交换。


作者介绍

小明,信也科技基础架构研发工程师,容器云团队成员。

退出移动版