I wrote a blog post about running a Neo4j cluster in Windows Containers but I didn’t go into too much detail about setting them up. In this post, I’ll walk through setting up Windows Containers and some helpful tips to avoid some problems.
This post is mainly aggregated documentation from Docker, Microsoft, Stefan Scherer and other sources from when I was installing Windows Containers myself. Also, this is about Windows Containers, not Docker for Windows, which is a different product.
Before we start - Documentation
Like most things, it’s always a good idea to read up on them before diving in…
The documentation both on the Docker website and the Microsoft website is surprisingly good! It has some good examples and walk throughs as well some in depth documentation.
The Community and Support section also has a list of blog posts and videos for using Windows Containers
As Windows Containers are relatively new, and Linux Containers are not new, a lot of the documenation out there is targetted for Linux Containers. When searching for help and issues, I found it best to use the Windows Container
search term instead of Docker for Windows
to avoid links that were not relevant
What are Containers, Images and Docker?
The Microsoft Windows Containers website has a good introduction to Windows Containers
TLDR version
-
Images contain all the filesystem and registry changes for an application to run
-
Images can contain other images
-
All Images must, eventually, include an OS Image (Server Core or Nano)
-
Images are deployed and executed as Containers on Container Hosts
-
Many Containers can be deployed from the same Image but are all independent of each other
-
Docker wraps up the management of Containers, Images and Container Hosts in an easier to use format
Container Image
All the file and registry changes required to run an application are packaged into Container Images. For those that have used Application Virtualisation before (VMware ThinApp, Microsoft AppV etc.), this should sound familiar. An image can include other images so you can layer, or compose, them together, for example, if you have a website that ran on IIS, the website Image would include the IIS image. Indeed, all images must include at least a Container OS Image. For Windows Containers this is either the Windows Server Core or Nano Server OS Image.
The name Container Image can be confusing so they’re normally just called Images
Container OS Image
The Container OS image is the first layer in potentially many image layers that make up a container. This image provides the operating system environment. A Container OS Image is Immutable, it cannot be modified.
Container Host
Physical or Virtual computer system configured with the Windows Container feature. The container host will run one or more Windows Containers.
Container
Containers are created when you execute a Container Image, however each Container will have its own copy of the Image which means you can deploy many containers from the same Image safely.
Windows Containers has two isolation models, unlike Linux which has only one.
Windows Server Containers - provide application isolation through process and namespace isolation technology. A Windows Server container shares a kernel with the container host and all containers running on the host.
Hyper-V Containers – expand on the isolation provided by Windows Server Containers by running each container in a highly optimized virtual machine. In this configuration, the kernel of the container host is not shared with the Hyper-V Containers.
Server 2016 supports both isolation modes, while Windows 10 only supports the Hyper-V Containers. I could not find any documentation on the isolation modes supported by Nano Server but I suspect it is both isolation modes.
Docker
Docker is a management and packaging layer on top of Containers. This means we can use the Docker API on a Linux Container Host or a Windows Container Host with little change. Also, other tools which build upon Docker, for example, Docker Compose, Docker Swarm or Kubernetes can start to be used with Windows Containers.
While the Docker API is the same between Windows and Linux, there are some very different things between them such as volume mounting and networking which I will cover later.
What installation options are there?
So how do we install Windows Containers and Docker? This blog post is about creating a development environment so I won’t comment on how to deploy Windows Containers in production.
Windows Containers
For development environments, there are a few options:
- Install locally on Windows 10
- Install locally on Server 2016
- Install a Windows 10 or Server 2016 in Virtual Machine
I personally wouldn’t recommend Nano Server for development environment due to how tricky they can be to setup, however it’s possible to do.
Installing locally
The Microsoft documentation is very straightforward and is the same for Server 2016 and Windows 10
Windows Server 2016
PS> Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
PS> Install-Package -Name docker -ProviderName DockerMsftProvider
PS> Restart-Computer -Force
Windows 10
PS> Enable-WindowsOptionalFeature -Online -FeatureName containers -All
PS> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
PS> Restart-Computer -Force
PS> Invoke-WebRequest "https://test.docker.com/builds/Windows/x86_64/docker-1.13.0-rc4.zip" -OutFile "$env:TEMP\docker-1.13.0-rc4.zip" -UseBasicParsing
PS> Expand-Archive -Path "$env:TEMP\docker-1.13.0-rc4.zip" -DestinationPath $env:ProgramFiles
PS> # Add path to this PowerShell session immediately
PS> $env:path += ";$env:ProgramFiles\Docker"
PS> # For persistent use after a reboot
PS> $existingMachinePath = [Environment]::GetEnvironmentVariable("Path",[System.EnvironmentVariableTarget]::Machine)
PS> [Environment]::SetEnvironmentVariable("Path", $existingMachinePath + ";$env:ProgramFiles\Docker", [EnvironmentVariableTarget]::Machine)
PS> dockerd --register-service
PS> Start-Service Docker
Install in a VM
This is basically the same as Installing Locally, but within a Virtual Machine.
Install in a VM using Vagrant
Stefan Scherer has created a Vagrant configuration which:
After provisioning the box has the following tools installed:
Windows Server 2016 with Docker Engine CS 1.12 and client
docker-machine 0.8.2
docker-compose 1.9.0
Docker Tab completion for PowerShell (posh-docker)
Chocolatey
Git command line
Git Tab completion for PowerShell (posh-git)
SSH client
Which means you can run Windows Containers in both isolation modes and Linux containers (docker-machine)! Stefan also has some Docker-Swarm examples too which I intend to look into further.
Docker Client
So now we have a Windows Container Host installed, and a Docker Engine running to manage the Host, now we need the docker client. Note if you installed Docker on Windows 2016 there should already be a client installed, but you may want to install an updated version.
Fortunately Chocolatey has a docker client package available at https://chocolatey.org/packages/docker which is maintained by Stefan Scherer and Ahmet Alp Balkan
PS> choco install docker
If you would like the most release Docker Client, you can manually download it from the Github Docker Releases page. These zip files need to be extracted (like in the Windows 10 installation instructions) before use
Connecting to remote Docker Hosts
You don’t have to install the Docker client on the same host as the Docker server, the Docker API also runs over a network (TCP) connection.
Setting up the Docker Server for remote connections
By default, the Docker Server will only listen on a Named Pipe connection, which cannot be accessed remotely. However, it is fairly easy to add additional listeners:
-
Edit
C:\ProgramData\docker\config\daemon.json
. If it does not exist, just create the text file -
Add or modify the hosts setting and append
tcp://0.0.0.0:2375
{
"hosts": ["tcp://0.0.0.0:2375","npipe://"]
}
- Save the file and restart the Docker Service
This configuration will listen on all IP addresses on port 2375 (Default Docker port). However, if you would like to restrict the manager to, say, a dedicated Management Network Interface (common in production environments), change the IP address appropriately.
More information on configuring the Docker Server on Windows is available on the Microsoft Windows Containers documentation website.
Setting up the Docker Client for remote connections
You can specify a remote Docker Server when using the Docker Client in two ways:
-
Use the
-H
or--host
command line parameter -
Set the
DOCKER_HOST
environment variable
For example, to connect to a remote Docker Server at IP Address 10.0.0.1
on the default port of `23751
PS> docker -H tcp://10.0.0.1:2375 info
or
PS> $ENV:DOCKER_HOST = 'tcp://10.0.0.1:2375'
PS> docker info
To use a local Docker Server over named pipes, use a host of npipe:////./pipe/docker_engine
.
Container OS Images
You can start the download of the two Container OS Images by running the appropriate docker pull
command;
Nano Server Container OS Image (600 MB or so)
PS> docker pull microsoft/nanoserver
Windows Server 2016 Server Core Container OS Image (9GB or so)
PS> docker pull microsoft/windowsservercore
File System information
Containers have an isolated file system from the host, which means any file you modify inside the container won’t affect the host. But what about log files or database files how do I mount them inside the container so they can modify them?
When you start a container, you can specify to mount directories on the host inside of the container. Any modifications to the files will be persisted on the host.
For example;
Let’s say I have log directory on my host called C:\ContainerLogs
and my container writes to log files in the C:\Logs
directory. When I start the container, I use a command line similar to below;
PS> docker run -it --volume C:\ContainerLogs:C:\Logs microsoft/nanoserver
...
C:\>dir
Volume in drive C has no label.
Volume Serial Number is 7055-B6A6
Directory of C:\
11/04/2016 09:04 AM 1,894 License.txt
01/14/2017 09:22 PM <SYMLINKD> logs [\\?\ContainerMappedDirectories\24E62B19-4E1A-41B8-9B60-26DCC654A55F]
07/16/2016 04:20 AM <DIR> Program Files
07/16/2016 04:09 AM <DIR> Program Files (x86)
11/04/2016 09:05 AM <DIR> Users
01/14/2017 09:23 PM <DIR> Windows
1 File(s) 1,894 bytes
5 Dir(s) 21,205,929,984 bytes free
C:\>
Note that there is a directory called logs
inside the container and that is a symlink, not a real directory like Users
or Windows
.
See the Docker documentation on volumes for more information
Containers and Images on the Host
By default, the images are stored in C:\ProgramData\docker\windowsfilter
and containers in C:\ProgramData\docker\containers
. To display this information run docker info
and look for the Docker Root Dir
PS> docker info
...
Docker Root Dir: C:\ProgramData\docker
...
Networking Information
Networking within Windows Containers can be a tricky to understand at first. If you come from a Virtual Machine background, it feels like normal Virtual Switches as used in HyperV (or VMware ESX). If you come from a Linux Container background, it feels like the usual NAT switches used there. But in both cases there are some subtle and infuriating differences.
Unlike Linux Containers, Windows Containers offer more networking options and I suggest reading the Windows Container Networking document for more detailed infomation.
Layer 2 Bridge and Layer 2 Tunnel
These are more complex networking options which I don’t feel most development environments will take advantage of.
Network Address Translation (NAT)
This is the most common networking option for Windows Containers. The Container Host will use the native WinNAT features of the operating system to assign an IP address to Containers and then use port mapping to redirect network traffic appropriately. However there are some limitations with the NAT network;
Only one NAT internal IP prefix is supported per container host, so ‘multiple’ NAT networks must be defined by partitioning the prefix
Container endpoints are only reachable from the container host using container internal IPs and ports
Issue - Only one NAT prefix
WinNAT will only allow one IP prefix (x.x.x.x/y) to be available for translation. This doesn’t mean you can’t have multiple NAT based Docker Networks, just that the setup for the WinNAT is more complicated, for example (taken from the documentation);
If you wanted two NAT networks, 172.16.0.0/16
and 172.17.0.0/16
, then the WinNAT prefix must encompass both of those networks: 172.0.0.0/8
, 172.16.0.0/12
or 172.16.0.0/15
are all examples of valid WinNAT prefixes.
Creating a custom NAT network
The Setup NAT Network documentation describes the setup process in detail.
For this example, I’ll walk through the high level steps for setting up two Container NAT Networks (172.16.0.0/16
and 172.17.0.0/16
) and WinNAT with a prefix of 172.16.0.0/12
, which encompases both of the smaller NAT networks. I also assume there is already an Internal vSwitch created.
PS> Stop-Service docker
PS> Get-ContainerNetwork | Remove-ContainerNetwork -force
PS> Get-NetNat | Remove-NetNat
- Stop the docker service as it will not allow the networks to be modified while running
- Remove the Docker NAT network using the
Remove-ContainerNetwork
cmdlet as this is not possible using the docker client - Remove the WinNAT configuration, but not remove the associated vSwitch
Edit the docker service daemon configuration file `C:\ProgramData\docker\config\daemon.json` and add `bridge: none`.
- By default, the Docker service creates a default NAT network, which we don’t want. Add
bridge: none
tells docker not to create the default network
PS> New-ContainerNetwork –Name nat1 –Mode NAT –SubnetPrefix 172.16.0.0/16 -GatewayAddress 172.16.0.1
PS> Get-Netnat | Remove-NetNAT
PS> New-ContainerNetwork –name nat2 –Mode NAT –SubnetPrefix 172.17.0.0/16 -GatewayAddress 172.17.0.1
PS> Get-Netnat | Remove-NetNAT
For each NAT network you want to configure
-
Create a new NAT container network
-
Remove the created WinNAT allocation. We override that later with our larger prefix
PS> New-NetNat -Name SharedNAT -InternalIPInterfaceAddressPrefix 172.16.0.0/12
PS> Start-Service docker
- Configure WinNAT with our larger prefix
- Start the docker service
Issue - No localhost
People who have used Linux Containers before will find this issue very quickly. If you are on the Container Host, you cannot access NAT ports using localhost. Elton Stoneman has a good writeup in this blog post.
This is important to remember if you want to do container-to-container networking or you want to test a container resource.
To find the IP Address of container run:
PS> docker inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' <Container ID>
Where <Container ID>
is a tag or ID of running container.
Transparent Networks
This networking option should be quite familiar. The container has its own networking stack and appears as a network host in its own right, no hiding behind NAT devices!. You can create a transparent network using the docker client:
PS> docker network create -d transparent -o com.docker.network.windowsshim.interface="Ethernet 2" TransparentNet2
The -o com.docker.network.windowsshim.interface="Ethernet 2"
binds the transparent network to the network interface called Ethernet 2
on the Container Host
Assigning IP Addresses to containers can be done via DHCP, however I saw that the Container IP address is not shown when inspecting the container (docker inspec ....
). This means, while it’s easy to assign IPs the Host cannot coordinate resources when using tools such as Docker Compose.
You can assign Static IPs at container creation time using the --ip
option, for example
C:\> docker run -it --network=TransparentNet2 --ip 10.123.174.105 microsoft/nanoserver cmdlet
This isn’t the most scalable solution but it’s just enough for development environments.
Additional Tools
Portainer
While the docker client is very powerful, there is always a need for a more graphical view of managing containers. Portainer is a cross platform web based UI for managing a Docker Host, which is very simple to run and use. Which is great, but they also have a Windows Container deployment option and are actively working to make their product work better with Windows Containers. If you don’t want to run Portainer in a container, you can simply download the application and run it locally.
It’s also open source so you can lodge issues, or even fix bugs yourself!
Comments