Compare commits

..

3 Commits

Author SHA1 Message Date
294ef0f17f Fix error handling
An error could be thrown here if the status was nil

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2021-01-04 11:19:37 +00:00
32c00f0e9e Use the openfaas namespace for core services
All services like docker and k8s.io use their own namespaces
for core services, this change moves openfaas services into
the openfaas namespace instead of the default one.

The main change is that logs will look like:

journalctl -t openfaas:gateway

Instead of "default:gateway"

Function logs will remain unaffected and scheduled in the
openfaas-fn namespace.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2021-01-04 10:54:12 +00:00
2533c065bf Add user support for custom containers
Custom containers in the compose file can have a directory
mounted to store state for things like a database. This requires
a specific user since influxdb/postgresql and other containers
create folders and update permissions on start-up.

Tested with influxdb on Ubuntu with userid 1000, which failed
before the change.

Adds a grace period in the e2e tests.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2021-01-04 09:55:37 +00:00
5 changed files with 64 additions and 31 deletions

View File

@ -62,4 +62,5 @@ test-e2e:
sleep 3 sleep 3
/usr/local/bin/faas-cli list /usr/local/bin/faas-cli list
sleep 3 sleep 3
/usr/local/bin/faas-cli logs figlet --follow=false | grep Forking journalctl -t openfaas-fn:figlet --no-pager
/usr/local/bin/faas-cli logs figlet --since 15m --follow=false | grep Forking

View File

@ -125,14 +125,16 @@ echo logs | faas-cli invoke figlet
Core services as defined in the docker-compose.yaml file are deployed as containers by faasd. Core services as defined in the docker-compose.yaml file are deployed as containers by faasd.
The namespace is `openfaas` for core services.
View the logs for a component by giving its NAME: View the logs for a component by giving its NAME:
```bash ```bash
journalctl -t default:NAME journalctl -t openfaas:NAME
journalctl -t default:gateway journalctl -t openfaas:gateway
journalctl -t default:queue-worker journalctl -t openfaas:queue-worker
``` ```
You can also use `-f` to follow the logs, or `--lines` to tail a number of lines, or `--since` to give a timeframe. You can also use `-f` to follow the logs, or `--lines` to tail a number of lines, or `--since` to give a timeframe.

View File

@ -3,4 +3,11 @@ package pkg
const ( const (
// FunctionNamespace is the default containerd namespace functions are created // FunctionNamespace is the default containerd namespace functions are created
FunctionNamespace = "openfaas-fn" FunctionNamespace = "openfaas-fn"
// faasdNamespace is the containerd namespace services are created
faasdNamespace = "openfaas"
faasServicesPullAlways = false
defaultSnapshotter = "overlayfs"
) )

View File

@ -27,31 +27,34 @@ func Remove(ctx context.Context, client *containerd.Client, name string) error {
container, containerErr := client.LoadContainer(ctx, name) container, containerErr := client.LoadContainer(ctx, name)
if containerErr == nil { if containerErr == nil {
found := true taskFound := true
t, err := container.Task(ctx, nil) t, err := container.Task(ctx, nil)
if err != nil { if err != nil {
if errdefs.IsNotFound(err) { if errdefs.IsNotFound(err) {
found = false taskFound = false
} else { } else {
return fmt.Errorf("unable to get task %s: ", err) return fmt.Errorf("unable to get task %s: ", err)
} }
} }
if found { if taskFound {
status, _ := t.Status(ctx) status, err := t.Status(ctx)
fmt.Printf("Status of %s is: %s\n", name, status.Status)
log.Printf("Need to kill %s\n", name)
err := killTask(ctx, t)
if err != nil { if err != nil {
log.Printf("Unable to get status for: %s, error: %s", name, err.Error())
} else {
log.Printf("Status of %s is: %s\n", name, status.Status)
}
log.Printf("Need to kill task: %s\n", name)
if err = killTask(ctx, t); err != nil {
return fmt.Errorf("error killing task %s, %s, %s", container.ID(), name, err) return fmt.Errorf("error killing task %s, %s, %s", container.ID(), name, err)
} }
} }
err = container.Delete(ctx, containerd.WithSnapshotCleanup) if err := container.Delete(ctx, containerd.WithSnapshotCleanup); err != nil {
if err != nil {
return fmt.Errorf("error deleting container %s, %s, %s", container.ID(), name, err) return fmt.Errorf("error deleting container %s, %s, %s", container.ID(), name, err)
} }
} else { } else {
service := client.SnapshotService("") service := client.SnapshotService("")
key := name + "snapshot" key := name + "snapshot"
@ -70,6 +73,7 @@ func killTask(ctx context.Context, task containerd.Task) error {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(1) wg.Add(1)
var err error var err error
go func() { go func() {
defer wg.Done() defer wg.Done()
if task != nil { if task != nil {
@ -114,6 +118,7 @@ func getResolver(ctx context.Context, configFile *configfile.ConfigFile) (remote
} }
return ac.Username, ac.Password, nil return ac.Username, ac.Password, nil
} }
authOpts := []docker.AuthorizerOpt{docker.WithAuthCreds(credFunc)} authOpts := []docker.AuthorizerOpt{docker.WithAuthCreds(credFunc)}
authorizer := docker.NewDockerAuthorizer(authOpts...) authorizer := docker.NewDockerAuthorizer(authOpts...)
opts := docker.ResolverOptions{ opts := docker.ResolverOptions{
@ -128,7 +133,7 @@ func PrepareImage(ctx context.Context, client *containerd.Client, imageName, sna
resolver remotes.Resolver resolver remotes.Resolver
) )
if _, stErr := os.Stat(filepath.Join(dockerConfigDir, config.ConfigFileName)); stErr == nil { if _, statErr := os.Stat(filepath.Join(dockerConfigDir, config.ConfigFileName)); statErr == nil {
configFile, err := config.Load(dockerConfigDir) configFile, err := config.Load(dockerConfigDir)
if err != nil { if err != nil {
return nil, err return nil, err
@ -137,8 +142,8 @@ func PrepareImage(ctx context.Context, client *containerd.Client, imageName, sna
if err != nil { if err != nil {
return empty, err return empty, err
} }
} else if !os.IsNotExist(stErr) { } else if !os.IsNotExist(statErr) {
return empty, stErr return empty, statErr
} }
var image containerd.Image var image containerd.Image
@ -150,7 +155,6 @@ func PrepareImage(ctx context.Context, client *containerd.Client, imageName, sna
image = img image = img
} else { } else {
img, err := client.GetImage(ctx, imageName) img, err := client.GetImage(ctx, imageName)
if err != nil { if err != nil {
if !errdefs.IsNotFound(err) { if !errdefs.IsNotFound(err) {
@ -187,9 +191,11 @@ func pullImage(ctx context.Context, client *containerd.Client, resolver remotes.
rOpts := []containerd.RemoteOpt{ rOpts := []containerd.RemoteOpt{
containerd.WithPullUnpack, containerd.WithPullUnpack,
} }
if resolver != nil { if resolver != nil {
rOpts = append(rOpts, containerd.WithResolver(resolver)) rOpts = append(rOpts, containerd.WithResolver(resolver))
} }
img, err := client.Pull(ctx, imageName, rOpts...) img, err := client.Pull(ctx, imageName, rOpts...)
if err != nil { if err != nil {
return empty, fmt.Errorf("cannot pull: %s", err) return empty, fmt.Errorf("cannot pull: %s", err)

View File

@ -26,14 +26,11 @@ import (
) )
const ( const (
defaultSnapshotter = "overlayfs"
workingDirectoryPermission = 0644 workingDirectoryPermission = 0644
// faasdNamespace is the containerd namespace services are created
faasdNamespace = "default"
faasServicesPullAlways = false
) )
type Service struct { type Service struct {
// Image is the container image registry reference, in an OCI format.
Image string Image string
Env []string Env []string
Name string Name string
@ -42,6 +39,10 @@ type Service struct {
Args []string Args []string
DependsOn []string DependsOn []string
Ports []ServicePort Ports []ServicePort
// User in the docker-compose.yaml spec can set as follows:
// a user-id, username, userid:groupid or user:group
User string
} }
type ServicePort struct { type ServicePort struct {
@ -161,12 +162,17 @@ func (s *Supervisor) Start(svcs []Service) error {
Options: []string{"rbind", "ro"}, Options: []string{"rbind", "ro"},
}) })
if len(svc.User) > 0 {
log.Printf("Running %s with user: %q", svc.Name, svc.User)
}
newContainer, err := s.client.NewContainer( newContainer, err := s.client.NewContainer(
ctx, ctx,
svc.Name, svc.Name,
containerd.WithImage(image), containerd.WithImage(image),
containerd.WithNewSnapshot(svc.Name+"-snapshot", image), containerd.WithNewSnapshot(svc.Name+"-snapshot", image),
containerd.WithNewSpec(oci.WithImageConfig(image), containerd.WithNewSpec(oci.WithImageConfig(image),
withUserOrDefault(svc.User),
oci.WithCapabilities(svc.Caps), oci.WithCapabilities(svc.Caps),
oci.WithMounts(mounts), oci.WithMounts(mounts),
withOCIArgs(svc.Args), withOCIArgs(svc.Args),
@ -201,21 +207,21 @@ func (s *Supervisor) Start(svcs []Service) error {
log.Printf("%s has IP: %s\n", newContainer.ID(), ip.String()) log.Printf("%s has IP: %s\n", newContainer.ID(), ip.String())
hosts, _ := ioutil.ReadFile("hosts") hosts, err := ioutil.ReadFile("hosts")
if err != nil {
log.Printf("Unable to read hosts file: %s\n", err.Error())
}
hosts = []byte(string(hosts) + fmt.Sprintf(` hosts = []byte(string(hosts) + fmt.Sprintf(`
%s %s %s %s
`, ip, svc.Name)) `, ip, svc.Name))
writeErr := ioutil.WriteFile("hosts", hosts, workingDirectoryPermission)
if writeErr != nil { if err := ioutil.WriteFile("hosts", hosts, workingDirectoryPermission); err != nil {
log.Printf("Error writing file %s %s\n", "hosts", writeErr) log.Printf("Error writing file: %s %s\n", "hosts", err)
} }
// os.Chown("hosts", 101, 101)
_, err = task.Wait(ctx) if _, err := task.Wait(ctx); err != nil {
if err != nil { log.Printf("Task wait error: %s\n", err)
log.Printf("Wait err: %s\n", err)
return err return err
} }
@ -223,7 +229,7 @@ func (s *Supervisor) Start(svcs []Service) error {
// log.Println("Exited: ", exitStatusC) // log.Println("Exited: ", exitStatusC)
if err = task.Start(ctx); err != nil { if err = task.Start(ctx); err != nil {
log.Printf("Task err: %s\n", err) log.Printf("Task start error: %s\n", err)
return err return err
} }
} }
@ -253,6 +259,16 @@ func (s *Supervisor) Remove(svcs []Service) error {
return nil return nil
} }
func withUserOrDefault(userstr string) oci.SpecOpts {
if len(userstr) > 0 {
return oci.WithUser(userstr)
}
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
return nil
}
}
func withOCIArgs(args []string) oci.SpecOpts { func withOCIArgs(args []string) oci.SpecOpts {
if len(args) > 0 { if len(args) > 0 {
return oci.WithProcessArgs(args...) return oci.WithProcessArgs(args...)
@ -305,6 +321,7 @@ func ParseCompose(config *compose.Config) ([]Service, error) {
Env: env, Env: env,
Mounts: mounts, Mounts: mounts,
DependsOn: s.DependsOn, DependsOn: s.DependsOn,
User: s.User,
Ports: convertPorts(s.Ports), Ports: convertPorts(s.Ports),
} }
} }