diff --git a/.github/workflows/traefik-hub.yaml b/.github/workflows/traefik-hub.yaml new file mode 100644 index 0000000..7b691bd --- /dev/null +++ b/.github/workflows/traefik-hub.yaml @@ -0,0 +1,65 @@ +name: Traefik Hub Static Analyzer + +on: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + permissions: + checks: write + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Lint Traefik Hub CRDs with hub-static-analyzer + uses: traefik/hub-static-analyzer-action@main + with: + token: ${{ secrets.GH_TOKEN }} + lint: true + lint-format: checkstyle + lint-output-file: ./output.xml + + - name: Annotate code + if: ${{ !cancelled() }} + uses: Juuxel/publish-checkstyle-report@v1 + with: + reports: | + ./output.xml + + diff: + runs-on: ubuntu-latest + permissions: + checks: write + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Lint Traefik Hub CRDs with hub-static-analyzer + uses: traefik/hub-static-analyzer-action@main + with: + token: ${{ secrets.GH_TOKEN }} + diff: true + diff-range: "origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF}" + diff-output-file: ./output.md + + - name: Prepare report + shell: bash + run: | + set -u + + echo "# Traefik Hub Report:" > header.md + echo "" >> header.md + echo "The following changes have been detected." >> header.md + echo "" >> header.md + + - name: Write report + if: ${{ hashFiles('./output.md') != ''}} + uses: mshick/add-pr-comment@v2 + with: + message-path: | + header.md + output.md diff --git a/api-server/.gitignore b/api-server/.gitignore new file mode 100644 index 0000000..849ddff --- /dev/null +++ b/api-server/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/api-server/Dockerfile b/api-server/Dockerfile new file mode 100644 index 0000000..244ad26 --- /dev/null +++ b/api-server/Dockerfile @@ -0,0 +1,12 @@ +# syntax=docker/dockerfile:1.2 +# Alpine +FROM alpine + +RUN apk --no-cache --no-progress add ca-certificates tzdata git \ + && rm -rf /var/cache/apk/* + +ARG TARGETPLATFORM +COPY ./dist/$TARGETPLATFORM/api-server / + +ENTRYPOINT ["/api-server"] +EXPOSE 3000 diff --git a/api-server/Makefile b/api-server/Makefile new file mode 100644 index 0000000..f161d66 --- /dev/null +++ b/api-server/Makefile @@ -0,0 +1,34 @@ +.PHONY: default check test build image + +# Default build target +GOOS := $(shell go env GOOS) +GOARCH := $(shell go env GOARCH) +DOCKER_BUILD_PLATFORMS ?= linux/amd64,linux/arm64 + +default: check test build + +dist: + mkdir dist + +build: dist + CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -v -trimpath -ldflags '-s' -o "./dist/${GOOS}/${GOARCH}/api-server" . + +test: + go test -v -cover ./... + +check: + golangci-lint run + +build-linux-arm64: export GOOS := linux +build-linux-arm64: export GOARCH := arm64 +build-linux-arm64: + make build + +build-linux-amd64: export GOOS := linux +build-linux-amd64: export GOARCH := amd64 +build-linux-amd64: + make build + +## Build Multi archs Docker image +multi-arch-image-%: build-linux-amd64 build-linux-arm64 + docker buildx build $(DOCKER_BUILDX_ARGS) -t mmatur/traefik-hub:$* --platform=$(DOCKER_BUILD_PLATFORMS) . diff --git a/api-server/go.mod b/api-server/go.mod new file mode 100644 index 0000000..73724cc --- /dev/null +++ b/api-server/go.mod @@ -0,0 +1,3 @@ +module api-server + +go 1.21 diff --git a/api-server/main.go b/api-server/main.go new file mode 100644 index 0000000..25175ca --- /dev/null +++ b/api-server/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "flag" + "log" + "math/rand" + "net/http" + "os" + "strconv" + "time" +) + +func main() { + openapispec := flag.String("openapi", "", "openapispec") + datafile := flag.String("data", "", "file to put data in") + latency := flag.Duration("latency", 0, "latency to add") + errorrate := flag.Int("errorrate", 0, "latency to add") + flag.Parse() + + var openapi []byte + + if openapispec != nil && *openapispec != "" { + var err error + openapi, err = os.ReadFile(*openapispec) + if err != nil { + log.Fatal(err) + } + } + + var data []byte + if datafile != nil && *datafile != "" { + var err error + data, err = os.ReadFile(*datafile) + if err != nil { + log.Fatal(err) + } + } + + log.Fatal(http.ListenAndServe(":3000", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/openapi.yaml" { + _, _ = rw.Write(openapi) + return + } + + if latency != nil && *latency > 0 { + time.Sleep(*latency) + } + + status := req.URL.Query().Get("status") + if status == "" { + if errorrate != nil && *errorrate > 0 { + if rand.Int()%100 < *errorrate { + rw.WriteHeader(http.StatusInternalServerError) + return + } + } + switch req.Method { + case http.MethodGet, http.MethodPut: + rw.WriteHeader(http.StatusOK) + _, _ = rw.Write(data) + case http.MethodPost: + rw.WriteHeader(http.StatusCreated) + _, _ = rw.Write([]byte(`{"id":4}`)) + case http.MethodDelete: + rw.WriteHeader(http.StatusNoContent) + } + + return + } + + atoi, err := strconv.Atoi(status) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + rw.WriteHeader(atoi) + }))) +} diff --git a/apps/base/apps/api-access.yaml b/apps/base/apps/api-access.yaml new file mode 100644 index 0000000..29e2cac --- /dev/null +++ b/apps/base/apps/api-access.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIAccess +metadata: + name: crm-internal +spec: + groups: + - internal + apiSelector: + matchLabels: + module: crm + area: employee + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIAccess +metadata: + name: crm-all +spec: + groups: + - crm-user + apiCollectionSelector: + matchLabels: + module: crm + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIAccess +metadata: + name: custom-pick +spec: + groups: + - support + apis: + - name: world-time-api-external-name + namespace: apps + apiSelector: + matchExpressions: + - key: area + operator: In + values: + - flights + - tickets + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIAccess +metadata: + name: admins +spec: + groups: + - admin + apiSelector: + matchExpressions: + - key: area + operator: Exists diff --git a/apps/base/apps/api-collections.yaml b/apps/base/apps/api-collections.yaml new file mode 100644 index 0000000..1ed2597 --- /dev/null +++ b/apps/base/apps/api-collections.yaml @@ -0,0 +1,11 @@ +apiVersion: hub.traefik.io/v1alpha1 +kind: APICollection +metadata: + name: crm-all + labels: + module: crm +spec: + pathPrefix: "/crm" + apiSelector: + matchLabels: + module: crm diff --git a/apps/base/apps/api-gateway.yaml b/apps/base/apps/api-gateway.yaml new file mode 100644 index 0000000..743fd04 --- /dev/null +++ b/apps/base/apps/api-gateway.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIGateway +metadata: + name: my-gateway + labels: + area: crm +spec: + apiAccesses: + - crm-all + - crm-internal + - admins + - custom-pick + # customDomains: + # - gateway.YOUR_ENV_NAME.demo.traefiklabs.tech diff --git a/apps/base/apps/api-portal.yaml b/apps/base/apps/api-portal.yaml new file mode 100644 index 0000000..86a7c6f --- /dev/null +++ b/apps/base/apps/api-portal.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIPortal +metadata: + name: my-kubecon-portal +spec: + title: "Traefik Kubecon" + description: "API Portal for Kubecon" + apiGateway: my-gateway + # ui: + # logoUrl: https://traefik.io/favicon.png + # ui: + # service: + # name: hub-apiportal-ui + # namespace: portal-ui + # port: + # number: 80 + # customDomains: + # - portal.YOUR_ENV_NAME.demo.traefiklabs.tech diff --git a/apps/base/apps/api-rate-limit.yaml b/apps/base/apps/api-rate-limit.yaml new file mode 100644 index 0000000..8dea246 --- /dev/null +++ b/apps/base/apps/api-rate-limit.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIRateLimit +metadata: + name: crm-user-ratelimit +spec: + limit: 2 # 2 requests + period: 5s # 5 seconds + groups: + - crm-user + apiSelector: + matchLabels: + module: crm + apis: + - name: employee-api + namespace: apps +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIRateLimit +metadata: + name: support-ratelimit +spec: + limit: 1 + period: 1m + groups: + - support + apiSelector: + matchExpressions: + - key: area + operator: In + values: + - flights + - tickets +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIRateLimit +metadata: + name: fallback-ratelimit +spec: + limit: 2000 + period: 65s + anyGroups: true + apiSelector: {} diff --git a/apps/base/apps/custom-ui.yaml b/apps/base/apps/custom-ui.yaml new file mode 100644 index 0000000..a9a20a7 --- /dev/null +++ b/apps/base/apps/custom-ui.yaml @@ -0,0 +1,94 @@ +# Copied from, @see: https://github.com/traefik/hub-apiportal-ui/blob/main/manifest.yaml +--- +apiVersion: v1 +kind: Namespace +metadata: + name: portal-ui + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hub-apiportal-ui + namespace: portal-ui +spec: + replicas: 2 + selector: + matchLabels: + app: hub-apiportal-ui + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + labels: + app: hub-apiportal-ui + app.kubernetes.io/component: api-portal-ui + app.kubernetes.io/name: hub-apiportal-ui + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - hub-apiportal-ui + topologyKey: kubernetes.io/hostname + weight: 100 + automountServiceAccountToken: false + containers: + - image: immanuelfodor/hub-portal-ui:latest + name: hub-apiportal-ui + # Pull always in case there is a new customization added in the meantime + imagePullPolicy: Always + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 50m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 65534 + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - NET_RAW + +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: hub-apiportal-ui + namespace: portal-ui +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: hub-apiportal-ui + +--- +apiVersion: v1 +kind: Service +metadata: + name: hub-apiportal-ui + namespace: portal-ui + labels: + app.kubernetes.io/component: api-portal-ui + app.kubernetes.io/name: hub-apiportal-ui +spec: + ports: + - name: web + port: 80 + targetPort: 3000 + selector: + app: hub-apiportal-ui + type: ClusterIP diff --git a/apps/base/apps/customers/api-versioned.yaml b/apps/base/apps/customers/api-versioned.yaml new file mode 100644 index 0000000..803ed1b --- /dev/null +++ b/apps/base/apps/customers/api-versioned.yaml @@ -0,0 +1,154 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: API +metadata: + name: customer-api-versioned + namespace: apps + labels: + area: customers + module: crm +spec: + pathPrefix: "/customers-versioned" + currentVersion: customer-api-v2-1-0 + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIVersion +metadata: + name: customer-api-v1 + namespace: apps +spec: + apiName: customer-api-versioned + release: 1.0.0 + title: "Catch-all" + service: + name: customer-app + port: + number: 3000 + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIVersion +metadata: + name: customer-api-v2 + namespace: apps +spec: + apiName: customer-api-versioned + release: 2.0.0 + title: "URI path" + routes: + - pathPrefix: /v2 + stripPathPrefix: true + service: + name: customer-app-v2 + port: + number: 3000 + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIVersion +metadata: + name: customer-api-v3 + namespace: apps +spec: + apiName: customer-api-versioned + release: 3.0.0 + title: "Query param" + routes: + - queryParams: + v: "3" + service: + name: customer-app-v3 + port: + number: 3000 + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIVersion +metadata: + name: customer-api-v4 + namespace: apps +spec: + apiName: customer-api-versioned + release: 4.0.0 + title: "Header with multiple blocks & query params" + routes: + - headers: + Version: "4" + - queryParams: + v: "4" + lang: hu + - pathPrefix: /v4 + stripPathPrefix: true + service: + name: customer-app-v4 + port: + number: 3000 + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: APIVersion +metadata: + name: customer-api-v2-1-0 + namespace: apps +spec: + apiName: customer-api-versioned + release: 2.1.0 + title: "URI path + CORS + Headers" + routes: + - pathPrefix: /v2.1 + stripPathPrefix: true + service: + name: customer-app-v2 + port: + number: 3000 + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + cors: + allowCredentials: true + allowHeaders: + - '*' + allowMethods: + - GET + - HEAD + - POST + - PUT + - PATCH + - DELETE + - CONNECT + - OPTIONS + #- TRACE + allowOriginList: + - '*' + maxAge: 0 + headers: + request: + set: + "X-Request-Header": "Extra request header" + "X-Username": "Somebody" + delete: + - "Unnecessary-Request-Header" + response: + set: + "X-Response-Header": "Extra response header" + "X-API-Server": "Traefik Hub" + delete: + - "Secret-Response-Header" diff --git a/apps/base/apps/customers/api.yaml b/apps/base/apps/customers/api.yaml new file mode 100644 index 0000000..c76b1cb --- /dev/null +++ b/apps/base/apps/customers/api.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: API +metadata: + name: customer-api + namespace: apps + labels: + area: customers + module: crm +spec: + pathPrefix: "/customers" + service: + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + name: customer-app + port: + number: 3000 \ No newline at end of file diff --git a/apps/base/apps/customers/customer-v2.yaml b/apps/base/apps/customers/customer-v2.yaml new file mode 100644 index 0000000..690a1d2 --- /dev/null +++ b/apps/base/apps/customers/customer-v2.yaml @@ -0,0 +1,66 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: customer-data-v2 + namespace: apps +data: + api.json: | + { + "customers": [ + { "id": 4, "firstName": "John v2", "lastName": "Doe v2", "points": 100, "status": "bronze" }, + { "id": 5, "firstName": "Jane v2", "lastName": "Doe v2", "points": 200, "status": "silver" }, + { "id": 6, "firstName": "John v2", "lastName": "Smith v2", "points": 300, "status": "gold" } + ] + } + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: customer-app-v2 + namespace: apps +spec: + replicas: 1 + selector: + matchLabels: + app: customer-app-v2 + template: + metadata: + labels: + app: customer-app-v2 + spec: + containers: + - name: api + image: mmatur/traefik-hub:kubecon + args: ["-data", "/api/api.json", "-openapi", "/public/openapi.yaml"] + imagePullPolicy: Always + volumeMounts: + - name: api-data + mountPath: /api + - name: openapi + mountPath: /public + volumes: + - name: api-data + configMap: + name: customer-data-v2 + - name: openapi + configMap: + name: customer-apispec + +--- +apiVersion: v1 +kind: Service +metadata: + name: customer-app-v2 + namespace: apps + labels: + app: customer-app-v2 +spec: + type: ClusterIP + ports: + - port: 3000 + name: api + selector: + app: customer-app-v2 + diff --git a/apps/base/apps/customers/customer-v3.yaml b/apps/base/apps/customers/customer-v3.yaml new file mode 100644 index 0000000..1a33ebd --- /dev/null +++ b/apps/base/apps/customers/customer-v3.yaml @@ -0,0 +1,65 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: customer-data-v3 + namespace: apps +data: + api.json: | + { + "customers": [ + { "id": 4, "firstName": "John v3", "lastName": "Doe v3", "points": 100, "status": "bronze" }, + { "id": 5, "firstName": "Jane v3", "lastName": "Doe v3", "points": 200, "status": "silver" }, + { "id": 6, "firstName": "John v3", "lastName": "Smith v3", "points": 300, "status": "gold" } + ] + } + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: customer-app-v3 + namespace: apps +spec: + replicas: 1 + selector: + matchLabels: + app: customer-app-v3 + template: + metadata: + labels: + app: customer-app-v3 + spec: + containers: + - name: api + image: mmatur/traefik-hub:kubecon + args: ["-data", "/api/api.json", "-openapi", "/public/openapi.yaml"] + imagePullPolicy: Always + volumeMounts: + - name: api-data + mountPath: /api + - name: openapi + mountPath: /public + volumes: + - name: api-data + configMap: + name: customer-data-v3 + - name: openapi + configMap: + name: customer-apispec + +--- +apiVersion: v1 +kind: Service +metadata: + name: customer-app-v3 + namespace: apps + labels: + app: customer-app-v3 +spec: + type: ClusterIP + ports: + - port: 3000 + name: api + selector: + app: customer-app-v3 diff --git a/apps/base/apps/customers/customer-v4.yaml b/apps/base/apps/customers/customer-v4.yaml new file mode 100644 index 0000000..2d1c7b7 --- /dev/null +++ b/apps/base/apps/customers/customer-v4.yaml @@ -0,0 +1,65 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: customer-data-v4 + namespace: apps +data: + api.json: | + { + "customers": [ + { "id": 4, "firstName": "John v4", "lastName": "Doe v4", "points": 100, "status": "bronze" }, + { "id": 5, "firstName": "Jane v4", "lastName": "Doe v4", "points": 200, "status": "silver" }, + { "id": 6, "firstName": "John v4", "lastName": "Smith v4", "points": 300, "status": "gold" } + ] + } + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: customer-app-v4 + namespace: apps +spec: + replicas: 1 + selector: + matchLabels: + app: customer-app-v4 + template: + metadata: + labels: + app: customer-app-v4 + spec: + containers: + - name: api + image: mmatur/traefik-hub:kubecon + args: ["-data", "/api/api.json", "-openapi", "/public/openapi.yaml"] + imagePullPolicy: Always + volumeMounts: + - name: api-data + mountPath: /api + - name: openapi + mountPath: /public + volumes: + - name: api-data + configMap: + name: customer-data-v4 + - name: openapi + configMap: + name: customer-apispec + +--- +apiVersion: v1 +kind: Service +metadata: + name: customer-app-v4 + namespace: apps + labels: + app: customer-app-v4 +spec: + type: ClusterIP + ports: + - port: 3000 + name: api + selector: + app: customer-app-v4 diff --git a/apps/base/apps/customers/customer.yaml b/apps/base/apps/customers/customer.yaml new file mode 100644 index 0000000..8230a7d --- /dev/null +++ b/apps/base/apps/customers/customer.yaml @@ -0,0 +1,66 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: customer-data + namespace: apps +data: + api.json: | + { + "customers": [ + { "id": 1, "firstName": "John", "lastName": "Doe", "points": 100, "status": "bronze" }, + { "id": 2, "firstName": "Jane", "lastName": "Doe", "points": 200, "status": "silver" }, + { "id": 3, "firstName": "John", "lastName": "Smith", "points": 300, "status": "gold" } + ] + } + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: customer-app + namespace: apps +spec: + replicas: 1 + selector: + matchLabels: + app: customer-app + template: + metadata: + labels: + app: customer-app + spec: + containers: + - name: api + image: mmatur/traefik-hub:kubecon + args: ["-data", "/api/api.json", "-openapi", "/public/openapi.yaml"] + imagePullPolicy: Always + volumeMounts: + - name: api-data + mountPath: /api + - name: openapi + mountPath: /public + volumes: + - name: api-data + configMap: + name: customer-data + - name: openapi + configMap: + name: customer-apispec + +--- +apiVersion: v1 +kind: Service +metadata: + name: customer-app + namespace: apps + labels: + app: customer-app +spec: + type: ClusterIP + ports: + - port: 3000 + name: api + selector: + app: customer-app + diff --git a/apps/base/apps/customers/kustomization.yaml b/apps/base/apps/customers/kustomization.yaml new file mode 100644 index 0000000..1ee9926 --- /dev/null +++ b/apps/base/apps/customers/kustomization.yaml @@ -0,0 +1,11 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: apps +resources: + - api.yaml + - api-versioned.yaml + - spec.yaml + - customer.yaml + - customer-v2.yaml + - customer-v3.yaml + - customer-v4.yaml diff --git a/apps/base/apps/customers/spec.yaml b/apps/base/apps/customers/spec.yaml new file mode 100644 index 0000000..947c764 --- /dev/null +++ b/apps/base/apps/customers/spec.yaml @@ -0,0 +1,170 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: customer-apispec + namespace: apps +data: + openapi.yaml: | + openapi: "3.0.0" + info: + version: 1.0.0 + title: Customers + description: Traefik Airlines Customer API + contact: + name: Traefik Airlines Support + url: 'https://traefik.io/' + license: + name: Apache 2.0 + url: 'https://spdx.org/licenses/Apache-2.0.html' + servers: + - url: https://api.traefik.localhost + paths: + /customers: + get: + summary: Retrieve customers + operationId: getCustomers + tags: + - customers + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of customers + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Customers" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a customer + operationId: createCustomer + tags: + - customers + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /customers/{customerId}: + get: + summary: Info for a specific customer + operationId: showCustomerById + tags: + - customers + parameters: + - name: customerId + in: path + required: true + description: The id of the customer + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Customer" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + put: + summary: Update a customer + operationId: updateCustomer + tags: + - customers + parameters: + - name: customerId + in: path + required: true + description: The id of the customer + schema: + type: string + responses: + '200': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + summary: Delete a customer + operationId: deleteCustomer + tags: + - customers + parameters: + - name: customerId + in: path + required: true + description: The id of the customer + schema: + type: string + responses: + '200': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + components: + schemas: + Customer: + type: object + required: + - id + - firstName + - lastName + properties: + id: + type: integer + format: int64 + firstName: + type: string + lastName: + type: string + points: + type: integer + format: int64 + status: + type: string + Customers: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Customer" + Error: + type: object + required: + - message + properties: + message: + type: string \ No newline at end of file diff --git a/apps/base/apps/employee/api.yaml b/apps/base/apps/employee/api.yaml new file mode 100644 index 0000000..adecf8f --- /dev/null +++ b/apps/base/apps/employee/api.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: API +metadata: + name: employee-api + namespace: apps + labels: + area: employee + module: crm +spec: + pathPrefix: "/employees" + service: + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + name: employee-app + port: + number: 3000 \ No newline at end of file diff --git a/apps/base/apps/employee/employee.yaml b/apps/base/apps/employee/employee.yaml new file mode 100644 index 0000000..045c813 --- /dev/null +++ b/apps/base/apps/employee/employee.yaml @@ -0,0 +1,67 @@ + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: employee-data + namespace: apps +data: + api.json: | + { + "employees": [ + { "id": 1, "firstName": "John", "lastName": "Doe", "role": "pilot", "homeAirport": "RIC" }, + { "id": 2, "firstName": "Jane", "lastName": "Doe", "role": "engineer", "homeAirport": "CDG" }, + { "id": 3, "firstName": "John", "lastName": "Smith", "role": "attendant", "homeAirport": "DTW" } + ] + } + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: employee-app + namespace: apps +spec: + replicas: 1 + selector: + matchLabels: + app: employee-app + template: + metadata: + labels: + app: employee-app + spec: + containers: + - name: api + image: mmatur/traefik-hub:kubecon + args: ["-data", "/api/api.json", "-openapi", "/public/openapi.yaml"] + imagePullPolicy: Always + volumeMounts: + - name: api-data + mountPath: /api + - name: openapi + mountPath: /public + volumes: + - name: api-data + configMap: + name: employee-data + - name: openapi + configMap: + name: employee-apispec + +--- +apiVersion: v1 +kind: Service +metadata: + name: employee-app + namespace: apps + labels: + app: employee-app +spec: + type: ClusterIP + ports: + - port: 3000 + name: api + selector: + app: employee-app + diff --git a/apps/base/apps/employee/kustomization.yaml b/apps/base/apps/employee/kustomization.yaml new file mode 100644 index 0000000..35d31da --- /dev/null +++ b/apps/base/apps/employee/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: apps +resources: + - api.yaml + - spec.yaml + - employee.yaml diff --git a/apps/base/apps/employee/spec.yaml b/apps/base/apps/employee/spec.yaml new file mode 100644 index 0000000..ac86696 --- /dev/null +++ b/apps/base/apps/employee/spec.yaml @@ -0,0 +1,169 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: employee-apispec + namespace: apps +data: + openapi.yaml: | + openapi: "3.0.0" + info: + version: 1.0.0 + title: Employee + description: Traefik Airlines Employee API + contact: + name: Traefik Airlines Support + url: 'https://traefik.io/' + license: + name: Apache 2.0 + url: 'https://spdx.org/licenses/Apache-2.0.html' + servers: + - url: https://api.traefik.localhost + paths: + /employees: + get: + summary: Get employees + operationId: getEmployees + tags: + - employees + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of employees + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Employees" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a employee + operationId: createEmployee + tags: + - employees + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /employees/{employeeId}: + get: + summary: Info for a specific employee + operationId: showEmployeeById + tags: + - employees + parameters: + - name: employeeId + in: path + required: true + description: The id of the employee + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Employee" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + put: + summary: Update an employee + operationId: updateEmployee + tags: + - employees + parameters: + - name: employeeId + in: path + required: true + description: The id of the employee + schema: + type: string + responses: + '200': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + summary: Delete a employee + operationId: deleteEmployee + tags: + - employees + parameters: + - name: employeeId + in: path + required: true + description: The id of the employee + schema: + type: string + responses: + '200': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + components: + schemas: + Employee: + type: object + required: + - id + - firstName + - lastName + properties: + id: + type: integer + format: int64 + firstName: + type: string + lastName: + type: string + role: + type: string + homeAirport: + type: string + Employees: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Employee" + Error: + type: object + required: + - message + properties: + message: + type: string diff --git a/apps/base/apps/external/api.yaml b/apps/base/apps/external/api.yaml new file mode 100644 index 0000000..39fcd9e --- /dev/null +++ b/apps/base/apps/external/api.yaml @@ -0,0 +1,65 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: API +metadata: + name: world-time-api-external-name + namespace: apps + labels: + area: external + external: name +spec: + pathPrefix: /world-time-api + service: + openApiSpec: + url: https://worldtimeapi.org/api + name: world-time-api + port: + number: 443 + +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: API +metadata: + name: httpbin-external-name + namespace: apps + labels: + area: external + external: name +spec: + pathPrefix: /httpbin + service: + openApiSpec: + url: https://httpbin.org/spec.json + name: httpbin + port: + number: 443 + cors: + allowCredentials: true + allowHeaders: + - '*' + allowMethods: + - GET + - HEAD + - POST + - PUT + - PATCH + - DELETE + - CONNECT + - OPTIONS + #- TRACE + allowOriginList: + - '*' + maxAge: 0 + headers: + request: + set: + "X-Request-Header": "Extra httpbin request header" + "X-Username": "Somebody" + delete: + - "Unnecessary-Request-Header" + response: + set: + "X-Response-Header": "Extra httpbin response header" + "X-API-Server": "Traefik Hub" + delete: + - "Secret-Response-Header" diff --git a/apps/base/apps/external/kustomization.yaml b/apps/base/apps/external/kustomization.yaml new file mode 100644 index 0000000..79ba154 --- /dev/null +++ b/apps/base/apps/external/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: apps +resources: + - api.yaml + - service.yaml diff --git a/apps/base/apps/external/service.yaml b/apps/base/apps/external/service.yaml new file mode 100644 index 0000000..dddd415 --- /dev/null +++ b/apps/base/apps/external/service.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: world-time-api + namespace: apps +spec: + type: ExternalName + externalName: worldtimeapi.org + ports: + - port: 443 + +--- +apiVersion: v1 +kind: Service +metadata: + name: httpbin + namespace: apps +spec: + type: ExternalName + externalName: httpbin.org + ports: + - port: 443 diff --git a/apps/base/apps/flight/api.yaml b/apps/base/apps/flight/api.yaml new file mode 100644 index 0000000..7015465 --- /dev/null +++ b/apps/base/apps/flight/api.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: API +metadata: + name: flight-api + namespace: apps + labels: + area: flights + module: erp +spec: + pathPrefix: "/flights" + service: + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + name: flight-app + port: + number: 3000 diff --git a/apps/base/apps/flight/flight.yaml b/apps/base/apps/flight/flight.yaml new file mode 100644 index 0000000..c045b34 --- /dev/null +++ b/apps/base/apps/flight/flight.yaml @@ -0,0 +1,67 @@ + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: flight-data + namespace: apps +data: + api.json: | + { + "flights": [ + { "id": 1, "code": "TL123", "src": "JFK", "dest": "CDG" }, + { "id": 2, "code": "TL234", "src": "CDG", "dest": "JFK" }, + { "id": 3, "code": "TL345", "src": "CDG", "dest": "LYS" } + ] + } + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: flight-app + namespace: apps +spec: + replicas: 1 + selector: + matchLabels: + app: flight-app + template: + metadata: + labels: + app: flight-app + spec: + containers: + - name: api + image: mmatur/traefik-hub:kubecon + args: ["-data", "/api/api.json", "-openapi", "/public/openapi.yaml"] + imagePullPolicy: Always + volumeMounts: + - name: api-data + mountPath: /api + - name: openapi + mountPath: /public + volumes: + - name: api-data + configMap: + name: flight-data + - name: openapi + configMap: + name: flight-apispec + +--- +apiVersion: v1 +kind: Service +metadata: + name: flight-app + namespace: apps + labels: + app: flight-app +spec: + type: ClusterIP + ports: + - port: 3000 + name: api + selector: + app: flight-app + diff --git a/apps/base/apps/flight/kustomization.yaml b/apps/base/apps/flight/kustomization.yaml new file mode 100644 index 0000000..bc697fa --- /dev/null +++ b/apps/base/apps/flight/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: apps +resources: + - api.yaml + - spec.yaml + - flight.yaml diff --git a/apps/base/apps/flight/spec.yaml b/apps/base/apps/flight/spec.yaml new file mode 100644 index 0000000..f32490f --- /dev/null +++ b/apps/base/apps/flight/spec.yaml @@ -0,0 +1,166 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: flight-apispec + namespace: apps +data: + openapi.yaml: | + openapi: "3.0.0" + info: + version: 1.0.0 + title: Flights + description: Traefik Airlines Flights API + contact: + name: Traefik Airlines Support + url: 'https://traefik.io/' + license: + name: Apache 2.0 + url: 'https://spdx.org/licenses/Apache-2.0.html' + servers: + - url: https://api.traefik.localhost + paths: + /flights: + get: + summary: Retrieve flights + operationId: gettFlights + tags: + - flights + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of flights + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Flights" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a flight + operationId: createFlight + tags: + - flights + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /flights/{flightId}: + get: + summary: Info for a specific flight + operationId: showFlightById + tags: + - flights + parameters: + - name: flightId + in: path + required: true + description: The id of the flight + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Flight" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + put: + summary: Update a flight + operationId: updateFlight + tags: + - flights + parameters: + - name: flightId + in: path + required: true + description: The id of the flight + schema: + type: string + responses: + '200': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + summary: Delete a flight + operationId: deleteFlight + tags: + - flights + parameters: + - name: flightId + in: path + required: true + description: The id of the flight + schema: + type: string + responses: + '200': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + components: + schemas: + Flight: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + code: + type: string + source: + type: string + destination: + type: string + Flights: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Flight" + Error: + type: object + required: + - message + properties: + message: + type: string diff --git a/apps/base/apps/kustomization.yaml b/apps/base/apps/kustomization.yaml new file mode 100644 index 0000000..d5fc797 --- /dev/null +++ b/apps/base/apps/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: apps +resources: + - namespace.yaml + - customers/ + - employee/ + - external/ + - flight/ + - ticket/ + - api-access.yaml + - api-collections.yaml + - api-gateway.yaml + - api-portal.yaml +# - custom-ui.yaml diff --git a/apps/base/apps/namespace.yaml b/apps/base/apps/namespace.yaml new file mode 100644 index 0000000..fd6e0f9 --- /dev/null +++ b/apps/base/apps/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: apps diff --git a/apps/base/apps/ticket/api.yaml b/apps/base/apps/ticket/api.yaml new file mode 100644 index 0000000..3bfdd09 --- /dev/null +++ b/apps/base/apps/ticket/api.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: hub.traefik.io/v1alpha1 +kind: API +metadata: + name: ticket-api + namespace: apps + labels: + area: tickets + module: crm +spec: + pathPrefix: "/tickets" + service: + openApiSpec: + path: /openapi.yaml + port: + number: 3000 + name: ticket-app + port: + number: 3000 \ No newline at end of file diff --git a/apps/base/apps/ticket/kustomization.yaml b/apps/base/apps/ticket/kustomization.yaml new file mode 100644 index 0000000..8b59384 --- /dev/null +++ b/apps/base/apps/ticket/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: apps +resources: + - api.yaml + - spec.yaml + - ticket.yaml diff --git a/apps/base/apps/ticket/spec.yaml b/apps/base/apps/ticket/spec.yaml new file mode 100644 index 0000000..1e7d1b4 --- /dev/null +++ b/apps/base/apps/ticket/spec.yaml @@ -0,0 +1,174 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ticket-openapi + namespace: apps +data: + openapi.yaml: | + openapi: "3.0.0" + info: + version: 1.0.0 + title: Tickets + description: Traefik Airlines Tickets API + contact: + name: Traefik Airlines Support + url: 'https://traefik.io/' + license: + name: Apache 2.0 + url: 'https://spdx.org/licenses/Apache-2.0.html' + servers: + - url: https://api.traefik.localhost + paths: + /tickets: + get: + summary: Retrieve tickets + operationId: getTickets + tags: + - tickets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of tickets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Tickets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a ticket + operationId: createTicket + tags: + - tickets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /tickets/{ticketId}: + get: + summary: Info for a specific ticket + operationId: showTicketById + tags: + - tickets + parameters: + - name: ticketId + in: path + required: true + description: The id of the ticket + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Ticket" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + put: + summary: Update a ticket + operationId: updateTicket + tags: + - tickets + parameters: + - name: ticketId + in: path + required: true + description: The id of the ticket + schema: + type: string + responses: + '200': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + summary: Delete a ticket + operationId: deleteTicket + tags: + - tickets + parameters: + - name: ticketId + in: path + required: true + description: The id of the ticket + schema: + type: string + responses: + '200': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + components: + schemas: + Ticket: + type: object + required: + - id + - flightCode + properties: + id: + type: integer + format: int64 + flightCode: + type: string + fare: + type: integer + format: int64 + class: + type: string + available: + type: integer + format: int64 + total: + type: integer + format: int64 + Tickets: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Ticket" + Error: + type: object + required: + - message + properties: + message: + type: string + \ No newline at end of file diff --git a/apps/base/apps/ticket/ticket.yaml b/apps/base/apps/ticket/ticket.yaml new file mode 100644 index 0000000..4c6fe22 --- /dev/null +++ b/apps/base/apps/ticket/ticket.yaml @@ -0,0 +1,66 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ticket-data + namespace: apps +data: + api.json: | + { + "tickets": [ + { "id": 1, "flightCode": "TL123", "fare": 500, "class": "first", "available": 5, "total": 20 }, + { "id": 2, "flightCode": "TL234", "fare": 200, "class": "economy", "available": 2, "total": 5 }, + { "id": 3, "flightCode": "TL345", "fare": 300, "class": "business", "available": 3, "total": 10 } + ] + } + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ticket-app + namespace: apps +spec: + replicas: 1 + selector: + matchLabels: + app: ticket-app + template: + metadata: + labels: + app: ticket-app + spec: + containers: + - name: api + image: mmatur/traefik-hub:kubecon + args: ["-data", "/api/api.json", "-openapi", "/public/openapi.yaml"] + imagePullPolicy: Always + volumeMounts: + - name: api-data + mountPath: /api + - name: openapi + mountPath: /public + volumes: + - name: api-data + configMap: + name: ticket-data + - name: openapi + configMap: + name: ticket-openapi + +--- +apiVersion: v1 +kind: Service +metadata: + name: ticket-app + namespace: apps + labels: + app: ticket-app +spec: + type: ClusterIP + ports: + - port: 3000 + name: api + selector: + app: ticket-app + diff --git a/apps/base/kustomization.yaml b/apps/base/kustomization.yaml new file mode 100644 index 0000000..aaf6346 --- /dev/null +++ b/apps/base/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - monitoring + - traefik-hub + - apps diff --git a/apps/base/monitoring/grafana/config.yaml b/apps/base/monitoring/grafana/config.yaml new file mode 100644 index 0000000..8dfcffc --- /dev/null +++ b/apps/base/monitoring/grafana/config.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-datasource-config + namespace: monitoring +data: + prometheus.yaml: |- + # @see: https://grafana.com/docs/grafana/latest/datasources/prometheus/ + # @see: https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources + apiVersion: 1 + datasources: + - name: Prometheus + type: prometheus + uid: prometheus + access: proxy + url: http://prometheus.monitoring.svc:9090 + editable: true + orgId: 1 + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-config + namespace: monitoring +data: + hub.yaml: |- + # @see: https://grafana.com/tutorials/provision-dashboards-and-data-sources/ + # @see: https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards + apiVersion: 1 + providers: + - name: Hub API Management Dashboards + folder: Traefik Hub + type: file + orgId: 1 + updateIntervalSeconds: 10 + options: + path: /dashboards/hub + foldersFromFilesStructure: false diff --git a/apps/base/monitoring/grafana/deployment.yaml b/apps/base/monitoring/grafana/deployment.yaml new file mode 100644 index 0000000..6926505 --- /dev/null +++ b/apps/base/monitoring/grafana/deployment.yaml @@ -0,0 +1,78 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grafana + namespace: monitoring + labels: + app: grafana +spec: + selector: + matchLabels: + app: grafana + template: + metadata: + labels: + app: grafana + spec: + securityContext: + fsGroup: 472 + supplementalGroups: + - 0 + containers: + - name: grafana + image: grafana/grafana:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + name: http-grafana + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /robots.txt + port: 3000 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 2 + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 3000 + timeoutSeconds: 1 + resources: + requests: + cpu: 250m + memory: 750Mi + volumeMounts: + - mountPath: /var/lib/grafana + name: grafana-pv + # @see: https://devopscube.com/setup-grafana-kubernetes/ + - mountPath: /etc/grafana/provisioning/datasources + name: grafana-datasource-config + - mountPath: /etc/grafana/provisioning/dashboards + name: grafana-dashboard-config + - mountPath: /dashboards/hub + name: grafana-hub-dashboards + volumes: + - name: grafana-pv + persistentVolumeClaim: + claimName: grafana-pvc + - name: grafana-datasource-config + configMap: + defaultMode: 420 + name: grafana-datasource-config + - name: grafana-dashboard-config + configMap: + defaultMode: 420 + name: grafana-dashboard-config + - name: grafana-hub-dashboards + configMap: + defaultMode: 420 + name: grafana-hub-dashboards + diff --git a/apps/base/monitoring/grafana/grafana-dashboards/cluster.json b/apps/base/monitoring/grafana/grafana-dashboards/cluster.json new file mode 100644 index 0000000..d052337 --- /dev/null +++ b/apps/base/monitoring/grafana/grafana-dashboards/cluster.json @@ -0,0 +1,1207 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "iconColor": "red", + "name": "flux events", + "target": { + "limit": 100, + "matchAny": false, + "tags": [ + "flux" + ], + "type": "tags" + } + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 24, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "count(gotk_resource_info{exported_namespace=~\"$namespace\", customresource_kind=~\"Kustomization|HelmRelease\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "range": false, + "refId": "A" + } + ], + "title": "Cluster Reconcilers", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 28, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "count(gotk_resource_info{exported_namespace=~\"$namespace\", customresource_kind=~\"Kustomization|HelmRelease\", ready=\"False\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "range": false, + "refId": "A" + } + ], + "title": "Failing Reconcilers", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 29, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "count(gotk_resource_info{exported_namespace=~\"$namespace\", customresource_kind=~\"GitRepository|HelmRepository|Bucket|OCIRepository\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "range": false, + "refId": "A" + } + ], + "title": "Kubernetes Manifests Sources", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 30, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "count(gotk_resource_info{exported_namespace=~\"$namespace\", customresource_kind=~\"GitRepository|HelmRepository|Bucket|OCIRepository\", ready=\"False\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "range": false, + "refId": "A" + } + ], + "title": "Failing Sources", + "type": "stat" + }, + { + "datasource": "${DS_PROMETHEUS}", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 61 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 0, + "y": 5 + }, + "id": 8, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "text": {}, + "valueMode": "color" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind)", + "interval": "", + "legendFormat": "{{kind}}", + "refId": "A" + } + ], + "title": "Reconciler ops avg. duration", + "type": "bargauge" + }, + { + "datasource": "${DS_PROMETHEUS}", + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 61 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 12, + "y": 5 + }, + "id": 31, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "text": {}, + "valueMode": "color" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket|OCIRepository\"}[5m])) by (kind)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket|OCIRepository\"}[5m])) by (kind)", + "interval": "", + "legendFormat": "{{kind}}", + "refId": "A" + } + ], + "title": "Source ops avg. duration", + "type": "bargauge" + }, + { + "collapsed": false, + "datasource": "${DS_PROMETHEUS}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 15, + "panels": [], + "title": "Status", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [ + { + "options": { + "False": { + "color": "red", + "index": 1, + "text": "Not Ready" + }, + "True": { + "color": "blue", + "index": 0, + "text": "Ready" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "basic", + "type": "color-background" + } + } + ] + } + ] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 33, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Status" + } + ] + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "gotk_resource_info{exported_namespace=~\"$namespace\", customresource_kind=~\"Kustomization|HelmRelease\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Cluster reconciliation readiness ", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "__name__": true, + "app": true, + "chart_name": true, + "chart_source_name": true, + "container": true, + "customresource_group": true, + "customresource_kind": false, + "customresource_version": true, + "endpoint": true, + "exported_namespace": false, + "gotk_type": true, + "instance": true, + "job": true, + "kubernetes_namespace": true, + "kubernetes_pod_name": true, + "namespace": true, + "pod": true, + "pod_template_hash": true, + "revision": true, + "service": true, + "source_name": true, + "status": true, + "suspended": true, + "type": true + }, + "indexByName": { + "Time": 0, + "Value": 15, + "__name__": 1, + "container": 2, + "customresource_group": 4, + "customresource_kind": 5, + "customresource_version": 6, + "endpoint": 7, + "exported_namespace": 3, + "instance": 8, + "job": 9, + "name": 10, + "namespace": 11, + "pod": 12, + "ready": 13, + "service": 14 + }, + "renameByName": { + "Value": "", + "customresource_kind": "Kind", + "exported_namespace": "Namespace", + "kind": "Kind", + "name": "Name", + "namespace": "Operator Namespace", + "ready": "Status" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [ + { + "options": { + "False": { + "color": "red", + "index": 1, + "text": "Not Ready" + }, + "True": { + "color": "blue", + "index": 0, + "text": "Ready" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "basic", + "type": "color-background" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "noValue", + "value": "Ready" + }, + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 34, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Status" + } + ] + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "gotk_resource_info{exported_namespace=~\"$namespace\", customresource_kind=~\"GitRepository|HelmRepository|Bucket|OCIRepository\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Source acquisition readiness ", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "__name__": true, + "app": true, + "bucket_name": true, + "container": true, + "customresource_group": true, + "customresource_kind": false, + "customresource_version": true, + "endpoint": true, + "exported_namespace": false, + "gotk_type": true, + "instance": true, + "job": true, + "kubernetes_namespace": true, + "kubernetes_pod_name": true, + "namespace": true, + "pod": true, + "pod_template_hash": true, + "ready": false, + "revision": true, + "service": true, + "status": true, + "suspended": true, + "type": true, + "url": true + }, + "indexByName": { + "Time": 0, + "Value": 15, + "__name__": 1, + "container": 2, + "customresource_group": 5, + "customresource_kind": 6, + "customresource_version": 7, + "endpoint": 8, + "exported_namespace": 4, + "instance": 9, + "job": 10, + "name": 11, + "namespace": 3, + "pod": 12, + "ready": 13, + "service": 14 + }, + "renameByName": { + "Value": "", + "customresource_kind": "Kind", + "exported_namespace": "Namespace", + "kind": "Kind", + "name": "Name", + "namespace": "Operator Namespace", + "ready": "Status" + } + } + } + ], + "type": "table" + }, + { + "collapsed": false, + "datasource": "${DS_PROMETHEUS}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 17, + "panels": [], + "title": "Timing", + "type": "row" + }, + { + "datasource": "${DS_PROMETHEUS}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + }, + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsNull", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 27, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind, name)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind, name)", + "hide": false, + "interval": "", + "legendFormat": "{{kind}}/{{name}}", + "refId": "B" + } + ], + "title": "Cluster reconciliation duration", + "type": "timeseries" + }, + { + "datasource": "${DS_PROMETHEUS}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + }, + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsNull", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 35, + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket|OCIRepository\"}[5m])) by (kind, name)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket|OCIRepository\"}[5m])) by (kind, name)", + "hide": false, + "interval": "", + "legendFormat": "{{kind}}/{{name}}", + "refId": "B" + } + ], + "title": "Source acquisition duration", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 38, + "style": "light", + "tags": [ + "flux" + ], + "templating": { + "list": [ + { + "allValue": "", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "$DS_PROMETHEUS" + }, + "definition": "label_values(gotk_reconcile_condition, namespace)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "operator_namespace", + "options": [], + "query": { + "query": "label_values(gotk_reconcile_condition, namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "$DS_PROMETHEUS" + }, + "definition": "label_values(gotk_resource_info,exported_namespace)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(gotk_resource_info,exported_namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "Prometheus" + }, + "hide": 0, + "includeAll": false, + "label": "Datasource", + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Flux Cluster Stats", + "uid": "flux-cluster", + "version": 4, + "weekStart": "" +} diff --git a/apps/base/monitoring/grafana/grafana-dashboards/control-plane.json b/apps/base/monitoring/grafana/grafana-dashboards/control-plane.json new file mode 100644 index 0000000..588c455 --- /dev/null +++ b/apps/base/monitoring/grafana/grafana-dashboards/control-plane.json @@ -0,0 +1,1724 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "iconColor": "red", + "name": "flux events", + "target": { + "limit": 100, + "matchAny": false, + "tags": [ + "flux" + ], + "type": "tags" + } + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 24, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(go_info{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", + "interval": "", + "legendFormat": "pods", + "refId": "A" + } + ], + "title": "Controllers", + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "#EAB839", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 23, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "max(workqueue_longest_running_processor_seconds{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", + "hide": false, + "interval": "", + "legendFormat": "seconds", + "refId": "B" + } + ], + "title": "Max Work Queue", + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "#EAB839", + "value": 500000000 + }, + { + "color": "red", + "value": 900000000 + } + ] + }, + "unit": "decbits" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 25, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(go_memstats_alloc_bytes{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Memory", + "type": "gauge" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "#EAB839", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 26, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\",pod=~\".*-controller-.*\"}[1m]))", + "interval": "", + "legendFormat": "requests", + "refId": "A" + } + ], + "title": "API Requests", + "type": "stat" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 21, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "total", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\",code!~\"2..\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "errors", + "refId": "B" + } + ], + "title": "Kubernetes API Requests", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 15, + "panels": [], + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "refId": "A" + } + ], + "title": "Resource Usage", + "type": "row" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "rate(process_cpu_seconds_total{namespace=\"$namespace\",pod=~\".*-controller-.*\"}[1m])", + "interval": "", + "legendFormat": "{{pod}}", + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 13, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(container_memory_working_set_bytes{namespace=\"$namespace\",container!=\"POD\",container!=\"\",pod=~\".*-controller-.*\"}) by (pod)", + "hide": false, + "interval": "", + "legendFormat": "{{pod}}", + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 17, + "panels": [], + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "refId": "A" + } + ], + "title": "Reconciliation Stats", + "type": "row" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 27, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "workqueue_longest_running_processor_seconds{name=\"kustomization\"}", + "hide": false, + "interval": "", + "legendFormat": "kustomizations", + "refId": "B" + } + ], + "title": "Cluster Reconciliation Duration", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "opm" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"kustomization\",result!=\"error\"}[1m])) by (controller)", + "format": "time_series", + "interval": "", + "legendFormat": "successful reconciliations ", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"kustomization\",result=\"error\"}[1m])) by (controller)", + "format": "time_series", + "interval": "", + "legendFormat": "failed reconciliations ", + "refId": "B" + } + ], + "title": "Cluster Reconciliations ops/min", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 29, + "panels": [], + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "refId": "A" + } + ], + "title": "Sources Stats", + "type": "row" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "opm" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 43 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"gitrepository\",result!=\"error\"}[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "successful git pulls", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"gitrepository\",result=\"error\"}[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "failed git pulls", + "refId": "B" + } + ], + "title": "Git Repos ops/min", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "opm" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 43 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"ocirepository\",result!=\"error\"}[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "successful oci pulls", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"ocirepository\",result=\"error\"}[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "failed oci pulls", + "refId": "B" + } + ], + "title": "OCI Repos ops/min", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "opm" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 52 + }, + "id": 31, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmrepository\",result!=\"error\"}[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "successful helm pulls", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmrepository\",result=\"error\"}[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "failed helm pulls", + "refId": "B" + } + ], + "title": "Helm Repos ops/min", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "opm" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 52 + }, + "id": 32, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"bucket\",result!=\"error\"}[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "successful bucket pulls", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"bucket\",result=\"error\"}[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "failed bucket pulls", + "refId": "B" + } + ], + "title": "Buckets ops/min", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 61 + }, + "id": 19, + "panels": [], + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "refId": "A" + } + ], + "title": "Helm Stats", + "type": "row" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 62 + }, + "id": 9, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "list", + "placement": "right", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "histogram_quantile(0.50, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", + "hide": true, + "interval": "", + "legendFormat": "P50", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "histogram_quantile(0.90, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", + "hide": true, + "interval": "", + "legendFormat": "P90", + "refId": "B" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "histogram_quantile(0.99, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", + "hide": false, + "interval": "", + "legendFormat": "P99", + "refId": "C" + } + ], + "title": "Helm Release Duration", + "type": "timeseries" + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "decimals": 2, + "description": "", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 70 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.0.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmrelease\",result!=\"error\"}[1m])) by (controller)", + "format": "time_series", + "interval": "", + "legendFormat": "successful reconciliations ", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmrelease\",result=\"error\"}[1m])) by (controller)", + "format": "time_series", + "interval": "", + "legendFormat": "failed reconciliations ", + "refId": "B" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Helm Releases ops/min", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1102", + "format": "opm", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:1103", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "opm" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 70 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmchart\",result!=\"error\"}[1m])) by (controller)", + "format": "time_series", + "interval": "", + "legendFormat": "successful chart pulls", + "refId": "A" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmchart\",result=\"error\"}[1m])) by (controller)", + "format": "time_series", + "interval": "", + "legendFormat": "failed chart pulls", + "refId": "B" + } + ], + "title": "Helm Charts ops/min", + "type": "timeseries" + } + ], + "refresh": "10s", + "schemaVersion": 38, + "style": "light", + "tags": [ + "flux" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "Prometheus" + }, + "hide": 2, + "includeAll": false, + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "flux-system", + "value": "flux-system" + }, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "workqueue_work_duration_seconds_count", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "workqueue_work_duration_seconds_count", + "refId": "Prometheus-namespace-Variable-Query" + }, + "refresh": 2, + "regex": "/.*namespace=\"([^\"]*).*/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Flux Control Plane", + "uid": "flux-control-plane", + "version": 2, + "weekStart": "" +} diff --git a/apps/base/monitoring/grafana/grafana-dashboards/dashboard-hub.json b/apps/base/monitoring/grafana/grafana-dashboards/dashboard-hub.json new file mode 100644 index 0000000..2017633 --- /dev/null +++ b/apps/base/monitoring/grafana/grafana-dashboards/dashboard-hub.json @@ -0,0 +1,917 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "traefik_hub_portals_ratio{portal_type=\"single_cluster\"}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Number of portal", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 3, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "traefik_hub_api_gateways_ratio{}", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Number of gateway", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "traefik_hub_api_keys_ratio", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "# API keys", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "traefik_hub_api_rate_limits_ratio", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "# API rate limits", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "traefik_hub_acps_ratio", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "# ACPs", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "traefik_hub_api_nodes_ratio", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "# API nodes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "traefik_hub_api_gateway_apis_ratio", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "# API gateways API", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 25 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "traefik_hub_api_accesses_ratio", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "API accesses", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "displayName": "API count", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 25 + }, + "id": 2, + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.1.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "traefik_hub_apis_ratio", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "traefik_hub_api_versions_ratio", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "# API and API versions", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "kubernetes" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "API Management", + "uid": "eb0a9268-05e8-4ec4-8d2c-c1705da6a885", + "version": 1, + "weekStart": "" +} diff --git a/apps/base/monitoring/grafana/grafana-dashboards/hub-apis.json b/apps/base/monitoring/grafana/grafana-dashboards/hub-apis.json new file mode 100644 index 0000000..47c9d67 --- /dev/null +++ b/apps/base/monitoring/grafana/grafana-dashboards/hub-apis.json @@ -0,0 +1,721 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": true, + "expr": "sum(increase(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[$__range]))", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total # of requests", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 2, + "x": 3, + "y": 0 + }, + "id": 9, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": true, + "expr": "", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "# Users", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038720/users?orgId=1&refresh=5s&var-email=${__field.labels.email}" + } + ], + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 5, + "y": 0 + }, + "id": 8, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (email) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m])) > 0", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Per users", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038720/users?orgId=1&refresh=5s&var-email=${__field.labels.email}" + } + ], + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 8, + "y": 0 + }, + "id": 11, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (method) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m])) > 0", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Per method", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038720/users?orgId=1&refresh=5s&var-email=${__field.labels.email}" + } + ], + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 11, + "y": 0 + }, + "id": 12, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (code) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m])) > 0", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Per code", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "dark-red", + "value": 3 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 2, + "x": 14, + "y": 0 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "100*sum(increase(traefik_hub_api_requests_total{api_name=~\"$api_name\", code!~\"2.+\"}[$__range]))/sum(increase(traefik_hub_api_requests_total{api_name=~\"$api_name\", code=~\".+\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average error rate", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 8 + }, + "id": 1, + "interval": "10s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(rate(traefik_hub_api_requests_total{api_name=~\"$api_name\", code!~\"2.*\"}[1m]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "All requests", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m]))", + "hide": false, + "instant": false, + "legendFormat": "Non 200 Requests", + "range": true, + "refId": "B" + } + ], + "title": "Requests per API", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 8 + }, + "id": 10, + "interval": "10s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "avg by (api_name) (rate(traefik_hub_api_requests_duration_milliseconds_sum{api_name=~\"$api_name\"}[1m])) > 0", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "", + "legendFormat": "{{api_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Latency per API in ms", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 38, + "tags": [], + "templating": { + "list": [ + { + "allValue": ".*", + "current": { + "selected": true, + "text": "flight-api", + "value": "flight-api" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(api_name)", + "hide": 0, + "includeAll": false, + "label": "API Name", + "multi": false, + "name": "api_name", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(api_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "API", + "uid": "bbc789b4-e88e-42a1-b969-1ce2b0038725", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/apps/base/monitoring/grafana/grafana-dashboards/hub-main.json b/apps/base/monitoring/grafana/grafana-dashboards/hub-main.json new file mode 100644 index 0000000..e504cb5 --- /dev/null +++ b/apps/base/monitoring/grafana/grafana-dashboards/hub-main.json @@ -0,0 +1,979 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": true, + "expr": "sum(increase(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[$__range]))", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total # of requests", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038725/api?orgId=1&refresh=5s&var-api_name=${__field.labels.api_name}" + } + ], + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 3, + "y": 0 + }, + "id": 8, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (api_name) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m])) > 0", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Per API", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038725/api?orgId=1&refresh=5s&var-api_name=${__field.labels.api_name}" + } + ], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 6, + "y": 0 + }, + "id": 9, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": true, + "expr": "sum by (api_name) (increase(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[$__range])) > 0", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{api_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total # of requests per API", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "red", + "value": 2.5 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 2, + "x": 11, + "y": 0 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "100*sum(increase(traefik_hub_api_requests_total{api_name=~\"$api_name\", code!~\"2.+\"}[$__range]))/sum(increase(traefik_hub_api_requests_total{api_name=~\"$api_name\", code=~\".+\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average error rate", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "links": [], + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 13, + "y": 0 + }, + "id": 11, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (code) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m])) > 0", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Per API", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 100, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038725/api?orgId=1&refresh=5s&var-api_name=${__field.labels.api_name}" + } + ], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 10, + "interval": "10s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "sum (rate(traefik_hub_api_requests_duration_milliseconds_sum{api_name=~\"$api_name\"}[1m])) > 0", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "", + "legendFormat": "All requests", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum (rate(traefik_hub_api_requests_duration_milliseconds_sum{api_name=~\"$api_name\", code!~\"2.*\"}[1m])) > 0", + "hide": false, + "instant": false, + "legendFormat": "Non 200 Requests", + "range": true, + "refId": "B" + } + ], + "title": "Error requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038725/api?orgId=1&refresh=5s&var-api_name=${__field.labels.api_name}" + } + ], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 8 + }, + "id": 1, + "interval": "10s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by (api_name) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m])) > 0", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{api_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Requests per API", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038725/api?orgId=1&refresh=5s&var-api_name=${__field.labels.api_name}" + } + ], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 8 + }, + "id": 14, + "interval": "10s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "avg by (api_name) (rate(traefik_hub_api_requests_duration_milliseconds_sum{api_name=~\"$api_name\"}[1m])) > 0", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{api_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Latency per API", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038725/api?orgId=1&refresh=5s&var-api_name=${__field.labels.api_name}" + } + ], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 17 + }, + "id": 12, + "interval": "10s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "100*sum by (api_name) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\", code!~\"2.*\"}[1m]))/sum by (api_name) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{api_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Error rate per API", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [ + { + "title": "", + "url": "http://grafana.docker.localhost/d/bbc789b4-e88e-42a1-b969-1ce2b0038725/api?orgId=1&refresh=5s&var-api_name=${__field.labels.api_name}" + } + ], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 17 + }, + "id": 13, + "interval": "10s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by (api_name) (rate(traefik_hub_api_requests_total{api_name=~\"$api_name\"}[1m])) > 0", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{api_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Requests per API", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 38, + "tags": [], + "templating": { + "list": [ + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(api_name)", + "hide": 0, + "includeAll": true, + "label": "API Name", + "multi": true, + "name": "api_name", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(api_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Hub Dashboard", + "uid": "c54cf5b9-6504-4074-9b5a-f9af39c8e1fb", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/apps/base/monitoring/grafana/grafana-dashboards/hub-users.json b/apps/base/monitoring/grafana/grafana-dashboards/hub-users.json new file mode 100644 index 0000000..32681e7 --- /dev/null +++ b/apps/base/monitoring/grafana/grafana-dashboards/hub-users.json @@ -0,0 +1,611 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 5, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "traefik_hub_api_keys_ratio{user_email=\"$email\"}", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "# Tokens", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 3, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": true, + "expr": "sum(increase(traefik_hub_api_requests_total{email=~\"$email\", token_name=~\"$token_name\", code=~\".+\"}[$__range]))", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total # of requests", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 6, + "y": 0 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "count(count by (api_name) (traefik_hub_api_requests_total{email=~\"$email\", token_name=~\"$token_name\"}))\n", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "# APIs", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 9, + "y": 0 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "100*sum(increase(traefik_hub_api_requests_total{email=~\"$email\", token_name=~\"$token_name\", code!~\"2.+\"}[$__range]))/sum(increase(traefik_hub_api_requests_total{email=~\"$email\", token_name=~\"$token_name\", code=~\".+\"}[$__range]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Average error rate", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 12, + "y": 0 + }, + "id": 7, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (api_name) (rate(traefik_hub_api_requests_total{email=~\"$email\", token_name=~\"$token_name\",code=~\".+\"}[1m])) > 0", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Per API", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 15, + "y": 0 + }, + "id": 8, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by (token_name) (rate(traefik_hub_api_requests_total{email=~\"$email\", token_name=~\"$token_name\",code=~\".+\"}[1m])) > 0", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Per Token", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 22, + "x": 0, + "y": 8 + }, + "id": 1, + "interval": "10s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by (code, api_name, method, token_name) (rate(traefik_hub_api_requests_total{email=~\"$email\", token_name=~\"$token_name\",code=~\".+\"}[1m])) > 0", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "[{{code}}] {{api_name}} {{method}} {{token_name}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Panel Title", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 38, + "tags": [], + "templating": { + "list": [ + { + "allValue": ".*", + "current": { + "selected": false, + "text": "julien.salleyron@traefik.io", + "value": "julien.salleyron@traefik.io" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(email)", + "hide": 0, + "includeAll": false, + "label": "Email", + "multi": false, + "name": "email", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(email)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(traefik_hub_api_requests_duration_milliseconds_bucket{email=~\"$email\"},token_name)", + "hide": 0, + "includeAll": true, + "label": "Token Name", + "multi": true, + "name": "token_name", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(traefik_hub_api_requests_duration_milliseconds_bucket{email=~\"$email\"},token_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Users", + "uid": "bbc789b4-e88e-42a1-b969-1ce2b0038720", + "version": 4, + "weekStart": "" +} \ No newline at end of file diff --git a/apps/base/monitoring/grafana/grafana-dashboards/logs.json b/apps/base/monitoring/grafana/grafana-dashboards/logs.json new file mode 100644 index 0000000..4d9f58d --- /dev/null +++ b/apps/base/monitoring/grafana/grafana-dashboards/logs.json @@ -0,0 +1,332 @@ +{ + "__inputs": [ + { + "name": "DS_LOKI", + "label": "Loki", + "description": "", + "type": "datasource", + "pluginId": "loki", + "pluginName": "Loki" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "iconColor": "red", + "name": "flux events", + "target": { + "limit": 100, + "matchAny": false, + "tags": [ + "flux" + ], + "type": "tags" + } + } + ] + }, + "description": "Flux logs collected from Kubernetes, stored in Loki", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 29, + "iteration": 1653748775696, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": "${DS_LOKI}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": "${DS_LOKI}", + "expr": "sum(count_over_time({namespace=~\"$namespace\", stream=~\"$stream\", app =~\"$controller\"} | json | __error__!=\"JSONParserErr\" | level=~\"$level\" |= \"$query\" [$__interval]))", + "instant": false, + "legendFormat": "Log count", + "range": true, + "refId": "A" + } + ], + "type": "timeseries" + }, + { + "datasource": "${DS_LOKI}", + "description": "Logs from services running in Kubernetes", + "gridPos": { + "h": 25, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 2, + "options": { + "dedupStrategy": "numbers", + "enableLogDetails": false, + "prettifyLogMessage": true, + "showCommonLabels": false, + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": "${DS_LOKI}", + "expr": "{namespace=~\"$namespace\", stream=~\"$stream\", app =~\"$controller\"} | json | __error__!=\"JSONParserErr\" | level=~\"$level\" |= \"$query\"", + "refId": "A" + } + ], + "type": "logs" + } + ], + "refresh": "10s", + "schemaVersion": 36, + "style": "light", + "tags": [ + "flux" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "description": "String to search for", + "hide": 0, + "label": "Search Query", + "name": "query", + "options": [ + { + "selected": true, + "text": "", + "value": "" + } + ], + "query": "", + "skipUrlSync": false, + "type": "textbox" + }, + { + "allValue": "info|error", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "hide": 0, + "includeAll": true, + "multi": false, + "name": "level", + "options": [ + { + "selected": true, + "text": "All", + "value": "$__all" + }, + { + "selected": false, + "text": "info", + "value": "info" + }, + { + "selected": false, + "text": "error", + "value": "error" + } + ], + "query": "info,error", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "allValue": ".+", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": "${DS_LOKI}", + "definition": "label_values(app)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "controller", + "options": [], + "query": "label_values(app)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".+", + "current": { + "selected": true, + "text": [ + "flux-system" + ], + "value": [ + "flux-system" + ] + }, + "datasource": "${DS_LOKI}", + "definition": "label_values(namespace)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "namespace", + "options": [], + "query": "label_values(namespace)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".+", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "${DS_LOKI}", + "definition": "label_values(stream)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "stream", + "options": [], + "query": "label_values(stream)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "Loki", + "value": "Loki" + }, + "hide": 0, + "includeAll": false, + "label": "Datasource", + "multi": false, + "name": "DS_LOKI", + "options": [], + "query": "loki", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Flux Logs", + "uid": "flux-logs", + "version": 2 +} diff --git a/apps/base/monitoring/grafana/ingress.yaml b/apps/base/monitoring/grafana/ingress.yaml new file mode 100644 index 0000000..a416844 --- /dev/null +++ b/apps/base/monitoring/grafana/ingress.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: grafana + namespace: monitoring +spec: + entryPoints: + - web + routes: + - kind: Rule + match: Host(`grafana.docker.localhost`) + services: + - name: grafana + port: 3000 + namespace: monitoring diff --git a/apps/base/monitoring/grafana/kustomization.yaml b/apps/base/monitoring/grafana/kustomization.yaml new file mode 100644 index 0000000..1ebd78f --- /dev/null +++ b/apps/base/monitoring/grafana/kustomization.yaml @@ -0,0 +1,22 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: monitoring +resources: + - pv.yaml + - config.yaml + - deployment.yaml + - service.yaml + - ingress.yaml + +configMapGenerator: + - behavior: create + files: + - dashboard-hub.json=grafana-dashboards/dashboard-hub.json + - hub-apis.json=grafana-dashboards/hub-apis.json + - hub-main.json=grafana-dashboards/hub-main.json + - hub-users.json=grafana-dashboards/hub-users.json + - cluster.json=grafana-dashboards/cluster.json + - control-plane.json=grafana-dashboards/control-plane.json + - logs.json=grafana-dashboards/logs.json + name: grafana-hub-dashboards + namespace: monitoring diff --git a/apps/base/monitoring/grafana/pv.yaml b/apps/base/monitoring/grafana/pv.yaml new file mode 100644 index 0000000..03e01d1 --- /dev/null +++ b/apps/base/monitoring/grafana/pv.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: grafana-pvc + namespace: monitoring +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/apps/base/monitoring/grafana/service.yaml b/apps/base/monitoring/grafana/service.yaml new file mode 100644 index 0000000..ab12900 --- /dev/null +++ b/apps/base/monitoring/grafana/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: grafana + namespace: monitoring +spec: + ports: + - port: 3000 + protocol: TCP + targetPort: http-grafana + name: web + selector: + app: grafana + sessionAffinity: None diff --git a/apps/base/monitoring/kustomization.yaml b/apps/base/monitoring/kustomization.yaml new file mode 100644 index 0000000..1cd334e --- /dev/null +++ b/apps/base/monitoring/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: monitoring +resources: + - namespace.yaml + - prometheus + - grafana diff --git a/apps/base/monitoring/namespace.yaml b/apps/base/monitoring/namespace.yaml new file mode 100644 index 0000000..ff7ae1b --- /dev/null +++ b/apps/base/monitoring/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring diff --git a/apps/base/monitoring/prometheus/config.yaml b/apps/base/monitoring/prometheus/config.yaml new file mode 100644 index 0000000..1fec715 --- /dev/null +++ b/apps/base/monitoring/prometheus/config.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: prometheus-rules + namespace: monitoring +data: + general.yaml: | + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: prometheus-core + namespace: monitoring +data: + prometheus.yaml: | + global: + scrape_interval: 10s + scrape_timeout: 10s + evaluation_interval: 10s + + # Prom is deployed statically, not through the Prom Operator, so instead of using a ServiceMonitor or an annotation, we add the jobs here + # @see: https://www.infracloud.io/blogs/prometheus-operator-helm-guide/#how-does-prometheus-find-all-the-targets-to-monitor-and-scrape + scrape_configs: + # Scrape prometheus itself, the job name is added as a label `job=` to any timeseries scraped from this config + - job_name: 'prometheus' + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + static_configs: + - targets: ['localhost:9090'] diff --git a/apps/base/monitoring/prometheus/deployment.yaml b/apps/base/monitoring/prometheus/deployment.yaml new file mode 100644 index 0000000..37c5fea --- /dev/null +++ b/apps/base/monitoring/prometheus/deployment.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: prometheus-core + namespace: monitoring + labels: + app: prometheus + component: core +spec: + replicas: 1 + selector: + matchLabels: + app: prometheus + template: + metadata: + name: prometheus-main + labels: + app: prometheus + component: core + spec: + serviceAccountName: prometheus-k8s + securityContext: + fsGroup: 2000 + runAsUser: 1000 + runAsNonRoot: true + containers: + - name: prometheus + image: prom/prometheus:v2.47.0-rc.0 + args: + - '--enable-feature=otlp-write-receiver' + - '--storage.tsdb.retention=60d' + - '--storage.tsdb.path="/prometheus/data/"' + - '--web.enable-lifecycle' + - '--config.file=/prometheus/config/prometheus.yaml' + ports: + - name: web + containerPort: 80 + - name: webui + containerPort: 9090 + resources: + requests: + cpu: 500m + memory: 500M + limits: + cpu: 500m + memory: 500M + volumeMounts: + - name: config-volume + mountPath: /prometheus/config + - name: rules-volume + mountPath: /prometheus/rules + - name: prometheus-storage + mountPath: /prometheus + - name: configmap-reload + image: jimmidyson/configmap-reload:v0.2.2 + imagePullPolicy: IfNotPresent + args: + - --volume-dir=/etc/config + - --volume-dir=/etc/rules + - --webhook-url=http://localhost:9090/-/reload + volumeMounts: + - name: config-volume + mountPath: /etc/config + readOnly: true + - name: rules-volume + mountPath: /etc/rules + readOnly: true + resources: + limits: + cpu: 10m + memory: 10Mi + requests: + cpu: 10m + memory: 10Mi + volumes: + - name: prometheus-storage + persistentVolumeClaim: + claimName: prometheus-storage + - name: config-volume + configMap: + name: prometheus-core + - name: rules-volume + configMap: + name: prometheus-rules diff --git a/apps/base/monitoring/prometheus/ingress.yaml b/apps/base/monitoring/prometheus/ingress.yaml new file mode 100644 index 0000000..c189aec --- /dev/null +++ b/apps/base/monitoring/prometheus/ingress.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: prometheus + namespace: monitoring +spec: + entryPoints: + - web + routes: + - kind: Rule + match: Host(`prometheus.docker.localhost`) + services: + - name: prometheus + port: 9090 + namespace: monitoring diff --git a/apps/base/monitoring/prometheus/kustomization.yaml b/apps/base/monitoring/prometheus/kustomization.yaml new file mode 100644 index 0000000..cf11605 --- /dev/null +++ b/apps/base/monitoring/prometheus/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: monitoring +resources: + - rbac.yaml + - pv.yaml + - config.yaml + - deployment.yaml + - service.yaml + - ingress.yaml diff --git a/apps/base/monitoring/prometheus/pv.yaml b/apps/base/monitoring/prometheus/pv.yaml new file mode 100644 index 0000000..afef25f --- /dev/null +++ b/apps/base/monitoring/prometheus/pv.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: prometheus-storage + namespace: monitoring +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: local-path + # storageClassName: gp2 # AWS diff --git a/apps/base/monitoring/prometheus/rbac.yaml b/apps/base/monitoring/prometheus/rbac.yaml new file mode 100644 index 0000000..f5c24a6 --- /dev/null +++ b/apps/base/monitoring/prometheus/rbac.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: prometheus +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: prometheus +subjects: + - kind: ServiceAccount + name: prometheus-k8s + namespace: monitoring + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: prometheus +rules: + - apiGroups: [""] + resources: + - nodes + - services + - endpoints + - pods + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: + - configmaps + verbs: ["get"] + - nonResourceURLs: ["/metrics"] + verbs: ["get"] + - apiGroups: ["extensions"] + resources: + - ingresses + verbs: ["get", "list", "watch"] + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: prometheus-k8s + namespace: monitoring diff --git a/apps/base/monitoring/prometheus/service.yaml b/apps/base/monitoring/prometheus/service.yaml new file mode 100644 index 0000000..c51db55 --- /dev/null +++ b/apps/base/monitoring/prometheus/service.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: prometheus-remote-write + namespace: monitoring + labels: + app: prometheus + component: core +spec: + ports: + - port: 80 + protocol: TCP + name: web + selector: + app: prometheus + component: core + +--- +apiVersion: v1 +kind: Service +metadata: + name: prometheus + namespace: monitoring + labels: + app: prometheus + component: core +spec: + type: NodePort + ports: + - port: 9090 + protocol: TCP + name: webui + selector: + app: prometheus + component: core diff --git a/apps/base/traefik-hub/kustomization.yaml b/apps/base/traefik-hub/kustomization.yaml new file mode 100644 index 0000000..5941c0c --- /dev/null +++ b/apps/base/traefik-hub/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: traefik-hub +resources: + - namespace.yaml + - token.yaml + - repository.yaml + - release.yaml diff --git a/apps/base/traefik-hub/namespace.yaml b/apps/base/traefik-hub/namespace.yaml new file mode 100644 index 0000000..ac34169 --- /dev/null +++ b/apps/base/traefik-hub/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: traefik-hub diff --git a/apps/base/traefik-hub/release.yaml b/apps/base/traefik-hub/release.yaml new file mode 100644 index 0000000..22dd824 --- /dev/null +++ b/apps/base/traefik-hub/release.yaml @@ -0,0 +1,30 @@ +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: traefik-hub + namespace: traefik-hub +spec: + releaseName: traefik-hub + chart: + spec: + chart: traefik-hub + sourceRef: + kind: HelmRepository + name: traefik-hub + interval: 50m + install: + remediation: + retries: 3 + values: + additionalArguments: + - --hub.metrics.opentelemetry.insecure + - --hub.metrics.opentelemetry.address=prometheus.monitoring:9090 + - --hub.metrics.opentelemetry.path=/api/v1/otlp/v1/metrics + hub: + metrics: + opentelemetry: + insecure: true + address: prometheus.monitoring:9090 + path: /api/v1/otlp/v1/metrics + service: + type: LoadBalancer diff --git a/apps/base/traefik-hub/repository.yaml b/apps/base/traefik-hub/repository.yaml new file mode 100644 index 0000000..0f0ca80 --- /dev/null +++ b/apps/base/traefik-hub/repository.yaml @@ -0,0 +1,8 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: traefik-hub + namespace: traefik-hub +spec: + interval: 5m + url: https://traefik.github.io/charts diff --git a/apps/base/traefik-hub/token.yaml b/apps/base/traefik-hub/token.yaml new file mode 100644 index 0000000..d5576a7 --- /dev/null +++ b/apps/base/traefik-hub/token.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Secret +metadata: + name: hub-agent-token +data: + token: YTU3MTEyMDEtMDFkMS00NjFhLWJhNjAtZjg3YjE5OTExZjg3 diff --git a/clusters/kubecon/flux-system/gotk-sync.yaml b/clusters/kubecon/flux-system/gotk-sync.yaml index fdb5d05..2e39f45 100644 --- a/clusters/kubecon/flux-system/gotk-sync.yaml +++ b/clusters/kubecon/flux-system/gotk-sync.yaml @@ -11,7 +11,7 @@ spec: branch: master secretRef: name: flux-system - url: ssh://git@github.com/mmatur/hub-kubecon-demo + url: ssh://git@github.com/traefik/hub-kubecon-demo --- apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization @@ -25,3 +25,16 @@ spec: sourceRef: kind: GitRepository name: flux-system +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: kubecon + namespace: flux-system +spec: + interval: 10s + path: ./apps/base + prune: true + sourceRef: + kind: GitRepository + name: flux-system diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..39d74a3 --- /dev/null +++ b/readme.md @@ -0,0 +1,131 @@ +# Traefik Hub Kubecon NA 2023 + +## Deploying Traefik Hub + +Login to the Traefik Hub UI, add a new agent and copy your token. + +Now, open a terminal and run these commands: + +## Install monitoring + +### API management metrics with OTel + +Install Prometheus with OTel ingestion support + install Grafana + add dashboards: + +```shell +kubectl apply -f tools/monitoring-api-otel/monitoring.yaml +kubectl create configmap grafana-hub-dashboards \ + --from-file=tools/monitoring-api-otel/dashboard-hub.json --from-file=tools/monitoring-api-otel/hub-users.json \ + --from-file=tools/monitoring-api-otel/hub-apis.json \ + --from-file=tools/monitoring-api-otel/hub-main.json \ + -o yaml --dry-run=client -n monitoring | kubectl apply -f - +``` + +(Re)install the Hub agent with OTel metrics enabled and Hub exposed as an LB: + +```shell +# Complete the install steps (token, etc) from above or the Hub UI, then run helm with the new parameters +# Export your token +export HUB_TOKEN=a03940a1-3540-4726-81aa-bd926ce1c5f1 + +# Hub prod env +helm upgrade --install --namespace traefik-hub traefik-hub traefik/traefik-hub \ + --set additionalArguments='{--hub.metrics.opentelemetry.insecure,--hub.metrics.opentelemetry.address=prometheus.monitoring:9090,--hub.metrics.opentelemetry.path=/api/v1/otlp/v1/metrics}' \ + --set service.type=LoadBalancer +``` + +### Ingress controller mode + +```shell +# Add the Helm repository +helm repo add traefik https://traefik.github.io/charts +helm repo update + +# Install the Ingress Controller +kubectl create namespace traefik-hub +kubectl create secret generic hub-agent-token --namespace traefik-hub --from-literal=token=$HUB_TOKEN +helm upgrade --install --namespace traefik-hub traefik-hub traefik/traefik-hub \ + --set additionalArguments='{--hub.metrics.opentelemetry.insecure,--hub.metrics.opentelemetry.address=prometheus.monitoring:9090,--hub.metrics.opentelemetry.path=/api/v1/otlp/v1/metrics}' \ + --set service.type=LoadBalancer +``` + +## Custom domain + +> :warning: Traefik Labs Internal! + +When using an environment from [env-on-demand](https://github.com/traefik/env-on-demand/issues "Link to GitHub repo for creating on-demand envs") you can control the DNS records to enable custom domains. + +After updating the records, edit the custom domain section in `api-gateway.yaml` and `api-portal.yaml` and apply them (or reapply if did it previously). + +```shell +# Add your env name and DNS token +export ENV_NAME=your-env-on-demand-cluster-name +export DNS_TOKEN=your-dns-token-from-the-env-on-demand-bot-reply + +# Choose one of the ingresses +export INGRESS_CONTROLLER=hub +export INGRESS_CONTROLLER=nginx +export INGRESS_CONTROLLER=emissary +``` + +Run these commands for the domain setup: + +```shell +case $INGRESS_CONTROLLER in + hub) + export SVC_NAME=traefik + export SVC_NS=traefik-hub + ;; + nginx) + export SVC_NAME=ingress-nginx-controller + export SVC_NS=ingress-nginx + ;; + emissary) + export SVC_NAME=emissary-emissary-ingress + export SVC_NS=emissary + ;; +esac + +for subdomain in portal gateway prometheus grafana ; do + echo "" + echo "Creating CNAME record for ${subdomain} ..." + curl --location --request POST "https://cf.infra.traefiklabs.tech/dns/env-on-demand?s=${subdomain}" \ + --header "X-TraefikLabs-User: ${ENV_NAME}" \ + --header "Content-Type: application/json" \ + --header "Authorization: Bearer ${DNS_TOKEN}" \ + --data-raw "{\"Value\": \"$(kubectl get service $SVC_NAME -n $SVC_NS --no-headers | awk {'print $4'})\"}" + echo "" + echo "Creating CNAME record for ${subdomain} ACME challenge ..." + curl --location --request POST "https://cf.infra.traefiklabs.tech/dns/env-on-demand?s=_acme-challenge.${subdomain}" \ + --header "X-TraefikLabs-User: ${ENV_NAME}" \ + --header "Content-Type: application/json" \ + --header "Authorization: Bearer ${DNS_TOKEN}" \ + --data-raw "{\"Value\": \"acme-challenge.traefikhub.io\"}" +done +``` + + +## Deploy APIs and Portal + +Apply all resources in one step: + +```shell +kubectl apply \ + -f apis/namespace.yaml \ + -f apis/customers/ \ + -f apis/employee/ \ + -f apis/flight/ \ + -f apis/ticket/ \ + -f apis/external/ \ + -f apis/api-collections.yaml \ + -f api-access.yaml \ + -f api-rate-limit.yaml \ + -f api-gateway.yaml \ + -f api-portal.yaml \ + -f custom-ui.yaml +``` + +## Demo playbook + +If you need to do a demo on Hub, here is a [demo playbook](./demo-playbook.md) for you. + diff --git a/traffic.sh b/traffic.sh new file mode 100755 index 0000000..454b5e2 --- /dev/null +++ b/traffic.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# +TOKEN=PQRbrvwK46kY39gMJ9jpwf1iTgaHvxfeKZJA5QdR +GW_DOMAIN="subjective-squirrel-ktmkru.cv5yg484.traefikhub.io" + +random() { + NUMBER=$RANDOM + let "NUMBER%=$1" + + # add 10 to have a minimum of 10 for the -c option of hey + let "NUMBER+=10" + echo $NUMBER +} + +call() { + METHOD=$1 + HPATH=$2 + RDNB=$3 + ERRORRATE=$4 + NB=$(random $RDNB) + + echo "$HPATH => " + + let "ERR=$NB/100*$ERRORRATE" + + hey -m "$METHOD" -c 10 -n $NB \ + -H 'accept: application/json' \ + -H "Authorization: Bearer $TOKEN" "https://$GW_DOMAIN$HPATH" | grep responses + hey -m "$METHOD" -c 1 -n $ERR \ + -H 'accept: application/json' \ + -H "Authorization: Bearer $TOKEN" "https://$GW_DOMAIN$HPATH?status=503" | grep responses + echo "" + echo "" + +} + +for (( ; ; )) +do + call 'GET' "/customers/customers" 1000 2 + call 'GET' "/flights/flights" 1000 5 + call 'GET' "/tickets/tickets" 1000 2 + call 'POST' "/tickets/tickets" 1000 2 + call 'POST' "/employees/employees" 1000 2 + + sleep 2; +done;