Deploying To More Than One Azure Subscription With Pulumi

If you are deploying Azure resources using an Infrastructure as Code solution, you will often deploy these resources to a single Azure subscription. However, it is not uncommon to deploy most of your resources in one subscription but deploy some things to another subscription. Some common examples include:

  • Splitting resources between a core subscription and a client-specific subscription
  • Deploying to shared resources like DNS Zone, virtual network etc. that are in a shared subscription
  • Having different layers of resources controlled by different teams, in different subscriptions
  • Retrieving data from other subscriptions, such as Key Vault secrets

Achieving this through Infrastructure as Code is possible but not always straightforward. In this article, we will have a look at how to accomplish this using Pulumi. You can see how to do this in Terraform in a previous article.

Pulumi Azure Provider

When you create a new Pulumi Azure project, it will use the default Azure provider and deploy resources into either whatever subscription you have selected in the CLI when you run it or explicitly set the subscription ID using an environment variable it will use that. You don’t need to create a provider object for this; Pulumi does it for you.

If we want to deploy some resources to a second subscription, we need to create a new Pulumi Azure provider in our code that provides details of that subscription. Once we do that, we can then use that provider for our specific resources.

In this example, we will use the Pulumi Azure Native provider; you can take a similar approach in the older Azure provider. This example is in C#, but the same principle applies to other languages.

     var sub2Provider = new Pulumi.AzureNative.Provider("sub2Provider", new Pulumi.AzureNative.ProviderArgs()
        {
            SubscriptionId = "8878cdfb-ece5-464f-820b-d40829877899"
        });

We are creating a new Azure Native provider and setting the Subscription ID to the second subscription we want to use. In this scenario, we assume that the account we are running our deployment as has the right to deploy to both subscriptions. We will look at a different option for this shortly.

Now we have created the provider, we then need to use this for any resources we want to deploy to that subscription. To do this, we use the “CustomResourceOptions” section to set this.

 var sub2Rg = new Pulumi.AzureNative.Resources.ResourceGroup("sub2Rg", new ResourceGroupArgs()
        {
            ResourceGroupName = "sub2ResourceGoup"

        }, new CustomResourceOptions(){Provider =  sub2Provider});

CustomResourceOptions allows you to specify several different options for your resources. We are only interested in the provider option, pointing it at the provider we created earlier. All the resources that are to be deployed to the first sub don’t have a provider set and so will use the default one.

We can then deploy this, and resources will get deployed to the correct subscription.

Additional Provider Properties

The above example provides only the minimum required option for the provider, which is the subscription ID. As mentioned, this uses the same credentials that the rest of your Pulumi deployment is using. If you need to provide additional credentials for deploying to the second subscription, you can do so with additional properties in the provider.

The example below retrieves a service principal username and password from the stack configuration file. If you’re doing this, be sure to add the password to the config as a secret so that it is encrypted.

        var config = new Config("demo");

        var sub2Provider = new Pulumi.AzureNative.Provider("sub2Provider", new Pulumi.AzureNative.ProviderArgs()
        {
            SubscriptionId = "8878cdfb-ece5-464f-820b-d40829877899",
            ClientId = config.Require("servicePrincipalId")
            ClientSecret = config.RequireSecret("servicePrincipalSecret")

            
        });

Alternatively if you don’t want to store this data in your config, you could instead use Managed Service Identity to authenticate:

        var sub2Provider = new Pulumi.AzureNative.Provider("sub2Provider", new Pulumi.AzureNative.ProviderArgs()
        {
            SubscriptionId = "8878cdfb-ece5-464f-820b-d40829877899",
            UseMsi = true
        });

Providers Everywhere

Using this approach, you can create as many providers as you wish and deploy to as many different subscriptions as you want. That said, if you find yourself deploying to lots of additional subscriptions in a single deployment, then you might want to review your code to make sure it really belongs in a single deployment or might be better broken up into separate stages.