-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Add k8s observer #185
Add k8s observer #185
Conversation
This adds a k8s observer as well as reworking some of the observer data structures. Work is still ongoing to add an end to end test to validate it against a real k8s instance and dynamically start a real receiver. Before Endpoint was an interface with methods like `ID()`, `Target()`, etc. but these had a smell because they were acting as simple data getters for struct values. This changes Endpoint to be a struct itself that has members like `ID` and `Target`. It has a member `Details` that can currently either be a Pod (ie just has an IP address) or a Port (has a pod IP but also an associated port). Currently this `Details` is an `interface{}` as there are currently no methods in which to base an interface off of. This is basically due to a limitation in Go which doesn't have true class inheritance. It is no worse than before since when `Endpoint` was an interface the consumer would have to type switch to check what concrete type it was (pod, port, etc.). I have tried structuring it many ways and this is the least worse I've found thus far.
extension/observer/k8s/config.go
Outdated
type Config struct { | ||
configmodels.ExtensionSettings `mapstructure:",squash"` | ||
|
||
// For example, node name can be passed to each agent with the downward API as follows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a "downward API"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extension/observer/k8s/config.go
Outdated
// env: | ||
// - name: K8S_NODE_NAME | ||
// valueFrom: | ||
// fieldRef: | ||
// fieldPath: spec.nodeName | ||
// | ||
// Then set this value to ${K8S_NODE_NAME} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand this example. Please clarify. What does env
signify here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's part of a pod spec. Should make more sense based on the link above but I can expand the comment.
extension/observer/k8s/factory.go
Outdated
return &Factory{createK8sConfig: func() (*rest.Config, error) { | ||
restConfig, err := rest.InClusterConfig() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed creating Kubernetes in-cluster REST config: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears that this will result in "failed creating Kubernetes in-cluster REST config" repeated twice when called from CreateExtension()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
extension/observer/k8s/handler.go
Outdated
|
||
// OnAdd is called in response to a pod being added. | ||
func (h *handler) OnAdd(obj interface{}) { | ||
pod := obj.(*v1.Pod) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it guaranteed for obj
to be v1.Pod
? Perhaps be defensive and check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The kubernetes cache syncers are of a single type and we only sync pods so not too worried about any other type sneaking in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would still be careful. I assume checking the interface has no or little cost. I get an eye pain from this unprotected conversion :-)
If for some reason you do not want to add the check I would document that obj is expected to be of particular interface and why the expectation.
extension/observer/k8s/handler.go
Outdated
oldEndpoints := map[string]observer.Endpoint{} | ||
newEndpoints := map[string]observer.Endpoint{} | ||
|
||
for _, e := range h.convertPodToEndpoints(oldObj.(*v1.Pod)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. Maybe be defensive and check the type assertion?
extension/observer/k8s/handler.go
Outdated
|
||
var removedEndpoints, updatedEndpoints, addedEndpoints []observer.Endpoint | ||
|
||
for _, e := range newEndpoints { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to add a comment to explain what this loop is doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Labels() map[string]string | ||
Target string | ||
// Details contains additional context about the endpoint such as a Pod or Port. | ||
Details interface{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you mentioned this in the PR but just to double check is there any trait that Details
must exhibit that we can define an interface on? Perhaps it has to have a Name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in the future it might have something like ToMap() map[string]string
that returns a map of the object using semantic conventions but it's not needed yet. If something like Name would be common across all Details then it would go into the Endpoint instead I think. I considered Type()
but that's really just duplicating the Go type so not much help.
Co-Authored-By: Tigran Najaryan <4194920+tigrannajaryan@users.noreply.github.com>
…r-contrib into k8s
extension/observer/k8s/factory.go
Outdated
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package k8s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use k8s_observer
as package
Please resolve the conflict. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, except a couple unchecked interface type assertions, which make me a bit uncomfortable and appear to be against our "don't crash" recommendation: https://github.com/open-telemetry/opentelemetry-collector/blob/master/CONTRIBUTING.md#do-not-crash-after-startup
extension/observer/k8s/handler.go
Outdated
|
||
// OnAdd is called in response to a pod being added. | ||
func (h *handler) OnAdd(obj interface{}) { | ||
pod := obj.(*v1.Pod) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would still be careful. I assume checking the interface has no or little cost. I get an eye pain from this unprotected conversion :-)
If for some reason you do not want to add the check I would document that obj is expected to be of particular interface and why the expectation.
I will change it so you can sleep at night. :-P |
Thank you! :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please fix the build (something about goimports).
@tigrannajaryan build is green now |
@bogdandrutu are you OK or you need to review more? |
Ship it :) |
* Add k8s observer This adds a k8s observer as well as reworking some of the observer data structures. Work is still ongoing to add an end to end test to validate it against a real k8s instance and dynamically start a real receiver. Before Endpoint was an interface with methods like `ID()`, `Target()`, etc. but these had a smell because they were acting as simple data getters for struct values. This changes Endpoint to be a struct itself that has members like `ID` and `Target`. It has a member `Details` that can currently either be a Pod (ie just has an IP address) or a Port (has a pod IP but also an associated port). Currently this `Details` is an `interface{}` as there are currently no methods in which to base an interface off of. This is basically due to a limitation in Go which doesn't have true class inheritance. It is no worse than before since when `Endpoint` was an interface the consumer would have to type switch to check what concrete type it was (pod, port, etc.). I have tried structuring it many ways and this is the least worse I've found thus far. * fix go.mod * remove e2e test that is not ready * Apply suggestions from code review Co-Authored-By: Tigran Najaryan <4194920+tigrannajaryan@users.noreply.github.com> * review updates * review updates * rename package * cleanup deps * fix type changes * fix linting * fix build Co-authored-by: Tigran Najaryan <4194920+tigrannajaryan@users.noreply.github.com>
* Add license automatically * Add addlicense to make default goal * Add dependency to internal tools
This adds a k8s observer as well as reworking some of the observer data
structures. Work is still ongoing to add an end to end test to validate it
against a real k8s instance and dynamically start a real receiver.
Before Endpoint was an interface with methods like
ID()
,Target()
, etc.but these had a smell because they were acting as simple data getters for
struct values. This changes Endpoint to be a struct itself that has members
like
ID
andTarget
. It has a memberDetails
that can currently either bea Pod (ie just has an IP address) or a Port (has a pod IP but also an
associated port). Currently this
Details
is aninterface{}
as there arecurrently no methods in which to base an interface off of. This is basically
due to a limitation in Go which doesn't have true class inheritance. It is no
worse than before since when
Endpoint
was an interface the consumer wouldhave to type switch to check what concrete type it was (pod, port, etc.). I
have tried structuring it many ways and this is the least worse I've found thus
far.