Compare commits

..

15 Commits
0.6.0 ... 0.7.1

Author SHA1 Message Date
040b426a19 Set all permissions to 0644 vs a mixture
This appeared to prevent the provider's secret code from
creating files in its working directory. The patch makes all
code use the same permission.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-28 12:48:00 +00:00
251cb2d08a Update faasd version
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-28 12:45:45 +00:00
5c48ac1a70 Add secrets support
Adds secrets support and binding of secrets at runtime to
functions. Files are written in plain-text to a 0644 permission
folder which can only be read by root and the containers
requesting the secret through the OpenFaaS API.

Tested by deploying an alpine function using "cat" as its
fprocess.

Happy to revisit at a later date and look into encryption at
rest. This should be on-par with using Kubernetes in its
default unencrypted state.

Fixes: #29

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-28 11:41:54 +00:00
7c166979c9 Update README.md 2020-01-27 09:01:23 +00:00
36843ad1d4 Update README.md 2020-01-26 21:23:12 +00:00
3bc041ba04 Update README.md 2020-01-26 21:19:53 +00:00
dd3f9732b4 Update to latest version of faasd 2020-01-26 21:18:54 +00:00
6c10d18f59 Bump release of faasd for cloud-config example
This version includes a fix for long-running task deletions

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-26 21:08:29 +00:00
969fc566e1 Fix unhandled range error and extend deletion timeout
Fixes a bug when attempting to access a non-existant IP from
GetIPfromPID called via the list API.

Renames the provider from faas-containerd

Updates function deletion grace period to 30s to prevent any
errors in the REST API during a long-running deletion.

Tested on Linux with the figlet function which by default takes
around 5s to delete due to its write_timeout value, the deletion
now blocks rather than throwing an error.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-26 21:03:37 +00:00
a4710db664 Add runc to cloud-config
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-26 12:08:37 +00:00
df2de7ee5c Add cloud-config for use with multipass.run / VMs
Also removes netns from the CI

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-26 12:01:55 +00:00
2d8b2b1f73 Fix Go 1.13 curl command 2020-01-23 21:59:15 +00:00
6e5bc27d9a Add some missing steps from faas-containerd
Adds missing steps from faasd-containerd and from and 
my blog post.
2020-01-23 21:57:22 +00:00
2eb1df9517 Update to latest CNI release for plugins
Required for armhf.
2020-01-23 15:23:18 +00:00
c133b9c4ab Switch data directory to /var/lib/faasd
Fix: #26

Tested e2e on Ubuntu with `x86_64`

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-01-22 16:46:26 +00:00
16 changed files with 361 additions and 52 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ hosts
basic-auth-user
basic-auth-password
/bin
/secrets

View File

@ -2,7 +2,7 @@ Version := $(shell git describe --tags --dirty)
GitCommit := $(shell git rev-parse HEAD)
LDFLAGS := "-s -w -X main.Version=$(Version) -X main.GitCommit=$(GitCommit)"
CONTAINERD_VER := 1.3.2
CNI_VERSION := v0.8.4
CNI_VERSION := v0.8.5
ARCH := amd64
.PHONY: all
@ -22,7 +22,6 @@ prepare-test:
curl -sLSf https://github.com/containerd/containerd/releases/download/v$(CONTAINERD_VER)/containerd-$(CONTAINERD_VER).linux-amd64.tar.gz > /tmp/containerd.tar.gz && sudo tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/ --strip-components=1
curl -SLfs https://raw.githubusercontent.com/containerd/containerd/v1.3.2/containerd.service | sudo tee /etc/systemd/system/containerd.service
sudo systemctl daemon-reload && sudo systemctl start containerd
sudo curl -fSLs "https://github.com/genuinetools/netns/releases/download/v0.5.3/netns-linux-amd64" --output "/usr/local/bin/netns" && sudo chmod a+x "/usr/local/bin/netns"
sudo /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
sudo mkdir -p /opt/cni/bin
curl -sSL https://github.com/containernetworking/plugins/releases/download/$(CNI_VERSION)/cni-plugins-linux-$(ARCH)-$(CNI_VERSION).tgz | sudo tar -xz -C /opt/cni/bin
@ -37,7 +36,7 @@ prepare-test:
.PHONY: test-e2e
test-e2e:
sudo cat /run/faasd/secrets/basic-auth-password | /usr/local/bin/faas-cli login --password-stdin
sudo cat /var/lib/faasd/secrets/basic-auth-password | /usr/local/bin/faas-cli login --password-stdin
/usr/local/bin/faas-cli store deploy figlet --env write_timeout=1s --env read_timeout=1s
sleep 2
/usr/local/bin/faas-cli list -v

149
README.md
View File

@ -1,6 +1,8 @@
# faasd - serverless with containerd
[![Build Status](https://travis-ci.com/alexellis/faasd.svg?branch=master)](https://travis-ci.com/alexellis/faasd)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![OpenFaaS](https://img.shields.io/badge/openfaas-serverless-blue.svg)](https://www.openfaas.com)
faasd is a Golang supervisor that bundles OpenFaaS for use with containerd instead of a container orchestrator like Kubernetes or Docker Swarm.
@ -10,6 +12,10 @@ faasd is a Golang supervisor that bundles OpenFaaS for use with containerd inste
* faasd is multi-arch, so works on `x86_64`, armhf and arm64
* faasd downloads, starts and supervises the core components to run OpenFaaS
![demo](https://pbs.twimg.com/media/EPNQz00W4AEwDxM?format=jpg&name=small)
> Demo of faasd running in KVM
## What does faasd deploy?
* faasd - itself, and its [faas-provider](https://github.com/openfaas/faas-provider)
@ -52,7 +58,7 @@ Other operations are pending development in the provider such as:
For Windows users, install [Git Bash](https://git-scm.com/downloads) along with multipass or vagrant. You can also use WSL1 or WSL2 which provides a Linux environment.
You will also need [containerd v1.3.2](https://github.com/containerd/containerd) and the [CNI plugins v0.8.4](https://github.com/containernetworking/plugins)
You will also need [containerd v1.3.2](https://github.com/containerd/containerd) and the [CNI plugins v0.8.5](https://github.com/containernetworking/plugins)
[faas-cli](https://github.com/openfaas/faas-cli) is optional, but recommended.
@ -60,6 +66,8 @@ Other operations are pending development in the provider such as:
Pending:
* [ ] Add support for using container images in third-party public registries
* [ ] Add support for using container images in private third-party registries
* [ ] Monitor and restart any of the core components at runtime if the container stops
* [ ] Bundle/package/automate installation of containerd - [see bootstrap from k3s](https://github.com/rancher/k3s)
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
@ -75,7 +83,7 @@ Done:
* [x] Clear / remove containers and tasks with SIGTERM / SIGINT
* [x] Determine armhf/arm64 containers to run for gateway
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faasd-provider HTTP API
* [x] Setup custom working directory for faasd `/run/faasd/`
* [x] Setup custom working directory for faasd `/var/lib/faasd/`
* [x] Use CNI to create network namespaces and adapters
## Tutorial: Get started on armhf / Raspberry Pi
@ -84,9 +92,120 @@ You can run this tutorial on your Raspberry Pi, or adapt the steps for a regular
* [faasd - lightweight Serverless for your Raspberry Pi](https://blog.alexellis.io/faasd-for-lightweight-serverless/)
## Hacking (build from source)
## Tutorial: Multipass & KVM for MacOS/Linux, or Windows (with cloud-config)
Install the CNI plugins:
* [Get up and running with your own faasd installation on your Mac/Ubuntu or Windows with cloud-config](https://gist.github.com/alexellis/6d297e678c9243d326c151028a3ad7b9)
## Tutorial: Manual installation
### Get containerd
You have three options - binaries for PC, binaries for armhf, or build from source.
* Install containerd `x86_64` only
```sh
export VER=1.3.2
curl -sLSf https://github.com/containerd/containerd/releases/download/v$VER/containerd-$VER.linux-amd64.tar.gz > /tmp/containerd.tar.gz \
&& sudo tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/ --strip-components=1
containerd -version
```
* Or get my containerd binaries for armhf
Building containerd on armhf is extremely slow.
```sh
curl -sSL https://github.com/alexellis/containerd-armhf/releases/download/v1.3.2/containerd.tgz | sudo tar -xvz --strip-components=2 -C /usr/local/bin/
```
* Or clone / build / install [containerd](https://github.com/containerd/containerd) from source:
```sh
export GOPATH=$HOME/go/
mkdir -p $GOPATH/src/github.com/containerd
cd $GOPATH/src/github.com/containerd
git clone https://github.com/containerd/containerd
cd containerd
git fetch origin --tags
git checkout v1.3.2
make
sudo make install
containerd --version
```
Kill any old containerd version:
```sh
# Kill any old version
sudo killall containerd
sudo systemctl disable containerd
```
Start containerd in a new terminal:
```sh
sudo containerd &
```
#### Enable forwarding
> This is required to allow containers in containerd to access the Internet via your computer's primary network interface.
```sh
sudo /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
```
Make the setting permanent:
```sh
echo "net.ipv4.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
```
### Hacking (build from source)
#### Get build packages
```sh
sudo apt update \
&& sudo apt install -qy \
runc \
bridge-utils
```
You may find alternatives for CentOS and other distributions.
#### Install Go 1.13 (x86_64)
```sh
curl -sSLf https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz > go.tgz
sudo rm -rf /usr/local/go/
sudo mkdir -p /usr/local/go/
sudo tar -xvf go.tgz -C /usr/local/go/ --strip-components=1
export GOPATH=$HOME/go/
export PATH=$PATH:/usr/local/go/bin/
go version
```
#### Or on Raspberry Pi (armhf)
```sh
curl -SLsf https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz > go.tgz
sudo rm -rf /usr/local/go/
sudo mkdir -p /usr/local/go/
sudo tar -xvf go.tgz -C /usr/local/go/ --strip-components=1
export GOPATH=$HOME/go/
export PATH=$PATH:/usr/local/go/bin/
go version
```
#### Install the CNI plugins:
* For PC run `export ARCH=amd64`
* For RPi/armhf run `export ARCH=arm`
@ -95,7 +214,9 @@ Install the CNI plugins:
Then run:
```sh
export CNI_VERSION=v0.8.4
export ARCH=amd64
export CNI_VERSION=v0.8.5
sudo mkdir -p /opt/cni/bin
curl -sSL https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/cni-plugins-linux-${ARCH}-${CNI_VERSION}.tgz | sudo tar -xz -C /opt/cni/bin
```
@ -113,28 +234,28 @@ go build
# sudo ./faasd up
```
### Build and run (binaries)
#### Build and run `faasd` (binaries)
```sh
# For x86_64
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.4/faasd" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.2/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.4.4/faasd-armhf" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.2/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.4.4/faasd-arm64" \
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.2/faasd-arm64" \
-o "/usr/local/bin/faasd" \
&& sudo chmod a+x "/usr/local/bin/faasd"
```
### At run-time
#### At run-time
Look in `hosts` in the current working folder or in `/run/faasd/` to get the IP for the gateway or Prometheus
Look in `hosts` in the current working folder or in `/var/lib/faasd/` to get the IP for the gateway or Prometheus
```sh
127.0.0.1 localhost
@ -148,7 +269,7 @@ Look in `hosts` in the current working folder or in `/run/faasd/` to get the IP
The IP addresses are dynamic and may change on every launch.
Since faasd-provider uses containerd heavily it is not running as a container, but as a stand-alone process. Its port is available via the bridge interface, i.e. openfaas0.
Since faasd-provider uses containerd heavily it is not running as a container, but as a stand-alone process. Its port is available via the bridge interface, i.e. `openfaas0`
* Prometheus will run on the Prometheus IP plus port 8080 i.e. http://[prometheus_ip]:9090/targets
@ -158,8 +279,8 @@ Since faasd-provider uses containerd heavily it is not running as a container, b
* basic-auth
You will then need to get the basic-auth password, it is written to `/run/faasd/secrets/basic-auth-password` if you followed the above instructions.
The default Basic Auth username is `admin`, which is written to `/run/faasd/secrets/basic-auth-user`, if you wish to use a non-standard user then create this file and add your username (no newlines or other characters)
You will then need to get the basic-auth password, it is written to `/var/lib/faasd/secrets/basic-auth-password` if you followed the above instructions.
The default Basic Auth username is `admin`, which is written to `/var/lib/faasd/secrets/basic-auth-user`, if you wish to use a non-standard user then create this file and add your username (no newlines or other characters)
#### Installation with systemd

27
cloud-config.txt Normal file
View File

@ -0,0 +1,27 @@
#cloud-config
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8Q/aUYUr3P1XKVucnO9mlWxOjJm+K01lHJR90MkHC9zbfTqlp8P7C3J26zKAuzHXOeF+VFxETRr6YedQKW9zp5oP7sN+F2gr/pO7GV3VmOqHMV7uKfyUQfq7H1aVzLfCcI7FwN2Zekv3yB7kj35pbsMa1Za58aF6oHRctZU6UWgXXbRxP+B04DoVU7jTstQ4GMoOCaqYhgPHyjEAS3DW0kkPW6HzsvJHkxvVcVlZ/wNJa1Ie/yGpzOzWIN0Ol0t2QT/RSWOhfzO1A2P0XbPuZ04NmriBonO9zR7T1fMNmmtTuK7WazKjQT3inmYRAqU6pe8wfX8WIWNV7OowUjUsv alex@alexr.local
package_update: true
packages:
- runc
runcmd:
- curl -sLSf https://github.com/containerd/containerd/releases/download/v1.3.2/containerd-1.3.2.linux-amd64.tar.gz > /tmp/containerd.tar.gz && tar -xvf /tmp/containerd.tar.gz -C /usr/local/bin/ --strip-components=1
- curl -SLfs https://raw.githubusercontent.com/containerd/containerd/v1.3.2/containerd.service | tee /etc/systemd/system/containerd.service
- systemctl daemon-reload && systemctl start containerd
- /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
- mkdir -p /opt/cni/bin
- curl -sSL https://github.com/containernetworking/plugins/releases/download/v0.8.5/cni-plugins-linux-amd64-v0.8.5.tgz | tar -xz -C /opt/cni/bin
- mkdir -p /go/src/github.com/alexellis/
- cd /go/src/github.com/alexellis/ && git clone https://github.com/alexellis/faasd
- curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.7.0/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- cd /go/src/github.com/alexellis/faasd/ && /usr/local/bin/faasd install
- systemctl status -l containerd --no-pager
- journalctl -u faasd-provider --no-pager
- systemctl status -l faasd-provider --no-pager
- systemctl status -l faasd --no-pager
- curl -sSLf https://cli.openfaas.com | sh
- sleep 5 && journalctl -u faasd --no-pager
- cat /var/lib/faasd/secrets/basic-auth-password | /usr/local/bin/faas-cli login --password-stdin

View File

@ -18,8 +18,11 @@ var installCmd = &cobra.Command{
RunE: runInstall,
}
const faasdwd = "/run/faasd"
const faasdProviderWd = "/run/faasd-provider"
const workingDirectoryPermission = 0644
const faasdwd = "/var/lib/faasd"
const faasdProviderWd = "/var/lib/faasd-provider"
func runInstall(_ *cobra.Command, _ []string) error {
@ -87,7 +90,7 @@ func runInstall(_ *cobra.Command, _ []string) error {
}
fmt.Println(`Login with:
sudo cat /run/faasd/secrets/basic-auth-password | faas-cli login -s`)
sudo cat /var/lib/faasd/secrets/basic-auth-password | faas-cli login -s`)
return nil
}
@ -102,7 +105,7 @@ func binExists(folder, name string) error {
func ensureWorkingDir(folder string) error {
if _, err := os.Stat(folder); err != nil {
err = os.MkdirAll(folder, 0600)
err = os.MkdirAll(folder, workingDirectoryPermission)
if err != nil {
return err
}

View File

@ -39,14 +39,14 @@ func runProvider(_ *cobra.Command, _ []string) error {
}
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
[]byte(`127.0.0.1 localhost`), 0644)
[]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`), 0644)
[]byte(`nameserver 8.8.8.8`), workingDirectoryPermission)
if writeResolvErr != nil {
return fmt.Errorf("cannot write resolv.conf file: %s", writeResolvErr)
@ -66,17 +66,20 @@ func runProvider(_ *cobra.Command, _ []string) error {
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),
DeployHandler: handlers.MakeDeployHandler(client, cni, userSecretPath),
FunctionReader: handlers.MakeReadHandler(client),
ReplicaReader: handlers.MakeReplicaReaderHandler(client),
ReplicaUpdater: handlers.MakeReplicaUpdateHandler(client, cni),
UpdateHandler: handlers.MakeUpdateHandler(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),
}
log.Printf("Listening on TCP port: %d\n", *config.TCPPort)

View File

@ -53,7 +53,7 @@ var defaultCNIConf = fmt.Sprintf(`
}
`, pkg.DefaultNetworkName, pkg.DefaultBridgeName, pkg.DefaultSubnet)
const secretMountDir = "/run/secrets"
const containerSecretMountDir = "/run/secrets"
func runUp(_ *cobra.Command, _ []string) error {
@ -193,7 +193,7 @@ func makeFile(filePath, fileContents string) error {
return nil
} else if os.IsNotExist(err) {
log.Printf("Writing to: %q\n", filePath)
return ioutil.WriteFile(filePath, []byte(fileContents), 0644)
return ioutil.WriteFile(filePath, []byte(fileContents), workingDirectoryPermission)
} else {
return err
}
@ -208,18 +208,18 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
Image: "docker.io/openfaas/basic-auth-plugin:0.18.10" + archSuffix,
Env: []string{
"port=8080",
"secret_mount_path=" + secretMountDir,
"secret_mount_path=" + containerSecretMountDir,
"user_filename=basic-auth-user",
"pass_filename=basic-auth-password",
},
Mounts: []pkg.Mount{
pkg.Mount{
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
Dest: path.Join(secretMountDir, "basic-auth-password"),
Dest: path.Join(containerSecretMountDir, "basic-auth-password"),
},
pkg.Mount{
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-user"),
Dest: path.Join(secretMountDir, "basic-auth-user"),
Dest: path.Join(containerSecretMountDir, "basic-auth-user"),
},
},
Caps: []string{"CAP_NET_RAW"},
@ -257,18 +257,18 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
"faas_nats_port=4222",
"auth_proxy_url=http://basic-auth-plugin:8080/validate",
"auth_proxy_pass_body=false",
"secret_mount_path=" + secretMountDir,
"secret_mount_path=" + containerSecretMountDir,
"scale_from_zero=true",
},
Image: "docker.io/openfaas/gateway:0.18.8" + archSuffix,
Mounts: []pkg.Mount{
pkg.Mount{
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
Dest: path.Join(secretMountDir, "basic-auth-password"),
Dest: path.Join(containerSecretMountDir, "basic-auth-password"),
},
pkg.Mount{
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-user"),
Dest: path.Join(secretMountDir, "basic-auth-user"),
Dest: path.Join(containerSecretMountDir, "basic-auth-user"),
},
},
Caps: []string{"CAP_NET_RAW"},
@ -284,17 +284,17 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
"max_inflight=1",
"write_debug=false",
"basic_auth=true",
"secret_mount_path=" + secretMountDir,
"secret_mount_path=" + containerSecretMountDir,
},
Image: "docker.io/openfaas/queue-worker:0.9.0",
Mounts: []pkg.Mount{
pkg.Mount{
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-password"),
Dest: path.Join(secretMountDir, "basic-auth-password"),
Dest: path.Join(containerSecretMountDir, "basic-auth-password"),
},
pkg.Mount{
Src: path.Join(path.Join(wd, "secrets"), "basic-auth-user"),
Dest: path.Join(secretMountDir, "basic-auth-user"),
Dest: path.Join(containerSecretMountDir, "basic-auth-user"),
},
},
Caps: []string{"CAP_NET_RAW"},

View File

@ -161,8 +161,12 @@ func GetIPfromPID(pid int) (*net.IP, error) {
if addrsErr != nil {
return nil, fmt.Errorf("unable to find address for veth pair using: %v %s", peerIDs, addrsErr)
}
return &addrs[0].CIDRs[0].IP, nil
if len(addrs) > 0 && len(addrs[0].CIDRs) > 0 {
return &addrs[0].CIDRs[0].IP, nil
}
return nil, fmt.Errorf("no IP found for function")
}
// NetID generates the network IF based on task name and task PID

View File

@ -60,8 +60,13 @@ func GetFunction(client *containerd.Client, name string) (Function, error) {
if svc.Status == "running" {
replicas = 1
f.pid = task.Pid()
// Get container IP address
ip, _ := GetIPfromPID(int(task.Pid()))
ip, getIPErr := GetIPfromPID(int(task.Pid()))
if getIPErr != nil {
return Function{}, getIPErr
}
f.IP = ip.String()
}
} else {

View File

@ -49,6 +49,8 @@ func MakeDeleteHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
}
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
// TODO: this needs to still happen if the task is paused
if function.replicas != 0 {
err = DeleteCNINetwork(ctx, cni, client, name)
if err != nil {

View File

@ -22,7 +22,7 @@ import (
"github.com/pkg/errors"
)
func MakeDeployHandler(client *containerd.Client, cni gocni.CNI) func(w http.ResponseWriter, r *http.Request) {
func MakeDeployHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
@ -45,11 +45,15 @@ func MakeDeployHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
return
}
name := req.Service
err = validateSecrets(secretMountPath, req.Secrets)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
name := req.Service
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
deployErr := deploy(ctx, req, client, cni)
deployErr := deploy(ctx, req, client, cni, secretMountPath)
if deployErr != nil {
log.Printf("[Deploy] error deploying %s, error: %s\n", name, deployErr)
http.Error(w, deployErr.Error(), http.StatusBadRequest)
@ -58,7 +62,7 @@ func MakeDeployHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
}
}
func deploy(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, cni gocni.CNI) error {
func deploy(ctx context.Context, req types.FunctionDeployment, client *containerd.Client, cni gocni.CNI, secretMountPath string) error {
imgRef := "docker.io/" + req.Image
if strings.Index(req.Image, ":") == -1 {
@ -81,6 +85,15 @@ func deploy(ctx context.Context, req types.FunctionDeployment, client *container
envs := prepareEnv(req.EnvProcess, req.EnvVars)
mounts := getMounts()
for _, secret := range req.Secrets {
mounts = append(mounts, specs.Mount{
Destination: path.Join("/var/openfaas/secrets", secret),
Type: "bind",
Source: path.Join(secretMountPath, secret),
Options: []string{"rbind", "ro"},
})
}
name := req.Service
container, err := client.NewContainer(
@ -177,3 +190,12 @@ func getMounts() []specs.Mount {
})
return mounts
}
func validateSecrets(secretMountPath string, secrets []string) error {
for _, secret := range secrets {
if _, err := os.Stat(path.Join(secretMountPath, secret)); err != nil {
return fmt.Errorf("unable to find secret: %s", secret)
}
}
return nil
}

View File

@ -8,10 +8,11 @@ import (
)
const (
//OrchestrationIdentifier identifier string for provider orchestration
// OrchestrationIdentifier identifier string for provider orchestration
OrchestrationIdentifier = "containerd"
//ProviderName name of the provider
ProviderName = "faas-containerd"
// ProviderName name of the provider
ProviderName = "faasd"
)
//MakeInfoHandler creates handler for /system/info endpoint

View File

@ -0,0 +1,111 @@
package handlers
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"github.com/containerd/containerd"
"github.com/openfaas/faas-provider/types"
)
const secretFilePermission = 0644
func MakeSecretHandler(c *containerd.Client, mountPath string) func(w http.ResponseWriter, r *http.Request) {
err := os.MkdirAll(mountPath, secretFilePermission)
if err != nil {
log.Printf("Creating path: %s, error: %s\n", mountPath, err)
}
return func(w http.ResponseWriter, r *http.Request) {
if r.Body != nil {
defer r.Body.Close()
}
switch r.Method {
case http.MethodGet:
listSecrets(c, w, r, mountPath)
case http.MethodPost:
createSecret(c, w, r, mountPath)
case http.MethodPut:
createSecret(c, w, r, mountPath)
case http.MethodDelete:
deleteSecret(c, w, r, mountPath)
default:
w.WriteHeader(http.StatusBadRequest)
return
}
}
}
func listSecrets(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
files, err := ioutil.ReadDir(mountPath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
secrets := []types.Secret{}
for _, f := range files {
secrets = append(secrets, types.Secret{Name: f.Name()})
}
bytesOut, _ := json.Marshal(secrets)
w.Write(bytesOut)
}
func createSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
secret, err := parseSecret(r)
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = ioutil.WriteFile(path.Join(mountPath, secret.Name), []byte(secret.Value), secretFilePermission)
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func parseSecret(r *http.Request) (types.Secret, error) {
secret := types.Secret{}
bytesOut, err := ioutil.ReadAll(r.Body)
if err != nil {
return secret, err
}
err = json.Unmarshal(bytesOut, &secret)
return secret, err
}
func deleteSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
secret, err := parseSecret(r)
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = os.Remove(path.Join(mountPath, secret.Name))
if err != nil {
log.Printf("[secret] error %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@ -15,7 +15,7 @@ import (
"github.com/openfaas/faas-provider/types"
)
func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI) func(w http.ResponseWriter, r *http.Request) {
func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI, secretMountPath string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
@ -47,6 +47,11 @@ func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
return
}
err = validateSecrets(secretMountPath, req.Secrets)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
if function.replicas != 0 {
err = DeleteCNINetwork(ctx, cni, client, name)
@ -62,7 +67,7 @@ func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
return
}
deployErr := deploy(ctx, req, client, cni)
deployErr := deploy(ctx, req, client, cni, secretMountPath)
if deployErr != nil {
log.Printf("[Update] error deploying %s, error: %s\n", name, deployErr)
http.Error(w, deployErr.Error(), http.StatusBadRequest)

View File

@ -53,8 +53,11 @@ func Remove(ctx context.Context, client *containerd.Client, name string) error {
return nil
}
// From Stellar
// Adapted from Stellar - https://github.com/stellar
func killTask(ctx context.Context, task containerd.Task) error {
killTimeout := 30 * time.Second
wg := &sync.WaitGroup{}
wg.Add(1)
var err error
@ -69,11 +72,12 @@ func killTask(ctx context.Context, task containerd.Task) error {
if err := task.Kill(ctx, unix.SIGTERM, containerd.WithKillAll); err != nil {
log.Printf("error killing container task: %s", err)
}
select {
case <-wait:
task.Delete(ctx)
return
case <-time.After(5 * time.Second):
case <-time.After(killTimeout):
if err := task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil {
log.Printf("error force killing container task: %s", err)
}

View File

@ -24,10 +24,11 @@ import (
"github.com/opencontainers/runtime-spec/specs-go"
)
const workingDirectoryPermission = 0644
const defaultSnapshotter = "overlayfs"
const (
// TODO: CNIBinDir and CNIConfDir should maybe be globally configurable?
// CNIBinDir describes the directory where the CNI binaries are stored
CNIBinDir = "/opt/cni/bin"
// CNIConfDir describes the directory where the CNI plugin's configuration is stored
@ -90,7 +91,7 @@ func (s *Supervisor) Start(svcs []Service) error {
%s faas-containerd`, ip)
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
[]byte(hosts), 0644)
[]byte(hosts), workingDirectoryPermission)
if writeHostsErr != nil {
return fmt.Errorf("cannot write hosts file: %s", writeHostsErr)
@ -206,7 +207,7 @@ func (s *Supervisor) Start(svcs []Service) error {
hosts = []byte(string(hosts) + fmt.Sprintf(`
%s %s
`, ip, svc.Name))
writeErr := ioutil.WriteFile("hosts", hosts, 0644)
writeErr := ioutil.WriteFile("hosts", hosts, workingDirectoryPermission)
if writeErr != nil {
log.Printf("Error writing file %s %s\n", "hosts", writeErr)