前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >基于Packer打包Windows镜像的避坑指南

基于Packer打包Windows镜像的避坑指南

原创
作者头像
Windows技术交流
修改2024-12-11 21:43:02
修改2024-12-11 21:43:02
1590
举报
文章被收录于专栏:Windows技术交流

2023年我第一次用腾讯云Packer的时候,当时还是1.8.6版本,那时第一次用,并没有吃透Packer,比如:

代码语言:txt
复制
使用Packer须知:

1、Linux依赖SSH建立网络连接,通过SSH下发指令,在provisioners部分通过脚本实现业务逻辑。

2、Windows依赖WinRM建立网络连接,通过WinRM下发指令。
由于Windows公共镜像默认没有完全启用WinRM,因此需要借助UserData启用WinRM,原本调用RunInstances接口时放在UserData里的业务代码则放在provisioners部分执行。

3、Packer可以分步骤衔接,甚至相邻的两个步骤之间可以用重启机器来衔接。

当时发现一样的UserData代码在Packer里面没有作用,就以为是Packer不兼容而放弃使用Packer了(确实也是Packer应该优化的地方,好的功能不应该那么多先决条件,哪怕不得不这样,也得有像样的文档来指导使用者),当时我摸索使用后记录的那篇文档虽然有不完全对的地方,但温故知新仍有意义,比如摘录的这部分如下图。

2024年有个契机第二次使用Packer,深入进行了研究,发现Packer确实跟普通调用云API接口创建机器打包镜像还是有些区别的。

1、Packer创建打包机时给UserData传的代码越快执行完越好,Packer的机制并不会等UserData代码执行完成后再去执行provisioners模块,且Packer在Windows系统上强依赖WinRM,而云厂商的sysprep公共镜像一般都是没完全放开WinRM的,加之没有完整的Windows打包镜像的例子作参考,首次使用Packer打包Windows镜像极易出现误解,以为不兼容UserData。

2、Packer从1.9.0版本开始,Plugin跟Packer主程序分开了,需要单独安装Plugin,否则会报builder unknown,而1.9.0之前比如1.8.7版本的Packer.exe是自带Plugin的,无需安装。

各厂商Packer Plugin文档总入口 https://developer.hashicorp.com/packer/integrations/hashicorp/alicloud

阿里云文档 https://developer.hashicorp.com/packer/integrations/hashicorp/alicloud

腾讯云文档 https://developer.hashicorp.com/packer/integrations/hashicorp/tencentcloud

1)Packer下载

https://developer.hashicorp.com/packer/install

2)Packer初始化(安装Plugin)

执行:packer init require.pkr.hcl安装plugin

代码语言:txt
复制
packer init require.pkr.hcl

require.pkr.hcl的内容如下图圈出

回显示例: Installed plugin github.com/hashicorp/tencentcloud v1.2.0 in "C:/Users/Administrator/AppData/Roaming/packer.d/plugins/github.com/hashicorp/tencentcloud/packer-plugin-tencentcloud_v1.2.0_x5.0_windows_amd64.exe"

除过packer.exe init templateName.pkr.hcl方式安装plugin外,还可以用packer plugins install sourceurl

sourceurl就是templateName.pkr.hcl中source值

例如:

代码语言:txt
复制
packer plugins install github.com/hashicorp/tencentcloud
packer plugins install github.com/hashicorp/alicloud

注意事项:在使用packer plugins install sourceurl方式时可能在NAT网络中命中GitHub限频(PACKER_GITHUB_API_TOKEN,403 API rate limit exceeded),最好找一台云服务器做客户端,plugin一般三五十MB,国内机器从GitHub获取比较慢,耐心等待。

3)客户端和CVM之间的网络要通,最好指定config.json的时候在其中分配公网

比如我指定了

阿里云:

代码语言:txt
复制
"associate_public_ip_address":true,

腾讯云:

代码语言:txt
复制
"associate_public_ip_address": true,

当然,如果客户端和CVM之间是内网互通的,不指定公网也行

3、腾讯云完整的Windows Packer配置文件qcloud_windows.json内容如下

代码语言:txt
复制
{
  "builders": [
    {
      "type": "tencentcloud-cvm",
      "secret_id": "yours",
      "secret_key": "yours",
      "region": "ap-seoul",
      "zone": "ap-seoul-1",
      "instance_type": "S5.MEDIUM2",
	  "source_image_id": "img-bhvhr6pr",
	  "disk_size": 40,
	  "vpc_id": "yours",
	  "subnet_id": "yours",
	  "communicator": "winrm",
	  "winrm_port": 5985,
	  "winrm_username": "Administrator",
	  "winrm_password": "yours",
      "image_name": "yours",
      "disk_type": "CLOUD_PREMIUM",
	  "security_group_id": "yours",
      "packer_debug": true,
      "associate_public_ip_address": true,
      "run_tags": {
        "good": "luck"
      },
      "user_data_file": "winrm_enable_userdata.ps1"
  }],
  "provisioners": [{
    "type": "powershell",
    "inline": ["mkdir C:\\test -force;wget  http://$(your  business script url) -Outfile c:\\test\\packer.ps1;powershell -file c:\\test\\packer.ps1 2>&1 >$null;"]
  }]
}

机型必须指定,但云盘类型参数可以删除(如果指定的话,是"disk_type": "CLOUD_PREMIUM"),删除后会自动选用云盘类型CLOUD_PREMIUM,指定的机型如果发不出货的时候会有下图报错,需要调整config.json换别的机型

winrm_enable_userdata.ps1内容如下:

代码语言:txt
复制
#ps1
<powershell>

# MAKE SURE IN YOUR PACKER CONFIG TO SET:
#
#
#    "winrm_username": "Administrator",
#    "winrm_insecure": true,
#    "winrm_use_ssl": true,
#
#

net user Administrator "你的密码"

write-output "Running User Data Script"
write-host "(host) Running User Data Script"

Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore

#Start-Service winrm
if($(get-service winrm).Status -notmatch "Running"){cmd.exe /c net start winrm}

# Don't set this before Set-ExecutionPolicy as it throws an error
$ErrorActionPreference = "Continue"

# Remove HTTP listener
Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse

# Create a self-signed certificate to let ssl work
$Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "packer"
New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint -Force

# WinRM
write-output "Setting up WinRM"
write-host "(host) setting up WinRM"

cmd.exe /c winrm quickconfig -q
cmd.exe /c winrm set "winrm/config" '@{MaxTimeoutms="1800000"}'
cmd.exe /c winrm set "winrm/config/winrs" '@{MaxMemoryPerShellMB="1024"}'
cmd.exe /c winrm set "winrm/config/service" '@{AllowUnencrypted="true"}'
cmd.exe /c winrm set "winrm/config/client" '@{AllowUnencrypted="true"}'
cmd.exe /c winrm set "winrm/config/service/auth" '@{Basic="true"}'
cmd.exe /c winrm set "winrm/config/client/auth" '@{Basic="true"}'
cmd.exe /c winrm set "winrm/config/service/auth" '@{CredSSP="true"}'
cmd.exe /c winrm set "winrm/config/listener?Address=*+Transport=HTTPS" "@{Port=`"5986`";Hostname=`"packer`";CertificateThumbprint=`"$($Cert.Thumbprint)`"}"
cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes
cmd.exe /c netsh firewall add portopening TCP 5986 "Port 5986"
cmd.exe /c net stop winrm
cmd.exe /c sc.exe config winrm start= auto
cmd.exe /c net start winrm

netsh firewall set service remotedesktop enable

netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL  profile=ALL
#netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL  profile=Domain
#netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL  profile=Standard

netsh firewall set service RemoteAdmin enable
#netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL
#netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL  profile=Current

netsh advfirewall firewall add rule name="Open Port 5985" dir=in action=allow protocol=TCP localport=5985
netsh advfirewall firewall add rule name="Open Port 5986" dir=in action=allow protocol=TCP localport=5986
# Restart WinRM, and set it so that it auto-launches on startup.
cmd.exe /c net stop winrm
cmd.exe /c sc.exe config winrm start= auto

winrm quickconfig -q -force 2>&1 > $null
winrm quickconfig -q 2>&1 > $null

Set-Item WSMan:localhost\client\trustedhosts -value * -Force -Confirm:$false -EA 0 2>&1 >$null

restart-service winrm 2>&1 > $null

netstat -ato|findstr ":5985 :5986"
winrs -r:http://127.0.0.1:5985 hostname

</powershell>

provisioners部分powershell inline中的代码,可以多行,用英文逗号分隔,涉及文件路径的,用\\分隔,例如

代码语言:txt
复制
  "provisioners": [
  {
    "type": "powershell",
    "inline": [
      "mkdir C:\\test -force",
      "wget  http://$(your  business script url) -Outfile c:\\test\\packer.ps1",
      "powershell -file c:\\test\\packer.ps1 2>&1 >$null"
    ]
  }
  ]

除过inline,provisioners部分还可以加file、script、windows-restart等,例如

