You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/05/18 13:49:47 UTC

(skywalking-horizon-ui) branch main updated (1fd9659 -> 80565f5)

This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


    from 1fd9659  logging: treat local `node dist/server.js` as production (quiet); only `pnpm dev` is dev
     new c4df7d8  docker: zero-config boot — HORIZON_SERVER_HOST=0.0.0.0 by default in the image
     new 80565f5  ci: multi-arch publish-image — native amd64 + arm64 builds, OCI manifest list

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/publish-image.yaml | 200 ++++++++++++++++++++++++-----------
 Dockerfile                           |   2 +
 apps/bff/src/config/schema.ts        |  16 ++-
 3 files changed, 154 insertions(+), 64 deletions(-)


(skywalking-horizon-ui) 02/02: ci: multi-arch publish-image — native amd64 + arm64 builds, OCI manifest list

Posted by wu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit 80565f563b6ea47c274141029d8ef6bd33b7eeac
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Mon May 18 21:49:27 2026 +0800

    ci: multi-arch publish-image — native amd64 + arm64 builds, OCI manifest list
    
    The previous single-arch (linux/amd64) image worked on amd64 hosts but
    forced Apple Silicon Macs and AWS Graviton boxes through Rosetta /
    QEMU translation — argon2's CPU-intensive hashing took the brunt of
    the slowdown. With this change the canonical SHA tag on GHCR resolves
    to the host's native arch automatically.
    
    structure
    - New `tags` job computes the canonical SHA + moving tag set (`:main`,
      `:vX.Y.Z`, `:latest`, `:major.minor`) once and outputs them so the
      matrix build + the manifest job share the same vocabulary without
      duplicating the shell math.
    - `build` matrix runs on native runners — `ubuntu-latest` for amd64,
      `ubuntu-24.04-arm` for arm64 (free for public ASF repos). Each
      produces its own `./dist/` with the correct argon2 native binding,
      pushes `<base>:<sha>-<arch>`, and uses an arch-scoped GHA cache.
    - `manifest` job stitches the two `:<sha>-<arch>` tags into a single
      OCI manifest list per moving tag via `docker buildx imagetools
      create`. Pulling the canonical `:<sha>` (or `:main`, `:vX.Y.Z`, etc.)
      from any host selects the matching arch.
    
    trade-offs / follow-ups
    - If the org doesn't have `ubuntu-24.04-arm` runners enabled, the
      arm64 build fails and the manifest step short-circuits. Fix is org-
      side (enable the runner) or drop the arm64 matrix entry and accept
      Rosetta-emulation on Macs.
    - Per-arch debugger tags (`<sha>-amd64`, `<sha>-arm64`) remain
      permanently pushed so investigators can pull a specific arch without
      going through the manifest.
---
 .github/workflows/publish-image.yaml | 200 ++++++++++++++++++++++++-----------
 1 file changed, 138 insertions(+), 62 deletions(-)

diff --git a/.github/workflows/publish-image.yaml b/.github/workflows/publish-image.yaml
index 0fe0a9a..4387d5c 100644
--- a/.github/workflows/publish-image.yaml
+++ b/.github/workflows/publish-image.yaml
@@ -14,19 +14,38 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-# Publish a Horizon UI container image to GHCR on:
+# Publish a multi-arch Horizon UI container image to GHCR on:
 #   - push to `main`  (tagged with `main` + the full commit SHA)
 #   - any `v*` tag    (tagged with the version + the full commit SHA)
 #
-# The full commit SHA is the canonical, immutable identifier — moving
-# tags like `main` and `vX.Y.Z` are conveniences that point at the same
-# SHA-built image. Operators should pull by SHA in production.
+# Architecture story
+#   The image is a pure copy-in of `./dist/` (no compile or network
+#   inside `docker build`). The `dist/node_modules/` carries `argon2`'s
+#   native binding — that binding is platform-specific, so a single
+#   amd64-built dist/ cannot be packaged into an arm64-correct image.
+#   We build each architecture on its own native GitHub runner
+#   (`ubuntu-latest` for amd64, `ubuntu-24.04-arm` for arm64), push a
+#   per-arch SHA-suffix tag, and a final `manifest` job stitches them
+#   into a single SHA-canonical OCI manifest list. Pulling the canonical
+#   tag from any host (Apple Silicon, AWS Graviton, plain x86_64)
+#   selects the right arch automatically — no Rosetta / QEMU emulation
+#   needed at run time.
 #
-# Only login / setup-qemu / setup-buildx are pulled from `docker/*` —
-# everything else (tag computation, buildx build+push) is shell-driven
-# to stay within ASF infra's third-party-action allow-list. SHAs are
-# the same ones `apache/skywalking`'s publish-docker.yaml uses, so
-# they're already vetted at the org level.
+# Tagging
+#   - `:<40-char-sha>`           always (canonical, immutable)
+#   - `:main`                    on push to main
+#   - `:<ver>` + `:latest`       on v* tags (ver = the tag with leading `v` stripped)
+#   - `:<major.minor>`           on v* tags (e.g. `1.2` from `v1.2.3`)
+#
+# Per-arch interim tags
+#   - `:<sha>-amd64` / `:<sha>-arm64` are also pushed so a debugger can
+#     pull a specific arch directly. The manifest job is the only thing
+#     that exposes the canonical tag without an arch suffix.
+#
+# Only login / setup-buildx / setup-node are pulled from third-party
+# actions — everything else (tag computation, buildx build+push,
+# manifest stitching) is shell-driven to stay within ASF infra's
+# third-party-action allow-list. SHAs are vetted at the org level.
 name: publish-image
 
 on:
@@ -45,22 +64,77 @@ concurrency:
   cancel-in-progress: false
 
 jobs:
-  build-and-push:
+  # ── Compute tags once so the per-arch matrix + the manifest job all
+  # ── work from the same set without duplicating the shell math.
+  tags:
     if: github.repository == 'apache/skywalking-horizon-ui'
-    name: Build + push
+    name: Compute image tags
     runs-on: ubuntu-latest
+    outputs:
+      base: ${{ steps.tags.outputs.base }}
+      moving: ${{ steps.tags.outputs.moving }}
+    steps:
+      - id: tags
+        env:
+          REGISTRY: ghcr.io
+          IMAGE_NAME: ${{ github.repository }}
+        run: |
+          set -eu
+          base="${REGISTRY}/${IMAGE_NAME}"
+          # lower-case the image path — GHCR rejects mixed-case names.
+          base="$(echo "$base" | tr '[:upper:]' '[:lower:]')"
+          sha="${{ github.sha }}"
+          # `moving` = the SET of canonical/moving tags the manifest
+          # job needs to stitch (one per `imagetools create -t ...`).
+          # Space-separated; consumers split on whitespace.
+          moving="${base}:${sha}"
+          if [ "${{ github.ref }}" = "refs/heads/main" ]; then
+            moving="${moving} ${base}:main"
+          fi
+          if [ "${{ github.ref_type }}" = "tag" ]; then
+            ver="${GITHUB_REF_NAME#v}"
+            moving="${moving} ${base}:${ver} ${base}:latest"
+            mm="${ver%.*}"
+            if [ "${mm}" != "${ver}" ]; then
+              moving="${moving} ${base}:${mm}"
+            fi
+          fi
+          echo "base=${base}" >> "$GITHUB_OUTPUT"
+          echo "moving=${moving}" >> "$GITHUB_OUTPUT"
+          echo "::notice::Canonical SHA: ${sha}"
+          echo "::notice::Moving tags: ${moving}"
+
+  # ── Per-arch native build. Each runner produces its own `./dist/`
+  # ── (with the correct argon2 native binding) and pushes
+  # ── `<base>:<sha>-<arch>`. The manifest job below stitches the
+  # ── `:<sha>-amd64` + `:<sha>-arm64` pair into the canonical tag set.
+  build:
+    if: github.repository == 'apache/skywalking-horizon-ui'
+    needs: tags
+    name: Build ${{ matrix.arch }}
+    runs-on: ${{ matrix.runner }}
     timeout-minutes: 30
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - runner: ubuntu-latest
+            platform: linux/amd64
+            arch: amd64
+          # Free arm64 runners for public repos. If the org doesn't have
+          # them enabled, this matrix entry fails and the manifest job
+          # below short-circuits — fix is to either enable them on the
+          # org or drop arm64 here and accept Rosetta-emulation on Macs.
+          - runner: ubuntu-24.04-arm
+            platform: linux/arm64
+            arch: arm64
     env:
-      REGISTRY: ghcr.io
-      IMAGE_NAME: ${{ github.repository }}
+      BASE: ${{ needs.tags.outputs.base }}
     steps:
       - uses: actions/checkout@v4
         with:
           persist-credentials: false
 
-      # The Dockerfile is a pure copy-in of `./dist/` (no compile or
-      # network inside `docker build`). Build the artifact on the
-      # runner first; the image just lays it out under /app.
       - name: Set up Node
         uses: actions/setup-node@v4
         with:
@@ -72,71 +146,73 @@ jobs:
       - name: Install workspace deps
         run: pnpm install --frozen-lockfile
 
-      - name: Build self-contained ./dist/
+      - name: Build self-contained ./dist/ (native ${{ matrix.arch }})
+        # `pnpm package` runs `pnpm deploy` whose `node_modules` contains
+        # the argon2 binding for the current runner's architecture.
+        # Running this on `ubuntu-24.04-arm` produces an arm64-correct
+        # tree; on `ubuntu-latest` it's amd64.
         run: pnpm package
 
-      - name: Set up QEMU
-        uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392
-
       - name: Set up Docker Buildx
         uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
 
       - name: Log in to GHCR
         uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
         with:
-          registry: ${{ env.REGISTRY }}
+          registry: ghcr.io
           username: ${{ github.actor }}
           password: ${{ secrets.GITHUB_TOKEN }}
 
-      # Compute image tags in shell so we don't need docker/metadata-action.
-      # Canonical = full 40-char SHA; moving tags layered per trigger.
-      - name: Compute image tags
-        id: tags
+      - name: Build + push (${{ matrix.arch }})
         run: |
           set -eu
-          base="${REGISTRY}/${IMAGE_NAME}"
-          # lower-case the image path — GHCR rejects mixed-case names.
-          base="$(echo "$base" | tr '[:upper:]' '[:lower:]')"
-          sha="${{ github.sha }}"
-          tags="-t ${base}:${sha}"
-          if [ "${{ github.ref }}" = "refs/heads/main" ]; then
-            tags="${tags} -t ${base}:main"
-          fi
-          if [ "${{ github.ref_type }}" = "tag" ]; then
-            # Strip the leading `v` from `vX.Y.Z` for the semver tag.
-            ver="${GITHUB_REF_NAME#v}"
-            tags="${tags} -t ${base}:${ver} -t ${base}:latest"
-            # Bonus: major.minor (e.g. 1.2 from 1.2.3) for the common
-            # "track the minor line" usage. POSIX shell substitution.
-            mm="${ver%.*}"
-            if [ "${mm}" != "${ver}" ]; then
-              tags="${tags} -t ${base}:${mm}"
-            fi
-          fi
-          echo "IMAGE_BASE=${base}" >> "$GITHUB_ENV"
-          echo "TAG_ARGS=${tags}" >> "$GITHUB_ENV"
-          echo "tags=${tags}" >> "$GITHUB_OUTPUT"
-
-      - name: Build + push
-        run: |
-          set -eu
-          # Single-platform (linux/amd64) for now. The `./dist/` is built
-          # on the GitHub-hosted runner (linux/amd64), and `argon2` ships
-          # native bindings — cross-arch packaging would need a per-arch
-          # matrix of build jobs that each produce their own dist/ +
-          # platform-tagged image, then a buildx imagetools merge into a
-          # manifest list. Easy follow-up when arm64 demand is real.
           docker buildx build \
-            --platform linux/amd64 \
+            --platform ${{ matrix.platform }} \
             --file Dockerfile \
             --label "org.opencontainers.image.source=https://github.com/${{ github.repository }}" \
             --label "org.opencontainers.image.revision=${{ github.sha }}" \
             --label "org.opencontainers.image.title=Apache SkyWalking Horizon UI" \
             --label "org.opencontainers.image.description=Next-generation web UI for Apache SkyWalking." \
             --label "org.opencontainers.image.licenses=Apache-2.0" \
-            --cache-from type=gha \
-            --cache-to type=gha,mode=max \
-            ${TAG_ARGS} \
+            --cache-from type=gha,scope=${{ matrix.arch }} \
+            --cache-to type=gha,mode=max,scope=${{ matrix.arch }} \
+            -t "${BASE}:${{ github.sha }}-${{ matrix.arch }}" \
             --push \
             .
-          echo "::notice::Published ${IMAGE_BASE}@sha=${{ github.sha }}"
+          echo "::notice::Pushed ${BASE}:${{ github.sha }}-${{ matrix.arch }}"
+
+  # ── Stitch the per-arch tags into a single OCI manifest list per
+  # ── canonical/moving tag. Pulling any of these tags from any host
+  # ── selects the matching arch automatically.
+  manifest:
+    if: github.repository == 'apache/skywalking-horizon-ui'
+    needs: [tags, build]
+    name: Stitch multi-arch manifest
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+    env:
+      BASE: ${{ needs.tags.outputs.base }}
+      MOVING: ${{ needs.tags.outputs.moving }}
+    steps:
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
+
+      - name: Log in to GHCR
+        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Create manifest list per moving tag
+        run: |
+          set -eu
+          amd="${BASE}:${{ github.sha }}-amd64"
+          arm="${BASE}:${{ github.sha }}-arm64"
+          for tag in ${MOVING}; do
+            echo "::group::Stitch ${tag}"
+            docker buildx imagetools create -t "${tag}" "${amd}" "${arm}"
+            docker buildx imagetools inspect "${tag}" | head -40
+            echo "::endgroup::"
+          done
+          echo "::notice::Published multi-arch manifest @sha=${{ github.sha }} (amd64+arm64)"


(skywalking-horizon-ui) 01/02: docker: zero-config boot — HORIZON_SERVER_HOST=0.0.0.0 by default in the image

Posted by wu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit c4df7d8cc0ae8644ecf84acc79654489886ac651
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Mon May 18 21:47:34 2026 +0800

    docker: zero-config boot — HORIZON_SERVER_HOST=0.0.0.0 by default in the image
    
    Without this the container booted but bound `127.0.0.1` (the schema
    default), which inside Docker is container-internal-loopback —
    `docker run -p 8081:8081 horizon-ui:local` (no config mount) reached
    the published port but got 502s with no clear cause. An operator
    trying the image with zero arguments hit this immediately.
    
    Added env-var fallbacks for `server.host` / `server.port` in the
    config schema (same pattern as the existing `HORIZON_AUDIT_FILE` /
    `HORIZON_SETUP_FILE` / `HORIZON_ALARMS_FILE` / `HORIZON_WIRE_LOG_FILE`
    overrides). An explicit value in `horizon.yaml` still wins.
    
    The Dockerfile sets `HORIZON_SERVER_HOST=0.0.0.0` and
    `HORIZON_SERVER_PORT=8081` so the image's out-of-the-box behaviour is:
      - boot in unconfigured state (warns, doesn't crash — already wired)
      - bind to 0.0.0.0:8081 so the published port reaches the BFF
      - serve the login page showing the "Auth not configured" banner
    
    Verified: `docker run --rm -p 8081:8081 horizon-ui:local` reaches the
    SPA + API; health endpoint reports configured: false + setupHint; logs
    quiet at default error level; LOG_LEVEL=info opens the access log.
---
 Dockerfile                    |  2 ++
 apps/bff/src/config/schema.ts | 16 ++++++++++++++--
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index c55f6d0..336b28a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -59,6 +59,8 @@ RUN mkdir -p /data && chown horizon:horizon /data
 VOLUME ["/data"]
 
 ENV NODE_ENV=production \
+    HORIZON_SERVER_HOST=0.0.0.0 \
+    HORIZON_SERVER_PORT=8081 \
     HORIZON_STATIC_DIR=/app/static \
     HORIZON_CONFIG=/app/horizon.yaml \
     HORIZON_AUDIT_FILE=/data/horizon-audit.jsonl \
diff --git a/apps/bff/src/config/schema.ts b/apps/bff/src/config/schema.ts
index ecb3aed..bd4b118 100644
--- a/apps/bff/src/config/schema.ts
+++ b/apps/bff/src/config/schema.ts
@@ -17,10 +17,22 @@
 
 import { z } from 'zod';
 
+// Env-var-overridable bind defaults. The Docker image sets
+// `HORIZON_SERVER_HOST=0.0.0.0` so a zero-config `docker run -p 8081:8081
+// horizon-ui:local` reaches the BFF (the YAML default `127.0.0.1` would
+// bind container-loopback and silently 502 from the host side). An
+// explicit `server.host`/`server.port` in horizon.yaml always wins.
+const serverHostDefault = process.env.HORIZON_SERVER_HOST ?? '127.0.0.1';
+const serverPortDefault = (() => {
+  const raw = process.env.HORIZON_SERVER_PORT;
+  if (!raw) return 8081;
+  const n = Number(raw);
+  return Number.isFinite(n) && n > 0 ? Math.floor(n) : 8081;
+})();
 const serverSchema = z
   .object({
-    host: z.string().default('127.0.0.1'),
-    port: z.number().int().positive().default(8081),
+    host: z.string().default(serverHostDefault),
+    port: z.number().int().positive().default(serverPortDefault),
     staticDir: z.string().optional(),
   })
   .strict();