This post will show you how to use PowerShell to deploy Windows Server Containers using Windows Server 2016 (WS2016) Technical Preview 3 (TPv3).
Note: I wanted to show you how to deploy IIS, but I found that IIS would only work on my first container, and fail on the others.
This example will deploy multiple containers running nginx web server on the same VM host. NAT will be used to network the VMs using a private IP range on the VM host’s internal virtual switch.
Note: The VM host is created at this point, with a working NATing virtual switch that has an IP range of 192.168.250.0/24, with 192.168.250.1 assigned to the VM host.
Create the nginx Container Image
The beauty of containers is that you create a set of reusable container images that have a parent child relationship. The images are stored in a flat file repository.
Note: In TPv3, the repository is local on the VM host. Microsoft will add a shared repository feature in later releases of WS2016.
Log into the VM host (which runs Server Core) and launch PowerShell
In this example I will create a new container using the default WindowsServerCore container OS image. Note that I capture the instance of the new container in $Container; this allows me to easily reference the container and it’s attributes in later cmdlets:
$Container = New-Container -Name nginx -ContainerImageName WindowsServerCore -SwitchName "Virtual Switch"
The container is linked to the virtual switch in the VM host called “Virtual Switch”. This virtual switch is associated with the VM’s sole virtual NIC, and sharing is enabled to allow the VM to also have network connectivity. The switch is enabled for NATing, meaning that containers that connect to the switch will have an IP of 192.168.250.x (in my setup). More on this stuff later.
Start the new container:
Wait 30 seconds for the container to boot up and then remote into it:
Enter-PSSession -ContainerId $Container.ContainerId -RunAsAdministrator
I would normally use IIS here, but I had trouble with IIS in Windows Server Containers (TPv3). So instead, I’m going to deploy nginx web server. Run the following to download the installer (zip file):
WGet -Uri 'http://nginx.org/download/nginx-1.9.3.zip' -OutFile "c:\nginx-1.9.3.zip"
The next command will expand the zip file to c:\nginx-1.9.3\
Expand-Archive -Path C:\nginx-1.9.3.zip -DestinationPath c:\ -Force
There isn’t really an installer. nginx exists as an executable that can be run, which you’ll see later. The service “install” is done, so now we’ll exit from the remote session:
We now have a golden container that we want to capture. To do this, we must first shut down the container:
Now we create a new reusable container image called nginx:
New-ContainerImage -Container $Container -Publisher AFinn -Name nginx -Version 1.0
The process only captures the differences between the original container (created from the WindowsServerCore container OS image) and where the machine is now. The new container image will be linked to the image that created the container. So, if I create a container image called nginx, it will have a parent of WindowsServerCore.
I’m done with the nginx container so I’ll remove it:
Remove-Container $Container –Force
Deploying A Service Using A Container
The beauty of containers is how quick it is to deploy a new service. We can deploy a new nginx web server by simply deploying a new container from the nginx container image. All dependencies, WindowsServerCore in this case, will also be automatically deployed in the container.
Actually, “deploy” is the wrong word. In fact, a link is created to the images in the repository. Changes are saved with the container. So, if I was to add content to a new nginx container, the container will contain the web content, and use the service and OS data from the nginx container image in the repository, and OS stuff from the VM host and the container OS image in the repository.
Let’s deploy a a new container with nginx. Once again I will store the resulting object in a variable for later use:
$Web2 = New-Container -Name Web2 -ContainerImageName nginx -SwitchName "Virtual Switch"
Then we start the container:
Wait 30 seconds before you attempt to remote into the container:
Enter-PSSession -ContainerId $Web2.ContainerId –RunAsAdministrator
Now I browse into the extracted nginx folder:
And then I start up the web service:
Yes, I could have figured out how to autostart ngnix in the original template container. Let’s move on …
I want to confirm that nginx is running, so I check what ports are listening using:
I then retrieve the IP of the container:
Remember that the container lives in the NAT network of the virtual switch. In my lab, the LAN is 172.16.0.0/16. My VM host has 192.168.250.0/24 configured (Install-ContainerHost.ps1) as the NAT range. In this case, the new container, Web2 has an IP of 192.168.250.2.
I then exit the remote session:
There’s two steps left to allow HTTP traffic to the web service in the container. First, we need to create a NAT rule. The container will communicate on the LAN via the IP of the VM host. We need to create a rule that says that any TCP traffic on a select port (TCP 82 here) will be forwarded to TCP 80 of the container (192.168.250.2). Run this on the VM host:
Add-NetNatStaticMapping -NatName "ContainerNat" -Protocol TCP -ExternalIPAddress 0.0.0.0 -InternalIPAddress 192.168.250.2 -InternalPort 80 -ExternalPort 82
Finally, I need to create a firewall rule in the VM host to allow inbound TCP 82 traffic:
New-NetFirewallRule -Name "TCP82" -DisplayName "HTTP on TCP/82" -Protocol tcp -LocalPort 82 -Action Allow -Enabled True
Now if I open up a browser on the LAN, I should be able to browse to the web service in the container. My VM host has an IP of 172.16.250.27 so I browse to http://172.16.250.27:82/ and the default nginx page appears.
Deploy More of the Service
OK, we got one web server up. The beauty of containers is that you can quickly deploy lots of identical services. Let’s do that again. The next snippet of code will deploy an additional nginx container, start it, wait 30 seconds, and then log into it via session remoting:
$Web3 = New-Container -Name Web3 -ContainerImageName nginx -SwitchName "Virtual Switch"
Enter-PSSession -ContainerId $Web3.ContainerId -RunAsAdministrator
I then start nginx, verify that it’s running, and get the NAT IP of the container (192.168.250.3).
Now I can create a NAT mapping for the container in the networking of the VM host. In this case we will forward traffic to TCP 83 to 192.168.250.4 (the container):
Add-NetNatStaticMapping -NatName "ContainerNat" -Protocol TCP -ExternalIPAddress 0.0.0.0 -InternalIPAddress 192.168.250.3 -InternalPort 80 -ExternalPort 83
And then we open up a firewall rule on the VM host to allow inbound traffic on TCP 83:
New-NetFirewallRule -Name "TCP83" -DisplayName "HTTP on TCP/83" -Protocol tcp -LocalPort 83 -Action Allow -Enabled True
Now I can browse to identical but independent nginx web services on container 1 on http://172.16.250.27:82/ and http://172.16.250.27:83/, all accomplished with very little work and a tiny footprint. One might be production. One might be test. I could fire up another for development. And there’s nothing stopping me firing up more to troubleshoot, branch code, and test upgrades, and more, getting a quick and identical deployment every time that I can dump in seconds:
Remove-Container $Web2, $Web3
If you have apps that are suitable (stateless and no AD requirement) then containers could be very cool.