
## 概述

**cloud-init** 是云实例初始化的行业标准工具，支持所有主流云平台（AWS、Azure、GCP、OpenStack 等）。它在实例首次启动时自动配置网络、存储、SSH 密钥、用户账户等，实现云实例的自动化部署。

本文深入分析 cloud-init 的：
- 配置类型与格式
- 启动执行流程
- 条件判断机制
- 数据源架构

---

## 一、配置类型

cloud-init 处理三类配置数据：**meta-data**、**user-data** 和 **vendor-data**。

### 1.1 Meta-data（实例元数据）

meta-data 是**云平台提供的实例元信息**，描述实例本身的基本属性。

#### 格式

YAML 格式，包含实例标识和平台信息：

```yaml
# meta-data 示例
instance-id: i-87018aed              # 必需：实例唯一标识
local-hostname: web-server-01        # 建议：主机名
public-keys:                         # 建议：SSH 公钥
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ...
placement:
  availability-zone: us-east-1a     # 可用区
local-ipv4: 10.223.26.178           # 内网 IP
public-ipv4: 184.72.174.120         # 公网 IP
instance-type: m1.large             # 实例类型
ami-id: ami-fd4aa494                # 镜像 ID
security-groups: default            # 安全组
```

#### 必需字段

| 字段 | 说明 | 示例 |
|------|------|------|
| `instance-id` | 实例唯一标识，用于判断首次启动 | `i-87018aed` |

#### 建议字段

| 字段 | 说明 |
|------|------|
| `local-hostname` | 实例主机名 |
| `public-keys` | SSH 公钥列表 |
| `placement/availability-zone` | 可用区信息 |

#### 来源

meta-data 通常由**云平台元数据服务**提供：
- AWS EC2: `http://169.254.169.254/latest/meta-data/`
- Azure: `http://169.254.169.254/metadata/instance`
- GCE: `http://metadata.google.internal/computeMetadata/v1/instance/`

### 1.2 User-data（用户配置）

user-data 是**用户提供的初始化配置**，控制实例的具体设置。

#### 格式类型

user-data 支持多种格式：

| 格式 | 标识 | 说明 | 执行时机 |
|------|------|------|----------|
| **Cloud Config** | `#cloud-config` | YAML 配置文件 | Final 阶段 |
| **Shell Script** | `#!` | Bash 脚本 | Final 阶段 |
| **Boothook** | `#cloud-boothook` | 早期钩子脚本 | Local 阶段 |
| **Include** | `#include` | URL 列表（下载更多配置） | Network 阶段 |
| **Mime Multi Part** | Content-Type | 混合多种格式 | 各阶段 |

#### Cloud Config 示例

```yaml
#cloud-config

# 用户管理
users:
  - name: deploy
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ...

# 软件包安装
packages:
  - nginx
  - docker.io
  - python3-pip

# 写文件
write_files:
  - path: /etc/nginx/sites-available/app
    content: |
      server {
          listen 80;
          server_name _;
          location / {
              proxy_pass http://127.0.0.1:8000;
          }
      }
    permissions: '0644'

# 运行命令
runcmd:
  - [ systemctl, enable, nginx ]
  - [ systemctl, start, nginx ]
  - docker run -d -p 8000:8000 myapp:latest

# 挂载点
mounts:
  - [ /dev/sdb, /data, ext4, defaults, 0, 2 ]

# SSH 配置
ssh_authorized_keys:
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ...
```

#### Shell Script 示例

```bash
#!/bin/bash
set -euo pipefail

echo "=== 开始初始化 ==="
apt-get update
apt-get install -y nginx docker.io

systemctl enable nginx docker
systemctl start nginx docker

echo "=== 初始化完成 ==="
```

#### Boothook 示例（最早执行）

```bash
#cloud-boothook
# 这个脚本在 Local 阶段执行，早于网络配置
echo "Boothook executed at $(date)" > /tmp/boothook.log
```

