Packer是一个有用的工具,用于创建预先构建的机器镜像。虽然它通常与为各种平台创建Linux镜像有关,但它对Windows也有一流的支持。
我们想解释一下为什么有人应该考虑添加Packer制作的镜像,并深入探讨它对Windows服务器DevOps环境的各种好处。
动机
预建镜像在很多方面都很有用。 Packer可以使用相同的构建配置和配置配方来创建生产中使用的AWS AMI和Azure机器镜像,以及在Virtualbox和Vagrant中用于本地测试的机器镜像。 这允许团队使用在生产中运行的相同设置,以及他们的同事正在使用的设置,来开发和测试他们的代码。
在这种设置中,你在开发过程的早期使用Packer。我们遵循的工作流程是先创建镜像,然后在未来的任何时候都可以使用该镜像进行开发和部署。 这就把安装软件和配置镜像的工作转移到你的部署时间之前很久。因此,在部署时少了一个步骤,Windows服务器镜像将完全配置好,并以正确的软件和设置配置好。
使用预先建立的镜像还有一个额外的好处,即我们能够在机器创建阶段及早发现配置和设置错误。 当我们在创建Windows服务器镜像时,任何在部署过程中发生的错误都会被及早发现。 我们将确信,我们预先建立的Windows服务器镜像在部署时已经准备好了。
这在任何情况下都是很方便的。想象一下这样的情景:我们需要在我们的Windows服务器上安装一个外部软件。 也许我们需要在部署前将我们的Windows服务器设置为Puppet代理。 作为其中的一部分,我们想在设置过程中使用一个简单的Powershell脚本下载.msi 。
$msi_source = "https://downloads.puppetlabs.com/windows/puppet6/puppet-agent-6.4.2-x64.msi"
$msi_dest = "C:\Windows\Temp\puppet-agent-6.4.2-x64.msi"
Invoke-WebRequest -Uri $msi_source -OutFile $msi_dest
从供应商那里下载和获取该软件的任何问题都可能延迟我们整个Windows服务器的部署,并可能导致停机或生产错误。 这类问题的出现可能有多种原因:
- 网络出现了意外的问题,使我们的服务器无法获得该文件
- 软件供应商的网站关闭了
- 我们的下载URL中甚至出现了一个不起眼的错字
这些DevOps的痛点不应该在部署时出现。 如果我们为我们的生产型Windows服务器准备一个预建和预配置的镜像,我们就可以部署新的服务器,知道它们会被安全地配置和设置为我们喜欢的。
什么是Packer?
到目前为止,我们已经讨论了为什么工程师会在他们的DevOps设置中使用预构建的Windows镜像,但没有讨论具体的工具和方法。 让我们来介绍一下Packer工具,以及为什么它如此适合这个问题领域。
Packer是一个由HashiCorp开发的用于创建机器镜像的开源工具。 它是一个理想的工具,用于我们在这里的目的,我们想从一个构建模板文件为多个平台(AWS、Azure、Virtualbox)创建镜像。
在高层次上,Packer的工作原理是允许我们定义我们想用构建器来创建机器或图像的平台。有各种平台的构建器,我们将在我们的例子中触及使用其中的几个。
Packer让我们做的下一件事是使用供应者来定义我们希望Packer运行的步骤。 我们在packer配置文件中定义这些配置步骤,packer将使用它们来设置我们的机器镜像,与我们的构建器所针对的平台无关。
正如我们之前提到的,Packer 对 Windows 的支持非常好。 我们将在后面深入讨论使用文件配置器和powershell 配置器的问题。现在值得一提的是,我们可以使用文件供应器将文件上传到我们正在构建的Windows服务器机器。 同样,我们可以使用PowerShell配置器在我们正在构建的Windows服务器上运行我们在主机(我们用来创建Windows服务器镜像的那台)上的Powershell脚本。
琐碎的细节--一个真实的例子
Packer通过使用一个JSON格式的配置文件来工作。 这个配置文件也被称为Packer构建模板。 你在这个构建模板中为Packer指定我们之前讨论过的构建器和供应器。
在这一点上,如果你想自己跟随并尝试这个例子中接下来的几个步骤,你应该首先在你的机器上安装Packer。Packer的官方安装指南在这里,如果你需要安装Vagrant,那么请遵循这里的官方安装指南。 另外,在这里查看本博文的对应代码库。
Packer是一个成熟的、使用良好的工具,有许多优秀的模板和例子可供各种使用情况使用。 在我们的例子中,我们的模板代码以Stefan Scherer的Packer Windows模板为基础。 该资源库中的一套模板是入门的绝佳资源。 与我们的例子有关的构建模板可以在与本博客相关的代码库中找到,但我们接下来会讨论一些重要的细节。
首先,我们要讨论的是构建器部分。对于我们正在使用的Vagrant盒子构建器。
{
"boot_wait": "2m",
"communicator": "winrm",
"cpus": 2,
"disk_size": "{{user `disk_size`}}",
"floppy_files": [
"{{user `autounattend`}}",
"./scripts/disable-screensaver.ps1",
"./scripts/disable-winrm.ps1",
"./scripts/enable-winrm.ps1",
"./scripts/microsoft-updates.bat",
"./scripts/win-updates.ps1",
"./scripts/unattend.xml",
"./scripts/sysprep.bat"
],
"guest_additions_mode": "disable",
"guest_os_type": "Windows2016_64",
"headless": "{{user `headless`}}",
"iso_checksum": "{{user `iso_checksum`}}",
"iso_checksum_type": "{{user `iso_checksum_type`}}",
"iso_url": "{{user `iso_url`}}",
"memory": 2048,
"shutdown_command": "a:/sysprep.bat",
"type": "virtualbox-iso",
"vm_name": "WindowsServer2019",
"winrm_username": "vagrant",
"winrm_password": "vagrant",
"winrm_timeout": "{{user `winrm_timeout`}}"
}
这里的一行:
"{{user `autounattend`}}",
是指Packer构建模板文件中variables 部分的变量autounattend 。
"variables": {
"autounattend": "./answer_files/Autounattend.xml",
当你启动一个Windows服务器安装镜像时(就像我们在这里用Packer做的那样),你通常会使用Autounattend.xml ,以自动执行通常会提示用户的安装指令。在这里,我们使用软盘驱动器(floppy_files 部分)在虚拟机上挂载这个文件。 我们也使用这个功能将PowerShell脚本加载到虚拟机上。例如,win-updates.ps1 ,在创建Windows服务器镜像的时候安装最新的更新。
我们还将添加额外的脚本,以便与供应者一起运行。这些脚本在打包器构建模板的provisioners 部分,与每个builders 部分的条目所指定的任何特定平台无关。
我们的构建模板中的供应者部分看起来像下面这样。
"provisioners": [
{
"execute_command": "{{ .Vars }} cmd /c \"{{ .Path }}\"",
"scripts": [
"./scripts/vm-guest-tools.bat",
"./scripts/enable-rdp.bat"
],
"type": "windows-shell"
},
{
"scripts": [
"./scripts/debloat-windows.ps1"
],
"type": "powershell"
},
{
"restart_timeout": "{{user `restart_timeout`}}",
"type": "windows-restart"
},
{
"execute_command": "{{ .Vars }} cmd /c \"{{ .Path }}\"",
"scripts": [
"./scripts/pin-powershell.bat",
"./scripts/set-winrm-automatic.bat",
"./scripts/uac-enable.bat",
"./scripts/compile-dotnet-assemblies.bat",
"./scripts/dis-updates.bat"
],
"type": "windows-shell"
}
],
我们同时使用powershell配置器和Windows Shell配置器,用于旧的Windows CMD脚本。我们使用供应者来运行这些脚本,而不是像我们之前为Vagrant盒子做的builder ,把它们放在软盘里,原因是这些脚本是通用于所有平台的,我们希望我们的构建模板能够针对它们。出于这个原因,我们希望这些脚本能够运行,而不管我们使用的是什么平台的构建模板。
在Vagrant中创建和运行一个本地Windows服务器
对于在本地运行我们的Windows服务器,一般的概述是:
- 首先,我们将用Packer建立我们的Windows服务器Vagrant盒子文件
- 我们将该盒子添加到Vagrant中
- 然后用我们的Vagrantfile模板初始化它
- 最后,我们将启动它
构建Packer盒子可以用packer build命令完成。在我们的例子中,我们的Windows服务器构建模板叫做windows_2019.json ,所以我们用以下命令来启动Packer构建
packer build windows_2019.json
如果我们有多个构建器,我们可以告诉Packer,我们只想用命令来使用虚拟盒类型。
packer build --only=virtualbox-iso windows_2019.json
(注意,我们之前在packer build模板的Vagrant box builder部分设置的type 值是:"type": "virtualbox-iso", )。
接下来,我们将用vagrant box add 命令将盒子添加到vagrant中,该命令的使用方法如下。
vagrant box add BOX_NAME BOX_FILE
或者更准确地说,在我们的例子中,我们调用这个命令的方式是:
vagrant box add windows_2019_virtualbox windows_2019_virtualbox.box
然后,我们需要初始化我们的
vagrant init --template vagrantfile-windows_2019.template windows_2019_virtualbox
并启动它:
vagrant up
在这一点上,我们将在Vagrant中拥有一个完全配置和运行的Windows服务器。
我们上面用来构建和使用Packer构建模板的一系列命令被整齐地封装在Makefile目标中。 如果你使用本博文附带的 repo中的示例代码,你可以简单地运行以下make 命令。
make packer-build-box
make vagrant-add-box
make vagrant-init
make vagrant-up
结论
在这一点上,尽管我们只是将这个Vagrant盒子及其相关的Vagrant文件用于本地测试目的,但我们已经消除了在Windows服务器设置过程中可能出现的错误。 当我们把这个盒子用于未来的开发和测试时(或者给其他同事做同样的事情),我们不需要担心我们的某个设置脚本会失败,我们需要修复它才能继续工作。通过使用Packer来创建我们的Windows服务器镜像,我们已经能够消除一整类的DevOps错误和一个特殊的开发痛点。
我们还知道,如果我们能够用Packer建立我们的盒子,并运行配置步骤,我们就会有一个与我们的生产图像相同的图像,我们可以用它来测试和工作。