Monitorovanie je dôležitou súčasťou spustenia spoľahlivého softvéru, avšak mnohé tímy objavujú prerušenia až po tom, čo sa začnú zobrazovať sťažnosti používateľov.Predstavte si, že dostanete správu Slack o 2 hodine ráno, ktorá vám hovorí, že vaše rozhrania API sú vypnuté viac ako hodinu a nikto si ich nevšimol, kým sa zákazníci nezačali sťažovať. V tomto tutoriále vás budem viesť krokmi, ako vybudovať aplikáciu na monitorovanie stavu od začiatku.Na konci tohto článku budete mať systém, ktorý: Vyskúšajte svoje služby podľa plánu (HTTP, TCP, DNS a ďalšie) Detekuje výpadky a posiela upozornenia do rôznych komunikačných kanálov (Teams, Slack atď.) Sleduje incidenty s automatickým otvorením/zatvorením Exponuje metriky pre Prometheus a Grafana dosky Prejsť na Docker Pre túto aplikáciu budem používať Go, pretože je rýchly, kompiluje na jeden binárny pre podporu cez platformy a zaobchádza s concurrency, čo je dôležité pre aplikáciu, ktorá potrebuje súčasne monitorovať viaceré koncové body. Čo budujeme Budeme budovať aplikáciu Go "StatusD". číta konfig súbor, ktorý má zoznam služieb na monitorovanie, skúma ich a vytvára incidenty, požiarne upozornenia, keď sa niečo pokazí. Tech Stack Used: Golangová Postgresky Grafana (Prometheus pre metriku) Dockerová Nginxová Tu je architektúra na vysokej úrovni: ┌─────────────────────────────────────────────────────────────────┐ │ Docker Compose │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ Postgres │ │Prometheus│ │ Grafana │ │ Nginx │ │ │ │ DB │ │ (metrics)│ │(dashboard)│ │ (reverse proxy) │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │ │ │ │ │ │ │ │ └─────────────┴─────────────┴──────────────────┘ │ │ │ │ │ ┌─────────┴─────────┐ │ │ │ StatusD │ │ │ │ (our Go app) │ │ │ └─────────┬─────────┘ │ │ │ │ └──────────────────────────────┼──────────────────────────────────┘ │ ┌────────────────┼────────────────┐ ▼ ▼ ▼ ┌────────┐ ┌────────┐ ┌────────┐ │Service │ │Service │ │Service │ │ A │ │ B │ │ C │ └────────┘ └────────┘ └────────┘ Projektová štruktúra Predtým, než napíšeme kód, poďme pochopiť, ako sa kusy hodia dohromady. Nižšie je naša projektová štruktúra: status-monitor/ ├── cmd/statusd/ │ └── main.go # Application entry point ├── internal/ │ ├── models/ │ │ └── models.go # Data structures (Asset, Incident, etc.) │ ├── probe/ │ │ ├── probe.go # Probe registry │ │ └── http.go # HTTP probe implementation │ ├── scheduler/ │ │ └── scheduler.go # Worker pool and scheduling │ ├── alert/ │ │ └── engine.go # State machine and notifications │ ├── notifier/ │ │ └── teams.go # Teams/Slack integration │ ├── store/ │ │ └── postgres.go # Database layer │ ├── api/ │ │ └── handlers.go # REST API │ └── config/ │ └── manifest.go # Config loading ├── config/ │ ├── manifest.json # Services to monitor │ └── notifiers.json # Notification channels ├── migrations/ │ └── 001_init_schema.up.sql ├── docker-compose.yml ├── Dockerfile └── entrypoint.sh Základné dátové modely Tu budeme definovať naše „typy“, čo v podstate znamená, že budeme definovať, ako vyzerá „monitorovaná služba“. Budeme definovať štyri „typy“: Asset: Toto je služba, ktorú chceme monitorovať. ProbeResult: Čo sa stane, keď skontrolujeme majetok; odpoveď, latencia atď. Incident: Toto sleduje, keď sa niečo pokazí, t. j. keď ProbeResult vráti neočakávanú odpoveď (a keď sa služba obnoví). Oznámenie: Toto je upozornenie alebo správa odoslaná na definovaný komunikačný kanál, napr. Teams, Slack, e-mail atď. Definujte typy v kóde: // internal/models/models.go package models import "time" // Asset represents a monitored service type Asset struct { ID string `json:"id"` AssetType string `json:"assetType"` // http, tcp, dns, etc. Name string `json:"name"` Address string `json:"address"` IntervalSeconds int `json:"intervalSeconds"` TimeoutSeconds int `json:"timeoutSeconds"` ExpectedStatusCodes []int `json:"expectedStatusCodes,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // ProbeResult contains the outcome of a single health check type ProbeResult struct { AssetID string Timestamp time.Time Success bool LatencyMs int64 Code int // HTTP status code Message string // Error message if failed } // Incident tracks a service outage type Incident struct { ID string AssetID string StartedAt time.Time EndedAt *time.Time // nil if still open Severity string Summary string } // Notification is what we send to Slack/Teams type Notification struct { AssetID string AssetName string Event string // "DOWN", "RECOVERY", "UP" Timestamp time.Time Details string } Všimnite si Nie všetky koncové body vrátia 200, niektoré môžu vrátiť 204 alebo presmerovanie.To vám umožňuje definovať, čo "zdravé" znamená pre každú službu. ExpectedStatusCodes Databázová schéma Budeme na to používať PostgreSQL a tu je naša schéma: -- migrations/001_init_schema.up.sql CREATE TABLE IF NOT EXISTS assets ( id TEXT PRIMARY KEY, name TEXT NOT NULL, address TEXT NOT NULL, asset_type TEXT NOT NULL DEFAULT 'http', interval_seconds INTEGER DEFAULT 300, timeout_seconds INTEGER DEFAULT 5, expected_status_codes TEXT, metadata JSONB, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS probe_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), asset_id TEXT NOT NULL REFERENCES assets(id), timestamp TIMESTAMP WITH TIME ZONE NOT NULL, success BOOLEAN NOT NULL, latency_ms BIGINT NOT NULL, code INTEGER, message TEXT ); CREATE TABLE IF NOT EXISTS incidents ( id SERIAL PRIMARY KEY, asset_id TEXT NOT NULL REFERENCES assets(id), severity TEXT DEFAULT 'INITIAL', summary TEXT, started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ended_at TIMESTAMP ); -- Indexes for common queries CREATE INDEX IF NOT EXISTS idx_probe_events_asset_id_timestamp ON probe_events(asset_id, timestamp DESC); CREATE INDEX IF NOT EXISTS idx_incidents_asset_id ON incidents(asset_id); CREATE INDEX IF NOT EXISTS idx_incidents_ended_at ON incidents(ended_at); Kľúčový pohľad je na Tu indexujeme podľa aktíva a časovej pečiatky (v zostupnom poradí), čo nám umožňuje rýchlo vyhľadávať výsledky prieskumu služby. probe_events(asset_id, timestamp DESC) Vytvorenie skúšobného systému Chceme podporovať prieskum viacerých typov protokolov: HTTPS, TCP, DNS atď. Bez toho, aby sme museli písať zložité vyhlásenie o prepínaní. Najprv definujeme, ako vyzerá sonda: // internal/probe/probe.go package probe import ( "context" "fmt" "github.com/yourname/status/internal/models" ) // Probe defines the interface for checking service health type Probe interface { Probe(ctx context.Context, asset models.Asset) (models.ProbeResult, error) } // registry holds all probe types var registry = make(map[string]func() Probe) // Register adds a probe type to the registry func Register(assetType string, factory func() Probe) { registry[assetType] = factory } // GetProbe returns a probe for the given asset type func GetProbe(assetType string) (Probe, error) { factory, ok := registry[assetType] if !ok { return nil, fmt.Errorf("unknown asset type: %s", assetType) } return factory(), nil } Teraz implementujte HTTP sondu: // internal/probe/http.go package probe import ( "context" "io" "net/http" "time" "github.com/yourname/status/internal/models" ) func init() { Register("http", func() Probe { return &httpProbe{} }) } type httpProbe struct{} func (p *httpProbe) Probe(ctx context.Context, asset models.Asset) (models.ProbeResult, error) { result := models.ProbeResult{ AssetID: asset.ID, Timestamp: time.Now(), } client := &http.Client{ Timeout: time.Duration(asset.TimeoutSeconds) * time.Second, } req, err := http.NewRequestWithContext(ctx, http.MethodGet, asset.Address, nil) if err != nil { result.Success = false result.Message = err.Error() return result, err } start := time.Now() resp, err := client.Do(req) result.LatencyMs = time.Since(start).Milliseconds() if err != nil { result.Success = false result.Message = err.Error() return result, err } defer resp.Body.Close() // Read body (limit to 1MB) io.ReadAll(io.LimitReader(resp.Body, 1024*1024)) result.Code = resp.StatusCode // Check if status code is expected if len(asset.ExpectedStatusCodes) > 0 { for _, code := range asset.ExpectedStatusCodes { if code == resp.StatusCode { result.Success = true return result, nil } } result.Success = false result.Message = "unexpected status code" } else { result.Success = resp.StatusCode < 400 } return result, nil } Funkcia init() sa spustí automaticky, keď sa spustí aplikácia Go. To pridá HTTP sondy do registra bez zmeny kódu. Chcete pridať TCP sondy?Vytvoriť , implementovať rozhranie a zaregistrovať ho v . tcp.go init() Plánovanie a konkurencia Musíme preskúmať všetky naše aktíva podľa harmonogramu a na to budeme používať pracovný fond. Pracovný fond nám umožňuje spustiť viacero sond súčasne bez toho, aby sme pre každú službu vytvorili goroutine. // internal/scheduler/scheduler.go package scheduler import ( "context" "sync" "time" "github.com/yourname/status/internal/models" "github.com/yourname/status/internal/probe" ) type JobHandler func(result models.ProbeResult) type Scheduler struct { workers int jobs chan models.Asset tickers map[string]*time.Ticker handler JobHandler mu sync.Mutex done chan struct{} wg sync.WaitGroup } func NewScheduler(workerCount int, handler JobHandler) *Scheduler { return &Scheduler{ workers: workerCount, jobs: make(chan models.Asset, 100), tickers: make(map[string]*time.Ticker), handler: handler, done: make(chan struct{}), } } func (s *Scheduler) Start(ctx context.Context) { for i := 0; i < s.workers; i++ { s.wg.Add(1) go s.worker(ctx) } } func (s *Scheduler) ScheduleAssets(assets []models.Asset) error { s.mu.Lock() defer s.mu.Unlock() for _, asset := range assets { interval := time.Duration(asset.IntervalSeconds) * time.Second ticker := time.NewTicker(interval) s.tickers[asset.ID] = ticker s.wg.Add(1) go s.scheduleAsset(asset, ticker) } return nil } func (s *Scheduler) scheduleAsset(asset models.Asset, ticker *time.Ticker) { defer s.wg.Done() for { select { case <-s.done: ticker.Stop() return case <-ticker.C: s.jobs <- asset } } } func (s *Scheduler) worker(ctx context.Context) { defer s.wg.Done() for { select { case <-s.done: return case asset := <-s.jobs: p, err := probe.GetProbe(asset.AssetType) if err != nil { continue } result, _ := p.Probe(ctx, asset) s.handler(result) } } } func (s *Scheduler) Stop() { close(s.done) close(s.jobs) s.wg.Wait() } Každý majetok dostane svoj vlastný ticker goroutine, ktorý funguje iba harmonogramy. Keď je čas na kontrolu majetku, ticker posiela sondovú prácu do kanála. Existuje pevný počet pracovníkov goroutine, ktoré počúvajú kanál a vykonávajú skutočné sondovanie. Nevykonávame sondy priamo v goroutine tickerov, pretože sondy môžu blokovať, zatiaľ čo čakajú na odpoveď siete alebo časovanie. Napríklad pri 4 zamestnancoch a 100 aktívach sa v každom okamihu spustia iba 4 sondy, a to aj v prípade, že tickeri strieľajú súčasne. zabezpečiť, aby všetci zamestnanci boli čistí. sync.WaitGroup Detekcia incidentu: Štátny stroj Keď prieskum zlyhá, automaticky nepredpokladáme zlyhanie. Mohlo by to byť zlyhanie siete. Ak však zlyhá znova, vytvoríme incident. Keď sa obnoví, incident zatvoríme a oznamujeme. Toto je štátny stroj: hore → dole → hore. Ako vybudovať motor: // internal/alert/engine.go package alert import ( "context" "fmt" "sync" "time" "github.com/yourname/status/internal/models" "github.com/yourname/status/internal/store" ) type NotifierFunc func(ctx context.Context, notification models.Notification) error type AssetState struct { IsUp bool LastProbeTime time.Time OpenIncidentID string } type Engine struct { store store.Store notifiers map[string]NotifierFunc mu sync.RWMutex assetState map[string]AssetState } func NewEngine(store store.Store) *Engine { return &Engine{ store: store, notifiers: make(map[string]NotifierFunc), assetState: make(map[string]AssetState), } } func (e *Engine) RegisterNotifier(name string, fn NotifierFunc) { e.mu.Lock() defer e.mu.Unlock() e.notifiers[name] = fn } func (e *Engine) Process(ctx context.Context, result models.ProbeResult, asset models.Asset) error { e.mu.Lock() defer e.mu.Unlock() state := e.assetState[result.AssetID] state.LastProbeTime = result.Timestamp // State hasn't changed? Nothing to do. if state.IsUp == result.Success { e.assetState[result.AssetID] = state return nil } // Save probe event if err := e.store.SaveProbeEvent(ctx, result); err != nil { return err } if result.Success && !state.IsUp { // Recovery! return e.handleRecovery(ctx, asset, state) } else if !result.Success && state.IsUp { // Outage! return e.handleOutage(ctx, asset, state, result) } return nil } func (e *Engine) handleOutage(ctx context.Context, asset models.Asset, state AssetState, result models.ProbeResult) error { incidentID, err := e.store.CreateIncident(ctx, asset.ID, fmt.Sprintf("Service %s is down", asset.Name)) if err != nil { return err } state.IsUp = false state.OpenIncidentID = incidentID e.assetState[asset.ID] = state notification := models.Notification{ AssetID: asset.ID, AssetName: asset.Name, Event: "DOWN", Timestamp: result.Timestamp, Details: result.Message, } return e.sendNotifications(ctx, notification) } func (e *Engine) handleRecovery(ctx context.Context, asset models.Asset, state AssetState) error { if state.OpenIncidentID != "" { e.store.CloseIncident(ctx, state.OpenIncidentID) } state.IsUp = true state.OpenIncidentID = "" e.assetState[asset.ID] = state notification := models.Notification{ AssetID: asset.ID, AssetName: asset.Name, Event: "RECOVERY", Timestamp: time.Now(), Details: "Service has recovered", } return e.sendNotifications(ctx, notification) } func (e *Engine) sendNotifications(ctx context.Context, notification models.Notification) error { for name, notifier := range e.notifiers { if err := notifier(ctx, notification); err != nil { fmt.Printf("notifier %s failed: %v\n", name, err) } } return nil } Kľúčový pohľad: Sledujeme stav v pamäti pre rýchle vyhľadávania, ale pretrvávajú incidenty do databázy pre trvanlivosť. Ak proces reštartuje, môžeme obnoviť stav z otvorených incidentov. assetState Odosielanie oznámení V prípade, že sa niečo pokazí, ľudia potrebujú vedieť. Musíme poslať oznámenie na rôzne komunikačné kanály. Poďme definovať náš oznamovateľ tímov: // internal/notifier/teams.go package notifier import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" "github.com/yourname/status/internal/models" ) type TeamsNotifier struct { webhookURL string client *http.Client } func NewTeamsNotifier(webhookURL string) *TeamsNotifier { return &TeamsNotifier{ webhookURL: webhookURL, client: &http.Client{Timeout: 10 * time.Second}, } } func (t *TeamsNotifier) Notify(ctx context.Context, n models.Notification) error { emoji := "🟢" if n.Event == "DOWN" { emoji = "🔴" } card := map[string]interface{}{ "type": "message", "attachments": []map[string]interface{}{ { "contentType": "application/vnd.microsoft.card.adaptive", "content": map[string]interface{}{ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "type": "AdaptiveCard", "version": "1.4", "body": []map[string]interface{}{ { "type": "TextBlock", "text": fmt.Sprintf("%s %s - %s", emoji, n.AssetName, n.Event), "weight": "Bolder", "size": "Large", }, { "type": "FactSet", "facts": []map[string]interface{}{ {"title": "Service", "value": n.AssetName}, {"title": "Status", "value": n.Event}, {"title": "Time", "value": n.Timestamp.Format(time.RFC1123)}, {"title": "Details", "value": n.Details}, }, }, }, }, }, }, } body, _ := json.Marshal(card) req, _ := http.NewRequestWithContext(ctx, "POST", t.webhookURL, bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := t.client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 300 { return fmt.Errorf("Teams webhook returned %d", resp.StatusCode) } return nil } Tímy používajú adaptívne karty pre bohaté formátovanie.Môžete definovať rôzne notifikátory pre iné komunikačné kanály, napríklad Slack, Discord atď. Zvyšok ohňa Potrebujeme koncové body na vyhľadávanie stavu služieb, ktoré monitorujeme.Na tento účel budeme používať Chi, ktorý je ľahký smerovač, ktorý podporuje parametre trasy ako . /assets/{id} Poďme definovať apis: // internal/api/handlers.go package api import ( "encoding/json" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/yourname/status/internal/store" ) type Server struct { store store.Store mux *chi.Mux } func NewServer(s store.Store) *Server { srv := &Server{store: s, mux: chi.NewRouter()} srv.mux.Use(middleware.Logger) srv.mux.Use(middleware.Recoverer) srv.mux.Route("/api", func(r chi.Router) { r.Get("/health", srv.health) r.Get("/assets", srv.listAssets) r.Get("/assets/{id}/events", srv.getAssetEvents) r.Get("/incidents", srv.listIncidents) }) return srv } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.mux.ServeHTTP(w, r) } func (s *Server) health(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "healthy"}) } func (s *Server) listAssets(w http.ResponseWriter, r *http.Request) { assets, err := s.store.GetAssets(r.Context()) if err != nil { http.Error(w, err.Error(), 500) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(assets) } func (s *Server) getAssetEvents(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") events, _ := s.store.GetProbeEvents(r.Context(), id, 100) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(events) } func (s *Server) listIncidents(w http.ResponseWriter, r *http.Request) { incidents, _ := s.store.GetOpenIncidents(r.Context()) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(incidents) } Vyššie uvedený kód definuje malý server HTTP API, ktorý odhaľuje 4 koncové body iba na čítanie: GET /api/health - Zdravotná kontrola (je služba v prevádzke?) GET /api/assets - Zoznam všetkých monitorovaných služieb GET /api/assets/{id}/events - Získajte históriu prieskumu pre konkrétnu službu GET /api/incidents - Zoznam otvorených incidentov Dockerovanie aplikácie Dockerizácia aplikácie je pomerne rovnomerná, pretože Go kompiluje do jedného binárneho. Budeme používať viacstupňovú zostavu, aby sme udržali konečný obrázok malý: # Dockerfile FROM golang:1.24-alpine AS builder WORKDIR /app RUN apk add --no-cache git COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o statusd ./cmd/statusd/ FROM alpine:latest WORKDIR /app RUN apk --no-cache add ca-certificates COPY --from=builder /app/statusd . COPY entrypoint.sh . RUN chmod +x /app/entrypoint.sh EXPOSE 8080 ENTRYPOINT ["/app/entrypoint.sh"] Posledná fáza je len Alpine plus naše binárne - zvyčajne pod 20 MB. Skript vstupného bodu vytvára reťazec pripojenia databázy z environmentálnych premenných: #!/bin/sh # entrypoint.sh DB_HOST=${DB_HOST:-localhost} DB_PORT=${DB_PORT:-5432} DB_USER=${DB_USER:-status} DB_PASSWORD=${DB_PASSWORD:-status} DB_NAME=${DB_NAME:-status_db} DB_CONN_STRING="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}" exec ./statusd \ -manifest /app/config/manifest.json \ -notifiers /app/config/notifiers.json \ -db "$DB_CONN_STRING" \ -workers 4 \ -api-port 8080 Spoločnosť Docker Compose: Putting It All Together Jeden súbor, ktorý ich všetky upraví: # docker-compose.yml version: "3.8" services: postgres: image: postgres:15-alpine container_name: status_postgres environment: POSTGRES_USER: status POSTGRES_PASSWORD: changeme POSTGRES_DB: status_db volumes: - postgres_data:/var/lib/postgresql/data - ./migrations:/docker-entrypoint-initdb.d healthcheck: test: ["CMD-SHELL", "pg_isready -U status"] interval: 10s timeout: 5s retries: 5 networks: - status_network statusd: build: . container_name: status_app environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USER=status - DB_PASSWORD=changeme - DB_NAME=status_db volumes: - ./config:/app/config:ro depends_on: postgres: condition: service_healthy networks: - status_network prometheus: image: prom/prometheus:latest container_name: status_prometheus volumes: - ./docker/prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus networks: - status_network depends_on: - statusd grafana: image: grafana/grafana:latest container_name: status_grafana environment: GF_SECURITY_ADMIN_USER: admin GF_SECURITY_ADMIN_PASSWORD: admin volumes: - grafana_data:/var/lib/grafana networks: - status_network depends_on: - prometheus nginx: image: nginx:alpine container_name: status_nginx volumes: - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./docker/nginx/conf.d:/etc/nginx/conf.d:ro ports: - "80:80" depends_on: - statusd - grafana - prometheus networks: - status_network networks: status_network: driver: bridge volumes: postgres_data: prometheus_data: grafana_data: Niekoľko vecí, ktoré treba poznamenať: Zdravotná kontrola PostgreSQL: Služba statusd čaká, kým nebude Postgres skutočne pripravený, nie práve spustený. Config mount: My mount ./config ako iba na čítanie. Upravte manifest lokálne a bežiaci kontajner uvidí zmeny. Nginx: Presmeruje externú prevádzku na prístrojové dosky Grafana a Prometheus. Konfigurácia súborov Aplikácia číta dva súbory: a manifest.json notifiers.json The file lists the assets we want to monitor. Each asset needs an ID, a probe type, and an address. The controls how often we check (60 = once per minute). lets you define what "healthy" means. Some endpoints return 301 redirects or 204 No Content, and that's fine. manifest.json intervalSeconds expectedStatusCodes // config/manifest.json { "assets": [ { "id": "api-prod", "assetType": "http", "name": "Production API", "address": "https://api.example.com/health", "intervalSeconds": 60, "timeoutSeconds": 5, "expectedStatusCodes": [200], "metadata": { "env": "prod", "owner": "platform-team" } }, { "id": "web-prod", "assetType": "http", "name": "Production Website", "address": "https://www.example.com", "intervalSeconds": 120, "timeoutSeconds": 10, "expectedStatusCodes": [200, 301] } ] } The controls where to send alerts. You define notification channels (Teams, Slack), then set policies for which channels fire on which events. means you won't get spammed more than once every 5 minutes for the same issue. notifiers.json throttleSeconds: 300 // config/notifiers.json { "notifiers": { "teams": { "type": "teams", "webhookUrl": "https://outlook.office.com/webhook/your-webhook-url" } }, "notificationPolicy": { "onDown": ["teams"], "onRecovery": ["teams"], "throttleSeconds": 300, "repeatAlerts": false } } bežať to docker-compose up -d To je to. päť služieb spin up: PostgreSQL ukladá vaše dáta StatusD skúša vaše služby Prometheus zbiera metriky Grafana zobrazuje ovládacie panely (http://localhost:80) Nginx rotuje všetko Pozrite si logá: docker logs -f status_app Mali by ste vidieť: Loading assets manifest... Loaded 2 assets Loading notifiers config... Loaded 1 notifiers Connecting to database... Starting scheduler... [✓] Production API (api-prod): 45ms [✓] Production Website (web-prod): 120ms Zhrnutie Teraz máte monitorovací systém, ktorý: Prečítanie služieb z konfigurácie JSON Vyskúšajte ich v rozvrhu pomocou pracovného bazéna Detekuje prerušenia a vytvára incidenty Odosiela oznámenia do aplikácie Teams/Slack Zobraziť všetky metriky pre Prometheus Spustiť Docker s jedným príkazom Tento výukový program vám pomôže nasadiť fungujúci monitorovací systém. Avšak, tam je viac pod kapotou, ktoré sme prehľadali. Prerušovače obvodov zabraňujú kaskádovému zlyhaniu, keď služba klope Multi-tier eskalácie upozornenia manažérov, ak inžinier na volanie nereaguje Deduplikácia výstrahy zabraňuje výstražným búrkam Adaptívne sondové intervaly kontrolujú počas incidentov častejšie Hot-reload konfigurácia bez reštartu služby Výpočty SLA a sledovanie súladu