ambex

package
v1.11.2-rc.0 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2021 License: Apache-2.0 Imports: 46 Imported by: 0

README

Ambex: Ambassador Experimental^H^H^H^H^H^H^H^H^H^H^H^H ADS service

Ambassador prior to v0.50.0 worked by writing out Envoy v1 JSON configuration files, then triggering a hot restart of Envoy. This works, but it has some unpleasant limitations:

  • Restarts take awhile, and as a result you can't change the configuration very quickly.
  • Restarts can drop connections (cf Envoy #2776; more on this later).
  • Envoy itself is deprecating the v1 configuration, and will only support v2 in a bit.

To get around these limitations, and generally go for a better experience all 'round, we want to switch to the so-called xDS model, in which Envoy's configuration is supplied by its various "Discovery Services": e.g. the CDS is the Cluster Discovery Service; the EDS is the Endpoint Discovery Service. For Ambassador, the Aggregated Discovery Service or ADS is the one we want to use -- basically, it brings the other services together under one aegis and lets you just tell Envoy "get everything dynamically."

However, the whole ADS thing is a bit of a pain:

  • Envoy makes a bidirectional gRPC stream to the ADS server.
  • The ADS then makes gRPC calls to the Envoy to feed the Envoy configuration elements, but:
  • The ADS has to carefully order things such that the configuration elements match what Envoy expects for consistency.

Rather than do all that logic by hand, we'll use the Envoy go-control-plane[^1] for the heavy lifting. This is also something of a pain, given that it's not well documented, but here's the deal:

  • The root of the world is a SnapshotCache:

    • import github.com/datawire/ambassador/pkg/envoy-control-plane/cache, then refer to cache.SnapshotCache.
    • A collection of internally consistent configuration objects is a Snapshot (cache.Snapshot).
    • Snapshots are collected in the SnapshotCache.
    • A given SnapshotCache can hold configurations for multiple Envoys, identified by the Envoy nodeID, which must be configured for the Envoy.
  • The SnapshotCache can only hold go-control-plane configuration objects, so you have to build these up to hand to the SnapshotCache.

  • The gRPC stuff is handled by a Server:

    • import github.com/datawire/ambassador/pkg/envoy-control-plane/server, then refer to server.Server.
    • Our runManagementServer function (largely ripped off from the go-control-plane tests) gets this running. It takes a SnapshotCache (cleverly called config for no reason I (Flynn) understand) and a standard Go gRPCServer as arguments.
    • ALL the gRPC madness is handled by the Server, with the assistance of the methods in its callback object.
  • Once the Server is running, Envoy can open a gRPC stream to it.

    • On connection, Envoy will get handed the most recent Snapshot that the Server's SnapshotCache knows about.
    • Whenever a newer Snapshot is added to the SnapshotCache, that Snapshot will get sent to the Envoy.
  • We manage the SnapshotCache by loading Envoy configuration files on disk:

    • it ignores files that start with a . (hidden files)
    • it interprets *.json files as JSON-encoded protobuf
    • it interprets *.pb files as text-encoded protobuf
    • all other files are ignored As for when it loads those files:
    • By default when we get a SIGHUP we reload the configuration.
    • When passed the --watch argument we reload whenever any file in the directory changes. Be careful about updating files atomically if you use this!

[^1]: The Envoy go-control-plane usually refers to github.com/envoyproxy/go-control-plane, but we've "forked" it as github.com/datawire/ambassador/pkg/envoy-control-plane in order to build it against the protobufs for our patched Envoy.

Running Ambex

You'll need the Go toolchain, and will want to have a functioning envoy.

Then, you can run the ambex CLI using busyambassador:

go run github.com/datawire/ambassador/cmd/busyambassador ambex ARGs...

If you're on a platform other than GNU/Linux, in order to have a functioning envoy, you may want to run all of this in the builder shell: make shell.

Try it out

You'll want to run both ambex and an instance of envoy with a boostrap config pointing at that `ambex.

  1. First, start the ambex:

    go run github.com/datawire/ambassador/cmd/busyambassador ambex ./example/ambex/
    

    or

    go run github.com/datawire/ambassador/cmd/busyambassador ambex --watch ./example/ambex/
    
  2. Second, in another shell, start the envoy:

    envoy -l debug -c ./example/envoy/bootstrap-ads.yaml
    

You should now be able to run some curls in (yet) another shell:

$ curl localhost:8080/hello
Hello ADS!!!

$ curl localhost:8080/get
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Connection": "close",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.54.0",
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000"
  },
  "origin": "72.74.69.156",
  "url": "http://httpbin.org/get"
}

