-
Notifications
You must be signed in to change notification settings - Fork 17
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 backwards compatible CRD versioning #240
Add backwards compatible CRD versioning #240
Conversation
This gets the code generator to create a client for us.
So that it does not depend on the specific version of the package.
Required by the client generator.
Present in the sample-controller[0]. [0]: https://github.com/kubernetes/sample-controller/blob/cec2a01dbb602b4c3c44260c6728f7247ef662cb/pkg/apis/samplecontroller/v1alpha1/register.go
Instructs the client generator to create a client for the Habitat resource.
We now rely on the code-generator.
To maintain backwards compatibility with clusters created using Deployments.
The backwards compatible version, still using Deployments.
This was generated using the following command: `./generate-groups.sh deepcopy,client github.com/kinvolk/habitat-operator/pkg/client github.com/kinvolk/habitat-operator/pkg/apis "habitat:v1beta1"`
The code-generator had somehow failed to add the habitat import.
The StatefulSet-based controller is v1beta2, the Deployment-based one will be v1beta1.
Scoped by version, in case we decide to change it.
Because each controller should create a CRD of the appropriate version.
We also need informers, so we might as well create all of them.
Instead of creating the watchers by hand, we use the auto-generated factories.
We only run tests against the latest version, and assume that the previous ones have already been tested.
Use methods that are specific to the Habitat type.
We were checking the wrong variable, which by this time was `nil`, so we were returning `nil, nil`.
The name we were passing was of the form `name/version`, which is invalid. What we actually want is of the `resource.group` form.
Instead of master, which was not compatible with client-go 6.0.0.
Straight from commit b6c1929.
It wasn't used.
For consistency with the v1beta2 controller.
Re: your comments on Once that happens, we will simply remove the If we introduce a completely separate field for the spec, once support for multiple versions lands, we will have to support looking for two different fields ( |
Making backward-incompatible type changes is not a thing we plan to do, yes, but you never know. Anyway, I was opting for a separate spec struct per version, so we don't have a struct with bag of fields that are supported in various versions. Just want to avoid having someone using the
Who's "we"? Developers? We will need to support it anyway for some time, even if we release a proper If "we" are users, then the change will involve renaming
With introduction of |
So to summarize, your wish to use Couldn't we solve that with documentation? Having both Or alternatively, could we e.g. add a subkey to the spec instead, like |
In practice this would look like: apiVersion: habitat.sh/v1beta1
kind: Habitat
metadata:
name: example-persistent-habitat
customVersion: v1beta2
spec:
# the core/redis habitat service packaged as a Docker image
image: kinvolk/redis-hab
count: 3
# the presence of this key activates persistent storage
v1beta2:
persistentStorage:
# the size of the volume that will be mounted in each Pod
size: 1Gi
# a StorageClass object with name "standard" must be created beforehand by
# the cluster administrator
storageClassName: example-sc
# the location under which the volume will be mounted
mountPath: /tmp/foobar
env:
- name: HAB_REDIS
# this is needed to make redis accept connections from other hosts
# see https://redis.io/topics/security#protected-mode
# NOTE: do not use this setting in a production environment
value: '{ "protected-mode": "no" }'
service:
name: redis
topology: leader
# if not present, defaults to "default"
group: foobar
|
We could solve it with documentation, but how would documentation look like if we have a single
The
But I'm not really sure how that flies with yaml and client-go and whatnot. So I thought that repeating custom version in spec name is the least bad thing (better than something like
This |
Ah, now it's clear what you meant by I rather imagined something like:
and
|
In a hypothetical
i.e. each field would be related to a release. Is it ideal? No. But we are working around a deficiency in Kubernetes.
This is a good point. |
What does the upgrade path look like? When multiple-version CRDs land
When we can drop support for this hack
|
About hypothetical About CRD versioning: This is for the future, but I suppose that when CRD versioning lands, we don't do anything immediately I think. I would say that if we need to make another breaking change, then we switch to CRD versioning at the same time. So if the latest custom version is |
67eb76f
to
0b908a8
Compare
Instead of defining v1beta2 fields in the Spec struct, we add a completely separate spec.v1beta2 struct for v1beta2. The v1beta2 controller will only access this struct, whereas other controllers will simply ignore it. This allows us to introduce backwards-incompatible changes to fields (e.g. switching to a pointer type).
8c263e1
to
bede304
Compare
Especially on Travis, where we can't SSH into the machines, this will help with debugging failing builds.
bede304
to
0bb38db
Compare
PTALA. |
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.
Some nits.
Not-sure-if-blocking item: Both operators are taking care of the peer watch file config map. Hopefully they won't end up in a loop endlessly updating it, because the other operator changed its contents.
Do we have something like a "string enum"? We could use one for the customVersion
field to give an error when somebody writes a wrong value there, like v1beta3
when there is no such custom version yet, or make a typo c1beta2
.
examples/leader/habitat.yml
Outdated
# count must be at least 3 for a leader-follower topology | ||
count: 3 | ||
service: | ||
name: consul |
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.
Heh, while at it, change this to "redis", please. :)
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.
Sorry 😅
pkg/apis/habitat/v1beta1/types.go
Outdated
@@ -61,7 +61,8 @@ type HabitatSpec struct { | |||
// Optional. | |||
Env []corev1.EnvVar `json:"env,omitempty"` | |||
// +optional |
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.
This +optional
should be the last comment line of the field, right?
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 made another comment about it, later. This one was made on a commit and github marked it as outdated immediately.
pkg/apis/habitat/v1beta1/types.go
Outdated
@@ -61,7 +61,8 @@ type HabitatSpec struct { | |||
// Optional. | |||
Env []corev1.EnvVar `json:"env,omitempty"` | |||
// +optional | |||
PersistentStorage *PersistentStorage `json:"persistentStorage,omitempty"` | |||
// V1beta2 are fields for the v1beta2 type. | |||
V1beta2 *V1beta2 `json:"v1beta2"` |
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.
Capitalize "beta" in type name? So it is V1Beta2
? WDYT?
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 made another comment about it, later. This one was made on a commit and github marked it as outdated immediately.
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.
No, this is the capitalization format used in kubernetes.
I guess they consider 1beta1
one word.
@@ -18,15 +18,15 @@ image: linux | |||
$(SUDO) docker build -t "$(IMAGE):$(TAG)" . | |||
|
|||
test: | |||
go test -v $(shell go list ./... | grep -v /vendor/ | grep -v /test/) | |||
go test $(shell go list ./... | grep -v /vendor/ | grep -v /test/) |
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.
Why dropping -v
from the tests? Will you get any meaningful output without it? You wrote "too much information" in the commit message, but recently you asked me about the output from tests, so that left me wondering…
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.
You're right that the commit message is maybe not very descriptive, but the point is that these are the unit tests, and the output was basically useless IMO.
Where we don't see any output for a very long time is in e2e tests.
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.
Oh, right. Those are unit tests.
No output in e2e tests may be caused by the fact the the output from the test case is printed when the test case finishes. So if the test case takes long to complete, you won't see anything for a long time.
pkg/apis/habitat/v1beta1/types.go
Outdated
@@ -51,8 +60,9 @@ type HabitatSpec struct { | |||
// The EnvVar type is documented at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#envvar-v1-core. | |||
// Optional. | |||
Env []corev1.EnvVar `json:"env,omitempty"` | |||
// Optional. | |||
PersistentStorage *PersistentStorage `json:"persistentStorage,omitempty"` | |||
// +optional |
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.
Shouldn't this line be a last one?
} | ||
|
||
func validateCustomObject(h habv1beta1.Habitat) error { | ||
spec := h.Spec.V1beta2 |
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 assume that this is the first time we access the fields of spec.V1beta2
, so a nil
pointer check should be in place here, right?
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.
Good catch. Hopefully we can introduce schema validation soon.
if err != nil { | ||
level.Error(hc.logger).Log("msg", "Could not find Habitat for StatefulSet", "name", d.Name) | ||
level.Error(hc.logger).Log("msg", "Could not find Habitat for StatefulSet", "name", sts.Name) | ||
return | ||
} | ||
|
||
hc.enqueue(h) |
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.
Call to the checkCustomVersionMatch
function is missing here. We don't want the v1beta2
operator to handle v1beta3
stateful sets. I'm wondering if this checkCustomVersionMatch
could be added to the enqueue
function to have a single place where this thing is checked.
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.
Good point, I'll do that.
if err != nil { | ||
level.Error(hc.logger).Log("msg", "Could not find Habitat for StatefulSet", "name", d.Name) | ||
level.Error(hc.logger).Log("msg", "Could not find Habitat for StatefulSet", "name", sts.Name) | ||
return | ||
} | ||
|
||
hc.enqueue(h) |
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.
Ditto.
if err != nil { | ||
// Could not find Habitat, it must have already been removed. | ||
level.Debug(hc.logger).Log("msg", "Could not find Habitat for StatefulSet", "name", d.Name) | ||
level.Debug(hc.logger).Log("msg", "Could not find Habitat for StatefulSet", "name", sts.Name) | ||
return | ||
} | ||
|
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.
Ditto.
pkg/apis/habitat/v1beta1/types.go
Outdated
PersistentStorage *PersistentStorage `json:"persistentStorage,omitempty"` | ||
// +optional | ||
// V1beta2 are fields for the v1beta2 type. | ||
V1beta2 *V1beta2 `json:"v1beta2"` |
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.
Capitalize b
in beta
so we have V1Beta2
? WDYT?
To reduce the number of services we need to package.
Per requirements.
Instead of having it spread around too many places.
PTALA. |
fb7cdd7
to
eacbcfb
Compare
LFAD. |
OMG. |
This PR does several things.
CustomVersion
There is currently no way to run multiple versions of a CRD. The
CustomVersion
field works around this by "overloading" the Habitat type.All controllers receive all
Habitat
objects, but then decide which ones to skip based on the value (and presence) of theCustomVersion
field.Code generator
This PR also reorganizes the
pkg
tree to match the expectations of the code-generator. The code-generator is used to generate client, listers and watchers.In the future, we will be able to maintain multiple versions more easily thanks to this structure.
The use of the code-generator also implied changes in the controller, because the structure of the client, listers and watchers has changed.
Backwards compatibility
The new field allows us to maintain backwards-compatibility with clusters that are already running
Deployment
-backedHabitat
objects.Closes #215.
Closes #216.