用 systemd 从零做一个系统健康巡检服务:脚本、沙箱与磁盘满排查实战

用 systemd 从零做一个系统健康巡检服务:脚本、沙箱与磁盘满排查实战

这是我运维学习第 2 周的产出:不写玩具 demo,做一个每天自动跑、真能发现问题的巡检服务。文章会讲清楚三件事------为什么这么设计、脚本怎么写、怎么验证它真的有用。


一、为什么做这个项目

学 systemd 的时候,很多人停留在"会 systemctl start/stop 就行"。但运维面试和实际工作里,真正被问的是:

  • 你会不会写 unit 文件?
  • 你知道 Type=oneshot 和 Type=simple 的区别吗?
  • 定时任务为什么有时用 timer 而不是 cron?
  • 服务以 root 跑,你怎么降低风险?

所以我给自己定了一个有实际作用的产出:一个每日系统健康巡检服务 。它每天定时采集负载、内存、磁盘、inode、大文件、已删除未释放文件、关键服务状态,把结果写进 journald,异常项带 WARN / CRIT 标记。

这件事的价值在于:它不是"为了练 systemd 而 systemd",而是模拟了运维日常里非常真实的一个需求------定时巡检 + 统一日志 + 异常可追溯。


二、整体设计

先看架构,三个文件各司其职:

text

bash 复制代码
/usr/local/bin/health-check.sh             巡检脚本,负责"查什么"
/etc/systemd/system/health-check.service   定义"怎么跑",带安全加固
/etc/systemd/system/health-check.timer     定义"什么时候跑"

执行链路:

text

markdown 复制代码
timer(每天 08:00)
   └── 触发 service(oneshot)
          └── 执行脚本
                 └── 输出到 journald

设计上有几个关键决策,后面会逐一解释:

决策 选择 原因
调度方式 systemd timer 日志统一进 journald,支持关机补跑
服务类型 Type=oneshot 脚本跑完就退出,不需要常驻
运行身份 root + 收紧 capability lsof 跨进程需要特权,但只保留最小能力
日志去向 stdout → journald 不自己写日志文件,避免日志散落和轮转问题
退出码 0/1/2 对应 OK/WARN/CRIT 方便后续接监控告警

三、巡检脚本:查什么,怎么查

完整脚本放在文末附录,这里讲设计思路。

3.1 检查项分两层

基础层(每次必查,开销小):

  • 负载:读 /proc/loadavg
  • 内存:读 /proc/meminfo 的 MemTotal 和 MemAvailable
  • 分区使用率:df -PT
  • inode 使用率:df -iPT
  • 关键服务状态:systemctl is-active
  • 失败单元:systemctl list-units --state=failed

深挖层(只在分区超阈值时触发,开销大):

  • du 下钻 Top 目录
  • 大文件 Top N
  • /var/log 大日志
  • lsof +L1 已删除未释放

这个分层很重要。定时任务如果每次都全盘 du 和 find /,在几千台机器上就是灾难。正常时不深挖,异常时才深挖,这是运维脚本的基本素养。

3.2 退出码有语义

bash

bash 复制代码
if (( criticals > 0 )); then
    exit 2
elif (( warnings > 0 )); then
    exit 1
else
    exit 0
fi

这样 systemctl status 会显示 failed,ExecMainStatus 能拿到 1 或 2,后续接 Prometheus、Zabbix 或自建告警时,直接看退出码就行,不用解析日志文本。

3.3 为什么输出到 stdout 而不是写文件

很多教程会让你把巡检结果写到 /var/log/health-check.log。我不推荐,原因:

  1. 日志文件需要自己处理轮转,否则巡检脚本自己成了磁盘满的元凶。
  2. journald 自带轮转、压缩、按时间查询。
  3. journalctl -u health-check.service --since today 一条命令就能看,不用记日志路径。

所以脚本只 printf 到 stdout,由 systemd 接管。


四、磁盘满排查链路:这是重点

磁盘满排查是运维高频场景,也是最容易踩坑的地方。完整套路是五步:

text

markdown 复制代码
1. df -h          哪个分区满
2. df -i          是不是 inode 满
3. du 下钻        满的分区里哪个目录大
4. find 大文件    哪个单文件大
5. lsof +L1       有没有已删除但没释放的文件

第 5 步是最经典的坑。你 rm 了一个 10G 日志,df 还是满的,因为写日志的进程还持有文件句柄。只有重启进程,或者 : > /proc/PID/fd/N 清空,空间才会真正释放。

脚本把这五步都实现了,下面挑几个关键点解释。

4.1 不只检查 /,检查所有本地分区

bash

bash 复制代码
df -PT | awk -v re="$EXCLUDE_FSTYPES" 'NR>1 && $2 !~ re { ... }'
  • df -P:POSIX 格式,保证一行一个文件系统,长设备名不换行。
  • df -T:显示文件系统类型,用来排除 tmpfs、overlay、proc 等伪文件系统。
  • 生产机器常有 /data、/var 单独分区,只盯 / 会漏。

4.2 inode 必须单独检查

bash

bash 复制代码
df -iPT

inode 满的典型现象:df -h 看还有空间,但新建文件报 No space left on device。常见原因是邮件队列、缓存目录、session 文件堆了海量小文件。所以 inode 和磁盘使用率是两条独立的告警线。

4.3 du 下钻只在超阈值时跑

bash

bash 复制代码
du -x --max-depth=1 "$mnt" | sort -rn | head -n 8
  • -x:不跨文件系统。从 / 下钻时不会统计到挂载在 /data 的另一块盘。
  • --max-depth=1:只看第一层,脚本只给 Top 目录,逐层下钻是人工后续动作。
  • 性能关键 :只有 df 超 80% 的分区才触发 du,正常机器上这个分支根本不执行。

4.4 大文件扫描限定目录 + -xdev

bash

bash 复制代码
find "$dir" -xdev -type f -size +500M -printf '%s\t%p\n'
  • -xdev:不跨文件系统,避免从 /var 扫到别处。
  • -printf '%s\t%p\n':输出字节数 + 路径,方便按大小排序。
  • 只扫 /var /tmp /home /data /opt,不扫 /,避免全盘 IO。

4.5 已删除未释放:lsof +L1

bash

swift 复制代码
lsof +L1 | awk '$7 ~ /^[0-9]+$/ {print $7"\t"$1"\t"$2"\t"$9}'
  • +L1:显示 link count 小于 1 的文件,也就是已删除但仍有进程持有 fd 的文件。
  • 输出大小、命令、PID、原路径。
  • 发现后处理:重启对应进程,或 : > /proc/PID/fd/N 清空(谨慎,可能影响进程行为)。

五、service 单元:安全加固的权衡

先看完整配置:

ini

ini 复制代码
[Unit]
Description=System Health Check & Report
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/health-check.sh

User=root
Group=root
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectClock=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
CapabilityBoundingSet=CAP_SYS_PTRACE CAP_DAC_READ_SEARCH

5.1 为什么用 root,而不是 nobody

第一版我用了 User=nobody,看起来更安全。但测试时发现 lsof +L1 看不到其他进程的文件句柄------因为读取 /proc/PID/fd 需要 CAP_SYS_PTRACE,读取任意文件需要 CAP_DAC_READ_SEARCH,nobody 都没有。

巡检的本质是只读诊断,它需要看到全系统的信息。所以正确做法不是降权到 nobody,而是:

用 root 身份,但丢掉除诊断之外的所有能力,同时把文件系统设为只读。

这就是 CapabilityBoundingSet=CAP_SYS_PTRACE CAP_DAC_READ_SEARCH 的含义:只保留两个能力,其余全部丢弃。即使脚本被篡改,攻击者也只能读,不能写文件系统,不能加载内核模块,不能改内核参数。

5.2 关键加固项解释

配置 作用
ProtectSystem=strict 整个文件系统只读,root 也无法写。这是最重要的边界
ProtectHome=read-only /home 只读可见。不能用 yes,因为脚本要扫 /home 大文件
NoNewPrivileges=yes 禁止通过 SUID 等方式提权
ProtectKernelTunables=yes /proc/sys、/sys 只读,防止改内核参数
ProtectKernelModules=yes 禁止加载内核模块
RestrictAddressFamilies 只允许 Unix socket 和 IPv4/IPv6,巡检不需要其他协议

这个组合的效果:有 root 身份读系统信息,但没有写文件系统的能力,也没有多余特权。 对只读巡检脚本来说,这是安全性和功能性的合理平衡。

5.3 一个容易忽略的坑:不要开 PrivateTmp

