概述
CentOS 良好地兼容了 RHEL (Red Hat Enterprise Linux) ,国内外有大量的使用者。但在 2024 年 6 月 30 日以后,CentOS Linux 7 也结束了生命周期,不再维护并且不能再进行软件的安装了。
在作者撰写的时间点,有一些兼容的发行版还在维护并且可以安装软件,本文选择了 Anolis 7.9 ISO 作为举例,详细描述了从一个 ISO 安装文件到最后可以在亚马逊云科技上使用的 AMI 的整个流程。
如果读者有类似于在亚马逊云科技上从 ISO文件安装、定制自己的操作系统的需求,也可以参考本文。
方案选择
为了将 ISO 安装文件变成一个可以运行的实例,首先需要在一个支持 KVM 的环境里启动虚拟机,使用 ISO 作为安装用的镜像来安装系统。
有三类方法可以实现
- 本地工作站 + 虚拟化应用程序(VMware, VirtualBox, Parallels 等)
- 本地服务器 + 虚拟化管理程序(KVM Hypervisor)
- 亚马逊云科技上启动祼机,安装开源的 KVM 虚拟化管理程序
本方案中,使用了下面的环境作为举例
- 裸机实例 metal
- 主机操作系统选择了 Ubuntu 24.04
- 主机安装开源的虚拟化解决方案 KVM/QEMU
- 要移植的操作系统选择 Anolis 7.9 版本的 ISO 作为举例
方案架构
该架构展示了从企业数据中心或云导出虚拟机(VM)并将其转换为 Amazon Machine Image(AMI)的完整流程。详细描述如下:
- 导出和导入:在企业的数据中心或云上,将已经安装、定制好的停止的实例(Stopped VM)导出成某种格式的镜像文件(例如:qcow2, vhdx, vmdk, ova, raw 等),然后上传到 Amazon S3 存储桶中。
- AMI 创建:在亚马逊云科技上,将已经上传到 S3 桶的镜像文件,导出成一个卷的快照(EBS snapshot),然后根据快照,生成 AMI(Amazon Machine Image)镜像。
- AMI 复制和创建:在创建 AMI 后,AMI 可以被复制(Copy)到其他区域,例如EU区域。这个步骤允许在全球范围内共享和使用 AMI,并且可以被用于启动新的实例。
具体安装流程
一、安装 ISO 镜像
配置虚拟化环境
在 Amazon EC2 上启动 metal 类型的实例:c6i.metal,使用 Ubuntu 24.04 x86_64 操作系统。
安装虚拟化程序:
# 安装 kvm虚拟化程序
$ sudo apt update
$ sudo apt install virtinst cpu-checker libvirt-daemon-system
# 确认虚拟化管理进程启动
$ sudo systemctl status libvirtd
创建临时虚拟机使用的磁盘,<name> 请使用实际情况下的名字,大小使用实际情况下的大小
$ qemu-img create -f qcow2 name.qcow2 10G
Formatting 'name.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=10737418240 lazy_refcounts=off refcount_bits=16
启动临时虚拟机,安装操作系统,参数以实际情况为准
# 将用户添加到 libvirt 组
sudo adduser $USER kvm
# 设置 /etc/libvirt/qemu.conf 中 user, group 用户,例如 ubuntu, kvm
user = "ubuntu"
group = "kvm"
# 新建一个磁盘,使用 iso 进行安装
$ virt-install \
--name name \
--memory size \
--vcpus size \
--disk /path/to/name.qcow2,size=size \
--os-variant os \
--network bridge=bridge \
--cdrom /path/to/file.iso
# 例如:
$ virt-install \
--name "Anolis-7.9" \
--arch x86_64 \
--cpu host \
--vcpus 64 \
--memory 10240 \
--disk anolis.qcow2,size=10 \
--network bridge=virbr0 \
--os-variant centos7 \
--hvm \
--cdrom AnolisOS-7.9-Minimal-x86_64-dvd.iso
此时,virt-install 命令会启动 iso 里的操作系统镜像,加载安装程序。
安装 ISO 镜像
当执行完创建虚拟机全命令后,需要连接到该主机的 hypervisor,这里使用 virt-manager 远程连接,如图:
在本方案中,使用了类 RHEL 的操作系统,在安装过程中,需要注意以下情况:
- 在安装的时候,建议选择 English 语言
- 网络要开启,并且采用 dhcp 的方式获取IP
- 软件安装
一般 Linux 发行版都可能会有 Server 或 Minimal 的安装模式,在这里建议选择 Server 或者 Minimal Install 安装选项,因为一般在云上的环境是通过 ssh 登录,选择带图形的安装选项会安装大量用不到的软件,但请根据实际情况进行选择。
因为在云上的环境,建议直接使用 “Standard partition”
创建至少 2 个分区
/boot/efi |
使用 EFI 文件系统,容量至少使用 1 GB |
/ |
使用 ext4 或 XFS 文件系统,容量可以使用剩余部分,或再留 1 到 2 个 GB |
注意:
- 或者直接分一个 /boot 分区,使用 ext4 文件系统,容量至少使用 1 GB
- swap 分区一般在云环境里可以不用创建挂载,但有的 Linux 发行版在安装过程中必须被创建。可以先创建此分区,在之后的配置中去除
在创建用户时,可以不设置 root 密码,也不建议设置 root 密码,因为在云的环境里一般都是不建议使用 root 进行登录的。
- 直接创建一个新用户,比如 “ec2-user” 或其他用户名,这也是以后登录系统的默认用户名。
- 有的 Linux 发行版在创建用户时可以选择将刚创建的用户设置为管理员,例如, “Make this user administrator“。
- 有的 Linux 发行版在创建用户时可以不设置密码,例如取消勾选 “Require a password to use this account“,在云上一般使用非对称密钥的方式登录系统,所以可以不给用户设置密码,等安装完后,通过 cloud-init 程序,将用户公钥传入到实例中,然后通过 ssh 程序和私钥登录实例。
完成设置,并且开始安装,安装整个安装过程,如图:
当系统安装完成后,重启系统,接下来就需要为系统能在云上运行进行配置了。
配置临时环境
在此,已经成功将 ISO 镜像在临时的虚拟里安装成一个可运行的操作系统。使用 “ec2-user” 作为用户名,密码直接按回车或输入设置的密码,可以登录进操作系统。
为了在云上使用操作系统,需要确认:
- 安装 openssh
- 安装 cloud-init
- 确认系统内核支持 NVMe
- 确认系统内核支持 ENA
- 安装 dhcp
- 确认系统网络使用 dhcp 获取 IP 地址
- 确认系统盘挂载使用 UUID
- 删除 swap 分区挂载
- 清除遗留信息
在登录了临时创建的虚拟机环境后,执行以下步骤:
1. 检查 openssh 是否安装,用于登录操作系统
$ sudo systemctl status sshd
$ mkdir ~/.ssh
2. 安装、配置 cloud-init,用于在启动实例的时候,接收用户的非对称密钥公钥
$ sudo yum install -y cloud-init
$ sudo systemctl enable cloud-init
# 配置 /etc/cloud/cloud.cfg,如下:
users:
- default
disable_root: true
ssh_pwauth: false
mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service', '0', '2']
resize_rootfs_tmp: /dev
ssh_deletekeys: 1
ssh_genkeytypes: ~
syslog_fix_perms: ~
disable_vmware_customization: false
datasource_list: [ Ec2 ]
# Example datasource config
datasource:
Ec2:
support_xen: false
timeout: 5
max_wait: 300
# The modules that run in the 'init' stage
cloud_init_modules:
- disk_setup
- migrator
- source-address
- pip-source
- seed_random
- bootcmd
- write-files
- growpart
- resizef
- disk_setup
- mounts
- set_hostname
- update_hostname
- update_etc_hosts
- ca-certs
- rsyslog
- users-groups
- ssh
cloud_config_modules:
- ssh-import-id
- mounts
- locale
- set-passwords
- spacewalk
- yum-add-repo
- ntp
- timezone
- disable-ec2-metadata
- runcmd
# The modules that run in the 'final' stage
cloud_final_modules:
- package-update-upgrade-install
- puppet
- chef
- mcollective
- salt-minion
- rightscale_userdata
- scripts-vendor
- scripts-per-once
- scripts-per-boot
- scripts-per-instance
- scripts-user
- ssh-authkey-fingerprints
- keys-to-console
- phone-home
- final-message
- power-state-change
# System and/or distro specific settings
# (not accessible to handlers/transforms)
system_info:
default_user:
name: ec2-user
lock_passwd: true
gecos: Cloud User
groups: [adm, systemd-journal]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/bash
distro: anolis
paths:
cloud_dir: /var/lib/cloud/
templates_dir: /etc/cloud/templates/
ssh_svcname: sshd
# vim:syntax=yaml
3. 系统内核支持 NVMe,用于加载基于卷的磁盘
$ modinfo nvme
filename: /lib/modules/3.10.0-1160.an7.x86_64/kernel/drivers/nvme/host/nvme.ko.xz
version: 1.0
license: GPL
author: Matthew Wilcox <willy@linux.intel.com>
retpoline: Y
rhelversion: 7.9
srcversion: 85008DAE2F874D129C4BD39
...
4. 系统内核支持 ENA,用于支持在亚马逊云科技上的 ENA 网络。如果没有 ena driver ,可以按如下安装
$ sudo yum upgrade kernel -y
$ sudo reboot
# 删除旧 kernel
$ yum list kernel
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
Installed Packages
kernel.x86_64 3.10.0-1160.an7 @anaconda/7
kernel.x86_64 3.10.0-1160.119.1.0.1.an7 @updates
$ uname -r
3.10.0-1160.119.1.0.1.an7.x86_64
$ sudo yum remove kernel-3.10.0-1160.an7
# 安装ena driver
$ sudo yum install kernel-devel-$(uname -r) gcc git patch rpm-build wget -y
$ cd /usr/src/
$ sudo wget https://github.com/amzn/amzn-drivers/archive/master.zip
$ sudo unzip master.zip
$ cd amzn-drivers-master/kernel/linux/ena
$ sudo make
$ sudo rm /lib/modules/$(uname -r)/kernel/drivers/net/ethernet/amazon/ena/ena.*
$ sudo cp ena.ko /lib/modules/$(uname -r)/
$ sudo depmod --all
# 禁止网卡改名
$ sudo sed -i '/^GRUB\_CMDLINE\_LINUX/s/\"$/\ net\.ifnames\=0\"/' /etc/default/grub
$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# 检查 nvme, ena drivers
$ modinfo nvme
filename: /lib/modules/3.10.0-1160.119.1.0.1.an7.x86_64/kernel/drivers/nvme/host/nvme.ko.xz
version: 1.0
license: GPL
author: Matthew Wilcox <willy@linux.intel.com>
retpoline: Y
rhelversion: 7.9
srcversion: 645BE67B13344F7BDCD071A
...
$ modinfo ena
filename: /lib/modules/3.10.0-1160.119.1.0.1.an7.x86_64/kernel/drivers/net/ethernet/amazon/ena/ena.ko
version: 2.13.3g
license: GPL
description: Elastic Network Adapter (ENA)
author: Amazon.com, Inc. or its affiliates
retpoline: Y
rhelversion: 7.9
srcversion: E6368FFE8AF23D5D0D1B861
...
# 在 initramfs 里增加必要的 drivers
$ sudo dracut -f --add-drivers "nvme xen-netfront xen-blkfront ena"
$ sudo lsinitrd /boot/initramfs-$(uname -r).img | grep nvme
$ sudo lsinitrd /boot/initramfs-$(uname -r).img | grep ena
5. 检查 dhcp,用于获取 IP 地址
$ cat /etc/sysconfig/network-scripts/ifcfg-eth0
TYPE="Ethernet"
PROXY_METHOD="none"
BROWSER_ONLY="no"
BOOTPROTO="dhcp"
DEFROUTE="yes"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_FAILURE_FATAL="no"
IPV6_ADDR_GEN_MODE="stable-privacy"
NAME="eth0"
UUID="97657ba1-1604-4a87-93df-f426e2c5f1a0"
DEVICE="eth0"
ONBOOT="yes"
6. 确认系统盘挂载使用 UUID,而不是直接使用设备名,如果是使用设备名,在新的云环境中,设备名有可能会变更,造成挂载失败
$ blkid
/dev/vda2: UUID="145d8385-fc3b-4568-be4e-c2be0b3a01c9" TYPE="xfs"
/dev/vda1: UUID="9912cf13-8adb-4d79-9744-47779619363b" TYPE="ext4"
确认 /etc/fstab 中的设备使用 UUID,UUID 可以通过以上命令查询,如下所示
$ cat /etc/fstab
UUID=145d8385-fc3b-4568-be4e-c2be0b3a01c9 / xfs defaults 0 1
UUID=9912cf13-8adb-4d79-9744-47779619363b /boot ext4 defaults 0 2
7. 设置串口(推荐)
$ systemctl enable serial-getty@ttyS0.service
$ systemctl start serial-getty@ttyS0.service
8. 删除 swap 分区
如果安装的时候设置了 swap 分区,可以在这时删除。请根据实际情况选择删除或不删除。
在 /etc/fstab 中注释/删除 swap 分区。如果需要,可以通过 fdisk 将 swap 分区删除掉,同时可以扩展根分区,再扩展根文件系统,此步骤可选。
9. 清除遗留信息
当安装完 cloud-init 后,需要将 cloud-init 的 cache 清理,以便启动新实例的时候执行 cloud-init
$ sudo cloud-init clean
$ sudo cloud-init status
status: not run
# 清除软件安装缓存
$ sudo yum clean all
这样,就完成了最后的配置工作。
二、导出镜像
VM Import/Export 支持的系统版本和内核版本见链接。
镜像格式 |
说明 |
备注 |
qcow2 |
qcow2 格式是 QEMU 模拟器支持的一种磁盘格式,是用文件的形式来表示一块固定大小的块设备磁盘 |
亚马逊云科技不支持 |
ova |
Open Virtual Appliance (OVA) 格式,支持多盘的镜像导入 |
亚马逊云科技支持 |
vmdk |
VMDK 是 VMware 创建的虚拟硬盘格式,兼容 VMware ESX 和 VMware vSphere 虚拟化产品 |
亚马逊云科技支持 |
vhd |
VHD/VHDX 格式,兼容 Microsoft Hyper-V, Microsoft Azure 和 Citrix Xen 虚拟化产品 |
亚马逊云科技支持 |
raw |
原生文件,没有文件格式 |
亚马逊云科技支持 |
临时虚拟机镜像被成功创建后,将磁盘文件转换成在亚马逊云科技上能用的格式。首先关闭临时虚拟机
三、格式转换
当使用 qcow2 格式的磁盘文件时,因为亚马逊云科技不直接支持 qcow2,需要将 qcow2 转换成为 raw 格式。在 Ubuntu 的裸金属主机上安装 qemu-img 软件包,然后进行镜像的格式转换。这里的 name 是您实际的文件名,例如:
$ sudo apt install -y qemu-utils
$ qemu-img convert name.qcow2 name.raw
四、镜像导入到 Amazon S3
当您安装好 aws-cli 命令后,使用您 Access Key, Security Key 来配置使用亚马逊云科技的服务,例如:
$ sudo snap install aws-cli --classic
$ aws configure
AWS Access Key ID [****************ONBZ]:
AWS Secret Access Key [****************6ILi]:
Default region name [ap-northeast-1]:
Default output format []:
将镜像上传到 Amazon S3
$ aws s3 cp /path/to/name.raw s3://example-bucket
/path/to/ 为您的实际路径,name.raw 为您的具体文件名,example-bucket 为 bucket 名字,请根据实际情况更改。
五、使用 VM Import/Export 导入镜像
使用 VM Import/Export 服务可以将支持的格式(vhd, vmdk, raw 等格式)从 Amazon S3 上导成 Amazon EC2 可使用的 AMI 镜像格式。
创建 vmimport role
1. 创建一个用于创建 vmimport role 的 trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "vmie.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals":{
"sts:Externalid": "vmimport"
}
}
}
]
}
2. 创建 vmimport role
$ aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json
这里的 trust-policy.json 如果是在当前路径,那么可以像上面的命令一样不用改;如果是在其他路径上,可以写直接路径,例如 file:///home/ec2-user/vmimport/trust-policy.json。
3. 创建角色策略文件 role-policy.json,将 import-image-tokyo-bucket 替换为真实的 bucket 名字
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Action":[
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket"
],
"Resource":[
"arn:aws:s3:::example-bucket ",
"arn:aws:s3:::exmaple-bucket/*"
]
},
{
"Effect":"Allow",
"Action":[
"ec2:ModifySnapshotAttribute",
"ec2:CopySnapshot",
"ec2:RegisterImage",
"ec2:Describe*"
],
"Resource":"*"
}
]
}
4. 设置角色策略
$ aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.json
这里的 role-policy.json 如果是在当前路径,那么可以像上面的命令一样不用改;如果是在其他路径上,可以写直接路径,例如 file:///home/ec2-user/vmimport/role-policy.json。
导入成 EBS Snapshot
VM Import 支持将镜像文件直接导入成 AMI 镜像,也支持将镜像文件导入成 EBS Snapshot。这里用导入成 EBS Snapshot 举例。
1. 创建用于描述镜像文件的文件 containers.json,将 import-image-tokyo-bucket 替换为真实的 bucket 名字,S3Key 使用真实的对象名
{
"Description": "Anolis 7.9",
"Format": "raw",
"UserBucket": {
"S3Bucket": "example-bucket",
"S3Key": "anolis79.raw"
}
}
2. 将镜像文件导入成 EBS Snapshot
$ aws ec2 import-snapshot --disk-container file://containers.json
命令会显示类似如下输出
{
"ImportTaskId": "import-snap-16abfbf2de1fd0e4t",
"SnapshotTaskDetail": {
"DiskImageSize": 0.0,
"Format": "raw",
"Progress": "0",
"Status": "active",
"StatusMessage": "pending",
"UserBucket": {
"S3Bucket": "example-bucket",
"S3Key": "anolis.raw"
}
}
}
如果想查看目前 import 的状态
$ aws ec2 describe-import-snapshot-tasks --import-task-id import-snap-16abfbf2de1fd0e4t
{
"ImportSnapshotTasks": [
{
"ImportTaskId": "import-snap-16abfbf2de1fd0e4t",
"SnapshotTaskDetail": {
"DiskImageSize": 0.0,
"Format": "raw",
"Progress": "4",
"SnapshotId": "",
"Status": "active",
"StatusMessage": "pending",
"UserBucket": {
"S3Bucket": "example-bucket",
"S3Key": "anolis.raw"
}
},
"Tags": []
}
]
}
# 等待完成,然后找出 snapshot id
$ aws ec2 describe-import-snapshot-tasks --import-task-id import-snap-16abfbf2de1fd0e4t
{
"ImportSnapshotTasks": [
{
"ImportTaskId": "import-snap-16abfbf2de1fd0e4t",
"SnapshotTaskDetail": {
"DiskImageSize": 10737418240.0,
"Format": "raw",
"SnapshotId": "snap-0e108b59bf4a3163e",
"Status": "completed",
"UserBucket": {
"S3Bucket": "example-bucket",
"S3Key": "anolis.raw"
}
},
"Tags": []
}
]
}
创建 AMI
使用 aws-cli 将导入的 EBS Snapshot 创建成为 Amazon EC2 可用的 AMI 镜像,Snapshot id 可以在 Amazon EC2 的 Console 界面 Elastic Block Store > Snapshots 里查询到
$ aws ec2 register-image \
--name "Anolis 7.91" \
--description "Customized Anolis 7.9 AMI" \
--architecture x86_64 \
--virtualization-type hvm \
--root-device-name "/dev/sda1" \
--block-device-mappings "{\"DeviceName\": \"/dev/sda1\",\"Ebs\": {\"SnapshotId\": \"snap-0e108b59bf4a3163e \"}}" \
--ena-support
{
"ImageId": " ami-example"
}
验证
通过登录亚马逊云科技的 EC2 控制台启动一个x86_64 架构的实例,镜像使用刚刚注册生成的 AMI ID,使用您的私钥,通过 ssh 命令登录实例,进行验证。
如果遇到了实例启动了半天没有反应,或者等了很久,然后突然可以用密码的方式登录了,那是因为系统的 cloud-init 不支持 IMDB v2,可以将实例的 IMDB v2 从 Required 改成 Optional ,具体操作如下:
- 停止(stop)实例
- 选中该实例
- 选择 Actions, Instance settings, Modify instance metadata options.
- 在 Modify instance metadata options 对话框里,选择:
- 在 Instance metadata service 下,确保 Enable 选上
- 在 IMDSv2 下, 选择 Optional
- 选择 Save
然后再重新启动实例。
总结
本文详细地介绍了如何在亚马逊云科技上,使用 Amazon EC2 搭建一个虚拟化环境(KVM),然后在该环境里将一个 ISO 文件安装成可以在亚马逊云科技上可以使用的 AMI 镜像。
本文适合于想要通过使用 ISO 文件从头安装、定制一个操作系统(包括国产操作系统)的用户,也适用于尝试解决 CentOS Linux 7 结束生命周期之后,寻找其他方案的用户。
参考
[1] https://docs.aws.amazon.com/vm-import/latest/userguide/prerequisites.html
[2] https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking-ena.html
[3] https://repost.aws/knowledge-center/install-ena-driver-rhel-ec2
[4] https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/deploying_rhel_8_on_amazon_web_services/assembly_deploying-a-virtual-machine-on-aws_cloud-content-aws#completing-the-rhel-installation_deploying-a-virtual-machine-on-aws
本篇作者