深入浅出KVM虚拟机创建——手动及脚本部署——Ansible安全基线配置(二)

举报
Prism 发表于 2025/11/13 11:42:11 2025/11/13
【摘要】 引言大家好,欢迎来到我的 《Ansible 安全自动化:从服务器到K8S的堡垒之路》 系列教程,我是Prism!在这个系列里,我们的目标是彻底告别繁琐的人工配置,用 Ansible 打造一套固若金汤、一键部署的服务器与K8S安全基线本篇为KVM手工及Shell脚本创建,通过本篇文章的学习,您将了解:1、手动创建KVM虚拟机2、Shell脚本创建虚拟机链接:1、如您需进一步理解KVM技术原理...

引言

大家好,欢迎来到我的 《Ansible 安全自动化:从服务器到K8S的堡垒之路》 系列教程,我是Prism!
在这个系列里,我们的目标是彻底告别繁琐的人工配置,用 Ansible 打造一套固若金汤、一键部署的服务器与K8S安全基线
本篇为KVM手工及Shell脚本创建,通过本篇文章的学习,您将了解:
1、手动创建KVM虚拟机
2、Shell脚本创建虚拟机

链接
1、如您需进一步理解KVM技术原理,请拜读:深入理解KVM技术原理
2、如您需进一步理解virsh命令,请拜读:KVM基础

预告: 下一期将介绍Ansible部署KVM虚拟机

注意
1、本教程使用的系统镜像是qcow2格式,如果您需要安装iso格式的镜像(需要console连接或VNC连接很麻烦) ,请移步别的文章
2、如果大家对本文的某些概念不清楚,强烈建议大家读链接中的内容,本人找了三个完全不懂KVM的小白做实验,看完我的一系列文章后,都能做到知其然也知其所以然
3、如大家有更好的建议,欢迎提出,如果喜欢煮波的内容,点点关注不迷路,谢谢大家!!!*

本想将Ansible创建虚拟机的部分也放在该文档中,但是受到篇幅限制,只能另起一篇文章,不好意思了,大家!

准备好了吗?发车咯!!!

环境准备

1、手动部署和脚本部署的环境

sudo apt update -y
sudo apt install -y \
    qemu-kvm \
    qemu-utils \
    libvirt-daemon-system \
    libvirt-clients \
    bridge-utils \
    virt-install \
    wget
#  启动服务并设置开机自启
sudo systemctl enable --now libvirtd
# 添加用户权限
sudo usermod -aG libvirt "$CALLING_USER"
sudo usermod -aG kvm "$CALLING_USER"

2、创建或查看SSH公钥

创建SSH公钥(如果您之前没有创建)

# 之后按需填写内容即可
ssh-keygen -t rsa -b 4096 -f ~/.ssh/kvm_rsa -N ""

查看SSH公钥

# 列出所有密钥
ls -l ~/.ssh
# 查看密钥,注意替换密钥名
cat kvm_rsa

手动创建虚拟机

如果您只是部署单个虚拟机,且没有自动化工具部署的脚本可用,那么手动部署是最快的解决方案

一、开辟“仓库”(存储池)

在宿主机上创建一个物理目录,然后用virsh命令将其"定义"并"激活"为一个Libvirt存储池

1、创建物理目录

#  创建物理目录
sudo mkdir -p /kvm/images/1

2、使用virsh定义存储池,告诉Libvirt,这个目录从现在开始归它管了,我们给它起个名字叫kvm-pool

# pool-define-as [名字] [类型] --target [路径]
# 我们使用最简单的dir类型
virsh pool-define-as my-vm-pool dir --target /kvm/images/1

3、构建并激活存储池

定义只是创建了配置,现在我们正式“开张”这个仓库

# 构建存储池(让Libvirt识别)
virsh pool-build my-vm-pool

# 激活(启动)存储池
virsh pool-start my-vm-pool

# 设置自动启动:让宿主机重启后,这个"仓库"也能自动”开门营业“
virsh pool-autostart my-vm-pool

4、验证存储池是否创建成功

virsh pool-list --all

二、准备“物料”(Base镜像)和“链接克隆”硬盘

