cloud-init-coder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloud-Init Coder

Cloud-Init 配置编写指南

Overview

概述

Cloud-init is the industry standard for cross-platform cloud instance initialization. It runs on first boot to configure users, packages, files, and services before the instance becomes available.
Cloud-init是跨平台云实例初始化的行业标准。它会在实例首次启动时运行,在实例可用之前配置用户、软件包、文件和服务。

Core Format

核心格式

Cloud-init configs start with
#cloud-config
:
yaml
#cloud-config
package_update: true
packages:
  - nginx
  - docker.io
cloud-init配置文件以
#cloud-config
开头:
yaml
#cloud-config
package_update: true
packages:
  - nginx
  - docker.io

User Management

用户管理

Create Deploy User

创建部署用户

yaml
#cloud-config
users:
  - name: deploy
    groups: docker, sudo
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... deploy@example.com
yaml
#cloud-config
users:
  - name: deploy
    groups: docker, sudo
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... deploy@example.com

Multiple Users

多用户配置

yaml
#cloud-config
users:
  - default  # Keep cloud provider's default user
  - name: deploy
    groups: docker
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... key1
  - name: monitoring
    groups: adm
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... monitoring-key
yaml
#cloud-config
users:
  - default  # 保留云服务商的默认用户
  - name: deploy
    groups: docker
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... key1
  - name: monitoring
    groups: adm
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... monitoring-key

Package Installation

软件包安装

Basic Packages

基础软件包

yaml
#cloud-config
package_update: true
package_upgrade: true
packages:
  - docker.io
  - docker-compose-plugin
  - nginx
  - certbot
  - python3-certbot-nginx
  - fail2ban
  - ufw
yaml
#cloud-config
package_update: true
package_upgrade: true
packages:
  - docker.io
  - docker-compose-plugin
  - nginx
  - certbot
  - python3-certbot-nginx
  - fail2ban
  - ufw

From Custom Repositories

从自定义仓库安装

yaml
#cloud-config
apt:
  sources:
    docker:
      source: "deb [arch=amd64] https://download.docker.com/linux/ubuntu $RELEASE stable"
      keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88

packages:
  - docker-ce
  - docker-ce-cli
  - containerd.io
yaml
#cloud-config
apt:
  sources:
    docker:
      source: "deb [arch=amd64] https://download.docker.com/linux/ubuntu $RELEASE stable"
      keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88

packages:
  - docker-ce
  - docker-ce-cli
  - containerd.io

SSH Hardening

SSH 加固

Declarative SSH Lockdown

声明式SSH锁定

Prefer declarative
ssh_pwauth: false
over runcmd sed commands:
yaml
#cloud-config
ssh_pwauth: false  # Disable password auth at cloud-init level

runcmd:
  # Additional hardening via sshd_config
  - sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
  - systemctl restart sshd
优先使用声明式的
ssh_pwauth: false
而非runcmd中的sed命令:
yaml
#cloud-config
ssh_pwauth: false  # 在cloud-init层面禁用密码认证

runcmd:
  # 通过sshd_config进行额外加固
  - sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
  - systemctl restart sshd

Full SSH Hardening

完整SSH加固配置

yaml
#cloud-config
ssh_pwauth: false  # Declarative - cleaner than sed

runcmd:
  # Disable root login (or use prohibit-password for key-only root)
  - sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
  - sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config

  # Disable password authentication (backup for ssh_pwauth)
  - sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config

  # Increase keepalive for stable connections
  - sed -i 's/^#\?ClientAliveInterval.*/ClientAliveInterval 60/' /etc/ssh/sshd_config
  - sed -i 's/^#\?ClientAliveCountMax.*/ClientAliveCountMax 10/' /etc/ssh/sshd_config

  # Restart SSH
  - systemctl restart sshd
yaml
#cloud-config
ssh_pwauth: false  # 声明式配置 - 比sed更简洁

runcmd:
  # 禁用root登录(或使用prohibit-password仅允许密钥登录root)
  - sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
  - sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config

  # 禁用密码认证(作为ssh_pwauth的备份)
  - sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config

  # 增加保活时间以维持稳定连接
  - sed -i 's/^#\?ClientAliveInterval.*/ClientAliveInterval 60/' /etc/ssh/sshd_config
  - sed -i 's/^#\?ClientAliveCountMax.*/ClientAliveCountMax 10/' /etc/ssh/sshd_config

  # 重启SSH服务
  - systemctl restart sshd

Docker Setup

Docker 配置

Docker with Compose

Docker 与 Compose 配置

yaml
#cloud-config
package_update: true
packages:
  - docker.io
  - docker-compose-plugin

groups:
  - docker

users:
  - name: deploy
    groups: docker
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA...

runcmd:
  - systemctl enable --now docker
  - usermod -aG docker deploy
yaml
#cloud-config
package_update: true
packages:
  - docker.io
  - docker-compose-plugin

groups:
  - docker

users:
  - name: deploy
    groups: docker
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA...

runcmd:
  - systemctl enable --now docker
  - usermod -aG docker deploy

Docker with Custom Daemon Config

自定义Docker守护进程配置

yaml
#cloud-config
write_files:
  - path: /etc/docker/daemon.json
    content: |
      {
        "log-driver": "json-file",
        "log-opts": {
          "max-size": "10m",
          "max-file": "3"
        },
        "storage-driver": "overlay2"
      }

runcmd:
  - systemctl enable --now docker
yaml
#cloud-config
write_files:
  - path: /etc/docker/daemon.json
    content: |
      {
        "log-driver": "json-file",
        "log-opts": {
          "max-size": "10m",
          "max-file": "3"
        },
        "storage-driver": "overlay2"
      }

runcmd:
  - systemctl enable --now docker

File Creation

文件创建

Write Configuration Files

编写配置文件

yaml
#cloud-config
write_files:
  - path: /etc/nginx/sites-available/app
    content: |
      server {
          listen 80;
          server_name example.com;
          location / {
              proxy_pass http://127.0.0.1:3000;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
          }
      }
    owner: root:root
    permissions: '0644'

  - path: /opt/app/.env
    content: |
      RAILS_ENV=production
      PORT=3000
    owner: deploy:deploy
    permissions: '0600'
yaml
#cloud-config
write_files:
  - path: /etc/nginx/sites-available/app
    content: |
      server {
          listen 80;
          server_name example.com;
          location / {
              proxy_pass http://127.0.0.1:3000;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
          }
      }
    owner: root:root
    permissions: '0644'

  - path: /opt/app/.env
    content: |
      RAILS_ENV=production
      PORT=3000
    owner: deploy:deploy
    permissions: '0600'

Download Files

下载文件

yaml
#cloud-config
runcmd:
  - curl -fsSL https://example.com/setup.sh -o /opt/setup.sh
  - chmod +x /opt/setup.sh
  - /opt/setup.sh
yaml
#cloud-config
runcmd:
  - curl -fsSL https://example.com/setup.sh -o /opt/setup.sh
  - chmod +x /opt/setup.sh
  - /opt/setup.sh

Service Configuration

服务配置

Enable and Start Services

启用并启动服务

yaml
#cloud-config
runcmd:
  - systemctl enable --now docker
  - systemctl enable --now nginx
  - systemctl enable --now fail2ban
yaml
#cloud-config
runcmd:
  - systemctl enable --now docker
  - systemctl enable --now nginx
  - systemctl enable --now fail2ban

Systemd Service Creation

Systemd 服务创建

yaml
#cloud-config
write_files:
  - path: /etc/systemd/system/myapp.service
    content: |
      [Unit]
      Description=My Application
      After=network.target docker.service
      Requires=docker.service

      [Service]
      Type=simple
      User=deploy
      WorkingDirectory=/opt/app
      ExecStart=/usr/bin/docker compose up
      ExecStop=/usr/bin/docker compose down
      Restart=always
      RestartSec=10

      [Install]
      WantedBy=multi-user.target

runcmd:
  - systemctl daemon-reload
  - systemctl enable --now myapp
yaml
#cloud-config
write_files:
  - path: /etc/systemd/system/myapp.service
    content: |
      [Unit]
      Description=My Application
      After=network.target docker.service
      Requires=docker.service

      [Service]
      Type=simple
      User=deploy
      WorkingDirectory=/opt/app
      ExecStart=/usr/bin/docker compose up
      ExecStop=/usr/bin/docker compose down
      Restart=always
      RestartSec=10

      [Install]
      WantedBy=multi-user.target

runcmd:
  - systemctl daemon-reload
  - systemctl enable --now myapp

Firewall Configuration

防火墙配置

UFW Setup

UFW 配置

yaml
#cloud-config
packages:
  - ufw

runcmd:
  - ufw default deny incoming
  - ufw default allow outgoing
  - ufw allow ssh
  - ufw allow http
  - ufw allow https
  - ufw --force enable
yaml
#cloud-config
packages:
  - ufw

runcmd:
  - ufw default deny incoming
  - ufw default allow outgoing
  - ufw allow ssh
  - ufw allow http
  - ufw allow https
  - ufw --force enable

Terraform/OpenTofu Integration

Terraform/OpenTofu 集成

Inline User Data

内联用户数据

hcl
resource "digitalocean_droplet" "app" {
  name   = "app-server"
  image  = "ubuntu-22-04-x64"
  size   = "s-1vcpu-1gb"
  region = "nyc1"

  user_data = <<-EOT
    #cloud-config
    package_update: true
    packages:
      - docker.io
      - docker-compose-plugin
    users:
      - name: deploy
        groups: docker
        sudo: ALL=(ALL) NOPASSWD:ALL
        shell: /bin/bash
        ssh_authorized_keys:
          - ${var.deploy_ssh_key}
    runcmd:
      - systemctl enable --now docker
  EOT
}
hcl
resource "digitalocean_droplet" "app" {
  name   = "app-server"
  image  = "ubuntu-22-04-x64"
  size   = "s-1vcpu-1gb"
  region = "nyc1"

  user_data = <<-EOT
    #cloud-config
    package_update: true
    packages:
      - docker.io
      - docker-compose-plugin
    users:
      - name: deploy
        groups: docker
        sudo: ALL=(ALL) NOPASSWD:ALL
        shell: /bin/bash
        ssh_authorized_keys:
          - ${var.deploy_ssh_key}
    runcmd:
      - systemctl enable --now docker
  EOT
}

Template File

模板文件

hcl
undefined
hcl
undefined

templates/cloud-init.yaml

templates/cloud-init.yaml

#cloud-config package_update: true packages:
  • docker.io users:
  • name: ${username} groups: docker ssh_authorized_keys:
    • ${ssh_key}
#cloud-config package_update: true packages:
  • docker.io users:
  • name: ${username} groups: docker ssh_authorized_keys:
    • ${ssh_key}

main.tf

main.tf

resource "digitalocean_droplet" "app" { user_data = templatefile("${path.module}/templates/cloud-init.yaml", { username = var.deploy_user ssh_key = var.deploy_ssh_key }) }
undefined
resource "digitalocean_droplet" "app" { user_data = templatefile("${path.module}/templates/cloud-init.yaml", { username = var.deploy_user ssh_key = var.deploy_ssh_key }) }
undefined

Complete Production Example

完整生产环境示例

yaml
#cloud-config
package_update: true
package_upgrade: true

packages:
  - docker.io
  - docker-compose-plugin
  - fail2ban
  - ufw
  - unattended-upgrades

groups:
  - docker

users:
  - name: deploy
    groups: docker, sudo
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... deploy-key

write_files:
  - path: /etc/docker/daemon.json
    content: |
      {
        "log-driver": "json-file",
        "log-opts": { "max-size": "10m", "max-file": "3" }
      }

  - path: /etc/fail2ban/jail.local
    content: |
      [sshd]
      enabled = true
      port = ssh
      filter = sshd
      maxretry = 3
      bantime = 3600

runcmd:
  # Docker
  - systemctl enable --now docker

  # SSH hardening
  - sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
  - sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
  - sed -i 's/^#\?ClientAliveInterval.*/ClientAliveInterval 60/' /etc/ssh/sshd_config
  - sed -i 's/^#\?ClientAliveCountMax.*/ClientAliveCountMax 10/' /etc/ssh/sshd_config
  - systemctl restart sshd

  # Firewall
  - ufw default deny incoming
  - ufw default allow outgoing
  - ufw allow ssh
  - ufw allow http
  - ufw allow https
  - ufw --force enable

  # Fail2ban
  - systemctl enable --now fail2ban

  # Auto-updates
  - systemctl enable --now unattended-upgrades

