亚马逊AWS官方博客

从 ISO 到 AMI – 迁移其他操作系统到亚马逊云科技

概述

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)的完整流程。详细描述如下:

  1. 导出和导入:在企业的数据中心或云上,将已经安装、定制好的停止的实例(Stopped VM)导出成某种格式的镜像文件(例如:qcow2, vhdx, vmdk, ova, raw 等),然后上传到 Amazon S3 存储桶中。
  1. AMI 创建:在亚马逊云科技上,将已经上传到 S3 桶的镜像文件,导出成一个卷的快照(EBS snapshot),然后根据快照,生成 AMI(Amazon Machine Image)镜像。
  1. 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 进行登录的。

  1. 直接创建一个新用户,比如 “ec2-user” 或其他用户名,这也是以后登录系统的默认用户名。
  2. 有的 Linux 发行版在创建用户时可以选择将刚创建的用户设置为管理员,例如, “Make this user administrator“。
  3. 有的 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 原生文件,没有文件格式 亚马逊云科技支持

临时虚拟机镜像被成功创建后,将磁盘文件转换成在亚马逊云科技上能用的格式。首先关闭临时虚拟机

$ sudo poweroff

三、格式转换

当使用 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 ,具体操作如下:

  1. 停止(stop)实例
  2. 选中该实例
  3. 选择 Actions, Instance settings, Modify instance metadata options.
  4. 在 Modify instance metadata options 对话框里,选择:
    1. 在 Instance metadata service 下,确保 Enable 选上
    2. 在 IMDSv2 下, 选择 Optional
    3. 选择 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

本篇作者

王磊

亚马逊云科技解决方案架构师。曾作为亚马逊云科技的计算服务(Amazon EC2)产品解决方案架构师,对于 GNU/Linux 操作系统(Amazon Linux、RHEL、Ubuntu 等)以及基础设施即服务(Infrastructure as a Service)有着丰富的经验。目前负责通用行业的解决方案架构师。曾就职于 Microsoft、Red Hat、Canonical 等公司。