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
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:
Start-Container $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:
Exit
We now have a golden container that we want to capture. To do this, we must first shut down the container:
Stop-Container $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:
Start-Container $Web2
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:
cd c:\nginx-1.9.3\
And then I start up the web service:
start nginx
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:
NetStat –AN
I then retrieve the IP of the container:
IPConfig
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:
Exit
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" Start-Container $Web3 Sleep 30 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).
cd c:\nginx-1.9.3\ start nginx NetStat -AN IPconfig exit
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.
Thanks for the blog post. I have some questions:
You wrote: “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.”
How/where exactly did you set the 192.168.250.0 ? Because the default MS Install-ContainerHost.ps1 at https://aka.ms/tp4/Install-ContainerHost just sets NATSubnetPrefix to default: 172.16.0.0/12
Is that how you set/override the range?
I tried to setup a Windows Container that runs an IIS and with host port binding, but having problems accessing the container via the host IP and bound port. I’ve put up a detailed problem description on StackExchange/SuperUser: http://superuser.com/questions/1057223/windows-container-port-binding-on-windows-server-2016-not-working
I am new to Windows Containers and not an expert on networking. I am a bit confused about the IP ranges and wording you mention in the article. You are talking about “VM host” instead of just “host”. Why do you say “VM”? I thought Windows Containers run natively on Windows Server 2016, and if I am not using Hyper-V, there is no intermediate VM in between, or am I wrong?
Then I am also confused about the IP address of the VM host.
You wrote:
“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.”
Later on you wrote “My VM host has an IP of 172.16.250.27”. Can you explain a bit about these two different IP ranges.
When I follow the Getting started docs from Microsoft at https://msdn.microsoft.com/en-us/virtualization/windowscontainers/quick_start/inplace_setup, and with their standard Install-ContainerHost.ps1, the IP range of my Virtual Switch/vEthernet is 172.16.0.x, my host has IP 172.16.0.1, my container has 172.16.0.2 and the ethernet0 address of my host is 10.10.0.79.