ᕕ( ᐛ )ᕗ Jimyag's Blog

深入理解 cloud-init:配置、流程与数据源

· 2307 字 · 约 11 分钟

概述

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

本文深入分析 cloud-init 的:

  • 配置类型与格式
  • 启动执行流程
  • 条件判断机制
  • 数据源架构

一、配置类型

cloud-init 处理三类配置数据:meta-datauser-datavendor-data

1.1 Meta-data(实例元数据)

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

格式

YAML 格式,包含实例标识和平台信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 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 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/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 示例(最早执行)

1
2
3
#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 启动阶段总览

  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 工具

关键逻辑

1
2
3
4
5
# 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

时序要求

1
2
3
# systemd 依赖关系
Before=network-pre.target
After=hv_kvp_daemon.service

目的

  1. 查找本地数据源(不需要网络)
  2. 应用网络配置

执行流程

  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):

1
2
3
4
5
6
7
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

时序要求

1
2
3
4
5
# systemd 依赖关系
After=cloud-init-local.service
After=NetworkManager.service
Before=network-online.target
Before=sshd.service

目的

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

执行流程

  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

数据处理顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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

时序要求

1
After=network-online.target

目的:运行配置模块(不影响关键启动)

执行的模块(来自 /etc/cloud/cloud.cfg):

1
2
3
4
5
6
7
8
9
cloud_config_modules:
  - ssh_import_id
  - keyboard
  - locale
  - set_passwords
  - rsyslog
  - ca-certs
  - ntp
  - timezone

2.6 Final 阶段(最终阶段)

systemd 服务cloud-final.service

时序要求

1
2
# 在大多数服务之后运行
Before=rc-local.service

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

执行内容

  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 首次启动判断

核心问题:如何判断当前启动是"首次启动"还是"后续启动"?

判断逻辑

  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):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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  # 后续启动

缓存位置

1
2
3
/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 永远只执行一次 系统级初始化

频率控制实现

1
2
3
4
5
/var/lib/cloud/instance/sem/
├── config-ssh          # 已执行标记
├── config-users_groups
├── config-runcmd
└── scripts-per-instance/

判断逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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 配置

1
2
# /etc/cloud/cloud.cfg
manual_cache_clean: true

行为对比

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

安全影响

1
2
3
4
5
# 手动清理缓存(重置实例)
cloud-init clean

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

四、数据源架构

数据源(DataSource)是 cloud-init 获取配置数据的抽象接口

4.1 数据源类层次

  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 基类接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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. 种子目录

    1
    2
    3
    
    /var/lib/cloud/seed/nocloud/
    ├── meta-data    # 实例元数据
    └── user-data    # 用户配置
    
  2. 内核命令行

    1
    2
    
    # GRUB 配置
    GRUB_CMDLINE_LINUX="ds=nocloud-net;s=http://example.com/seed/"
    
  3. ISO 镜像

    1
    2
    3
    
    # 创建种子 ISO
    genisoimage -output seed.iso -volid cidata -joliet -rock \
      user-data meta-data
    
  4. DMI 数据

    1
    2
    
    # 在虚拟机配置中设置
    # System Serial Number = "ds=nocloud"
    

查找顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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

使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 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 结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "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

1
2
3
4
5
6
{
  "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 模块执行频率配置

1
2
3
4
5
6
7
# /etc/cloud/cloud.cfg
cloud_config_modules:
  - mounts               # 默认 PER_INSTANCE
  - ssh                  # 默认 PER_INSTANCE
  - [apt_update_upgrade, always]  # 强制 PER_ALWAYS
  - runcmd
  - puppet

七、实战示例

7.1 创建完整云配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#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 本地测试流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 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 调试技巧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 查看执行日志
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 个数据源实现,支持所有主流云平台

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

#Cloud-Init #Cloud #Linux #Devops #Virtualization