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

Use official Kubernetes python client #41

Merged
merged 12 commits into from
Apr 21, 2017
Merged

Use official Kubernetes python client #41

merged 12 commits into from
Apr 21, 2017

Conversation

danielfrg
Copy link
Contributor

@danielfrg danielfrg commented Apr 14, 2017

This PR changes the objects.pyto use the official python kubernetes client. Hopefully this makes k8s specs creation a little easier by adding a pure python dependency.

This also solves #6 by adding a simple nodeSelector to the Pod spec.

I also tried to solve #1 but using /user/{userid} as a path for liveliness return a 404 because of Hub redirects or something (not 100% sure) any other ideas on how to check status of the Notebook server?

@yuvipanda
Copy link
Collaborator

Thank you for working on this! This looks great, am reviewing it now.

Copy link
Collaborator

@yuvipanda yuvipanda left a comment

Choose a reason for hiding this comment

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

It looks ok to me, but I'd like the following changes:

  1. Make using kubernetes client be a separate commit from adding more features. Ideally the new features would be a different PR, but separate commits are also ok. This makes it easier to merge, review and test.
  2. Fix the tests.

This is great work - thanks for taking it on!

pod.metadata = V1ObjectMeta()
pod.metadata.name = name
pod.metadata.labels = {}
pod.metadata.labels.update({"name": name})
Copy link
Collaborator

Choose a reason for hiding this comment

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

why not just have this be one line?

Copy link
Collaborator

Choose a reason for hiding this comment

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

pod.metadata.labels = labels.copy()
pod.metadata.labels['name'] = name

seems clearer and possibly faster?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, thats better. Done

@@ -11,13 +33,15 @@ def make_pod_spec(
run_as_uid,
fs_gid,
env,
node_selector,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you split out the additional features into separate commits (or even a separate PR) so it's easier to review? I'll leave commnets about them here too tho

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 have removed the node_selector things I will make another PR for that.

notebook_container.resources = V1ResourceRequirements()
notebook_container.resources.requests = {"cpu": cpu_guarantee, "memory": mem_guarantee}
notebook_container.resources.limits = {"cpu": cpu_limit, "memory": mem_limit}
# notebook_container.liveness_probe = V1Probe()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's get the commented code out? Also would be nice if it were a separate commit :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, i wanted to have it there temporally in case you wanted it on this PR. It's gone.

)

