Skip to content

Commit 2329550

Browse files
evanfarrarSamze
andauthored
Max in flight (#3907)
Adds max_in_flight to /v3/deployments * This currently just creates initial set of deployment instances to max_in_flight, and then waits for all instances to be ready before batching another round of max_in_flight instances. A follow up PR can improve this behaviour so that as soon as a new instance is ready, we spin down an old one and create another new process. * Canary deployments *always* start with a single single. max_in_flight is honoured post continuation for the rest of the instances. Closes #3878 --------- Co-authored-by: Evan Farrar <[email protected]> Co-authored-by: Sam Gunaratne <[email protected]>
1 parent 181e241 commit 2329550

File tree

14 files changed

+619
-14
lines changed

14 files changed

+619
-14
lines changed

app/actions/deployment_create.rb

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,28 @@ def create(app:, user_audit_info:, message:)
4141
)
4242
end
4343

44+
desired_instances = desired_instances(app.oldest_web_process, previous_deployment)
45+
4446
deployment = DeploymentModel.create(
4547
app: app,
4648
state: starting_state(message),
4749
status_value: DeploymentModel::ACTIVE_STATUS_VALUE,
4850
status_reason: DeploymentModel::DEPLOYING_STATUS_REASON,
4951
droplet: target_state.droplet,
5052
previous_droplet: previous_droplet,
51-
original_web_process_instance_count: desired_instances(app.oldest_web_process, previous_deployment),
53+
original_web_process_instance_count: desired_instances,
5254
revision_guid: revision&.guid,
5355
revision_version: revision&.version,
54-
strategy: message.strategy
56+
strategy: message.strategy,
57+
max_in_flight: message.max_in_flight
5558
)
5659
MetadataUpdate.update(deployment, message)
5760

5861
supersede_deployment(previous_deployment)
5962

60-
process = create_deployment_process(app, deployment.guid, revision)
63+
process_instances = starting_process_instances(message, desired_instances)
64+
65+
process = create_deployment_process(app, deployment.guid, revision, process_instances)
6166
# Need to transition from STOPPED to STARTED to engage the ProcessObserver to desire the LRP.
6267
# It'd be better to do this via Diego::Runner.new(process, config).start,
6368
# but it is nontrivial to get that working in test.
@@ -75,8 +80,8 @@ def create(app:, user_audit_info:, message:)
7580
raise error
7681
end
7782

78-
def create_deployment_process(app, deployment_guid, revision)
79-
process = clone_existing_web_process(app, revision)
83+
def create_deployment_process(app, deployment_guid, revision, process_instances)
84+
process = clone_existing_web_process(app, revision, process_instances)
8085

8186
DeploymentProcessModel.create(
8287
deployment_guid: deployment_guid,
@@ -87,7 +92,7 @@ def create_deployment_process(app, deployment_guid, revision)
8792
process
8893
end
8994

90-
def clone_existing_web_process(app, revision)
95+
def clone_existing_web_process(app, revision, process_instances)
9196
web_process = app.newest_web_process
9297
command = if revision
9398
revision.commands_by_process_type[ProcessTypes::WEB]
@@ -99,7 +104,7 @@ def clone_existing_web_process(app, revision)
99104
app: app,
100105
type: ProcessTypes::WEB,
101106
state: ProcessModel::STOPPED,
102-
instances: 1,
107+
instances: process_instances,
103108
command: command,
104109
memory: web_process.memory,
105110
file_descriptors: web_process.file_descriptors,
@@ -170,7 +175,8 @@ def deployment_for_stopped_app(app, message, previous_deployment, previous_dropl
170175
original_web_process_instance_count: desired_instances(app.oldest_web_process, previous_deployment),
171176
revision_guid: revision&.guid,
172177
revision_version: revision&.version,
173-
strategy: DeploymentModel::ROLLING_STRATEGY
178+
strategy: DeploymentModel::ROLLING_STRATEGY,
179+
max_in_flight: message.options ? message.options[:max_in_flight] : 1
174180
)
175181

176182
MetadataUpdate.update(deployment, message)
@@ -211,6 +217,14 @@ def starting_state(message)
211217
end
212218
end
213219

220+
def starting_process_instances(message, desired_instances)
221+
if message.strategy == DeploymentModel::CANARY_STRATEGY
222+
1
223+
else
224+
[message.max_in_flight, desired_instances].min
225+
end
226+
end
227+
214228
def log_rollback_event(app_guid, user_id, revision_id, strategy)
215229
TelemetryLogger.v3_emit(
216230
'rolled-back-app',

app/messages/deployment_create_message.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class DeploymentCreateMessage < MetadataBaseMessage
77
droplet
88
revision
99
strategy
10+
options
1011
]
1112

1213
validates_with NoAdditionalKeysValidator
@@ -15,6 +16,12 @@ class DeploymentCreateMessage < MetadataBaseMessage
1516
allow_nil: true
1617
validate :mutually_exclusive_droplet_sources
1718

19+
validates :options,
20+
allow_nil: true,
21+
hash: true
22+
23+
validate :validate_max_in_flight
24+
1825
def app_guid
1926
relationships&.dig(:app, :data, :guid)
2027
end
@@ -27,12 +34,26 @@ def revision_guid
2734
revision&.dig(:guid)
2835
end
2936

37+
def max_in_flight
38+
options&.dig(:max_in_flight) || 1
39+
end
40+
3041
private
3142

3243
def mutually_exclusive_droplet_sources
3344
return unless revision.present? && droplet.present?
3445

3546
errors.add(:droplet, "Cannot set both fields 'droplet' and 'revision'")
3647
end
48+
49+
def validate_max_in_flight
50+
return unless options.present? && options.is_a?(Hash) && options[:max_in_flight]
51+
52+
max_in_flight = options[:max_in_flight]
53+
54+
return unless !max_in_flight.is_a?(Integer) || max_in_flight < 1
55+
56+
errors.add(:max_in_flight, 'must be an integer greater than 0')
57+
end
3758
end
3859
end

app/presenters/v3/deployment_presenter.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ def to_hash
1919
}
2020
},
2121
strategy: deployment.strategy,
22+
options: {
23+
max_in_flight: deployment.max_in_flight
24+
},
2225
droplet: {
2326
guid: deployment.droplet_guid
2427
},
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Sequel.migration do
2+
change do
3+
alter_table(:deployments) do
4+
add_column :max_in_flight, Integer, null: false, default: 1
5+
end
6+
end
7+
end

