Skip to main content

Exposing a model

Publish a model at an HTTPS address under your own domain, and decide who may call it. A model with no external access is reachable only inside its cluster.

Before you start​

In the console​

Create the access policy and its first key​

  1. Open Integrations > Access Policies and select API keys. Enter a Policy name and select Create. AstroPulse holds the keys for this policy; it starts with none.

    Open zoom view

    Access Policies with the New API-key policy form: Policy name inference-callers, Create and Cancel

    Access Policies with the New API-key policy form: Policy name inference-callers, Create and CancelAccess Policies with the New API-key policy form: Policy name inference-callers, Create and Cancel
  2. On the policy's row, select Keys. Enter a Label, optionally an Expires date, and select Create key. The key is shown once: store it before you close the dialog.

    Open zoom view

    Keys for inference-callers: an existing key with Revoke, and the Label and Expires fields with Create key

    Keys for inference-callers: an existing key with Revoke, and the Label and Expires fields with Create keyKeys for inference-callers: an existing key with Revoke, and the Label and Expires fields with Create key

Expose the model​

  1. Open Workloads > Models > Deploy model and go to the Access step. For a running model, open it and select Update.

  2. Under External access, pick Serve on your domain. Type a hostname, or leave it blank to generate one.

  3. Under Who may call it, select the access policy. Anyone (no authentication) serves the model with no credential check; it needs the organization owner role and is recorded in the audit log.

    Open zoom view

    The Access step: External access Serve on apps.example.com, a hostname field, and Who may call it with inference-callers selected

    The Access step: External access Serve on apps.example.com, a hostname field, and Who may call it with inference-callers selectedThe Access step: External access Serve on apps.example.com, a hostname field, and Who may call it with inference-callers selected
  4. Continue to Review, where the dry run checks the cluster can expose the model, then Deploy.

Call it from the browser​

  1. Open the model from Workloads > Models. On Overview, the Endpoint section shows the public address, the in-cluster address, and Who may call it.
  2. Select Try it to chat with the model from your browser. Paste a key the policy accepts. Try it then shows the key masked to its last four characters, with Change key to use another; a new key starts a new conversation.

Try it is off until the endpoint is ready, with the reason under it when AstroPulse gives one, and off for a model that makes embeddings. Closing the drawer or switching tabs keeps the key and the conversation. Reloading the page or leaving the Models page forgets both. The key is never saved in the browser.

Close or reopen the endpoint​

  1. In the Endpoint section, select Close endpoint, then Close endpoint again to confirm. Callers can no longer reach the model at its address. The model keeps running inside the cluster, its access policy stays, and the hostname stays reserved.

    Open zoom view

    Close this endpoint? Callers can no longer reach the model at its address. Keep it open or Close endpoint

    Close this endpoint? Callers can no longer reach the model at its address. Keep it open or Close endpointClose this endpoint? Callers can no longer reach the model at its address. Keep it open or Close endpoint
  2. To serve callers again, select Reopen endpoint. AstroPulse checks the policy, its keys and the certificate again, and the endpoint returns to ready within seconds, with no redeploy. A redeploy never reopens a closed endpoint.

You should see​

The Endpoint section shows the pill Ready for callers and the policy's rule, for example "Callers must send an API key from access policy qwen-keys.", the public address with Copy and Try it, and Close endpoint.

Open zoom view

The Endpoint section: Ready, the public URL with Try it, the in-cluster URL, Who may call it qwen-keys revision 1, and Close endpoint

The Endpoint section: Ready, the public URL with Try it, the in-cluster URL, Who may call it qwen-keys revision 1, and Close endpointThe Endpoint section: Ready, the public URL with Try it, the in-cluster URL, Who may call it qwen-keys revision 1, and Close endpoint

Before the policy holds a key, the pill reads Not working with the notice The access policy has no API keys: "This endpoint can't serve until its access policy holds an API key. It opens by itself once one exists, with no redeploy." The endpoint becomes ready within about a minute of the first key.

A call without a key gets 401; a call with a key the policy accepts gets 200.

Keys and revoked keys​

A key created while the endpoint is ready works within about 10 seconds; until then it gets 401. A revoked key gets 401 within about 10 seconds. Other keys are not affected, and neither change needs a redeploy. Revoke a key under Integrations > Access Policies, Keys, Revoke.

Ready, but the name does not resolve yet​

Ready for callers means the certificate, gateway, route and policy check out and the name answered from inside your cluster. It does not mean every caller's DNS resolver can see the name yet.

If someone looks up the hostname before its DNS record exists, their resolver caches "no such host" for the zone's negative TTL: the smaller of the SOA record's TTL and its minimum field, up to 15 minutes on a default Route 53 zone. During that time callers get Could not resolve host, with no HTTP status, while the console shows Ready for callers. A name two or more labels below your domain, such as chat.models.example.com before anything exists under models., is the most likely to be cached as missing.

To check the endpoint meanwhile, ask a public resolver for the name and pin the call to the address it returns (see With astroctl). If no public resolver answers after a few minutes, check that DNS Automation runs with gatewayRoutes: true, or publish the record at your DNS provider.

Shorten the negative TTL​

Set the SOA record's TTL to 60 seconds on the zone that holds your endpoint hostnames. That limits a cached miss to about a minute and changes nothing for names that already exist.

  1. In the Route 53 console, open Hosted zones, then your public zone.
  2. Edit the SOA record, set its TTL to 60, and leave its value as it is.

The same with the AWS CLI is under With astroctl.

A private endpoint​

Set exposure to Private to serve the model through an internal load balancer, reachable only from the cluster's network. Moving a running model from public to private warns on the dry run and on the apply:

Warning: Load balancer is replaced
This application's endpoint moves from public exposure to private exposure:
its address changes, the old load balancer goes once nothing else uses it,
any hostname follows through external-dns, and connections drop briefly.

Rolling back to a version without private exposure warns that the rollback "can make <host> reachable from the internet. To keep it off the internet, close the application's endpoint before you roll back".

The address and the model id​

The endpoint's path follows the API format and the runtime:

API formatRuntimePath
V2any/v2/models/<app>/infer, the open inference protocol
OpenAI (also when left out)vLLM (generative)/v1: /v1/chat/completions, /v1/completions, /v1/embeddings
defaulta predictive format AstroPulse installed (SKLearn, XGBoost, …)/v1/models/<app>:predict. Set V2 for /v2/models/<app>/infer
defaulta runtime pinned by namenone reported: AstroPulse does not know where a custom runtime serves

Inside the cluster, the endpoint is http://<app>-predictor.<namespace>.svc.cluster.local with the same path. The model id a request sends in its model field is neither the application name nor the repository id. The model's Overview shows it next to the endpoint.

Who checks the credential​

The Gateway add-on terminates TLS and enforces the access policy. An authenticated endpoint is only possible through the Gateway. A cluster missing a piece refuses to expose a model rather than publish an https:// endpoint that nothing terminates or nothing guards.

A cert-manager or ExternalDNS already on the cluster can be used instead of AstroPulse's: name its ClusterIssuer with astroctl infra k8s exposure set, and an ExternalDNS that runs the Gateway route source publishes route hostnames. DNS is reported on the endpoint, never required.

Other ways in​

  • Nova: ask it in plain language, for example "expose qwen-chat on acme.ai behind the partner-keys policy".
  • SDK and API: the Python SDK and the API reference use the same manifest and endpoint calls.

With astroctl​

Terminal
APP=qwen-chat; PROFILE=prod-inference; DOMAIN=acme.ai; POLICY=partner-keys
ENDPOINT_HOST="${APP}.${DOMAIN}"

cat > policy.yaml <<YAML
apiVersion: platform.astropulse.io/v1
kind: AccessPolicy
metadata:
name: $POLICY
spec:
apiKeys:
header: Authorization
managed: true
YAML
astroctl access-policy apply -f policy.yaml # revision: 1, no keys

cat > model.yaml <<YAML
apiVersion: platform.astropulse.io/v1
kind: Application
spec:
name: $APP
profileName: $PROFILE
externalAccess:
domain: $DOMAIN
accessPolicyRef:
name: $POLICY
source:
type: Model
model:
uri: hf://Qwen/Qwen2.5-0.5B-Instruct:7ae557604adf67be50417f59c2c2f167def9a775
serving:
maxContextTokens: 2048
resources:
cpu: "2"
memory: 6Gi
YAML
astroctl app apply -f model.yaml
astroctl app endpoint get "$APP" # phase: Error, no API keys yet

(umask 077; astroctl access-policy key create "$POLICY" --label first-caller -o json > key.json)
KEY=$(jq -r .key key.json) # shown once; delete key.json once stored

until astroctl app endpoint get "$APP" -o json | jq -e '.phase == "Ready"' >/dev/null; do sleep 10; done
dig +short "${ENDPOINT_HOST}" @1.1.1.1 # IP addresses once the record is published
curl -sS -o /dev/null -w '%{http_code}\n' "https://${ENDPOINT_HOST}/v1/models" # 401
curl -sS -o /dev/null -w '%{http_code}\n' "https://${ENDPOINT_HOST}/v1/models" -H "Authorization: Bearer $KEY" # 200

The resources and maxContextTokens size this model for a CPU node; on a GPU node add resources.accelerator (see Sizing a replica).

Manage the endpoint and check the cluster:

Terminal
astroctl app endpoint get <app> # desiredState, phase, message, probe, dns, url
astroctl app endpoint close <app> # asks you to type the app name; --yes without a terminal
astroctl app endpoint reopen <app>
astroctl infra k8s exposure get <cluster> # issuer, DNS and whether a model takes the gateway path
astroctl infra k8s exposure set <cluster> --cluster-issuer my-issuer # --clear removes it

probe is AstroPulse calling the endpoint itself: vantage: Cluster from inside, Public from outside. probe.result: Refused means an unauthenticated call was refused, so the policy is enforced. The DNS line in astroctl app get and the dns field are read from the hostname's own zone, never through a shared resolver:

DNS lineMeaning
resolves to <addresses>; matches the exposure addressThe zone publishes the record and it points at the cluster. A caller's resolver can still hold a cached miss
resolves to <addresses>; the hostname resolves to an address the exposure does not publishThe record points somewhere else
does not resolve yet (the hostname does not resolve yet)The zone has no record for the hostname
the exposure publishes no public address (NodePort); the hostname cannot be comparedExpected on clusters without a cloud load balancer
no DNS line and no dns fieldNo name server answered in time, or the name answered only from inside the cluster (a private or split-horizon name). Not a miss. Also absent with -o json or -o yaml, and without a public hostname

Pin a call to the address a public resolver returned, while a caller's resolver still caches a miss:

Terminal
ADDRESS=$(dig +short "${ENDPOINT_HOST}" @1.1.1.1 | tail -n 1)
curl -sS -o /dev/null -w '%{http_code}\n' "https://${ENDPOINT_HOST}/v1/models" \
-H "Authorization: Bearer $KEY" --resolve "${ENDPOINT_HOST}:443:${ADDRESS}" # 200

Shorten the negative TTL on Route 53 from the CLI. Confirm echo prints exactly one zone ID; two mean a public and a private zone share the name, so set ZONE_ID to the one aws route53 get-hosted-zone shows with "PrivateZone": false:

Terminal
ZONE_NAME="${DOMAIN}"
NS1=$(dig +short NS "${ZONE_NAME}" | head -n 1)
dig +noall +answer SOA "${ZONE_NAME}" "@${NS1}" # 2nd column: TTL; last number: minimum
ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name "${ZONE_NAME}" \
--query "HostedZones[?Name=='${ZONE_NAME}.'].Id" --output text)
echo "${ZONE_ID}"
SOA_VALUE=$(aws route53 list-resource-record-sets --hosted-zone-id "${ZONE_ID}" \
--query "ResourceRecordSets[?Type=='SOA'].ResourceRecords[0].Value" --output text)
aws route53 change-resource-record-sets --hosted-zone-id "${ZONE_ID}" \
--change-batch "{\"Changes\":[{\"Action\":\"UPSERT\",\"ResourceRecordSet\":{\"Name\":\"${ZONE_NAME}.\",\"Type\":\"SOA\",\"TTL\":60,\"ResourceRecords\":[{\"Value\":\"${SOA_VALUE}\"}]}}]}"
dig +noall +answer SOA "${ZONE_NAME}" "@${NS1}" # 60 in the 2nd column

See the CLI reference for app endpoint, access-policy and infra k8s exposure.

Settings​

All fields are under the application's externalAccess.

domain

The verified domain the model is served under, at <app>.<domain>. In the console, Serve on under External access. Set this or hostname, not both. Requires accessPolicyRef.

Accepts
a domain your organization verified
Default
none: the model is reachable only inside the cluster
hostname

The exact hostname to serve at: a verified domain's apex, or a name under it. Set this or domain, not both.

Accepts
a hostname under a verified domain
Default
none
accessPolicyRef.nameRequired

The access policy whose API keys or tokens callers must send. In the console, Who may call it. Required with domain or hostname: it never defaults to public.

Accepts
the name of an existing access policy
Default
none
accessPolicyRef.revision

The policy revision the endpoint enforces. It is kept with the deployed version: a policy update reaches the model on its next deploy, and a rollback restores the revision it was deployed with.

Accepts
a revision number of the policy
Default
the latest revision at apply
accessPolicyRef.none

The explicit opt-out: TLS and no caller authentication. In the console, Anyone (no authentication). Needs the organization owner role.

Accepts
only UnauthenticatedPublicInference, compared exactly
Default
none
exposure

Who can reach the endpoint: Public, the internet, or Private, the cluster's network only.

Accepts
Public, Private
Default
the Gateway add-on's exposure
accessType

How traffic reaches the model. LoadBalancer is refused for a model: it hands out a raw address with no domain and no certificate.

Accepts
Ingress
Default
Ingress, which for a model with a policy is the Gateway path

The API format, which sets the path, is serving.apiFormat on the model; see Settings on Serve a model.

If it's refused​

Endpoint states are read on the model's Overview, in the Endpoint section: the pill reads Not ready yet (Pending), Ready for callers, Closed to callers or Not working (Error), with the message under Try it.

