-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathspot-instance-tenant.yaml
More file actions
executable file
·315 lines (268 loc) · 9.61 KB
/
spot-instance-tenant.yaml
File metadata and controls
executable file
·315 lines (268 loc) · 9.61 KB
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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# Spot Instance Optimization Multi-Pool Tenant Example
#
# This example demonstrates cost optimization using a mix of:
# - On-demand instances for guaranteed compute availability
# - Spot instances for elastic capacity and cost savings
#
# IMPORTANT ARCHITECTURE NOTE:
# All pools form ONE unified RustFS cluster. Data is striped uniformly
# across all volumes regardless of instance type. Cost savings come from
# cheaper spot instance COMPUTE, not from storage differentiation.
# Both pools use the SAME storage class to avoid performance degradation.
#
# Use Case: 70-90% cost reduction while maintaining reliability
#
# Prerequisites:
# - Nodes with spot instance taints:
# kubectl taint node <spot-node> spot-instance=true:NoSchedule
# - Node labels for instance lifecycle:
# kubectl label node <node> instance-lifecycle=spot
# kubectl label node <node> instance-lifecycle=on-demand
#
# Resources created:
# - 2 StatefulSets (critical-pool on-demand, elastic-pool spot)
# - 80 total PVCs
# - 3 Services (shared)
# - RBAC resources
#
# Cost Example:
# - On-demand: 4 × c5.4xlarge = $2,720/month
# - Spot: 16 × c5.4xlarge = $816/month (70% savings)
# - Total: $3,536/month vs $13,600/month all on-demand (74% savings)
apiVersion: rustfs.com/v1alpha1
kind: Tenant
metadata:
name: cost-optimized
namespace: storage
labels:
cost-profile: optimized
environment: production
spec:
image: rustfs/rustfs:latest
pools:
# CRITICAL POOL: On-demand instances
# For data requiring guaranteed availability
- name: critical-pool
servers: 4
persistence:
volumesPerServer: 4 # 4 × 4 = 16 volumes
volumeClaimTemplate:
storageClassName: standard-ssd # Same as elastic pool
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Ti
labels:
instance-type: on-demand
criticality: high
sla: "99.99"
annotations:
description: "Critical data on on-demand instances"
backup-policy: "continuous"
# TARGET: On-demand instances only
nodeSelector:
instance-lifecycle: on-demand
# AVOID: Spot instance nodes (additional safety)
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: instance-lifecycle
operator: In
values: ["on-demand"]
- key: karpenter.sh/capacity-type
operator: NotIn
values: ["spot"]
# High priority to prevent eviction
priorityClassName: system-cluster-critical
# Higher resources for critical workloads
resources:
requests:
cpu: "8"
memory: "32Gi"
limits:
cpu: "16"
memory: "64Gi"
# ELASTIC POOL: Spot instances
# For elastic capacity and cost optimization
- name: elastic-pool
servers: 16
persistence:
volumesPerServer: 4 # 16 × 4 = 64 volumes
volumeClaimTemplate:
storageClassName: standard-ssd
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 4Ti
labels:
instance-type: spot
criticality: normal
sla: "99.5"
annotations:
description: "Elastic capacity on spot instances"
backup-policy: "daily"
cost-center: "elastic-storage"
# TARGET: Spot instance nodes
nodeSelector:
instance-lifecycle: spot
# TOLERATE: Spot instance taints
tolerations:
- key: "spot-instance"
operator: "Equal"
value: "true"
effect: "NoSchedule"
- key: "karpenter.sh/disruption"
operator: "Exists"
effect: "NoSchedule"
# DISTRIBUTE: Across multiple spot instance types/zones
# to reduce correlated failures
topologySpreadConstraints:
- maxSkew: 2
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
rustfs.pool: elastic-pool
- maxSkew: 4
topologyKey: node.kubernetes.io/instance-type
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
rustfs.pool: elastic-pool
# Standard resources for elastic pool
resources:
requests:
cpu: "4"
memory: "16Gi"
limits:
cpu: "8"
memory: "32Gi"
env:
- name: RUST_LOG
value: "info"
---
# Deployment Guide
# 1. Label nodes by instance lifecycle:
# # On-demand nodes
# kubectl label node on-demand-node-1 instance-lifecycle=on-demand
# kubectl label node on-demand-node-2 instance-lifecycle=on-demand
#
# # Spot instance nodes
# kubectl label node spot-node-1 instance-lifecycle=spot
# kubectl label node spot-node-2 instance-lifecycle=spot
# 2. Taint spot nodes (prevents non-tolerant workloads):
# kubectl taint node spot-node-1 spot-instance=true:NoSchedule
# kubectl taint node spot-node-2 spot-instance=true:NoSchedule
# 3. Create namespace:
# kubectl create namespace storage
# 4. Create system-cluster-critical priority class (if not exists):
# # Usually exists by default, verify with:
# kubectl get priorityclass system-cluster-critical
# 5. Apply tenant:
# kubectl apply -f spot-instance-tenant.yaml
# 6. Verify critical pool on on-demand instances:
# kubectl get pods -n storage -l rustfs.pool=critical-pool -o wide
# # Should show pods on on-demand nodes only
# 7. Verify elastic pool on spot instances:
# kubectl get pods -n storage -l rustfs.pool=elastic-pool -o wide
# # Should show pods on spot nodes
# 8. Monitor spot instance interruptions:
# kubectl get events -n storage --field-selector reason=Evicted
#
# # When spot instance is interrupted, RustFS will:
# # - Automatically handle the failure via erasure coding
# # - Continue serving from remaining nodes
# # - Pod will reschedule to another spot instance
# 9. Check cost allocation:
# kubectl get pods -n storage -o custom-columns=\
# NAME:.metadata.name,\
# POOL:.metadata.labels.rustfs\\.pool,\
# LIFECYCLE:.spec.nodeSelector.instance-lifecycle,\
# CPU:.spec.containers[0].resources.requests.cpu,\
# MEM:.spec.containers[0].resources.requests.memory
---
# Cost Analysis:
# On-Demand Pool (critical-pool):
# - 4 servers on c5.4xlarge instances
# - $0.68/hour × 24 × 30 × 4 = $1,958/month
# - Guaranteed availability
# - 16 volumes × 2Ti = 32Ti storage
# Spot Pool (elastic-pool):
# - 16 servers on c5.4xlarge spot instances
# - ~$0.20/hour (70% discount) × 24 × 30 × 16 = $2,304/month
# - Interruption risk (typically <5% per hour)
# - 64 volumes × 4Ti = 256Ti storage
# Total Cost:
# - This config: ~$4,262/month for 288Ti
# - All on-demand: ~$13,608/month for 288Ti
# - Savings: ~$9,346/month (69% reduction)
# Reliability:
# - RustFS erasure coding tolerates node failures
# - With 80 total volumes (16 critical + 64 elastic):
# - Can lose up to 40 volumes and still function (50% failure tolerance)
# - Critical pool on guaranteed infrastructure
# - Spot interruptions handled gracefully
---
# Cloud Provider Examples:
# AWS:
# # On-demand nodes
# kubectl label node <node> instance-lifecycle=on-demand \
# node.kubernetes.io/instance-type=c5.4xlarge \
# karpenter.sh/capacity-type=on-demand
#
# # Spot nodes
# kubectl label node <node> instance-lifecycle=spot \
# node.kubernetes.io/instance-type=c5.4xlarge \
# karpenter.sh/capacity-type=spot
# kubectl taint node <node> karpenter.sh/disruption=spot:NoSchedule
# GCP:
# # On-demand nodes
# kubectl label node <node> instance-lifecycle=on-demand \
# cloud.google.com/gke-preemptible=false
#
# # Preemptible (spot) nodes
# kubectl label node <node> instance-lifecycle=spot \
# cloud.google.com/gke-preemptible=true
# kubectl taint node <node> cloud.google.com/gke-preemptible=true:NoSchedule
# Azure:
# # On-demand nodes
# kubectl label node <node> instance-lifecycle=on-demand \
# kubernetes.azure.com/scalesetpriority=regular
#
# # Spot nodes
# kubectl label node <node> instance-lifecycle=spot \
# kubernetes.azure.com/scalesetpriority=spot
# kubectl taint node <node> kubernetes.azure.com/scalesetpriority=spot:NoSchedule
---
# Handling Spot Interruptions:
# RustFS automatically handles spot interruptions:
# 1. Spot node receives termination notice (2 minutes warning)
# 2. Kubernetes evicts pods
# 3. RustFS continues serving from remaining nodes (erasure coding)
# 4. Kubernetes reschedules pod to another spot instance
# 5. RustFS automatically rebalances data
# Monitor interruptions:
# kubectl get events -n storage -w | grep -i "evicted\|preempt"
# Check RustFS cluster health during interruptions:
# mc admin info global
# # Should show cluster healthy even with some nodes down
---
# Best Practices:
# 1. Maintain minimum on-demand capacity:
# - At least servers × volumesPerServer >= 4 on on-demand
# - Ensures cluster survives even if all spot instances interrupted
# 2. Use topology spread for spot pools:
# - Distribute across zones and instance types
# - Reduces correlated interruptions
# 3. Set appropriate priority classes:
# - Critical pool: system-cluster-critical or high-priority
# - Elastic pool: default or low-priority
# 4. Monitor costs:
# - Track actual spot prices
# - Adjust instance types based on availability/cost
# 5. Test interruptions:
# - Drain spot nodes to simulate interruptions
# - Verify RustFS continues operating
# - Verify automatic pod rescheduling