• Systemd 的坑:老子当年怎么就没发现这玩意这么能瞎JB优化?

    今天开会,又TM是一堆PPT。讲什么微服务架构,云原生最佳实践。我呸!最佳实践?连Systemd都没搞明白,玩个屁的云原生!一个个就知道抄概念,真要出了问题,抓瞎!
    就说Systemd这玩意,启动管理是方便了不少,但是后面加的那些花里胡哨的功能,简直就是瞎JB优化!尤其是那些什么资源限制,自动重启,网络配置,没一个省心的!稍微配置不对,就给你挖个坑,让你跳进去爬都爬不出来。
    就说我前几天遇到的一个破事。有个服务,用Systemd管理,设置了Restart=on-failure。正常情况下,服务挂了自动重启,看起来挺好。结果呢?这SB玩意,服务一挂,它疯狂重启,每次重启都失败,然后疯狂打印日志,直接把磁盘给干满了!你TM倒是给个重启次数限制啊!
    最后排查,发现是代码里有个空指针异常,导致服务直接core dump。这TM是代码的问题,没错。但是Systemd你能不能聪明点?你TM要是检测到连续重启失败超过三次,就应该直接停止重启啊!非得把磁盘干满才罢休?
    还有那些个什么LimitCPULimitMemory,设置起来倒是挺方便,但是你TM知道背后的实现原理吗?这玩意实际上就是cgroups!cgroups!你没听错,就是Linux内核里的cgroups!但是Systemd把这些东西封装的太好了,让你感觉好像很简单,点点鼠标就能搞定。但是一旦出了问题,你想debug,想深入理解,就TM懵逼了!
    我当年刚开始玩Linux的时候,哪有这些破玩意!那时候启动脚本都是手写的,一行一行敲出来的。虽然麻烦,但是每一个细节都清清楚楚。现在倒好,年轻人一个个只会用Systemd,出了问题就只会重启重启再重启!
    就拿配置来说,Systemd的unit文件,看着挺简洁,但是背后藏着一堆坑。比如这个Type=simpleType=forkingType=oneshot,你TM知道这些类型到底有什么区别吗?你TM知道Type=forking的服务,Systemd是怎么判断服务是否启动成功的吗?
    告诉你,Type=forking的服务,Systemd会根据PID文件来判断服务是否启动成功。如果服务启动后没有创建PID文件,或者PID文件里的PID不是当前进程的PID,Systemd就会认为服务启动失败,然后就开始TM的重启!
    所以,如果你用Type=forking,你的服务启动脚本一定要正确创建PID文件!否则,等着被Systemd坑死吧!
    举个例子,下面是一个典型的Type=forking的unit文件:

    [Unit]
    Description=My Awesome Service
    After=network.target
    [Service]
    Type=forking
    PIDFile=/var/run/my-awesome-service.pid
    ExecStart=/usr/local/bin/my-awesome-service start
    ExecStop=/usr/local/bin/my-awesome-service stop
    Restart=on-failure
    [Install]
    WantedBy=multi-user.target
    

    看到了吗?PIDFile这行至关重要!如果你的/usr/local/bin/my-awesome-service start脚本没有正确创建/var/run/my-awesome-service.pid文件,Systemd就会认为服务启动失败,然后就开始疯狂重启!
    还有那些个什么ExecStartPreExecStartPostExecStopPreExecStopPost,看着挺花哨,但是用不好就是给自己挖坑。比如,你可以在ExecStartPre里做一些初始化工作,但是如果ExecStartPre执行失败了,Systemd会直接停止启动服务,但是不会告诉你具体原因!你只能去看日志,一行一行排查,简直TM浪费时间!
    所以,我TM说,这些新技术,新工具,用起来是方便,但是一定要理解背后的原理。不要只会用,不会修。否则,等着被这些破玩意坑死吧!
    最后,再补充一句,Systemd的日志系统也是个坑。默认情况下,Systemd会将所有日志都写入到二进制日志文件里。你想查看日志,必须用journalctl命令。这TM简直反人类!我只想用tail -f /var/log/mylog.log来看日志不行吗?非得用journalctl,还要记住一堆参数,简直就是折磨!
    所以,我一般都会把Systemd的日志配置成同时写入到文本文件里,方便我用传统的工具来查看日志。
    修改/etc/systemd/journald.conf文件,设置:

    Storage=persistent
    SystemMaxFileSize=100M
    ForwardToSyslog=yes
    

    然后重启systemd-journald服务:

    systemctl restart systemd-journald
    

    这样,Systemd就会将日志同时写入到/var/log/syslog或者/var/log/messages里,方便你用tailgrep等命令来查看日志。
    总之,Systemd这玩意,用好了是神器,用不好就是坑。年轻人,多学点底层原理,少抄点概念。否则,等着被这些破玩意坑死吧!

  • 深挖Containerd CRI的Socket死锁

    早上开会,又听那帮产品经理在那儿吹逼,什么云原生、微服务,叨逼叨个没完,真想把他们的脑浆子抠出来看看是不是浆糊。一个个连DockerFile都不会写,还他妈云原生架构,原生个JB!
    行,既然他们这么喜欢K8S,那我就来好好聊聊这玩意儿,特别是Containerd这块儿。别以为写几个YAML就能上天了,底层的东西,你们这帮小年轻根本没概念。
    今天就说说Containerd CRI的Socket死锁。这玩意儿,说白了,就是Containerd在处理CRI请求的时候,Socket卡住了,导致Pod起不来,应用挂逼。别跟我说重启Containerd,重启能解决问题,还要我这老家伙干嘛?
    这事儿的根源往往在于Containerd处理CRI请求的逻辑不够健壮,特别是在并发高,或者网络环境复杂的情况下,容易出现Socket资源竞争,最终导致死锁。具体现象就是`ctr task exec`、`kubectl exec`之类的命令卡住,Pod状态一直是`ContainerCreating`或者`CrashLoopBackOff`。
    要排查这玩意儿,首先得看Containerd的日志。`/var/log/containerd/containerd.log`,瞪大你的狗眼,看看里面有没有类似下面的错误信息:
    “`
    ERRO[2024-10-27T10:00:00.000000000+08:00] failed to serve request error=”rpc error: code = Unavailable desc = connection error: desc = \”transport: Error while dialing: dial unix /run/containerd/containerd.sock: connect: connection refused\””
    “`
    这玩意儿,就是Containerd的Socket通信出了问题。要么是Containerd进程挂了,要么就是Socket连接被占满了。
    接下来,得祭出神器`strace`。这玩意儿能追踪进程的系统调用,是排查底层问题的利器。
    “`bash
    strace -p $(pidof containerd) -s 2048 -f -o containerd.strace
    “`
    这条命令会跟踪Containerd进程的所有系统调用,并将结果输出到`containerd.strace`文件中。然后,用你那可怜的脑子分析这个文件。重点关注`accept`、`send`、`recv`、`close`等Socket相关的系统调用。看看有没有长时间阻塞在某个调用上的情况。
    比如,你可能会发现大量的线程都阻塞在`accept`调用上,这意味着Containerd没有足够的线程来处理新的连接请求。这可能是由于线程池配置不合理,或者某些请求处理时间过长导致的。
    解决这个问题,可以尝试以下几种方法:
    1. **调整Containerd的配置**。在`/etc/containerd/config.toml`中,可以调整`grpc.max_concurrent_streams`参数,增加并发处理能力。
    “`toml
    [grpc]
    max_concurrent_streams = 1000 # 默认是500,可以适当增加
    “`
    重启Containerd生效。
    2. **检查网络环境**。确保Containerd和kubelet之间的网络通信正常。如果使用了网络插件,也要检查网络插件的配置是否正确。
    3. **优化应用代码**。如果某些请求处理时间过长,可能是由于应用代码存在性能问题。需要对应用代码进行优化,减少请求处理时间。
    还有一种情况,就是Socket文件被其他进程占用了。可以用`lsof`命令来查看:
    “`bash
    lsof /run/containerd/containerd.sock
    “`
    如果发现有其他进程占用了这个Socket文件,那就把那个进程干掉。
    最后,如果以上方法都无效,那就只能祭出终极大法:**升级Containerd版本**。新版本通常会修复一些已知的Bug,并优化性能。
    总之,解决Containerd CRI的Socket死锁问题,需要深入理解Containerd的底层原理,熟练掌握各种排查工具,以及具备丰富的实战经验。别指望百度一下就能解决问题,真正的技术,是靠积累和思考得来的。
    TMD,写完这篇文章,感觉又老了十岁。这帮小年轻,啥时候才能真正理解底层技术的重要性?天天只会玩YAML,迟早要出大事!

  • Docker容器中使用Nvidia GPU

    在容器环境中使用物理机的Nvidia GPU,宿主机自身需要安装好显卡驱动,还要结合使用官方提供的Toolkit,创建新的容器运行时,并在启动容器时使用对应的运行时环境。以下是在Ubuntu 22.04系统中,docker容器使用物理机显卡的简单配置过程.

    Bash
    nvidia-smi -L
    # 物理机上执行,检测显卡是否成功驱动

    参考:https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html

    1. 配置APT仓库
    Bash
    curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
      && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
        sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
        sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
    sudo apt-get update
    1. 安装软件包
    Bash
    sudo apt-get install -y nvidia-container-toolkit
    1. docker容器运行时配置
    Bash
    sudo nvidia-ctk runtime configure --runtime=docker
    sudo systemctl restart docker
    1. 测试
    Bash
    docker run -it --rm --gpus=all ubuntu:22.04 nvidia-smi -L

    附:docker配置文件(/etc/docker/daemon.json)参考

    执行nvidia-ctk runtime configure –runtime=docker时会往配置中文件中写入相应的runtime配置

    JSON
    {
        "default-ulimits": {
            "memlock": {
                "Hard": -1,
                "Name": "memlock",
                "Soft": -1
            }
        },
        "exec-opts": [
            "native.cgroupdriver=cgroupfs"
        ],
        "runtimes": {
            "nvidia": {
                "path": "nvidia-container-runtime",
                "runtimeArgs": []
            }
        },
        "bridge": "none",
        "iptables": false,
        "live-restore": true
    }