当前位置: 首页 > news >正文

构建企业级Docker日志驱动:将容器日志无缝发送到腾讯云CLS

源码地址:https://github.com/k8scat/docker-log-driver-tencent-cls

在现代云原生架构中,容器化应用已经成为主流部署方式。随着容器数量的快速增长,如何高效地收集、存储和分析容器日志成为了一个关键挑战。传统的日志收集方式往往存在以下问题:

  • 日志分散在各个容器中,难以统一管理
  • 缺乏结构化的日志格式,不利于后续分析
  • 日志存储成本高,且难以进行实时查询
  • 缺乏统一的日志检索和监控机制

为了解决这些问题,我们开发了一个专门的 Docker 日志驱动,将容器日志直接发送到腾讯云的 CLS(Cloud Log Service)日志服务。这个驱动实现了与 Docker 日志系统的深度集成,提供了高性能、可靠的日志传输能力。

技术架构设计

整体架构

该日志驱动采用了模块化的设计架构,主要包含以下几个核心组件:

  1. Driver 模块:负责管理日志流和容器生命周期
  2. Logger 模块:处理日志格式化和发送逻辑
  3. Client 模块:封装腾讯云 CLS SDK 的调用
  4. Server 模块:提供 Docker 插件接口服务
  5. 配置管理模块:处理各种配置参数的解析和验证

这种分层架构确保了代码的可维护性和可扩展性,每个模块都有明确的职责边界。

核心数据结构

项目定义了多个关键的数据结构来支持日志驱动的功能:

type Driver struct {streams          map[string]*logStreamcontainerStreams map[string]*logStreammu               sync.RWMutexfs               fileSystemnewTencentCLSLogger newTencentCLSLoggerFuncprocessLogs      func(stream *logStream)logger           *zap.Logger
}type TencentCLSLogger struct {client            clientformatter         *messageFormattercfg               *loggerConfigbuffer            chan stringmu                sync.MutexpartialLogsBuffer *partialLogBufferwg                sync.WaitGroupclosed            chan struct{}logger            *zap.Logger
}

这些数据结构的设计充分考虑了并发安全性和资源管理,确保了在高并发场景下的稳定运行。

核心功能实现

日志流管理

日志驱动的核心功能是管理容器的日志流。每个容器启动时,驱动会创建一个独立的日志流来处理该容器的所有日志输出:

func (d *Driver) StartLogging(streamPath string, containerDetails *ContainerDetails) (stream *logStream, err error) {d.logger.Info("starting logging", zap.String("stream_path", streamPath), zap.Any("container_details", containerDetails))d.mu.RLock()if _, ok := d.streams[streamPath]; ok {d.mu.RUnlock()return nil, errors.New("already logging")}d.mu.RUnlock()name := "container:" + containerDetails.ContainerNamestream = &logStream{streamPath:       streamPath,containerDetails: containerDetails,logger:           d.logger.Named(name),fs:               d.fs,stop:             make(chan struct{}),}// 初始化日志流if err := d.initializeStream(stream); err != nil {return nil, err}// 启动日志处理协程go d.processLogs(stream)return stream, nil
}

这种设计确保了每个容器的日志都能被独立处理,避免了不同容器之间的日志混淆。

日志处理流程

日志处理采用了异步非阻塞的设计模式,确保不会影响容器的正常运行:

