cloud-init:新机器开机即就绪

每次开新机器都要重复一遍「装软件、建用户、加 SSH key、配防火墙」。cloud-init 让这些在开机时就自动完成。

一、它是什么

云厂商在创建实例时,可以传一段「user-data」,系统首次启动时会读取并执行。这就是 cloud-init。

bash
# 在已有机器上确认是否安装
cloud-init --version
systemctl status cloud-init

# 看上次执行的日志
sudo cat /var/log/cloud-init-output.log
cloud-init status

主流云平台(阿里云、腾讯云、AWS、Vultr、DigitalOcean)的镜像都预装了。

二、最小可用的 user-data

yaml
#cloud-config
hostname: baize-node-1
timezone: Asia/Shanghai

users:
  - name: baize
    groups: sudo
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3...  your-key-comment

package_update: true
packages:
  - curl
  - git
  - vim
  - tmux
  - htop
  - nginx

ssh_pwauth: false
disable_root: true

第一行 #cloud-config 必须写,这是格式标识。

三、常用模块

跑命令

yaml
runcmd:
  - apt update
  - apt install -y fail2ban
  - ufw allow 22/tcp
  - ufw allow 80/tcp
  - ufw allow 443/tcp
  - ufw --force enable
  - systemctl enable --now nginx
  - [ sh, -c, "echo done > /root/init-ok" ]

runcmd 每次开机都会跑(不只是首次)。只跑一次用 bootcmd(早期)或自己加判断文件。

写文件

yaml
write_files:
  - path: /etc/nginx/conf.d/baize.conf
    permissions: "0644"
    owner: root:root
    content: |
      server {
          listen 80;
          server_name www.bzii.cn;
          root /var/www/baize;
          index index.html;
      }

  - path: /opt/scripts/hello.sh
    permissions: "0755"
    content: |
      #!/usr/bin/env bash
      echo "hello from cloud-init"

  - path: /root/.bashrc-extra
    append: true
    content: |
      alias ll="ls -alhF"

内核参数与 apt 源

yaml
write_files:
  - path: /etc/sysctl.d/99-tuning.conf
    content: |
      net.core.somaxconn = 65535
      vm.swappiness = 10

apt:
  sources:
    docker.list:
      source: "deb [arch=amd64] https://download.docker.com/linux/debian bookworm stable"
      keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88

挂载磁盘

yaml
disk_setup:
  /dev/vdb:
    table_type: gpt
    layout: true

fs_setup:
  - device: /dev/vdb
    partition: 1
    filesystem: ext4

mounts:
  - [ /dev/vdb1, /data, ext4, "defaults,noatime", "0", "2" ]

四、一份完整的初始化模板

yaml
#cloud-config
hostname: baize-node
timezone: Asia/Shanghai
disable_root: true
ssh_pwauth: false

users:
  - name: baize
    groups: [sudo, docker]
    shell: /bin/bash
    sudo: "ALL=(ALL) NOPASSWD:ALL"
    lock_passwd: true
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3...

package_update: true
package_upgrade: true
packages:
  - curl wget git vim tmux htop btop ncdu mtr jq rsync
  - ufw fail2ban nginx certbot python3-certbot-nginx

write_files:
  - path: /etc/sysctl.d/99-baize.conf
    content: |
      net.core.somaxconn = 65535
      vm.swappiness = 10

runcmd:
  - sed -i "s/^#\?Port 22/Port 2222/" /etc/ssh/sshd_config
  - sed -i "s/^#\?PermitRootLogin.*/PermitRootLogin no/" /etc/ssh/sshd_config
  - sed -i "s/^#\?PasswordAuthentication.*/PasswordAuthentication no/" /etc/ssh/sshd_config
  - systemctl reload ssh
  - ufw allow 2222/tcp
  - ufw allow 80/tcp
  - ufw allow 443/tcp
  - ufw --force enable
  - sysctl --system
  - echo "init done" > /var/log/baize-init.ok

final_message: "主机 $hostname 初始化完成,用时 $UPTIME 秒"

注意:改 SSH 端口后要记得放行新端口,否则开机后你就连不上了。这是最危险的一步,务必先测。

五、怎么传 user-data

平台位置
阿里云创建实例 → 高级选项 → 用户数据
腾讯云创建实例 → 高级设置 → 自定义数据
AWSUser data 字段
Vultr / DOStartup Script / User Data

部分平台要求 base64 编码:

bash
base64 -w0 user-data.yaml

六、调试

这是最容易卡住的地方。看日志:

bash
sudo cat /var/log/cloud-init-output.log     # 脚本输出
sudo cat /var/log/cloud-init.log            # 详细过程

cloud-init status --long
cloud-init status --wait                    # 等待完成

# 看配置有没有被正确解析
sudo cloud-init query --all
sudo cat /var/lib/cloud/instance/user-data.txt

# 检查语法(YAML 错误是最常见问题)
cloud-init devel schema --config-file user-data.yaml

# 重新跑一次(调试时很有用)
sudo cloud-init clean --logs
sudo cloud-init init

cloud-init devel schema 一定要跑:90% 的失败是 YAML 缩进错误,这个命令能直接指出来。

七、常见坑

问题原因
完全没执行#cloud-config 头 / 平台没传成功
包没装上网络未就绪就跑了,或包名错
SSH key 没生效用户名写错,或 ssh_pwauth 冲突
改了 SSH 端口连不上新端口没放行
命令没跑runcmd 缩进错,或用了 shell 特性(它不经过 bash)

runcmd 里的命令不经过 shell,所以 > 重定向、&& 管道都无效。要用的话写成 - [ sh, -c, "command > file" ]

八、cloud-init vs 手动 vs 配置管理工具

方案适用
手动一两台机器,不常开新机
cloud-init每次开新机都要同样的初始化
Ansible机器多、需要持续管理配置
打包自定义镜像初始化内容多且耗时

我的用法:

  • cloud-init 做基础初始化(用户、SSH、防火墙、基础软件);
  • 剩下的用一份 shell 脚本或 Ansible 补;
  • 如果初始化很长(比如要编译),就做好一次打快照,以后从快照开。

cloud-init 的价值是把「新机器」这件事变得可重复。写一次,以后每台都一样。