Compare commits

...

14 Commits

Author SHA1 Message Date
Laurent Ulrich
a5a5854276 Tables are now updated only if status of host changers 2023-09-15 23:35:16 +02:00
b869e7442a race...ben j'enleve 2023-08-20 20:58:35 +02:00
72fd02704f correction erreur de build 2023-08-20 20:57:07 +02:00
dbbd7e5944 Test with shell executor 2023-08-20 20:50:23 +02:00
2b6019d465 not the right name 2023-08-20 12:00:03 +02:00
8ea021afb5 First test of gitlab CI/CD 2023-08-20 11:56:03 +02:00
6183d41d92 - Possibilité de rediriger le log vers syslog avec le flag -syslog (#1)
- Possibilité de régler le niveau de log (0,1,2) avec le flag -verbose (#2)
2023-08-18 08:08:40 +02:00
25126b2cdf Actualiser README.md 2023-08-17 21:10:04 +02:00
23d9a4c4ad Actualiser README.md 2023-08-17 21:09:18 +02:00
Laurent ULRICH
7aa75b1040 Added missing go.sum for AutoDevOps 2023-08-17 20:37:12 +02:00
b10c025860 gofmt 2023-08-17 16:14:13 +02:00
ceabd1d76b Fonctionne sur FreeBSD 2023-08-17 16:10:12 +02:00
Charlie Root
02546e58f2 modification du rc 2023-08-17 14:08:13 +00:00
1a514d3b82 added a first version of rc script for freebsd 2023-08-17 15:46:01 +02:00
5 changed files with 236 additions and 125 deletions

28
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,28 @@
# You can copy and paste this template into a new `.gitlab-ci.yml` file.
# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword.
#
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Go.gitlab-ci.yml
stages:
- test
- build
format:
stage: test
script:
- go fmt $(go list ./... | grep -v /vendor/)
- go vet $(go list ./... | grep -v /vendor/)
compile:
stage: build
script:
- mkdir -p mybinaries
- go build -o mybinaries ./...
artifacts:
paths:
- mybinaries

32
README.md Normal file
View File

@@ -0,0 +1,32 @@
HostChecker
===========
HostChecker est un outil très simpliste, qui teste la disponibilité de services distants (tcp, http, smtp) pour établir la disponibilité d'un serveur (host).
En fonction du résultat du test, il ajoute ou supprime le serveur (désigné par son IP) d'une table PF.
L'outil est écrit spécifiquement pour répondre à un besoin précis.
Sur des pare-feux réseau FreeBSD ou OpenBSD, PF (packet filter) permet de distribuer un traffic entrant vers un ou plusieurs serveurs `backends` en round-robin.
Si l'un de ces serveurs est indisponible, PF ne peut le détecter et continue de distribuer du traffic vers ce backend, générant donc des erreurs. Il existe plusieurs moyens pour pallier à ce problème, dont l'utilisation d'un proxy applicatif sur le pare-feu lui-même ou l'utilisation d'IP virtuelles redondées sur les serveurs backends.
HostChecker permet d'avoir une troisième solution, celle de gérer dynamiquement la liste des serveurs backends référencés dans une table PF.
Configuration
--------------
La configuration se fait dans un seul fichier YAML qui décrit un ou plusieurs groupes de serveurs, les méthodes de test et les tables PF a mettre à jour.
```
cluster-web:
tables:
- cluster-web-hosts
hosts:
- 10.10.1.10
- 10.10.1.11
check:
- type: http
- port: 80
- interval: 5
- timeout: 2
```

3
go.sum Normal file
View File

@@ -0,0 +1,3 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,17 +1,17 @@
/*
Copyright 2023 Laurent Ulrich (laurentu@gmail.com)
Copyright 2023 Laurent Ulrich (laurentu@gmail.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
@@ -29,6 +29,7 @@ import "errors"
import "net"
import "os/exec"
import "flag"
import "log/syslog"
type Check struct {
Type string `yaml:"type"`
@@ -47,13 +48,24 @@ type Group struct {
Check Check `yaml:"check"`
}
var verbose int
func main() {
if verbose > 0 {
log.Println("Starting")
}
confFileName := flag.String("f", "/usr/local/etc/hostchecker.yaml", "YAML configuration file")
flag.IntVar(&verbose, "verbose", 0, "Set logs verbosity")
useSyslog := flag.Bool("syslog", false, "Send logs to syslog")
flag.Parse()
if *useSyslog == true {
syslogWriter, err := syslog.New(syslog.LOG_ERR, "hostchecker")
if err != nil {
log.Fatal("Error opening syslog #", err)
}
log.SetOutput(syslogWriter)
}
var waitGroup sync.WaitGroup
var conf map[string]Group
@@ -72,7 +84,9 @@ func main() {
}
stopChannel := make(chan bool)
for name, group := range conf {
if verbose > 0 {
log.Println("Checking group", name, group)
}
waitGroup.Add(1)
go checkGroup(name, group, &waitGroup, stopChannel)
}
@@ -82,17 +96,23 @@ func main() {
signal.Notify(exit, os.Interrupt)
s := <-exit
if verbose > 1 {
log.Println("Received signal", s)
}
if verbose > 0 {
log.Println("main closing stopChannel")
}
close(stopChannel)
waitGroup.Wait()
}
func checkGroup(name string, group Group, waitGroup *sync.WaitGroup, stopChannel chan bool) {
channels := make(map[string]chan int)
statusPerHost := make(map[string]int)
for _, host := range group.Hosts {
channel := make(chan int, 1)
channels[host] = channel
statusPerHost[host] = -1
waitGroup.Add(1)
go checkHost(channels[host], name, host, group.Check, waitGroup, stopChannel)
}
@@ -100,19 +120,28 @@ func checkGroup(name string, group Group, waitGroup *sync.WaitGroup, stopChannel
for {
select {
case <-stopChannel:
if verbose > 0 {
log.Println("checkGroup", name, "stopChannel")
}
waitGroup.Done()
return
break
default:
for host, channel := range channels {
select {
case stop := <-stopChannel:
if verbose > 0 {
log.Println("checkGroup", name, "stopChannel", stop)
}
break
case status := <-channel:
if verbose > 1 {
log.Println("Status for ", host, "was", statusPerHost[host], "in group", name)
log.Println("Status for ", host, "is", status, "in group", name)
updateTables( host, status, group.Tables )
}
if statusPerHost[host] == -1 || statusPerHost[host] != status {
statusPerHost[host] = status
updateTables(host, status, group.Tables)
}
default:
time.Sleep(100 * time.Millisecond)
}
@@ -121,7 +150,7 @@ func checkGroup(name string, group Group, waitGroup *sync.WaitGroup, stopChannel
}
}
func updateTables( host string, status int, tables []string ) {
func updateTables(host string, status int, tables []string) {
for _, table := range tables {
op := "add"
if status != 0 {
@@ -142,7 +171,9 @@ func checkHost(status chan<- int, group string, host string, check Check, waitGr
for {
select {
case <-stopChannel:
if verbose > 0 {
log.Println("checkHost", host, "group", group, "stopChannel")
}
waitGroup.Done()
return
default:
@@ -160,19 +191,20 @@ func checkHost(status chan<- int, group string, host string, check Check, waitGr
}
if err != nil {
status <- 1
if verbose > 1 {
log.Println("checkHost", host, "group", group, "error", err)
}
} else {
status <- 0
}
}
time.Sleep(300*time.Millisecond)
time.Sleep(300 * time.Millisecond)
}
}
}
func CheckHTTP(host string, check Check) error {
client := &http.Client{
Timeout: time.Duration(check.Timeout) * time.Second,
}
@@ -194,7 +226,7 @@ func CheckHTTP(host string, check Check) error {
}
func CheckTCP(host string, check Check) error {
cnx, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, check.Port), time.Duration(check.Timeout) * time.Second)
cnx, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, check.Port), time.Duration(check.Timeout)*time.Second)
if err != nil {
return err
}
@@ -208,11 +240,15 @@ func CheckSMTP(host string, check Check) error {
func validateConfiguration(conf map[string]Group) error {
for name, group := range conf {
if verbose > 1 {
log.Println("Validating configuration", name)
}
if len(group.Tables) == 0 {
return errors.New(fmt.Sprintf("No tables in group %s", name))
}
if verbose > 1 {
log.Println("Hosts", group.Hosts)
}
if len(group.Hosts) == 0 {
return errors.New(fmt.Sprintf("No hosts in group %s", name))
}
@@ -249,7 +285,7 @@ func validateConfiguration(conf map[string]Group) error {
}
default:
return errors.New(fmt.Sprintf("Check type should be http, smtp or tcp in group %s",name))
return errors.New(fmt.Sprintf("Check type should be http, smtp or tcp in group %s", name))
}
if group.Check.Interval == 0 {
group.Check.Interval = 5

12
hostchecker.rc Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
. /etc/rc.subr
name=hostchecker
rcvar=hostchecker_enable
command="/usr/local/bin/${name}"
command_args="&"
load_rc_config $name
run_rc_command "$1"