diff --git a/cmd/balancer.go b/cmd/balancer.go index 943bd94..b3043f6 100644 --- a/cmd/balancer.go +++ b/cmd/balancer.go @@ -20,51 +20,31 @@ package main import ( "errors" + "fmt" + "log" + "net" "net/netip" + "time" "github.com/davidcoles/cue" + "github.com/davidcoles/cue/mon" "github.com/davidcoles/xvs" ) type Client = *xvs.Client - -type tuple struct { - addr netip.Addr - port uint16 - prot uint8 -} - type Balancer struct { - Client *xvs.Client - ProbeFunc func(vip, rip, nat netip.Addr, check cue.Check) (bool, string) // see Probe() below -} - -// Only needed if you need to override the built in monitoring health checking mechanism -// Here we use it to run checks against the NAT address in the network namespace. -// ProbeFunc method in Balancer not needed if you don't require this functionality -func (b *Balancer) Probe(vip netip.Addr, rip netip.Addr, check cue.Check) (bool, string) { - - f := b.ProbeFunc - - if f == nil { - return false, "No probe function defined" - } - - nat, ok := b.Client.NATAddress(vip, rip) - - if !ok { - return false, "No NAT destination defined for " + vip.String() + "/" + rip.String() - } - - return f(vip, rip, nat, check) + NetNS *nns + Logger *logger + Client *xvs.Client } +// interface method called by the director when the load balancer needs to be reconfigured func (b *Balancer) Configure(services []cue.Service) error { target := map[tuple]cue.Service{} for _, s := range services { - target[tuple{addr: s.Address, port: s.Port, prot: s.Protocol}] = s + target[tuple{Address: s.Address, Port: s.Port, Protocol: s.Protocol}] = s for _, d := range s.Destinations { if s.Port != d.Port { @@ -75,7 +55,7 @@ func (b *Balancer) Configure(services []cue.Service) error { svcs, _ := b.Client.Services() for _, s := range svcs { - key := tuple{addr: s.Service.Address, port: s.Service.Port, prot: s.Service.Protocol} + key := tuple{Address: s.Service.Address, Port: s.Service.Port, Protocol: s.Service.Protocol} if _, wanted := target[key]; !wanted { b.Client.RemoveService(s.Service) } @@ -100,3 +80,182 @@ func (b *Balancer) Configure(services []cue.Service) error { return nil } + +// interface method alled by mon when a destination's heatlh status transistions up or down +func (b *Balancer) Notify(instance mon.Instance, state bool) { + if logger := b.Logger; logger != nil { + logger.NOTICE("notify", notifyLog(instance, state)) + } +} + +// interface method called by mon when a destination needs to be probed - find the NAT address and probe that via the netns +func (b *Balancer) Probe(_ *mon.Mon, instance mon.Instance, check mon.Check) (ok bool, diagnostic string) { + + vip := instance.Service.Address + rip := instance.Destination.Address + nat, ok := b.Client.NATAddress(vip, rip) + + if !ok { + diagnostic = "No NAT destination defined for " + vip.String() + "/" + rip.String() + } else { + ok, diagnostic = b.NetNS.Probe(nat, check) + } + + if b.Logger != nil { + b.Logger.DEBUG("probe", probeLog(instance, nat, check, ok, diagnostic)) + } + + return ok, diagnostic +} + +func (b *Balancer) Multicast(multicast string) { + go b.multicast_send(multicast) + go b.multicast_recv(multicast) +} + +const maxDatagramSize = 1500 + +func (b *Balancer) multicast_send(address string) { + + addr, err := net.ResolveUDPAddr("udp", address) + + if err != nil { + log.Fatal(err) + } + + conn, err := net.DialUDP("udp", nil, addr) + + if err != nil { + log.Fatal(err) + } + + conn.SetWriteBuffer(maxDatagramSize * 100) + + ticker := time.NewTicker(time.Millisecond * 10) + + var buff [maxDatagramSize]byte + + for { + select { + case <-ticker.C: + n := 0 + + read_flow: + f := b.Client.ReadFlow() + if len(f) > 0 { + buff[n] = uint8(len(f)) + + copy(buff[n+1:], f[:]) + n += 1 + len(f) + if n < maxDatagramSize-100 { + goto read_flow + } + } + + if n > 0 { + conn.Write(buff[:n]) + } + } + } +} + +func (b *Balancer) multicast_recv(address string) { + udp, err := net.ResolveUDPAddr("udp", address) + + if err != nil { + log.Fatal(err) + } + + s := []string{`|`, `/`, `-`, `\`} + var x int + + conn, err := net.ListenMulticastUDP("udp", nil, udp) + + conn.SetReadBuffer(maxDatagramSize * 1000) + + buff := make([]byte, maxDatagramSize) + + for { + nread, _, err := conn.ReadFromUDP(buff) + fmt.Print(s[x%4] + "\b") + x++ + if err == nil { + for n := 0; n+1 < nread; { + l := int(buff[n]) + o := n + 1 + n = o + l + if l > 0 && n <= nread { + b.Client.WriteFlow(buff[o:n]) + } + } + } + } +} + +func probeLog(instance mon.Instance, addr netip.Addr, check mon.Check, status bool, reason string) map[string]any { + + kv := map[string]any{ + "reason": reason, + "status": updown(status), + "proto": proto(instance.Service.Protocol), + "saddr": instance.Service.Address.String(), + "sport": instance.Service.Port, + "daddr": instance.Destination.Address.String(), + "dport": instance.Destination.Port, + "probe": check.Type, + "pport": check.Port, + "paddr": addr, + } + + switch check.Type { + case "dns": + if check.Method { + kv["method"] = "tcp" + } else { + kv["method"] = "udp" + } + case "http": + fallthrough + case "https": + if check.Method { + kv["method"] = "HEAD" + } else { + kv["method"] = "GET" + } + + if check.Host != "" { + kv["host"] = check.Host + } + + if check.Path != "" { + kv["path"] = check.Path + } + + if len(check.Expect) > 0 { + kv["expect"] = fmt.Sprintf("%v", check.Expect) + } + } + + return kv +} + +func notifyLog(instance mon.Instance, status bool) map[string]any { + return map[string]any{ + "status": updown(status), + "proto": proto(instance.Service.Protocol), + "saddr": instance.Service.Address.String(), + "sport": instance.Service.Port, + "daddr": instance.Destination.Address.String(), + "dport": instance.Destination.Port, + } +} + +func proto(p uint8) string { + switch p { + case TCP: + return "tcp" + case UDP: + return "udp" + } + return fmt.Sprintf("%d", p) +} diff --git a/cmd/config.go b/cmd/config.go index fffed63..d3429b3 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -133,8 +133,8 @@ func Load(file string) (*Config, error) { type ipport = IPPort type IPPort struct { - Addr netip.Addr - Port uint16 + Address netip.Addr + Port uint16 } func (i *ipport) MarshalJSON() ([]byte, error) { @@ -159,7 +159,7 @@ func (i *ipport) UnmarshalJSON(data []byte) error { } func (i ipport) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("%s:%d", i.Addr, i.Port)), nil + return []byte(fmt.Sprintf("%s:%d", i.Address, i.Port)), nil } func (i *ipport) UnmarshalText(data []byte) error { @@ -182,7 +182,7 @@ func (i *ipport) UnmarshalText(data []byte) error { return errors.New("Badly formed ip:port - IP: " + m[1]) } - i.Addr = ip + i.Address = ip if m[3] != "" { @@ -203,14 +203,15 @@ func (i *ipport) UnmarshalText(data []byte) error { /**********************************************************************/ +type tuple = Tuple type Tuple struct { - Addr netip.Addr + Address netip.Addr Port uint16 Protocol uint8 } func (i *Tuple) Compare(j *Tuple) (r int) { - if r = i.Addr.Compare(j.Addr); r != 0 { + if r = i.Address.Compare(j.Address); r != 0 { return r } @@ -267,7 +268,7 @@ func (t Tuple) MarshalText() ([]byte, error) { return nil, errors.New("Invalid protocol") } - return []byte(fmt.Sprintf("%s:%d:%s", t.Addr, t.Port, p)), nil + return []byte(fmt.Sprintf("%s:%d:%s", t.Address, t.Port, p)), nil } func (t *Tuple) UnmarshalText(data []byte) error { @@ -292,7 +293,7 @@ func (t *Tuple) UnmarshalText(data []byte) error { return errors.New("Badly formed ip:port:protocol - IP " + text) } - t.Addr = ip + t.Address = ip port, err := strconv.Atoi(m[2]) if err != nil { @@ -359,7 +360,7 @@ func (c *Config) parse() []cue.Service { for ipp, svc := range c.Services { service := cue.Service{ - Address: ipp.Addr, + Address: ipp.Address, Port: ipp.Port, Protocol: ipp.Protocol, Required: svc.Need, @@ -369,7 +370,7 @@ func (c *Config) parse() []cue.Service { for ap, dst := range svc.Destinations { destination := cue.Destination{ - Address: ap.Addr, + Address: ap.Address, Port: ap.Port, Weight: dst.Weight, Disabled: dst.Disabled, diff --git a/cmd/config.pl b/cmd/config.pl index d650388..3018886 100755 --- a/cmd/config.pl +++ b/cmd/config.pl @@ -62,6 +62,10 @@ $json->{'logging'}->{'alert'}+=0; } +if(defined $json->{'logging'}) { + $json->{'logging'}->{'syslog'} = jsonbool($json->{'logging'}->{'syslog'}); +} + if(defined $json->{'defcon'}) { $json->{'defcon'}+=0; given($json->{'defcon'}) { diff --git a/cmd/go.mod b/cmd/go.mod index a5b0eba..bb190e8 100644 --- a/cmd/go.mod +++ b/cmd/go.mod @@ -3,7 +3,7 @@ module vc5 go 1.20 require ( - github.com/davidcoles/cue v0.0.4 + github.com/davidcoles/cue v0.0.7 github.com/davidcoles/xvs v0.1.9 github.com/elastic/go-elasticsearch/v7 v7.17.10 ) diff --git a/cmd/go.sum b/cmd/go.sum index de33de3..2b0e4cc 100644 --- a/cmd/go.sum +++ b/cmd/go.sum @@ -1,5 +1,5 @@ -github.com/davidcoles/cue v0.0.4 h1:DnDmr5PuKCypMk7e7pY+Nd0upxCz/Jjixp07/g7ulpg= -github.com/davidcoles/cue v0.0.4/go.mod h1:uUTkCvlkD+nqbmP9XyA63BGMW3g3JJRYC0q8vJKSXMc= +github.com/davidcoles/cue v0.0.7 h1:4flG/3DUEY3lx1yjDbxJglfKi///rG9MNcHznd3ZaH4= +github.com/davidcoles/cue v0.0.7/go.mod h1:uUTkCvlkD+nqbmP9XyA63BGMW3g3JJRYC0q8vJKSXMc= github.com/davidcoles/xvs v0.1.9 h1:fWDumZAuT7oikBJ2KVBEbWHiFPJQFtGf9ry+6Osl0Z8= github.com/davidcoles/xvs v0.1.9/go.mod h1:xSFQ+/pDWhsMyH0DuAJr92ujAdPnRMcqqBbY+BRkRoE= github.com/elastic/go-elasticsearch/v7 v7.17.10 h1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo= diff --git a/cmd/logger.go b/cmd/logger.go index 17b107c..477e02d 100644 --- a/cmd/logger.go +++ b/cmd/logger.go @@ -23,12 +23,15 @@ import ( "encoding/json" "fmt" "log" + "log/syslog" "net/http" "os" "sort" "strings" "sync" "time" + + "github.com/davidcoles/cue/bgp" ) const ( @@ -58,6 +61,7 @@ func (s secret) MarshalText() ([]byte, error) { return []byte("************"), n func (s *secret) String() string { return "************" } type logger struct { + Syslog bool `json:"syslog,omitempty"` Slack secret `json:"slack,omitempty"` Teams secret `json:"teams,omitempty"` Alert uint8 `json:"alert,omitempty"` @@ -68,12 +72,15 @@ type logger struct { count uint64 history []entry - slack chan string - teams chan string + slack chan string + teams chan string + syslog *syslog.Writer } type index = int64 +var syslogger *syslog.Writer + func init() { HOSTNAME, _ = os.Hostname() if HOSTNAME == "" { @@ -165,9 +172,13 @@ func (l *logger) log(lev uint8, f string, a ...any) { l.console(level(lev) + " " + f + " " + text) } + if l.Syslog { + l.logSyslog(lev, f+": "+text) + } + if lev <= l.Alert { - l.sendSlack(text) - l.sendTeams(text) + l.sendSlack(fmt.Sprintf("%s %s: %s", level(lev), f, text)) + l.sendTeams(fmt.Sprintf("%s %s: %s", level(lev), f, text)) } l.Elasticsearch.log(string(js), HOSTNAME) @@ -177,6 +188,45 @@ func (l *logger) log(lev uint8, f string, a ...any) { } } +func (l *logger) logSyslog(level uint8, text string) { + l.mutex.Lock() + defer l.mutex.Unlock() + + var err error + + if l.syslog == nil { + l.syslog, err = syslog.New(syslog.LOG_WARNING, "") + if err != nil { + log.Println("syslog:", err) + } + } + + if s := l.syslog; s != nil { + switch level { + case EMERG: + err = s.Emerg(text) + case ALERT: + err = s.Alert(text) + case CRIT: + err = s.Crit(text) + case ERR: + err = s.Err(text) + case WARNING: + err = s.Warning(text) + case NOTICE: + err = s.Notice(text) + case INFO: + err = s.Info(text) + case DEBUG: + err = s.Debug(text) + } + + if err != nil { + log.Println("syslog:", err) + } + } +} + func (l *logger) elasticAlert() bool { l.mutex.Lock() defer l.mutex.Unlock() @@ -327,4 +377,20 @@ func (l *sub) NOTICE(s string, a ...any) { l.log(NOTICE, s, a...) } func (l *sub) INFO(s string, a ...any) { l.log(INFO, s, a...) } func (l *sub) DEBUG(s string, a ...any) { l.log(DEBUG, s, a...) } +func (l *sub) BGPPeer(peer string, params bgp.Parameters, add bool) { + if add { + l.NOTICE("add", KV{"peer": peer}) + } else { + l.NOTICE("remove", KV{"peer": peer}) + } +} + +func (l *sub) BGPSession(peer string, local bool, reason string) { + if local { + l.NOTICE("local", KV{"peer": peer, "reason": reason}) + } else { + l.ERR("remote", KV{"peer": peer, "reason": reason}) + } +} + type Logger = *sub diff --git a/cmd/main.go b/cmd/main.go index 5340633..bce5b46 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -40,8 +40,6 @@ import ( "github.com/davidcoles/cue" "github.com/davidcoles/cue/bgp" - "github.com/davidcoles/cue/mon" - lb "github.com/davidcoles/xvs" ) @@ -170,11 +168,6 @@ func main() { log.Fatal(err) } - if config.Multicast != "" { - go multicast_send(client, config.Multicast) - go multicast_recv(client, config.Multicast) - } - pool := bgp.NewPool(address.As4(), config.BGP, nil, logs.sub("bgp")) if pool == nil { @@ -183,16 +176,18 @@ func main() { go spawn(logs, client.Namespace(), os.Args[0], "-s", socket.Name(), client.NamespaceAddress()) - af_unix := unix(socket.Name()) + balancer := &Balancer{ + NetNS: NetNS(socket.Name()), + Logger: logs, + Client: client, + } director := &cue.Director{ - Logger: logs.sub("director"), - Balancer: &Balancer{ - Client: client, - ProbeFunc: func(vip, rip, nat netip.Addr, check mon.Check) (bool, string) { - return probe(af_unix, vip, rip, nat, check, logs) - }, - }, + Balancer: balancer, + } + + if config.Multicast != "" { + balancer.Multicast(config.Multicast) } err = director.Start(config.parse()) @@ -478,7 +473,7 @@ func serviceStatus(config *Config, client Client, director *cue.Director, old ma xs := lb.Service{Address: svc.Address, Port: svc.Port, Protocol: lb.Protocol(svc.Protocol)} xse, _ := client.Service(xs) - t := Tuple{Addr: svc.Address, Port: svc.Port, Protocol: svc.Protocol} + t := Tuple{Address: svc.Address, Port: svc.Port, Protocol: svc.Protocol} cnf, _ := config.Services[t] available := svc.Available() diff --git a/cmd/xvs.go b/cmd/xvs.go index 48be573..fb9cc89 100644 --- a/cmd/xvs.go +++ b/cmd/xvs.go @@ -39,107 +39,78 @@ import ( // XVS specific routines -const maxDatagramSize = 1500 - -func multicast_send(client Client, address string) { - - addr, err := net.ResolveUDPAddr("udp", address) - - if err != nil { - log.Fatal(err) - } - - conn, err := net.DialUDP("udp", nil, addr) - - if err != nil { - log.Fatal(err) - } - - conn.SetWriteBuffer(maxDatagramSize * 100) - - ticker := time.NewTicker(time.Millisecond * 10) +type query struct { + Address string `json:"address"` + Check mon.Check `json:"check"` +} - var buff [maxDatagramSize]byte +type reply struct { + OK bool `json:"ok"` + Diagnostic string `json:"diagnostic"` +} +// spawn a server (specified by args) which runs in the network namespace - if it dies then restart it +func spawn(logs *logger, netns string, args ...string) { + F := "netns" for { - select { - case <-ticker.C: - n := 0 - - read_flow: - f := client.ReadFlow() - if len(f) > 0 { - buff[n] = uint8(len(f)) - - copy(buff[n+1:], f[:]) - n += 1 + len(f) - if n < maxDatagramSize-100 { - goto read_flow - } - } + logs.DEBUG(F, "Spawning daemon", args) + + cmd := exec.Command("ip", append([]string{"netns", "exec", netns}, args...)...) + _, _ = cmd.StdinPipe() + stderr, _ := cmd.StderrPipe() + stdout, _ := cmd.StdoutPipe() - if n > 0 { - conn.Write(buff[:n]) + reader := func(s string, fh io.ReadCloser) { + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + logs.WARNING(F, s, scanner.Text()) } } - } -} - -func multicast_recv(client Client, address string) { - udp, err := net.ResolveUDPAddr("udp", address) - - if err != nil { - log.Fatal(err) - } - s := []string{`|`, `/`, `-`, `\`} - var x int - - conn, err := net.ListenMulticastUDP("udp", nil, udp) - - conn.SetReadBuffer(maxDatagramSize * 1000) + go reader("stderr", stderr) - buff := make([]byte, maxDatagramSize) + if err := cmd.Start(); err != nil { + logs.ERR(F, "Daemon", err) + } else { + reader("stdout", stdout) - for { - nread, _, err := conn.ReadFromUDP(buff) - fmt.Print(s[x%4] + "\b") - x++ - if err == nil { - for n := 0; n+1 < nread; { - l := int(buff[n]) - o := n + 1 - n = o + l - if l > 0 && n <= nread { - client.WriteFlow(buff[o:n]) - } + if err := cmd.Wait(); err != nil { + logs.ERR(F, "Daemon", err) } } + + logs.ERR(F, "Daemon exited") + + time.Sleep(1 * time.Second) } } -type query struct { - Address string `json:"address"` - Check mon.Check `json:"check"` +type nns struct { + client *http.Client } -type reply struct { - OK bool `json:"ok"` - Diagnostic string `json:"diagnostic"` +func NetNS(socket string) *nns { + return &nns{ + client: &http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", socket) + }, + }, + }, + } } -func probe(client *http.Client, vip, rip, addr netip.Addr, check mon.Check, l *logger) (bool, string) { - - q := query{Address: addr.String(), Check: check} +func (n *nns) Probe(addr netip.Addr, check mon.Check) (bool, string) { buff := new(bytes.Buffer) - err := json.NewEncoder(buff).Encode(&q) + err := json.NewEncoder(buff).Encode(&query{Address: addr.String(), Check: check}) if err != nil { return false, "Internal error marshalling probe: " + err.Error() } - resp, err := client.Post("http://unix/probe", "application/octet-stream", buff) + resp, err := n.client.Post("http://unix/probe", "application/octet-stream", buff) if err != nil { return false, "Internal error contacting netns daemon: " + err.Error() @@ -161,99 +132,9 @@ func probe(client *http.Client, vip, rip, addr netip.Addr, check mon.Check, l *l return false, fmt.Sprintf("%d response: %s", resp.StatusCode, r.Diagnostic) } - type KV = map[string]any - //expect := fmt.Sprintf("%v", check.Expect) - //method := "" - - kv := KV{ - "event": "healthcheck", - "nat": addr.String(), - "vip": vip.String(), - "rip": rip.String(), - "port": check.Port, - "type": check.Type, - //"method": method, - //"host": check.Host, - //"path": check.Path, - //"expect": expect, - "status": updown(r.OK), - "diagnostic": r.Diagnostic, - } - - switch check.Type { - case "dns": - if check.Method { - kv["method"] = "tcp" - } else { - kv["method"] = "udp" - } - case "http": - fallthrough - case "https": - if check.Method { - kv["method"] = "GET" - } else { - kv["method"] = "HEAD" - } - - if check.Host != "" { - kv["host"] = check.Host - } - - if check.Path != "" { - kv["path"] = check.Path - } - - if len(check.Expect) > 0 { - kv["expect"] = fmt.Sprintf("%v", check.Expect) - } - } - - //kv["check"] = check - - if l != nil { - l.DEBUG("PROBER", kv) - } - return r.OK, r.Diagnostic } -// spawn a server (specified by args) which runs in the network namespace - if it dies then restart it -func spawn(logs *logger, netns string, args ...string) { - F := "netns" - for { - logs.DEBUG(F, "Spawning daemon", args) - - cmd := exec.Command("ip", append([]string{"netns", "exec", netns}, args...)...) - _, _ = cmd.StdinPipe() - stderr, _ := cmd.StderrPipe() - stdout, _ := cmd.StdoutPipe() - - reader := func(s string, fh io.ReadCloser) { - scanner := bufio.NewScanner(fh) - for scanner.Scan() { - logs.WARNING(F, s, scanner.Text()) - } - } - - go reader("stderr", stderr) - - if err := cmd.Start(); err != nil { - logs.ERR(F, "Daemon", err) - } else { - reader("stdout", stdout) - - if err := cmd.Wait(); err != nil { - logs.ERR(F, "Daemon", err) - } - } - - logs.ERR(F, "Daemon exited") - - time.Sleep(1 * time.Second) - } -} - // server to run in the network namespace - receive probes from unix socket, pass to the 'mon' object to execute func netns(socket string, addr netip.Addr) { @@ -268,7 +149,7 @@ func netns(socket string, addr netip.Addr) { } }() - monitor, err := mon.New(addr, nil, nil, nil) + monitor, err := mon.New(addr, nil, nil) if err != nil { log.Fatal(err) @@ -297,6 +178,7 @@ func netns(socket string, addr netip.Addr) { } var q query + var rep reply err = json.Unmarshal(body, &q) @@ -306,12 +188,13 @@ func netns(socket string, addr netip.Addr) { return } - rip := netip.MustParseAddr(q.Address) - vip := rip // fake the vip - NAT will take care of filling in the right address - - var rep reply + addr, err := netip.ParseAddr(q.Address) - rep.OK, rep.Diagnostic = monitor.Probe(vip, rip, q.Check) + if err == nil { + rep.OK, rep.Diagnostic = monitor.Probe(addr, q.Check) + } else { + rep.Diagnostic = "probe request: " + err.Error() + } js, err := json.Marshal(&rep) @@ -330,16 +213,6 @@ func netns(socket string, addr netip.Addr) { log.Fatal(server.Serve(s)) } -func unix(socket string) *http.Client { - return &http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socket) - }, - }, - } -} - func ethtool(i string) { exec.Command("ethtool", "-K", i, "rx", "off").Output() exec.Command("ethtool", "-K", i, "tx", "off").Output() diff --git a/doc/NOTES.md b/doc/NOTES.md index f9c38f6..1eb8099 100644 --- a/doc/NOTES.md +++ b/doc/NOTES.md @@ -40,3 +40,109 @@ using percpu hash - not using LACP: * More complete BGP4 implementation * BFD implementation (maybe no need for this with 3s hold time) +## Elasticsearch + +wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add - + +echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | \ + sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list + +apt update +apt install elasticsearch kibana + +cat >>/etc/elasticsearch/elasticsearch.yml < index patterns -> create index pattern +... +roles -> create role -> load-balancer : index perms on my-index-name +users -> create user -> load-balancer : roles->load-balancer + +edit logging params ... + +logging: + elasticsearch: + addresses: + - http://10.9.8.7:9200/ + # add more addresses if you have a cluster + index: my-index-name + username: load-balancer + password: Rarkensh1droj + + + +echo "deb [signed-by=/etc/apt/keyrings/elastic.gpg] https://artifacts.elastic.co/packages/7.x/apt stable main" >/etc/apt/sources.list.d/elastic-7.x.list + +cat >/etc/apt/keyrings/elastic.gpg <