This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Deploying PPC

Steps to deploy the PPC cluster

By default, the PCT is configured to deploy a FIPS-enabled cluster using FIPS-compliant node OS images.

Non-FIPS deployment: Before proceeding with the installation, complete the steps in Configuring PPC for Non-FIPS Deployment on Microsoft Azure to modify the required Terraform configuration files. Then proceed with either the automated or component-based installation below.

FIPS deployment: No additional configuration is required. Proceed directly with either the automated or component-based installation below.

The PPC cluster can be deployed using one of the following methods:

Automated script-based installation: This method requires the bootstrap.sh script to deploy the PPC cluster. The script installs the required software and dependencies, prompts to provide the required information, and deploys the cluster automatically.

Manual component-based installation: This method requires to perform the deployment of each component to install the PPC cluster. The configuration files are edited to provide the required information and then deploy the cluster.

Note: A PPC can be deployed in regions where minimum two zones are present.

1 - Production configuration

Review and override these settings before deploying to production for high availability, data safety, and security compliance.

The default terraform.tfvars values are optimized for development and staging environments. Before deploying to production, override the settings below to ensure high availability, data safety, and security compliance.

Applying the changes

  • Script-based installation: After the bootstrap script generates the terraform.tfvars files, edit them before the script runs tofu apply, or modify and re-apply individually using OpenTofu after installation.

  • Component-based installation: Edit the relevant terraform.tfvars for each module before running tofu apply.

Storage redundancy

Modules: 00_backup_storage, 04_aks_addons

By default, the backup storage account uses Locally Redundant Storage (LRS), which stores data only within a single datacenter. When AKS nodes span multiple availability zones, an LRS-backed disk cannot be reattached to a node in a different zone during failover, causing pod scheduling to fail.

Recommendations:

  • Use ZRS (Zone-Redundant Storage) when nodes are spread across availability zones in the same region.
  • Use GRS (Geo-Redundant Storage) if cross-region disaster recovery is required.
  • Use Premium_ZRS as the storage class for Kubernetes persistent volumes.

In 00_backup_storage/terraform.tfvars:

storage_account_replication_type = "ZRS"   # use "GRS" for cross-region redundancy

System node pool sizing

Module: 02_aks_cluster

The default system node pool VM size (Standard_D2ds_v4, 2 vCPU / 8 GiB RAM) can become resource-constrained under production load when Karpenter, CoreDNS, and CSI drivers run concurrently on the same node.

Recommendation: Use Standard_D4ds_v4 (4 vCPU / 16 GiB) or larger for production system node pools.

In 02_aks_cluster/terraform.tfvars, edit the following value:

system_node_pool_vm_size = "Standard_D4ds_v4"   # or Standard_D8ds_v4 for heavier loads

Key Vault SKU

Module: 00_backup_storage

The default Key Vault SKU is standard, which uses software-protected keys. For organizations with compliance requirements (such as FIPS 140-2 Level 2 or Level 3), the premium SKU provides HSM-backed encryption keys.

Recommendation: Use premium when your security or compliance policy requires HSM-backed key protection.

In 00_backup_storage/terraform.tfvars:

key_vault_sku = "premium"

Note: Confirm with your compliance team whether HSM-backed keys are required before changing this setting. The premium SKU incurs additional Azure costs.

Availability zones

Modules: 02_aks_cluster, 03_node_pool

The default zone configuration is ["1", "2", "3"]. Not all Azure regions support three availability zones, and not all VM SKUs are available in every zone. Using a zone that does not support your VM SKU causes cluster creation to fail.

Important: system_node_pool_zones (module 02) and node_pool_zones (module 03) must use the same set of zones. If the zones differ, disks provisioned in one zone cannot be attached to nodes rescheduled into another zone.

Before deploying, verify zone and VM SKU availability:

# List availability zones for your region
az account list-locations \
  --query "[?name=='<region>'].{Region:name, Zones:availabilityZoneMappings}" \
  -o table

# Check which zones support your VM SKU
az vm list-skus \
  --location <region> \
  --size Standard_D4ds_v4 \
  --query "[].{Name:name, Zones:locationInfo[0].zones}" \
  -o table

Replace <region> with your Azure region, for example eastus.

In both 02_aks_cluster/terraform.tfvars and 03_node_pool/terraform.tfvars, set matching values:

# 02_aks_cluster/terraform.tfvars
system_node_pool_zones = ["1", "2"]    # adjust to the zones your region supports

# 03_node_pool/terraform.tfvars
node_pool_zones = ["1", "2"]           # must match system_node_pool_zones exactly

AKS cluster tier (sku_tier)

Module: 02_aks_cluster

The sku_tier setting controls the AKS API server uptime SLA, available node capacity, and supported features.

TierUptime SLANotes
FreeNo SLASuitable for development and testing only
Standard99.9% (99.95% with Availability Zones)Recommended for production
Premium99.9% (99.95% with Availability Zones) + LTSRequired if long-term support is needed

Recommendation: Set sku_tier = "Standard" for all production deployments. Use "Premium" if your organization requires long-term support (LTS) guarantees.

In 02_aks_cluster/terraform.tfvars:

sku_tier = "Standard"

Note: Standard and Premium tiers incur an additional hourly cost per cluster. See the AKS pricing page for current rates.

Storage account network access

Module: 00_backup_storage

The backup storage account is created with public network access enabled by default. In production, restrict access using your organization’s existing network controls.

Azure provides three options:

MethodIsolation levelTypical use
Private EndpointsAssigns the storage account a private IP inside your VNet. Removes it from public internet.Recommended for most enterprise deployments.
VNet service endpointsRoutes traffic through the VNet without a private IP.Lower cost alternative to Private Endpoints.
Azure Firewall / NSGNetwork-layer traffic filtering.Supplement to either option above.

Apply the appropriate controls after deployment. The example below uses the service endpoint approach:

# Allow access from a specific VNet subnet
az storage account network-rule add \
  --account-name <storage-account-name> \
  --resource-group <resource-group> \
  --vnet-name <vnet-name> \
  --subnet <subnet-name>

# Deny all other public access
az storage account update \
  --name <storage-account-name> \
  --resource-group <resource-group> \
  --default-action Deny

Note: Consult your organization’s security team to select the appropriate access control method before deploying to production.

SettingDefaultProduction recommendationModule
storage_account_replication_typeLRSZRS or GRS00_backup_storage
system_node_pool_vm_sizeStandard_D2ds_v4Standard_D4ds_v4 or larger02_aks_cluster
key_vault_skustandardpremium (if HSM required)00_backup_storage
system_node_pool_zones["1","2","3"]Verify per region/SKU; match node_pool_zones02_aks_cluster
node_pool_zones["1","2","3"]Must match system_node_pool_zones03_node_pool
sku_tierFreeStandard or Premium02_aks_cluster
Storage account network accessPublic (default)Private Endpoints, VNet service endpoints, or FirewallPost-deploy

2 - Configuring PPC for Non-FIPS Deployment on Microsoft Azure

Complete the steps provided in this section to configure the Protegrity Cluster Template (PCT) for a non-FIPS PPC deployment on Azure AKS.

Before you begin

The following files require changes:

FileSettingValue
deployment/iac/azure/cluster/modules/02_aks_cluster/terraform.tfvarsfips_enabledfalse
deployment/iac/azure/cluster/modules/03_node_pool/terraform.tfvarsfips_enabledfalse

Configuring PPC for Non-FIPS Deployment on Azure

For a non-FIPS PPC deployment on Azure AKS, perform the following steps:

  1. Disable FIPS in the AKS Cluster Module.

a. Navigate to the 02_aks_cluster module.

cd iac/azure/cluster/modules/02_aks_cluster/

b. Edit the terraform.tfvars file. Locate the fips_enabled parameter and set it to false.

fips_enabled                  = false

c. Save the file and exit the editor.

  1. Disable FIPS in the Node Pool Module.

a. Navigate to the 03_node_pool module.

cd iac/azure/cluster/modules/03_node_pool/

b. Edit the terraform.tfvars file. Locate the fips_enabled parameter and set it to false.

fips_enabled                  = false

c. Save the file and exit the editor.

After saving all these configuration changes, proceed with deploying the cluster using one of the following methods:

Note: The Script-based and Component-based installation instructions reference default FIPS-enabled settings. When following either installation method, do not revert or overwrite the values configured on this page. Keep fips_enabled set to false in 02_aks_cluster and 03_node_pool.

3 - Automated script-based installation

Complete the steps provided in this section to deploy PPC in Microsoft Azure AKS.

Before you begin

  • Ensure that all the required resources are in the same resource-group. If the resources are in different resource groups, then ensure to move the resources to the same group before proceeding for PPC deployment.

  • The repository provides a bootstrap script that automatically installs or updates the following tools on the jump box:

    • Azure CLI - Required to communicate with your Microsoft Azure account.
    • Helm - Required to manage Kubernetes packages.
    • jq - Required to parse JSON.
    • kubectl - Required to communicate with the Kubernetes cluster.
    • OpenTofu - Required to manage infrastructure as code.
    • oras: Required to pull non‑container, generic OCI artifacts from the registry that are not handled by standard container tooling.

Note: If you are performing a non-FIPS deployment, ensure that you have already completed the steps in the section Configuring PPC for Non-FIPS Deployment on Microsoft Azure before proceeding. Do not revert fips_enabled to true or change node_os back to a FIPS-compliant image during the steps below.

Architecture Selection

The step [1b] Select Node Architecture in the bootstrap script prompts to select the node architecture interactively during deployment.

  • amd64 (x86-64)
Node PoolVM SizevCPUsMemory
SystemStandard_D2as_v528 GB
UserStandard_D8as_v5832 GB
  • arm64
Node PoolVM SizevCPUsMemory
SystemStandard_D2ps_v528 GB
UserStandard_D8ps_v5832 GB

The bootstrap script prompts you to select the node architecture interactively during deployment (step 1b).

Deploying the PPC

The bootstrap script prompts for variables to be set to complete the deployment. Run the following command and follow the instructions on the screen.

./bootstrap.sh --cloud azure

