Install chef client on Windows Container and Connect to Chef Server

With a traditional windows machine the traditional “knife bootstrap windows winrm …” approach to bootstrapping works fine.  It is not however so straight forward for a container.  When you first spin up a container (assume a blank server core)  you have two methods to interact with it, Direct Powershell from the container host or the docker client, neither of which work with “knife” (in so far as I know).  You also don’t know the admin password.  There are going to be different methods to solving this problem, this is just one example that I have found to be simple and easy.  I am assuming you already have a chef server setup and have a minimal amount of familiarity with it.

Step one: Download the Validation key into a file named “validation.pem” and store it in an empty folder.  Then create a file ‘first-boot.json’ in that same folder. Contents:

{“run_list”:[]}

Of course you can add additional parameters to this file as you see fit.

You’ll also need to know your ‘chef_server_url’ and ‘validation_client_name’ (which you can get from “Generate knife Config” in the chef server.

Step two:  Spin up a container (more details on this here)

$ps = "Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/Microsoft/Virtualization-Documentation/master/windows-server-container-tools/Wait-Service/Wait-Service.ps1' -OutFile 'c:\Wait-Service.ps1';c:\Wait-Service.ps1 -ServiceName WinRm -AllowServiceRestart"
($cid = docker run -d microsoft/windowsservercore powershell.exe -executionpolicy bypass $ps)

Step three: Install the chef client and connect to chef server.  There are 3 lines to update with your specific info

Invoke-Command -ContainerId $cid -RunAsAdministrator -ScriptBlock{Invoke-WebRequest -uri "https://omnitruck.chef.io/install.ps1" -OutFile c:\install.ps1;c:\install.ps1;Install}
$cPath = 'c:\'
$local = '' # update this from above
docker cp -L $local $cid`:$cPath
$chefURL = '' # update with your chef server URL
$validationClientName = '' # update this with your info
Invoke-Command -ContainerId $cid -RunAsAdministrator -ScriptBlock{
@" chef_server_url '$using:chefURL'
validation_client_name '$using:validationClientName'
file_cache_path 'c:/chef/cache'
file_backup_path 'c:/chef/backup'
cache_options ({:path => 'c:/chef/cache/checksums', :skip_expires => true})
node_name '$env:COMPUTERNAME'
log_level :info log_location STDOUT "@ | Out-File 'c:\chef\client.rb' -Encoding utf8 -Force
chef-client -c c:/chef/client.rb -j c:/chef/first-boot.json
}

That's it.  Run the following if you want to inspect the client.rb that is create for troubleshooting

$name = (((docker ps --no-trunc -a| Select-String $cid).ToString()).Normalize()).Split(" ")[-1]
$ps = "get-content c:\chef\client.rb"
docker exec $($name) powershell.exe -executionpolicy bypass $ps

Keep a Windows Container Running -Docker

The nature of a docker container is to start up, run a task, then go away.  It is uninterested in whatever service you want to run that is expected to be available to clients.  In order to keep the container running you need to give it something to do when you create the container.  This can be done with 2 simple lines of powershell.

## This string, when run by the container, will grab a script from microsoft and run it.
$ps = "Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/Microsoft/Virtualization-Documentation/master/windows-server-container-tools/Wait-Service/Wait-Service.ps1' -OutFile 'c:\Wait-Service.ps1';c:\Wait-Service.ps1 -ServiceName WinRm -AllowServiceRestart"
## This will start the container and feed it the string above then while capturing the containerID that you'll want for later
($cid = docker run -d microsoft/windowsservercore powershell.exe -executionpolicy bypass $ps)
## While were at it, lets get the name of the container, it will come in handy as well
($name = (((docker $dkrRemote ps --no-trunc -a| Select-String $cid).ToString()).Normalize()).Split(" ")[-1])

The Wait-Service.ps1 file is provided by Microsoft via github.  Since this is a hosted file and could change, I would recommend downloading it and storing it on a web server you control to avoid unplanned changes.  Another alternative is to download the file and include it as part of your image build process and skipping the Invoke-webrequest part.

The variable $cid holds the ContainerID which is very useful for copying over files and restarting a container, etc.

You of course can substitute in any container image you like.  I prefer to work with a clean container.