### 1.3 Vendor-data（厂商配置）

vendor-data 是**云厂商提供的配置**，通常包含：
- 云平台特定的优化设置
- 监控 Agent 安装
- 平台集成脚本

格式与 user-data 相同，但优先级低于 user-data。

---

## 二、启动执行流程

cloud-init 的启动分为**五个阶段**，每个阶段由独立的 systemd 服务管理。

### 2.1 启动阶段总览

```mermaid
graph TD
    A[Detect<br/>平台检测] --> B[Local<br/>网络前阶段]
    B --> C[Network<br/>网络阶段]
    C --> D[Config<br/>配置阶段]
    D --> E[Final<br/>最终阶段]
    
    B -->|阻塞| F[network-pre.target]
    C -->|阻塞| G[sshd.service]
    
    subgraph 服务依赖
        F
        G
        H[network-online.target]
    end
```

### 2.2 Detect 阶段（平台检测）

**目的**：检测运行平台，决定是否启用 cloud-init。

**执行者**：`ds-identify` 工具

**关键逻辑**：
```bash
# ds-identify 检测逻辑
1. 检查 DMI 信息（制造商、产品名）
2. 检查 /sys/class/dmi/id/product_uuid
3. 检查内核命令行参数
4. 匹配已知云平台特征
```

**结果**：
- 找到平台 → 启用 cloud-init 服务
- 未找到平台 → 禁用 cloud-init 服务

**平台检测示例**：
| 平台 | DMI 标识 |
|------|----------|
| AWS EC2 | `Amazon EC2` |
| Azure | `Microsoft Corporation` |
| GCE | `Google Compute Engine` |
| VMware | `VMware, Inc.` |
| LXD | `LXD` |

### 2.3 Local 阶段（本地阶段）

**systemd 服务**：`cloud-init-local.service`

**时序要求**：
```ini
# systemd 依赖关系
Before=network-pre.target
After=hv_kvp_daemon.service
```

**目的**：
1. 查找本地数据源（不需要网络）
2. 应用网络配置

**执行流程**：

```mermaid
graph TD
    A[开始] --> B{查找本地数据源}
    B -->|找到| C[读取 meta-data]
    B -->|未找到| D[等待网络数据源]
    C --> E{有网络配置?}
    E -->|有| F[渲染网络配置]
    E -->|无| G[使用 fallback 配置]
    F --> H[写入 /etc/network/interfaces.d/]
    G --> I[DHCP on eth0]
    H --> J[退出，等待网络启动]
    I --> J
    D --> J
```

**网络配置来源优先级**：
1. 内核命令行 `ip=` 参数
2. 数据源提供的网络配置
3. 系统配置 `/etc/cloud/cloud.cfg`
4. Fallback：DHCP on eth0

**关键代码**（`cloudinit/sources/__init__.py`）：
```python
class DataSource:
    network_config_sources = (
        NetworkConfigSource.CMD_LINE,      # 1. 内核命令行
        NetworkConfigSource.INITRAMFS,      # 2. Initramfs
        NetworkConfigSource.SYSTEM_CFG,     # 3. 系统配置
        NetworkConfigSource.DS,             # 4. 数据源
    )
```

### 2.4 Network 阶段（网络阶段）

**systemd 服务**：`cloud-init-network.service`

**时序要求**：
```ini
# systemd 依赖关系
After=cloud-init-local.service
After=NetworkManager.service
Before=network-online.target
Before=sshd.service
```

**目的**：
1. 处理需要网络的数据源
2. 下载并处理 user-data
3. 执行 early handlers

**执行流程**：

```mermaid
graph TD
    A[网络就绪] --> B[连接元数据服务]
    B --> C[下载 user-data]
    C --> D[下载 vendor-data]
    D --> E{处理 #include}
    E -->|有| F[递归下载 URL 内容]
    E -->|无| G{解压 gzip}
    F --> G
    G --> H{解析格式}
    H -->|Shell Script| I[存储到 scripts/]
    H -->|Cloud Config| J[合并配置]
    H -->|Boothook| K[立即执行]
    I --> L[继续下一阶段]
    J --> L
    K --> L
```

**数据处理顺序**：
```python
def process_user_data(raw_data):
    # 1. Gzip 解压
    if raw_data.startswith(b'\x1f\x8b'):
        raw_data = gzip.decompress(raw_data)
    
    # 2. Mime Multi Part 处理
    if is_mime_multipart(raw_data):
        for part in parse_mime(raw_data):
            process_user_data(part)  # 递归处理
    
    # 3. 根据开头标识分发处理
    if raw_data.startswith('#!'):
        handle_shell_script(raw_data)
    elif raw_data.startswith('#cloud-config'):
        handle_cloud_config(raw_data)
    elif raw_data.startswith('#cloud-boothook'):
        handle_boothook(raw_data)
    elif raw_data.startswith('#include'):
        handle_include(raw_data)
```

### 2.5 Config 阶段（配置阶段）

**systemd 服务**：`cloud-config.service`

**时序要求**：
```ini
After=network-online.target
```

**目的**：运行配置模块（不影响关键启动）

**执行的模块**（来自 `/etc/cloud/cloud.cfg`）：
```yaml
cloud_config_modules:
  - ssh_import_id
  - keyboard
  - locale
  - set_passwords
  - rsyslog
  - ca-certs
  - ntp
  - timezone
```

### 2.6 Final 阶段（最终阶段）

**systemd 服务**：`cloud-final.service`

**时序要求**：
```ini
# 在大多数服务之后运行
Before=rc-local.service
```

**目的**：执行用户脚本和最终配置

**执行内容**：
```mermaid
graph LR
    A[runcmd] --> B[scripts/per-instance]
    B --> C[scripts/per-boot]
    C --> D[scripts/user]
    D --> E[package 安装]
    E --> F[配置管理工具]
```

**执行顺序**：
1. `runcmd` 指令
2. `/var/lib/cloud/scripts/per-instance/`
3. `/var/lib/cloud/scripts/per-boot/`
4. `/var/lib/cloud/scripts/user/`
5. Package 安装（`packages:`）
6. 配置管理工具（Puppet、Ansible、Chef）

---

## 三、条件判断机制

cloud-init 使用**多重机制**判断是否执行特定配置。

### 3.1 首次启动判断

**核心问题**：如何判断当前启动是"首次启动"还是"后续启动"？

**判断逻辑**：

```mermaid
graph TD
    A[启动] --> B{缓存存在?}
    B -->|否| C[首次启动]
    B -->|是| D{instance-id 匹配?}
    D -->|是| E[后续启动]
    D -->|否| F[新实例首次启动]
    
    C --> G[执行所有 per-instance 配置]
    E --> H[只执行 per-boot 配置]
    F --> G
```

**关键代码**（`cloudinit/stages.py`）：
```python
def check_if_new_instance_id(self):
    # 从缓存读取上次的 instance-id
    previous_id = self.get_previous_instance_id()
    
    # 从数据源获取当前 instance-id
    current_id = self.datasource.get_instance_id()
    
    # 比较
    if previous_id == NO_PREVIOUS_INSTANCE_ID:
        return True  # 首次启动
    elif previous_id != current_id:
        return True  # 新实例（从镜像创建）
    else:
        return False  # 后续启动
```

**缓存位置**：
```
/var/lib/cloud/instance/obj.pkl           # Pickled 对象缓存
/var/lib/cloud/data/previous-instance-id  # 上次 instance ID
/var/lib/cloud/data/instance-id           # 当前 instance ID
```

### 3.2 执行频率控制

cloud-init 定义了三种执行频率：