第一版我加了 PrivateTmp=yes,结果扫描 /tmp 大文件时永远是 (none)。原因是 PrivateTmp 给服务分配了独立的 /tmp 命名空间,服务看到的不是宿主机 /tmp。

所以这里有意不启用 PrivateTmp,并在文档里注明。这是个很典型的"安全配置和功能需求冲突"的例子,需要根据实际场景权衡。


六、timer 单元:为什么不用 cron

ini

ini 复制代码
[Unit]
Description=Daily Health Check Timer

[Timer]
OnCalendar=*-*-* 08:00:00
Persistent=true
RandomizedDelaySec=120
AccuracySec=1min

[Install]
WantedBy=timers.target

6.1 timer 相对 cron 的优势

对比项 cron systemd timer
日志 需要自己重定向到文件 自动进 journald,journalctl -u 查看
关机补跑 不支持 Persistent=true 支持
随机延迟 需要自己写脚本实现 RandomizedDelaySec 一行配置
依赖管理 弱 可以 After=、Requires=
查看下次执行 crontab -l 看不出 systemctl list-timers 一目了然
安全加固 无 支持完整的沙箱配置

所以新项目我优先用 timer。cron 仍然适合简单场景,但 timer 在可观测性和安全性上明显更好。

6.2 几个参数的含义

  • OnCalendar=*-*-* 08:00:00:每天 08:00 触发。格式可以用 systemd-analyze calendar 验证。
  • Persistent=true:如果 08:00 时机器关机,开机后补跑一次。这是 timer 相对 cron 最实用的优势之一。
  • RandomizedDelaySec=120:在 08:00 后随机延迟最多 120 秒。多台机器同时触发时,避免对共享存储或监控系统造成瞬时压力。
  • AccuracySec=1min:允许 1 分钟精度,systemd 可以合并唤醒以省电。

验证时间表达式:

bash

arduino 复制代码
systemd-analyze calendar "*-*-* 08:00:00"

七、部署与验证

7.1 部署

bash

bash 复制代码
# 1. 创建脚本并赋权
sudo chmod 755 /usr/local/bin/health-check.sh

# 2. 重载 systemd
sudo systemctl daemon-reload

# 3. 启用并启动 timer
sudo systemctl enable --now health-check.timer

# 4. 确认 timer 已生效
systemctl list-timers --all | grep health-check

7.2 手动触发并查看报告

bash

css 复制代码
sudo systemctl start health-check.service
journalctl -u health-check.service --no-pager --since today

正常机器上的输出大概是这样:

text

erlang 复制代码
--- Disk Usage (per filesystem) ---
[OK]   Disk / (xfs) used: 42%
[OK]   Disk /boot (xfs) used: 18%

--- Inode Usage (per filesystem) ---
[OK]   Inode / (xfs) used: 6%

--- Top 5 Files > 500MB (scanned dirs) ---
       (none)

--- Deleted Files Still Held Open (Top 5) ---
       (none)

7.3 三个模拟测试

光看到"全部 OK"不够,要验证告警真的能触发。

测试 1:大文件告警

bash

bash 复制代码
sudo dd if=/dev/zero of=/var/tmp/test-big.img bs=1M count=600 status=none
sudo systemctl start health-check.service
journalctl -u health-check.service --no-pager --since "1 min ago" | grep -A6 "Top 5 Files"
sudo rm -f /var/tmp/test-big.img

应看到 /var/tmp/test-big.img 600M 出现在 Top Files 段。

测试 2:已删除未释放

bash

javascript 复制代码
sudo dd if=/dev/zero of=/var/tmp/deleted-test.img bs=1M count=200 status=none
sudo bash -c 'exec 3>/var/tmp/deleted-test.img; rm -f /var/tmp/deleted-test.img; sleep 300' &
sudo systemctl start health-check.service
journalctl -u health-check.service --no-pager --since "1 min ago" | grep -A6 "Deleted Files"
sudo pkill -f "sleep 300"

应看到 deleted-test.img 出现在 Deleted Files 段,大小约 200M。这个测试很有价值,因为它验证了脚本能发现"rm 了但空间没释放"的经典问题。

测试 3:阈值告警和退出码

bash

lua 复制代码
sudo sed -i 's/^DISK_WARN=80/DISK_WARN=1/' /usr/local/bin/health-check.sh
sudo systemctl start health-check.service
systemctl status health-check.service
sudo sed -i 's/^DISK_WARN=1/DISK_WARN=80/' /usr/local/bin/health-check.sh

