diff --git a/.github/workflows/prow-action.yml b/.github/workflows/prow-action.yml index 871f69f8f..8a9190180 100644 --- a/.github/workflows/prow-action.yml +++ b/.github/workflows/prow-action.yml @@ -14,7 +14,7 @@ jobs: if: github.repository == 'velero-io/velero' runs-on: ubuntu-latest steps: - - uses: jpmcb/prow-github-actions@v1.1.3 + - uses: jpmcb/prow-github-actions@f4d01dd4b13f289014c23fe5a19878a2479cb35b # v1.1.3 with: # TODO: before allowing the /lgtm command, see if we can block merging if changelog labels are missing. prow-commands: | diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 064bef70a..6db2503f0 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -1,18 +1,32 @@ -on: +name: Automatic Rebase + +on: issue_comment: types: [created] -name: Automatic Rebase + +permissions: {} + jobs: rebase: name: Rebase - if: github.repository == 'velero-io/velero' && github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') + if: >- + github.repository == 'velero-io/velero' && + github.event.issue.pull_request != null && + github.event.comment.body == '/rebase' && + contains( + fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), + github.event.comment.author_association + ) runs-on: ubuntu-latest + permissions: + # contents: write is required because updating the pull request branch + # pushes commits to the head branch. + contents: write + pull-requests: write steps: - - name: Checkout the latest code - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: Automatic Rebase - uses: cirrus-actions/rebase@1.8 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Rebase pull request + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.issue.number }} + run: gh pr update-branch "$PR_NUMBER" --repo "$GH_REPO" --rebase diff --git a/changelogs/unreleased/10093-chlins b/changelogs/unreleased/10093-chlins new file mode 100644 index 000000000..143c6b856 --- /dev/null +++ b/changelogs/unreleased/10093-chlins @@ -0,0 +1 @@ +Replace rebase action with GitHub CLI diff --git a/changelogs/unreleased/10126-blackpiglet b/changelogs/unreleased/10126-blackpiglet new file mode 100644 index 000000000..451b1c2a0 --- /dev/null +++ b/changelogs/unreleased/10126-blackpiglet @@ -0,0 +1 @@ +Use "" as parentSnapshot for DU when BackupType is incremental. \ No newline at end of file diff --git a/pkg/backup/actions/csi/pvc_action.go b/pkg/backup/actions/csi/pvc_action.go index 259ec5783..69676da39 100644 --- a/pkg/backup/actions/csi/pvc_action.go +++ b/pkg/backup/actions/csi/pvc_action.go @@ -536,14 +536,10 @@ func newDataUpload( vsc *snapshotv1api.VolumeSnapshotContent, fsType string, ) *velerov2alpha1.DataUpload { - var parentSnapshot string - switch backup.Spec.BackupType { - case velerov1api.BackupTypeFull: + parentSnapshot := "" + + if backup.Spec.BackupType == velerov1api.BackupTypeFull { parentSnapshot = veleroshared.DataUploadParentSnapshotNone - case velerov1api.BackupTypeIncremental: - parentSnapshot = veleroshared.DataUploadParentSnapshotAuto - default: - parentSnapshot = veleroshared.DataUploadParentSnapshotAuto } dataUpload := &velerov2alpha1.DataUpload{ diff --git a/pkg/backup/actions/csi/pvc_action_test.go b/pkg/backup/actions/csi/pvc_action_test.go index 9c8405efc..b454cae9d 100644 --- a/pkg/backup/actions/csi/pvc_action_test.go +++ b/pkg/backup/actions/csi/pvc_action_test.go @@ -45,7 +45,6 @@ import ( crclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" - veleroshared "github.com/vmware-tanzu/velero/pkg/apis/velero/shared" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" "github.com/vmware-tanzu/velero/pkg/builder" @@ -162,7 +161,7 @@ func TestExecute(t *testing.T) { SourcePVC: "testPVC", SourceNamespace: "velero", OperationTimeout: metav1.Duration{Duration: 1 * time.Minute}, - ParentSnapshot: veleroshared.DataUploadParentSnapshotAuto, + ParentSnapshot: "", }, }, }, @@ -2199,7 +2198,7 @@ func TestNewDataUpload(t *testing.T) { backupType: velerov1api.BackupTypeIncremental, vsClassName: ptr.To("test-vs-class"), uploaderConfig: &velerov1api.UploaderConfigForBackup{ParallelFilesUpload: 10}, - expectedParentSnap: "auto", + expectedParentSnap: "", expectedDataMoverCfg: map[string]string{ uploaderUtil.ParallelFilesUpload: "10", }, @@ -2209,7 +2208,7 @@ func TestNewDataUpload(t *testing.T) { backupType: "", vsClassName: ptr.To("test-vs-class"), uploaderConfig: &velerov1api.UploaderConfigForBackup{ParallelFilesUpload: 0}, - expectedParentSnap: "auto", + expectedParentSnap: "", expectedDataMoverCfg: nil, }, } diff --git a/pkg/cbtservice/csi_service_impl.go b/pkg/cbtservice/csi_service_impl.go index 4d0ea3fca..f4ea23de7 100644 --- a/pkg/cbtservice/csi_service_impl.go +++ b/pkg/cbtservice/csi_service_impl.go @@ -86,6 +86,12 @@ func (s *ServiceImpl) GetAllocatedBlocks(ctx context.Context, snapshot string, r return err } + saNamespace := "" + if s.SAName != "" { + // The SA is created in the same namespace as Velero server. vsNamespace is the namespace of Velero server. + saNamespace = s.vsNamespace + } + args := iterator.Args{ SnapshotName: snapshot, Emitter: &emitterImpl{ @@ -95,7 +101,7 @@ func (s *ServiceImpl) GetAllocatedBlocks(ctx context.Context, snapshot string, r Clients: clients, Namespace: s.vsNamespace, // DataUpload is created in the same namespace as Velero server. vsNamespace is the namespace of the Velero server. - SANamespace: s.vsNamespace, // The SA is created in the same namespace as Velero server. vsNamespace is the namespace of Velero server. + SANamespace: saNamespace, SAName: s.SAName, TokenExpirySecs: iterator.DefaultTokenExpirySeconds, MaxResults: 0, // If 0 then the CSI driver decides the value. diff --git a/pkg/cmd/cli/datamover/backup.go b/pkg/cmd/cli/datamover/backup.go index 07ac7dc18..aa0b2bcfb 100644 --- a/pkg/cmd/cli/datamover/backup.go +++ b/pkg/cmd/cli/datamover/backup.go @@ -331,6 +331,7 @@ func (s *dataMoverBackup) createDataPathService() (dataPathService, error) { s.config.changeID, s.config.volumeID, s.config.snapshotID, + s.cbtService, s.logger, ), nil } diff --git a/pkg/datamover/backup_micro_service.go b/pkg/datamover/backup_micro_service.go index cb5aeb3fe..53409b461 100644 --- a/pkg/datamover/backup_micro_service.go +++ b/pkg/datamover/backup_micro_service.go @@ -34,6 +34,7 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" + "github.com/vmware-tanzu/velero/pkg/cbtservice" "github.com/vmware-tanzu/velero/pkg/datapath" "github.com/vmware-tanzu/velero/pkg/repository" "github.com/vmware-tanzu/velero/pkg/uploader" @@ -71,6 +72,7 @@ type BackupMicroService struct { changeID string volumeID string snapshotID string + cbtService cbtservice.Service } type dataPathResult struct { @@ -80,7 +82,7 @@ type dataPathResult struct { func NewBackupMicroService(ctx context.Context, client client.Client, kubeClient kubernetes.Interface, dataUploadName string, namespace string, nodeName string, sourceTargetPath datapath.AccessPoint, dataPathMgr *datapath.Manager, repoEnsurer *repository.Ensurer, cred *credentials.CredentialGetter, - duInformer cache.Informer, changeID string, volumeID string, snapshotID string, log logrus.FieldLogger) *BackupMicroService { + duInformer cache.Informer, changeID string, volumeID string, snapshotID string, cbtService cbtservice.Service, log logrus.FieldLogger) *BackupMicroService { return &BackupMicroService{ ctx: ctx, client: client, @@ -98,6 +100,7 @@ func NewBackupMicroService(ctx context.Context, client client.Client, kubeClient changeID: changeID, volumeID: volumeID, snapshotID: snapshotID, + cbtService: cbtService, } } @@ -210,6 +213,7 @@ func (r *BackupMicroService) RunCancelableDataPath(ctx context.Context) (string, VolumeID: r.volumeID, ChangeID: r.changeID, SnapshotID: r.snapshotID, + CBTService: r.cbtService, }); err != nil { return "", errors.Wrap(err, "error starting data path backup") } diff --git a/pkg/datapath/data_path.go b/pkg/datapath/data_path.go index 6e36ce6af..2ec750805 100644 --- a/pkg/datapath/data_path.go +++ b/pkg/datapath/data_path.go @@ -57,6 +57,7 @@ type BackupStartParam struct { VolumeID string ChangeID string SnapshotID string + CBTService cbtservice.Service } // RestoreStartParam define the input param for restore start @@ -203,6 +204,7 @@ func (dp *generalDataPath) StartBackup(source AccessPoint, uploaderConfig map[st VolumeID: backupParam.VolumeID, ChangeID: backupParam.ChangeID, }, + Service: backupParam.CBTService, }, source.VolMode, uploaderConfig, diff --git a/pkg/repository/udmrepo/kopialib/lib_repo.go b/pkg/repository/udmrepo/kopialib/lib_repo.go index 151bf1cb2..c7bb65a43 100644 --- a/pkg/repository/udmrepo/kopialib/lib_repo.go +++ b/pkg/repository/udmrepo/kopialib/lib_repo.go @@ -19,7 +19,6 @@ package kopialib import ( "context" "encoding/json" - "fmt" "io" "os" "strings" @@ -913,8 +912,7 @@ func (kow *kopiaObjectWriterEx) Write(p []byte) (int, error) { kow.entryLock.Unlock() buffOffset := curPos - offset - objName := fmt.Sprintf("%s-b%v", kow.description, entryID) - kow.writeObjectAsync(objName, entryID, p[buffOffset:buffOffset+kow.blockSize]) + kow.writeObjectAsync(entryID, p[buffOffset:buffOffset+kow.blockSize]) curPos += kow.blockSize } @@ -922,38 +920,38 @@ func (kow *kopiaObjectWriterEx) Write(p []byte) (int, error) { return length, nil } -func (kow *kopiaObjectWriterEx) writeObject(objName string, p []byte) (object.ID, error) { +func (kow *kopiaObjectWriterEx) writeObject(p []byte) (object.ID, error) { writer := kow.rawRepoWriter.NewObjectWriter(kopia.SetupKopiaLog(kow.ctx, kow.logger), object.WriterOptions{ - Description: objName, + Description: kow.description, Compressor: kow.compressor, Splitter: kow.splitter, }) if writer == nil { - return object.EmptyID, errors.Errorf("error opening writer for %s", objName) + return object.EmptyID, errors.New("error opening writer") } defer writer.Close() written, err := writer.Write(p) if err != nil { - return object.EmptyID, errors.Wrapf(err, "error writing for %s", objName) + return object.EmptyID, errors.Wrap(err, "error writing data") } if written != len(p) { - return object.EmptyID, errors.Errorf("short write for %s", objName) + return object.EmptyID, errors.New("short write") } objID, err := writer.Result() if err != nil { - return object.EmptyID, errors.Wrapf(err, "error flushing data for %s", objName) + return object.EmptyID, errors.Wrap(err, "error flushing data") } return objID, nil } -func (kow *kopiaObjectWriterEx) writeObjectSync(objName string, entry int, p []byte) error { - objID, err := kow.writeObject(objName, p) +func (kow *kopiaObjectWriterEx) writeObjectSync(entry int, p []byte) error { + objID, err := kow.writeObject(p) if err != nil { return err } @@ -965,10 +963,10 @@ func (kow *kopiaObjectWriterEx) writeObjectSync(objName string, entry int, p []b return nil } -func (kow *kopiaObjectWriterEx) writeObjectAsync(objName string, entryID int, p []byte) { +func (kow *kopiaObjectWriterEx) writeObjectAsync(entryID int, p []byte) { if kow.asyncWritesSem == nil { - if err := kow.writeObjectSync(objName, entryID, p); err != nil { - kow.saveWriteError(errors.Wrapf(err, "error writing object for %s", objName)) + if err := kow.writeObjectSync(entryID, p); err != nil { + kow.saveWriteError(errors.Wrapf(err, "error writing object for %s, entry %d", kow.description, entryID)) } } else { kow.asyncWritesSem <- struct{}{} @@ -977,8 +975,8 @@ func (kow *kopiaObjectWriterEx) writeObjectAsync(objName string, entryID int, p copy(buffer, p) kow.asyncWritesGroup.Go(func() { - if err := kow.writeObjectSync(objName, entryID, buffer); err != nil { - kow.saveWriteError(errors.Wrapf(err, "error writing object for %s", objName)) + if err := kow.writeObjectSync(entryID, buffer); err != nil { + kow.saveWriteError(errors.Wrapf(err, "error writing object for %s, entry %d", kow.description, entryID)) } kow.asyncBuffer.Return(buffer) @@ -987,10 +985,10 @@ func (kow *kopiaObjectWriterEx) writeObjectAsync(objName string, entryID int, p } } -func (kow *kopiaObjectWriterEx) writeZeroObject(objName string, entryID int) error { +func (kow *kopiaObjectWriterEx) writeZeroObject(entryID int) error { if kow.zeroObject == object.EmptyID { zeroBuffer := make([]byte, kow.blockSize) - objectID, err := kow.writeObject(objName, zeroBuffer) + objectID, err := kow.writeObject(zeroBuffer) if err != nil { return err } @@ -1071,9 +1069,8 @@ func (kow *kopiaObjectWriterEx) WriteAt(p []byte, offset int64) (int, error) { }) kow.entryLock.Unlock() - objName := fmt.Sprintf("%s-b%v", kow.description, entryID) - if err := kow.writeZeroObject(objName, entryID); err != nil { - return 0, errors.Wrapf(err, "error writing zero object for %s", objName) + if err := kow.writeZeroObject(entryID); err != nil { + return 0, errors.Wrapf(err, "error writing zero object for %s, entry %v", kow.description, entryID) } curPos += kow.blockSize @@ -1093,8 +1090,7 @@ func (kow *kopiaObjectWriterEx) WriteAt(p []byte, offset int64) (int, error) { kow.entryLock.Unlock() buffOffset := curPos - offset - objName := fmt.Sprintf("%s-b%v", kow.description, entryID) - kow.writeObjectAsync(objName, entryID, p[buffOffset:buffOffset+kow.blockSize]) + kow.writeObjectAsync(entryID, p[buffOffset:buffOffset+kow.blockSize]) curPos += kow.blockSize } diff --git a/pkg/repository/udmrepo/kopialib/lib_repo_ex_test.go b/pkg/repository/udmrepo/kopialib/lib_repo_ex_test.go index 6d698ec7b..c9b383ea2 100644 --- a/pkg/repository/udmrepo/kopialib/lib_repo_ex_test.go +++ b/pkg/repository/udmrepo/kopialib/lib_repo_ex_test.go @@ -291,7 +291,7 @@ func TestKopiaObjectWriterEx_Write(t *testing.T) { t.Helper() err := kow.getWriteError() require.Error(t, err) - assert.Contains(t, err.Error(), "error opening writer for -b0") + assert.Contains(t, err.Error(), "error writing object for , entry 0: error opening writer") }, }, { @@ -936,7 +936,7 @@ func TestKopiaObjectWriterEx_WriteAt(t *testing.T) { }, inputData: make([]byte, 1024), offset: 1024, - expectedErr: "error writing zero object for -b0: error writing for -b0: simulated zero object write error", + expectedErr: "error writing zero object for , entry 0: error writing data: simulated zero object write error", }, { name: "writeObject short write", @@ -964,7 +964,7 @@ func TestKopiaObjectWriterEx_WriteAt(t *testing.T) { t.Helper() err := kow.getWriteError() require.Error(t, err) - assert.Contains(t, err.Error(), "short write for -b0") + assert.Contains(t, err.Error(), "error writing object for , entry 0: short write") }, }, } diff --git a/pkg/uploader/block/uploader.go b/pkg/uploader/block/uploader.go index 0b937c82e..1d74bd462 100644 --- a/pkg/uploader/block/uploader.go +++ b/pkg/uploader/block/uploader.go @@ -19,12 +19,14 @@ package block import ( "bytes" "context" + "fmt" "io" "os" "runtime" "strconv" "strings" "sync" + "time" "github.com/cockroachdb/errors" "github.com/sirupsen/logrus" @@ -61,10 +63,11 @@ type Uploader interface { } type blockUploader struct { - ctx context.Context - repoWriter udmrepo.BackupRepo - progress uploader.ProgressUpdater - log logrus.FieldLogger + ctx context.Context + repoWriter udmrepo.BackupRepo + progress uploader.ProgressUpdater + log logrus.FieldLogger + lastProgressUpdate time.Time } func NewUploader(ctx context.Context, repoWriter udmrepo.BackupRepo, progress uploader.ProgressUpdater, log logrus.FieldLogger) Uploader { @@ -89,7 +92,7 @@ func (blkup *blockUploader) Backup(source sourceInfo, parentObject udmrepo.ID, b } destObj, err := blkup.repoWriter.NewObjectWriter(blkup.ctx, udmrepo.ObjectWriteOptions{ - Description: "BDEV:" + getObjectName(source.realSource), + Description: fmt.Sprintf("BDEV:%s-%s", getObjectName(source.realSource), snapStart.Format("2006-01-02-15-04-05")), DataType: udmrepo.ObjectDataTypeData, AccessMode: udmrepo.ObjectDataAccessModeBlock, ParentObject: parentObject, @@ -197,6 +200,17 @@ func (blkup *blockUploader) backupObject(dev *os.File, dest udmrepo.ObjectWriter return id, backupSize, objectSize, err } +func (blkup *blockUploader) UpdateProgress(p *uploader.Progress) { + if blkup.progress == nil { + return + } + + if time.Since(blkup.lastProgressUpdate) >= 10*time.Second || p.BytesDone == p.TotalBytes { + blkup.progress.UpdateProgress(p) + blkup.lastProgressUpdate = time.Now() + } +} + type readResult struct { buffer []byte offset int64 @@ -232,7 +246,7 @@ func (blkup *blockUploader) backupData(reader io.ReaderAt, writer udmrepo.Object go func() { defer wg.Done() defer close(quit) - written, lastPos, writeErr = backupWriteProc(blkup.ctx, writer, resultChan, list, aligned, totalCount, int(blockSize), blkup.progress) + written, lastPos, writeErr = backupWriteProc(blkup.ctx, writer, resultChan, list, aligned, totalCount, int(blockSize), blkup) }() wg.Wait() @@ -249,7 +263,7 @@ func (blkup *blockUploader) backupData(reader io.ReaderAt, writer udmrepo.Object written += s - blkup.progress.UpdateProgress(&uploader.Progress{BytesDone: aligned, TotalBytes: aligned}) + blkup.UpdateProgress(&uploader.Progress{BytesDone: aligned, TotalBytes: aligned}) } return written, aligned, nil @@ -418,7 +432,7 @@ func (blkup *blockUploader) restoreData(reader io.ReadSeeker, dest *os.File, bit go func() { defer wg.Done() defer close(quit) - written, writeErr = restoreWriteProc(blkup.ctx, dest, resultChan, list, totalLength, totalCount, int(blockSize), destPath, blkup.progress, blkup.log) + written, writeErr = restoreWriteProc(blkup.ctx, dest, resultChan, list, totalLength, totalCount, int(blockSize), destPath, blkup, blkup.log) }() wg.Wait() diff --git a/pkg/uploader/block/uploader_test.go b/pkg/uploader/block/uploader_test.go index 8a4708e35..f2de0e8c2 100644 --- a/pkg/uploader/block/uploader_test.go +++ b/pkg/uploader/block/uploader_test.go @@ -21,6 +21,7 @@ import ( "context" "io" "os" + "strings" "testing" "time" @@ -357,7 +358,7 @@ func TestBlockUploaderBackup(t *testing.T) { } repoWriter.On("NewObjectWriter", mock.Anything, mock.MatchedBy(func(opt udmrepo.ObjectWriteOptions) bool { - return opt.Description == "BDEV:data-volume1" && opt.BackupMode == backupMode + return strings.HasPrefix(opt.Description, "BDEV:data-volume1-") && opt.BackupMode == backupMode })).Return(objWriter, tc.createObjErr) } diff --git a/site/config.yaml b/site/config.yaml index 6eddc1e14..9edc08541 100644 --- a/site/config.yaml +++ b/site/config.yaml @@ -8,7 +8,7 @@ frontmatter: params: author: Velero Authors logo: Velero.svg - cncf_logo: cncf-white.svg + cncf_logo: cncf-color.svg hero: backgroundColor: med-blue versioning: true @@ -63,6 +63,9 @@ params: - title: Twitter fa_icon: fab fa-twitter url: https://twitter.com/projectvelero + - title: LinkedIn + fa_icon: fab fa-linkedin + url: https://www.linkedin.com/company/project-velero - title: Slack fa_icon: fab fa-slack url: https://kubernetes.slack.com/messages/velero diff --git a/site/content/_index.md b/site/content/_index.md index 79426ecc8..7d27dc709 100644 --- a/site/content/_index.md +++ b/site/content/_index.md @@ -32,7 +32,7 @@ secondary_ctas: url: /blog/Velero-is-an-Open-Source-Tool-to-Back-up-and-Migrate-Kubernetes-Clusters/ # Velero.io word list : ignore content: Learn about Velero and how to protect your Kubernetes resources and volumes. cta2: - title: How Do You Use Velero? - url: https://github.com/velero-io/velero/issues/1327 - content: See how Velero is helping others and tell the world how you use Velero. + title: Join the Velero Community + url: /community/ + content: Connect with other Velero users on Slack, attend community meetings, and contribute to the project. --- \ No newline at end of file diff --git a/site/content/posts/2026-07-30-Velero-Joins-CNCF-Sandbox.md b/site/content/posts/2026-07-30-Velero-Joins-CNCF-Sandbox.md new file mode 100644 index 000000000..cf5d0f61d --- /dev/null +++ b/site/content/posts/2026-07-30-Velero-Joins-CNCF-Sandbox.md @@ -0,0 +1,69 @@ +--- +title: "Velero Joins the CNCF Sandbox" +excerpt: Velero has been accepted into the Cloud Native Computing Foundation as a Sandbox project, bringing Kubernetes-native backup and disaster recovery under vendor-neutral, community-driven governance. +author_name: Shubham Pampattiwar +slug: Velero-Joins-CNCF-Sandbox +categories: ['velero','announcements'] +image: /img/cncf-color.svg +tags: ['Velero Team', 'Shubham Pampattiwar', 'CNCF'] +--- + +![CNCF Logo](/img/cncf-color.svg) + +We are excited to announce that Velero has been accepted into the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/) as a Sandbox project. This marks a significant milestone for the project, placing Velero under vendor-neutral, community-driven governance alongside other foundational cloud native tools. + +The CNCF Technical Oversight Committee (TOC) accepted the [Sandbox application](https://github.com/cncf/sandbox/issues/457), and the transition was formally announced at KubeCon + CloudNativeCon Europe 2026 in Amsterdam. + +## What This Means + +Joining the CNCF Sandbox means Velero is now governed by the same open, vendor-neutral principles that guide projects like Kubernetes, Prometheus, and Envoy. In practice, this means: + +- **Vendor-neutral governance**: No single company controls the project roadmap. Decisions are made through consensus-based processes with supermajority voting. +- **Community-driven development**: The project's direction is shaped by its maintainers and contributors, who represent multiple organizations. +- **Long-term sustainability**: CNCF provides a neutral home that ensures the project's continuity regardless of changes in any single company's priorities. + +For existing Velero users, nothing changes in how you use the tool. Velero continues to operate at the Kubernetes API layer, providing backup, restore, disaster recovery, and migration capabilities for your clusters and applications. + +## Our Journey + +Velero's journey began at Heptio, the Kubernetes company founded by Joe Beda and Craig McLuckie, where it was originally known as Ark. After VMware acquired Heptio in 2019, the project continued to grow under VMware's stewardship. Following Broadcom's acquisition of VMware, the decision was made to contribute Velero to the CNCF, ensuring the project's future under community governance. + +Throughout these transitions, one thing has remained constant: a growing and engaged open source community. Today, Velero has over 10,000 GitHub stars, 1,500+ forks, 500M+ Docker Hub pulls, and is used by organizations across industries for Kubernetes data protection. + +We are grateful to Broadcom for contributing Velero to the CNCF and to everyone who has contributed to the project over the years. + +## Current Maintainers + +Velero is maintained by engineers from multiple organizations, reflecting the project's vendor-neutral nature: + +| Maintainer | GitHub | Affiliation | +|---|---|---| +| Daniel Jiang | [@reasonerjt](https://github.com/reasonerjt) | Broadcom | +| Wenkai Yin | [@ywk253100](https://github.com/ywk253100) | Broadcom | +| Xun Jiang | [@blackpiglet](https://github.com/blackpiglet) | Broadcom | +| Yonghui Li | [@Lyndon-Li](https://github.com/Lyndon-Li) | Broadcom | +| Scott Seago | [@sseago](https://github.com/sseago) | Red Hat (OpenShift) | +| Shubham Pampattiwar | [@shubham-pampattiwar](https://github.com/shubham-pampattiwar) | Red Hat (OpenShift) | +| Tiger Kaovilai | [@kaovilai](https://github.com/kaovilai) | Red Hat (OpenShift) | +| Anshul Ahuja | [@anshulahuja98](https://github.com/anshulahuja98) | Microsoft (Azure) | + +## What's Next + +Joining the CNCF Sandbox is the beginning of a new chapter for Velero. Here is what we are focused on: + +- **Growing the community**: We want more contributors, more adopters, and more voices shaping the project's direction. Whether you are a user, operator, or developer, there is a place for you in the Velero community. +- **Strengthening the project**: We are continuing to improve Velero's core capabilities around backup performance, data protection, and ecosystem integration. +- **Path to Incubation**: Our goal is to demonstrate the community health, adoption, and maturity needed to advance to CNCF Incubation status. + +## Get Involved + +We welcome contributions of all kinds -- code, documentation, bug reports, feature requests, and feedback. + +- **Slack**: Join [#velero-users](https://kubernetes.slack.com/messages/velero) and [#velero-dev](https://kubernetes.slack.com/messages/velero-dev) on Kubernetes Slack +- **GitHub**: [github.com/velero-io/velero](https://github.com/velero-io/velero) +- **Community Meetings**: We hold bi-weekly community meetings alternating between US/Europe and US/Asia-friendly time zones. See the [community page](https://velero.io/community/) for details. +- **LinkedIn**: Follow us at [Project Velero](https://www.linkedin.com/company/project-velero) +- **Twitter/X**: [@projectvelero](https://twitter.com/projectvelero) +- **Contributing**: Check out our [contribution guide](https://velero.io/docs/main/start-contributing/) to get started. + +We are excited about this new chapter and look forward to building the future of Kubernetes data protection together with the community. diff --git a/site/static/img/cncf-color.svg b/site/static/img/cncf-color.svg new file mode 100644 index 000000000..6ed428836 --- /dev/null +++ b/site/static/img/cncf-color.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + +