Compare commits

...

5 Commits

Author SHA1 Message Date
1b7344bcb5 fix(config): supporting multiple env variables in a value.
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
ref #9
2023-07-10 17:02:56 +02:00
bcc73a97cc feat(k8s): adding kubernetes support
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
Cadoles/bouncer/pipeline/pr-develop This commit looks good
Now we can use skaffold and deploy bouncer in a kubernetes cluster

ref #10
2023-07-10 17:02:56 +02:00
b44ff2a68e doc: add proxy http api reference
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
2023-07-08 12:19:43 -06:00
c719fdca37 feat: add prometheus + grafterm dashboard in local dev environment 2023-07-08 12:18:38 -06:00
2b91c1e167 feat(store,repository): add more integration tests
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
2023-07-07 12:22:31 -06:00
32 changed files with 984 additions and 11 deletions

3
.gitignore vendored
View File

@ -7,4 +7,5 @@
/admin-key.json
/.bouncer-token
/data
/out
/out
.dockerconfigjson

View File

@ -101,6 +101,12 @@ gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
GITEA_RELEASE_ATTACHMENTS="$$(find .gitea-release/* -type f)" \
tools/gitea-release/bin/gitea-release.sh
grafterm: tools/grafterm/bin/grafterm
tools/grafterm/bin/grafterm -c ./misc/grafterm/dashboard.json -v job=bouncer-proxy -r 5s
siege:
siege -i -c 100 -f ./misc/siege/urls.txt
tools/gitea-release/bin/gitea-release.sh:
mkdir -p tools/gitea-release/bin
curl --output tools/gitea-release/bin/gitea-release.sh https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/gitea/gitea-release.sh
@ -110,6 +116,10 @@ tools/modd/bin/modd:
mkdir -p tools/modd/bin
GOBIN=$(PWD)/tools/modd/bin go install github.com/cortesi/modd/cmd/modd@latest
tools/grafterm/bin/grafterm:
mkdir -p tools/grafterm/bin
GOBIN=$(PWD)/tools/grafterm/bin go install github.com/slok/grafterm/cmd/grafterm@v0.2.0
full-version:
@echo $(FULL_VERSION)
@ -128,4 +138,12 @@ run-redis:
redis-shell:
docker exec -it \
bouncer-redis \
redis-cli
redis-cli
run-prometheus:
docker kill bouncer-prometheus || exit 0
docker run --rm -t \
--name bouncer-prometheus \
--network host \
-v $(PWD)/misc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus

View File

@ -10,6 +10,7 @@
- [(FR) - Layers](./fr/references/layers/README.md)
- [(FR) - Fichier de configuration](../misc/packaging/common/config.yml)
- [(FR) - API d'administration](./fr/references/admin_api.md)
## Tutoriels

View File

@ -0,0 +1,182 @@
# API d'administration
## Authentification
L'ensemble des appels aux APIs HTTP du service `bouncer-admin` sont authentifiées via l'utilisation d'un jeton [JWT](https://datatracker.ietf.org/doc/html/rfc7519) signé par la clé privée du serveur.
Le jeton d'accès doit être transmis avec l'ensemble des appels aux points d'entrée via l'entête HTTP `Authorization` en respectant la forme suivante:
```
Authorization: Bearer <jwt>
```
### Génération d'un jeton d'authentification
La génération d'un jeton d'authentification s'effectue via la commande suivante:
```shell
bouncer auth create-token --subject "<subject>" --role "<role>"
```
Où:
- `"<subject>"` est une chaîne de caractère arbitraire ayant pour objectif d'identifier de manière unique l'utilisateur associé au jeton;
- `"<role>"` peut prendre une des deux valeurs `reader` ou `writer` correspondant aux droits suivants respectifs:
- droit en lecture sur l'ensemble des entités (proxy, layer);
- droit en lecture ET en écriture sur l'ensemble des entités.
## Points d'entrée
### `POST /api/v1/proxies`
Créer un nouveau proxy
#### Exemple de corps de requête
```json5
{
"name": "myproxy", // OBLIGATOIRE - Nom du proxy
"to": "https://www.cadoles.com", // OBLIGATOIRE - Site distant ciblé par le proxy
"from": ["*"] // OPTIONNEL - Liste de patrons de filtrage associés au proxy
}
```
#### Exemple de résultat
```json5
{
"data": {
"proxy": {
"name": "myproxy",
"weight": 0,
"enabled": false,
"to": "https://www.cadoles.com",
"from": ["*"],
"createdAt": "2018-12-10T13:45:00.000Z",
"updatedAt": "2018-12-10T13:45:00.000Z"
}
}
}
```
#### Source
Voir [`internal/admin/proxy_route.go#createProxy()`](../../../internal/admin/proxy_route.go#createProxy)
### `GET /api/v1/proxies/{proxyName}`
Récupérer les informations complètes sur un proxy
#### Paramètres
- `{proxyName}` - Nom du proxy
#### Exemple de résultat
```json5
{
"data": {
"proxy": {
"name": "myproxy",
"weight": 0,
"enabled": false,
"to": "https://www.cadoles.com",
"from": ["*"],
"createdAt": "2018-12-10T13:45:00.000Z",
"updatedAt": "2018-12-10T13:45:00.000Z"
}
}
}
```
#### Source
Voir [`internal/admin/proxy_route.go#getProxy()`](../../../internal/admin/proxy_route.go#getProxy)
### `PUT /api/v1/proxies/{proxyName}`
Modifier un proxy
#### Exemple de corps de requête
```json5
{
"to": "https://www.cadoles.com", // OPTIONNEL - Site distant ciblé par le proxy
"from": ["mylocalproxydomain:*"], // OPTIONNEL - Liste de patrons de filtrage associés au proxy
"weight": 100, // OPTIONNEL - Poids à associer au proxy
"enabled": true, // OPTIONNEL - Activer/désactiver le proxy
}
```
#### Exemple de résultat
```json5
{
"data": {
"proxy": {
"name": "myproxy",
"weight": 100,
"enabled": true,
"to": "https://www.cadoles.com",
"from": ["mylocalproxydomain:*"],
"createdAt": "2018-12-10T13:45:00.000Z",
"updatedAt": "2020-10-02T15:09:00.000Z"
}
}
}
```
#### Source
Voir [`internal/admin/proxy_route.go#updateProxy()`](../../../internal/admin/proxy_route.go#updateProxy)
### `GET /api/v1/proxies?names={name1,name2,...}`
Lister les proxies existants
#### Paramètres
- `{names}` - Optionnel - Liste des noms de proxy à appliquer en tant que filtre
#### Exemple de résultat
```json5
{
"data": {
"proxies": [
{
"name": "myproxy",
"weight": 0,
"enabled": false,
}
]
}
}
```
#### Source
Voir [`internal/admin/proxy_route.go#queryProxy()`](../../../internal/admin/proxy_route.go#queryProxy)
## `DELETE /api/v1/proxies/{proxyName}`
Supprimer le proxy
#### Paramètres
- `{proxyName}` - Nom du proxy
#### Exemple de résultat
```json5
{
"data": {
"proxyName": "myproxy"
}
}
```
#### Source
Voir [`internal/admin/proxy_route.go#deleteProxy()`](../../../internal/admin/proxy_route.go#deleteProxy)

View File

@ -70,15 +70,15 @@ docker run --rm -t \
Surveiller les sources, compiler celles ci en cas de modifications et lancer les services `bouncer-proxy` et `bouncer-admin`.
#### `make test`
### `make test`
Exécuter les tests unitaires/d'intégration du projet.
#### `make build`
### `make build`
Compiler une version de développement du binaire `bouncer`.
#### `make docker-build`
### `make docker-build`
Construire une image Docker pour Bouncer.
@ -92,6 +92,13 @@ docker run \
bouncer server proxy run
```
### `make grafterm`
Afficher un tableau de bord [`grafterm`](https://github.com/slok/grafterm) branché sur l'instance Prometheus locale.
### `make siege`
Lancer une session de test [`siege`](https://github.com/JoeDog/siege) sur l'instance `bouncer-proxy` locale.
## Arborescence du projet
```bash

1
go.mod
View File

@ -6,6 +6,7 @@ require (
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230701194111-c6b3d482cca6
github.com/Masterminds/sprig/v3 v3.2.3
github.com/btcsuite/btcd/btcutil v1.1.3
github.com/drone/envsubst v1.0.3
github.com/getsentry/sentry-go v0.22.0
github.com/go-chi/chi/v5 v5.0.8
github.com/jedib0t/go-pretty/v6 v6.4.6

2
go.sum
View File

@ -163,6 +163,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=

View File

@ -6,11 +6,13 @@ import (
"strconv"
"time"
"github.com/drone/envsubst"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)
var reVar = regexp.MustCompile(`^\${(\w+)}$`)
// var reVar = regexp.MustCompile(`^\${(\w+)}$`)
var reVar = regexp.MustCompile(`\${(.*?)}`)
type InterpolatedString string
@ -130,14 +132,22 @@ type InterpolatedStringSlice []string
func (iss *InterpolatedStringSlice) UnmarshalYAML(value *yaml.Node) error {
var data []string
var evErr error
if err := value.Decode(&data); err != nil {
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into map", value.Value, value.Line)
}
for index, value := range data {
if match := reVar.FindStringSubmatch(value); len(match) > 0 {
value = os.Getenv(match[1])
//match := reVar.FindStringSubmatch(value)
re := regexp.MustCompile(`\${(.*?)}`)
res := re.FindAllStringSubmatch(value, 10)
if len(res) > 0 {
value, evErr = envsubst.EvalEnv(value)
if evErr != nil {
return evErr
}
}
data[index] = value

View File

@ -2,5 +2,5 @@ logger:
level: 0
format: human
http:
host: "0.0.0.0"
port: 3000
host: "${LISTEN_ADDR}"
port: 3000

View File

@ -2,6 +2,7 @@ package testsuite
import (
"context"
"reflect"
"testing"
"forge.cadoles.com/cadoles/bouncer/internal/store"
@ -48,6 +49,187 @@ var layerRepositoryTestCases = []layerRepositoryTestCase{
return errors.Errorf("layer.UpdatedAt should not be zero value")
}
return nil
},
},
{
Name: "Create then get layer",
Do: func(repo store.LayerRepository) error {
ctx := context.Background()
var proxyName store.ProxyName = "create_then_get_layer_proxy"
var layerName store.LayerName = "create_then_get_layer"
var layerType store.LayerType = "dummy"
var layerOptions store.LayerOptions = store.LayerOptions{
"foo": "bar",
"test": struct {
Items []int `json:"items"`
}{
Items: []int{1, 2, 3},
},
}
createdLayer, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
if err != nil {
return errors.WithStack(err)
}
foundLayer, err := repo.GetLayer(ctx, proxyName, layerName)
if err != nil {
return errors.WithStack(err)
}
if e, g := createdLayer.Name, foundLayer.Name; e != g {
return errors.Errorf("foundLayer.Name: expected '%v', got '%v'", createdLayer.Name, foundLayer.Name)
}
if e, g := createdLayer.CreatedAt, foundLayer.CreatedAt; !reflect.DeepEqual(e, g) {
return errors.Errorf("foundLayer.CreatedAt: expected '%v', got '%v'", createdLayer.CreatedAt, foundLayer.CreatedAt)
}
if e, g := createdLayer.UpdatedAt, foundLayer.UpdatedAt; !reflect.DeepEqual(e, g) {
return errors.Errorf("foundLayer.UpdatedAt: expected '%v', got '%v'", createdLayer.UpdatedAt, foundLayer.UpdatedAt)
}
if e, g := createdLayer.Enabled, foundLayer.Enabled; !reflect.DeepEqual(e, g) {
return errors.Errorf("foundLayer.Enabled: expected '%v', got '%v'", createdLayer.Enabled, foundLayer.Enabled)
}
if e, g := createdLayer.Weight, foundLayer.Weight; !reflect.DeepEqual(e, g) {
return errors.Errorf("foundLayer.Weight: expected '%v', got '%v'", createdLayer.Weight, foundLayer.Weight)
}
if e, g := createdLayer.Proxy, foundLayer.Proxy; !reflect.DeepEqual(e, g) {
return errors.Errorf("foundLayer.Proxy: expected '%v', got '%v'", createdLayer.Proxy, foundLayer.Proxy)
}
if e, g := createdLayer.Options, foundLayer.Options; !reflect.DeepEqual(e, g) {
return errors.Errorf("foundLayer.Options: expected '%v', got '%v'", createdLayer.Options, foundLayer.Options)
}
return nil
},
},
{
Name: "Create then delete layer",
Do: func(repo store.LayerRepository) error {
ctx := context.Background()
var layerName store.LayerName = "create_then_delete_layer"
var proxyName store.ProxyName = store.ProxyName(string(layerName) + "_proxy")
var layerType store.LayerType = "dummy"
var layerOptions store.LayerOptions = store.LayerOptions{
"foo": "bar",
"test": struct {
Items []int `json:"items"`
}{
Items: []int{1, 2, 3},
},
}
createdLayer, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
if err != nil {
return errors.WithStack(err)
}
if err := repo.DeleteLayer(ctx, createdLayer.Proxy, createdLayer.Name); err != nil {
return errors.WithStack(err)
}
foundLayer, err := repo.GetLayer(ctx, createdLayer.Proxy, createdLayer.Name)
if err == nil {
return errors.New("err should not be nil")
}
if !errors.Is(err, store.ErrNotFound) {
return errors.Errorf("err should be store.ErrNotFound, got '%+v'", err)
}
if foundLayer != nil {
return errors.Errorf("foundLayer should be nil, got '%v'", foundLayer)
}
return nil
},
},
{
Name: "Create already existing layer",
Do: func(repo store.LayerRepository) error {
ctx := context.Background()
var layerName store.LayerName = "create_already_existing_layer"
var proxyName store.ProxyName = store.ProxyName(string(layerName) + "_proxy")
var layerType store.LayerType = "dummy"
var layerOptions store.LayerOptions = store.LayerOptions{
"foo": "bar",
"test": struct {
Items []int `json:"items"`
}{
Items: []int{1, 2, 3},
},
}
_, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
if err != nil {
return errors.WithStack(err)
}
_, err = repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
if err == nil {
return errors.New("err should not be nil")
}
if !errors.Is(err, store.ErrAlreadyExist) {
return errors.Errorf("err: expected store.ErrAlreadyExists, got '%+v'", err)
}
return nil
},
},
{
Name: "Create then query layer",
Do: func(repo store.LayerRepository) error {
ctx := context.Background()
var layerName store.LayerName = "create_then_query_layer"
var proxyName store.ProxyName = store.ProxyName(string(layerName) + "_proxy")
var layerType store.LayerType = "dummy"
var layerOptions store.LayerOptions = store.LayerOptions{
"foo": "bar",
"test": struct {
Items []int `json:"items"`
}{
Items: []int{1, 2, 3},
},
}
createdLayer, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
if err != nil {
return errors.WithStack(err)
}
headers, err := repo.QueryLayers(ctx, createdLayer.Proxy)
if err != nil {
return errors.WithStack(err)
}
if len(headers) < 1 {
return errors.Errorf("len(headers): expected value > 1, got '%v'", len(headers))
}
found := false
for _, h := range headers {
if h.Name == createdLayer.Name {
found = true
break
}
}
if !found {
return errors.New("could not find created layer in query results")
}
return nil
},
},

View File

@ -83,6 +83,14 @@ var proxyRepositoryTestCases = []proxyRepositoryTestCase{
return errors.Errorf("foundProxy.To: expected '%v', got '%v'", createdProxy.To, foundProxy.To)
}
if e, g := createdProxy.Enabled, foundProxy.Enabled; e != g {
return errors.Errorf("foundProxy.Enabled: expected '%v', got '%v'", createdProxy.Enabled, foundProxy.Enabled)
}
if e, g := createdProxy.Weight, foundProxy.Weight; e != g {
return errors.Errorf("foundProxy.Weight: expected '%v', got '%v'", createdProxy.Weight, foundProxy.Weight)
}
if e, g := createdProxy.CreatedAt, foundProxy.CreatedAt; e != g {
return errors.Errorf("foundProxy.CreatedAt: expected '%v', got '%v'", createdProxy.CreatedAt, foundProxy.CreatedAt)
}
@ -127,7 +135,7 @@ var proxyRepositoryTestCases = []proxyRepositoryTestCase{
},
},
{
Name: "Create then query",
Name: "Create then query layer",
Do: func(repo store.ProxyRepository) error {
ctx := context.Background()

View File

@ -0,0 +1,196 @@
{
"version": "v1",
"datasources": {
"prometheus": {
"prometheus": {
"address": "http://127.0.0.1:9090"
}
}
},
"dashboard": {
"variables": {
"job": {
"constant": { "value": "bouncer-proxy" }
},
"interval": {
"interval": { "steps": 50 }
}
},
"widgets": [
{
"title": "Bouncer - Total queue sessions",
"gridPos": { "w": 20 },
"singlestat": {
"thresholds": [{ "color": "#47D038" }],
"query": {
"datasourceID": "prometheus",
"expr": "sum(bouncer_layer_queue_sessions{job=\"{{.job}}\"})"
}
}
},
{
"title": "Bouncer Traffic",
"gridPos": {
"w": 80
},
"graph": {
"queries": [
{
"datasourceID": "prometheus",
"expr": "sum(rate(bouncer_proxy_director_proxy_requests_total{job=\"{{.job}}\"}[{{.interval}}]))",
"legend": "req/s"
}
]
}
},
{
"title": "Goroutines",
"gridPos": { "w": 20 },
"singlestat": {
"thresholds": [{ "color": "#47D038" }],
"query": {
"datasourceID": "prometheus",
"expr": "sum(go_goroutines{job=\"{{.job}}\"})"
}
}
},
{
"title": "GC duration",
"gridPos": { "w": 20 },
"singlestat": {
"unit": "second",
"query": {
"datasourceID": "prometheus",
"expr": "max(go_gc_duration_seconds{job=\"{{.job}}\"})"
}
}
},
{
"title": "Stack",
"gridPos": { "w": 20 },
"singlestat": {
"unit": "bytes",
"thresholds": [{ "color": "#22F1F1" }],
"query": {
"datasourceID": "prometheus",
"expr": "sum(go_memstats_stack_inuse_bytes{job=\"{{.job}}\"})"
}
}
},
{
"title": "Heap",
"gridPos": { "w": 20 },
"singlestat": {
"unit": "bytes",
"thresholds": [{ "color": "#22F1F1" }],
"query": {
"datasourceID": "prometheus",
"expr": "sum(go_memstats_heap_inuse_bytes{job=\"{{.job}}\"})"
}
}
},
{
"title": "Alloc",
"gridPos": { "w": 20 },
"singlestat": {
"unit": "bytes",
"thresholds": [{ "color": "#22F1F1" }],
"query": {
"datasourceID": "prometheus",
"expr": "sum(go_memstats_alloc_bytes{job=\"{{.job}}\"})"
}
}
},
{
"title": "Goroutines",
"gridPos": { "w": 50 },
"graph": {
"visualization": {
"legend": { "disable": true },
"yAxis": { "unit": "", "decimals": 2 }
},
"queries": [
{
"datasourceID": "prometheus",
"expr": "sum(go_goroutines{job=\"{{.job}}\"})"
}
]
}
},
{
"title": "GC duration",
"gridPos": { "w": 50 },
"graph": {
"queries": [
{
"datasourceID": "prometheus",
"expr": "max(go_gc_duration_seconds{job=\"{{.job}}\"}) by (quantile)",
"legend": "Q{{.quantile}}"
}
],
"visualization": {
"yAxis": { "unit": "second" },
"seriesOverride": [
{ "regex": "^Q0$", "color": "#F9E2D2" },
{ "regex": "^Q0.25$", "color": "#F2C96D" },
{ "regex": "^Q0.5(0)?$", "color": "#EAB839" },
{ "regex": "^Q0.75$", "color": "#EF843C" },
{ "regex": "^Q1(.0)?$", "color": "#E24D42" }
]
}
}
},
{
"title": "Memory",
"gridPos": { "w": 50 },
"graph": {
"visualization": {
"yAxis": { "unit": "byte", "decimals": 0 }
},
"queries": [
{
"datasourceID": "prometheus",
"expr": "sum(go_memstats_stack_inuse_bytes{job=\"{{.job}}\"})",
"legend": "stack inuse"
},
{
"datasourceID": "prometheus",
"expr": "sum(go_memstats_heap_inuse_bytes{job=\"{{.job}}\"})",
"legend": "heap inuse"
},
{
"datasourceID": "prometheus",
"expr": "sum(go_memstats_alloc_bytes{job=\"{{.job}}\"})",
"legend": "alloc"
}
]
}
},
{
"title": "Memory ops rate",
"gridPos": {
"w": 50
},
"graph": {
"queries": [
{
"datasourceID": "prometheus",
"expr": "sum(rate(go_memstats_frees_total{job=\"{{.job}}\"}[{{.interval}}]))",
"legend": "frees/s"
},
{
"datasourceID": "prometheus",
"expr": "sum(rate(go_memstats_mallocs_total{job=\"{{.job}}\"}[{{.interval}}]))",
"legend": "mallocs/s"
},
{
"datasourceID": "prometheus",
"expr": "sum(rate(go_memstats_lookups_total{job=\"{{.job}}\"}[{{.interval}}]))",
"legend": "lookups/s"
}
]
}
}
]
}
}

View File

@ -0,0 +1,49 @@
FROM golang:1.20 AS BUILD
RUN apt-get update \
&& apt-get install -y make
ARG YQ_VERSION=4.34.1
RUN mkdir -p /usr/local/bin \
&& wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64 \
&& chmod +x /usr/local/bin/yq
COPY . /src
WORKDIR /src
RUN make GORELEASER_ARGS='build --rm-dist --single-target --snapshot' goreleaser
# Patch config
RUN /src/dist/bouncer_linux_amd64_v1/bouncer -c '' config dump > /src/dist/bouncer_linux_amd64_v1/config.yml \
&& yq -i '.layers.queue.templateDir = "/usr/share/bouncer/layers/queue/templates"' /src/dist/bouncer_linux_amd64_v1/config.yml \
&& yq -i '.admin.auth.privateKey = "/etc/bouncer/admin-key.json"' /src/dist/bouncer_linux_amd64_v1/config.yml \
&& yq -i '.redis.adresses = ["redis:6379"]' /src/dist/bouncer_linux_amd64_v1/config.yml
FROM alpine:3.18 AS RUNTIME
ARG DUMB_INIT_VERSION=1.2.5
RUN apk add --no-cache ca-certificates
RUN mkdir -p /usr/local/bin \
&& wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_x86_64 \
&& chmod +x /usr/local/bin/dumb-init
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
RUN mkdir -p /usr/local/bin /usr/share/bouncer/bin /etc/bouncer
COPY --from=BUILD /src/dist/bouncer_linux_amd64_v1/bouncer /usr/share/bouncer/bin/bouncer
COPY --from=BUILD /src/layers /usr/share/bouncer/layers
COPY --from=BUILD /src/dist/bouncer_linux_amd64_v1/config.yml /etc/bouncer/config.yml
RUN ln -s /usr/share/bouncer/bin/bouncer /usr/local/bin/bouncer
EXPOSE 8080
EXPOSE 8081
ENV BOUNCER_CONFIG=/etc/bouncer/config.yml
CMD ["bouncer"]

View File

@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: bouncer
resources:
- ./resources/namespace.yaml
- ./resources/bouncer-server
- ./resources/bouncer-admin
- ./resources/redis

View File

@ -0,0 +1 @@
{"d":"JuBw5OsGv3rPgVczxUgtJ6iUQ41LQu4Xpu-t8IKI_z8r-BZBlbndxidPmRlGZASLGL3rhY4qw6_ScFxakrMpCreO1RMU0kqtz--N48BXFnW5tEgr1voyyKP__bPssQNn6PgkoyAd11es7MEKlBff_DtGrcSkVRgU0zDZB-vIU0aNEIZPNw0icbYqc1u_QQNPpBU9cw6P33WHhzvfCVAkZKRszwznhiPM08n1vjpiA7e1kQ8a6OC4IFZBvohkmpmyOq1g1OLRABQ83YPCjGjCAejO-jEWkbLksp6rAl_YYpCvfBAjFV76JuZq4eh5IU82LsSfi3PGYBkhxWuLY779XQ","dp":"gljHOQowGK7fVn2DJizWtgRIDJuKpKnoX2PWNJUbm2WZwcEPZalAkxn7Y-w_reLVJZuRpfKEUMS-Tn3-CwI1ZjCHPqMPTXcoG0Pe2E-Z88jOs9lW4XSOASiiM980VIvkV1xCxDJkN3NsDFQ9j9kRGnKuMnsucCW3AKaU917hXNU","dq":"mqY19JcEBDnzS70_XkAsOKqPzemOScax66b-4N6zrsgeLVlRjHffY9uCAgBWzlxOidRdQN8q23ZJB4fqsKB2w00Iw7Jxx94IoAKGjKDT5iB48Y_kdKLAwSHRTXsqA9GG3po_H_JpP_EqX4TDBYtqQZuBD_tACP9HbLYMi_V2YU8","e":"AQAB","kty":"RSA","n":"sam0X0BGcuFwX8z3Wde8cv2o_zl6A9ghpkT0tCjw8qH3GNWrbAqzncSWdHBzoChBgAbuTOVs-ixYC0KeUhwFdc8Ul-jmKJWFaS8kIr3y4EH62-vLgMuIKfaxbsyUG6KMkJfnftge1jPO4ccddNej9msxcqTxu37dcgstutwtd6QkS9p5RrNbDBc8-Z7SQ4TuxJfP8msXRnCPJ-I44yszGdQF1Np2DXakJHVn8PBrDh3iSFwORw8jxNS4oS0OlBl5aSc0t5XkkaNcSU2a50SKts290w54fl6MPJ1sLnnznLy4uu37-nrfEUvqRLDZL9B1F82RM1dtLIIiN4gnSrMlpQ","p":"wOmFPhAT_wXWzMuwtEdYIer3-CiOWxFKpFL09eEJkJ29MIUchEaoiJaUAghqPxM48llfOVaUaLbFVxmo5U3fyjNMaP-nHMUBwojutykMK-gC2R3J4bQgFWfKbGSL7M7UsextAvpq9iiOuR0LNE-xTfCgPIxHVdPZskO3yx0DkjM","q":"68OGRb0tLRjb_PpkGctcSjEz_vvcyjzxGL-fn4_h4GCw98Xrj6Y4rZ4lfWWRSeDohSvdd-ICSlxvxkQOIOcA0H7jyJcBC0KDs4hX5BRGJNDri3QX0ry4_F1ptAdbfiFgQGqCfMRCr7L60Tfd_6tLczvny7eEBKQNGdj6dLfhgMc","qi":"DFwixyxUDf0REPLLa8hOKieRL95_AH9rbYWzStBOdSjKWra5l0reD6a4bbvAYvl0e8qCcRI6S8Nzpz0BYm4sJL7poVOnjxqvBY3Q9Ppf4Mq8lW39pOCJcqOHIvvYHsMjTC5uwp7Yg2p0GvxuUibbyNL1PXf6WZ_szVP_oSMrCXA"}

View File

@ -0,0 +1,36 @@
admin:
http:
host: 127.0.0.1
port: 8081
cors:
allowedOrigins:
- http://localhost:3001
allowCredentials: true
allowMethods:
- POST
- GET
- PUT
- DELETE
allowedHeaders:
- Origin
- Accept
- Content-Type
- Authorization
- Sentry-Trace
debug: false
auth:
issuer: http://127.0.0.1:8081
privateKey: /etc/bouncer/admin-key.json
metrics:
enabled: true
endpoint: /.bouncer/metrics
basicAuth: null
redis:
addresses:
- ${REDIS_SENTINEL_HOST}:${REDIS_SENTINEL_PORT}
master: "${REDIS_MASTER_NAME}"
logger:
level: 3
format: human

View File

@ -0,0 +1,17 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./resources/service.yaml
- ./resources/deployment.yaml
configMapGenerator:
- name: bouncer-admin-config
files:
- ./files/config.yml
- ./files/admin-key.json
- name: bouncer-admin-env
literals:
- REDIS_SENTINEL_HOST="rfs-$(REDIS_SERVICE_NAME)"
- REDIS_SENTINEL_PORT="26379"
- REDIS_MASTER_NAME="mymaster"

View File

@ -0,0 +1,38 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bouncer-admin
labels:
app: bouncer-admin
spec:
replicas: 3
selector:
matchLabels:
app: bouncer-admin
template:
metadata:
labels:
app: bouncer-admin
spec:
containers:
- name: bouncer-admin
image: reg.cadoles.com/cadoles/bouncer:v2023.7.8-b44ff2a
command: ["bouncer"]
args: ["--debug", "-c", "/etc/bouncer/config.yml", "server", "admin", "run"]
imagePullPolicy: Always
envFrom:
- configMapRef:
name: bouncer-admin-env
env:
- name: REDIS_SENTINEL_HOST
value: "rfs-$(REDIS_SERVICE_NAME)"
ports:
- name: bouncer-admin
containerPort: 8081
volumeMounts:
- mountPath: /etc/bouncer/
name: bouncer-admin-config
volumes:
- name: bouncer-admin-config
configMap:
name: bouncer-admin-config

View File

@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
labels:
io.kompose.service: bouncer-admin
name: bouncer-admin
spec:
type: ClusterIP
ports:
- name: bouncer-admin
port: 8081
targetPort: 8080
selector:
io.kompose.service: bouncer-admin

View File

@ -0,0 +1,22 @@
proxy:
http:
host: 0.0.0.0
port: 8080
metrics:
enabled: true
endpoint: /.bouncer/metrics
basicAuth: null
layers:
queue:
templateDir: /usr/share/bouncer/layers/queue/templates
defaultKeepAlive: 1m0s
redis:
addresses:
- ${RFS_BOUNCER_REDIS_SERVICE_HOST}:${RFS_BOUNCER_REDIS_SERVICE_PORT}
master: ""
logger:
level: 3
format: human

View File

@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./resources/service.yaml
- ./resources/deployment.yaml
configMapGenerator:
- name: bouncer-server-config
files:
- ./files/config.yml

View File

@ -0,0 +1,31 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bouncer-server
labels:
app: bouncer-server
spec:
replicas: 3
selector:
matchLabels:
app: bouncer-server
template:
metadata:
labels:
app: bouncer-server
spec:
containers:
- name: bouncer-server
image: reg.cadoles.com/cadoles/bouncer:v2023.7.8-b44ff2a
command: ["bouncer", "-c", "/etc/bouncer/config.yml", "server", "proxy", "run"]
imagePullPolicy: Always
ports:
- name: bouncer-server
containerPort: 8080
volumeMounts:
- mountPath: /etc/bouncer/
name: bouncer-server-config
volumes:
- name: bouncer-server-config
configMap:
name: bouncer-server-config

View File

@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
labels:
io.kompose.service: bouncer-server
name: bouncer-server
spec:
type: ClusterIP
ports:
- name: bouncer-server
port: 8080
targetPort: 8080
selector:
io.kompose.service: bouncer-server

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: bouncer

View File

@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./resources/redis-cluster.yaml
vars:
- name: REDIS_SERVICE_NAME
objref:
name: bouncer-redis
apiVersion: databases.spotahome.com/v1
kind: RedisFailover
fieldref:
fieldpath: metadata.name

View File

@ -0,0 +1,21 @@
apiVersion: databases.spotahome.com/v1
kind: RedisFailover
metadata:
name: bouncer-redis
spec:
sentinel:
replicas: 3
resources:
requests:
cpu: 100m
limits:
memory: 100Mi
redis:
replicas: 3
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 400m
memory: 500Mi

View File

@ -0,0 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: bouncer-dev
resources:
- ../../base
secretGenerator:
- files:
- secrets/dockerconfig/.dockerconfigjson
name: regcred-dev
type: kubernetes.io/dockerconfigjson
patches:
- path: patches/add-registry-pull-secret.patch.yaml
target:
kind: Deployment
version: v1

View File

@ -0,0 +1,4 @@
- op: add
path: "/spec/template/spec/imagePullSecrets"
value:
- name: regcred-dev

View File

@ -0,0 +1,7 @@
scrape_configs:
- job_name: bouncer-proxy
metrics_path: /.bouncer/metrics
static_configs:
- targets:
- "localhost:8080"
scrape_interval: 5s

6
misc/siege/urls.txt Normal file
View File

@ -0,0 +1,6 @@
http://localhost:8080/blog/
http://localhost:8080/services/
http://localhost:8080/
http://localhost:8080/recrutement/
http://localhost:8080/faq/
http://localhost:8080/societe/histoire/

View File

@ -14,4 +14,9 @@ layers/**
{
daemon +sigint: make run-redis
}
misc/prometheus/prometheus.yml
{
daemon +sigint: make run-prometheus
}

42
skaffold.yaml Normal file
View File

@ -0,0 +1,42 @@
apiVersion: skaffold/v3
kind: Config
metadata:
name: bouncer
manifests:
kustomize:
paths:
- misc/k8s/kustomization/base
profiles:
- name: dev
manifests:
kustomize:
paths:
- misc/k8s/kustomization/overlays/dev
activation:
- command: dev
build:
local:
push: true
tagPolicy:
sha256: {}
artifacts:
- image: reg.cadoles.com/cadoles/bouncer
context: .
sync:
infer:
- cmd/**
- internal/**
- layers/**
- tools/**
- data/**
docker:
dockerfile: misc/images/bouncer/Dockerfile
deploy:
statusCheckDeadlineSeconds: 600