-
Notifications
You must be signed in to change notification settings - Fork 297
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
feat(plus-17): add StatefulSet construct #400
Conversation
c8086df
to
7c49942
Compare
@iliapolo Any thoughts on this one? I know there could probably be a set of higher level constructs still for some common statefulset cases, but this seems like a pretty good place to start as it basically mirrors Deployment. |
@abierbaum only glanced through it, looks ok, i'll take a closer look soon. Thanks! |
* | ||
* @default undefined - allocate a default service based upon servicePort and serviceName. | ||
*/ | ||
readonly service?: ServiceProps; |
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.
We can just accept a Service
here instead, it will prevent some logic duplication and keep things more modular. Its ok to require the caller to instantiate other constructs.
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 thought about that but it ends up making the initialization more complex. I was trying to get an API as easy to use as the deployment expose() call.
For example when I am using this in my production chart I end up calling as below. (note: I could have left off the service props in this case since this is default, but I put it there to be explicit for other people that come along later). The key is that people can just say the service name and port and get a good statefulset with standard options, but they can override the type if they want to do something like a node port or something like that.
const redis_set = new kplus.StatefulSet(this, 'redis-set', {
serviceName: 'redis-srv',
replicas: 2,
containers: [redis_container],
servicePort: redis_port,
service: {
type: kplus.ServiceType.CLUSTER_IP,
clusterIP: undefined, // force it to allocate
},
});
If the user had to allocate the Service it would look like:
const redis_srv = new Service(this, 'RedisService', {
metadata: {name: 'redis-srv'},
type: ServiceType.CLUSTER_IP,
clusterIP: undefined,
ports: [{port: redis_port}],
})
const redis_set = new kplus.StatefulSet(this, 'redis-set', {
serviceName: 'redis-srv',
replicas: 2,
containers: [redis_container],
servicePort: redis_port,
service: redis_srv
});
And the callee would have to make sure the service name strings match and the ports match. Seems like this is the type of thing we would want to wrap to make it simpler to handle and then if they need to modify the service they can patch it.
What do you think though? I probably missed something in this one.
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 thought about that but it ends up making the initialization more complex. I was trying to get an API as easy to use as the deployment expose() call.
This is a little different, because a stateful set cannot exist without a service, as opposed to a deployment. I understand the idea was to implicitly create a service, mimicking the expose
behavior, but this should be implemented by accepting an optional Service
, and defaulting to auto creating it using the information of the stateful set (i.e assigning some name based on the stateful set name, and looking up the correct ports using the containers of the stateful set).
We can definitely do something like that in the future, but I think it's better to think about that in a subsequent PR.
And the callee would have to make sure the service name strings match and the ports match.
Why? what I was thinking the caller would have to do is this:
const redis_set = new kplus.StatefulSet(this, 'redis-set', {
replicas: 2,
containers: [redis_container],
service: redis_srv
});
Notice that serviceName
and servicePort
is not passed, because they can be extracted from the service object using service.name
and service.ports
. Which means we don't delegate any burden on the caller except for calling new Service
.
Will that not work?
* | ||
* @default undefined | ||
*/ | ||
readonly servicePort?: number; |
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.
Won't be needed if we accept a Service
.
* | ||
* @default undefined | ||
*/ | ||
readonly serviceName?: string; |
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
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.
We would still need it if the Service was optional. But if the service was required we could remove it yes.
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.
Yes, my plan was to make it required at the moment.
* | ||
* Returns a a copy. Use `selectByLabel()` to add labels. | ||
*/ | ||
get labelSelector(): Record<string, string> { |
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.
To be consistent:
get labelSelector(): Record<string, string> { | |
public get labelSelector(): Record<string, string> { |
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.
Got it. I am so used to my production projects just adding this type of thing automatically using prettier and tslint that I forgot. Sorry.
This PR has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled. |
Dont close |
235ce24
to
2224781
Compare
@iliapolo Sorry for the delay. I got this working to where I was able to use it in production and forgot to come back to clean it up for upstream consumption. I have reviewed your comments, made some fixes, and added some feedback. Please let me know your thoughts. I would love to get this through this week. |
* | ||
* @default undefined - allocate a default service based upon servicePort and serviceName. | ||
*/ | ||
readonly service?: ServiceProps; |
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 thought about that but it ends up making the initialization more complex. I was trying to get an API as easy to use as the deployment expose() call.
This is a little different, because a stateful set cannot exist without a service, as opposed to a deployment. I understand the idea was to implicitly create a service, mimicking the expose
behavior, but this should be implemented by accepting an optional Service
, and defaulting to auto creating it using the information of the stateful set (i.e assigning some name based on the stateful set name, and looking up the correct ports using the containers of the stateful set).
We can definitely do something like that in the future, but I think it's better to think about that in a subsequent PR.
And the callee would have to make sure the service name strings match and the ports match.
Why? what I was thinking the caller would have to do is this:
const redis_set = new kplus.StatefulSet(this, 'redis-set', {
replicas: 2,
containers: [redis_container],
service: redis_srv
});
Notice that serviceName
and servicePort
is not passed, because they can be extracted from the service object using service.name
and service.ports
. Which means we don't delegate any burden on the caller except for calling new Service
.
Will that not work?
* | ||
* @default undefined | ||
*/ | ||
readonly serviceName?: string; |
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.
Yes, my plan was to make it required at the moment.
throw new Error('Cannot build a stateful set with a Service without ports.'); | ||
} | ||
|
||
this._service = new Service(this, 'Service', { |
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 also makes it impossible to accept an existing service in the future. Since a stateful set only needs a service name, I imagine that eventually it should accept an IService
, which we don't currently have.
}); | ||
|
||
if (!props.servicePort && !props.service?.ports) { | ||
throw new Error('Cannot build a stateful set with a Service without ports.'); |
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 will move to the _toKube
method and validate the ports
property of the given service object.
This PR has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled. |
Dont close |
e72dc00
to
cdec555
Compare
@iliapolo I think this last update should address the core issue pretty well. I changed it to take a Service as input. After seeing it in action, I agree this is a good change for this use case. note: I rebased off latest master. |
This may have been a good idea, but it makes things painful when trying to apply changes for statefulset's where you want to have an assigned ClusterIP automatically. You end up with immutable change warnings because you really don't want to pass '', you want to pass undefined so you just get whatever it assigned.
Co-authored-by: Eli Polonsky <[email protected]>
cdec555
to
17b2947
Compare
@iliapolo Have you had a chance to get back to this one yet? |
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.
@abierbaum this is great. Just some minor nit-picks around naming conventions :)
import { Volume } from './volume'; | ||
|
||
|
||
export enum PodManagementPolicy { |
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.
Add some doc strings. Should also be available in the k8s spec.
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.
Updated.
|
||
const chart = Testing.chart(); | ||
|
||
const service = new kplus.Service(chart, 'test_service', {ports: [{port: 80}] }); |
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.
const service = new kplus.Service(chart, 'test_service', {ports: [{port: 80}] }); | |
const service = new kplus.Service(chart, 'TestService', {ports: [{port: 80}] }); |
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.
Changed.
expect(srv_spec.type).toEqual(kplus.ServiceType.CLUSTER_IP); | ||
expect(srv_spec.clusterIP).toBeUndefined(); | ||
expect(srv_spec.ports![0].port).toEqual(80); |
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 doesn't seem related to a statefulset
test..if its not covered in the service
tests, lets add it there, but if it is, we can drop it.
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.
Removed.
|
||
const resources = Testing.synth(chart); | ||
const srv_spec = resources[0].spec; | ||
const set_spec = resources[1].spec; |
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.
const set_spec = resources[1].spec; | |
const setSpec = resources[1].spec; |
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.
changed.
expect(srv_spec.type).toEqual(kplus.ServiceType.NODE_PORT); | ||
expect(srv_spec.ports![0].port).toEqual(9000); |
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.
again, doesn't seem necessary.
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.
removed.
@iliapolo I have made the changes suggested. I apologize to all for the amount time it took to respond and get everything completed. |
Your pull request will be updated and merged automatically (do not update manually). |
Pull request has been modified.
@abierbaum No worries, i've been lagging as well :) Thanks so much for this PR, its awesome. |
Your pull request will be updated and merged automatically (do not update manually). |
This is a first pass at a statefulset construct. It works well for the cases I need and should serve well as a starting point. Note that I copied the concepts from Deployment so it should look pretty familiar.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license