@spine-event-engine/deployment-gce 2.0.0-snapshot.11
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.
- package/LICENSE +201 -0
- package/README.md +373 -0
- package/REFERENCE.md +98 -0
- package/dist/discovery/gce-node-discovery.d.ts +56 -0
- package/dist/discovery/gce-node-discovery.d.ts.map +1 -0
- package/dist/discovery/gce-node-discovery.js +78 -0
- package/dist/discovery/gce-node-discovery.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata/gce-metadata-service.d.ts +47 -0
- package/dist/metadata/gce-metadata-service.d.ts.map +1 -0
- package/dist/metadata/gce-metadata-service.js +65 -0
- package/dist/metadata/gce-metadata-service.js.map +1 -0
- package/dist/node/application-node.d.ts +35 -0
- package/dist/node/application-node.d.ts.map +1 -0
- package/dist/node/application-node.js +46 -0
- package/dist/node/application-node.js.map +1 -0
- package/dist/registrar/gce-registrar-log.d.ts +11 -0
- package/dist/registrar/gce-registrar-log.d.ts.map +1 -0
- package/dist/registrar/gce-registrar-log.js +44 -0
- package/dist/registrar/gce-registrar-log.js.map +1 -0
- package/dist/registrar/gce-registrar.d.ts +90 -0
- package/dist/registrar/gce-registrar.d.ts.map +1 -0
- package/dist/registrar/gce-registrar.js +167 -0
- package/dist/registrar/gce-registrar.js.map +1 -0
- package/dist/registrar/operations.d.ts +61 -0
- package/dist/registrar/operations.d.ts.map +1 -0
- package/dist/registrar/operations.js +96 -0
- package/dist/registrar/operations.js.map +1 -0
- package/dist/registry/gce-registry-reader.d.ts +23 -0
- package/dist/registry/gce-registry-reader.d.ts.map +1 -0
- package/dist/registry/gce-registry-reader.js +42 -0
- package/dist/registry/gce-registry-reader.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/examples/application.ts +208 -0
- package/examples/deployment-settings.ts +119 -0
- package/examples/gateway.ts +86 -0
- package/package.json +40 -0
- package/terraform/.terraform.lock.hcl +22 -0
- package/terraform/main.tf +402 -0
- package/terraform/terraform.tfvars.example +57 -0
- package/terraform/variables.tf +319 -0
- package/terraform/versions.tf +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# Deploy a Spine TS application on GCE
|
|
2
|
+
|
|
3
|
+
`@spine-event-engine/deployment-gce` helps you run one Spine TS application
|
|
4
|
+
across several Google Compute Engine (GCE) virtual machines. It includes an
|
|
5
|
+
editable Terraform template and small TypeScript entrypoints. The template
|
|
6
|
+
keeps application nodes, the Gateway, and the simple delivery server private;
|
|
7
|
+
you provide the public TLS, authentication, and traffic-routing edge that fits
|
|
8
|
+
your organisation.
|
|
9
|
+
|
|
10
|
+
This experimental snapshot package is for operators who already own
|
|
11
|
+
the application images and their Google Cloud environment. Install its library
|
|
12
|
+
API when writing the application or Gateway entrypoints:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @spine-event-engine/deployment-gce@snapshot
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Copy the packaged Terraform directory into your deployment repository only
|
|
19
|
+
when you want its GCE reference topology; installing the library neither
|
|
20
|
+
creates cloud resources nor configures your application Gateway, authentication,
|
|
21
|
+
sessions, secrets, or storage.
|
|
22
|
+
|
|
23
|
+
For exact discovery, lease, and lifecycle behavior, read the
|
|
24
|
+
[deployment reference](REFERENCE.md).
|
|
25
|
+
|
|
26
|
+
## Before you begin
|
|
27
|
+
|
|
28
|
+
You need an existing Google Cloud project, VPC network, regional subnetwork,
|
|
29
|
+
and a service account with only the permissions required by your images and
|
|
30
|
+
their application configuration. Install [Google Cloud CLI](https://cloud.google.com/sdk/docs/install)
|
|
31
|
+
and Terraform 1.6 or newer. Authenticate Terraform through Application Default
|
|
32
|
+
Credentials:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
gcloud auth application-default login
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Build and publish three immutable images: your application, one standalone
|
|
39
|
+
Gateway, and the in-memory simple delivery server. Use image digests such as
|
|
40
|
+
`@sha256:...`, not mutable tags. The template deliberately does not publish an
|
|
41
|
+
image, choose Datastore, MySQL, or another storage backend, create secrets, or
|
|
42
|
+
configure an identity provider.
|
|
43
|
+
|
|
44
|
+
## First Terraform plan
|
|
45
|
+
|
|
46
|
+
Copy the packaged template into an operator-owned deployment repository. The
|
|
47
|
+
following commands assume its Terraform directory is your current directory;
|
|
48
|
+
create a values file and obtain a validated plan before wiring advanced Gateway
|
|
49
|
+
or registry behavior:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cp terraform.tfvars.example terraform.tfvars
|
|
53
|
+
terraform init
|
|
54
|
+
terraform fmt -check
|
|
55
|
+
terraform validate
|
|
56
|
+
terraform plan -var-file=terraform.tfvars
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Replace every placeholder with your existing project, network, service account,
|
|
60
|
+
and immutable image digests before `plan`. A plan showing only private Spine
|
|
61
|
+
resources is the first success; review it before any `terraform apply`.
|
|
62
|
+
|
|
63
|
+
## What this deployment creates
|
|
64
|
+
|
|
65
|
+
The application managed instance group (MIG) is regional and distributes
|
|
66
|
+
identical application nodes across your selected zones. A ready node registers
|
|
67
|
+
its private listener in a durable application registry. The single
|
|
68
|
+
Gateway reads the same registry every 10 seconds and routes commands, queries,
|
|
69
|
+
and subscriptions to the live nodes.
|
|
70
|
+
|
|
71
|
+
```mermaid
|
|
72
|
+
flowchart LR
|
|
73
|
+
Edge["Your TLS and authentication edge"] --> Gateway["One private Gateway"]
|
|
74
|
+
Gateway --> Registry["Durable application node registry"]
|
|
75
|
+
Registry --> Gateway
|
|
76
|
+
Gateway --> AppA["Application node A"]
|
|
77
|
+
Gateway --> AppB["Application node B"]
|
|
78
|
+
AppA --> Delivery["One in-memory simple delivery server"]
|
|
79
|
+
AppB --> Delivery
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The template gives the Gateway and delivery server separate one-instance MIGs
|
|
83
|
+
and stable private addresses behind internal passthrough load balancers. This
|
|
84
|
+
is easy to inspect and lets an operator move them to separate failure or
|
|
85
|
+
resource boundaries. A smaller deployment may colocate the two processes, but
|
|
86
|
+
the simple delivery server remains in-memory: it is neither durable nor highly
|
|
87
|
+
available.
|
|
88
|
+
|
|
89
|
+
This is a fixed, single-Gateway topology. Multiple Gateways and Cloud Run are
|
|
90
|
+
outside the supported offering.
|
|
91
|
+
|
|
92
|
+
Every listener receives only private VPC traffic and health checks. Terraform
|
|
93
|
+
does not create an external IP, public load balancer, TLS certificate,
|
|
94
|
+
authentication provider, or Internet firewall rule.
|
|
95
|
+
|
|
96
|
+
## Configure the template
|
|
97
|
+
|
|
98
|
+
Edit the existing `terraform.tfvars` copied for the first plan and replace every placeholder. Keep it outside source
|
|
99
|
+
control because it identifies your network and deployment, even though it
|
|
100
|
+
contains no secret values.
|
|
101
|
+
|
|
102
|
+
Set the project, region, two or more application zones, VPC/subnetwork,
|
|
103
|
+
least-privilege service-account email, and the three image digests. Set
|
|
104
|
+
`registry_namespace` and `registry_storage_reference` identically for
|
|
105
|
+
application and Gateway images. Each entrypoint passes that reference to its
|
|
106
|
+
application-supplied `registryStorage.storageFactoryFor(reference)` resolver. The
|
|
107
|
+
resolver chooses the shared durable `StorageFactory`; Terraform only transports
|
|
108
|
+
the reference and never chooses a storage engine.
|
|
109
|
+
|
|
110
|
+
`application_secret_reference` and `gateway_secret_reference` are identifiers
|
|
111
|
+
your images resolve through the selected configuration mechanism. They are passed
|
|
112
|
+
as environment values only. Terraform neither reads secret values nor writes
|
|
113
|
+
them into state.
|
|
114
|
+
|
|
115
|
+
Grant the VM service account `roles/artifactregistry.reader` on each Artifact
|
|
116
|
+
Registry repository that stores these images. The template uses the broad
|
|
117
|
+
`cloud-platform` OAuth scope so Google Cloud IAM can evaluate that role; the IAM
|
|
118
|
+
role, not the scope alone, authorizes image pulls.
|
|
119
|
+
|
|
120
|
+
Every image must use an Artifact Registry host such as
|
|
121
|
+
`us-docker.pkg.dev`. Each COS startup script extracts that exact host from its
|
|
122
|
+
configured immutable image name, creates a writable Docker configuration under
|
|
123
|
+
`/var/lib/spine-docker`, and runs `docker-credential-gcr configure-docker` for
|
|
124
|
+
that host before it pulls the image. The helper uses the attached VM service
|
|
125
|
+
account; no registry credential or key is placed in Terraform, metadata, or the
|
|
126
|
+
Docker command.
|
|
127
|
+
|
|
128
|
+
## Connect your application entrypoints
|
|
129
|
+
|
|
130
|
+
The application image starts a managed application node. Its private VM port is
|
|
131
|
+
the Node Coordinator port; managed children are complete application replicas
|
|
132
|
+
on loopback and are never discoverable directly. The deployer must set both
|
|
133
|
+
`application_process_count` and `delivery_shard_count` in Terraform. They are
|
|
134
|
+
independent: the first selects Node processes, while the second is passed to
|
|
135
|
+
your context assembly for its explicit Delivery strategy.
|
|
136
|
+
|
|
137
|
+
Use the complete, tested
|
|
138
|
+
[`GceApplicationEntrypoint`](examples/application.ts) instead of copying only
|
|
139
|
+
its happy path. It starts the managed application, publishes the Coordinator
|
|
140
|
+
only after it is ready, withdraws that lease before shutdown, and preserves
|
|
141
|
+
startup or cleanup failures. Supply your complete child assembly through
|
|
142
|
+
`createServer`, and pass the same environment object when testing custom
|
|
143
|
+
deployment values. The framework-owned `SPINE_MANAGED_SERVER_CHILD` marker is
|
|
144
|
+
read from that environment so child executions do not publish separate leases.
|
|
145
|
+
|
|
146
|
+
The Gateway resolves the same storage reference and namespace through its
|
|
147
|
+
environment. It manages a GCE discovery lifecycle that refreshes the complete
|
|
148
|
+
live-node registry snapshot every 10 seconds, stops that schedule when the
|
|
149
|
+
browser server stops, and then closes its registry. Supply browser collaborators
|
|
150
|
+
in `browserOptions`. Choose one admission mode there: authenticated mode
|
|
151
|
+
supplies `sessions` and may supply named durable subscription bindings; public
|
|
152
|
+
mode supplies `publicAccess: true`, and the framework owns process-local
|
|
153
|
+
bindings. Public mode cannot supply bindings.
|
|
154
|
+
|
|
155
|
+
<!-- docs-snippet-path: packages/deployment-gce/examples/gateway.ts -->
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { LeasedNodeRegistry } from "@spine-event-engine/deployment";
|
|
159
|
+
import { GceNodeDiscovery } from "@spine-event-engine/deployment-gce";
|
|
160
|
+
import { BrowserServer, type BrowserServerOptions } from "@spine-event-engine/server/browser";
|
|
161
|
+
|
|
162
|
+
type GatewayBrowserOptions = BrowserServerOptions extends infer Options
|
|
163
|
+
? Options extends BrowserServerOptions
|
|
164
|
+
? Omit<Options, "host" | "port" | "discovery">
|
|
165
|
+
: never
|
|
166
|
+
: never;
|
|
167
|
+
|
|
168
|
+
import {
|
|
169
|
+
GceDeploymentSettings,
|
|
170
|
+
type DeploymentEnvironment,
|
|
171
|
+
type RegistryStorageResolver,
|
|
172
|
+
} from "./deployment-settings.js";
|
|
173
|
+
|
|
174
|
+
export interface GatewayOptions {
|
|
175
|
+
readonly browser: GatewayBrowserOptions;
|
|
176
|
+
readonly registryStorage: RegistryStorageResolver;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const GceGatewayEntrypoint = Object.freeze({
|
|
180
|
+
async run(
|
|
181
|
+
options: GatewayOptions,
|
|
182
|
+
environment: DeploymentEnvironment = process.env,
|
|
183
|
+
): Promise<void> {
|
|
184
|
+
const registry = new LeasedNodeRegistry({
|
|
185
|
+
factory: options.registryStorage.storageFactoryFor(
|
|
186
|
+
GceDeploymentSettings.registryStorageReference(environment),
|
|
187
|
+
),
|
|
188
|
+
namespace: GceDeploymentSettings.registryNamespace(environment),
|
|
189
|
+
});
|
|
190
|
+
const discovery = new GceNodeDiscovery({ registry });
|
|
191
|
+
await BrowserServer.run({
|
|
192
|
+
host: "0.0.0.0",
|
|
193
|
+
port: GceDeploymentSettings.port(environment, "PORT"),
|
|
194
|
+
...options.browser,
|
|
195
|
+
discovery,
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The complete, packaged examples are
|
|
202
|
+
[`examples/application.ts`](examples/application.ts) and
|
|
203
|
+
[`examples/gateway.ts`](examples/gateway.ts). They deliberately keep business
|
|
204
|
+
contexts, storage configuration, identity, and (when using authenticated mode)
|
|
205
|
+
durable subscription bindings in your application code. The GCE entrypoint's returned handle withdraws its
|
|
206
|
+
Coordinator lease before stopping child replicas; abrupt VM loss remains covered
|
|
207
|
+
by the existing lease expiry. The managed child marker is framework-owned
|
|
208
|
+
process state: it makes the shared module assemble a child replica without
|
|
209
|
+
creating a competing VM lease.
|
|
210
|
+
|
|
211
|
+
## Deploy
|
|
212
|
+
|
|
213
|
+
Initialize the provider, then make the first safe success a formatting,
|
|
214
|
+
validation, and plan review. Apply only the reviewed plan.
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
terraform init
|
|
218
|
+
terraform fmt -check
|
|
219
|
+
terraform validate
|
|
220
|
+
terraform plan -out=tfplan
|
|
221
|
+
terraform apply tfplan
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The plan should contain one application MIG, one Gateway MIG, one delivery MIG,
|
|
225
|
+
and private load balancers only. The [reference](REFERENCE.md) records scaling,
|
|
226
|
+
replacement, rollback, registry, and image-pull limits before you change those
|
|
227
|
+
operations.
|
|
228
|
+
|
|
229
|
+
The first VM can take several minutes to start. Autohealing waits for the
|
|
230
|
+
configured startup delay (120 seconds by default) before treating a failed
|
|
231
|
+
application listener as unhealthy.
|
|
232
|
+
|
|
233
|
+
The template uses Container-Optimized OS and runs each image with a startup
|
|
234
|
+
script. It configures the exact Artifact Registry host with
|
|
235
|
+
`docker-credential-gcr`, then uses `docker run --rm --network host`. If a
|
|
236
|
+
container exits, Docker removes it and the listener health check fails; the MIG
|
|
237
|
+
can then repair that VM after the startup delay. Use
|
|
238
|
+
`journalctl -u google-startup-scripts.service` or the VM serial-console log to
|
|
239
|
+
diagnose credential-helper, startup-script, and container failures.
|
|
240
|
+
|
|
241
|
+
## Verify the deployment
|
|
242
|
+
|
|
243
|
+
Check that GCE created the three groups and that all intended instances become
|
|
244
|
+
healthy:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
gcloud compute instance-groups managed list --regions=REGION
|
|
248
|
+
gcloud compute instance-groups managed list-instances spine-application --region=REGION
|
|
249
|
+
terraform output gateway_private_address
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
From a trusted VPC client or your operator-managed edge, reach the returned
|
|
253
|
+
Gateway address. Post a command, query its Projection, and activate a durable
|
|
254
|
+
subscription. The Gateway may need one 10-second refresh interval after a
|
|
255
|
+
fresh application node becomes ready.
|
|
256
|
+
|
|
257
|
+
Each VM obtains a unique registration identity for its ready Coordinator,
|
|
258
|
+
renews its lease every 20 seconds, and leases it for 60 seconds. A graceful
|
|
259
|
+
entrypoint close withdraws that lease before it stops its managed children. A
|
|
260
|
+
crash may leave a row behind temporarily, but it is ignored after expiry;
|
|
261
|
+
healthy registrars perform finite cleanup.
|
|
262
|
+
|
|
263
|
+
## Scale application nodes
|
|
264
|
+
|
|
265
|
+
With the default `autoscaling_enabled = false`, Terraform controls manual capacity:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
terraform apply -var='application_replicas=4'
|
|
269
|
+
terraform apply -var='application_replicas=0'
|
|
270
|
+
terraform apply -var='application_replicas=2'
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
At zero nodes, the registry becomes empty after at most the 60-second lease
|
|
274
|
+
expiry. The Gateway remains alive but reports backend unavailability until a
|
|
275
|
+
node returns; it keeps refreshing every 10 seconds. If an autoscaler is already
|
|
276
|
+
enabled, first remove it in the same transition:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
terraform apply -var='autoscaling_enabled=false' -var='application_replicas=0'
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
To let Compute Engine scale the same application version, set
|
|
283
|
+
`autoscaling_enabled = true`. For `autoscaling_signal = "cpu"`,
|
|
284
|
+
`autoscaling_target` is the CPU utilization target. For
|
|
285
|
+
`autoscaling_signal = "monitoring"`, choose the metric name, a filter that
|
|
286
|
+
selects only the intended resource and series, a metric target kind, and the
|
|
287
|
+
declared `per_instance` or `whole_group` scope. A per-instance filter must set
|
|
288
|
+
`resource.type = "gce_instance"`; a whole-group filter must select another
|
|
289
|
+
resource type, such as `global`. Terraform validates that relationship, then
|
|
290
|
+
omits the MIG size and GCE is the sole capacity owner.
|
|
291
|
+
|
|
292
|
+
CPU and per-instance metrics require a running VM, so they cannot scale from
|
|
293
|
+
zero. For scale-from-zero, use a `whole_group` Cloud Monitoring metric that is
|
|
294
|
+
produced while no application VM exists, and set
|
|
295
|
+
`autoscaling_min_replicas = 0`; otherwise use an operator action or schedule.
|
|
296
|
+
Internal passthrough load-balancer utilization is not a suitable autoscaling
|
|
297
|
+
signal for this topology.
|
|
298
|
+
|
|
299
|
+
## Replace an application version
|
|
300
|
+
|
|
301
|
+
For a compatible change, publish a new immutable application digest, set it in
|
|
302
|
+
`terraform.tfvars`, and apply. The regional MIG performs a proactive rolling
|
|
303
|
+
replacement with one permitted surge instance for each selected application
|
|
304
|
+
zone and no planned unavailable instance. Old and new nodes can overlap, so
|
|
305
|
+
they must understand the same stored data and messages during the rollout.
|
|
306
|
+
|
|
307
|
+
For an incompatible business-logic or data change, first disable any enabled
|
|
308
|
+
autoscaler and set application capacity to zero. Wait until the old nodes exit,
|
|
309
|
+
apply the new image while capacity remains zero, then restore manual capacity
|
|
310
|
+
or explicitly enable the new autoscaler. This framework does not negotiate
|
|
311
|
+
compatibility. Pending Inbox work may execute under the new version, so make
|
|
312
|
+
the change safe for those messages before starting it.
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
terraform apply -var='autoscaling_enabled=false' -var='application_replicas=0'
|
|
316
|
+
test -z "$(gcloud compute instance-groups managed list-instances spine-application --region=REGION --format='value(instance)')"
|
|
317
|
+
# Set the new application_image digest in terraform.tfvars.
|
|
318
|
+
terraform apply -var='autoscaling_enabled=false' -var='application_replicas=0'
|
|
319
|
+
terraform apply -var='autoscaling_enabled=false' -var='application_replicas=2'
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The Gateway normally stays in place during an application replacement. A
|
|
323
|
+
Gateway interruption disconnects browser clients. In authenticated mode, durable
|
|
324
|
+
subscription definitions survive only when the supplied named bindings use the
|
|
325
|
+
same persistent application storage. Public-mode definitions are process-local
|
|
326
|
+
and end with the Gateway process. Clients reconnect and issue an authoritative query.
|
|
327
|
+
Replacing `gateway_image` performs its explicit one-unavailable, zero-surge
|
|
328
|
+
singleton update and therefore causes that interruption. Replacing
|
|
329
|
+
`delivery_image` performs the same singleton update; the supplied in-memory
|
|
330
|
+
delivery server loses its state whenever its process stops.
|
|
331
|
+
|
|
332
|
+
## Roll back
|
|
333
|
+
|
|
334
|
+
To roll back a compatible application image, restore the prior immutable digest
|
|
335
|
+
and run `terraform apply`. GCE creates the previous instance template and
|
|
336
|
+
performs the same rolling update. For an incompatible rollback, disable an
|
|
337
|
+
enabled autoscaler, reduce the application group to zero, wait for shutdown,
|
|
338
|
+
apply the prior image, then restore manual capacity or deliberately re-enable
|
|
339
|
+
autoscaling. Confirm the group is empty before applying the prior image:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
terraform apply -var='autoscaling_enabled=false' -var='application_replicas=0'
|
|
343
|
+
test -z "$(gcloud compute instance-groups managed list-instances spine-application --region=REGION --format='value(instance)')"
|
|
344
|
+
# Restore the prior application_image digest in terraform.tfvars.
|
|
345
|
+
terraform apply -var='autoscaling_enabled=false' -var='application_replicas=0'
|
|
346
|
+
terraform apply -var='autoscaling_enabled=false' -var='application_replicas=2'
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Troubleshooting
|
|
350
|
+
|
|
351
|
+
| Symptom | Check |
|
|
352
|
+
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
353
|
+
| Gateway has no backend | Confirm the application group has healthy instances, registry references and namespace match, then allow a 10-second refresh. |
|
|
354
|
+
| A node remains listed after a crash | Wait up to 60 seconds for lease expiry; cleanup is finite and does not make expired nodes routable. |
|
|
355
|
+
| An application VM keeps restarting | Check the image starts its listener on `HOST=0.0.0.0` and `PORT`, and increase the startup delay only when its real startup needs it. |
|
|
356
|
+
| A client cannot reach the Gateway | Reach the private output from the VPC or configure a separate TLS/authentication edge; this module creates no public path. |
|
|
357
|
+
| Autoscaling does not wake zero nodes | CPU and per-instance metrics cannot observe an empty group; use a whole-group metric with `autoscaling_min_replicas = 0` or set a manual/scheduled minimum. |
|
|
358
|
+
| Delivery state disappeared | The supplied delivery server is in-memory and loses state whenever it is replaced or restarted. Do not describe it as durable or highly available. |
|
|
359
|
+
| A container fails during startup | Read `journalctl -u google-startup-scripts.service` or serial-console output; `docker run --rm` removes an exited container and health checks trigger MIG repair. |
|
|
360
|
+
|
|
361
|
+
## Remove the deployment
|
|
362
|
+
|
|
363
|
+
Disable any enabled autoscaler and remove application capacity first when you
|
|
364
|
+
need a controlled domain shutdown, then destroy the infrastructure:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
terraform apply -var='autoscaling_enabled=false' -var='application_replicas=0'
|
|
368
|
+
terraform destroy
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`terraform destroy` removes only resources created by this template. It does not
|
|
372
|
+
delete the application registry, application storage, externally managed
|
|
373
|
+
secrets, images, VPC, or an operator-managed public edge.
|
package/REFERENCE.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# GCE deployment reference
|
|
2
|
+
|
|
3
|
+
Read the [deployment guide](README.md) before using this exact template
|
|
4
|
+
contract. It supports one Gateway; Multiple-Gateway routing and Cloud Run are
|
|
5
|
+
outside this offering.
|
|
6
|
+
|
|
7
|
+
The library supplies discovery and entrypoint helpers. The editable Terraform
|
|
8
|
+
reference is a separate operator choice, and neither it nor this package owns
|
|
9
|
+
the public Gateway policy, authentication/session implementation, secrets, or
|
|
10
|
+
application storage.
|
|
11
|
+
|
|
12
|
+
## Deployment template
|
|
13
|
+
|
|
14
|
+
The packaged `terraform` directory is an editable Google Compute Engine
|
|
15
|
+
reference topology. It requires existing private network/subnetwork, service
|
|
16
|
+
account, external configuration/secret identifiers, immutable image digests,
|
|
17
|
+
and application-selected durable storage. It creates a regional application
|
|
18
|
+
MIG, a one-instance Gateway MIG, and a one-instance in-memory delivery-server
|
|
19
|
+
MIG. Internal passthrough load balancers provide stable private Gateway and
|
|
20
|
+
delivery addresses. It intentionally creates no public edge, TLS certificate,
|
|
21
|
+
identity provider, storage engine, secret value, Cloud Run resource, or second
|
|
22
|
+
Gateway.
|
|
23
|
+
|
|
24
|
+
The application group has a 120-second default autohealing startup delay. Its
|
|
25
|
+
manual target size is present only when `autoscaling_enabled` is false; an
|
|
26
|
+
enabled regional autoscaler is the sole owner of capacity. CPU and
|
|
27
|
+
per-instance Monitoring metrics cannot revive a zero-instance group. A
|
|
28
|
+
whole-group Monitoring metric can, if the operator supplies one that continues
|
|
29
|
+
to exist at zero capacity.
|
|
30
|
+
|
|
31
|
+
The template uses Container-Optimized OS plus a `docker run` startup script. It
|
|
32
|
+
does not use the deprecated container startup agent or a
|
|
33
|
+
`gce-container-declaration` metadata value. Each immutable image must use an
|
|
34
|
+
Artifact Registry `*-docker.pkg.dev` host. The relevant COS startup script
|
|
35
|
+
extracts that host from the image, exports a writable
|
|
36
|
+
`HOME=/var/lib/spine-docker` and `DOCKER_CONFIG=$HOME/.docker`, then invokes
|
|
37
|
+
`docker-credential-gcr configure-docker --registries=<exact-host>` before its
|
|
38
|
+
Docker pull. This stores only credential-helper configuration; the helper uses
|
|
39
|
+
the attached service account, whose repository-level
|
|
40
|
+
`roles/artifactregistry.reader` and `cloud-platform` OAuth scope authorize the
|
|
41
|
+
pull.
|
|
42
|
+
|
|
43
|
+
## Metadata and node identity
|
|
44
|
+
|
|
45
|
+
`GceMetadataService.read(signal)` requests project ID, zone, numeric instance
|
|
46
|
+
ID, and `network-interfaces/0/ip` from the GCE metadata service. Every request
|
|
47
|
+
uses `Metadata-Flavor: Google`; unsuccessful, empty, or malformed responses
|
|
48
|
+
reject, as does cancellation. `GceMetadataProvider` is injectable for tests or
|
|
49
|
+
an operator-managed metadata integration.
|
|
50
|
+
|
|
51
|
+
`GceApplicationNode.create(metadata, options)` returns the stable ID
|
|
52
|
+
`gce/<project>/<zone>/<numeric-instance-id>`. `port` must be a safe TCP port.
|
|
53
|
+
Without an override the endpoint is private-address HTTP; an explicit canonical
|
|
54
|
+
HTTP(S) endpoint and compatible TLS server name override it. No public address
|
|
55
|
+
is inferred. In a managed application entrypoint, this endpoint is the
|
|
56
|
+
node-local Coordinator listener. Managed child listeners are loopback-only and
|
|
57
|
+
are never published. The registrar persists this identity through the
|
|
58
|
+
deployment package's approved application-node lease record and typed node ID.
|
|
59
|
+
|
|
60
|
+
## Registrar
|
|
61
|
+
|
|
62
|
+
`new GceRegistrar(options)` requires either a ready `node` or a metadata port.
|
|
63
|
+
When `node` and `metadata` are both omitted, it creates `GceMetadataService`
|
|
64
|
+
automatically. It creates a UUID registration identity unless `identity` is
|
|
65
|
+
supplied. The injected `scheduler`, `now`, and `deadlines` seams make timing
|
|
66
|
+
deterministic. `operationTimeoutMs` is a positive safe integer and defaults to
|
|
67
|
+
20,000 milliseconds.
|
|
68
|
+
|
|
69
|
+
Call `start()` only after listener readiness. The initial record expires after
|
|
70
|
+
60,000 milliseconds; scheduled renewal runs at 20,000-millisecond intervals.
|
|
71
|
+
Initial write failure or loss leaves the registrar unconfirmed. The next cycle
|
|
72
|
+
looks up the exact row: the same live identity confirms it, while absence or a
|
|
73
|
+
different identity permits one conditional register attempt. Renewal and
|
|
74
|
+
cleanup are serialized with this confirmation work.
|
|
75
|
+
|
|
76
|
+
`close()` fences future ticks, aborts and joins admitted cooperative work, and
|
|
77
|
+
conditionally removes only the matching registration identity. It must finish
|
|
78
|
+
before the listener network close. A deadline never detaches an operation: the
|
|
79
|
+
registrar waits for the admitted promise to settle before deletion.
|
|
80
|
+
|
|
81
|
+
## Discovery
|
|
82
|
+
|
|
83
|
+
`GceRegistryReader.read(signal)` reads the full live registry snapshot at its
|
|
84
|
+
injected clock time, which defaults to `Date.now`. `GceNodeDiscovery` manages one
|
|
85
|
+
`LeasedNodeRegistry`, wraps that reader in `ScheduledNodeDiscovery`, and is the
|
|
86
|
+
Gateway-facing `NodeDiscovery`. Its production scheduler is optional and
|
|
87
|
+
unref'ed; expired rows are filtered immediately, so scale-to-zero produces an
|
|
88
|
+
empty membership snapshot. A later registrar makes its node discoverable and
|
|
89
|
+
also resumes bounded cleanup of abandoned expired rows. Registry-read failures
|
|
90
|
+
retain the Gateway's previous valid membership until a later refresh succeeds.
|
|
91
|
+
|
|
92
|
+
`watch(onSnapshot)` returns a stop operation. Calling that operation or
|
|
93
|
+
`close()` permanently stops future refresh scheduling, waits for any admitted
|
|
94
|
+
read, and then closes the registry it created. `close()` is idempotent: callers
|
|
95
|
+
share the same promise and the registry closes once. The owner still attempts
|
|
96
|
+
registry closure if stopping discovery fails. It rethrows one close failure;
|
|
97
|
+
when both operations fail, it rejects with an `AggregateError` containing both
|
|
98
|
+
causes. Do not separately close the registry passed to `GceNodeDiscovery`.
|