Compare commits

..

22 Commits
0.9.0 ... 0.9.4

Author SHA1 Message Date
93825e8354 Add null-checking for labels
Fixes an issue introduced in #45 which was undetected. When
users do not pass in "labels" to the deployment - or a valid
empty object, then a nil dereference causes a panic.

Fixes: #101

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-09-11 12:15:33 +01:00
6752a61a95 Proxy the gateway using TCP
There appeared to be an issue with logs appearing #98 and #68

@LucasRoesler spent a considerable amount of time looking into
this and concluded that the faas-provider and approach we are
taking to stream logs from journalctl as a process was
working as expected.

The issue appears to have been with the proxy code and its
use of a HTTP connection. Somewhere within the code, a buffer
was holding onto the data before flushing it 20-30 seconds later

This appeared to users as if the logs were not working at all.

Before fixing, the gateway container was tested by exposing
it over an SSH tunnel and inlets tunnel, both worked as
expected. The updates have been tested on multipass with
Ubuntu 18.04 and a binary built locally.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-09-08 13:19:51 +01:00
16a8d2ac6c Add subdomain variable to terraform
Tested by running against a new DO cluster. Readme updated with the new variable name and a
brief description of it

Signed-off-by: Simon Emms <simon@simonemms.com>
2020-08-25 09:10:06 +01:00
68ac4dfecb Add ignore files
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-08-13 16:18:28 +01:00
c2480ab30a Upgrade versions for terraform scripts
Update containerd, faasd and caddy 2 versions.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-08-13 16:06:18 +01:00
d978c19e23 Upgrade containerd version and add note to cloud-config
* Adds note to change public key in cloud-config
* Upgrades containerd version

Tested on DigitalOcean.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-08-13 14:26:02 +01:00
038b92c5b4 Add faas-cli step
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-08-10 12:05:04 +01:00
f1a1f374d9 Pin containerd service version for dev instructions
The pinned version fixes and issue with containerd timing out
when starting.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-08-10 12:03:12 +01:00
24692466d8 Update start-order for dev instructions
There appeared to be a new error with containerd not having any
kind of network configs. Reported by @LucasRoesler.

containerd is taking a very long time to start with a basic
multipass VM, > 90s. This may be a red herring, but hope the
change will be helpful.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-08-10 11:43:22 +01:00
bdfff4e8c5 Update roadmap and known-issues
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-07-24 09:28:33 +01:00
e3589a4ed1 Correct spelling of Canonical in tutorial
Correct spelling of Canonical in tutorial

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-07-24 08:35:21 +01:00
b865e55c85 Update intro to faasd
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-07-23 11:49:50 +01:00
89a728db16 Add links for containerd
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-07-23 11:46:37 +01:00
2237dfd44d Update verbiage and intro to project
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-07-23 11:44:52 +01:00
4423a5389a Move multipass tutorial into the repo
Taken from here:
https://gist.github.com/alexellis/6d297e678c9243d326c151028a3ad7b9

So that PRs can be sent and updates made.

Closes: #84

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-06-28 10:57:09 +01:00
a6a4502c89 Update roadmap and backlog
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-06-28 10:45:39 +01:00
8b86e00128 Add additional authors
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-06-17 15:23:42 +01:00
3039773fbd Rename mac file to darwin suffix
The mac reference was incorrect, it should be darwin.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-06-17 14:48:00 +01:00
5b92e7793d Move graph logic into package
Graph logic moves into depgraph package and makes internal
fields inaccessible. Completes feedback from @LucasRoesler
from previous PR where the dependency graph was added for 0.9.1

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-06-17 14:33:58 +01:00
88f1aa0433 Update docs for Graph and Node
Updates godoc and adds Add() method instead of using
append on the private slice.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-06-17 13:40:09 +01:00
2b9efd29a0 Add depends_on field for core service ordering
* Adds depends_on fields to compose YAML
* Updates parsing code to copy across depends_on field to
openfaas service from compose service definition
* Adds algorithm and unit tests for finding order
* Applies order to up.go command
* Makes unit testing on MacOS possible through build directives

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
2020-06-17 13:40:09 +01:00
db5312158c Update cloud-config example to 0.9.0 2020-06-10 20:36:53 +01:00
25 changed files with 916 additions and 249 deletions

View File

@ -1,6 +1,8 @@
MIT License
Copyright (c) 2019 Alex Ellis
Copyright (c) 2020 Alex Ellis
Copyright (c) 2020 OpenFaaS Ltd
Copyright (c) 2020 OpenFaas Author(s)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,39 +1,35 @@
# faasd - lightweight OSS serverless 🐳
# faasd - Serverless for everyone else
faasd is built for everyone else, for those who have no desire to manage expensive infrastructure.
[![Build Status](https://travis-ci.com/openfaas/faasd.svg?branch=master)](https://travis-ci.com/openfaas/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)
![Downloads](https://img.shields.io/github/downloads/openfaas/faasd/total)
faasd is the same OpenFaaS experience and ecosystem, but without Kubernetes. Functions and microservices can be deployed anywhere with reduced overheads whilst retaining the portability of containers and cloud-native tooling such as containerd and CNI.
faasd is [OpenFaaS](https://github.com/openfaas/) reimagined, but without the cost and complexity of Kubernetes. It runs on a single host with very modest requirements, making it very fast and easy to manage. Under the hood it uses [containerd](https://containerd.io/) and [Container Networking Interface (CNI)](https://github.com/containernetworking/cni) along with the same core OpenFaaS components from the main project.
## When should you use faasd over OpenFaaS on Kubernetes?
* You have a cost sensitive project - run faasd on a 5-10 USD VPS or on your Raspberry Pi
* When you just need a few functions or microservices, without the cost of a cluster
* When you don't have the bandwidth to learn or manage Kubernetes
* To deploy embedded apps in IoT and edge use-cases
* To shrink-wrap applications for use with a customer or client
faasd does not create the same maintenance burden you'll find with maintaining, upgrading, and securing a Kubernetes cluster. You can deploy it and walk away, in the worst case, just deploy a new VM and deploy your functions again.
## About faasd
* is a single Golang binary
* can be set-up and left alone to run your applications
* is multi-arch, so works on Intel `x86_64` and ARM out the box
* uses the same core components and ecosystem of OpenFaaS
* is multi-arch, so works on Intel `x86_64` and ARM out the box
* can be set-up and left alone to run your applications
![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) for containerd - CRUD for functions and services, implements the OpenFaaS REST API
* [Prometheus](https://github.com/prometheus/prometheus) - for monitoring of services, metrics, scaling and dashboards
* [OpenFaaS Gateway](https://github.com/openfaas/faas/tree/master/gateway) - the UI portal, CLI, and other OpenFaaS tooling can talk to this.
* [OpenFaaS queue-worker for NATS](https://github.com/openfaas/nats-queue-worker) - run your invocations in the background without adding any code. See also: [asynchronous invocations](https://docs.openfaas.com/reference/triggers/#async-nats-streaming)
* [NATS](https://nats.io) for asynchronous processing and queues
You'll also need:
* [CNI](https://github.com/containernetworking/plugins)
* [containerd](https://github.com/containerd/containerd)
* [runc](https://github.com/opencontainers/runc)
You can use the standard [faas-cli](https://github.com/openfaas/faas-cli) along with pre-packaged functions from *the Function Store*, or build your own using any OpenFaaS template.
## Tutorials
### Get started on DigitalOcean, or any other IaaS
@ -42,9 +38,9 @@ If your IaaS supports `user_data` aka "cloud-init", then this guide is for you.
* [Build a Serverless appliance with faasd](https://blog.alexellis.io/deploy-serverless-faasd-with-cloud-init/)
### Run locally on MacOS, Linux, or Windows with Multipass.run
### Run locally on MacOS, Linux, or Windows with multipass
* [Get up and running with your own faasd installation on your Mac/Ubuntu or Windows with cloud-config](https://gist.github.com/alexellis/6d297e678c9243d326c151028a3ad7b9)
* [Get up and running with your own faasd installation on your Mac/Ubuntu or Windows with cloud-config](/docs/MULTIPASS.md)
### Get started on armhf / Raspberry Pi
@ -91,6 +87,22 @@ journalctl -t openfaas-fn:figlet -f &
echo logs | faas-cli invoke figlet
```
## What does faasd deploy?
* faasd - itself, and its [faas-provider](https://github.com/openfaas/faas-provider) for containerd - CRUD for functions and services, implements the OpenFaaS REST API
* [Prometheus](https://github.com/prometheus/prometheus) - for monitoring of services, metrics, scaling and dashboards
* [OpenFaaS Gateway](https://github.com/openfaas/faas/tree/master/gateway) - the UI portal, CLI, and other OpenFaaS tooling can talk to this.
* [OpenFaaS queue-worker for NATS](https://github.com/openfaas/nats-queue-worker) - run your invocations in the background without adding any code. See also: [asynchronous invocations](https://docs.openfaas.com/reference/triggers/#async-nats-streaming)
* [NATS](https://nats.io) for asynchronous processing and queues
You'll also need:
* [CNI](https://github.com/containernetworking/plugins)
* [containerd](https://github.com/containerd/containerd)
* [runc](https://github.com/opencontainers/runc)
You can use the standard [faas-cli](https://github.com/openfaas/faas-cli) along with pre-packaged functions from *the Function Store*, or build your own using any OpenFaaS template.
### Manual / developer instructions
See [here for manual / developer instructions](docs/DEV.md)
@ -109,7 +121,7 @@ For templates built by the community see: `faas-cli template store list`, you ca
### Workshop
[The OpenFaaS workshop](https://github.com/openfaas/workshop/) is a set of 12 self-paced labs and provides a great starting point
[The OpenFaaS workshop](https://github.com/openfaas/workshop/) is a set of 12 self-paced labs and provides a great starting point for learning the features of openfaas. Not all features will be available or usable with faasd.
### Community support
@ -117,7 +129,7 @@ An active community of almost 3000 users awaits you on Slack. Over 250 of those
* [Join Slack](https://slack.openfaas.io/)
## Backlog
## Roadmap
### Supported operations
@ -141,19 +153,26 @@ Other operations are pending development in the provider such as:
* `faas auth` - supported for Basic Authentication, but OAuth2 & OIDC require a patch
## Todo
### Backlog
Pending:
* [ ] Add support for using container images in third-party public registries
* [ ] Add support for using container images in private third-party registries
* [ ] [Store and retrieve annotations in function spec](https://github.com/openfaas/faasd/pull/86) - in progress
* [ ] Offer live rolling-updates, with zero downtime - requires moving to IDs vs. names for function containers
* [ ] An installer for faasd and dependencies - runc, containerd
* [ ] 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
* [ ] Provide [simple Caddyfile example](https://blog.alexellis.io/https-inlets-local-endpoints/) in the README showing how to expose the faasd proxy on port 80/443 with TLS
Done:
### Known-issues
* [ ] [containerd can't pull image from Github Docker Package Registry](https://github.com/containerd/containerd/issues/3291)
### Completed
* [x] Provide a cloud-init configuration for faasd bootstrap
* [x] Configure core services from a docker-compose.yaml file
* [x] Store and fetch logs from the journal
* [x] Add support for using container images in third-party public registries
* [x] Add support for using container images in private third-party registries
* [x] Provide a cloud-config.txt file for automated deployments of `faasd`
* [x] Inject / manage IPs between core components for service to service communication - i.e. so Prometheus can scrape the OpenFaaS gateway - done via `/etc/hosts` mount
* [x] Add queue-worker and NATS

View File

@ -1,5 +1,6 @@
#cloud-config
ssh_authorized_keys:
## Note: Replace with your own public key
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8Q/aUYUr3P1XKVucnO9mlWxOjJm+K01lHJR90MkHC9zbfTqlp8P7C3J26zKAuzHXOeF+VFxETRr6YedQKW9zp5oP7sN+F2gr/pO7GV3VmOqHMV7uKfyUQfq7H1aVzLfCcI7FwN2Zekv3yB7kj35pbsMa1Za58aF6oHRctZU6UWgXXbRxP+B04DoVU7jTstQ4GMoOCaqYhgPHyjEAS3DW0kkPW6HzsvJHkxvVcVlZ/wNJa1Ie/yGpzOzWIN0Ol0t2QT/RSWOhfzO1A2P0XbPuZ04NmriBonO9zR7T1fMNmmtTuK7WazKjQT3inmYRAqU6pe8wfX8WIWNV7OowUjUsv alex@alexr.local
package_update: true
@ -8,21 +9,21 @@ 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
- curl -sLSf https://github.com/containerd/containerd/releases/download/v1.3.5/containerd-1.3.5-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.5/containerd.service | tee /etc/systemd/system/containerd.service
- systemctl daemon-reload && systemctl start containerd
- systemctl enable 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/openfaas/
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.2/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd && git checkout 0.9.2
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.9.2/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- cd /go/src/github.com/openfaas/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
- sleep 60 && journalctl -u faasd --no-pager
- cat /var/lib/faasd/secrets/basic-auth-password | /usr/local/bin/faas-cli login --password-stdin

View File

@ -17,6 +17,7 @@ services:
target: /run/secrets/basic-auth-user
cap_add:
- CAP_NET_RAW
nats:
image: docker.io/library/nats-streaming:0.11.2
command:
@ -25,6 +26,7 @@ services:
- "8222"
- "--store=memory"
- "--cluster_id=faas-cluster"
prometheus:
image: docker.io/prom/prometheus:v2.14.0
volumes:
@ -33,6 +35,7 @@ services:
target: /etc/prometheus/prometheus.yml
cap_add:
- CAP_NET_RAW
gateway:
image: "docker.io/openfaas/gateway:0.18.17${ARCH_SUFFIX}"
environment:
@ -58,6 +61,11 @@ services:
target: /run/secrets/basic-auth-user
cap_add:
- CAP_NET_RAW
depends_on:
- basic-auth-plugin
- nats
- prometheus
queue-worker:
image: docker.io/openfaas/queue-worker:0.11.2
environment:
@ -80,3 +88,5 @@ services:
target: /run/secrets/basic-auth-user
cap_add:
- CAP_NET_RAW
depends_on:
- nats

View File

@ -14,10 +14,67 @@
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.5](https://github.com/containernetworking/plugins)
You will also need [containerd v1.3.5](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.
If you're using multipass, then allocate sufficient resources:
```sh
multipass launch \
--mem 4G \
-c 2 \
-n faasd
# Then access its shell
multipass shell faasd
```
### Get runc
```sh
sudo apt update \
&& sudo apt install -qy \
runc \
bridge-utils \
make
```
### Get faas-cli (optional)
Having `faas-cli` on your dev machine is useful for testing and debug.
```bash
curl -sLS https://cli.openfaas.com | sudo sh
```
#### Install the CNI plugins:
* For PC run `export ARCH=amd64`
* For RPi/armhf run `export ARCH=arm`
* For arm64 run `export ARCH=arm64`
Then run:
```sh
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
# Make a config folder for CNI definitions
sudo mkdir -p /etc/cni/net.d
# Make an initial loopback configuration
sudo sh -c 'cat >/etc/cni/net.d/99-loopback.conf <<-EOF
{
"cniVersion": "0.3.1",
"type": "loopback"
}
EOF'
```
### Get containerd
You have three options - binaries for PC, binaries for armhf, or build from source.
@ -25,8 +82,8 @@ You have three options - binaries for PC, binaries for armhf, or build from sour
* 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 \
export VER=1.3.5
curl -sSL 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
@ -37,7 +94,7 @@ containerd -version
Building `containerd` on armhf is extremely slow, so I've provided binaries for you.
```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/
curl -sSL https://github.com/alexellis/containerd-armhf/releases/download/v1.3.5/containerd.tgz | sudo tar -xvz --strip-components=2 -C /usr/local/bin/
```
* Or clone / build / install [containerd](https://github.com/containerd/containerd) from source:
@ -49,7 +106,7 @@ containerd -version
git clone https://github.com/containerd/containerd
cd containerd
git fetch origin --tags
git checkout v1.3.2
git checkout v1.3.5
make
sudo make install
@ -60,7 +117,11 @@ containerd -version
#### Ensure containerd is running
```sh
curl -sLS https://raw.githubusercontent.com/containerd/containerd/master/containerd.service > /tmp/containerd.service
curl -sLS https://raw.githubusercontent.com/containerd/containerd/v1.3.5/containerd.service > /tmp/containerd.service
# Extend the timeouts for low-performance VMs
echo "[Manager]" | tee -a /tmp/containerd.service
echo "DefaultTimeoutStartSec=3m" | tee -a /tmp/containerd.service
sudo cp /tmp/containerd.service /lib/systemd/system/
sudo systemctl enable containerd
@ -69,7 +130,7 @@ sudo systemctl daemon-reload
sudo systemctl restart containerd
```
Or run ad-hoc:
Or run ad-hoc. This step can be useful for exploring why containerd might fail to start.
```sh
sudo containerd &
@ -106,10 +167,10 @@ You may find alternative package names for CentOS and other Linux distributions.
#### Install Go 1.13 (x86_64)
```sh
curl -sSLf https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz > go.tgz
curl -sSLf https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz > /tmp/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
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
export GOPATH=$HOME/go/
export PATH=$PATH:/usr/local/go/bin/
@ -120,8 +181,8 @@ go version
You should also add the following to `~/.bash_profile`:
```sh
export GOPATH=$HOME/go/
export PATH=$PATH:/usr/local/go/bin/
echo "export GOPATH=\$HOME/go/" | tee -a $HOME/.bash_profile
echo "export PATH=\$PATH:/usr/local/go/bin/" | tee -a $HOME/.bash_profile
```
#### Or on Raspberry Pi (armhf)
@ -138,22 +199,6 @@ 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`
* For arm64 run `export ARCH=arm64`
Then run:
```sh
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
```
#### Clone faasd and its systemd unit files
```sh
@ -168,9 +213,12 @@ git clone https://github.com/openfaas/faasd
cd $GOPATH/src/github.com/openfaas/faasd
cd faasd
make local
# Install the binary
sudo cp bin/faasd /usr/local/bin
```
#### Build and run `faasd` (binaries)
#### Or, download and run `faasd` (binaries)
```sh
# For x86_64
@ -191,9 +239,9 @@ sudo mv /tmp/faasd /usr/local/bin/
#### Install `faasd`
This step installs faasd as a systemd unit file, creates files in `/var/lib/faasd`, and writes out networking configuration for the CNI bridge networking plugin.
```sh
# Install with systemd
sudo cp bin/faasd /usr/local/bin
sudo faasd install
2020/02/17 17:38:06 Writing to: "/var/lib/faasd/secrets/basic-auth-password"

141
docs/MULTIPASS.md Normal file
View File

@ -0,0 +1,141 @@
# Tutorial - faasd with multipass
## Get up and running with your own faasd installation on your Mac
[multipass from Canonical](https://multipass.run) is like Docker Desktop, but for getting Ubuntu instead of a Docker daemon. It works on MacOS, Linux, and Windows with the same consistent UX. It's not fully open-source, and uses some proprietary add-ons / binaries, but is free to use.
For Linux using Ubuntu, you can install the packages directly, or use `sudo snap install multipass --classic` and follow this tutorial. For Raspberry Pi, [see my tutorial here](https://blog.alexellis.io/faasd-for-lightweight-serverless/).
John McCabe has also tested faasd on Windows with multipass, [see his tweet](https://twitter.com/mccabejohn/status/1221899154672308224).
## Use-case:
Try out [faasd](https://github.com/openfaas/faasd) in a single command using a cloud-config file to get a VM which has:
* port 22 for administration and
* port 8080 for the OpenFaaS REST API.
![Example](https://pbs.twimg.com/media/EPNQz00W4AEwDxM?format=jpg&name=medium)
The above screenshot is [from my tweet](https://twitter.com/alexellisuk/status/1221408788395298819/), feel free to comment there.
It took me about 2-3 minutes to run through everything after installing multipass.
## Let's start the tutorial
* Get [multipass.run](https://multipass.run)
* Get my cloud-config.txt file
```sh
curl -sSLO https://raw.githubusercontent.com/openfaas/faasd/master/cloud-config.txt
```
* Update the SSH key to match your own, edit `cloud-config.txt`:
Replace the 2nd line with the contents of `~/.ssh/id_rsa.pub`:
```
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8Q/aUYUr3P1XKVucnO9mlWxOjJm+K01lHJR90MkHC9zbfTqlp8P7C3J26zKAuzHXOeF+VFxETRr6YedQKW9zp5oP7sN+F2gr/pO7GV3VmOqHMV7uKfyUQfq7H1aVzLfCcI7FwN2Zekv3yB7kj35pbsMa1Za58aF6oHRctZU6UWgXXbRxP+B04DoVU7jTstQ4GMoOCaqYhgPHyjEAS3DW0kkPW6HzsvJHkxvVcVlZ/wNJa1Ie/yGpzOzWIN0Ol0t2QT/RSWOhfzO1A2P0XbPuZ04NmriBonO9zR7T1fMNmmtTuK7WazKjQT3inmYRAqU6pe8wfX8WIWNV7OowUjUsv alex@alexr.local
```
* Boot the VM
```sh
multipass launch --cloud-init cloud-config.txt --name faasd
```
* Get the VM's IP and connect with `ssh`
```sh
multipass info faasd
Name: faasd
State: Running
IPv4: 192.168.64.14
Release: Ubuntu 18.04.3 LTS
Image hash: a720c34066dc (Ubuntu 18.04 LTS)
Load: 0.79 0.19 0.06
Disk usage: 1.1G out of 4.7G
Memory usage: 145.6M out of 985.7M
```
Set the variable `IP`:
```
export IP="192.168.64.14"
```
You can also try to use `jq` to get the IP into a variable:
```sh
export IP=$(multipass info faasd --format json| jq '.info.faasd.ipv4[0]' | tr -d '\"')
```
Connect to the IP listed:
```sh
ssh ubuntu@$IP
```
Log out once you know it works.
* Let's capture the authentication password into a file for use with `faas-cli`
```
ssh ubuntu@192.168.64.14 "sudo cat /var/lib/faasd/secrets/basic-auth-password" > basic-auth-password
```
## Try faasd (OpenFaaS)
* Login from your laptop (the host)
```
export OPENFAAS_URL=http://$IP:8080
cat basic-auth-password | faas-cli login -s
```
* Deploy a function and invoke it
```
faas-cli store deploy figlet --env write_timeout=1s
echo "faasd" | faas-cli invoke figlet
faas-cli describe figlet
# Run async
curl -i -d "faasd-async" $OPENFAAS_URL/async-function/figlet
# Run async with a callback
curl -i -d "faasd-async" -H "X-Callback-Url: http://some-request-bin.com/path" $OPENFAAS_URL/async-function/figlet
```
You can also checkout the other store functions: `faas-cli store list`
* Try the UI
Head over to the UI from your laptop and remember that your password is in the `basic-auth-password` file. The username is `admin.:
```
echo http://$IP:8080
```
* Stop/start the instance
```sh
multipass stop faasd
```
* Delete, if you want to:
```
multipass delete --purge faasd
```
You now have a faasd appliance on your Mac. You can also use this cloud-init file with public cloud like AWS or DigitalOcean.
* If you want a public IP for your faasd VM, then just head over to [inlets.dev](https://inlets.dev/)
* Try my more complete walk-through / tutorial with Raspberry Pi, or run the same steps on your multipass VM, including how to develop your own functions and services - https://blog.alexellis.io/faasd-for-lightweight-serverless/
* You might also like [Building containers without Docker](https://blog.alexellis.io/building-containers-without-docker/)
* Star/fork [faasd](https://github.com/openfaas/faasd) on GitHub

3
docs/bootstrap/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/.terraform/
/terraform.tfstate
/terraform.tfstate.backup

View File

@ -1,5 +1,6 @@
#cloud-config
ssh_authorized_keys:
## Note: Replace with your own public key
- ${ssh_key}
package_update: true
@ -8,8 +9,8 @@ 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
- curl -sLSf https://github.com/containerd/containerd/releases/download/v1.3.5/containerd-1.3.5-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.5/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
@ -18,12 +19,12 @@ runcmd:
- mkdir -p /var/lib/faasd/secrets/
- echo ${gw_password} > /var/lib/faasd/secrets/basic-auth-password
- echo admin > /var/lib/faasd/secrets/basic-auth-user
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.1/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd && cd faasd && git checkout 0.9.2
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.9.2/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- cd /go/src/github.com/openfaas/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
- sleep 5 && journalctl -u faasd --no-pager

View File

@ -0,0 +1,3 @@
/.terraform/
/terraform.tfstate
/terraform.tfstate.backup

View File

@ -10,6 +10,7 @@
| ------------ | ------------------- | --------------- |
| `do_token` | Digitalocean API token | None |
| `do_domain` | Public domain used for the faasd gateway | None |
| `do_subdomain` | Public subdomain used for the faasd gateway | `faasd` |
| `letsencrypt_email` | Email used by when ordering TLS certificate from Letsencrypt | `""` |
| `do_create_record` | When set to `true`, a new DNS record will be created. This works only if your domain (`do_domain`) is managed by Digitalocean | `false` |
| `do_region` | Digitalocean region for creating the droplet | `fra1` |

View File

@ -31,8 +31,8 @@ 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
- curl -sLSf https://github.com/containerd/containerd/releases/download/v1.3.5/containerd-1.3.5-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.5/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
@ -41,8 +41,8 @@ runcmd:
- mkdir -p /var/lib/faasd/secrets/
- echo ${gw_password} > /var/lib/faasd/secrets/basic-auth-password
- echo admin > /var/lib/faasd/secrets/basic-auth-user
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.1/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd && cd faasd && git checkout 0.9.2
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.9.2/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
- cd /go/src/github.com/openfaas/faasd/ && /usr/local/bin/faasd install
- systemctl status -l containerd --no-pager
- journalctl -u faasd-provider --no-pager
@ -50,7 +50,7 @@ runcmd:
- systemctl status -l faasd --no-pager
- curl -sSLf https://cli.openfaas.com | sh
- sleep 5 && journalctl -u faasd --no-pager
- wget https://github.com/caddyserver/caddy/releases/download/v2.0.0-rc.2/caddy_2.0.0-rc.2_linux_amd64.tar.gz -O /tmp/caddy.tar.gz && tar -zxvf /tmp/caddy.tar.gz -C /usr/bin/ caddy
- wget https://github.com/caddyserver/caddy/releases/download/v2.1.1/caddy_2.1.1_linux_amd64.tar.gz -O /tmp/caddy.tar.gz && tar -zxvf /tmp/caddy.tar.gz -C /usr/bin/ caddy
- wget https://raw.githubusercontent.com/caddyserver/dist/master/init/caddy.service -O /etc/systemd/system/caddy.service
- systemctl daemon-reload
- systemctl enable caddy

View File

@ -8,6 +8,10 @@ variable "do_token" {
variable "do_domain" {
description = "Your public domain"
}
variable "do_subdomain" {
description = "Your public subdomain"
default = "faasd"
}
variable "letsencrypt_email" {
description = "Email used to order a certificate from Letsencrypt"
}
@ -43,7 +47,7 @@ data "template_file" "cloud_init" {
vars = {
gw_password=random_password.password.result,
ssh_key=data.local_file.ssh_key.content,
faasd_domain_name="faasd.${var.do_domain}"
faasd_domain_name="${var.do_subdomain}.${var.do_domain}"
letsencrypt_email=var.letsencrypt_email
}
}
@ -70,7 +74,7 @@ output "droplet_ip" {
}
output "gateway_url" {
value = "https://faasd.${var.do_domain}/"
value = "https://${var.do_subdomain}.${var.do_domain}/"
}
output "password" {
@ -78,5 +82,5 @@ output "password" {
}
output "login_cmd" {
value = "faas-cli login -g https://faasd.${var.do_domain}/ -p ${random_password.password.result}"
value = "faas-cli login -g https://${var.do_subdomain}.${var.do_domain}/ -p ${random_password.password.result}"
}

View File

@ -1,3 +1,4 @@
do_token = ""
do_domain = ""
do_subdomain = ""
letsencrypt_email = ""

View File

@ -18,19 +18,6 @@ type Dev struct {
CIDRs []*net.IPNet `json:"CIDRs,omitempty"`
}
func linkToNetDev(link netlink.Link) (Dev, error) {
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
if err != nil {
return Dev{}, err
}
netDev := Dev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr}
for _, addr := range addrs {
netDev.CIDRs = append(netDev.CIDRs, addr.IPNet)
}
return netDev, nil
}
// ConnectedToBridgeVethPeerIds returns peer indexes of veth links connected to
// the given bridge. The peer index is used to query from a container netns
// whether the container is connected to the bridge.

View File

@ -0,0 +1,10 @@
// +build darwin
package cninetwork
import "github.com/vishvananda/netlink"
func linkToNetDev(link netlink.Link) (Dev, error) {
return Dev{}, nil
}

View File

@ -0,0 +1,19 @@
// +build linux
package cninetwork
import "github.com/vishvananda/netlink"
func linkToNetDev(link netlink.Link) (Dev, error) {
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
if err != nil {
return Dev{}, err
}
netDev := Dev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr}
for _, addr := range addrs {
netDev.CIDRs = append(netDev.CIDRs, addr.IPNet)
}
return netDev, nil
}

106
pkg/depgraph/depgraph.go Normal file
View File

@ -0,0 +1,106 @@
package depgraph
import "log"
// Node represents a node in a Graph with
// 0 to many edges
type Node struct {
Name string
Edges []*Node
}
// Graph is a collection of nodes
type Graph struct {
nodes []*Node
}
func NewDepgraph() *Graph {
return &Graph{
nodes: []*Node{},
}
}
// Nodes returns the nodes within the graph
func (g *Graph) Nodes() []*Node {
return g.nodes
}
// Contains returns true if the target Node is found
// in its list
func (g *Graph) Contains(target *Node) bool {
for _, g := range g.nodes {
if g.Name == target.Name {
return true
}
}
return false
}
// Add places a Node into the current Graph
func (g *Graph) Add(target *Node) {
g.nodes = append(g.nodes, target)
}
// Remove deletes a target Node reference from the
// list of nodes in the graph
func (g *Graph) Remove(target *Node) {
var found *int
for i, n := range g.nodes {
if n == target {
found = &i
break
}
}
if found != nil {
g.nodes = append(g.nodes[:*found], g.nodes[*found+1:]...)
}
}
// Resolve retruns a list of node names in order of their dependencies.
// A use case may be for determining the correct order to install
// software packages, or to start services.
// Based upon the algorithm described by Ferry Boender in the following article
// https://www.electricmonk.nl/log/2008/08/07/dependency-resolving-algorithm/
func (g *Graph) Resolve() []string {
resolved := &Graph{}
unresolved := &Graph{}
for _, node := range g.nodes {
resolve(node, resolved, unresolved)
}
order := []string{}
for _, node := range resolved.Nodes() {
order = append(order, node.Name)
}
return order
}
// resolve mutates the resolved graph for a given starting
// node. The unresolved graph is used to detect a circular graph
// error and will throw a panic. This can be caught with a resolve
// in a go routine.
func resolve(node *Node, resolved, unresolved *Graph) {
unresolved.Add(node)
for _, edge := range node.Edges {
if !resolved.Contains(edge) && unresolved.Contains(edge) {
log.Panicf("edge: %s may be a circular dependency", edge.Name)
}
resolve(edge, resolved, unresolved)
}
for _, r := range resolved.nodes {
if r.Name == node.Name {
return
}
}
resolved.Add(node)
unresolved.Remove(node)
}

View File

@ -0,0 +1,41 @@
package depgraph
import "testing"
func Test_RemoveMedial(t *testing.T) {
g := Graph{nodes: []*Node{}}
a := &Node{Name: "A"}
b := &Node{Name: "B"}
c := &Node{Name: "C"}
g.nodes = append(g.nodes, a)
g.nodes = append(g.nodes, b)
g.nodes = append(g.nodes, c)
g.Remove(b)
for _, n := range g.nodes {
if n.Name == b.Name {
t.Fatalf("Found deleted node: %s", n.Name)
}
}
}
func Test_RemoveFinal(t *testing.T) {
g := Graph{nodes: []*Node{}}
a := &Node{Name: "A"}
b := &Node{Name: "B"}
c := &Node{Name: "C"}
g.nodes = append(g.nodes, a)
g.nodes = append(g.nodes, b)
g.nodes = append(g.nodes, c)
g.Remove(c)
for _, n := range g.nodes {
if n.Name == c.Name {
t.Fatalf("Found deleted node: %s", c.Name)
}
}
}

41
pkg/deployment_order.go Normal file
View File

@ -0,0 +1,41 @@
package pkg
import (
"log"
"github.com/openfaas/faasd/pkg/depgraph"
)
func buildDeploymentOrder(svcs []Service) []string {
graph := buildServiceGraph(svcs)
order := graph.Resolve()
log.Printf("Start-up order:\n")
for _, node := range order {
log.Printf("- %s\n", node)
}
return order
}
func buildServiceGraph(svcs []Service) *depgraph.Graph {
graph := depgraph.NewDepgraph()
nodeMap := map[string]*depgraph.Node{}
for _, s := range svcs {
n := &depgraph.Node{Name: s.Name}
nodeMap[s.Name] = n
graph.Add(n)
}
for _, s := range svcs {
for _, d := range s.DependsOn {
nodeMap[s.Name].Edges = append(nodeMap[s.Name].Edges, nodeMap[d])
}
}
return graph
}

View File

@ -0,0 +1,224 @@
package pkg
import (
"log"
"testing"
)
func Test_buildDeploymentOrder_ARequiresB(t *testing.T) {
svcs := []Service{
{
Name: "A",
DependsOn: []string{"B"},
},
{
Name: "B",
DependsOn: []string{},
},
}
order := buildDeploymentOrder(svcs)
if len(order) < len(svcs) {
t.Fatalf("length of order too short: %d", len(order))
}
got := order[0]
want := "B"
if got != want {
t.Fatalf("%s should be last to be installed, but was: %s", want, got)
}
}
func Test_buildDeploymentOrder_ARequiresBAndC(t *testing.T) {
svcs := []Service{
{
Name: "A",
DependsOn: []string{"B", "C"},
},
{
Name: "B",
DependsOn: []string{},
},
{
Name: "C",
DependsOn: []string{},
},
}
order := buildDeploymentOrder(svcs)
if len(order) < len(svcs) {
t.Fatalf("length of order too short: %d", len(order))
}
a := indexStr(order, "a")
b := indexStr(order, "b")
c := indexStr(order, "c")
if a > b {
t.Fatalf("a should be after dependencies")
}
if a > c {
t.Fatalf("a should be after dependencies")
}
}
func Test_buildDeploymentOrder_ARequiresBRequiresC(t *testing.T) {
svcs := []Service{
{
Name: "A",
DependsOn: []string{"B"},
},
{
Name: "B",
DependsOn: []string{"C"},
},
{
Name: "C",
DependsOn: []string{},
},
}
order := buildDeploymentOrder(svcs)
if len(order) < len(svcs) {
t.Fatalf("length of order too short: %d", len(order))
}
got := order[0]
want := "C"
if got != want {
t.Fatalf("%s should be last to be installed, but was: %s", want, got)
}
got = order[1]
want = "B"
if got != want {
t.Fatalf("%s should be last to be installed, but was: %s", want, got)
}
got = order[2]
want = "A"
if got != want {
t.Fatalf("%s should be last to be installed, but was: %s", want, got)
}
}
func Test_buildDeploymentOrderCircularARequiresBRequiresA(t *testing.T) {
svcs := []Service{
{
Name: "A",
DependsOn: []string{"B"},
},
{
Name: "B",
DependsOn: []string{"A"},
},
}
defer func() { recover() }()
buildDeploymentOrder(svcs)
t.Fatalf("did not panic as expected")
}
func Test_buildDeploymentOrderComposeFile(t *testing.T) {
// svcs := []Service{}
file, err := LoadComposeFileWithArch("../", "docker-compose.yaml", func() (string, string) {
return "x86_64", "Linux"
})
if err != nil {
t.Fatalf("unable to load compose file: %s", err)
}
svcs, err := ParseCompose(file)
if err != nil {
t.Fatalf("unable to parse compose file: %s", err)
}
for _, s := range svcs {
log.Printf("Service: %s\n", s.Name)
for _, d := range s.DependsOn {
log.Printf("Link: %s => %s\n", s.Name, d)
}
}
order := buildDeploymentOrder(svcs)
if len(order) < len(svcs) {
t.Fatalf("length of order too short: %d", len(order))
}
queueWorker := indexStr(order, "queue-worker")
nats := indexStr(order, "nats")
gateway := indexStr(order, "gateway")
prometheus := indexStr(order, "prometheus")
if prometheus > gateway {
t.Fatalf("Prometheus order was after gateway, and should be before")
}
if nats > gateway {
t.Fatalf("NATS order was after gateway, and should be before")
}
if nats > queueWorker {
t.Fatalf("NATS order was after queue-worker, and should be before")
}
}
func Test_buildDeploymentOrderOpenFaaS(t *testing.T) {
svcs := []Service{
{
Name: "queue-worker",
DependsOn: []string{"nats"},
},
{
Name: "prometheus",
DependsOn: []string{},
},
{
Name: "gateway",
DependsOn: []string{"prometheus", "nats", "basic-auth-plugin"},
},
{
Name: "basic-auth-plugin",
DependsOn: []string{},
},
{
Name: "nats",
DependsOn: []string{},
},
}
order := buildDeploymentOrder(svcs)
if len(order) < len(svcs) {
t.Fatalf("length of order too short: %d", len(order))
}
queueWorker := indexStr(order, "queue-worker")
nats := indexStr(order, "nats")
gateway := indexStr(order, "gateway")
prometheus := indexStr(order, "prometheus")
if prometheus > gateway {
t.Fatalf("Prometheus order was after gateway, and should be before")
}
if nats > gateway {
t.Fatalf("NATS order was after gateway, and should be before")
}
if nats > queueWorker {
t.Fatalf("NATS order was after queue-worker, and should be before")
}
}
func indexStr(st []string, t string) int {
for n, s := range st {
if s == t {
return n
}
}
return -1
}

View File

@ -69,6 +69,7 @@ func deploy(ctx context.Context, req types.FunctionDeployment, client *container
if err != nil {
return err
}
imgRef := reference.TagNameOnly(r).String()
snapshotter := ""
@ -98,6 +99,11 @@ func deploy(ctx context.Context, req types.FunctionDeployment, client *container
name := req.Service
labels := map[string]string{}
if req.Labels != nil {
labels = *req.Labels
}
container, err := client.NewContainer(
ctx,
name,
@ -108,7 +114,7 @@ func deploy(ctx context.Context, req types.FunctionDeployment, client *container
oci.WithCapabilities([]string{"CAP_NET_RAW"}),
oci.WithMounts(mounts),
oci.WithEnv(envs)),
containerd.WithContainerLabels(*req.Labels),
containerd.WithContainerLabels(labels),
)
if err != nil {

View File

@ -1,11 +1,10 @@
package pkg
import (
"context"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"time"
@ -27,6 +26,10 @@ type Proxy struct {
Port int
}
type proxyState struct {
Host string
}
// Start listening and forwarding HTTP to the host
func (p *Proxy) Start(gatewayChan chan string, done chan bool) error {
tcp := p.Port
@ -44,83 +47,37 @@ func (p *Proxy) Start(gatewayChan chan string, done chan bool) error {
fmt.Printf("Gateway: %s\n", ps.Host)
s := &http.Server{
Addr: fmt.Sprintf(":%d", tcp),
ReadTimeout: p.Timeout,
WriteTimeout: p.Timeout,
MaxHeaderBytes: 1 << 20, // Max header of 1MB
Handler: http.HandlerFunc(makeProxy(&ps)),
l, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", tcp))
if err != nil {
log.Printf("Error: %s", err.Error())
return err
}
go func() {
log.Printf("[proxy] Begin listen on %d\n", p.Port)
if err := s.ListenAndServe(); err != http.ErrServerClosed {
log.Printf("Error ListenAndServe: %v", err)
defer l.Close()
for {
// Wait for a connection.
conn, err := l.Accept()
if err != nil {
acceptErr := fmt.Errorf("Unable to accept on %d, error: %s", tcp, err.Error())
log.Printf("%s", acceptErr.Error())
return acceptErr
}
}()
log.Println("[proxy] Wait for done")
<-done
log.Println("[proxy] Done received")
if err := s.Shutdown(context.Background()); err != nil {
log.Printf("[proxy] Error in Shutdown: %v", err)
}
upstream, err := net.Dial("tcp", fmt.Sprintf("%s", ps.Host))
return nil
}
if err != nil {
log.Printf("unable to dial to %s, error: %s", ps.Host, err.Error())
return err
}
go pipe(conn, upstream)
go pipe(upstream, conn)
// copyHeaders clones the header values from the source into the destination.
func copyHeaders(destination http.Header, source *http.Header) {
for k, v := range *source {
vClone := make([]string, len(v))
copy(vClone, v)
destination[k] = vClone
}
}
type proxyState struct {
Host string
}
func makeProxy(ps *proxyState) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
query := ""
if len(r.URL.RawQuery) > 0 {
query = "?" + r.URL.RawQuery
}
upstream := fmt.Sprintf("http://%s%s%s", ps.Host, r.URL.Path, query)
fmt.Printf("[faasd] proxy: %s\n", upstream)
if r.Body != nil {
defer r.Body.Close()
}
wrapper := ioutil.NopCloser(r.Body)
upReq, upErr := http.NewRequest(r.Method, upstream, wrapper)
copyHeaders(upReq.Header, &r.Header)
if upErr != nil {
log.Println(upErr)
http.Error(w, upErr.Error(), http.StatusInternalServerError)
return
}
upRes, upResErr := http.DefaultClient.Do(upReq)
if upResErr != nil {
log.Println(upResErr)
http.Error(w, upResErr.Error(), http.StatusInternalServerError)
return
}
copyHeaders(w.Header(), &upRes.Header)
w.WriteHeader(upRes.StatusCode)
io.Copy(w, upRes.Body)
}
func pipe(from net.Conn, to net.Conn) {
defer from.Close()
io.Copy(from, to)
}

View File

@ -34,12 +34,13 @@ const (
)
type Service struct {
Image string
Env []string
Name string
Mounts []Mount
Caps []string
Args []string
Image string
Env []string
Name string
Mounts []Mount
Caps []string
Args []string
DependsOn []string
}
type Mount struct {
@ -92,7 +93,7 @@ func (s *Supervisor) Start(svcs []Service) error {
images := map[string]containerd.Image{}
for _, svc := range svcs {
fmt.Printf("Preparing: %s with image: %s\n", svc.Name, svc.Image)
fmt.Printf("Preparing %s with image: %s\n", svc.Name, svc.Image)
img, err := service.PrepareImage(ctx, s.client, svc.Image, defaultSnapshotter, faasServicesPullAlways)
if err != nil {
@ -104,12 +105,26 @@ func (s *Supervisor) Start(svcs []Service) error {
}
for _, svc := range svcs {
fmt.Printf("Reconciling: %s\n", svc.Name)
fmt.Printf("Removing old container for: %s\n", svc.Name)
containerErr := service.Remove(ctx, s.client, svc.Name)
if containerErr != nil {
return containerErr
}
}
order := buildDeploymentOrder(svcs)
for _, key := range order {
var svc *Service
for _, s := range svcs {
if s.Name == key {
svc = &s
break
}
}
fmt.Printf("Starting: %s\n", svc.Name)
image := images[svc.Name]
@ -123,7 +138,6 @@ func (s *Supervisor) Start(svcs []Service) error {
Options: []string{"rbind", "rw"},
})
}
}
mounts = append(mounts, specs.Mount{
@ -153,11 +167,11 @@ func (s *Supervisor) Start(svcs []Service) error {
)
if containerCreateErr != nil {
log.Printf("Error creating container %s\n", containerCreateErr)
log.Printf("Error creating container: %s\n", containerCreateErr)
return containerCreateErr
}
log.Printf("Created container %s\n", newContainer.ID())
log.Printf("Created container: %s\n", newContainer.ID())
task, err := newContainer.NewTask(ctx, cio.NewCreator(cio.WithStdio))
if err != nil {
@ -176,6 +190,7 @@ func (s *Supervisor) Start(svcs []Service) error {
if err != nil {
return err
}
log.Printf("%s has IP: %s\n", newContainer.ID(), ip.String())
hosts, _ := ioutil.ReadFile("hosts")
@ -277,10 +292,11 @@ func ParseCompose(config *compose.Config) ([]Service, error) {
Name: s.Name,
Image: s.Image,
// ShellCommand is just an alias of string slice
Args: []string(s.Command),
Caps: s.CapAdd,
Env: env,
Mounts: mounts,
Args: []string(s.Command),
Caps: s.CapAdd,
Env: env,
Mounts: mounts,
DependsOn: s.DependsOn,
}
}
@ -289,6 +305,12 @@ func ParseCompose(config *compose.Config) ([]Service, error) {
// LoadComposeFile is a helper method for loading a docker-compose file
func LoadComposeFile(wd string, file string) (*compose.Config, error) {
return LoadComposeFileWithArch(wd, file, env.GetClientArch)
}
// LoadComposeFileWithArch is a helper method for loading a docker-compose file
func LoadComposeFileWithArch(wd string, file string, archGetter ArchGetter) (*compose.Config, error) {
file = path.Join(wd, file)
b, err := ioutil.ReadFile(file)
if err != nil {
@ -300,7 +322,7 @@ func LoadComposeFile(wd string, file string) (*compose.Config, error) {
return nil, err
}
archSuffix, err := GetArchSuffix(env.GetClientArch)
archSuffix, err := GetArchSuffix(archGetter)
if err != nil {
return nil, err
}

View File

@ -7,8 +7,10 @@ import (
)
func Test_ParseCompose(t *testing.T) {
wd := "testdata"
expected := map[string]Service{
want := map[string]Service{
"basic-auth-plugin": {
Name: "basic-auth-plugin",
Image: "docker.io/openfaas/basic-auth-plugin:0.18.17",
@ -73,7 +75,8 @@ func Test_ParseCompose(t *testing.T) {
Dest: path.Join("/run/secrets", "basic-auth-user"),
},
},
Caps: []string{"CAP_NET_RAW"},
Caps: []string{"CAP_NET_RAW"},
DependsOn: []string{"nats"},
},
"queue-worker": {
Name: "queue-worker",
@ -103,32 +106,39 @@ func Test_ParseCompose(t *testing.T) {
},
}
compose, err := LoadComposeFile(wd, "docker-compose.yaml")
compose, err := LoadComposeFileWithArch(wd, "docker-compose.yaml", func() (string, string) { return "x86_64", "Linux" })
if err != nil {
t.Fatalf("can not read docker-compose fixture: %s", err)
t.Fatalf("can't read docker-compose file: %s", err)
}
services, err := ParseCompose(compose)
if err != nil {
t.Fatalf("can not parse compose services: %s", err)
t.Fatalf("can't parse compose services: %s", err)
}
if len(services) != len(expected) {
t.Fatalf("expected: %d services, got: %d", len(expected), len(services))
if len(services) != len(want) {
t.Fatalf("want: %d services, got: %d", len(want), len(services))
}
for _, service := range services {
exp, ok := expected[service.Name]
exp, ok := want[service.Name]
if service.Name == "gateway" {
if len(service.DependsOn) == 0 {
t.Fatalf("gateway should have at least one depends_on entry")
}
}
if !ok {
t.Fatalf("unexpected service: %s", service.Name)
t.Fatalf("incorrect service: %s", service.Name)
}
if service.Name != exp.Name {
t.Fatalf("incorrect service Name:\n\texpected: %s,\n\tgot: %s", exp.Name, service.Name)
t.Fatalf("incorrect service Name:\n\twant: %s,\n\tgot: %s", exp.Name, service.Name)
}
if service.Image != exp.Image {
t.Fatalf("incorrect service Image:\n\texpected: %s,\n\tgot: %s", exp.Image, service.Image)
t.Fatalf("incorrect service Image:\n\twant: %s,\n\tgot: %s", exp.Image, service.Image)
}
equalStringSlice(t, exp.Env, service.Env)
@ -136,41 +146,41 @@ func Test_ParseCompose(t *testing.T) {
equalStringSlice(t, exp.Args, service.Args)
if !reflect.DeepEqual(exp.Mounts, service.Mounts) {
t.Fatalf("incorrect service Mounts:\n\texpected: %+v,\n\tgot: %+v", exp.Mounts, service.Mounts)
t.Fatalf("incorrect service Mounts:\n\twant: %+v,\n\tgot: %+v", exp.Mounts, service.Mounts)
}
}
}
func equalStringSlice(t *testing.T, expected, found []string) {
func equalStringSlice(t *testing.T, want, found []string) {
t.Helper()
if (expected == nil) != (found == nil) {
t.Fatalf("unexpected nil slice: expected %+v, got %+v", expected, found)
if (want == nil) != (found == nil) {
t.Fatalf("unexpected nil slice: want %+v, got %+v", want, found)
}
if len(expected) != len(found) {
t.Fatalf("unequal slice length: expected %+v, got %+v", expected, found)
if len(want) != len(found) {
t.Fatalf("unequal slice length: want %+v, got %+v", want, found)
}
for i := range expected {
if expected[i] != found[i] {
t.Fatalf("unexpected value at postition %d: expected %s, got %s", i, expected[i], found[i])
for i := range want {
if want[i] != found[i] {
t.Fatalf("unexpected value at postition %d: want %s, got %s", i, want[i], found[i])
}
}
}
func equalMountSlice(t *testing.T, expected, found []Mount) {
func equalMountSlice(t *testing.T, want, found []Mount) {
t.Helper()
if (expected == nil) != (found == nil) {
t.Fatalf("unexpected nil slice: expected %+v, got %+v", expected, found)
if (want == nil) != (found == nil) {
t.Fatalf("unexpected nil slice: want %+v, got %+v", want, found)
}
if len(expected) != len(found) {
t.Fatalf("unequal slice length: expected %+v, got %+v", expected, found)
if len(want) != len(found) {
t.Fatalf("unequal slice length: want %+v, got %+v", want, found)
}
for i := range expected {
if !reflect.DeepEqual(expected[i], found[i]) {
t.Fatalf("unexpected value at postition %d: expected %s, got %s", i, expected[i], found[i])
for i := range want {
if !reflect.DeepEqual(want[i], found[i]) {
t.Fatalf("unexpected value at postition %d: want %s, got %s", i, want[i], found[i])
}
}
}
@ -178,7 +188,7 @@ func equalMountSlice(t *testing.T, expected, found []Mount) {
func Test_GetArchSuffix(t *testing.T) {
cases := []struct {
name string
expected string
want string
foundArch string
foundOS string
err string
@ -192,37 +202,37 @@ func Test_GetArchSuffix(t *testing.T) {
name: "x86 has no suffix",
foundOS: "Linux",
foundArch: "x86_64",
expected: "",
want: "",
},
{
name: "unknown arch has no suffix",
foundOS: "Linux",
foundArch: "anything_else",
expected: "",
want: "",
},
{
name: "armhf has armhf suffix",
foundOS: "Linux",
foundArch: "armhf",
expected: "-armhf",
want: "-armhf",
},
{
name: "armv7l has armhf suffix",
foundOS: "Linux",
foundArch: "armv7l",
expected: "-armhf",
want: "-armhf",
},
{
name: "arm64 has arm64 suffix",
foundOS: "Linux",
foundArch: "arm64",
expected: "-arm64",
want: "-arm64",
},
{
name: "aarch64 has arm64 suffix",
foundOS: "Linux",
foundArch: "aarch64",
expected: "-arm64",
want: "-arm64",
},
}
@ -231,15 +241,15 @@ func Test_GetArchSuffix(t *testing.T) {
suffix, err := GetArchSuffix(testArchGetter(tc.foundArch, tc.foundOS))
if tc.err != "" && err == nil {
t.Fatalf("expected error %s but got nil", tc.err)
t.Fatalf("want error %s but got nil", tc.err)
} else if tc.err != "" && err.Error() != tc.err {
t.Fatalf("expected error %s, got %s", tc.err, err.Error())
t.Fatalf("want error %s, got %s", tc.err, err.Error())
} else if tc.err == "" && err != nil {
t.Fatalf("unexpected error %s", err.Error())
}
if suffix != tc.expected {
t.Fatalf("expected suffix %s, got %s", tc.expected, suffix)
if suffix != tc.want {
t.Fatalf("want suffix %s, got %s", tc.want, suffix)
}
})
}

View File

@ -1,12 +1,12 @@
version: "3.7"
services:
basic-auth-plugin:
image: docker.io/openfaas/basic-auth-plugin:0.18.17
image: "docker.io/openfaas/basic-auth-plugin:0.18.17${ARCH_SUFFIX}"
environment:
port: "8080"
secret_mount_path: "/run/secrets"
user_filename: "basic-auth-user"
pass_filename: "basic-auth-password"
- port=8080
- secret_mount_path=/run/secrets
- user_filename=basic-auth-user
- pass_filename=basic-auth-password
volumes:
# we assume cwd == /var/lib/faasd
- type: bind
@ -17,6 +17,7 @@ services:
target: /run/secrets/basic-auth-user
cap_add:
- CAP_NET_RAW
nats:
image: docker.io/library/nats-streaming:0.11.2
command:
@ -25,6 +26,7 @@ services:
- "8222"
- "--store=memory"
- "--cluster_id=faas-cluster"
prometheus:
image: docker.io/prom/prometheus:v2.14.0
volumes:
@ -33,21 +35,22 @@ services:
target: /etc/prometheus/prometheus.yml
cap_add:
- CAP_NET_RAW
gateway:
image: docker.io/openfaas/gateway:0.18.17
image: "docker.io/openfaas/gateway:0.18.17${ARCH_SUFFIX}"
environment:
basic_auth: "true"
functions_provider_url: "http://faasd-provider:8081/"
direct_functions: "false"
read_timeout: "60s"
write_timeout: "60s"
upstream_timeout: "65s"
faas_nats_address: "nats"
faas_nats_port: "4222"
auth_proxy_url: "http://basic-auth-plugin:8080/validate"
auth_proxy_pass_body: "false"
secret_mount_path: "/run/secrets"
scale_from_zero: "true"
- basic_auth=true
- functions_provider_url=http://faasd-provider:8081/
- direct_functions=false
- read_timeout=60s
- write_timeout=60s
- upstream_timeout=65s
- faas_nats_address=nats
- faas_nats_port=4222
- auth_proxy_url=http://basic-auth-plugin:8080/validate
- auth_proxy_pass_body=false
- secret_mount_path=/run/secrets
- scale_from_zero=true
volumes:
# we assume cwd == /var/lib/faasd
- type: bind
@ -58,18 +61,23 @@ services:
target: /run/secrets/basic-auth-user
cap_add:
- CAP_NET_RAW
depends_on:
- basic-auth-plugin
- nats
- prometheus
queue-worker:
image: docker.io/openfaas/queue-worker:0.11.2
environment:
faas_nats_address: "nats"
faas_nats_port: "4222"
gateway_invoke: "true"
faas_gateway_address: "gateway"
ack_wait: "5m5s"
max_inflight: "1"
write_debug: "false"
basic_auth: "true"
secret_mount_path: "/run/secrets"
- faas_nats_address=nats
- faas_nats_port=4222
- gateway_invoke=true
- faas_gateway_address=gateway
- ack_wait=5m5s
- max_inflight=1
- write_debug=false
- basic_auth=true
- secret_mount_path=/run/secrets
volumes:
# we assume cwd == /var/lib/faasd
- type: bind
@ -80,3 +88,5 @@ services:
target: /run/secrets/basic-auth-user
cap_add:
- CAP_NET_RAW
depends_on:
- nats