Skip to content

fix: make KService naming injective (#7622) - #7746

Draft
paullongtan wants to merge 2 commits into
flyteorg:mainfrom
paullongtan:fix/7622-kservicename-injective-hashing
Draft

fix: make KService naming injective (#7622)#7746
paullongtan wants to merge 2 commits into
flyteorg:mainfrom
paullongtan:fix/7622-kservicename-injective-hashing

Conversation

@paullongtan

@paullongtan paullongtan commented Aug 1, 2026

Copy link
Copy Markdown

Issue

Related to #7622

Draft. The naming change here is complete and tested, but it breaks INTERNAL_APP_ENDPOINT_PATTERN in a way that needs a design decision before this is ready to merge. See Open question below and the discussion on #7622.

KServiceName() is non-injective, so distinct (project, domain, name) triples can map to the same Knative Service name. This would allow collisions to silently overwrite another app's spec instead of creating a separate kService.

Collision cause pointed out in the issue:

  1. Delimiter ambiguity. Fields were joined with -, but project and domain may themselves contain -. {name: svc, project: team, domain: prod-x} and {name: svc, project: team-prod, domain: x} both produced svc-team-prod-x.
  2. Weak hash. Names over 63 chars fell back to a 32-bit SHA-256 suffix, which may cause a birthday problem.

A third cause not pointed out:
The old implementation lowercased the whole joined string (project/domain/appName) during hash and returned it directly when it fit in 63 chars, with no digest at all. So apps named Foo and foo in one project/domain will always collide — the name field permits both capitalized and non-capitalized letters A-Z (flyteidl2/app/app_definition.proto:33).

Summary

Injective name derivation

KServiceName now returns:

k-<lowercase-app-name>-<26-char base32 digest>
  • The digest is SHA-256 over the app's full identity — org, project, domain, name — with each field length-prefixed, so the encoding is unambiguous whatever the fields contain. Truncated to 16 bytes (128 bits) and encoded with a lowercase-alphanumeric base32 alphabet so it is a valid DNS label.
  • The identity is hashed exactly as given. Project, domain, and name are case-sensitive in Flyte, so folding case here would map two distinct apps onto one KService. Only the readable prefix is lowercased, because a DNS label must be.
  • The k- prefix versions the format and guarantees the name starts with a letter, as Knative requires (DNS-1035).

App Identity

Identity is now read from labels, not a joined annotation.
flyte.org/app-id stored {project}/{domain}/{name} as a /-joined string and was parsed back with SplitN — the same ambiguity as the name, for the same reason.

  • The new identifierFromKService function reads flyte.org/project, flyte.org/domain and flyte.org/app-name, which already existed as separate labels, so no value can be mistaken for a delimiter. flyte.org/app-id will be deleted afterwards.
  • Ingress URL derived from identity. kServiceToStatus took the app ID by re-parsing the annotation; it now receives the *Identifier its caller already resolved, so the KService name and the URL reported for it are computed from one identity rather than two independently parsed ones.

The public URL shape changes as a side effect, from {name}-{project}-{domain}.{base_domain} to k-{name}-{digest}.{base_domain}. This stays correct without further change because Knative's domain-template is
{{.Name}}.{{.Domain}}, so the route follows the KService name by construction.

Open question — INTERNAL_APP_ENDPOINT_PATTERN

This is why the PR is a draft. INTERNAL_APP_ENDPOINT_PATTERN (added in #7323) is injected into every app pod as a template with one substitution slot:

http://{app_fqdn}-proj-dev.flyte.svc.cluster.local

The SDK's AppEndpoint("some-app") string-replaces {app_fqdn} with the target app's name. That worked because everything except the name was constant across a project/domain and could be baked in at deploy time.

Any injective scheme puts a digest of the full identity in the name, and that digest varies with the target's name — so there is no constant suffix left to bake in, and the SDK cannot compute one. Every URL built from the pattern now resolves to nothing.

The injection site is marked with a TODO in this PR rather than silently left broken; it must be resolved before merge.

Three options, discussed in more detail on #7622:

  1. Digest covers only (org, project, domain), name stays a literal segment. The digest is then constant per project/domain and the existing template keeps working with no SDK or proto change. Still injective: name is capped at 30 chars (app_definition.proto:30) against a 34-char budget so it never truncates, and the fixed-width digest makes right-parsing unambiguous. Requires app names to be unique case-insensitively.
  2. Server-resolved. Add internal_url to Ingress, have AppEndpoint read it via GetApp, retire the env var. Structurally cleanest, puts no constraint on names, and enables cross-project discovery — but needs a proto change plus a coordinated SDK rollout.
  3. SDK reimplements the hash. Works, but duplicates the naming algorithm across repos with no shared test vectors.

Guidance on which direction to take is welcome.

Related PRs

Signed-off-by: paullongtan <paullongtan@gmail.com>
Signed-off-by: paullongtan <paullongtan@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant