我这里使用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] : 请注意保存生成的公钥和私钥和生成的配置文件,相同目录下运行脚本会覆盖旧的生成文件,密钥默认使用已有的密钥!")
Comments NOTHING