| 频率 | 常量 | 说明 | 典型用途 |
|------|------|------|----------|
| **PER_INSTANCE** | `once-per-instance` | 每个实例执行一次 | 用户创建、包安装 |
| **PER_ALWAYS** | `always` | 每次启动都执行 | 日志清理、临时文件 |
| **PER_ONCE** | `once` | 永远只执行一次 | 系统级初始化 |

**频率控制实现**：
```
/var/lib/cloud/instance/sem/
├── config-ssh          # 已执行标记
├── config-users_groups
├── config-runcmd
└── scripts-per-instance/
```

**判断逻辑**：
```python
def run_module(module_name, freq):
    sem_file = f"/var/lib/cloud/instance/sem/{module_name}"
    
    if freq == PER_ALWAYS:
        return True  # 总是执行
    
    if freq == PER_INSTANCE:
        if os.path.exists(sem_file):
            return False  # 已执行，跳过
        else:
            touch(sem_file)
            return True
    
    if freq == PER_ONCE:
        global_sem = f"/var/lib/cloud/sem/{module_name}"
        if os.path.exists(global_sem):
            return False
        else:
            touch(global_sem)
            return True
```

### 3.3 manual_cache_clean 配置

**问题场景**：某些情况下，instance-id 判断会产生误判：
1. 元数据服务不稳定，无法获取 instance-id
2. 攻击者可以伪造数据源，触发重置

**解决方案**：`manual_cache_clean` 配置

```yaml
# /etc/cloud/cloud.cfg
manual_cache_clean: true
```

**行为对比**：

| 配置 | instance-id 不匹配时 | 行为描述 |
|------|---------------------|----------|
| `false`（默认） | 清理缓存，重新初始化 | `check` 模式，信任数据源 |
| `true` | 不清理，保持缓存 | `trust` 模式，信任缓存 |

**安全影响**：
```bash
# 手动清理缓存（重置实例）
cloud-init clean

# 或手动删除
rm -rf /var/lib/cloud/instance/*
```

---

## 四、数据源架构

数据源（DataSource）是 cloud-init 获取配置数据的**抽象接口**。

### 4.1 数据源类层次

```mermaid
graph TD
    A[DataSource 基类] --> B[DataSourceEc2]
    A --> C[DataSourceAzure]
    A --> D[DataSourceGCE]
    A --> E[DataSourceOpenStack]
    A --> F[DataSourceNoCloud]
    A --> G[DataSourceVMware]
    A --> H[DataSourceLXD]
    A --> I[其他 24 个...]
    
    subgraph 公有云
        B
        C
        D
    end
    
    subgraph 私有云
        E
        G
    end
    
    subgraph 本地测试
        F
        H
    end
```

### 4.2 DataSource 基类接口

```python
class DataSource(metaclass=abc.ABCMeta):
    # 基本属性
    dsname = "_undef"           # 数据源名称
    dsmode = DSMODE_NETWORK    # 运行模式
    
    # 必须实现的方法
    @abc.abstractmethod
    def _get_data(self):
        """获取数据，返回 True/False 表示是否成功"""
        pass
    
    # 可选重写的方法
    def get_userdata(self):
        """返回 user-data"""
        return self.userdata
    
    def get_instance_id(self):
        """返回 instance-id"""
        return self.metadata.get('instance-id')
    
    def get_public_ssh_keys(self):
        """返回 SSH 公钥列表"""
        return self.metadata.get('public-keys', [])
    
    def get_network_config(self):
        """返回网络配置"""
        return self._network_config
```

### 4.3 支持的数据源

#### 公有云数据源

| 数据源 | 文件 | 平台 |
|--------|------|------|
| DataSourceEc2 | `DataSourceEc2.py` | AWS EC2 |
| DataSourceAzure | `DataSourceAzure.py` | Microsoft Azure |
| DataSourceGCE | `DataSourceGCE.py` | Google Compute Engine |
| DataSourceOracle | `DataSourceOracle.py` | Oracle Cloud |
| DataSourceIBMCloud | `DataSourceIBMCloud.py` | IBM Cloud |
| DataSourceAliYun | `DataSourceAliYun.py` | 阿里云 |
| DataSourceHetzner | `DataSourceHetzner.py` | Hetzner Cloud |

