Ubuntu系统用久了,apt缓存和旧内核镜像会不断堆积,占满磁盘空间。最直接的解决办法就是:定期执行apt clean清除下载缓存,用autoremove删除不再需要的旧内核和依赖包,再配合crontab定时任务自动化执行。一台长期运行的Ubuntu服务器,光apt缓存就可能占掉几百MB甚至上GB,旧内核镜像每个版本都有几十MB,积累下来非常可观。下面我把具体操作、脚本编写、定时配置全部讲透。

一、apt缓存到底是什么,为什么要清理

每次你执行apt install或者apt upgrade,系统都会把下载的.deb安装包缓存到/var/cache/apt/archives/目录下。这些文件在安装完成后其实已经没用了,但系统不会自动删除。另外,/var/lib/apt/lists/目录下存放的是软件源的索引信息,每次更新源的时候也会重新下载覆盖,旧数据同样会残留。长期不清理,这些目录会越来越大,尤其是服务器频繁更新软件包的场景下。

旧内核镜像的问题更严重。Ubuntu每次内核更新都会保留旧版本,目的是防止新内核出问题时可以回退。但如果你的系统已经稳定运行了几个月,保留三四个旧内核完全没有必要。每个内核镜像文件(vmlinuz、initrd.img、System.map等)加上modules目录,一个版本轻松占到200-400MB。四个旧内核就是将近1GB的空间浪费。

二、手动清理apt缓存的具体命令

先说最基础的清理操作。打开终端,依次执行以下命令:

sudo apt clean

这条命令会清空/var/cache/apt/archives/目录下所有已下载的.deb文件,释放空间最直接。注意,clean和autoclean不一样,autoclean只删除过时的、无法再下载到的包,clean是全部清除。

sudo apt autoclean

如果你想保留最近下载的包(万一要重装),可以用autoclean,它只清理那些在软件源里已经找不到的旧版本包。

sudo rm -rf /var/lib/apt/lists/*

这条命令删除软件源索引缓存。删除之后下次执行apt update会重新下载,不影响系统正常使用,但会让更新源那一步稍微慢一点。

sudo apt autoremove --purge

autoremove会删除所有不再被任何软件依赖的包,加上--purge参数会连同配置文件一起清除。这一步对清理旧内核和孤立依赖包非常关键。

三、专门清理旧内核镜像的方法

查看当前系统安装了哪些内核:

dpkg --list | grep linux-image

输出结果类似这样:

ii  linux-image-5.15.0-91-generic    5.15.0-91.101    amd64    Signed kernel image generic
ii  linux-image-5.15.0-88-generic    5.15.0-88.98     amd64    Signed kernel image generic
ii  linux-image-5.15.0-86-generic    5.15.0-86.96     amd64    Signed kernel image generic

一般来说,只保留当前正在使用的内核和最近一个旧版本就够了。可以用以下命令只保留最新的两个内核:

sudo apt install -y byobu

上面这条是开玩笑的,正确的做法是直接指定要删除的内核版本:

sudo apt remove --purge linux-image-5.15.0-86-generic linux-headers-5.15.0-86-generic

把版本号换成你要删除的旧内核。如果你不想一个一个手动删,可以用更聪明的办法,只保留当前运行的内核和上一个版本:

CURRENT_KERNEL=$(uname -r)
sudo apt remove --purge $(dpkg --list | grep linux-image | awk '{print $2}' | grep -v "$(uname -r)" | grep -v "$(uname -r | sed 's/-[0-9]*$//')" | head -n -1)

这条命令的逻辑是:获取当前内核版本,然后把除了当前内核和同系列上一个小版本之外的所有内核都删掉。不过说实话,这条命令比较复杂,容易出错,我更推荐下面的脚本方案。

四、编写自动化清理脚本

把所有清理操作写成一个shell脚本,放在/usr/local/bin/目录下,方便调用和定时执行。

#!/bin/bash
# ubuntu_cleanup.sh - 清理apt缓存和旧内核
# 作者:运维实践
# 用途:定期清理Ubuntu系统磁盘空间

LOG_FILE="/var/log/ubuntu_cleanup.log"
KEEP_KERNELS=2  # 保留的内核数量

echo "====== $(date '+%Y-%m-%d %H:%M:%S') 开始清理 ======" >> $LOG_FILE

# 1. 清理apt缓存
echo "清理apt缓存..." >> $LOG_FILE
apt clean >> $LOG_FILE 2>&1
apt autoclean >> $LOG_FILE 2>&1
rm -rf /var/lib/apt/lists/* >> $LOG_FILE 2>&1

# 2. 删除不需要的依赖包
echo "删除孤立依赖包..." >> $LOG_FILE
apt autoremove --purge -y >> $LOG_FILE 2>&1

# 3. 清理旧内核(保留最近KEEP_KERNELS个)
echo "清理旧内核..." >> $LOG_FILE
CURRENT=$(uname -r | sed 's/-[0-9]*$//')
INSTALLED_KERNELS=$(dpkg --list | grep "^ii" | grep "linux-image" | awk '{print $2}' | grep "$CURRENT" | sort -V)
KERNEL_COUNT=$(echo "$INSTALLED_KERNELS" | wc -l)

if [ $KERNEL_COUNT -gt $KEEP_KERNELS ]; then
    REMOVE_COUNT=$((KERNEL_COUNT - KEEP_KERNELS))
    KERNELS_TO_REMOVE=$(echo "$INSTALLED_KERNELS" | head -n $REMOVE_COUNT)
    for kernel in $KERNELS_TO_REMOVE; do
        echo "删除内核: $kernel" >> $LOG_FILE
        apt remove --purge -y $kernel >> $LOG_FILE 2>&1
    done
fi

# 4. 清理journal日志(保留7天)
echo "清理journal日志..." >> $LOG_FILE
journalctl --vacuum-time=7d >> $LOG_FILE 2>&1

# 5. 清理临时文件
echo "清理临时文件..." >> $LOG_FILE
rm -rf /tmp/* >> $LOG_FILE 2>&1
rm -rf /var/tmp/* >> $LOG_FILE 2>&1

# 计算释放空间
FREED=$(df -h / | awk 'NR==2 {print $4}')
echo "====== 清理完成,当前可用空间: $FREED ======" >> $LOG_FILE
echo "" >> $LOG_FILE

保存脚本后,赋予执行权限:

sudo chmod +x /usr/local/bin/ubuntu_cleanup.sh

这个脚本做了五件事:清apt缓存、删孤立依赖、保留指定数量内核、清理journal日志、删临时文件。每次执行都会记录到/var/log/ubuntu_cleanup.log,方便排查问题。

五、配置crontab定时任务

脚本写好了,接下来让它自动跑。编辑root用户的crontab:

sudo crontab -e

在文件末尾添加一行,比如每周日凌晨3点执行:

0 3 * * 0 /usr/local/bin/ubuntu_cleanup.sh

crontab的五个字段分别是:分钟、小时、日、月、星期。上面的意思是每周日(0)凌晨3点(3)执行。如果你的服务器负载比较高,可以改成凌晨4点或者5点,避开业务高峰。

如果你想每天执行,可以改成:

0 4 * * * /usr/local/bin/ubuntu_cleanup.sh

如果你想每三天执行一次:

0 4 */3 * * /usr/local/bin/ubuntu_cleanup.sh

