From be8574ecd0e31bac570baad0b88c69eded49aad7 Mon Sep 17 00:00:00 2001 From: "Alex Ellis (OpenFaaS Ltd)" Date: Sun, 1 Mar 2020 20:02:52 +0000 Subject: [PATCH] Always pull images by default The behaviour prior to this patch caused some confusion for users since they expected a behaviour like Swarm / Kubernetes which always pulls images by default, even if cached. I've tested the change and it is working as expected. By default images are always pulled upon deployment. To revert to the prior behaviour, simply add to faasd up: --pull-policy=IfNotPresent Signed-off-by: Alex Ellis (OpenFaaS Ltd) --- cmd/provider.go | 156 +++++++++++++++++--------------- cmd/root.go | 2 +- cmd/up.go | 3 +- pkg/provider/handlers/deploy.go | 8 +- pkg/provider/handlers/update.go | 8 +- pkg/service/service.go | 54 ++++++++--- pkg/supervisor.go | 9 +- 7 files changed, 141 insertions(+), 99 deletions(-) diff --git a/cmd/provider.go b/cmd/provider.go index bc9ffb6..327f0b7 100644 --- a/cmd/provider.go +++ b/cmd/provider.go @@ -19,82 +19,96 @@ import ( "github.com/spf13/cobra" ) -var providerCmd = &cobra.Command{ - Use: "provider", - Short: "Run the faasd-provider", - RunE: runProvider, -} - -func runProvider(_ *cobra.Command, _ []string) error { - - config, providerConfig, err := config.ReadFromEnv(types.OsEnv{}) - if err != nil { - return err +func makeProviderCmd() *cobra.Command { + var command = &cobra.Command{ + Use: "provider", + Short: "Run the faasd-provider", } - log.Printf("faasd-provider starting..\tService Timeout: %s\n", config.WriteTimeout.String()) + command.Flags().String("pull-policy", "Always", `Set to "Always" to force a pull of images upon deployment, or "IfNotPresent" to try to use a cached image.`) - wd, err := os.Getwd() - if err != nil { - return err + command.RunE = func(_ *cobra.Command, _ []string) error { + + pullPolicy, flagErr := command.Flags().GetString("pull-policy") + if flagErr != nil { + return flagErr + } + + alwaysPull := false + if pullPolicy == "Always" { + alwaysPull = true + } + + config, providerConfig, err := config.ReadFromEnv(types.OsEnv{}) + if err != nil { + return err + } + + log.Printf("faasd-provider starting..\tService Timeout: %s\n", config.WriteTimeout.String()) + + wd, err := os.Getwd() + if err != nil { + return err + } + + writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"), + []byte(`127.0.0.1 localhost`), workingDirectoryPermission) + + if writeHostsErr != nil { + return fmt.Errorf("cannot write hosts file: %s", writeHostsErr) + } + + writeResolvErr := ioutil.WriteFile(path.Join(wd, "resolv.conf"), + []byte(`nameserver 8.8.8.8`), workingDirectoryPermission) + + if writeResolvErr != nil { + return fmt.Errorf("cannot write resolv.conf file: %s", writeResolvErr) + } + + cni, err := cninetwork.InitNetwork() + if err != nil { + return err + } + + client, err := containerd.New(providerConfig.Sock) + if err != nil { + return err + } + + defer client.Close() + + invokeResolver := handlers.NewInvokeResolver(client) + + userSecretPath := path.Join(wd, "secrets") + + bootstrapHandlers := types.FaaSHandlers{ + FunctionProxy: proxy.NewHandlerFunc(*config, invokeResolver), + DeleteHandler: handlers.MakeDeleteHandler(client, cni), + DeployHandler: handlers.MakeDeployHandler(client, cni, userSecretPath, alwaysPull), + FunctionReader: handlers.MakeReadHandler(client), + ReplicaReader: handlers.MakeReplicaReaderHandler(client), + ReplicaUpdater: handlers.MakeReplicaUpdateHandler(client, cni), + UpdateHandler: handlers.MakeUpdateHandler(client, cni, userSecretPath, alwaysPull), + HealthHandler: func(w http.ResponseWriter, r *http.Request) {}, + InfoHandler: handlers.MakeInfoHandler(Version, GitCommit), + ListNamespaceHandler: listNamespaces(), + SecretHandler: handlers.MakeSecretHandler(client, userSecretPath), + LogHandler: func(w http.ResponseWriter, r *http.Request) { + if r.Body != nil { + defer r.Body.Close() + } + + w.WriteHeader(http.StatusNotImplemented) + w.Write([]byte(`Logs are not implemented for faasd`)) + }, + } + + log.Printf("Listening on TCP port: %d\n", *config.TCPPort) + bootstrap.Serve(&bootstrapHandlers, config) + return nil } - writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"), - []byte(`127.0.0.1 localhost`), workingDirectoryPermission) - - if writeHostsErr != nil { - return fmt.Errorf("cannot write hosts file: %s", writeHostsErr) - } - - writeResolvErr := ioutil.WriteFile(path.Join(wd, "resolv.conf"), - []byte(`nameserver 8.8.8.8`), workingDirectoryPermission) - - if writeResolvErr != nil { - return fmt.Errorf("cannot write resolv.conf file: %s", writeResolvErr) - } - - cni, err := cninetwork.InitNetwork() - if err != nil { - return err - } - - client, err := containerd.New(providerConfig.Sock) - if err != nil { - return err - } - - defer client.Close() - - invokeResolver := handlers.NewInvokeResolver(client) - - userSecretPath := path.Join(wd, "secrets") - - bootstrapHandlers := types.FaaSHandlers{ - FunctionProxy: proxy.NewHandlerFunc(*config, invokeResolver), - DeleteHandler: handlers.MakeDeleteHandler(client, cni), - DeployHandler: handlers.MakeDeployHandler(client, cni, userSecretPath), - FunctionReader: handlers.MakeReadHandler(client), - ReplicaReader: handlers.MakeReplicaReaderHandler(client), - ReplicaUpdater: handlers.MakeReplicaUpdateHandler(client, cni), - UpdateHandler: handlers.MakeUpdateHandler(client, cni, userSecretPath), - HealthHandler: func(w http.ResponseWriter, r *http.Request) {}, - InfoHandler: handlers.MakeInfoHandler(Version, GitCommit), - ListNamespaceHandler: listNamespaces(), - SecretHandler: handlers.MakeSecretHandler(client, userSecretPath), - LogHandler: func(w http.ResponseWriter, r *http.Request) { - if r.Body != nil { - defer r.Body.Close() - } - - w.WriteHeader(http.StatusNotImplemented) - w.Write([]byte(`Logs are not implemented for faasd`)) - }, - } - - log.Printf("Listening on TCP port: %d\n", *config.TCPPort) - bootstrap.Serve(&bootstrapHandlers, config) - - return nil + return command } func listNamespaces() func(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/root.go b/cmd/root.go index a63494a..cb398b4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,7 +14,7 @@ func init() { rootCommand.AddCommand(versionCmd) rootCommand.AddCommand(upCmd) rootCommand.AddCommand(installCmd) - rootCommand.AddCommand(providerCmd) + rootCommand.AddCommand(makeProviderCmd()) rootCommand.AddCommand(collectCmd) } diff --git a/cmd/up.go b/cmd/up.go index 95351f5..326597c 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -14,8 +14,8 @@ import ( "github.com/pkg/errors" - "github.com/openfaas/faasd/pkg" "github.com/alexellis/k3sup/pkg/env" + "github.com/openfaas/faasd/pkg" "github.com/sethvargo/go-password/password" "github.com/spf13/cobra" ) @@ -116,6 +116,7 @@ func runUp(_ *cobra.Command, _ []string) error { log.Println(fileErr) return } + host := "" lines := strings.Split(string(fileData), "\n") for _, line := range lines { diff --git a/pkg/provider/handlers/deploy.go b/pkg/provider/handlers/deploy.go index f382be8..64a95b8 100644 --- a/pkg/provider/handlers/deploy.go +++ b/pkg/provider/handlers/deploy.go @@ -23,7 +23,7 @@ import ( "github.com/pkg/errors" ) -func MakeDeployHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string) func(w http.ResponseWriter, r *http.Request) { +func MakeDeployHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string, alwaysPull bool) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { @@ -54,7 +54,7 @@ func MakeDeployHandler(client *containerd.Client, cni gocni.CNI, secretMountPath name := req.Service ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace) - deployErr := deploy(ctx, req, client, cni, secretMountPath) + deployErr := deploy(ctx, req, client, cni, secretMountPath, alwaysPull) if deployErr != nil { log.Printf("[Deploy] error deploying %s, error: %s\n", name, deployErr) http.Error(w, deployErr.Error(), http.StatusBadRequest) @@ -63,7 +63,7 @@ func MakeDeployHandler(client *containerd.Client, cni gocni.CNI, secretMountPath } } -func deploy(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, cni gocni.CNI, secretMountPath string) error { +func deploy(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, cni gocni.CNI, secretMountPath string, alwaysPull bool) error { r, err := reference.ParseNormalizedNamed(req.Image) if err != nil { return err @@ -75,7 +75,7 @@ func deploy(ctx context.Context, req types.FunctionDeployment, client *container snapshotter = val } - image, err := service.PrepareImage(ctx, client, imgRef, snapshotter) + image, err := service.PrepareImage(ctx, client, imgRef, snapshotter, alwaysPull) if err != nil { return errors.Wrapf(err, "unable to pull image %s", imgRef) } diff --git a/pkg/provider/handlers/update.go b/pkg/provider/handlers/update.go index 088f1b4..4999b86 100644 --- a/pkg/provider/handlers/update.go +++ b/pkg/provider/handlers/update.go @@ -8,15 +8,15 @@ import ( "log" "net/http" - "github.com/openfaas/faasd/pkg/cninetwork" - "github.com/openfaas/faasd/pkg/service" "github.com/containerd/containerd" "github.com/containerd/containerd/namespaces" gocni "github.com/containerd/go-cni" "github.com/openfaas/faas-provider/types" + "github.com/openfaas/faasd/pkg/cninetwork" + "github.com/openfaas/faasd/pkg/service" ) -func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string) func(w http.ResponseWriter, r *http.Request) { +func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string, alwaysPull bool) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { @@ -68,7 +68,7 @@ func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI, secretMountPath return } - deployErr := deploy(ctx, req, client, cni, secretMountPath) + deployErr := deploy(ctx, req, client, cni, secretMountPath, alwaysPull) if deployErr != nil { log.Printf("[Update] error deploying %s, error: %s\n", name, deployErr) http.Error(w, deployErr.Error(), http.StatusBadRequest) diff --git a/pkg/service/service.go b/pkg/service/service.go index f61ea7f..5459d10 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -122,11 +122,12 @@ func getResolver(ctx context.Context, configFile *configfile.ConfigFile) (remote return docker.NewResolver(opts), nil } -func PrepareImage(ctx context.Context, client *containerd.Client, imageName, snapshotter string) (containerd.Image, error) { +func PrepareImage(ctx context.Context, client *containerd.Client, imageName, snapshotter string, pullAlways bool) (containerd.Image, error) { var ( empty containerd.Image resolver remotes.Resolver ) + if _, stErr := os.Stat(filepath.Join(dockerConfigDir, config.ConfigFileName)); stErr == nil { configFile, err := config.Load(dockerConfigDir) if err != nil { @@ -140,22 +141,29 @@ func PrepareImage(ctx context.Context, client *containerd.Client, imageName, sna return empty, stErr } - image, err := client.GetImage(ctx, imageName) - if err != nil { - if !errdefs.IsNotFound(err) { + var image containerd.Image + if pullAlways { + img, err := pullImage(ctx, client, resolver, imageName) + if err != nil { return empty, err } - rOpts := []containerd.RemoteOpt{ - containerd.WithPullUnpack, - } - if resolver != nil { - rOpts = append(rOpts, containerd.WithResolver(resolver)) - } - img, err := client.Pull(ctx, imageName, rOpts...) - if err != nil { - return empty, fmt.Errorf("cannot pull: %s", err) - } + image = img + } else { + + img, err := client.GetImage(ctx, imageName) + if err != nil { + if !errdefs.IsNotFound(err) { + return empty, err + } + img, err := pullImage(ctx, client, resolver, imageName) + if err != nil { + return empty, err + } + image = img + } else { + image = img + } } unpacked, err := image.IsUnpacked(ctx, snapshotter) @@ -171,3 +179,21 @@ func PrepareImage(ctx context.Context, client *containerd.Client, imageName, sna return image, nil } + +func pullImage(ctx context.Context, client *containerd.Client, resolver remotes.Resolver, imageName string) (containerd.Image, error) { + + var empty containerd.Image + + rOpts := []containerd.RemoteOpt{ + containerd.WithPullUnpack, + } + if resolver != nil { + rOpts = append(rOpts, containerd.WithResolver(resolver)) + } + img, err := client.Pull(ctx, imageName, rOpts...) + if err != nil { + return empty, fmt.Errorf("cannot pull: %s", err) + } + + return img, nil +} diff --git a/pkg/supervisor.go b/pkg/supervisor.go index de114f7..96d95ea 100644 --- a/pkg/supervisor.go +++ b/pkg/supervisor.go @@ -8,13 +8,13 @@ import ( "os" "path" - "github.com/openfaas/faasd/pkg/cninetwork" - "github.com/openfaas/faasd/pkg/service" "github.com/containerd/containerd" "github.com/containerd/containerd/cio" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/oci" gocni "github.com/containerd/go-cni" + "github.com/openfaas/faasd/pkg/cninetwork" + "github.com/openfaas/faasd/pkg/service" "github.com/containerd/containerd/namespaces" "github.com/opencontainers/runtime-spec/specs-go" @@ -24,7 +24,8 @@ const ( defaultSnapshotter = "overlayfs" workingDirectoryPermission = 0644 // faasdNamespace is the containerd namespace services are created - faasdNamespace = "default" + faasdNamespace = "default" + faasServicesPullAlways = false ) type Service struct { @@ -88,7 +89,7 @@ func (s *Supervisor) Start(svcs []Service) error { for _, svc := range svcs { fmt.Printf("Preparing: %s with image: %s\n", svc.Name, svc.Image) - img, err := service.PrepareImage(ctx, s.client, svc.Image, defaultSnapshotter) + img, err := service.PrepareImage(ctx, s.client, svc.Image, defaultSnapshotter, faasServicesPullAlways) if err != nil { return err }