Deploy multiple instances of Azure Cloud Service

Hi.

We have a multi-tenant SaaS application that consists of about 10 cloud services per environment. We then have multiple environments (prod, preprod, test, etc.) plus each developer might have their own environment in their own Azure subscription.

We had an intricate variable configuration in Octopus 2.6 to cover all these cloud services and the deployment process was pretty much 10 deploy cloud service steps, each with a different variable for the cloud service. I was looking forward to Octopus 3.0 because my understanding was that we could clean this up a little using cloud services as deployment targets and then we would simply select a role for each step and the environment and role would define the cloud service.

The other benefit I saw in this was that one of our cloud services provides customer-specific communications and needs to be deployed multiple times. When I say multiple times, I don’t mean multiple instances of the role. I actually mean that it gets deployed to multiple cloud services, one per customer that needs it. Despite being the same code, each of these cloud services will be in a customer-specific virtual network. My understanding of the Octopus 3.0 design was that I could assign multiple cloud services the same role and they would all have the same thing deployed to them, just as would happen with standard Tentacles.

The problem is that now, since Octopus 3.1 has for the most part gone back to the 2.6 way, I’m stuck using variables again. I thought the deployment targets and accounts made sense because I could make proper use of environments, which is a big part of my choice of using Octopus in the first place. Now that deployment targets are deprecated, accounts makes little sense to me as it seems to be at odds with environments. If I set up a deploy step and link it to an account, then I can’t promote it to another environment.

My big problem right now is that I’m not sure how to deploy my special cloud service multiple times. The only thing I can think of is to add a step to the process for each customer it needs to be deployed for. However, that means we’re now mixing the process with the configuration, which also seems to go against Octopus’s intentions. It also means I’d have to update the process and recreate releases each time a new customer comes on board. Surely there has to be a better way.

Could you please help me find a way to achieve my deployment requirements? If that involves reinstating cloud services as deployment targets for those that want it (I don’t see why you can’t offer both options) then that would be even better!

Cheers,
Richard

Hi Richard,

Thanks for your detailed question. I understand that the 3.0 way seems to a more elegant solution for your situation. I agree. This was a tough decision for us. We’ve had similar feedback from a few customers, and at this point I believe it’s possible we’ll keep both methods (possibly merging them slightly). We certainly haven’t committed to a deadline for removing the 3.0 targets.

All of which is to say, for right now, if the the 3.0 targets are working well for you then I would recommend you continue using them. You seem to understand well the alternatives.

