Cloud-init ESXi 尝试

发布于 10 天前  31 次阅读


我这里使用ESXI 7.0.3 和 jammy-server-cloudimg-amd64.ova 进行尝试cloud-init

使用ova创建虚拟机 ,先不要开机,在编辑->高级 中添加cloud-init的base64参数,保存后开机

  • guestinfo.userdata
  • guestinfo.userdata.encoding="base64"
  • guestinfo.metadata
  • guestinfo.metadata.encoding="base64"

python生成配置脚本

import uuid
import base64
import shutil
import subprocess
from pathlib import Path
from textwrap import dedent
from pyfiglet import Figlet

# --- 配置区 ---
OUTPUT_BASE_DIR = Path("./cloudinit_output")
HOSTNAMES = [
    "ubuntu-master",
    "ubuntu-worker01",
    "ubuntu-worker02"
]
# --- 配置区结束 ---

def generate_ssh_keypair(key_path: Path):
    key_path.parent.mkdir(parents=True, exist_ok=True)
    if key_path.exists():
        print(f"密钥文件已存在,跳过生成:{key_path}")
        return
    print(f"生成新的 SSH 密钥对: {key_path}")
    try:
        subprocess.run(
            ["ssh-keygen", "-t", "rsa", "-b", "4096", "-f", str(key_path), "-N", "", "-q"],
            check=True
        )
        print(f"密钥对已生成: {key_path} 和 {key_path.with_suffix('.pub')}")
    except (FileNotFoundError, subprocess.CalledProcessError) as e:
        print(f"[X] ssh-keygen 命令执行失败: {e}")
        print("[i] 请确保您的系统中安装了 OpenSSH 客户端,并且 'ssh-keygen' 在系统 PATH 中。")
        raise


def get_ssh_pubkey(pubkey_path: Path) -> str:
    if not pubkey_path.exists():
        raise FileNotFoundError(f"公钥文件不存在: {pubkey_path}")
    return pubkey_path.read_text(encoding="utf-8").strip()


def manual_input_pubkey() -> str:
    while True:
        print("\n请输入 SSH 公钥(单行格式,如 ssh-rsa AAAA...),输入完成后按回车:")
        key = input("公钥: ").strip()
        if key.startswith("ssh-rsa") or key.startswith("ssh-ed25519"):
            return key
        print("输入无效。公钥通常以 'ssh-rsa' 或 'ssh-ed25519' 开头。请重试。")


"""
        #cloud-config
        hostname: {hostname}
        fqdn: {hostname}.local
        manage_etc_hosts: true

        # 用户和认证设置
        disable_root: false
        ssh_pwauth: false

        users:
          - name: root
            # 关键修正:'ssh_authorized_keys' 必须与 'name' 对齐
            ssh_authorized_keys:
              - {ssh_key}

        # (可选) 设置 root 密码
        # chpasswd:
        #   list: |
        #     root:password123
        #   expire: false

        # 启动后执行的命令
        runcmd:
          # 1. 替换 APT 软件源为清华大学镜像 (推荐保留)
          - sed -i 's@https?://[^/]+/ubuntu/@https://mirrors.tuna.tsinghua.edu.cn/ubuntu/@g' /etc/apt/sources.list

          # 2. 增强 SSH 安全性,禁止 root 密码登录
          - sed -i 's/^#?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config

          # 3. 重启 SSH 服务使配置生效
          - systemctl restart sshd

          # 4. 更新系统
          - apt-get update
          - apt-get upgrade -y
    """
def generate_user_data(hostname: str, ssh_key: str) -> str:
    return dedent(f"""\
        #cloud-config
        hostname: {hostname}
        fqdn: {hostname}.local
        manage_etc_hosts: true
        disable_root: false
        ssh_pwauth: false
        users:
          - name: root
            ssh_authorized_keys:
              - {ssh_key}
        runcmd:
          - sed -i 's@https?://[^/]+/ubuntu/@https://mirrors.tuna.tsinghua.edu.cn/ubuntu/@g' /etc/apt/sources.list
          - sed -i 's/^#?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
          - systemctl restart sshd
          - apt-get update
          - apt-get upgrade -y
    """)

def generate_meta_data(hostname: str) -> str:
    instance_id = f"{hostname}-{uuid.uuid4().hex[:8]}"
    return dedent(f"""\
        instance-id: {instance_id}
        local-hostname: {hostname}
    """)


