Skip to content

feat: add autoscaling configuration for prebuilds #408

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

evgeniy-scherbina
Copy link

@evgeniy-scherbina evgeniy-scherbina commented May 29, 2025

Relates to coder/internal#312
Needed for coder/coder#18126

Exposes an autoscaling field under coder_workspace_preset:

data "coder_workspace_preset" "us-nix" {
  ...
  
  prebuilds = {
	  instances = 0                  # default to 0 instances
	  
	  autoscaling {
		  timezone = "UTC"             # only a single timezone may be used for simplicity
		  
		  # scale to 3 instances during the work week
		  schedule {
		    cron = "* 8-18 * * 1-5"    # from 8AM-6:59PM, Mon-Fri, UTC
		    instances = 3              # scale to 3 instances
		  }
		  
		  # scale to 1 instance on Saturdays for urgent support queries
		  schedule {
		    cron = "* 8-14 * * 6"      # from 8AM-2:59PM, Sat, UTC
		    instances = 1              # scale to 1 instance
		  }
	  }
  }
}

@evgeniy-scherbina evgeniy-scherbina force-pushed the prebuilds-autoscaling-mechanism branch from 15cae29 to 543a735 Compare June 11, 2025 17:35
Schema: map[string]*schema.Schema{
"timezone": {
Type: schema.TypeString,
Description: "The timezone to use for the autoscaling schedule (e.g., \"UTC\", \"America/New_York\").",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can you include a link to an acceptable list of TZs here? I think https://en.wikipedia.org/wiki/List_of_tz_database_time_zones suffices but I'm not sure.

https://pkg.go.dev/time#LoadLocation says it references the "IANA Time Zone database".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the help output of coder schedule {start|stop} for some sample wording. The phrase "timezone" is slightly misleading here as it's the location that influences the timezone depending on wibbly wobbly timey wimey stuff.


_, err := time.LoadLocation(timezone)
if err != nil {
return nil, []error{fmt.Errorf("failed to load location: %w", err)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nil, []error{fmt.Errorf("failed to load location: %w", err)}
return nil, []error{fmt.Errorf("failed to load timezone %q: %w", timezone, err)}

MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"timezone": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love that we explicitly don't have a default value here 👍 nice.

Copy link
Member

@johnstcn johnstcn Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend defaulting to TZ or /etc/localtime, and falling back to UTC. There's a helper function tz.TimezoneIANA for this.

EDIT: given that this is for the provider, you might actually be correct in not specifying any default here as we can't assume that the timezone in which the provider is running is the same timezone as coderd.

Schema: map[string]*schema.Schema{
"cron": {
Type: schema.TypeString,
Description: "A cron expression that defines when this schedule should be active. The cron expression must be in the format \"* HOUR * * DAY-OF-WEEK\" where HOUR is 0-23 and DAY-OF-WEEK is 0-6 (Sunday-Saturday). The minute, day-of-month, and month fields must be \"*\".",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why must day-of-month and month be *?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this relates to the "weekly cron schedule" concept we already use for schedules?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems so, but I think that's a strange requirement since nothing in this implementation is on a weekly cadence.

},
},
},
},
}
}

// validatePrebuildsCronSpec ensures that the minute, day-of-month and month options of spec are all set to *
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to add the why here, not just the what.

Copy link
Member

@johnstcn johnstcn Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to coderd/schedule/cron EDIT: sorry, thought this was a different project :P

autoscaling {}
}
}`,
ExpectError: regexp.MustCompile(`The argument "[^"]+" is required, but no definition was found.`),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: let's check for a precise name to ensure we don't get false positives.
Or is it like this because we can't know which argument will show up first?

},
},
{
Name: "Prebuilds is set with an autoscaling field with 2 schedules",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do we check that these schedules are not conflicting? I didn't find any checks in coder/coder#18126 from a quick scan. I'll review that PR in more detail tomorrow.

Copy link
Contributor

@dannykopping dannykopping Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the current time matches any defined autoscaling schedule, the corresponding number of instances is used.

This makes me nervous because it introduces some non-determinism in the case of conflicting/overlapping schedules. If we don't evaluate cron schedules in the same order, we could flip back and forth between conflicting schedules, which could cause a lot of churn and confusion.


// validatePrebuildsCronSpec ensures that the minute, day-of-month and month options of spec are all set to *
func validatePrebuildsCronSpec(spec string) error {
parts := strings.Fields(spec)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Howcome we don't use PrebuildsCRONParser here?

@spikecurtis
Copy link
Contributor

I really don't think we should call this "autoscaling" as that already has a specific meaning in Cloud circles which this is not.

Autoscaling is adjusting instance counts dynamically based on measured load.

This is adjusting instance counts based on a schedule. Some possible names:

  • Scale sceduling
  • Schedule-scaling
  • Pool scheduling
  • Prebuild scheduling

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants