The gcp:gkebackup/restorePlan:RestorePlan resource, part of the Pulumi GCP provider, defines how GKE Backup for GKE restores cluster state from backups: which namespaces or applications to restore, how to handle conflicts, and what transformations to apply. This guide focuses on four capabilities: namespace and application-level restore scoping, conflict resolution modes, transformation rules for namespace renaming, and dependency-based restore ordering.
Restore plans reference existing GKE clusters and BackupPlans. Clusters must have the GKE Backup Agent addon enabled. The examples are intentionally small. Combine them with your own backup strategies and cluster configurations.
Restore all namespaces with conflict handling
Teams recovering from cluster failures often need to restore everything at once, bringing back all namespaces and their resources in a single operation.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "restore-all-ns-cluster",
location: "us-central1",
initialNodeCount: 1,
workloadIdentityConfig: {
workloadPool: "my-project-name.svc.id.goog",
},
addonsConfig: {
gkeBackupAgentConfig: {
enabled: true,
},
},
deletionProtection: true,
network: "default",
subnetwork: "default",
});
const basic = new gcp.gkebackup.BackupPlan("basic", {
name: "restore-all-ns",
cluster: primary.id,
location: "us-central1",
backupConfig: {
includeVolumeData: true,
includeSecrets: true,
allNamespaces: true,
},
});
const allNs = new gcp.gkebackup.RestorePlan("all_ns", {
name: "restore-all-ns",
location: "us-central1",
backupPlan: basic.id,
cluster: primary.id,
restoreConfig: {
allNamespaces: true,
namespacedResourceRestoreMode: "FAIL_ON_CONFLICT",
volumeDataRestorePolicy: "RESTORE_VOLUME_DATA_FROM_BACKUP",
clusterResourceRestoreScope: {
allGroupKinds: true,
},
clusterResourceConflictPolicy: "USE_EXISTING_VERSION",
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="restore-all-ns-cluster",
location="us-central1",
initial_node_count=1,
workload_identity_config={
"workload_pool": "my-project-name.svc.id.goog",
},
addons_config={
"gke_backup_agent_config": {
"enabled": True,
},
},
deletion_protection=True,
network="default",
subnetwork="default")
basic = gcp.gkebackup.BackupPlan("basic",
name="restore-all-ns",
cluster=primary.id,
location="us-central1",
backup_config={
"include_volume_data": True,
"include_secrets": True,
"all_namespaces": True,
})
all_ns = gcp.gkebackup.RestorePlan("all_ns",
name="restore-all-ns",
location="us-central1",
backup_plan=basic.id,
cluster=primary.id,
restore_config={
"all_namespaces": True,
"namespaced_resource_restore_mode": "FAIL_ON_CONFLICT",
"volume_data_restore_policy": "RESTORE_VOLUME_DATA_FROM_BACKUP",
"cluster_resource_restore_scope": {
"all_group_kinds": True,
},
"cluster_resource_conflict_policy": "USE_EXISTING_VERSION",
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkebackup"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("restore-all-ns-cluster"),
Location: pulumi.String("us-central1"),
InitialNodeCount: pulumi.Int(1),
WorkloadIdentityConfig: &container.ClusterWorkloadIdentityConfigArgs{
WorkloadPool: pulumi.String("my-project-name.svc.id.goog"),
},
AddonsConfig: &container.ClusterAddonsConfigArgs{
GkeBackupAgentConfig: &container.ClusterAddonsConfigGkeBackupAgentConfigArgs{
Enabled: pulumi.Bool(true),
},
},
DeletionProtection: pulumi.Bool(true),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
basic, err := gkebackup.NewBackupPlan(ctx, "basic", &gkebackup.BackupPlanArgs{
Name: pulumi.String("restore-all-ns"),
Cluster: primary.ID(),
Location: pulumi.String("us-central1"),
BackupConfig: &gkebackup.BackupPlanBackupConfigArgs{
IncludeVolumeData: pulumi.Bool(true),
IncludeSecrets: pulumi.Bool(true),
AllNamespaces: pulumi.Bool(true),
},
})
if err != nil {
return err
}
_, err = gkebackup.NewRestorePlan(ctx, "all_ns", &gkebackup.RestorePlanArgs{
Name: pulumi.String("restore-all-ns"),
Location: pulumi.String("us-central1"),
BackupPlan: basic.ID(),
Cluster: primary.ID(),
RestoreConfig: &gkebackup.RestorePlanRestoreConfigArgs{
AllNamespaces: pulumi.Bool(true),
NamespacedResourceRestoreMode: pulumi.String("FAIL_ON_CONFLICT"),
VolumeDataRestorePolicy: pulumi.String("RESTORE_VOLUME_DATA_FROM_BACKUP"),
ClusterResourceRestoreScope: &gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs{
AllGroupKinds: pulumi.Bool(true),
},
ClusterResourceConflictPolicy: pulumi.String("USE_EXISTING_VERSION"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "restore-all-ns-cluster",
Location = "us-central1",
InitialNodeCount = 1,
WorkloadIdentityConfig = new Gcp.Container.Inputs.ClusterWorkloadIdentityConfigArgs
{
WorkloadPool = "my-project-name.svc.id.goog",
},
AddonsConfig = new Gcp.Container.Inputs.ClusterAddonsConfigArgs
{
GkeBackupAgentConfig = new Gcp.Container.Inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs
{
Enabled = true,
},
},
DeletionProtection = true,
Network = "default",
Subnetwork = "default",
});
var basic = new Gcp.GkeBackup.BackupPlan("basic", new()
{
Name = "restore-all-ns",
Cluster = primary.Id,
Location = "us-central1",
BackupConfig = new Gcp.GkeBackup.Inputs.BackupPlanBackupConfigArgs
{
IncludeVolumeData = true,
IncludeSecrets = true,
AllNamespaces = true,
},
});
var allNs = new Gcp.GkeBackup.RestorePlan("all_ns", new()
{
Name = "restore-all-ns",
Location = "us-central1",
BackupPlan = basic.Id,
Cluster = primary.Id,
RestoreConfig = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigArgs
{
AllNamespaces = true,
NamespacedResourceRestoreMode = "FAIL_ON_CONFLICT",
VolumeDataRestorePolicy = "RESTORE_VOLUME_DATA_FROM_BACKUP",
ClusterResourceRestoreScope = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs
{
AllGroupKinds = true,
},
ClusterResourceConflictPolicy = "USE_EXISTING_VERSION",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.container.inputs.ClusterWorkloadIdentityConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs;
import com.pulumi.gcp.gkebackup.BackupPlan;
import com.pulumi.gcp.gkebackup.BackupPlanArgs;
import com.pulumi.gcp.gkebackup.inputs.BackupPlanBackupConfigArgs;
import com.pulumi.gcp.gkebackup.RestorePlan;
import com.pulumi.gcp.gkebackup.RestorePlanArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var primary = new Cluster("primary", ClusterArgs.builder()
.name("restore-all-ns-cluster")
.location("us-central1")
.initialNodeCount(1)
.workloadIdentityConfig(ClusterWorkloadIdentityConfigArgs.builder()
.workloadPool("my-project-name.svc.id.goog")
.build())
.addonsConfig(ClusterAddonsConfigArgs.builder()
.gkeBackupAgentConfig(ClusterAddonsConfigGkeBackupAgentConfigArgs.builder()
.enabled(true)
.build())
.build())
.deletionProtection(true)
.network("default")
.subnetwork("default")
.build());
var basic = new BackupPlan("basic", BackupPlanArgs.builder()
.name("restore-all-ns")
.cluster(primary.id())
.location("us-central1")
.backupConfig(BackupPlanBackupConfigArgs.builder()
.includeVolumeData(true)
.includeSecrets(true)
.allNamespaces(true)
.build())
.build());
var allNs = new RestorePlan("allNs", RestorePlanArgs.builder()
.name("restore-all-ns")
.location("us-central1")
.backupPlan(basic.id())
.cluster(primary.id())
.restoreConfig(RestorePlanRestoreConfigArgs.builder()
.allNamespaces(true)
.namespacedResourceRestoreMode("FAIL_ON_CONFLICT")
.volumeDataRestorePolicy("RESTORE_VOLUME_DATA_FROM_BACKUP")
.clusterResourceRestoreScope(RestorePlanRestoreConfigClusterResourceRestoreScopeArgs.builder()
.allGroupKinds(true)
.build())
.clusterResourceConflictPolicy("USE_EXISTING_VERSION")
.build())
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: restore-all-ns-cluster
location: us-central1
initialNodeCount: 1
workloadIdentityConfig:
workloadPool: my-project-name.svc.id.goog
addonsConfig:
gkeBackupAgentConfig:
enabled: true
deletionProtection: true
network: default
subnetwork: default
basic:
type: gcp:gkebackup:BackupPlan
properties:
name: restore-all-ns
cluster: ${primary.id}
location: us-central1
backupConfig:
includeVolumeData: true
includeSecrets: true
allNamespaces: true
allNs:
type: gcp:gkebackup:RestorePlan
name: all_ns
properties:
name: restore-all-ns
location: us-central1
backupPlan: ${basic.id}
cluster: ${primary.id}
restoreConfig:
allNamespaces: true
namespacedResourceRestoreMode: FAIL_ON_CONFLICT
volumeDataRestorePolicy: RESTORE_VOLUME_DATA_FROM_BACKUP
clusterResourceRestoreScope:
allGroupKinds: true
clusterResourceConflictPolicy: USE_EXISTING_VERSION
When allNamespaces is true, the restore operation processes every namespace in the backup. The namespacedResourceRestoreMode controls what happens when restored resources conflict with existing ones; FAIL_ON_CONFLICT stops the restore if any resource already exists. The volumeDataRestorePolicy determines whether to restore persistent volume data from the backup. The clusterResourceRestoreScope with allGroupKinds set to true restores all cluster-scoped resources like CustomResourceDefinitions and StorageClasses, while clusterResourceConflictPolicy uses existing versions when conflicts occur.
Roll back specific namespaces with deletion
When a deployment goes wrong in production, teams need to roll back specific namespaces to a known-good state without affecting the rest of the cluster.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "rollback-ns-cluster",
location: "us-central1",
initialNodeCount: 1,
workloadIdentityConfig: {
workloadPool: "my-project-name.svc.id.goog",
},
addonsConfig: {
gkeBackupAgentConfig: {
enabled: true,
},
},
deletionProtection: true,
network: "default",
subnetwork: "default",
});
const basic = new gcp.gkebackup.BackupPlan("basic", {
name: "rollback-ns",
cluster: primary.id,
location: "us-central1",
backupConfig: {
includeVolumeData: true,
includeSecrets: true,
allNamespaces: true,
},
});
const rollbackNs = new gcp.gkebackup.RestorePlan("rollback_ns", {
name: "rollback-ns-rp",
location: "us-central1",
backupPlan: basic.id,
cluster: primary.id,
restoreConfig: {
selectedNamespaces: {
namespaces: ["my-ns"],
},
namespacedResourceRestoreMode: "DELETE_AND_RESTORE",
volumeDataRestorePolicy: "RESTORE_VOLUME_DATA_FROM_BACKUP",
clusterResourceRestoreScope: {
selectedGroupKinds: [
{
resourceGroup: "apiextension.k8s.io",
resourceKind: "CustomResourceDefinition",
},
{
resourceGroup: "storage.k8s.io",
resourceKind: "StorageClass",
},
],
},
clusterResourceConflictPolicy: "USE_EXISTING_VERSION",
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="rollback-ns-cluster",
location="us-central1",
initial_node_count=1,
workload_identity_config={
"workload_pool": "my-project-name.svc.id.goog",
},
addons_config={
"gke_backup_agent_config": {
"enabled": True,
},
},
deletion_protection=True,
network="default",
subnetwork="default")
basic = gcp.gkebackup.BackupPlan("basic",
name="rollback-ns",
cluster=primary.id,
location="us-central1",
backup_config={
"include_volume_data": True,
"include_secrets": True,
"all_namespaces": True,
})
rollback_ns = gcp.gkebackup.RestorePlan("rollback_ns",
name="rollback-ns-rp",
location="us-central1",
backup_plan=basic.id,
cluster=primary.id,
restore_config={
"selected_namespaces": {
"namespaces": ["my-ns"],
},
"namespaced_resource_restore_mode": "DELETE_AND_RESTORE",
"volume_data_restore_policy": "RESTORE_VOLUME_DATA_FROM_BACKUP",
"cluster_resource_restore_scope": {
"selected_group_kinds": [
{
"resource_group": "apiextension.k8s.io",
"resource_kind": "CustomResourceDefinition",
},
{
"resource_group": "storage.k8s.io",
"resource_kind": "StorageClass",
},
],
},
"cluster_resource_conflict_policy": "USE_EXISTING_VERSION",
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkebackup"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("rollback-ns-cluster"),
Location: pulumi.String("us-central1"),
InitialNodeCount: pulumi.Int(1),
WorkloadIdentityConfig: &container.ClusterWorkloadIdentityConfigArgs{
WorkloadPool: pulumi.String("my-project-name.svc.id.goog"),
},
AddonsConfig: &container.ClusterAddonsConfigArgs{
GkeBackupAgentConfig: &container.ClusterAddonsConfigGkeBackupAgentConfigArgs{
Enabled: pulumi.Bool(true),
},
},
DeletionProtection: pulumi.Bool(true),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
basic, err := gkebackup.NewBackupPlan(ctx, "basic", &gkebackup.BackupPlanArgs{
Name: pulumi.String("rollback-ns"),
Cluster: primary.ID(),
Location: pulumi.String("us-central1"),
BackupConfig: &gkebackup.BackupPlanBackupConfigArgs{
IncludeVolumeData: pulumi.Bool(true),
IncludeSecrets: pulumi.Bool(true),
AllNamespaces: pulumi.Bool(true),
},
})
if err != nil {
return err
}
_, err = gkebackup.NewRestorePlan(ctx, "rollback_ns", &gkebackup.RestorePlanArgs{
Name: pulumi.String("rollback-ns-rp"),
Location: pulumi.String("us-central1"),
BackupPlan: basic.ID(),
Cluster: primary.ID(),
RestoreConfig: &gkebackup.RestorePlanRestoreConfigArgs{
SelectedNamespaces: &gkebackup.RestorePlanRestoreConfigSelectedNamespacesArgs{
Namespaces: pulumi.StringArray{
pulumi.String("my-ns"),
},
},
NamespacedResourceRestoreMode: pulumi.String("DELETE_AND_RESTORE"),
VolumeDataRestorePolicy: pulumi.String("RESTORE_VOLUME_DATA_FROM_BACKUP"),
ClusterResourceRestoreScope: &gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs{
SelectedGroupKinds: gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindArray{
&gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindArgs{
ResourceGroup: pulumi.String("apiextension.k8s.io"),
ResourceKind: pulumi.String("CustomResourceDefinition"),
},
&gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindArgs{
ResourceGroup: pulumi.String("storage.k8s.io"),
ResourceKind: pulumi.String("StorageClass"),
},
},
},
ClusterResourceConflictPolicy: pulumi.String("USE_EXISTING_VERSION"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "rollback-ns-cluster",
Location = "us-central1",
InitialNodeCount = 1,
WorkloadIdentityConfig = new Gcp.Container.Inputs.ClusterWorkloadIdentityConfigArgs
{
WorkloadPool = "my-project-name.svc.id.goog",
},
AddonsConfig = new Gcp.Container.Inputs.ClusterAddonsConfigArgs
{
GkeBackupAgentConfig = new Gcp.Container.Inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs
{
Enabled = true,
},
},
DeletionProtection = true,
Network = "default",
Subnetwork = "default",
});
var basic = new Gcp.GkeBackup.BackupPlan("basic", new()
{
Name = "rollback-ns",
Cluster = primary.Id,
Location = "us-central1",
BackupConfig = new Gcp.GkeBackup.Inputs.BackupPlanBackupConfigArgs
{
IncludeVolumeData = true,
IncludeSecrets = true,
AllNamespaces = true,
},
});
var rollbackNs = new Gcp.GkeBackup.RestorePlan("rollback_ns", new()
{
Name = "rollback-ns-rp",
Location = "us-central1",
BackupPlan = basic.Id,
Cluster = primary.Id,
RestoreConfig = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigArgs
{
SelectedNamespaces = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigSelectedNamespacesArgs
{
Namespaces = new[]
{
"my-ns",
},
},
NamespacedResourceRestoreMode = "DELETE_AND_RESTORE",
VolumeDataRestorePolicy = "RESTORE_VOLUME_DATA_FROM_BACKUP",
ClusterResourceRestoreScope = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs
{
SelectedGroupKinds = new[]
{
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindArgs
{
ResourceGroup = "apiextension.k8s.io",
ResourceKind = "CustomResourceDefinition",
},
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindArgs
{
ResourceGroup = "storage.k8s.io",
ResourceKind = "StorageClass",
},
},
},
ClusterResourceConflictPolicy = "USE_EXISTING_VERSION",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.container.inputs.ClusterWorkloadIdentityConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs;
import com.pulumi.gcp.gkebackup.BackupPlan;
import com.pulumi.gcp.gkebackup.BackupPlanArgs;
import com.pulumi.gcp.gkebackup.inputs.BackupPlanBackupConfigArgs;
import com.pulumi.gcp.gkebackup.RestorePlan;
import com.pulumi.gcp.gkebackup.RestorePlanArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigSelectedNamespacesArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var primary = new Cluster("primary", ClusterArgs.builder()
.name("rollback-ns-cluster")
.location("us-central1")
.initialNodeCount(1)
.workloadIdentityConfig(ClusterWorkloadIdentityConfigArgs.builder()
.workloadPool("my-project-name.svc.id.goog")
.build())
.addonsConfig(ClusterAddonsConfigArgs.builder()
.gkeBackupAgentConfig(ClusterAddonsConfigGkeBackupAgentConfigArgs.builder()
.enabled(true)
.build())
.build())
.deletionProtection(true)
.network("default")
.subnetwork("default")
.build());
var basic = new BackupPlan("basic", BackupPlanArgs.builder()
.name("rollback-ns")
.cluster(primary.id())
.location("us-central1")
.backupConfig(BackupPlanBackupConfigArgs.builder()
.includeVolumeData(true)
.includeSecrets(true)
.allNamespaces(true)
.build())
.build());
var rollbackNs = new RestorePlan("rollbackNs", RestorePlanArgs.builder()
.name("rollback-ns-rp")
.location("us-central1")
.backupPlan(basic.id())
.cluster(primary.id())
.restoreConfig(RestorePlanRestoreConfigArgs.builder()
.selectedNamespaces(RestorePlanRestoreConfigSelectedNamespacesArgs.builder()
.namespaces("my-ns")
.build())
.namespacedResourceRestoreMode("DELETE_AND_RESTORE")
.volumeDataRestorePolicy("RESTORE_VOLUME_DATA_FROM_BACKUP")
.clusterResourceRestoreScope(RestorePlanRestoreConfigClusterResourceRestoreScopeArgs.builder()
.selectedGroupKinds(
RestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindArgs.builder()
.resourceGroup("apiextension.k8s.io")
.resourceKind("CustomResourceDefinition")
.build(),
RestorePlanRestoreConfigClusterResourceRestoreScopeSelectedGroupKindArgs.builder()
.resourceGroup("storage.k8s.io")
.resourceKind("StorageClass")
.build())
.build())
.clusterResourceConflictPolicy("USE_EXISTING_VERSION")
.build())
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: rollback-ns-cluster
location: us-central1
initialNodeCount: 1
workloadIdentityConfig:
workloadPool: my-project-name.svc.id.goog
addonsConfig:
gkeBackupAgentConfig:
enabled: true
deletionProtection: true
network: default
subnetwork: default
basic:
type: gcp:gkebackup:BackupPlan
properties:
name: rollback-ns
cluster: ${primary.id}
location: us-central1
backupConfig:
includeVolumeData: true
includeSecrets: true
allNamespaces: true
rollbackNs:
type: gcp:gkebackup:RestorePlan
name: rollback_ns
properties:
name: rollback-ns-rp
location: us-central1
backupPlan: ${basic.id}
cluster: ${primary.id}
restoreConfig:
selectedNamespaces:
namespaces:
- my-ns
namespacedResourceRestoreMode: DELETE_AND_RESTORE
volumeDataRestorePolicy: RESTORE_VOLUME_DATA_FROM_BACKUP
clusterResourceRestoreScope:
selectedGroupKinds:
- resourceGroup: apiextension.k8s.io
resourceKind: CustomResourceDefinition
- resourceGroup: storage.k8s.io
resourceKind: StorageClass
clusterResourceConflictPolicy: USE_EXISTING_VERSION
The selectedNamespaces property targets specific namespaces by name. Setting namespacedResourceRestoreMode to DELETE_AND_RESTORE removes existing resources in those namespaces before restoring from backup, ensuring a clean rollback. The selectedGroupKinds array limits which cluster-scoped resources are restored, here targeting only CustomResourceDefinitions and StorageClasses.
Restore individual applications by name
Application-level recovery targets specific workloads without touching other resources in the namespace or cluster.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "rollback-app-cluster",
location: "us-central1",
initialNodeCount: 1,
workloadIdentityConfig: {
workloadPool: "my-project-name.svc.id.goog",
},
addonsConfig: {
gkeBackupAgentConfig: {
enabled: true,
},
},
deletionProtection: true,
network: "default",
subnetwork: "default",
});
const basic = new gcp.gkebackup.BackupPlan("basic", {
name: "rollback-app",
cluster: primary.id,
location: "us-central1",
backupConfig: {
includeVolumeData: true,
includeSecrets: true,
allNamespaces: true,
},
});
const rollbackApp = new gcp.gkebackup.RestorePlan("rollback_app", {
name: "rollback-app-rp",
location: "us-central1",
backupPlan: basic.id,
cluster: primary.id,
restoreConfig: {
selectedApplications: {
namespacedNames: [{
name: "my-app",
namespace: "my-ns",
}],
},
namespacedResourceRestoreMode: "DELETE_AND_RESTORE",
volumeDataRestorePolicy: "REUSE_VOLUME_HANDLE_FROM_BACKUP",
clusterResourceRestoreScope: {
noGroupKinds: true,
},
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="rollback-app-cluster",
location="us-central1",
initial_node_count=1,
workload_identity_config={
"workload_pool": "my-project-name.svc.id.goog",
},
addons_config={
"gke_backup_agent_config": {
"enabled": True,
},
},
deletion_protection=True,
network="default",
subnetwork="default")
basic = gcp.gkebackup.BackupPlan("basic",
name="rollback-app",
cluster=primary.id,
location="us-central1",
backup_config={
"include_volume_data": True,
"include_secrets": True,
"all_namespaces": True,
})
rollback_app = gcp.gkebackup.RestorePlan("rollback_app",
name="rollback-app-rp",
location="us-central1",
backup_plan=basic.id,
cluster=primary.id,
restore_config={
"selected_applications": {
"namespaced_names": [{
"name": "my-app",
"namespace": "my-ns",
}],
},
"namespaced_resource_restore_mode": "DELETE_AND_RESTORE",
"volume_data_restore_policy": "REUSE_VOLUME_HANDLE_FROM_BACKUP",
"cluster_resource_restore_scope": {
"no_group_kinds": True,
},
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkebackup"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("rollback-app-cluster"),
Location: pulumi.String("us-central1"),
InitialNodeCount: pulumi.Int(1),
WorkloadIdentityConfig: &container.ClusterWorkloadIdentityConfigArgs{
WorkloadPool: pulumi.String("my-project-name.svc.id.goog"),
},
AddonsConfig: &container.ClusterAddonsConfigArgs{
GkeBackupAgentConfig: &container.ClusterAddonsConfigGkeBackupAgentConfigArgs{
Enabled: pulumi.Bool(true),
},
},
DeletionProtection: pulumi.Bool(true),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
basic, err := gkebackup.NewBackupPlan(ctx, "basic", &gkebackup.BackupPlanArgs{
Name: pulumi.String("rollback-app"),
Cluster: primary.ID(),
Location: pulumi.String("us-central1"),
BackupConfig: &gkebackup.BackupPlanBackupConfigArgs{
IncludeVolumeData: pulumi.Bool(true),
IncludeSecrets: pulumi.Bool(true),
AllNamespaces: pulumi.Bool(true),
},
})
if err != nil {
return err
}
_, err = gkebackup.NewRestorePlan(ctx, "rollback_app", &gkebackup.RestorePlanArgs{
Name: pulumi.String("rollback-app-rp"),
Location: pulumi.String("us-central1"),
BackupPlan: basic.ID(),
Cluster: primary.ID(),
RestoreConfig: &gkebackup.RestorePlanRestoreConfigArgs{
SelectedApplications: &gkebackup.RestorePlanRestoreConfigSelectedApplicationsArgs{
NamespacedNames: gkebackup.RestorePlanRestoreConfigSelectedApplicationsNamespacedNameArray{
&gkebackup.RestorePlanRestoreConfigSelectedApplicationsNamespacedNameArgs{
Name: pulumi.String("my-app"),
Namespace: pulumi.String("my-ns"),
},
},
},
NamespacedResourceRestoreMode: pulumi.String("DELETE_AND_RESTORE"),
VolumeDataRestorePolicy: pulumi.String("REUSE_VOLUME_HANDLE_FROM_BACKUP"),
ClusterResourceRestoreScope: &gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs{
NoGroupKinds: pulumi.Bool(true),
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "rollback-app-cluster",
Location = "us-central1",
InitialNodeCount = 1,
WorkloadIdentityConfig = new Gcp.Container.Inputs.ClusterWorkloadIdentityConfigArgs
{
WorkloadPool = "my-project-name.svc.id.goog",
},
AddonsConfig = new Gcp.Container.Inputs.ClusterAddonsConfigArgs
{
GkeBackupAgentConfig = new Gcp.Container.Inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs
{
Enabled = true,
},
},
DeletionProtection = true,
Network = "default",
Subnetwork = "default",
});
var basic = new Gcp.GkeBackup.BackupPlan("basic", new()
{
Name = "rollback-app",
Cluster = primary.Id,
Location = "us-central1",
BackupConfig = new Gcp.GkeBackup.Inputs.BackupPlanBackupConfigArgs
{
IncludeVolumeData = true,
IncludeSecrets = true,
AllNamespaces = true,
},
});
var rollbackApp = new Gcp.GkeBackup.RestorePlan("rollback_app", new()
{
Name = "rollback-app-rp",
Location = "us-central1",
BackupPlan = basic.Id,
Cluster = primary.Id,
RestoreConfig = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigArgs
{
SelectedApplications = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigSelectedApplicationsArgs
{
NamespacedNames = new[]
{
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigSelectedApplicationsNamespacedNameArgs
{
Name = "my-app",
Namespace = "my-ns",
},
},
},
NamespacedResourceRestoreMode = "DELETE_AND_RESTORE",
VolumeDataRestorePolicy = "REUSE_VOLUME_HANDLE_FROM_BACKUP",
ClusterResourceRestoreScope = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs
{
NoGroupKinds = true,
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.container.inputs.ClusterWorkloadIdentityConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs;
import com.pulumi.gcp.gkebackup.BackupPlan;
import com.pulumi.gcp.gkebackup.BackupPlanArgs;
import com.pulumi.gcp.gkebackup.inputs.BackupPlanBackupConfigArgs;
import com.pulumi.gcp.gkebackup.RestorePlan;
import com.pulumi.gcp.gkebackup.RestorePlanArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigSelectedApplicationsArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var primary = new Cluster("primary", ClusterArgs.builder()
.name("rollback-app-cluster")
.location("us-central1")
.initialNodeCount(1)
.workloadIdentityConfig(ClusterWorkloadIdentityConfigArgs.builder()
.workloadPool("my-project-name.svc.id.goog")
.build())
.addonsConfig(ClusterAddonsConfigArgs.builder()
.gkeBackupAgentConfig(ClusterAddonsConfigGkeBackupAgentConfigArgs.builder()
.enabled(true)
.build())
.build())
.deletionProtection(true)
.network("default")
.subnetwork("default")
.build());
var basic = new BackupPlan("basic", BackupPlanArgs.builder()
.name("rollback-app")
.cluster(primary.id())
.location("us-central1")
.backupConfig(BackupPlanBackupConfigArgs.builder()
.includeVolumeData(true)
.includeSecrets(true)
.allNamespaces(true)
.build())
.build());
var rollbackApp = new RestorePlan("rollbackApp", RestorePlanArgs.builder()
.name("rollback-app-rp")
.location("us-central1")
.backupPlan(basic.id())
.cluster(primary.id())
.restoreConfig(RestorePlanRestoreConfigArgs.builder()
.selectedApplications(RestorePlanRestoreConfigSelectedApplicationsArgs.builder()
.namespacedNames(RestorePlanRestoreConfigSelectedApplicationsNamespacedNameArgs.builder()
.name("my-app")
.namespace("my-ns")
.build())
.build())
.namespacedResourceRestoreMode("DELETE_AND_RESTORE")
.volumeDataRestorePolicy("REUSE_VOLUME_HANDLE_FROM_BACKUP")
.clusterResourceRestoreScope(RestorePlanRestoreConfigClusterResourceRestoreScopeArgs.builder()
.noGroupKinds(true)
.build())
.build())
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: rollback-app-cluster
location: us-central1
initialNodeCount: 1
workloadIdentityConfig:
workloadPool: my-project-name.svc.id.goog
addonsConfig:
gkeBackupAgentConfig:
enabled: true
deletionProtection: true
network: default
subnetwork: default
basic:
type: gcp:gkebackup:BackupPlan
properties:
name: rollback-app
cluster: ${primary.id}
location: us-central1
backupConfig:
includeVolumeData: true
includeSecrets: true
allNamespaces: true
rollbackApp:
type: gcp:gkebackup:RestorePlan
name: rollback_app
properties:
name: rollback-app-rp
location: us-central1
backupPlan: ${basic.id}
cluster: ${primary.id}
restoreConfig:
selectedApplications:
namespacedNames:
- name: my-app
namespace: my-ns
namespacedResourceRestoreMode: DELETE_AND_RESTORE
volumeDataRestorePolicy: REUSE_VOLUME_HANDLE_FROM_BACKUP
clusterResourceRestoreScope:
noGroupKinds: true
The selectedApplications property identifies applications by name and namespace. This narrows the restore scope from entire namespaces to specific workloads. Setting volumeDataRestorePolicy to REUSE_VOLUME_HANDLE_FROM_BACKUP reattaches existing persistent volumes rather than creating new ones. The noGroupKinds flag prevents restoring any cluster-scoped resources, focusing entirely on the application itself.
Transform namespace names during restore
Disaster recovery scenarios sometimes require restoring to a different namespace to avoid conflicts or test recovery procedures without disrupting production.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "rename-ns-cluster",
location: "us-central1",
initialNodeCount: 1,
workloadIdentityConfig: {
workloadPool: "my-project-name.svc.id.goog",
},
addonsConfig: {
gkeBackupAgentConfig: {
enabled: true,
},
},
deletionProtection: true,
network: "default",
subnetwork: "default",
});
const basic = new gcp.gkebackup.BackupPlan("basic", {
name: "rename-ns",
cluster: primary.id,
location: "us-central1",
backupConfig: {
includeVolumeData: true,
includeSecrets: true,
allNamespaces: true,
},
});
const renameNs = new gcp.gkebackup.RestorePlan("rename_ns", {
name: "rename-ns-rp",
location: "us-central1",
backupPlan: basic.id,
cluster: primary.id,
restoreConfig: {
selectedNamespaces: {
namespaces: ["ns1"],
},
namespacedResourceRestoreMode: "FAIL_ON_CONFLICT",
volumeDataRestorePolicy: "REUSE_VOLUME_HANDLE_FROM_BACKUP",
clusterResourceRestoreScope: {
noGroupKinds: true,
},
transformationRules: [
{
description: "rename namespace from ns1 to ns2",
resourceFilter: {
groupKinds: [{
resourceKind: "Namespace",
}],
jsonPath: ".metadata[?(@.name == 'ns1')]",
},
fieldActions: [{
op: "REPLACE",
path: "/metadata/name",
value: "ns2",
}],
},
{
description: "move all resources from ns1 to ns2",
resourceFilter: {
namespaces: ["ns1"],
},
fieldActions: [{
op: "REPLACE",
path: "/metadata/namespace",
value: "ns2",
}],
},
],
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="rename-ns-cluster",
location="us-central1",
initial_node_count=1,
workload_identity_config={
"workload_pool": "my-project-name.svc.id.goog",
},
addons_config={
"gke_backup_agent_config": {
"enabled": True,
},
},
deletion_protection=True,
network="default",
subnetwork="default")
basic = gcp.gkebackup.BackupPlan("basic",
name="rename-ns",
cluster=primary.id,
location="us-central1",
backup_config={
"include_volume_data": True,
"include_secrets": True,
"all_namespaces": True,
})
rename_ns = gcp.gkebackup.RestorePlan("rename_ns",
name="rename-ns-rp",
location="us-central1",
backup_plan=basic.id,
cluster=primary.id,
restore_config={
"selected_namespaces": {
"namespaces": ["ns1"],
},
"namespaced_resource_restore_mode": "FAIL_ON_CONFLICT",
"volume_data_restore_policy": "REUSE_VOLUME_HANDLE_FROM_BACKUP",
"cluster_resource_restore_scope": {
"no_group_kinds": True,
},
"transformation_rules": [
{
"description": "rename namespace from ns1 to ns2",
"resource_filter": {
"group_kinds": [{
"resource_kind": "Namespace",
}],
"json_path": ".metadata[?(@.name == 'ns1')]",
},
"field_actions": [{
"op": "REPLACE",
"path": "/metadata/name",
"value": "ns2",
}],
},
{
"description": "move all resources from ns1 to ns2",
"resource_filter": {
"namespaces": ["ns1"],
},
"field_actions": [{
"op": "REPLACE",
"path": "/metadata/namespace",
"value": "ns2",
}],
},
],
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkebackup"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("rename-ns-cluster"),
Location: pulumi.String("us-central1"),
InitialNodeCount: pulumi.Int(1),
WorkloadIdentityConfig: &container.ClusterWorkloadIdentityConfigArgs{
WorkloadPool: pulumi.String("my-project-name.svc.id.goog"),
},
AddonsConfig: &container.ClusterAddonsConfigArgs{
GkeBackupAgentConfig: &container.ClusterAddonsConfigGkeBackupAgentConfigArgs{
Enabled: pulumi.Bool(true),
},
},
DeletionProtection: pulumi.Bool(true),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
basic, err := gkebackup.NewBackupPlan(ctx, "basic", &gkebackup.BackupPlanArgs{
Name: pulumi.String("rename-ns"),
Cluster: primary.ID(),
Location: pulumi.String("us-central1"),
BackupConfig: &gkebackup.BackupPlanBackupConfigArgs{
IncludeVolumeData: pulumi.Bool(true),
IncludeSecrets: pulumi.Bool(true),
AllNamespaces: pulumi.Bool(true),
},
})
if err != nil {
return err
}
_, err = gkebackup.NewRestorePlan(ctx, "rename_ns", &gkebackup.RestorePlanArgs{
Name: pulumi.String("rename-ns-rp"),
Location: pulumi.String("us-central1"),
BackupPlan: basic.ID(),
Cluster: primary.ID(),
RestoreConfig: &gkebackup.RestorePlanRestoreConfigArgs{
SelectedNamespaces: &gkebackup.RestorePlanRestoreConfigSelectedNamespacesArgs{
Namespaces: pulumi.StringArray{
pulumi.String("ns1"),
},
},
NamespacedResourceRestoreMode: pulumi.String("FAIL_ON_CONFLICT"),
VolumeDataRestorePolicy: pulumi.String("REUSE_VOLUME_HANDLE_FROM_BACKUP"),
ClusterResourceRestoreScope: &gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs{
NoGroupKinds: pulumi.Bool(true),
},
TransformationRules: gkebackup.RestorePlanRestoreConfigTransformationRuleArray{
&gkebackup.RestorePlanRestoreConfigTransformationRuleArgs{
Description: pulumi.String("rename namespace from ns1 to ns2"),
ResourceFilter: &gkebackup.RestorePlanRestoreConfigTransformationRuleResourceFilterArgs{
GroupKinds: gkebackup.RestorePlanRestoreConfigTransformationRuleResourceFilterGroupKindArray{
&gkebackup.RestorePlanRestoreConfigTransformationRuleResourceFilterGroupKindArgs{
ResourceKind: pulumi.String("Namespace"),
},
},
JsonPath: pulumi.String(".metadata[?(@.name == 'ns1')]"),
},
FieldActions: gkebackup.RestorePlanRestoreConfigTransformationRuleFieldActionArray{
&gkebackup.RestorePlanRestoreConfigTransformationRuleFieldActionArgs{
Op: pulumi.String("REPLACE"),
Path: pulumi.String("/metadata/name"),
Value: pulumi.String("ns2"),
},
},
},
&gkebackup.RestorePlanRestoreConfigTransformationRuleArgs{
Description: pulumi.String("move all resources from ns1 to ns2"),
ResourceFilter: &gkebackup.RestorePlanRestoreConfigTransformationRuleResourceFilterArgs{
Namespaces: pulumi.StringArray{
pulumi.String("ns1"),
},
},
FieldActions: gkebackup.RestorePlanRestoreConfigTransformationRuleFieldActionArray{
&gkebackup.RestorePlanRestoreConfigTransformationRuleFieldActionArgs{
Op: pulumi.String("REPLACE"),
Path: pulumi.String("/metadata/namespace"),
Value: pulumi.String("ns2"),
},
},
},
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "rename-ns-cluster",
Location = "us-central1",
InitialNodeCount = 1,
WorkloadIdentityConfig = new Gcp.Container.Inputs.ClusterWorkloadIdentityConfigArgs
{
WorkloadPool = "my-project-name.svc.id.goog",
},
AddonsConfig = new Gcp.Container.Inputs.ClusterAddonsConfigArgs
{
GkeBackupAgentConfig = new Gcp.Container.Inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs
{
Enabled = true,
},
},
DeletionProtection = true,
Network = "default",
Subnetwork = "default",
});
var basic = new Gcp.GkeBackup.BackupPlan("basic", new()
{
Name = "rename-ns",
Cluster = primary.Id,
Location = "us-central1",
BackupConfig = new Gcp.GkeBackup.Inputs.BackupPlanBackupConfigArgs
{
IncludeVolumeData = true,
IncludeSecrets = true,
AllNamespaces = true,
},
});
var renameNs = new Gcp.GkeBackup.RestorePlan("rename_ns", new()
{
Name = "rename-ns-rp",
Location = "us-central1",
BackupPlan = basic.Id,
Cluster = primary.Id,
RestoreConfig = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigArgs
{
SelectedNamespaces = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigSelectedNamespacesArgs
{
Namespaces = new[]
{
"ns1",
},
},
NamespacedResourceRestoreMode = "FAIL_ON_CONFLICT",
VolumeDataRestorePolicy = "REUSE_VOLUME_HANDLE_FROM_BACKUP",
ClusterResourceRestoreScope = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs
{
NoGroupKinds = true,
},
TransformationRules = new[]
{
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigTransformationRuleArgs
{
Description = "rename namespace from ns1 to ns2",
ResourceFilter = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigTransformationRuleResourceFilterArgs
{
GroupKinds = new[]
{
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigTransformationRuleResourceFilterGroupKindArgs
{
ResourceKind = "Namespace",
},
},
JsonPath = ".metadata[?(@.name == 'ns1')]",
},
FieldActions = new[]
{
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigTransformationRuleFieldActionArgs
{
Op = "REPLACE",
Path = "/metadata/name",
Value = "ns2",
},
},
},
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigTransformationRuleArgs
{
Description = "move all resources from ns1 to ns2",
ResourceFilter = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigTransformationRuleResourceFilterArgs
{
Namespaces = new[]
{
"ns1",
},
},
FieldActions = new[]
{
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigTransformationRuleFieldActionArgs
{
Op = "REPLACE",
Path = "/metadata/namespace",
Value = "ns2",
},
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.container.inputs.ClusterWorkloadIdentityConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs;
import com.pulumi.gcp.gkebackup.BackupPlan;
import com.pulumi.gcp.gkebackup.BackupPlanArgs;
import com.pulumi.gcp.gkebackup.inputs.BackupPlanBackupConfigArgs;
import com.pulumi.gcp.gkebackup.RestorePlan;
import com.pulumi.gcp.gkebackup.RestorePlanArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigSelectedNamespacesArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var primary = new Cluster("primary", ClusterArgs.builder()
.name("rename-ns-cluster")
.location("us-central1")
.initialNodeCount(1)
.workloadIdentityConfig(ClusterWorkloadIdentityConfigArgs.builder()
.workloadPool("my-project-name.svc.id.goog")
.build())
.addonsConfig(ClusterAddonsConfigArgs.builder()
.gkeBackupAgentConfig(ClusterAddonsConfigGkeBackupAgentConfigArgs.builder()
.enabled(true)
.build())
.build())
.deletionProtection(true)
.network("default")
.subnetwork("default")
.build());
var basic = new BackupPlan("basic", BackupPlanArgs.builder()
.name("rename-ns")
.cluster(primary.id())
.location("us-central1")
.backupConfig(BackupPlanBackupConfigArgs.builder()
.includeVolumeData(true)
.includeSecrets(true)
.allNamespaces(true)
.build())
.build());
var renameNs = new RestorePlan("renameNs", RestorePlanArgs.builder()
.name("rename-ns-rp")
.location("us-central1")
.backupPlan(basic.id())
.cluster(primary.id())
.restoreConfig(RestorePlanRestoreConfigArgs.builder()
.selectedNamespaces(RestorePlanRestoreConfigSelectedNamespacesArgs.builder()
.namespaces("ns1")
.build())
.namespacedResourceRestoreMode("FAIL_ON_CONFLICT")
.volumeDataRestorePolicy("REUSE_VOLUME_HANDLE_FROM_BACKUP")
.clusterResourceRestoreScope(RestorePlanRestoreConfigClusterResourceRestoreScopeArgs.builder()
.noGroupKinds(true)
.build())
.transformationRules(
RestorePlanRestoreConfigTransformationRuleArgs.builder()
.description("rename namespace from ns1 to ns2")
.resourceFilter(RestorePlanRestoreConfigTransformationRuleResourceFilterArgs.builder()
.groupKinds(RestorePlanRestoreConfigTransformationRuleResourceFilterGroupKindArgs.builder()
.resourceKind("Namespace")
.build())
.jsonPath(".metadata[?(@.name == 'ns1')]")
.build())
.fieldActions(RestorePlanRestoreConfigTransformationRuleFieldActionArgs.builder()
.op("REPLACE")
.path("/metadata/name")
.value("ns2")
.build())
.build(),
RestorePlanRestoreConfigTransformationRuleArgs.builder()
.description("move all resources from ns1 to ns2")
.resourceFilter(RestorePlanRestoreConfigTransformationRuleResourceFilterArgs.builder()
.namespaces("ns1")
.build())
.fieldActions(RestorePlanRestoreConfigTransformationRuleFieldActionArgs.builder()
.op("REPLACE")
.path("/metadata/namespace")
.value("ns2")
.build())
.build())
.build())
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: rename-ns-cluster
location: us-central1
initialNodeCount: 1
workloadIdentityConfig:
workloadPool: my-project-name.svc.id.goog
addonsConfig:
gkeBackupAgentConfig:
enabled: true
deletionProtection: true
network: default
subnetwork: default
basic:
type: gcp:gkebackup:BackupPlan
properties:
name: rename-ns
cluster: ${primary.id}
location: us-central1
backupConfig:
includeVolumeData: true
includeSecrets: true
allNamespaces: true
renameNs:
type: gcp:gkebackup:RestorePlan
name: rename_ns
properties:
name: rename-ns-rp
location: us-central1
backupPlan: ${basic.id}
cluster: ${primary.id}
restoreConfig:
selectedNamespaces:
namespaces:
- ns1
namespacedResourceRestoreMode: FAIL_ON_CONFLICT
volumeDataRestorePolicy: REUSE_VOLUME_HANDLE_FROM_BACKUP
clusterResourceRestoreScope:
noGroupKinds: true
transformationRules:
- description: rename namespace from ns1 to ns2
resourceFilter:
groupKinds:
- resourceKind: Namespace
jsonPath: .metadata[?(@.name == 'ns1')]
fieldActions:
- op: REPLACE
path: /metadata/name
value: ns2
- description: move all resources from ns1 to ns2
resourceFilter:
namespaces:
- ns1
fieldActions:
- op: REPLACE
path: /metadata/namespace
value: ns2
The transformationRules array applies modifications during restore. Each rule has a resourceFilter that selects which resources to transform using jsonPath expressions, and fieldActions that specify modifications. The first rule renames the namespace resource itself from “ns1” to “ns2” by replacing the metadata name field. The second rule updates the namespace reference in all resources from “ns1” to “ns2”, moving them to the new namespace.
Merge restored resources with existing state
GitOps workflows need to restore backup data without overwriting resources managed by continuous deployment pipelines.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "gitops-mode-cluster",
location: "us-central1",
initialNodeCount: 1,
workloadIdentityConfig: {
workloadPool: "my-project-name.svc.id.goog",
},
addonsConfig: {
gkeBackupAgentConfig: {
enabled: true,
},
},
deletionProtection: true,
network: "default",
subnetwork: "default",
});
const basic = new gcp.gkebackup.BackupPlan("basic", {
name: "gitops-mode",
cluster: primary.id,
location: "us-central1",
backupConfig: {
includeVolumeData: true,
includeSecrets: true,
allNamespaces: true,
},
});
const gitopsMode = new gcp.gkebackup.RestorePlan("gitops_mode", {
name: "gitops-mode",
location: "us-central1",
backupPlan: basic.id,
cluster: primary.id,
restoreConfig: {
allNamespaces: true,
namespacedResourceRestoreMode: "MERGE_SKIP_ON_CONFLICT",
volumeDataRestorePolicy: "RESTORE_VOLUME_DATA_FROM_BACKUP",
clusterResourceRestoreScope: {
allGroupKinds: true,
},
clusterResourceConflictPolicy: "USE_EXISTING_VERSION",
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="gitops-mode-cluster",
location="us-central1",
initial_node_count=1,
workload_identity_config={
"workload_pool": "my-project-name.svc.id.goog",
},
addons_config={
"gke_backup_agent_config": {
"enabled": True,
},
},
deletion_protection=True,
network="default",
subnetwork="default")
basic = gcp.gkebackup.BackupPlan("basic",
name="gitops-mode",
cluster=primary.id,
location="us-central1",
backup_config={
"include_volume_data": True,
"include_secrets": True,
"all_namespaces": True,
})
gitops_mode = gcp.gkebackup.RestorePlan("gitops_mode",
name="gitops-mode",
location="us-central1",
backup_plan=basic.id,
cluster=primary.id,
restore_config={
"all_namespaces": True,
"namespaced_resource_restore_mode": "MERGE_SKIP_ON_CONFLICT",
"volume_data_restore_policy": "RESTORE_VOLUME_DATA_FROM_BACKUP",
"cluster_resource_restore_scope": {
"all_group_kinds": True,
},
"cluster_resource_conflict_policy": "USE_EXISTING_VERSION",
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkebackup"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("gitops-mode-cluster"),
Location: pulumi.String("us-central1"),
InitialNodeCount: pulumi.Int(1),
WorkloadIdentityConfig: &container.ClusterWorkloadIdentityConfigArgs{
WorkloadPool: pulumi.String("my-project-name.svc.id.goog"),
},
AddonsConfig: &container.ClusterAddonsConfigArgs{
GkeBackupAgentConfig: &container.ClusterAddonsConfigGkeBackupAgentConfigArgs{
Enabled: pulumi.Bool(true),
},
},
DeletionProtection: pulumi.Bool(true),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
basic, err := gkebackup.NewBackupPlan(ctx, "basic", &gkebackup.BackupPlanArgs{
Name: pulumi.String("gitops-mode"),
Cluster: primary.ID(),
Location: pulumi.String("us-central1"),
BackupConfig: &gkebackup.BackupPlanBackupConfigArgs{
IncludeVolumeData: pulumi.Bool(true),
IncludeSecrets: pulumi.Bool(true),
AllNamespaces: pulumi.Bool(true),
},
})
if err != nil {
return err
}
_, err = gkebackup.NewRestorePlan(ctx, "gitops_mode", &gkebackup.RestorePlanArgs{
Name: pulumi.String("gitops-mode"),
Location: pulumi.String("us-central1"),
BackupPlan: basic.ID(),
Cluster: primary.ID(),
RestoreConfig: &gkebackup.RestorePlanRestoreConfigArgs{
AllNamespaces: pulumi.Bool(true),
NamespacedResourceRestoreMode: pulumi.String("MERGE_SKIP_ON_CONFLICT"),
VolumeDataRestorePolicy: pulumi.String("RESTORE_VOLUME_DATA_FROM_BACKUP"),
ClusterResourceRestoreScope: &gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs{
AllGroupKinds: pulumi.Bool(true),
},
ClusterResourceConflictPolicy: pulumi.String("USE_EXISTING_VERSION"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "gitops-mode-cluster",
Location = "us-central1",
InitialNodeCount = 1,
WorkloadIdentityConfig = new Gcp.Container.Inputs.ClusterWorkloadIdentityConfigArgs
{
WorkloadPool = "my-project-name.svc.id.goog",
},
AddonsConfig = new Gcp.Container.Inputs.ClusterAddonsConfigArgs
{
GkeBackupAgentConfig = new Gcp.Container.Inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs
{
Enabled = true,
},
},
DeletionProtection = true,
Network = "default",
Subnetwork = "default",
});
var basic = new Gcp.GkeBackup.BackupPlan("basic", new()
{
Name = "gitops-mode",
Cluster = primary.Id,
Location = "us-central1",
BackupConfig = new Gcp.GkeBackup.Inputs.BackupPlanBackupConfigArgs
{
IncludeVolumeData = true,
IncludeSecrets = true,
AllNamespaces = true,
},
});
var gitopsMode = new Gcp.GkeBackup.RestorePlan("gitops_mode", new()
{
Name = "gitops-mode",
Location = "us-central1",
BackupPlan = basic.Id,
Cluster = primary.Id,
RestoreConfig = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigArgs
{
AllNamespaces = true,
NamespacedResourceRestoreMode = "MERGE_SKIP_ON_CONFLICT",
VolumeDataRestorePolicy = "RESTORE_VOLUME_DATA_FROM_BACKUP",
ClusterResourceRestoreScope = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs
{
AllGroupKinds = true,
},
ClusterResourceConflictPolicy = "USE_EXISTING_VERSION",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.container.inputs.ClusterWorkloadIdentityConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs;
import com.pulumi.gcp.gkebackup.BackupPlan;
import com.pulumi.gcp.gkebackup.BackupPlanArgs;
import com.pulumi.gcp.gkebackup.inputs.BackupPlanBackupConfigArgs;
import com.pulumi.gcp.gkebackup.RestorePlan;
import com.pulumi.gcp.gkebackup.RestorePlanArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var primary = new Cluster("primary", ClusterArgs.builder()
.name("gitops-mode-cluster")
.location("us-central1")
.initialNodeCount(1)
.workloadIdentityConfig(ClusterWorkloadIdentityConfigArgs.builder()
.workloadPool("my-project-name.svc.id.goog")
.build())
.addonsConfig(ClusterAddonsConfigArgs.builder()
.gkeBackupAgentConfig(ClusterAddonsConfigGkeBackupAgentConfigArgs.builder()
.enabled(true)
.build())
.build())
.deletionProtection(true)
.network("default")
.subnetwork("default")
.build());
var basic = new BackupPlan("basic", BackupPlanArgs.builder()
.name("gitops-mode")
.cluster(primary.id())
.location("us-central1")
.backupConfig(BackupPlanBackupConfigArgs.builder()
.includeVolumeData(true)
.includeSecrets(true)
.allNamespaces(true)
.build())
.build());
var gitopsMode = new RestorePlan("gitopsMode", RestorePlanArgs.builder()
.name("gitops-mode")
.location("us-central1")
.backupPlan(basic.id())
.cluster(primary.id())
.restoreConfig(RestorePlanRestoreConfigArgs.builder()
.allNamespaces(true)
.namespacedResourceRestoreMode("MERGE_SKIP_ON_CONFLICT")
.volumeDataRestorePolicy("RESTORE_VOLUME_DATA_FROM_BACKUP")
.clusterResourceRestoreScope(RestorePlanRestoreConfigClusterResourceRestoreScopeArgs.builder()
.allGroupKinds(true)
.build())
.clusterResourceConflictPolicy("USE_EXISTING_VERSION")
.build())
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: gitops-mode-cluster
location: us-central1
initialNodeCount: 1
workloadIdentityConfig:
workloadPool: my-project-name.svc.id.goog
addonsConfig:
gkeBackupAgentConfig:
enabled: true
deletionProtection: true
network: default
subnetwork: default
basic:
type: gcp:gkebackup:BackupPlan
properties:
name: gitops-mode
cluster: ${primary.id}
location: us-central1
backupConfig:
includeVolumeData: true
includeSecrets: true
allNamespaces: true
gitopsMode:
type: gcp:gkebackup:RestorePlan
name: gitops_mode
properties:
name: gitops-mode
location: us-central1
backupPlan: ${basic.id}
cluster: ${primary.id}
restoreConfig:
allNamespaces: true
namespacedResourceRestoreMode: MERGE_SKIP_ON_CONFLICT
volumeDataRestorePolicy: RESTORE_VOLUME_DATA_FROM_BACKUP
clusterResourceRestoreScope:
allGroupKinds: true
clusterResourceConflictPolicy: USE_EXISTING_VERSION
Setting namespacedResourceRestoreMode to MERGE_SKIP_ON_CONFLICT allows the restore to proceed even when resources already exist. Instead of failing or deleting existing resources, this mode skips conflicting resources and only restores what’s missing. This preserves resources managed by GitOps tooling while recovering data from backup.
Control resource restoration order with dependencies
Complex applications with custom resources often require specific restoration order to satisfy dependencies between resource types.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.container.Cluster("primary", {
name: "restore-order-cluster",
location: "us-central1",
initialNodeCount: 1,
workloadIdentityConfig: {
workloadPool: "my-project-name.svc.id.goog",
},
addonsConfig: {
gkeBackupAgentConfig: {
enabled: true,
},
},
deletionProtection: true,
network: "default",
subnetwork: "default",
});
const basic = new gcp.gkebackup.BackupPlan("basic", {
name: "restore-order",
cluster: primary.id,
location: "us-central1",
backupConfig: {
includeVolumeData: true,
includeSecrets: true,
allNamespaces: true,
},
});
const restoreOrder = new gcp.gkebackup.RestorePlan("restore_order", {
name: "restore-order",
location: "us-central1",
backupPlan: basic.id,
cluster: primary.id,
restoreConfig: {
allNamespaces: true,
namespacedResourceRestoreMode: "FAIL_ON_CONFLICT",
volumeDataRestorePolicy: "RESTORE_VOLUME_DATA_FROM_BACKUP",
clusterResourceRestoreScope: {
allGroupKinds: true,
},
clusterResourceConflictPolicy: "USE_EXISTING_VERSION",
restoreOrder: {
groupKindDependencies: [
{
satisfying: {
resourceGroup: "stable.example.com",
resourceKind: "kindA",
},
requiring: {
resourceGroup: "stable.example.com",
resourceKind: "kindB",
},
},
{
satisfying: {
resourceGroup: "stable.example.com",
resourceKind: "kindB",
},
requiring: {
resourceGroup: "stable.example.com",
resourceKind: "kindC",
},
},
],
},
},
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.container.Cluster("primary",
name="restore-order-cluster",
location="us-central1",
initial_node_count=1,
workload_identity_config={
"workload_pool": "my-project-name.svc.id.goog",
},
addons_config={
"gke_backup_agent_config": {
"enabled": True,
},
},
deletion_protection=True,
network="default",
subnetwork="default")
basic = gcp.gkebackup.BackupPlan("basic",
name="restore-order",
cluster=primary.id,
location="us-central1",
backup_config={
"include_volume_data": True,
"include_secrets": True,
"all_namespaces": True,
})
restore_order = gcp.gkebackup.RestorePlan("restore_order",
name="restore-order",
location="us-central1",
backup_plan=basic.id,
cluster=primary.id,
restore_config={
"all_namespaces": True,
"namespaced_resource_restore_mode": "FAIL_ON_CONFLICT",
"volume_data_restore_policy": "RESTORE_VOLUME_DATA_FROM_BACKUP",
"cluster_resource_restore_scope": {
"all_group_kinds": True,
},
"cluster_resource_conflict_policy": "USE_EXISTING_VERSION",
"restore_order": {
"group_kind_dependencies": [
{
"satisfying": {
"resource_group": "stable.example.com",
"resource_kind": "kindA",
},
"requiring": {
"resource_group": "stable.example.com",
"resource_kind": "kindB",
},
},
{
"satisfying": {
"resource_group": "stable.example.com",
"resource_kind": "kindB",
},
"requiring": {
"resource_group": "stable.example.com",
"resource_kind": "kindC",
},
},
],
},
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/gkebackup"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("restore-order-cluster"),
Location: pulumi.String("us-central1"),
InitialNodeCount: pulumi.Int(1),
WorkloadIdentityConfig: &container.ClusterWorkloadIdentityConfigArgs{
WorkloadPool: pulumi.String("my-project-name.svc.id.goog"),
},
AddonsConfig: &container.ClusterAddonsConfigArgs{
GkeBackupAgentConfig: &container.ClusterAddonsConfigGkeBackupAgentConfigArgs{
Enabled: pulumi.Bool(true),
},
},
DeletionProtection: pulumi.Bool(true),
Network: pulumi.String("default"),
Subnetwork: pulumi.String("default"),
})
if err != nil {
return err
}
basic, err := gkebackup.NewBackupPlan(ctx, "basic", &gkebackup.BackupPlanArgs{
Name: pulumi.String("restore-order"),
Cluster: primary.ID(),
Location: pulumi.String("us-central1"),
BackupConfig: &gkebackup.BackupPlanBackupConfigArgs{
IncludeVolumeData: pulumi.Bool(true),
IncludeSecrets: pulumi.Bool(true),
AllNamespaces: pulumi.Bool(true),
},
})
if err != nil {
return err
}
_, err = gkebackup.NewRestorePlan(ctx, "restore_order", &gkebackup.RestorePlanArgs{
Name: pulumi.String("restore-order"),
Location: pulumi.String("us-central1"),
BackupPlan: basic.ID(),
Cluster: primary.ID(),
RestoreConfig: &gkebackup.RestorePlanRestoreConfigArgs{
AllNamespaces: pulumi.Bool(true),
NamespacedResourceRestoreMode: pulumi.String("FAIL_ON_CONFLICT"),
VolumeDataRestorePolicy: pulumi.String("RESTORE_VOLUME_DATA_FROM_BACKUP"),
ClusterResourceRestoreScope: &gkebackup.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs{
AllGroupKinds: pulumi.Bool(true),
},
ClusterResourceConflictPolicy: pulumi.String("USE_EXISTING_VERSION"),
RestoreOrder: &gkebackup.RestorePlanRestoreConfigRestoreOrderArgs{
GroupKindDependencies: gkebackup.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyArray{
&gkebackup.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyArgs{
Satisfying: &gkebackup.RestorePlanRestoreConfigRestoreOrderGroupKindDependencySatisfyingArgs{
ResourceGroup: pulumi.String("stable.example.com"),
ResourceKind: pulumi.String("kindA"),
},
Requiring: &gkebackup.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyRequiringArgs{
ResourceGroup: pulumi.String("stable.example.com"),
ResourceKind: pulumi.String("kindB"),
},
},
&gkebackup.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyArgs{
Satisfying: &gkebackup.RestorePlanRestoreConfigRestoreOrderGroupKindDependencySatisfyingArgs{
ResourceGroup: pulumi.String("stable.example.com"),
ResourceKind: pulumi.String("kindB"),
},
Requiring: &gkebackup.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyRequiringArgs{
ResourceGroup: pulumi.String("stable.example.com"),
ResourceKind: pulumi.String("kindC"),
},
},
},
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "restore-order-cluster",
Location = "us-central1",
InitialNodeCount = 1,
WorkloadIdentityConfig = new Gcp.Container.Inputs.ClusterWorkloadIdentityConfigArgs
{
WorkloadPool = "my-project-name.svc.id.goog",
},
AddonsConfig = new Gcp.Container.Inputs.ClusterAddonsConfigArgs
{
GkeBackupAgentConfig = new Gcp.Container.Inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs
{
Enabled = true,
},
},
DeletionProtection = true,
Network = "default",
Subnetwork = "default",
});
var basic = new Gcp.GkeBackup.BackupPlan("basic", new()
{
Name = "restore-order",
Cluster = primary.Id,
Location = "us-central1",
BackupConfig = new Gcp.GkeBackup.Inputs.BackupPlanBackupConfigArgs
{
IncludeVolumeData = true,
IncludeSecrets = true,
AllNamespaces = true,
},
});
var restoreOrder = new Gcp.GkeBackup.RestorePlan("restore_order", new()
{
Name = "restore-order",
Location = "us-central1",
BackupPlan = basic.Id,
Cluster = primary.Id,
RestoreConfig = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigArgs
{
AllNamespaces = true,
NamespacedResourceRestoreMode = "FAIL_ON_CONFLICT",
VolumeDataRestorePolicy = "RESTORE_VOLUME_DATA_FROM_BACKUP",
ClusterResourceRestoreScope = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs
{
AllGroupKinds = true,
},
ClusterResourceConflictPolicy = "USE_EXISTING_VERSION",
RestoreOrder = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigRestoreOrderArgs
{
GroupKindDependencies = new[]
{
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyArgs
{
Satisfying = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigRestoreOrderGroupKindDependencySatisfyingArgs
{
ResourceGroup = "stable.example.com",
ResourceKind = "kindA",
},
Requiring = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyRequiringArgs
{
ResourceGroup = "stable.example.com",
ResourceKind = "kindB",
},
},
new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyArgs
{
Satisfying = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigRestoreOrderGroupKindDependencySatisfyingArgs
{
ResourceGroup = "stable.example.com",
ResourceKind = "kindB",
},
Requiring = new Gcp.GkeBackup.Inputs.RestorePlanRestoreConfigRestoreOrderGroupKindDependencyRequiringArgs
{
ResourceGroup = "stable.example.com",
ResourceKind = "kindC",
},
},
},
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.container.inputs.ClusterWorkloadIdentityConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigArgs;
import com.pulumi.gcp.container.inputs.ClusterAddonsConfigGkeBackupAgentConfigArgs;
import com.pulumi.gcp.gkebackup.BackupPlan;
import com.pulumi.gcp.gkebackup.BackupPlanArgs;
import com.pulumi.gcp.gkebackup.inputs.BackupPlanBackupConfigArgs;
import com.pulumi.gcp.gkebackup.RestorePlan;
import com.pulumi.gcp.gkebackup.RestorePlanArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigClusterResourceRestoreScopeArgs;
import com.pulumi.gcp.gkebackup.inputs.RestorePlanRestoreConfigRestoreOrderArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var primary = new Cluster("primary", ClusterArgs.builder()
.name("restore-order-cluster")
.location("us-central1")
.initialNodeCount(1)
.workloadIdentityConfig(ClusterWorkloadIdentityConfigArgs.builder()
.workloadPool("my-project-name.svc.id.goog")
.build())
.addonsConfig(ClusterAddonsConfigArgs.builder()
.gkeBackupAgentConfig(ClusterAddonsConfigGkeBackupAgentConfigArgs.builder()
.enabled(true)
.build())
.build())
.deletionProtection(true)
.network("default")
.subnetwork("default")
.build());
var basic = new BackupPlan("basic", BackupPlanArgs.builder()
.name("restore-order")
.cluster(primary.id())
.location("us-central1")
.backupConfig(BackupPlanBackupConfigArgs.builder()
.includeVolumeData(true)
.includeSecrets(true)
.allNamespaces(true)
.build())
.build());
var restoreOrder = new RestorePlan("restoreOrder", RestorePlanArgs.builder()
.name("restore-order")
.location("us-central1")
.backupPlan(basic.id())
.cluster(primary.id())
.restoreConfig(RestorePlanRestoreConfigArgs.builder()
.allNamespaces(true)
.namespacedResourceRestoreMode("FAIL_ON_CONFLICT")
.volumeDataRestorePolicy("RESTORE_VOLUME_DATA_FROM_BACKUP")
.clusterResourceRestoreScope(RestorePlanRestoreConfigClusterResourceRestoreScopeArgs.builder()
.allGroupKinds(true)
.build())
.clusterResourceConflictPolicy("USE_EXISTING_VERSION")
.restoreOrder(RestorePlanRestoreConfigRestoreOrderArgs.builder()
.groupKindDependencies(
RestorePlanRestoreConfigRestoreOrderGroupKindDependencyArgs.builder()
.satisfying(RestorePlanRestoreConfigRestoreOrderGroupKindDependencySatisfyingArgs.builder()
.resourceGroup("stable.example.com")
.resourceKind("kindA")
.build())
.requiring(RestorePlanRestoreConfigRestoreOrderGroupKindDependencyRequiringArgs.builder()
.resourceGroup("stable.example.com")
.resourceKind("kindB")
.build())
.build(),
RestorePlanRestoreConfigRestoreOrderGroupKindDependencyArgs.builder()
.satisfying(RestorePlanRestoreConfigRestoreOrderGroupKindDependencySatisfyingArgs.builder()
.resourceGroup("stable.example.com")
.resourceKind("kindB")
.build())
.requiring(RestorePlanRestoreConfigRestoreOrderGroupKindDependencyRequiringArgs.builder()
.resourceGroup("stable.example.com")
.resourceKind("kindC")
.build())
.build())
.build())
.build())
.build());
}
}
resources:
primary:
type: gcp:container:Cluster
properties:
name: restore-order-cluster
location: us-central1
initialNodeCount: 1
workloadIdentityConfig:
workloadPool: my-project-name.svc.id.goog
addonsConfig:
gkeBackupAgentConfig:
enabled: true
deletionProtection: true
network: default
subnetwork: default
basic:
type: gcp:gkebackup:BackupPlan
properties:
name: restore-order
cluster: ${primary.id}
location: us-central1
backupConfig:
includeVolumeData: true
includeSecrets: true
allNamespaces: true
restoreOrder:
type: gcp:gkebackup:RestorePlan
name: restore_order
properties:
name: restore-order
location: us-central1
backupPlan: ${basic.id}
cluster: ${primary.id}
restoreConfig:
allNamespaces: true
namespacedResourceRestoreMode: FAIL_ON_CONFLICT
volumeDataRestorePolicy: RESTORE_VOLUME_DATA_FROM_BACKUP
clusterResourceRestoreScope:
allGroupKinds: true
clusterResourceConflictPolicy: USE_EXISTING_VERSION
restoreOrder:
groupKindDependencies:
- satisfying:
resourceGroup: stable.example.com
resourceKind: kindA
requiring:
resourceGroup: stable.example.com
resourceKind: kindB
- satisfying:
resourceGroup: stable.example.com
resourceKind: kindB
requiring:
resourceGroup: stable.example.com
resourceKind: kindC
The restoreOrder property defines dependency relationships between resource types. Each groupKindDependency specifies a satisfying resource that must be restored before a requiring resource. In this configuration, kindA must be restored before kindB, and kindB before kindC. This ensures custom resources are created in the correct order to satisfy their dependencies.
Beyond these examples
These snippets focus on specific restore plan features: namespace and application-level scoping, conflict resolution strategies, and transformation rules and restore ordering. They’re intentionally minimal rather than full disaster recovery solutions.
The examples reference pre-existing infrastructure such as GKE clusters with Backup Agent addon enabled, and BackupPlans with completed backups. They focus on configuring the restore plan rather than provisioning the backup infrastructure.
To keep things focused, common restore patterns are omitted, including:
- Volume restoration policy variations (NO_VOLUME_DATA_RESTORATION, volumeDataRestorePolicyBindings)
- Cluster resource filtering (excludedGroupKinds, excludedNamespaces)
- Labels and descriptions for organizational metadata
- Complex transformation rules (COPY operations, multiple field actions)
These omissions are intentional: the goal is to illustrate how each restore feature is wired, not provide drop-in disaster recovery modules. See the GKE Backup RestorePlan resource reference for all available configuration options.
Let's configure GCP GKE Backup Restore Plans
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Configuration & Immutability
backupPlan, cluster, location, name, and project properties are immutable. Changes to these require replacing the resource.labels field is non-authoritative and only manages labels in your configuration. Use effectiveLabels to see all labels on the resource, including those set by other clients.Namespace & Application Selection
You have four options in restoreConfig:
- All namespaces - Set
allNamespaces: true - Specific namespaces - Use
selectedNamespaceswith a list - Exclude namespaces - Use
excludedNamespacesto restore all except specified ones - No namespaces - Set
noNamespaces: trueto restore only cluster-scoped resources
selectedApplications in restoreConfig with namespacedNames specifying the application name and namespace.Conflict Resolution
The namespacedResourceRestoreMode has three options:
- FAIL_ON_CONFLICT - Restore fails if resources already exist
- DELETE_AND_RESTORE - Deletes existing resources before restoring (useful for rollbacks)
- MERGE_SKIP_ON_CONFLICT - Merges with existing resources, skipping conflicts (GitOps mode)
clusterResourceConflictPolicy in restoreConfig. The common option is USE_EXISTING_VERSION, which keeps existing cluster resources instead of overwriting them.Volume Data Restoration
The volumeDataRestorePolicy has three options:
- RESTORE_VOLUME_DATA_FROM_BACKUP - Restores volume data from the backup
- REUSE_VOLUME_HANDLE_FROM_BACKUP - Reuses the original volume handle without copying data
- NO_VOLUME_DATA_RESTORATION - Skips volume data restoration entirely
volumeDataRestorePolicyBindings to override the default policy for specific volume types like GCE_PERSISTENT_DISK.Cluster Resource Scope
Configure clusterResourceRestoreScope with one of these options:
- allGroupKinds: true - Restore all cluster resources
- selectedGroupKinds - Restore only specified resource kinds (e.g., CustomResourceDefinition, StorageClass)
- excludedGroupKinds - Restore all except specified kinds
- noGroupKinds: true - Skip cluster resources entirely
Advanced Features
transformationRules with field actions. For example, to rename a namespace from ns1 to ns2, create two rules: one to REPLACE /metadata/name on the Namespace resource, and another to REPLACE /metadata/namespace on all resources in ns1.restoreOrder with groupKindDependencies to specify that certain resource kinds must be restored before others (e.g., kindA before kindB before kindC).Using a different cloud?
Explore containers guides for other cloud providers: