512MB Alpine Linux 云服务器初始化实录:扩容、ZRAM、流量统计与微信告警
这篇文章记录一台小规格云服务器从购买、首次登录,到磁盘扩容、内存优化、安全检查、流量统计和微信告警的完整过程。
最初的选购和配置思路参考了这篇云服务器记录,但本文中的分区、内核和流量阈值均按这台实例的实际情况重新核对。免费流量额度及计费规则可能调整,应以自己的云控制台为准。
中间还踩过一次比较严重的坑:在仅有约 40MB 的 /boot 分区上直接升级内核,重启后系统无法识别根磁盘,SSH 也随之超时。最终通过重新写入操作系统恢复,并重新完成全部配置。
1. 服务器配置与用途
本次使用的实例规格如下:
| 项目 | 配置 |
|---|---|
| CPU | 2 vCPU |
| 内存 | 512MB |
| 系统盘 | 5GB ESSD |
| 公网带宽 | 80Mbps 峰值,按流量计费 |
| 操作系统 | Alpine Linux 3.20.2 |
| 初始内核 | 6.6.48-0-virt |
512MB 内存理论上也能启动精简后的 Ubuntu Server,但可用空间会明显更紧。Alpine 使用 BusyBox、musl 和 apk,基础系统很小,更适合这种配置。
2. 安全组与首次登录
安全组入方向只开放 SSH:
| 协议 | 端口 | 来源 |
|---|---|---|
| TCP | 22 | 自己当前公网 IP /32 |
不建议为了省事把 SSH 来源长期设置为 0.0.0.0/0。如果家庭宽带公网 IP 发生变化,再到安全组中更新来源即可。
在本地 Windows 命令行登录:
ssh root@<服务器公网IP>
首次连接会看到主机指纹确认提示。确认 IP 与实例无误后输入 yes。密码输入过程中不会显示字符,这是 SSH 的正常行为。
如果创建实例时选择了"创建后设置"登录凭证,可以先从云控制台终端进入系统,再设置 root 密码:
passwd root
两类 SSH 报错含义不同:
| 报错 | 常见原因 |
|---|---|
Permission denied | 用户名、密码或 SSH 认证配置不正确 |
Connection timed out | 安全组、客户端公网 IP、SSH 服务或系统启动异常 |
3. 初始状态检查
登录后先做只读检查,不要立即升级系统:
cat /etc/alpine-release
uname -a
free -m
df -h /
cat /proc/partitions
fdisk -l /dev/vda
mount | grep ' on / '
cat /etc/fstab
ip addr
ip route
netstat -lntp
rc-status -a
当时的关键结果是:
Alpine: 3.20.2
Kernel: 6.6.48-0-virt
Memory: 431MiB 可见
Swap: 0
Disk: /dev/vda 共 5GB
Root: /dev/vda2 只有约 976MB
Boot: /dev/vda1 约 47.6MB
Filesystem: ext4
虽然云盘容量是 5GB,但自定义镜像中的根分区没有自动扩展,只使用了约 1GB。剩余磁盘空间还没有分配给 /dev/vda2。
ip addr 中只看到类似 172.18.x.x 的私网地址也是正常的。云厂商通常在虚拟网络层把公网 IP 映射到实例,公网 IP 不一定直接出现在系统网卡上。
4. 扩展根分区到 5GB
镜像已经配置了 Alpine 3.20 的阿里云镜像源,但 community 仓库被注释。先备份配置并启用它:
cp /etc/apk/repositories /etc/apk/repositories.bak
sed -i 's|^#http://mirrors.aliyun.com/alpine/v3.20/community|http://mirrors.aliyun.com/alpine/v3.20/community|' /etc/apk/repositories
apk update
安装分区和 ext4 扩容工具:
apk add cloud-utils-growpart e2fsprogs e2fsprogs-extra util-linux
扩展第二个分区,再在线扩展 ext4 文件系统:
growpart /dev/vda 2
resize2fs /dev/vda2
验证:
lsblk -f
df -h / /boot
最终结果:
/dev/vda2 4.9G 63.9M 4.6G 1% /
/dev/vda1 39.7M 23.9M 12.5M 66% /boot
根分区已经使用完整云盘,/boot 仍保持约 40MB。这是当前镜像的分区设计,不应在没有迁移和备份方案的情况下强行调整。
5. 一次内核升级导致的启动故障
扩容后,我曾执行完整的:
apk upgrade
它把 linux-virt 从 6.6.48 升级到了 6.6.142。当时 /boot 只剩约 12.5MB,新的内核、initramfs 与 VirtIO 驱动链没有正确形成可启动组合。
重启后的表现是:
- 系统无法正常识别
/dev/vda根磁盘; - SSH 22 端口无法建立连接;
- 本地只看到
Connection timed out; - 普通重启和"重新初始化云盘"没有解决问题。
最终恢复方式是在云控制台执行"更换操作系统",使用原来的自定义 Alpine 镜像重新写入系统盘,然后重新扩容和配置。
恢复后再次确认三处内核版本一致:
uname -r
apk info -v | grep '^linux-virt-'
ls -1 /lib/modules
结果为:
6.6.48-0-virt
linux-virt-6.6.48-r0
6.6.48-0-virt
这台机器目前保留可正常启动的 6.6.48-0-virt。后续只执行 apk update 和按需 apk add <软件包>,不直接运行完整 apk upgrade,尤其不要在没有处理 /boot 空间和 initramfs 的情况下升级 linux-virt。
6. 配置 384MB ZRAM
这台机器只有约 431MiB 可见内存,并且初始没有 Swap。ZRAM 会在内存中创建压缩交换区,用少量 CPU 换取更高的有效内存容量,比磁盘 Swap 更适合这种小实例。
先检查内核支持:
modprobe zram
ls -l /dev/zram0
cat /sys/block/zram0/comp_algorithm
本机支持:
lzo lzo-rle [lz4] lz4hc
方括号表示当前算法,默认已经是速度较好的 lz4。
6.1 临时验证
先临时启用 384MB:
zramctl /dev/zram0 --size 384M
mkswap -L zram0 /dev/zram0
swapon -p 100 /dev/zram0
zramctl
swapon --show
free -h
确认无误后,安装 Alpine 官方管理服务:
apk add zram-init zram-init-openrc
cp /etc/conf.d/zram-init /etc/conf.d/zram-init.bak
默认配置会创建两个 ZRAM 设备,第二个还准备挂载到 /tmp,不适合本机。因此只保留一个 Swap 设备:
sed -i \
-e 's/^num_devices=.*/num_devices=1/' \
-e 's/^flag0=.*/flag0=100/' \
-e 's/^size0=.*/size0=384/' \
-e 's/^maxs0=.*/maxs0=2/' \
-e 's/^algo0=.*/algo0=lz4/' \
/etc/conf.d/zram-init
相关配置最终为:
load_on_start=yes
unload_on_stop=yes
num_devices=1
type0=swap
flag0=100
size0=384
maxs0=2
algo0=lz4
labl0=zram_swap
6.2 切换为 OpenRC 服务
因为前面已经手动创建过临时 ZRAM,所以先关闭并重置它:
swapoff /dev/zram0
zramctl --reset /dev/zram0
再交给 OpenRC 管理:
rc-service zram-init start
rc-update add zram-init default
重启后验证:
rc-service zram-init status
zramctl
swapon --show
free -h
结果如下:
/dev/zram0 lz4 384M [SWAP]
priority: 100
service: started
ZRAM 的 384M 是逻辑容量,不会在启动时一次性占满 384MB 内存。只有产生交换数据时才逐步使用压缩后的物理内存。
7. 做一次只读安全检查
由于系统来自自定义镜像,正式使用前检查账号、公钥、端口、服务和计划任务。
7.1 可登录账号
awk -F: '$7 !~ /(nologin|false)$/ {print $1 ":" $3 ":" $6 ":" $7}' /etc/passwd
结果只有 root 具备普通交互 Shell;sync、shutdown 和 halt 是 Alpine 内置系统账号。
7.2 root SSH 公钥
if [ -f /root/.ssh/authorized_keys ]; then
ssh-keygen -lf /root/.ssh/authorized_keys
else
echo "no authorized_keys"
fi
本机没有预装陌生公钥。
7.3 监听端口和开机服务
netstat -lntup
rc-status default
对外监听的只有 SSH 22 端口。默认服务为:
acpid
crond
sshd
openntpd
zram-init
0.0.0.0:22 和 :::22 分别表示 IPv4 与 IPv6 监听,并不代表开放了两个不同端口。真正允许哪些公网地址访问,仍由云安全组决定。
7.4 定时任务、启动脚本和进程
cat /etc/crontabs/root
ls -la /etc/local.d
ps aux
检查结果:
- root crontab 只有 Alpine 默认的周期维护任务;
/etc/local.d只有 README,没有自定义启动脚本;- 进程均为内核线程、网络、日志、时间同步、SSH 和终端服务;
- 没有发现陌生下载器、代理或异常后台进程。
8. 使用 vnStat 持久化统计流量
我最初尝试安装阿里云主机监控 Agent,但它在这套自定义 Alpine 环境中安装失败。检查后没有发现残留进程或 OpenRC 服务,因此改用更轻量的 vnStat。
安装:
apk add vnstat
Alpine 会同时安装 vnstat-openrc。先启动守护进程,让它创建数据库并发现 eth0:
rc-update add vnstatd default
rc-service vnstatd start
rc-service vnstatd status
验证数据库中的接口:
vnstat --iflist
vnstat --dbiflist
vnstat -i eth0
如果数据库已经存在但没有 eth0,可以在守护进程启动后执行:
vnstat --add -i eth0
rc-service vnstatd restart
不要在数据库首次创建前急着运行 vnstat --add,否则可能看到数据库不存在的提示。
8.1 将保存间隔调整为 1 分钟
vnStat 默认每 5 分钟保存一次数据库。80Mbps 满速 5 分钟理论上可产生约 3GB 流量,告警可能过晚,因此将保存间隔改为 1 分钟。
原配置行以分号开头,分号表示注释:
;SaveInterval 5
备份并启用新值:
cp /etc/vnstat.conf /etc/vnstat.conf.bak
sed -i 's/^;SaveInterval[[:space:]].*/SaveInterval 1/' /etc/vnstat.conf
rc-service vnstatd restart
验证实际配置:
vnstat --showconfig | grep '^SaveInterval'
rc-service vnstatd status
结果应为:
SaveInterval 1
status: started
常用查询命令:
vnstat -i eth0 # 摘要
vnstat -d -i eth0 # 每日流量
vnstat -m -i eth0 # 每月流量
其中 rx 是接收流量,tx 是服务器发送流量。按流量计费时重点关注 tx。
需要注意,vnStat 只统计安装之后这台机器网卡看到的流量,不等同于云厂商账单,也看不到同一账号下其他产品共享的流量额度。
9. 通过 Server酱发送微信告警
Server酱提供一个简单的 HTTP 接口,可以把服务器通知发送到微信。本次只在超过阈值时推送,不做每日汇报,因此产生的网络流量可以忽略。
先在 Server酱官网 使用微信登录、绑定推送通道,并获取以 SCT 开头的 SendKey。
安装 curl 并创建凭证目录:
apk add curl
mkdir -p /etc/traffic-guard
chmod 700 /etc/traffic-guard
umask 077
安全读取 SendKey。下面命令中的 SCT_SENDKEY 是变量名,不需要替换:
read -r -s -p "Paste SendKey (hidden): " SCT_SENDKEY
看到提示后再粘贴真正的 SendKey 并回车,输入内容不会显示。随后保存:
printf '\n'
printf '%s\n' "$SCT_SENDKEY" > /etc/traffic-guard/serverchan.key
chmod 600 /etc/traffic-guard/serverchan.key
unset SCT_SENDKEY
只验证格式,不输出密钥:
ls -l /etc/traffic-guard/serverchan.key
grep -q '^SCT' /etc/traffic-guard/serverchan.key \
&& echo "SendKey format OK" \
|| echo "SendKey format invalid"
10. 创建 18GB 微信告警脚本
脚本使用 vnStat 2.12 自带的结构化阈值判断:
vnstat -i eth0 --alert 0 3 monthly tx 18 GB
这里使用十进制 GB,不是二进制 GiB。脚本达到阈值后发送一次微信消息,并创建当月标记文件,避免每分钟重复推送。
创建 /usr/local/sbin/traffic-alert:
#!/bin/sh
PATH=/usr/sbin:/usr/bin:/sbin:/bin
IFACE=eth0
LIMIT_GB=18
KEY_FILE=/etc/traffic-guard/serverchan.key
STATE_DIR=/var/lib/traffic-guard
MONTH=$(date +%Y-%m)
MARKER="$STATE_DIR/warned-$MONTH-${LIMIT_GB}GB"
mkdir -p "$STATE_DIR"
chmod 700 "$STATE_DIR"
if [ ! -s "$KEY_FILE" ]; then
logger -t traffic-alert "ServerChan key file is missing or empty"
exit 1
fi
if [ "${1:-}" = "--test" ]; then
notice_title="Alpine ECS traffic alert test"
notice_body="Scheduled monthly traffic alert is working."
create_marker=0
else
[ -e "$MARKER" ] && exit 0
if vnstat -i "$IFACE" --alert 0 3 monthly tx "$LIMIT_GB" GB; then
exit 0
fi
notice_title="Alpine ECS traffic warning"
notice_body="Monthly outbound traffic on $IFACE has exceeded ${LIMIT_GB} GB.
$(vnstat -m -i "$IFACE")
Check Alibaba Cloud CDT usage and stop unexpected traffic."
create_marker=1
fi
notice_key=$(cat "$KEY_FILE")
notice_response=$(curl -sS --connect-timeout 10 --max-time 20 \
--data-urlencode "title=$notice_title" \
--data-urlencode "desp=$notice_body" \
"https://sctapi.ftqq.com/${notice_key}.send") || exit 1
unset notice_key
if ! printf '%s' "$notice_response" |
grep -Eq '"code"[[:space:]]*:[[:space:]]*0([,}])'; then
logger -t traffic-alert "ServerChan rejected the notification"
exit 1
fi
unset notice_response
if [ "$create_marker" -eq 1 ]; then
: > "$MARKER"
chmod 600 "$MARKER"
fi
logger -t traffic-alert "Notification sent successfully"
exit 0
设置权限并检查语法:
chmod 700 /usr/local/sbin/traffic-alert
sh -n /usr/local/sbin/traffic-alert
sh -n 没有输出代表语法检查通过。测试微信推送:
/usr/local/sbin/traffic-alert --test
echo "exit=$?"
返回 exit=0 且微信收到消息,说明脚本正常。测试模式不会创建 18GB 告警标记。
11. 每分钟自动检查
先把 root crontab 备份到 crontab 目录之外,避免备份文件被 crond 当成新任务:
cp /etc/crontabs/root /root/crontab.root.before-traffic-alert
幂等添加任务,重复执行也不会添加多行:
grep -qxF '* * * * * /usr/local/sbin/traffic-alert' /etc/crontabs/root \
|| echo '* * * * * /usr/local/sbin/traffic-alert' >> /etc/crontabs/root
重启并验证 crond:
rc-service crond restart
rc-service crond status
tail -n 5 /etc/crontabs/root
最终任务为:
* * * * * /usr/local/sbin/traffic-alert
未达到阈值时,脚本只查询本地 SQLite 数据库,不访问互联网。只有超过 18GB 或手动执行 --test 时,才会向 Server酱发送一个几 KB 到几十 KB 的 HTTPS 请求。
需要停用告警时,只删除这一条任务:
sed -i '\|^\* \* \* \* \* /usr/local/sbin/traffic-alert$|d' /etc/crontabs/root
rc-service crond restart
12. 最终状态
完成配置并重启后,最终状态如下:
| 项目 | 最终结果 |
|---|---|
| 内核 | 6.6.48-0-virt |
| 根分区 | 约 4.9GB |
| 可用内存 | 约 431MiB |
| ZRAM | 384MB,lz4,优先级 100 |
| 公网监听 | 仅 SSH 22 |
| 流量统计 | vnStat,数据每分钟保存 |
| 微信告警 | 月出站流量超过十进制 18GB 时通知一次 |
最终验证命令:
uname -r
df -h / /boot
free -h
zramctl
swapon --show
rc-service zram-init status
rc-service vnstatd status
rc-service crond status
netstat -lntup
vnstat -m -i eth0
13. 这次配置最重要的经验
- 云盘容量变大,不代表镜像中的根分区会自动扩展,必须同时扩分区和文件系统。
- 512MB 实例更适合 Alpine;ZRAM 能显著降低轻量服务因内存不足退出的概率。
- 自定义镜像不能无脑执行完整系统升级,尤其要先检查
/boot、内核模块和 initramfs。 - SSH 超时和密码错误是两类故障,应分别从网络链路与认证配置排查。
- vnStat 的统计是本机视角,不等于云厂商账单,但很适合做低成本的早期告警。
- 80Mbps 只是峰值能力,按流量计费时速度越高,异常流量消耗额度也越快。
- 告警凭证必须单独保存在权限为
600的文件中,不能硬编码进公开脚本。
到这里,这台 2 核 512MB 的 Alpine 小服务器已经完成基础初始化,可以作为 Linux、网络和轻量后端实验环境继续使用。