func (d *Driver) defaultProcessLogs(stream *logStream, processedNotifier chan<- struct{}) {defer func() {if err := stream.Close(); err != nil {d.logger.Error("failed to close stream", zap.Error(err))}}()logs := NewLogs(stream)for logs.Next() {select {case <-stream.stop:returndefault:}entry := logs.Log()log := &logger.Message{Line:         entry.GetLine(),Source:       entry.GetSource(),Timestamp:    time.Unix(0, entry.GetTimeNano()),PLogMetaData: partialLog,}// 发送到腾讯云 CLSif err := stream.tencentCLSLogger.Log(log); err != nil {stream.logger.Error("failed to log to tencent cls logger", zap.Error(err))}// 可选:保存到本地文件if stream.jsonLogger != nil {if err := stream.jsonLogger.Log(log); err != nil {stream.logger.Error("failed to log to json logger", zap.Error(err))}}}
}

这种设计确保了日志处理的可靠性和性能,即使在网络不稳定的情况下也能保证日志的完整性。

日志格式化与模板

驱动支持灵活的日志格式配置,用户可以通过模板来自定义日志的输出格式:

type messageFormatter struct {template         *fasttemplate.TemplatecontainerDetails *ContainerDetailsattrs            map[string]string
}func (f *messageFormatter) tagFunc(msg *logger.Message) fasttemplate.TagFunc {return func(w io.Writer, tag string) (int, error) {switch tag {case "log":return w.Write(msg.Line)case "timestamp":return w.Write([]byte(msg.Timestamp.UTC().Format(time.RFC3339)))case "container_id":return w.Write([]byte(f.containerDetails.ID()))case "container_name":return w.Write([]byte(f.containerDetails.Name()))case "image_name":return w.Write([]byte(f.containerDetails.ImageName()))case "daemon_name":return w.Write([]byte(f.containerDetails.DaemonName))}if value, ok := f.attrs[tag]; ok {return w.Write([]byte(value))}return 0, fmt.Errorf("%w: %s", errUnknownTag, tag)}
}

支持的模板标签包括:

  • {log}:原始日志内容
  • {timestamp}:日志时间戳
  • {container_id}:容器ID
  • {container_name}:容器名称
  • {image_name}:镜像名称
  • {daemon_name}:Docker 守护进程名称

腾讯云 CLS 集成

驱动使用腾讯云官方提供的 SDK 来实现与 CLS 服务的集成:

type Client struct {logger   *zap.Loggercfg      ClientConfigproducer *tencentcloud_cls_sdk_go.AsyncProducerClientcallback *clsCallback
}func (c *Client) SendMessage(text string) error {addLogMap := map[string]string{}if err := json.Unmarshal([]byte(text), &addLogMap); err != nil {c.logger.Debug("failed to unmarshal log", zap.String("log", text), zap.Error(err))addLogMap["content"] = text}// 添加实例信息if c.cfg.InstanceInfo != "" {instanceInfo := map[string]string{}if err := json.Unmarshal([]byte(c.cfg.InstanceInfo), &instanceInfo); err != nil {addLogMap["instance"] = c.cfg.InstanceInfo} else {for k, v := range instanceInfo {addLogMap["__instance__."+k] = v}}}// 添加容器详情if len(c.cfg.AppendContainerDetailsKeys) > 0 {for _, k := range c.cfg.AppendContainerDetailsKeys {switch k {case "container_id":addLogMap["__container_details__.container_id"] = c.cfg.ContainerDetails.ContainerIDcase "container_name":addLogMap["__container_details__.container_name"] = c.cfg.ContainerDetails.ContainerName// ... 其他字段}}}log := tencentcloud_cls_sdk_go.NewCLSLog(time.Now().Unix(), addLogMap)err := c.producer.SendLog(c.cfg.TopicID, log, c.callback)if err != nil {return fmt.Errorf("failed to send message: %w", err)}return nil
}

这种设计确保了日志能够以结构化的形式发送到 CLS,便于后续的查询和分析。

配置管理与灵活性

配置参数设计

驱动支持丰富的配置参数,满足不同场景的需求:

  • 必需参数

    • endpoint:腾讯云 CLS 服务端点
    • secret_id:腾讯云 API 密钥 ID
    • secret_key:腾讯云 API 密钥
    • topic_id:CLS 主题 ID
  • 可选参数

    • template:日志格式模板
    • filter-regex:日志过滤正则表达式
    • retries:重试次数
    • timeout:请求超时时间
    • no-file:是否禁用本地文件存储
    • keep-file:容器停止后是否保留日志文件

配置解析与验证

驱动实现了完善的配置解析和验证机制:

func parseLoggerConfig(containerDetails *ContainerDetails) (*loggerConfig, error) {clientConfig, err := parseClientConfig(containerDetails)if err != nil {return nil, fmt.Errorf("failed to parse client config: %w", err)}attrs, err := containerDetails.ExtraAttributes(nil)if err != nil {return nil, fmt.Errorf("failed to parse extra attributes: %w", err)}cfg := defaultLoggerConfigcfg.ClientConfig = clientConfigcfg.Attrs = attrs// 解析模板配置if template, ok := containerDetails.Config[cfgTemplateKey]; ok {cfg.Template = template}// 解析过滤正则表达式if filterRegex, ok := containerDetails.Config[cfgFilterRegexKey]; ok {cfg.FilterRegex, err = regexp.Compile(filterRegex)if err != nil {return nil, fmt.Errorf("failed to parse %q option: %w", cfgFilterRegexKey, err)}}if err := cfg.Validate(containerDetails.Config); err != nil {return nil, err}return &cfg, nil
}

这种设计确保了配置的正确性和一致性,避免了因配置错误导致的问题。

错误处理与可靠性

重试机制

驱动实现了完善的重试机制,确保在网络不稳定的情况下能够可靠地发送日志:

type ClientConfig struct {Endpoint     stringSecretID     stringSecretKey    stringTopicID      stringInstanceInfo stringAppendContainerDetailsKeys []stringContainerDetails           *ContainerDetails// 重试配置Retries int// 超时配置Timeout time.Duration
}

部分日志处理

对于跨多个日志条目的长日志,驱动实现了部分日志的缓冲和组装机制:

type partialLogBuffer struct {logs map[string]*logger.Messagemu   sync.Mutex
}func (b *partialLogBuffer) Append(log *logger.Message) (*logger.Message, bool) {if log.PLogMetaData == nil {panic("log must be partial")}b.mu.Lock()defer b.mu.Unlock()plog, exists := b.logs[log.PLogMetaData.ID]if !exists {plog = new(logger.Message)*plog = *logb.logs[plog.PLogMetaData.ID] = plogplog.Line = make([]byte, 0, 16*1024)plog.PLogMetaData = nil}plog.Line = append(plog.Line, log.Line...)if log.PLogMetaData.Last {delete(b.logs, log.PLogMetaData.ID)return plog, true}return nil, false
}

这种设计确保了长日志的完整性,避免了日志被截断的问题。

性能优化与监控

异步处理

驱动采用了异步处理模式,确保日志发送不会阻塞容器的正常运行:

func (l *TencentCLSLogger) Log(log *logger.Message) error {if l.isClosed() {return errLoggerClosed}if log.PLogMetaData != nil {assembledLog, last := l.partialLogsBuffer.Append(log)if !last {return nil}*log = *assembledLog}if l.cfg.FilterRegex != nil && !l.cfg.FilterRegex.Match(log.Line) {l.logger.Debug("message is filtered out by regex", zap.String("regex", l.cfg.FilterRegex.String()))return nil}text := l.formatter.Format(log)l.send(text)return nil
}

日志级别控制

驱动支持灵活的日志级别控制,便于调试和监控:

func newLogger(env string, logLevel string) (*zap.Logger, error) {var cfg zap.Configif env == "production" {cfg = zap.NewProductionConfig()} else {cfg = zap.NewDevelopmentConfig()}var err errorcfg.Level, err = zap.ParseAtomicLevel(logLevel)if err != nil {return nil, fmt.Errorf("failed to parse log level: %w", err)}cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderreturn cfg.Build()
}

部署与使用

插件安装

驱动以 Docker 插件的形式提供,安装过程简单:

# 安装插件
docker plugin install k8scat/docker-log-driver-tencent-cls:latest \--alias tencent-cls \--grant-all-permissions

容器级别使用

在运行容器时指定日志驱动:

docker run --log-driver=tencent-cls \--log-opt endpoint="<endpoint>" \--log-opt secret_id="<secret_id>" \--log-opt secret_key="<secret_key>" \--log-opt topic_id="<topic_id>" \--log-opt template="{container_name}: {log}" \your_image

全局配置

在 Docker 守护进程级别配置默认日志驱动:

{"log-driver": "tencent-cls","log-opts": {"endpoint": "<endpoint>","secret_id": "<secret_id>","secret_key": "<secret_key>","topic_id": "<topic_id>"}
}

总结

这个 Docker 日志驱动项目展示了如何构建一个企业级的日志收集解决方案。通过深度集成 Docker 的日志系统,我们实现了高性能、可靠的日志传输能力。项目的主要特点包括:

  • 高性能:采用异步处理模式,确保不影响容器性能
  • 高可靠:实现了完善的重试机制和错误处理
  • 高灵活:支持丰富的配置选项和自定义模板
  • 易部署:以 Docker 插件形式提供,安装使用简单
  • 易维护:模块化设计,代码结构清晰

这个项目为容器化应用的日志管理提供了一个完整的解决方案,能够满足企业级应用的各种需求。未来可以考虑添加更多功能,如日志压缩、加密传输、多租户支持等,进一步提升产品的竞争力。

http://www.dtcms.com/a/296547.html

相关文章:

  • 《AI流程编排中的Graph观测:设计原理与集成实践》
  • 网卡配置网卡ip和经过网关的ip
  • PAT 甲级题目讲解:1003《Emergency》
  • JavaSE:对一门面向对象语言有一个初步认识
  • Java 大视界 -- Java 大数据在智能教育自适应学习路径规划与学习效果强化中的应用(362)
  • LeetCode 10:正则表达式匹配
  • MyBatis-Plus--PageHelper 与 MyBatis-Plus 分页插件
  • SAP全自动化工具开发:Excel自动上传与邮件通知系统
  • Flutter之Widget体系与布局原理
  • 汉字转拼音
  • Kiro AI是如何帮我实现发布 harpoon 项目
  • 如何使用 php-vulnerability-hunter
  • 中国网专访百胜软件董事长兼CEO黄飞:中台助力新时代下零售行业的探索
  • go下载包
  • go语言基础教程:【1】基础语法:变量
  • Verilog 提取信号的上升沿或者下降沿
  • Python中常用标准库(时间库、随机库、正则表达式)
  • shell 正则表达式
  • Golang 语言中的指针介绍
  • 高版本Android跨应用广播通信实例
  • 40、鸿蒙Harmony Next开发:UI场景化-组件截图(ComponentSnapshot)
  • C++第一节课入门
  • Qt 元对象系统(Meta-Object System)解析
  • 前端安全问题怎么解决
  • 企业资产管理智能化:IT运维如何借力数字化管理提效避坑?
  • 配置DNS服务的正反向解析
  • 详解FreeRTOS开发过程(六)-- 队列
  • ESP32- 项目应用1 智能手表之更新时间 #3
  • Linux系统常用命令
  • 浅析飞算JavaAI “撤回需求” 功能:让需求管理更灵活