2020-04-27 23:19:27 +02:00
package main
import (
"context"
"flag"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/remotewrite"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
"github.com/VictoriaMetrics/metrics"
)
var (
rulePath = flagutil . NewArray ( "rule" , ` Path to the file with alert rules .
Supports patterns . Flag can be specified multiple times .
Examples :
- rule / path / to / file . Path to a single file with alerting rules
- rule dir / * . yaml - rule / * . yaml . Relative path to all . yaml files in "dir" folder ,
absolute path to all . yaml files in root . ` )
validateTemplates = flag . Bool ( "rule.validateTemplates" , true , "Indicates to validate annotation and label templates" )
httpListenAddr = flag . String ( "httpListenAddr" , ":8880" , "Address to listen for http connections" )
2020-05-04 23:51:22 +02:00
datasourceURL = flag . String ( "datasource.url" , "" , "Victoria Metrics or VMSelect url. Required parameter." +
" E.g. http://127.0.0.1:8428" )
basicAuthUsername = flag . String ( "datasource.basicAuth.username" , "" , "Optional basic auth username for -datasource.url" )
basicAuthPassword = flag . String ( "datasource.basicAuth.password" , "" , "Optional basic auth password for -datasource.url" )
remoteWriteURL = flag . String ( "remotewrite.url" , "" , "Optional URL to Victoria Metrics or VMInsert where to persist alerts state" +
" in form of timeseries. E.g. http://127.0.0.1:8428" )
remoteWriteUsername = flag . String ( "remotewrite.basicAuth.username" , "" , "Optional basic auth username for -remotewrite.url" )
remoteWritePassword = flag . String ( "remotewrite.basicAuth.password" , "" , "Optional basic auth password for -remotewrite.url" )
remoteReadURL = flag . String ( "remoteread.url" , "" , "Optional URL to Victoria Metrics or VMSelect that will be used to restore alerts" +
" state. This configuration makes sense only if `vmalert` was configured with `remotewrite.url` before and has been successfully persisted its state." +
" E.g. http://127.0.0.1:8428" )
remoteReadUsername = flag . String ( "remoteread.basicAuth.username" , "" , "Optional basic auth username for -remoteread.url" )
remoteReadPassword = flag . String ( "remoteread.basicAuth.password" , "" , "Optional basic auth password for -remoteread.url" )
remoteReadLookBack = flag . Duration ( "remoteread.lookback" , time . Hour , "Lookback defines how far to look into past for alerts timeseries." +
" For example, if lookback=1h then range from now() to now()-1h will be scanned." )
evaluationInterval = flag . Duration ( "evaluationInterval" , time . Minute , "How often to evaluate the rules. Default 1m" )
2020-04-27 23:19:27 +02:00
notifierURL = flag . String ( "notifier.url" , "" , "Prometheus alertmanager URL. Required parameter. e.g. http://127.0.0.1:9093" )
externalURL = flag . String ( "external.url" , "" , "External URL is used as alert's source for sent alerts to the notifier" )
)
func main ( ) {
envflag . Parse ( )
buildinfo . Init ( )
logger . Init ( )
checkFlags ( )
ctx , cancel := context . WithCancel ( context . Background ( ) )
2020-04-27 23:33:55 +02:00
eu , err := getExternalURL ( * externalURL , * httpListenAddr , false )
2020-04-27 23:19:27 +02:00
if err != nil {
2020-05-10 18:58:17 +02:00
logger . Fatalf ( "can not get external url: %s " , err )
2020-04-27 23:19:27 +02:00
}
notifier . InitTemplateFunc ( eu )
2020-05-10 18:58:17 +02:00
manager := & manager {
groups : make ( map [ uint64 ] * Group ) ,
2020-04-27 23:19:27 +02:00
storage : datasource . NewVMStorage ( * datasourceURL , * basicAuthUsername , * basicAuthPassword , & http . Client { } ) ,
2020-05-10 18:58:17 +02:00
notifier : notifier . NewAlertManager ( * notifierURL , func ( group , alert string ) string {
return fmt . Sprintf ( "%s/api/v1/%s/%s/status" , eu , group , alert )
2020-04-27 23:19:27 +02:00
} , & http . Client { } ) ,
}
if * remoteWriteURL != "" {
c , err := remotewrite . NewClient ( ctx , remotewrite . Config {
Addr : * remoteWriteURL ,
FlushInterval : * evaluationInterval ,
2020-05-04 23:51:22 +02:00
BasicAuthUser : * remoteWriteUsername ,
BasicAuthPass : * remoteWritePassword ,
2020-04-27 23:19:27 +02:00
} )
if err != nil {
logger . Fatalf ( "failed to init remotewrite client: %s" , err )
}
2020-05-10 18:58:17 +02:00
manager . rw = c
2020-04-27 23:19:27 +02:00
}
2020-05-04 23:51:22 +02:00
if * remoteReadURL != "" {
2020-05-10 18:58:17 +02:00
manager . rr = datasource . NewVMStorage ( * remoteReadURL , * remoteReadUsername , * remoteReadPassword , & http . Client { } )
2020-05-04 23:51:22 +02:00
}
2020-05-10 18:58:17 +02:00
if err := manager . start ( ctx , * rulePath , * validateTemplates ) ; err != nil {
logger . Fatalf ( "failed to start: %s" , err )
}
2020-05-09 11:32:12 +02:00
2020-05-10 18:58:17 +02:00
go func ( ) {
// init reload metrics with positive values to improve alerting conditions
configSuccess . Set ( 1 )
configTimestamp . Set ( uint64 ( time . Now ( ) . UnixNano ( ) ) / 1e9 )
sigHup := procutil . NewSighupChan ( )
for {
<- sigHup
configReloads . Inc ( )
logger . Infof ( "SIGHUP received. Going to reload rules %q ..." , * rulePath )
if err := manager . update ( ctx , * rulePath , * validateTemplates , false ) ; err != nil {
configReloadErrors . Inc ( )
configSuccess . Set ( 0 )
logger . Errorf ( "error while reloading rules: %s" , err )
continue
}
configSuccess . Set ( 1 )
configTimestamp . Set ( uint64 ( time . Now ( ) . UnixNano ( ) ) / 1e9 )
logger . Infof ( "Rules reloaded successfully from %q" , * rulePath )
}
} ( )
2020-05-09 11:32:12 +02:00
2020-05-10 18:58:17 +02:00
rh := & requestHandler { m : manager }
2020-05-09 11:32:12 +02:00
go httpserver . Serve ( * httpListenAddr , ( rh ) . handler )
2020-04-27 23:19:27 +02:00
sig := procutil . WaitForSigterm ( )
logger . Infof ( "service received signal %s" , sig )
if err := httpserver . Stop ( * httpListenAddr ) ; err != nil {
logger . Fatalf ( "cannot stop the webservice: %s" , err )
}
cancel ( )
2020-05-10 18:58:17 +02:00
manager . close ( )
2020-04-27 23:19:27 +02:00
}
var (
2020-05-10 18:58:17 +02:00
configReloads = metrics . NewCounter ( ` vmalert_config_last_reload_total ` )
configReloadErrors = metrics . NewCounter ( ` vmalert_config_last_reload_errors_total ` )
configSuccess = metrics . NewCounter ( ` vmalert_config_last_reload_successful ` )
configTimestamp = metrics . NewCounter ( ` vmalert_config_last_reload_success_timestamp_seconds ` )
2020-04-27 23:19:27 +02:00
)
func getExternalURL ( externalURL , httpListenAddr string , isSecure bool ) ( * url . URL , error ) {
if externalURL != "" {
return url . Parse ( externalURL )
}
hname , err := os . Hostname ( )
if err != nil {
return nil , err
}
port := ""
if ipport := strings . Split ( httpListenAddr , ":" ) ; len ( ipport ) > 1 {
port = ":" + ipport [ 1 ]
}
schema := "http://"
if isSecure {
schema = "https://"
}
return url . Parse ( fmt . Sprintf ( "%s%s%s" , schema , hname , port ) )
}
func checkFlags ( ) {
if * notifierURL == "" {
flag . PrintDefaults ( )
logger . Fatalf ( "notifier.url is empty" )
}
if * datasourceURL == "" {
flag . PrintDefaults ( )
logger . Fatalf ( "datasource.url is empty" )
}
}