Enable AKS Flux Extension with Infrastructure as Code

Flux is a solution for doing GitOps on your AKS cluster. You can configure your cluster configuration in Git and have it sync with your cluster and set up all your resources. If you want an intro to GitOps this is a great primer.

Microsoft recently announced support for Flux v2 on AKS using the new Flux GitOps extension. Using this extension, you can enable Flux, deploy the required Flux resources and configure your GitOps sources and get your cluster syncing with Git. The documentation for this is pretty good if you want to enable the extension using the Azure CLI, but if you’re creating your AKS cluster using Infrastructure as Code (which you should be!) then it’s not very well documented at all, and if you don’t know where to look it can be challenging to figure out how to do it. This article will show you how to get this set up in your IaC tool of choice.

Using IaC is a bit more complicated than with the CLI, as there are two components you need to deploy:

  • The extension, which setups up the Flux controllers and deploys the required containers
  • The flux configuration, which hooks up Flux to your Git repository

The CLI commands bundle these all up together, but for IaC, we need to define them separately.

Pre-Requisites

The pre-requisites for this are similar to those defined in the docs.

Register the AKS extension feature on your subscription:

az feature register --namespace Microsoft.ContainerService --name AKS-ExtensionManager

Register the required resource providers:

az provider register --namespace Microsoft.Kubernetes
az provider register --namespace Microsoft.ContainerService
az provider register --namespace Microsoft.Kubernetes configuration

Enable the Flux Extension

We’re now ready to add the Flux extension to the cluster, setting up the flux controllers. Extensions are a relatively new feature for AKS, coming from Azure ARC. They look to be replacing AKS add-ons and are separate sub-resource types rather than being a part of the top-level AKS resource. The code below will enable the extension on your cluster. I’m using an existing cluster to apply the extension in the examples below. You can, of course, deploy the AKS cluster as part of the same code.

The Flux components will get deployed to the “flux-system” namespace in this example. The scope must be set to cluster; currently, namespace scoped Flux is not supported.

Once the extension is installed, you should see a Flux-System namespace created and the Flux pods running inside it.

Bicep

resource aks 'Microsoft.ContainerService/managedClusters@2021-10-01' existing = {
  name: 'akscluster001'
}

resource flux 'Microsoft.KubernetesConfiguration/extensions@2021-09-01' = {
  name: 'flux'
  scope: aks
  properties: {
    extensionType: 'microsoft.flux'
    scope: {
      cluster: {
        releaseNamespace: 'flux-system'
      }
    }
    autoUpgradeMinorVersion: true
  }
}

Pulumi

The example below is in C#. This can be transformed to other languages using the documentation here.

var fluxExtension = new Pulumi.AzureNative.KubernetesConfiguration.V20210901.Extension("fluxExtension", new Pulumi.AzureNative.KubernetesConfiguration.V20210901.ExtensionArgs
{
    ClusterName = aks.Name,
    ClusterRp = "Microsoft.ContainerService",
    ExtensionType = "microsoft.flux",
    Scope = new Pulumi.AzureNative.KubernetesConfiguration.V20210901.Inputs.ScopeArgs()
    {
        Cluster = new Pulumi.AzureNative.KubernetesConfiguration.V20210901.Inputs.ScopeClusterArgs()
        {
            ReleaseNamespace = "flux-system"
        }
    },
    AutoUpgradeMinorVersion = true,
    ClusterResourceName = "managedClusters",
    ExtensionName = "flux",
    ResourceGroupName = rgName

});

Terraform

There was no Terraform resource for defining AKS extensions at the time of writing.

Flux Configuration

Once the extension is deployed, we can now create a Flux Configuration. The Flux configuration links Flux to your Git repository and defines:

  • The git repo you want to use
  • The branch you want to use
  • The root Customization objects you want to run, which will then deploy the rest of your workloads

You can define multiple Flux Configurations, as each one will be tied to a specific Git repo, so if you have multiple repositories, you will have multiple configurations.

The examples below connect to public Git repositories. We will look at authenticated repositories in a moment. Once deployed, you should see the resources being deployed. If you install the Flux CLI, you can view the status of these resources with the command.

flux get all -n flux-system

Bicep

resource fluxConfig 'Microsoft.KubernetesConfiguration/fluxConfigurations@2021-11-01-preview' = {
  name: 'gitops-demo'
  scope: aks
  dependsOn: [
    flux
  ]
  properties: {
    scope: 'cluster'
    namespace: 'gitops-demo'
    sourceKind: 'GitRepository'
    suspend: false
    gitRepository: {
      url: 'https://github.com/fluxcd/flux2-kustomize-helm-example'
      timeoutInSeconds: 600
      syncIntervalInSeconds: 600
      repositoryRef: {
        branch: 'main'
      }

    }
    kustomizations: {
      infra: {
        path: './infrastructure'
        dependsOn: []
        timeoutInSeconds: 600
        syncIntervalInSeconds: 600
        validation: 'none'
        prune: true
      }
      apps: {
        path: './apps/staging'
        dependsOn: [
          {
            kustomizationName: 'infra'
          }
        ]
        timeoutInSeconds: 600
        syncIntervalInSeconds: 600
        retryIntervalInSeconds: 600
        prune: true
      }
    }
  }
}

Pulumi

  var flux = new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.FluxConfiguration("flux", new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.FluxConfigurationArgs
    {
        ClusterName = aks.Name,
        ClusterResourceName = "managedClusters",
        ClusterRp = "Microsoft.ContainerService",
        ResourceGroupName = rgName,
        Namespace = "flux-system",
        Scope = Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.ScopeType.Cluster,
        FluxConfigurationName = "gitops-demo",
        GitRepository = new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.Inputs.GitRepositoryDefinitionArgs()
        {
            Url = "https://github.com/fluxcd/flux2-kustomize-helm-example",
            RepositoryRef = new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.Inputs.RepositoryRefDefinitionArgs
            {
                Branch = "main",
            },
            SyncIntervalInSeconds = 200,
            TimeoutInSeconds = 600,
        },
        Customizations =
        {
            { "infra", new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.Inputs.KustomizationDefinitionArgs
            {
                    DependsOn = {}},
                Path = "./infra",
                SyncIntervalInSeconds = 600,
                TimeoutInSeconds = 600,
                Prune = true
            } },
            { "apps", new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.Inputs.KustomizationDefinitionArgs
            {
                DependsOn = {new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.Inputs.DependsOnDefinitionArgs(){
                KustomizationName = "infra"
                }},
                Path = "./apps/staging",
                SyncIntervalInSeconds = 600,
                TimeoutInSeconds = 600,
                Prune = true
            } }
        }
    }, new CustomResourceOptions() { DependsOn = {fluxExtension}});


Authentication with Git

If you are using a private Git repo, you will need to authenticate with it. Using the CLI, there are some HTTP auth options; these don’t exist in the IaC option. Instead, we can use the ConfigurationProtectedSettings option to pass in some settings that will be converted into a Kubernetes secret called <configuration name>-protected-settings so, in our example, the secret is called gitops-demo-protected-parameters. We can then use the LocalAuthRef option to refer to that secret for auth.

For GitHub or Azure DevOps, you can generate a PAT token and specify this as the password in your secret, the username is not important, but I usually use my account name. The values you define in the template need to be bas64 encoded.

Bicep

resource fluxConfig 'Microsoft.KubernetesConfiguration/fluxConfigurations@2021-11-01-preview' = {
  name: 'gitops-demo'
  scope: aks
  dependsOn: [
    flux
  ]
  properties: {
    scope: 'cluster'
    namespace: 'srs-namespace'
    sourceKind: 'GitRepository'
    suspend: false
    gitRepository: {
      url: 'https://github.com/fluxcd/flux2-kustomize-helm-example'
      timeoutInSeconds: 600
      syncIntervalInSeconds: 600
      LocalAuthRef: 'gitops-demo-protected-parameters'
      repositoryRef: {
        branch: 'main'
      }

    }
    ConfigurationProtectedSetting: {
      username: '<Base 64 encoded username>'
      password: '<Base 64 encoded PAT token'
    }
    customizations: {
    ...

Pulumi

  var flux = new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.FluxConfiguration("flux", new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.FluxConfigurationArgs
            {
                ClusterName = aks.Name,
                ClusterResourceName = "managedClusters",
                ClusterRp = "Microsoft.ContainerService",
                ResourceGroupName = rgName,
                Namespace = "flux-system",
                Scope = Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.ScopeType.Cluster,
                FluxConfigurationName = "gitops-demo",
                GitRepository = new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.Inputs.GitRepositoryDefinitionArgs()
                {
                    Url = "https://github.com/fluxcd/flux2-kustomize-helm-example",
                    RepositoryRef = new Pulumi.AzureNative.KubernetesConfiguration.V20220101Preview.Inputs.RepositoryRefDefinitionArgs
                    {
                        Branch = "main",

                    },
                    SyncIntervalInSeconds = 200,
                    TimeoutInSeconds = 600,
                    LocalAuthRef = "gitops-demo-protected-parameters"

                },
                ConfigurationProtectedSettings =
                {
                    {"username", "<Base 64 encoded username>" },
                    {"password", "Base 64 encoded PAT token>" }
                },

                Customizations =
            {
             ...