working_dir = Unicode(
"/home",
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the default should be None, which should make it use whatever is specified in the Docker image (which is the current default). Should probably be called singleuser_working_dir as well (I've been trying to do that for all new properties)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes, I agree that its a better default. Also renamed the property to singleuser_working_dir.

@@ -109,6 +108,26 @@ def _namespace_default(self):
"""
).tag(config=True)


node_selector = Dict(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this be a separate commit / PR? We need to be careful to support both simple property based selectors and more complex ones...

@danielfrg
Copy link
Contributor Author

I have fixed the tests and removed the node_selector logic and I will make a separate PR for that Issue.

You can probably Squash and Merge this one if you want to keep history clean about changes.

@@ -109,6 +108,13 @@ def _namespace_default(self):
"""
).tag(config=True)

singleuser_working_dir = Unicode(
help="""
Copy link
Collaborator

Choose a reason for hiding this comment

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

set the default explicitly to None and also allow_none?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@yuvipanda
Copy link
Collaborator

(I'll take a more detailed look + test on Monday!)

Copy link
Collaborator

@yuvipanda yuvipanda left a comment

Choose a reason for hiding this comment

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

More minor changes that I missed on first round - sorry about that! Otherwise it looks good, and I want us to test this on a couple of different k8s clusters before merging. What cluster have you been testing this against? GKE or minikube?

notebook_container.ports.append(port_)
notebook_container.env = []
for key, value in env.items():
env_ = V1EnvVar()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could just be:

notebook_container.env = [V1EnvVar(k, v) for k, v in env.items()]

no? https://github.com/kubernetes-incubator/client-python/blob/84bf6cc9dc206cb3314a5b4d2fa1a9c2fa2c5137/kubernetes/client/models/v1_env_var.py#L24 says it takes the properties as params to init

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, thats good. Done

@@ -433,6 +442,15 @@ def _hub_connect_port_default(self):
"""
)

user_storage_match_labels = Dict(
help="""
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the default for this? I'm trying to understand what exactly this feature does - does it allow binding to an existing PVC that isn't created by this? Under what circumstances would I use 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.

So by default its nothing so the default behavior should be the same.

No, it still creates a PVC spec as it was doing before. This new property basically it just passes the matchLabels field from the PVC spec so its possible to filter between existing PV. Lets say you have multiple PV you can select which one you want.

From: https://kubernetes.io/docs/concepts/storage/persistent-volumes/ - PersistentVolumeClaims section.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, so is it for the case where the PV is not dynamically created by using a StorageClass?

Copy link
Collaborator

Choose a reason for hiding this comment

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

ok! I think we should set the default explicitly, and explain why you should use this and when in the help. The current help text isn't sufficient, IMO.

Also in the future, it'd be nice to have these as a separate PR, rather than as part of one :) Makes it easier to review.

I'll do some testing and try to get this merged tomorrow!

Copy link
Collaborator

@yuvipanda yuvipanda Apr 18, 2017

Choose a reason for hiding this comment

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

The more I think about it, the more I think we should take it out now. I think way to include this would be:

  1. Write a 'LabelSelector' trait type, that can be used to express both matchLabels and matchExpressions
  2. Use that for this and for nodeSelectors

I think that's the cleanest and most future compatible way of doing this. what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It should probably also support expanding {username} and friends in the label values, to provide more targeted matching. Let's definitely get it out of this 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.

Ok, its gone now. Yes, it was for that case where you already had existing PVs. This is the way I have always used PV + PVC, more manual than the automatic creation.

# This makes sure that we don't accidentally give access to the whole
# kubernetes API to the users in the spawned pods.
# See https://github.com/kubernetes/kubernetes/issues/16779#issuecomment-157460294
hack_volumes = [{
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this belongs here, and not in the make_pod_spec. We could add an option to allow service account access in here when it's here, and this also keeps the make_pod_spec agnostic about what we do. Someone has actually forked this to make this disabling optional, and that's easier to do when this is here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I agree with keeping make_pod_spec independent of the hack i just wanted to keep the kubernetes_client code in one place. I put it back now.

cmd=real_cmd,
run_as_uid=singleuser_uid,
fs_gid=singleuser_fs_gid,
env=self.get_env(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Passing named args is much cleaner, thank you for moving it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍


pod.spec.containers = []

notebook_container = V1Container()
Copy link
Collaborator

Choose a reason for hiding this comment

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

We could also construct notebook_container this way (passing params to init) since that supports it too (https://github.com/kubernetes-incubator/client-python/blob/84bf6cc9dc206cb3314a5b4d2fa1a9c2fa2c5137/kubernetes/client/models/v1_container.py). I'm not super attached to it here, though - because there's a lot more params.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I agree, personally I don't like to pass 3+ params to a function.

@danielfrg
Copy link
Contributor Author

This changes I have only tested on minikube but I still yet to see something in k8s that doesn't work from minukube to GCP.

@@ -433,6 +442,15 @@ def _hub_connect_port_default(self):
"""
)

user_storage_match_labels = Dict(
help="""
Copy link
Collaborator

@yuvipanda yuvipanda Apr 18, 2017

Choose a reason for hiding this comment

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

The more I think about it, the more I think we should take it out now. I think way to include this would be:

  1. Write a 'LabelSelector' trait type, that can be used to express both matchLabels and matchExpressions
  2. Use that for this and for nodeSelectors

I think that's the cleanest and most future compatible way of doing this. what do you think?

Copy link
Collaborator

@yuvipanda yuvipanda left a comment

Choose a reason for hiding this comment

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

Tested locally, is ok! \o/

@yuvipanda yuvipanda merged commit 228799a into jupyterhub:master Apr 21, 2017
@yuvipanda
Copy link
Collaborator

Thank you for the patch!

@yuvipanda
Copy link
Collaborator

I'm going to test this now with https://github.com/jupyterhub/helm-chart

@yuvipanda
Copy link
Collaborator

I filed #43 with further options on how to move forward getting rid of our custom code in favor of the official client.

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.

2 participants