baker

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 11, 2021 License: MIT Imports: 20 Imported by: 0

README

   ____        _                                
  | __ )  __ _| | _____ _ __      __ _  ___   
  |  _ \ / _  | |/ / _ \ '__|    / _  |/ _ \  
  | |_) | (_| |   <  __/ |   _  | (_| | (_) | 
  |____/ \__,_|_|\_\___|_|  (_)  \__, |\___/  
                                 |___/

Introduction

Baker.go is a dynamic http reverse proxy designed to be highly extensible. It has 4 major parts.

  • Watcher

listen to orchestration system and produce container object

// Watcher defines how driver should react
type Watcher interface {
	// Container gets the next available container
	// this is a blocking calls, if Container object is nil
	// it means that Watcher has been closed
	Container() (*Container, error)
}
  • Pinger

consumes container objects from Watcher and ping each container for health check and retrieve configuration and produce Service object

// Pinger defines how container can be ping to get extra information
// and construct Service object
type Pinger interface {
	// Service if service returns an nil value, it means that Pinger has
	// some internal error and it will no longer return service
	Service() (*Service, error)
}
  • Store

consumes service objects from Pinger and store them in highly optimize trie data structure for fast access

// Store will be used to get Container
type Store interface {
	// Query returns a service based on domain and path
	Query(domain, path string) *Service
}
  • Router

Router uses Store to query each service, extract recipes and proxy requests to each container. Router is http.Handler compatible.

Features

  • Include Docker driver to listen to Docker's events
  • Has exposed a driver interface which can be easily hook to other orchestration engines
  • Dynamic configuration, no need to restart reverse proxy in order to change the configuration
  • Uses only Standard Library
  • Uses a custom Trie data structure, to compute fast path pattern matching
  • It can be use as library as it has implemented http.Handler interface
  • Highly extendable as most of the components have exposed interfaces
  • Middleware like feature to change the incoming traffic, outgoing traffic and custom error handling
  • Round-Robin load balancing by default
  • Automatically updates and creates SSL certificates using Let's Encrypt

Usage

First we need to run Baker inside docker. The following docker-compose.yml

version: '3.5'

services:
  baker:
    image: alinz/baker.go:latest

    environment:
      # enables ACME system
      - BAKER_ACME=NO
      # folder location which holds all certification
      - BAKER_ACME_PATH=/acme/cert
      - BAKER_LOG_LEVEL=DEBUG

    ports:
      - '80:80'
      - '443:443'

    # make sure to use the right network
    networks:
      - baker

    volumes:
      # make sure it can access to main docker.sock
      - /var/run/docker.sock:/var/run/docker.sock
      - ./acme/cert:/acme/cert

networks:
  baker:
    name: baker_net
    driver: bridge

Then for each service, a following docker-compose can be used. The only requirements is labels and networks. Make sure both baker and service has the same network interface

version: '3.5'

services:
  service1:
    image: service:latest

    labels:
      - 'baker.enable=true'
      - 'baker.network=baker_net'
      - 'baker.service.port=8000'
      - 'baker.service.ping=/config'

    networks:
      - baker

networks:
  baker:
    external:
      name: baker_net    

The service, should expose a REST endpoint which returns a configuration, the configuration endpoint act as a health check and providing realtime configuration:

[
  {
    "domain": "example.com",
    "path": "/sample1",
    "ready": true
  },
  {
    "domain": "example.com",
    "path": "/sample2",
    "ready": false
  },
  {
    "domain": "example1.com",
    "path": "/sample1*",
    "ready": true,
    "recipes": [
      {
        "name": "ReplacePath",
        "config": {
          "search": "/sample1",
          "replace": "",
          "times": 1
        }
      }
    ]
  }
]

Custom Baker

Baker is very extensible and easy to build. If a new recipe needed to be added to Router, it can be done in few lines of code as describe in following code, or can be found in cmd/baker/main.go

package main

import (
	"net/http"

	"github.com/alinz/baker.go"
)

func main() {
  watcher := baker.NewDockerWatcher(baker.DefaultDockerWatcherConfig)
  pinger := baker.NewBasePinger(watcher)
  store := baker.NewBaseStore(pinger)
  router := baker.NewBaseRouter(store)

  // add all recipes and processors with a unique names here
  router.AddProcessor("ReplacePath", baker.CreateProcessorPathReplace)

  go watcher.Start()

  http.ListenAndServe(":80", router)
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound      = errors.New("not found")
	ErrWatcherClosed = errors.New("watcher closed")
	ErrPingerClosed  = errors.New("pinger closed")
)
View Source
var DefaultDockerWatcherConfig = DockerWatcherConfig{
	Host: "http://localhost",
	Client: &http.Client{
		Transport: &http.Transport{
			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
				return net.Dial("unix", "/var/run/docker.sock")
			},
		},
	},
	Size: 10,
}

Functions

This section is empty.

Types

type AcmeServer

type AcmeServer struct {
	// contains filtered or unexported fields
}

AcmeServer contains all logic to handle both on port http and https

func NewAcmeServer

func NewAcmeServer(policyManager PolicyManager, certPath string) *AcmeServer

NewAcmeServer creates acme.Server

func (*AcmeServer) Start

func (s *AcmeServer) Start(handler http.Handler) error

Start starts both http and https servers and initialize acme object NOTE: this methid is a blocking call, either run this as last statement or run it with a go command

type BasePinger

type BasePinger struct {
	// contains filtered or unexported fields
}

func NewBasePinger

func NewBasePinger(watcher Watcher) *BasePinger

func (*BasePinger) Service

func (p *BasePinger) Service() (*Service, error)

Service calls each available container and create a service object

type BaseRouter

type BaseRouter struct {
	// contains filtered or unexported fields
}

func NewBaseRouter

func NewBaseRouter(store Store) *BaseRouter

func (*BaseRouter) AddProcessor

func (r *BaseRouter) AddProcessor(name string, builder func(config json.RawMessage) (Processor, error)) *BaseRouter

func (*BaseRouter) HostPolicy

func (br *BaseRouter) HostPolicy(ctx context.Context, host string) error

func (*BaseRouter) ServeHTTP

func (br *BaseRouter) ServeHTTP(w http.ResponseWriter, r *http.Request)

type BaseStore

type BaseStore struct {
	// contains filtered or unexported fields
}

func NewBaseStore

func NewBaseStore(pinger Pinger) *BaseStore

NewBaseStore creates BaseStore object

func (*BaseStore) Query

func (br *BaseStore) Query(domain, path string) *Service

type Container

type Container struct {
	// Unique ID generated by container orcestration
	ID string
	// Type shows what type of container it is
	Type ContainerType
	// Active show wheather container is added or removed
	Active bool
	// RemoteAddr is IP:PORT which container is listening to
	RemoteAddr net.Addr
	// ConfigPath is the path to extract config data
	ConfigPath string
	// Err if error detected by orcestration system, it pass it here. In most case it means that Container is dead
	Err error
}

Container is piece of object which contains information about each Node or Container

func (Container) String

func (c Container) String() string

type ContainerType

type ContainerType string

ContainerType defines which watcher produce them

const (
	// DockerContainer defines by docker driver
	DockerContainer ContainerType = "docker"
)

type DockerWatcher

type DockerWatcher struct {
	// contains filtered or unexported fields
}

func NewDockerWatcher

func NewDockerWatcher(config DockerWatcherConfig) *DockerWatcher

func (*DockerWatcher) Container

func (d *DockerWatcher) Container() (*Container, error)

func (*DockerWatcher) Start

func (d *DockerWatcher) Start() error

Start is a blocking call, the caller for this method should run in a separate go routine

NOTE: upon on returning this function, the internal channel will be closed, if a restart requires, a new instance of DockerWatcher needs to be instantiated

type DockerWatcherConfig

type DockerWatcherConfig struct {
	Host   string
	Client *http.Client
	Size   int
}

type Domains

type Domains struct {
	// contains filtered or unexported fields
}

func NewDomains

func NewDomains() *Domains

func (*Domains) Add

func (d *Domains) Add(service *Service)

func (*Domains) Get

func (d *Domains) Get(domain, path string) *Service

func (*Domains) Remove

func (d *Domains) Remove(service *Service)

type Endpoints

type Endpoints struct {
	// contains filtered or unexported fields
}

func NewEndpoints

func NewEndpoints() *Endpoints

func (*Endpoints) Add

func (e *Endpoints) Add(service *Service)

func (*Endpoints) Get

func (s *Endpoints) Get() *Service

func (*Endpoints) Remove

func (s *Endpoints) Remove(service *Service)

type Paths

type Paths struct {
	// contains filtered or unexported fields
}

func NewPaths

func NewPaths() *Paths

func (*Paths) Add

func (p *Paths) Add(service *Service)

func (*Paths) Get

func (p *Paths) Get(path string) *Endpoints

func (*Paths) Remove

func (p *Paths) Remove(service *Service)

type Pinger

type Pinger interface {
	// Service if service returns an nil value, it means that Pinger has
	// some internal error and it will no longer return service
	Service() (*Service, error)
}

Pinger defines how container can be ping to get extra information and construct Service object

type PolicyManager

type PolicyManager interface {
	HostPolicy(ctx context.Context, host string) error
}

PolicyManager is a

type Processor

type Processor interface {
	Request(r *http.Request)
	Response(r *http.Response) error
	HandleError(rw http.ResponseWriter, r *http.Request, err error)
}

func CreateProcessorPathAppend

func CreateProcessorPathAppend(raw json.RawMessage) (Processor, error)

func CreateProcessorPathReplace

func CreateProcessorPathReplace(raw json.RawMessage) (Processor, error)

type ProcessorPathAppend

type ProcessorPathAppend struct {
	Begin string `json:"begin"`
	End   string `json:"end"`
}

func (*ProcessorPathAppend) HandleError

func (p *ProcessorPathAppend) HandleError(w http.ResponseWriter, r *http.Request, err error)

func (*ProcessorPathAppend) Request

func (p *ProcessorPathAppend) Request(r *http.Request)

func (*ProcessorPathAppend) Response

func (p *ProcessorPathAppend) Response(r *http.Response) error

type ProcessorPathReplace

type ProcessorPathReplace struct {
	Search  string `json:"search"`
	Replace string `json:"replace"`
	Times   int    `json:"times"`
}

func (*ProcessorPathReplace) HandleError

func (p *ProcessorPathReplace) HandleError(w http.ResponseWriter, r *http.Request, err error)

func (*ProcessorPathReplace) Request

func (p *ProcessorPathReplace) Request(r *http.Request)

func (*ProcessorPathReplace) Response

func (p *ProcessorPathReplace) Response(r *http.Response) error

type Recipe

type Recipe struct {
	Name   string          `json:"name"`
	Config json.RawMessage `json:"config"`
}

type Rule

type Rule struct {
	Domain  string    `json:"domain"`
	Path    string    `json:"path"`
	Ready   bool      `json:"ready"`
	Recipes []*Recipe `json:"recipes"`
}

Rule contains basic information about domain, path and availability of each container

func (Rule) String

func (r Rule) String() string

type Rules

type Rules []*Rule

Rules a wrapper around array of Rule to make it simpler to work with array of rules

func (Rules) Each

func (r Rules) Each(fn func(r *Rule))

Each loop through each rule and call given func

type Service

type Service struct {
	Rule      *Rule
	Container *Container
}

Service is a set of configuration that points to a specific running container Each container can have multiple services linked to them

func (Service) String

func (s Service) String() string

type Services

type Services struct {
	// contains filtered or unexported fields
}

func NewServices

func NewServices() *Services

func (*Services) Add

func (s *Services) Add(service *Service)

func (*Services) Clean

func (s *Services) Clean()

func (*Services) Each

func (s *Services) Each(fn func(service *Service, i int))

type Store

type Store interface {
	// Query returns a service based on domain and path
	Query(domain, path string) *Service
}

Store will be used ti get Container

type Watcher

type Watcher interface {
	// Container gets the next available container
	// this is a blocking calls, if Container object is nil
	// it means that Watcher has been closed
	Container() (*Container, error)
}

Watcher defines how driver should react

Directories

Path Synopsis
cmd
baker command
examples
service1 command
internal
url

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL