Seems all the cool peeps are running Applications in containers these days, and considering I haven’t posted in a while – I thought I’d share a working example to quickly create instances of the Cireson SCSM Portal for your dev, test environments to help you be one of those cool peeps!
Publish the different versions to your container repo to give you the ability to freely move the portal around your various environments to automate the deployment, recovery or scaling of the portal quickly.
Overview of Steps:
- Creating the Docker Host
- Creating the GSMA Account, Credspec file and Permissions
- Building the Portal Docker Image
- Running the Image
Step1: Creating the Docker Host
On a Windows 2016 server, Start up a PowerShell command window running as an administrator and run the following commands to download and install the latest version of Docker:
Install-Module DockerMsftProvider -Force
Install-Package Docker -ProviderName DockerMsftProvider -Force
Step 2: Permissions
As docker does not yet have active directory support, we must configure a few things in AD and the docker host to have the portal permissions working happily as they would with a domain joined server. Using Group Managed Service Accounts on the host any service running on the nested container as LocalSystem can act as the the gMSA account as their domain indentity (more information here).
- Create the gMSA account, in my example I created the account called GMSA_Docker (See https://blogs.technet.microsoft.com/askpfeplat/2012/12/16/windows-server-2012-group-managed-service-accounts/ for more information about creating gMSA Accounts)
New-ADServiceAccount -name GMSA_DOCKER -DNSHostName GMSA_DOCKER.EVALLAB.LOCAL -PrincipalsAllowedToRetrieveManagedPassword 'Domain Computers'
- Import AD Modules & Install the gMSA Account on the Docker Host
Add-WindowsFeature RSAT-AD-PowerShell Import-Module ActiveDirectory Install-AdServiceAccount GMSA_DOCKER Test-AdServiceAccount GMSA_DOCKER
- Create the CredentialSpec file using CredentialSpec.psm1
Import-Module ./CredentialSpec.psm1 New-CredentialSpec -Name GMSA_DOCKER -AccountName GMSA_DOCKER
- .Add GMSA_Docker to your SCSM Administrators group for SCSM, and grant GMSA_DOCKER$ permissions in SQL Server to ServiceManager & ServiceManagement (Or if creating a new portal database – grant sysadmin rights)
Click here for the Docker zip file.
Step 3: Portal Docker Build
If you not familiar with docker, I’d recommend following the examples on the docker website here.
First we need to automate everything the portal needs in order to get built, which we declare in a dockerfile. Luckily the Cireson SCSM Portal comes already scripted in PowerShell for all the pre-reqs and install – so the only changes we need to make to the install process is to have the AppPool, CacheBuilder and Platform cache run as LocalSystem so the container can impersonate the gMSA account that has the correct SQL and Service Manager permissions.
We will need the following on a new container to complete a build with the Cireson SCSM Portal
- A Local Admin Account that we can set for the parameters installportal.ps1 requires for services.
- Some folders on the image to hold the packages and scripts we want locally
- Cireson SCSM Portal for SCSM Zip package downloadable from downloads.cireson.com
- Some Powershell scripts to switch the Service Accounts to LocalSystem
- Other bits and bobs
Bring this all together in a dockerfile – too easy right? Will break about the dockerfile with what each step is doing (The complete dockerfile used in this example can be downloaded from the post above and adjusted to suit your environment).
# escape=`
FROM microsoft/dotnet-framework:3.5
LABEL maintainer "Joe Burrows"
# Container Parameters
ARG smp_setup_zip_uri=https://ciresonreleases.blob.core.windows.net/servicemgrportal/PreviousVersions/8.4.3.2016.zip
ARG ManagementDBName=ServiceManagement
ENV ManagementDBName ${ManagementDBName}
ENV smp_setup_zip_uri ${smp_setup_zip_uri} SHELL ["powershell","-Command"]
By default the escape character in Docker is \ – this becomes a pain when working with Windows containers so is changeable by declaring # escape=` at the beginning.
The image we are using as the base is the Official Microsoft Server Core Image for docker with dotnet-framework 3.5
Then we set ARG which are the augments we can use when running a docker build (more on this latter), we are setting a default in case nothing is declared in the docker build command – and then storing them as environment variables that can called in the scripts in the steps latter in the docker file.
Next we create our Local Admin User using the net user command and net localgroup with a super secure password
# Container Image Environment Configuration ##Create Local Admin User
RUN NET USER ContainerAdmin P@ssw0rd /add /y /expires:never; `NET LOCALGROUP Administrators ContainerAdmin /add
Next we want create our working folders, download the portal installer ZIP to our image from our Environment Variable we stored above, download our custom scripts, unblock the zip, unzip, run the installpreqs and remove that annoying Default IIS Web Site.
# Download SMP Setup Zip, Container
Scripts and Install Pre-reqs
EXPOSE 80
RUN md C:\Setup; `
md C:\Scripts; `
Write-host "Downloading SMP From $ENV:smp_setup_zip_uri...."; `
invoke-WebRequest -outfile C:\Setup\SMP.Zip $ENV:smp_setup_zip_uri; `
Write-host "Downloading Scripts From https://ciresonresources.blob.core.windows.net/docker/EvalLab/smp.....";`
invoke-WebRequest -outfile C:\Scripts\Update-Service.ps1 https://ciresonresources.blob.core.windows.net/docker/EvalLab/smp/Update-Service.ps1;`
invoke-WebRequest -outfile C:\Scripts\Run-Database-Dac.ps1 https://ciresonresources.blob.core.windows.net/docker/EvalLab/smp/Run-Database-Dac.ps1;`
unblock-file -path C:\Setup\SMP.Zip; `
Expand-Archive C:\Setup\SMP.Zip C:\Setup; `.\Setup\InstallPreReq.ps1; `Remove-Website -Name 'Default Web Site'
Before we run the InstallPortal.PS1 script we need to edit the script so it installs the App Pool to Run as LocalSystem (Rather than a Windows AD User), this is achieved by finding and replacing some lines in the script as below:
## Replace AppPool Account Settings in Installer script to Use LocalSystem
RUN((Get-Content "C:\Setup\InstallPortal.ps1")-creplace 'appPool.processModel.identityType = 3','appPool.processModel.identityType = 2') | Set-Content "C:\Setup\InstallPortal.ps1"
Then we are good to declare the parameters the InstallPortal script requires for installation:
RUN
& {C:\Setup\InstallPortal.ps1 `
-ManagementServer 'EvalLab-SCSM.evallab.local' `
-SQLServer 'EvalLab-SQL.evallab.local' `
-PortalUser '.\ContainerAdmin' `
-SiteRootPath 'C:\InetPub\' `
-PortalPassword 'P@ssw0rd' `
-CreateManagementDB $true `
-ManagementDBName $ENV:ManagementDBName `
-ExecuteDac $true `
-InstallManagementPacks $true `
-ApplicationTitle 'Cireson Eval Lab Portal' `
-InstallWebsite $true `
-ManagementServerBinaryPath '\\EvalLab-SCSM.evallab.local\c$\Program Files\Microsoft System Center\Service Manager\SDK Binaries' `
-RunCacheBuilderAsService $true `
-CacheServiceUserName '.\ContainerAdmin' `
-CacheServicePassword 'P@ssw0rd' `
-AnalystsADGroup 'SCSM Analysts' `
-AssetManagementADGroup 'SCSM Asset Managers' `
-KnowledgeManagerADGroup 'SCSM KB Managers' `
-SMSQLServer 'EvalLab-SQL.evallab.local' `
-SMTPServerName 'EvalLab-SCSM.evallab.local' `
-SMTPServerPort 25 `
-SMTPEmailReturnAddress 'Cireson@EvalLab.Local' `
-AnalyticServiceUserName '.\ContainerAdmin' `
-AnalyticServicePassword 'P@ssw0rd'`
-InstallAnalytic $true `
-AnalyticFrequencyStartDate '2017-12-09T16:17:43.747224+00:00' `
}; `
Next we want to run our custom script that we downloaded to the image to switch the installed services from our local user to LocalSystem
& \Scripts\Update-Service.ps1 "CacheBuilder"; `
& \Scripts\Update-Service.ps1 "Platform_Cache"
And also Delete the customspace folder (As we want to bind the customspace folder from the dockerhost to the container – to make customization easier and if we delete containers we don’t want to lose customizations)
#Empty CustomSpace Folder so it can be mapped as a Volume from the docker Host in the docker run command
RUN Remove-item C:\InetPub\CiresonPortal\CustomSpace -Recurse -Force -verbose
The last step is optional but I create a scheduled task to re-run the DAC file on container startup incase the image was pulled from another environment I want to make sure the database gets upgraded correctly.
#Create Scheduled task to run DAC On container start to Upgrade database incase image pulled from Repo rather than built locally
RUN schtasks /Create /TN Run-Database-Dac /SC ONSTART /TR 'c:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe C:\Scripts\Run-Database-Dac.ps1' /ru SYSTEM
With all the above in a file called ‘dockerfile’ we can then run docker build to create an image with all the above steps run. Set our build arg to the installer zip we want in the below example we using 8.4.3.2016. We do need to set –security-opt to tell docker to run as GMSA_Docker:
$SMPVersion = "8.4.3.2016",
$SMPZipURL = "https://ciresonreleases.blob.core.windows.net/servicemgrportal/PreviousVersions/$SMPVersion.zip" Docker build -t portal:$SMPVersion --security-opt "credentialspec=file://GMSA_Docker.json" --build-arg smp_setup_zip_uri=$SMPZipURL .
Now we successfully have 8.4.3.2016 saved into an image we can quickly run or share across multiple environments
docker images will show the newly created image with the tag:
Step 4: Running the Image
Now we have the image we want to run the image using docker run and setting the needed switches:
- -dit to run detached
- -p port bindings (in this case mapping port 80 on the host to port 80 on the nested container – recommend creating a transparentnetwork so you dont have to nat ports to the containerhost, once the network is created you can simply assign an IP address per container replace -p with –network={NameOfYourTransparentNetwork} –ip={IPAddressToAssignContainer} and the EXPOSE ports declared in the docker file will be accessible on that IP address)
- –dns IP address of the DNS server in the vnet so the container can do internal name resolution
- -v to bin a folder on the host to a folder on the windows container (In this case custom space so anything in C:\Container-Volumes\SMP\CustomSpace will be available to the portal in the container)
- –name container name – optional but I like to have the container named after the application its running rather than the random names the docker daemon assigns.
- -h Windows Container Host name – must match the name of the GMSA account its impersonating.
- Credentialspec (as created from the powershell script in the permissions step)
- Docker Image name to run
docker run -dit -p 80:80 --dns=172.21.21.5 -v C:\Container-Volumes\SMP\CustomSpace:C:\Inetpub\CiresonPortal\CustomSpace --dns-search=evallab.local --name=SMP-Portal -h GMSA_Docker --security-opt "credentialspec=file://GMSA_Docker.json" portal:8.4.3.2016
Let us know if this is of help and feel free to reach out through the Cireson Community for more tips and tricks!