Skip to content

Commit

Permalink
Support for multiple BSLs/Clouds (#47)
Browse files Browse the repository at this point in the history
- Update documentation about usage
- Update documentation about restic
  • Loading branch information
Lirt authored Nov 15, 2022
1 parent 8683782 commit d9fc7f3
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 37 deletions.
124 changes: 105 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ Below is a matrix of plugin versions and Velero versions for which the compatibi

| Plugin Version | Velero Version |
| :------------- | :------------- |
| v0.3.x | v1.4.x, v1.5.x, v1.6.x, v1.7.x, v1.8.x |
| v0.4.x | v1.4.x, v1.5.x, v1.6.x, v1.7.x, v1.8.x, 1.9.x |
| v0.3.x | v1.4.x, v1.5.x, v1.6.x, v1.7.x, v1.8.x, 1.9.x |
| v0.2.x | v1.4.x, v1.5.x |
| v0.1.x | v1.4.x, v1.5.x |

Expand Down Expand Up @@ -85,6 +86,8 @@ export OS_SWIFT_TENANT_NAME=<TENANT_NAME>
export OS_SWIFT_USERNAME=<USERNAME>
```

This option does not support using multiple clouds (or BSLs) for backups.

### Authentication using file

You can also authenticate using file in [`clouds.y(a)ml` format](https://docs.openstack.org/python-openstackclient/pike/configuration/index.html#clouds-yaml).
Expand All @@ -93,7 +96,16 @@ Easiest way is to create file `/etc/openstack/clouds.y(a)ml` with content like t

```yaml
clouds:
<CLOUD_NAME>:
<CLOUD_NAME_1>:
region_name: <REGION_NAME>
auth:
auth_url: "<AUTH_URL /v3>"
username: <USERNAME>
password: <PASSWORD>
project_name: <PROJECT_NAME>
project_domain_name: <PROJECT_DOMAIN_NAME>
user_domain_name: <USER_DOMAIN_NAME>
<CLOUD_NAME_2>:
region_name: <REGION_NAME>
auth:
auth_url: "<AUTH_URL /v3>"
Expand All @@ -108,7 +120,14 @@ Or when authenticating using [Application Credentials](https://docs.openstack.or
```yaml
clouds:
<CLOUD_NAME>:
<CLOUD_NAME_1>:
region_name: <REGION_NAME>
auth:
auth_url: "<AUTH_URL /v3>"
application_credential_name: <APPLICATION_CREDENTIAL_NAME>
application_credential_id: <APPLICATION_CREDENTIAL_ID>
application_credential_secret: <APPLICATION_CREDENTIAL_SECRET>
<CLOUD_NAME_2>:
region_name: <REGION_NAME>
auth:
auth_url: "<AUTH_URL /v3>"
Expand All @@ -117,6 +136,44 @@ clouds:
application_credential_secret: <APPLICATION_CREDENTIAL_SECRET>
```
These 2 options allow you also to authenticate against multiple Openstack Clouds at the same time. The way you can leverage this functionality is scenario where you want to store backups in 2 different locations. This scenario doesn't apply for Volume Snapshots as they always need to be created in the same cloud and region as where your PVCs are created!
Example of BSLs:
```yaml
---
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: my-backup-in-cloud1
namespace: velero
spec:
accessMode: ReadWrite
config:
cloud: cloud1
# optional region
region: fra1
default: false
objectStorage:
bucket: velero-backup-cloud1
provider: community.openstack.org/openstack
---
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: my-backup-in-cloud2
namespace: velero
spec:
accessMode: ReadWrite
config:
cloud: cloud2
# optional region
region: lon
default: false
objectStorage:
bucket: velero-backup-cloud2
provider: community.openstack.org/openstack
```
## Installation
### Install using Velero CLI
Expand All @@ -127,30 +184,41 @@ Initialize velero plugin:
# Initialize velero from scratch:
velero install \
--provider "community.openstack.org/openstack" \
--plugins lirt/velero-plugin-for-openstack:v0.3.1 \
--plugins lirt/velero-plugin-for-openstack:v0.4.0 \
--bucket <BUCKET_NAME> \
--no-secret

# Or add plugin to existing velero:
velero plugin add lirt/velero-plugin-for-openstack:v0.3.1
velero plugin add lirt/velero-plugin-for-openstack:v0.4.0
```

Note: If you want to use plugin built for `arm` or `arm64` architecture, you can use tag such as this `lirt/velero-plugin-for-openstack:v0.3.1-arm64`.
Note: If you want to use plugin built for `arm` or `arm64` architecture, you can use tag such as this `lirt/velero-plugin-for-openstack:v0.4.0-arm64`.

Change configuration of `backupstoragelocations.velero.io`:

```yaml
spec:
objectStorage:
bucket: <BUCKET_NAME>
provider: community.openstack.org/openstack
spec:
objectStorage:
bucket: <BUCKET_NAME>
provider: community.openstack.org/openstack
# # Optional config
# config:
# cloud: cloud1
# region: fra
# # If you want to enable restic you need to set resticRepoPrefix to this value:
# # resticRepoPrefix: swift:<BUCKET_NAME>:/<PATH>
# resticRepoPrefix: swift:my-awesome-bucket:/restic # Example
```

Change configuration of `volumesnapshotlocations.velero.io`:

```yaml
spec:
provider: community.openstack.org/openstack
spec:
provider: community.openstack.org/openstack
# optional config
# config:
# cloud: cloud1
# region: fra
```

### Install Using Helm Chart
Expand All @@ -170,15 +238,24 @@ configuration:
backupStorageLocation:
bucket: my-swift-bucket
# caCert: <CERT_CONTENTS_IN_BASE64>
# # Optional config
# config:
# cloud: cloud1
# region: fra
# # If you want to enable restic you need to set resticRepoPrefix to this value:
# # resticRepoPrefix: swift:<BUCKET_NAME>:/<PATH>
# resticRepoPrefix: swift:my-awesome-bucket:/restic # Example
initContainers:
- name: velero-plugin-openstack
image: lirt/velero-plugin-for-openstack:v0.3.1
image: lirt/velero-plugin-for-openstack:v0.4.0
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /target
name: plugins
snapshotsEnabled: true
backupsEnabled: true
# Optionally enable restic
# deployRestic: true
```

Make sure that secret `velero-credentials` exists and has proper format and content.
Expand All @@ -194,7 +271,7 @@ helm upgrade \
--install \
--namespace velero \
--values values.yaml \
--version 2.29.8
--version 2.32.1
```

## Volume Backups
Expand All @@ -203,10 +280,18 @@ Please note two things regarding volume backups:
1. The snapshots are done using flag `--force`. The reason is that volumes in state `in-use` cannot be snapshotted without it (they would need to be detached in advance). In some cases this can make snapshot contents inconsistent.
2. Snapshots in the cinder backend are not always supposed to be used as durable. In some cases for proper availability, the snapshot need to be backed up to off-site storage. Please consult if your cinder backend creates durable snapshots with your cloud provider.

Volume backups with Velero can also be done using [Restic](https://velero.io/docs/main/restic/).
### Native VolumeSnapshots

Alternative Kubernetes native solution (GA since 1.20) for volume snapshots (not backups) are [VolumeSnapshots](https://kubernetes.io/docs/concepts/storage/volume-snapshots/) using [snapshot-controller](https://kubernetes-csi.github.io/docs/snapshot-controller.html).

### Restic

Volume backups with Velero can also be done using [Restic](https://velero.io/docs/main/restic/). Please understand that this repository does not provide any functionality for restic and restic implementation is done purely in Velero code.

There is a common similarity that `restic` can use Openstack Swift as object storage for backups. Restic way of authentication and implementation is however very different from this repository and it means that some ways of authentication that work here will not work with restic. Please refer to [official restic documentation](https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#openstack-swift) to understand how are you supposed to configure authentication variables with restic.

Recommended way of using this plugin with restic is to use authentication with environment variables and only for 1 cloud and 1 BackupStorageLocation. In the BSL you need to configure `config.resticRepoPrefix: swift:<BUCKET_NAME>:/<PATH>` - for example `config.resticRepoPrefix: swift:my-awesome-bucket:/restic`.

## Known Issues

- [Incompatibility with Cinder version 13.0.0 (Rocky)](https://github.com/Lirt/velero-plugin-for-openstack/issues/20)
Expand All @@ -219,17 +304,18 @@ go mod tidy
go build

# Build image
docker buildx --file docker/Dockerfile \
docker buildx build \
--file docker/Dockerfile \
--platform "linux/amd64" \
--tag lirt/velero-plugin-for-openstack:v0.3.1 \
--tag lirt/velero-plugin-for-openstack:v0.4.0 \
--load \
.

# Build and push image for linux amd64, arm64, arm
docker buildx build \
--file docker/Dockerfile \
--platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 \
--tag lirt/velero-plugin-for-openstack:v0.3.1 \
--tag lirt/velero-plugin-for-openstack:v0.4.0 \
--push \
.

Expand All @@ -239,7 +325,7 @@ docker buildx build \
for platform in linux/amd64 linux/arm/v6 linux/arm/v7 linux/arm64; do
docker buildx build \
--file docker/Dockerfile \
--tag lirt/velero-plugin-for-openstack:v0.3.1 \
--tag lirt/velero-plugin-for-openstack:v0.4.0 \
--platform "${platform}" \
--load \
.
Expand Down
1 change: 1 addition & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [`v0.2.1` (Dockerfile)](https://github.com/Lirt/velero-plugin-for-openstack/blob/v0.2.1/docker/Dockerfile)
* [`v0.3.0` (Dockerfile)](https://github.com/Lirt/velero-plugin-for-openstack/blob/v0.3.0/docker/Dockerfile)
* [`v0.3.1` (Dockerfile)](https://github.com/Lirt/velero-plugin-for-openstack/blob/v0.3.1/docker/Dockerfile)
* [`v0.4.0` (Dockerfile)](https://github.com/Lirt/velero-plugin-for-openstack/blob/v0.4.0/docker/Dockerfile)

# Velero plugin for OpenStack

Expand Down
16 changes: 13 additions & 3 deletions src/cinder/block_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cinder
import (
"fmt"
"math/rand"
"os"
"strconv"

"github.com/Lirt/velero-plugin-swift/src/utils"
Expand Down Expand Up @@ -41,19 +42,28 @@ func (b *BlockStore) Init(config map[string]string) error {
b.config = config

// Authenticate to Openstack
err := utils.Authenticate(&b.provider, "cinder", b.log)
err := utils.Authenticate(&b.provider, "cinder", config, b.log)
if err != nil {
return fmt.Errorf("failed to authenticate against openstack: %v", err)
}

if b.client == nil {
region := utils.GetEnv("OS_REGION_NAME", "")
// If we haven't set client before or we use multiple clouds - get new client
if b.client == nil || config["cloud"] != "" {
region, ok := os.LookupEnv("OS_REGION_NAME")
if !ok {
if config["region"] != "" {
region = config["region"]
} else {
region = "RegionOne"
}
}
b.client, err = openstack.NewBlockStorageV3(b.provider, gophercloud.EndpointOpts{
Region: region,
})
if err != nil {
return fmt.Errorf("failed to create cinder storage client: %v", err)
}
b.log.Infof("Successfully created service client with endpoint %v using region %v", b.client.Endpoint, region)
}

return nil
Expand Down
15 changes: 12 additions & 3 deletions src/swift/object_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,31 @@ func NewObjectStore(log logrus.FieldLogger) *ObjectStore {
func (o *ObjectStore) Init(config map[string]string) error {
o.log.Infof("ObjectStore.Init called")

err := utils.Authenticate(&o.provider, "swift", o.log)
err := utils.Authenticate(&o.provider, "swift", config, o.log)
if err != nil {
return fmt.Errorf("failed to authenticate against Openstack: %v", err)
}

if o.client == nil {
// If we haven't set client before or we use multiple clouds - get new client
if o.client == nil || config["cloud"] != "" {
region, ok := os.LookupEnv("OS_SWIFT_REGION_NAME")
if !ok {
region = utils.GetEnv("OS_REGION_NAME", "")
region, ok = os.LookupEnv("OS_REGION_NAME")
if !ok {
if config["region"] != "" {
region = config["region"]
} else {
region = "RegionOne"
}
}
}
o.client, err = openstack.NewObjectStorageV1(o.provider, gophercloud.EndpointOpts{
Region: region,
})
if err != nil {
return fmt.Errorf("failed to create swift storage object: %v", err)
}
o.log.Infof("Successfully created service client with endpoint %v using region %v", o.client.Endpoint, region)
}

return nil
Expand Down
31 changes: 19 additions & 12 deletions src/utils/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ import (
)

// Authenticate to Openstack and write client result to **pc
func Authenticate(pc **gophercloud.ProviderClient, service string, log logrus.FieldLogger) error {
// If service client is already initialized and contains auth result
// we know we were already authenticated
if *pc != nil {
clientAuthResult := (*pc).GetAuthResult()
if clientAuthResult != nil {
return nil
}
}

func Authenticate(pc **gophercloud.ProviderClient, service string, config map[string]string, log logrus.FieldLogger) error {
var err error
var clientOpts clientconfig.ClientOpts

// If we authenticate against multiple clouds, we cannot use reauthentication
if cloud, ok := config["cloud"]; ok {
log.Infof("Authentication will be done for cloud %v", cloud)
clientOpts.Cloud = cloud
} else {
// If service client is already initialized and contains auth result
// we know we were already authenticated
if *pc != nil {
clientAuthResult := (*pc).GetAuthResult()
if clientAuthResult != nil {
return nil
}
}
}

if _, ok := os.LookupEnv("OS_SWIFT_AUTH_URL"); ok && service == "swift" {
log.Infof("Authenticating against Swift using special swift environment variables (see README.md)")
log.Infof("Trying to authenticate against SwiftStack using special swift environment variables (see README.md)")

clientOpts.AuthInfo = &clientconfig.AuthInfo{
ApplicationCredentialID: os.Getenv("OS_SWIFT_APPLICATION_CREDENTIAL_ID"),
ApplicationCredentialName: os.Getenv("OS_SWIFT_APPLICATION_CREDENTIAL_NAME"),
Expand Down Expand Up @@ -67,7 +74,7 @@ func Authenticate(pc **gophercloud.ProviderClient, service string, log logrus.Fi
if err != nil {
return err
}
log.Infof("Authentication successful")
log.Infof("Authentication against identity endpoint %v was successful", (*pc).IdentityEndpoint)

return nil
}

0 comments on commit d9fc7f3

Please sign in to comment.