Compare commits

..

6 Commits
0.1.6 ... 0.2.1

Author SHA1 Message Date
d19d8998d8 Fix vendoring for go-execute
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2019-12-24 10:30:24 +00:00
376c8e5d7b Update to latest release
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2019-12-24 10:11:54 +00:00
3ee52c6ed7 Remove tasks and containers on SIGINT/SIGTERM
* Cleans-up and removes faasd containers/tasks when receiving
SIGINT/SIGTERM

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2019-12-24 10:09:56 +00:00
da16bdeee8 Extract services to method
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2019-12-24 09:58:24 +00:00
ad97b6db58 Add systemd utility package
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2019-12-24 09:12:34 +00:00
5bb68e15f5 Check for dependencies before installing
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2019-12-24 09:01:34 +00:00
12 changed files with 463 additions and 90 deletions

10
Gopkg.lock generated
View File

@ -37,6 +37,14 @@
pruneopts = "UT"
revision = "9e921883ac929bbe515b39793ece99ce3a9d7706"
[[projects]]
digest = "1:74860eb071d52337d67e9ffd6893b29affebd026505aa917ec23131576a91a77"
name = "github.com/alexellis/go-execute"
packages = ["pkg/v1"]
pruneopts = "UT"
revision = "961405ea754427780f2151adff607fa740d377f7"
version = "0.3.0"
[[projects]]
digest = "1:386ca0ac781cc1b630b3ed21725759770174140164b3faf3810e6ed6366a970b"
name = "github.com/containerd/containerd"
@ -440,6 +448,7 @@
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/alexellis/go-execute/pkg/v1",
"github.com/containerd/containerd",
"github.com/containerd/containerd/cio",
"github.com/containerd/containerd/containers",
@ -451,6 +460,7 @@
"github.com/spf13/cobra",
"github.com/vishvananda/netlink",
"github.com/vishvananda/netns",
"golang.org/x/sys/unix",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,30 +1,3 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/containerd/containerd"
version = "1.3.2"
@ -37,6 +10,11 @@
name = "github.com/spf13/cobra"
version = "0.0.5"
[[constraint]]
name = "github.com/alexellis/go-execute"
version = "0.3.0"
[prune]
go-tests = true
unused-packages = true

View File

@ -63,21 +63,21 @@ go build && sudo ./faasd
Or get from binaries:
### Build and run faas-containerd
### Build and run
```sh
# For x86_64
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.1.3/faasd" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.2.1/faasd" \
-o "/usr/local/bin/faasd" \
&& sudo chmod a+x "/usr/local/bin/faasd"
# armhf
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.1.3/faasd-armhf" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.2.1/faasd-armhf" \
-o "/usr/local/bin/faasd" \
&& sudo chmod a+x "/usr/local/bin/faasd"
# arm64
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.1.3/faasd-arm64" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.2.1/faasd-arm64" \
-o "/usr/local/bin/faasd" \
&& sudo chmod a+x "/usr/local/bin/faasd"
```

75
cmd/install.go Normal file
View File

@ -0,0 +1,75 @@
package cmd
import (
"fmt"
"os"
"path"
systemd "github.com/alexellis/faasd/pkg/systemd"
"github.com/spf13/cobra"
)
var installCmd = &cobra.Command{
Use: "install",
Short: "Install faasd",
RunE: runInstall,
}
func runInstall(_ *cobra.Command, _ []string) error {
err := binExists("/usr/local/bin/", "faas-containerd")
if err != nil {
return err
}
err = binExists("/usr/local/bin/", "netns")
if err != nil {
return err
}
err = systemd.InstallUnit("faas-containerd")
if err != nil {
return err
}
err = systemd.InstallUnit("faasd")
if err != nil {
return err
}
err = systemd.DaemonReload()
if err != nil {
return err
}
err = systemd.Enable("faas-containerd")
if err != nil {
return err
}
err = systemd.Enable("faasd")
if err != nil {
return err
}
err = systemd.Start("faas-containerd")
if err != nil {
return err
}
err = systemd.Start("faasd")
if err != nil {
return err
}
return nil
}
func binExists(folder, name string) error {
findPath := path.Join(folder, name)
if _, err := os.Stat(findPath); err != nil {
return fmt.Errorf("unable to stat %s, install this binary before continuing", findPath)
}
return nil
}

View File

@ -22,6 +22,7 @@ const WelcomeMessage = "Welcome to faasd"
func init() {
rootCommand.AddCommand(versionCmd)
rootCommand.AddCommand(upCmd)
rootCommand.AddCommand(installCmd)
}
var rootCommand = &cobra.Command{

View File

@ -1,9 +1,13 @@
package cmd
import (
"fmt"
"log"
"os"
"os/signal"
"path"
"sync"
"syscall"
"time"
"github.com/alexellis/faasd/pkg"
@ -18,8 +22,57 @@ var upCmd = &cobra.Command{
func runUp(_ *cobra.Command, _ []string) error {
services := makeServiceDefinitions()
start := time.Now()
supervisor, err := pkg.NewSupervisor("/run/containerd/containerd.sock")
if err != nil {
return err
}
log.Printf("Supervisor created in: %s\n", time.Since(start).String())
start = time.Now()
err = supervisor.Start(services)
if err != nil {
return err
}
defer supervisor.Close()
log.Printf("Supervisor init done in: %s\n", time.Since(start).String())
shutdownTimeout := time.Second * 1
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
log.Printf("faasd: waiting for SIGTERM or SIGINT\n")
<-sig
log.Printf("Signal received.. shutting down server in %s\n", shutdownTimeout.String())
err := supervisor.Remove(services)
if err != nil {
fmt.Println(err)
}
time.AfterFunc(shutdownTimeout, func() {
wg.Done()
})
}()
wg.Wait()
return nil
}
func makeServiceDefinitions() []pkg.Service {
wd, _ := os.Getwd()
svcs := []pkg.Service{
return []pkg.Service{
pkg.Service{
Name: "nats",
Env: []string{""},
@ -71,28 +124,4 @@ func runUp(_ *cobra.Command, _ []string) error {
Caps: []string{"CAP_NET_RAW"},
},
}
start := time.Now()
supervisor, err := pkg.NewSupervisor("/run/containerd/containerd.sock")
if err != nil {
return err
}
log.Printf("Supervisor created in: %s\n", time.Since(start).String())
start = time.Now()
err = supervisor.Start(svcs)
if err != nil {
return err
}
defer supervisor.Close()
log.Printf("Supervisor init done in: %s\n", time.Since(start).String())
time.Sleep(time.Minute * 120)
return nil
}

View File

@ -0,0 +1,12 @@
[Unit]
Description=faasd-containerd
[Service]
MemoryLimit=500M
ExecStart=/usr/local/bin/faas-containerd
Restart=on-failure
RestartSec=10s
WorkingDirectory=/usr/local/bin/
[Install]
WantedBy=multi-user.target

13
hack/faasd.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=faasd
After=faas-containerd.service
[Service]
MemoryLimit=500M
ExecStart={{.Cwd}}/faasd up
Restart=on-failure
RestartSec=10s
WorkingDirectory={{.Cwd}}
[Install]
WantedBy=multi-user.target

View File

@ -44,6 +44,18 @@ func (s *Supervisor) Close() {
defer s.client.Close()
}
func (s *Supervisor) Remove(svcs []Service) error {
ctx := namespaces.WithNamespace(context.Background(), "default")
for _, svc := range svcs {
err := removeContainer(ctx, s.client, svc.Name)
if err != nil {
return err
}
}
return nil
}
func (s *Supervisor) Start(svcs []Service) error {
ctx := namespaces.WithNamespace(context.Background(), "default")
@ -61,7 +73,7 @@ func (s *Supervisor) Start(svcs []Service) error {
images := map[string]containerd.Image{}
for _, svc := range svcs {
fmt.Printf("Preparing: %s\n", svc.Name)
fmt.Printf("Preparing: %s with image: %s\n", svc.Name, svc.Image)
img, err := prepareImage(ctx, s.client, svc.Image)
if err != nil {
@ -70,7 +82,6 @@ func (s *Supervisor) Start(svcs []Service) error {
images[svc.Name] = img
size, _ := img.Size(ctx)
fmt.Printf("Prepare done for: %s, %d bytes\n", svc.Image, size)
}
for _, svc := range svcs {
@ -78,37 +89,9 @@ func (s *Supervisor) Start(svcs []Service) error {
image := images[svc.Name]
container, containerErr := s.client.LoadContainer(ctx, svc.Name)
if containerErr == nil {
found := true
t, err := container.Task(ctx, nil)
if err != nil {
if errdefs.IsNotFound(err) {
found = false
} else {
return fmt.Errorf("unable to get task %s: ", err)
}
}
if found {
status, _ := t.Status(ctx)
fmt.Println("Status:", status.Status)
// if status.Status == containerd.Running {
log.Println("need to kill", svc.Name)
err := killTask(ctx, t)
if err != nil {
return fmt.Errorf("error killing task %s, %s, %s", container.ID(), svc.Name, err)
}
// }
}
err = container.Delete(ctx, containerd.WithSnapshotCleanup)
if err != nil {
return fmt.Errorf("error deleting container %s, %s, %s", container.ID(), svc.Name, err)
}
containerErr := removeContainer(ctx, s.client, svc.Name)
if containerErr != nil {
return containerErr
}
mounts := []specs.Mount{}
@ -185,7 +168,7 @@ func (s *Supervisor) Start(svcs []Service) error {
return err
}
ip := getIP(container.ID(), task.Pid())
ip := getIP(newContainer.ID(), task.Pid())
hosts, _ := ioutil.ReadFile("hosts")
@ -324,3 +307,37 @@ func killTask(ctx context.Context, task containerd.Task) error {
return err
}
func removeContainer(ctx context.Context, client *containerd.Client, name string) error {
container, containerErr := client.LoadContainer(ctx, name)
if containerErr == nil {
found := true
t, err := container.Task(ctx, nil)
if err != nil {
if errdefs.IsNotFound(err) {
found = false
} else {
return fmt.Errorf("unable to get task %s: ", err)
}
}
if found {
status, _ := 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 {
return fmt.Errorf("error killing task %s, %s, %s", container.ID(), name, err)
}
}
err = container.Delete(ctx, containerd.WithSnapshotCleanup)
if err != nil {
return fmt.Errorf("error deleting container %s, %s, %s", container.ID(), name, err)
}
}
return nil
}

100
pkg/systemd/systemd.go Normal file
View File

@ -0,0 +1,100 @@
package systemd
import (
"bytes"
"fmt"
"os"
"path/filepath"
"text/template"
execute "github.com/alexellis/go-execute/pkg/v1"
)
func Enable(unit string) error {
task := execute.ExecTask{Command: "systemctl",
Args: []string{"enable", unit},
StreamStdio: false,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("error executing task %s %v, stderr: %s", task.Command, task.Args, res.Stderr)
}
return nil
}
func Start(unit string) error {
task := execute.ExecTask{Command: "systemctl",
Args: []string{"start", unit},
StreamStdio: false,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("error executing task %s %v, stderr: %s", task.Command, task.Args, res.Stderr)
}
return nil
}
func DaemonReload() error {
task := execute.ExecTask{Command: "systemctl",
Args: []string{"daemon-reload"},
StreamStdio: false,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("error executing task %s %v, stderr: %s", task.Command, task.Args, res.Stderr)
}
return nil
}
func InstallUnit(name string) error {
tmpl, err := template.ParseFiles("./hack/" + name + ".service")
wd, _ := os.Getwd()
var tpl bytes.Buffer
userData := struct {
Cwd string
}{
Cwd: wd,
}
err = tmpl.Execute(&tpl, userData)
if err != nil {
return err
}
err = writeUnit(name+".service", tpl.Bytes())
if err != nil {
return err
}
return nil
}
func writeUnit(name string, data []byte) error {
f, err := os.Create(filepath.Join("/lib/systemd/system", name))
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(data)
return err
}

21
vendor/github.com/alexellis/go-execute/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Inlets
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

117
vendor/github.com/alexellis/go-execute/pkg/v1/exec.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
package execute
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strings"
)
type ExecTask struct {
Command string
Args []string
Shell bool
Env []string
Cwd string
// StreamStdio prints stdout and stderr directly to os.Stdout/err as
// the command runs.
StreamStdio bool
// PrintCommand prints the command before executing
PrintCommand bool
}
type ExecResult struct {
Stdout string
Stderr string
ExitCode int
}
func (et ExecTask) Execute() (ExecResult, error) {
argsSt := ""
if len(et.Args) > 0 {
argsSt = strings.Join(et.Args, " ")
}
if et.PrintCommand {
fmt.Println("exec: ", et.Command, argsSt)
}
var cmd *exec.Cmd
if et.Shell {
var args []string
if len(et.Args) == 0 {
startArgs := strings.Split(et.Command, " ")
script := strings.Join(startArgs, " ")
args = append([]string{"-c"}, fmt.Sprintf("%s", script))
} else {
script := strings.Join(et.Args, " ")
args = append([]string{"-c"}, fmt.Sprintf("%s %s", et.Command, script))
}
cmd = exec.Command("/bin/bash", args...)
} else {
if strings.Index(et.Command, " ") > 0 {
parts := strings.Split(et.Command, " ")
command := parts[0]
args := parts[1:]
cmd = exec.Command(command, args...)
} else {
cmd = exec.Command(et.Command, et.Args...)
}
}
cmd.Dir = et.Cwd
if len(et.Env) > 0 {
cmd.Env = os.Environ()
for _, env := range et.Env {
cmd.Env = append(cmd.Env, env)
}
}
stdoutBuff := bytes.Buffer{}
stderrBuff := bytes.Buffer{}
var stdoutWriters io.Writer
var stderrWriters io.Writer
if et.StreamStdio {
stdoutWriters = io.MultiWriter(os.Stdout, &stdoutBuff)
stderrWriters = io.MultiWriter(os.Stderr, &stderrBuff)
} else {
stdoutWriters = &stdoutBuff
stderrWriters = &stderrBuff
}
cmd.Stdout = stdoutWriters
cmd.Stderr = stderrWriters
startErr := cmd.Start()
if startErr != nil {
return ExecResult{}, startErr
}
exitCode := 0
execErr := cmd.Wait()
if execErr != nil {
if exitError, ok := execErr.(*exec.ExitError); ok {
exitCode = exitError.ExitCode()
}
}
return ExecResult{
Stdout: string(stdoutBuff.Bytes()),
Stderr: string(stderrBuff.Bytes()),
ExitCode: exitCode,
}, nil
}