如何从已配置的CentOS EC2 AMI镜像创建Docker镜像?
能不能直接从AMI创建Docker镜像?
嘿,先给你一个明确的答案:不行。AMI是EC2虚拟机的完整磁盘镜像,包含了操作系统内核、引导分区这类Docker完全不需要的东西——Docker镜像是基于宿主机内核的用户空间快照,两者的底层结构完全不兼容,没有工具能直接把AMI转成Docker镜像。不过别担心,我们可以通过提取AMI里的系统内容来构建镜像,下面的方案能最大程度保留你已安装的Java、MySQL、Gradle、应用和所有配置,几乎不用做额外的Docker调优。
最优省心方案:从AMI启动实例后打包镜像
这个方案利用你已经调试好的EC2实例(毕竟AMI就是从它生成的,肯定能正常运行),直接把实例里的用户空间内容打包成Docker镜像,步骤简单,出错率极低:
步骤1:从你的AMI启动一台新EC2实例
就像平时启动EC2一样,选你的AMI启动实例,确保实例能正常运行,所有服务(Java、MySQL、你的应用)都能正常启动——这一步只是用来导出文件系统,用完可以删掉。
步骤2:在实例上安装Docker
我们需要用Docker来打包镜像,所以先在这台临时实例上装Docker:
# CentOS系统的Docker安装命令,复制粘贴就行 sudo yum update -y sudo yum install docker -y sudo systemctl start docker sudo usermod -aG docker $USER # 退出当前会话再重新登录,让Docker权限生效
步骤3:打包实例的用户空间文件系统
我们要把根目录下的所有内容打包成tar包,但要排除一些Docker容器不需要的目录(比如宿主机的内核相关文件、临时文件):
# 先切换到临时目录,避免打包时把tar文件本身也包进去 cd /tmp # 打包根目录,排除不必要的目录 sudo tar --exclude='/proc/*' --exclude='/sys/*' --exclude='/dev/*' --exclude='/tmp/*' --exclude='/run/*' --exclude='/mnt/*' --exclude='/var/lib/docker/*' -czf ec2-root.tar.gz /
解释下排除的目录:/proc、/sys、/dev是宿主机的内核虚拟文件系统,容器里会自己生成;/tmp、/run是临时目录;/var/lib/docker是我们刚装的Docker的存储目录,这些都不需要打包。
步骤4:将tar包导入为Docker镜像
用docker import命令直接把tar包转换成Docker镜像:
docker import ec2-root.tar.gz my-ec2-app:v1
这时候你就有了一个包含EC2实例所有内容的Docker镜像了。
步骤5:添加启动命令并验证
导入的镜像默认没有启动命令,你需要给它设置启动应用的指令。先运行容器测试一下:
docker run -it --name test-app my-ec2-app:v1 /bin/bash
进入容器后,测试Java、MySQL、Gradle是否能正常运行,你的应用能不能启动。确认没问题后,有两种方式添加启动命令:
- 用
docker commit:在运行的测试容器里启动你的应用,然后提交镜像:
docker commit test-app my-ec2-app:final
- 写一个极简的Dockerfile(更推荐,方便后续修改):
创建一个名为Dockerfile的文件,内容如下:
FROM my-ec2-app:v1 # 替换成你实际的应用启动脚本或命令 CMD ["/path/to/your/app/start.sh"]
然后构建最终镜像:
docker build -t my-ec2-app:final .
备选方案:通过EBS快照导出文件(略复杂)
如果你不想启动实例,可以把AMI对应的EBS快照挂载到另一台EC2实例上,然后打包挂载的磁盘内容。不过这个步骤需要处理磁盘挂载、文件权限,不如直接启动实例省心,所以除非你有特殊需求,否则优先选上面的方案。
几个小提醒
- MySQL数据持久化:默认情况下,Docker容器重启后,MySQL的数据会丢失。如果需要持久化,可以后续把MySQL的数据目录挂载到宿主机或者Docker卷,但这是优化步骤,先确保镜像能正常运行再说。
- 端口映射:运行容器时,记得用
-p参数把应用端口暴露出来,比如docker run -p 8080:8080 my-ec2-app:final。 - systemd兼容性:CentOS的systemd在Docker容器里可能会有问题,如果你的服务是用
systemctl管理的,建议直接用服务的启动脚本或者二进制文件启动,而不是用systemctl start。
内容的提问来源于stack exchange,提问作者Joe