这里假设您已经下载好了qcow2格式的Base镜像,我使用的是Rocky9,另外,本系列教程都将采用Rocky9

# 若您已有镜像,直接执行下列命令
qemu-img create -f qcow2 -F qcow2 \
    -b "/kvm/iso/Rocky9-Base.qcow2" \
    "/kvm/images/1/linux1.qcow2"
# 若您没有镜像,可以执行以下命令下载一个Rocky9镜像(请在您存放镜像的目录下执行该命令)
wget https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
mv Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 Rocky9-Base.qcow2
qemu-img create -f qcow2 -F qcow2 \
    -b "/kvm/iso/Rocky9-Base.qcow2" \
    "/kvm/images/1/linux1.qcow2"

三、创建并编辑cloud-init配置文件

如果您是第一次编写该配置文件,建议先编写一个没有数据的模板,方便日后复用,这里为了图方便,直接创建


vim /tmp/user-data-linux1.yaml 

千万不要删掉#cloud-config

#cloud-config
# 指定创建的用户
user: prism
# 给他 sudo 权限
sudo: ['ALL=(ALL) NOPASSWD:ALL']
# 确保家目录被创建
system_info:
  default_user:
    name: prism

# 设置主机名
hostname: linux1

# 添加您的公钥  
ssh_authorized_keys:
  - XXX

四、执行virsh-install安装虚拟机

按需填写配置,尤其注意您的地址的填写

virt-install \
    --name "linux1" \
    --vcpus 2 \
    --memory 2048 \
    --disk path="/kvm/images/1/linux1.qcow2",device=disk \
    --network network=default \
    --os-variant "rocky9.0" \
    --import \
    --noautoconsole \
    --cloud-init "user-data=/tmp/user-data-linux1.yaml"

–name:虚拟机名
–vcpus:核心数
–memory:内存
–disk:指定磁盘文件位置
–import:导入现有磁盘(直接导入一个已经准备好的磁盘镜像,省去安装系统的漫长等待)
–network:指定虚拟网络(知识点扩展包2中有详解)
–os-variant:告诉libvirt,这台机器跑的是Rocky Linux9
–noautoconsole:不自动连接控制台,机器创建完成后,不要自动弹出一个控制台窗口
–cloud-init:指定cloud-init配置文件

五、验证

# 查看linux1的ip
virsh domifaddr linux1
# 查看是否能连接
ssh [prism]@[ip_addr]
# 查看用户权限是否出现问题
sudo su root
# 确认无误后,设置虚拟机为开机自动启动
exit
virsh autostart linux1
# 删除临时配置文件
rm /tmp/user-data-linux1.yaml

六、创建多台虚拟机

重复第二到第六步即可,注意更改文件名(主机名)

如您发现您无法使用SSH密钥登录虚拟机,您可以移步知识扩展包2查阅设置密码登录的方法

Shell脚本创建虚拟机

在对幂等性要求不高(if语句不多),且是单机部署的环境中,使用Shell脚本创建虚拟机是效率最高的解决方案(编写完脚本后,一个命令生成多个虚拟机的感觉不要太爽!)
本人对脚本的理解在知识点扩展包中,如有需要,请移步

一、创建脚本

其实如果脚本的使用目标是高度自定义的一次性脚本或脚本较为简单,可以考虑不使用配置脚本,直接使用一个文件完成所有部署,强行使用配置文件和运行脚本分离也许会成为过度设计
但是这里我主要是面向大家,故采用分离的模式,大家也完全可以为了省事,直接把相关变量硬编码在脚本中

1、创建脚本运行目录

mkdir -p /script/kvm
cd /script/kvm
# 创建配置脚本
touch config.sh
# 创建环境配置脚本
touch setup.sh
# 创建运行脚本
touch create.sh
# 创建销毁脚本
touch delete.sh

2、编辑脚本

配置文件

#!/usr/bin/env bash
# config.sh

# 1、 用户自定义路径
export KVM_BASE_PATH="/kvm" # 存放虚拟机的位置的根目录
export TEMP_CONFIG_PATH="/tmp/kvm_configs"

# 2、 存储池配置
export STORAGE_POOL_NAME="my_kvm_pool"
export STORAGE_POOL_PATH="${KVM_BASE_PATH}/images/1"

# 3、 基础镜像配置
export BASE_IMAGE_DIR="${KVM_BASE_PATH}/iso"
export BASE_IMAGE_NAME="Rocky9-Base.qcow2" # 如果您已有基础镜像,那么填入到这里
# 定义一个默认的基础镜像下载地址,如您没有基础镜像,则自动下载
export BASE_IMAGE_URL="https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
export BASE_IMAGE_FULL_PATH="${BASE_IMAGE_DIR}/${BASE_IMAGE_NAME}"

# 4、 虚拟机规格
export VM_VCPUS=2
export VM_MEMORY=2048
export OS_VARIANT="rocky9.0"

# 5、 要创建的虚拟机列表
export VM_NAME_PREFIX="linux"
export VM_COUNT=9  # 要创建的虚拟机数量
VMS_TO_CREATE=() # 创建一个空数组
for i in $(seq 1 $VM_COUNT); do
    VMS_TO_CREATE+=("${VM_NAME_PREFIX}${i}") 
done
export VMS_TO_CREATE

export USER_SSH_PUBLIC_KEY="ssh-ed25519 AAAAC3NzaC...[你的公钥]...com"

# 6、 虚拟机内用户定义
USER_NAME="prism"

环境安装脚本

#!/usr/bin/env bash
# setup.sh

# 权限检查
if [[ "$EUID" -ne 0 ]]; then
  echo "错误: 此脚本 必须以 sudo (或 root) 身份运行!"
  exit 1
fi

# 获取“真正”调用 sudo 的那个用户名,比如 'prism'
# 如果直接是 root 登录,就用 'root'
CALLING_USER="${SUDO_USER:-$USER}"

echo "开始准备 KVM 宿主机环境"
echo "将为用户 '$CALLING_USER' 配置权限"
sleep 2

# 1更新和安装 (假设是 Ubuntu/Debian)
echo "更新 apt 缓存"
apt update -y

echo "安装 KVM 软件包全家桶"
apt install -y \
    qemu-kvm \
    qemu-utils \
    libvirt-daemon-system \
    libvirt-clients \
    bridge-utils \
    virt-install \
    wget

# 2、 启动服务
echo "启动并设置自启 libvirtd 服务"
systemctl enable --now libvirtd

# 3、设置用户权限
echo "正在将用户 '$CALLING_USER' 添加到 'libvirt' 和 'kvm' 组"
# (usermod 是幂等的,重复添加也没关系)
usermod -aG libvirt "$CALLING_USER"
usermod -aG kvm "$CALLING_USER"

echo "主机环境部署完毕,请您重启终端"

编辑运行脚本

#!/usr//bin/env bash
# create.sh
print_header() {
    echo "============================================="
    echo ""
    echo "============================================="
}
# 1、加载配置文件与环境检查
CONFIG_FILE="./config.sh"
if [ ! -f "$CONFIG_FILE" ]; then
    echo " 错误: 配置文件 '$CONFIG_FILE' 未找到!"
    exit 1
fi
source "$CONFIG_FILE"

print_header "检查环境依赖"

#检查1、 关键命令是否存在
if ! command -v virsh &> /dev/null; then
    echo "错误: 'virsh' (libvirt-clients) 未安装!"
    echo "请先运行 'sudo ./setup_host.sh' 来准备环境"
    exit 1
fi

if ! command -v qemu-img &> /dev/null; then
    echo "错误: 'qemu-img' (qemu-utils) 未安装!"
    echo "请先运行 'sudo ./setup_host.sh' 来准备环境"
    exit 1
fi

# 检查2、 权限是否OK (我们假设非 root 用户运行)
if [[ "$EUID" -ne 0 ]]; then
    if ! groups | grep -qE '(libvirt|kvm)'; then
        echo "警告: 您不在 'libvirt' 或 'kvm' 组中!"
        echo "如果您刚刚才运行了 'setup_host.sh',请退出并重新登录!"
        echo "否则,您可能需要 'sudo' 权限来运行此脚本"
        echo "5秒后继续"
        sleep 5
    else
        echo "权限检查通过 (libvirt/kvm 组成员)"
    fi
