diff --git a/.github/workflows/e2e.ci.yaml b/.github/workflows/e2e.ci.yaml index f9d1d4f..9470a94 100644 --- a/.github/workflows/e2e.ci.yaml +++ b/.github/workflows/e2e.ci.yaml @@ -45,12 +45,9 @@ jobs: - name: Horizon + OAP 11 + BanyanDB (cluster) config: test/e2e/e2e-oap11-banyandb-cluster.yaml slug: oap11-banyandb-cluster - - name: Horizon + OAP 10.4 + Elasticsearch - config: test/e2e/e2e-oap10-elasticsearch.yaml - slug: oap10-elasticsearch - - name: Horizon + OAP 10.4 + BanyanDB 0.10 - config: test/e2e/e2e-oap10-banyandb.yaml - slug: oap10-banyandb + - name: Config override via ConfigMap (OAP + Horizon) + config: test/e2e/e2e-config-override.yaml + slug: config-override name: ${{ matrix.test.name }} steps: - uses: actions/checkout@v2 diff --git a/chart/skywalking/templates/NOTES.txt b/chart/skywalking/templates/NOTES.txt index f8dd9d7..af26930 100644 --- a/chart/skywalking/templates/NOTES.txt +++ b/chart/skywalking/templates/NOTES.txt @@ -84,8 +84,8 @@ it exposed points Horizon at the wrong service. Full horizon.yaml config reference: https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md -Horizon 1.0.0 ships its commented example AS the shipped config — the image's -/app/horizon.yaml is fully env-tokenized. This chart replaces that file with -the ConfigMap rendered from `ui.config`. +Horizon is configured by environment variable: the image's /app/horizon.yaml is +fully env-tokenized, and this chart mounts nothing over it unless you set +`ui.config`. Use `ui.extraEnv` and `ui.envFromSecret`. {{- end }} diff --git a/chart/skywalking/templates/oap-deployment.yaml b/chart/skywalking/templates/oap-deployment.yaml index 4d26f7f..f21cfa4 100644 --- a/chart/skywalking/templates/oap-deployment.yaml +++ b/chart/skywalking/templates/oap-deployment.yaml @@ -165,6 +165,14 @@ spec: - name: {{ $key }} value: {{ $value | quote }} {{- end }} + {{- range .Values.oap.extraEnv }} + - {{ toYaml . | nindent 10 | trim }} + {{- end }} +{{- if .Values.oap.envFromSecret }} + envFrom: + - secretRef: + name: {{ .Values.oap.envFromSecret | quote }} +{{- end }} volumeMounts: {{- include "skywalking.oap.volumeMounts" . | nindent 8 }} diff --git a/chart/skywalking/templates/oap-init.job.yaml b/chart/skywalking/templates/oap-init.job.yaml index 8b4f05d..1e4e349 100644 --- a/chart/skywalking/templates/oap-init.job.yaml +++ b/chart/skywalking/templates/oap-init.job.yaml @@ -94,6 +94,14 @@ spec: - name: {{ $key }} value: {{ $value | quote }} {{- end }} + {{- range .Values.oap.extraEnv }} + - {{ toYaml . | nindent 10 | trim }} + {{- end }} +{{- if .Values.oap.envFromSecret }} + envFrom: + - secretRef: + name: {{ .Values.oap.envFromSecret | quote }} +{{- end }} volumeMounts: {{- include "skywalking.oap.volumeMounts" . | nindent 8 }} diff --git a/chart/skywalking/templates/ui-configmap.yaml b/chart/skywalking/templates/ui-configmap.yaml index e399509..1ec56cc 100644 --- a/chart/skywalking/templates/ui-configmap.yaml +++ b/chart/skywalking/templates/ui-configmap.yaml @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -{{- if .Values.ui.enabled }} +{{- if and .Values.ui.enabled .Values.ui.config }} {{- /* horizon.yaml is built by deep-merging chart-computed values with `ui.config`. @@ -67,7 +67,12 @@ default/wildcard certificate, so it covers this host too. */ -}} {{- end }} {{- $_ := set $serverDefaults "publicUrl" (printf "${HORIZON_PUBLIC_URL:%s://%s}" $scheme $entry) }} {{- end }} -{{- $defaults := dict "oap" $oapDefaults "server" $serverDefaults }} +{{- /* Mounting this file replaces the image's, so any token it does not carry +is lost and the matching HORIZON_* variable goes inert. Users are the one field +where that is silently fatal -- the pod still reports Ready -- so the token is +preserved here unless the operator writes users of their own. */ -}} +{{- $authDefaults := dict "local" (dict "users" "${HORIZON_AUTH_LOCAL_USERS:[]}") }} +{{- $defaults := dict "oap" $oapDefaults "server" $serverDefaults "auth" $authDefaults }} {{- $userConfig := default (dict) .Values.ui.config }} {{- $merged := mergeOverwrite (deepCopy $defaults) (deepCopy $userConfig) }} apiVersion: v1 diff --git a/chart/skywalking/templates/ui-deployment.yaml b/chart/skywalking/templates/ui-deployment.yaml index f60da57..c546ae3 100644 --- a/chart/skywalking/templates/ui-deployment.yaml +++ b/chart/skywalking/templates/ui-deployment.yaml @@ -43,9 +43,11 @@ spec: component: "{{ .Values.ui.name }}" release: {{ .Release.Name }} annotations: - # Roll the pod when horizon.yaml changes — ConfigMap subPath mounts - # don't auto-update at runtime. +{{- if .Values.ui.config }} + # A subPath mount never updates in place, so roll the pod when the + # rendered horizon.yaml changes. checksum/config: {{ include (print $.Template.BasePath "/ui-configmap.yaml") . | sha256sum }} +{{- end }} {{- with .Values.ui.podAnnotations }} {{ toYaml . | indent 8 }} {{- end }} @@ -91,34 +93,67 @@ spec: readinessProbe: {{ toYaml .Values.ui.readinessProbe | indent 10 }} {{- end }} -{{- if or .Values.ui.envFromSecret .Values.ui.extraEnv }} +{{- $oap := include "skywalking.oap.fullname" . }} env: + # Horizon's image ships a fully env-tokenized /app/horizon.yaml, so the + # chart supplies only what it can compute and leaves every other field to + # the image's own defaults -- which keeps all of them settable through + # ui.extraEnv / ui.envFromSecret. Mounting a file here instead would + # replace that file and silently make those variables inert. + - name: HORIZON_SERVER_PORT + value: {{ .Values.ui.service.internalPort | quote }} + - name: HORIZON_OAP_QUERY_URL + value: "http://{{ $oap }}:{{ .Values.oap.ports.rest }}" +{{- if .Values.oap.ports.admin }} + - name: HORIZON_OAP_ADMIN_URL + value: "http://{{ $oap }}:{{ .Values.oap.ports.admin }}" +{{- end }} +{{- $zipkin := index .Values.oap.ports "zipkin-query" }} +{{- if $zipkin }} + - name: HORIZON_OAP_ZIPKIN_URL + value: "http://{{ $oap }}:{{ $zipkin }}/zipkin" +{{- end }} +{{- if and .Values.ui.ingress.enabled .Values.ui.ingress.hosts }} +{{- $entry := first .Values.ui.ingress.hosts }} +{{- $host := first (splitList "/" $entry) }} +{{- $scheme := "http" }} +{{- range .Values.ui.ingress.tls }} +{{- if or (empty (.hosts | default list)) (has $host (.hosts | default list)) }}{{ $scheme = "https" }}{{ end }} +{{- end }} + # SSO callbacks and the OAuth issuer are built from this; derived per + # host, since a tls block may cover only some of them. + - name: HORIZON_PUBLIC_URL + value: "{{ $scheme }}://{{ $entry }}" +{{- end }} {{- range .Values.ui.extraEnv }} - {{ toYaml . | nindent 10 | trim }} {{- end }} -{{- end }} {{- if .Values.ui.envFromSecret }} envFrom: - secretRef: - name: {{ .Values.ui.envFromSecret }} + name: {{ .Values.ui.envFromSecret | quote }} {{- end }} volumeMounts: +{{- if .Values.ui.config }} - name: horizon-config mountPath: /app/horizon.yaml subPath: horizon.yaml readOnly: true +{{- end }} - name: horizon-data mountPath: /data {{- with .Values.ui.extraVolumeMounts }} {{ toYaml . | indent 8 }} {{- end }} volumes: +{{- if .Values.ui.config }} - name: horizon-config configMap: name: {{ template "skywalking.ui.fullname" . }} items: - key: horizon.yaml path: horizon.yaml +{{- end }} - name: horizon-data {{- if .Values.ui.persistence.enabled }} persistentVolumeClaim: diff --git a/chart/skywalking/values.yaml b/chart/skywalking/values.yaml index 4628270..a5e95c1 100644 --- a/chart/skywalking/values.yaml +++ b/chart/skywalking/values.yaml @@ -104,10 +104,42 @@ oap: # image: my/query-proxy:1.0.0 # ports: # - { name: http, containerPort: 8080 } + # OAP can be configured three ways, the same as the UI below: + # + # 1. `oap.env` / `oap.extraEnv` -- environment variables. `env` is a plain + # map; `extraEnv` is a list, so it can carry `valueFrom` for a single + # value out of a Secret or the downward API. + # 2. `oap.envFromSecret` -- every key of a Secret as an env var. Note + # Kubernetes gives an explicit `env` entry precedence over `envFrom`, and + # the chart sets SW_ES_PASSWORD / SW_DATA_SOURCE_PASSWORD itself from the + # storage values -- so to source those from a Secret use `extraEnv` + # (below), which lands in `env` and wins. `envFromSecret` suits variables + # the chart does not set. + # 3. `oap.config` -- files rendered into a ConfigMap and + # mounted at /skywalking/config. Unlike Horizon, OAP reads real files for + # things like log4j2.xml and the OAL/MAL rules, so this is the only way to + # supply those. env: # more env, please refer to https://hub.docker.com/r/apache/skywalking-oap-server # or https://github.com/apache/skywalking-docker/blob/master/6/6.4/oap/README.md#sw_telemetry + # A list, so entries may use valueFrom. Use it where `oap.env` cannot reach -- + # a single credential from a Secret, or a value from the downward API. This is + # also the only way to override a variable the chart sets itself, since these + # land in `env` and an explicit `env` entry beats `envFrom`: + # + # extraEnv: + # - name: SW_DATA_SOURCE_PASSWORD + # valueFrom: + # secretKeyRef: + # name: oap-postgres + # key: password + extraEnv: [] + # Every key of a pre-created Secret becomes an env var in the OAP container + # (and in the init Job, which needs the same storage credentials). + # envFromSecret: oap-secrets + envFromSecret: "" + # Allows you to add any config files in /skywalking/config # such as log4j2.xml, oal/core.oal, etc. config: {} @@ -224,9 +256,10 @@ ui: # defaults to 101 (alpine's typical `nobody` GID). securityContext: fsGroup: 101 - # Persistent state directory used by the BFF for audit log, setup state, - # alarm state, and wire debug log. Without persistence these files land in - # the container's writable layer and are lost on pod restart. + # Persistent state directory (/data). Horizon 1.0.0 writes the wire debug log + # there when debugLog is enabled; the login audit is not file-backed at all, + # its only backend is Postgres. Without persistence anything written there + # lands in the container's writable layer and is lost on restart. persistence: enabled: false # existingClaim: "" @@ -253,51 +286,37 @@ ui: initialDelaySeconds: 10 periodSeconds: 10 failureThreshold: 6 - # Extra `horizon.yaml` content, deep-merged over the values the chart computes - # (the in-cluster OAP URLs, the ingress-derived publicUrl, and the BFF port). - # Schema reference: - # https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md + # Horizon can be configured three ways. The image ships a complete + # /app/horizon.yaml in which every field is a `${HORIZON_*:default}` + # placeholder, so the first two need no file at all: # - # EMPTY BY DESIGN. Horizon's image ships a fully env-tokenized - # /app/horizon.yaml, and the chart's ConfigMap replaces it -- so every field - # written here as a literal makes its `HORIZON_*` env var inert. Prefer - # `ui.extraEnv` / `ui.envFromSecret` for anything the chart does not compute, - # and set a field here only when you want to pin it regardless of env. + # 1. `ui.extraEnv` -- plain values, one env var per field + # 2. `ui.envFromSecret` -- the same, from a Secret, for anything sensitive + # 3. `ui.config` -- a horizon.yaml rendered into a ConfigMap and + # mounted over the image's copy # - # Some examples, with the env var that does the same job: + # Prefer 1 and 2. Structured settings take JSON in a single variable, so a + # Secret covers even the nested ones: # - # Only fields whose built-in default reads the environment can be set by a - # bare env var -- templates.mode, server.*, ai.*, mcp.*, oauth.*, audit.enabled - # among them: + # HORIZON_AUTH_LOCAL_USERS=[{"username":"admin","passwordHash":"$argon2id$...","roles":["admin"]}] + # HORIZON_AUTH_LDAP={"url":"ldaps://...","userBaseDn":"..."} + # HORIZON_RBAC_ROLES={...} # - # templates.mode HORIZON_TEMPLATES_MODE `readonly` on OAP 10.x - # (with oap.ports.admin: null) + # `ui.config` is for pinning a field regardless of the environment. Setting it + # mounts a file over the image's, which REPLACES that file: any field you do + # not write falls back to Horizon's built-in default, and its `HORIZON_*` + # variable stops working unless you write the token yourself + # (`cookieSecure: ${HORIZON_SESSION_COOKIE_SECURE:false}`). The chart's own + # computed values are merged in as tokens, so OAP stays reachable either way. + # Left empty, no ConfigMap is created and nothing is mounted. # - # Everything else has a literal default, so its HORIZON_* var is reachable ONLY - # through a `${...}` token written here. Setting the var alone does nothing: - # - # session: - # cookieSecure: ${HORIZON_SESSION_COOKIE_SECURE:false} - # server: - # trustProxy: ${HORIZON_TRUST_PROXY:false} - # oap: - # auth: ${HORIZON_OAP_AUTH:null} + # Field reference: + # https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md # # NO LOGIN IS CONFIGURED BY DEFAULT, and the BFF does not refuse to start # without one -- it boots, logs an error, serves the login page, and answers # the readiness probe with 200, so the pod goes Ready and nobody can log in. - # Supply users before you rely on the deployment. auth.local.users has a - # literal default, so HORIZON_AUTH_LOCAL_USERS in a Secret does NOTHING on its - # own -- the token has to be written here for the variable to reach it. Put the - # token in `config` and the value in a Secret named by `ui.envFromSecret`: - # - # config: - # auth: - # local: - # users: - # - username: admin - # passwordHash: "${HORIZON_ADMIN_HASH}" - # roles: [admin] + # Set HORIZON_AUTH_LOCAL_USERS before you rely on the deployment. config: {} # Optional: pass extra env vars into the BFF container. Use this to # provide values referenced by `${VAR}` interpolation in `config` diff --git a/docs/contributing/e2e-tests.md b/docs/contributing/e2e-tests.md index 8ec8011..324f2b3 100644 --- a/docs/contributing/e2e-tests.md +++ b/docs/contributing/e2e-tests.md @@ -50,8 +50,6 @@ each of those pairings needs. | `test/e2e/e2e-oap11-elasticsearch.yaml` | `$OAP_REPO:$OAP_TAG` (11.0.0) | `oap.storageType=elasticsearch` against the ECK subchart, left on by the chart's `elasticsearch.enabled: true` | Pre-installs the ECK CRDs out of `chart/skywalking/charts/eck-operator-3.3.1.tgz` and passes `eck-operator.installCRDs=false`. | | `test/e2e/e2e-oap11-banyandb-standalone.yaml` | 11.0.0 | BanyanDB 0.11 (`$BANYANDB_REPO:$BANYANDB_TAG`) | `oap.storageType=banyandb`, `elasticsearch.enabled=false`, `banyandb.enabled=true`, `banyandb.standalone.enabled=true`, `banyandb.cluster.enabled=false`, `banyandb.auth.enabled=true`. | | `test/e2e/e2e-oap11-banyandb-cluster.yaml` | 11.0.0 | BanyanDB 0.11, cluster mode | As above but `banyandb.standalone.enabled=false`, `banyandb.cluster.enabled=true`. | -| `test/e2e/e2e-oap10-elasticsearch.yaml` | `$OAP_10_REPO:$OAP_10_TAG` (10.4.0) | ECK Elasticsearch, with the same CRD pre-install and `eck-operator.installCRDs=false` as the cell above | `oap.ports.admin=null`, `ui.config.templates.mode=readonly`. | -| `test/e2e/e2e-oap10-banyandb.yaml` | 10.4.0 | BanyanDB 0.10.3 (`$BANYANDB_0_10_REPO:$BANYANDB_0_10_TAG`), standalone with auth | `oap.ports.admin=null`, `ui.config.templates.mode=readonly`. | The three OAP 11 cells also switch Zipkin on — `oap.ports.zipkin-query=9412` plus `SW_RECEIVER_ZIPKIN=default` and `SW_QUERY_ZIPKIN=default` — because the chart only emits @@ -191,8 +189,6 @@ versions under test are pinned. | `UI_REPO` / `UI_TAG` | `docker.io/apache/skywalking-ui` : `horizon-1.0.0` | Yes. Dev images live on GHCR (`apache/skywalking-horizon-ui`) if CI needs an unreleased fix. | | `BANYANDB_REPO` / `BANYANDB_TAG` | `ghcr.io/apache/skywalking-banyandb` at commit `3b83e18…` | Yes. A GHCR commit pin rather than `docker.io/apache/skywalking-banyandb:0.11.0` **on purpose**: that commit *is* the v0.11.0 tag, and naming it pins the exact source under test instead of a tag that can be re-pushed. The release image is published; user-facing install docs quote it. | | `SATELLITE_REPO` / `SATELLITE_TAG` | `ghcr.io/apache/skywalking-satellite/skywalking-satellite` at a commit tag | Yes. | -| `OAP_10_REPO` / `OAP_10_TAG` | `docker.io/apache/skywalking-oap-server` : `10.4.0` | **No — frozen.** 10.4.0 is the last v10 release. | -| `BANYANDB_0_10_REPO` / `BANYANDB_0_10_TAG` | `docker.io/apache/skywalking-banyandb` : `0.10.3` | **No — frozen.** OAP 10.4.0 pins `compatibleServerApiVersions` to BanyanDB API 0.10, so this pair never moves again. | The OAP 11 line moves as a trio: OAP 11.0.0 accepts BanyanDB server API 0.11 only, and Horizon 1.0.0 is the UI tested against it — see [Version Compatibility](../evaluate/version-compatibility.md). diff --git a/docs/evaluate/version-compatibility.md b/docs/evaluate/version-compatibility.md index 0f1f4ca..b6c24d1 100644 --- a/docs/evaluate/version-compatibility.md +++ b/docs/evaluate/version-compatibility.md @@ -4,7 +4,7 @@ Which OAP, UI and storage versions go together for chart **5.0.0**. Only two pai ## The tested set -This is the combination the three OAP 11 cells in `test/e2e/` install on every CI run, so it is the set the chart is best known to work with. The other two cells hold the OAP 10.4 line described further down. +This is the combination every cell in `test/e2e/` installs on each CI run, so it is the set the chart is best known to work with. The OAP 10.4 line described further down is documented but **not** covered by CI. | Component | Version | Where you set it | |---|---|---| diff --git a/docs/evaluate/what-this-chart-deploys.md b/docs/evaluate/what-this-chart-deploys.md index 88640f8..4370bc9 100644 --- a/docs/evaluate/what-this-chart-deploys.md +++ b/docs/evaluate/what-this-chart-deploys.md @@ -29,7 +29,7 @@ With everything else left at its default (`elasticsearch.enabled=true`), that re | Service | `skywalking-skywalking-helm-oap` | ClusterIP; ports `11800`, `12800`, `17128` | | Deployment | `skywalking-skywalking-helm-ui` | `ui.replicas: 1`, `strategy: Recreate` | | Service | `skywalking-skywalking-helm-ui` | ClusterIP, `80` → container `8081` | -| ConfigMap | `skywalking-skywalking-helm-ui` | the `horizon.yaml` mounted at `/app/horizon.yaml` | +| ConfigMap | `skywalking-skywalking-helm-ui` | only when `ui.config` is set — the rendered `horizon.yaml`, mounted over the image's | | ServiceAccount | `skywalking-skywalking-helm-oap` | used by both the OAP Deployment and the init Job | | Role + RoleBinding | `skywalking-skywalking-helm` | `get/watch/list` on pods, configmaps | | ClusterRole + ClusterRoleBinding | `skywalking-skywalking-helm` | `get/watch/list` on pods, pods/log, endpoints, services, nodes, namespaces, configmaps, deployments, replicasets, Istio `serviceentries` | diff --git a/docs/expose/tls.md b/docs/expose/tls.md index 135f7c7..ca53d89 100644 --- a/docs/expose/tls.md +++ b/docs/expose/tls.md @@ -135,13 +135,15 @@ ui: cookieSecure: true ``` -**Set it in `ui.config`, not as an env var.** The `HORIZON_SESSION_COOKIE_SECURE` -environment variable is inert under this chart. The chart mounts its own generated -`horizon.yaml` over `/app/horizon.yaml`, and that file omits the `session:` block -entirely. Horizon expands `${...}` over the raw *text* of the config file, so with no -`session:` block there is no token to expand and the schema default (`false`) wins — -`ui.extraEnv` will not change it. This is the general rule for the chart's ConfigMap and -is covered in [Configure Horizon](../ui/configure.md). +Set `HORIZON_SESSION_COOKIE_SECURE=true` whenever the UI is served over HTTPS — browsers refuse a +`Secure` cookie over plain HTTP, so without it the session cookie travels in the clear: + +```yaml +ui: + extraEnv: + - name: HORIZON_SESSION_COOKIE_SECURE + value: "true" +``` If you still want the value overridable by env — for example to keep one values file for both an HTTP dev cluster and an HTTPS production one — write the token yourself, quoted so diff --git a/docs/expose/ui-service-and-ingress.md b/docs/expose/ui-service-and-ingress.md index 9f530e1..cb449cb 100644 --- a/docs/expose/ui-service-and-ingress.md +++ b/docs/expose/ui-service-and-ingress.md @@ -92,10 +92,19 @@ Details worth knowing: - The host entry is used **verbatim**, path included: `hosts: [skywalking.example.com/ui]` yields `publicUrl: http://skywalking.example.com/ui`. - The scheme is decided by `ui.ingress.tls` alone. TLS terminated further out (a cloud LB in front of an ingress with no `tls:` block) still renders `http://` — set the value yourself in that case. -- It is written as a `${HORIZON_PUBLIC_URL:...}` token, so the derived value is only a default: `ui.extraEnv`/`ui.envFromSecret` can still override it at runtime. +- It is written as a `${HORIZON_PUBLIC_URL:...}` token, so the derived value is only a default: `ui.extraEnv` can still override it at runtime. `ui.envFromSecret` cannot: Kubernetes gives an explicit `env` entry precedence over `envFrom`, and the chart sets this one in `env`. - To pin it in the chart instead, set `ui.config.server.publicUrl` — a literal there wins over the derived default, and makes `HORIZON_PUBLIC_URL` inert. See [Configure Horizon](../ui/configure.md). -Also set `ui.config.session.cookieSecure: true` when you serve the UI over HTTPS, so the session cookie is not sent in the clear. The `HORIZON_SESSION_COOKIE_SECURE` env var will not do it — the chart's `horizon.yaml` omits the `session:` block, so there is no token to expand. See [TLS](tls.md). +Also set `HORIZON_SESSION_COOKIE_SECURE=true` when you serve the UI over HTTPS, so the session cookie is not sent in the clear: + +```yaml +ui: + extraEnv: + - name: HORIZON_SESSION_COOKIE_SECURE + value: "true" +``` + +See [TLS](tls.md). ## `server.trustProxy` and the client address @@ -112,7 +121,7 @@ Accepted forms are a **hop count** (`1` = one proxy in front, `2` = a proxy behi `true` is **refused at boot** — the pod will not start. Blanket trust would let any caller set `X-Forwarded-For` and choose the address written to the audit log, so Horizon requires you to name how far the trust extends. Pick the hop count or CIDR that matches your actual topology; too large a hop count has the same problem as `true`. -Set it in `ui.config`, not via `ui.extraEnv`. `HORIZON_TRUST_PROXY` is inert under this chart: the generated `horizon.yaml` has no `server.trustProxy` line, so there is no `${...}` token for Horizon to expand and the schema default (`false`) wins. Write `trustProxy: "${HORIZON_TRUST_PROXY:1}"` under `ui.config.server` if you want it env-overridable. `server.trustProxy` is read once when the HTTP server is constructed, so it only takes effect on a pod restart — the Deployment's `checksum/config` annotation makes `helm upgrade` do that for you. +Set it with `HORIZON_TRUST_PROXY` in `ui.extraEnv` — a hop count (`1` for one proxy in front) or the ingress address/CIDR. `true` is refused at boot, since it would let any caller choose the address recorded. It is read once when the HTTP server is constructed, so a change takes effect on the next pod restart. ## Replicas and sticky sessions diff --git a/docs/operate/oap-configuration.md b/docs/operate/oap-configuration.md index 57e73e2..f2bc980 100644 --- a/docs/operate/oap-configuration.md +++ b/docs/operate/oap-configuration.md @@ -1,15 +1,21 @@ # Configure OAP -How to change OAP backend behaviour from the chart: environment variables (`oap.env`), overridden -configuration files under `/skywalking/config` (`oap.config`), and runtime dynamic configuration -backed by a ConfigMap (`oap.dynamicConfig`). +OAP is configured three ways, the same three the UI offers: environment variables (`oap.env`, +`oap.extraEnv`), a Secret exposed as environment variables (`oap.envFromSecret`), and files dropped +into `/skywalking/config` (`oap.config`). On top of those sits `oap.dynamicConfig`, OAP's own +runtime-rule mechanism. -## Three levers +Prefer the first two. Reach for `oap.config` only for the files env cannot express — see +[why OAP still needs a file](#why-oap-still-needs-a-file-where-horizon-does-not). + +## The levers | Value | What it changes | Applies to | Takes effect | |---|---|---|---| -| `oap.env` | Environment variables on the OAP container | OAP Deployment **and** the OAP init Job | On pod restart (Helm rolls the Deployment because the pod spec changed) | -| `oap.config` | Files dropped into `/skywalking/config` | OAP Deployment **and** the OAP init Job | On pod restart — see the caveat below, a config-only change does **not** roll the Deployment | +| `oap.env` | Environment variables, as a plain map | OAP Deployment **and** the OAP init Job | On restart — `helm upgrade` rolls the Deployment, and re-creates the init Job (its name hashes the values) | +| `oap.extraEnv` | Environment variables, as a **list** — entries may carry `valueFrom` | OAP Deployment **and** the OAP init Job | Same | +| `oap.envFromSecret` | Every key of an existing Secret, as environment variables | OAP Deployment **and** the OAP init Job | Same — but editing the Secret's *contents* changes no pod field, so nothing rolls; restart the pods yourself | +| `oap.config` | Files placed into `/skywalking/config` | OAP Deployment **and** the OAP init Job | On restart — but editing a file's *contents* changes no pod field, so nothing rolls; see the caveat below | | `oap.dynamicConfig` | Runtime rules OAP re-reads from a ConfigMap | OAP Deployment only | Within `oap.dynamicConfig.period` seconds, no restart | Environment variables win over the shipped configuration files: OAP's `application.yml` resolves @@ -18,25 +24,32 @@ baked into the image. That only holds while the placeholder survives — if you `oap.config` and write a literal where the shipped file had `${SW_SOMETHING:...}`, the literal wins and the variable is ignored. -## Environment variables (`oap.env`) +## Why OAP still needs a file where Horizon does not -`oap.env` is a plain map of name to value. Every entry is appended to the container `env` list (the -chart quotes the value, so numbers and booleans are safe to write unquoted). +Horizon's image ships a `/app/horizon.yaml` in which *every* field is a `${HORIZON_*:default}` +token, so the chart mounts nothing by default and sets plain env vars instead — see +[Configure Horizon](../ui/configure.md). -```shell -helm upgrade --install skywalking oci://registry-1.docker.io/apache/skywalking-helm \ - --version 5.0.0 -n skywalking --create-namespace \ - --set oap.image.tag=11.0.0 \ - --set oap.storageType=banyandb \ - --set ui.image.tag=horizon-1.0.0 \ - --set elasticsearch.enabled=false \ - --set banyandb.enabled=true \ - --set banyandb.image.tag=0.11.0 \ - --set oap.env.SW_ENVOY_METRIC_ALS_HTTP_ANALYSIS=k8s-mesh \ - --set oap.env.SW_ENVOY_METRIC_ALS_TCP_ANALYSIS=k8s-mesh -``` +OAP is only half that. `application.yml` is tokenized the same way, so every setting in it is +reachable from the environment, and so is `bydb.yml` — the BanyanDB group file, every shard/TTL +knob in it a `${SW_STORAGE_BANYANDB_*}`. The logging and rule documents are not: OAP reads those as +**real files**, and no variable can carry them. + +| File in `/skywalking/config` | Why env cannot reach it | +|---|---| +| `log4j2.xml` | Levels and appenders are literals. The image's copy has no `${...}` at all — the Dockerfile deletes the tarball's file (which carries `${sys:oap.logDir}`, a JVM system property, not an env var) and ships a console-only one | +| `oal/*.oal` | OAL source OAP compiles at boot — a script, not a setting | +| `otel-rules/*`, `meter-analyzer-config/*`, `log-mal-rules/*`, `lal/*`, `metadata-service-mapping.yaml` | MAL/LAL rule documents, read whole | + +So: environment variables for anything in `application.yml` or `bydb.yml`, `oap.config` for the +logging and rule files. + +## Environment variables + +### `oap.env` — a map -Or in a values file, which is easier once you have more than one or a value contains commas: +A plain map of name to value. Every entry is appended to the container `env` list (the chart quotes +the value, so numbers and booleans are safe to write unquoted). It cannot express `valueFrom`. ```yaml oap: @@ -47,28 +60,185 @@ oap: SW_CORE_RECORD_DATA_TTL: 3 ``` +`--set oap.env.SW_CORE_RECORD_DATA_TTL=3` works as well, but a values file is easier once you have +more than one, or a value contains commas. + The full list of variables OAP understands is upstream: [Configuration Vocabulary](https://skywalking.apache.org/docs/main/latest/en/setup/backend/configuration-vocabulary/). -### Variables the chart already manages +### `oap.extraEnv` — a list, for `valueFrom` + +A list of raw Kubernetes `EnvVar` entries, rendered verbatim after `oap.env`. Use it where a map +cannot reach — anything needing `valueFrom`: one value out of a Secret (`secretKeyRef`) or a +ConfigMap (`configMapKeyRef`), a pod field (`fieldRef`), a resource limit (`resourceFieldRef`). + +```yaml +oap: + extraEnv: + - name: SW_DATA_SOURCE_PASSWORD + valueFrom: + secretKeyRef: + name: oap-postgres + key: password +``` -Do not set these through `oap.env` — `oap.env` is rendered *after* them, producing a duplicate entry -in the same `env` list instead of a clean override. Use the dedicated value instead. +### `oap.envFromSecret` — a whole Secret + +The name of a **pre-existing** Secret in the release namespace; the chart turns it into an +`envFrom.secretRef` on both OAP containers, so every key becomes a variable. The chart does not +create the Secret, and does not mark the reference optional — if it is missing, the pods sit in +`CreateContainerConfigError`. + +```yaml +oap: + envFromSecret: oap-storage +``` + +### Precedence + +The rendered `env` list is, in order: the variables the chart computes, then `oap.env`, then +`oap.extraEnv`. Kubernetes resolves `envFrom` first and lets `env` replace it, and within `env` a +later entry with the same name replaces an earlier one. So: + +- **`oap.extraEnv` can override anything**, including the storage block the chart computes. Both + entries stay visible in the pod spec; the later one is what the process sees. +- **`oap.envFromSecret` can only fill names the chart leaves unset.** A key that collides with a + variable the chart already writes as `env` is silently ignored. + +### Variables the chart already manages | Variable(s) | Set by the chart from | Use this instead | |---|---|---| | `JAVA_OPTS` | `oap.javaOpts` plus `-Dmode=no-init` (`-Dmode=init` in the init Job) | `oap.javaOpts` (default `-Xmx2g -Xms2g`) | -| `SW_STORAGE`, `SW_STORAGE_ES_CLUSTER_NODES`, `SW_ES_USER`, `SW_ES_PASSWORD`, `SW_JDBC_URL`, `SW_DATA_SOURCE_USER`, `SW_DATA_SOURCE_PASSWORD`, `SW_STORAGE_BANYANDB_TARGETS`, `SW_STORAGE_BANYANDB_USER`, `SW_STORAGE_BANYANDB_PASSWORD` | `oap.storageType` and the storage backend's own values | [Pick a Storage Backend](../storage/choose-a-backend.md) | +| `SW_STORAGE`, `SW_STORAGE_ES_CLUSTER_NODES`, `SW_ES_USER`, `SW_ES_PASSWORD`, `SW_JDBC_URL`, `SW_DATA_SOURCE_USER`, `SW_DATA_SOURCE_PASSWORD`, `SW_STORAGE_BANYANDB_TARGETS`, `SW_STORAGE_BANYANDB_USER`, `SW_STORAGE_BANYANDB_PASSWORD` | `oap.storageType` and the storage backend's own values | [Pick a Storage Backend](../storage/choose-a-backend.md); for the credentials, [Storage credentials from a Secret](#storage-credentials-from-a-secret) | | `SW_CLUSTER`, `SW_CLUSTER_K8S_NAMESPACE`, `SW_CLUSTER_K8S_LABEL` | Fixed to `kubernetes` plus the release namespace and label selector | Nothing — the chart always runs OAP in Kubernetes cluster mode | | `SW_RECEIVER_ZIPKIN`, `SW_RECEIVER_ZIPKIN_REST_PORT`, `SW_QUERY_ZIPKIN`, `SW_QUERY_ZIPKIN_REST_PORT` | Rendered only when `oap.ports.zipkin-receiver` / `oap.ports.zipkin-query` are set | `oap.ports` | | `SW_CONFIGURATION`, `SW_CONFIG_CONFIGMAP_PERIOD` | Rendered when `oap.dynamicConfig.enabled` is `true` | `oap.dynamicConfig` | | `SKYWALKING_COLLECTOR_UID` | The pod UID, via the downward API | Nothing | -`JAVA_OPTS` is the one that bites: a second entry can shadow the chart's `-Dmode=no-init`, which is -what makes the Deployment leave schema creation to the init Job. Put JVM flags in `oap.javaOpts`. +`JAVA_OPTS` is the one that bites: a second entry replaces the chart's, dropping `-Dmode=no-init` — +which is what makes the Deployment leave schema creation to the init Job. Put JVM flags in +`oap.javaOpts`. + +All three env mechanisms also apply to the one-shot OAP init Job, so schema-affecting variables +(storage credentials, TTL, index settings) reach the process that creates the schema. See +[The OAP Init Job](oap-init-job.md). + +## Storage credentials from a Secret -`oap.env` is also applied to the one-shot OAP init Job, so schema-affecting variables (storage TTL, -index settings) reach the process that creates the schema. See [The OAP Init Job](oap-init-job.md). +The storage block is computed from values, which means a password written in `values.yaml` ends up +as a literal in the OAP pod spec. Keep it in a Secret instead. (Embedded Elasticsearch, +`elasticsearch.enabled=true`, is already safe: the chart reads `SW_ES_PASSWORD` from the +ECK-generated secret with a `secretKeyRef`. Everything below is for the external backends.) + +### External Elasticsearch — `oap.envFromSecret` + +`SW_ES_USER` / `SW_ES_PASSWORD` are only emitted for an external cluster when +`elasticsearch.config.user` / `.password` are non-empty. Leave both empty and the names are free for +a Secret to fill: + +```shell +kubectl create secret generic oap-storage -n skywalking \ + --from-literal=SW_ES_USER=skywalking \ + --from-literal=SW_ES_PASSWORD='' +``` + +```yaml +# oap-storage.yaml +oap: + image: + tag: 11.0.0 + storageType: elasticsearch + envFromSecret: oap-storage +ui: + image: + tag: horizon-1.0.0 +elasticsearch: + enabled: false + config: + host: es.internal + port: + http: 9200 +``` + +Confirm the render before installing — the Deployment *and* the Job must both carry the +`secretRef`, or schema creation authenticates with nothing: + +```shell +helm template sw chart/skywalking -f oap-storage.yaml \ + -s templates/oap-deployment.yaml -s templates/oap-init.job.yaml \ + | grep -E '^kind:|SW_ES|SW_STORAGE$|secretRef|name: oap-storage' +``` + +```text +kind: Deployment + - name: SW_STORAGE + - secretRef: + name: oap-storage +kind: Job + - name: SW_STORAGE + - secretRef: + name: oap-storage +``` + +No `SW_ES_PASSWORD` appears in either pod spec: the only copy lives in the Secret. + +### PostgreSQL — `oap.extraEnv` + +PostgreSQL is different, because the chart writes `SW_DATA_SOURCE_PASSWORD` **unconditionally** from +`postgresql.auth.password`. An `env` entry beats `envFrom`, so a key of that name in +`oap.envFromSecret` would be ignored. Override it with `oap.extraEnv`, which renders after the +storage block: + +```yaml +# oap-postgres.yaml +oap: + image: + tag: 11.0.0 + storageType: postgresql + extraEnv: + - name: SW_DATA_SOURCE_PASSWORD + valueFrom: + secretKeyRef: + name: oap-postgres + key: password +ui: + image: + tag: horizon-1.0.0 +elasticsearch: + enabled: false +postgresql: + enabled: false + config: + host: pg.internal + auth: + password: "" # the shadowed literal is still in the pod spec — keep it empty +``` + +```shell +helm template sw chart/skywalking -f oap-postgres.yaml \ + -s templates/oap-deployment.yaml | grep -A4 SW_DATA_SOURCE_PASSWORD +``` + +```text + - name: SW_DATA_SOURCE_PASSWORD + value: "" + - name: SW_DATA_SOURCE_PASSWORD + valueFrom: + secretKeyRef: + key: password + name: oap-postgres +``` + +Two entries, and the second wins. Swap `-s templates/oap-init.job.yaml` into that command to see +the init Job carrying the same pair. + +BanyanDB behaves like Elasticsearch: the chart writes `SW_STORAGE_BANYANDB_USER` / +`SW_STORAGE_BANYANDB_PASSWORD` only when `banyandb.auth.enabled` is `true` **and** +`banyandb.auth.users` is non-empty. `banyandb.auth.enabled` defaults to `false` — note that +`banyandb.auth.users` does *not* default to empty, it carries an `admin`/`banyandb` entry — so +against an external cluster leave `auth.enabled` off and `oap.envFromSecret` fills both names +cleanly. ## Configuration file overrides (`oap.config`) @@ -97,7 +267,7 @@ oap: # three levels also work ``` -That renders to these mounts in the OAP container: +That renders to these mounts, in the OAP container **and** in the init Job: | `oap.config` path | Mounted at | |---|---| @@ -121,9 +291,10 @@ kubectl exec -n skywalking deploy/skywalking-skywalking-helm-oap -- ls /skywalki Two consequences worth knowing: -- **A config-only change does not restart OAP.** The Deployment's pod template does not embed a - checksum of this ConfigMap, and `subPath` mounts do not track ConfigMap updates. After a - `helm upgrade` that only touches `oap.config`, roll the pods yourself: +- **Editing a file's contents does not restart OAP.** The Deployment's pod template does not embed + a checksum of this ConfigMap, and `subPath` mounts do not track ConfigMap updates. (Adding or + removing a *path* does roll it — that changes the mount list.) After a `helm upgrade` that only + rewrites a file already in `oap.config`, roll the pods yourself: ```shell kubectl rollout restart -n skywalking deploy/skywalking-skywalking-helm-oap ``` @@ -131,12 +302,8 @@ Two consequences worth knowing: - **Avoid `-` collisions in the flattened key space.** A top-level key literally named `oal-core.oal` and a nested `oal` → `core.oal` produce the same ConfigMap key. -Files most commonly overridden here: `log4j2.xml` (log level and appenders), `oal/*.oal` -([OAL scripts](https://skywalking.apache.org/docs/main/latest/en/concepts-and-designs/oal/)) and -`metadata-service-mapping.yaml` (Kubernetes-to-service naming for the Envoy/mesh receivers). - -Secrets — TLS material, keystores — should go through `oap.secretMounts` rather than `oap.config`, -which is a plain ConfigMap. +Secrets do not belong here — it is a plain ConfigMap. Credentials go through `oap.envFromSecret` or +`oap.extraEnv`; TLS material and keystores, which have to be files, go through `oap.secretMounts`. ## Dynamic configuration (`oap.dynamicConfig`) @@ -200,6 +367,7 @@ kubectl logs -n skywalking deploy/skywalking-skywalking-helm-oap | grep -i confi ## Related - [The OAP Init Job](oap-init-job.md) — which changes re-run schema creation +- [Configure Horizon](../ui/configure.md) — the same three mechanisms on the UI side - [Scaling and the OAP Cluster](scaling.md) - [OAP Endpoints for Agents](../expose/oap-endpoints.md) — `oap.ports` and the Service - [skywalking Chart Values](../reference/skywalking-chart-values.md) — every `oap.*` value diff --git a/docs/reference/skywalking-chart-values.md b/docs/reference/skywalking-chart-values.md index cb0fcfa..fbcc0f8 100644 --- a/docs/reference/skywalking-chart-values.md +++ b/docs/reference/skywalking-chart-values.md @@ -31,6 +31,25 @@ Both fail the render rather than defaulting: | `banyandb.image.tag` | `banyandb.enabled=true` — otherwise the subchart is not rendered at all | `0.11.0` | | `satellite.image.tag` | `satellite.enabled=true` (`false` by default) | `v1.3.0` | +Both components are configurable three ways, and the chart's own values follow that split: + +| mechanism | OAP | Horizon UI | +|---|---|---| +| env vars | `oap.env` (map), `oap.extraEnv` (list, so entries may use `valueFrom`) | `ui.extraEnv` (list) | +| env vars from a Secret | `oap.envFromSecret` (Deployment **and** init Job) | `ui.envFromSecret` | +| a file via ConfigMap | `oap.config` | `ui.config` (opt-in) | + +Prefer the first two for both. The two files are not equivalent: Horizon's image ships a complete +`/app/horizon.yaml` in which every field is a `${HORIZON_*:default}` placeholder, so nothing has to +be mounted and `ui.config` replaces a file that already works. OAP reads real files for `log4j2.xml` +and the OAL/MAL rules, which no environment variable can supply, so `oap.config` is the only route +to those. See [Configure Horizon](../ui/configure.md) and +[OAP configuration](../operate/oap-configuration.md). + +Kubernetes resolves `envFrom` before `env`, and a later `env` entry wins over an earlier one of the +same name. So a Secret cannot override a variable the chart itself sets — `oap.extraEnv` / +`ui.extraEnv`, both rendered last, can. + The following table lists the configurable parameters of the Skywalking chart and their default values. | Parameter | Description | Default | @@ -50,10 +69,10 @@ The following table lists the configurable parameters of the Skywalking chart an | `oap.image.tag` | OAP container image tag. Required on every install -- no default | `null` | | `oap.image.pullPolicy` | OAP container image pull policy | `IfNotPresent` | | `oap.ports.grpc` | OAP grpc port for tracing or metric | `11800` | -| `oap.ports.rest` | OAP http port for the GraphQL query protocol (used by the UI and by `swctl`) | `12800` | -| `oap.ports.admin` | OAP admin REST port (admin-server, status, inspect, ui-management, dsl-debugging, runtime-rule). Introduced in OAP 11, which enables all of them by default and serves `/status/*` and `/debugging/*` here exclusively. Set to `null` on any OAP 10.x release, where 17128 is the AI-pipeline URI-recognition server instead | `17128` | +| `oap.ports.rest` | OAP http port for the GraphQL query protocol (used by the UI and by `swctl`). The chart always builds the UI's `HORIZON_OAP_QUERY_URL` from it | `12800` | +| `oap.ports.admin` | OAP admin REST port (admin-server, status, inspect, ui-management, dsl-debugging, runtime-rule). Introduced in OAP 11, which enables all of them by default and serves `/status/*` and `/debugging/*` here exclusively. Set to `null` on any OAP 10.x release, where 17128 is the AI-pipeline URI-recognition server instead. The chart sets the UI's `HORIZON_OAP_ADMIN_URL` only while this port is set | `17128` | | `oap.ports.zipkin-receiver` | OAP http port for Zipkin receiver(not exposed by default) | not set (commented out in `values.yaml`) | -| `oap.ports.zipkin-query` | OAP http port for querying Zipkin traces and UI(not exposed by default) | not set (commented out in `values.yaml`) | +| `oap.ports.zipkin-query` | OAP http port for querying Zipkin traces and UI(not exposed by default). When set, the chart also gives the UI `HORIZON_OAP_ZIPKIN_URL`, so the Zipkin trace pages resolve | not set (commented out in `values.yaml`) | | `oap.ports.promql` / `logql` / `traceql` / `metrics` | Further OAP listeners, each commented out in `values.yaml`. Any key added under `oap.ports` becomes both a container port and a Service port, named after the key | not set (commented out in `values.yaml`) | | `oap.replicas` | OAP k8s deployment replicas | `2` | | `oap.service.type` | OAP svc type | `ClusterIP` | @@ -67,17 +86,19 @@ The following table lists the configurable parameters of the Skywalking chart an | `oap.startupProbe` | Configuration fields for the [startupProbe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/). `values.yaml` ships `{}` and the template renders the fallback shown here; setting the value replaces it wholesale. The default budget (`failureThreshold` * `periodSeconds` = 300s) is large enough for OAP to wait in no-init mode while the OAP init Job creates the storage schema. | `tcpSocket.port: 12800`
`failureThreshold: 30`
`periodSeconds: 10` | `oap.livenessProbe` | Configuration fields for the [livenessProbe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | `tcpSocket.port: 12800`
`initialDelaySeconds: 5`
`periodSeconds: 10` | `oap.readinessProbe` | Configuration fields for the [readinessProbe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | `tcpSocket.port: 12800`
`initialDelaySeconds: 5`
`periodSeconds: 10` -| `oap.env` | OAP environment variables | not set | +| `oap.env` | OAP environment variables as a plain **map** (`SW_...: value`), rendered into both the OAP Deployment and the OAP init Job. A map cannot carry `valueFrom` — use `oap.extraEnv` for that | not set | +| `oap.extraEnv` | The same, but a **list** of whole env entries, so an item may use `valueFrom` (a `secretKeyRef`, or the downward API). Rendered into the Deployment and the init Job alike, after the chart's storage env and after `oap.env` — so an entry repeating one of those names overrides it. The only way to replace a chart-computed value such as `SW_DATA_SOURCE_PASSWORD` | `[]` | +| `oap.envFromSecret` | Name of a pre-created Secret; every key becomes an env var on the OAP container **and** on the init Job's container, which needs the same storage credentials. For keys the chart does not set itself — `env` beats `envFrom`, and the chart emits `SW_ES_PASSWORD` for the bundled ES (from the ECK secret) and `SW_DATA_SOURCE_PASSWORD` on every `postgresql` install, so those two have to go through `oap.extraEnv`. A `SW_ES_PASSWORD` here reaches OAP only against an external ES with `elasticsearch.config.password` left empty | `""` | | `oap.podAnnotations` | Annotations applied to all OAP pods | not set (commented out in `values.yaml`) | -| `oap.config` | Extra files written into `/skywalking/config` (e.g. `log4j2.xml`, `oal/core.oal`), keyed by path | `{}` | +| `oap.config` | Files rendered into a ConfigMap and mounted into `/skywalking/config`, keyed by path (`log4j2.xml`, `oal/core.oal`, `metadata-service-mapping.yaml`). OAP reads these as real files, so unlike Horizon this is the only way to supply them — no env var can | `{}` | | `oap.secretMounts` | Secrets to mount into the OAP pod, each `{name, secretName, path}` | `[]` | | `oap.securityContext` | Allows you to set the [securityContext](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) for the pod | `{}` | | `oap.sidecars` | Extra sidecar containers to run in the OAP pod (appended to the pod's `containers` list, rendered through `tpl`) | `[]` | -| `ui.enabled` | Deploy the Horizon UI. Set `false` to skip the UI Deployment, Service, Ingress, ConfigMap, and PVC entirely (useful when an external UI talks to OAP directly) | `true` | +| `ui.enabled` | Deploy the Horizon UI. Set `false` to skip the UI Deployment, Service, Ingress, PVC — and the `ui.config` ConfigMap, if one is configured — entirely (useful when an external UI talks to OAP directly) | `true` | | `ui.name` | Web UI deployment name | `ui` | | `ui.replicas` | Web UI k8s deployment replicas. Keep at `1` unless your ingress provides sticky sessions — the Horizon BFF holds the session table in memory | `1` | | `ui.image.repository` | Horizon UI container image. Release images: Docker Hub `apache/skywalking-ui` tagged `horizon-x.y.z`. Dev images: `ghcr.io/apache/skywalking-horizon-ui` | `skywalking.docker.scarf.sh/apache/skywalking-ui` | -| `ui.image.tag` | Horizon UI image tag (required), e.g. `horizon-1.0.0`. Horizon releases independently of OAP and 1.0.0 works against OAP 10.4.0 and 11.x alike (for 10.x also set `ui.config.templates.mode: readonly`). The legacy booster UI is not supported — `apache/skywalking-ui` publishes no 11.x tag | `null` | +| `ui.image.tag` | Horizon UI image tag (required), e.g. `horizon-1.0.0`. Horizon releases independently of OAP and 1.0.0 works against OAP 10.4.0 and 11.x alike (on 10.x also set `HORIZON_TEMPLATES_MODE=readonly` and `oap.ports.admin=null`). The legacy booster UI is not supported — `apache/skywalking-ui` publishes no 11.x tag | `null` | | `ui.image.pullPolicy` | Web UI container image pull policy | `IfNotPresent` | | `ui.nodeAffinity` | Web UI node affinity policy | `{}` | | `ui.nodeSelector` | Web UI labels for pod assignment | `{}` | @@ -89,7 +110,7 @@ The following table lists the configurable parameters of the Skywalking chart an | `ui.ingress.tls` | Associate TLS with the Ingress | `[]` | | `ui.service.type` | Web UI svc type | `ClusterIP` | | `ui.service.externalPort` | external port for the service | `80` | -| `ui.service.internalPort` | internal port for the service (Horizon BFF binds 8081) | `8081` | +| `ui.service.internalPort` | internal port for the service (Horizon BFF binds 8081). One source for three things: the chart passes it to the container as `HORIZON_SERVER_PORT`, opens the container port `page` on it, and both probes target `page` — so the BFF, the port and the probes cannot drift apart | `8081` | | `ui.service.externalIPs` | external IP addresses | `nil` | | `ui.service.loadBalancerIP` | Load Balancer IP address | `nil` | | `ui.service.annotations` | Kubernetes service annotations | `{}` | @@ -106,13 +127,14 @@ The following table lists the configurable parameters of the Skywalking chart an | `ui.persistence.annotations` | Annotations applied to the chart-managed PVC | `{}` | | `ui.resources` | UI node resources requests & limits | `{}` | | `ui.podAnnotations` | Annotations applied to all UI pods | not set (commented out in `values.yaml`) | -| `ui.config` | `horizon.yaml` content (deep-merged onto chart defaults that point `oap.queryUrl`/`adminUrl`/`zipkinUrl` at the in-cluster OAP; `zipkinUrl` is derived from `oap.ports.zipkin-query` and only resolves to a usable URL when that port is set, and `server.publicUrl` is derived from `ui.ingress.hosts[0]` when an ingress is enabled). See the [horizon.yaml reference](https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md). Empty by default: the chart-computed values are written as `${HORIZON_*:}` tokens, so anything not set here stays overridable by env via `ui.extraEnv` / `ui.envFromSecret`. A field set here as a literal makes its `HORIZON_*` env var inert | `{}` | -| `ui.config.templates.mode` | `live` reads/writes dashboard templates through OAP 11's `/ui-management/templates*` admin REST API; `readonly` renders the templates bundled in the image and makes the config surface display-only. Set `readonly` against OAP 10.4.0 — it has no such REST surface, and `live` blocks every layer-driven page. Changing this needs a BFF restart | `live` | -| `ui.config.server.publicUrl` | Public base URL operators reach Horizon at; used for SSO callbacks and as the OAuth issuer. Derived from the first `ui.ingress.hosts` entry when an ingress is enabled; set explicitly to override | derived, else `""` | -| `ui.config.server.trustProxy` | Whether to believe `X-Forwarded-For` for the client address in the login audit. Use a hop count (`1` = one proxy in front) or the ingress address/CIDR; `true` is refused at boot | `false` | -| `ui.envFromSecret` | Reference a Secret whose keys are exposed as env vars in the BFF container, for use with `${VAR}` interpolation in `ui.config` (e.g. admin password hash) | `""` | -| `ui.extraEnv` | Extra env vars passed to the BFF container | `[]` | -| `ui.extraVolumes` | Extra volumes for the UI pod. Needed by the two Horizon settings that take a filesystem path: `auth.tokensFile` (API tokens Secret) and `sourceMaps.bootMountDir` (durable `.map` files at `/app/sourcemaps`) | `[]` | +| `ui.extraEnv` | Env vars on the BFF container, as a list, so an entry may use `valueFrom`. **The primary way to configure Horizon**: the image's `horizon.yaml` is one `${HORIZON_*:default}` placeholder per field, and structured blocks take JSON in a single variable (`HORIZON_AUTH_LOCAL_USERS`, `HORIZON_AUTH_LDAP`, `HORIZON_AUTH_SSO`, `HORIZON_RBAC_ROLES`, `HORIZON_PERFORMANCE`, `HORIZON_LAYERS_EXCLUDED`). The chart itself sets `HORIZON_SERVER_PORT` and `HORIZON_OAP_QUERY_URL` always, plus `HORIZON_OAP_ADMIN_URL`, `HORIZON_OAP_ZIPKIN_URL` and `HORIZON_PUBLIC_URL` when their sources are configured; everything else falls through to the image's own defaults | `[]` | +| `ui.envFromSecret` | Name of a pre-created Secret; every key becomes an env var on the BFF container. Where the secret-bearing fields belong — `HORIZON_AUTH_LOCAL_USERS`, `HORIZON_AUTH_LDAP`, `HORIZON_AI_API_KEY`, `HORIZON_OAUTH_SIGNING_KEY`, `HORIZON_AUDIT_POSTGRES`. A Secret is sufficient on its own: no `ui.config` and no `${VAR}` token anywhere | `""` | +| `HORIZON_AUTH_LOCAL_USERS` | Not a chart value — the env var that creates logins, set through `ui.envFromSecret`. JSON array: `[{"username":"admin","passwordHash":"$argon2id$...","roles":["admin"]}]`. There is no built-in `admin/admin`, and the BFF does not fail closed: with no users it boots, serves the login page and passes the readiness probe, and nobody can log in | not set | +| `HORIZON_TEMPLATES_MODE` | Also an env var, via `ui.extraEnv`. `live` reads/writes dashboard templates through OAP 11's `/ui-management/templates*` admin REST API; `readonly` renders the templates bundled in the image and makes the config surface display-only. Set `readonly` against OAP 10.4.0 — it has no such REST surface, and `live` blocks every layer-driven page. Changing this needs a BFF restart | `live` (image default) | +| `HORIZON_PUBLIC_URL` | Public base URL operators reach Horizon at; used for SSO callbacks and as the OAuth issuer. Set by the chart from the first `ui.ingress.hosts` entry when the ingress is enabled and has hosts — `https` when a `ui.ingress.tls` block covers that host (an entry with no `hosts` counts as covering), otherwise `http`. Override with `ui.extraEnv` | derived, else the image's `""` | +| `HORIZON_TRUST_PROXY` | Whether to believe `X-Forwarded-For` for the client address in the login audit. Use a hop count (`1` = one proxy in front) or the ingress address/CIDR; `true` is refused at boot | `false` (image default) | +| `ui.config` | Opt-in `horizon.yaml`. Empty — the default — means **no ConfigMap and no mount**, and the image's own complete file is used. Setting it renders a ConfigMap and mounts it over `/app/horizon.yaml`, *replacing* that file: any field you do not write falls back to Horizon's built-in default, and its `HORIZON_*` variable stops working unless you write the token yourself (`cookieSecure: ${HORIZON_SESSION_COOKIE_SECURE:false}`). Three things are merged in for you: the OAP URLs and the ingress-derived `publicUrl` as tokens, `server.port` as a literal, and `auth.local.users` as `${HORIZON_AUTH_LOCAL_USERS:[]}` unless you write users of your own — so OAP stays reachable, the BFF keeps binding `ui.service.internalPort`, and a Secret-supplied login keeps working. Use it only to pin a field regardless of the environment; otherwise prefer `ui.extraEnv` / `ui.envFromSecret`. [horizon.yaml reference](https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md) | `{}` | +| `ui.extraVolumes` | Extra volumes for the UI pod. Needed by the two Horizon settings that take a filesystem path rather than a value: `auth.tokensFile` / `HORIZON_AUTH_TOKENS_FILE` (API tokens Secret) and `sourceMaps.bootMountDir` / `HORIZON_SOURCEMAPS_DIR` (durable `.map` files, `/app/sourcemaps` in the image) | `[]` | | `ui.extraVolumeMounts` | Extra volume mounts for the BFF container, paired with `ui.extraVolumes` | `[]` | | `oapInit.nodeAffinity` | OAP init job node affinity policy | `{}` | | `oapInit.nodeSelector` | OAP init job labels for master pod assignment | `{}` | diff --git a/docs/storage/choose-a-backend.md b/docs/storage/choose-a-backend.md index 492efb7..3937e2a 100644 --- a/docs/storage/choose-a-backend.md +++ b/docs/storage/choose-a-backend.md @@ -41,13 +41,14 @@ fails the render). On top of that: Anything else you pass to `oap.storageType` is forwarded to `SW_STORAGE` unchanged, but the chart adds **no** connection env vars and **no** init container for it — you would have to supply the -whole connection through `oap.env.*` yourself, which emits literal `value:` entries only. +whole connection yourself through `oap.env` (literal `value:` entries), `oap.extraEnv` (entries may +carry `valueFrom`) or `oap.envFromSecret`. ### Where the connection details come from | | Embedded (`*.enabled=true`) | External (`*.enabled=false`) | |---|---|---| -| Elasticsearch | `{release}-elasticsearch-es-http:9200` (name from `elasticsearch.fullnameOverride` when set; the port is hard-coded `9200`, `elasticsearch.config.port.http` applies to external clusters only); user is `elastic`, password read from the ECK-generated secret `{release}-elasticsearch-es-elastic-user`, key `elastic` | `elasticsearch.config.host` + `elasticsearch.config.port.http`; `elasticsearch.config.user` / `.password` are rendered as **plaintext env values**, and `oap.env` is no better — the OAP templates have no `envFrom` / `secretKeyRef` path, so OAP credentials always land in the Deployment as literals | +| Elasticsearch | `{release}-elasticsearch-es-http:9200` (name from `elasticsearch.fullnameOverride` when set; the port is hard-coded `9200`, `elasticsearch.config.port.http` applies to external clusters only); user is `elastic`, password read from the ECK-generated secret `{release}-elasticsearch-es-elastic-user`, key `elastic` | `elasticsearch.config.host` + `elasticsearch.config.port.http`; `elasticsearch.config.user` / `.password` are rendered as **plaintext env values**; leave both empty and supply `SW_ES_USER` / `SW_ES_PASSWORD` from a Secret instead, via `oap.envFromSecret` or `oap.extraEnv` — see [Storage credentials from a Secret](../operate/oap-configuration.md#storage-credentials-from-a-secret) | | PostgreSQL | host `{release}-postgresql`, port `postgresql.containerPorts.postgresql` (`5432`), database `postgresql.auth.database` (`skywalking`) | `postgresql.config.host`; the port, database, username and password still come from `postgresql.containerPorts.postgresql` and `postgresql.auth.*` — there is no `postgresql.config.port` | | BanyanDB | `{release}-banyandb-grpc:` / `-http:`, ports taken from `banyandb.standalone.*Svc.port` or `banyandb.cluster.liaison.*Svc.port` (defaults `17912` gRPC, `17913` HTTP) | `banyandb.config.grpcAddress` (default `banyandb-grpc:17912`) and `banyandb.config.httpAddress` (default `banyandb-http:17913`) | diff --git a/docs/storage/elasticsearch.md b/docs/storage/elasticsearch.md index bad4ada..3f9136e 100644 --- a/docs/storage/elasticsearch.md +++ b/docs/storage/elasticsearch.md @@ -212,8 +212,11 @@ elasticsearch: `host` and `port.http` feed both `SW_STORAGE_ES_CLUSTER_NODES` and the `wait-for-elasticsearch` init container probe. `user` and `password` are only emitted when non-empty, and they are rendered as -literal env values in the OAP pod spec — treat the values file as a secret. For a TLS-fronted -cluster add `--set oap.env.SW_STORAGE_ES_HTTP_PROTOCOL=https` (OAP defaults to `http`). +literal env values in the OAP pod spec. Leave both empty and put `SW_ES_USER` / `SW_ES_PASSWORD` in a +Secret referenced by `oap.envFromSecret` instead — see +[Storage credentials from a Secret](../operate/oap-configuration.md#storage-credentials-from-a-secret). +For a TLS-fronted cluster add `--set oap.env.SW_STORAGE_ES_HTTP_PROTOCOL=https` (OAP defaults to +`http`). `chart/skywalking/values-my-es.yaml` is the ready-made example. It already carries the three required values, so nothing else has to be passed: diff --git a/docs/storage/postgresql.md b/docs/storage/postgresql.md index 75c96ff..cca1a9b 100644 --- a/docs/storage/postgresql.md +++ b/docs/storage/postgresql.md @@ -148,4 +148,4 @@ helm template skywalking chart/skywalking \ **The wait loop is unbounded.** Unlike the Elasticsearch and BanyanDB init containers, which give up after 60 attempts, `wait-for-postgresql` retries forever. An OAP pod stuck in `Init:0/1` with `Waiting for postgresql...` in the init container log means a wrong host, port, user, or a firewalled database — it will not fail on its own. See [Install and Startup Failures](../troubleshooting/install-and-startup.md). -**Credentials are visible in the pod spec.** `SW_DATA_SOURCE_PASSWORD` is a literal `value:`, and `oap.env` renders string values only (no `valueFrom`), so there is no supported way to source it from a Secret today. Anyone who can read the OAP Pod, Deployment or Job can read the password. +**The computed `SW_DATA_SOURCE_PASSWORD` is a literal `value:`** taken from `postgresql.auth.password`, so anyone who can read the OAP Pod, Deployment or Job can read whatever you put there. The chart writes it unconditionally, and an `env` entry beats `envFrom`, so a key of that name in `oap.envFromSecret` is ignored — override it with `oap.extraEnv` (which renders after the storage block) and leave `postgresql.auth.password` empty. See [Storage credentials from a Secret](../operate/oap-configuration.md#storage-credentials-from-a-secret). diff --git a/docs/troubleshooting/ui-and-login.md b/docs/troubleshooting/ui-and-login.md index 494f1c5..4ac0bc3 100644 --- a/docs/troubleshooting/ui-and-login.md +++ b/docs/troubleshooting/ui-and-login.md @@ -113,7 +113,7 @@ server: ``` The in-cluster address is the default and the env var still wins. Anything **you** write into -`ui.config` is emitted as a plain literal, and a literal makes the matching variable inert — which is +`ui.config`, when you set it, is emitted as a plain literal, and a literal makes the matching variable inert — which is why `ui.config` is empty by default. Check what actually got mounted before anything else: @@ -186,18 +186,15 @@ URI registered with the provider, and if a gateway serves Horizon under a path p (`https://example.com/horizon/`), `publicUrl` must carry that prefix — it is also what the BFF uses to build root-relative redirects back to `/login`. -While you are here, two neighbouring settings — and both belong in `ui.config`, not in `ui.extraEnv`. -Unlike `server.publicUrl`, neither field's schema default reads the environment, so with the chart's -ConfigMap mounted over `/app/horizon.yaml` neither of the two situations above applies, and a -`HORIZON_SESSION_COOKIE_SECURE` or `HORIZON_TRUST_PROXY` variable is inert: +While you are here, two neighbouring settings, both plain environment variables: ```yaml ui: - config: - session: - cookieSecure: true # serving over HTTPS - server: - trustProxy: 1 # hop count — 1 = one proxy in front; an address/CIDR list also works + extraEnv: + - name: HORIZON_SESSION_COOKIE_SECURE + value: "true" # serving over HTTPS + - name: HORIZON_TRUST_PROXY + value: "1" # hop count — 1 = one proxy in front; an address/CIDR list also works ``` `trustProxy` is what makes the login audit record the real client address rather than the ingress. diff --git a/docs/ui/configure.md b/docs/ui/configure.md index 229601d..3d3fef1 100644 --- a/docs/ui/configure.md +++ b/docs/ui/configure.md @@ -1,152 +1,277 @@ # Configure Horizon -How the chart configures the Horizon UI container: what it writes into `horizon.yaml`, why almost everything else should be an environment variable instead, and the two settings — `templates.mode` and `/data` persistence — that most deployments need to touch. - -## The image is configured by environment variable - -The Horizon image ships `/app/horizon.yaml` in which **every field is a `${HORIZON_*:default}` token**. Horizon expands `${...}` over the raw *text* of that file before parsing it as YAML, so the container is meant to run with the shipped file and only the env vars you care about. +Horizon's image ships a complete `/app/horizon.yaml` in which **all 62 fields are +`${HORIZON_*:default}` placeholders**, expanded over the raw text of that file before it is parsed +as YAML. The chart mounts nothing over it. It sets the handful of values it can compute as plain +environment variables and leaves the image's file intact, so every other field stays settable from +the environment. Precedence is: **env var → the file's `:default` → the built-in schema default.** -Two consequences fall out of that, and both are the reason this chart writes as little as it does: - -- A field written as a **plain literal** in the config file makes its `HORIZON_*` env var **silently inert** — the token it would have replaced is no longer there to expand. -- A field the file **omits** falls back to a built-in default, and only a handful of those consult the environment. +## Three mechanisms -So a config file full of literals does not merely restate defaults — it disables most of the image's configuration surface. **Prefer [`ui.extraEnv` and `ui.envFromSecret`](#prefer-env-vars) for everything the chart does not compute.** - -## What the chart writes +| | Value | Reach for it when | +|---|---|---| +| 1. Environment variables | `ui.extraEnv` | Anything not sensitive. A list, so an entry may carry `valueFrom` | +| 2. A Secret's keys as environment variables | `ui.envFromSecret` | Password hashes, LDAP bind passwords, OAP credentials, API keys | +| 3. A `horizon.yaml` through a ConfigMap | `ui.config` | Pinning a field so no environment can change it — opt-in, and it **replaces** the image's file | -`ui.enabled` creates a ConfigMap named `{release}-skywalking-helm-ui` (key `horizon.yaml`), mounted over `/app/horizon.yaml` as a read-only `subPath`. The chart writes **only the values the image cannot know**, and writes them as tokens so env still wins: +**Prefer 1 and 2.** They are how the image is meant to be configured, and they leave all 62 fields +reachable. `ui.config` is `{}` by default: no ConfigMap is created and nothing is mounted until you +set it. -| `horizon.yaml` field | Source in `values.yaml` | Written when | -|---|---|---| -| `oap.queryUrl` | in-cluster OAP service + `oap.ports.rest` (`12800`) | always | -| `oap.adminUrl` | in-cluster OAP service + `oap.ports.admin` (`17128`) | always | -| `oap.zipkinUrl` | in-cluster OAP service + `oap.ports.zipkin-query` + `/zipkin` | only when `oap.ports.zipkin-query` is set | -| `server.publicUrl` | first entry of `ui.ingress.hosts`, `https` if `ui.ingress.tls` is non-empty else `http` | only when `ui.ingress.enabled` **and** `ui.ingress.hosts` are set | -| `server.port` | `ui.service.internalPort` (`8081`) | always | +Two rules govern how the two environment mechanisms combine, and both are plain Kubernetes: -With chart defaults, the rendered file is just: +- **`env` beats `envFrom`.** The chart's computed values are `env` entries, so a key of the same + name in the Secret behind `ui.envFromSecret` is ignored. Override a chart-computed value with + `ui.extraEnv` instead. +- **`ui.extraEnv` is appended after the computed entries**, and for a duplicate name the last entry + is the one the container sees. That is what makes such an override work. -```yaml -oap: - adminUrl: ${HORIZON_OAP_ADMIN_URL:http://sw-skywalking-helm-oap:17128} - queryUrl: ${HORIZON_OAP_QUERY_URL:http://sw-skywalking-helm-oap:12800} -server: - port: 8081 -``` +The field list itself is owned upstream: +[horizon.yaml reference](https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md). -Add a Zipkin query port and an ingress and two more lines appear: +## What the chart sets -```yaml -oap: - adminUrl: ${HORIZON_OAP_ADMIN_URL:http://sw-skywalking-helm-oap:17128} - queryUrl: ${HORIZON_OAP_QUERY_URL:http://sw-skywalking-helm-oap:12800} - zipkinUrl: ${HORIZON_OAP_ZIPKIN_URL:http://sw-skywalking-helm-oap:9412/zipkin} -server: - port: 8081 - publicUrl: ${HORIZON_PUBLIC_URL:http://skywalking.example.com} -``` +| Environment variable | Value | Set when | +|---|---|---| +| `HORIZON_SERVER_PORT` | `ui.service.internalPort` (`8081`) | always | +| `HORIZON_OAP_QUERY_URL` | in-cluster OAP service + `oap.ports.rest` (`12800`) | always | +| `HORIZON_OAP_ADMIN_URL` | in-cluster OAP service + `oap.ports.admin` (`17128`) | only when `oap.ports.admin` is set | +| `HORIZON_OAP_ZIPKIN_URL` | in-cluster OAP service + `oap.ports.zipkin-query` + `/zipkin` | only when `oap.ports.zipkin-query` is set | +| `HORIZON_PUBLIC_URL` | first entry of `ui.ingress.hosts`, `https` when a `tls` block covers that host, else `http` | only when `ui.ingress.enabled` **and** `ui.ingress.hosts` are set | -Render it yourself before installing: +With chart defaults that is three variables. Render it yourself before installing: ```shell helm template sw chart/skywalking \ --set oap.image.tag=11.0.0 \ --set oap.storageType=banyandb \ --set ui.image.tag=horizon-1.0.0 \ - -s templates/ui-configmap.yaml + -s templates/ui-deployment.yaml ``` -Notes on the table: - -- `oap.zipkinUrl` is omitted rather than blanked when no Zipkin port is exposed: Horizon's schema requires a valid URL there, and an empty value fails at boot. -- `server.port` is the one **literal**, not a token — the container port comes from `ui.service.internalPort`, so the BFF has to bind that same port. `HORIZON_SERVER_PORT` is therefore inert; change `ui.service.internalPort` instead. The probes follow it automatically — both target the container's named `page` port, so nothing else needs changing. -- `server.host` is not written at all — the image's own `ENV` already sets it to `0.0.0.0`. -- Anything equal to Horizon's own default is deliberately left out, so upstream owns it. +```yaml + env: + - name: HORIZON_SERVER_PORT + value: "8081" + - name: HORIZON_OAP_QUERY_URL + value: "http://sw-skywalking-helm-oap:12800" + - name: HORIZON_OAP_ADMIN_URL + value: "http://sw-skywalking-helm-oap:17128" +``` -## Two kinds of setting, and only one works from a bare env var +Expose the Zipkin query port and a TLS ingress and the other two appear: -Horizon reads the environment in two different ways, and the difference decides how you set a field: +```yaml + - name: HORIZON_OAP_ZIPKIN_URL + value: "http://sw-skywalking-helm-oap:9412/zipkin" + - name: HORIZON_PUBLIC_URL + value: "https://skywalking.example.com" +``` -- **Fields whose built-in default reads the environment.** Roughly eighteen of them, including - `templates.mode`, `server.host`, `server.port`, `server.publicUrl`, and the whole `ai`, `mcp`, - `oauth` and `audit.enabled` set. Omit them from `ui.config` and the matching `HORIZON_*` variable - is honoured on its own. -- **Everything else.** Their defaults are plain literals, so a `HORIZON_*` variable reaches them - **only** through a `${...}` token written into the mounted file. Setting the variable alone does - nothing at all — silently. +Notes on the table: -`session.cookieSecure`, `server.trustProxy`, `auth.local.users` and `oap.auth` are all in the second -group. For those, put the token in `ui.config` and supply the value from the environment: +- `HORIZON_OAP_ZIPKIN_URL` is omitted rather than blanked when no Zipkin port is exposed: Horizon's + schema requires a valid URL there, and an empty value fails at boot. `HORIZON_OAP_ADMIN_URL` is + conditional for a different reason — on OAP 10.x, port `17128` is the AI-pipeline URI-recognition + server, not the admin REST host, so set `oap.ports.admin: null` there and the variable disappears. +- `HORIZON_PUBLIC_URL`'s scheme is derived **per host**, because a `tls` block may cover only some + of them. A `tls` entry with no `hosts` is the controller's default certificate and covers this one. + An entry in `ui.ingress.hosts` may carry a path (`skywalking.example.com/ui`); the whole entry + becomes the public URL, while only the hostname before the first `/` is matched against + `tls[].hosts`. SSO callbacks are built from that URL, so the path has to be the one the ingress + actually serves the UI on. +- **`server.port` follows `ui.service.internalPort`.** The container port comes from the same value, + and both probes target the container's named `page` port, so they follow it too. Do not override + `HORIZON_SERVER_PORT` through `ui.extraEnv` — the BFF would bind a port nothing routes to. Change + `ui.service.internalPort`. +- `server.host` is not set by the chart; the image's own `ENV` already carries + `HORIZON_SERVER_HOST=0.0.0.0`. + +## Environment variables (`ui.extraEnv`) + +One variable per field, appended to the container's `env`: ```yaml ui: - envFromSecret: horizon-secrets # supplies HORIZON_OAP_AUTH extraEnv: + - name: HORIZON_TEMPLATES_MODE + value: readonly - name: HORIZON_SESSION_COOKIE_SECURE value: "true" - name: HORIZON_TRUST_PROXY value: "1" - config: - session: - cookieSecure: ${HORIZON_SESSION_COOKIE_SECURE:false} - server: - trustProxy: ${HORIZON_TRUST_PROXY:false} - oap: - auth: ${HORIZON_OAP_AUTH:null} + - name: HORIZON_OAP_QUERY_URL # overrides the chart's computed value + value: http://oap.observability.svc:12800 ``` -That keeps secrets out of the ConfigMap — the token is what is rendered, the value arrives at -container start. It is the same pattern [Set Up Logins](logins.md) uses for `auth.local.users`. +It is a list, so an entry may take its value from a single Secret key or the downward API rather +than from the values file: -For the first group no token is needed: +```yaml +ui: + extraEnv: + - name: HORIZON_OAP_AUTH + valueFrom: + secretKeyRef: + name: horizon-oap + key: auth.json +``` + +### Structured blocks take JSON in one variable + +Fields that are objects or lists rather than scalars take their **whole block** as JSON in a single +variable. A `:null` default in the shipped file means "fall through to the built-in default". + +| Variable | Field | +|---|---| +| `HORIZON_AUTH_LOCAL_USERS` | `auth.local.users` | +| `HORIZON_AUTH_LDAP` | `auth.ldap` | +| `HORIZON_AUTH_BREAK_GLASS` | `auth.breakGlass` — honored only when `backend=ldap` and the LDAP probe is failing | +| `HORIZON_AUTH_SSO` | `auth.sso` | +| `HORIZON_RBAC_ROLES` | `rbac.roles` | +| `HORIZON_RBAC_LANDING_BY_ROLE` | `rbac.landingByRole` — post-login landing route per role | +| `HORIZON_OAP_AUTH` | `oap.auth` — basic-auth for the BFF's outbound calls to OAP | +| `HORIZON_OAP_MQE` | `oap.mqe` — host/port override, defaults to the query host | +| `HORIZON_PERFORMANCE` | `performance` — BFF→OAP fan-out and caps | +| `HORIZON_LAYERS_EXCLUDED` | `layers.excluded` — an array of `{key, reason}`; `[]` surfaces every reported layer | +| `HORIZON_AUDIT_POSTGRES` | `audit.postgres` — connection settings for the sign-in audit; a secret, so keep it in `ui.envFromSecret` | +| `HORIZON_AI_STARTERS` | `ai.starters` | +| `HORIZON_OAUTH_CLIENT_METADATA_HOSTS` | `oauth.clientMetadataHosts` | + +Each variable carries the **whole** block, replacing it rather than merging into it — so restate the +parts of the default you want to keep. `layers.excluded` defaults to `FAAS` and `VIRTUAL_GATEWAY`, +which is why hiding one more means naming all three: ```yaml ui: extraEnv: - - name: HORIZON_TEMPLATES_MODE # works with ui.config left empty - value: readonly + - name: HORIZON_LAYERS_EXCLUDED + value: '[{"key":"FAAS"},{"key":"VIRTUAL_GATEWAY"},{"key":"SO11Y_OAP","reason":"Internal."}]' + - name: HORIZON_PERFORMANCE + value: '{"bulk":{"dashboard":{"bulkSize":8}}}' +``` + +The value is injected into the file's text and parsed there, so it must be a **single flow value** — +one line, or continuation lines indented under the first. A newline at column zero ends the value +and breaks the parse. + +## Secrets (`ui.envFromSecret`) + +`ui.envFromSecret` names a pre-created Secret and becomes an `envFrom.secretRef` on the UI +container, so **every** key of it arrives as an environment variable. One Secret carries everything +sensitive (`$HASH` below is an Argon2id password hash — [Set Up Logins](logins.md) mints one): + +```shell +kubectl create secret generic horizon-secrets \ + -n "${SKYWALKING_RELEASE_NAMESPACE}" \ + --from-literal=HORIZON_AUTH_LOCAL_USERS='[{"username":"admin","passwordHash":"'"$HASH"'","roles":["admin"]}]' \ + --from-literal=HORIZON_OAP_AUTH='{"username":"skywalking","password":"changeme"}' ``` -The full field list is owned upstream: [horizon.yaml reference](https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md). +```yaml +ui: + envFromSecret: horizon-secrets +``` -## When to use `ui.config` anyway +That is the whole configuration. Nothing has to be written into `ui.config` for those variables to +be read — the tokens they fill are already in the image's file. -`ui.config` is deep-merged **over** the chart-computed values, so it can pin any field regardless of env — which is exactly what you want for a value that must not be overridable, and exactly what you do not want everywhere else. It is `{}` by default. +## `ui.config`, and what it costs -Two rules: +Setting `ui.config` creates a ConfigMap named `{release}-skywalking-helm-ui` (key `horizon.yaml`) +and mounts it read-only as a `subPath` over `/app/horizon.yaml`. That **replaces** the image's file +rather than merging with it, which has two consequences: -- **A literal you write here kills that field's `HORIZON_*` var.** Writing `templates.mode: readonly` into `ui.config` means `HORIZON_TEMPLATES_MODE` no longer does anything for this deployment. That is fine when pinning is the intent. -- **`${VAR}` tokens you write here still expand.** This is how secrets stay out of the ConfigMap — write the shape in `ui.config`, keep the secret in a Secret referenced by `ui.envFromSecret` / `ui.extraEnv`: +- A field you do not write there falls back to Horizon's **built-in** default — not to the + `:default` in the image's file, which is no longer present. +- That field's `HORIZON_*` variable stops working, because the token it would have expanded is gone. + Silently. + +The chart merges its computed values back in, so OAP stays reachable either way. The OAP URLs and +`server.publicUrl` go in as `${VAR:default}` tokens and stay env-overridable. `server.port` does +not — it is written as a literal, because the container port and both probes come from +`ui.service.internalPort` and the BFF has to bind the same one, so `HORIZON_SERVER_PORT` is the one +computed value `ui.config` really does make inert: + +```shell +helm template sw chart/skywalking \ + --set oap.image.tag=11.0.0 \ + --set oap.storageType=banyandb \ + --set ui.image.tag=horizon-1.0.0 \ + --set ui.config.templates.mode=readonly \ + -s templates/ui-configmap.yaml +``` + +```yaml +data: + horizon.yaml: | + oap: + adminUrl: ${HORIZON_OAP_ADMIN_URL:http://sw-skywalking-helm-oap:17128} + queryUrl: ${HORIZON_OAP_QUERY_URL:http://sw-skywalking-helm-oap:12800} + server: + port: 8081 + templates: + mode: readonly +``` + +`templates.mode` is now pinned: `HORIZON_TEMPLATES_MODE` does nothing for this deployment, which is +exactly the point of writing it there. The same goes for any other field you put in `ui.config`. + +Fields you leave out fall back to Horizon's built-in defaults, and their `HORIZON_*` variables have +no token to fill — with one exception the chart handles for you. `auth.local.users` keeps its +`${HORIZON_AUTH_LOCAL_USERS:[]}` token unless you write users yourself, so a deployment taking its +users from a Secret does not lose them the moment `ui.config` is set. The OAP URLs are preserved the +same way. ```yaml ui: - envFromSecret: horizon-admin + envFromSecret: horizon-secrets config: + templates: + mode: readonly auth: local: - users: - - username: admin - passwordHash: "${HORIZON_ADMIN_HASH}" - roles: [admin] + users: ${HORIZON_AUTH_LOCAL_USERS:[]} ``` -Overriding a chart-computed URL — for example to point Horizon at an OAP outside the release — works the same way, but the env var is the lighter option: +Every field you want to stay env-settable needs its token restated like that, which is the whole +reason to prefer mechanisms 1 and 2 and to keep `ui.config` down to what must be pinned. -```yaml -ui: - extraEnv: - - name: HORIZON_OAP_QUERY_URL - value: http://oap.observability.svc:12800 -``` +## Horizon is not OAP + +Both components offer the same three mechanisms, but the balance between them is different, because +the two images read configuration differently: + +| | Horizon (`ui.*`) | OAP (`oap.*`) | +|---|---|---| +| Environment variables | `ui.extraEnv` (list) | `oap.env` (map, no `valueFrom`) and `oap.extraEnv` (list) | +| From a Secret | `ui.envFromSecret` | `oap.envFromSecret` — applied to the OAP Deployment **and** the init Job | +| Files | `ui.config` — a last resort | `oap.config` — the only way to supply some things | + +Horizon's entire configuration is one env-tokenized file the image already ships, so a file mount +buys nothing but the ability to pin. **OAP reads real files**: `log4j2.xml`, the OAL and MAL rule +sets, `metadata-service-mapping.yaml`. Its `application.yml` resolves settings as `${SW_*:default}` +the same way, so environment variables cover settings — but nothing except a mounted file can supply +a rule set, so `oap.config` stays a first-class mechanism there. + +Use `oap.extraEnv` where `oap.env` cannot reach: it is a list, so entries may carry `valueFrom` for +a single credential out of a Secret or a value from the downward API. `oap.envFromSecret` covers the +init Job as well, which needs the same storage credentials as the Deployment. See +[Configure OAP](../operate/oap-configuration.md). ## `templates.mode` -`live` (Horizon's default) reads and writes dashboard templates through OAP 11's `/ui-management/templates*` admin REST API and persists them in OAP storage. In that mode OAP is the only source: if the template store cannot be read, layer pages are blocked rather than falling back to the bundled templates. +`live` (Horizon's default) reads and writes dashboard templates through OAP 11's +`/ui-management/templates*` admin REST API and persists them in OAP storage. In that mode OAP is the +only source: if the template store cannot be read, layer pages are blocked rather than falling back +to the bundled templates. -**Against OAP 10.x you must set `readonly`.** OAP 10 does not serve that REST surface, so `live` blocks every layer-driven page. `readonly` renders the templates bundled in the image and makes the configuration surface display-only; dashboards, traces, logs, topology, alarms and profiling all work. +**Against OAP 10.x you must set `readonly`.** OAP 10 does not serve that REST surface, so `live` +blocks every layer-driven page. `readonly` renders the templates bundled in the image and makes the +configuration surface display-only; dashboards, traces, logs, topology, alarms and profiling all +work. ```yaml ui: @@ -155,20 +280,15 @@ ui: value: readonly ``` -or, pinned in the file: - -```yaml -ui: - config: - templates: - mode: readonly -``` - Changing the mode requires a BFF restart, not just a config reload. ## Persistence (`/data`) -The image declares `/data` as its state volume and routes the BFF's OAP wire debug log there (`HORIZON_WIRE_LOG_FILE=/data/horizon-wire.jsonl`, written only when `debugLog.enabled`); anything else you point at a path under `/data` lands there too. The chart always mounts a volume at `/data` — an `emptyDir` by default, so **that state is gone whenever the pod is replaced** (upgrade, reschedule, delete). Turn on a PVC for anything you intend to keep: +The image declares `/data` as its state volume and routes the BFF's OAP wire debug log there +(`HORIZON_WIRE_LOG_FILE=/data/horizon-wire.jsonl`, written only when `debugLog.enabled`); anything +else you point at a path under `/data` lands there too. The chart always mounts a volume at `/data` +— an `emptyDir` by default, so **that state is gone whenever the pod is replaced** (upgrade, +reschedule, delete). Turn on a PVC for anything you intend to keep: ```yaml ui: @@ -188,16 +308,25 @@ ui: | `ui.persistence.size` | `1Gi` | | | `ui.persistence.annotations` | `{}` | applied to the chart-managed PVC | -The image runs as the non-root `horizon` user, so any volume mounted into the container must be group-writable by it. `ui.securityContext.fsGroup` defaults to `101` for exactly this reason — keep it (or set an equivalent) when you override `ui.securityContext`, and apply the same thought to anything you add through `ui.extraVolumeMounts`. +The image runs as the non-root `horizon` user, so any volume mounted into the container must be +group-writable by it. `ui.securityContext.fsGroup` defaults to `101` for exactly this reason — keep +it (or set an equivalent) when you override `ui.securityContext`, and apply the same thought to +anything you add through `ui.extraVolumeMounts`. -Keep `ui.replicas: 1`. The BFF holds its session table in memory, the Deployment uses the `Recreate` strategy for that reason, and a `ReadWriteOnce` PVC cannot be mounted by pods on two different nodes anyway. +Keep `ui.replicas: 1`. The BFF holds its session table in memory, the Deployment uses the `Recreate` +strategy for that reason, and a `ReadWriteOnce` PVC cannot be mounted by pods on two different nodes +anyway. ## Settings that take a path, not a value -Two Horizon 1.0.0 settings are configured by filesystem path, so they need `ui.extraVolumes` / `ui.extraVolumeMounts`: +Two Horizon 1.0.0 settings name a file or directory rather than carrying a value, so they need +`ui.extraVolumes` / `ui.extraVolumeMounts` — but the path itself is still an ordinary variable: -- `auth.tokensFile` — API tokens for callers with no browser (scripts, CI, MCP clients). Mount a Secret. -- `sourceMaps.bootMountDir` — durable `.map` files for the Browser Errors tab. The image sets this to `/app/sourcemaps`; without a volume there, runtime uploads live in BFF memory only and are lost on pod restart. +- `auth.tokensFile` (`HORIZON_AUTH_TOKENS_FILE`) — API tokens for callers with no browser (scripts, + CI, MCP clients). Empty by default; mount a Secret and point the variable at it. +- `sourceMaps.bootMountDir` (`HORIZON_SOURCEMAPS_DIR`) — durable `.map` files for the Browser Errors + tab. The image already sets it to `/app/sourcemaps`, so only the volume is missing; without one, + runtime uploads live in BFF memory and are lost on pod restart. ```yaml ui: @@ -209,26 +338,49 @@ ui: - name: horizon-tokens mountPath: /app/tokens readOnly: true - config: - auth: - tokensFile: /app/tokens/tokens.json + extraEnv: + - name: HORIZON_AUTH_TOKENS_FILE + value: /app/tokens/tokens.json ``` ## Applying a change -The UI Deployment carries a `checksum/config` annotation over the rendered ConfigMap, because a `subPath` ConfigMap mount does **not** update inside a running container. Any change to `ui.config` — or to a value that feeds a computed field — therefore rolls the pod on the next `helm upgrade`. Changing `ui.extraEnv` rolls it too, and env is read once at process start — but editing the *contents* of the Secret behind `ui.envFromSecret` changes no pod field, so nothing rolls: `kubectl rollout restart` the Deployment yourself. +| What you changed | Does the pod roll? | +|---|---| +| `ui.extraEnv`, or a value feeding a computed variable | Yes — the pod spec changed, so `helm upgrade` rolls it | +| The **contents** of the Secret behind `ui.envFromSecret` | No — no pod field changed | +| `ui.config` | Yes — the Deployment carries a `checksum/config` annotation over the rendered ConfigMap | -Verify what actually landed in the container: +Environment is read once at process start, so a Secret edit needs a restart you ask for yourself: ```shell +kubectl rollout restart -n "${SKYWALKING_RELEASE_NAMESPACE}" \ + deploy/${SKYWALKING_RELEASE_NAME}-skywalking-helm-ui +``` + +The `checksum/config` annotation exists only while `ui.config` is set, and it is there because a +`subPath` ConfigMap mount never updates inside a running container. + +Verify what actually reached the container: + +```shell +# what the chart and your values set +kubectl exec -n "${SKYWALKING_RELEASE_NAMESPACE}" \ + deploy/${SKYWALKING_RELEASE_NAME}-skywalking-helm-ui -- env | grep '^HORIZON_' | sort + +# the file being expanded: the image's 62 tokens, unless ui.config is set kubectl exec -n "${SKYWALKING_RELEASE_NAMESPACE}" \ deploy/${SKYWALKING_RELEASE_NAME}-skywalking-helm-ui -- cat /app/horizon.yaml ``` +For whether authentication took, `/api/auth/health` reports it without a login — see +[Set Up Logins](logins.md). + ## Next - [Horizon UI in This Chart](horizon-ui.md) — what the image is, and why booster UI is gone - [Set Up Logins](logins.md) — no login is configured by default -- [UI Service and Ingress](../expose/ui-service-and-ingress.md) — where `server.publicUrl` comes from +- [Configure OAP](../operate/oap-configuration.md) — the same three mechanisms on the backend +- [UI Service and Ingress](../expose/ui-service-and-ingress.md) — where `HORIZON_PUBLIC_URL` comes from - [UI and Login Problems](../troubleshooting/ui-and-login.md) - [skywalking Chart values](../reference/skywalking-chart-values.md) — every `ui.*` value diff --git a/docs/ui/logins.md b/docs/ui/logins.md index 0d14f55..0369c59 100644 --- a/docs/ui/logins.md +++ b/docs/ui/logins.md @@ -4,6 +4,10 @@ Horizon UI ships with **no accounts at all**, and a chart install that skips thi Deployment that reports healthy while nobody can sign in. This page shows how to confirm that state, how to seed a throwaway demo login, and how to configure real users from a Kubernetes Secret. +Both paths set one variable, `HORIZON_AUTH_LOCAL_USERS`, whose value is a **JSON array of users**. +The image's `/app/horizon.yaml` reads it (`users: ${HORIZON_AUTH_LOCAL_USERS:[]}`), and the chart +mounts nothing over that file by default — so an environment variable is all it takes. + ## There is no default login, and the pod still goes Ready Horizon has no built-in `admin/admin` fallback, and the chart configures no users of its own. The BFF @@ -38,8 +42,8 @@ kubectl logs -n "${SKYWALKING_RELEASE_NAMESPACE}" \ ## Demo logins (publicly-known credentials) For a first run on a trusted network, paste this into a values file. It seeds `admin/admin` and -`skywalking/skywalking` using `argon2id` hashes of those exact plaintexts — the same pair the chart's -own e2e tests use. +`skywalking/skywalking` using `argon2id` hashes of those exact plaintexts — byte-for-byte what +`test/e2e/values.yaml` feeds the chart's own e2e. > **These hashes are published in this repository.** Anyone can read them and derive the passwords. > Use them only on a network you control, and replace them before the UI is reachable by anyone else. @@ -47,17 +51,11 @@ own e2e tests use. ```yaml # demo-values.yaml ui: - config: - auth: - backend: local # the default; shown for clarity - local: - users: - - username: admin # password: admin - passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$eemqy1r72oSXR58y8VpRqw$Bn/dULrmJTHEi3263KfgWDEwQmUsqNLi3xwyv/DekHM" - roles: [admin] - - username: skywalking # password: skywalking - passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$Zqj8HhQDqm8d5c2MipHYZw$BsaCnu4bdd4uadIldx3wwYLsdo47Thxb7Lv1MXpWG2Q" - roles: [viewer, maintainer] + extraEnv: + - name: HORIZON_AUTH_LOCAL_USERS + value: >- + [{"username":"admin","passwordHash":"$argon2id$v=19$m=65536,t=3,p=4$eemqy1r72oSXR58y8VpRqw$Bn/dULrmJTHEi3263KfgWDEwQmUsqNLi3xwyv/DekHM","roles":["admin"]}, + {"username":"skywalking","passwordHash":"$argon2id$v=19$m=65536,t=3,p=4$Zqj8HhQDqm8d5c2MipHYZw$BsaCnu4bdd4uadIldx3wwYLsdo47Thxb7Lv1MXpWG2Q","roles":["viewer","maintainer"]}] ``` ```shell @@ -82,10 +80,18 @@ kubectl port-forward -n "${SKYWALKING_RELEASE_NAMESPACE}" \ open http://127.0.0.1:8080 ``` -Pass the hashes through a values **file**, not `--set`: a hash is full of `,` and `=`, which `--set` -reads as its own separators (and of `$`, which the shell would expand first). +Two rules for the value, both about how it is carried rather than what it means: + +- Pass it through a values **file**, not `--set`: a hash is full of `,` and `=`, which `--set` reads + as its own separators (and of `$`, which the shell would expand first). +- The value must reach the container as **one line**. Horizon expands the variable into the text of + `horizon.yaml` and then parses the file, so a newline inside the value lands mid-sequence at column + 0 and the parse fails — the BFF exits at boot and the pod crash-loops. To wrap it for readability + use a folded block (`>-`) with every continuation line at the **same** indentation as the first, as + above: YAML folds those into single spaces. Indenting a continuation line deeper, or using `|-`, + keeps the newline and breaks the pod. -## Production: hashes from a Secret +## Production: users from a Secret Generate your own hash first. The CLI lives in the Horizon UI repository and reads the password from `argv` or stdin: @@ -99,22 +105,7 @@ HASH=$(pnpm --filter bff cli:hash 'your-strong-password' | tail -1) Passwords longer than 64 characters are refused — the login route rejects them too, so a hash of one could never be signed in with. -From there, pick one of two shapes. Both put the hash in a Secret and reference it with -`ui.envFromSecret`, which the chart turns into an `envFrom.secretRef` on the UI container. - -### Why a token in `ui.config` is required either way - -The chart mounts its ConfigMap **over** the image's `/app/horizon.yaml`, and that rendered file -contains only `oap.*` and `server.*` — no `auth:` block. Horizon expands `${VAR}` over the raw text of -whatever file is at that path, so a `HORIZON_*` variable is read only if a matching token is present -in the text. `auth.local.users` has a plain `[]` schema default and is **not** env-backed. - -**Setting `HORIZON_AUTH_LOCAL_USERS` through `ui.envFromSecret` alone therefore does nothing** — the -token it would fill is not in the file the chart mounted. You must write the token into `ui.config`. - -### Option A — one JSON array for all users - -Best when users are managed as a unit and you would rather not restate them in the values file. +Put the same JSON in a Secret, under the key `HORIZON_AUTH_LOCAL_USERS`: ```shell kubectl create secret generic horizon-users \ @@ -122,58 +113,16 @@ kubectl create secret generic horizon-users \ --from-literal=HORIZON_AUTH_LOCAL_USERS='[{"username":"admin","passwordHash":"'"$HASH"'","roles":["admin"]}]' ``` -```yaml -# my-values.yaml -ui: - envFromSecret: horizon-users - config: - auth: - local: - users: "${HORIZON_AUTH_LOCAL_USERS:[]}" -``` - -The JSON must be a **single line** — it is substituted into YAML text, where a newline would end the -value. If the Secret key is missing or empty the token falls back to `[]`, which is the silent -lockout again, so check `/api/auth/health` after rolling out. - -### Option B — a `${VAR}` per hash - -Best when the user list is stable and belongs in version control, with only the secrets held out. - -```shell -kubectl create secret generic horizon-admin \ - -n "${SKYWALKING_RELEASE_NAMESPACE}" \ - --from-literal=HORIZON_ADMIN_HASH="$HASH" -``` +Point the chart at it. `ui.envFromSecret` becomes an `envFrom.secretRef` on the UI container, so +**every** key of that Secret arrives as an environment variable — one Secret can carry the users, +`HORIZON_OAP_AUTH`, and anything else sensitive: ```yaml # my-values.yaml ui: - envFromSecret: horizon-admin - config: - auth: - local: - users: - - username: admin - passwordHash: "${HORIZON_ADMIN_HASH}" - roles: [admin] -``` - -Use `ui.extraEnv` instead of `ui.envFromSecret` when you want to pick individual keys out of an -existing Secret: - -```yaml -ui: - extraEnv: - - name: HORIZON_ADMIN_HASH - valueFrom: - secretKeyRef: - name: horizon-admin - key: passwordHash + envFromSecret: horizon-users ``` -### Install with it - ```shell helm install "${SKYWALKING_RELEASE_NAME}" \ oci://registry-1.docker.io/apache/skywalking-helm \ @@ -188,15 +137,41 @@ helm install "${SKYWALKING_RELEASE_NAME}" \ -f my-values.yaml ``` -A `helm upgrade` that changes `ui.config` rolls the UI pod on its own: the Deployment carries a -`checksum/config` annotation over the rendered ConfigMap. Changing only the **Secret** does not — env -vars are read once at container start, so restart the Deployment yourself: +Use `ui.extraEnv` instead when the users live under a different key of a Secret you already have, +or when you want only that one key out of it: + +```yaml +ui: + extraEnv: + - name: HORIZON_AUTH_LOCAL_USERS + valueFrom: + secretKeyRef: + name: horizon-users + key: users.json +``` + +Check `/api/auth/health` after the rollout: an empty or missing value falls back to `[]`, which is +the silent lockout again. `configured: true` is the confirmation. + +### Rolling the pod after a change + +Editing `ui.extraEnv` changes a pod field, so `helm upgrade` rolls the UI on its own. Editing the +**contents** of the Secret behind `ui.envFromSecret` changes no pod field and rolls nothing — and env +is read once at container start, so restart it yourself: ```shell kubectl rollout restart -n "${SKYWALKING_RELEASE_NAMESPACE}" \ deploy/${SKYWALKING_RELEASE_NAME}-skywalking-helm-ui ``` +### If you also set `ui.config` + +`ui.config` is empty by default and nothing is mounted. Setting it replaces the image's +`/app/horizon.yaml` with a rendered one — but the chart keeps the +`${HORIZON_AUTH_LOCAL_USERS:[]}` token in it unless you write users of your own, so the Secret above +keeps working either way. Write `auth.local.users` in `ui.config` only if you want to pin users +regardless of the environment. + ## Roles `roles` on a user is a list of role names from `rbac.roles`. Horizon ships four: @@ -208,18 +183,25 @@ kubectl rollout restart -n "${SKYWALKING_RELEASE_NAMESPACE}" \ | `operator` | Maintainer, plus writes — dashboard and overview templates, DSL rules, live debugging, profiling tasks, source maps. Alarm rules stay read-only for every role | | `admin` | `*` | -A user with an empty `roles` list can sign in and see nothing. Define your own names by setting -`ui.config.rbac.roles`; see the [horizon.yaml reference](https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md). +A user with an empty `roles` list can sign in and see nothing. Define your own names with +`HORIZON_RBAC_ROLES`, whose value is the whole `rbac.roles` block as JSON; see the +[horizon.yaml reference](https://github.com/apache/skywalking-horizon-ui/blob/main/docs/setup/horizon-yaml.md). ## Beyond local users -LDAP, SSO (OIDC/OAuth2), break-glass accounts and API tokens are all configured under `auth` in the -same `ui.config` block, and follow the same rule: write the field there, keep the secret in a Secret -and reference it with a `${VAR}` token. +The other auth backends work the same way — one variable, one JSON value, from `ui.extraEnv` or a +Secret via `ui.envFromSecret`: + +| what | variable | value | +|---|---|---| +| Pick the backend | `HORIZON_AUTH_BACKEND` | `local` (default) or `ldap` | +| LDAP directory | `HORIZON_AUTH_LDAP` | `{"url":"ldaps://ldap.corp:636","userBaseDn":"...","groupMappings":[...]}` | +| SSO (OIDC/OAuth2) | `HORIZON_AUTH_SSO` | `{"providers":[...],"roles":{...}}`; additive to the backend, not a replacement | +| Break-glass account | `HORIZON_AUTH_BREAK_GLASS` | JSON; honoured only with `backend: ldap`, and only while the LDAP probe fails | -`auth.tokensFile` — API tokens for callers with no browser (scripts, CI, MCP clients) — takes a -**path**, not a value, so it also needs `ui.extraVolumes` / `ui.extraVolumeMounts`. See -[Configure Horizon](configure.md). +`auth.tokensFile` — API tokens for callers with no browser (scripts, CI, MCP clients) — is the +exception: `HORIZON_AUTH_TOKENS_FILE` takes a **path**, not a value, so the tokens themselves need +`ui.extraVolumes` / `ui.extraVolumeMounts`. See [Configure Horizon](configure.md). - [Horizon UI in This Chart](horizon-ui.md) — what the BFF is and how it talks to OAP - [UI and Login Problems](../troubleshooting/ui-and-login.md) — symptoms and fixes diff --git a/test/e2e/e2e-config-override.yaml b/test/e2e/e2e-config-override.yaml new file mode 100644 index 0000000..1981da1 --- /dev/null +++ b/test/e2e/e2e-config-override.yaml @@ -0,0 +1,104 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Covers the third configuration mechanism for both components: a file rendered +# into a ConfigMap and mounted over the image's own. +# +# The other five cells all take the default path -- Horizon configured purely by +# environment variable, with no ConfigMap created at all -- so without this the +# opt-in mount would ship untested. It is also the riskier path: mounting over +# Horizon's /app/horizon.yaml REPLACES it, and the chart has to merge its +# computed values back in as tokens or the UI loses OAP entirely. +# +# Deliberately cheap: no istio, no bookinfo, no traffic generator. What is under +# test is whether the overrides are read, which needs neither. + +setup: + env: kind + file: kind28.yaml + init-system-environment: env + kind: + expose-ports: + - namespace: default + resource: service/skywalking-ui + port: 80 + steps: + - name: install yq + command: bash test/e2e/setup-e2e-shell/install.sh yq + - name: install kubectl + command: bash test/e2e/setup-e2e-shell/install.sh kubectl + - name: Install helm + command: bash test/e2e/setup-e2e-shell/install.sh helm + - name: Update chart dependencies + command: helm dep up chart/skywalking + - name: Install SkyWalking with both overrides + command: | + helm install skywalking chart/skywalking \ + --set fullnameOverride=skywalking \ + --set oap.replicas=1 \ + --set ui.image.repository=$UI_REPO \ + --set ui.image.tag=$UI_TAG \ + --set oap.image.repository=$OAP_REPO \ + --set oap.image.tag=$OAP_TAG \ + --set oap.storageType=banyandb \ + --set elasticsearch.enabled=false \ + --set banyandb.enabled=true \ + --set banyandb.image.repository=$BANYANDB_REPO \ + --set banyandb.image.tag=$BANYANDB_TAG \ + --set banyandb.standalone.enabled=true \ + --set banyandb.cluster.enabled=false \ + --set 'ui.config.session.ttlMinutes=17' \ + --set 'oap.config.log4j2\.xml=' \ + -f test/e2e/values.yaml + wait: + - namespace: default + resource: deployments/skywalking-oap + for: condition=available + - namespace: default + resource: deployments/skywalking-ui + for: condition=available + timeout: 20m + +verify: + retry: + count: 20 + interval: 10s + cases: + # The UI ConfigMap exists only because ui.config was set, and is mounted over + # the image's file. + - query: | + kubectl -n default get deployment skywalking-ui -o json | yq -p json -o yaml '{"mounted": ([.spec.template.spec.containers[0].volumeMounts[] | select(.mountPath == "/app/horizon.yaml")] | length > 0)}' + expected: expected/override-ui-mounted.yml + + # Login through a mounted file proves two things at once. Horizon parsed the + # file -- a malformed one aborts the BFF at boot, uncaught -- and the chart + # merged the users token back in, without which the mount would replace the + # image's file, leave HORIZON_AUTH_LOCAL_USERS with nothing to expand into, + # and produce a Ready pod nobody can log into. + - query: | + bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get /api/auth/me | yq -p json -o yaml '{"username": .username}' + expected: expected/override-login.yml + + # And the merged tokens kept OAP reachable -- the failure mode a naive mount + # would produce is queryUrl reverting to 127.0.0.1. + - query: | + bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get /api/oap/info | yq -p json -o yaml '{"reachable": .reachable}' + expected: expected/override-oap-reachable.yml + + # OAP's own override: the file landed at /skywalking/config and OAP still + # started, which a malformed log4j2.xml would prevent. + - query: | + kubectl -n default get deployment skywalking-oap -o json | yq -p json -o yaml '{"mounted": ([.spec.template.spec.containers[0].volumeMounts[] | select(.mountPath == "/skywalking/config/log4j2.xml")] | length > 0)}' + expected: expected/override-oap-mounted.yml diff --git a/test/e2e/e2e-oap10-banyandb.yaml b/test/e2e/e2e-oap10-banyandb.yaml deleted file mode 100644 index 0508ddf..0000000 --- a/test/e2e/e2e-oap10-banyandb.yaml +++ /dev/null @@ -1,143 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This file is used to show how to write configuration files and can be used to test. - -setup: - env: kind - file: kind28.yaml - init-system-environment: env - kind: - expose-ports: - - namespace: istio-system - resource: service/skywalking-ui - port: 80 - steps: - - name: install yq - command: bash test/e2e/setup-e2e-shell/install.sh yq - - name: install kubectl - command: bash test/e2e/setup-e2e-shell/install.sh kubectl - - name: install istio - command: | - bash test/e2e/setup-e2e-shell/install.sh istioctl - istioctl install -y --set profile=demo \ - --set meshConfig.defaultConfig.envoyAccessLogService.address=skywalking-satellite.istio-system:11800 \ - --set meshConfig.enableEnvoyAccessLogService=true - kubectl label namespace default istio-injection=enabled - - name: Install helm - command: bash test/e2e/setup-e2e-shell/install.sh helm - - name: Update chart dependencies - command: helm dep up chart/skywalking - - name: Install SkyWalking - command: | - helm -n istio-system install skywalking chart/skywalking \ - --set fullnameOverride=skywalking \ - --set oap.env.SW_ENVOY_METRIC_ALS_HTTP_ANALYSIS=k8s-mesh \ - --set oap.env.SW_ENVOY_METRIC_ALS_TCP_ANALYSIS=k8s-mesh \ - --set oap.env.K8S_SERVICE_NAME_RULE='e2e::${service.metadata.name}' \ - --set oap.replicas=1 \ - --set ui.image.repository=$UI_REPO \ - --set ui.image.tag=$UI_TAG \ - --set oap.image.repository=$OAP_10_REPO \ - --set oap.image.tag=$OAP_10_TAG \ - --set oap.storageType=banyandb \ - --set elasticsearch.enabled=false \ - --set banyandb.enabled=true \ - --set banyandb.image.repository=$BANYANDB_0_10_REPO \ - --set banyandb.image.tag=$BANYANDB_0_10_TAG \ - --set banyandb.standalone.enabled=true \ - --set banyandb.cluster.enabled=false \ - --set banyandb.auth.enabled=true \ - --set oap.ports.admin=null \ - --set ui.config.templates.mode=readonly \ - --set satellite.enabled=true \ - --set satellite.image.repository=$SATELLITE_REPO \ - --set satellite.image.tag=$SATELLITE_TAG \ - -f test/e2e/values.yaml - wait: - - namespace: istio-system - resource: deployments/skywalking-oap - for: condition=available - - namespace: istio-system - resource: deployments/skywalking-satellite - for: condition=available - - namespace: istio-system - resource: deployments/skywalking-ui - for: condition=available - - name: Deploy demo services - command: | - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/platform/kube/bookinfo.yaml - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/networking/bookinfo-gateway.yaml - # Enable TCP services - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/platform/kube/bookinfo-ratings-v2.yaml - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/platform/kube/bookinfo-db.yaml - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/networking/destination-rule-all.yaml - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/networking/virtual-service-ratings-db.yaml - wait: - - namespace: default - resource: pod - for: condition=Ready - - name: Generate traffic - path: traffic-gen.yaml - wait: - - namespace: default - resource: pod - for: condition=Ready - timeout: 25m - -verify: - retry: - count: 30 - interval: 10s - cases: - # Every assertion below goes through Horizon's BFF: horizon -> oap -> storage. - # Nothing talks to OAP's GraphQL directly -- that would test OAP, not the chart. - - # The operator can log in, and lands with the role the values file granted. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get /api/auth/me | yq -p json -o yaml '{"username": .username, "roles": .roles}' - expected: expected/horizon-me.yml - - # Cluster Status -> Query pane. Proves oap.queryUrl reaches the in-cluster OAP. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get /api/oap/info | yq -p json -o yaml '{"reachable": .reachable, "version": .version}' - expected: expected/horizon-oap-info.yml - - # Inverted: the admin host arrived in OAP 11, so 10.4 has none and Horizon must - # be running readonly. These endpoints answer HTTP 200 either way and report the - # failure in the body, so the assertion flips rather than disappearing. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get '/api/preflight?refresh=1' | yq -p json -o yaml '{"adminReachable": .adminReachable, "templatesMode": .templatesMode}' - expected: expected/horizon-admin-readonly.yml - - # readonly still serves templates -- from the bundle in the image, without ever - # contacting OAP. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get '/api/admin/templates/sync-status?force=true' | yq -p json -o yaml '{"mode": .mode, "unreachable": .unreachable}' - expected: expected/horizon-templates-readonly.yml - - # The mesh services the demo app produces, listed through Horizon. - # NOTE: the layer is MESH, not GENERAL -- the fixture feeds OAP via Envoy ALS - # with k8s-mesh analysis, so nothing lands in GENERAL. Asserting .reachable here - # would be worthless: the handler returns reachable:true with an empty list even - # when OAP is down. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get /api/layer/MESH/services | yq -p json -o yaml '{"layer": .layer, "services": ([.services[].name] | sort)}' - expected: expected/horizon-mesh-services.yml - - # service_cpm for one of them -- the full read path through to storage. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} post /api/layer/MESH/dashboard '{"service":"e2e::productpage","scope":"service","widgets":[{"id":"cpm","title":"Service CPM","type":"card","expressions":["service_cpm"]}]}' | yq -p json -o yaml '{"reachable": .reachable, "id": .widgets[0].id, "positive": (.widgets[0].value != null and .widgets[0].value > 0)}' - expected: expected/horizon-service-cpm.yml diff --git a/test/e2e/e2e-oap10-elasticsearch.yaml b/test/e2e/e2e-oap10-elasticsearch.yaml deleted file mode 100644 index c201182..0000000 --- a/test/e2e/e2e-oap10-elasticsearch.yaml +++ /dev/null @@ -1,141 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This file is used to show how to write configuration files and can be used to test. - -setup: - env: kind - file: kind28.yaml - init-system-environment: env - kind: - expose-ports: - - namespace: istio-system - resource: service/skywalking-ui - port: 80 - steps: - - name: install yq - command: bash test/e2e/setup-e2e-shell/install.sh yq - - name: install kubectl - command: bash test/e2e/setup-e2e-shell/install.sh kubectl - - name: install istio - command: | - bash test/e2e/setup-e2e-shell/install.sh istioctl - istioctl install -y --set profile=demo \ - --set meshConfig.defaultConfig.envoyAccessLogService.address=skywalking-satellite.istio-system:11800 \ - --set meshConfig.enableEnvoyAccessLogService=true - kubectl label namespace default istio-injection=enabled - - name: Install helm - command: bash test/e2e/setup-e2e-shell/install.sh helm - - name: Install ECK operator - command: | - helm dep up chart/skywalking - tar xzf chart/skywalking/charts/eck-operator-3.3.1.tgz -C /tmp eck-operator/charts/eck-operator-crds - helm -n istio-system install eck-crds /tmp/eck-operator/charts/eck-operator-crds \ - --create-namespace - - name: Install SkyWalking - command: | - helm -n istio-system install skywalking chart/skywalking \ - --set fullnameOverride=skywalking \ - --set eck-operator.installCRDs=false \ - --set oap.env.SW_ENVOY_METRIC_ALS_HTTP_ANALYSIS=k8s-mesh \ - --set oap.env.SW_ENVOY_METRIC_ALS_TCP_ANALYSIS=k8s-mesh \ - --set oap.env.K8S_SERVICE_NAME_RULE='e2e::${service.metadata.name}' \ - --set oap.replicas=1 \ - --set ui.image.repository=$UI_REPO \ - --set ui.image.tag=$UI_TAG \ - --set oap.image.repository=$OAP_10_REPO \ - --set oap.image.tag=$OAP_10_TAG \ - --set oap.storageType=elasticsearch \ - --set oap.ports.admin=null \ - --set ui.config.templates.mode=readonly \ - --set satellite.enabled=true \ - --set satellite.image.repository=$SATELLITE_REPO \ - --set satellite.image.tag=$SATELLITE_TAG \ - -f test/e2e/values.yaml - wait: - - namespace: istio-system - resource: deployments/skywalking-oap - for: condition=available - - namespace: istio-system - resource: deployments/skywalking-satellite - for: condition=available - - namespace: istio-system - resource: deployments/skywalking-ui - for: condition=available - - name: Deploy demo services - command: | - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/platform/kube/bookinfo.yaml - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/networking/bookinfo-gateway.yaml - # Enable TCP services - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/platform/kube/bookinfo-ratings-v2.yaml - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/platform/kube/bookinfo-db.yaml - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/networking/destination-rule-all.yaml - kubectl apply -f https://raw.githubusercontent.com/istio/istio/$ISTIO_VERSION/samples/bookinfo/networking/virtual-service-ratings-db.yaml - wait: - - namespace: default - resource: pod - for: condition=Ready - - name: Generate traffic - path: traffic-gen.yaml - wait: - - namespace: default - resource: pod - for: condition=Ready - timeout: 25m - -verify: - retry: - count: 30 - interval: 10s - cases: - # Every assertion below goes through Horizon's BFF: horizon -> oap -> storage. - # Nothing talks to OAP's GraphQL directly -- that would test OAP, not the chart. - - # The operator can log in, and lands with the role the values file granted. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get /api/auth/me | yq -p json -o yaml '{"username": .username, "roles": .roles}' - expected: expected/horizon-me.yml - - # Cluster Status -> Query pane. Proves oap.queryUrl reaches the in-cluster OAP. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get /api/oap/info | yq -p json -o yaml '{"reachable": .reachable, "version": .version}' - expected: expected/horizon-oap-info.yml - - # Inverted: the admin host arrived in OAP 11, so 10.4 has none and Horizon must - # be running readonly. These endpoints answer HTTP 200 either way and report the - # failure in the body, so the assertion flips rather than disappearing. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get '/api/preflight?refresh=1' | yq -p json -o yaml '{"adminReachable": .adminReachable, "templatesMode": .templatesMode}' - expected: expected/horizon-admin-readonly.yml - - # readonly still serves templates -- from the bundle in the image, without ever - # contacting OAP. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get '/api/admin/templates/sync-status?force=true' | yq -p json -o yaml '{"mode": .mode, "unreachable": .unreachable}' - expected: expected/horizon-templates-readonly.yml - - # The mesh services the demo app produces, listed through Horizon. - # NOTE: the layer is MESH, not GENERAL -- the fixture feeds OAP via Envoy ALS - # with k8s-mesh analysis, so nothing lands in GENERAL. Asserting .reachable here - # would be worthless: the handler returns reachable:true with an empty list even - # when OAP is down. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} get /api/layer/MESH/services | yq -p json -o yaml '{"layer": .layer, "services": ([.services[].name] | sort)}' - expected: expected/horizon-mesh-services.yml - - # service_cpm for one of them -- the full read path through to storage. - - query: | - bash test/e2e/script/horizon.sh http://${service_skywalking_ui_host}:${service_skywalking_ui_80} post /api/layer/MESH/dashboard '{"service":"e2e::productpage","scope":"service","widgets":[{"id":"cpm","title":"Service CPM","type":"card","expressions":["service_cpm"]}]}' | yq -p json -o yaml '{"reachable": .reachable, "id": .widgets[0].id, "positive": (.widgets[0].value != null and .widgets[0].value > 0)}' - expected: expected/horizon-service-cpm.yml diff --git a/test/e2e/env b/test/e2e/env index da5edce..bc37cca 100644 --- a/test/e2e/env +++ b/test/e2e/env @@ -36,14 +36,3 @@ SATELLITE_REPO=ghcr.io/apache/skywalking-satellite/skywalking-satellite # image instead. BANYANDB_TAG=3b83e18fb0481d02e44eaa5df137fcf7b000754b BANYANDB_REPO=ghcr.io/apache/skywalking-banyandb - -# The OAP 10 compatibility line. FROZEN: 10.4.0 is the last v10 release, and it -# pins compatibleServerApiVersions to 0.10, so this pair never moves again. -# Horizon 1.0.0 supports this combination with `oap.ports.admin=null` and -# `ui.config.templates.mode=readonly` -- the admin host and its /ui-management -# template store are OAP 11 additions. -# Unlike the 0.11 line above, both of these are real published release tags. -OAP_10_TAG=10.4.0 -OAP_10_REPO=docker.io/apache/skywalking-oap-server -BANYANDB_0_10_TAG=0.10.3 -BANYANDB_0_10_REPO=docker.io/apache/skywalking-banyandb diff --git a/test/e2e/expected/override-login.yml b/test/e2e/expected/override-login.yml new file mode 100644 index 0000000..2f258ce --- /dev/null +++ b/test/e2e/expected/override-login.yml @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +username: admin diff --git a/test/e2e/expected/override-oap-mounted.yml b/test/e2e/expected/override-oap-mounted.yml new file mode 100644 index 0000000..187e125 --- /dev/null +++ b/test/e2e/expected/override-oap-mounted.yml @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +mounted: true diff --git a/test/e2e/expected/override-oap-reachable.yml b/test/e2e/expected/override-oap-reachable.yml new file mode 100644 index 0000000..20d3097 --- /dev/null +++ b/test/e2e/expected/override-oap-reachable.yml @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +reachable: true diff --git a/test/e2e/expected/override-ui-mounted.yml b/test/e2e/expected/override-ui-mounted.yml new file mode 100644 index 0000000..187e125 --- /dev/null +++ b/test/e2e/expected/override-ui-mounted.yml @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +mounted: true diff --git a/test/e2e/values.yaml b/test/e2e/values.yaml index 9b3a9b4..98092e6 100644 --- a/test/e2e/values.yaml +++ b/test/e2e/values.yaml @@ -25,17 +25,16 @@ oap: # same hashes that ship in chart/skywalking/values.yaml; we re-state them # here so the test overlay is explicit about what the credentials are. ui: - config: - auth: - backend: local - local: - users: - - username: admin # password: admin - passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$eemqy1r72oSXR58y8VpRqw$Bn/dULrmJTHEi3263KfgWDEwQmUsqNLi3xwyv/DekHM" - roles: [admin] - - username: skywalking # password: skywalking - passwordHash: "$argon2id$v=19$m=65536,t=3,p=4$Zqj8HhQDqm8d5c2MipHYZw$BsaCnu4bdd4uadIldx3wwYLsdo47Thxb7Lv1MXpWG2Q" - roles: [viewer, maintainer] + # The e2e logs in through Horizon, so it needs users. This is the path the + # docs tell operators to use: a plain env var carrying JSON. In a real + # deployment the same variable comes from a Secret via ui.envFromSecret -- + # here it is inline because the hashes are publicly known test credentials. + extraEnv: + - name: HORIZON_AUTH_LOCAL_USERS + value: >- + [{"username":"admin","passwordHash":"$argon2id$v=19$m=65536,t=3,p=4$eemqy1r72oSXR58y8VpRqw$Bn/dULrmJTHEi3263KfgWDEwQmUsqNLi3xwyv/DekHM","roles":["admin"]}, + {"username":"skywalking","passwordHash":"$argon2id$v=19$m=65536,t=3,p=4$Zqj8HhQDqm8d5c2MipHYZw$BsaCnu4bdd4uadIldx3wwYLsdo47Thxb7Lv1MXpWG2Q","roles":["viewer","maintainer"]}] + elasticsearch: nodeSets: - name: default