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

Expose container ports on the collector pod #1070

Merged

Conversation

kristinapathak
Copy link
Contributor

@kristinapathak kristinapathak commented Sep 1, 2022

fixes #1011, adding a way to expose container ports for service and pod monitoring.

Uses functions from this package to validate port name and number. If the name is too long, we cut it off to fit.

@kristinapathak kristinapathak force-pushed the expose-container-ports branch 2 times, most recently from 17c405b to ede60ac Compare September 2, 2022 00:44
@kristinapathak kristinapathak marked this pull request as ready for review September 8, 2022 03:04
@kristinapathak kristinapathak requested a review from a team September 8, 2022 03:04
pkg/collector/container.go Outdated Show resolved Hide resolved
apis/v1alpha1/opentelemetrycollector_types.go Show resolved Hide resolved
pkg/collector/container.go Outdated Show resolved Hide resolved
pkg/collector/container.go Fixed Show fixed Hide fixed
pkg/collector/adapters/config_to_ports_test.go Outdated Show resolved Hide resolved
pkg/collector/adapters/config_to_ports_test.go Outdated Show resolved Hide resolved
pkg/collector/container.go Outdated Show resolved Hide resolved
pkg/collector/container.go Outdated Show resolved Hide resolved
pkg/collector/container.go Outdated Show resolved Hide resolved
pkg/collector/container.go Outdated Show resolved Hide resolved
// ignore kubeletstats receiver as it holds the field key endpoint, and it
// is a scraper, we only expose endpoint through k8s service objects for
// receivers that aren't scrapers.
case name == "kubeletstats":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this there should be a config option, with sane defaults, to control which receivers types should be ignored.
I'm thinking about custom build instances of the collector which could contain receivers not available in contrib.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

singleContainerPortFromConfigEndpoint() is based on singlePortFromConfigEndpoint() so both functions have this logic. Would you prefer that I add configuration for this in this PR? I'm also happy to open an issue and fix this separately.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pavolloffay What do you think about making this configurable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this should be a separate issue given this logic is already in the operator. The concern is valid, but i think adding the logic to this PR is going to expand the scope of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created an issue for this:
#1095

pkg/collector/container_test.go Outdated Show resolved Hide resolved
@@ -81,6 +81,13 @@ type OpenTelemetryCollectorSpec struct {
// +optional
// +listType=atomic
Ports []v1.ServicePort `json:"ports,omitempty"`
// ContainerPorts allows a set of ports to be exposed by the underlying v1.Container. By default, the operator
// will attempt to infer the required ports by parsing the .Spec.Config property but this property can be
// used to open additional ports that can't be inferred by the operator, like for custom receivers. Any ports
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like for custom receivers.

I would assume that custom receiver ports should be defined in the spec.ports and the container ports are rather used for the ports that should not be exposed in the service.

Do we even need to have service ports and container ports? We could use the service ports to open the ports on the container. Is there any use-case when container ports != service ports?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure - I included this for the flexibility and because the types are different. I can change it so we parse the configured service ports into container ports if that's the direction we want to go in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pavolloffay, do you want me to change the logic to parse the container ports from the list of service ports? Or are you waiting for others' input on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no hard requirement to expose additional configuration in the CR then I would opt-in for deriving the ports automatically for now

Copy link
Member

@pavolloffay pavolloffay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinearls could you please review as well? You were looking into this in the past.

pkg/collector/container.go Show resolved Hide resolved
nameErrs := validation.IsValidPortName(truncName)
numErrs := validation.IsValidPortNum(int(p.Port))
if len(nameErrs) > 0 || len(numErrs) > 0 {
logger.Error(errInvalidPort, "dropping container port", "port.name", truncName, "port.num", p.Port,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should rather emit k8s event to let the user know that the validation failed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinearls you did some logging-related work recently.

Do you think we should use k8s events here or normal logs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pavolloffay I don't really know, to be honest. I think logging would be fine but we are not very consistent about this.

// Container builds a container for the given collector.
func Container(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) corev1.Container {
image := otelcol.Spec.Image
if len(image) == 0 {
image = cfg.CollectorImage()
}

// build container ports from service ports
ports := getConfigContainerPorts(logger, otelcol.Spec.Config)
for _, p := range otelcol.Spec.Ports {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function does a couple of different things:

  1. name truncation
  2. validation
  3. create a container port

The truncation and validation should be done in the defaulting and validating webhook. The otelcol spec received in this function should be valid and prepared for use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validating webhook can as well return user-facing error if the name validation fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't add the current truncation to the defaulting webhook because it causes an import cycle:

package github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1
	imports github.com/open-telemetry/opentelemetry-operator/pkg/naming
	imports github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1: import cycle not allowed

Is it reasonable for us to error out when a Spec Port is too long (like any other port naming error)? Or should I truncate a different way?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user can change the spec in a meaningful way to meet the requirements than returning the error is fine

specPorts: []corev1.ServicePort{
{
// this port name contains a non alphanumeric character, which is invalid.
Name: "-test🦄port",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:P

Copy link
Member

@kevinearls kevinearls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@jaronoff97 jaronoff97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only change to be made is move some of the validation / mutation to the corresponding webhooks, otherwise this looks great. Awesome stuff 🥳

@kristinapathak kristinapathak force-pushed the expose-container-ports branch 2 times, most recently from ed81ddc to dcdd998 Compare September 27, 2022 03:08
assert.NoError(t, err)
return
}
assert.ErrorContains(t, err, test.expectedErr)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is error checking against a string value which isn't ideal. Some other options are:

  1. Create errors that are wrapped in the returned fmt.Errorf so that I can use assert.ErrorIs().
  2. Check that the error isn't nil instead of what its value is: assert.Error().

Comment on lines +137 to +148
truncName := naming.Truncate(p.Name, maxPortLen)
if p.Name != truncName {
logger.Info("truncating container port name",
"port.name.prev", p.Name, "port.name.new", truncName)
}
nameErrs := validation.IsValidPortName(truncName)
numErrs := validation.IsValidPortNum(int(p.Port))
if len(nameErrs) > 0 || len(numErrs) > 0 {
logger.Error(errInvalidPort, "dropping container port", "port.name", truncName, "port.num", p.Port,
"port.name.errs", nameErrs, "num.errs", numErrs)
continue
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be able to move this truncation+validation to inside the adapters.ConfigToReceiverPorts() function - what do you think, @pavolloffay?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validation and truncation can definitely live in other package to avoid import cycle

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to move it and it caused an import cycle. 😞 So I'm just going to leave the code as it is.

// validate autoscale with horizontal pod autoscaler
if r.Spec.MaxReplicas != nil {
if *r.Spec.MaxReplicas < int32(1) {
if *r.Spec.MaxReplicas <= int32(1) {
Copy link
Contributor Author

@kristinapathak kristinapathak Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to match the error string explanation (max replicas should be more than one) but I don't think there's a problem if maxReplicas is 1? I can change the error string instead of changing this expression if others agree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of any limitations regarding MaxReplicas and I think rather the error message should be changed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be ideally changed in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved: #1136

I will remove these changes from this PR.

// validate autoscale with horizontal pod autoscaler
if r.Spec.MaxReplicas != nil {
if *r.Spec.MaxReplicas < int32(1) {
if *r.Spec.MaxReplicas <= int32(1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of any limitations regarding MaxReplicas and I think rather the error message should be changed.

@pavolloffay pavolloffay changed the title expose container ports Expose container ports on the collector pod Oct 4, 2022
@pavolloffay pavolloffay merged commit 10404eb into open-telemetry:main Oct 4, 2022
ItielOlenick pushed a commit to ItielOlenick/opentelemetry-operator that referenced this pull request May 1, 2024
* expose container ports

* get container ports from service ports

* add, fix tests

* move logic changes to their own branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add the ability to expose a port on a collector container
5 participants