Skip to content

Commit c150bc3

Browse files
authored
Merge pull request #43 from ioaiaaii/feat/fontend-otel-instrumentation
OpenTelemetry Instrumentation
2 parents 1b69149 + d329574 commit c150bc3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4850
-321
lines changed

Makefile

+13
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,16 @@ conventional-changelog: conventional-commit-lint
7878
# Optional target: create a GitHub release with the changelog
7979
conventional-changelog-release:
8080
@docker run -v "$$PWD":/workdir quay.io/git-chglog/git-chglog --config $(CONVENTIONAL_CHANGELOG)/release-config.yml ${TAG}
81+
82+
83+
otel-dev:
84+
@echo "Starting OpenTelemetry Collector..."
85+
@docker run \
86+
-v $(PWD)/build/ci/.otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml \
87+
-p 1888:1888 \
88+
-p 8888:8888 \
89+
-p 8889:8889 \
90+
-p 13133:13133 \
91+
-p 4317:4317 \
92+
-p 4318:4318 \
93+
otel/opentelemetry-collector-contrib

README.md

+36
Original file line numberDiff line numberDiff line change
@@ -1 +1,37 @@
11
# ioaiaaii.net
2+
3+
So, this is an over-engineered website :).
4+
Wanted to gather my work from here and there and to maintain o project for experiments.
5+
This is a BFF, with Vue.js and Go backend, engineered using Clean Architecture and adhering to SRE principles.
6+
7+
## System Design and API Spec
8+
9+
![Architecture](./docs/desing/diagram.svg)
10+
11+
## ADRs
12+
13+
## Release Engineering
14+
TBD
15+
### Repo Operator
16+
TBD
17+
### Build
18+
TBD
19+
- distroless
20+
- kaniko
21+
- hermetic
22+
23+
### CI
24+
25+
TBD
26+
- GH Workflows caching
27+
- resuable
28+
29+
### Releasing
30+
TBD
31+
### Packaging
32+
TBD
33+
## Observability
34+
TBD
35+
36+
## Profiling and Capacity Planning
37+
TBD

build/ci/.otel-collector-config.yaml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
receivers:
2+
otlp:
3+
protocols:
4+
grpc:
5+
endpoint: "0.0.0.0:4317"
6+
http:
7+
endpoint: "0.0.0.0:4318"
8+
cors:
9+
allowed_origins:
10+
- "http://localhost:3000"
11+
allowed_headers:
12+
- "Content-Type"
13+
- "X-Opentelemetry-Agent"
14+
- "User-Agent"
15+
max_age: 7200
16+
17+
processors:
18+
batch: {}
19+
20+
exporters:
21+
prometheus:
22+
endpoint: "0.0.0.0:8889"
23+
otlp:
24+
endpoint: "0.0.0.0:4317"
25+
tls:
26+
insecure: true
27+
debug:
28+
verbosity: detailed
29+
30+
service:
31+
pipelines:
32+
metrics:
33+
receivers: [otlp]
34+
processors: [batch]
35+
exporters: [debug]
36+
traces:
37+
receivers: [otlp]
38+
processors: [batch]
39+
exporters: [debug]
40+
logs:
41+
receivers: [otlp]
42+
processors: [batch]
43+
exporters: [debug]
44+
telemetry:
45+
logs:
46+
level: debug
47+

cmd/ioaiaaii.net/main.go

+58-12
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,86 @@
11
package main
22

33
import (
4-
"log"
4+
"context"
5+
"log/slog"
6+
"os"
7+
"os/signal"
8+
"sync"
9+
"syscall"
510
"time"
611

12+
"ioaiaaii.net/config"
713
"ioaiaaii.net/internal/controller/httpcontroller"
814
"ioaiaaii.net/internal/infrastructure/cache"
915
"ioaiaaii.net/internal/infrastructure/persistence/storage"
16+
"ioaiaaii.net/internal/infrastructure/telemetry"
1017
"ioaiaaii.net/internal/infrastructure/transport/httpserver"
1118
"ioaiaaii.net/internal/usecase/content"
1219
"ioaiaaii.net/website/data"
1320
)
1421

1522
func main() {
23+
slog.Info("Application starting...")
24+
// Load configuration
25+
cfg := config.LoadConfig()
1626

17-
var dataConf data.Config
27+
// Create a signal-aware context for graceful shutdown
28+
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
29+
defer cancel()
1830

19-
err := dataConf.LoadData()
31+
// Initialize OpenTelemetry SDK for metrics
32+
slog.Info("Initializing OpenTelemetry...")
33+
shutdownTelemetry, err := telemetry.SetupOTelSDK(ctx, cfg.OtelConfig)
2034
if err != nil {
21-
log.Fatalf("Error loading data: %v", err)
35+
slog.ErrorContext(ctx, "Failed to initialize OpenTelemetry", "error", err.Error())
36+
os.Exit(1) // Exit immediately on critical error
2237
}
38+
defer func() {
39+
shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 10*time.Second)
40+
defer cancelShutdown()
2341

24-
// 2. Initialize the repository (file-based repository in this case)
42+
if err := shutdownTelemetry(shutdownCtx); err != nil {
43+
slog.ErrorContext(ctx, "Error during OpenTelemetry shutdown", "error", err.Error())
44+
} else {
45+
slog.Info("OpenTelemetry shut down gracefully.")
46+
}
47+
}()
48+
49+
// Step 5: Initialize data configuration
50+
slog.Info("Loading data configuration...")
51+
var dataConf data.Files
52+
if err := dataConf.LoadFiles(); err != nil {
53+
slog.ErrorContext(ctx, "Error loading data", "error", err.Error())
54+
os.Exit(1) // Exit immediately on critical error
55+
}
56+
57+
// Step 6: Initialize repository (file-based repository in this case)
58+
slog.Info("Initializing storage repository...")
2559
fetchedData := storage.NewFileContent(dataConf)
2660

27-
// Add in-memory caching layer with a 10-minute TTL
61+
// Step 7: Add in-memory caching layer with a 30-minute TTL
62+
slog.Info("Initializing in-memory cache...")
2863
cachedRepo := cache.NewInMemoryCacheRepository(fetchedData, 30*time.Minute)
2964

30-
// 3. Initialize the use case by injecting the repository
65+
// Step 8: Initialize use case by injecting repository
66+
slog.Info("Initializing content use case...")
3167
contentUsecase := content.NewContentUsecase(cachedRepo)
3268

33-
// 4. Initialize the HTTP handler (controller) by injecting the use case
69+
// Step 9: Initialize HTTP handler (controller) by injecting use case
70+
slog.Info("Initializing HTTP handler...")
3471
contentHandler := httpcontroller.NewContentHandler(contentUsecase)
3572

36-
// 5. Setup and start the HTTP server
37-
app := httpserver.SetupHTTPServer(contentHandler)
38-
// 6. Start the HTTP server
39-
log.Fatal(app.Listen(":8080"))
73+
// Step 10: Initialize WaitGroup for managing goroutines
74+
var wg sync.WaitGroup
75+
76+
// Step 11: Start the HTTP server with the context
77+
slog.Info("Starting HTTP server...")
78+
httpserver.StartHTTPServer(ctx, contentHandler, &wg, cfg.APIConfig)
79+
80+
// Step 12: Wait for all goroutines to finish
81+
slog.Info("Waiting for goroutines to finish...")
82+
wg.Wait()
83+
84+
// Step 13: Log application shutdown
85+
slog.Info("Application shut down gracefully.")
4086
}

config/config.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package config
2+
3+
import (
4+
"os"
5+
"strconv"
6+
"time"
7+
)
8+
9+
// Default configuration values
10+
const (
11+
DefaultApiHTTPPort = "8080"
12+
DefaultAPIEnv = "production"
13+
DefaultAPIHTTPServerTimeout = 30
14+
DefaultAPIHTTPServerShutdownTimeout = 5
15+
DefaultTelemetryServiceName = "ioaiaaii-api"
16+
DefaultTelemetryShutdownTimeout = 10
17+
)
18+
19+
// APIConfig holds configurations related to the HTTP API.
20+
type APIConfig struct {
21+
HTTPPort string
22+
HTTPServerTimeout time.Duration
23+
HTTPServerShutdownTimeout time.Duration
24+
StoreEndpoint string
25+
}
26+
27+
// OtelConfig holds configurations related to OpenTelemetry.
28+
type OtelConfig struct {
29+
EnableTelemetry bool // Whether to enable OTel metrics
30+
OtelEndpoint string // OTel exporter endpoint
31+
ServiceName string // Metric Service Name
32+
ShutdownTimeout int // Timeout for graceful shutdown (in seconds)
33+
}
34+
35+
// Config holds the consolidated application-wide configuration.
36+
type Config struct {
37+
APIConfig APIConfig
38+
OtelConfig OtelConfig
39+
}
40+
41+
// LoadConfig initializes the Config struct based on environment variables.
42+
func LoadConfig() Config {
43+
return Config{
44+
APIConfig: loadAPIConfig(),
45+
OtelConfig: loadOtelConfig(),
46+
}
47+
}
48+
49+
// loadAPIConfig initializes APIConfig based on environment variables.
50+
func loadAPIConfig() APIConfig {
51+
return APIConfig{
52+
HTTPPort: getEnv("API_HTTP_PORT", DefaultApiHTTPPort),
53+
HTTPServerTimeout: time.Duration(getEnvAsInt("API_HTTP_SERVER_TIMEOUT", DefaultAPIHTTPServerTimeout)) * time.Second,
54+
HTTPServerShutdownTimeout: time.Duration(getEnvAsInt("API_HTTP_SERVER_SHUTDOWN_TIMEOUT", DefaultAPIHTTPServerShutdownTimeout)) * time.Second,
55+
}
56+
}
57+
58+
// loadOtelConfig initializes OtelConfig based on environment variables.
59+
func loadOtelConfig() OtelConfig {
60+
otelEndpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
61+
enableTelemetry := os.Getenv("OTEL_ENABLED") == "true" // Enable telemetry if the endpoint is set
62+
63+
return OtelConfig{
64+
EnableTelemetry: enableTelemetry,
65+
OtelEndpoint: otelEndpoint,
66+
ServiceName: getEnv("OTEL_SERVICE_NAME", DefaultTelemetryServiceName),
67+
ShutdownTimeout: getEnvAsInt("OTEL_SHUTDOWN_TIMEOUT", DefaultTelemetryShutdownTimeout),
68+
}
69+
}
70+
71+
// getEnv retrieves the value of the environment variable or returns a default value.
72+
func getEnv(key, defaultValue string) string {
73+
if value, exists := os.LookupEnv(key); exists {
74+
return value
75+
}
76+
return defaultValue
77+
}
78+
79+
// getEnvAsInt retrieves an environment variable as an integer or returns a default value.
80+
func getEnvAsInt(key string, defaultValue int) int {
81+
if value, exists := os.LookupEnv(key); exists {
82+
if intValue, err := strconv.Atoi(value); err == nil {
83+
return intValue
84+
}
85+
}
86+
return defaultValue
87+
}

deploy/helm/ioaiaaii/Chart.lock

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
dependencies:
22
- name: common
33
repository: oci://registry-1.docker.io/bitnamicharts
4-
version: 2.26.0
5-
digest: sha256:5ff7837915aef0067bd32271ee2b10c990774c16c4b6fe0a7c5eb6e53530ce08
6-
generated: "2024-10-25T09:44:42.266314+03:00"
4+
version: 2.27.0
5+
digest: sha256:b711ab5874abf868a0c64353a790f17771758cee6f802acb9819be004c8460af
6+
generated: "2024-11-20T20:18:36.262178+02:00"

deploy/helm/ioaiaaii/Chart.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ annotations:
33
licenses: Apache-2.0
44
type: application
55
apiVersion: v2
6-
appVersion: v1.1.8
6+
appVersion: v1.1.9
77
dependencies:
88
- name: common
99
repository: oci://registry-1.docker.io/bitnamicharts
@@ -19,4 +19,4 @@ name: ioaiaaii
1919
sources:
2020
- https://github.com/ioaiaaii/ioaiaaii.net/tree/master/deploy
2121
- https://github.com/ioaiaaii/ioaiaaii.net/tree/master/build/package
22-
version: 1.0.1
22+
version: 1.2.0
-15.4 KB
Binary file not shown.
15.4 KB
Binary file not shown.

deploy/helm/ioaiaaii/templates/_helpers.tpl

+12
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@ Return the proper Docker Image Registry Secret Names
44
{{- define "ioaiaaii.imagePullSecrets" -}}
55
{{- include "common.images.renderPullSecrets" (dict "images" (list .Values.web.image) "context" $) -}}
66
{{- end -}}
7+
8+
9+
{{/*
10+
Return a list of telemetry environment variables based on .Values.telemetry settings
11+
*/}}
12+
{{- define "ioaiaaii.telemetry.env" -}}
13+
{{- if .Values.telemetry.enabled -}}
14+
- name: OTEL_ENABLED
15+
value: "true"
16+
{{- include "common.tplvalues.render" (dict "value" .Values.telemetry.otelEnvVars "context" $) | nindent 0 -}}
17+
{{- end -}}
18+
{{- end -}}

deploy/helm/ioaiaaii/templates/web/deployment.yaml

+8-1
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,17 @@ spec:
7272
{{- else if .Values.web.args }}
7373
args: {{- include "common.tplvalues.render" (dict "value" .Values.web.args "context" $) | nindent 12 }}
7474
{{- end }}
75+
{{- if or .Values.telemetry.enabled .Values.web.extraEnvVars }}
76+
env:
77+
{{- if .Values.telemetry.enabled }}
78+
# Include OpenTelemetry environment variables
79+
{{- include "ioaiaaii.telemetry.env" . | nindent 12 }}
80+
{{- end }}
7581
{{- if .Values.web.extraEnvVars }}
76-
env:
82+
# Include additional custom environment variables
7783
{{- include "common.tplvalues.render" (dict "value" .Values.web.extraEnvVars "context" $) | nindent 12 }}
7884
{{- end }}
85+
{{- end }}
7986
{{- if .Values.web.resources }}
8087
resources: {{- toYaml .Values.web.resources | nindent 12 }}
8188
{{- else if ne .Values.web.resourcesPreset "none" }}

deploy/helm/ioaiaaii/values.yaml

+11-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ ingress:
2222
ingressClassName: nginx
2323
path: /
2424
annotations:
25-
cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer
25+
cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer
26+
# even if the usage would be extremely low
27+
nginx.ingress.kubernetes.io/limit-connections: "5" # Max concurrent connections per client IP
28+
nginx.ingress.kubernetes.io/limit-rps: "10" # Max requests per second
29+
nginx.ingress.kubernetes.io/limit-rate-after: "1000000" # Allow 1 MB at full speed
30+
nginx.ingress.kubernetes.io/limit-rate: "100" # Then throttle to 100 bytes/second
2631
tls:
2732
- hosts:
2833
- ioaiaaii.net
@@ -45,6 +50,11 @@ networkPolicy:
4550
ingressNSMatchLabels: {}
4651
ingressNSPodMatchLabels: {}
4752

53+
telemetry:
54+
enabled: true
55+
otelEnvVars:
56+
- name: OTEL_EXPORTER_OTLP_ENDPOINT
57+
value: "http://otel-collector.observability.svc.cluster.local:4317"
4858
web:
4959
image:
5060
repository: micro-repo/ioaiaaii

0 commit comments

Comments
 (0)