Skip to content

Commit 9327152

Browse files
committed
feat: Create a backup mechanism using Buildah image
A backup of workspace is done using Buildah and storing a content of the workspace PVC into a container image. The image is later stored in a registry and can be used to recover data. A prototype script was updated and stored under project-backup directory and is build alongside the controller. The backup job calls the script and execute following steps: - mount a volume with workspace data - build container image using buildah - push image to registry configured by the operator admin Signed-off-by: Ales Raszka <[email protected]>
1 parent dffd7e6 commit 9327152

27 files changed

+265
-94
lines changed

.github/workflows/next-build.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ jobs:
7373
quay.io/devfile/project-clone:sha-${{ steps.git-sha.outputs.sha }}
7474
file: ./project-clone/Dockerfile
7575

76+
- name: Build and push
77+
uses: docker/build-push-action@0a97817b6ade9f46837855d676c4cca3a2471fc9 #v4.2.1
78+
with:
79+
context: ./project-backup
80+
push: true
81+
platforms: linux/amd64, linux/arm64, linux/ppc64le, linux/s390x
82+
tags: |
83+
quay.io/devfile/project-backup:next
84+
quay.io/devfile/project-backup:sha-${{ steps.git-sha.outputs.sha }}
85+
file: ./project-backup/Dockerfile
86+
7687
build-next-olm-imgs:
7788
runs-on: ubuntu-latest
7889
needs: build-next-imgs
@@ -147,6 +158,7 @@ jobs:
147158
export TAG="sha-${{ needs.build-next-imgs.outputs.git-sha }}"
148159
export DEFAULT_DWO_IMG="quay.io/devfile/devworkspace-controller:$TAG"
149160
export PROJECT_CLONE_IMG="quay.io/devfile/project-clone:$TAG"
161+
export PROJECT_BACKUP_IMG="quay.io/devfile/project-backup:$TAG"
150162
151163
# Next builds are not rolled out unless the version is incremented. We want to use semver
152164
# prerelease tags to make sure each new build increments on the previous one, e.g.

.github/workflows/pr.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,6 @@ jobs:
130130
-
131131
name: Check if project-clone dockerimage build is working
132132
run: docker build -f ./project-clone/Dockerfile .
133+
-
134+
name: Check if project-backup containerimage build is working
135+
run: docker build -f ./project-backup/Containerfile project-backup/

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export DWO_IMG ?= quay.io/devfile/devworkspace-controller:next
2626
export DWO_BUNDLE_IMG ?= quay.io/devfile/devworkspace-operator-bundle:next
2727
export DWO_INDEX_IMG ?= quay.io/devfile/devworkspace-operator-index:next
2828
export PROJECT_CLONE_IMG ?= quay.io/devfile/project-clone:next
29+
export PROJECT_BACKUP_IMG ?= quay.io/devfile/project-clone:next
2930
export PULL_POLICY ?= Always
3031
export DEFAULT_ROUTING ?= basic
3132
export KUBECONFIG ?= ${HOME}/.kube/config
@@ -128,6 +129,7 @@ _print_vars:
128129
@echo " DWO_BUNDLE_IMG=$(DWO_BUNDLE_IMG)"
129130
@echo " DWO_INDEX_IMG=$(DWO_INDEX_IMG)"
130131
@echo " PROJECT_CLONE_IMG=$(PROJECT_CLONE_IMG)"
132+
@echo " PROJECT_BACKUP_IMG=$(PROJECT_BACKUP_IMG)"
131133
@echo " PULL_POLICY=$(PULL_POLICY)"
132134
@echo " ROUTING_SUFFIX=$(ROUTING_SUFFIX)"
133135
@echo " DEFAULT_ROUTING=$(DEFAULT_ROUTING)"
@@ -369,6 +371,7 @@ help: Makefile
369371
@echo 'Supported environment variables:'
370372
@echo ' DWO_IMG - Image used for controller'
371373
@echo ' PROJECT_CLONE_IMG - Image used for project-clone init container'
374+
@echo ' PROJECT_BACKUP_IMG - Image used for project-backup workspace backup container'
372375
@echo ' NAMESPACE - Namespace to use for deploying controller'
373376
@echo ' KUBECONFIG - Kubeconfig which should be used for accessing to the cluster. Currently is: $(KUBECONFIG)'
374377
@echo ' ROUTING_SUFFIX - Cluster routing suffix (e.g. $$(minikube ip).nip.io, apps-crc.testing)'

apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ type BackupCronJobConfig struct {
8282
// +kubebuilder:default:="0 2 * * *"
8383
// +kubebuilder:validation:Optional
8484
Schedule string `json:"schedule,omitempty"`
85+
// A registry where backup images are stored. Images are stored
86+
// in {registry}/backup-${DEVWORKSPACE_NAMESPACE}-${DEVWORKSPACE_NAME}
87+
// +kubebuilder:validation:Optional
88+
Registry string `json:"registry,omitempty"`
8589
}
8690

8791
type RoutingConfig struct {

build/scripts/generate_deployment.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
set -e
3232

3333
# List of environment variables that will be replaced by envsubst
34-
SUBST_VARS='$NAMESPACE $DWO_IMG $PROJECT_CLONE_IMG $ROUTING_SUFFIX $DEFAULT_ROUTING $PULL_POLICY'
34+
SUBST_VARS='$NAMESPACE $DWO_IMG $PROJECT_CLONE_IMG $PROJECT_BACKUP_IMG $ROUTING_SUFFIX $DEFAULT_ROUTING $PULL_POLICY'
3535

3636
SCRIPT_DIR=$(cd "$(dirname "$0")"; pwd)
3737
DEPLOY_DIR="$SCRIPT_DIR/../../deploy/"
@@ -58,6 +58,11 @@ Arguments:
5858
'--use-defaults' is passed; otherwise, the value of the PROJECT_CLONE_IMG
5959
environment variable is used. If unspecifed, the default value of
6060
'quay.io/devfile/project-clone:next' is used.
61+
--project-backup-image
62+
Image to use for the project backup workspace. Used only when
63+
'--use-defaults' is passed; otherwise, the value of the PROJECT_BACKUP_IMG
64+
environment variable is used. If unspecifed, the default value of
65+
'quay.io/devfile/project-backup:next' is used.
6166
--split-yaml
6267
Parse output file combined.yaml into a yaml file for each record
6368
in combined yaml. Files are output to the 'objects' subdirectory
@@ -96,6 +101,10 @@ while [[ "$#" -gt 0 ]]; do
96101
PROJECT_CLONE_IMG=$2
97102
shift
98103
;;
104+
--project-backup-image)
105+
PROJECT_BACKUP_IMG=$2
106+
shift
107+
;;
99108
--split-yamls)
100109
SPLIT_YAMLS=true
101110
;;
@@ -118,6 +127,7 @@ if $USE_DEFAULT_ENV; then
118127
export NAMESPACE=devworkspace-controller
119128
export DWO_IMG=${DEFAULT_DWO_IMG:-"quay.io/devfile/devworkspace-controller:next"}
120129
export PROJECT_CLONE_IMG=${PROJECT_CLONE_IMG:-"quay.io/devfile/project-clone:next"}
130+
export PROJECT_BACKUP_IMG=${PROJECT_BACKUP_IMG:-"quay.io/devfile/project-backup:next"}
121131
export PULL_POLICY=Always
122132
export DEFAULT_ROUTING=basic
123133
export DEVWORKSPACE_API_VERSION=a6ec0a38307b63a29fad2eea945cc69bee97a683

controllers/backupcronjob/backupcronjob_controller.go

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
2424
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
25+
"github.com/devfile/devworkspace-operator/internal/images"
2526
"github.com/devfile/devworkspace-operator/pkg/conditions"
2627
"github.com/devfile/devworkspace-operator/pkg/config"
2728
"github.com/devfile/devworkspace-operator/pkg/constants"
@@ -137,11 +138,10 @@ func (r *BackupCronJobReconciler) SetupWithManager(mgr ctrl.Manager) error {
137138
Complete(r)
138139
}
139140

140-
// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;create;update;patch;delete
141-
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list
142-
// +kubebuilder:rbac:groups=controller.devfile.io,resources=devworkspaceoperatorconfigs,verbs=get;list;watch
143-
// +kubebuilder:rbac:groups=workspace.devfile.io,resources=devworkspaces,verbs=get;list
144141
// +kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list
142+
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;create;update;patch;delete
143+
// +kubebuilder:rbac:groups=controller.devfile.io,resources=devworkspaceoperatorconfigs,verbs=get;list;update;patch;watch
144+
// +kubebuilder:rbac:groups=workspace.devfile.io,resources=devworkspaces,verbs=get;list
145145

146146
// Reconcile is the main reconciliation loop for the BackupCronJob controller.
147147
func (r *BackupCronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@@ -180,7 +180,7 @@ func (r *BackupCronJobReconciler) isBackupEnabled(config *controllerv1alpha1.Dev
180180
}
181181

182182
// startCron starts the cron scheduler with the backup job according to the provided configuration.
183-
func (r *BackupCronJobReconciler) startCron(ctx context.Context, backUpConfig *controllerv1alpha1.BackupCronJobConfig, logger logr.Logger) {
183+
func (r *BackupCronJobReconciler) startCron(ctx context.Context, req ctrl.Request, backUpConfig *controllerv1alpha1.BackupCronJobConfig, logger logr.Logger) {
184184
log := logger.WithName("backup cron")
185185
log.Info("Starting backup cron scheduler")
186186

@@ -198,7 +198,7 @@ func (r *BackupCronJobReconciler) startCron(ctx context.Context, backUpConfig *c
198198
taskLog := logger.WithName("cronTask")
199199

200200
taskLog.Info("Starting DevWorkspace backup job")
201-
if err := r.executeBackupSync(ctx, logger); err != nil {
201+
if err := r.executeBackupSync(ctx, req, backUpConfig, logger); err != nil {
202202
taskLog.Error(err, "Failed to execute backup job for DevWorkspaces")
203203
}
204204
taskLog.Info("DevWorkspace backup job finished")
@@ -231,7 +231,7 @@ func (r *BackupCronJobReconciler) stopCron(logger logr.Logger) {
231231

232232
// executeBackupSync executes the backup job for all DevWorkspaces in the cluster that
233233
// have been stopped in the last N minutes.
234-
func (r *BackupCronJobReconciler) executeBackupSync(ctx context.Context, logger logr.Logger) error {
234+
func (r *BackupCronJobReconciler) executeBackupSync(ctx context.Context, req ctrl.Request, backUpConfig *controllerv1alpha1.BackupCronJobConfig, logger logr.Logger) error {
235235
log := logger.WithName("executeBackupSync")
236236
log.Info("Executing backup sync for all DevWorkspaces")
237237
devWorkspaces := &dw.DevWorkspaceList{}
@@ -248,7 +248,7 @@ func (r *BackupCronJobReconciler) executeBackupSync(ctx context.Context, logger
248248
dwID := dw.Status.DevWorkspaceId
249249
log.Info("Found DevWorkspace", "namespace", dw.Namespace, "devworkspace", dw.Name, "id", dwID)
250250

251-
if err := r.createBackupJob(&dw, ctx, logger); err != nil {
251+
if err := r.createBackupJob(&dw, ctx, req, backUpConfig, logger); err != nil {
252252
log.Error(err, "Failed to create backup Job for DevWorkspace", "id", dwID)
253253
continue
254254
}
@@ -285,8 +285,12 @@ func (r *BackupCronJobReconciler) wasStoppedInTimeRange(workspace *dw.DevWorkspa
285285
return false
286286
}
287287

288+
func ptrInt64(i int64) *int64 { return &i }
289+
func ptrInt32(i int32) *int32 { return &i }
290+
func ptrBool(b bool) *bool { return &b }
291+
288292
// createBackupJob creates a Kubernetes Job to back up the workspace's PVC data.
289-
func (r *BackupCronJobReconciler) createBackupJob(workspace *dw.DevWorkspace, ctx context.Context, logger logr.Logger) error {
293+
func (r *BackupCronJobReconciler) createBackupJob(workspace *dw.DevWorkspace, ctx context.Context, req ctrl.Request, backUpConfig *controllerv1alpha1.BackupCronJobConfig, logger logr.Logger) error {
290294
log := logger.WithName("createBackupJob")
291295
dwID := workspace.Status.DevWorkspaceId
292296

@@ -307,38 +311,47 @@ func (r *BackupCronJobReconciler) createBackupJob(workspace *dw.DevWorkspace, ct
307311
Template: corev1.PodTemplateSpec{
308312
Spec: corev1.PodSpec{
309313
RestartPolicy: corev1.RestartPolicyNever,
314+
SecurityContext: &corev1.PodSecurityContext{
315+
FSGroup: ptrInt64(0),
316+
},
310317
Containers: []corev1.Container{
311318
{
312-
Name: "backup",
313-
Image: defaultBackupImage,
314-
Command: []string{"/bin/sh", "-c"},
319+
Name: "backup-workspace",
315320
Env: []corev1.EnvVar{
316-
{
317-
Name: "DEVWORKSPACE_NAME",
318-
Value: workspace.Name,
319-
},
320-
{
321-
Name: "DEVWORKSPACE_NAMESPACE",
322-
Value: workspace.Namespace,
323-
},
324-
{
325-
Name: "WORKSPACE_ID",
326-
Value: dwID,
327-
},
321+
{Name: "DEVWORKSPACE_NAME", Value: workspace.Name},
322+
{Name: "DEVWORKSPACE_NAMESPACE", Value: workspace.Namespace},
323+
{Name: "WORKSPACE_ID", Value: dwID},
328324
{
329325
Name: "BACKUP_SOURCE_PATH",
330326
Value: "/workspace/" + dwID + "/" + constants.DefaultProjectsSourcesRoot,
331327
},
328+
{Name: "STORAGE_DRIVER", Value: "overlay"},
329+
{Name: "BUILDAH_ISOLATION", Value: "chroot"},
330+
{Name: "DEVWORKSPACE_BACKUP_REGISTRY", Value: backUpConfig.Registry},
331+
{Name: "BUILDAH_PUSH_OPTIONS", Value: "--tls-verify=false"},
332332
},
333-
// TODO: Replace the following command with actual backup logic
333+
Image: images.GetProjectBackupImage(),
334+
// Image: "localhost:5001/workspace-backup-image:v3",
334335
Args: []string{
335-
"echo \"Starting backup for workspace $WORKSPACE_ID\" && ls -l \"$BACKUP_SOURCE_PATH\" && sleep 1 && echo Backup completed for workspace " + dwID,
336+
"/workspace-recovery.sh",
337+
"--backup",
336338
},
337339
VolumeMounts: []corev1.VolumeMount{
338340
{
339341
MountPath: "/workspace",
340342
Name: "workspace-data",
341343
},
344+
{
345+
MountPath: "/var/lib/containers",
346+
Name: "build-storage",
347+
},
348+
},
349+
SecurityContext: &corev1.SecurityContext{
350+
RunAsUser: ptrInt64(0),
351+
Capabilities: &corev1.Capabilities{
352+
Add: []corev1.Capability{"SYS_ADMIN", "SYS_CHROOT"},
353+
},
354+
AllowPrivilegeEscalation: ptrBool(false),
342355
},
343356
},
344357
},
@@ -351,6 +364,12 @@ func (r *BackupCronJobReconciler) createBackupJob(workspace *dw.DevWorkspace, ct
351364
},
352365
},
353366
},
367+
{
368+
Name: "build-storage",
369+
VolumeSource: corev1.VolumeSource{
370+
EmptyDir: &corev1.EmptyDirVolumeSource{},
371+
},
372+
},
354373
},
355374
},
356375
},

deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml

Lines changed: 6 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/kubernetes/combined.yaml

Lines changed: 9 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/kubernetes/objects/devworkspace-controller-manager.Deployment.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)