2018-05-13 13:28:09 +00:00
/ *
2020-08-12 22:13:44 +00:00
Copyright 2020 the Velero contributors .
2018-05-13 13:28:09 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2018-06-08 18:03:07 +00:00
package restic
2018-02-28 01:35:35 +00:00
import (
"context"
"fmt"
2020-07-22 19:07:52 +00:00
"net/http"
2018-02-28 01:35:35 +00:00
"os"
"strings"
2020-06-24 16:55:18 +00:00
2020-07-22 19:07:52 +00:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2020-06-24 16:55:18 +00:00
"github.com/vmware-tanzu/velero/internal/util/managercontroller"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
2020-07-22 19:07:52 +00:00
"github.com/vmware-tanzu/velero/pkg/metrics"
2018-02-28 01:35:35 +00:00
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
2019-07-03 20:20:32 +00:00
v1 "k8s.io/api/core/v1"
2018-02-28 01:35:35 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-06-24 16:55:18 +00:00
"k8s.io/apimachinery/pkg/runtime"
2019-07-03 20:20:32 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2018-02-28 01:35:35 +00:00
kubeinformers "k8s.io/client-go/informers"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
2020-06-24 16:55:18 +00:00
ctrl "sigs.k8s.io/controller-runtime"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/buildinfo"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/signals"
"github.com/vmware-tanzu/velero/pkg/controller"
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
"github.com/vmware-tanzu/velero/pkg/util/logging"
2020-06-24 16:55:18 +00:00
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/manager"
)
var (
scheme = runtime . NewScheme ( )
2018-02-28 01:35:35 +00:00
)
2020-07-22 19:07:52 +00:00
const (
// the port where prometheus metrics are exposed
defaultMetricsAddress = ":8085"
)
2018-06-08 18:03:07 +00:00
func NewServerCommand ( f client . Factory ) * cobra . Command {
2018-09-25 21:46:29 +00:00
logLevelFlag := logging . LogLevelFlag ( logrus . InfoLevel )
2019-07-30 23:29:34 +00:00
formatFlag := logging . NewFormatFlag ( )
2018-02-28 01:35:35 +00:00
2018-09-25 21:46:29 +00:00
command := & cobra . Command {
2019-06-11 01:20:17 +00:00
Use : "server" ,
Short : "Run the velero restic server" ,
Long : "Run the velero restic server" ,
Hidden : true ,
2018-02-28 01:35:35 +00:00
Run : func ( c * cobra . Command , args [ ] string ) {
logLevel := logLevelFlag . Parse ( )
2018-06-08 18:03:07 +00:00
logrus . Infof ( "Setting log-level to %s" , strings . ToUpper ( logLevel . String ( ) ) )
2018-02-28 01:35:35 +00:00
2019-07-30 23:29:34 +00:00
logger := logging . DefaultLogger ( logLevel , formatFlag . Parse ( ) )
2019-04-15 17:27:04 +00:00
logger . Infof ( "Starting Velero restic server %s (%s)" , buildinfo . Version , buildinfo . FormattedGitSHA ( ) )
2018-02-28 01:35:35 +00:00
2019-08-09 17:23:16 +00:00
f . SetBasename ( fmt . Sprintf ( "%s-%s" , c . Parent ( ) . Name ( ) , c . Name ( ) ) )
2020-07-22 19:07:52 +00:00
s , err := newResticServer ( logger , f , defaultMetricsAddress )
2018-02-28 01:35:35 +00:00
cmd . CheckError ( err )
s . run ( )
} ,
}
2020-08-12 22:13:44 +00:00
command . Flags ( ) . Var ( logLevelFlag , "log-level" , fmt . Sprintf ( "The level at which to log. Valid values are %s." , strings . Join ( logLevelFlag . AllowedValues ( ) , ", " ) ) )
command . Flags ( ) . Var ( formatFlag , "log-format" , fmt . Sprintf ( "The format for log output. Valid values are %s." , strings . Join ( formatFlag . AllowedValues ( ) , ", " ) ) )
2018-02-28 01:35:35 +00:00
return command
}
2018-06-08 18:03:07 +00:00
type resticServer struct {
2019-01-25 03:33:07 +00:00
kubeClient kubernetes . Interface
veleroClient clientset . Interface
veleroInformerFactory informers . SharedInformerFactory
kubeInformerFactory kubeinformers . SharedInformerFactory
podInformer cache . SharedIndexInformer
secretInformer cache . SharedIndexInformer
logger logrus . FieldLogger
ctx context . Context
cancelFunc context . CancelFunc
2019-07-03 20:20:32 +00:00
fileSystem filesystem . Interface
2020-06-24 16:55:18 +00:00
mgr manager . Manager
2020-07-22 19:07:52 +00:00
metrics * metrics . ServerMetrics
metricsAddress string
2018-02-28 01:35:35 +00:00
}
2020-07-22 19:07:52 +00:00
func newResticServer ( logger logrus . FieldLogger , factory client . Factory , metricAddress string ) ( * resticServer , error ) {
2018-02-28 01:35:35 +00:00
2019-08-09 17:23:16 +00:00
kubeClient , err := factory . KubeClient ( )
2018-02-28 01:35:35 +00:00
if err != nil {
2019-08-09 17:23:16 +00:00
return nil , err
2018-02-28 01:35:35 +00:00
}
2019-08-09 17:23:16 +00:00
veleroClient , err := factory . Client ( )
2018-02-28 01:35:35 +00:00
if err != nil {
2019-08-09 17:23:16 +00:00
return nil , err
2018-02-28 01:35:35 +00:00
}
// use a stand-alone pod informer because we want to use a field selector to
// filter to only pods scheduled on this node.
podInformer := corev1informers . NewFilteredPodInformer (
kubeClient ,
2018-06-22 19:07:23 +00:00
metav1 . NamespaceAll ,
2018-02-28 01:35:35 +00:00
0 ,
cache . Indexers { cache . NamespaceIndex : cache . MetaNamespaceIndexFunc } ,
func ( opts * metav1 . ListOptions ) {
opts . FieldSelector = fmt . Sprintf ( "spec.nodeName=%s" , os . Getenv ( "NODE_NAME" ) )
} ,
)
2018-06-22 19:07:23 +00:00
// use a stand-alone secrets informer so we can filter to only the restic credentials
2019-01-25 03:33:07 +00:00
// secret(s) within the velero namespace
2018-06-22 19:07:23 +00:00
//
2019-01-25 03:33:07 +00:00
// note: using an informer to access the single secret for all velero-managed
2018-06-22 19:07:23 +00:00
// restic repositories is overkill for now, but will be useful when we move
// to fully-encrypted backups and have unique keys per repository.
secretInformer := corev1informers . NewFilteredSecretInformer (
kubeClient ,
2019-08-09 17:23:16 +00:00
factory . Namespace ( ) ,
2018-06-22 19:07:23 +00:00
0 ,
cache . Indexers { cache . NamespaceIndex : cache . MetaNamespaceIndexFunc } ,
func ( opts * metav1 . ListOptions ) {
opts . FieldSelector = fmt . Sprintf ( "metadata.name=%s" , restic . CredentialsSecretName )
} ,
)
2018-02-28 01:35:35 +00:00
ctx , cancelFunc := context . WithCancel ( context . Background ( ) )
2020-06-24 16:55:18 +00:00
clientConfig , err := factory . ClientConfig ( )
if err != nil {
return nil , err
}
ctrl . SetLogger ( zap . New ( zap . UseDevMode ( true ) ) )
velerov1api . AddToScheme ( scheme )
mgr , err := ctrl . NewManager ( clientConfig , ctrl . Options {
Scheme : scheme ,
} )
if err != nil {
return nil , err
}
2019-07-03 20:20:32 +00:00
s := & resticServer {
2019-01-25 03:33:07 +00:00
kubeClient : kubeClient ,
veleroClient : veleroClient ,
2019-08-09 17:23:16 +00:00
veleroInformerFactory : informers . NewFilteredSharedInformerFactory ( veleroClient , 0 , factory . Namespace ( ) , nil ) ,
2019-01-25 03:33:07 +00:00
kubeInformerFactory : kubeinformers . NewSharedInformerFactory ( kubeClient , 0 ) ,
podInformer : podInformer ,
secretInformer : secretInformer ,
logger : logger ,
ctx : ctx ,
cancelFunc : cancelFunc ,
2019-07-03 20:20:32 +00:00
fileSystem : filesystem . NewFileSystem ( ) ,
2020-06-24 16:55:18 +00:00
mgr : mgr ,
2020-07-22 19:07:52 +00:00
metricsAddress : metricAddress ,
2019-07-03 20:20:32 +00:00
}
if err := s . validatePodVolumesHostPath ( ) ; err != nil {
return nil , err
}
return s , nil
2018-02-28 01:35:35 +00:00
}
2018-06-08 18:03:07 +00:00
func ( s * resticServer ) run ( ) {
2018-02-28 01:35:35 +00:00
signals . CancelOnShutdown ( s . cancelFunc , s . logger )
2020-07-22 19:07:52 +00:00
go func ( ) {
metricsMux := http . NewServeMux ( )
metricsMux . Handle ( "/metrics" , promhttp . Handler ( ) )
s . logger . Infof ( "Starting metric server for restic at address [%s]" , s . metricsAddress )
if err := http . ListenAndServe ( s . metricsAddress , metricsMux ) ; err != nil {
s . logger . Fatalf ( "Failed to start metric server for restic at [%s]: %v" , s . metricsAddress , err )
}
} ( )
s . metrics = metrics . NewResticServerMetrics ( )
s . metrics . RegisterAllMetrics ( )
s . metrics . InitResticMetricsForNode ( os . Getenv ( "NODE_NAME" ) )
2018-02-28 01:35:35 +00:00
s . logger . Info ( "Starting controllers" )
backupController := controller . NewPodVolumeBackupController (
s . logger ,
2019-01-25 03:33:07 +00:00
s . veleroInformerFactory . Velero ( ) . V1 ( ) . PodVolumeBackups ( ) ,
s . veleroClient . VeleroV1 ( ) ,
2018-02-28 01:35:35 +00:00
s . podInformer ,
2018-06-22 19:07:23 +00:00
s . secretInformer ,
2018-02-28 01:35:35 +00:00
s . kubeInformerFactory . Core ( ) . V1 ( ) . PersistentVolumeClaims ( ) ,
2019-07-10 22:16:21 +00:00
s . kubeInformerFactory . Core ( ) . V1 ( ) . PersistentVolumes ( ) ,
2020-07-22 19:07:52 +00:00
s . metrics ,
2020-06-24 16:55:18 +00:00
s . mgr . GetClient ( ) ,
2018-02-28 01:35:35 +00:00
os . Getenv ( "NODE_NAME" ) ,
)
restoreController := controller . NewPodVolumeRestoreController (
s . logger ,
2019-01-25 03:33:07 +00:00
s . veleroInformerFactory . Velero ( ) . V1 ( ) . PodVolumeRestores ( ) ,
s . veleroClient . VeleroV1 ( ) ,
2018-02-28 01:35:35 +00:00
s . podInformer ,
2018-06-22 19:07:23 +00:00
s . secretInformer ,
2018-02-28 01:35:35 +00:00
s . kubeInformerFactory . Core ( ) . V1 ( ) . PersistentVolumeClaims ( ) ,
2019-07-10 22:16:21 +00:00
s . kubeInformerFactory . Core ( ) . V1 ( ) . PersistentVolumes ( ) ,
2020-06-24 16:55:18 +00:00
s . mgr . GetClient ( ) ,
2018-02-28 01:35:35 +00:00
os . Getenv ( "NODE_NAME" ) ,
)
2019-01-25 03:33:07 +00:00
go s . veleroInformerFactory . Start ( s . ctx . Done ( ) )
2018-02-28 01:35:35 +00:00
go s . kubeInformerFactory . Start ( s . ctx . Done ( ) )
go s . podInformer . Run ( s . ctx . Done ( ) )
2018-06-22 19:07:23 +00:00
go s . secretInformer . Run ( s . ctx . Done ( ) )
2018-02-28 01:35:35 +00:00
2020-06-24 16:55:18 +00:00
// TODO(2.0): presuming all controllers and resources are converted to runtime-controller
// by v2.0, the block from this line and including the `s.mgr.Start() will be
// deprecated, since the manager auto-starts all the caches. Until then, we need to start the
// cache for them manually.
2018-02-28 01:35:35 +00:00
2020-06-24 16:55:18 +00:00
// Adding the controllers to the manager will register them as a (runtime-controller) runnable,
// so the manager will ensure the cache is started and ready before all controller are started
s . mgr . Add ( managercontroller . Runnable ( backupController , 1 ) )
s . mgr . Add ( managercontroller . Runnable ( restoreController , 1 ) )
2018-02-28 01:35:35 +00:00
2020-06-24 16:55:18 +00:00
s . logger . Info ( "Controllers starting..." )
if err := s . mgr . Start ( ctrl . SetupSignalHandler ( ) ) ; err != nil {
s . logger . Fatal ( "Problem starting manager" , err )
}
2018-02-28 01:35:35 +00:00
}
2019-07-03 20:20:32 +00:00
// validatePodVolumesHostPath validates that the pod volumes path contains a
// directory for each Pod running on this node
func ( s * resticServer ) validatePodVolumesHostPath ( ) error {
files , err := s . fileSystem . ReadDir ( "/host_pods/" )
if err != nil {
return errors . Wrap ( err , "could not read pod volumes host path" )
}
// create a map of directory names inside the pod volumes path
dirs := sets . NewString ( )
for _ , f := range files {
if f . IsDir ( ) {
dirs . Insert ( f . Name ( ) )
}
}
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
pods , err := s . kubeClient . CoreV1 ( ) . Pods ( "" ) . List ( s . ctx , metav1 . ListOptions { FieldSelector : fmt . Sprintf ( "spec.nodeName=%s,status.phase=Running" , os . Getenv ( "NODE_NAME" ) ) } )
2019-07-03 20:20:32 +00:00
if err != nil {
return errors . WithStack ( err )
}
valid := true
for _ , pod := range pods . Items {
dirName := string ( pod . GetUID ( ) )
// if the pod is a mirror pod, the directory name is the hash value of the
// mirror pod annotation
if hash , ok := pod . GetAnnotations ( ) [ v1 . MirrorPodAnnotationKey ] ; ok {
dirName = hash
}
if ! dirs . Has ( dirName ) {
valid = false
s . logger . WithFields ( logrus . Fields {
"pod" : fmt . Sprintf ( "%s/%s" , pod . GetNamespace ( ) , pod . GetName ( ) ) ,
"path" : "/host_pods/" + dirName ,
} ) . Debug ( "could not find volumes for pod in host path" )
}
}
if ! valid {
return errors . New ( "unexpected directory structure for host-pods volume, ensure that the host-pods volume corresponds to the pods subdirectory of the kubelet root directory" )
}
return nil
}