The only suggestion I would make if you did move to the 3.1 steps: rather than using multiple steps, would be to leverage variables wherever possible. By using consistent naming conventions for your Azure resources (Cloud Services, Storage Accounts, etc), and scoping variables to environments, I believe in many cases you can avoid creating multiple steps (as a trivial example: MyCloudService-#{Octopus.Environment.Name}).

Thanks again for the feedback.

Regards,
Michael R

Hi Michael,

I haven’t actually set up the 3.0 targets yet due to the big deprecation warning on that page. I don’t particularly want to go to the effort of setting it all up, only for it to be removed in the future. However, I’m still not entirely sure how I would do this properly in the 3.1 model.

I think you might have misunderstood me when I referred to multiple steps. Consider this example. I have 4 cloud service packages to deploy and the third one needs to go to two cloud services.

Packages Cloud Services
P1 CS1a
P2 CS2a
P3 CS3a & CS3b
P4 CS4a

I already use variables for the cloud service names and was able to have 4 steps to deploy all packages to CS*a. However, now that I also need to deploy P3 to CS3b, is there a way to do that without requiring a duplicate step in the process?

Richard

Hi Michael,

I thought I’d create all the 3.0 targets to give it a try but I think I must be missing something. I’ve created Cloud Service deployment targets for my cloud services. I’m now trying to change the process to point to the roles referenced by those deployment targets. However, the Deploy an Azure Cloud Service step requires me to select an account, cloud service and storage account and doesn’t have the option to select a role. What am I missing?

Richard

Richard,

To answer your second question first:

the Deploy an Azure Cloud Service step requires me to select an account, cloud service and storage account and doesn’t have the option to select a role.

If you are using the 3.0 Azure targets, you must use the regular Deploy a NuGet package step type, not the Deploy an Azure Cloud Service step type. I apologize, I realize this is confusing. This is what I was referring to when I said we may merge the approaches.

Basically:
Option 1 (3.0 way): Azure Subscription Account + Azure targets (Web App or Cloud Service) + Deploy a NuGet package step
Option 2 (3.1 way): Deploy an Azure Cloud Service step

For your first question:

is there a way to do that without requiring a duplicate step in the process?

No, you will need to create a separate step to deploy another package.

I hope this helps. If you have any further questions, don’t hesitate to ask.

Regards,
Michael

Hi Michael,

I’ve created the process using the Deploy a NuGet package step type and it works really well. The most important thing for us is that we can deploy the same package to multiple cloud services simply by assigning the same role to multiple deployment targets. This is a massive win for us so I’m really hoping you don’t remove the 3.0 targets from Octopus. (If anything I think it would be better to keep the Azure deployment targets and allow variables to be used when specifying the cloud service name rather than having different step types for cloud services.) But either way, we will be relying on these Azure deployment targets and they work exactly as I would expect, just like normal Tentacles and that’s fantastic. Please don’t remove them.

My current problem is that I’m trying to make some changes to the .cscfg file in a pre-deployment script. I’ve got script modules that load the file and make the changes and then I call those script modules from the pre-deployment script in each step that deploys to a cloud service. How do I get the path of the .cscfg file? Is there a variable I can use or should I just look in the current path for the file name I want. Also, should I do it in the pre-deployment script or the deployment script?

Richard

Hi Michael,

I’ve hit another problem. We have a lot of variables that contain other variables. If I use a variable in a PowerShell script and that variable contains another variable, it doesn’t seem to resolve the other variable and simply outputs #{NestedVariable}. Is there a way to get the actual resolved value?

Richard

Richard,

Once Octopus locates your .cscfg file, it stores the path in a variable: #{OctopusAzureConfigurationFile}. You should be able to use this in your script. In your situation, I don’t believe it will make a difference whether you run your script on pre-deploy or deploy.

In case you’re interested, Calamari is open-source. The command responsible for deploying cloud-services is located here. This can be helpful if you want to see what executes when.

Regarding nested variables, the value of a variable can certainly contain another variables. But unfortunately you cannot set the value of the nested variable and use the outer variable in the same PowerShell script. Is this your scenario? There are a couple of useful variables you can set to help debug variable issues.

Regards,
Michael

Hi Michael,

The #{OctopusAzureConfigurationFile} variable is what I was after. Thanks.

As for nested variables, I’m not referring to setting variables in the script but rather simply evaluating pre-existing nested variables. In other words, Octopus doesn’t resolve the variables inside other variables when you use it in PowerShell. However, it does for config file updates.

Anyway, I wrote the following script module to solve my problem:

function Get-OctopusVariable
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    if ($OctopusParameters.ContainsKey($Name))
    {
        [regex]::Replace($OctopusParameters[$Name], "(?:#{)(.+?)(?:})", {
            param($match)
            Get-OctopusVariable $match.Groups[1]
        })
    }
    else
    {
        "#{$Name}"
    }
}

I’ve got everything else working now so I think we can close this discussion. Just please don’t forget how important and useful the 3.0 targets are to us. It’s pretty much the only way we can achieve what we need so please don’t remove them. :slight_smile: I’m happy to discuss this further with your team if necessary.

Thanks for your help,
Richard.

I’m glad that helped.

Regarding nested variables, could you provide some more information on your scenario? Is it a run-script step, or a script packaged in a .nupkg file, or a pre/deploy/post script configured via the UI? And could you give an example of your variable setup and how you are using them? Nested variables definitely are supported. You shouldn’t have to write your own implementation (though nice work!).

Regarding the Azure deployment-targets, we will definitely consider your scenario. We appreciate the feedback.

Hi Michael,

I’ll try to explain the variables thing a bit better with a (simplified) example.

A project has a few variables:

Name Value Scope
Something.Login Username=#{Something.Login.Username};Password=#{Something.Login.Password}
Something.Login.Username Default
Something.Login.Username Richard My Environment
Something.Login.Password Default
Something.Login.Password P@55w0rd My Environment

We use this pattern a lot so the basic structure of a variable is defined with no scope but then we fill in the blanks with scoped variables. (You’ll also notice the defaults which are provided to prevent issues arising when there is no matching scoped variable.)

In a pre-deployment PowerShell script, I want to get the Something.Login variable (the first one that has the structure and placeholders for the scoped components). So I access it with $OctopusParameters['Something.Login].

Let’s assume this is deploying to the My Environment environment. Unfortunately, my PowerShell script will get Username=#{Something.Login.Username};Password=#{Something.Login.Password} because it doesn’t evaluate the nested variables. Instead, I would expect it to evaluate nested variables and return Username=Richard;Password=P@55w0rd. The Cmdlet I posted above does indeed evaluate the nested variables and return the value I would expect because it recursively looks for Octopus’s variable markers ${ ... } and replaces them.

Cheers,
Richard

Richard,
I just replicated your scenario (same variable names and environment names) and accessing $OctopusParameters['Something.Login'] in My Environment gave the correctly substituted variable.

Something is causing your variables to not resolve. Is it possible you have a line-break in the Something.Login variable value (as it appears in your example)? Or is there possibly a trailing space in one of your variable names? It must be something like that, because in general your scenario is perfectly valid.