2023年我第一次用腾讯云Packer的时候,当时还是1.8.6版本,那时第一次用,并没有吃透Packer,比如:
使用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
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值
例如:
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的时候在其中分配公网
比如我指定了
阿里云:
"associate_public_ip_address":true,
腾讯云:
"associate_public_ip_address": true,
当然,如果客户端和CVM之间是内网互通的,不指定公网也行
3、腾讯云完整的Windows Packer配置文件qcloud_windows.json内容如下
{
"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内容如下:
#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中的代码,可以多行,用英文逗号分隔,涉及文件路径的,用\\分隔,例如
"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等,例如
"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}"
]
}
]
"& $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内容如下
{
"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内容如下:
[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:
packer.exe build config.json
Powershell:
.\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,所以没影响
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 删除。