Configure Horizon and OAP by environment variable, mount a file only on request - #195
Merged
Conversation
…on request
Horizon's image ships a complete /app/horizon.yaml in which all 62 fields are
${HORIZON_*:default} placeholders, and the chart was mounting a ConfigMap over
it on every install. That mount replaces the file rather than merging with it,
so every token the chart did not carry over was gone and the matching HORIZON_*
variable stopped working -- silently. HORIZON_AUTH_LOCAL_USERS supplied from a
Secret did nothing, which is the first thing an operator tries.
The chart no longer mounts anything by default. It sets what it can compute as
plain environment variables -- HORIZON_SERVER_PORT, HORIZON_OAP_QUERY_URL, and
HORIZON_OAP_ADMIN_URL / HORIZON_OAP_ZIPKIN_URL / HORIZON_PUBLIC_URL when the
corresponding port or ingress is configured -- and leaves the image's file
intact, so every other field stays settable. Structured settings take JSON in
one variable, so a Secret covers users, LDAP, SSO and the RBAC table with no
file anywhere.
ui.config remains as the third mechanism, for pinning a field regardless of the
environment, and is now opt-in: no ConfigMap is created and nothing is mounted
until it is set. When it is set the chart merges back the values a mount would
otherwise lose -- the OAP URLs and publicUrl as tokens, server.port as a
literal, and auth.local.users as ${HORIZON_AUTH_LOCAL_USERS:[]} unless the
operator writes users of their own. That last one CI caught: without it,
ui.config plus envFromSecret -- the documented production pattern -- produces a
Ready pod nobody can log into.
OAP gains the same shape. It had oap.env, a plain map that cannot express
valueFrom, and no envFrom at all, so a storage credential could only be a
literal in values or a file mount. Adds oap.extraEnv (a list, so entries can
carry valueFrom) and oap.envFromSecret, both applied to the Deployment and to
the init Job. Note Kubernetes gives an explicit env entry precedence over
envFrom, and the chart sets SW_ES_PASSWORD and SW_DATA_SOURCE_PASSWORD itself --
so sourcing those from a Secret needs extraEnv, and the docs say so.
oap.config stays as it was, and the docs explain why the two components differ:
OAP reads real files from /skywalking/config for log4j2.xml and the OAL/MAL
rules, so a ConfigMap is the only way to supply those. Horizon does not.
Dropping the mount also restores hot reload -- a subPath mount is never updated
in place, so Horizon's file watcher could never fire -- and removes the
checksum/config annotation that forced a pod recreate on every config change.
Secret names are quoted everywhere they are rendered. `null`, `true` and `123`
are valid Secret names that would otherwise emit non-string YAML.
E2E moves its login to HORIZON_AUTH_LOCAL_USERS so CI exercises the documented
path rather than the escape hatch, and adds one cell covering the ConfigMap
path for both components at OAP 11 + BanyanDB. Its login assertion is the
load-bearing one: it proves Horizon parsed the mounted file, since a malformed
one aborts the BFF at boot uncaught, and that the merge kept the users token
alive. The OAP 10.4 cells are removed -- this suite covers OAP 11 -- and the
docs no longer claim CI coverage for that line, though the settings it needs
are still documented.
wu-sheng
force-pushed
the
configure-horizon-by-env
branch
from
August 30, 2026 10:03
acd51d5 to
2681615
Compare
kezhenxu94
approved these changes
Aug 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #194.
Horizon's image ships a complete
/app/horizon.yamlin which all 62 fields are${HORIZON_*:default}placeholders. The chart was mounting a ConfigMap over it on every install — and that mount replaces the file rather than merging with it. So every token the chart didn't carry over was gone, and the matchingHORIZON_*variable stopped working, silently.The practical consequence:
HORIZON_AUTH_LOCAL_USERSsupplied from a Secret did nothing. That is the first thing an operator tries.What changes
The chart no longer mounts anything by default. It sets what it can compute as plain environment variables and leaves the image's file intact, so every other field stays settable:
HORIZON_SERVER_PORTui.service.internalPortHORIZON_OAP_QUERY_URLHORIZON_OAP_ADMIN_URLoap.ports.adminis setHORIZON_OAP_ZIPKIN_URLoap.ports.zipkin-queryis setHORIZON_PUBLIC_URLStructured settings take JSON in a single variable, so a Secret covers users, LDAP, SSO and the RBAC table with no file anywhere:
ui.configremains as the third mechanism, for pinning a field regardless of the environment. It is now opt-in: no ConfigMap is created and nothing is mounted until it is set. When it is set, the chart merges its computed values in as tokens, so OAP stays reachable.OAP gains the same shape. It had
oap.env— a plain map that cannot expressvalueFrom— and noenvFromat all, so a storage credential could only be a literal in values or a file mount. Addsoap.extraEnv(a list, so entries can carryvalueFrom) andoap.envFromSecret, both applied to the Deployment and the init Job, which needs the same storage credentials.oap.configstays as it was, and the docs now say why the two components differ: OAP reads real files from/skywalking/configforlog4j2.xmland the OAL/MAL rules, so a ConfigMap is the only way to supply those. Horizon doesn't, so it doesn't need one.Falls out for free
Hot reload starts working — a
subPathmount is never updated in place by Kubernetes, so Horizon's file watcher could never fire — and there is no longer achecksum/configannotation forcing a pod recreate on every config change.E2E
The suite's login moves from
ui.configtoHORIZON_AUTH_LOCAL_USERS, so CI exercises the path the documentation recommends rather than the escape hatch.A sixth cell,
e2e-config-override.yaml, covers the ConfigMap path for both components — otherwise the opt-in mount would ship untested, and it is the riskier path. It asserts the mounts exist, that Horizon readtemplates.modefrom the mounted file, that OAP'slog4j2.xmllanded at/skywalking/config, and — the one that matters — that OAP is still reachable while a file is mounted, which is what breaks if the chart ever stops merging its computed values back in. Deliberately cheap: BanyanDB standalone, no istio, no bookinfo, no traffic.One bug caught in review, worth recording
The e2e login initially used a folded scalar whose continuation line was indented one space deeper than the first. In YAML a more-indented line inside a folded scalar is literal, so the newline survived into the env var; substituted into the image's
users: ${HORIZON_AUTH_LOCAL_USERS:[]}at indent 4 it produced an under-indented flow sequence.Horizon parses with the npm
yamlpackage, which rejects that, and the boot path doesn't catch the error — so the UI would have crash-looped and every cell failed. PyYAML accepts the same document, which is how it passed a first check. Verified against the real parser now.Docs
configure.mdrebuilt around the three mechanisms;logins.mdshows a Secret alone configuring logins;oap-configuration.mddocuments the new OAP values; the values reference carries the new rows.Several pages asserted the opposite of current behaviour rather than merely lagging it — "
HORIZON_TRUST_PROXYis inert under this chart", "theHORIZON_SESSION_COOKIE_SECUREenv var will not do it". Both work now, and those pages led operators down a dead end. Corrected inui-service-and-ingress.md,tls.mdandui-and-login.md.Also corrects the
values.yamlclaim that/dataholds the login audit — Horizon 1.0.0's audit backend is Postgres or nothing;/dataholds the wire debug log.Verification
helm lintclean; all three storage backends render; theui.configpath renders with the OAP tokens merged; menu paths resolve with no orphans; no broken links; workflows and all six e2e configs parse.