配置完成后,可以用以下命令确认crontab是否生效:

sudo crontab -l
六、清理前后如何监控磁盘变化

建议在清理前记录磁盘使用情况,方便对比效果。可以在脚本里加上df命令的记录,或者单独写一个监控小脚本:

#!/bin/bash
# disk_monitor.sh - 记录磁盘使用情况
LOG="/var/log/disk_usage.log"
echo "$(date '+%Y-%m-%d %H:%M:%S') $(df -h / | tail -1)" >> $LOG

把这个也加到crontab里,每天记录一次,一个月后你就能看到清理策略的实际效果。用df -h查看整体使用,用du -sh /var/cache/apt/archives/单独看缓存大小,用du -sh /boot看内核占用。

七、几个容易踩的坑和注意事项

第一,不要在系统正在升级的时候执行清理脚本。如果apt正在安装新内核,你把旧内核删了,万一新内核启动失败就麻烦了。建议脚本里加个判断,检查是否有apt进程在运行:

if pgrep -x "apt-get" > /dev/null || pgrep -x "dpkg" > /dev/null; then
    echo "apt正在运行,跳过本次清理" >> $LOG_FILE
    exit 0
fi

第二,保留内核的数量不要设成1。至少保留2个,给自己留一个回退的机会。生产环境建议保留2-3个。

第三,不要用rm -rf直接删/boot目录下的文件。用apt remove --purge才是正规操作,它会同时更新grub引导配置。手动删文件可能导致grub配置和实际文件不一致,重启后引导失败。

第四,如果你用的是Ubuntu Server,没有桌面环境,journal日志可能不大。但如果是桌面版,journalctl --vacuum-time=7d能省不少空间。根据实际情况调整保留天数。

第五,对于使用snap包的系统,snap也会产生大量缓存。可以额外加上:

snap list --all | awk '/disabled/{print $1, $3}' | while read snapname revision; do
    sudo snap remove "$snapname" --revision="$revision"
done

这会清理所有已禁用的旧版本snap包,对桌面用户尤其有用。

八、总结与最佳实践建议

Ubuntu系统的磁盘清理不是一次性的事情,而是需要长期维护的运维习惯。核心思路就三步:清缓存、删旧内核、清日志。手动操作适合偶尔为之,但生产环境一定要上自动化脚本加定时任务。脚本要有日志记录,要有错误处理,要避开apt运行时段。保留策略要合理,不能为了省空间把系统搞崩。建议每周或每三天执行一次,具体频率根据服务器的软件更新频率和磁盘增长速度来定。定期查看/var/log/ubuntu_cleanup.log,确认每次清理都正常执行,这才是一个成熟运维该有的习惯。