def create_cloud_init_iso(hostname: str, source_dir: Path):
    command_name = "genisoimage"
    command_path = shutil.which(command_name)
    if not command_path:
        command_name = "mkisofs"
        command_path = shutil.which(command_name)

    if not command_path:
        print(f"[!] 警告: 未找到 'genisoimage' 或 'mkisofs' 命令,无法为 {hostname} 创建 seed.iso。")
        print("    - 在 Debian/Ubuntu 上, 请运行: sudo apt-get install -y cloud-image-utils")
        print("    - 在 CentOS/RHEL/Fedora 上, 请运行: sudo yum install -y genisoimage")
        print("    - 在 Windows 上, 您可以安装 cdrtools 或手动创建 ISO。")
        return

    iso_path = source_dir / "seed.iso"
    print(f"正在为 {hostname} 创建 cloud-init ISO: {iso_path} (使用 {command_name})")

    try:
        subprocess.run(
            [
                command_path,
                "-output", str(iso_path),
                "-volid", "cidata",
                "-joliet",
                "-rock",
                str(source_dir)
            ],
            check=True,
            capture_output=True
        )
        print(f"[√] 成功创建 {iso_path}")
    except subprocess.CalledProcessError as e:
        print(f"[X] 使用 {command_name} 创建 ISO 失败:")
        print(f"    错误码: {e.returncode}")
        print(f"    错误输出: {e.stderr.decode(errors='ignore')}")


def main():
    print("--- Cloud-Init 配置生成器 ---")
    ssh_pub_key = ""
    while True:
        choice = input(
            "请选择 SSH 公key获取方式:\n1) 手动输入公钥\n2) 自动生成/使用现有密钥对\n请输入 1 或 2: ").strip()
        if choice in ["1", "2"]: break
        print("无效选择,请重试。")
    try:
        if choice == "1":
            ssh_pub_key = manual_input_pubkey()
        else:
            priv_key_path = OUTPUT_BASE_DIR / "auth" / "id_rsa"
            generate_ssh_keypair(priv_key_path)
            ssh_pub_key = get_ssh_pubkey(priv_key_path.with_suffix(".pub"))
            print(f"\n[i] 将使用以下公钥(位于 {priv_key_path.with_suffix('.pub')}):")
            print(ssh_pub_key)
    except Exception as e:
        print(f"\n[X] 获取SSH密钥时发生错误: {e}")
        return

    while True:
        out_choice = input(
            "\n请选择输出格式:\n1) 生成独立文件和 seed.iso (用于 NoCloud 数据源)\n2) 生成 Base64 键值对 (同时保存原始YAML文件)\n请输入 1 或 2: ").strip()
        if out_choice in ["1", "2"]: break
        print("无效选择,请重试。")

    print("\n--- 开始生成配置文件 ---")
    if out_choice == "1":
        for hostname in HOSTNAMES:
            host_dir = OUTPUT_BASE_DIR / hostname
            host_dir.mkdir(parents=True, exist_ok=True)
            user_data = generate_user_data(hostname, ssh_pub_key)
            meta_data = generate_meta_data(hostname)
            (host_dir / "user-data").write_text(user_data, encoding="utf-8")
            (host_dir / "meta-data").write_text(meta_data, encoding="utf-8")
            print(f"[√] 已为 {hostname} 生成配置文件于: {host_dir}/")
            create_cloud_init_iso(hostname, host_dir)
    else:  # out_choice == "2"
        all_configs_path = OUTPUT_BASE_DIR / "all_hosts_guestinfo.txt"
        all_configs_path.parent.mkdir(parents=True, exist_ok=True)
        with all_configs_path.open("w", encoding="utf-8") as f_all:
            for i, hostname in enumerate(HOSTNAMES):
                print(f"\n--- 正在处理: {hostname} ---")
                # 关键修复:使用 ssh_pub_key 而不是 ssh_key
                user_data_str = generate_user_data(hostname, ssh_pub_key)
                meta_data_str = generate_meta_data(hostname)
                host_dir = OUTPUT_BASE_DIR / hostname
                host_dir.mkdir(parents=True, exist_ok=True)
                (host_dir / "user-data.yaml").write_text(user_data_str, encoding="utf-8")
                (host_dir / "meta-data.yaml").write_text(meta_data_str, encoding="utf-8")
                print(f"[i] 原始YAML文件已保存至: {host_dir}/")
                user_data_b64 = base64.b64encode(user_data_str.encode("utf-8")).decode("utf-8")
                meta_data_b64 = base64.b64encode(meta_data_str.encode("utf-8")).decode("utf-8")
                output_content = dedent(f"""\
                    # Hostname: {hostname}
                    guestinfo.userdata="{user_data_b64}"
                    guestinfo.userdata.encoding="base64"
                    guestinfo.metadata="{meta_data_b64}"
                    guestinfo.metadata.encoding="base64"
                """)
                print(f"[√] 正在生成 Base64 配置...")
                if i > 0: f_all.write("\n")
                f_all.write(output_content)
        print(f"\n[√] 所有主机的 Base64 配置已合并保存到: {all_configs_path}")

    print("\n--- 所有任务完成 ---")


if __name__ == "__main__":
    try:
        f = Figlet(font='slant')
        print(f.renderText('cloud-init creat config by inuyume'))
    except NameError:
        print("--- cloud-init creat config by inuyume ---")

    main()

    print(
        "\n[Hint] : 如果想使用SSH密码,请在generate_user_data的dedent中修改prohibit-password为yes并取消chpasswd的注释并修改默认的password123")
    print(
        "\n[Hint] : 请注意保存生成的公钥和私钥和生成的配置文件,相同目录下运行脚本会覆盖旧的生成文件,密钥默认使用已有的密钥!")

最后更新于 2025-07-21