mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-19 04:26:34 +00:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
5c48ac1a70 | |||
7c166979c9 | |||
36843ad1d4 | |||
3bc041ba04 | |||
dd3f9732b4 | |||
6c10d18f59 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ hosts
|
||||
basic-auth-user
|
||||
basic-auth-password
|
||||
/bin
|
||||
/secrets
|
||||
|
38
README.md
38
README.md
@ -1,6 +1,8 @@
|
||||
# faasd - serverless with containerd
|
||||
|
||||
[](https://travis-ci.com/alexellis/faasd)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](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 of faasd running in KVM
|
||||
|
||||
## What does faasd deploy?
|
||||
|
||||
* faasd - itself, and its [faas-provider](https://github.com/openfaas/faas-provider)
|
||||
@ -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
|
||||
@ -84,7 +92,13 @@ 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/)
|
||||
|
||||
## Get containerd
|
||||
## Tutorial: Multipass & KVM for MacOS/Linux, or Windows (with cloud-config)
|
||||
|
||||
* [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.
|
||||
|
||||
@ -136,7 +150,7 @@ Start containerd in a new terminal:
|
||||
```sh
|
||||
sudo containerd &
|
||||
```
|
||||
### Enable forwarding
|
||||
#### Enable forwarding
|
||||
|
||||
> This is required to allow containers in containerd to access the Internet via your computer's primary network interface.
|
||||
|
||||
@ -150,9 +164,9 @@ Make the setting permanent:
|
||||
echo "net.ipv4.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
|
||||
```
|
||||
|
||||
## Hacking (build from source)
|
||||
### Hacking (build from source)
|
||||
|
||||
### Get build packages
|
||||
#### Get build packages
|
||||
|
||||
```sh
|
||||
sudo apt update \
|
||||
@ -163,7 +177,7 @@ sudo apt update \
|
||||
|
||||
You may find alternatives for CentOS and other distributions.
|
||||
|
||||
### Install Go 1.13 (x86_64)
|
||||
#### Install Go 1.13 (x86_64)
|
||||
|
||||
```sh
|
||||
curl -sSLf https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz > go.tgz
|
||||
@ -177,7 +191,7 @@ export PATH=$PATH:/usr/local/go/bin/
|
||||
go version
|
||||
```
|
||||
|
||||
### Or on Raspberry Pi (armhf)
|
||||
#### Or on Raspberry Pi (armhf)
|
||||
|
||||
```sh
|
||||
curl -SLsf https://dl.google.com/go/go1.13.6.linux-armv6l.tar.gz > go.tgz
|
||||
@ -191,7 +205,7 @@ export PATH=$PATH:/usr/local/go/bin/
|
||||
go version
|
||||
```
|
||||
|
||||
### Install the CNI plugins:
|
||||
#### Install the CNI plugins:
|
||||
|
||||
* For PC run `export ARCH=amd64`
|
||||
* For RPi/armhf run `export ARCH=arm`
|
||||
@ -220,26 +234,26 @@ go build
|
||||
# sudo ./faasd up
|
||||
```
|
||||
|
||||
### Build and run `faasd` (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 `/var/lib/faasd/` to get the IP for the gateway or Prometheus
|
||||
|
||||
|
@ -16,7 +16,7 @@ runcmd:
|
||||
- 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.6.1/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
|
||||
- curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.6.2/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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
111
pkg/provider/handlers/secret.go
Normal file
111
pkg/provider/handlers/secret.go
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Reference in New Issue
Block a user