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

[kubernetes] Only use kube annotations for sd once per pod #2901

Merged
merged 6 commits into from
Nov 28, 2016

Conversation

mikekap
Copy link
Contributor

@mikekap mikekap commented Oct 8, 2016

What does this PR do?

Currently the kube annotations are used for service discovery once per docker container. This causes "duplicate" checks to be instantiated if you have sidecar containers in your kubernetes pod.

This PR changes the annotations to annotate a specific container to monitor (by name).

Motivation

Seeing dup checks in my cluster :X.

Testing Guidelines

Unit tests. I will also be deploying this to my internal cluster and monitoring.

Additional Notes

The various tuples flying around between config & backend are getting a bit old. AFAICT there are 4 distinct sets:

  • Config->Backend: [(source, (check_name, init_config, instance_tpl))]
  • _get_config_templates -> _get_check_configs: [(source, (check_name, init_config, instance_tpl, variables))]
  • _get_check_configs -> get_configs: [(source, (check_name, init_config, instance))]

It's getting a bit weird. I removed trace_configs as a variation because that was getting to be a bit too much. I think there was also a bug where some code didn't handle this case.

@mikekap mikekap closed this Oct 8, 2016
@mikekap mikekap reopened this Oct 8, 2016
@mikekap mikekap closed this Oct 9, 2016
@mikekap mikekap reopened this Oct 9, 2016
@mikekap mikekap force-pushed the kube-sd-only-one branch 2 times, most recently from e70e5b5 to 118db09 Compare October 9, 2016 04:57
@hkaj
Copy link
Member

hkaj commented Oct 13, 2016

thanks @mikekap i'll review it shortly

@hkaj
Copy link
Member

hkaj commented Oct 18, 2016

Hi @mikekap
Thanks for contributing (again!) 🎉

That's an issue I should have caught before. Our lack of integration testing for k8s is getting punishing.
Also, thank you for removing the trace_configs thing. It really didn't age well.

Your approach in this PR works, but I wonder if we shouldn't modify the annotation format instead of ignoring all but one container per pod. Running checks against several containers in the same pod should be an option. What do you think of changing annotation keys like so?

com.datadoghq.sd/<full_image_name>/check_names
com.datadoghq.sd/<full_image_name>/init_configs
com.datadoghq.sd/<full_image_name>/instances

AbstractConfigStore._get_kube_config has both annotations and the identifier (which should already be full_image_name in this case) so we can easily do the matching there.

This feature is not documented yet so it shouldn't be a problem.
What do you think?

@mikekap
Copy link
Contributor Author

mikekap commented Oct 19, 2016

We could definitely add support for monitoring all containers. It would probably be more intuitive to use the kubernetes container name rather than the image name though. One thing I'm not sure of - is the multi-container model more flexible? Currently, even though we only init checks for one of the containers, you can easily monitor the other containers in a pod by just changing the port (since all containers in a pod share the same ip).

On a higher level, the kubernetes model is a bit shoehorned into the docker model here. The "unit" of kubernetes is a pod. Ideally we'd want to monitor pods no matter how many containers are running or not running in those pods. That is, if some container fails to start, or docker is down, the monitoring should still apply. That's a bit outside the scope of this PR to fix though.

@hkaj
Copy link
Member

hkaj commented Oct 19, 2016

Using the kubernetes container name is fine since it's also quick to extract and guaranteed to exist. Go for it 👍
The reason for the multi-container model is that to resolve template variables we use the docker inspect of the container (by looking through all ports used by the container and selecting the right one for %%port%% for instance). So tying the template to a container rather than a pod seems to make more sense (but I might be wrong).
The thing is service discovery is not targeting only kubernetes but container environments at large. So it is likely to stay container-centric and not pod-centric, at least for now. We have some other work in progress to make pod/service/deployment monitoring easier and more powerful though. Notably improvements to the kubernetes integration. So stay tuned ;)

@mikekap
Copy link
Contributor Author

mikekap commented Oct 21, 2016

Done. I haven't tested this outside of unit tests yet since I would need to migrate my existing usage.

@olivielpeau olivielpeau added this to the 5.10.0 milestone Oct 21, 2016
Copy link
Member

@hkaj hkaj left a comment

Choose a reason for hiding this comment

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

Thanks for the update @mikekap
I left one small comment in the code, looks good otherwise. I'll give it a try tomorrow.

@@ -223,6 +230,14 @@ def _get_additional_tags(self, container_inspect, *args):
tags.append('pod_name:%s' % pod_metadata.get('name'))
return tags

def _get_kube_container_name(self, c_id):
pods = self.kubeutil.retrieve_pods_list().get('items', [])
Copy link
Member

Choose a reason for hiding this comment

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

not super excited about calling this for every container, can we maybe build the mapping c_id -> c_name once before entering this loop https://github.com/mikekap/dd-agent/blob/4790dd8df8417900a62fb776f0e10fa3491fb749/utils/service_discovery/sd_docker_backend.py#L258 to reduce api calls? It should be fine since it's coming from the kubelet but with nodes running hundreds of containers if there's a hiccups we'll feel it.

@hkaj
Copy link
Member

hkaj commented Oct 27, 2016

Looks like this scheme is not going to work:

The Pod "redis-django" is invalid:
* metadata.annotations: Invalid value: "com.datadoghq.sd/frontend/check_names": must match the regex ([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9] (e.g. 'MyName' or 'my.name' or '123-abc') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName'

Let's use dots instead of slashes?

@mikekap
Copy link
Contributor Author

mikekap commented Oct 27, 2016

Sounds good. I'm a bit swamped this week so I'll get around to making those changes on the weekend if you don't mind. Sorry for the delay

@@ -26,7 +26,8 @@
INIT_CONFIGS = 'init_configs'
INSTANCES = 'instances'
KUBE_ANNOTATIONS = 'kube_annotations'
KUBE_ANNOTATION_PREFIX = 'com.datadoghq.sd/'
KUBE_CONTAINER_NAME = 'kube_container_name'
KUBE_ANNOTATION_PREFIX = 'sd.datadoghq.com'
Copy link
Member

Choose a reason for hiding this comment

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

typo I guess, it's com.datadoghq.sd

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I may have snuck this one in there. I changed the annotation to be service-discovery.datadoghq.com/checks.XXXX. I've realized that kube annotations don't follow the "reverse dns" scheme at all and I didn't want to deviate from that. Let me know if you'd prefer to change it back to what you were suggesting.

…r the first container in a pod.

Otherwise you could end up instantiating several instances of the same check when the pod contains more than one container.
Also get rid of the trace_config madness
Particularly this goes from O(4-5N) calls to the kubernetes API to 1.
@mikekap mikekap closed this Nov 2, 2016
@mikekap mikekap reopened this Nov 2, 2016
@remh remh added this to the 5.10.1 milestone Nov 2, 2016
@remh remh removed this from the 5.10.0 milestone Nov 2, 2016
@truthbk truthbk modified the milestones: 5.11.0, 5.10.1 Nov 17, 2016
Copy link
Member

@hkaj hkaj left a comment

Choose a reason for hiding this comment

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

Hey @mikekap sorry for the delay here. I left 2 comments, once they're fixed we'll merge this. Don't worry about the conflicts I'll resolve them at merge time. It's about code I changed recently.

Thanks a bunch!

check_names = json.loads(kube_annotations[KUBE_ANNOTATION_PREFIX + CHECK_NAMES])
init_config_tpls = json.loads(kube_annotations[KUBE_ANNOTATION_PREFIX + INIT_CONFIGS])
instance_tpls = json.loads(kube_annotations[KUBE_ANNOTATION_PREFIX + INSTANCES])
prefix = '{}/{}.'.format(KUBE_ANNOTATION_PREFIX, kube_container_name)
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't require the container name here, it's already defined in the metadata.
prefix = '{}.'.format(KUBE_ANNOTATION_PREFIX) is enough.
and the annotations would look like

annotations:
        service-discovery.datadoghq.com.check_names: '["supervisord"]'
        service-discovery.datadoghq.com.init_configs: '[{}]'
        service-discovery.datadoghq.com.instances: '[{"name": "super", "host": "%%host%%"}]'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

HK - I think you might have forgotten some of the context from above. The main point of doing this PR is so we instantiate the checks above once per pod rather than once per container (since pods can have a few containers and you can't annotate containers directly in kubernetes; this was previously creating N instances of the checks above where N=containers in a pod).

Copy link
Member

Choose a reason for hiding this comment

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

welp, yes indeed.

"""Get the list of checks applied to a container from the identifier_to_checks cache in the config store.
Use the DATADOG_ID label or the image."""
inspect = state.inspect_container(c_id)
Copy link
Member

Choose a reason for hiding this comment

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

So we actually have another issue to take care of here, which is that if the container which the ID is from is already removed at this point, inspect will be None and we won't reload anything although there's a check that now fails on a missing container.

To be clear it's a pre-existing bug, not yours, but let's fix it here. In this situation we can't really decide which check to reload since the container isn't here anymore, so I think we should trigger a full reload.
Adding this block here does the job:

        # if the container was removed we can't tell which check is concerned
        # so we have to reload everything
        if not inspect:
            self.reload_check_configs = True
            return

@hkaj hkaj merged commit 3ae3d38 into DataDog:master Nov 28, 2016
@hkaj
Copy link
Member

hkaj commented Nov 28, 2016

🎉 thanks again @mikekap

@mikekap mikekap deleted the kube-sd-only-one branch November 28, 2016 18:45
@mikekap
Copy link
Contributor Author

mikekap commented Nov 28, 2016

Wow - thanks for getting this in @hkaj! I was just coming back from the break and getting ready to tackle merging this. Sorry you had to go through that.

@hkaj
Copy link
Member

hkaj commented Nov 28, 2016

No worries, it was an easy conflict. I'll finish polishing a documentation update for it and we should be good to go!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants