diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f2d451b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,23 @@ +![Latest Build](https://img.shields.io/github/actions/workflow/status/acouvreur/sablier/build.yml?style=flat-square&branch=main)![Go Report](https://goreportcard.com/badge/github.com/acouvreur/sablier?style=flat-square) ![Go Version](https://img.shields.io/github/go-mod/go-version/acouvreur/sablier?style=flat-square) ![Latest Release](https://img.shields.io/github/release/acouvreur/sablier/all.svg?style=flat-square) + +# Sablier - Scale to Zero + +Sablier is an API that start containers for a given duration. + +It provides an integrations with multiple reverse proxies and different loading strategies. + +Which allows you to start your containers on demand and shut them down automatically as soon as there's no activity. + +![Hourglass](https://raw.githubusercontent.com/acouvreur/sablier/main/docs/img/hourglass.png) + +## Glossary + +I'll use these terms in order to be provider agnostic. + +- **Session**: A Session is a set of **instances** +- **Instance**: An instance is either a docker container, docker swarm service, kubernetes deployment or kubernetes statefulset + +## Credits + +- [Hourglass icons created by Vectors Market - Flaticon](https://www.flaticon.com/free-icons/hourglass) +- [tarampampam/error-pages](https://github.com/tarampampam/error-pages/) for the themes \ No newline at end of file diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 0000000..d4d85a4 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,24 @@ +- [Introduction](/) +- [Getting started](/getting-started) +- [Installation](/installation) +- [Configuration](/configuration) +- [Strategies](/strategies) +- [Themes](/themes) +- [FAQ](/faq) +- [Versioning](/versioning) +- **Providers** + - [Overview](/providers/overview) + - [Docker](/providers/docker) + - [Docker Swarm](/providers/docker_swarm) + - [Kubernetes](/providers/kubernetes) +- **Reverse Proxy Plugins** + - [Overview](/plugins/overview) + - [Traefik](/plugins/traefik) + - [Nginx](/plugins/nginx) + - [Caddy](/plugins/caddy) +- **Guides** + - [Overview](/guides/overview) + - [VSCode Server with Traefik and Kubernetes](/guides/code-server-traefik-kubernetes.md) +- **Links** +- [Github](https://github.com/acouvreur/sablier) +- [Docker Hub](https://hub.docker.com/r/acouvreur/sablier) \ No newline at end of file diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 0000000..bb18fe9 --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,95 @@ +# Documentation for Sablier + + +## Documentation for API Endpoints + +All URIs are relative to *http://localhost:10000* + +| Class | Method | HTTP request | Description | +|------------ | ------------- | ------------- | -------------| +| *ScaleApi* | [**scaleBlocking**](Apis/ScaleApi.md#scaleblocking) | **GET** /api/strategies/blocking | Hangs the request until the services are ready | +*ScaleApi* | [**scaleDynamic**](Apis/ScaleApi.md#scaledynamic) | **GET** /api/strategies/dynamic | The waiting page for the given services | +| *ThemeApi* | [**getTheme**](Apis/ThemeApi.md#gettheme) | **GET** /api/strategies/dynamoc/themes | | + + + +## Documentation for Models + + - [instance](./Models/instance.md) + - [session](./Models/session.md) + - [status](./Models/status.md) + - [themes](./Models/themes.md) + + + +## Documentation for Authorization + +All endpoints do not require authorization. + +## API + +To run the following examples you can create two containers: + +- `docker create --name nginx nginx` +- `docker create --name apache httpd` + +### GET `/api/strategies/dynamic` + +**Description**: The `/api/strategies/dynamic` endpoint allows you to request a waiting page for multiple instances + +| Parameter | Value | Description | +| -------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `names` | array of string | The instances to be started (cannot be used with `group` parameter) | +| `group` | string | The instance group to be started (using `sablier.group=mygroup` labels) (cannot be used with `names` parameter) | +| `session_duration` | duration [time.ParseDuration](https://pkg.go.dev/time#ParseDuration) | The session duration for all services, which will reset at each subsequent calls | +| `show_details` *(optional)* | bool | The details about instances | +| `display_name` *(optional)* | string | The display name | +| `theme` *(optional)* | string | The theme to use | +| `refresh_frequency` *(optional)* | duration [time.ParseDuration](https://pkg.go.dev/time#ParseDuration) | The refresh frequency for the loading page | + +Go to http://localhost:10000/api/strategies/dynamic?names=nginx&names=apache&session_duration=5m&show_details=true&display_name=example&theme=hacker-terminal&refresh_frequency=10s and you should see + +A special header `X-Sablier-Session-Status` is returned and will have the value `ready` if all instances are ready. Or else `not-ready`. + +![API Dynamic Prompt image](docs/img/api-dynamic.png) + +### GET `/api/strategies/blocking` + +**Description**: The `/api/strategies/blocking` endpoint allows you to wait until the instances are ready + +| Parameter | Value | Description | +| ---------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `names` | array of string | The instances to be started (cannot be used with `group` parameter) | +| `group` | string | The instance group to be started (using `sablier.group=mygroup` labels) (cannot be used with `names` parameter) | +| `session_duration` | duration [time.ParseDuration](https://pkg.go.dev/time#ParseDuration) | The session duration for all services, which will reset at each subsequent calls | +| `timeout` *(optional)* | duration [time.ParseDuration](https://pkg.go.dev/time#ParseDuration) | The maximum time to wait for instances to be ready | + +A special header `X-Sablier-Session-Status` is returned and will have the value `ready` if all instances are ready. Or else `not-ready`. + +**Curl example** +```bash +curl -X GET -v "http://localhost:10000/api/strategies/blocking?names=nginx&names=apache&session_duration=5m&timeout=5s" +* Trying 127.0.0.1:10000... +* Connected to localhost (127.0.0.1) port 10000 (#0) +> GET /api/strategies/blocking?names=nginx&names=apache&session_duration=5m&timeout=30s HTTP/1.1 +> Host: localhost:10000 +> User-Agent: curl/7.74.0 +> Accept: */* +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< Content-Type: application/json; charset=utf-8 +< X-Sablier-Session-Status: ready +< Date: Mon, 14 Nov 2022 19:20:50 GMT +< Content-Length: 245 +< +{"session": + {"instances": + [ + {"instance":{"name":"nginx","currentReplicas":1,"desiredReplicas":1,"status":"ready"},"error":null}, + {"instance":{"name":"apache","currentReplicas":1,"desiredReplicas":1,"status":"ready"},"error":null} + ], + "status":"ready" + } +} +``` \ No newline at end of file diff --git a/docs/assets/img/docker.svg b/docs/assets/img/docker.svg new file mode 100644 index 0000000..2666809 --- /dev/null +++ b/docs/assets/img/docker.svg @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/docs/assets/img/github.svg b/docs/assets/img/github.svg new file mode 100644 index 0000000..aa05db9 --- /dev/null +++ b/docs/assets/img/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/img/integration.png b/docs/assets/img/integration.png new file mode 100644 index 0000000..c339f5c Binary files /dev/null and b/docs/assets/img/integration.png differ diff --git a/docs/img/reverse-proxy-integration.png b/docs/assets/img/reverse-proxy-integration.png similarity index 100% rename from docs/img/reverse-proxy-integration.png rename to docs/assets/img/reverse-proxy-integration.png diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..a4445eb --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,122 @@ +# Configuration + +There are three different ways to define configuration options in Sablier: + +1. In a configuration file +2. As environment variables +3. In the command-line arguments + +These ways are evaluated in the order listed above. + +If no value was provided for a given option, a default value applies. + +## Configuration File + +At startup, Sablier searches for configuration in a file named sablier.yml (or sablier.yaml) in: + +- `/etc/sablier/` +- `$XDG_CONFIG_HOME/` +- `$HOME/.config/` +- `.` *(the working directory).* + +You can override this using the configFile argument. + +```bash +sablier --configFile=path/to/myconfigfile.yml +``` + +```yaml +provider: + # Provider to use to manage containers (docker, swarm, kubernetes) + name: docker +server: + # The server port to use + port: 10000 + # The base path for the API + base-path: / +storage: + # File path to save the state (default stateless) + file: +sessions: + # The default session duration (default 5m) + default-duration: 5m + # The expiration checking interval. + # Higher duration gives less stress on CPU. + # If you only use sessions of 1h, setting this to 5m is a good trade-off. + expiration-interval: 20s +logging: + level: trace +strategy: + dynamic: + # Custom themes folder, will load all .html files recursively (default empty) + custom-themes-path: + # Show instances details by default in waiting UI + show-details-by-default: false + # Default theme used for dynamic strategy (default "hacker-terminal") + default-theme: hacker-terminal + # Default refresh frequency in the HTML page for dynamic strategy + default-refresh-frequency: 5s + blocking: + # Default timeout used for blocking strategy (default 1m) + default-timeout: 1m +``` + +## Environment Variables + +All environment variables can be used in the form of the config file such as + +```yaml +strategy: + dynamic: + custom-themes-path: /my/path +``` + +Becomes + +```bash +STRATEGY_DYNAMIC_CUSTOM_THEMES_PATH=/my/path +``` + +## Arguments + +To get the list of all available arguments: + +```bash +sablier --help + +# or + +docker run acouvreur/sablier[:version] --help +# ex: docker run acouvreur/sablier:1.4.0-beta.3 --help +``` + +All arguments can be used in the form of the config file such as + +```yaml +strategy: + dynamic: + custom-themes-path: /my/path +``` + +Becomes + +```bash +sablier start --strategy.dynamic.custom-themes-path /my/path +``` + +## Reference + +``` + -h, --help help for start + --provider.name string Provider to use to manage containers [docker swarm kubernetes] (default "docker") + --server.base-path string The base path for the API (default "/") + --server.port int The server port to use (default 10000) + --sessions.default-duration duration The default session duration (default 5m0s) + --sessions.expiration-interval duration The expiration checking interval. Higher duration gives less stress on CPU. If you only use sessions of 1h, setting this to 5m is a good trade-off. (default 20s) + --storage.file string File path to save the state + --strategy.blocking.default-timeout duration Default timeout used for blocking strategy (default 1m0s) + --strategy.dynamic.custom-themes-path string Custom themes folder, will load all .html files recursively + --strategy.dynamic.default-refresh-frequency duration Default refresh frequency in the HTML page for dynamic strategy (default 5s) + --strategy.dynamic.default-theme string Default theme used for dynamic strategy (default "hacker-terminal") + --strategy.dynamic.show-details-by-default Show the loading instances details by default (default true) +``` diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..c9a1e8f --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,5 @@ +# Frequenty Asked Questions + +## How to start multiple instances? + +## How do I do \ No newline at end of file diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000..0f8f98b Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..2f5c77a --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,178 @@ +# Getting started + +This Getting Started will get you through what you need to understand how to use Sablier as a scale to zero middleware with a reverse proxy. + +![integration](assets/img/integration.png) + +## Identify your provider + +The first thing you need to do is to identify your [Provider](/providers/overview). + +?> A Provider is how Sablier can interact with your instances and scale them up and down to zero. + +You can check the available providers [here](/providers/overview?id=available-providers). + +## Identify your reverse proxy + +Once you've identified you're [Provider](/providers/overview), you'll want to identify your [Reverse Proxy](/plugins/overview). + +?> Because Sablier is designed as an API that can be used on its own, reverse proxy integrations acts as a client of that API. + +You can check the available reverse proxy plugins [here](/plugins/overview?id=available-reverse-proxies) + +## Connect it all together + +- Let's say we're using the [Docker Provider](/providers/docker). +- Let's say we're using the [Caddy Reverse Proxy Plugin](/plugins/caddy). + +### 1. Initial setup with Caddy + +Suppose this is your initial setup with Caddy. You have your reverse proxy with a Caddyfile that does a simple reverse proxy on `/whoami`. + + + +#### **docker-compose.yaml** + +```yaml +services: + proxy: + image: caddy:2.6.4 + ports: + - "8080:80" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + + whoami: + image: containous/whoami:v1.5.0 +``` + +#### **Caddyfile** + +```Caddyfile +:80 { + route /whoami { + reverse_proxy whoami:80 + } +} +``` + + + +At this point you can run `docker compose up` and go to `http://localhost:8080/whoami` and you will see your service. + + +### 2. Install Sablier with the Docker Provider + +Add the Sablier container in the `docker-compose.yaml` file. + +```yaml +services: + proxy: + image: caddy:2.6.4 + ports: + - "8080:80" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + + whoami: + image: containous/whoami:v1.5.0 + + sablier: + image: acouvreur/sablier:1.4.0-beta.3 + command: + - start + - --provider.name=docker + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' +``` + +### 3. Add the Sablier Caddy Plugin to Caddy + +Because Caddy does not provide any runtime evaluation for the plugins, we need to build Caddy with this specific plugin. + +I'll use the provided Dockerfile to build the custom Caddy image. + +```bash +docker build https://github.com/acouvreur/sablier.git#v1.4.0-beta.3:plugins/caddy + --build-arg=CADDY_VERSION=2.6.4 + -t caddy:2.6.4-with-sablier +``` + +Then change the image to from `caddy:2.6.4` to `caddy:2.6.4-with-sablier` + +```yaml +services: + proxy: + image: caddy:2.6.4-with-sablier + ports: + - "8080:80" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + + whoami: + image: containous/whoami:v1.5.0 + + sablier: + image: acouvreur/sablier:1.4.0-beta.3 + command: + - start + - --provider.name=docker + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' +``` + +### 4. Configure Caddy to use the Sablier Caddy Plugin on the `whoami` service + +This is how you opt-in your services and link them with the plugin. + + + +#### **docker-compose.yaml** + +```yaml +services: + proxy: + image: caddy:local + ports: + - "8080:80" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + + whoami: + image: containous/whoami:v1.5.0 + labels: + - sablier.enable=true + - sablier.group=demo + + sablier: + image: acouvreur/sablier:local + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' +``` + +#### **Caddyfile** + +```Caddyfile +:80 { + route /whoami { + sablier url=http://sablier:10000 { + group demo + session_duration 1m + dynamic { + display_name My Whoami Service + } + } + + reverse_proxy whoami:80 + } +} +``` + +Here we've configured the following things when we're accessing the service on `http://localhost:8080/whoami`: +- The containers that have the label `sablier.group=demo` will be started on demand +- The period of innactivity after which the containers should be shut down is one minute +- It uses the dynamic configuration and configures the title with `My Whoami Service` + + + +?> We've assigned the group `demo` to the service and we use this to identify the workload i \ No newline at end of file diff --git a/docs/guides/code-server-prompt.png b/docs/guides/code-server-prompt.png deleted file mode 100644 index 1a3145b..0000000 Binary files a/docs/guides/code-server-prompt.png and /dev/null differ diff --git a/docs/guides/code-server-traefik-kubernetes.md b/docs/guides/code-server-traefik-kubernetes.md index 7bf747c..4820998 100644 --- a/docs/guides/code-server-traefik-kubernetes.md +++ b/docs/guides/code-server-traefik-kubernetes.md @@ -1,13 +1,4 @@ -# Sablier Guide: Code-Server + Traefik + Kubernetes Ingress - -- [Sablier Guide: Code-Server + Traefik + Kubernetes Ingress](#sablier-guide-code-server--traefik--kubernetes-ingress) - - [1. Prerequisites](#1-prerequisites) - - [2. Create the Kubernetes Cluster using K3S](#2-create-the-kubernetes-cluster-using-k3s) - - [3. Deploy Traefik using Helm](#3-deploy-traefik-using-helm) - - [3. Deploy Sablier](#3-deploy-sablier) - - [4. Deploy Code-Server](#4-deploy-code-server) - - [5. Routing Code-Server through Traefik with the Sablier Plugin Middleware](#5-routing-code-server-through-traefik-with-the-sablier-plugin-middleware) - - [6. Clean up](#6-clean-up) +# Code-Server + Traefik + Kubernetes Ingress ## 1. Prerequisites diff --git a/docs/guides/overview.md b/docs/guides/overview.md new file mode 100644 index 0000000..a170755 --- /dev/null +++ b/docs/guides/overview.md @@ -0,0 +1,5 @@ +# Guides + +Guides are here to help you to understand more how to use this API in complex use cases. + +Because the nature of Sablier involves multiple reverse proxies and multiple providers, the complexity multiplies. \ No newline at end of file diff --git a/docs/guides/sablier-middleware-loading.png b/docs/guides/sablier-middleware-loading.png deleted file mode 100644 index 05b7010..0000000 Binary files a/docs/guides/sablier-middleware-loading.png and /dev/null differ diff --git a/docs/health.md b/docs/health.md new file mode 100644 index 0000000..2048a7d --- /dev/null +++ b/docs/health.md @@ -0,0 +1,23 @@ +## Sablier Healthcheck + +### Using the `/health` route + +You can use the route `/health` to check for healthiness. + +- Returns 200 `OK` when ready +- Returns 503 `Service Unavailable` when terminating + +### Using the `sablier health` command + +You can use the command `sablier health` to check for healthiness. + +`sablier health` takes on argument `--url` which defaults to `http://localhost:10000/health`. + +```yml +services: + sablier: + image: acouvreur/sablier:1.4.0-beta.3 + healthcheck: + test: ["sablier", "health"] + interval: 1m30s +``` \ No newline at end of file diff --git a/docs/img/api-dynamic.png b/docs/img/api-dynamic.png deleted file mode 100644 index e4d82f7..0000000 Binary files a/docs/img/api-dynamic.png and /dev/null differ diff --git a/docs/img/demo.gif b/docs/img/demo.gif deleted file mode 100644 index f11f3f7..0000000 Binary files a/docs/img/demo.gif and /dev/null differ diff --git a/docs/img/ghost.png b/docs/img/ghost.png deleted file mode 100644 index 67108e5..0000000 Binary files a/docs/img/ghost.png and /dev/null differ diff --git a/docs/img/hacker-terminal.png b/docs/img/hacker-terminal.png deleted file mode 100644 index 3e52216..0000000 Binary files a/docs/img/hacker-terminal.png and /dev/null differ diff --git a/docs/img/hourglass.png b/docs/img/hourglass.png deleted file mode 100644 index 3ea3a4c..0000000 Binary files a/docs/img/hourglass.png and /dev/null differ diff --git a/docs/img/matrix.png b/docs/img/matrix.png deleted file mode 100644 index e464b93..0000000 Binary files a/docs/img/matrix.png and /dev/null differ diff --git a/docs/img/shuffle.png b/docs/img/shuffle.png deleted file mode 100644 index e4234bc..0000000 Binary files a/docs/img/shuffle.png and /dev/null differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..f141acc --- /dev/null +++ b/docs/index.html @@ -0,0 +1,59 @@ + + + + + Sablier - Scale to Zero + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..ac46501 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,42 @@ + +# Install Sablier on its own + +You can install Sablier with the following flavors: + +- Use the Docker image +- Use the binary distribution +- Compile your binary from the sources + +## Use the Docker image + +- **Docker Hub**: [acouvreur/sablier](https://hub.docker.com/r/acouvreur/sablier) +- **Github Container Registry**: [ghcr.io/acouvreur/sablier](https://github.com/acouvreur/sablier/pkgs/container/sablier) + +Choose one of the Docker images and run it with one sample configuration file: + +- [sablier.yaml](https://raw.githubusercontent.com/acouvreur/sablier/main/sablier.sample.yaml) + +```bash +docker run -d -p 10000:10000 \ + -v $PWD/sablier.yml:/etc/sablier/sablier.yml acouvreur/sablier:1.4.0-beta.3 +``` + +## Use the binary distribution + +Grab the latest binary from the [releases](https://github.com/acouvreur/sablier/releases) page. + +And run it: + +```bash +./sablier --help +``` + +## Compile your binary from the sources + +```bash +git clone git@github.com:acouvreur/sablier.git +cd sablier +make +# Output will change depending on your distro +./sablier_draft_linux-amd64 +``` \ No newline at end of file diff --git a/docs/plugins/caddy.md b/docs/plugins/caddy.md new file mode 100644 index 0000000..6f0b015 --- /dev/null +++ b/docs/plugins/caddy.md @@ -0,0 +1,88 @@ +# Caddy Sablier Plugin + +Caddy Sablier Plugin. + +## Provider compatibility grid + +| Provider | Dynamic | Blocking | +| --------------------------------------- | :-----: | :------: | +| [Docker](/providers/docker) | ✅ | ✅ | +| [Docker Swarm](/providers/docker_swarm) | ✅ | ✅ | +| [Kubernetes](/providers/kubernetes) | ❌ | ❌ | + +## Install the plugin to Caddy + +Because Caddy does not do runtime evaluation, you need to build the base image with the plugin source code. + +In order to use the custom plugin for Caddy, you need to bundle it with Caddy. +Here I'll show you two options with Docker. + + + +#### **Using the provided Dockerfile** + +```bash +docker build https://github.com/acouvreur/sablier.git#v1.4.0-beta.3:plugins/caddy + --build-arg=CADDY_VERSION=2.6.4 + -t caddy:2.6.4-with-sablier +``` + +#### **Updating your Caddy Dockerfile** + +```docker +ARG CADDY_VERSION=2.6.4 +FROM caddy:${CADDY_VERSION}-builder AS builder + +ADD https://github.com /acouvreur/sablier.git#v1.4.0-beta.3 /sablier + +RUN xcaddy build \ + --with github.com/acouvreur/sablier/plugins/caddy=/sablier/plugins/caddy + +FROM caddy:${CADDY_VERSION} + +COPY --from=builder /usr/bin/caddy /usr/bin/caddy +``` + + + +## Configuration + +You can have the following configuration: + +```Caddyfile +:80 { + route /my/route { + sablier [=http://sablier:10000] { + [names container1,container2,...] + [group mygroup] + [session_duration 30m] + dynamic { + [display_name This is my display name] + [show_details yes|true|on] + [theme hacker-terminal] + [refresh_frequency 2s] + } + blocking { + [timeout 1m] + } + } + reverse_proxy myservice:port + } +} +``` + +### Exemple with a minimal configuration + +Almost all options are optional and you can setup very simple rules to use the server default values. + +```Caddyfile +:80 { + route /my/route { + sablier { + group mygroup + dynamic + } + reverse_proxy myservice:port + } +} +``` \ No newline at end of file diff --git a/docs/plugins/nginx.md b/docs/plugins/nginx.md new file mode 100644 index 0000000..08b4148 --- /dev/null +++ b/docs/plugins/nginx.md @@ -0,0 +1,94 @@ +# Nginx Sablier Plugin + +The Nginx Sablier Plugin is a javascript file that runs through the NJS Module. + +## Provider compatibility grid + +| Provider | Dynamic | Blocking | +| --------------------------------------- | :-----: | :------: | +| [Docker](/providers/docker) | ✅ | ✅ | +| [Docker Swarm](/providers/docker_swarm) | ✅ | ✅ | +| [Kubernetes](/providers/kubernetes) | ❌ | ❌ | + + +## Install the plugin to NGINX + +1. Load the `ngx_http_js_module.so` in the main nginx config file `/etc/nginx/nginx.conf` + ```nginx + load_module modules/ngx_http_js_module.so; + ``` +2. Copy/volume the `sablier.js` file to `/etc/nginx/conf.d/sablier.js` + +## Configure the plugin + +Use this sample for your APIs + +```nginx +js_import conf.d/sablier.js; + +resolver 127.0.0.11 valid=10s ipv6=off; + +server { +listen 80; + + subrequest_output_buffer_size 32k; + + # The internal location to reach sablier API + set $sablierUrl /sablier; + # Shared variable for default session duration + set $sablierSessionDuration 1m; + + # internal location for sablier middleware + # here, the sablier API is a container named "sablier" inside the same network as nginx + location /sablier/ { + internal; + proxy_method GET; + proxy_pass http://sablier:10000/; + } + + # A named location that can be used by the sablier middleware to redirect + location @whoami { + # Use variable in order to refresh DNS cache + set $whoami_server whoami; + proxy_pass http://$whoami_server:80; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # The actual location to match your API + # Will answer by a waiting page or redirect to your app if + # it is already up + location /dynamic/whoami { + set $sablierDynamicShowDetails true; + set $sablierDynamicRefreshFrequency 5s; + set $sablierNginxInternalRedirect @whoami; + set $sablierGroup my-group; + set $sablierDynamicName "Dynamic Whoami"; + set $sablierDynamicTheme hacker-terminal; + js_content sablier.call; + } +} +``` + +### Available variables + +You can configure the middleware behavior with the following variables: + +**General Configuration** + +- `set $sablierUrl` The internal routing to reach Sablier API +- `set $sablierGroup` Group name to use to filter by label, ignored if sablierNames is set +- `set $sablierSessionDuration` The session duration after which containers/services/deployments instances are shutdown +- `set $sablierNginxInternalRedirect` The internal location for the service to redirect e.g. @nginx + +**Dynamic Configuration** + +*if any of these variables is set, then all Blocking Configuration is ignored* + +- `set $sablierDynamicName` +- `set $sablierDynamicShowDetails` Set to true or false to show details specifcally for this middleware, unset to use Sablier server defaults +- `set $sablierDynamicTheme` The theme to use +- `set $sablierDynamicRefreshFrequency` The loading page refresh frequency + +**Blocking Configuration** + +- `set $sablierBlockingTimeout` waits until services are up and running but will not wait more than `timeout` \ No newline at end of file diff --git a/docs/plugins/overview.md b/docs/plugins/overview.md new file mode 100644 index 0000000..b4a473f --- /dev/null +++ b/docs/plugins/overview.md @@ -0,0 +1,25 @@ +# Reverse Proxy Plugins + +## What is a Reverse Proxy Plugin ? + +Reverse proxy plugins are the integration with a reverse proxy. + +?> Because Sablier is designed as an API that can be used on its own, reverse proxy integrations acts as a client of that API. + +It leverages the API calls to plugin integration to catch in-flight requests to Sablier. + +![Reverse Proxy Integration](../assets/img/reverse-proxy-integration.png) + +## Available Reverse Proxies + +| Reverse Proxy | Docker | Docker Swarm mode | Kubernetes | Podman | +| ---------------------------- | :----: | :---------------: | :--------: | :-------------------------------------------------------: | +| [Traefik](/plugins/traefik) | ✅ | ✅ | ✅ | [See #70](https://github.com/acouvreur/sablier/issues/70) | +| [Nginx](/plugins/nginx) | ✅ | ✅ | ❌ | +| [Caddy](/plugins/caddy) | ✅ | ✅ | ❌ | + +## Runtime and Compiled plugins + +Some reverse proxies have the capability to evaluate the plugins at runtime (Traefik with Yaegi, NGINX with Lua and JS plugins) which means the reverse proxy provides a way to consume the plugin directly. + +Some others enforce you to rebuild your reverse proxy (Caddy). \ No newline at end of file diff --git a/docs/plugins/traefik.md b/docs/plugins/traefik.md new file mode 100644 index 0000000..705f8ee --- /dev/null +++ b/docs/plugins/traefik.md @@ -0,0 +1,160 @@ +# Traefik Sablier Plugin + +The [Traefik Sablier Plugin](https://plugins.traefik.io/plugins/633b4658a4caa9ddeffda119/sablier) in the plugin catalog. + +## Provider compatibility grid + +| Provider | Dynamic | Blocking | +| --------------------------------------- | :-----: | :-----------------------------------------------------: | +| [Docker](/providers/docker) | ✅ | ✅ | +| [Docker Swarm](/providers/docker_swarm) | ✅ | ✅ | +| [Kubernetes](/providers/kubernetes) | ✅ | ❌ [#62](https://github.com/acouvreur/sablier/issues/62) | + +## Prerequisites + + + +#### **Docker** + +**Traefik will evict containers from its pool if they are stopped.** + +Meaning labels attached to containers to autodiscover them is not possible with this plugin. + +You have to use the dynamic config file provider instead. + +**❌ You cannot do the following:** + +```yaml +whoami: + image: containous/whoami:v1.5.0 + labels: + - traefik.enable + - traefik.http.routers.whoami.rule=PathPrefix(`/whoami`) + - traefik.http.routers.whoami.middlewares=my-sablier@file +``` + +**✅ You should do the following instead:** + +```yaml +http: + services: + whoami: + loadBalancer: + servers: + - url: "http://whoami:80" + + routers: + whoami: + rule: PathPrefix(`/whoami`) + entryPoints: + - "http" + middlewares: + - my-sablier@file + service: "whoami" +``` +*dynamic-config.yaml* + + +#### **Docker Swarm** + +**Traefik will evict services from its pool if they have 0 replicas.** + +In order to use service labels, you have to add the following option on top of each services that will use this plugin. + +See also [`traefik.docker.lbswarm`](https://doc.traefik.io/traefik/routing/providers/docker/#traefikdockerlbswarm) label + +```yaml +services: + whoami: + image: containous/whoami:v1.5.0 + deploy: + replicas: 0 + labels: + - traefik.docker.lbswarm=true +``` + +Traefik also have [allowEmptyServices](https://doc.traefik.io/traefik/providers/docker/#allowemptyservices) option which can be used instead. + +But the blocking strategy won't work for the same reasons described in [#62](https://github.com/acouvreur/sablier/issues/62). + +#### **Kubernetes** + +**Traefik will evict deployments from its pool if they have 0 endpoints available.** + +You must use [`allowEmptyServices`](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#allowemptyservices) + +The blocking strategy is not supported because everytime the underlying configuration changes, the whole router is regenrated, thus changing the router during a request will still map to the old router. For more details, see [#62](https://github.com/acouvreur/sablier/issues/62). + + + +## Install the plugin to Traefik + + + +#### **File (YAML)** + +```yaml +experimental: + plugins: + sablier: + moduleName: "github.com/acouvreur/sablier" + version: "v1.4.0-beta.3" +``` + +#### **CLI** + +```bash +--experimental.plugins.sablier.modulename=github.com/acouvreur/sablier +--experimental.plugins.sablier.version=v1.4.0-beta.3 +``` + + + +## Configure the plugin using the Dynamic Configuration + + + +#### **File (YAML)** + +```yaml +http: + middlewares: + my-sablier: + plugin: + sablier: + group: default + dynamic: + displayName: My Title + refreshFrequency: 5s + showDetails: "true" + theme: hacker-terminal + sablierUrl: http://sablier:10000 + sessionDuration: 1m +``` + +#### **Kubernetes CRD** + +```yaml +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: my-sablier + namespace: my-namespace +spec: + plugin: + sablier: + group: default + dynamic: + displayName: My Title + refreshFrequency: 5s + showDetails: "true" + theme: hacker-terminal + sablierUrl: http://sablier:10000 + sessionDuration: 1m +``` + + + +## Configuration reference + +TODO \ No newline at end of file diff --git a/docs/providers/docker.md b/docs/providers/docker.md new file mode 100644 index 0000000..0d62afe --- /dev/null +++ b/docs/providers/docker.md @@ -0,0 +1,64 @@ +# Docker + +The Docker provider communicates with the `docker.sock` socket to start and stop containers on demand. + +## Use the Docker provider + +In order to use the docker provider you can configure the [provider.name](TODO) property. + + + +#### **File (YAML)** + +```yaml +provider: + name: docker +``` + +#### **CLI** + +```bash +sablier start --provider.name=docker +``` + +#### **Environment Variable** + +```bash +PROVIDER_NAME=docker +``` + + + +!> **Ensure that Sablier has access to the docker socket!** + +```yaml +services: + sablier: + image: acouvreur/sablier:1.4.0-beta.3 + command: + - start + - --provider.name=docker + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' +``` + +## Register containers + +For Sablier to work, it needs to know which docker container to start and stop. + +You have to register your containers by opting-in with labels. + +```yaml +services: + whoami: + image: containous/whoami:v1.5.0 + labels: + - sablier.enable=true + - sablier.group=mygroup +``` + +## How does Sablier knows when a container is ready? + +If the container defines a Healthcheck, then it will check for healthiness before stating the `ready` status. + +If the containers does not define a Healthcheck, then as soon as the container has the status `started` \ No newline at end of file diff --git a/docs/providers/docker_swarm.md b/docs/providers/docker_swarm.md new file mode 100644 index 0000000..dcfb6d8 --- /dev/null +++ b/docs/providers/docker_swarm.md @@ -0,0 +1,66 @@ +# Docker Swarm + +The Docker Swarm provider communicates with the `docker.sock` socket to scale services on demand. + +## Use the Docker Swarm provider + +In order to use the docker provider you can configure the [provider.name](TODO) property. + + + +#### **File (YAML)** + +```yaml +provider: + name: docker_swarm +``` + +#### **CLI** + +```bash +sablier start --provider.name=docker_swarm +``` + +#### **Environment Variable** + +```bash +PROVIDER_NAME=docker_swarm +``` + + + + +!> **Ensure that Sablier has access to the docker socket!** + +```yaml +services: + sablier: + image: acouvreur/sablier:1.4.0-beta.3 + command: + - start + - --provider.name=docker_swarm + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' +``` + +## Register services + +For Sablier to work, it needs to know which docker services to scale up and down. + +You have to register your services by opting-in with labels. + +```yaml +services: + whoami: + image: containous/whoami:v1.5.0 + deploy: + labels: + - sablier.enable=true + - sablier.group=mygroup +``` + +## How does Sablier knows when a service is ready? + +Sablier checks for the service replicas. As soon as the current replicas matches the wanted replicas, then the service is considered `ready`. + +?> Docker Swarm uses the container's healthcheck to check if the container is up and running. So the provider has a native healthcheck support. \ No newline at end of file diff --git a/docs/providers/kubernetes.md b/docs/providers/kubernetes.md new file mode 100644 index 0000000..40ddee5 --- /dev/null +++ b/docs/providers/kubernetes.md @@ -0,0 +1,98 @@ +# Kubernetes + +Sablier assumes that it is deployed within the Kubernetes cluster to use the Kubernetes API internally. + +## Use the Kubernetes provider + +In order to use the docker provider you can configure the [provider.name](TODO) property. + + + +#### **File (YAML)** + +```yaml +provider: + name: kubernetes +``` + +#### **CLI** + +```bash +sablier start --provider.name=kubernetes +``` + +#### **Environment Variable** + +```bash +PROVIDER_NAME=kubernetes +``` + + + +!> **Ensure that Sablier has the necessary roles!** + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: sablier +rules: + - apiGroups: + - apps + - "" + resources: + - deployments + - statefulsets + verbs: + - get # Retrieve info about specific dep + - list # Events + - watch # Events + - apiGroups: + - apps + - "" + resources: + - deployments/scale + - statefulsets/scale + verbs: + - patch # Scale up and down + - update # Scale up and down + - get # Retrieve info about specific dep + - list # Events + - watch # Events +``` + +## Register Deployments + +For Sablier to work, it needs to know which deployments to scale up and down. + +You have to register your deployments by opting-in with labels. + + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: whoami + labels: + app: whoami + sablier.enable: "true" + sablier.group: mygroup +spec: + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: containous/whoami:v1.5.0 +``` + +## How does Sablier knows when a deployment is ready? + +Sablier checks for the deployment replicas. As soon as the current replicas matches the wanted replicas, then the deployment is considered `ready`. + +?> Kubernetes uses the Pod healthcheck to check if the Pod is up and running. So the provider has a native healthcheck support. \ No newline at end of file diff --git a/docs/providers/overview.md b/docs/providers/overview.md new file mode 100644 index 0000000..4b5b3df --- /dev/null +++ b/docs/providers/overview.md @@ -0,0 +1,24 @@ +# Providers + +## What is a Provider? + +A Provider is how Sablier can interact with your instances. + +A Provider typically have the following capabilities: +- Start an instance +- Stop an instance +- Get the current status of an instance +- Listen for instance lifecycle events (started, stopped) + +## Available providers + +| | Provider | Name | Details | +| :---: | --------------------------------------- | -------------- | -------------------------------------------------------- | +| | [Docker](/providers/docker) | `docker` | Stop and start containers on demand | +| | [Docker Swarm](/providers/docker_swarm) | `docker_swarm` | Scale down to zero and up services on demand | +| | [Docker](/providers/kubernetes) | `kubernetes` | Scale down and up deployments and statefulsets on demand | +| | [Podman](/providers/podman) | `podman` | Work in progress | +| | [EC2](/providers/ec2) | `ec2` | Work in progress | +| | [Systemd](/providers/systemd) | `systemd` | Work in progress | + +*Your Provider is not on the list? Open an issue or a pull request to add this functionnality here!* \ No newline at end of file diff --git a/docs/strategies.md b/docs/strategies.md new file mode 100644 index 0000000..b3878e5 --- /dev/null +++ b/docs/strategies.md @@ -0,0 +1,59 @@ +# Strategies + +When configuring + +## Dynamic Strategy + +The **Dynamic Strategy** provides a waiting page for your session. + +?> This strategy is well suited for a user that would access a frontend directly and expects to see a loading page. + +```plantuml +@startuml + +User -> Proxy: Website Request +Proxy -> Sablier: Reverse Proxy Plugin Request Session Status +Proxy <-- Sablier: Returns the X-Sablier-Status Header + +alt `X-Sablier-Status` value is `not-ready` + + User <-- Proxy: Serve the waiting page + loop until `X-Sablier-Status` value is `ready` + User -> Proxy: Self-Reload Waiting Page + Proxy -> Sablier: Reverse Proxy Plugin Request Session Status + Sablier -> Provider: Request Instance Status + Sablier <-- Provider: Response Instance Status + Proxy <-- Sablier: Returns the waiting page + User <-- Proxy: Serve the waiting page + end + +end + +User <-- Proxy: Content + +@enduml +``` +## Blocking Strategy + +The **Blocking Strategy** hangs the request until your session is ready. + +?> This strategy is well suited for an API communication. + +```plantuml +@startuml + +User -> Proxy: Website Request +Proxy -> Sablier: Reverse Proxy Plugin Request Session Status +Sablier -> Provider: Request Instance Status + +alt `Instance` status is `not-ready` + +end + +Sablier <-- Provider: Response Instance Status +Proxy <-- Sablier: Response + +User <-- Proxy: Content + +@enduml +``` \ No newline at end of file diff --git a/docs/THEMES.md b/docs/themes.md similarity index 79% rename from docs/THEMES.md rename to docs/themes.md index c35129b..473257c 100644 --- a/docs/THEMES.md +++ b/docs/themes.md @@ -1,24 +1,28 @@ -# Creating your own loading theme +# Themes + +Sablier comes with a set of default theme that you can use. + +You can also extend the themes by providing your own and they will be templated as Go Templates. + +## The embedded themes + + +| Name | Preview | +| :---------------: | :-------------------------------------------------------------------------------------------------------: | +| `ghost` | ![ghost](https://raw.githubusercontent.com/acouvreur/sablier/main/docs/img/ghost.png) | +| `shuffle` | ![shuffle](https://raw.githubusercontent.com/acouvreur/sablier/main/docs/img/shuffle.png) | +| `hacker-terminal` | ![hacker-terminal](https://raw.githubusercontent.com/acouvreur/sablier/main/docs/img/hacker-terminal.png) | +| `matrix` | ![matrix](https://raw.githubusercontent.com/acouvreur/sablier/main/docs/img/matrix.png) | + -- [Creating your own loading theme](#creating-your-own-loading-theme) - - [Custom themes locations](#custom-themes-locations) - - [Create a custom theme](#create-a-custom-theme) - - [Available Go Template Values](#available-go-template-values) - - [The `` tag](#the-meta-http-equivrefresh--tag) - - [The `showDetails` option](#the-showdetails-option) - - [The embedded themes](#the-embedded-themes) - - [How to load my custom theme](#how-to-load-my-custom-theme) - - [See the available themes from the API](#see-the-available-themes-from-the-api) ## Custom themes locations -You can use the argument `--strategy.dynamic.coustom-themes` to define the location to search upon starting. +You can use the argument `--strategy.dynamic.custom-themes` to define the location to search upon starting. -By default, the docker image looks for theme inside the `/etc/sablier/themes` folder. +By default, the docker image looks for themes located inside the `/etc/sablier/themes` folder. ```yaml -version: 3.9 - services: sablier: image: acouvreur/sablier:1.4.0-beta.3 @@ -30,7 +34,6 @@ services: It will look recursively for themes with the `.html` extension. - You **cannot** load new themes added in the folder without restarting - - Why? Because we build a theme whitelist in order to prevent malicious payload crafting by using `theme=../../very_secret.txt` - You **can** modify the existing themes files ## Create a custom theme @@ -72,10 +75,6 @@ So the first step to create your own theme is to include the `HTML http-e If `showDetails` is set to `false` then the `.InstanceStates` will be an empty array. -## The embedded themes - -See the [embedded themes](../app/http/pages/themes/). - ## How to load my custom theme You can load themes by specifying their name and their relative path from the `--strategy.dynamic.custom-themes-path` value. @@ -90,14 +89,14 @@ You can load themes by specifying their name and their relative path from the `- Such as -```shell +```bash curl 'http://localhost:10000/api/strategies/dynamic?session_duration=1m&names=nginx&theme=custom1' ``` ## See the available themes from the API -``` -> curl 'http://localhost:10000/api/strategies/dynamic/themes' +```bash +curl 'http://localhost:10000/api/strategies/dynamic/themes' ``` ```json { diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 0000000..1696de1 --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,15 @@ +# Versioning + +Sablier follows the [Semantic Versioning 2.0.0](https://semver.org/) Specification (SemVer). + +Given a version number MAJOR.MINOR.PATCH, increment the: + + 1. MAJOR version when you make incompatible API changes + 2. MINOR version when you add functionality in a backwards compatible manner + 3. PATCH version when you make backwards compatible bug fixes + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. + +This process is fully automated using [Semantic Release](https://github.com/semantic-release/semantic-release). + +The configuration is [release.config.js](https://github.com/acouvreur/sablier/blob/main/release.config.js). \ No newline at end of file