Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8e3022e

Browse files
evgeniy-scherbinadannykoppingmatifali
authored
docs: add documentation for prebuild scheduling feature (#18462)
Follow-up to#18126Changes:- address issue mentioned here:#18126 (comment)- add docs for prebuilds scheduling---------Co-authored-by: Danny Kopping <danny@coder.com>Co-authored-by: Atif Ali <atif@coder.com>
1 parentda5d5ba commit8e3022e

File tree

4 files changed

+124
-30
lines changed

4 files changed

+124
-30
lines changed

‎coderd/prebuilds/preset_snapshot.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,14 @@ func (p PresetSnapshot) CalculateState() *ReconciliationState {
267267
// - ActionTypeBackoff: Only BackoffUntil is set, indicating when to retry
268268
// - ActionTypeCreate: Only Create is set, indicating how many prebuilds to create
269269
// - ActionTypeDelete: Only DeleteIDs is set, containing IDs of prebuilds to delete
270-
func (pPresetSnapshot)CalculateActions(clock quartz.Clock,backoffInterval time.Duration) ([]*ReconciliationActions,error) {
270+
func (pPresetSnapshot)CalculateActions(backoffInterval time.Duration) ([]*ReconciliationActions,error) {
271271
// TODO: align workspace states with how we represent them on the FE and the CLI
272272
// right now there's some slight differences which can lead to additional prebuilds being created
273273

274274
// TODO: add mechanism to prevent prebuilds being reconciled from being claimable by users; i.e. if a prebuild is
275275
// about to be deleted, it should not be deleted if it has been claimed - beware of TOCTOU races!
276276

277-
actions,needsBackoff:=p.needsBackoffPeriod(clock,backoffInterval)
277+
actions,needsBackoff:=p.needsBackoffPeriod(p.clock,backoffInterval)
278278
ifneedsBackoff {
279279
returnactions,nil
280280
}

‎coderd/prebuilds/preset_snapshot_test.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ func TestNoPrebuilds(t *testing.T) {
8686
preset(true,0,current),
8787
}
8888

89-
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,nil,nil,nil,nil,quartz.NewMock(t),testutil.Logger(t))
89+
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,nil,nil,nil,nil,clock,testutil.Logger(t))
9090
ps,err:=snapshot.FilterByPreset(current.presetID)
9191
require.NoError(t,err)
9292

9393
state:=ps.CalculateState()
94-
actions,err:=ps.CalculateActions(clock,backoffInterval)
94+
actions,err:=ps.CalculateActions(backoffInterval)
9595
require.NoError(t,err)
9696

9797
validateState(t, prebuilds.ReconciliationState{/*all zero values*/ },*state)
@@ -108,12 +108,12 @@ func TestNetNew(t *testing.T) {
108108
preset(true,1,current),
109109
}
110110

111-
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,nil,nil,nil,nil,quartz.NewMock(t),testutil.Logger(t))
111+
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,nil,nil,nil,nil,clock,testutil.Logger(t))
112112
ps,err:=snapshot.FilterByPreset(current.presetID)
113113
require.NoError(t,err)
114114

115115
state:=ps.CalculateState()
116-
actions,err:=ps.CalculateActions(clock,backoffInterval)
116+
actions,err:=ps.CalculateActions(backoffInterval)
117117
require.NoError(t,err)
118118