Exposed model needs an access decision: "Exposing a model publishes an inference endpoint, and externalAccess.accessPolicyRef does not say who may call it."
Cause
A domain or hostname is set with no policy.
Fix
Select a policy under Who may call it.
"externalAccess.accessPolicyRef needs the model to be exposed: set externalAccess.domain or hostname, or remove accessPolicyRef"
Cause
A policy is set with no domain or hostname.
Fix
Pick a domain under External access, or remove the policy.
"accessPolicyRef.none <value> is not valid: the only accepted value is "unauthenticated-public-inference", compared exactly"
Cause
The opt-out value is misspelled.
Fix
Write UnauthenticatedPublicInference exactly, or choose Anyone (no authentication) in the console.
Model source cannot use a load balancer: "externalAccess.accessType LoadBalancer is not supported for a model source. Expose it through Ingress or the gateway path instead, which AstroPulse fronts with a verified domain and certificate."
Cause
accessType is LoadBalancer.
Fix
Remove accessType.
Deploy refused: "Authenticated endpoints need the gateway add-on", "No certificate issuer for this cluster", "Certificate issuer is not ready" or "Certificate issuer has not been observed yet"
Cause
The cluster lacks a piece the endpoint needs.
Fix
Install the Gateway, or fix the certificate issuer, on the cluster's Add-ons tab, then deploy the model again. For a Let's Encrypt issuer see the Certificates add-on.
"Managed endpoint cannot go back to open exposure"
Cause
An update removes the access policy from a model that has a managed endpoint.
Fix
Keep the policy, or select Close endpoint first.
Not working with "The access policy has no API keys. Create one in AstroPulse." and the notice The access policy has no API keys
Cause
The policy uses API keys and holds none. Expected for a new managed policy.
Fix
Select Create an API key, or create one under Integrations > Access Policies. The endpoint opens by itself within about a minute, with no redeploy. If it stays, check the key is on the policy the model names.
Not ready yet with "Waiting for your cluster to pick up this change." or "Waiting for the gateway to accept the route."
Cause
The cluster has not applied the change yet, or the gateway is taking the new route. Both usually pass in under a minute.
Fix
Wait, then look again.
Not ready yet with "Waiting for a replica: the gateway is checking keys, but no replica of this model is ready to answer yet."
Cause
The route and policy are in place, and the model has no ready replica.
Fix
Read the model's status; see How to debug a model.
Not ready yet with "Waiting for the previous route to be withdrawn."
Cause
The endpoint is moving, for example between public and private exposure.
Fix
Wait; the new route follows.
Not ready yet with "Waiting for the load balancer: ...", "Waiting for DNS: ...", "Waiting for the certificate: ..." or "Waiting for the probe: ..."
Cause
A check is still waiting, and the message names it. Every close and reopen also passes through this state.
Fix
If it stays, check the certificate issuer on the cluster's Add-ons tab.
Not ready yet with "API key Secret is unavailable"
Cause
The cluster cannot read the Secret that holds the policy's API keys yet.
Fix
Look again in a minute. If it stays, check the Gateway add-on on the cluster's Add-ons tab.
Not working with "API key Secret <namespace>/<name> was not written by AstroPulse"
Cause
A Secret with the name AstroPulse uses already exists and was not created by AstroPulse.
Fix
Remove or rename that Secret, then select Reopen endpoint.
Not working with "the endpoint answered without refusing: <detail>; the serving route is withdrawn"
Cause
An unauthenticated request was answered, so AstroPulse withdrew the route rather than serve an open endpoint.
Fix
Check the policy under Integrations > Access Policies, then select Reopen endpoint.
Ready for callers, and a caller gets Could not resolve host
Cause
The name is published, and the caller's resolver still holds an earlier "no such host" answer.
Fix
Ready for callers with "AstroPulse couldn't confirm <host> from inside your cluster: ..."
Cause
For about five minutes the hostname did not answer, did not resolve, or showed another certificate from inside the cluster. A hairpin, egress rule, private DNS zone or CDN can cause this on an endpoint that serves its callers, and a real outage reads the same.
Fix
Test the endpoint from where your callers are. If they cannot reach it either, follow the message.
Ready for callers with a message that only the gateway was checked
Cause
The cluster's agent predates this check, or the gateway has no load balancer.
Fix
Test the endpoint from where your callers are.
Closed to callers
Cause
Someone closed the endpoint. The model keeps serving inside the cluster, and a redeploy does not reopen it.
Fix
Select Reopen endpoint.
401 with the key
Cause
A new key is not active yet, or the key is wrong.
Fix
Wait 10 seconds and call again. If it stays 401, check the key and that it belongs to the policy the model names.
"application "<app>" has no policy-managed endpoint: one exists once it is exposed with externalAccess.accessPolicyRef" (HTTP 404 from astroctl app endpoint get)
Cause
The model has no access policy, so it has no managed endpoint. Also seen for a few seconds right after the first apply.
Fix
If the model names a policy, look again in a few seconds. To expose it, see In the console.
Domain verification refused: "The TXT record at <name> does not contain the expected verification value. ..."
Cause
The domain's TXT record is missing or holds another value.
Fix
Publish the value shown under Integrations > Domains, wait for DNS, then verify again.

For a certificate that stays not ready, follow cert-manager's own order: Certificate, CertificateRequest, the ClusterIssuer, then Order and Challenge (cert-manager troubleshooting).

Next​