Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Improve Windows packer examples #4258

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
58 changes: 58 additions & 0 deletions images/setup-ssh.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<powershell>
# Credit to : https://github.com/chorrell/packer-aws-windows-openssh/blob/main/files/SetupSsh.ps1

# Don't display progress bars
# See: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.3#progresspreference
$ProgressPreference = 'SilentlyContinue'
Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore
# Don't set this before Set-ExecutionPolicy as it throws an error
$ErrorActionPreference = "stop"
# Install OpenSSH using Add-WindowsCapability
# See: https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=powershell#install-openssh-for-windows

Write-Output 'Installing and starting ssh-agent'
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
Set-Service -Name ssh-agent -StartupType Automatic
Start-Service ssh-agent

Write-Output 'Installing and starting sshd'
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd

# Confirm the Firewall rule is configured. It should be created automatically by setup. Run the following to verify
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
} else {
Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
}

# Set default shell to Powershell
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force

$keyDownloadScript = Join-Path $env:ProgramData 'ssh\download-key.ps1'

@'
# Download private key to $env:ProgramData\ssh\administrators_authorized_keys
$openSSHAuthorizedKeys = Join-Path $env:ProgramData 'ssh\administrators_authorized_keys'

$keyUrl = "http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key"
Invoke-WebRequest $keyUrl -OutFile $openSSHAuthorizedKeys

# Ensure ACL for administrators_authorized_keys is correct
# See https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration#authorizedkeysfile
icacls.exe $openSSHAuthorizedKeys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
'@ | Out-File $keyDownloadScript

# Create Task
$taskName = "DownloadKey"
$principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument "-NoProfile -File ""$keyDownloadScript"""
$trigger = New-ScheduledTaskTrigger -AtStartup
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName $taskName -Description $taskName

# Fetch key via $keyDownloadScript
& Powershell.exe -ExecutionPolicy Bypass -File $keyDownloadScript

</powershell>
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ cmd.exe /c net stop winrm
cmd.exe /c sc config winrm start= auto
cmd.exe /c net start winrm

</powershell>
</powershell>
34 changes: 27 additions & 7 deletions images/windows-core-2019/github_agent.windows.pkr.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ variable "region" {
default = "eu-west-1"
}

variable "aws_account_number" {
description = "The AWS account number"
type = string
default = ""
}

variable "instance_type" {
description = "The instance type Packer will use for the builder"
type = string
default = "t3a.medium"
default = "c7i-flex.xlarge"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to a larger instance for better build times. If we'd rather default to smaller in the example, I can replace it.

}

variable "ebs_delete_on_termination" {
Expand Down Expand Up @@ -62,12 +68,21 @@ locals {

source "amazon-ebs" "githubrunner" {
ami_name = "github-runner-windows-core-2019-${formatdate("YYYYMMDDhhmm", timestamp())}"
communicator = "winrm"
ami_users = [ var.aws_account_number ]
ami_regions = var.aws_region_mirror_list
ami_description = "GitHub Actions runner AMI Windows Core 2019 Pro Video"

instance_type = var.instance_type
region = var.region
associate_public_ip_address = var.associate_public_ip_address
temporary_security_group_source_public_ip = var.temporary_security_group_source_public_ip

user_data_file = "../setup-ssh.ps1"
communicator = "ssh"
ssh_username = "Administrator"
ssh_file_transfer_method = "sftp"
ssh_timeout = "15m"

source_ami_filter {
filters = {
name = "Windows_Server-2019-English-Core-ContainersLatest-*"
Expand All @@ -77,19 +92,16 @@ source "amazon-ebs" "githubrunner" {
most_recent = true
owners = ["amazon"]
}

tags = {
OS_Version = "windows-core-2019"
Release = "Latest"
Base_AMI_Name = "{{ .SourceAMIName }}"
}
user_data_file = "./bootstrap_win.ps1"
winrm_insecure = true
winrm_port = 5986
winrm_use_ssl = true
winrm_username = "Administrator"

launch_block_device_mappings {
device_name = "/dev/sda1"
volume_size = 100
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could leave this parameterized. I've just set this to 100 gigs hardcoded.

delete_on_termination = "${var.ebs_delete_on_termination}"
}
}
Expand All @@ -114,8 +126,16 @@ build {
})
], var.custom_shell_commands)
}

# Needed to make the chocolatey install pathing changes stick for any subsequent provisioning script that you want to run.
provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Restarted!'}\""
restart_timeout = "5m"
}

post-processor "manifest" {
output = "manifest.json"
strip_path = true
}

}
83 changes: 52 additions & 31 deletions images/windows-core-2019/windows-provisioner.ps1
Original file line number Diff line number Diff line change
@@ -1,52 +1,73 @@
# Set up preferences for output
$ErrorActionPreference = "Continue"
$VerbosePreference = "Continue"
$ProgressPreference = "SilentlyContinue"

# Install Chocolatey
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
$env:chocolateyUseWindowsCompression = 'true'
Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression

# Add Chocolatey to powershell profile
$ChocoProfileValue = @'
$ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
if (Test-Path($ChocolateyProfile)) {
Import-Module "$ChocolateyProfile"
# Print out our powershell version just so we know what the default is.
$PSVersionTable

# Just checking disk space
Get-PSDrive C | Select-Object Used,Free

# Make sure that we have a profile to set and write to. If not, create it.
$powershellProfile = "C:\Users\Administrator\Documents\WindowsPowerShell\profile.ps1"
Write-Output "Checking for profile at $powershellProfile"
if (!(Test-Path -Path $powershellProfile)) {
Write-Output "Creating $powershellProfile cause it doesn't exist yet"
New-Item -Type File -Path $powershellProfile -Force
}
Write-Output "Checking for profile at $profile"
if (!(Test-Path -Path $profile)) {
Write-Output "Creating $profile cause it doesn't exist yet"
New-Item -Type File -Path $profile -Force
}

refreshenv
'@
# Write it to the $profile location
Set-Content -Path "$PsHome\Microsoft.PowerShell_profile.ps1" -Value $ChocoProfileValue -Force
# Source it
. "$PsHome\Microsoft.PowerShell_profile.ps1"
# Install Chocolatey
Write-Output "Setting up Chocolatey install execution policy"
Get-ExecutionPolicy
Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
Write-Output "Downloading and running Chocolatey install script"
# NOTE : We use the System.Net.WebClient object, because using Invoke-Webrequest is ridiculously slow with larger files. Using this halves download times.
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
choco feature enable -n allowGlobalConfirmation

refreshenv
# NOTE / IMPORTANT : DO NOT SET THE POWERSHELL PROFILE. FOR SOME REASON IT BREAKS THE SFTP PROVISIONER.
# Likely due to the fact that the powershell profile will generate output when it is sourced, which causes
# a "Error processing command: Error uploading ps script containing env vars: sftpSession error: packet too long"
# failure when trying to upload the next script.

Write-Host "Installing cloudwatch agent..."
Invoke-WebRequest -Uri https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi -OutFile C:\amazon-cloudwatch-agent.msi
# Install cloudwatch agent
Write-Output "Installing cloudwatch agent..."
$cloudwatchURL = "https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi"
$cloudwatchLocation = "C:\amazon-cloudwatch-agent.msi"
$cloudwatchParams = '/i', 'C:\amazon-cloudwatch-agent.msi', '/qn', '/L*v', 'C:\CloudwatchInstall.log'
Start-Process "msiexec.exe" $cloudwatchParams -Wait -NoNewWindow
Remove-Item C:\amazon-cloudwatch-agent.msi
$webClient = New-Object System.Net.WebClient
$webClient.Downloadfile($cloudwatchURL, $cloudwatchLocation)
Write-Output "Installing Cloudwatch agent from $cloudwatchLocation"
Start-Process "msiexec.exe" -ArgumentList $cloudwatchParams -Wait -NoNewWindow
Remove-Item $cloudwatchLocation

# Install dependent tools
Write-Host "Installing additional development tools"
Write-Output "Installing additional development tools"
choco install git awscli -y
refreshenv

Write-Host "Creating actions-runner directory for the GH Action installtion"
# Install Github Actions runner
Write-Output "Creating actions-runner directory for the GH Action installtion"
New-Item -ItemType Directory -Path C:\actions-runner ; Set-Location C:\actions-runner

Write-Host "Downloading the GH Action runner from ${action_runner_url}"
Invoke-WebRequest -Uri ${action_runner_url} -OutFile actions-runner.zip

Write-Host "Un-zip action runner"
Write-Output "Downloading the GH Action runner from ${action_runner_url}"
# Invoke-WebRequest -Uri ${action_runner_url} -OutFile actions-runner.zip
$webClient.Downloadfile("${action_runner_url}", "$PWD\actions-runner.zip")
Write-Output "Unzip action runner"
Expand-Archive -Path actions-runner.zip -DestinationPath .

Write-Host "Delete zip file"
Write-Output "Delete zip file"
Remove-Item actions-runner.zip

$action = New-ScheduledTaskAction -WorkingDirectory "C:\actions-runner" -Execute "PowerShell.exe" -Argument "-File C:\start-runner.ps1"
$trigger = New-ScheduledTaskTrigger -AtStartup
Register-ScheduledTask -TaskName "runnerinit" -Action $action -Trigger $trigger -User System -RunLevel Highest -Force

C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
# TODO : The line below failed on my instances. I removed it, and everything works just fine. Something is funky here.
C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
Write-Output "Finished running windows provisioner."
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ cmd.exe /c net stop winrm
cmd.exe /c sc config winrm start= auto
cmd.exe /c net start winrm

</powershell>
</powershell>
52 changes: 38 additions & 14 deletions images/windows-core-2022/github_agent.windows.pkr.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ variable "region" {
default = "eu-west-1"
}

variable "aws_account_number" {
description = "The AWS account number"
type = string
default = ""
}

variable "aws_region_mirror_list" {
description = "The list of regions to mirror the AMI to"
type = list(string)
default = ["eu-west-1"]
}

variable "instance_type" {
description = "The instance type Packer will use for the builder"
type = string
default = "c7i-flex.xlarge"
}

variable "security_group_id" {
description = "The ID of the security group Packer will associate with the builder to enable access"
type = string
Expand All @@ -30,11 +48,6 @@ variable "subnet_id" {
default = null
}

variable "root_volume_size_gb" {
type = number
default = 30
}

variable "ebs_delete_on_termination" {
description = "Indicates whether the EBS volume is deleted on instance termination."
type = bool
Expand Down Expand Up @@ -72,15 +85,24 @@ locals {
}

source "amazon-ebs" "githubrunner" {
ami_name = "github-runner-windows-core-2022-${formatdate("YYYYMMDDhhmm", timestamp())}"
communicator = "winrm"
instance_type = "m4.xlarge"
ami_name = "github-runner-windows-core-2022-${formatdate("YYYYMMDDhhmm", timestamp())}"
ami_users = [ var.aws_account_number ]
ami_regions = var.aws_region_mirror_list
ami_description = "GitHub Actions runner AMI Windows Core 2019 Pro Video"

instance_type = var.instance_type
region = var.region
security_group_id = var.security_group_id
subnet_id = var.subnet_id
associate_public_ip_address = var.associate_public_ip_address
temporary_security_group_source_public_ip = var.temporary_security_group_source_public_ip

user_data_file = "../setup-ssh.ps1"
communicator = "ssh"
ssh_username = "Administrator"
ssh_file_transfer_method = "sftp"
ssh_timeout = "15m"

source_ami_filter {
filters = {
name = "Windows_Server-2022-English-Full-ECS_Optimized-*"
Expand All @@ -90,20 +112,16 @@ source "amazon-ebs" "githubrunner" {
most_recent = true
owners = ["amazon"]
}

tags = {
OS_Version = "windows-core-2022"
Release = "Latest"
Base_AMI_Name = "{{ .SourceAMIName }}"
}
user_data_file = "./bootstrap_win.ps1"
winrm_insecure = true
winrm_port = 5986
winrm_use_ssl = true
winrm_username = "Administrator"

launch_block_device_mappings {
device_name = "/dev/sda1"
volume_size = "${var.root_volume_size_gb}"
volume_size = 100
delete_on_termination = "${var.ebs_delete_on_termination}"
}
}
Expand All @@ -128,6 +146,12 @@ build {
})
], var.custom_shell_commands)
}

provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Restarted!'}\""
restart_timeout = "5m"
}

post-processor "manifest" {
output = "manifest.json"
strip_path = true
Expand Down
Loading
Loading