@spine-event-engine/deployment-gce 2.0.0-snapshot.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +336 -0
  3. package/REFERENCE.md +93 -0
  4. package/dist/discovery/gce-node-discovery.d.ts +56 -0
  5. package/dist/discovery/gce-node-discovery.d.ts.map +1 -0
  6. package/dist/discovery/gce-node-discovery.js +78 -0
  7. package/dist/discovery/gce-node-discovery.js.map +1 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +20 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/metadata/gce-metadata-service.d.ts +47 -0
  13. package/dist/metadata/gce-metadata-service.d.ts.map +1 -0
  14. package/dist/metadata/gce-metadata-service.js +65 -0
  15. package/dist/metadata/gce-metadata-service.js.map +1 -0
  16. package/dist/node/application-node.d.ts +35 -0
  17. package/dist/node/application-node.d.ts.map +1 -0
  18. package/dist/node/application-node.js +46 -0
  19. package/dist/node/application-node.js.map +1 -0
  20. package/dist/registrar/gce-registrar-log.d.ts +11 -0
  21. package/dist/registrar/gce-registrar-log.d.ts.map +1 -0
  22. package/dist/registrar/gce-registrar-log.js +44 -0
  23. package/dist/registrar/gce-registrar-log.js.map +1 -0
  24. package/dist/registrar/gce-registrar.d.ts +90 -0
  25. package/dist/registrar/gce-registrar.d.ts.map +1 -0
  26. package/dist/registrar/gce-registrar.js +167 -0
  27. package/dist/registrar/gce-registrar.js.map +1 -0
  28. package/dist/registrar/operations.d.ts +61 -0
  29. package/dist/registrar/operations.d.ts.map +1 -0
  30. package/dist/registrar/operations.js +96 -0
  31. package/dist/registrar/operations.js.map +1 -0
  32. package/dist/registry/gce-registry-reader.d.ts +23 -0
  33. package/dist/registry/gce-registry-reader.d.ts.map +1 -0
  34. package/dist/registry/gce-registry-reader.js +42 -0
  35. package/dist/registry/gce-registry-reader.js.map +1 -0
  36. package/dist/tsconfig.tsbuildinfo +1 -0
  37. package/examples/application.ts +208 -0
  38. package/examples/deployment-settings.ts +119 -0
  39. package/examples/gateway.ts +85 -0
  40. package/package.json +41 -0
  41. package/terraform/.terraform.lock.hcl +22 -0
  42. package/terraform/main.tf +402 -0
  43. package/terraform/terraform.tfvars.example +57 -0
  44. package/terraform/variables.tf +319 -0
  45. package/terraform/versions.tf +15 -0
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@spine-event-engine/deployment-gce",
3
+ "version": "2.0.0-snapshot.2",
4
+ "license": "Apache-2.0",
5
+ "type": "module",
6
+ "description": "Google Compute Engine deployment adapter for Spine TS application nodes.",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/SpineEventEngine/spine-ts",
10
+ "directory": "packages/deployment-gce"
11
+ },
12
+ "publishConfig": {
13
+ "registry": "https://registry.npmjs.org/",
14
+ "access": "public",
15
+ "tag": "snapshot"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "REFERENCE.md",
27
+ "examples",
28
+ "terraform"
29
+ ],
30
+ "dependencies": {
31
+ "loglayer": "9.4.0",
32
+ "@spine-event-engine/deployment": "2.0.0-snapshot.2"
33
+ },
34
+ "devDependencies": {
35
+ "@bufbuild/protobuf": "2.12.1",
36
+ "@spine-event-engine/auth": "2.0.0-snapshot.2",
37
+ "@spine-event-engine/server": "2.0.0-snapshot.2",
38
+ "@spine-event-engine/proto": "2.0.0-snapshot.2",
39
+ "@spine-event-engine/storage": "2.0.0-snapshot.2"
40
+ }
41
+ }
@@ -0,0 +1,22 @@
1
+ # This file is maintained automatically by "terraform init".
2
+ # Manual edits may be lost in future updates.
3
+
4
+ provider "registry.terraform.io/hashicorp/google" {
5
+ version = "6.50.0"
6
+ constraints = "~> 6.0"
7
+ hashes = [
8
+ "h1:79CwMTsp3Ud1nOl5hFS5mxQHyT0fGVye7pqpU0PPlHI=",
9
+ "zh:1f3513fcfcbf7ca53d667a168c5067a4dd91a4d4cccd19743e248ff31065503c",
10
+ "zh:3da7db8fc2c51a77dd958ea8baaa05c29cd7f829bd8941c26e2ea9cb3aadc1e5",
11
+ "zh:3e09ac3f6ca8111cbb659d38c251771829f4347ab159a12db195e211c76068bb",
12
+ "zh:7bb9e41c568df15ccf1a8946037355eefb4dfb4e35e3b190808bb7c4abae547d",
13
+ "zh:81e5d78bdec7778e6d67b5c3544777505db40a826b6eb5abe9b86d4ba396866b",
14
+ "zh:8d309d020fb321525883f5c4ea864df3d5942b6087f6656d6d8b3a1377f340fc",
15
+ "zh:93e112559655ab95a523193158f4a4ac0f2bfed7eeaa712010b85ebb551d5071",
16
+ "zh:d3efe589ffd625b300cef5917c4629513f77e3a7b111c9df65075f76a46a63c7",
17
+ "zh:d4a4d672bbef756a870d8f32b35925f8ce2ef4f6bbd5b71a3cb764f1b6c85421",
18
+ "zh:e13a86bca299ba8a118e80d5f84fbdd708fe600ecdceea1a13d4919c068379fe",
19
+ "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
20
+ "zh:fec30c095647b583a246c39d557704947195a1b7d41f81e369ba377d997faef6",
21
+ ]
22
+ }
@@ -0,0 +1,402 @@
1
+ locals {
2
+ application_image_host = split("/", var.application_image)[0]
3
+ delivery_image_host = split("/", var.delivery_image)[0]
4
+ gateway_image_host = split("/", var.gateway_image)[0]
5
+ runtime_ports = [var.application_port, var.gateway_port, var.delivery_port]
6
+ runtime_tags = ["spine-runtime"]
7
+ }
8
+
9
+ resource "google_compute_health_check" "application" {
10
+ name = "spine-application"
11
+ check_interval_sec = 10
12
+ timeout_sec = 5
13
+ healthy_threshold = 2
14
+ unhealthy_threshold = 3
15
+
16
+ tcp_health_check {
17
+ port = var.application_port
18
+ }
19
+ }
20
+
21
+ resource "google_compute_health_check" "gateway" {
22
+ name = "spine-gateway"
23
+ check_interval_sec = 10
24
+ timeout_sec = 5
25
+ healthy_threshold = 2
26
+ unhealthy_threshold = 3
27
+
28
+ tcp_health_check {
29
+ port = var.gateway_port
30
+ }
31
+ }
32
+
33
+ resource "google_compute_health_check" "delivery" {
34
+ name = "spine-delivery"
35
+ check_interval_sec = 10
36
+ timeout_sec = 5
37
+ healthy_threshold = 2
38
+ unhealthy_threshold = 3
39
+
40
+ tcp_health_check {
41
+ port = var.delivery_port
42
+ }
43
+ }
44
+
45
+ resource "google_compute_firewall" "private_runtime" {
46
+ name = "spine-private-runtime"
47
+ network = var.network
48
+ direction = "INGRESS"
49
+ source_ranges = distinct(concat(var.private_source_ranges, var.health_check_source_ranges))
50
+ target_tags = local.runtime_tags
51
+
52
+ allow {
53
+ protocol = "tcp"
54
+ ports = [for port in local.runtime_ports : tostring(port)]
55
+ }
56
+ }
57
+
58
+ resource "google_compute_address" "gateway" {
59
+ name = "spine-gateway"
60
+ region = var.region
61
+ address_type = "INTERNAL"
62
+ purpose = "GCE_ENDPOINT"
63
+ subnetwork = var.subnetwork
64
+ }
65
+
66
+ resource "google_compute_address" "delivery" {
67
+ name = "spine-delivery"
68
+ region = var.region
69
+ address_type = "INTERNAL"
70
+ purpose = "GCE_ENDPOINT"
71
+ subnetwork = var.subnetwork
72
+ }
73
+
74
+ resource "google_compute_instance_template" "application" {
75
+ name_prefix = "spine-application-"
76
+ machine_type = var.machine_type
77
+ tags = local.runtime_tags
78
+
79
+ disk {
80
+ source_image = "projects/cos-cloud/global/images/family/cos-stable"
81
+ auto_delete = true
82
+ boot = true
83
+ }
84
+
85
+ network_interface {
86
+ network = var.network
87
+ subnetwork = var.subnetwork
88
+ }
89
+
90
+ service_account {
91
+ email = var.service_account_email
92
+ scopes = ["cloud-platform"]
93
+ }
94
+
95
+ metadata_startup_script = <<-EOT
96
+ #!/usr/bin/env bash
97
+ set -eu
98
+ export HOME=/var/lib/spine-docker
99
+ export DOCKER_CONFIG="$HOME/.docker"
100
+ mkdir -p "$DOCKER_CONFIG"
101
+ docker-credential-gcr configure-docker --registries=${local.application_image_host}
102
+ docker --config "$DOCKER_CONFIG" run --rm --network host \
103
+ -e HOST=0.0.0.0 \
104
+ -e PORT=${var.application_port} \
105
+ -e APPLICATION_PROCESS_COUNT=${var.application_process_count} \
106
+ -e DELIVERY_SERVER_URL=http://${google_compute_forwarding_rule.delivery.ip_address}:${var.delivery_port} \
107
+ -e DELIVERY_SHARD_COUNT=${var.delivery_shard_count} \
108
+ -e REGISTRY_NAMESPACE=${var.registry_namespace} \
109
+ -e REGISTRY_STORAGE_REFERENCE=${var.registry_storage_reference} \
110
+ -e APPLICATION_SECRET_REFERENCE=${var.application_secret_reference} \
111
+ ${var.application_image}
112
+ EOT
113
+
114
+ lifecycle {
115
+ create_before_destroy = true
116
+ }
117
+ }
118
+
119
+ resource "google_compute_instance_template" "gateway" {
120
+ name_prefix = "spine-gateway-"
121
+ machine_type = var.machine_type
122
+ tags = local.runtime_tags
123
+
124
+ disk {
125
+ source_image = "projects/cos-cloud/global/images/family/cos-stable"
126
+ auto_delete = true
127
+ boot = true
128
+ }
129
+
130
+ network_interface {
131
+ network = var.network
132
+ subnetwork = var.subnetwork
133
+ }
134
+
135
+ service_account {
136
+ email = var.service_account_email
137
+ scopes = ["cloud-platform"]
138
+ }
139
+
140
+ metadata_startup_script = <<-EOT
141
+ #!/usr/bin/env bash
142
+ set -eu
143
+ export HOME=/var/lib/spine-docker
144
+ export DOCKER_CONFIG="$HOME/.docker"
145
+ mkdir -p "$DOCKER_CONFIG"
146
+ docker-credential-gcr configure-docker --registries=${local.gateway_image_host}
147
+ docker --config "$DOCKER_CONFIG" run --rm --network host \
148
+ -e HOST=0.0.0.0 \
149
+ -e PORT=${var.gateway_port} \
150
+ -e REGISTRY_NAMESPACE=${var.registry_namespace} \
151
+ -e REGISTRY_STORAGE_REFERENCE=${var.registry_storage_reference} \
152
+ -e GATEWAY_SECRET_REFERENCE=${var.gateway_secret_reference} \
153
+ ${var.gateway_image}
154
+ EOT
155
+
156
+ lifecycle {
157
+ create_before_destroy = true
158
+ }
159
+ }
160
+
161
+ resource "google_compute_instance_template" "delivery" {
162
+ name_prefix = "spine-delivery-"
163
+ machine_type = var.machine_type
164
+ tags = local.runtime_tags
165
+
166
+ disk {
167
+ source_image = "projects/cos-cloud/global/images/family/cos-stable"
168
+ auto_delete = true
169
+ boot = true
170
+ }
171
+
172
+ network_interface {
173
+ network = var.network
174
+ subnetwork = var.subnetwork
175
+ }
176
+
177
+ service_account {
178
+ email = var.service_account_email
179
+ scopes = ["cloud-platform"]
180
+ }
181
+
182
+ metadata_startup_script = <<-EOT
183
+ #!/usr/bin/env bash
184
+ set -eu
185
+ export HOME=/var/lib/spine-docker
186
+ export DOCKER_CONFIG="$HOME/.docker"
187
+ mkdir -p "$DOCKER_CONFIG"
188
+ docker-credential-gcr configure-docker --registries=${local.delivery_image_host}
189
+ docker --config "$DOCKER_CONFIG" run --rm --network host \
190
+ -e HOST=0.0.0.0 \
191
+ -e PORT=${var.delivery_port} \
192
+ ${var.delivery_image}
193
+ EOT
194
+
195
+ lifecycle {
196
+ create_before_destroy = true
197
+ }
198
+ }
199
+
200
+ resource "google_compute_region_instance_group_manager" "application" {
201
+ name = "spine-application"
202
+ region = var.region
203
+ base_instance_name = "spine-application"
204
+ distribution_policy_zones = var.application_zones
205
+ target_size = var.autoscaling_enabled ? null : var.application_replicas
206
+
207
+ version {
208
+ instance_template = google_compute_instance_template.application.self_link_unique
209
+ }
210
+
211
+ named_port {
212
+ name = "grpc"
213
+ port = var.application_port
214
+ }
215
+
216
+ auto_healing_policies {
217
+ health_check = google_compute_health_check.application.self_link
218
+ initial_delay_sec = var.application_startup_delay_sec
219
+ }
220
+
221
+ update_policy {
222
+ type = "PROACTIVE"
223
+ minimal_action = "REPLACE"
224
+ replacement_method = "SUBSTITUTE"
225
+ max_surge_fixed = length(var.application_zones)
226
+ max_unavailable_fixed = 0
227
+ }
228
+
229
+ lifecycle {
230
+ precondition {
231
+ condition = var.autoscaling_min_replicas <= var.autoscaling_max_replicas
232
+ error_message = "Autoscaling minimum replicas must not exceed the maximum."
233
+ }
234
+
235
+ precondition {
236
+ condition = !var.autoscaling_enabled || var.autoscaling_signal == "cpu" || trimspace(var.autoscaling_metric) != ""
237
+ error_message = "Monitoring autoscaling requires a metric name."
238
+ }
239
+
240
+ precondition {
241
+ condition = !var.autoscaling_enabled || var.autoscaling_signal != "monitoring" || var.autoscaling_min_replicas >= 1 || var.autoscaling_metric_scope == "whole_group"
242
+ error_message = "Monitoring minimum replicas of zero requires a whole_group Monitoring metric."
243
+ }
244
+
245
+ precondition {
246
+ condition = !var.autoscaling_enabled || var.autoscaling_signal != "cpu" || var.autoscaling_min_replicas >= 1
247
+ error_message = "CPU autoscaling requires at least one application node."
248
+ }
249
+
250
+ precondition {
251
+ condition = !var.autoscaling_enabled || var.autoscaling_signal != "monitoring" || (
252
+ var.autoscaling_metric_scope == "per_instance" &&
253
+ can(regex("resource\\.type\\s*=\\s*\"gce_instance\"", var.autoscaling_metric_filter))
254
+ ) || (
255
+ var.autoscaling_metric_scope == "whole_group" &&
256
+ can(regex("resource\\.type\\s*=", var.autoscaling_metric_filter)) &&
257
+ !can(regex("resource\\.type\\s*=\\s*\"gce_instance\"", var.autoscaling_metric_filter))
258
+ )
259
+ error_message = "per_instance Monitoring metrics require resource.type = \"gce_instance\"; whole_group metrics require a resource.type other than \"gce_instance\"."
260
+ }
261
+ }
262
+ }
263
+
264
+ resource "google_compute_region_autoscaler" "application" {
265
+ count = var.autoscaling_enabled ? 1 : 0
266
+ name = "spine-application"
267
+ region = var.region
268
+ target = google_compute_region_instance_group_manager.application.id
269
+
270
+ autoscaling_policy {
271
+ min_replicas = var.autoscaling_min_replicas
272
+ max_replicas = var.autoscaling_max_replicas
273
+
274
+ dynamic "cpu_utilization" {
275
+ for_each = var.autoscaling_signal == "cpu" ? [true] : []
276
+ content {
277
+ target = var.autoscaling_target
278
+ }
279
+ }
280
+
281
+ dynamic "metric" {
282
+ for_each = var.autoscaling_signal == "cpu" ? [] : [true]
283
+ content {
284
+ name = var.autoscaling_metric
285
+ filter = var.autoscaling_metric_filter
286
+ type = var.autoscaling_metric_target_type
287
+ target = var.autoscaling_target
288
+ }
289
+ }
290
+ }
291
+ }
292
+
293
+ resource "google_compute_region_instance_group_manager" "gateway" {
294
+ name = "spine-gateway"
295
+ region = var.region
296
+ base_instance_name = "spine-gateway"
297
+ distribution_policy_zones = [var.singleton_zone]
298
+ target_size = 1
299
+
300
+ version {
301
+ instance_template = google_compute_instance_template.gateway.self_link_unique
302
+ }
303
+
304
+ named_port {
305
+ name = "gateway"
306
+ port = var.gateway_port
307
+ }
308
+
309
+ auto_healing_policies {
310
+ health_check = google_compute_health_check.gateway.self_link
311
+ initial_delay_sec = var.application_startup_delay_sec
312
+ }
313
+
314
+ update_policy {
315
+ type = "PROACTIVE"
316
+ minimal_action = "REPLACE"
317
+ replacement_method = "SUBSTITUTE"
318
+ max_surge_fixed = 0
319
+ max_unavailable_fixed = 1
320
+ }
321
+ }
322
+
323
+ resource "google_compute_region_instance_group_manager" "delivery" {
324
+ name = "spine-delivery"
325
+ region = var.region
326
+ base_instance_name = "spine-delivery"
327
+ distribution_policy_zones = [var.singleton_zone]
328
+ target_size = 1
329
+
330
+ version {
331
+ instance_template = google_compute_instance_template.delivery.self_link_unique
332
+ }
333
+
334
+ named_port {
335
+ name = "grpc"
336
+ port = var.delivery_port
337
+ }
338
+
339
+ auto_healing_policies {
340
+ health_check = google_compute_health_check.delivery.self_link
341
+ initial_delay_sec = var.application_startup_delay_sec
342
+ }
343
+
344
+ update_policy {
345
+ type = "PROACTIVE"
346
+ minimal_action = "REPLACE"
347
+ replacement_method = "SUBSTITUTE"
348
+ max_surge_fixed = 0
349
+ max_unavailable_fixed = 1
350
+ }
351
+ }
352
+
353
+ resource "google_compute_region_backend_service" "gateway" {
354
+ name = "spine-gateway"
355
+ region = var.region
356
+ protocol = "TCP"
357
+ load_balancing_scheme = "INTERNAL"
358
+ health_checks = [google_compute_health_check.gateway.id]
359
+
360
+ backend {
361
+ group = google_compute_region_instance_group_manager.gateway.instance_group
362
+ }
363
+ }
364
+
365
+ resource "google_compute_region_backend_service" "delivery" {
366
+ name = "spine-delivery"
367
+ region = var.region
368
+ protocol = "TCP"
369
+ load_balancing_scheme = "INTERNAL"
370
+ health_checks = [google_compute_health_check.delivery.id]
371
+
372
+ backend {
373
+ group = google_compute_region_instance_group_manager.delivery.instance_group
374
+ }
375
+ }
376
+
377
+ resource "google_compute_forwarding_rule" "gateway" {
378
+ name = "spine-gateway"
379
+ region = var.region
380
+ load_balancing_scheme = "INTERNAL"
381
+ backend_service = google_compute_region_backend_service.gateway.id
382
+ network = var.network
383
+ subnetwork = var.subnetwork
384
+ ip_address = google_compute_address.gateway.id
385
+ ports = [tostring(var.gateway_port)]
386
+ }
387
+
388
+ resource "google_compute_forwarding_rule" "delivery" {
389
+ name = "spine-delivery"
390
+ region = var.region
391
+ load_balancing_scheme = "INTERNAL"
392
+ backend_service = google_compute_region_backend_service.delivery.id
393
+ network = var.network
394
+ subnetwork = var.subnetwork
395
+ ip_address = google_compute_address.delivery.id
396
+ ports = [tostring(var.delivery_port)]
397
+ }
398
+
399
+ output "gateway_private_address" {
400
+ description = "Stable private address for an operator-managed TLS and authentication edge."
401
+ value = google_compute_forwarding_rule.gateway.ip_address
402
+ }
@@ -0,0 +1,57 @@
1
+ # This module never creates a public TLS/authentication edge. It creates only
2
+ # private VM listeners, so select the existing VPC and subnetwork deliberately.
3
+ project_id = "my-project"
4
+ region = "us-central1"
5
+ application_zones = ["us-central1-a", "us-central1-b"]
6
+ singleton_zone = "us-central1-a"
7
+ network = "projects/my-project/global/networks/private"
8
+ subnetwork = "projects/my-project/regions/us-central1/subnetworks/apps"
9
+
10
+ # Use a least-privilege account that can access only the application-owned
11
+ # configuration and external secret references required by these images.
12
+ service_account_email = "spine-runtime@my-project.iam.gserviceaccount.com"
13
+
14
+ # Replace each placeholder with a published immutable image digest.
15
+ application_image = "us-docker.pkg.dev/my-project/spine/application@sha256:REPLACE"
16
+ gateway_image = "us-docker.pkg.dev/my-project/spine/gateway@sha256:REPLACE"
17
+ delivery_image = "us-docker.pkg.dev/my-project/spine/simple-delivery@sha256:REPLACE"
18
+
19
+ # The images interpret these references. Terraform neither reads nor stores
20
+ # secret values and does not select an application storage engine.
21
+ registry_namespace = "production-application-nodes"
22
+ registry_storage_reference = "spine-production-storage"
23
+ application_secret_reference = "projects/my-project/secrets/application-runtime"
24
+ gateway_secret_reference = "projects/my-project/secrets/gateway-runtime"
25
+
26
+ # Manual capacity applies while autoscaling is disabled.
27
+ application_replicas = 2
28
+
29
+ # Set both values deliberately. They are independent: one VM starts this many
30
+ # complete application children, while the application builds its contexts with
31
+ # this many Delivery shards.
32
+ application_process_count = 2
33
+ delivery_shard_count = 2
34
+
35
+ # GCE owns capacity when this is true. CPU uses a utilization target. Monitoring
36
+ # uses a separately chosen metric name, resource/series filter, target kind,
37
+ # and scope. Per-instance filters must use `resource.type = "gce_instance"`.
38
+ # Whole-group filters must use another resource type, such as `global`.
39
+ autoscaling_enabled = false
40
+ autoscaling_signal = "cpu"
41
+ autoscaling_metric_scope = "per_instance"
42
+ autoscaling_metric = ""
43
+ autoscaling_metric_filter = "resource.type = \"gce_instance\""
44
+ autoscaling_metric_target_type = "GAUGE"
45
+ autoscaling_target = 0.7
46
+ autoscaling_min_replicas = 1
47
+ autoscaling_max_replicas = 8
48
+
49
+ # A whole-group Monitoring metric can wake an empty group only when it exists
50
+ # without an application VM. Use these explicit values for that case:
51
+ # autoscaling_signal = "monitoring"
52
+ # autoscaling_metric_scope = "whole_group"
53
+ # autoscaling_metric = "custom.googleapis.com/spine/pending_work"
54
+ # autoscaling_metric_filter = "resource.type = \"global\""
55
+ # autoscaling_metric_target_type = "GAUGE"
56
+ # autoscaling_target = 1
57
+ # autoscaling_min_replicas = 0