openfaas / faas

OpenFaaS - Serverless Functions Made Simple
https://www.openfaas.com
MIT License
25.06k stars 1.93k forks source link

Support wanted for running a registry on localhost #1796

Closed brianjenkins94 closed 1 year ago

brianjenkins94 commented 1 year ago

My actions before raising this issue

Why do you need this?

Trying to run OpenFaaS in a devcontainer.

This would be super convenient for sharing a local test environment across a team.

Who is this for?

Myself

Expected Behaviour

The function that I deployed successfully should be runnable.

Current Behaviour

The function that I deployed is stuck in "Status: Not ready"

$ kubectl get events -n openfaas-fn --sort-by=.metadata.creationTimestamp
LAST SEEN   TYPE      REASON              OBJECT                        MESSAGE
3m6s        Normal    Scheduled           pod/hello-78f469bc95-bwnm5    Successfully assigned openfaas-fn/hello-78f469bc95-bwnm5 to kind-control-plane
3m6s        Normal    SuccessfulCreate    replicaset/hello-78f469bc95   Created pod: hello-78f469bc95-bwnm5
3m6s        Normal    ScalingReplicaSet   deployment/hello              Scaled up replica set hello-78f469bc95 to 1
99s         Normal    Pulling             pod/hello-78f469bc95-bwnm5    Pulling image "127.0.0.1:5000/hello:latest"
99s         Warning   Failed              pod/hello-78f469bc95-bwnm5    Failed to pull image "127.0.0.1:5000/hello:latest": rpc error: code = Unknown desc = failed to pull and unpack image "127.0.0.1:5000/hello:latest": failed to resolve reference "127.0.0.1:5000/hello:latest": failed to do request: Head "http://127.0.0.1:5000/v2/hello/manifests/latest": dial tcp 127.0.0.1:5000: connect: connection refused
99s         Warning   Failed              pod/hello-78f469bc95-bwnm5    Error: ErrImagePull
63s         Normal    BackOff             pod/hello-78f469bc95-bwnm5    Back-off pulling image "127.0.0.1:5000/hello:latest"
74s         Warning   Failed              pod/hello-78f469bc95-bwnm5    Error: ImagePullBackOff

Are you a GitHub Sponsor (Yes/No?)

Check at: https://github.com/sponsors/openfaas

List All Possible Solutions and Workarounds

The troubleshooting guide suggests that it's because I haven't configured any image pull secrets but I tried adding these commands and didn't see any change.

kubectl create secret docker-registry regcred -n openfaas \
    --docker-server=localhost:5000 \
    --docker-username=$USERNAME \
    --docker-password=$PASSWORD

kubectl patch serviceaccount default -n openfaas -p '{"imagePullSecrets": [{"name": "regcred"}]}'

Adding credentials (at least like this) results in 401 Unauthorized:

USERNAME="admin"
PASSWORD="admin"

docker run --entrypoint htpasswd httpd:2 -Bbn "$USERNAME" "$PASSWORD" > docker/auth/htpasswd

docker run -d --restart=always -p 5000:5000 --name "kind-registry" \
    --volume "$(pwd)/docker/auth":/auth \
    --env "REGISTRY_AUTH=htpasswd" \
    --env "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
    --env "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
    registry:2

echo -n "$PASSWORD" | docker login -u $USERNAME --password-stdin localhost:5000

Trying from an interactive shell:

$ docker login localhost:5000
Username: admin
Password: 
Error response from daemon: Get "http://localhost:5000/v2/": net/http: request canceled (Client.Timeout exceeded while awaiting headers)

So I'm at a real loss here. It seems like I need some combination of htaccess + Self-signed certificates + imagePullSecrets to fix what seems like an authentication problem between kubernetes and my private registry.

Steps to Reproduce (for bugs)

  1. Open this folder in VSCode: openfaas-devcontainer-issue.zip

  2. Choose "Dev Containers: Reopen in Container"

postCreateCommand ``` Running the postCreateCommand from devcontainer.json... [35412 ms] Start: Run in container: /bin/sh -c /entrypoint.sh Downloading: kind 2023/05/11 21:38:44 Looking up version for kind 2023/05/11 21:38:45 Found: v0.18.0 Downloading: https://github.com/kubernetes-sigs/kind/releases/download/v0.18.0/kind-linux-arm64 6.50 MiB / 6.50 MiB [-----------------------------------------------------------------------------------------------------------------------------] 100.00% /tmp/kind-linux-arm64 written. 2023/05/11 21:38:45 Looking up version for kind 2023/05/11 21:38:45 Found: v0.18.0 2023/05/11 21:38:45 Copying /tmp/kind-linux-arm64 to /root/.arkade/bin/kind Wrote: /root/.arkade/bin/kind (6.819MB) # Add arkade binary directory to your PATH variable export PATH=$PATH:$HOME/.arkade/bin/ # Test the binary: /root/.arkade/bin/kind # Or install with: sudo mv /root/.arkade/bin/kind /usr/local/bin/ 🐳 arkade needs your support: https://github.com/sponsors/alexellis Deleting cluster "kind" ... aeb1ce1ebe51a5ac2ca0beaa3bcca29ec93455660d8e41b5638c4c092e75a534 Creating cluster "kind" ... ✓ Ensuring node image (kindest/node:v1.26.3) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-kind" You can now use your cluster with: kubectl cluster-info --context kind-kind Have a nice day! 👋 configmap/local-registry-hosting created Using Kubeconfig: /root/.kube/config Client: aarch64, Linux 2023/05/11 21:39:01 User dir established as: /root/.arkade/ 2023/05/11 21:39:01 Looking up version for helm 2023/05/11 21:39:01 Found: v3.12.0 Downloading: https://get.helm.sh/helm-v3.12.0-linux-arm64.tar.gz /tmp/helm-v3.12.0-linux-arm64.tar.gz written. 2023/05/11 21:39:01 Looking up version for helm 2023/05/11 21:39:02 Found: v3.12.0 2023/05/11 21:39:02 Extracted: /tmp/helm 2023/05/11 21:39:02 Copying /tmp/helm to /root/.arkade/bin/helm Downloaded to: /root/.arkade/bin/helm helm "openfaas" has been added to your repositories Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "openfaas" chart repository Update Complete. ⎈Happy Helming!⎈ VALUES values-arm64.yaml Command: /root/.arkade/bin/helm [upgrade --install openfaas openfaas/openfaas --namespace openfaas --values /tmp/charts/openfaas/values-arm64.yaml --set clusterRole=false --set gateway.directFunctions=false --set operator.create=false --set dashboard.enabled=false --set dashboard.publicURL=http://127.0.0.1:8080 --set serviceType=NodePort --set openfaasImagePullPolicy=IfNotPresent --set faasnetes.imagePullPolicy=Always --set basicAuthPlugin.replicas=1 --set queueWorker.replicas=1 --set queueWorker.maxInflight=1 --set basic_auth=true --set gateway.replicas=1 --set ingressOperator.create=false --set autoscaler.enabled=false] Release "openfaas" does not exist. Installing it now. NAME: openfaas LAST DEPLOYED: Thu May 11 21:39:04 2023 NAMESPACE: openfaas STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: To verify that openfaas has started, run: kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas" To retrieve the admin password, run: echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode) ======================================================================= = OpenFaaS has been installed. = ======================================================================= # Get the faas-cli curl -SLsf https://cli.openfaas.com | sudo sh # Forward the gateway to your machine kubectl rollout status -n openfaas deploy/gateway kubectl port-forward -n openfaas svc/gateway 8080:8080 & # If basic auth is enabled, you can now log into your gateway: PASSWORD=$(kubectl get secret -n openfaas basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode; echo) echo -n $PASSWORD | faas-cli login --username admin --password-stdin faas-cli store deploy figlet faas-cli list # For Raspberry Pi faas-cli store list \ --platform armhf faas-cli store deploy figlet \ --platform armhf # Find out more at: # https://github.com/openfaas/faas 🐳 arkade needs your support: https://github.com/sponsors/alexellis Waiting for deployment spec update to be observed... Waiting for deployment spec update to be observed... Waiting for deployment "alertmanager" rollout to finish: 0 out of 1 new replicas have been updated... Waiting for deployment "alertmanager" rollout to finish: 0 of 1 updated replicas are available... deployment "alertmanager" successfully rolled out Waiting for deployment "gateway" rollout to finish: 0 of 1 updated replicas are available... deployment "gateway" successfully rolled out deployment "nats" successfully rolled out deployment "prometheus" successfully rolled out deployment "queue-worker" successfully rolled out Downloading: faas-cli 2023/05/11 21:39:33 Looking up version for faas-cli 2023/05/11 21:39:33 Found: 0.16.3 Downloading: https://github.com/openfaas/faas-cli/releases/download/0.16.3/faas-cli-arm64 2.05 MiB / 8.12 MiB [------------------------------->_________________________________________________________________________________________8.12 MiB / 8.12 MiB [-----------------------------------------------------------------------------------------------------------------------------] 100.00% /tmp/faas-cli-arm64 written. 2023/05/11 21:39:34 Looking up version for faas-cli 2023/05/11 21:39:34 Found: 0.16.3 2023/05/11 21:39:34 Copying /tmp/faas-cli-arm64 to /root/.arkade/bin/faas-cli Wrote: /root/.arkade/bin/faas-cli (8.52MB) # Add arkade binary directory to your PATH variable export PATH=$PATH:$HOME/.arkade/bin/ # Test the binary: /root/.arkade/bin/faas-cli # Or install with: sudo mv /root/.arkade/bin/faas-cli /usr/local/bin/ 🐳 arkade needs your support: https://github.com/sponsors/alexellis Fetch templates from repository: https://github.com/openfaas/templates.git at 2023/05/11 21:39:34 Attempting to expand templates from https://github.com/openfaas/templates.git 2023/05/11 21:39:35 Fetched 18 template(s) : [csharp dockerfile go java11 java11-vert-x node node12 node12-debian node14 node16 node17 node18 php7 php8 python python3 python3-debian ruby] from https://github.com/openfaas/templates.git Calling the OpenFaaS server to validate the credentials... credentials saved for admin http://localhost:8080 up to date, audited 1 package in 82ms found 0 vulnerabilities > faas > cd functions && faas-cli build && faas-cli push && faas-cli deploy 2023/05/11 21:39:36 No templates found in current directory. 2023/05/11 21:39:36 Attempting to expand templates from https://github.com/openfaas/templates.git 2023/05/11 21:39:37 Fetched 18 template(s) : [csharp dockerfile go java11 java11-vert-x node node12 node12-debian node14 node16 node17 node18 php7 php8 python python3 python3-debian ruby] from https://github.com/openfaas/templates.git [0] > Building hello. Clearing temporary build folder: ./build/hello/ Preparing: ./hello/ build/hello/function Building: 127.0.0.1:5000/hello:latest with node18 template. Please wait.. #1 [internal] load build definition from Dockerfile #1 sha256:f02023dee9b1b303fdf6de66b3cfd2ba3d1b08a1e9fe09e27fce6af65f045d35 #1 transferring dockerfile: 1.45kB done #1 DONE 0.0s #2 [internal] load .dockerignore #2 sha256:9424cf76e0d8177d9da5da41a305440c34ddba8bd2f01c6fb101157b76dabd61 #2 transferring context: 55B done #2 DONE 0.0s #4 [internal] load metadata for ghcr.io/openfaas/of-watchdog:0.9.11 #4 sha256:cad8e51ef81455a787b6aa1d6762f83f4d0ab1be84d1b17f4f45fc4782de20b8 #4 DONE 1.0s #3 [internal] load metadata for docker.io/library/node:18-alpine #3 sha256:5ee8ea8626137e4a4e007f934eed3f693a5155be1152f5d3acaa7750c4ab9271 #3 DONE 1.2s #6 [watchdog 1/1] FROM ghcr.io/openfaas/of-watchdog:0.9.11@sha256:b84fd6db48e31e3d65dcd36672bf49b7a7b3dfced49ce602f51e877d9d1d56e4 #6 sha256:b2153fd55de6ccf74e39c361b3df8d91c44fc8bf83e00cb757517a67819c47cc #6 DONE 0.0s #5 [ship 1/16] FROM docker.io/library/node:18-alpine@sha256:1ccc70acda680aa4ba47f53e7c40b2d4d6892de74817128e0662d32647dd7f4d #5 sha256:90c65eeaac94ce503fd87b08cd18bbe1ac1af413242831bb6891b3a0de5e47c2 #5 DONE 0.0s #13 [internal] load build context #13 sha256:28e9063f7f7bd2d8bce2444ee573baca48930d3f099234369816739b70291030 #13 transferring context: 4.53kB done #13 DONE 0.0s #18 [ship 12/16] COPY --chown=app:app function/*.json ./ #18 sha256:cd987d8c3f134e6019ed66e1e27fce8387d72ed82d4ec4ab2a7d4ab928831fbb #18 CACHED #17 [ship 11/16] WORKDIR /home/app/function #17 sha256:bce8c29ae5db99784a8a3d7bbbe16279a013f9170b355e28fb1c419f4cbbb9c0 #17 CACHED #19 [ship 13/16] RUN npm i #19 sha256:08dba011147056fd8d2f593157060209f1d2c6c3eccf8d6c7552956de401932b #19 CACHED #12 [ship 7/16] WORKDIR /home/app #12 sha256:333f6a8792e47bc078e4dc693bcb1ffbff7d2a02da86dc31dd63724a28fff8f5 #12 CACHED #15 [ship 9/16] RUN npm i #15 sha256:a5acb084cbe3308913218eb00f0726118f058763b885ed05a43b162a643eaae5 #15 CACHED #8 [ship 3/16] RUN chmod +x /usr/bin/fwatchdog #8 sha256:6a798658a2f7ffc445df3cc827a4d1fa4bb16524ef21be8f291f5fd380d6d7b8 #8 CACHED #10 [ship 5/16] RUN chmod 777 /tmp #10 sha256:9b399d42dd9f107ef16c016a39ac55897edc71ff2b6bc892fcaa808c401a11b9 #10 CACHED #7 [ship 2/16] COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog #7 sha256:4f6ad53730ff5bb89028d3a5f1b57aac60d87053726cd73ba0350726a3a32a7c #7 CACHED #9 [ship 4/16] RUN apk --no-cache add curl ca-certificates && addgroup -S app && adduser -S -g app app #9 sha256:7e45c922b5825e88b1b3fa8977fd8159fe6c19b8f06cd5cd5c823418fe89edb2 #9 CACHED #21 [ship 15/16] RUN npm test #21 sha256:65f95e118c816387a35a9b40c145c2e5ef37eec54dc9171c71fd7c1814f7925b #21 CACHED #14 [ship 8/16] COPY --chown=app:app package.json ./ #14 sha256:20e95c1d4c5252a8a6612897e8eefc622e63d3f278755e6a74bd05bf65515426 #14 CACHED #16 [ship 10/16] COPY --chown=app:app index.js ./ #16 sha256:236ea3578b85147bf9db1b76718811103112486f9f906c68715881ecfa7e8c06 #16 CACHED #20 [ship 14/16] COPY --chown=app:app function/ ./ #20 sha256:4f7bcd04710faec10ac7d1d9a3cc3c4a3c07ab2120ec927a3ab5fd7a1320fef4 #20 CACHED #11 [ship 6/16] RUN mkdir -p /home/app/function #11 sha256:df96068bbb8ca6df91616c3af05b09dbb7473c95d42ae53923eaf1c0f9e2878f #11 CACHED #22 [ship 16/16] WORKDIR /home/app/ #22 sha256:6cad50ba5a8ee6fba5a20f6d498285e76eca9364272eac72b5ed64d9e81dba08 #22 CACHED #23 exporting to image #23 sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00 #23 exporting layers done #23 writing image sha256:bfd0097834db33fdee5787959b50bdf0eddda77570dc19767f7f2a6ac826cbe6 done #23 naming to 127.0.0.1:5000/hello:latest done #23 DONE 0.0s Image: 127.0.0.1:5000/hello:latest built. [0] < Building hello done in 1.52s. [0] Worker done. Total build time: 1.52s [0] > Pushing hello [127.0.0.1:5000/hello:latest] The push refers to repository [127.0.0.1:5000/hello] 5f70bf18a086: Pushed 0556e0c2ca3b: Pushed 2a4373e149b2: Pushed 084b53da8258: Pushed eefc5ded2f0f: Pushed d4729e245ded: Pushed 5d2d95f71579: Pushed 834dcea68970: Pushed 43389e345c2d: Pushed 73952033ea3a: Pushed 553517dde820: Pushed 84a016b92e6a: Pushed 1e45f0cebea3: Pushed d9f41532a73b: Pushed 6c0a2592426a: Pushed 1984e605c08a: Pushed 26cbea5cba74: Pushed latest: digest: sha256:51997e15afbc7f265c4a3e2427ec09079ee278542f6a083400ee8f322d7e7aaa size: 4279 [0] < Pushing hello [127.0.0.1:5000/hello:latest] done. [0] Worker done. Deploying: hello. Deployed. 202 Accepted. URL: http://localhost:8080/function/hello npm notice npm notice New minor version of npm available! 9.5.1 -> 9.6.6 npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.6.6 npm notice Run npm install -g npm@9.6.6 to update! npm notice > Listening on http://admin:5sHqkzxxM1ee@localhost:8080/ ```

Everything looks good, the function has deployed with a 202 Accepted.

  1. Ctrl+Click the "Listening on" URL to bring up the dashboard

  2. Observe: "Status: Not ready"

Context

Trying to run kind in a devcontainer

Your Environment

openfaas-devcontainer-issue.zip

The troubleshooting guide got me to the source of the problem but I'm not sure what to do next.

OpenFaaS Pro Report ``` Gateway gateway image: ghcr.io/openfaas/gateway:0.26.3 controller image: ghcr.io/openfaas/faas-netes:0.16.7 gateway_replicas: 1 gateway_timeout - read: 65s write: 65s upstream: 60s controller_mode: faas-netes controller_timeout - read: 65s write: 65s Queue-worker queue_worker_image: ghcr.io/openfaas/queue-worker:0.13.3 queue_worker_replicas: 1 queue_worker_ack_wait: 60s queue_worker_max_inflight: 50 Function namespaces: - openfaas-fn Features detected: - ✅ Async - ❌ Pro gateway - ❌ HA Gateway - ❌ Operator mode - ❌ Autoscaler - ❌ Dashboard - ❌ JetStream - ❌ Istio Advanced features: - ❌ Function Builder API - ✅ Multiple namespaces Other: - Kubernetes version: v1.26.3 - Asynchronous concurrency (cluster): 50 Total functions in cluster: 1 1 functions in (openfaas-fn): hello (1 replicas) - read_timeout - write_timeout - exec_timeout resources and limits - requests: - limits: Warnings: ⚠️ queue-worker maximum concurrency is (50), this may be too low ⚠️ queue-worker replicas want >= 3 but got 1, (not Highly Available (HA)) ⚠️ Use external NATS to ensure high-availability and persistence ⚠️ gateway replicas want >= 3 but got 1, (not Highly Available (HA)) ⚠️ NATS Streaming will be deprecated and replaced with NATS JetStream: https://www.openfaas.com/blog/jetstream-for-openfaas/ ⚠️ Operator mode is not enabled, OpenFaaS Pro customers should use the OpenFaaS operator ⚠️ Non-root flag is not set for the controller/operator ⚠️ hello.openfaas-fn read_timeout is not set ⚠️ hello.openfaas-fn write_timeout is not set ⚠️ hello.openfaas-fn exec_timeout is not set ⚠️ hello.openfaas-fn no memory requests set ⚠️ no functions in namespace openfaas-fn are configured to scale down, this may be inefficient ⚠️ at least one function in namespace openfaas-fn does not set the file system to read-only ```
brianjenkins94 commented 1 year ago

Okay, I am now reasonably certain that the problems I am encountering are because I haven't set up a self-signed certificate.

It still seems weird to me that I was able to push the image to the registry without issue, but retrieving it is the problem.

For those following along at home, we are here:

devcontainer.json ```jsonc // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/typescript-node { "name": "Existing Dockerfile", "build": { "context": "../docker/", "dockerfile": "../docker/Dockerfile" }, "remoteUser": "root", "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "ms-vscode-remote.remote-containers", "streetsidesoftware.code-spell-checker", "unifiedjs.vscode-mdx" ] } }, "features": { "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { "version": "latest", "moby": true }, "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": { "minikube": "none", "version": "latest" } }, "forwardPorts": [8080], "postCreateCommand": "/entrypoint.sh", "runArgs": ["--network=host"] } ```
Dockerfile ```dockerfile # syntax=docker/dockerfile:1.4 FROM mcr.microsoft.com/devcontainers/base:ubuntu ENV container=docker ENV OPENFAAS_URL=http://localhost:8080 RUN sudo apt-get update > /dev/null \ # Node.js && wget -qO- https://deb.nodesource.com/setup_lts.x | sudo sh \ # Common && sudo apt-get install -y file nodejs \ # Arkade && curl -fsSL https://get.arkade.dev | sudo sh COPY --chmod=755 ./entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] ```
entrypoint.sh ```sh #!/bin/sh # kind arkade get kind sudo mv ~/.arkade/bin/kind /usr/local/bin/ kind delete cluster docker container rm --force "$(docker ps --filter name="kind-" --all --quiet)" docker run -d --restart=always -p 127.0.0.1:5000:5000 --name "kind-registry" registry:2 sed "s/\t/ /g" < Listening on http://admin:$PASSWORD@localhost:8080/" ```

