Quantcast
Channel: Linux Archives | Vince‘s hut
Viewing all articles
Browse latest Browse all 10

Packer + Cloud-init + PXE

$
0
0

需求

我有一个 Proxmox VE 服务器来提供虚拟化服务,随着虚拟机装系统的次数越来越多,我发现我需要构建虚拟机模板,就像 VPS 商家那样在使用时直接克隆一个出来。不然每次装系统太累了。

于是我手搓了好几个虚拟机模板,按照步骤安装系统,设置源,安装需要的软件等等。暂时解决了我需要立即可用的虚拟机的问题。

以上的方案在我用了一段时间后,我发现:实际使用依旧非常困难。最大的问题莫过于每次克隆出来的虚拟机都需要进行软件包升级。以及我无法使用 cloudinit 初始化虚拟机,虚拟机的主硬盘扩容困难,需要借助救援系统。

因此我打算结合我的 Drone CI 和 Packer 以及 Cloudinit 来构建一个快速可用、易于扩容的方便的镜像。

我之前写过一篇 HashiCorp vagrant 的文章:《Vagrant 入门指北》,Packer 和 Vagrant 的功能有些类似,Vagrant 面向的是本地安装的虚拟机,例如 Virtualbox,Hyper-V 等。而 Packer 是为 Cloud Stack, Open Stack, Proxmox 等提供镜像。

结合 Packer 与 Cloudinit

为什么有 Packer 负责构建镜像了,还需要使用 Cloudinit 呢?实际上二者面向的是不同步骤。当然,cloudinit 和 packer 能达成相同的目标。但是实现方式却不一样,难易程度也不一样。

举个例子:packer 构建出的更像是 docker 的基础镜像,类似于 debian 镜像,cloudinit 更像是使用这个基础镜像,然后在其上继续构建镜像。

我通常会将通用软件包的安装交给 packer,cloudinit 将会配置 ssh 密钥、用户名、网络配置等,做个性化的配置,这样使用一个模板,但是可以产生多个不同的系统配置,用于不同工作。即使单独使用任意一个工具,也能达成相同的目的,但是需要创建多个虚拟机模板,灵活性就很差了。

Why Not Cloud Images?

原因还是在于我想定制模板,即使使用一些发行版提供的 cloud images,你还是需要卸载软件,设置软件源等等。还有一些发行版并不提供这类镜像。再者,很多发行版的 cloud images 通常会进行 Nightly Build,下载下来后还是要进行一些设置才能达到方便使用。每次导入这个镜像也比较麻烦,还需要打开命令行操作。

这类镜像还有一个问题就是 swap 分区的大小没法设置,以及它们的启动方式为 Legacy BIOS 启动,而我想使用 UEFI,以提供更方便的磁盘和分区支持。

从头开始制作镜像

截至本文编写时,Packer 官方的教程是使用 docker ,构建 docker 镜像。为虚拟机平台提供镜像的教程比较少,只有一个 AWS 的教程。只具有参考价值,适合去了解packer。

总的来说构建镜像 packer 需要一些配置:

  • Communicators 负责连接虚拟机的配置,一般默认使用 ssh 无需配置
  • Builders 非常重要的配置,负责连接虚拟化平台,也包含有虚拟机配置信息
  • Provisioners 使用 communicators 连接虚拟机,安装软件、配置系统

官方推荐使用 hcl 作为配置文件格式,这个格式和我们常见的格式不太一样,不过配置看起来非常清晰。

构建思路

配置

首先分享一个基础的为 debian 构建镜像的配置:

packer {
  required_plugins {
    proxmox = {
      version = "~> 1"
      source  = "github.com/hashicorp/proxmox"
    }
    ansible = {
      version = "~> 1"
      source  = "github.com/hashicorp/ansible"
    }
  }
}
variable "password" {
  type    = string
  default = "12345"
}

