阅读 521

kubernetes通过kubectl无法查看日志问题处理过程

问题现象

输入:

kubectl logs --tail 200 -f -n xxx pod-name-xxxx.
复制代码

返回结果:

error: You must be logged in to the server (the server has asked for the client to provide credentials (get nodes))
复制代码

已知信息

  1. kubernetes版本:1.17.2
  2. 证书前不久已更新

排查过程

看到这个报错,很直观的信息,锁定在证书问题上。 我立即执行了kubectl get nodes 。能够返回节点信息。问题到这里,那我们基本锁定了应用是kube-apiserver与kubelet交互过程中出现了证书问题。 难道是证书过期?这个应该不太可能,前不久我才更新了master的证书!秉承严谨的态度,我还是登陆到master节点,输入命令

kubeadm alpha certs check-expiration
ls /etc/kubernetes
复制代码

image.png

image.png 没有问题,证书没有过期,kubelet的证书也自动轮转过。 为了确认证书没问题,我使用curl配合证书直接调用kubelet的接口。

curl -k --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key https://xxx.xxx.xx.xx:10250/metrics
复制代码

没有问题,能够正常返回监控度量数据。 既然证书本身没有问题,那大概就是apiserver使用上的问题了。再次抱着严谨的态度,输入命令:

ps -ef|grep kube-apiserver
ps -ef|grep kubelet
复制代码

image.png

image.png 没有问题,kube-apiserver和kubelet配置的证书路径都是正确的。 那这就很神奇了,既然配置目前看都没有问题,那我开始怀疑是不是kube-apiserver证书没生效? 索性使用重启大法。

## 选择一个kube-apiserver的pod
kubectl delete pod -n kube-system kube-apiserver-xxxxx
复制代码

再次执行

kubectl logs --tail 200 -f -n xxx pod-name-xxxx.
复制代码

未生效!!!惊呆了!不过没关系,毕竟我什么大场面没见过?!默默思考原因,kube-apiserver实际上是static pod,我执行删除pod,是否真的有重启吗?赶紧查看kube-apiserver的进程。 结果是,启动时间没有变化!那我再来重启一次,在各master节点上逐个执行

## 找到kube-apiserver的container id
docker ps |grep kube-apiserver
## 重启kube-apiserver
docker resart xxxxx
复制代码

再次执行

kubectl logs --tail 200 -f -n xxxx pod-name-xxxx
复制代码

完美!问题解决,但是不要着急,解决问题固然重要,但我们也要搞清楚整个操作过程中,与我们预期不一致的地方,以及确认真正的根因。

原因分析

在这里可能部分人会有疑惑几点?

疑惑点一

为什么明明证书更新过,查看节点信息都可以,查看pod日志怎么就不行呢?

  1. 首先我们知道,kubectl 执行命令本质上是跟apiserver进行交互的,这个过程是没有问题,apiserver验证你客户端在~/.kube/config里的信息。
  2. 查看节点信息,只是通过apiserver从etcd获取信息。而查看日志需要通过apiserver,apiserver找到你要查看日志pod所在节点的kubelet,并发送请求给kubelet,这个通信过程也需要进行证书验证。
  3. 另外普及一点,kubelet处理logs请求,实际上是将请求转发给CRI,CRI将会启动一个临时的streaming server服务,后续apiserver的信息获取是与streaming server进行交互。

image.png kubectl logs 和 kube exec 具体的详细实现都是类似的,大家可以通过这篇文章详细了解,cloud.tencent.com/developer/a…

疑惑点二

为什么执行过程中,使用kubectl删除pod不会重启kube-apiserver的进程呢?

默认情况下kubelet是会定时扫描/etc/kubernetes/manifests ,kubelet 会定期的扫描这个文件夹下的 YAML/JSON 文件来创建/删除静态 Pod

那kubelet应该全权管控static pod的。创建pod后,kubelet会将信息上传给apiserver,apiserver只是记录该条信息,controller-manager是没有相关的控制器来管理该资源的。实际上我们通过kubectl能够获取到该pod的信息,当你删除该pod后,kubelet会自身会实时检测到该static pod的信息,如果发现自身维护的static pod的信息,会将信息重新上传,并没有实际控制static pod的状态。

翻了下源码,kubelet会针对static pod在apiserver里维护一个mirro pod的信息。 再看pod更新的源码解读:

  1. kubelet监听到删除事件后,会判断是否为mirror pod
  2. 如果判断是mirror pod,则执行DeleteMirrorPod,执行承购,会标志已经删除过,然后会立马重新创建。
  3. 删除static pod仅仅是删除etcd信息,后续又会立马再常见static的信息。
// Create Mirror Pod for Static Pod if it doesn't already exist
    if kubepod.IsStaticPod(pod) {
        podFullName := kubecontainer.GetPodFullName(pod)
        deleted := false
        if mirrorPod != nil {
            if mirrorPod.DeletionTimestamp != nil || !kl.podManager.IsMirrorPodOf(mirrorPod, pod) {
                // The mirror pod is semantically different from the static pod. Remove
                // it. The mirror pod will get recreated later.
                klog.Warningf("Deleting mirror pod %q because it is outdated", format.Pod(mirrorPod))
                if err := kl.podManager.DeleteMirrorPod(podFullName); err != nil {
                    klog.Errorf("Failed deleting mirror pod %q: %v", format.Pod(mirrorPod), err)
                } else {
                    deleted = true
                }
            }
        }
        if mirrorPod == nil || deleted {
            node, err := kl.GetNode()
            if err != nil || node.DeletionTimestamp != nil {
                klog.V(4).Infof("No need to create a mirror pod, since node %q has been removed from the cluster", kl.nodeName)
            } else {
                klog.V(4).Infof("Creating a mirror pod for static pod %q", format.Pod(pod))
                if err := kl.podManager.CreateMirrorPod(pod); err != nil {
                    klog.Errorf("Failed creating a mirror pod for %q: %v", format.Pod(pod), err)
                }
            }
        }
    }
    ......
func (mc *basicMirrorClient) DeleteMirrorPod(podFullName string) error {
    if mc.apiserverClient == nil {
        return nil
    }
    name, namespace, err := kubecontainer.ParsePodFullName(podFullName)
    if err != nil {
        klog.Errorf("Failed to parse a pod full name %q", podFullName)
        return err
    }
    klog.V(2).Infof("Deleting a mirror pod %q", podFullName)
    // TODO(random-liu): Delete the mirror pod with uid precondition in mirror pod manager
    if err := mc.apiserverClient.CoreV1().Pods(namespace).Delete(name, metav1.NewDeleteOptions(0)); err != nil && !errors.IsNotFound(err) {
        klog.Errorf("Failed deleting a mirror pod %q: %v", podFullName, err)
    }
    return nil
}
......
func (mc *basicMirrorClient) CreateMirrorPod(pod *v1.Pod) error {
   if mc.apiserverClient == nil {
      return nil
   }
   // Make a copy of the pod.
   copyPod := *pod
   copyPod.Annotations = make(map[string]string)
   for k, v := range pod.Annotations {
      copyPod.Annotations[k] = v
   }
   hash := getPodHash(pod)
   // 标记注释信息,后续会根据该注释信息,判断是否为static pod
   copyPod.Annotations[kubetypes.ConfigMirrorAnnotationKey] = hash
   apiPod, err := mc.apiserverClient.CoreV1().Pods(copyPod.Namespace).Create(&copyPod)
   if err != nil && errors.IsAlreadyExists(err) {
      // Check if the existing pod is the same as the pod we want to create.
      if h, ok := apiPod.Annotations[kubetypes.ConfigMirrorAnnotationKey]; ok && h == hash {
         return nil
      }
   }
   return err
}
复制代码

image.png 另外也分享一遍关于kubelet创建pod的文章,blog.csdn.net/hahachenche…

疑惑点三

为什么需要重启才能生效呢?controller-manager没有重启,证书不是也生效了吗?

证书既然过期了,k8s调度没有问题,只是apiserver与kubelet之间通信的证书没有生效。难道是这是两个安全验证的机制不一样?带着这个问题我查了下。

controller-manager和schduler都是利用的client-go去调用apiserver,controller-manager和schduler是利用kubernetes的配置文件/etc/kubernetes/controller-manager.conf,/etc/kubernetes/scheduler.conf来进行安全通信。kubernetse的证书的文章可以参考:cloud.tencent.com/developer/a…

而apiserver调用kubelet,是通过读取apiserver初始化的配置缓存信息,获取kubelet的client证书,再进行https的安全通信。

这三个组件其实都没有自动加载证书的功能,如果我们更新了证书,如果没有重启进程的话,应该是都不能证书生效的。 而这个问题的实际情况是,controller-manager, scheduler很巧的是两个节点都发生过重启,再通过apiserver接口进行选举,也能选举成功。所以scheduler和controller-manager服务也不受影响。 所以最终整个k8s服务体现出来,查看信息,更新应用都没有问题,apiserver调用kubelet接口时才有问题。

根因确定

综合前面的分析,根因与我们预期的一致,apiserver进程未加载到最新的kubelet的客户端证书。

如何避免

  1. k8s的各核心组件的日志监控进行配置,能够及早发现问题
  2. 规范证书更新,根据这次的经验,形成合理的证书更新脚本,定期更新证书

结束语

文章中必然会有一些不严谨的地方,还希望大家包涵,大家吸取精华(如果有的话),去其糟粕。如果大家感兴趣可以关我的公众号:gungunxi。我的微信号:lcomedy2021

文章分类
后端
文章标签