119119
validateState(t, prebuilds.ReconciliationState{
@@ -156,7 +156,7 @@ func TestOutdatedPrebuilds(t *testing.T) {
156156

157157
// THEN: we should identify that this prebuild is outdated and needs to be deleted.
158158
state:=ps.CalculateState()
159-
actions,err:=ps.CalculateActions(clock,backoffInterval)
159+
actions,err:=ps.CalculateActions(backoffInterval)
160160
require.NoError(t,err)
161161
validateState(t, prebuilds.ReconciliationState{
162162
Actual:1,
@@ -174,7 +174,7 @@ func TestOutdatedPrebuilds(t *testing.T) {
174174

175175
// THEN: we should not be blocked from creating a new prebuild while the outdate one deletes.
176176
state=ps.CalculateState()
177-
actions,err=ps.CalculateActions(clock,backoffInterval)
177+
actions,err=ps.CalculateActions(backoffInterval)
178178
require.NoError(t,err)
179179
validateState(t, prebuilds.ReconciliationState{Desired:1},*state)
180180
validateActions(t, []*prebuilds.ReconciliationActions{
@@ -223,7 +223,7 @@ func TestDeleteOutdatedPrebuilds(t *testing.T) {
223223
// THEN: we should identify that this prebuild is outdated and needs to be deleted.
224224
// Despite the fact that deletion of another outdated prebuild is already in progress.
225225
state:=ps.CalculateState()
226-
actions,err:=ps.CalculateActions(clock,backoffInterval)
226+
actions,err:=ps.CalculateActions(backoffInterval)
227227
require.NoError(t,err)
228228
validateState(t, prebuilds.ReconciliationState{
229229
Actual:1,
@@ -467,7 +467,7 @@ func TestInProgressActions(t *testing.T) {
467467

468468
// THEN: we should identify that this prebuild is in progress.
469469
state:=ps.CalculateState()
470-
actions,err:=ps.CalculateActions(clock,backoffInterval)
470+
actions,err:=ps.CalculateActions(backoffInterval)
471471
require.NoError(t,err)
472472
tc.checkFn(*state,actions)
473473
})
@@ -510,7 +510,7 @@ func TestExtraneous(t *testing.T) {
510510

511511
// THEN: an extraneous prebuild is detected and marked for deletion.
512512
state:=ps.CalculateState()
513-
actions,err:=ps.CalculateActions(clock,backoffInterval)
513+
actions,err:=ps.CalculateActions(backoffInterval)
514514
require.NoError(t,err)
515515
validateState(t, prebuilds.ReconciliationState{
516516
Actual:2,Desired:1,Extraneous:1,Eligible:2,
@@ -685,13 +685,13 @@ func TestExpiredPrebuilds(t *testing.T) {
685685
}
686686

687687
// WHEN: calculating the current preset's state.
688-
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,running,nil,nil,nil,quartz.NewMock(t),testutil.Logger(t))
688+
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,running,nil,nil,nil,clock,testutil.Logger(t))
689689
ps,err:=snapshot.FilterByPreset(current.presetID)
690690
require.NoError(t,err)
691691

692692
// THEN: we should identify that this prebuild is expired.
693693
state:=ps.CalculateState()
694-
actions,err:=ps.CalculateActions(clock,backoffInterval)
694+
actions,err:=ps.CalculateActions(backoffInterval)
695695
require.NoError(t,err)
696696
tc.checkFn(running,*state,actions)
697697
})
@@ -727,7 +727,7 @@ func TestDeprecated(t *testing.T) {
727727

728728
// THEN: all running prebuilds should be deleted because the template is deprecated.
729729
state:=ps.CalculateState()
730-
actions,err:=ps.CalculateActions(clock,backoffInterval)
730+
actions,err:=ps.CalculateActions(backoffInterval)
731731
require.NoError(t,err)
732732
validateState(t, prebuilds.ReconciliationState{
733733
Actual:1,
@@ -774,13 +774,13 @@ func TestLatestBuildFailed(t *testing.T) {
774774
}
775775

776776
// WHEN: calculating the current preset's state.
777-
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,running,inProgress,backoffs,nil,quartz.NewMock(t),testutil.Logger(t))
777+
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,running,inProgress,backoffs,nil,clock,testutil.Logger(t))
778778
psCurrent,err:=snapshot.FilterByPreset(current.presetID)
779779
require.NoError(t,err)
780780

781781
// THEN: reconciliation should backoff.
782782
state:=psCurrent.CalculateState()
783-
actions,err:=psCurrent.CalculateActions(clock,backoffInterval)
783+
actions,err:=psCurrent.CalculateActions(backoffInterval)
784784
require.NoError(t,err)
785785
validateState(t, prebuilds.ReconciliationState{
786786
Actual:0,Desired:1,
@@ -798,7 +798,7 @@ func TestLatestBuildFailed(t *testing.T) {
798798

799799
// THEN: it should NOT be in backoff because all is OK.
800800
state=psOther.CalculateState()
801-
actions,err=psOther.CalculateActions(clock,backoffInterval)
801+
actions,err=psOther.CalculateActions(backoffInterval)
802802
require.NoError(t,err)
803803
validateState(t, prebuilds.ReconciliationState{
804804
Actual:1,Desired:1,Eligible:1,
@@ -812,7 +812,7 @@ func TestLatestBuildFailed(t *testing.T) {
812812
psCurrent,err=snapshot.FilterByPreset(current.presetID)
813813
require.NoError(t,err)
814814
state=psCurrent.CalculateState()
815-
actions,err=psCurrent.CalculateActions(clock,backoffInterval)
815+
actions,err=psCurrent.CalculateActions(backoffInterval)
816816
require.NoError(t,err)
817817
validateState(t, prebuilds.ReconciliationState{
818818
Actual:0,Desired:1,
@@ -867,15 +867,15 @@ func TestMultiplePresetsPerTemplateVersion(t *testing.T) {
867867
},
868868
}
869869

870-
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,nil,inProgress,nil,nil,quartz.NewMock(t),testutil.Logger(t))
870+
snapshot:=prebuilds.NewGlobalSnapshot(presets,nil,nil,inProgress,nil,nil,clock,testutil.Logger(t))
871871

872872
// Nothing has to be created for preset 1.
873873
{
874874
ps,err:=snapshot.FilterByPreset(presetOpts1.presetID)
875875
require.NoError(t,err)
876876

877877
state:=ps.CalculateState()
878-
actions,err:=ps.CalculateActions(clock,backoffInterval)
878+
actions,err:=ps.CalculateActions(backoffInterval)
879879
require.NoError(t,err)
880880

881881
validateState(t, prebuilds.ReconciliationState{
@@ -891,7 +891,7 @@ func TestMultiplePresetsPerTemplateVersion(t *testing.T) {
891891
require.NoError(t,err)
892892

893893
state:=ps.CalculateState()
894-
actions,err:=ps.CalculateActions(clock,backoffInterval)
894+
actions,err:=ps.CalculateActions(backoffInterval)
895895
require.NoError(t,err)
896896

897897
validateState(t, prebuilds.ReconciliationState{
@@ -995,7 +995,7 @@ func TestPrebuildScheduling(t *testing.T) {
995995
require.NoError(t,err)
996996

997997
state:=ps.CalculateState()
998-
actions,err:=ps.CalculateActions(clock,backoffInterval)
998+
actions,err:=ps.CalculateActions(backoffInterval)
999999
require.NoError(t,err)
10001000

10011001
validateState(t, prebuilds.ReconciliationState{
@@ -1016,7 +1016,7 @@ func TestPrebuildScheduling(t *testing.T) {
10161016
require.NoError(t,err)
10171017

10181018
state:=ps.CalculateState()
1019-
actions,err:=ps.CalculateActions(clock,backoffInterval)
1019+
actions,err:=ps.CalculateActions(backoffInterval)
10201020
require.NoError(t,err)
10211021

10221022
validateState(t, prebuilds.ReconciliationState{

‎docs/admin/templates/extending-templates/prebuilt-workspaces.md

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Prebuilt workspaces are:
1212
- Created and maintained automatically by Coder to match your specified preset configurations.
1313
- Claimed transparently when developers create workspaces.
1414
- Monitored and replaced automatically to maintain your desired pool size.
15+
- Automatically scaled based on time-based schedules to optimize resource usage.
1516

1617
##Relationship to workspace presets
1718

@@ -111,6 +112,105 @@ prebuilt workspace can remain before it is considered expired and eligible for c
111112
Expired prebuilt workspaces are removed during the reconciliation loop to avoid stale environments and resource waste.
112113
New prebuilt workspaces are only created to maintain the desired count if needed.
113114

115+
###Scheduling
116+
117+
Prebuilt workspaces support time-based scheduling to scale the number of instances up or down.
118+
This allows you to reduce resource costs during off-hours while maintaining availability during peak usage times.
119+
120+
Configure scheduling by adding a`scheduling` block within your`prebuilds` configuration:
121+
122+
```tf
123+
data "coder_workspace_preset" "goland" {
124+
name = "GoLand: Large"
125+
parameters {
126+
jetbrains_ide = "GO"
127+
cpus = 8
128+
memory = 16
129+
}
130+
131+
prebuilds {
132+
instances = 0 # default to 0 instances
133+
134+
scheduling {
135+
timezone = "UTC" # only a single timezone may be used for simplicity
136+
137+
# scale to 3 instances during the work week
138+
schedule {
139+
cron = "* 8-18 * * 1-5" # from 8AM-6:59PM, Mon-Fri, UTC
140+
instances = 3 # scale to 3 instances
141+
}
142+
143+
# scale to 1 instance on Saturdays for urgent support queries
144+
schedule {
145+
cron = "* 8-14 * * 6" # from 8AM-2:59PM, Sat, UTC
146+
instances = 1 # scale to 1 instance
147+
}
148+
}
149+
}
150+
}
151+
```
152+
153+
**Scheduling configuration:**
154+
155+
-**`timezone`**: The timezone for all cron expressions (required). Only a single timezone is supported per scheduling configuration.
156+
-**`schedule`**: One or more schedule blocks defining when to scale to specific instance counts.
157+
-**`cron`**: Cron expression interpreted as continuous time ranges (required).
158+
-**`instances`**: Number of prebuilt workspaces to maintain during this schedule (required).
159+
160+
**How scheduling works:**
161+
162+
1. The reconciliation loop evaluates all active schedules every reconciliation interval (`CODER_WORKSPACE_PREBUILDS_RECONCILIATION_INTERVAL`).
163+
2. The schedule that matches the current time becomes active. Overlapping schedules are disallowed by validation rules.
164+
3. If no schedules match the current time, the base`instances` count is used.
165+
4. The reconciliation loop automatically creates or destroys prebuilt workspaces to match the target count.
166+
167+
**Cron expression format:**
168+
169+
Cron expressions follow the format:`* HOUR DOM MONTH DAY-OF-WEEK`
170+
171+
-`*` (minute): Must always be`*` to ensure the schedule covers entire hours rather than specific minute intervals
172+
-`HOUR`: 0-23, range (e.g., 8-18 for 8AM-6:59PM), or`*`
173+
-`DOM` (day-of-month): 1-31, range, or`*`
174+
-`MONTH`: 1-12, range, or`*`
175+
-`DAY-OF-WEEK`: 0-6 (Sunday=0, Saturday=6), range (e.g., 1-5 for Monday to Friday), or`*`
176+
177+
**Important notes about cron expressions:**
178+
179+
-**Minutes must always be`*`**: To ensure the schedule covers entire hours
180+
-**Time ranges are continuous**: A range like`8-18` means from 8AM to 6:59PM (inclusive of both start and end hours)
181+
-**Weekday ranges**:`1-5` means Monday through Friday (Monday=1, Friday=5)
182+
-**No overlapping schedules**: The validation system prevents overlapping schedules.
183+
184+
**Example schedules:**
185+
186+
```tf
187+
# Business hours only (8AM-6:59PM, Mon-Fri)
188+
schedule {
189+
cron = "* 8-18 * * 1-5"
190+
instances = 5
191+
}
192+
193+
# 24/7 coverage with reduced capacity overnight and on weekends
194+
schedule {
195+
cron = "* 8-18 * * 1-5" # Business hours (8AM-6:59PM, Mon-Fri)
196+
instances = 10
197+
}
198+
schedule {
199+
cron = "* 19-23,0-7 * * 1,5" # Evenings and nights (7PM-11:59PM, 12AM-7:59AM, Mon-Fri)
200+
instances = 2
201+
}
202+
schedule {
203+
cron = "* * * * 6,0" # Weekends
204+
instances = 2
205+
}
206+
207+
# Weekend support (10AM-4:59PM, Sat-Sun)
208+
schedule {
209+
cron = "* 10-16 * * 6,0"
210+
instances = 1
211+
}
212+
```
213+
114214
###Template updates and the prebuilt workspace lifecycle
115215

116216
Prebuilt workspaces are not updated after they are provisioned.
@@ -195,12 +295,6 @@ The prebuilt workspaces feature has these current limitations:
195295

196296
[View issue](https://github.com/coder/internal/issues/364)
197297

198-
-**Autoscaling**
199-
200-
Prebuilt workspaces remain running until claimed. There's no automated mechanism to reduce instances during off-hours.
201-
202-
[View issue](https://github.com/coder/internal/issues/312)
203-
204298
###Monitoring and observability
205299

206300
####Available metrics

‎enterprise/coderd/prebuilds/reconcile.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ func (c *StoreReconciler) CalculateActions(ctx context.Context, snapshot prebuil
518518
returnnil,ctx.Err()
519519
}
520520

521-
returnsnapshot.CalculateActions(c.clock,c.cfg.ReconciliationBackoffInterval.Value())
521+
returnsnapshot.CalculateActions(c.cfg.ReconciliationBackoffInterval.Value())
522522
}
523523

524524
func (c*StoreReconciler)WithReconciliationLock(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp