Skip to content

Commit

Permalink
Split service discovery into composable components (#341)
Browse files Browse the repository at this point in the history
The `linkerd2_proxy_resolve` crate, which was split out of the main
application in #318, contains a monolithic `Resolve` implementation.
Over time, this code has evolved in ways that have made it difficult to
modify and extend, especially and functionality cannot be tested
independently.

This change reorganizes the proxy's service discovery logic into a few
composable components. The `Resolve` API has been updated to facilitate
this.

Service discovery consists of two primary traits: `Resolve` and
`Discover`. `Resolve` is used within the proxy to model discovery
sources like the Destination API and DNS; and `Discover` updates a load
balancer with changes to its replica pool.

The former `Resolve` trait has been replaced with a trait alias for
`Service`, which allows for back-pressure, etc. Furthermore, the API has
been updated so that it is easier to gracefully recover after a
resolution stream is lost (by changing `Update` to contain a `Vec`).

Now that `Resolve` is implemented as a `Service`, it becomes
straightforward to model service discovery in terms of a stack of
services, as we do throughout the project:

* The `linkerd2-proxy-api-resolve` crate provides an implementation of
  `Resolve` backed by the destination API. It does NOT handle
  reconnects, buffering/spawning, etc.
* The `linkerd2-request-filter` crate provides a simple
  middleware that can fail requests based on the request. This is used to
  implement the destination client's name/suffix restrictions.
* The `linkerd2-proxy-resolve` crate provides `Resolve`-middlewares that
  implement error recovery/backoff and endpoint-type-mapping.
* The `linkerd2-proxy-discover crate provides a suite of middlewares
  that facilitate powering a balancer with a `Resolve`. Specifically:
  * `buffer` ensures that discovery updates are processed even when the
    balancer is not polling for updates.
  * `from_resolve` wraps a `Resolve` to produce `Discover` response.
  * `make_endpoint` wraps a `Discover`-producing service to build each
    inserted endpoint into a Service.

Almost all of this logic was happening in a single module, coupled to
our gRPC interface. Now, these components may be easily re-purposed (for
instance, in order to back discovery by DNS).
  • Loading branch information
olix0r committed Sep 17, 2019
1 parent c99f643 commit 73aa7dc
Show file tree
Hide file tree
Showing 33 changed files with 1,944 additions and 1,206 deletions.
89 changes: 52 additions & 37 deletions Cargo.lock
Expand Up @@ -314,11 +314,6 @@ dependencies = [
"unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "hex"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "hostname"
version = "0.1.4"
Expand Down Expand Up @@ -604,15 +599,17 @@ dependencies = [
"linkerd2-identity 0.1.0",
"linkerd2-metrics 0.1.0",
"linkerd2-proxy-api 0.1.8 (git+https://github.com/linkerd/linkerd2-proxy-api?rev=ddbc3a4f7f8b0058801f896d27974d19ee98094c)",
"linkerd2-proxy-api-resolve 0.1.0",
"linkerd2-proxy-core 0.1.0",
"linkerd2-proxy-discover 0.1.0",
"linkerd2-proxy-resolve 0.1.0",
"linkerd2-reconnect 0.1.0",
"linkerd2-request-filter 0.1.0",
"linkerd2-router 0.1.0",
"linkerd2-signal 0.1.0",
"linkerd2-stack 0.1.0",
"linkerd2-task 0.1.0",
"linkerd2-timeout 0.1.0",
"linkerd2-trace-context 0.1.0",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
"procinfo 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
Expand Down Expand Up @@ -665,34 +662,62 @@ dependencies = [
"tower-grpc-build 0.1.0 (git+https://github.com/tower-rs/tower-grpc)",
]

[[package]]
name = "linkerd2-proxy-api-resolve"
version = "0.1.0"
dependencies = [
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"linkerd2-identity 0.1.0",
"linkerd2-proxy-api 0.1.8 (git+https://github.com/linkerd/linkerd2-proxy-api?rev=ddbc3a4f7f8b0058801f896d27974d19ee98094c)",
"linkerd2-proxy-core 0.1.0",
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tower 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tower-grpc 0.1.0 (git+https://github.com/tower-rs/tower-grpc)",
"tracing 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "linkerd2-proxy-core"
version = "0.1.0"
dependencies = [
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"linkerd2-drain 0.1.0",
"linkerd2-error 0.1.0",
"tower 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "linkerd2-proxy-resolve"
name = "linkerd2-proxy-discover"
version = "0.1.0"
dependencies = [
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"linkerd2-addr 0.1.0",
"linkerd2-dns-name 0.1.0",
"linkerd2-error 0.1.0",
"linkerd2-identity 0.1.0",
"linkerd2-proxy-api 0.1.8 (git+https://github.com/linkerd/linkerd2-proxy-api?rev=ddbc3a4f7f8b0058801f896d27974d19ee98094c)",
"linkerd2-proxy-core 0.1.0",
"linkerd2-task 0.1.0",
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tower-grpc 0.1.0 (git+https://github.com/tower-rs/tower-grpc)",
"tokio 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
"tower 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tower-util 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing-futures 0.0.1-alpha.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "linkerd2-proxy-resolve"
version = "0.1.0"
dependencies = [
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"linkerd2-error 0.1.0",
"linkerd2-proxy-core 0.1.0",
"linkerd2-task 0.1.0",
"tokio 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
"tower 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "linkerd2-reconnect"
version = "0.1.0"
Expand All @@ -704,6 +729,15 @@ dependencies = [
"tracing 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "linkerd2-request-filter"
version = "0.1.0"
dependencies = [
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"tower 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "linkerd2-router"
version = "0.1.0"
Expand Down Expand Up @@ -764,22 +798,6 @@ dependencies = [
"tower-service 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "linkerd2-trace-context"
version = "0.1.0"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"linkerd2-error 0.1.0",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
"tower 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "lock_api"
version = "0.1.5"
Expand Down Expand Up @@ -1676,20 +1694,19 @@ dependencies = [
[[package]]
name = "tower-balance"
version = "0.1.0"
source = "git+https://github.com/tower-rs/tower#793e2e8e94b5ca6c1d12171b3909d78505ab6667"
source = "git+https://github.com/tower-rs/tower#b39a4881d8eff6cbbb3a49b95db8492f6f8afb15"
dependencies = [
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"tower-discover 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tower-layer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tower-load 0.1.0 (git+https://github.com/tower-rs/tower)",
"tower-service 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tower-util 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
Expand Down Expand Up @@ -1765,7 +1782,7 @@ dependencies = [
[[package]]
name = "tower-load"
version = "0.1.0"
source = "git+https://github.com/tower-rs/tower#793e2e8e94b5ca6c1d12171b3909d78505ab6667"
source = "git+https://github.com/tower-rs/tower#b39a4881d8eff6cbbb3a49b95db8492f6f8afb15"
dependencies = [
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
Expand Down Expand Up @@ -1816,7 +1833,7 @@ dependencies = [
[[package]]
name = "tower-spawn-ready"
version = "0.1.0"
source = "git+https://github.com/tower-rs/tower#793e2e8e94b5ca6c1d12171b3909d78505ab6667"
source = "git+https://github.com/tower-rs/tower#b39a4881d8eff6cbbb3a49b95db8492f6f8afb15"
dependencies = [
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
Expand Down Expand Up @@ -1854,7 +1871,6 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing-attributes 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
Expand Down Expand Up @@ -1901,7 +1917,7 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.1.1"
source = "git+https://github.com/tokio-rs/tracing#49dab30847823a10f4398595616c19c0ee96d737"
source = "git+https://github.com/tokio-rs/tracing#198e62a613e1fcf623e8f2c66e1192504f5e9b2f"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
Expand Down Expand Up @@ -2240,7 +2256,6 @@ dependencies = [
"checksum gzip-header 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0a9fcfe1c9ee125342355b2467bc29b9dfcb2124fcae27edb9cee6f4cc5ecd40"
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
"checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
"checksum hostname 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "58fab6e177434b0bb4cd344a4dabaa5bd6d7a8d792b1885aebcae7af1091d1cb"
"checksum http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "fe67e3678f2827030e89cc4b9e7ecd16d52f132c0b940ab5005f88e821500f6a"
"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
Expand Down
39 changes: 22 additions & 17 deletions Cargo.toml
Expand Up @@ -12,8 +12,11 @@ members = [
"lib/linkerd2-identity",
"lib/linkerd2-metrics",
"lib/linkerd2-opencensus",
"lib/linkerd2-proxy-api-resolve",
"lib/linkerd2-proxy-core",
"lib/linkerd2-proxy-discover",
"lib/linkerd2-proxy-resolve",
"lib/linkerd2-request-filter",
"lib/linkerd2-reconnect",
"lib/linkerd2-router",
"lib/linkerd2-signal",
Expand All @@ -37,23 +40,25 @@ flaky_tests = []

[dependencies]
hyper-balance = { path = "lib/hyper-balance" }
linkerd2-addr = { path = "lib/linkerd2-addr" }
linkerd2-conditional = { path = "lib/linkerd2-conditional" }
linkerd2-dns-name = { path = "lib/linkerd2-dns-name" }
linkerd2-error = { path = "lib/linkerd2-error" }
linkerd2-fallback = { path = "lib/linkerd2-fallback" }
linkerd2-identity = { path = "lib/linkerd2-identity" }
linkerd2-metrics = { path = "lib/linkerd2-metrics" }
linkerd2-proxy-core = { path = "lib/linkerd2-proxy-core" }
linkerd2-exp-backoff = { path = "lib/linkerd2-exp-backoff" }
linkerd2-proxy-resolve = { path = "lib/linkerd2-proxy-resolve" }
linkerd2-reconnect = { path = "lib/linkerd2-reconnect" }
linkerd2-router = { path = "lib/linkerd2-router" }
linkerd2-signal = { path = "lib/linkerd2-signal" }
linkerd2-stack = { path = "lib/linkerd2-stack" }
linkerd2-task = { path = "lib/linkerd2-task" }
linkerd2-timeout = { path = "lib/linkerd2-timeout" }
linkerd2-trace-context = { path = "lib/linkerd2-trace-context" }
linkerd2-addr = { path = "lib/linkerd2-addr" }
linkerd2-conditional = { path = "lib/linkerd2-conditional" }
linkerd2-dns-name = { path = "lib/linkerd2-dns-name" }
linkerd2-error = { path = "lib/linkerd2-error" }
linkerd2-fallback = { path = "lib/linkerd2-fallback" }
linkerd2-identity = { path = "lib/linkerd2-identity" }
linkerd2-metrics = { path = "lib/linkerd2-metrics" }
linkerd2-exp-backoff = { path = "lib/linkerd2-exp-backoff" }
linkerd2-proxy-core = { path = "lib/linkerd2-proxy-core" }
linkerd2-proxy-api-resolve = { path = "lib/linkerd2-proxy-api-resolve" }
linkerd2-proxy-discover = { path = "lib/linkerd2-proxy-discover" }
linkerd2-proxy-resolve = { path = "lib/linkerd2-proxy-resolve" }
linkerd2-reconnect = { path = "lib/linkerd2-reconnect" }
linkerd2-request-filter = { path = "lib/linkerd2-request-filter" }
linkerd2-router = { path = "lib/linkerd2-router" }
linkerd2-signal = { path = "lib/linkerd2-signal" }
linkerd2-stack = { path = "lib/linkerd2-stack" }
linkerd2-task = { path = "lib/linkerd2-task" }
linkerd2-timeout = { path = "lib/linkerd2-timeout" }

linkerd2-proxy-api = { git = "https://github.com/linkerd/linkerd2-proxy-api", rev = "ddbc3a4f7f8b0058801f896d27974d19ee98094c" }

Expand Down
18 changes: 18 additions & 0 deletions lib/linkerd2-proxy-api-resolve/Cargo.toml
@@ -0,0 +1,18 @@
[package]
name = "linkerd2-proxy-api-resolve"
version = "0.1.0"
authors = ["Linkerd Developers <cncf-linkerd-dev@lists.cncf.io>"]
edition = "2018"
publish = false

[dependencies]
futures = "0.1"
linkerd2-identity = { path = "../linkerd2-identity" }
linkerd2-proxy-api = { git = "https://github.com/linkerd/linkerd2-proxy-api", rev = "ddbc3a4f7f8b0058801f896d27974d19ee98094c" }
linkerd2-proxy-core = { path = "../linkerd2-proxy-core" }
prost = "0.5.0"
tower-grpc = { git = "https://github.com/tower-rs/tower-grpc", default-features = false, features = ["protobuf"] }
indexmap = "1.0"
tokio-sync = "0.1"
tower = "0.1"
tracing = "0.1"
10 changes: 10 additions & 0 deletions lib/linkerd2-proxy-api-resolve/src/lib.rs
@@ -0,0 +1,10 @@
use linkerd2_identity as identity;
use linkerd2_proxy_api as api;
use linkerd2_proxy_core as core;

mod metadata;
mod pb;
mod resolve;

pub use self::metadata::{Metadata, ProtocolHint};
pub use self::resolve::Resolve;
76 changes: 76 additions & 0 deletions lib/linkerd2-proxy-api-resolve/src/metadata.rs
@@ -0,0 +1,76 @@
use crate::identity;
use indexmap::IndexMap;

/// Metadata describing an endpoint.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Metadata {
/// An endpoint's relative weight.
///
/// A weight of 0 means that the endpoint should never be preferred over a
/// non 0-weighted endpoint.
///
/// The default weight, corresponding to 1.0, is 10,000. This enables us to
/// specify weights as small as 0.0001 and as large as 400,000+.
///
/// A float is not used so that this type can implement `Eq`.
weight: u32,

/// Arbitrary endpoint labels. Primarily used for telemetry.
labels: IndexMap<String, String>,

/// A hint from the controller about what protocol (HTTP1, HTTP2, etc) the
/// destination understands.
protocol_hint: ProtocolHint,

/// How to verify TLS for the endpoint.
identity: Option<identity::Name>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProtocolHint {
/// We don't what the destination understands, so forward messages in the
/// protocol we received them in.
Unknown,
/// The destination can receive HTTP2 messages.
Http2,
}

// === impl Metadata ===

impl Metadata {
pub fn empty() -> Self {
Self {
labels: IndexMap::default(),
protocol_hint: ProtocolHint::Unknown,
identity: None,
weight: 10_000,
}
}

pub fn new(
labels: IndexMap<String, String>,
protocol_hint: ProtocolHint,
identity: Option<identity::Name>,
weight: u32,
) -> Self {
Self {
labels,
protocol_hint,
identity,
weight,
}
}

/// Returns the endpoint's labels from the destination service, if it has them.
pub fn labels(&self) -> &IndexMap<String, String> {
&self.labels
}

pub fn protocol_hint(&self) -> ProtocolHint {
self.protocol_hint
}

pub fn identity(&self) -> Option<&identity::Name> {
self.identity.as_ref()
}
}

0 comments on commit 73aa7dc

Please sign in to comment.