-
Notifications
You must be signed in to change notification settings - Fork 37
/
Copy pathstateful-set.ts
281 lines (241 loc) · 8.92 KB
/
stateful-set.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import { ApiObject, Lazy, Duration } from 'cdk8s';
import { Construct } from 'constructs';
import * as container from './container';
import { IScalable, ScalingTarget } from './horizontal-pod-autoscaler';
import * as k8s from './imports/k8s';
import * as service from './service';
import * as workload from './workload';
/**
* Controls how pods are created during initial scale up, when replacing pods on nodes,
* or when scaling down.
*
* The default policy is `OrderedReady`, where pods are created in increasing order
* (pod-0, then pod-1, etc) and the controller will wait until each pod is ready before
* continuing. When scaling down, the pods are removed in the opposite order.
*
* The alternative policy is `Parallel` which will create pods in parallel to match the
* desired scale without waiting, and on scale down will delete all pods at once.
*/
export enum PodManagementPolicy {
ORDERED_READY = 'OrderedReady',
PARALLEL = 'Parallel',
}
/**
* Properties for initialization of `StatefulSet`.
*/
export interface StatefulSetProps extends workload.WorkloadProps {
/**
* Service to associate with the statefulset.
*
* @default - A new headless service will be created.
*/
readonly service?: service.Service;
/**
* Number of desired pods.
*
* @default 1
*/
readonly replicas?: number;
/**
* Pod management policy to use for this statefulset.
*
* @default PodManagementPolicy.ORDERED_READY
*/
readonly podManagementPolicy?: PodManagementPolicy;
/**
* Indicates the StatefulSetUpdateStrategy that will be employed to update Pods in the StatefulSet when a revision is made to Template.
*
* @default - RollingUpdate with partition set to 0
*/
readonly strategy?: StatefulSetUpdateStrategy;
/**
* Minimum duration for which a newly created pod should be ready without any of its container crashing,
* for it to be considered available. Zero means the pod will be considered available as soon as it is ready.
*
* This is an alpha field and requires enabling StatefulSetMinReadySeconds feature gate.
*
* @see https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#min-ready-seconds
* @default Duration.seconds(0)
*/
readonly minReady?: Duration;
}
/**
* StatefulSet is the workload API object used to manage stateful applications.
*
* Manages the deployment and scaling of a set of Pods, and provides guarantees
* about the ordering and uniqueness of these Pods.
*
* Like a Deployment, a StatefulSet manages Pods that are based on an identical
* container spec. Unlike a Deployment, a StatefulSet maintains a sticky identity
* for each of their Pods. These pods are created from the same spec, but are not
* interchangeable: each has a persistent identifier that it maintains across any
* rescheduling.
*
* If you want to use storage volumes to provide persistence for your workload, you
* can use a StatefulSet as part of the solution. Although individual Pods in a StatefulSet
* are susceptible to failure, the persistent Pod identifiers make it easier to match existing
* volumes to the new Pods that replace any that have failed.
*
* Using StatefulSets
* ------------------
* StatefulSets are valuable for applications that require one or more of the following.
*
* - Stable, unique network identifiers.
* - Stable, persistent storage.
* - Ordered, graceful deployment and scaling.
* - Ordered, automated rolling updates.
*/
export class StatefulSet extends workload.Workload implements IScalable {
/**
* Number of desired pods.
*/
public readonly replicas?: number;
/**
* Management policy to use for the set.
*/
public readonly podManagementPolicy: PodManagementPolicy;
/**
* The update startegy of this stateful set.
*/
public readonly strategy: StatefulSetUpdateStrategy;
/**
* Minimum duration for which a newly created pod should be ready without
* any of its container crashing, for it to be considered available.
*/
public readonly minReady: Duration;
/**
* @see base.Resource.apiObject
*/
protected readonly apiObject: ApiObject;
public readonly resourceType = 'statefulsets';
public hasAutoscaler = false;
public readonly service: service.Service;
constructor(scope: Construct, id: string, props: StatefulSetProps) {
super(scope, id, props);
this.apiObject = new k8s.KubeStatefulSet(this, 'Resource', {
metadata: props.metadata,
spec: Lazy.any({ produce: () => this._toKube() }),
});
this.service = props.service ?? this._createHeadlessService();
this.apiObject.addDependency(this.service);
this.replicas = props.replicas;
this.strategy = props.strategy ?? StatefulSetUpdateStrategy.rollingUpdate(),
this.podManagementPolicy = props.podManagementPolicy ?? PodManagementPolicy.ORDERED_READY;
this.minReady = props.minReady ?? Duration.seconds(0);
this.service.select(this);
if (this.isolate) {
this.connections.isolate();
}
}
private _createHeadlessService() {
const myPorts = container.extractContainerPorts(this);
const myPortNumbers = myPorts.map(p => p.number);
const ports: service.ServicePort[] = myPorts.map(p => ({ port: p.number, targetPort: p.number, protocol: p.protocol, name: p.name }));
if (ports.length === 0) {
throw new Error(`Unable to create a service for the stateful set ${this.name}: StatefulSet ports cannot be determined.`);
}
// validate the ports are owned by our containers
for (const port of ports) {
const targetPort = port.targetPort ?? port.port;
if (!myPortNumbers.includes(targetPort)) {
throw new Error(`Unable to expose stateful set ${this.name} via a service: Port ${targetPort} is not exposed by any container`);
}
}
const metadata: any = { namespace: this.metadata.namespace };
return new service.Service(this, 'Service', {
selector: this,
ports,
metadata,
clusterIP: 'None',
type: service.ServiceType.CLUSTER_IP,
});
}
/**
* @internal
*/
public _toKube(): k8s.StatefulSetSpec {
return {
replicas: this.hasAutoscaler ? undefined : (this.replicas ?? 1),
serviceName: this.service.name,
minReadySeconds: this.minReady.toSeconds(),
template: {
metadata: this.podMetadata.toJson(),
spec: this._toPodSpec(),
},
selector: this._toLabelSelector(),
podManagementPolicy: this.podManagementPolicy,
updateStrategy: this.strategy._toKube(),
};
}
/**
* @see IScalable.markHasAutoscaler()
*/
public markHasAutoscaler() {
this.hasAutoscaler = true;
}
/**
* @see IScalable.toScalingTarget()
*/
public toScalingTarget(): ScalingTarget {
return {
kind: this.apiObject.kind,
apiVersion: this.apiObject.apiVersion,
name: this.name,
containers: this.containers,
replicas: this.replicas,
};
}
}
/**
* Options for `StatefulSetUpdateStrategy.rollingUpdate`.
*/
export interface StatefulSetUpdateStrategyRollingUpdateOptions {
/**
* If specified, all Pods with an ordinal that is greater than or equal to the partition will
* be updated when the StatefulSet's .spec.template is updated. All Pods with an ordinal that
* is less than the partition will not be updated, and, even if they are deleted, they will be
* recreated at the previous version.
*
* If the partition is greater than replicas, updates to the pod template will not be propagated to Pods.
* In most cases you will not need to use a partition, but they are useful if you want to stage an
* update, roll out a canary, or perform a phased roll out.
*
* @see https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions
* @default 0
*/
readonly partition?: number;
}
/**
* StatefulSet update strategies.
*/
export class StatefulSetUpdateStrategy {
/**
* The controller will not automatically update the Pods in a StatefulSet.
* Users must manually delete Pods to cause the controller to create new Pods
* that reflect modifications.
*/
public static onDelete(): StatefulSetUpdateStrategy {
return new StatefulSetUpdateStrategy({
type: 'OnDelete',
});
}
/**
* The controller will delete and recreate each Pod in the StatefulSet.
* It will proceed in the same order as Pod termination (from the largest ordinal to the smallest),
* updating each Pod one at a time. The Kubernetes control plane waits until an updated
* Pod is Running and Ready prior to updating its predecessor.
*/
public static rollingUpdate(options: StatefulSetUpdateStrategyRollingUpdateOptions = {}): StatefulSetUpdateStrategy {
return new StatefulSetUpdateStrategy({
type: 'RollingUpdate',
rollingUpdate: { partition: options.partition ?? 0 },
});
}
private constructor(private readonly strategy: k8s.StatefulSetUpdateStrategy) {}
/**
* @internal
*/
public _toKube(): k8s.StatefulSetUpdateStrategy {
return this.strategy;
}
}