Skip to content

Deploy to a local platform

This tutorial takes a service from your machine to a running, exposed deployment on a local ankka platform, and then operates it: reading its logs, restarting it and reading who did what. It uses the orders service from Your first service in Scala; a Python service differs in two places, both shown.

You need Docker, kind, kubectl, sbt and the ankka CLI, all from Install the tools. The platform runs in a kind cluster, which is a Kubernetes cluster inside a Docker container. It needs a few gigabytes of memory for Docker, and the first deploy takes several minutes.

Create the platform

From the root of the ankka repository, create the cluster and deploy the platform into it:

kind create cluster --name ankka --config kustomization/kind.yaml
./kustomization/deploy-local.sh

just up does both. The cluster must be created from kustomization/kind.yaml, because that file publishes the platform gateway's ports on your machine, 8080 and 8443, and kind can only do that when a cluster is created. The deploy script refuses a cluster without them, and refuses any kubectl context other than kind-ankka, so it can never touch a real cluster by accident.

The script installs what the platform stands on — CloudNativePG for databases, cert-manager for certificates, Envoy Gateway for routing, and Keycloak for identity — builds the platform's images, loads them into the cluster, and deploys the operator and the control plane. It creates a development user, dev with password dev, and exports the local certificate authority's root to ~/.ankka/local-ca.crt. It ends by printing the control plane's address. Install a local platform describes each step.

Point the CLI at it and log in

ankka config set url https://api.127.0.0.1.sslip.io:8443
ankka config set ca ~/.ankka/local-ca.crt
ankka login

ankka login prints an address and a code. Open the address in a browser, enter the code, and sign in as dev with password dev. The CLI saves a renewable login for this control plane, and ankka whoami shows who you are.

The CLI trusts the local platform's certificate because you told it where the root is. Nothing on your machine is asked to trust it, and there is no option to skip verification.

127.0.0.1.sslip.io is a public DNS convention that resolves any name ending in an address to that address, so nothing on your machine needs configuring. If your resolver blocks it, see Networking and TLS.

Create an organization and a project

A service lives in a project, and a project belongs to an organization:

ankka organizations create acme --name "Acme Corp"
ankka projects create checkout --name Checkout -O acme
ankka config set project checkout

Setting the project saves passing -p checkout on every service command. Organizations, projects and members explains who may do what.

Build the image and load it

In the orders project, build its image into your local Docker daemon, then copy it into the cluster:

sbt Docker/publishLocal                              # orders:<version> and orders:latest
kind load docker-image orders:latest --name ankka

The local platform has no image registry. kind load puts the image on the cluster's node, and the platform runs every workload with imagePullPolicy: IfNotPresent, so the node uses the loaded image rather than trying to pull it.

For a Python service, build the image from a Dockerfile that contains your process and the SDK, and load it the same way. The sidecar is the platform's own image and is already loaded.

Apply the descriptor

The template made service.json, the service's desired state:

service.json
{
  "name": "orders",
  "service": {
    "image": "orders:latest",
    "runtime": "0.2.0"
  }
}

runtime is the ankka version the service was built against. The template fills it in from the version it was created with, and the platform refuses one outside its supported range. Upgrade ankka states the rule.

A Python service's descriptor names how it is hosted and which protocol its SDK speaks:

service.json
{
  "name": "cart",
  "service": {
    "image": "cart:latest",
    "hosting": "process",
    "protocol": "1.0"
  }
}

Apply it and watch the service come up:

ankka services apply -f service.json
ankka services list
# NAME    STATUS            INSTANCES  GEN  IMAGE
# orders  UpdateInProgress  0/1        1    orders:latest

Run ankka services list again after a minute:

NAME    STATUS  INSTANCES  GEN  IMAGE
orders  Ready   1/1        1    orders:latest

That descriptor is the whole of it: an image, and nothing about databases or ports. The platform gave the service a Postgres database of its own with ankka's schema applied, an address inside the cluster, and a place in its own cluster of instances. It reports Ready only once the instance has joined that cluster and bound its HTTP port. ankka services get orders shows the full status, including the database the platform provisioned. Service lifecycle states lists every status.

Expose it

A service is private by default: reachable inside the cluster and nowhere else. Exposing it is a separate decision:

ankka services expose orders
# https://orders-checkout.127.0.0.1.sslip.io:8443

The hostname is derived by the platform as <service>-<project>.<base domain>. Call it over HTTPS with the local root:

curl --cacert ~/.ankka/local-ca.crt -XPOST https://orders-checkout.127.0.0.1.sslip.io:8443/items/i1 \
     -H 'content-type: application/json' -d '{"name":"Widget","count":2}'
curl --cacert ~/.ankka/local-ca.crt https://orders-checkout.127.0.0.1.sslip.io:8443/items/i1
# {"id":"i1","name":"Widget","count":2}

Exposure changes who can reach an endpoint, not who is allowed to call it. The template's endpoint declares Acl.AllowAll, which is fine here and means "anyone on the internet" on a real installation. Decide each endpoint's ACL before exposing it; HTTP endpoints shows how.

Read its logs

ankka services logs orders
ankka services logs orders --tail 50
ankka services logs orders --previous      # the container before the last restart

The CLI reads what Kubernetes holds for the service's instances at the moment you ask. It is not a log store: there is no search and no retention beyond the current and previous container. Logs says more.

Restart it

ankka services restart orders
ankka services list
curl --cacert ~/.ankka/local-ca.crt https://orders-checkout.127.0.0.1.sslip.io:8443/items/i1
# {"id":"i1","name":"Widget","count":2}

A restart replaces every instance, one at a time. Each new instance joins the running cluster and takes over its share of entities before an old one stops, so the URL keeps answering throughout and the item is the same item: its events are in the service's database, not in the instance.

Read its history

ankka services history orders

Every change to a service is recorded with who asked for it and when: the apply, the expose, the restart. The control plane keeps its own state as event sourced entities, so its journal is the audit trail.

Change it and deploy again

Change the code, rebuild the image, load it and apply again:

sbt Docker/publishLocal
kind load docker-image orders:latest --name ankka
ankka services restart orders

The descriptor still says orders:latest, so applying it again changes nothing Kubernetes can see, and the running instances would keep the image they started with. restart rolls them onto the image just loaded. With a registry you would tag each build with its own version, change the descriptor's image, and apply, which rolls the instances by itself. Scale and roll out covers both.

Clean up

ankka services unexpose orders     # the hostname stops answering; nothing else changes
ankka services delete orders       # the instances go; the database is kept
kind delete cluster --name ankka   # everything

Deleting a service never deletes its database. Applying a descriptor with the same name later recovers the data. Deleting the kind cluster removes everything, databases included.