Edit and/or add more files to the ./example/ambex/ directory in order to play with more configurations and see them reload instantaneously (if you used the --watch flag), or when-triggered (if you didn't use the --watch flag; trigger a relead by signaling the process with killall -HUP ambex).

Clean up

Kill Ambex and Envoy with Ctrl-C.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Clone

func Clone(src proto.Message) proto.Message

func Decode added in v1.11.2

func Decode(name string) (proto.Message, error)

func GetAmbassadorDrainTime added in v1.9.0

func GetAmbassadorDrainTime() time.Duration

The GetAmbassadorDrainTime function retuns the AMBASSADOR_DRAIN_TIME env var as a time.Duration

func ListenerToRdsListener added in v1.11.0

func ListenerToRdsListener(lnr *api.Listener) (*api.Listener, []*api.RouteConfiguration, error)

ListenerToRdsListener will take a listener definition and extract any inline RouteConfigurations replacing them with a reference to an RDS supplied route configuration. It does not modify the supplied listener, any configuration included in the result is copied from the input.

If the input listener does not match the expected form it is simply copied, i.e. it is the identity transform for any inputs not matching the expected form.

Example Input (that will get transformed in a non-identity way):

  • a listener configured with an http connection manager

  • that specifies an http router

  • that supplies its RouteConfiguration inline via the route_config field

    { "name": "...", ..., "filter_chains": [ { "filter_chain_match": {...}, "filters": [ { "name": "envoy.filters.network.http_connection_manager", "typed_config": { "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", "http_filters": [...], "route_config": { "virtual_hosts": [ { "name": "ambassador-listener-8443-*", "domains": ["*"], "routes": [...], } ] } } } ] } ] }

Example Output:

  • a duplicate listener that defines the "rds" field instead of the "route_config" field

  • and a list of route configurations

  • with route_config_name supplied in such a way as to correlate the two together

    lnr, routes, err := ListenerToRdsListener(...)

    lnr = { "name": "...", ..., "filter_chains": [ { "filter_chain_match": {...}, "filters": [ { "name": "envoy.filters.network.http_connection_manager", "typed_config": { "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", "http_filters": [...], "rds": { "config_source": { "ads": {} }, "route_config_name": "ambassador-listener-8443-routeconfig-0" } } } ] } ] }

    routes = [ { "name": "ambassador-listener-8443-routeconfig-0", "virtual_hosts": [ { "name": "ambassador-listener-8443-*", "domains": ["*"], "routes": [...], } ] } ]

func Main added in v1.3.0

func Main(ctx context.Context, Version string, rawArgs ...string) error

func Main2 added in v1.10.0

func Main2(ctx context.Context, Version string, getUsage MemoryGetter, rawArgs ...string) error

func Merge

func Merge(to, from proto.Message)

func Updater added in v1.9.0

func Updater(ctx context.Context, updates <-chan Update, getUsage MemoryGetter) error

The Updator function will run forever (or until the ctx is canceled) and look for updates on the incoming channel. If memory usage is constrained as reported by the getUsage function, updates will be rate limited to guarantee that there are only so many stale configs in memory at a time. The function assumes updates are cumulative and it will drop old queued updates if a new update arrives.

Types

type Args added in v1.10.0

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

type Hasher

type Hasher struct {
}

Hasher returns node ID as an ID

func (Hasher) ID

func (h Hasher) ID(node *core.Node) string

ID function

type MemoryGetter added in v1.9.0

type MemoryGetter func() int

Function type for fetching memory usage as a percentage.

type Update added in v1.9.0

type Update struct {
	Version string
	Update  func() error
}

An Update encapsulates everything needed to perform an update (of envoy configuration). The version string is for logging purposes, the Updator func does the actual work of updating.

type Validatable

type Validatable interface {
	proto.Message
	Validate() error
}

Not sure if there is a better way to do this, but we cast to this so we can call the generated Validate method.

Jump to

Keyboard shortcuts

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