2610 字
约 8 分钟
16
S01-X05 · 云服务器从裸机到可上线:最小步骤清单

一台刚开的云主机,默认离“能对外服务”差着十件事:密码登录开着、防火墙只有厂商那层、时钟可能偏、日志无限增长、备份不存在、崩了没人知道。任何一件漏掉,都会在某个深夜变成事故。

这篇是一份照着敲的十步清单,每步只留最小动作,容易出错的地方单独标出。


1. 十步清单总览

步 动作 完成标志 漏了会怎样
1 建普通用户 + 装公钥 ssh deploy@host 免密可登 一直用 root 密码,爆破目标
2 防火墙(安全组 + ufw) 只开 22/80/443 数据库端口暴露公网
3 时区与 NTP timedatectl 显示同步 TLS 校验失败、日志时间错乱
4 swap 取舍 有或明确没有 内存打满直接 OOM kill
5 Docker(若用) docker run hello-world 手工装运行时,升级地狱
6 反向代理 nginx -t 通过、80 可达 应用端口直接对外
7 TLS 证书链完整、自动续期 浏览器告警、API 调用失败
8 日志轮转 logrotate -d 无错 磁盘写满,全站 502
9 备份 能从备份恢复一次 数据丢失不可逆
10 监控与自检 有告警通道、脚本可跑 故障靠用户投诉发现

顺序不能乱:先收紧入口,再谈服务;先有备份,再谈上线。


2. 第 1 步:用户与密钥

$ adduser deploy
$ usermod -aG sudo deploy
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@SERVER_IP
$ sudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
$ sudo chmod 600 /home/deploy/.ssh/authorized_keys
检查项 正确值 错误后果
~/.ssh 权限 700 sshd 拒绝使用密钥
authorized_keys 权限 600 同上,日志提示 bad permissions
家目录 不能 group/other 可写 同上
公钥内容 单行、以 ssh-ed25519 或 ssh-rsa 开头 多行粘贴导致整段失效

先开新终端验证免密能登,再改 sshd 配置;顺序反了会把自己锁在门外。


3. 第 2 步:双层防火墙

$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw allow 22/tcp
$ sudo ufw allow 80,443/tcp
$ sudo ufw enable
$ sudo ufw status verbose
层 位置 管什么 谁先拦截
云安全组 厂商网络层 实例级入站/出站 先,包还没到网卡
ufw / firewalld 主机内核 netfilter 进程与端口 后

两层作用不同:安全组挡“谁能把包送进来”,ufw 挡“进来了能不能到端口”。只配一层,换厂商或加网卡就会漏。

Docker 会绕过 ufw:发布端口由 DOCKER-USER 链处理,ufw deny 8080 无效。要么在 DOCKER-USER 加规则,要么让容器只绑 127.0.0.1 由 nginx 转发。


4. 第 3 步:时区与 NTP

$ sudo timedatectl set-timezone Asia/Shanghai
$ timedatectl
$ systemctl status systemd-timesyncd --no-pager
$ chronyc tracking        # 装了 chrony 时
指标 正常 异常处理
System clock synchronized yes 查 UDP 123 是否被安全组拦
NTP service active systemctl enable --now systemd-timesyncd

时钟不对表现隐蔽:证书“尚未生效”、JWT 一签发就过期、日志顺序错乱。先查这一步比怀疑代码快。


5. 第 4 步:swap 的取舍

$ sudo fallocate -l 2G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
$ free -h
场景 建议 理由
≤2G 内存、跑数据库 给 2G swap,vm.swappiness=10 避免 OOM 直接杀进程
≥8G 内存、纯应用 可以不给 换页反而拖慢
容器密集 给,且限制容器内存 否则一个容器吃满整机

swap 是缓冲不是方案,上了它仍然要配内存告警。


6. 配套项:自动补丁与暴力破解防护

$ sudo apt install -y unattended-upgrades fail2ban
$ sudo dpkg-reconfigure -plow unattended-upgrades
$ sudo fail2ban-client status sshd
项 建议 说明
unattended-upgrades 开,只装安全更新 默认不自动重启,内核要手动重启生效
fail2ban 看是否已禁密码登录 已禁密码 + 云厂商防护时收益有限
与 ufw 共存 走 iptables 链 sudo iptables -L -n 看 f2b 链

前者解决“三个月后才发现某个 CVE 没打”,建议一律开;后者在关掉密码登录后属于可选项,判断依据是还有没有暴露面。

7. 第 5–7 步:Docker、反代、TLS

$ curl -fsSL https://get.docker.com | sudo sh
$ sudo usermod -aG docker deploy
$ sudo mkdir -p /etc/docker && sudo tee /etc/docker/daemon.json <<'EOF'
{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }
EOF
$ sudo systemctl restart docker
server {
    listen 80;
    server_name example.com;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
$ sudo nginx -t && sudo systemctl reload nginx
$ sudo certbot --nginx -d example.com -d www.example.com
$ sudo certbot renew --dry-run
配置 不写会怎样
daemon.json 日志上限 容器日志无限增长,写满磁盘
X-Forwarded-For 应用只看到 127.0.0.1,限流失效
X-Forwarded-Proto 应用生成 http:// 链接,混合内容告警
certbot renew --dry-run 三个月后才发现续期失败

nginx -t 不通过绝不 reload:语法错误会让整站挂掉。改配置前先 cp 一份带时间戳的备份。


8. 第 8 步:日志轮转

$ sudo tee /etc/logrotate.d/app >/dev/null <<'EOF'
/var/log/app/*.log {
    daily
    rotate 14
    maxsize 100M
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}
EOF
$ sudo logrotate -d /etc/logrotate.d/app     # 预演,不实际执行
$ sudo logrotate -f /etc/logrotate.d/app     # 强制跑一次验证
指令 作用 坑
daily + maxsize 按时间或大小触发 只写 daily 时,一天内暴涨不管
copytruncate 复制后清空原文件 应用仍持有 fd 时安全,但可能丢几行
rotate 14 保留 14 份 与磁盘容量一起算
compress gzip 旧文件 —

journald 单独管:/etc/systemd/journald.conf 里设 SystemMaxUse=500M,否则 /var/log/journal 会悄悄吃几个 G。


9. 第 9 步:备份

$ crontab -e
# 每天 03:20 全量库 + 每小时增量文件
20 3 * * * /usr/bin/mysqldump --single-transaction --routines --triggers app > /backup/app-$(date +\%F).sql && gzip -f /backup/app-$(date +\%F).sql
30 3 * * * rclone sync /backup remote:bucket/backup --max-age 30d
备份对象 命令 验证方式
MySQL mysqldump --single-transaction 在临时库 source 一次
配置文件 tar czf /backup/etc-$(date +%F).tgz /etc 解包对比 diff -r
对象存储 rclone sync —
整机 云快照 定期演练恢复

cron 里的 % 必须转义成 \%。备份脚本要有错误输出与“超过 N 小时没新备份就告警”的检查——没有告警的备份等于没有备份。


10. 第 10 步:自检脚本与监控

#!/usr/bin/env bash
set -uo pipefail
fail=0
check() { if eval "$2" >/dev/null 2>&1; then echo "OK   $1"; else echo "FAIL $1"; fail=1; fi; }
check "nginx running"  "systemctl is-active --quiet nginx"
check "http 200"       "curl -fsS -o /dev/null -m 5 http://127.0.0.1/"
check "disk <85%"      "[ \$(df --output=pcent / | tail -1 | tr -dc 0-9) -lt 85 ]"
check "port 443 listen" "ss -tlnH 'sport = :443' | grep -q ."
check "cert >14d"      "openssl s_client -connect 127.0.0.1:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -checkend 1209600"
exit $fail
检查项 覆盖的故障
服务状态 进程挂了但机器还在
HTTP 探测 进程在但 upstream 不通
磁盘水位 慢性的“写满即 502”
证书剩余 续期失败

脚本放 /usr/local/bin/preflight.sh,chmod +x 后接 cron 或告警,上线前跑一次、日常定时跑。


11. 回滚点:改配置前先备份

改动 备份动作 回滚方式
nginx 配置 cp -a /etc/nginx /backup/nginx-$(date +%F-%H%M) 覆盖回去 + nginx -t + reload
sshd 配置 同上,保留原文件 覆盖 + sshd -t + reload
unit 文件 systemctl cat 输出存文件 覆盖 + daemon-reload
数据库结构 变更前 mysqldump --no-data 用快照或反向 SQL
整机 云快照 回滚快照(分钟级)

快照可靠但粒度粗、耗时,文件级备份快而细。两个都要:“误删一个文件”不值得回滚整机,“内核升级炸了”只能整机回滚。


12. 排错:六种上线即翻车

现象 原因 处理
改完 sshd 连不上 配置有误或密钥权限不对 控制台 VNC 进;sshd -t 先验证
80 端口不通但本机 curl 通 安全组未放行 / ufw 拦 两层都查
容器端口对外暴露 Docker 绕过 ufw 绑 127.0.0.1 或改 DOCKER-USER
证书续期失败 80 端口被占、DNS 变了 certbot renew --dry-run 定位
备份任务静默失败 cron 环境变量缺失、% 未转义 脚本里写全路径,重定向日志

13. 边界:这些不该自己搭

需求 更合适的方案 理由
高可用 托管负载均衡 + 多可用区 单机自建做不到故障切换
证书与 DNS 托管 ACM + DNS API 续期与解析耦合更少
日志与指标 托管可观测平台 自建 ELK 的维护量超过业务

一句话收口:先收口入口(用户、密钥、防火墙),再保证可观测(日志、监控、自检),最后才是服务本身。 十步里前四步和第八、九步与业务无关,但恰恰是最容易被跳过、也最容易在深夜咬人的部分。


14. 自测

  1. 云安全组与 ufw 各挡哪一层?为什么两层都要配?
  2. 为什么 ufw deny 8080 挡不住 Docker 发布的端口?
  3. 改 sshd 配置前必须先做什么验证动作?
  4. logrotate 里 copytruncate 解决什么问题,代价是什么?
  5. cron 里的 date +%F 为什么必须写成 \%F?
  6. 什么情况下该用云快照回滚,而不是文件级备份?
S01-X05 · 云服务器从裸机到可上线:最小步骤清单
https://www.clxhxhhr.top/posts/5712/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。