docs/v3/source/includes/api_resources/_deployments.erb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
}
1111
},
1212
"strategy": "canary",
13+
"options" : {
14+
"max_in_flight": 3,
15+
},
1316
"droplet": {
1417
"guid": "44ccfa61-dbcf-4a0d-82fe-f668e9d2a962"
1518
},
@@ -80,6 +83,9 @@
8083
"reason": "DEPLOYED"
8184
},
8285
"strategy": "rolling",
86+
"options" : {
87+
"max_in_flight": 1,
88+
},
8389
"droplet": {
8490
"guid": "44ccfa61-dbcf-4a0d-82fe-f668e9d2a962"
8591
},

docs/v3/source/includes/resources/deployments/_create.md.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Name | Type | Description | Default
7777
**droplet**<sup>[1]</sup> | _object_ | The droplet to deploy for the app; this will update the app's [current droplet](#get-current-droplet-association-for-an-app) to this droplet | The app's [current droplet](#get-current-droplet-association-for-an-app)
7878
**revision**<sup>[1]</sup> | _object_ | The [revision](#revisions) whose droplet to deploy for the app; this will update the app's [current droplet](#get-current-droplet-association-for-an-app) to this droplet |
7979
**strategy** | _string_ | The strategy to use for the deployment | `rolling`
80+
**options.max_in_flight** | _integer_ | The maximum number of new instances to deploy simultaneously | 1
8081
**metadata.labels** | [_label object_](#labels) | Labels applied to the deployment
8182
**metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the deployment
8283

docs/v3/source/includes/resources/deployments/_header.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Deployments are different than the traditional method of pushing app updates whi
1313
Deployment strategies supported:
1414

1515
* [Rolling deployments](https://docs.cloudfoundry.org/devguide/deploy-apps/rolling-deploy.html) allows for
16-
applications to be deployed without incurring downtime by gradually rolling out instances.
16+
applications to be deployed without incurring downtime by gradually rolling out instances. Max-in-flight can be configured
17+
to specify how many instances are rolled out simultaneously.
1718

1819
* Canary deployments deploy a single instance and pause for user evaluation. If the canary instance is deemed successful, the deployment can be resumed via the [continue action](#continue-a-deployment). The deployment then continues like a rolling deployment. This feature is experimental and is subject to change.

docs/v3/source/includes/resources/deployments/_object.md.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Name | Type | Description
1717
**status.details.last_successful_healthcheck** | _[timestamp](#timestamps)_ | Timestamp of the last successful healthcheck
1818
**status.details.last_status_change** | _[timestamp](#timestamps)_ | Timestamp of last change to status.value or status.reason
1919
**strategy** | _string_ | Strategy used for the deployment; supported strategies are `rolling` and `canary` (experimental)
20+
**options.max_in_flight** | _integer_ | The maximum number of new instances to deploy simultaneously
2021
**droplet.guid** | _string_ | The droplet guid that the deployment is transitioning the app to
2122
**previous_droplet.guid** | _string_ | The app's [current droplet guid](#get-current-droplet-association-for-an-app) before the deployment was created
2223
**new_processes** | _array_ | List of processes created as part of the deployment

lib/cloud_controller/deployment_updater/updater.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def scale_deployment
110110
end
111111

112112
scale_down_oldest_web_process_with_instances
113-
deploying_web_process.update(instances: deploying_web_process.instances + 1)
113+
deploying_web_process.update(instances: [deploying_web_process.instances + deployment.max_in_flight, deployment.original_web_process_instance_count].min)
114114
end
115115
end
116116

@@ -161,12 +161,12 @@ def scale_canceled_web_processes_to_zero
161161
def scale_down_oldest_web_process_with_instances
162162
process = oldest_web_process_with_instances
163163

164-
if process.instances == 1 && is_interim_process?(process)
164+
if process.instances <= deployment.max_in_flight && is_interim_process?(process)
165165
process.destroy
166166
return
167167
end
168168

169-
process.update(instances: process.instances - 1)
169+
process.update(instances: [(process.instances - deployment.max_in_flight), 0].max)
170170
end
171171

172172
def finalize_deployment

0 commit comments

Comments
 (0)