(kubeneters)Pod日志持久化

背景

有一天发现 kubesphere 的三个组件(ks-console、ks-apiserver、ks-controller-manager)挂了,但是没有任何日志留下,虽然通过一系列分析,可以推断是 ks-install 升级 kubesphere ,但是还是很难有确凿的证据来证明这一点,伴随着颅内不断响起 “敬畏生产” 的口号,由此便有了本文。
注意:笔者需要的是 pod 或 容器 级别日志,即 pod 或 容器 的标准输出与错误输出,而并非是应用日志,应用日志的博客很多,本文中也有答案。

pod 日志存储

pod 储存位置: /var/log/pod目录名/具体日志文件
pod 目录名:namespace_podName_pod-uid
具体日志名:k8s 对于日志有滚动分片机制,打包机制,可以基于时间和文件大小两个维度,对于不同pod kubelet有不同决策,具体可以改 /var/lib/kubelet/config.toml

k8s 日志存储机制

k8s 的 pod 日志,随着 pod 生命周期的结束而自动删除,生命周期结束后30s 是 k8s 准确来说应该是 kubelet 在做各种资源的删除,因为 pod 都是算临时的,一般创建 pod 都是基于 development ,因为 development 可以灵活的动态拓展副本个数。

踩坑记录

修改 kubelet 配置

k8s 的 pod 日志是由 kubelet 进行管理,所以理论上修改 kubelet 的配置可以移动日志的存放地址,也可以修改 kubelet 配置 或者是 container runtime 令其不触发日志删除策略。然后操作过后重启kubelet。
但是实际修改配置都未曾生效: 修改容器日志存储地址 与 修改日志存储策略。

持久卷

存储卷有三种,emptyDir,hostPath,nfs。

  • emptyDir卷 在 Pod 创建时自动创建 目录 或者 文件,在 Pod 销毁时自动清除。它位于 Pod 所在的节点上,可供 Pod 内的所有容器共享。pod 销毁时自动清除 这也就是 k8s 的日志机制,只是 pod 内容器共享这些数据。这 不是我们需要的。
  • hostPath卷就是 对应的目录或文件 挂载到了宿主机,然后 pod 内的所有的 容器都能共享,并且能持久化到宿主机,可以摆脱 pod 生命周期的约束。但是 pod 或 容器 的标准输出并不在其中,我们挂载出来的只能是应用日志,具体自己可以在启动命令中做日志重定向,或者自己代码中处理日志指向,再选择性挂载到宿主机,主要是约束好日志存放地址。并且这种方式只能在 pod 所在 node 中,如果需要查pod 日志,需要一个一个 node 找。
  • nfs 基于网络,指定一台 nfs 服务器即可,其他 Node 还需要安装 nfs-common ,才能用,与hostPath的区别就在于不用一个一个 Node 寻找 log。但是还是只能将容器内文件挂载出来。而容器标准输出与错误输出并不在容器内部…

踩坑记录

  1. 修改配置一定要重启对应的组件,例如 kubelet。
  2. 尽量不要修改容器运行时,目前 k8s 约定使用 containerd ,新版本不再兼容 docker ,如果硬要修改为 docker ,大概率会和我一样,重装集群,成本很大,切勿轻易尝试。
  3. 使用nfs下载依赖后要添加 /etc/exports 这里的挂载目录目录要和 pv 保持一致,pv 和 pvc 的storageClassName 一定要指定,且保持一致,不然绑定不上。记得刷新 exports 。
  4. pv、pvc 所在 namspace需要和 使用该持久卷的 deployment 保持一致。若无指定则在 default namespace 。
  5. 多个pvc同时挂载一个目录,改 etc/exports 加一个 no_root_squash。
  6. 日志源文件夹,目标文件夹,两个文件夹的子目录,都尽量给777权限,不然几乎必踩坑。

可行方案

ELK

可以说是日志收集的老方案了,但是也是经过了各种体量锤炼和打磨,但是我司规模有限,需要考虑各方面成本,便没有使用。
做法就是所有的 Node 都部署 filebeat 收集日志,然后发送给 logstash 进行数据清洗和采集,最后存到 es 持久化。k 指的是 kibana ,界面化监控。

利用 pod 生命周期

本身这个需求,不使用 ELK 完成 容器标准输出和错误输出,这件事情就比较罕见,因为使用集群的公司一般体量不小,一般会选择 ELK 这种豪华的阵容。
利用生命周期 preStop,是一个突发奇想,搜了一下,竟然还真有这个钩子。

关键步骤
将 源pod日志文件目录,和目标日志文件目录,使用 nfs 挂载进 pod ,因为 pod 的钩子只能操作容器内部,在pod将要停止时,就将其 rsync /var/log/pods/ /var/log/podsbak。

完整方案:

  1. 集群内所有节点安装 nfs 通过用服务。

  2. nfs 服务器创建 /etc/exports,选中访问的网段范围及挂载目录如:
    /var/log 192.168.31.203/24(rw,sync,no_subtree_check)

  3. 刷新 nfs 配置
    sudo exportfs -r

  4. 事先创建 /var/log/podsbak ,对于 /var/log/pods 尽量给高权限,最好777,不然可能钩子函数无权限执行,且没有日志排查。

  5. 创建 pv

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: nfs-log-service-pv
    spec:
      capacity:
        storage: 2Gi
      accessModes:
        - ReadWriteMany
      nfs:
        path: /var/log/
        server: 192.168.31.203
      storageClassName: "nfs-log-service"
    kubectl apply -f nfs-pv.yaml
    

    注意:pv 没有命名空间,但是一个 pv 只能被一个 pvc 绑定,因此 pv 一定要指定 storageClassName 不然无法与 pvc Bound,也正因此 每个命名空间都需要创建pv 和 pvc,pvc 的 storage <= pv 的 storage。

  6. 创建 pvc

    	apiVersion: v1
    	kind: PersistentVolumeClaim
    	metadata:
    	  name: pvc-service
    	  namespace: service
    	spec:
    	  accessModes:
    	    - ReadWriteMany
    	  resources:
    	    requests:
    	      storage: 2Gi
    	  storageClassName: "nfs-service"
    	kubectl apply -f nfs-pvc.yaml
    
  7. 编写 deployment 的 yaml

    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: testimage
      namespace: service
      annotations:
        deployment.kubernetes.io/revision: '16'
    spec:
      replicas: 1
      selector:
        matchLabels:
          infrastructure/service: testimage
      template:
        metadata:
          creationTimestamp: null
          labels:
            infrastructure/service: testimage
        spec:
          volumes:
            - name: nfs-volume
              persistentVolumeClaim:
                claimName: pvc-ingress-nginx
          containers:
            - name: testimage
              image: '192.168.31.96:5000/testimage'
              resources: {}
              volumeMounts:
                - name: nfs-volume
                  mountPath: /var/log/
              lifecycle:
                preStop:
                  exec:
                    command:
                      - sh
                      - '-c'
                      - cp -r /var/log/pods/ /var/log/podsbak/
              terminationMessagePath: /dev/termination-log
              terminationMessagePolicy: File
              imagePullPolicy: Always
          restartPolicy: Always
          terminationGracePeriodSeconds: 30
          dnsPolicy: ClusterFirst
          securityContext: {}
          schedulerName: default-scheduler
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxUnavailable: 25%
          maxSurge: 25%
      revisionHistoryLimit: 10
      progressDeadlineSeconds: 600
    
  8. 如果镜像包含 nginx 需在 nfs服务器节点生成 /var/log/nginx/access.log 以及 /var/log/nginx/error.log 并且给权限777 。不然会因为无法打开这2个文件报错,需要打开这两文件是因为防止 nginx 无法启动,需要记录 nginx 的标准输出和错误输出。

  9. 如果觉得全量传输太恶心了,可以用 rsync 做增量上传 和 断点续传,只需要 nfs 服务器节点有 rsync 即可。

其他方案

上述方案粗暴但简单有效,每个每次 pod 销毁 都会将全部日志文件复制一遍。可能 io 耗时间 损性能,且磁盘占用可能撑满持久卷,但是目前日志很小,一般都是12KB,最多的时候能达到 700KB,目前笔者用的是 cp ,传输速度方面读者可以选择 rsync ,磁盘占用方面可以开个 cron 定时任务来清理日志。以下还有两种方案可供参考

自己实现

可以实现类似于 filebeat 做日志收集,然后发到自己服务器上,数据清洗与否都不重要,起码日志在,有据可依。这种做法不用知道机器的 privatekey 或者 password ,也可以用 “拉” 的方式,一台机器定时去拉取日志文件。
这两种做法要考虑的就是定时间隙,以及密钥泄露的风险,前者部署麻烦,但是没有暴露密钥的风险,因为 “推” 是从服务器内部对外部接口的操作,而拉,是进入服务器复制文件的操作。
两种做法最大的问题就是增量的传输,而且有不稳定的网络io消耗,额外增加了运维和开发的成本。

TerminationMessagePath

k8s 自带的一种配置方式,可以将 pod 崩溃的原因记录到指定的文件中,目前没有采用这种方式的原因是:担心日志不全面,kubelet 认为重要的崩溃原因或者堆栈,可能不足以或者不适合我们排查问题。其次就是要在容器内获取 pod 名、命名空间 等一系列信息,无疑增加了复杂度。何况默认是将文件生成到每台被调度到的 Node 上,所以翻找这些 log 时间复杂度是 O(N),如果需要所有日志文件都在一台服务器,那么又回到了我们目前的方案 NFS ,而相比之下,terminationMessagePath 最大的好处就是增量复制,而增量复制我们可以在我们目前方案使用 rsync 实现。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值