代码语言:txt
复制
"provisioners": [
  {
	"type": "file",
	"source": "OSInit1.ps1",
	"destination": "$env:temp\\OSInit1.ps1"
  },
  {
	"type": "powershell",
	"inline": [
	  "powershell.exe -ExecutionPolicy ByPass -File $env:temp\\OSInit1.ps1",
	  "Set-Service RdAgent -StartupType Disabled",
	  "Set-Service WindowsAzureTelemetryService -StartupType Disabled",
	  "Set-Service WindowsAzureGuestAgent -StartupType Disabled",
	  "Remove-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\SysPrepExternal\\Generalize' -Name '*'"
	]
  },
  {
	"type": "powershell",
	"script": "OSInit2.ps1"
  },
  {
	"type": "windows-restart",
	"restart_command": "shutdown /r /f /t 0 /c \"packer restart\"",
	"restart_check_command": "powershell -command \"& {Write-Output 'restarted.'}\"",
	"restart_timeout": "8m"
  },
  {
	"type": "powershell",
	"inline": [
	  "if(Test-Path $Env:SystemRoot\\Panther\\unattend.xml){Remove-Item $Env:SystemRoot\\Panther\\unattend.xml -Force}",
	  "if(Test-Path $Env:SystemRoot\\system32\\Sysprep\\unattend.xml){Remove-Item $Env:SystemRoot\\system32\\Sysprep\\unattend.xml -Force}",
	  "if(Test-Path 'C:\\Program Files\\Cloudbase Solutions\\Cloudbase-Init\\conf\\Unattend.xml'){Remove-Item 'C:\\Program Files\\Cloudbase Solutions\\Cloudbase-Init\\conf\\Unattend.xml' -Force}",
	  "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /shutdown /generalize /oobe /unattend:C:\\Windows\\unattend.xml",
	  "Write-Output $(Get-Content -Path C:\\Windows\\Setup\\State\\State.ini -Raw)",
	  "while($true){ Start-Sleep -s 5;$imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; Write-Output $imageState.ImageState}"
	]
  }
]
代码语言:txt
复制
"& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /shutdown /generalize /oobe /unattend:C:\\Windows\\unattend.xml", 	  
其中这句中的C:\\Windows\\unattend.xml是在之前步骤中的代码里下载到这个位置的。

其中file类型是上传下载(默认是从本地上传到打包机),script类型是上传.json配置文件同级目录的本地脚本到打包机后执行,inline实际就是在打包机上执行对应代码,windows-restart类型就是重启打包机。

4、阿里云完整的Windows Packer配置文件alicloud_windows.json内容如下

代码语言:txt
复制
{
  "builders": [{
    "type":"alicloud-ecs",
    "access_key":"yours",
    "secret_key":"yours",
    "region":"ap-northeast-2",
    "image_name":"yours",
    "source_image":"win2019_1809_x64_dtc_en-us_40G_alibase_20241120.vhd",
	"associate_public_ip_address":true,
	"vpc_id":"vpc-mj7t963pgm9ichaq77xxx",
	"vswitch_id":"vsw-mj7ukes6d5jla179tayyy",
	"security_group_id":"sg-mj79ngpxfthksd26kzzz",
    "instance_type":"ecs.c7.large",
    "io_optimized":"true",
    "internet_charge_type":"PayByTraffic",
    "image_force_delete":"true",
    "communicator": "winrm",
    "winrm_port": 5985,
    "winrm_username": "Administrator",
    "winrm_password": "yours",
    "user_data_file": "winrm_enable_userdata_ali.ps1",
    "disable_stop_instance": "true",
    "system_disk_mapping":
      {
        "disk_name": "sysdisk",
        "disk_category": "cloud_auto",
        "disk_size": 40
      }
    ,
    "image_disk_mappings":[
      {
        "disk_name": "datadisk1",
        "disk_category": "cloud_auto",
        "disk_size": 20,
        "disk_delete_with_instance":true
      },
      {
        "disk_name": "datadisk2",
        "disk_category": "cloud_auto",
        "disk_size": 20,
        "disk_delete_with_instance":true
      }
    ]
  }],
  "provisioners": [
  {
    "type": "powershell",
    "script": "packer1.ps1"
  }
  ]
}

winrm_enable_userdata_ali.ps1内容如下:

代码语言:txt
复制
[powershell]
#ps1
# MAKE SURE IN YOUR PACKER CONFIG TO SET:
#
#
#    "winrm_username": "Administrator",
#    "winrm_insecure": true,
#    "winrm_use_ssl": true,
#
#

net user Administrator "你的密码"

write-output "Running User Data Script"
write-host "(host) Running User Data Script"

Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore

#Start-Service winrm
if($(get-service winrm).Status -notmatch "Running"){cmd.exe /c net start winrm}

# Don't set this before Set-ExecutionPolicy as it throws an error
$ErrorActionPreference = "stop"

# Remove HTTP listener
Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse

# Create a self-signed certificate to let ssl work
$Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "packer"
New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint -Force

# WinRM
write-output "Setting up WinRM"
write-host "(host) setting up WinRM"

cmd.exe /c winrm quickconfig -q
cmd.exe /c winrm set "winrm/config" '@{MaxTimeoutms="1800000"}'
cmd.exe /c winrm set "winrm/config/winrs" '@{MaxMemoryPerShellMB="1024"}'
cmd.exe /c winrm set "winrm/config/service" '@{AllowUnencrypted="true"}'
cmd.exe /c winrm set "winrm/config/client" '@{AllowUnencrypted="true"}'
cmd.exe /c winrm set "winrm/config/service/auth" '@{Basic="true"}'
cmd.exe /c winrm set "winrm/config/client/auth" '@{Basic="true"}'
cmd.exe /c winrm set "winrm/config/service/auth" '@{CredSSP="true"}'
cmd.exe /c winrm set "winrm/config/listener?Address=*+Transport=HTTPS" "@{Port=`"5986`";Hostname=`"packer`";CertificateThumbprint=`"$($Cert.Thumbprint)`"}"
cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes
cmd.exe /c netsh firewall add portopening TCP 5986 "Port 5986"
cmd.exe /c net stop winrm
cmd.exe /c sc.exe config winrm start= auto
cmd.exe /c net start winrm

netsh firewall set service remotedesktop enable

netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL  profile=ALL
#netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL  profile=Domain
#netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL  profile=Standard

netsh firewall set service RemoteAdmin enable
#netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL
#netsh.exe firewall set service type=RemoteAdmin mode=ENABLE scope=ALL  profile=Current

netsh advfirewall firewall add rule name="Open Port 5985" dir=in action=allow protocol=TCP localport=5985
netsh advfirewall firewall add rule name="Open Port 5986" dir=in action=allow protocol=TCP localport=5986
# Restart WinRM, and set it so that it auto-launches on startup.
cmd.exe /c net stop winrm
cmd.exe /c sc.exe config winrm start= auto

winrm quickconfig -q -force 2>&1 > $null
winrm quickconfig -q 2>&1 > $null

Set-Item WSMan:localhost\client\trustedhosts -value * -Force -Confirm:$false -EA 0 2>&1 >$null

restart-service winrm 2>&1 > $null

netstat -ato|findstr ":5985 :5986"
winrs -r:http://127.0.0.1:5985 hostname

5、第4和第5中提到的config.json和winrm_enable_userdata.ps1跟Packer主程序packer.exe放在一起,执行的时候切到主程序所在目录做build

Cmd:

代码语言:javascript
复制
packer.exe build config.json

Powershell:

代码语言:javascript
复制
.\packer.exe build config.json

建议使用高版本,测试当前最新版1.11.2正常。

6、打包机尽量使用独立干净网络的云服务器,不要使用自己的NAT办公网络,可能会有办公网的网络安全措施导致打包超时

比如下图中的报错“Script exited with non-zero exit status: 16001.”

7、腾讯云跟阿里云比有一个参数缺失,但目前测试下来,在sysprep场景没有直接影响,因为Sysprep.exe /shutdown了,既不是Sysprep.exe /reboot也不是Sysprep.exe /quit,所以没影响

代码语言:txt
复制
sysprep代码是放在provisioners部分执行的,关于Packer默认就会在执行完provisioners关机做镜像的开关控制,阿里云有个参数:disable_stop_instance

8、基于腾讯云API调用多个接口实现类似Packer打包镜像的效果的方案也可行

自动创建CVM、自动在UserData中实现业务逻辑后关机、自动检查机器状态已关机然后做镜像、自动检查镜像状态从CREATING变成NORMAL然后销毁用于创建镜像的机器。效果图如下:

我整理到开发者社区了 https://cloud.tencent.com/developer/article/2471664

9、最后,我想强调的是,一个好的自定义镜像,它应该是通用的,至少能兼容阿里云、腾讯云,能兼容二三十种常规机型而保证一致性,而不是一个镜像买不同机型的多台机器出现多种现象(如果是这样,那肯定是打包镜像的业务逻辑设计得不够严密)。

10、我苦心钻研的东西为什么总喜欢分享出来,就不怕偷师吗?我不在乎,你能学懂是你自己的能力好,本人文档不注重排版,在我看来就是笔记,算不上文档,倘若我的笔记对你有所帮助,也是你自己基础好、执行力强。我在开发者社区分享东西,主要是我记忆力每况愈下,并不是好为人师,好记性不如烂笔头。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档