VI Shipping

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 stash or atomicity, buckets for storages.

On the machine that deploys:

  • @toa.io/operations beside the CLI — see Installing;
  • docker, helm and kubectl on the PATH, with docker logged in to the registry and kubectl pointed at the cluster. The current docker and kubectl contexts 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]

# …
  • version is added. It is required to deploy. Without it: Context 'store' declares no version, which is what its chart is versioned by.
  • registry is where images are pushed: yours, in place of the generated registry.example.com/store; see Build Images.
  • resources is 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 --wait may 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

ForKubernetes objects
each compositiona Deployment named composition-<name>
each extension service no composition runsa Deployment named extension-<group>-<name>
each service that listens on a porta Service of the same name
each service reachable from outsidean Ingress of the same name
the component mapa ConfigMap, mounted into every pod

The gateway, for example, is extension-exposition-gateway.

Pods carry labels you can select by:

LabelOn
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 preflight run 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
KeyMeaning
hostshostnames of the ingress rules; at least one
classthe ingress class
annotationsannotations copied onto every Ingress
defaulttrue 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 .env file;
  • 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.

Search
Search the documentation