final_message: "Cloud-init completed after $UPTIME seconds"
yaml
#cloud-config
package_update: true
package_upgrade: true

packages:
  - docker.io
  - docker-compose-plugin
  - fail2ban
  - ufw
  - unattended-upgrades

groups:
  - docker

users:
  - name: deploy
    groups: docker, sudo
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... deploy-key

write_files:
  - path: /etc/docker/daemon.json
    content: |
      {
        "log-driver": "json-file",
        "log-opts": { "max-size": "10m", "max-file": "3" }
      }

  - path: /etc/fail2ban/jail.local
    content: |
      [sshd]
      enabled = true
      port = ssh
      filter = sshd
      maxretry = 3
      bantime = 3600

runcmd:
  # Docker
  - systemctl enable --now docker

  # SSH 加固
  - sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
  - sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
  - sed -i 's/^#\?ClientAliveInterval.*/ClientAliveInterval 60/' /etc/ssh/sshd_config
  - sed -i 's/^#\?ClientAliveCountMax.*/ClientAliveCountMax 10/' /etc/ssh/sshd_config
  - systemctl restart sshd

  # 防火墙
  - ufw default deny incoming
  - ufw default allow outgoing
  - ufw allow ssh
  - ufw allow http
  - ufw allow https
  - ufw --force enable

  # Fail2ban
  - systemctl enable --now fail2ban

  # 自动更新
  - systemctl enable --now unattended-upgrades

final_message: "Cloud-init completed after $UPTIME seconds"

Server Tuning

服务器调优

Performance and Cleanup

性能优化与清理

yaml
#cloud-config
runcmd:
  # Reduce swap usage (better for databases/apps with their own memory management)
  - |
    if ! grep -q "vm.swappiness=10" /etc/sysctl.conf; then
      echo "vm.swappiness=10" >> /etc/sysctl.conf
      sysctl -p
    fi

  # Set timezone
  - timedatectl set-timezone UTC  # Or: Europe/Berlin, America/New_York

  # Cleanup
  - apt-get autoremove -y
  - apt-get clean
yaml
#cloud-config
runcmd:
  # 减少swap使用(对使用自有内存管理的数据库/应用更友好)
  - |
    if ! grep -q "vm.swappiness=10" /etc/sysctl.conf; then
      echo "vm.swappiness=10" >> /etc/sysctl.conf
      sysctl -p
    fi

  # 设置时区
  - timedatectl set-timezone UTC  # 或者:Europe/Berlin, America/New_York

  # 清理
  - apt-get autoremove -y
  - apt-get clean

Swappiness Values

Swappiness 值说明

ValueBehavior
0
Only swap to avoid OOM
10
Minimal swapping (recommended for apps)
60
Default Ubuntu
100
Aggressive swapping
数值行为
0
仅在避免内存不足时使用swap
10
最小化swap使用(推荐给应用服务器)
60
Ubuntu默认值
100
激进式swap使用

Debugging

调试

Check Cloud-Init Status

检查Cloud-Init状态

bash
undefined
bash
undefined

View cloud-init status

查看cloud-init状态

cloud-init status
cloud-init status

View cloud-init logs

查看cloud-init日志

cat /var/log/cloud-init.log cat /var/log/cloud-init-output.log
cat /var/log/cloud-init.log cat /var/log/cloud-init-output.log

Re-run cloud-init (for testing)

重新运行cloud-init(用于测试)

sudo cloud-init clean sudo cloud-init init
undefined
sudo cloud-init clean sudo cloud-init init
undefined

Common Issues

常见问题

IssueCauseFix
YAML parse errorIndentation wrongUse 2-space indent, validate YAML
User not createdMissing
users:
key
Ensure
users:
is at root level
Packages not installed
package_update: false
Set
package_update: true
SSH key rejectedWrong key formatUse full public key string
Service not startingOrder dependencyUse
After=
in systemd unit
问题原因解决方法
YAML解析错误缩进错误使用2空格缩进,验证YAML格式
用户未创建缺少
users:
确保
users:
在根层级
软件包未安装
package_update: false
设置
package_update: true
SSH密钥被拒绝密钥格式错误使用完整的公钥字符串
服务未启动依赖顺序问题在systemd单元中使用
After=