There's something wrong with the volume mount currently. It's getting mounted as an empty folder.

alexellis commented 1 year ago

Hi @brianjenkins94

I haven't seen anything in your issue that makes it seem like OpenFaaS or K8s wouldn't work in a devcontainer specifically. I think a better title for this issue would be: "How do I use a local registry with OpenFaaS?"

There are some instructions that were contributed based upon a KinD (not an OpenFaaS) guide on how to use a registry on the same machine - https://docs.openfaas.com/tutorials/local-kind-registry/

Setting up a registry on localhost is a bit advanced, perhaps you'd prefer to use a short-lived registry instead like ttl.sh?

We use it a lot for local development and testing.

I'll get this closed, and let us know how you get on with ttl.sh or the KinD project's instructions for local registries.

Alex

alexellis commented 1 year ago

I had absolutely no issues running KinD and OpenFaaS in a devcontainer, and by following the tutorial in the openfaas docs (referenced above), I was also able to use a local registry.

Screenshot 2023-05-12 at 21 08 49

Let me know if this helped 🙂

brianjenkins94 commented 1 year ago

Hey Alex, I hugely appreciate you taking a look.

You were able to get everything working just by following the KinD guide?

You didn't have to do anything extra with htaccess, certificates, or setting imagePullSecrets?

Weird that I've been running into so many issues. I'll start again from scratch and see if I can make sense of how I got here.

Thanks!

alexellis commented 1 year ago

Delete everything that you have and only run the commands from the tutorial. The port is 5001 and you need to use localhost not the IP.

brianjenkins94 commented 1 year ago

It worked 🤦‍♂️

I've updated my previous comment to reflect what worked in the end.

Basically I thought myself smarter than the tutorial and thought I could expose the registry on the host and then get rid of what I had thought would be unnecessary redirection.

Rest assured I feel very silly now 🙂

alexellis commented 1 year ago

No problem.

If you'd like to help support the time we spend maintaining OpenFaaS and supporting users via GitHub, please consider setting up a GitHub Sponsorship with a set or custom tier. We want the Community Edition to continue to be available for free users :slightly_smiling_face:

https://github.com/sponsors/openfaas/