diff --git a/cmd/caplance/cmd/client.go b/cmd/caplance/cmd/client.go index b4c3dde..f495708 100644 --- a/cmd/caplance/cmd/client.go +++ b/cmd/caplance/cmd/client.go @@ -1,10 +1,10 @@ package cmd import ( - "log" "net" "github.com/pwpon500/caplance/internal/client" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -17,16 +17,20 @@ var clientCmd = &cobra.Command{ Short: "Start caplance in client mode", Long: `Mark this host as a backend, allowing packets to be forwarded to it from the load balancer.`, Run: func(cmd *cobra.Command, args []string) { + log.Infoln("Reading in config file") readConfig() vip := net.ParseIP(conf.VIP) if vip == nil { - log.Fatal("Could not parse vip: " + conf.VIP) + log.Fatalln("Could not parse vip: " + conf.VIP) } dataIP := net.ParseIP(conf.Client.DataIP) if dataIP == nil { - log.Fatal("Could not parse data ip: " + conf.Client.DataIP) + log.Fatalln("Could not parse data ip: " + conf.Client.DataIP) } - c := client.NewClient(vip, dataIP) + if conf.Client.Name == "" { + log.Fatalln("Please provide a client name") + } + c := client.NewClient(conf.Client.Name, vip, dataIP, conf.ReadTimeout, conf.WriteTimeout, conf.HealthRate, conf.Sockaddr) connectIP := net.ParseIP(conf.Client.ConnectIP) if connectIP == nil { connectIP = net.ParseIP(conf.Server.MngIP) @@ -34,9 +38,10 @@ var clientCmd = &cobra.Command{ log.Fatalf("Could not parse Client.ConnectIP (%v) or Server.MngIP (%v)\n", conf.Client.ConnectIP, conf.Server.MngIP) } } + log.Infoln("Starting client") err := c.Start(connectIP) if err != nil { - log.Fatal("Failed to start with error: " + err.Error()) + log.Fatalln("Failed to start with error: " + err.Error()) } }, } diff --git a/cmd/caplance/cmd/root.go b/cmd/caplance/cmd/root.go index 4adb03c..d53d953 100644 --- a/cmd/caplance/cmd/root.go +++ b/cmd/caplance/cmd/root.go @@ -13,6 +13,7 @@ type config struct { Client struct { ConnectIP string DataIP string + Name string } Server struct { MngIP string @@ -21,10 +22,9 @@ type config struct { VIP string Test bool - HealthRate int - RegisterTimeout int - ReadTimeout int - WriteTimeout int + HealthRate int + ReadTimeout int + WriteTimeout int Sockaddr string } diff --git a/cmd/caplance/cmd/server.go b/cmd/caplance/cmd/server.go index 5c4fcac..ae4d389 100644 --- a/cmd/caplance/cmd/server.go +++ b/cmd/caplance/cmd/server.go @@ -1,11 +1,11 @@ package cmd import ( - "log" "net" "strconv" "github.com/pwpon500/caplance/internal/balancer" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -18,6 +18,7 @@ var serverCmd = &cobra.Command{ Short: "Start caplance in server mode", Long: `Mark this host as the load balancer, forwarding packets to a set of backends.`, Run: func(cmd *cobra.Command, args []string) { + log.Infoln("Reading in config file") readConfig() vip := net.ParseIP(conf.VIP) if vip == nil { @@ -30,10 +31,11 @@ var serverCmd = &cobra.Command{ if conf.Server.BackendCapacity <= 0 { log.Fatal("Backend capacity " + strconv.Itoa(conf.Server.BackendCapacity) + " must be postive.") } - b, err := balancer.New(vip, mngIP, conf.Server.BackendCapacity) + b, err := balancer.New(vip, mngIP, conf.Server.BackendCapacity, conf.ReadTimeout, conf.WriteTimeout) if err != nil { log.Fatal("Error when creating balancer: " + err.Error()) } + log.Infoln("Starting load balancer") b.Start() }, } diff --git a/go.mod b/go.mod index 478c8e5..1c93291 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/kkdai/maglev v0.0.0-20190512112251-2d79cc08016e github.com/kr/pty v1.1.4 // indirect github.com/mdlayher/raw v0.0.0-20190419142535-64193704e472 // indirect + github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v0.0.5 github.com/spf13/viper v1.3.2 github.com/stamblerre/gocode v0.0.0-20190327203809-810592086997 // indirect diff --git a/go.sum b/go.sum index 9f376e6..8e3c029 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,7 @@ github.com/kkdai/maglev v0.0.0-20170625132216-9074cd53582b h1:e8NwpJMRdbVdN+w4/D github.com/kkdai/maglev v0.0.0-20170625132216-9074cd53582b/go.mod h1:+19zs6rDZlKbJTe83xieLtHpvaLS7jtFQPH3JK0KoKs= github.com/kkdai/maglev v0.0.0-20190512112251-2d79cc08016e h1:qU5KjWxBHf+AXk8lAnWbzl+9TWg+o2KZYuvwOYWsPpU= github.com/kkdai/maglev v0.0.0-20190512112251-2d79cc08016e/go.mod h1:+19zs6rDZlKbJTe83xieLtHpvaLS7jtFQPH3JK0KoKs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -55,6 +56,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= @@ -69,6 +72,7 @@ github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stamblerre/gocode v0.0.0-20190327203809-810592086997 h1:LF81AGV63kJoxjmSgQPT8FARAMHeY46CYQ4TNoVDWHM= github.com/stamblerre/gocode v0.0.0-20190327203809-810592086997/go.mod h1:EM2T8YDoTCvGXbEpFHxarbpv7VE26QD1++Cb1Pbh7Gs= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -107,6 +111,7 @@ golang.org/x/sys v0.0.0-20190309122539-980fc434d28e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug= golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190516014833-cab07311ab81 h1:5Q88vZAfC0WB8T1GHRLttQaZdCNeQHM40n41gMUeFlI= diff --git a/internal/balancer/backends/manager.go b/internal/balancer/backends/manager.go index b6c620d..deb3ad8 100644 --- a/internal/balancer/backends/manager.go +++ b/internal/balancer/backends/manager.go @@ -1,20 +1,15 @@ package backends import ( - "log" "math/rand" "net" "regexp" "strconv" "strings" - "github.com/pwpon500/caplance/pkg/util" -) + log "github.com/sirupsen/logrus" -const ( - REGISTER_TIMEOUT = 10 - READ_TIMEOUT = 30 - WRITE_TIMEOUT = 5 + "github.com/pwpon500/caplance/pkg/util" ) type managedBackend struct { @@ -30,10 +25,12 @@ type Manager struct { listenPort int // port to listen on handler *Handler // handler for backends managedBackends map[string]*managedBackend // map of backend name to its communicator + readTimeout int + writeTimeout int } // NewManager instantiates a new instance of the Manager object -func NewManager(ip net.IP, port, capacity int) (*Manager, error) { +func NewManager(ip net.IP, port, capacity, readTimeout, writeTimeout int) (*Manager, error) { handler, err := NewHandler(capacity) if err != nil { @@ -44,7 +41,9 @@ func NewManager(ip net.IP, port, capacity int) (*Manager, error) { listenIP: ip, listenPort: port, handler: handler, - managedBackends: make(map[string]*managedBackend)}, nil + managedBackends: make(map[string]*managedBackend), + readTimeout: readTimeout, + writeTimeout: writeTimeout}, nil } // Listen listens for new connections, registering them if needed @@ -53,12 +52,12 @@ func (m *Manager) Listen() { var err error m.listener, err = net.Listen("tcp", m.listenIP.String()+":"+strconv.Itoa(m.listenPort)) if err != nil { - log.Panic(err) + log.Panicln(err) } for { conn, err := m.listener.Accept() if err != nil { - log.Println(err) + log.Debugln(err) } go m.attemptRegister(conn) } @@ -77,11 +76,11 @@ func (m *Manager) GetBackends() []*Backend { // registration message should be in the following format: // REGISTER func (m *Manager) attemptRegister(conn net.Conn) { - comm := util.NewTCPCommunicator(conn, READ_TIMEOUT, WRITE_TIMEOUT) + comm := util.NewTCPCommunicator(conn, m.readTimeout, m.writeTimeout) response, err := comm.ReadLine() if err != nil { - log.Println(err) + log.Debugln(err) conn.Close() return } @@ -97,7 +96,7 @@ func (m *Manager) attemptRegister(conn net.Conn) { cleaner, err := regexp.Compile("[^a-zA-Z0-9-_.]+") if err != nil { - log.Panic(err) + log.Panicln(err) } cleanedName := cleaner.ReplaceAllString(tokens[1], "") @@ -110,7 +109,7 @@ func (m *Manager) attemptRegister(conn net.Conn) { err = m.handler.Add(cleanedName, ip) if err != nil { - log.Println(err) + log.Infoln(err) conn.Close() return } @@ -124,7 +123,7 @@ func (m *Manager) attemptRegister(conn net.Conn) { comm.WriteLine("INVALID error while trying to read sanity check") conn.Close() m.handler.Remove(cleanedName) - log.Println("Error while trying to sanity check: " + err.Error()) + log.Infoln("Error while trying to sanity check: " + err.Error()) } sanityTokens := strings.Split(sanityResponse, " ") @@ -132,7 +131,7 @@ func (m *Manager) attemptRegister(conn net.Conn) { comm.WriteLine("INVALID bad sanity check url") conn.Close() m.handler.Remove(cleanedName) - log.Println("Client udp sanity check failed") + log.Infoln("Client udp sanity check failed") return } @@ -155,10 +154,10 @@ func (m *Manager) monitor(name string) { message, err := comm.ReadLine() if err != nil { if errChk, ok := err.(net.Error); ok && errChk.Timeout() { - log.Println(err) + log.Warnln(err) m.deregisterClient(name, "health check timeout ran out") } else { - log.Println(err) + log.Warnln(err) m.deregisterClient(name, "error reading from tcp connection: "+err.Error()) } return @@ -218,4 +217,5 @@ func (m *Manager) deregisterClient(name, reason string) { comm := m.managedBackends[name].comm comm.WriteLine("DEREGISTERED " + name + " " + reason) comm.Close() + log.Infoln("Deregistered " + name) } diff --git a/internal/balancer/control.go b/internal/balancer/control.go index 0037b86..2474df5 100644 --- a/internal/balancer/control.go +++ b/internal/balancer/control.go @@ -2,7 +2,6 @@ package balancer import ( "errors" - "log" "net" "os" "os/signal" @@ -13,6 +12,7 @@ import ( "github.com/AkihiroSuda/go-netfilter-queue" "github.com/coreos/go-iptables/iptables" "github.com/pwpon500/caplance/internal/balancer/backends" + log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" ) @@ -26,11 +26,13 @@ type Balancer struct { testFlag bool // flag to check if we're in test mode mux sync.Mutex // lock to ensure we don't start and stop at the same time nfq *netfilter.NFQueue // queue to grab packets from the iptables nfqueue + readTimeout int + writeTimeout int } // New creates new Balancer. Throws error if capacity is not prime -func New(startVIP, toConnect net.IP, capacity int) (*Balancer, error) { - manager, err := backends.NewManager(toConnect, 1338, capacity) +func New(startVIP, toConnect net.IP, capacity, readTimeout, writeTimeout int) (*Balancer, error) { + manager, err := backends.NewManager(toConnect, 1338, capacity, readTimeout, writeTimeout) if err != nil { return nil, err } @@ -41,12 +43,14 @@ func New(startVIP, toConnect net.IP, capacity int) (*Balancer, error) { connectIP: toConnect, packets: make(chan []byte, 100), stopChan: make(chan os.Signal, 5), - testFlag: false}, nil + testFlag: false, + readTimeout: readTimeout, + writeTimeout: writeTimeout}, nil } // NewTest creates new Balancer with the testing flag on -func NewTest(startVIP, toConnect net.IP, capacity int) (*Balancer, error) { - back, err := New(startVIP, toConnect, capacity) +func NewTest(startVIP, toConnect net.IP, capacity, readTimeout, writeTimeout int) (*Balancer, error) { + back, err := New(startVIP, toConnect, capacity, readTimeout, writeTimeout) if err != nil { return nil, err } @@ -99,13 +103,13 @@ func (b *Balancer) Start() error { ipt, err := iptables.New() if err != nil { - log.Println(err) + log.Errorln(err) } ipt.Delete("filter", "INPUT", "-j", "NFQUEUE", "--queue-num", "0", "-d", b.vip.String(), "-p", "tcp") ipt.Delete("filter", "INPUT", "-j", "NFQUEUE", "--queue-num", "0", "-d", b.vip.String(), "-p", "udp") if graceful && !b.testFlag { - log.Println("Exiting") + log.Infoln("Exiting") os.Exit(0) } @@ -113,7 +117,7 @@ func (b *Balancer) Start() error { }() sig := <-b.stopChan graceful = true - log.Printf("caught sig: %+v \n", sig) + log.Warnf("caught sig: %+v \n", sig) b.stopChan <- sig }() diff --git a/internal/client/control.go b/internal/client/control.go index 6b16121..f8d3cd0 100644 --- a/internal/client/control.go +++ b/internal/client/control.go @@ -2,7 +2,6 @@ package client import ( "errors" - "log" "net" "os" "os/signal" @@ -12,19 +11,14 @@ import ( "syscall" "time" + log "github.com/sirupsen/logrus" + "github.com/pwpon500/caplance/pkg/util" ) // HealthState represents the current state of the client type HealthState int -const ( - REGISTER_TIMEOUT = 10 - READ_TIMEOUT = 20 - WRITE_TIMEOUT = 5 - SOCKADDR = "/var/sock/caplance.sock" -) - const ( // Unregistered represents the client state before registration Unregistered HealthState = 0 @@ -49,6 +43,10 @@ type Client struct { packets chan *rawPacket // channel of packets to process stopChan chan os.Signal // channel to capture SIGTERM and SIGINT for graceful stop unixSock net.Listener // unix sock for communicating with caplancectl + readTimeout int + writeTimeout int + healthRate int + sockaddr string } // struct to hold an individual data packet recieved from lb @@ -58,13 +56,18 @@ type rawPacket struct { } // NewClient creates a new Client object -func NewClient(vip, dataIP net.IP) *Client { +func NewClient(name string, vip, dataIP net.IP, readTimeout, writeTimeout, healthRate int, sockaddr string) *Client { return &Client{ - dataIP: dataIP, - vip: vip, - state: Unregistered, - packets: make(chan *rawPacket, 100), - stopChan: make(chan os.Signal, 5)} + dataIP: dataIP, + vip: vip, + state: Unregistered, + name: name, + packets: make(chan *rawPacket, 100), + stopChan: make(chan os.Signal, 5), + readTimeout: readTimeout, + writeTimeout: writeTimeout, + healthRate: healthRate, + sockaddr: sockaddr} } // Start attempts to register and listen for connections @@ -74,7 +77,7 @@ func (c *Client) Start(connectIP net.IP) error { if err != nil { return err } - c.comm = util.NewTCPCommunicator(conn, READ_TIMEOUT, WRITE_TIMEOUT) + c.comm = util.NewTCPCommunicator(conn, c.readTimeout, c.writeTimeout) ender := func() { c.comm.Close() } @@ -103,7 +106,7 @@ func (c *Client) Start(connectIP net.IP) error { sanityString := "" buf := make([]byte, mtu) - sanityFailTime := time.Now().Add(READ_TIMEOUT * time.Second) + sanityFailTime := time.Now().Add(time.Duration(c.readTimeout) * time.Second) for !strings.HasPrefix(sanityString, "SANITY") && !time.Now().After(sanityFailTime) { n, _, err := c.dataListener.ReadFrom(buf) if err != nil { @@ -116,7 +119,7 @@ func (c *Client) Start(connectIP net.IP) error { sanitySplit := strings.Split(sanityString, " ") if sanitySplit[0] != "SANITY" || len(sanitySplit) < 2 { ender() - return errors.New("failed to complete sanity check in " + strconv.Itoa(READ_TIMEOUT) + " seconds.") + return errors.New("failed to complete sanity check in " + strconv.Itoa(c.readTimeout) + " seconds.") } c.comm.WriteLine("SANE " + sanitySplit[1]) @@ -146,7 +149,7 @@ func (c *Client) Start(connectIP net.IP) error { go func() { defer c.gracefulStop() sig := <-c.stopChan - log.Printf("caught sig: %+v \n", sig) + log.Infof("caught sig: %+v \n", sig) c.stopChan <- sig }() diff --git a/internal/client/networking.go b/internal/client/networking.go index 843d6c8..5d6fe8a 100644 --- a/internal/client/networking.go +++ b/internal/client/networking.go @@ -2,7 +2,6 @@ package client import ( "errors" - "log" "net" "os" "strings" @@ -10,14 +9,12 @@ import ( "syscall" "time" + log "github.com/sirupsen/logrus" + "github.com/google/gopacket/pcap" "github.com/vishvananda/netlink" ) -const ( - HEALTH_RATE = 10 -) - func findDevice(ip net.IP) (string, error) { devices, err := pcap.FindAllDevs() if err != nil { @@ -56,24 +53,24 @@ func initPacketPool(size int) *sync.Pool { func (c *Client) manageBalancerConnection(wg *sync.WaitGroup) { defer wg.Done() go c.sendHealth() - defer log.Println("ended manage balancer") + defer log.Debugln("ended balancer connection management") for c.state == Active || c.state == Paused { message, err := c.comm.ReadLine() if err != nil { - log.Println("Read timeout exceeded. Stopping") + log.Errorln("Read timeout exceeded. Stopping") c.gracefulStop() return } tokens := strings.Split(message, " ") if len(tokens) < 1 { - log.Println("Empty message received from server") + log.Debugln("Empty message received from server") continue } switch tokens[0] { case "INVALID": - log.Println(message) + log.Debugln(message) case "DEREGISTERED": c.state = Deregistering @@ -88,10 +85,10 @@ func (c *Client) manageBalancerConnection(wg *sync.WaitGroup) { case "HEALTHACK": if len(tokens) < 2 { - log.Println("HEALTHACK received from server with no status code") + log.Debugln("HEALTHACK received from server with no status code") } default: - log.Println("Message received from server not matching spec: " + message) + log.Debugln("Message received from server not matching spec: " + message) } } @@ -99,9 +96,9 @@ func (c *Client) manageBalancerConnection(wg *sync.WaitGroup) { func (c *Client) sendHealth() { for c.state == Active || c.state == Paused { - log.Println("sending health") + log.Debugln("sending health") c.comm.WriteLine("HEALTH 200") - time.Sleep(HEALTH_RATE * time.Second) + time.Sleep(time.Duration(c.healthRate) * time.Second) } } @@ -185,7 +182,7 @@ func (c *Client) handlePackets(pool *sync.Pool) { packet := <-c.packets err := syscall.Sendto(fd, packet.payload[:packet.size], 0, &addr) if err != nil { - log.Println("Failed to write packet to local vip") + log.Warnln("Failed to write packet to local vip") } pool.Put(packet) @@ -205,7 +202,7 @@ func (c *Client) gracefulStop() { c.dataListener.Close() c.detachVIP() if r := recover(); r != nil { - log.Println(r) + log.Errorln(r) } os.Exit(0) } diff --git a/internal/client/rpc.go b/internal/client/rpc.go index 1562b4c..07a15b4 100644 --- a/internal/client/rpc.go +++ b/internal/client/rpc.go @@ -10,12 +10,12 @@ import ( ) func (c *Client) listenUnix() { - if err := os.RemoveAll(SOCKADDR); err != nil { - log.Fatal(err) + if err := os.RemoveAll(c.sockaddr); err != nil { + log.Panicln(err) } var err error - c.unixSock, err = net.Listen("unix", SOCKADDR) + c.unixSock, err = net.Listen("unix", c.sockaddr) if err != nil { log.Panicln(err) } diff --git a/test/configs/simple_config.yaml b/test/configs/simple_config.yaml index 90afde2..6190c88 100644 --- a/test/configs/simple_config.yaml +++ b/test/configs/simple_config.yaml @@ -2,6 +2,7 @@ vip: 10.0.0.50 client: dataIP: 10.0.0.2 + name: backend-1 server: mngIP: 10.0.0.1