variable "username" {
  type    = string
  default = "root@pam"
}
source "proxmox-iso" "debian" {
  boot_command = ["<down><down><enter><wait>", "<down><down><down><down><down><wait>", "e<wait>", "<down><down><down><end>", "preseed/url=https://example.com/preseed.cfg", "<f10>"]
  boot_wait    = "10s"
  disks {
    disk_size    = "10G"
    storage_pool = "local-lvm"
    type         = "virtio"
  }
  efi_config {
    efi_storage_pool  = "hdd01-vm"
    efi_type          = "4m"
    pre_enrolled_keys = true
  }
  insecure_skip_tls_verify = true
  iso_file                 = "hdd01-iso:iso/debian-12.6.0-amd64-DVD-1.iso"
  network_adapters {
    bridge = "vmbr0"
    model  = "virtio"
  }
  node                    = "hsd01"
  vm_id                   = 10000
  username                = "${var.username}"
  password                = "${var.password}"
  proxmox_url             = "https://192.168.1.10:8006/api2/json"
  memory                  = 2048
  cores                   = 1
  bios                    = "ovmf"
  ssh_password            = "123456"
  ssh_timeout             = "30m"
  ssh_username            = "vince"
  template_description    = "debian-12, generated on ${timestamp()}"
  template_name           = "debian-12"
  unmount_iso             = true
  cloud_init              = true
  cloud_init_storage_pool = "hdd01-vm"
}
build {
  sources = ["source.proxmox-iso.debian"]
  provisioner "shell" {
    scripts             = ["./init.sh"]
    execute_command     = "echo '123456' | sudo -S env {{ .Vars }} {{ .Path }}"
    pause_before        = "3s"
    timeout             = "10s"
    start_retry_timeout = "5m"
  }
  provisioner "ansible" {
    pause_before  = "3s"
    playbook_file = "./playbooks/upgrade_system_playbook.yml"
    extra_arguments = [
      "--extra-vars", "ansible_become_pass=123456",
      "--scp-extra-args", "'-O'", 
    ]
  }
  provisioner "shell" {
    inline            = ["echo '123456' | sudo -S systemctl reboot"]
    expect_disconnect = true
  }
  provisioner "ansible" {
    pause_before  = "3s"
    playbook_file = "./playbooks/install_package_playbook.yml"
    extra_arguments = [
      "--extra-vars", "ansible_become_pass=123456",
      "--scp-extra-args", "'-O'", 
      // "--become-password-file=./password.txt",
    ]
  }
}

虚拟机资源

首先需要配置一个支持 UEFI 启动的虚拟机这里可以看到:

  • memory = 2048
  • cores = 1
  • bios = “ovmf”
  • disk_size = “10G”

在这个配置中的 UEFI 磁盘类似于主板上存储 EFI 信息的 flash,而不是 EFI 分区,注意不要混淆概念。

Boot command

这个配置中有趣的是 boot_command 要注意的是:在 UEFI 启动时,不能直接进入 boot 命令提示符而是需要修改 grub 的启动菜单。只需要在合适的地方加入 preseed.cfg 的 url。

packer 可以通过 api 来实现模拟用户键盘输入的效果从而达到自动构建。packer 在构建时会启动一个 http 服务器你可以直接将 preseed.cfg 通过该服务器传递给虚拟机。

SSH

ssh 中的用户名和密码需要和 preseed.cfg 文件匹配,这样才能连接到虚拟机。这个 ssh_timeout 的时间需要留充足,这是留给装系统的时间,如果你安装的包较多,或者你在一块慢速的机械硬盘上安装,那么需要适当提高超时时间。

Cloud init

为了将来使用 cloudinit 为该虚拟机提供 cloudinit 磁盘。以开启该功能,除此之外在下一步安装软件的过程中也需要安装 cloudinit 才行。

Preseed.cfg

debian 提供了示例的 preseed.cfg 这是自动化安装关键。注意你需要提供分区配置,将 / 分区放在最后。在将来扩容磁盘时,cloudinit 会自动将该分区扩展占满空闲部分。下面是一个 4G swap 和 512M EFI 分区的示例

d-i partman-auto/expert_recipe string                        \
     unencrypted-install ::                                  \
             512 512 512 vfat                                \
                     $primary{ } $bootable{ }                \
                     method{ efi } format{ }                 \
                     use_filesystem{ } filesystem{ vfat }    \
                     mountpoint{ /boot }                     \
             .                                               \
             4096 4096 4096 linux-swap                       \
                     method{ swap } format{ }                \
             .                                               \
             5120 5120 1000000000 ext4                       \
                     $primary{ }                             \
                     method{ format } format{ }              \
                     use_filesystem{ } filesystem{ ext4 }    \
                     mountpoint{ / }                         \
             .

Provisioner

最后的 provisioner 对于大部分内容采用 ansible 进行 provision 来保证幂等性,当然只使用 shell 也是可以的。由于可能涉及内核的升级,需要重启系统,在这一步使用了一个 shell 命令,以及期望 ssh 断开。

inline            = ["echo '123456' | sudo -S systemctl reboot"]
expect_disconnect = true

在这一步安装 cloudinit,该VM就可以使用 cloudinit 了

PXE

如果跑通了上面这个流程那么可以试一下 PXE 启动,设置一下 dns 和 tftp。这样就不再需要使用iso文件了。虽然可以声明镜像的 url 不过大部分国内镜像站做了一些防护措施,会导致 packer 不能正常下载镜像。其次对于 debian 没有最新 iso 对应的 url 的固定链接。

使用 PXE 可以再次简化自动化安装流程。netboot 也是一个不错的项目。

构建与CI

hcl 的格式化使用 packer fmt filename.hcl 。接着 packer build . 这一步骤就类似于 docker build 构建镜像。等待系统安装完成后,packer 会自动将该虚拟机转换为模板。最后还可以连接 CI/CD 工具,以实现自动化构建镜像,需要用虚拟机的时候直接从模板克隆即可,时刻保证软件包是最新的。

更进一步,还可以安装好特定的应用,直接一键搭建一个应用。

The post Packer + Cloud-init + PXE appeared first on Vince‘s hut.


Viewing all articles
Browse latest Browse all 10

Trending Articles