#### 私有云数据源

| 数据源 | 文件 | 平台 |
|--------|------|------|
| DataSourceOpenStack | `DataSourceOpenStack.py` | OpenStack |
| DataSourceVMware | `DataSourceVMware.py` | VMware vSphere |
| DataSourceMAAS | `DataSourceMAAS.py` | Ubuntu MAAS |
| DataSourceSmartOS | `DataSourceSmartOS.py` | Joyent SmartOS |
| DataSourceCloudStack | `DataSourceCloudStack.py` | Apache CloudStack |

#### 本地/测试数据源

| 数据源 | 文件 | 用途 |
|--------|------|------|
| DataSourceNoCloud | `DataSourceNoCloud.py` | 本地测试、ISO 启动 |
| DataSourceConfigDrive | `DataSourceConfigDrive.py` | OpenStack ConfigDrive |
| DataSourceLXD | `DataSourceLXD.py` | LXD 容器 |
| DataSourceWSL | `DataSourceWSL.py` | Windows Subsystem for Linux |

### 4.4 NoCloud 数据源详解

NoCloud 是最常用的本地测试数据源。

#### 数据来源

1. **种子目录**：
   ```
   /var/lib/cloud/seed/nocloud/
   ├── meta-data    # 实例元数据
   └── user-data    # 用户配置
   ```

2. **内核命令行**：
   ```bash
   # GRUB 配置
   GRUB_CMDLINE_LINUX="ds=nocloud-net;s=http://example.com/seed/"
   ```

3. **ISO 镜像**：
   ```bash
   # 创建种子 ISO
   genisoimage -output seed.iso -volid cidata -joliet -rock \
     user-data meta-data
   ```

4. **DMI 数据**：
   ```bash
   # 在虚拟机配置中设置
   # System Serial Number = "ds=nocloud"
   ```

#### 查找顺序

```python
def _get_data(self):
    sources = [
        self._get_data_from_dmi(),        # 1. DMI 序列号
        self._get_data_from_cmdline(),    # 2. 内核命令行
        self._get_data_from_seed_dir(),   # 3. 种子目录
        self._get_data_from_iso(),        # 4. ISO 镜像
    ]
    
    for source in sources:
        if source:
            return True
    return False
```

#### 使用示例

```bash
# 1. 创建种子目录
sudo mkdir -p /var/lib/cloud/seed/nocloud

# 2. 写入 meta-data
cat <<EOF | sudo tee /var/lib/cloud/seed/nocloud/meta-data
instance-id: my-test-instance
local-hostname: test-vm
EOF

# 3. 写入 user-data
cat <<EOF | sudo tee /var/lib/cloud/seed/nocloud/user-data
#cloud-config
packages:
  - nginx
runcmd:
  - systemctl enable nginx
  - systemctl start nginx
EOF

# 4. 重置并重新运行
sudo cloud-init clean
sudo cloud-init init
```

---

## 五、目录结构详解

### 5.1 /var/lib/cloud 结构

```
/var/lib/cloud/
├── instance -> instances/i-87018aed/     # 当前实例符号链接
├── instances/
│   └── i-87018aed/
│       ├── boot-finished                 # 启动完成标记
│       ├── cloud-config.txt              # 合并后的完整配置
│       ├── user-data.txt                 # 原始 user-data
│       ├── user-data.txt.i               # 处理后的 user-data
│       ├── obj.pkl                       # Pickled 对象缓存
│       ├── datasource                    # 数据源信息
│       ├── sem/                          # 信号量目录（执行标记）
│       │   ├── config-ssh
│       │   ├── config-users_groups
│       │   ├── scripts-per-instance
│       │   └── ...
│       ├── scripts/                      # 脚本目录
│       │   ├── per-instance/            # 每实例执行一次
│       │   ├── per-boot/               # 每次启动执行
│       │   ├── per-once/                # 永远执行一次
│       │   └── user/                    # 用户脚本
│       ├── data/                         # 实例数据
│       └── handlers/                     # 处理器状态
├── scripts/
│   ├── per-instance/                     # 全局 per-instance 脚本
│   ├── per-boot/                         # 全局 per-boot 脚本
│   └── per-once/                         # 全局 per-once 脚本
├── seed/
│   └── nocloud/                          # NoCloud 种子目录
│       ├── meta-data
│       └── user-data
├── sem/                                  # 全局信号量
│   └── scripts.once
├── data/                                 # 全局数据
│   ├── previous-datasource
│   ├── previous-instance-id
│   └── previous-hostname
└── handlers/                             # 全局处理器
```

### 5.2 状态文件

#### /var/lib/cloud/data/status.json

```json
{
  "v1": {
    "init-local": {
      "errors": [],
      "start": 1712345678.123,
      "end": 1712345679.456
    },
    "init": {
      "errors": [],
      "start": 1712345680.789,
      "end": 1712345685.012
    },
    "modules-config": {
      "errors": [],
      "start": 1712345686.345,
      "end": 1712345690.678
    },
    "modules-final": {
      "errors": [],
      "start": 1712345691.901,
      "end": 1712345695.234
    },
    "datasource": "DataSourceEc2",
    "stage": null
  }
}
```

#### /var/lib/cloud/data/result.json

```json
{
  "v1": {
    "datasource": "DataSourceEc2",
    "errors": []
  }
}
```

---

## 六、配置模块详解

### 6.1 模块分类

cloud-init 的 63 个配置模块按功能分类：

#### 网络配置模块

| 模块 | 功能 | 阶段 |
|------|------|------|
| `cc_set_hostname` | 设置主机名 | Network |
| `cc_update_hostname` | 更新主机名 | Config |
| `cc_update_etc_hosts` | 更新 /etc/hosts | Config |
| `cc_resolv_conf` | 配置 DNS | Config |

#### 用户和认证模块

| 模块 | 功能 | 阶段 |
|------|------|------|
| `cc_users_groups` | 创建用户和组 | Config |
| `cc_ssh` | 配置 SSH | Config |
| `cc_set_passwords` | 设置用户密码 | Config |
| `cc_ssh_import_id` | 导入 SSH 密钥 | Config |
| `cc_ssh_authkey_fingerprints` | SSH 密钥指纹 | Final |

#### 存储模块

| 模块 | 功能 | 阶段 |
|------|------|------|
| `cc_disk_setup` | 磁盘分区 | Network |
| `cc_mounts` | 挂载点配置 | Config |
| `cc_growpart` | 扩展分区 | Config |
| `cc_resizefs` | 扩展文件系统 | Config |

#### 软件包模块

| 模块 | 功能 | 阶段 |
|------|------|------|
| `cc_package_update_upgrade_install` | 包安装和更新 | Config |
| `cc_apt_configure` | APT 源配置 | Config |
| `cc_yum_add_repo` | YUM 源配置 | Config |
| `cc_zypper_add_repo` | Zypper 源配置 | Config |

#### 脚本执行模块

| 模块 | 功能 | 阶段 |
|------|------|------|
| `cc_bootcmd` | 启动命令 | Network |
| `cc_runcmd` | 运行命令 | Final |
| `cc_scripts_per_instance` | 实例脚本 | Final |
| `cc_scripts_per_boot` | 启动脚本 | Final |
| `cc_scripts_user` | 用户脚本 | Final |

### 6.2 模块执行频率配置

```yaml
# /etc/cloud/cloud.cfg
cloud_config_modules:
  - mounts               # 默认 PER_INSTANCE
  - ssh                  # 默认 PER_INSTANCE
  - [apt_update_upgrade, always]  # 强制 PER_ALWAYS
  - runcmd
  - puppet
```

---

## 七、实战示例

### 7.1 创建完整云配置

```yaml
#cloud-config

# 1. 系统设置
hostname: web-server-01
fqdn: web-server-01.example.com
manage_etc_hosts: true
timezone: Asia/Shanghai
locale: en_US.UTF-8

# 2. 用户管理
users:
  - name: deploy
    primary_group: deploy
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    groups: [docker, sudo]
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7...

  - name: app
    system: true
    shell: /usr/sbin/nologin
    home: /opt/app

# 3. 软件包
package_update: true
package_upgrade: false
packages:
  - nginx
  - docker.io
  - docker-compose
  - python3-pip
  - git

# 4. 写文件
write_files:
  - path: /etc/nginx/sites-available/app
    content: |
      upstream backend {
          server 127.0.0.1:8000;
      }
      server {
          listen 80;
          server_name _;
          location / {
              proxy_pass http://backend;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
          }
      }
    permissions: '0644'

  - path: /opt/app/docker-compose.yml
    content: |
      version: '3'
      services:
        web:
          image: myapp:latest
          ports:
            - "8000:8000"
          restart: always
    permissions: '0644'
    owner: app:app

# 5. 挂载点
mounts:
  - [ /dev/sdb, /data, ext4, "defaults,noatime", 0, 2 ]
  - [ /dev/sdc, /logs, ext4, "defaults,noatime", 0, 2 ]

# 6. 运行命令
runcmd:
  # 启用服务
  - systemctl enable nginx docker
  - systemctl start nginx docker
  
  # 配置 Nginx
  - ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
  - rm -f /etc/nginx/sites-enabled/default
  - systemctl reload nginx
  
  # 启动应用
  - cd /opt/app && docker-compose up -d

# 7. SSH 配置
ssh_authorized_keys:
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7...

# 8. 最终消息
final_message: "云实例初始化完成！运行时间: $UPTIME 秒"
```

### 7.2 本地测试流程

```bash
# 1. 创建种子目录
sudo mkdir -p /var/lib/cloud/seed/nocloud

# 2. 写入配置
cat <<EOF | sudo tee /var/lib/cloud/seed/nocloud/meta-data
instance-id: test-vm-001
local-hostname: test-vm
EOF

cat <<EOF | sudo tee /var/lib/cloud/seed/nocloud/user-data
#cloud-config
packages:
  - nginx
runcmd:
  - systemctl enable nginx
  - systemctl start nginx
EOF

# 3. 清理并重新初始化
sudo cloud-init clean --logs
sudo cloud-init init --local
sudo cloud-init init

# 4. 检查状态
cloud-init status --wait
cloud-init query -f '{{ instance_data }}'
```

### 7.3 调试技巧

```bash
# 查看执行日志
journalctl -u cloud-init-local.service
journalctl -u cloud-init-network.service
journalctl -u cloud-config.service
journalctl -u cloud-final.service

# 查看 cloud-init 日志
cat /var/log/cloud-init-output.log
cat /var/log/cloud-init.log

# 分析执行时间
cloud-init analyze show
cloud-init analyze dump

# 查询实例数据
cloud-init query instance-id
cloud-init query ds.meta_data

# 验证配置语法
cloud-init schema --config-file user-data
```

---

## 总结

cloud-init 的核心机制：

1. **配置类型**：meta-data（实例元信息）、user-data（用户配置）、vendor-data（厂商配置）
2. **启动流程**：Detect → Local → Network → Config → Final 五个阶段
3. **条件判断**：基于 instance-id 判断首次启动，使用信号量控制执行频率
4. **数据源**：32 个数据源实现，支持所有主流云平台

理解这些核心概念，可以更好地控制和调试云实例的初始化过程。

