For quite some time now Containers and especially Docker are gaining traction. If you haven’t heard about them, there are a lot of good resources on the internet to help you get started but the idea is to take virtualization one step further. We moved from physical machines to virtual machines to allow more density and better usage of the hardware as well as easier failover and scaling and also better standardization through images. Containers are sometimes called “application virtualization”, which I think is a pretty well fitting term. You package everything you need for an application, e.g. .NET, the NAV Server and some custom dlls and put them in layers on top of a basic image. All containers share the host OS and only have a very small (relatively speaking) kernel. That means that e.g. 10 containers on a host still only have 1 OS and not 10 guest OS like it would be the case for VMs. If you go to Docker (while containers were around in Linux for a very long time only with the emergence of Docker Inc started the rise of containers) and scroll down a bit you find a good graphical representation. Through that concept it is also easy to package every dependency, put it with your application in a box and isolate it from other applications without the need to put it in a dedicated VM. No more “it ran in the dev environment because we have Visual Studio there but not in production” or “it worked in the test environment because we had a .NET fix implemented that isn’t in production yet”. Or from a software vendor standpoint it is a lot easier to make sure that no other application introduces some dependency which breaks your application. In the end, a container makes sure that the rest of the system is not affecting your application and vice versa while only having the minimally necessary overhead. All this is just the beginning as you also have clusters, orchestration, network tools and a lot more on top, but this post is mainly about NAV (and SQL Server) in containers, not containers themselves. To get more information especially about Windows Containers, this is a good starting point
I was thinking about trying containers for quite some time but the final push came when I attended the Continuous Lifecycle conference which happened to be held together with ContainerConf. There I went to a very good talk about Docker, Containers and Windows by Rainer Stropek (@rstropek on twitter and blogging here) where I got a good overview. As I had some time to kill afterwards I just started the Windows Server 2016 machine on Azure that I had already prepared a couple of weeks ago. It took a couple of minutes to install Docker and start it but then came the first positive impression: I found a ready-to-use Windows Server 2016 image by Microsoft and could easily run it. All I had to do was
docker pull microsoft/mssql-server-windows-express docker run -d -p 1433:1433 -v d:\downloads:c:\downloads --env sa_password=VerySecret --env accept_eula=y microsoft/mssql-server-windows-express
and I had my first Container up and running! The first step is to download the image, the second is to run it with parameters for port forwarding, sharing the host file system and setting the sa password. To connect to it I installed SQL Server Management Studio in the host machine which actually took a lot longer than downloading and starting the Container. After that I restored a Cronus DB from NAV 2017 RTM which worked just as expected. As there are no prepared Container images for NAV I had to create one. As base image I used a Windows Server 2016 Core with .NET framework 3.5. Again, two simple commands and it was up and running:
docker pull microsoft/dotnet-framework:3.5 docker run -it -p 7045-7048:7045-7048 -v d:\downloads:c:\downloads microsoft/dotnet-framework:3.5 microsoft/dotnet-framework:3.5 cmd
As you can probably guess this is exposing port 7045 to 7048 and running a cmd inside the image (-it tells it to open an interactive terminal). Now came the tricky part: installing NAV 2017 in a Windows Server 2016 Core container. For those really interested I will list all the details in the end but the short version is:
But in the end I have NAV 2017 running inside a Windows Server 2016 Core container connected to a SQL Server 2016 Express also running inside a Windows Server 2016 Core container. It cost me a couple of hours while I should have been sleeping, but it works 🙂
If you are like me, then maybe you can even understand that I to some degree did it just to find out if it works. But that is not the only reason. First of all there are the same benefits as stated in the beginning for hosters and customers: You could isolate NAV Server instances from other instances while still packing your host quite densely. E.g. you could have some instances for pre-production use, some for integration testing and some for development and each of those could use different CUs while still running on the same machine. Small customers could have only one NAV Server machine but would still be able to test new CUs and releases of vertical solutions. Custom dlls can be deployed without an interference. Disaster recovery scenarios can be implemented with very little downtime because containers start very fast. You could even move into some sort of “micro-service architecture” (not really because of the monolithic design of NAV, but at least an improvement) by separating SOAP, OData, Client Services and Job Queues into dedicated containers.
But I think it is even more interesting for partners developing their own solutions: Each developer can have his or her own environment very easily configured and updated using a container repository (I’ll get into some detail how that works in a future post). You can tag and version control the full environment necessary to run a given release of your solution including NAV CU, custom dlls and C/AL objects. If a customer asks you to fix something in an older release you just start the matching containers and work on it. No need to keep x different configurations and setups running or VMs lying around as the container technology very smartly only stores the deltas. And last but not least you can integrate this into your automated testing by not only creating your test databases from scratch but very easily creating the full environment.
From my point of view, a really very promising trend and technology where Microsoft definitely is working in a good direction by not implementing some proprietary stuff but openly cooperating with the rest of the world. Hopefully NAV will also start to really support it.
Before the explanation I have to say that I also only just started to work with containers. Probably a lot of the things I did can be achieved easier and better but this is the way that worked for me:
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Install-Module -Name DockerMsftProvider -Force Install-Package -Name docker -ProviderName DockerMsftProvider -Force
Client: Version: 1.12.2-cs2-ws-beta API version: 1.25 Go version: go1.7.1 Git commit: 050b611 Built: Tue Oct 11 02:35:40 2016 OS/Arch: windows/amd64 Server: Version: 1.12.2-cs2-ws-beta API version: 1.25 Go version: go1.7.1 Git commit: 050b611 Built: Tue Oct 11 02:35:40 2016 OS/Arch: windows/amd64
Stop-Service docker dockerd --unregister-service dockerd -g d:\docker --register-service Start-Service docker
docker pull microsoft/mssql-server-windows-express docker run -d -p 1433:1433 -v d:\downloads:c:\downloads --env sa_password=VerySecret --env accept_eula=y microsoft/mssql-server-windows-express
docker ps --> here you see the name of the container, in my case determined_einstein docker inspect -f="{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" determined_einstein --> this returns the IP, in my case 172.24.152.70
docker pull microsoft/dotnet-framework:3.5 docker run -it -p 7045-7048:7045-7048 -v d:\downloads:c:\downloads microsoft/dotnet-framework:3.5 microsoft/dotnet-framework:3.5 cmd
Open a PowerShell by just entering powershell and run the following to connect to the correct database server (database name is already correct):
$password = ConvertTo-SecureString "VerySecret" -AsPlainText -Force $cred = new-object -typename System.Management.Automation.PSCredential -ArgumentList sa, $password New-NAVEncryptionKey -KeyPath "c:\downloads\key" -Password $password Import-NAVEncryptionKey DynamicsNAV100 -KeyPath "c:\downloads\key" -Password $password -ApplicationDatabaseServer 172.24.152.70 -ApplicationDatabaseCredentials $cred -ApplicationDatabaseName "Demo Database NAV (10-0)" -Force
Import-PfxCertificate c:\downloads\key.pfx cert:\localmachine\my -Password $password --> this returns the thumbprint Set-NAVServerConfiguration DynamicsNAV100 -KeyName ServicesCertificateThumbprint -KeyValue 587E21B9FD05CF33BB4756B2D0683ED7E6E4C562 Set-NAVServerConfiguration DynamicsNAV100 -KeyName ServicesCertificateValidationEnabled -KeyValue false Set-NAVServerConfiguration DynamicsNAV100 -KeyName ClientServicesCredentialType -KeyValue NavUserPassword
$testCertificate = Get-ChildItem cert:\localmachine\my\587E21B9FD05CF33BB4756B2D0683ED7E6E4C562 $privateKeyPath = (Join-Path (Join-Path $env:ProgramData "Microsoft\Crypto\RSA\MachineKeys") $testCertificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName) $acl = Get-Acl $privateKeyPath -ErrorAction Stop $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("Users", [System.Security.AccessControl.FileSystemRights]::Read, [System.Security.AccessControl.AccessControlType]::Allow) $acl.AddAccessRule($accessRule) Set-Acl -aclobject $acl $privateKeyPath -ErrorAction Stop
Import-NAVServerLicense -LicenseFile 'C:\downloads\NAV2017-DVD\SQLDemoDatabase\CommonAppData\Microsoft\Microsoft Dynamics NAV\100\Database\Cronus.flf' -Database NavDatabase -SerVerInstance DynamicsNAV100
Set-NAVServerInstance DynamicsNAV100 -Restart
New-NAVServerUser -UserName Verwalter -Password $password -ServerInstance DynamicsNAV100
Get-EventLog Application -newest 10 | ForEach-Object { Write-Host $_.TimeGenerated $_.Message }
docker stop <name> docker commit <name> nav2017
docker run -it -p 7045-7048:7045-7048 nav2017 cmd
If you have any questions, feel free to contact me through the comments below. I’ll try to help (if I can)