The script prompts for the following variables.

  1. Enter AKS Cluster Name

    The following characters are allowed:

    • Lowercase letters: a-z
    • Numbers: 0-9
    • Hyphens: -

    The following characters are not allowed:

    • Uppercase letters: A-Z
    • Underscores: _
    • Spaces
    • Any special characters such as: / ? * + % ! @ # $ ^ & ( ) = [ ] { } : ; , .
    • Leading or trailing hyphens
    • Names longer than 10 characters (the cluster name must be 10 characters or fewer)

    Note: Ensure that the cluster name does not exceed 10 characters. Cluster names longer than this limit can cause the bootstrap script to fail in subsequent installation steps. If the installation fails because the cluster name exceeds the 10-character limit, correct the name and re-run the script.

    • Correction: Choose a cluster name with 31 characters or fewer.
    • Retry: Execute the installation command again with the updated name. The script will automatically handle the update and proceed with the bootstrap process.

    After the cluster name is accepted, the script prompts you to select the node architecture.

    [1b] Select Node Architecture

    [1b] Select Node Architecture
      1) amd64 (x86_64) — e.g. Standard_D8as_v5
      2) arm64 (ARM/Ampere) — e.g. Standard_D8ps_v5
    

    Enter 1 to select amd64 or 2 to select arm64.

    Note: Select amd64 for standard Intel/AMD 64-bit environments. Select arm64 for ARM-based environments such as AWS Graviton instances.

  2. Querying for available Resource Groups

    The script queries for the available Resource Groups.

    Enter the Resource Group name from the table []

    The script then automatically detects the location and subscription ID of the resource group.

  3. Enter UAMI Resource ID

    Provide the complete Azure resource ID for the UAMI used by AKS in the following format:

    /subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<identity-name>
    

    The UAMI client ID is detected automatically.

  4. Enter AKS Subnet Resource ID

    Provide the complete resource ID of the pre-existing subnet used for AKS nodes in the following format:

    /subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Network/virtualNetworks/<vnet-name>/subnets/<subnet-name>
    
  5. Enter Private DNS Zone Resource ID

    Provide the Private DNS zone ID used by the AKS private cluster in the following format:

    /subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Network/privateDnsZones/privatelink.<region>.azmk8s.io
    

    The script attempts to automatically detect network settings:

    • Virtual network address space
    • Service CIDR
    • DNS service IP

    If the detection fails, then default values configured in the terraform.tfvars file are used.

  1. Enter FQDN [name.domain.com]

    This is the Fully Qualified Domain Name for the ingress.

    Warning: Ensure that the FQDN does not exceed 50 characters and only the following characters are used:

    • Lowercase letters: a-z
    • Numbers: 0-9
    • Special characters: - .
  2. Storage Account and Key Vault provisioning

    Choose whether to use existing resources or create new resources:

    1) Use existing
    2) Provision new via Tofu
    
    • Enter 1 if an encrypted Storage Account and Key Vault are already provisioned for this cluster. The installer prompts for the Storage Account name, Key Vault name, backup container, Key Vault key name, and the Velero UAMI Resource ID.

    • Enter 2 to allow the installer to create a new Storage Account and Key Vault with the velero container, the pty-backup-key encryption key, and a Velero UAMI automatically. Only the new resource names are required.

  3. Enter Velero UAMI Resource ID (required)

    Enter the resource ID of the dedicated Velero User-Assigned Managed Identity (UAMI) in the format:

    /subscriptions/<subscription-id>/resourceGroups/<velero-resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<name>
    

    The script automatically validates the UAMI and detects the UAMI Client ID.

  4. Enter OpenSearch UAMI Resource ID (required)

    Enter the resource ID of the OpenSearch Velero User-Assigned Managed Identity (UAMI) in the format:

    /subscriptions/<subscription-id>/resourceGroups/<opensearch-resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<name>
    

    The script automatically validates the UAMI and detects the UAMI Client ID.

  5. Enter Image Registry Endpoint

The image repository from where the container images are retrieved.

Expected format: <hostname>[:port].

Do not include ‘https://’

  1. Enter Registry Username

Enter the username for the registry mentioned in the previous step. Leave this entry blank if the registry does not require authentication.

  1. Enter Registry Password or Access Token

Enter Password or Access Token for the registry.

Input is masked with * characters. Press Enter to keep the current value.

Leave this entry blank if the registry does not require authentication.

  1. Enter Owner Email (for cluster tagging)

Enter email address in the format name@domain.tld. This email is used for the cluster ‘Owner’ tag and the platform owner contact.

Note: The cluster creation process can take 10-15 minutes.

If the session is terminated during installation due to network issues, power outage, and so on, then the installation stops. To restart the installation, run the script again:

# Navigate to setup directory 
./bootstrap.sh --cloud azure

After the bootstrap script is completed, verify the cluster and workloads using the following commands:

# Confirm nodes are Ready
kubectl get nodes

# Confirm NFA workloads are Running
kubectl get pods -A

4 - Manual component-based installation

Complete the steps provided in this section to update the configuration files and deploy PPC in Microsoft Azure AKS.

This section is intended for enterprise customers who need full control over each stage of the AKS cluster deployment. Use this path when individual infrastructure stages must be reviewed or approved separately, when integration with an existing CI/CD pipeline is required, or when corporate change-management controls prevent running an all-in-one automated script. Each stage is self-contained and can be executed by different teams on the same jump box.

Before deploying to production: The default values are optimized for development and staging. Review the Production configuration section to override settings for high availability, data safety, and security compliance before running tofu apply.

Note: If you are performing a non-FIPS deployment, ensure you have already completed Configuring PPC for Non-FIPS Deployment on Microsoft Azure before proceeding. Do not revert fips_enabled to true when performing the following steps.

Deploying PPC using component-based installation

To install PPC, complete the following steps in order. Each step is described in detail in the sections below.

StepActionDescription
1Installing the toolsInstalls all required CLI tools (OpenTofu, Azure CLI, kubectl, Helm, and others) on the jump box, either automatically via bootstrap.sh install-tools --cloud azure or manually.
2Authenticating with Azure CLIAuthenticates with Azure using az login and verifies access to the target subscription, resource group, managed identities, subnet, and private DNS zone.
3Downloading and extracting the release PCTDownloads and extracts the PPC release archive on the jump box.
4Provisioning backup storage and key vault(Skip if using existing resources) Provisions a new Azure Storage Account, Blob container, Key Vault, and encryption key for storing PPC backup data using the 00_backup_storage Terraform module.
5Filling in each module’s terraform.tfvarsConfigures and applies five Terraform modules in sequence to provision all PPC infrastructure components.
5a01_identity moduleCreates RBAC role assignments for the pre-existing User-Assigned Managed Identities (UAMIs) required by AKS, Velero, and OpenSearch.
5b02_aks_cluster moduleProvisions the private AKS cluster with a system node pool, configures networking (Azure CNI Overlay and Cilium), enables Workload Identity, and creates federated identity credentials for Velero and OpenSearch.
5c03_node_pool moduleCreates a user node pool for application workloads with the specified VM size, architecture, and availability zone configuration.
5d04_aks_addons moduleCreates default and backup Kubernetes storage classes using Azure Managed Disks (Premium LRS) and removes the built-in default storage class annotation.
5e05_helm_releases moduleDeploys all PPC platform Helm charts including Velero, NFA/Eclipse, Insight, Reloader, and Envoy Gateway. This step may take 15–20 minutes.
6Verifying the installationConfirms all cluster nodes are in Ready state and all pods are in Running state.

Before you begin

Ensure to have the following prerequisites before proceeding with the installation.

Access and permissions

RequirementDetails
Azure RBACContributor and User Access Administrator on the target subscription or resource group.
Jump boxDebian or Red Hat VM in Azure with internet access to pull the release archive.

Required Roles

The Azure principal must have AKS cluster admin access.

The required Azure RBAC Roles are listed in the following table:

RoleScopeUsed by
ContributorResource groupCreate AKS cluster, node pool, storage account, Key Vault
User Access AdministratorResource groupCreate role assignments (module 01)
Storage Blob Data ContributorBackup storage accountAccess state files in backend, upload module 00 state
Key Vault AdministratorKey VaultCreate Key Vault and keys (module 00)
AKS Cluster AdminAKS clusterKubernetes API access for Helm, kubectl operations (modules 04, 05)

Deployment Resource Group

All resources created by the installer are provisioned in a single Azure resource group specified using resource_group_name in each terraform.tfvars file. This resource group must exist before running the installer.

The following resources are created inside this resource group.

StageResources Created
00_Backup StorageStorage Account, Key Vault, Key Vault Key, Blob Container
01_IdentityRBAC Role Assignments (references existing UAMIs; no new resources created)
02_AKS ClusterAKS Cluster, Federated Credentials
03_Node PoolAKS User Node Pool
04_AKS AddonsStorage Classes, Cluster Add-ons
05_Helm ReleasesKubernetes Namespaces, Secrets, Helm Charts

Installing the tools

After downloading and extracting the PPC-K8S-ALL_1.1.0.97.tar, run the following command to install the required tools.

./bootstrap.sh install-tools --cloud azure

Alternatively, the following tools must be installed and available on the jumpbox before starting:

Tool NameDescriptionVersion
OpenTofuOpen-source Terraform fork used to provision and manage infrastructureOpenTofu v1.11.5
Azure CLICommand-line tool for managing Azure resources2.88.0
kubectlKubernetes command-line tool for interacting with the clusterv1.36.3
HelmKubernetes package manager for deploying Helm chartsv3.21.3+g1ad6e68
skopeoTool for inspecting and copying container images between registriesskopeo version 1.18.0
jqLightweight command-line JSON processorjq-1.7
bcArbitrary-precision calculator used in shell scriptsbc 1.07.1
ssh-keygenTool for generating SSH key pairs used for secure accessOpenSSH_10.0p2 Debian-7+deb13u4, OpenSSL 3.5.6 7 Apr 2026
orasOCI Registry As Storage client for pushing and pulling artifacts1.3.3
cmctlCommand-line tool for managing cert-manager resourcesv2.5.0

Values to collect before starting

Gather the following values before populating each stage terraform.tfvars in Step 4.

ValueDescriptionExample
Cluster nameName of the AKS cluster to create or manage.my-aks-cluster
Resource groupAzure resource group containing the AKS cluster and related resources.rg-aks-prod
Azure subscription IDUnique identifier of the Azure subscription used for deployment.<subscription-id>
Azure regionAzure region where the cluster and resources are deployed.eastus
AKS UAMI resource IDFull resource ID of the user-assigned managed identity for AKS appliance framework workloads./subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-aks-applianceframework
AKS subnet resource IDFull resource ID of the virtual network subnet where AKS nodes are deployed./subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>
Private DNS zone resource IDFull resource ID of the private DNS zone used for AKS private cluster name resolution./subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/privateDnsZones/privatelink.eastus.azmk8s.io
Storage account nameAzure Storage Account used for Velero backups and Terraform remote state.existing name or provisioned in Step 3
Backup container nameBlob container within the storage account used by Velero for cluster backups.velero
Key Vault nameAzure Key Vault that holds the backup encryption key.existing name or provisioned in Step 3
Key Vault key nameName of the encryption key inside the Key Vault used by Velero.pty-backup-key
Velero UAMI resource IDFull resource ID of the user-assigned managed identity for Velero backup and restore operations./subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-aks-velero
OpenSearch UAMI resource IDFull resource ID of the user-assigned managed identity for OpenSearch workload identity./subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-aks-opensearch
Ingress FQDNFully qualified domain name used by the ingress controller to expose cluster services externally.myapp.example.com
Image registry endpointEndpoint of the container image registry from which cluster images are pulled.registry.example.com
Registry username & passwordCredentials for authenticating with the container image registry.provided by the team

Deploying the PPC

To deploy the PPC using component-based installation, perform the following steps:

1. Authenticating with Azure CLI

To authenticate with the Azure CLI on the jump box, run the following command.

az login
az account show

Ensure the target subscription, resource group, managed identities, subnet, and private DNS zone are accessible before continuing.

2. Downloading and extracting the release PCT

To download and extract the PCT, refer to Preparing for PPC deployment.

3. Provisioning backup storage and key vault

This is an optional step.

Note: Skip this step if an Azure Storage Account, backup container, Key Vault, and key already exist for this cluster. Note these values. They will be needed in next steps.

  1. Navigate to the 00_backup_storage directory.
cd iac/azure/cluster/modules/00_backup_storage
  1. Open terraform.tfvars and fill in the required values.
    resource_group_name    = "<resource-group>"
    aks_cluster_name       = "<cluster-name>"
    storage_account_name   = "<storage-account-name>"
    storage_container_name = "velero"
    key_vault_name         = "<key-vault-name>"
    key_name               = "pty-backup-key"

    azure_subscription_id = "<subscription-id>"
    azure_location        = "<region>"        # e.g. eastus
  1. Save the file and exit the editor.

  2. To apply the terraform configuration, run the following command.

    tofu init
    tofu plan          # review the role assignments that will be created
    tofu apply         # type "yes" when prompted
  1. Note the Storage Account, container, Key Vault, and key values for future steps.

4. Filling in each module’s terraform.tfvars

Each module under iac/azure/cluster/modules/ contains its own terraform.tfvars file. There is no single shared configuration file, so the terraform.tfvars file in every module must be updated before running the deployment.

Verify that the following values are the same across all modules:

  • resource_group_name
  • aks_cluster_name
  • azure_subscription_id
  • storage_account_name
  • storage_container_name
  1. 01_identity module

This stage creates RBAC role assignments for the pre-existing User-Assigned Managed Identities (UAMIs) required by AKS, Velero, and OpenSearch.

ResourcePurpose
Managed Identity Operator roleAllows AKS to assign the UAMI to nodes
Virtual Machine Contributor roleAllows AKS to manage VMs in the node resource group
Disk Snapshot Contributor roleAllows Velero to create and manage disk snapshots
Storage Blob Data Contributor (Velero)Allows Velero to read/write backup blobs
Key Vault Crypto User roleAllows Velero to encrypt/decrypt backups with the Key Vault key
Storage Blob Data Contributor (OpenSearch)Allows OpenSearch to store snapshot data in blob storage

To update the 01_identity module, perform the following steps.

a. Navigate to the iac/azure/cluster/modules/01_identity/ directory.

    cd iac/azure/cluster/modules/01_identity/

b. Edit the terraform.tfvars file to provide the required values.

    resource_group_name    = "<resource-group>" # e.g. "aks-rg-prod"
    aks_cluster_name       = "<cluster-name>" # e.g. "prod-aks-cluster"
    azure_subscription_id  = "<subscription-id>" # e.g. "1e9ef7b6-cdc4-4a6b-98f7-a408ac1e19e0"
    storage_account_name   = "<storage-account-name>" # e.g. "myclusterstateaccount"
    storage_container_name = "velero"

    # -------------------------------------------------
    # Pre-existing managed identities (provided by IT)
    # -------------------------------------------------
    uami_id = "</subscriptions/<subscription-id>/resourceGroups/<it-resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-aks-applianceframework>"

    velero_uami_id = "</subscriptions/<subscription-id>/resourceGroups/<velero-resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-aks-velero>" # required

    opensearch_uami_id = "</subscriptions/<subscription-id>/resourceGroups/<opensearch-resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aks-opensearch-identity>" # required

    # -------------------------------------------------
    # Key Vault (leave empty to skip KV role assignment)
    # -------------------------------------------------
    key_vault_name = "<key-vault-name>" # e.g. "my-key-vault"

c. Save the file and exit the editor.

d. To initialise, plan, and apply the changes, run the following commands.

    tofu init
    tofu plan          # review the role assignments that will be created
    tofu apply         # type "yes" when prompted

e. To verify the resources created in this stage, run the following commands

For AKS UAMI:

# Retrieve the Principal ID of the AKS User-Assigned Managed Identity (UAMI)
az identity show \
  --ids "<uami-resource-id>" \
  --query principalId \
  --output tsv
#Use the returned Principal ID to verify the role assignments:
az role assignment list \
  --assignee "<aks-uami-principal-id>" \
  --output table

For Velero UAMI

# Retrieve the Principal ID of the Velero UAMI
az identity show \
  --ids "<velero-uami-principal-id>" \
  --query principalId \
  --output tsv
#Use the returned Principal ID to verify the role assignments:
az role assignment list \
  --assignee "<velero-uami-principal-id>" \
  --output table

For OpenSearch UAMI

# Retrieve the Principal ID of the OpenSearch UAMI
az identity show \
  --ids "<opensearch-uami-principal-id>" \
  --query principalId \
  --output tsv
#Use the returned Principal ID to verify the role assignments:
az role assignment list \
  --assignee "<opensearch-uami-principal-id>" \
  --output table
  1. 02_aks_cluster module

This stage provisions the private AKS cluster with a system node pool, configures networking (Azure CNI Overlay and Cilium), enables Workload Identity, and creates federated identity credentials for Velero and OpenSearch.

ResourcePurpose
AKS clusterPrivate Kubernetes control plane with OIDC issuer and Workload Identity enabled
System node pool2-node pool for core Kubernetes components (CoreDNS, metrics-server, etc.)
Federated credential (Velero)Allows the Velero service account to authenticate as the Velero UAMI
Federated credential (OpenSearch)Allows the OpenSearch service account to authenticate as the OpenSearch UAMI
Kubeconfig contextAuto-configures kubectl access to the new cluster

To update the 02_aks_cluster module, perform the following steps.

a. Navigate to the iac/azure/cluster/modules/02_aks_cluster/ directory.

    cd iac/azure/cluster/modules/02_aks_cluster/

Note (Non-FIPS deployment): If you are deploying a non-FIPS cluster, ensure fips_enabled remains false as configured in the section Configuring PPC for Non-FIPS Deployment on Microsoft Azure. Do not change this value to true.

b. Edit the terraform.tfvars file to provide the required values.

    resource_group_name    = "<resource-group>" # e.g. "aks-rg-prod"
    aks_cluster_name       = "<cluster-name>" # e.g. "prod-aks-cluster"
    azure_subscription_id  = "<subscription-id>" # e.g. "1e9ef7b6-cdc4-4a6b-98f7-a408ac1e19e0"
    storage_account_name   = "<storage-account-name>" # e.g. "myclusterstateaccount"
    storage_container_name = "velero"

    # -------------------------------------------------
    # Identity (UAMI for kubelet and federated credentials)
    # -------------------------------------------------
    uami_id            = "</subscriptions/<subscription-id>/resourceGroups/<it-resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-aks-applianceframework>" # full UAMI resource ID (same as Stage 01)
    velero_uami_id     = "</subscriptions/<subscription-id>/resourceGroups/<velero-resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-aks-velero>" # full Velero UAMI resource ID (for federated credential)
    opensearch_uami_id = "</subscriptions/<subscription-id>/resourceGroups/<opensearch-resource-group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aks-opensearch-identity>" # full OpenSearch UAMI resource ID (for federated credential)

    # -------------------------------------------------
    # Cluster
    # -------------------------------------------------
    azure_location      = "<region>" # e.g. "eastus"  (auto-detected from resource group)
    subnet_id           = "</subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>>" # e.g. "/subscriptions/.../subnets/snet-aks-applianceframework"
    private_dns_zone_id = "</subscriptions/<subscription-id>/resourceGroups/<dns-resource-group>/providers/Microsoft.Network/privateDnsZones/privatelink.eastus.azmk8s.io>" # e.g. "/subscriptions/.../privateDnsZones/privatelink.eastus.azmk8s.io"

    # -------------------------------------------------
    # Networking (service CIDR + DNS IP)
    # -------------------------------------------------
    service_cidr   = "<10.0.0.0/16>" # auto-derived by updatevartf.sh (non-overlapping with the VNet)
    dns_service_ip = "<10.220.0.10>" # auto-derived: the .10 host of the chosen service_cidr
    pod_cidr       = "<10.0.0.0/16>" # auto-derived: non-overlapping with the VNet and service_cidr

    # -------------------------------------------------
    # System node pool
    # -------------------------------------------------
    architecture                  = "amd64" # amd64 or arm64
    system_node_pool_vm_size      = ""      # leave empty to auto-select based on architecture
    system_node_pool_node_count   = 2
    system_node_pool_disk_size_gb = 50
    system_node_pool_os_sku       = "AzureLinux"
    system_node_pool_zones        = ["1", "2", "3"]
    fips_enabled                  = true # set false for a non-FIPS node OS image (immutable)

    # -------------------------------------------------
    # Ownership / tagging
    # -------------------------------------------------
    owner_email                   = "<name@domain.tld>" # optional; blank = auto-detect from the Azure CLI identity

c. Save the file and exit the editor.

d. To initialise, plan, and apply the changes, run the following commands.

    tofu init
    tofu plan          # review the role assignments that will be created
    tofu apply         # type "yes" when prompted

e. To verify the resources created in this stage, run the following commands.

# Confirm the AKS cluster is in Succeeded state
az aks show --name <cluster-name> --resource-group <resource-group> \
  --query "{Name:name, State:provisioningState, Version:kubernetesVersion, Fqdn:privateFqdn}" \
  --output table

A sample output is displayed below:

Name              State      Version    Fqdn
----------------  ---------  ---------  -----------------------------------------------
<cluster-name>    Succeeded  1.35       <cluster-name>.privatelink.<region>.azmk8s.io
# Verify kubectl can reach the cluster
kubectl get nodes    # expect 2 system nodes in Ready state
# Verify federated credentials exist
az identity federated-credential list \
  --identity-name <velero-uami-name> \
  --resource-group <velero-uami-resource-group> --output table
az identity federated-credential list \
  --identity-name <opensearch-uami-name> \
  --resource-group <opensearch-uami-resource-group> --output table
  1. 03_node_pool module

This stage creates a user-managed node pool for application workloads. Node Auto Provisioning (NAP) handles autoscaling; no cluster autoscaler is configured on this pool.

ResourcePurpose
User node pool (userpool)Worker nodes for NFA platform, Insight, and other application pods

To update the 03_node_pool module, perform the following steps.

a. Navigate to the iac/azure/cluster/modules/03_node_pool/ directory.

    cd iac/azure/cluster/modules/03_node_pool/

Note (Non-FIPS deployment): If you are deploying a non-FIPS cluster, ensure fips_enabled remains false as configured in Configuring PPC for Non-FIPS Deployment on Microsoft Azure. Do not change this value to true.

b. Edit the terraform.tfvars file to provide the required values.

    resource_group_name    = "<resource-group>" # e.g. "aks-rg-prod"
    aks_cluster_name       = "<cluster-name>" # e.g. "prod-aks-cluster"
    azure_subscription_id  = "<subscription-id>" # e.g. "1e9ef7b6-cdc4-4a6b-98f7-a408ac1e19e0"
    storage_account_name   = "<storage-account-name>" # e.g. "myclusterstateaccount"
    storage_container_name = "velero"

    # -------------------------------------------------
    # Node pool
    # -------------------------------------------------
    subnet_id              = "</subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>>"      # same subnet as AKS nodes (Stage 02)
    architecture           = "amd64" # amd64 or arm64
    vm_size                = ""      # leave empty for architecture default
    node_pool_disk_size_gb = 120
    node_pool_desired_size = 3
    node_pool_zones        = ["1", "2", "3"]
    fips_enabled           = true # set false for a non-FIPS node OS image (immutable)

c. Save the file and exit the editor.

d. To initialise, plan, and apply the changes, run the following commands.

    tofu init
    tofu plan          # review the role assignments that will be created
    tofu apply         # type "yes" when prompted

Expected time for this stage is approximately 5 to 10 minutes for node pool provisioning.

e. To verify the resources created in this stage, run the following commands.

# Confirm the user node pool exists and is Succeeded
az aks nodepool show --cluster-name <cluster-name> \
  --resource-group <resource-group> --name userpool \
  --query "{Name:name, State:provisioningState, Count:count, VmSize:vmSize, OsSku:osSku}" \
  --output table

A sample output is displayed below:

Name      State      Count    VmSize      OsSku
--------  ---------  -------  ----------  ----------
userpool  Succeeded  <count>  <vm-size>   AzureLinux
# Verify all nodes are Ready (system + user nodes)
kubectl get nodes -o wide
# Confirm user pool nodes have the expected label
kubectl get nodes -l agentpool=userpool
  1. 04_aks_addons module

This stage creates the default and backup Kubernetes storage classes using Azure Managed Disks (Premium LRS) and removes the built-in default storage class annotation.

ResourcePurpose
ebs-sc storage classDefault storage class (Premium LRS, WaitForFirstConsumer, expandable)
ebs-bkp storage classBackup storage class used by Velero for PVC snapshots
Remove built-in default SCEnsures only ebs-sc is marked as the cluster default

To update the 04_aks_addons module, perform the following steps.

a. Navigate to the iac/azure/cluster/modules/04_aks_addons/ directory.

    cd iac/azure/cluster/modules/04_aks_addons/

b. Edit the terraform.tfvars file to provide the required values.

    resource_group_name    = "<resource-group>" # e.g. "aks-rg-prod"
    aks_cluster_name       = "<cluster-name>" # e.g. "prod-aks-cluster"
    azure_subscription_id  = "<subscription-id>" # e.g. "1e9ef7b6-cdc4-4a6b-98f7-a408ac1e19e0"
    storage_account_name   = "<storage-account-name>" # e.g. "myclusterstateaccount"
    storage_container_name = "velero"

c. Save the file and exit the editor.

d. To initialise, plan, and apply the changes, run the following commands.

    tofu init
    tofu plan          # review the role assignments that will be created
    tofu apply         # type "yes" when prompted

e. To verify the resources created in this stage, run the following commands.

# List storage classes and confirm ebs-sc is the default
kubectl get storageclass
# Verify ebs-sc details
kubectl describe storageclass ebs-sc
# Confirm the built-in 'default' storage class is no longer the default
kubectl get storageclass default -o jsonpath='{.metadata.annotations.storageclass\.kubernetes\.io/is-default-class}'
# expected output: empty or "false"

A sample output is displayed below:

storage_class_names = {
  "backup" = "ebs-bkp"
  "default" = "ebs-sc"
}
  1. 05_helm_releases module

This stage deploys all application Helm charts, configures Velero backup with the Azure plugin, and bootstraps the NFA platform and Insight analytics stack.

ResourcePurpose
Velero Helm releaseBackup and disaster recovery with Azure Blob Storage and disk snapshots
NFA / Eclipse Helm releaseMain platform stack (cert-manager, Keycloak, API Gateway, services)
Insight Helm releaseLog aggregation and analytics (OpenSearch, Dashboards, Fluentd)
Reloader Helm releaseAuto-restarts workloads on ConfigMap or Secret changes
Envoy Gateway Helm releaseAPI gateway and ingress proxy
VolumeSnapshotClassCSI snapshot class (csi-azure-vsc) for Azure Disk snapshots
Namespaces & registry secretsCreates namespaces with image-pull secrets for the private registry

To update the 05_helm_releases module, perform the following steps.

a. Navigate to the iac/azure/cluster/modules/05_helm_releases/ directory.

    cd iac/azure/cluster/modules/05_helm_releases/

b. Run the following commands and note the values.

# Get the Velero Client ID
az identity show \
  --name id-aks-velero \
  --resource-group aks-ppc \
  --query clientId \
  -o tsv
# Get the OpenSearch Client ID
az identity show \
  --name aks-ppc01-opensearch-identity \
  --resource-group aks-ppc \
  --query clientId \
  -o tsv

c. Edit the terraform.tfvars file to provide the required values.

    resource_group_name    = "<resource-group>" # e.g. "aks-rg-prod"
    aks_cluster_name       = "<cluster-name>" # e.g. "prod-aks-cluster"
    azure_subscription_id  = "<subscription-id>" # e.g. "1e9ef7b6-cdc4-4a6b-98f7-a408ac1e19e0"
    storage_account_name   = "<storage-account-name>" # e.g. "myclusterstateaccount"
    storage_container_name = "velero"
    
    # -------------------------------------------------
    # Networking
    # -------------------------------------------------
    subnet_id    = "</subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>>" # same subnet as Stages 02/03 (for VNet address space lookup)
    pod_cidr     = ""  # optional override; empty = auto-detected from the live AKS network profile
    architecture = "amd64" # amd64 or arm64
    node_os      = "AzureLinux_FIPS" # should mirror stage-03 user node pool os_sku + fips_enabled

    # -------------------------------------------------
    # Registry
    # -------------------------------------------------
    global_image_reg = "<registry endpoint>" # e.g. "harbor.example.com"
    harbor_secret    = "<harbour secret>" # e.g. "harbor-pull-secret"
    ingress_fqdn     = "<FQDN>" # e.g. "apps.example.com"

    # -------------------------------------------------
    # Ownership / tagging
    # -------------------------------------------------
    owner_email = "<name@domain.tld>" # optional; blank = auto-detect from the Azure CLI identity

    # -------------------------------------------------
    # Restore / backup
    # -------------------------------------------------
    restore             = false                               # set to true to restore from a previous backup
    backup_name         = "authnz-postgresql-schedule-backup" # Velero backup name used when restore = true
    insight_backup_name = ""                                  # Insight Velero backup name (K8s resources only); leave empty to auto-resolve latest

    # -------------------------------------------------
    # Workload Identity
    # -------------------------------------------------
    velero_uami_client_id     = "<velero_uami_client_id>" # client ID of the Velero UAMI 
    opensearch_uami_client_id = "<opensearch_uami_client_id>" # client ID of the OpenSearch UAMI    

c. Save the file and exit the editor.

d. Export registry credentials using environment variables. Do not put them in the tfvars file.

    export TF_VAR_username="<registry-username>"
    export TF_VAR_password="<registry-password>"

e. To initialise, plan, and apply the changes, run the following commands.

    tofu init
    tofu plan          # review the role assignments that will be created
    tofu apply         # type "yes" when prompted

Expected time for this stage is approximately 15 to 20 minutes.

f. To verify the resources created in this stage, run the following commands.

# Confirm all Helm releases are deployed
helm list -A
# Verify Velero is running
kubectl get pods -n pty-backup-recovery
# Verify NFA platform pods
kubectl get pods -A
# Verify Insight pods
kubectl get pods -n pty-insight
# Verify Envoy Gateway
kubectl get pods -n api-gateway
# Verify VolumeSnapshotClass exists
kubectl get volumesnapshotclass
# Check all pods across the cluster are healthy
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
# Confirm the ingress gateway has an IP
kubectl get gateway -A

A sample output is displayed below:

nfa_release_status = "deployed"
user_svc_private_key = <sensitive>
velero_release_status = "deployed"

All the nodes must be in the Ready state and all the pods must be in the Running or Completed state.

Verifying the installation

To verify the installation, run the following command.

kubectl get nodes
kubectl get pods -A