应看到 [WARN] Disk / used: ...,且 service 状态为 failed (Result: exit-code),退出码为 1。

7.4 验证沙箱配置生效

bash

css 复制代码
systemctl show health-check.service \
  -p ProtectSystem \
  -p ProtectHome \
  -p CapabilityBoundingSet \
  -p NoNewPrivileges

应看到 ProtectSystem=strict、CapabilityBoundingSet=cap_sys_ptrace cap_dac_read_search 等。


八、踩坑记录

把过程中遇到的几个问题记下来,都是真实会遇到的:

坑 1:User=nobody 导致 lsof +L1 数据缺失

现象:巡检报告里 Deleted Files 永远是 (none),但手动 lsof +L1 能看到文件。

原因:nobody 没有 CAP_SYS_PTRACE,读不了其他进程的 /proc/PID/fd。

解决:改用 root + CapabilityBoundingSet 收紧能力。

坑 2:PrivateTmp=yes 导致 /tmp 扫描不到

现象:在 /tmp 造了大文件,巡检报告里看不到。

原因:私有 /tmp 命名空间,服务看到的不是宿主机 /tmp。

解决:去掉 PrivateTmp=yes,并在文档里注明原因。

坑 3:df 输出换行导致解析错位

现象:某些长设备名在 df -h 输出里换行,导致 awk 字段错位。

原因:默认 df 输出格式对长设备名不友好。

解决:用 df -P(POSIX 格式),保证一行一个文件系统。

坑 4:du 跨文件系统统计

现象:对 / 做 du 时,把 /data 的数据也算进去了。

原因:du 默认跨文件系统。

解决:加 -x 参数。


九、总结与收获

这个项目做完,我对 systemd 的理解从"会用命令"变成了"知道怎么设计一个生产可用的服务"。几个核心收获:

  1. Type=oneshot 适合一次性任务,巡检、备份、清理这类脚本不需要常驻。
  2. timer 比 cron 更适合新项目,日志统一、支持补跑、可观测性好。
  3. 安全加固不是一味降权。巡检需要读全系统信息,正确做法是 root + 最小 capability + 只读文件系统,而不是降权到 nobody 导致功能缺失。
  4. 磁盘满排查是有套路的,df → df -i → du → find → lsof +L1,顺序不能乱。
  5. 脚本要分层设计,正常时轻量检查,异常时才深挖,避免定时任务拖慢系统。
  6. 验证比实现更重要。造大文件、造删除未释放、改阈值,三个测试跑通,才算真的能用。

如果后面要接监控,最简单的做法是让 Prometheus 的 textfile collector 读退出码,或者在脚本里加一段把 WARN/CRIT 计数推给告警系统。这个项目本身已经为后续扩展留好了接口。


附录:完整脚本

bash

bash 复制代码
#!/usr/bin/env bash
set -uo pipefail

# ===== 阈值 =====
DISK_WARN=80
DISK_CRIT=90
INODE_WARN=80
INODE_CRIT=90
MEM_WARN=80
MEM_CRIT=90
LOAD_WARN=4.0
LOAD_CRIT=8.0

# ===== 磁盘满排查参数 =====
BIGFILE_SIZE_MB=500
BIGFILE_TOPN=5
DU_TOPN=8
LOG_BIGFILE_SIZE_MB=50
DELETED_TOPN=5

BIGFILE_SCAN_DIRS="/var /tmp /home /data /opt"
EXCLUDE_FSTYPES="tmpfs|devtmpfs|proc|sysfs|cgroup|cgroup2|overlay|squashfs|nsfs|ramfs"

warnings=0
criticals=0
report=""

add_line() { report+="$1"$'\n'; }

mark() {
    local level="$1" msg="$2"
    if [[ "$level" == "CRIT" ]]; then
        criticals=$((criticals + 1))
    elif [[ "$level" == "WARN" ]]; then
        warnings=$((warnings + 1))
    fi
    add_line "[$level] $msg"
}

human() {
    numfmt --to=iec "$1" 2>/dev/null || echo "$1"
}

add_line "========== Health Check $(date '+%Y-%m-%d %H:%M:%S') =========="
add_line "Hostname: $(hostname)"
add_line "Uptime:   $(uptime -p 2>/dev/null || uptime)"
add_line ""

# ---------- 1. 负载 ----------
load1=$(awk '{print $1}' /proc/loadavg)
if (( $(echo "$load1 >= $LOAD_CRIT" | bc -l) )); then
    mark CRIT "Load(1m): ${load1} (threshold ${LOAD_CRIT})"
elif (( $(echo "$load1 >= $LOAD_WARN" | bc -l) )); then
    mark WARN "Load(1m): ${load1} (threshold ${LOAD_WARN})"
else
    add_line "[OK]   Load(1m): ${load1}"
fi

# ---------- 2. 内存 ----------
mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
mem_avail=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
mem_used_pct=$(awk -v t="$mem_total" -v a="$mem_avail" 'BEGIN {printf "%.1f", (t-a)/t*100}')
if (( $(echo "$mem_used_pct >= $MEM_CRIT" | bc -l) )); then
    mark CRIT "Memory used: ${mem_used_pct}% (threshold ${MEM_CRIT}%)"
elif (( $(echo "$mem_used_pct >= $MEM_WARN" | bc -l) )); then
    mark WARN "Memory used: ${mem_used_pct}% (threshold ${MEM_WARN}%)"
else
    add_line "[OK]   Memory used: ${mem_used_pct}%"
fi

# ---------- 3. 所有本地分区使用率 ----------
add_line ""
add_line "--- Disk Usage (per filesystem) ---"

full_mounts=()

while read -r fs type size used avail pcent mount; do
    [[ -z "${pcent:-}" ]] && continue
    if (( pcent >= DISK_CRIT )); then
        mark CRIT "Disk ${mount} (${type}) used: ${pcent}% (threshold ${DISK_CRIT}%)"
        full_mounts+=("$mount")
    elif (( pcent >= DISK_WARN )); then
        mark WARN "Disk ${mount} (${type}) used: ${pcent}% (threshold ${DISK_WARN}%)"
        full_mounts+=("$mount")
    else
        add_line "[OK]   Disk ${mount} (${type}) used: ${pcent}%"
    fi
done < <(df -PT 2>/dev/null | awk -v re="$EXCLUDE_FSTYPES" '
    NR>1 && $2 !~ re {
        gsub(/%/, "", $6);
        print $1, $2, $3, $4, $5, $6, $7
    }')

# ---------- 4. inode 使用率 ----------
add_line ""
add_line "--- Inode Usage (per filesystem) ---"

while read -r fs type inodes iused ifree ipcent mount; do
    [[ -z "${ipcent:-}" ]] && continue
    if (( ipcent >= INODE_CRIT )); then
        mark CRIT "Inode ${mount} (${type}) used: ${ipcent}% (threshold ${INODE_CRIT}%)"
    elif (( ipcent >= INODE_WARN )); then
        mark WARN "Inode ${mount} (${type}) used: ${ipcent}% (threshold ${INODE_WARN}%)"
    else
        add_line "[OK]   Inode ${mount} (${type}) used: ${ipcent}%"
    fi
done < <(df -iPT 2>/dev/null | awk -v re="$EXCLUDE_FSTYPES" '
    NR>1 && $2 !~ re {
        gsub(/%/, "", $6);
        print $1, $2, $3, $4, $5, $6, $7
    }')

# ---------- 5. 超阈值分区 du 下钻 ----------
if (( ${#full_mounts[@]} > 0 )); then
    add_line ""
    add_line "--- Top Directories on Full Filesystems ---"
    for mnt in "${full_mounts[@]}"; do
        add_line "  [$mnt]"
        while read -r kbytes path; do
            add_line "       $(human $((kbytes * 1024)))  $path"
        done < <(du -x --max-depth=1 "$mnt" 2>/dev/null | sort -rn | head -n "$DU_TOPN")
    done
fi

# ---------- 6. 大文件 Top N ----------
add_line ""
add_line "--- Top ${BIGFILE_TOPN} Files > ${BIGFILE_SIZE_MB}MB (scanned dirs) ---"

bigfile_found=0
while IFS=$'\t' read -r size path; do
    [[ -z "${path:-}" ]] && continue
    bigfile_found=1
    add_line "       $(human "$size")  $path"
done < <(
    for dir in $BIGFILE_SCAN_DIRS; do
        [[ -d "$dir" ]] || continue
        find "$dir" -xdev -type f -size +${BIGFILE_SIZE_MB}M \
            -printf '%s\t%p\n' 2>/dev/null
    done | sort -rn | head -n "$BIGFILE_TOPN"
)
(( bigfile_found == 0 )) && add_line "       (none)"

# ---------- 7. /var/log 大日志 Top ----------
add_line ""
add_line "--- Top 5 Log Files > ${LOG_BIGFILE_SIZE_MB}MB in /var/log ---"

log_found=0
while IFS=$'\t' read -r size path; do
    [[ -z "${path:-}" ]] && continue
    log_found=1
    add_line "       $(human "$size")  $path"
done < <(
    find /var/log -xdev -type f -size +${LOG_BIGFILE_SIZE_MB}M \
        -printf '%s\t%p\n' 2>/dev/null | sort -rn | head -n 5
)
(( log_found == 0 )) && add_line "       (none)"

# ---------- 8. 已删除但未释放的文件 ----------
add_line ""
add_line "--- Deleted Files Still Held Open (Top ${DELETED_TOPN}) ---"

deleted_found=0
while IFS=$'\t' read -r size comm pid name; do
    [[ -z "${name:-}" ]] && continue
    deleted_found=1
    add_line "       $(human "$size")  PID=$pid  $comm  $name"
done < <(
    lsof +L1 2>/dev/null \
        | awk 'NR>1 && $7 ~ /^[0-9]+$/ {
            name=$9;
            for (i=10; i<=NF; i++) name=name" "$i;
            print $7"\t"$1"\t"$2"\t"name
        }' \
        | sort -rn | head -n "$DELETED_TOPN"
)
(( deleted_found == 0 )) && add_line "       (none)"

# ---------- 9. 关键服务 ----------
add_line ""
add_line "--- Services ---"

SERVICES_TO_CHECK="sshd chronyd"
for svc in $SERVICES_TO_CHECK; do
    if ! systemctl list-unit-files --type=service 2>/dev/null \
            | awk '{print $1}' | grep -qx "${svc}.service"; then
        add_line "[SKIP] Service ${svc} not installed"
        continue
    fi
    state=$(systemctl is-active "$svc" 2>/dev/null || true)
    if [[ "$state" == "active" ]]; then
        add_line "[OK]   Service ${svc}: active"
    else
        mark CRIT "Service ${svc}: ${state:-unknown}"
    fi
done

# ---------- 10. 失败单元 ----------
add_line ""
add_line "--- Failed systemd Units ---"

failed_units=$(systemctl list-units --state=failed --no-legend --no-pager 2>/dev/null | awk '{print $1}')
if [[ -n "$failed_units" ]]; then
    mark CRIT "Failed units detected:"
    while IFS= read -r unit; do
        [[ -n "$unit" ]] && add_line "       - $unit"
    done <<< "$failed_units"
else
    add_line "[OK]   No failed systemd units"
fi

# ---------- 汇总 ----------
add_line ""
add_line "========== Summary: WARN=${warnings}, CRIT=${criticals} =========="

printf '%s\n' "$report"

if (( criticals > 0 )); then
    exit 2
elif (( warnings > 0 )); then
    exit 1
else
    exit 0
fi
相关推荐
GeW2 小时前
新质生产力路线图下,Linux 和数据库不能拆成两件事做
linux
云和数据.ChenGuang2 小时前
Python 闭包详解:从原理到实战
linux·开发语言·人工智能·python·kubernetes
Linux-lucky2 小时前
46-Linux学习之旅之LVS负载均衡与keepalived高可用
linux·运维·学习·ubuntu·负载均衡·lvs
Fruiticecake2 小时前
C 变量住在哪里,进程内存为什么越来越大:从 .data / .bss 到 smaps 排查内存增长
linux
Escalating_xu3 小时前
【Linux 网络编程】NAT、代理服务与内网穿透:从一个公网 IP,到一条真正可达的连接
linux·网络·tcp/ip
来让爷抱一个3 小时前
Linux 磁盘 100% 但 du 查不出东西,五种情况逐个排
linux·网络·chrome
皓月盈江5 小时前
GO使用Gin框架实现模板定义、解析与渲染
linux·golang·go·gin·定义模板·解析模板·渲染模板
T量子永生5 小时前
Unisoc(紫光展锐) Kernel 6.18 DDK 编译框架详解
linux·运维·spring
z06.6 小时前
Linux系统中MySQL的安装
linux·mysql