Deploy
The shop has to run somewhere other people can reach it. Toa ships tooling that deploys an application to Kubernetes with one command, from the Context alone: you write no Dockerfile, no chart and no Kubernetes manifest. Using it is a choice: you can run the processes yourself. Deployment explains the approach.
§What must exist
Toa deploys the application, not what it runs on. Locally that is the docker-compose.yaml toa create wrote, which npm run dock starts: RabbitMQ, MongoDB as
a replica set of one member, and Redis, on their usual ports, the first two with the user developer and the password secret that toa env --dev writes into .env. A deployment brings
none of it. Before the first deploy you need:
- a Kubernetes cluster, 1.27 or later, and a namespace in it;
- a container registry the cluster can pull from;
- a broker (RabbitMQ) and a database (MongoDB 8.0 or later, as a replica set or a sharded cluster) the cluster can reach, each with a user for the application;
- whatever else the Context names: Redis for
stashoratomicity, buckets for storages.
On the machine that deploys:
@toa.io/operationsbeside the CLI — see Installing;docker,helmandkubectlon thePATH, withdockerlogged in to the registry andkubectlpointed at the cluster. The currentdockerandkubectlcontexts are the ones used.
§What to write
The Context toa create wrote describes a local run. A deployment adds a version, changes two
keys, and gives every address a value for the environment it is deployed to:
# context.toa.yaml
name: store
version: 1.4.0
registry: registry.example.com/store
resources:
cpu: [200m, 1]
memory: [256Mi, 1Gi]
amqp: amqp://localhost
amqp@production: amqp://rmq.internal.example.com
mongodb: mongodb://localhost
mongodb@production: mongodb://mongo.internal.example.com
stash: redis://localhost
stash@production: redis://redis.internal.example.com
atomicity:
redis: redis://localhost
redis@production: redis://redis.internal.example.com
exposition:
authorities:
local: localhost:8000
local@production: api.example.com
ingress:
hosts: [localhost]
hosts@production: [api.example.com]
# … versionis added. It is required to deploy. Without it:Context 'store' declares no version, which is what its chart is versioned by.registryis where images are pushed: yours, in place of the generatedregistry.example.com/store; see Build Images.resourcesis generated as~, which deploys without requests and limits. State them, on the Context or on every workload; see Resources.- The addresses keep their local values and get one per environment, with the
@<environment>suffix. The same goes for the host the gateway answers on and the hosts of the ingress.
§The first deploy
The user names and passwords of the broker and the database are not in the Context, and neither
is the key the gateway encrypts tokens with, which the generated Context refers to as $IDENTITY_TOKENS_ENCRYPTION_KEY0. Ask what the cluster has to hold, put it there, and deploy:
$ npx toa export secrets production
toa-amqp-context.default:
username
password
toa-configuration:
IDENTITY_TOKENS_ENCRYPTION_KEY0
toa-mongodb.default:
username
password
$ npx toa conceal amqp-context.default username=store password=... -n store
$ npx toa conceal mongodb.default username=store password=... -n store
$ npx toa conceal configuration IDENTITY_TOKENS_ENCRYPTION_KEY0=$(npx toa key) -n store
$ npx toa deploy production -n store --wait Supply Secrets covers toa export secrets and toa conceal; toa key generates a key.
toa deploy checks the secrets, builds and pushes an image for every composition, and applies
the deployment as a Helm release named after the Context. You see the output of docker as
images build, a line for each image the registry already has,
Image already exists, skipping: registry.example.com/store/store/composition-core:3f9a1c02 and, with --wait, the command returns when the new replicas are ready and the replaced ones
are gone.
If a secret is missing, the deploy stops before building anything and names every missing key:
Secrets are not deployed: toa-mongodb.default/username, toa-mongodb.default/password A refused deploy changes nothing.
§Check it
$ helm status store -n store
$ kubectl get deployments -n store
NAME READY UP-TO-DATE AVAILABLE
composition-billing-wallets 2/2 2 2
composition-store-orders 2/2 2 2
extension-exposition-gateway 2/2 2 2
$ kubectl logs -n store -l toa/component-store-orders --tail 20 A pod is ready once its components are connected to everything they use. One that stays unready is waiting for the broker or the database; its log says which. Then make a request through the ingress, the way Expose It over HTTP did locally:
$ curl https://api.example.com/store/orders/ Look After a Running Application has more to look at.
§Options
$ npx toa deploy production --namespace store --wait --timeout 15m0s --namespace,-n— the Kubernetes namespace of the release, and the one whose secrets are checked.--wait,-w— return only when the rollout is complete. Without it the command returns once the deployment is applied. Use it in a pipeline, so that a failed rollout fails the pipeline.--timeout,-t— how long--waitmay take; five minutes by default. A rollout that makes no progress for fifteen minutes fails regardless.--dry,-d— print what would be applied and stop. No cluster is read, so it works without one.--mono— deploy the single-image form.
To get the chart as files instead of applying it:
$ npx toa export deployment production ./chart §What gets deployed
| For | Kubernetes objects |
|---|---|
| each composition | a Deployment named composition-<name> |
| each extension service no composition runs | a Deployment named extension-<group>-<name> |
| each service that listens on a port | a Service of the same name |
| each service reachable from outside | an Ingress of the same name |
| the component map | a ConfigMap, mounted into every pod |
The gateway, for example, is extension-exposition-gateway.
Pods carry labels you can select by:
| Label | On |
|---|---|
toa/composition: <name> | the pods of a composition |
toa/component-<namespace>-<name>: "1" | pods running that component; default is the namespace of one that has none |
toa/service-<group>-<name>: "1" | pods running that service |
Each container runs one fixed command: toa compose for a composition, toa serve for a
service, toa mono for the single-image form. It is not configurable. What a component has to
do when it starts belongs in its run commands; see Keep Something in a Process.
Your components do not open ports of their own. The ports the platform uses are listed in Ports.
§Limits your components live under
- A replica has five minutes to start. One that has not reported ready by then is restarted.
Everything a component does before it serves counts: its
preflightrun command and its migrations. - A replica has 45 seconds to stop. It keeps serving for five seconds while traffic is routed away, then finishes what is in progress. An operation still running when the time is up is cut off, and whatever called it sees a failure.
- Old and new replicas serve together during a rollout. Nothing is replaced until its replacement is ready. Release a New Version says what that requires of a change.
- What did not change is not redeployed. A release that changes one component rolls out the composition containing it and leaves the others running.
§Ingress
The gateway and the introspection and configuration pages are reachable from outside. The Context says under which hosts, once for all of them:
ingress:
hosts:
- api.example.com
class: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 16m
default: true | Key | Meaning |
|---|---|
hosts | hostnames of the ingress rules; at least one |
class | the ingress class |
annotations | annotations copied onto every Ingress |
default | true adds a rule without a host, as a fallback |
hosts must be hostnames the gateway is configured to answer on
(Authorities): the pages are published on the same hosts and call
the API beside them.
A service that needs an ingress where no host is given is refused:
Service 'introspection-explorer' declares an ingress, but no hosts are defined. Declare them in
the context's 'ingress' section. §One image for everything
For a preview or a small installation, --mono deploys the whole application as one workload:
every component and every extension service in a single process.
$ npx toa deploy staging --mono It produces one Deployment, one Service and one Ingress, all named mono.
# context.toa.yaml
mono:
replicas: 2
resources:
cpu: [200m, 2]
memory: [256Mi, 2Gi]
mono@preview:
replicas: 1 Without mono, it runs two replicas with the Context’s resources. Evicted components are left
out, as in any deployment.
toa build --mono, toa export deployment --mono, toa export images --mono and toa export tags --mono use the same layout. Switching between --mono and compositions later
changes nothing in the components.
§Deploying from a pipeline
A pipeline does what you do by hand: log in to the registry, point kubectl at the cluster,
install dependencies, and deploy.
$ npm ci
$ npx toa deploy production -n store -p application --wait --timeout 15m0s - Give the same environment name, or chain, to every command of a pipeline.
- Secrets are not part of a deploy. They are put in the cluster once, by whoever may know them, and the deploy only checks they are there.
- A pipeline interrupted in the middle of a rollout can leave the Helm release pending, and Helm
then refuses the next one. Check
helm status <context name>and roll back or uninstall the pending release before deploying again.
To separate building from deploying, run toa push first. A deploy of sources that were already
pushed builds nothing.
§Without the supplied tooling
Nothing in the runtime requires Kubernetes. A process needs:
- Node.js and the application’s sources with their dependencies installed, including
@toa.io/runtime; - the variables of its environment — what
toa env <environment>writes — in its environment or in a.envfile; - the component map, written by
toa map <environment>; - network access to the broker, the database and whatever else the Context names.
Then start processes with the same commands the images use:
$ npx toa compose components/store.orders components/billing.wallets
$ npx toa serve exposition The ecosystem.config.js that toa create wrote is this arrangement on one machine, with PM2 as
the supervisor: sys is toa serve configuration exposition introspection, and app is toa compose with every component.
or everything at once with toa mono components/*. Run as many copies of each as you want
replicas. Supervision, restarts and rolling replacement are then yours to arrange, and the rules
of Release a New Version apply to however you replace them.
A middle way is to let Toa build and render, and apply the result yourself: toa push, then toa export deployment for the chart.