陈日志 发布的文章

# coding: utf8

# 使用说明:
# 1. 默认参数启动
#    python loggenerator.py
# 2. 自定义参数启动
#    KEY1=VALUE1 KEY2=VALUE2 python loggenerator.py
#    支持参数:
#       TYPE: 日志类型,默认json,可选txt
#       MAX: 日志总量,默认10000000
#       SPEED: 每秒生成日志量,默认15000
#       OUTPUT: 日志文件,默认logs/sum.log
#       MAXSIZE: 日志文件大小,超过此大小将会进行轮转
import os
import gzip
import shutil
from random import random
import time
import json


def logCompressor(filepath):
    offset = 0
    maxsize = os.getenv('MAXSIZE', 50 * 1024 * 1024)
    r = ''
    while True:
        n = yield r
        if not n:
            return
        size = os.path.getsize(filepath)
        if size >= maxsize:
            tmpfile = "%s%d_tmp" % (filepath, int(random() * 1e17))
            shutil.move(filepath, tmpfile)
            gzip.GzipFile(filename="", mode='wb', compresslevel=9, fileobj=open(
                "%s-%s.%d.log.gz" % (filepath.split('.')[0], time.strftime("%Y.%m.%d", time.localtime()), offset),
                'wb')).write(open(tmpfile, 'rb').read())
            os.remove(tmpfile)
            offset += 1
            r = '200'
        else:
            r = '0'


def logGenerator(c, maxline, speed, filepath, logtype):
    if not os.path.exists(os.path.dirname(filepath)):
        os.mkdir(os.path.dirname(filepath))
    fb = open(filepath, 'a+')
    c.send(None)
    n = 0
    while n < maxline:
        start = time.time()
        s = 0 # 控制速率
        while s < speed:
            if logtype == 'json':
                m = {
                    "level": "INFO",
                    "date": time.strftime("%Y.%m.%d %H:%M:%S", time.localtime()),
                    "message": "time:%s, nothing to do!" % time.time(),
                    "business": "logGenerator:19",
                    "service": "loggenerator",
                    "hostname": "fluentd1"
                }
                m = json.dumps(m)
            else:
                m = '%s [INFO] [logGenerator:19] - time:%s, nothing to do!' % (time.strftime("%Y.%m.%d %H:%M:%S", time.localtime()), time.time())
            fb.write(m + "\n")
            n += 1
            s += 1
            r = c.send(n)
            if r == '200':
                fb.close()
                fb = open(filepath, 'w+')
        end = time.time()
        if end - start < 1:
            # 写入耗时小于1秒,控制写入速度
            time.sleep(1 - (end - start))
    c.close()


if __name__ == "__main__":
    maxline = os.getenv('MAX', 10000000)
    speed = os.getenv('SPEED', 15000)
    logfile = os.getenv('OUTPUT', 'logs/sum.log')
    logtype = os.getenv('TYPE', 'json')
    c = logCompressor(logfile)
    logGenerator(c, maxline, speed, logfile, logtype)

nf_conntrack 调优

netfilter

netfilter是linux内在的一个软件框架,用来管理网络数据包。

netfilter提供了5个hook来进行管理网络包。如下图:

netfilter-hooks

  • PREROUTING, 所有包都会经过这个hook
  • LOCAL INPUT, 进入本机的包会经过这个hook
  • FORWARD, 不进入本机的包,做转发的包会经过这个hook
  • LOCAL OUTPUT, 从本机出去的包会经过这个hook
  • POSTROUTING, 所有出去的包都会经过这个hook

netfilter进行包的管理,则需要记录每个连接的状态信息。这就是nf_conntrack的工作了。

nf_conntrack

nf_conntrack是netfilter的一个子系统。它记录了每个连接的状态信息。

nf_conntrack记录的信息包括,源ip、端口,目标ip、端口,连接状态,协议等。

连接状态包含以下几种:

  • NEW, 新创建的连接,发起连接方发出包后,还没收到回包,都处理这种状态。
  • ESTABLISHED, 已建立的连接,发起连接后,收到回包,这时处理已连接状态。
  • RELATED, 与其他连接相关联,其他的连接与此连接有关联。如ftp的控制连接和数据连接。
  • INVALID, 非法的连接,比如包的行为不合法。

nf_conntrack需要保存这些信息在它自己的数据结构中。其数据结构如下:

connection-tracking-structure

它是一个开链的哈希表,链表是一个双向表。每个哈希节点称为一个bucket,计算出同样哈希值的连接放到链表里连起来。 每个节点记录了请求方向、响应方向的消息。

哈希表的大小,也就是说哈希表的节点数,由nf_conntrack_buckets配置。

nf_conntrack能跟踪的最大连接数由nf_conntrack_max配置。

- 阅读剩余部分 -

在使用 Kubernetes 中,经常会遇到一个奇怪的现象,当 Pod 与 CoreDNS 在同一个 Node 节点时,此 Pod 无法解析 DNS 记录。

我清楚的知道 Service 是使用 iptables NAT DNAT + SNAT 规则实现的,因此我排查的方向是抓包查看数据包的流向。

经过抓包分析,发现同一个 Node 节点访问 Service 只是对访问的流量进行了 DNAT,没有做 SNAT。但当时并没有找到解决方法,今天又遇到了一样的问题,通过爬文找到了这篇文章,所描述的问题现象一模一样,也附带了解决方法,并有详细的排查过程,读者可以去拜读一下。

我的 Kubernetes 集群是使用 kubeadm 部署的,因此解决方法是修改 kube-proxy 的 ConfigMap

[root@k8s-master01 ~]# kubectl -n kube-system get cm
NAME                                 DATA   AGE
coredns                              1      20d
extension-apiserver-authentication   6      20d
kube-flannel-cfg                     2      20d
kube-proxy                           2      20d
kubeadm-config                       2      20d
kubelet-config-1.18                  1      20d
metrics-server-config                1      19d
kubectl -n kube-system edit cm kube-proxy

# 修改 masqueradeAll: true

重启所有节点的 kube-proxy:

[root@k8s-master01 monitoring]# kubectl -n kube-system get pods
NAME                                      READY   STATUS    RESTARTS   AGE
coredns-66bff467f8-5v2j7                  1/1     Running   1          20d
coredns-66bff467f8-gtrdb                  1/1     Running   0          26m
etcd-k8s-master01                         1/1     Running   1          20d
kube-apiserver-k8s-master01               1/1     Running   1          20d
kube-controller-manager-k8s-master01      1/1     Running   2          20d
kube-flannel-ds-94n7x                     1/1     Running   1          20d
kube-flannel-ds-nf26k                     1/1     Running   1          20d
kube-flannel-ds-nl2dl                     1/1     Running   1          20d
kube-flannel-ds-thl9f                     1/1     Running   2          20d
kube-proxy-gpxhb                          1/1     Running   1          20d
kube-proxy-k2gjh                          1/1     Running   1          20d
kube-proxy-lp5jw                          1/1     Running   1          20d
kube-proxy-z67qm                          1/1     Running   1          20d
kube-scheduler-k8s-master01               1/1     Running   3          20d
metrics-server-v0.3.6-5d9877d6b6-bqq8d    2/2     Running   2          19d
nfs-client-provisioner-5fbf5f4476-fpmdq   1/1     Running   2          10d
# 删掉Pod,让其重建
kubectl -n kube-system delete pod kube-proxy-gpxhb kube-proxy-k2gjh kube-proxy-lp5jw kube-proxy-z67qm

至此,问题解决。

说起日志,我相信每个从业人员都不陌生。从分析问题、观测软件运行状态等都离不开日志,程序日志记录了程序的运行轨迹,往往还包含了一些关键数据、错误信息等。

因此在使用 Kubernetes 的过程中,对应的日志收集也是我们不得不考虑的问题,我们需要日志去了解集群内部的运行状况。

Kubernetes 中的日志收集 VS 传统日志收集

传统应用中,往往是在虚拟机或物理机直接运行程序,程序日志输出到本机的文件系统的某个目录中,或者是由rsyslog、systemd-journald等工具托管。在此类环境中,日志目录是相对固定不变的,因此收集日志只需要访问日志目录即可。

而在 Kubernetes 中,日志收集相比传统虚拟机、物理机方式要复杂得多。

首先,Kubernetes 日志的形式非常多样化,一个完整的日志系统至少需要收集以下三种:

  • Kubernetes 各组件的运行日志,如 kubelet、docker、kube-prpoxy 等
  • 业务容器的运行日志,如 tomcat、nginx 等
  • Kubernetes 的各种 Event,如 Pod 的创建、删除、错误等事件

其次,我们知道 Pod 是“用完即焚”的,当 Pod 的生命周期结束后,其日志也会被删除。但是这个时候我们仍然希望可以看到具体的日志,用于查看和分析业务的运行情况,以及帮助我们发现出容器异常的原因。

再次,Kubernetes 集群中的资源状态可能随时会发生变化,Pod 实例数量随时都可能会受 HPA(Horizontal Pod Autoscaler ) 的影响或管理员的操作而变化。我们并无法预知 Pod 会在哪个节点上运行,而且 Kubernetes 工作节点也无时无刻可能会宕机。

在一切都是动态的场景下,Kubernetes 日志系统在设计时就要考虑这些不确定因素

几种常见的 Kubernetes 日志收集架构

Kubernetes 集群本身其实并没有提供日志收集的解决方案,但依赖 Kubernetes 自身提供的各项能力,可以帮助我们解决日志收集的诉求。根据上面提到的三大基本日志需求,一般来说我们有如下有三种方案来做日志收集:

  • 应用程序自身将日志推送到日志系统
  • 在每个节点上运行一个 Agent 来采集节点级别的日志
  • 在一个 Pod 内使用一个 Sidecar 容器来收集应用日志

下面分别分析这三种方案的使用场景和优缺点。

- 阅读剩余部分 -