some work done
This commit is contained in:
parent
a735b9cc5e
commit
09fe1cd163
13 changed files with 174 additions and 45 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -10,6 +10,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.giftfish.de/ston1th/haproxy-lb/pkg/api/types"
|
||||||
|
serverv1 "git.giftfish.de/ston1th/haproxy-lb/pkg/api/v1/server"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
@ -30,6 +33,14 @@ type Server struct {
|
||||||
debug bool
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type notFoundHandler struct {
|
||||||
|
s *Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
types.NewContext(w, r, nf.s).NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
// NewHTTPServer returns a new HTTPServer
|
// NewHTTPServer returns a new HTTPServer
|
||||||
func NewServer(log logr.Logger) *Server {
|
func NewServer(log logr.Logger) *Server {
|
||||||
s := &Server{
|
s := &Server{
|
||||||
|
|
@ -46,7 +57,7 @@ func NewServer(log logr.Logger) *Server {
|
||||||
debug: core.C.Debug,
|
debug: core.C.Debug,
|
||||||
}
|
}
|
||||||
s.mux.NotFoundHandler = ¬FoundHandler{s}
|
s.mux.NotFoundHandler = ¬FoundHandler{s}
|
||||||
for _, v := range routes {
|
for _, v := range serverv1.Routes {
|
||||||
s.mux.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
|
s.mux.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
|
||||||
}
|
}
|
||||||
s.start()
|
s.start()
|
||||||
|
|
@ -73,9 +84,9 @@ func (s *Server) start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) contextWrapper(h ctxHandler) http.HandlerFunc {
|
func (s *Server) contextWrapper(h types.CtxHandler) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
h(newContext(w, r, s))
|
h(types.NewContext(w, r, s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,21 +1,22 @@
|
||||||
package server
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-logr/logr"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newContext(w http.ResponseWriter, r *http.Request, srv *Server) *Context {
|
func NewContext(w http.ResponseWriter, r *http.Request, log logr.Logger) *Context {
|
||||||
h := w.Header()
|
h := w.Header()
|
||||||
h.Set("Content-Type", "application/json")
|
h.Set("Content-Type", "application/json")
|
||||||
return &Context{
|
return &Context{
|
||||||
Status: http.StatusOK,
|
Status: http.StatusOK,
|
||||||
Request: r,
|
Request: r,
|
||||||
Response: w,
|
Response: w,
|
||||||
s: srv,
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +27,7 @@ type Context struct {
|
||||||
|
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
Response http.ResponseWriter
|
Response http.ResponseWriter
|
||||||
s *Server
|
log logr.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method returns the request method
|
// Method returns the request method
|
||||||
|
|
@ -57,7 +58,7 @@ func (c *Context) Var(name string) (ret string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) log() {
|
func (c *Context) log() {
|
||||||
c.s.log.Info("access",
|
c.log.Info("access",
|
||||||
"addr", c.Request.RemoteAddr,
|
"addr", c.Request.RemoteAddr,
|
||||||
"method", c.Request.Method,
|
"method", c.Request.Method,
|
||||||
"url", c.Request.URL,
|
"url", c.Request.URL,
|
||||||
|
|
@ -100,4 +101,4 @@ func (c *Context) HTTPErr(err string, status int) {
|
||||||
c.log()
|
c.log()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ctxHandler func(*Context)
|
type CtxHandler func(*Context)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package api
|
package types
|
||||||
|
|
||||||
type Route struct {
|
type Route struct {
|
||||||
Path string
|
Path string
|
||||||
|
|
@ -5,13 +5,9 @@ healthCheckNodePort
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type LBConfig struct {
|
|
||||||
LoadBalancers []LoadBalancer
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoadBalancer struct {
|
type LoadBalancer struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip,omitempty"`
|
||||||
Options Options `json:"options,omitempty"`
|
Options Options `json:"options,omitempty"`
|
||||||
Ports []Port `json:"ports,omitempty"`
|
Ports []Port `json:"ports,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,12 @@ package server
|
||||||
import (
|
import (
|
||||||
//"golang.org/x/crypto/bcrypt"
|
//"golang.org/x/crypto/bcrypt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.giftfish.de/ston1th/haproxy-lb/pkg/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type notFoundHandler struct {
|
func authHandler(h types.CtxHandler) types.CtxHandler {
|
||||||
s *Server
|
return func(ctx *types.Context) {
|
||||||
}
|
|
||||||
|
|
||||||
func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
newContext(w, r, nf.s).NotFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
func authHandler(h ctxHandler) ctxHandler {
|
|
||||||
return func(ctx *Context) {
|
|
||||||
// TODO
|
// TODO
|
||||||
// test:123456
|
// test:123456
|
||||||
// test:$2b$10$zNfGTAQ94vifj2wtGsv1W.yZRe4/rDBzQixpB6FbrTed4BnHuNqBS
|
// test:$2b$10$zNfGTAQ94vifj2wtGsv1W.yZRe4/rDBzQixpB6FbrTed4BnHuNqBS
|
||||||
|
|
@ -23,7 +17,7 @@ func authHandler(h ctxHandler) ctxHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func healthzHandler(ctx *Context) {
|
func healthzHandler(ctx *types.Context) {
|
||||||
// TODO maybe report etcd/raft stats
|
// TODO maybe report etcd/raft stats
|
||||||
ctx.OK()
|
ctx.OK()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.giftfish.de/ston1th/haproxy-lb/pkg/api"
|
"git.giftfish.de/ston1th/haproxy-lb/pkg/api/types"
|
||||||
schemav1 "git.giftfish.de/ston1th/haproxy-lb/pkg/api/v1/schema"
|
schemav1 "git.giftfish.de/ston1th/haproxy-lb/pkg/api/v1/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const v1 = "/" + schemav1.Version
|
const v1 = "/" + schemav1.Version
|
||||||
|
|
||||||
var routes = []api.Route{
|
var routes = []types.Route{
|
||||||
{
|
{
|
||||||
"/healthz",
|
"/healthz",
|
||||||
healthzHandler,
|
healthzHandler,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ type Cluster interface {
|
||||||
|
|
||||||
type KV interface {
|
type KV interface {
|
||||||
Get(k string) ([]byte, error)
|
Get(k string) ([]byte, error)
|
||||||
|
GetPrefix(k string) (map[string][]byte, error)
|
||||||
Set(k string, v []byte) error
|
Set(k string, v []byte) error
|
||||||
Delete(k string) error
|
Delete(k string) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,60 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.giftfish.de/ston1th/vipman/pkg/cluster"
|
"git.giftfish.de/ston1th/haproxy-kb/pkg/cluster"
|
||||||
"git.giftfish.de/ston1th/vipman/pkg/config"
|
"git.giftfish.de/ston1th/haproxy-kb/pkg/config"
|
||||||
"git.giftfish.de/ston1th/vipman/pkg/vip"
|
"git.giftfish.de/ston1th/haproxy-kb/pkg/haproxy"
|
||||||
|
"git.giftfish.de/ston1th/haproxy-kb/pkg/vip"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewVIPController(cfg *config.Config) (callbacks cluster.Callbacks, err error) {
|
type UpdateAction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Add UpdateAction = iota
|
||||||
|
Delete
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigUpdate struct {
|
||||||
|
Action UpdateAction
|
||||||
|
Config haproxy.LoadBalancer
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfigUpdates(c chan ConfigUpdate) (cu []ConfigUpdate) {
|
||||||
|
for len(c) > 0 {
|
||||||
|
select {
|
||||||
|
case config := <-c:
|
||||||
|
cu = append(cu, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyConfigUpdates(cc cluster.CallbackContext, cu []ConfigUpdate) error {
|
||||||
|
for _, c := range cu {
|
||||||
|
switch c.Action {
|
||||||
|
case Add:
|
||||||
|
addIPs(cc, n, []string{c.Config.IP})
|
||||||
|
cc.Set(c.Config.IP, nil)
|
||||||
|
case Delete:
|
||||||
|
cc.Delete(c.Config.IP)
|
||||||
|
deleteIPs(cc, n, []string{c.Config.IP})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLBController(cfg *config.Config) (callbacks cluster.Callbacks, err error) {
|
||||||
n, err := vip.NewNetworkWithLabel(cfg.Interface, cfg.Label)
|
n, err := vip.NewNetworkWithLabel(cfg.Interface, cfg.Label)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//TODO
|
||||||
|
ha, err := haproxy.NewHAProxyManager("")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configUpdate := make(chan ConfigUpdate, 100)
|
||||||
|
|
||||||
callbacks = cluster.Callbacks{
|
callbacks = cluster.Callbacks{
|
||||||
Leader: func(ctx context.Context, cc cluster.CallbackContext) {
|
Leader: func(ctx context.Context, cc cluster.CallbackContext) {
|
||||||
t := time.NewTicker(time.Second * 10)
|
t := time.NewTicker(time.Second * 10)
|
||||||
|
|
@ -29,7 +72,20 @@ func NewVIPController(cfg *config.Config) (callbacks cluster.Callbacks, err erro
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
cc.Info("leading", "id", cc.ID())
|
cc.Info("leading", "id", cc.ID())
|
||||||
|
cfg, err := cc.Get("config")
|
||||||
|
if err != nil {
|
||||||
|
cc.Error("error getting config key", err)
|
||||||
|
<-t.C
|
||||||
|
continue
|
||||||
|
}
|
||||||
addIPs(cc, n, cfg.VirtualIPs)
|
addIPs(cc, n, cfg.VirtualIPs)
|
||||||
|
var lbs haproxy.Config
|
||||||
|
err = ha.UpdateConfig(ctx, lbs)
|
||||||
|
if err != nil {
|
||||||
|
cc.Error("error updating haproxy config", err)
|
||||||
|
<-t.C
|
||||||
|
continue
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Stop()
|
t.Stop()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package etcd
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.giftfish.de/ston1th/vipman/pkg/cluster"
|
"git.giftfish.de/ston1th/vipman/pkg/cluster"
|
||||||
//"go.etcd.io/etcd/clientv3"
|
//"go.etcd.io/etcd/clientv3"
|
||||||
|
|
@ -52,6 +53,23 @@ func (c *Cluster) Get(k string) (v []byte, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cluster) GetPrefix(k string) (m map[string][]byte, err error) {
|
||||||
|
r, err := c.cli.Get(c.ctx, c.kvPrefix+"/"+k, clientv3.WithPrefix())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(r.Kvs) == 0 {
|
||||||
|
err = ErrKeyNotFound
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m = make(map[string][]byte)
|
||||||
|
for _, kv := range r.Kvs {
|
||||||
|
k := strings.TrimPrefix(c.kvPrefix+"/", string(kv.Key))
|
||||||
|
m[k] = kv.Value
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cluster) Set(k string, v []byte) (err error) {
|
func (c *Cluster) Set(k string, v []byte) (err error) {
|
||||||
_, err = c.cli.Put(c.ctx, c.kvPrefix+"/"+k, string(v))
|
_, err = c.cli.Put(c.ctx, c.kvPrefix+"/"+k, string(v))
|
||||||
return
|
return
|
||||||
|
|
|
||||||
29
pkg/haproxy/config.go
Normal file
29
pkg/haproxy/config.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package haproxy
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
LoadBalancers []LoadBalancer
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadBalancer struct {
|
||||||
|
Name string
|
||||||
|
IP string
|
||||||
|
Options Options
|
||||||
|
Ports []Port
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Frontend []string
|
||||||
|
Backend []string
|
||||||
|
DefaultServer string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Port struct {
|
||||||
|
Port int
|
||||||
|
Servers []Server
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Name string
|
||||||
|
IP string
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package haproxy
|
package haproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -18,6 +21,7 @@ type HAProxyManager struct {
|
||||||
configFile string
|
configFile string
|
||||||
serviceManager ServiceManager
|
serviceManager ServiceManager
|
||||||
template *template.Template
|
template *template.Template
|
||||||
|
hash []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHAProxyManager(configFile string) (*HAProxyManager, error) {
|
func NewHAProxyManager(configFile string) (*HAProxyManager, error) {
|
||||||
|
|
@ -32,26 +36,32 @@ func NewHAProxyManager(configFile string) (*HAProxyManager, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ha *HAProxyManager) checkConfig(ctx context.Context, configFile string) error {
|
func (ha *HAProxyManager) checkConfig(ctx context.Context, lbs Config) (hash []byte, err error) {
|
||||||
return exec.CommandContext(ctx, "haproxy", "-c", "-f", configFile).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ha *HAProxyManager) UpdateConfig(ctx context.Context, lbs []config.LoadBalancer) error {
|
|
||||||
ha.Lock()
|
|
||||||
defer ha.Unlock()
|
|
||||||
tmp, err := ioutil.TempFile(os.TempDir(), "haproxy_")
|
tmp, err := ioutil.TempFile(os.TempDir(), "haproxy_")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
defer tmp.Close()
|
defer tmp.Close()
|
||||||
defer os.Remove(tmp.Name())
|
defer os.Remove(tmp.Name())
|
||||||
err = ha.template.Execute(tmp, lbs)
|
h := sha256.New()
|
||||||
|
w := io.MultiWriter(tmp, h)
|
||||||
|
err = ha.template.Execute(w, lbs)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash = h.Sum(nil)
|
||||||
|
return hash, exec.CommandContext(ctx, "haproxy", "-c", "-f", tmp.Name()).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ha *HAProxyManager) UpdateConfig(ctx context.Context, lbs Config) error {
|
||||||
|
ha.Lock()
|
||||||
|
defer ha.Unlock()
|
||||||
|
hash, err := ha.checkConfig(ctx, lbs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ha.checkConfig(ctx, tmp.Name())
|
if bytes.Equal(ha.hash, hash) {
|
||||||
if err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
file, err := os.OpenFile(ha.configFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
file, err := os.OpenFile(ha.configFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -63,5 +73,6 @@ func (ha *HAProxyManager) UpdateConfig(ctx context.Context, lbs []config.LoadBal
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
file.Close()
|
file.Close()
|
||||||
|
ha.hash = hash
|
||||||
return ha.serviceManager.Reload()
|
return ha.serviceManager.Reload()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ defaults
|
||||||
timeout client 5000ms
|
timeout client 5000ms
|
||||||
timeout tunnel 3600s
|
timeout tunnel 3600s
|
||||||
timeout check 10s
|
timeout check 10s
|
||||||
{{ range $item := . -}}
|
{{ range $item := .LoadBalancers -}}
|
||||||
{{ range $port := $item.Ports -}}
|
{{ range $port := $item.Ports -}}
|
||||||
frontend fr_{{ $item.Name }}_{{ $port.Port }}
|
frontend fr_{{ $item.Name }}_{{ $port.Port }}
|
||||||
bind {{ $item.IP }}:{{ $port.Port }}
|
bind {{ $item.IP }}:{{ $port.Port }}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,18 @@ func (kv *kv) Get(k string) ([]byte, error) {
|
||||||
return kv.m[k], nil
|
return kv.m[k], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (kv *kv) GetPrefix(pk string) (m map[string][]byte, err error) {
|
||||||
|
kv.mu.Lock()
|
||||||
|
defer kv.mu.Unlock()
|
||||||
|
m = make(map[string][]byte)
|
||||||
|
for k, v := range kv.m {
|
||||||
|
if strings.HasPrefix(k, pk) {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (kv *kv) Set(k string, v []byte) error {
|
func (kv *kv) Set(k string, v []byte) error {
|
||||||
if kv.r.State() != raft.Leader {
|
if kv.r.State() != raft.Leader {
|
||||||
return ErrNotLeader
|
return ErrNotLeader
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue