Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify Opaque Port Manifests #9803

Closed
deusxanima opened this issue Nov 10, 2022 · 4 comments · Fixed by #10827
Closed

Simplify Opaque Port Manifests #9803

deusxanima opened this issue Nov 10, 2022 · 4 comments · Fixed by #10827
Assignees

Comments

@deusxanima
Copy link
Contributor

deusxanima commented Nov 10, 2022

What problem are you trying to solve?

Currently our protocol detection opaque port markings are exclusive (i.e., we mark which ports we want to exclude from protocol detection), which creates issues for users who have to exclude massive port-ranges for integration with other infrastructure components such as syslog, for example. Adding massive ranges expands the manifest and we've seen issues where it also creates problems for the k8s api, not to mention the additional memory pressure on the proxies themselves which has to be accounted for.

@dwilliams782
Copy link

Just incase I'm misunderstanding, are you aware that the opaque-port annotation accepts a range, so config.linkerd.io/opaque-ports: 1-65535 is a valid config?

@deusxanima
Copy link
Contributor Author

@dwilliams782 good question. The issue presents specifically when port-ranges are used. With massive ranges each port in the range is added to the manifest which is then expanded to the point where it can 1. make the config and annotations a nightmare to manage, 2. increase proxy mem usage, and 3. overwhelm the k8s api due to the massive character count that is added to some events/requests.

@deusxanima deusxanima changed the title Allow for inclusive vs. exclusive port-range selections for opaque ports Simplify Opaque Port Manifests Dec 16, 2022
@olix0r olix0r added this to the stable-2.13.0 milestone Dec 19, 2022
@olix0r olix0r added the priority/P1 Planned for Release label Dec 19, 2022
@hawkw hawkw self-assigned this Dec 19, 2022
hawkw added a commit to linkerd/linkerd2-proxy that referenced this issue Dec 21, 2022
Depends on #2079

Currently, the `LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION`
environment variable accepts a comma-delimited list of individual port
numbers. Each of these ports is then stored as a separate cache entry in
the port policy cache to indicate that the port is opaque.

Unfortunately, this does not work well when a very large number of ports
are marked as opaque, because the proxy-injector will generate a massive
value for that environment variable, listing every individual port. This
can cause issues when this manifest becomes larger than the Kubernetes
API can reasonably handle. In addition, huge numbers of ports will all
be kept in memory as a separate cache entry by the proxy, increasing
proxy memory usage. See linkerd/linkerd2#9803 for details.

This branch changes the proxy so that the
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` environment
variable may contain a list of individual port numbers *and* port
ranges, which are specified as `<low>-<high>`.

Opaque ports are now stored using a `RangeInclusiveSet` from the
`rangemap` crate, rather than by creating individual cache entries for
each port. This means we are no longer storing a separate entry in the
cache for every port in a range, reducing memory consumption when there
are very large ranges of opaque ports.

This is the proxy half of the work towards resolving
linkerd/linkerd2#9803. Once this branch lands, we'll also have to change
the proxy-injector so that it no longer handles opaque port ranges by
generating a list of all the individual ports in the range, and simply
forwards those ranges as they were specified when generating the
environment variable.
olix0r pushed a commit to linkerd/linkerd2-proxy that referenced this issue Feb 24, 2023
inbound: Remove default policies (#2204)

When an inbound proxy accepts a connection, it needs a policy for the
inbound port. If there is no known policy, then a default inbound policy
is provided from the proxy's configuration. This presents a few
problems:

* The server-configured policy may be more restrictive than the default
  policy. This could all unauthorized connections to race into a
  pod (when ports are not documented in the the pod spec).
* The server-configured protocol configuration may differ from the
  proxy's configuration.
* The way the proxy reads environment configuration for default
  policies is inefficient for large port ranges. See
  linkerd/linkerd2#9803

## Solution

To simplify the proxy's configuration surface area, we remove synthetic
default policy configurations so that all inbound policies are provided
by the policy controller. This means that the proxy will require either
a cached policy or a response from the policy controller before it can
accept a connection. Proxies always discover and retain cached policies
for all ports documented in `LINKERD2_PROXY_INBOUND_PORTS`. Other
policies are discovered dynamically and cached for the discovery
idle timeout.

This inbound proxy now requires a policy controller client configuration.
Tests are updated to use a mock policy controller service.

The following environment variables are no longer used:

* `LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_IDENTITY`
* `LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS`
* `LINKERD2_PROXY_INBOUND_DEFAULT_POLICY`
* `LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS

See also #2186
@hawkw
Copy link
Contributor

hawkw commented Apr 19, 2023

Proxy PR linkerd/linkerd2-proxy#2395 changes the proxy to accept port ranges in the LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION environment variable. Once this PR merges, we'll also need to change the proxy-injector to pass the ranges through when populating this environment variable, rather than populating it with each individual port in the range. That should fix this issue!

hawkw added a commit to linkerd/linkerd2-proxy that referenced this issue Apr 25, 2023
…2395)

The proxy injector populates an environment variable,
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION`, with a list
of all ports marked as opaque. Currently, however, _the proxy _does not
actually use this environment variable_. Instead, opaque ports are
discovered from the policy controller. The opaque ports environment
variable was used only when running in the "fixed" inbound policy mode,
where all inbound policies are determined from environment variables,
and no policy controller address is provided. This mode is no longer
supported, and the policy controller address is now required, so the
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` environment
variable is not currently used to discover inbound opaque ports.

There are two issues with the current state of things. One is that
inbound policy discovery is _non-blocking_: when an inbound proxy
receives a connection on a port that it has not previously discovered a
policy for, it uses the default policy until it has successfully
discovered a policy for that port from the policy controller. This means
that the proxy may perform protocol detection on the first connection to
an opaque port. This isn't great, as it may result in a protocol
detection timeout error on a port that the user had previously marked as
opaque. It would be preferable for the proxy to read the environment
variable, and use it to determine whether the default policy for a port
is opaque, so that ports marked as opaque disable protocol detection
even before the "actual" policy is discovered.

The other issue with the
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` environment
variable is that it is currently a list of _individual port numbers_,
while the proxy injector can accept annotations that specify _ranges_ of
opaque ports. This means that when a very large number of ports are
marked as opaque, the proxy manifest must contain a list of each
individual port number in those ranges, making it potentially quite
large. See linkerd/linkerd2#9803 for details on this issue.

This branch addresses both of these problems. The proxy is changed so
that it will once again read the
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` environment
variable, and use it to determine which ports should have opaque
policies by default. The parsing of the environment variable is changed
to support specifying ports as a list of ranges, rather than a list of
individual port numbers. Along with a proxy-injector change, this would
resolve the manifest size issue described in linkerd/linkerd2#9803.

This is implemented by changing the `inbound::policy::Store` type to
also include a set of port ranges that are marked as opaque. When the
`Store` handles a `get_policy` call for a port that is not already in
the cache, it starts a control plane watch for that port just as it did
previously. However, when determining the initial _default_ value for
the policy, before the control plane discovery provides one, it checks
whether the port is in a range that is marked as opaque, and, if it is,
uses an opaque default policy instead.

This approach was chosen rather than pre-populating the `Store` with
policies for all opaque ports to better handle the case where very large
ranges are marked as opaque and are used infrequently. If the `Store`
was pre-populated with default policies for all such ports, it would
essentially behave as though all ports in
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` were also in
`LINKERD2_PROXY_INBOUND_PORTS`, and the proxy would immediately start a
policy controller discovery watch for all opaque ports, which would be
kept open for the proxy's entire lifetime. In cases where the opaque
ports ranges include ~10,000s of ports, this causes significant
unnecessary load on the policy controller. Storing opaque port ranges
separately and using them to determine the default policy as needed
allows opaque port policies to be treated the same as non-default ports,
which are discovered as needed and can be evicted from the cache if they
are unused. If a port is in both
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` *and*
`LINKERD2_PROXY_INBOUND_PORTS`, the proxy will start discovery eagerly
and retain the port in the cache forever, but the default policy will be
opaque.

I've also added a test for the behavior of opaque ports where the port's
policy has not been discovered from the policy controller. That test
fails on `main`, as the proxy attempts protocol detection, but passes on
this branch.

In addition, I changed the parsing of the `LINKERD2_PROXY_INBOUND_PORTS`
environment variable to also accept ranges, because it seemed like a
nice thing to do while I was here. :)
@hawkw
Copy link
Contributor

hawkw commented Apr 25, 2023

We've now merged the proxy release that modifies the proxy to accept ranges in the environment variable. I'm working on modifying the proxy-injector to actually emit ranges when patching a manifest.

This is somewhat more complex than simply removing code that turns a list of ranges into a list of individual port numbers, though, because the proxy-injector also performs filtering of the opaque port list so that it includes only those ports actually exposed by the Service:

func (conf *ResourceConfig) CreateOpaquePortsPatch() ([]byte, error) {
if conf.HasWorkloadAnnotation(k8s.ProxyOpaquePortsAnnotation) {
// The workload already has the opaque ports annotation so a patch
// does not need to be created.
return nil, nil
}
opaquePorts, ok := conf.GetConfigAnnotation(k8s.ProxyOpaquePortsAnnotation)
if ok {
// The workload's namespace has the opaque ports annotation, so it
// should inherit that value. A patch is created which adds that
// list.
return conf.CreateAnnotationPatch(opaquePorts)
}
// Both the workload and the namespace do not have the annotation so a
// patch is created which adds the default list.
defaultPorts := strings.Split(conf.GetValues().Proxy.OpaquePorts, ",")
var filteredPorts []string
if conf.IsPod() {
// The workload is a pod so only add the default opaque ports that it
// exposes as container ports.
filteredPorts = conf.FilterPodOpaquePorts(defaultPorts)
} else if conf.IsService() {
// The workload is a service so only add the default opaque ports that
// are exposed as a service port, or targeted as a targetPort.
service := conf.workload.obj.(*corev1.Service)
for _, p := range service.Spec.Ports {
port := strconv.Itoa(int(p.Port))
if p.TargetPort.Type == 0 && p.TargetPort.IntVal == 0 {
// The port's targetPort is not set, so add the port if is
// opaque by default. Checking that targetPort is not set
// avoids marking a port as opaque if it targets a port that
// not opaque (e.g. port=3306 and targetPort=80; 3306 should
// not be opaque)
if util.ContainsString(port, defaultPorts) {
filteredPorts = append(filteredPorts, port)
}
} else if util.ContainsString(strconv.Itoa(int(p.TargetPort.IntVal)), defaultPorts) {
// The port's targetPort is set; if it is opaque then port
// should also be opaque.
filteredPorts = append(filteredPorts, port)
}
}
}
if len(filteredPorts) == 0 {
// There are no default opaque ports to add so a patch does not need
// to be created.
return nil, nil
}
ports := strings.Join(filteredPorts, ",")
return conf.CreateAnnotationPatch(ports)
}

hawkw added a commit that referenced this issue Apr 27, 2023
Currently, the proxy injector will expand lists of opaque port ranges
into lists of individual port numbers. This is because the proxy has
historically not accepted port ranges in the
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` environment
variable. However, when very large ranges are used, the size of the
injected manifest can be quite large, since each individual port number
in a range must be listed separately.

Proxy PR linkerd/linkerd2-proxy#2395 changed the proxy to accept ranges
as well as individual port numbers in the opaque ports environment
variable, and this change was included in the latest proxy release
(v2.200.0). This means that the proxy-injector no longer needs to expand
large port ranges into individual port numbers, and can now simply
forward the list of ranges to the proxy. This branch changes the proxy
injector to do this, resolving issues with manifest size due to large
port ranges.

Closes #9803
risingspiral pushed a commit that referenced this issue May 4, 2023
Currently, the proxy injector will expand lists of opaque port ranges
into lists of individual port numbers. This is because the proxy has
historically not accepted port ranges in the
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` environment
variable. However, when very large ranges are used, the size of the
injected manifest can be quite large, since each individual port number
in a range must be listed separately.

Proxy PR linkerd/linkerd2-proxy#2395 changed the proxy to accept ranges
as well as individual port numbers in the opaque ports environment
variable, and this change was included in the latest proxy release
(v2.200.0). This means that the proxy-injector no longer needs to expand
large port ranges into individual port numbers, and can now simply
forward the list of ranges to the proxy. This branch changes the proxy
injector to do this, resolving issues with manifest size due to large
port ranges.

Closes #9803
risingspiral pushed a commit that referenced this issue May 5, 2023
Currently, the proxy injector will expand lists of opaque port ranges
into lists of individual port numbers. This is because the proxy has
historically not accepted port ranges in the
`LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION` environment
variable. However, when very large ranges are used, the size of the
injected manifest can be quite large, since each individual port number
in a range must be listed separately.

Proxy PR linkerd/linkerd2-proxy#2395 changed the proxy to accept ranges
as well as individual port numbers in the opaque ports environment
variable, and this change was included in the latest proxy release
(v2.200.0). This means that the proxy-injector no longer needs to expand
large port ranges into individual port numbers, and can now simply
forward the list of ranges to the proxy. This branch changes the proxy
injector to do this, resolving issues with manifest size due to large
port ranges.

Closes #9803

Signed-off-by: Eric Anderson <eric@buoyant.io>
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants