Skip to content

Commit

Permalink
Import export (#113)
Browse files Browse the repository at this point in the history
* add mover that sleeps

* add export

* add import

* improve strategy selection

* add readme

* cleanup args

* migrate to main
  • Loading branch information
BeryJu authored Jul 5, 2022
1 parent 7538926 commit 261e4de
Show file tree
Hide file tree
Showing 19 changed files with 494 additions and 99 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Go

on:
push:
branches: [ master ]
branches: [ main ]
pull_request:
branches: [ master ]
branches: [ main ]

jobs:

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ name: "CodeQL"

on:
push:
branches: [master]
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
branches: [main]
schedule:
- cron: '0 20 * * 2'

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/mover-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: ci-build

on:
push:
branches: [ master ]
branches: [ main ]
pull_request:
branches: [ master ]
branches: [ main ]

jobs:

Expand All @@ -27,5 +27,5 @@ jobs:
with:
context: mover
tags: ghcr.io/beryju/korb-mover:latest
push: ${{ github.ref == 'refs/heads/master' }}
push: ${{ github.ref == 'refs/heads/main' }}
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
55 changes: 51 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,22 @@ Usage:
korb [pvc [pvc]] [flags]
Flags:
--docker-image string Image to use for moving jobs (default "beryju.org/korb-mover:latest")
--container-image string Image to use for moving jobs (default "ghcr.io/beryju/korb-mover:latest")
--force Ignore warning which would normally halt the tool during validation.
-h, --help help for korb
--kubeConfig string (optional) absolute path to the kubeConfig file (default "/home/jens/.kube/config")
--kube-config string (optional) absolute path to the kubeconfig file (default "/Users/jens/.kube/config")
--new-pvc-name string Name for the new PVC. If empty, same name will be reused.
--new-pvc-namespace string Namespace for the new PVCs to be created in. If empty, the namespace from your kubeconfig file will be used.
--new-pvc-size string Size for the new PVC. If empty, the size of the source will be used. Accepts formats like used in Kubernetes Manifests (Gi, Ti, ...)
--new-pvc-storage-class string Storage class to use for the new PVC. If empty, the storage class of the source will be used.
--skip-pvc-bind-wait Skip waiting for PVC to be bound.
--source-namespace string Namespace where the old PVCs reside. If empty, the namespace from your kubeconfig file will be used.
-v, --version version for korb
--strategy string Strategy to use, by default will try to auto-select
requires at least 1 arg(s), only received 0
```

### Example
### Example (Moving from PVC to PVC)

```
~ ./korb --new-pvc-storage-class ontap-ssd redis-data-redis-master-0
Expand Down Expand Up @@ -79,3 +80,49 @@ DEBU[0048] deleting temporary PVC component=strategy stag
INFO[0050] And we're done component=strategy strategy=copy-twice-name
INFO[0050] Cleaning up... component=strategy strategy=copy-twice-name
```

### Example (Exporting from PVC to tar)

```
~ ./korb overseerr-config --strategy export
DEBU[0000] Created client from kubeconfig component=migrator kubeconfig=/Users/jens/.kube/config
DEBU[0000] Got current namespace component=migrator namespace=overseerr
DEBU[0000] Got Source PVC component=migrator name=overseerr-config uid=8e94240d-3c36-4fb1-baf0-5da1f6c44210
DEBU[0000] No new Name given, using old name component=migrator
INFO[0000] Strategy not compatible component=migrator error="Expected import file 'overseerr-config.tar' does not exist"
DEBU[0000] Compatible Strategies: component=migrator
DEBU[0000] Copy the PVC to the new Storage class and with new size and a new name, delete the old PVC, and copy it back to the old name. component=migrator identifier=copy-twice-name
DEBU[0000] Export PVC content into a tar archive. component=migrator identifier=export
DEBU[0000] User selected strategy component=migrator identifier=export
WARN[0000] This strategy assumes you've stopped all pods accessing this data. component=strategy strategy=export
DEBU[0000] starting mover job component=strategy strategy=export
DEBU[0000] Pod not in correct state yet component=mover-job phase=Pending
[...]
DEBU[0036] mover pod running, starting copy component=strategy strategy=export
tar: Removing leading `/' from member names
/source/
/source/db/
/source/db/db.sqlite3
⠧ downloading (110 kB, 43.824 kB/s) /source/db/db.sqlite3-shm
/source/db/db.sqlite3-wal
⠴ downloading (4.0 MB, 1.521 MB/s) /source/logs/
/source/logs/overseerr.log
/source/logs/.20136e5b8544ec13f7fc29ce3d35150d597108bb-audit.json
/source/logs/.d2109f103a9d757bc28894d508ee5579a3284e75-audit.json
/source/logs/.machinelogs.json
/source/logs/overseerr-2022-07-01.log.gz
/source/logs/overseerr-2022-07-05.log
⠦ downloading (4.2 MB, 1.521 MB/s) /source/logs/.machinelogs-2022-07-04.json.gz
/source/logs/overseerr-2022-05-24.log.gz
/source/logs/overseerr-2022-07-03.log.gz
/source/logs/overseerr-2022-06-29.log.gz
/source/logs/overseerr-2022-07-02.log.gz
/source/logs/overseerr-2022-05-25.log.gz
/source/logs/overseerr-2022-06-30.log.gz
/source/logs/overseerr-2022-07-04.log.gz
/source/logs/.machinelogs-2022-07-05.json
⠦ downloading (4.4 MB, 1.521 MB/s) /source/settings.json
INFO[0039] Finished copying component=strategy strategy=export
INFO[0039] Export at 'overseerr-config.tar' component=strategy strategy=export
INFO[0039] Cleaning up... component=strategy strategy=export
```
10 changes: 6 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

var kubeConfig string
var sourceNamespace string
var strategy string

var pvcNewStorageClass string
var pvcNewSize string
Expand All @@ -33,7 +34,7 @@ var rootCmd = &cobra.Command{
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
for _, pvc := range args {
m := migrator.New(kubeConfig)
m := migrator.New(kubeConfig, strategy)
m.Force = force
m.WaitForTempDestPVCBind = skipWaitPVCBind

Expand Down Expand Up @@ -75,9 +76,9 @@ func init() {
log.SetLevel(log.DebugLevel)

if home := homedir.HomeDir(); home != "" {
rootCmd.Flags().StringVar(&kubeConfig, "kubeConfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeConfig file")
rootCmd.Flags().StringVar(&kubeConfig, "kube-config", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
rootCmd.Flags().StringVar(&kubeConfig, "kubeConfig", "", "absolute path to the kubeconfig file")
rootCmd.Flags().StringVar(&kubeConfig, "kube-config", "", "absolute path to the kubeconfig file")
}
rootCmd.Flags().StringVar(&sourceNamespace, "source-namespace", "", "Namespace where the old PVCs reside. If empty, the namespace from your kubeconfig file will be used.")

Expand All @@ -89,5 +90,6 @@ func init() {
rootCmd.Flags().BoolVar(&force, "force", false, "Ignore warning which would normally halt the tool during validation.")
rootCmd.Flags().BoolVar(&skipWaitPVCBind, "skip-pvc-bind-wait", false, "Skip waiting for PVC to be bound.")

rootCmd.Flags().StringVar(&config.DockerImage, "docker-image", config.DockerImage, "Image to use for moving jobs")
rootCmd.Flags().StringVar(&config.ContainerImage, "container-image", config.ContainerImage, "Image to use for moving jobs")
rootCmd.Flags().StringVar(&strategy, "strategy", "", "Strategy to use, by default will try to auto-select")
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/google/go-cmp v0.5.6 // indirect
github.com/goware/prefixer v0.0.0-20160118172347-395022866408
github.com/imdario/mergo v0.3.11 // indirect
github.com/schollz/progressbar/v3 v3.8.6 // indirect
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0
k8s.io/api v0.24.2
Expand Down
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand All @@ -207,7 +208,13 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
Expand Down Expand Up @@ -237,8 +244,12 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/schollz/progressbar/v3 v3.8.6 h1:QruMUdzZ1TbEP++S1m73OqRJk20ON11m6Wqv4EoGg8c=
github.com/schollz/progressbar/v3 v3.8.6/go.mod h1:W5IEwbJecncFGBvuEh4A7HT1nZZ6WNIL2i3qbnI0WKY=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
Expand Down Expand Up @@ -273,6 +284,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -419,6 +432,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down
8 changes: 5 additions & 3 deletions mover/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
FROM alpine:latest
FROM alpine:3

RUN apk add --no-cache rsync && rm -rf /var/cache/apk/*
RUN apk add --no-cache rsync bash tar && rm -rf /var/cache/apk/*

VOLUME [ "/source", "/dest" ]

CMD [ "rsync", "-aHA", "--progress", "/source/", "/dest" ]
COPY ./entrypoint.sh /bin/entrypoint

ENTRYPOINT [ "/bin/entrypoint" ]
6 changes: 6 additions & 0 deletions mover/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash -xe
if [[ $1 == "sync" ]]; then
rsync -aHA --progress /source/ /dest
elif [[ $1 == "sleep" ]]; then
sleep infinity
fi
2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package config

var DockerImage = "ghcr.io/beryju/korb-mover:latest"
var ContainerImage = "ghcr.io/beryju/korb-mover:latest"
33 changes: 26 additions & 7 deletions pkg/migrator/migrator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package migrator

import (
"beryju.org/korb/pkg/strategies"
log "github.com/sirupsen/logrus"

"k8s.io/client-go/kubernetes"
Expand All @@ -24,12 +25,14 @@ type Migrator struct {
kConfig *rest.Config
kClient *kubernetes.Clientset

log *log.Entry
log *log.Entry
strategy string
}

func New(kubeconfigPath string) *Migrator {
func New(kubeconfigPath string, strategy string) *Migrator {
m := &Migrator{
log: log.WithField("component", "migrator"),
log: log.WithField("component", "migrator"),
strategy: strategy,
}
if kubeconfigPath != "" {
m.log.WithField("kubeconfig", kubeconfigPath).Debug("Created client from kubeconfig")
Expand Down Expand Up @@ -69,15 +72,31 @@ func (m *Migrator) Run() {
sourcePVC, compatibleStrategies := m.Validate()
m.log.Debug("Compatible Strategies:")
for _, compatibleStrategy := range compatibleStrategies {
m.log.Debug(compatibleStrategy.Description())
m.log.WithField("identifier", compatibleStrategy.Identifier()).Debug(compatibleStrategy.Description())
}
destTemplate := m.GetDestinationPVCTemplate(sourcePVC)
destTemplate.Name = m.DestPVCName

var selected strategies.Strategy

if len(compatibleStrategies) == 1 {
m.log.Debug("Only one compatible strategy, running")
err := compatibleStrategies[0].Do(sourcePVC, destTemplate, m.WaitForTempDestPVCBind)
if err != nil {
m.log.WithError(err).Warning("Failed to migrate")
selected = compatibleStrategies[0]
} else {
for _, strat := range compatibleStrategies {
if strat.Identifier() == m.strategy {
m.log.WithField("identifier", strat.Identifier()).Debug("User selected strategy")
selected = strat
break
}
}
}
if selected == nil {
m.log.Error("No (compatible) strategy selected.")
return
}
err := selected.Do(sourcePVC, destTemplate, m.WaitForTempDestPVCBind)
if err != nil {
m.log.WithError(err).Warning("Failed to migrate")
}
}
9 changes: 8 additions & 1 deletion pkg/migrator/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@ func (m *Migrator) Validate() (*v1.PersistentVolumeClaim, []strategies.Strategy)
baseStrategy := strategies.NewBaseStrategy(m.kConfig, m.kClient)
allStrategies := strategies.StrategyInstances(baseStrategy)
compatibleStrategies := make([]strategies.Strategy, 0)
ctx := strategies.MigrationContext{
PVCControllers: controllers,
SourcePVC: *pvc,
}
for _, strategy := range allStrategies {
if strategy.CompatibleWithControllers(controllers...) {
err := strategy.CompatibleWithContext(ctx)
if err == nil {
compatibleStrategies = append(compatibleStrategies, strategy)
} else {
m.log.WithError(err).Info("Strategy not compatible")
}
}
return pvc, compatibleStrategies
Expand Down
52 changes: 52 additions & 0 deletions pkg/mover/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package mover

import (
"bytes"
"io"
"os"

"github.com/goware/prefixer"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)

func (m *MoverJob) Exec(pod v1.Pod, config *rest.Config, cmd []string, input io.Reader, output io.Writer) error {
req := m.kClient.CoreV1().RESTClient().Post().Resource("pods").Name(pod.Name).Namespace(m.Namespace).SubResource("exec")
req.VersionedParams(
&v1.PodExecOptions{
Container: ContainerName,
Command: cmd,
Stdin: input != nil,
Stdout: true,
Stderr: true,
},
scheme.ParameterCodec,
)
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
if err != nil {
return err
}
errBuff := bytes.NewBuffer([]byte{})
prefixReader := prefixer.New(errBuff, "[mover logs]: ")
done := false
go func() {
for {
io.Copy(os.Stdout, prefixReader)
if done {
return
}
}
}()
err = exec.Stream(remotecommand.StreamOptions{
Stdin: input,
Stdout: output,
Stderr: os.Stdout,
})
done = true
if err != nil {
return err
}
return nil
}
Loading

0 comments on commit 261e4de

Please sign in to comment.