fi

# 2、创建物理目录
print_header "正在检查并创建物理目录"
for dir in "$STORAGE_POOL_PATH" "$BASE_IMAGE_DIR" "$TEMP_CONFIG_PATH"; do
    if [ ! -d "$dir" ]; then
        echo "--> 正在创建目录: $dir"
        # 假设您有 sudo 权限,或者 /kvm 目录权限正确
        mkdir -p "$dir"
    fi
done

print_header "正在检查基础镜像"
if [ ! -f "$BASE_IMAGE_FULL_PATH" ]; then
    echo "基础镜像不存在,正在从 $BASE_IMAGE_URL 下载"
    # 假设您有wget,并且有权限写入 $BASE_IMAGE_DIR
    wget -O "$BASE_IMAGE_FULL_PATH" "$BASE_IMAGE_URL"
    if [ $? -ne 0 ]; then
        echo "错误: 镜像下载失败!"
        exit 1
    fi
else
    echo "基础镜像 '$BASE_IMAGE_FULL_PATH' 已存在"
fi

# 3、创建 KVM 存储池
print_header "正在检查 KVM 存储池 '$STORAGE_POOL_NAME'..."
# 检查 'virsh' 是否需要 'sudo'
if ! virsh pool-info "$STORAGE_POOL_NAME" >/dev/null 2>&1; then
    echo "存储池不存在,正在创建"
    virsh pool-define-as --name "$STORAGE_POOL_NAME" --type dir --target "$STORAGE_POOL_PATH"
    virsh pool-build "$STORAGE_POOL_NAME"
    virsh pool-start "$STORAGE_POOL_NAME"
    virsh pool-autostart "$STORAGE_POOL_NAME"
else
    echo "存储池 '$STORAGE_POOL_NAME' 已激活。"
    virsh pool-refresh "$STORAGE_POOL_NAME" # (确保它刷新了新镜像)
fi


# 4、 循环创建所有虚拟机
print_header "开始批量创建虚拟机"

for vm_name in "${VMS_TO_CREATE[@]}"
do
    echo "\n正在处理: $vm_name"
    
    VM_DISK="${STORAGE_POOL_PATH}/${vm_name}.qcow2"
    USER_DATA_FILE="${TEMP_CONFIG_PATH}/user-data-${vm_name}.yaml"

    #创建链接克隆硬盘
    if [ ! -f "$VM_DISK" ]; then
        echo "正在创建链接克隆硬盘: $VM_DISK"
        qemu-img create -f qcow2 -F qcow2 \
            -b "$BASE_IMAGE_FULL_PATH" \
            "$VM_DISK"
    else
        echo "硬盘 $VM_DISK 已存在, 跳过。"
    fi

    # 创建 Cloud-Init 配置文件
    echo "生成 Cloud-Init 配置文件: $USER_DATA_FILE"
    cat > "$USER_DATA_FILE" <<EOF
#cloud-config

user: ${USER_NAME}
sudo: ['ALL=(ALL) NOPASSWD:ALL']
system_info:
  default_user:
    name: prism
hostname: ${vm_name}

ssh_authorized_keys:
  - ${SER_SSH_PUBLIC_KEY}
EOF

    # 创建 KVM 虚拟机
    if ! virsh dominfo "$vm_name" >/dev/null 2>&1; then
        echo "定义和安装虚拟机: $vm_name"
        virt-install \
            --name "$vm_name" \
            --vcpus "$VM_VCPUS" \
            --memory "$VM_MEMORY" \
            --disk path="$VM_DISK",device=disk \
            --network network=default \
            --os-variant "$OS_VARIANT" \
            --import \
            --noautoconsole \
            --cloud-init "user-data=${USER_DATA_FILE}"
    else
        echo "虚拟机 $vm_name 已定义, 跳过。"
    fi
    
    # 清理临时文件
    echo "-->清理 $USER_DATA_FILE"
    rm -f "$USER_DATA_FILE"

done

