Ubuntu官方提供3种镜像,desktop、server和cloud image,其中cloud image就是用于public cloud或者openstack上,便于自动化部署安装。

有时候我们想在官方cloud image上做一些定制,产生一个新的image。

首先假设我们已经下载了官方镜像:

ubuntu-22.04-server-cloudimg-amd64.img

其实,这里有很多种方法都可以对它进行定制,包括使用openstack的disk builder,很遗憾我没有把这个跑通,还可以使用libguestfs,libguestfs customize OS image非常简单,但制作的时候有两个问题,一是自带的官方的OS image disk size有限制,需要resize disk,二是用这个工具制作的镜像会非常大,具体原因我没有调查。

最终,我选择了用packer进行制作,实际上,kubernetes的image builder也用了packer。

使用packer制作镜像,最主要是编写一个template文件,如下:

$ cat template.json
{
  "builders": [
    {
      "type": "qemu",
      "iso_url": "ubuntu-22.04-server-cloudimg-amd64.img",
      "iso_checksum": "md5:9688fc9011dcd4d960620d2ebb98a639",
      "ssh_username": "ubuntu",
      "ssh_password": "a",
      "ssh_timeout": "1m",
      "ssh_port": 2222,
      "skip_nat_mapping": true,
      "disk_size": "40G",
      "disk_image": true,
      "disk_compression": true,
      "format": "qcow2",
      "headless": true,
      "accelerator": "kvm",
      "output_directory": "images",
      "qemuargs": [
        [ "-machine", "accel=kvm,type=q35" ],
        [ "-cpu", "host" ],
        [ "-m", "2G" ],
        [ "-smp", "2" ],
        [ "-nographic" ],
        [ "-device", "virtio-net-pci,netdev=net0" ],
        [ "-netdev", "user,id=net0,", "hostfwd=tcp::2222-:22" ],
        [ "-cdrom", "seed.img" ]
      ],
      "shutdown_command": "echo a |sudo -S shutdown -P now",
      "boot_command": [],
      "vm_name": "jammy-k8s-containerd-1.6.15.qcow2"
    }
  ],
  "provisioners": [
    {
      "type": "file",
      "source": "files/crictl.yaml",
      "destination": "/tmp/crictl.yaml"
    },
    {
      "execute_command": "echo a | {{.Vars}} sudo -E -S bash '{{.Path}}'",
      "scripts": [
        "prepare_k8s.sh"
      ],
      "type": "shell"
    }
  ]
}

以上的字段含义都可以在packer官方文档中找到解释,唯一需要注意的就是qemuargs 和ssh_port 字段是有关联的,如果qemuargs设置的不对,build image一定会失败,这里

-machine 表示使用kvm方式启动qemu

-device表示给这个qemu启动的vm配置一块叫net0的网络设备

-netdev和-device对应,它会使用net0设备,并且把host机器的2222端口和qemu启动的vm内的22端口做了映射,所以如果想要ssh访问qemu启动的vm,从host上就应该使用2222端口

-cdrom表示把cloud-init需要用的文件通过cdrom image的方式挂载到vm内,这样当vm启动的时候,就会使用seed.img中的文件,从而可以给这个vm添加一些简单的cloud-init user-data,此处seed.img如何制作呢?最简单的使用cloud-localds工具

$ cat user-data.yaml
#cloud-config

ssh_pwauth: true
user:
  lock_passwd: false
  hashed_passwd: $6$rounds=4096$D8xCAW1zbWkn6LDM$/UG31JlD7CZJiaGRZpwwdDWQqLe7tFpt4yxQPATf3x5M9qYx4o/EsB82pueNXMPTKk3slyMGvg3cSxxrNt054/
### make hashed_passwd: mkpasswd --method=SHA-512 --rounds=4096
$ cloud-localds seed.img user-data.yaml

user-data.yaml从cloud-init官方网站可以找到很多示例代码,而cloud-localds命令也非常简单,它也可以接收meta-data.yaml作为输入.

最后,provisioners里的prepare_k8s.sh脚本内可以是任何内容,比如:

$ cat prepare_k8s.sh
#!/bin/bash
# Install crictl
wget -q -c https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.26.0/crictl-v1.26.0-linux-amd64.tar.gz -O - | tar -xz -C /usr/bin

这样,我们就给这个image里安装了crictl工具,并且配置了默认的runtime是containerd。

最终我们使用如下命令

export PACKER_LOG=1
packer build template.json

它就会output_directory目录下生成一个名字为vm_name的文件。我们这里就是

images/jammy-k8s-containerd-1.6.15.qcow2

对于生成的文件,可以使用qemu进行测试运行:

qemu-system-x86_64  \
  -machine accel=kvm,type=q35 \
  -cpu host \
  -m 2G \
  -nographic \
  -device virtio-net-pci,netdev=net0 \
  -netdev user,id=net0,hostfwd=tcp::2222-:22 \
  -drive if=virtio,format=qcow2,index=0,file=images/jammy-k8s-containerd-1.6.15.qcow2
   #-drive if=virtio,format=raw,index=1,file=seed.img
   #-fda seed.img

此处比较有意思的是,如果我想测试seed.img,既可以使用-drive参数,也可以使用-fda参数,但它们在packer里都不能用,却只能使用-cdrom。


original refer