码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 从零开始写 Docker(十)---实现 mydocker logs 查看容器日志


    合集 - Docker(16)
    1.深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs2023-12-262.探索 Linux Namespace:Docker 隔离的神奇背后01-043.初探 Linux Cgroups:资源控制的奇妙世界01-094.深入剖析 Linux Cgroups 子系统:资源精细管理01-125.Docker 与 Linux Cgroups:资源隔离的魔法之旅01-166.Docker 魔法解密:探索 UnionFS 与 OverlayFS01-197.从零开始写 Docker(一)---实现 mydocker run 命令02-228.从零开始写 Docker(二)---优化:使用匿名管道传递参数02-269.从零开始写 Docker(三)---基于 cgroups 实现资源限制03-0110.从零开始写 Docker(四)---使用 pivotRoot 切换 rootfs 实现文件系统隔离03-0511.从零开始写 Docker(五)---基于 overlayfs 实现写操作隔离03-1212.从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载03-1413.从零开始写 Docker(七)---实现 mydocker commit 打包容器成镜像03-1914.从零开始写 Docker(八)---实现 mydocker run -d 支持后台运行容器03-2115.从零开始写 Docker(九)---实现 mydocker ps 查看运行中的容器03-26
    16.从零开始写 Docker(十)---实现 mydocker logs 查看容器日志04-09
    收起

    mydocker-logs.png

    本文为从零开始写 Docker 系列第十篇,实现类似 docker logs 的功能,使得我们能够查查看容器日志。


    完整代码见:https://github.com/lixd/mydocker
    欢迎 Star


    推荐阅读以下文章对 docker 基本实现有一个大致认识:

    • 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs
    • 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
    • 基于 cgroups 的资源限制
      • 初探 Linux Cgroups:资源控制的奇妙世界
      • 深入剖析 Linux Cgroups 子系统:资源精细管理
      • Docker 与 Linux Cgroups:资源隔离的魔法之旅
    • 基于 overlayfs 的文件系统:Docker 魔法解密:探索 UnionFS 与 OverlayFS
    • 基于 veth pair、bridge、iptables 等等技术的 Docker 网络:揭秘 Docker 网络:手动实现 Docker 桥接网络

    开发环境如下:

    root@mydocker:~# lsb_release -a
    No LSB modules are available.
    Distributor ID:	Ubuntu
    Description:	Ubuntu 20.04.2 LTS
    Release:	20.04
    Codename:	focal
    root@mydocker:~# uname -r
    5.4.0-74-generic
    

    注意:需要使用 root 用户

    1. 概述

    上一篇已经实现了mydocker ps 命令,可以查看到当前运行中的容器了。

    本篇主要实现 mydocker logs,让我们可以随时查看容器日志。

    一般来说,对于容器中运行的进程,使日志打印到标准输出是一个非常好的实现方案,因此需要将容器中的标准输出保存下来,以便需要的时候访问。

    我们就以此作为思路来实现 mydocker logs 命令:

    • 启动时将容器进程的标准输出挂载到/var/lib/mydocker/containers/{containerId}/{containerId}-json.log文件中
    • mydocker logs 则读取这个文件以获取容器日志

    实际上 docker 实现也类似,他会把容器日志存储在var/lib/docker/containers/{containerId}/{containerId}-json.log 文件中。

    2. 实现

    具体实现包括两部分:

    • 1)重定向输出到文件
    • 2)实现 mydocker logs 命令

    输出重定向

    首先,需要修改一下原来的实现,在创建后台运行容器的时候,把进程的标准输出重新定向一下到日志文件。

    前台容器依旧打印到 Stdout 即可

    func NewParentProcess(tty bool, volume, containerId string) (*exec.Cmd, *os.File) {
    // 省略其他内存
    	if tty {
    		cmd.Stdin = os.Stdin
    		cmd.Stdout = os.Stdout
    		cmd.Stderr = os.Stderr
    	} else {
    		// 对于后台运行容器,将 stdout、stderr 重定向到日志文件中,便于后续查看
    		dirPath := fmt.Sprintf(InfoLocFormat, containerId)
    		if err := os.MkdirAll(dirURL, constant.Perm0622); err != nil {
    			log.Errorf("NewParentProcess mkdir %s error %v", dirURL, err)
    			return nil, nil
    		}
    		stdLogFilePath := dirPath + LogFile
    		stdLogFile, err := os.Create(stdLogFilePath)
    		if err != nil {
    			log.Errorf("NewParentProcess create file %s error %v", stdLogFilePath, err)
    			return nil, nil
    		}
    		cmd.Stdout = stdLogFile
    		cmd.Stderr = stdLogFile
    	}
    // ...
    }
    

    实现 logs 命令

    在 main_command.go 中添加一个 logCommand:

    var logCommand = cli.Command{
        Name:  "logs",
        Usage: "print logs of a container",
        Action: func(context *cli.Context) error {
           if len(context.Args()) < 1 {
              return fmt.Errorf("please input your container name")
           }
           containerName := context.Args().Get(0)
           logContainer(containerName)
           return nil
        },
    }
    

    并加到 main 函数中。

    func main(){
      // 省略其他内容
      app.Commands = []cli.Command{
           initCommand,
           runCommand,
           commitCommand,
           listCommand,
           logCommand,
        }
    }
    

    具体实现如下:

    func logContainer(containerName string) {
        logFileLocation := fmt.Sprintf(container.InfoLocFormat, containerName) + container.LogFile
        file, err := os.Open(logFileLocation)
        defer file.Close()
        if err != nil {
           log.Errorf("Log container open file %s error %v", logFileLocation, err)
           return
        }
        content, err := ioutil.ReadAll(file)
        if err != nil {
           log.Errorf("Log container read file %s error %v", logFileLocation, err)
           return
        }
        _, err = fmt.Fprint(os.Stdout, string(content))
        if err != nil {
           log.Errorf("Log container Fprint  error %v", err)
           return
        }
    }
    

    实现很简单,根据 containerId 拼接出完整路径,读取文件内容并重定向到标准输出即可。

    3. 测试

    启动一个后台容器

    root@mydocker:~/feat-logs/mydocker# go build .
    root@mydocker:~/feat-logs/mydocker# ./mydocker run -d -name mytop top
    {"level":"info","msg":"createTty false","time":"2024-01-26T11:25:53+08:00"}
    {"level":"info","msg":"resConf:\u0026{ 0  }","time":"2024-01-26T11:25:53+08:00"}
    {"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-26T11:25:53+08:00"}
    {"level":"error","msg":"mkdir dir /root/merged error. mkdir /root/merged: file exists","time":"2024-01-26T11:25:53+08:00"}
    {"level":"error","msg":"mkdir dir /root/upper error. mkdir /root/upper: file exists","time":"2024-01-26T11:25:53+08:00"}
    {"level":"error","msg":"mkdir dir /root/work error. mkdir /root/work: file exists","time":"2024-01-26T11:25:53+08:00"}
    {"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work /root/merged]","time":"2024-01-26T11:25:53+08:00"}
    {"level":"info","msg":"command all is top","time":"2024-01-26T11:25:53+08:00"}
    

    查看容器列表

    root@mydocker:~/feat-logs/mydocker# ./mydocker ps
    {"level":"error","msg":"read file /var/lib/mydocker/containers/0439540405/config.json error open /var/lib/mydocker/containers/0439540405/config.json: no such file or directory","time":"2024-01-26T11:26:13+08:00"}
    {"level":"error","msg":"get container info error open /var/lib/mydocker/containers/0439540405/config.json: no such file or directory","time":"2024-01-26T11:26:13+08:00"}
    ID           NAME         PID         STATUS      COMMAND     CREATED
    0439540405   mytop        171754      running     top         2024-01-26 11:25:53
    

    容器 Id 为 0439540405,查看对应目录是否生成了日志文件

    root@mydocker:~/feat-logs/mydocker# ls /var/lib/mydocker/containers/0439540405/
    0439540405-json.log config.json
    

    其中的0439540405-json.log 就是日志文件,config.json 则是上一次添加的容器信息记录文件。

    查看日志文件内容

    root@mydocker:~/feat-logs/mydocker# cat /var/lib/mydocker/containers/0439540405/0439540405-json.log
    Mem: 1793456K used, 241932K free, 1064K shrd, 91276K buff, 1354272K cached
    CPU:  0.4% usr  0.0% sys  0.0% nic 99.3% idle  0.0% io  0.0% irq  0.2% sirq
    Load average: 0.01 0.01 0.00 1/141 4
      PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    

    说明,日志存储是正常的。

    接下里执行 mydocker logs 看看是否能查询到日志

    root@mydocker:~/feat-logs/mydocker# ./mydocker logs 0439540405
    Mem: 1793424K used, 241964K free, 1064K shrd, 91316K buff, 1354280K cached
    CPU:  0.0% usr  0.0% sys  0.0% nic  100% idle  0.0% io  0.0% irq  0.0% sirq
    Load average: 0.00 0.00 0.00 1/141 4
      PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    

    可以看到,mydocker logs 命令成功运行并输出了容器的日志。

    至此,说明我们的 mydocker logs 命令实现是 ok 的。

    4. 小结

    本篇主要实现 mydocker logs 命令,和 docker 实现基本类似:

    • 容器启动把 stdout、stderr 重定向到 /var/lib/mydocker/container/{containerId}/{containerId}-json.log 文件
    • 执行 mydocker logs 则根据容器 Id 找到对应文件,读取文件内容并打印

    【从零开始写 Docker 系列】持续更新中,搜索公众号【探索云原生】订阅,阅读更多文章。



    完整代码见:https://github.com/lixd/mydocker
    欢迎关注~

    相关代码见 feat-logs 分支,测试脚本如下:

    需要提前在 /root 目录准备好 busybox.tar 文件,具体见第四篇第二节。

    # 克隆代码
    git clone -b feat-logs https://github.com/lixd/mydocker.git
    cd mydocker
    # 拉取依赖并编译
    go mod tidy
    go build .
    # 测试 
    ./mydocker run -d -name c1 top
    # 查看容器 Id
    ./mydocker ps
    # 根据 Id 查询日志
    ./mydocker logs ${containerId}
    
  • 相关阅读:
    SparkSQL系列-8、分布式SQL引擎和Catalyst 优化器
    25.flink上下游算子之间数据是如何流动的(重要)
    [附源码]SSM计算机毕业设计新冠疫苗线上预约系统JAVA
    调用后台接口实现Excel导出功能
    浅析森林烟火AI检测算法的应用及场景使用说明
    Docker安全配置
    grafana部署时集成自定义datasource和dashboard
    青少年编程能力等级测评CPA Python编程(一级)
    高并发环境下压测故障
    闭包会牺牲多少性能?它是如何产生内存消耗及性能消耗的?
  • 原文地址:https://www.cnblogs.com/KubeExplorer/p/18123727
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号