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

Pruneable CRs #312

Merged
merged 2 commits into from
Aug 3, 2018
Merged

Pruneable CRs #312

merged 2 commits into from
Aug 3, 2018

Conversation

dturn
Copy link
Contributor

@dturn dturn commented Jul 6, 2018

Follow-up to #306 to support prunable CRDs.

Thoughts / questions:

  • Should custom_resource_definitions be a class method on CRD?
  • prunable? checks the labels as well as the annotations because the annotation wasn't sticking between deploys

definition: r_def, statsd_tags: @namespace_tags)

unless KubernetesDeploy.const_defined?(crd.kind)
KubernetesDeploy.const_set crd.kind, Class.new(KubernetesDeploy::CustomResource)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we avoid making classes? This could cause problems when used as a library against two different clusters.

Also this functions should be moved to its own class.

name: widgets.api.foobar.com
annotations:
kubernetes-deploy.shopify.io/metadata:
prunable: "true"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this malformed? (Is that why I needed the label)

Copy link
Contributor

@stefanmb stefanmb left a comment

Choose a reason for hiding this comment

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

This approach looks good. I think not querying the API to obtain all resources is reasonable, though it limits us to expressing deploy dependencies only between explicitly modelled resources (i.e. ones that correspond to classes we defined).

If it were up to me I would keep the DAG representation of the the deploy list, I think it's more conceptually correct and a better way to represent the problem than a linearly walking through the list. The list may be sufficient for our immediate purposes, but it seems like a gross simplification to only save a bit of code.

@@ -27,6 +27,8 @@
bucket
stateful_set
cron_job
custom_resource_definition
custom_resource
Copy link
Contributor

Choose a reason for hiding this comment

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

In the initial PR (https://github.com/Shopify/kubernetes-deploy/pull/188/files) this static list was removed in favour of a DAG representation. Do we plan to reintroduce the graph based representation?

Some advantages of the DAG:

  • Only expresses ordering between dependencies that actually matter (e.g. multiple solutions to topological sort are valid)
  • Can express dependencies between custom resources, a list that considers all CRs of equal deploy priority cannot.

Copy link
Contributor

Choose a reason for hiding this comment

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

Are you thinking of the predeploy sequence? I agree that it would be awesome to use a DAG, but this PR doesn't touch the predeploy list. (now anyway... maybe a past version I didn't look at did?)

end

def crds(sync_mediator)
sync_mediator.get_all(CustomResourceDefinition.kind).map do |r_def|
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we are not walking the entire API tree we can only express dependencies between resources that are explicitly modelled in KubernetesDeploy (i.e. there is a class defined for them in this project) or CRDs.

@dturn
Copy link
Contributor Author

dturn commented Jul 12, 2018

If it were up to me I would keep the DAG representation of the the deploy list, I think it's more conceptually correct and a better way to represent the problem than a linearly walking through the list. The list may be sufficient for our immediate purposes, but it seems like a gross simplification to only save a bit of code.

The goal of this PR is to be the minimal set of features to make pruning work. I think the dag is enough code to justify its own PR, if/when we decide to take it on.

@@ -27,6 +27,8 @@
bucket
stateful_set
cron_job
custom_resource_definition
custom_resource
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you thinking of the predeploy sequence? I agree that it would be awesome to use a DAG, but this PR doesn't touch the predeploy list. (now anyway... maybe a past version I didn't look at did?)

@@ -0,0 +1,14 @@
# frozen_string_literal: true
module KubernetesDeploy
class CustomResource < KubernetesResource
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't used, is it?

end

def prunable?
json_annotation = @definition.dig("metadata", "annotations", "kubernetes-deploy.shopify.io/metadata")
Copy link
Contributor

Choose a reason for hiding this comment

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

What are your thoughts (@stefanmb too) on one big annotation vs. many smaller ones? Or an inbetween option that groups related items (e.g. predeploy/dependencies, success/failure/timeout)?

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'd prefer to use smaller ones (e.x. kubernetes-deploy.shopify.io/prunable & kubernetes-deploy.shopify.io/predeploy`) that don't require us to store json strings.

@context = context
@logger = logger
@namespace_tags = namespace_tags
@cr_mapping = {}
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems unused


def crds(sync_mediator)
sync_mediator.get_all(CustomResourceDefinition.kind).map do |r_def|
KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd just do CustomResourceDefinition.new directly. build is basically just a class lookup mechanism.

names:
kind: Mail
listKind: MailList
plural: mails
Copy link
Contributor

Choose a reason for hiding this comment

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

no need for the example to carry over our incorrect plural, lol 😄

@@ -0,0 +1,13 @@
apiVersion: apiextensions.k8s.io/v1beta1
Copy link
Contributor

Choose a reason for hiding this comment

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

Adding this to hello-cloud (which strictly speaking is correct--I usually want all supported resources to be in this set) makes me want to build a real demo directory like we've been asked to, so that we don't encourage this gem to be used for globals.

class ResourceDiscoveryTest < KubernetesDeploy::IntegrationTest
def test_non_prunable_crd
assert_deploy_success(deploy_fixtures("resource-discovery", subset: ["shredders.yml"]))
assert_deploy_success(deploy_fixtures("resource-discovery", subset: ["shredders_cr.yml"]))
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these two separate deploys because CRDs aren't in the priority list?

Copy link
Contributor Author

@dturn dturn Jul 19, 2018

Choose a reason for hiding this comment

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

No, the CRD has to be added to the cluster before the CR can pass validation. Since we do validation before we deploy even pre-deploy resources we need two deployments.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, that makes sense.

@@ -1006,4 +1007,19 @@ def test_raise_on_yaml_missing_kind
" datapoint: value1"
], in_order: true)
end

def test_crd_can_be_successful
assert_deploy_success(deploy_fixtures("hello-cloud", subset: ["crd.yml"]))
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this test adds anything over the full_hello test you modified above.

Copy link
Contributor Author

@dturn dturn Jul 19, 2018

Choose a reason for hiding this comment

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

It doesn't, but this file is actually from the base branch (https://github.com/Shopify/kubernetes-deploy/pull/306/files#diff-43c27b92ee93912981b34f7d8b622714R1011) where it now is a legit test. I'll fix all of this up once that PR merges.

@dturn dturn force-pushed the add-crd branch 3 times, most recently from 182b829 to 04734e9 Compare July 21, 2018 00:28
@dturn dturn changed the base branch from add-crd to master July 31, 2018 21:17
@dturn dturn force-pushed the pruneable-cr branch 3 times, most recently from ac762f4 to 426b480 Compare July 31, 2018 21:54
@dturn
Copy link
Contributor Author

dturn commented Jul 31, 2018

This is ready for 👀 again.

Copy link
Contributor

@KnVerey KnVerey left a comment

Choose a reason for hiding this comment

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

Please add README docs for this to the PR :)


def crds(sync_mediator)
sync_mediator.get_all(CustomResourceDefinition.kind).map do |r_def|
CustomResourceDefinition.build(namespace: @namespace, context: @context, logger: @logger,
Copy link
Contributor

Choose a reason for hiding this comment

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

why build instead of new?

@namespace_tags = namespace_tags
end

def crds(sync_mediator)
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you consider using KubeClient for this? I'm thinking the sync mediator doesn't buy us anything special in this case, and it adds complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you worried about the sync mediator's cache? I'd assumed we wanted to discourage direct calls to kubeclient

Copy link
Contributor

Choose a reason for hiding this comment

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

Not really worried about the cache, since we clear that at the beginning of every sync cycle. We definitely want to discourage direct calls to kubeclient from resource classes--they should only be making calls during the sync cycle, and therefore should only need the sync mediator (and can all benefit from its cache if they do). But what we're doing here has nothing to do with the sync cycle and the mediator gives us no benefit. EjsonSecretProvisioner is an example of another piece of independent functionality that also takes care of its own calls.

Copy link
Contributor Author

@dturn dturn Aug 1, 2018

Choose a reason for hiding this comment

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

We can use kubeclient directly, but it would just be duplication of fetch_by_kind. This didn't seem like using the wrong abstraction, so I didn't duplicate the code.

@@ -0,0 +1,12 @@
---
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we enhance the CRD fixture set you added earlier rather than making another?

kind: Widget
metadata:
name: my-first-widget
status: "ok"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why include status? Once it is a proper subresource (1.11+), this will be ignored.

require 'test_helper'

class ResourceDiscoveryTest < KubernetesDeploy::IntegrationTest
def test_non_prunable_crd
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd recommend merging these two tests into a single test that:

  • deploys CRDs of both kinds and asserts that all is well
  • deploys the rest of the fixture set (CRs of all kinds + some non-CR) and asserts that results look good
  • deploys the non-CRs and asserts that the correct CRs were pruned/left alone

Technically that could easily cover the existing CRD success test as well. Since these tests are slow and can be flakey (inherently, because they hit a live minikube), I try to keep tests that are 100% overlapped by others to a minimum. I think we should move those CRD tests to this file regardless.

assert_deploy_success(deploy_fixtures("hello-cloud", subset: ["configmap-data.yml"]))

refute_logs_match("The following resources were pruned: widget \"my-first-shredder\"")
end
Copy link
Contributor

Choose a reason for hiding this comment

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

don't forget to clean up the CRDs. I'm thinking we should make test teardown do this generically by listing and deleting CRDs. That could just be in this suite if we move the other CRD tests over.

assert_deploy_success(deploy_fixtures("resource-discovery", subset: ["shredders.yml"]))
assert_deploy_success(deploy_fixtures("resource-discovery", subset: ["shredders_cr.yml"]))
# Deploy any other non-priority (predeployable) resource to trigger pruning
assert_deploy_success(deploy_fixtures("hello-cloud", subset: ["configmap-data.yml"]))
Copy link
Contributor

Choose a reason for hiding this comment

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

let's add a configmap (or whatever) to the fixture set we're using instead of deploying an unrelated one. The fixture sets are intended to be representing apps, so deploying a different one to the same ns is semantically odd.

metadata:
name: widgets.api.foobar.com
annotations:
kubernetes-deploy.shopify.io/prunable: "true"
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we have a third one that has the annotation, but set to "false"?

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 think its overkill, we already test the case where its not pruned.

Copy link
Contributor

@KnVerey KnVerey left a comment

Choose a reason for hiding this comment

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

🚢 after one last test fix

# Deploy any other non-priority (predeployable) resource to trigger pruning
assert_deploy_success(deploy_fixtures("crd", subset: %w(configmap-data.yml configmap2.yml)))

refute_logs_match("The following resources were pruned: widget \"my-first-mail\"")
Copy link
Contributor

Choose a reason for hiding this comment

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

wouldn't it say mail "my-first-mail" if it had been? This brings up that we should add an assertion that actually confirms the thing is still there by looking at the namespace.

@KnVerey
Copy link
Contributor

KnVerey commented Aug 2, 2018

In case you missed it on my previous review, we'll need docs for this feature. That can be a follow-up if you prefer.

Update readme and changelog
@dturn dturn merged commit 7e19140 into master Aug 3, 2018
@dturn dturn deleted the pruneable-cr branch August 3, 2018 19:02
@dturn dturn mentioned this pull request Aug 8, 2018
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.

3 participants