Cattle - options when the nodename is unknown?

Either I’m not getting DSC (which is highly likely) or there is a huge floor in attempting to treat servers like cattle as opposed to pets.

I know the role, but not the name of the server until the deployment has reached a certain phase as its name is generated dynamically. Part of the deployment is to configure it with DSC. I can use “localhost” or “inject” the name, but the latter involves re-compiling the configuration in the middle of the deployment.
What is the purpose of Node $AllNodes.where{$_.Role -eq “WebServer”}.NodeName within a configuration? My theory is to be able to do something like:

Configuration DSCTestNodes
{
    Node $AllNodes.where{$_.Role -eq "WebServer"}.NodeName
    {
        File TestFile
        {
            Ensure = "Present"
            Type = "File"
            Contents = "Role: $($node.FileContents)"
            DestinationPath = "D:\Temp\DsctestNode-WS.txt"    
        }    
    }

    Node $AllNodes.where{$_.Role -eq "Database"}.NodeName
    {
        File TestFile
        {
            Ensure = "Present"
            Type = "File"
            Contents = "Role: $($node.FileContents)"
            DestinationPath = "D:\Temp\DsctestNode-DB.txt"
        }     
    }
} 

Essentially, one configuration for multiple roles. But this has a potential of make a very long configuration. One of my configurations for a single role is already 500 lines long, and that is using composite resources! Surely it is better to make separate configurations in separate files, keeping them as short as possible? Also, one still needs to know the hostname ahead of compilation.

This moves me on to ConfigurationData. Taking note of a post written by one of the prevalent people on this site, if I were to literally interpret NodeName to mean NodeRole which seems a good idea, I am scuppered because at run time, DSC cannot find any such computer on my network, named WS or DB.

$ConfigurationData = @{
    AllNodes = @(
        @{
            NodeName="*"
            PSDscAllowPlainTextPassword=$true
            PsDscAllowDomainUser = $true
            TempDriveLetter = "P"
            #ADDomain = $CommonParameters.AdDomainName
        }
        @{
            NodeName="WS"
            Role = "WebServer"
            FileContents = "Web server"
            WindowsFeatures = @(
                    "GPMC"
                )
        }
        @{
            NodeName="DB"
            Role = "Datasbase"
            FileContents = "Database server"
            WindowsFeatures = @(
                    "GPMC"
                    "Windows-Server-Backup"
                )
        }
    )
} 

So here, in the AllNodes array, I define my common-to-all data, then data that is unique to each role. Herein lies the problem. NodeName has to be a valid and unique hostname for an existing server. But I don’t know its hostname!

If I leave out the nodename, compilation fails:

ValidateUpdate-ConfigurationData : all elements of AllNodes need to be hashtable and has a property 'NodeName'.

What about localhost. In this case, I have two roles, therefore I use localhost twice:

$ConfigurationData = @{
    AllNodes = @(
        @{
            NodeName="*"
            PSDscAllowPlainTextPassword=$true
            PsDscAllowDomainUser = $true
            TempDriveLetter = "P"
            #ADDomain = $CommonParameters.AdDomainName
        }
        @{
            NodeName="localhost"
            Role = "WebServer"
            FileContents = "AOS server"
            WindowsFeatures = @(
                    "GPMC"
                )
        }
        @{
            NodeName="localhost"
            Role = "AXR3DB"
            FileContents = "Database server"
            WindowsFeatures = @(
                    "GPMC"
                    "Windows-Server-Backup"
                )
        }
    )
} 

Fail:

ValidateUpdate-ConfigurationData : There are duplicated NodeNames 'localhost' in the configurationData passed in.

So bring on NonNodeData. I do away with the “named” nodes and move the role-specific data thus:

$ConfigurationData = @{
    AllNodes = @(
        @{
            NodeName="*"
            PSDscAllowPlainTextPassword=$true
            PsDscAllowDomainUser = $true
            TempDriveLetter = "P"
        }
    )
    NonNodeData = @{
        Roles = @{
            WebServer = @{
                FileContents = "This is webserver"
                WindowsFeatures = @(
                    "GPMC"
                )
            }
            Database = @{
                FileContents = "This is a database"
                WindowsFeatures = @(
                    "GPMC"
                    "Windows-Server-Backup"
                )
            }
        }
    }
} 

I then need to modify the configuration thus:

Configuration DSCTestNodes
{
    Node localhost
    {
        foreach($setting in $ConfigurationData.NonNodeData.Roles)
        {
            File TestFileWS
            {
                Ensure = "Present"
                Type = "File"
                Contents = "Role: $($setting.webserver.FileContents)"
                DestinationPath = "D:\Temp\DsctestNode-WS.txt"    
            }

            File TestFileDB
            {
                Ensure = "Present"
                Type = "File"
                Contents = "Role: $($setting.database.FileContents)"
                DestinationPath = "D:\Temp\DsctestNode-DB.txt"    
            }
        }    
    }
} 

Note, I am using Node localhost and referencing the “contents” for WebServer and database from the NonNodeData hash table. But wait a minute, that creates two files on localhost, aka every computer I run it on, regardless of its intended role. Not what I want. The only way DSC knows which configuration to apply is by manually telling it which MOF file to apply. So we are back to the configuration only applying one role. The ConfigurationData can still contain data for multiple roles.

Configuration DSCTestNodesWS
{
    $WebServerSettings = $ConfigurationData.NonNodeData.Roles.WebServer

    Node localhost
    {
        File TestFileWS
        {
            Ensure = "Present"
            Type = "File"
            Contents = "Role: $($WebServerSettings.FileContents)"
            DestinationPath = "D:\Temp\DsctestNode-WS.txt"    
        }
    }    
}

Configuration DSCTestNodesDB
{
    $databaseSettings = $ConfigurationData.NonNodeData.Roles.database

    Node localhost
    {
        File TestFileDB
        {
            Ensure = "Present"
            Type = "File"
            Contents = "Role: $($databaseSettings.FileContents)"
            DestinationPath = "D:\Temp\DsctestNode-DB.txt"    
        }
    }    
}

$ConfigurationData = @{
    AllNodes = @(
        @{
            NodeName="*"
            PSDscAllowPlainTextPassword=$true
            PsDscAllowDomainUser = $true
            TempDriveLetter = "P"
        }
    )
    NonNodeData = @{
        Roles = @{
            WebServer = @{
                FileContents = "This is webserver"
                WindowsFeatures = @(
                    "GPMC"
                )
            }
            Database = @{
                FileContents = "This is a database"
                WindowsFeatures = @(
                    "GPMC"
                    "Windows-Server-Backup"
                )
            }
        }
    }
}

DscTestNodesWS -OutputPath "D:\Configurations\DSCTestNodesWS" -verbose -ConfigurationData $ConfigurationData
DscTestNodesDB -OutputPath "D:\Configurations\DSCTestNodesDB" -verbose -ConfigurationData $ConfigurationData 

I realise I can parameterise the configuration, but it has the same effect of having to compile mid-deployment, so if I’m going to all that trouble, I might as well add the actual nodename. The best I can come up with is a separation that allows minimal compiling, i.e. using localhost and only compiling when the configuration changes – which during development is every time :-).

So, am I missing a trick, or is this a limitation of DSC in its current form (5.1)?

In my humble opinion, what would resolve much of this is for DSC to take parameters that are resolved at run time. I know MOF is a standard that probably doesn’t take parameters, but when have Microsoft ever adhered to standards!

Something I haven’t mentioned is that my DSC journey is primarily concentrating on Azure. The above is theory which applies on or off premise.

In theory, I can change the logic for my DSC configurations to work with defined NodeNames. I can deploy the unconfigured VMs using Azure deployment templates. Once deployed, I can query the resource group for NodeName and Role (using tags defined at deploy time) and dynamically build the $configurationData.AllNode section using something like:

$oVMs = Get-AzureRmResource -ResourceType "Microsoft.Compute/virtualMachines" -ResourceGroupName $ResourceGroupName | Where-Object{$_.Tags.role -ne $null}
$oVms | Foreach-object{
    $ConfigurationData.AllNodes += @{NodeName = $_.Name; Role= $_.tags.role}
}

Obviously, it does mean I have publish and compile each MOF for each deployment. I guess I could live with that, but and it’s a big but…

…I use Joe Levy’s UpdateLCMForAAPull template to register the VM with the Azure Pull server and apply the configuration. As far as I am aware, only one “NodeConfigurationName” can be specified as this template resource is per VM and the format of the value for NodeConfigurationName is configname.nodename (e.g. myDscConfig.computer1), so the node name is specified in the parameter. Therefore although I can generate multiple MOFs, I can’t apply them using Azure deployment templates. This leaves me with a few options:

  1. See if the template will deploy if NodeConfigurationName is left blank and then use PowerShell to push the configurations to the correct node.
  2. Build a complex template that adds instances of the UpdateLCMForAAPull template resource for each node. This is not for the faint-hearted, compounded by the fact that PowerShell and resource templates interpret JSON in different ways.
  3. Continue using localhost for nodename. One configuration fits all (of X role).

**Actually, looking at Azure Resource Manager, I might be wrong. Configuration.Name seems to accept an array. I’ll have a play and update.

It looks like you are creating a “monolythic” script that would contain all configurations for the project. What if instead, you broke them in to individual configurations in the DSC service and assigned them to nodes using the extension?

I just submitted an example to the quickstarts repo. It hasn’t merged yet but you can see it here:
https://github.com/Azure/azure-quickstart-templates/pull/4063

Hi Michael,

I think this is part of the point I’m trying to make. “Monolythic” configurations are hard to manage, certainly in Azure world. Currently, I have one configuration for each role. If multiple VMs of a given role are requested, I cheat and use PowerShell to loop through, triggering the deployment template that configures DSC.

Every example and document I read seems to suggest DSC is designed to work monolythically.

The “Azure Automation for configuration management” API looks very interesting. Only problem is that I’ll be revisiting work done 5 months ago. Something for version 2.0. :-).

Thanks

W.

Here is an Overview page (the branding is currently Azure Automation DSC). I am working on updates to the documentation, starting on the DSC language and working my way towards the service. If you come across docs that are confusing, please let me know so I can move them to the top of the list.

With a composable model, it is becoming easier to share configurations:

https://www.powershellgallery.com/packages/DomainControllerConfig/0.2.0/DisplayScript

As a tangent - here is a 15 minute demo walking through all of the new operations management capabilities end to end. There’s a LOT more here than what you asked about but you might be interested.

Hi Michael,

Thank you for the above.

I’m not sure if this is what you mean by “let you know if documentation is confusing”, but the article:
https://docs.microsoft.com/en-us/azure/virtual-machines/windows/extensions-dsc-template
suggests a different template schema to the one you’ve used in your quickstart examples.

For instance, you’re using protectedSettings.Items.registrationKeyPrivate. The link seems to suggest it’s now protectedSetting.ConfigurationArguments.

Probably wrong and I saw the note suggesting the contents “automatically adapts”.

I think one of the most confusing aspects of Azure DSC documentation is whether we are pushing or pulling the configuration: As I understand it, pulling is where the configuration is uploaded to an Azure automation account, the node is registered with the Automation Account and the pre-uploaded and compiled DSC configuration is applied.

Joe Levy created this template which I often refer to, however it hasn’t been update since he left MS for the dark(er) side.

In your automation-configuration example, are you pushing or pulling? I can’t work out how you register the node into Automation. I see some clever stuff in provisionConfiguration.json and can only assume it does it here.

Aha! I see (I think). So the provisionsConfiguration template creates the AA, publishes and compiles the DSC into Automation, then the provisionServer template “installs” the extension, thus registering the node in Automation DSC and applies the configuration “domainControllerConfig.localhost” (which was published and compiled using provisionConfiguration).

So Joe Levy’s way is old hat. A hard way of doing the same thing.

BTW, any idea when your PR will be accepted for 101-automation-configuration?

Thanks

W.

Hi Michael,

I notice in your provisionConfiguration.json template you’re referencing nuget packages. Is this a requirement or can I reference the .ZIP file as I currently do, where all the required DSC resources end up on the target node - magic by the Azure DSC extension I presume?

Thanks

W.

Hi Michael,

If you’re watching this, please comment on issues I’ve raised on your “automation-configuration” template:

https://github.com/Azure/azure-quickstart-templates/issues/4251

Thanks

W.

Ack - replied in repo