mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-23 23:33:23 +00:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
040b426a19 | |||
251cb2d08a | |||
5c48ac1a70 | |||
7c166979c9 | |||
36843ad1d4 | |||
3bc041ba04 | |||
dd3f9732b4 | |||
6c10d18f59 | |||
969fc566e1 | |||
a4710db664 | |||
df2de7ee5c | |||
2d8b2b1f73 | |||
6e5bc27d9a | |||
2eb1df9517 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ hosts
|
|||||||
basic-auth-user
|
basic-auth-user
|
||||||
basic-auth-password
|
basic-auth-password
|
||||||
/bin
|
/bin
|
||||||
|
/secrets
|
||||||
|
3
Makefile
3
Makefile
@ -2,7 +2,7 @@ Version := $(shell git describe --tags --dirty)
|
|||||||
GitCommit := $(shell git rev-parse HEAD)
|
GitCommit := $(shell git rev-parse HEAD)
|
||||||
LDFLAGS := "-s -w -X main.Version=$(Version) -X main.GitCommit=$(GitCommit)"
|
LDFLAGS := "-s -w -X main.Version=$(Version) -X main.GitCommit=$(GitCommit)"
|
||||||
CONTAINERD_VER := 1.3.2
|
CONTAINERD_VER := 1.3.2
|
||||||
CNI_VERSION := v0.8.4
|
CNI_VERSION := v0.8.5
|
||||||
ARCH := amd64
|
ARCH := amd64
|
||||||
|
|
||||||
.PHONY: all
|
.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 -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
|
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 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 /sbin/sysctl -w net.ipv4.conf.all.forwarding=1
|
||||||
sudo mkdir -p /opt/cni/bin
|
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
|
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
|
||||||
|
141
README.md
141
README.md
@ -1,6 +1,8 @@
|
|||||||
# faasd - serverless with containerd
|
# faasd - serverless with containerd
|
||||||
|
|
||||||
[](https://travis-ci.com/alexellis/faasd)
|
[](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.
|
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 is multi-arch, so works on `x86_64`, armhf and arm64
|
||||||
* faasd downloads, starts and supervises the core components to run OpenFaaS
|
* faasd downloads, starts and supervises the core components to run OpenFaaS
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> Demo of faasd running in KVM
|
||||||
|
|
||||||
## What does faasd deploy?
|
## What does faasd deploy?
|
||||||
|
|
||||||
* faasd - itself, and its [faas-provider](https://github.com/openfaas/faas-provider)
|
* 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.
|
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.
|
[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:
|
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
|
* [ ] 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)
|
* [ ] 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
|
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
|
||||||
@ -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/)
|
* [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 PC run `export ARCH=amd64`
|
||||||
* For RPi/armhf run `export ARCH=arm`
|
* For RPi/armhf run `export ARCH=arm`
|
||||||
@ -95,7 +214,9 @@ Install the CNI plugins:
|
|||||||
Then run:
|
Then run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export CNI_VERSION=v0.8.4
|
export ARCH=amd64
|
||||||
|
export CNI_VERSION=v0.8.5
|
||||||
|
|
||||||
sudo mkdir -p /opt/cni/bin
|
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
|
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,26 +234,26 @@ go build
|
|||||||
# sudo ./faasd up
|
# sudo ./faasd up
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build and run (binaries)
|
#### Build and run `faasd` (binaries)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# For x86_64
|
# 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" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
|
||||||
# armhf
|
# 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" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
|
||||||
# arm64
|
# 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" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/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
|
Look in `hosts` in the current working folder or in `/var/lib/faasd/` to get the IP for the gateway or Prometheus
|
||||||
|
|
||||||
@ -148,7 +269,7 @@ Look in `hosts` in the current working folder or in `/var/lib/faasd/` to get the
|
|||||||
|
|
||||||
The IP addresses are dynamic and may change on every launch.
|
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
|
* Prometheus will run on the Prometheus IP plus port 8080 i.e. http://[prometheus_ip]:9090/targets
|
||||||
|
|
||||||
|
27
cloud-config.txt
Normal file
27
cloud-config.txt
Normal 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
|
@ -18,7 +18,10 @@ var installCmd = &cobra.Command{
|
|||||||
RunE: runInstall,
|
RunE: runInstall,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const workingDirectoryPermission = 0644
|
||||||
|
|
||||||
const faasdwd = "/var/lib/faasd"
|
const faasdwd = "/var/lib/faasd"
|
||||||
|
|
||||||
const faasdProviderWd = "/var/lib/faasd-provider"
|
const faasdProviderWd = "/var/lib/faasd-provider"
|
||||||
|
|
||||||
func runInstall(_ *cobra.Command, _ []string) error {
|
func runInstall(_ *cobra.Command, _ []string) error {
|
||||||
@ -102,7 +105,7 @@ func binExists(folder, name string) error {
|
|||||||
|
|
||||||
func ensureWorkingDir(folder string) error {
|
func ensureWorkingDir(folder string) error {
|
||||||
if _, err := os.Stat(folder); err != nil {
|
if _, err := os.Stat(folder); err != nil {
|
||||||
err = os.MkdirAll(folder, 0600)
|
err = os.MkdirAll(folder, workingDirectoryPermission)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -39,14 +39,14 @@ func runProvider(_ *cobra.Command, _ []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
|
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
|
||||||
[]byte(`127.0.0.1 localhost`), 0644)
|
[]byte(`127.0.0.1 localhost`), workingDirectoryPermission)
|
||||||
|
|
||||||
if writeHostsErr != nil {
|
if writeHostsErr != nil {
|
||||||
return fmt.Errorf("cannot write hosts file: %s", writeHostsErr)
|
return fmt.Errorf("cannot write hosts file: %s", writeHostsErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeResolvErr := ioutil.WriteFile(path.Join(wd, "resolv.conf"),
|
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 {
|
if writeResolvErr != nil {
|
||||||
return fmt.Errorf("cannot write resolv.conf file: %s", writeResolvErr)
|
return fmt.Errorf("cannot write resolv.conf file: %s", writeResolvErr)
|
||||||
@ -66,17 +66,20 @@ func runProvider(_ *cobra.Command, _ []string) error {
|
|||||||
|
|
||||||
invokeResolver := handlers.NewInvokeResolver(client)
|
invokeResolver := handlers.NewInvokeResolver(client)
|
||||||
|
|
||||||
|
userSecretPath := path.Join(wd, "secrets")
|
||||||
|
|
||||||
bootstrapHandlers := types.FaaSHandlers{
|
bootstrapHandlers := types.FaaSHandlers{
|
||||||
FunctionProxy: proxy.NewHandlerFunc(*config, invokeResolver),
|
FunctionProxy: proxy.NewHandlerFunc(*config, invokeResolver),
|
||||||
DeleteHandler: handlers.MakeDeleteHandler(client, cni),
|
DeleteHandler: handlers.MakeDeleteHandler(client, cni),
|
||||||
DeployHandler: handlers.MakeDeployHandler(client, cni),
|
DeployHandler: handlers.MakeDeployHandler(client, cni, userSecretPath),
|
||||||
FunctionReader: handlers.MakeReadHandler(client),
|
FunctionReader: handlers.MakeReadHandler(client),
|
||||||
ReplicaReader: handlers.MakeReplicaReaderHandler(client),
|
ReplicaReader: handlers.MakeReplicaReaderHandler(client),
|
||||||
ReplicaUpdater: handlers.MakeReplicaUpdateHandler(client, cni),
|
ReplicaUpdater: handlers.MakeReplicaUpdateHandler(client, cni),
|
||||||
UpdateHandler: handlers.MakeUpdateHandler(client, cni),
|
UpdateHandler: handlers.MakeUpdateHandler(client, cni, userSecretPath),
|
||||||
HealthHandler: func(w http.ResponseWriter, r *http.Request) {},
|
HealthHandler: func(w http.ResponseWriter, r *http.Request) {},
|
||||||
InfoHandler: handlers.MakeInfoHandler(Version, GitCommit),
|
InfoHandler: handlers.MakeInfoHandler(Version, GitCommit),
|
||||||
ListNamespaceHandler: listNamespaces(),
|
ListNamespaceHandler: listNamespaces(),
|
||||||
|
SecretHandler: handlers.MakeSecretHandler(client, userSecretPath),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Listening on TCP port: %d\n", *config.TCPPort)
|
log.Printf("Listening on TCP port: %d\n", *config.TCPPort)
|
||||||
|
@ -193,7 +193,7 @@ func makeFile(filePath, fileContents string) error {
|
|||||||
return nil
|
return nil
|
||||||
} else if os.IsNotExist(err) {
|
} else if os.IsNotExist(err) {
|
||||||
log.Printf("Writing to: %q\n", filePath)
|
log.Printf("Writing to: %q\n", filePath)
|
||||||
return ioutil.WriteFile(filePath, []byte(fileContents), 0644)
|
return ioutil.WriteFile(filePath, []byte(fileContents), workingDirectoryPermission)
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -161,8 +161,12 @@ func GetIPfromPID(pid int) (*net.IP, error) {
|
|||||||
if addrsErr != nil {
|
if addrsErr != nil {
|
||||||
return nil, fmt.Errorf("unable to find address for veth pair using: %v %s", peerIDs, addrsErr)
|
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
|
// NetID generates the network IF based on task name and task PID
|
||||||
|
@ -60,8 +60,13 @@ func GetFunction(client *containerd.Client, name string) (Function, error) {
|
|||||||
if svc.Status == "running" {
|
if svc.Status == "running" {
|
||||||
replicas = 1
|
replicas = 1
|
||||||
f.pid = task.Pid()
|
f.pid = task.Pid()
|
||||||
|
|
||||||
// Get container IP address
|
// 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()
|
f.IP = ip.String()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -49,6 +49,8 @@ func MakeDeleteHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
|
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
|
||||||
|
|
||||||
|
// TODO: this needs to still happen if the task is paused
|
||||||
if function.replicas != 0 {
|
if function.replicas != 0 {
|
||||||
err = DeleteCNINetwork(ctx, cni, client, name)
|
err = DeleteCNINetwork(ctx, cni, client, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"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) {
|
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
|
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)
|
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
|
||||||
|
|
||||||
deployErr := deploy(ctx, req, client, cni)
|
deployErr := deploy(ctx, req, client, cni, secretMountPath)
|
||||||
if deployErr != nil {
|
if deployErr != nil {
|
||||||
log.Printf("[Deploy] error deploying %s, error: %s\n", name, deployErr)
|
log.Printf("[Deploy] error deploying %s, error: %s\n", name, deployErr)
|
||||||
http.Error(w, deployErr.Error(), http.StatusBadRequest)
|
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
|
imgRef := "docker.io/" + req.Image
|
||||||
if strings.Index(req.Image, ":") == -1 {
|
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)
|
envs := prepareEnv(req.EnvProcess, req.EnvVars)
|
||||||
mounts := getMounts()
|
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
|
name := req.Service
|
||||||
|
|
||||||
container, err := client.NewContainer(
|
container, err := client.NewContainer(
|
||||||
@ -177,3 +190,12 @@ func getMounts() []specs.Mount {
|
|||||||
})
|
})
|
||||||
return mounts
|
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
|
||||||
|
}
|
||||||
|
@ -8,10 +8,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
//OrchestrationIdentifier identifier string for provider orchestration
|
// OrchestrationIdentifier identifier string for provider orchestration
|
||||||
OrchestrationIdentifier = "containerd"
|
OrchestrationIdentifier = "containerd"
|
||||||
//ProviderName name of the provider
|
|
||||||
ProviderName = "faas-containerd"
|
// ProviderName name of the provider
|
||||||
|
ProviderName = "faasd"
|
||||||
)
|
)
|
||||||
|
|
||||||
//MakeInfoHandler creates handler for /system/info endpoint
|
//MakeInfoHandler creates handler for /system/info endpoint
|
||||||
|
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"
|
"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) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = validateSecrets(secretMountPath, req.Secrets)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
|
ctx := namespaces.WithNamespace(context.Background(), FunctionNamespace)
|
||||||
if function.replicas != 0 {
|
if function.replicas != 0 {
|
||||||
err = DeleteCNINetwork(ctx, cni, client, name)
|
err = DeleteCNINetwork(ctx, cni, client, name)
|
||||||
@ -62,7 +67,7 @@ func MakeUpdateHandler(client *containerd.Client, cni gocni.CNI) func(w http.Res
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
deployErr := deploy(ctx, req, client, cni)
|
deployErr := deploy(ctx, req, client, cni, secretMountPath)
|
||||||
if deployErr != nil {
|
if deployErr != nil {
|
||||||
log.Printf("[Update] error deploying %s, error: %s\n", name, deployErr)
|
log.Printf("[Update] error deploying %s, error: %s\n", name, deployErr)
|
||||||
http.Error(w, deployErr.Error(), http.StatusBadRequest)
|
http.Error(w, deployErr.Error(), http.StatusBadRequest)
|
||||||
|
@ -53,8 +53,11 @@ func Remove(ctx context.Context, client *containerd.Client, name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// From Stellar
|
// Adapted from Stellar - https://github.com/stellar
|
||||||
func killTask(ctx context.Context, task containerd.Task) error {
|
func killTask(ctx context.Context, task containerd.Task) error {
|
||||||
|
|
||||||
|
killTimeout := 30 * time.Second
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
var err error
|
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 {
|
if err := task.Kill(ctx, unix.SIGTERM, containerd.WithKillAll); err != nil {
|
||||||
log.Printf("error killing container task: %s", err)
|
log.Printf("error killing container task: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-wait:
|
case <-wait:
|
||||||
task.Delete(ctx)
|
task.Delete(ctx)
|
||||||
return
|
return
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(killTimeout):
|
||||||
if err := task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil {
|
if err := task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil {
|
||||||
log.Printf("error force killing container task: %s", err)
|
log.Printf("error force killing container task: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,11 @@ import (
|
|||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const workingDirectoryPermission = 0644
|
||||||
|
|
||||||
const defaultSnapshotter = "overlayfs"
|
const defaultSnapshotter = "overlayfs"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TODO: CNIBinDir and CNIConfDir should maybe be globally configurable?
|
|
||||||
// CNIBinDir describes the directory where the CNI binaries are stored
|
// CNIBinDir describes the directory where the CNI binaries are stored
|
||||||
CNIBinDir = "/opt/cni/bin"
|
CNIBinDir = "/opt/cni/bin"
|
||||||
// CNIConfDir describes the directory where the CNI plugin's configuration is stored
|
// 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)
|
%s faas-containerd`, ip)
|
||||||
|
|
||||||
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
|
writeHostsErr := ioutil.WriteFile(path.Join(wd, "hosts"),
|
||||||
[]byte(hosts), 0644)
|
[]byte(hosts), workingDirectoryPermission)
|
||||||
|
|
||||||
if writeHostsErr != nil {
|
if writeHostsErr != nil {
|
||||||
return fmt.Errorf("cannot write hosts file: %s", writeHostsErr)
|
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(`
|
hosts = []byte(string(hosts) + fmt.Sprintf(`
|
||||||
%s %s
|
%s %s
|
||||||
`, ip, svc.Name))
|
`, ip, svc.Name))
|
||||||
writeErr := ioutil.WriteFile("hosts", hosts, 0644)
|
writeErr := ioutil.WriteFile("hosts", hosts, workingDirectoryPermission)
|
||||||
|
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
log.Printf("Error writing file %s %s\n", "hosts", writeErr)
|
log.Printf("Error writing file %s %s\n", "hosts", writeErr)
|
||||||
|
Reference in New Issue
Block a user