# 5、询问是否设置开机自启
print_header "设置开机自启动"
echo "所有虚拟机已创建完毕。"
# 询问用户,-n 1 表示读取1个字符后立即返回,-r 表示禁止反斜杠转义,-p 指定提示符
read -n 1 -r -p "您是否希望将所有这些虚拟机设置为开机自启动? (y/N) " response
echo # 输出一个换行符,使界面更整洁

if [[ "$response" =~ ^([yY])$ ]]; then
    echo -e "\n正在为所有虚拟机设置开机自启动..."
    for vm_name in "${VMS_TO_CREATE[@]}"
    do
        # 再次检查虚拟机是否真的存在
        if virsh dominfo "$vm_name" >/dev/null 2>&1; then
            echo "启用 $vm_name"
            virsh autostart "$vm_name"
        else
            echo "警告: $vm_name 似乎未正确定义,跳过自启动设置。"
        fi
    done
    echo "开机自启动设置完毕!"
else
    echo -e "\n跳过设置开机自启动。"
fi


print_header "自动化任务执行完毕! "
echo "您可以使用 'virsh start <虚拟机名>' 来启动它们"

编辑销毁脚本

#!/usr/bin/env bash
# delete.sh

print_header() {
    echo "============================================="
    echo ""
    echo "============================================="
}

# 加载配置文件
CONFIG_FILE="./config.sh"
if [ ! -f "$CONFIG_FILE" ]; then
    echo "错误: 配置文件 '$CONFIG_FILE' 未找到!"
    exit 1
fi
source "$CONFIG_FILE"

# 检查用户是否启用了--all参数
DELETE_ALL=false
if [[ "$1" == "--all" ]]; then
    DELETE_ALL=true
fi
}

# 销毁虚拟机 ---
print_header "正在销毁并取消定义虚拟机"

for vm_name in "${VMS_TO_CREATE[@]}"
do
    echo "\n正在处理: $vm_name"
    
    # 检查 VM 是否已定义
    if virsh dominfo "$vm_name" >/dev/null 2>&1; then
        # 检查 VM 是否在运行
        if virsh domstate "$vm_name" | grep -q "running"; then
            echo "正在强制关机 $vm_name"
            virsh destroy "$vm_name"
        fi
        
        echo "正在取消定义 $vm_name"
        virsh undefine "$vm_name"
    else
        echo "虚拟机 $vm_name 已被取消定义, 跳过"
    fi

    # 删除虚拟机 硬盘
    VM_DISK="${STORAGE_POOL_PATH}/${vm_name}.qcow2"
    if [ -f "$VM_DISK" ]; then
        echo "正在删除硬盘: $VM_DISK"
        rm -f "$VM_DISK"
    else
        echo "硬盘 $VM_DISK 已删除, 跳过"
    fi
done

# --all参数启用后
if [[ "$DELETE_ALL" == "true" ]]; then
    
    #销毁存储池
    print_header "正在销毁存储池 '$STORAGE_POOL_NAME'"
    if virsh pool-info "$STORAGE_POOL_NAME" >/dev/null 2>&1; then
        echo "正在停用$STORAGE_POOL_NAME"
        virsh pool-destroy "$STORAGE_POOL_NAME"
        echo "正在取消定义$STORAGE_POOL_NAME"
        virsh pool-undefine "$STORAGE_POOL_NAME"
    else
        echo "存储池 $STORAGE_POOL_NAME 已被取消定义, 跳过"
    fi
    
    echo "--> 正在删除临时配置目录: $TEMP_CONFIG_PATH"
    rm -rf "$TEMP_CONFIG_PATH"

    print_header "彻底清理完成!"
else
    print_header " 虚拟机清理完成! "
    echo "(存储池 '$STORAGE_POOL_NAME'未被删除,如需删除,请在运行脚本时加上“--all”参数)"
fi

3、运行脚本

设置执行权限

# 转到脚本所在目录
cd /script/kvm
# 为脚本添加执行权限
chmod +x create.sh 
chmod +x delete.sh
chmod +x setup.sh

运行创建脚本

# 务必以root权限运行setup.sh
sudo ./setup.sh
# 运行创建虚拟机的脚本
./create.sh

运行销毁脚本

只销毁虚拟机及其镜像文件

./delete.sh

销毁虚拟机及存储池

./delete.sh --all
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。