Time-Dependent Workloads

4 min read

Some workloads don’t need a different configuration for different environments — they need a different configuration for different times. Dev environments should be off at night. Databases need a maintenance window. CDN edges should run more replicas when their timezone is awake.

Orkestra handles all of this declaratively. No CronJobs. No external schedulers. No custom controllers. The reconciler re-evaluates on every resync, and built-in time notes give you the vocabulary to express any time-based rule.


The three primitives

1. when: time: — resource lifecycle gates

Controls whether a resource group is created or deleted based on wall-clock time (UTC):

deployments:
  - name: "{{ .metadata.name }}"
    reconcile: true
    when:
      - time:
          after: "09:00"
          before: "18:00"
      - dayOfWeek:
          in: [Monday, Tuesday, Wednesday, Thursday, Friday]

The resource is created when both conditions pass and removed automatically when either fails. On the next resync it is re-created if the window reopened. Nothing else needed.

2. Built-in time notes — template expressions

Five built-in notes expose time state to template expressions, status fields, and user-defined notes:

NoteSignatureReturns
weekdayweekdaytrue Mon–Fri UTC
weekendweekendtrue Sat–Sun UTC
timeInWindowtimeInWindow "HH:MM" "HH:MM"true when after ≤ now < before UTC
timeNotInWindowtimeNotInWindow "HH:MM" "HH:MM"opposite of timeInWindow
nextCronnextCron "cron-expr"next fire time as RFC3339 string

timeInWindow and timeNotInWindow accept 24-hour UTC strings. They do not wrap midnight — use two separate windows if you need cross-midnight coverage.

3. User-defined notes — composable building blocks

Time notes become most useful when composed into domain-specific vocabulary:

notes:
  - name: inBusinessHours
    expression: '{{ and weekday (timeInWindow "09:00" "18:00") }}'

  - name: nextBusinessHour
    expression: '{{ nextCron "0 9 * * 1-5" }}'

Now inBusinessHours and nextBusinessHour are available everywhere in the Katalog — in status fields, resource attributes, when: conditions via field:, and other user-defined notes.


Status fields driven by time

Status fields can express the operator’s current state through the same notes:

status:
  fields:
    - path: phase
      value: "Suspended"
    - path: phase
      value: "Active"
      when:
        - field: "{{ inBusinessHours }}"
          equals: "true"
    - path: message
      value: "Suspended — resumes {{ nextBusinessHour }}"
    - path: message
      value: "Environment is running"
      when:
        - field: "{{ inBusinessHours }}"
          equals: "true"

The status updates on every reconcile. Control Center shows the live state without any additional tooling.


Four common patterns

Business hours — provision and deprovision on a schedule

Dev environments, staging clusters, preview apps — things that should run 9–5 Mon–Fri and nothing else. The operator creates the Deployment and Service when the window opens and removes them when it closes. No human involvement.

Maintenance window — drain and restore on a cron

Databases need a weekly window for vacuuming, reindexing, or backups. Read replicas scale to zero, a ConfigMap signals downstream load balancers, and the replicas come back automatically when the window closes.

Regional peak — per-timezone replica scaling

A CDN edge, API gateway, or cache layer that serves multiple timezones. Each region’s deployment scales up during local business hours and scales down overnight — all from a single operator, no per-region CronJobs.

Workload autoscaling — replica count driven by time conditions

autoscale: on a Deployment declaration uses when: time: and when: dayOfWeek: as scaling conditions. Instead of creating or removing the Deployment, it patches spec.replicas — scaling to a higher count during peak and a lower count off-peak. A cooldown prevents oscillation at window boundaries. See Workload Autoscaler for the full model.


How the resync drives it

All three patterns work because Orkestra’s reconciler runs on a configurable resync interval:

operatorBox:
  reconciler:
    resync: 1m

On every resync, the reconciler re-evaluates all notes and when: conditions against the current time. A resource that should no longer exist gets deleted. A resource that should exist gets created or updated. The cluster converges to the correct state for right now.


Try it

ork init --pack use-cases/temporal
cd temporal/01-business-hours
ork simulate
ork run