linkerd2 icon indicating copy to clipboard operation
linkerd2 copied to clipboard

View linkerd dashboard at non-root URL

Open davidcorbin opened this issue 5 years ago • 14 comments

Bug Report

What is the issue?

TLDR: Linkerd dashboard dependencies aren't using relative URL paths.

I would like to be able to have the linkerd dashboard running behind a reverse proxy at a URL like example.com/linkerd/. When I put linkerd behind a reverse proxy, the main page loads but can't find get the javascript dependencies retrieved via an absolute path.

Screen Shot 2019-11-11 at 10 54 12 PM

How can it be reproduced?

Add a k8s ingress that routes a non-root path to the dashboard. In this case, I'm using Traefik but Nginx, HA proxy, etc. would work.

Example ingress configuration:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: linkerd-dashboard-ingress
  namespace: linkerd
  annotations:
    kubernetes.io/ingress.class: "traefik"
    traefik.frontend.rule.type: PathPrefixStrip
spec:
  rules:
  - http:
      paths:
      - path: /linkerd
        backend:
          serviceName: linkerd-web
          servicePort: 8084

Logs, error output, etc

See image above.

linkerd check output

$ linkerd check
kubernetes-api
--------------
√ can initialize the client
√ can query the Kubernetes API

kubernetes-version
------------------
√ is running the minimum Kubernetes API version
√ is running the minimum kubectl version

linkerd-config
--------------
√ control plane Namespace exists
√ control plane ClusterRoles exist
√ control plane ClusterRoleBindings exist
√ control plane ServiceAccounts exist
√ control plane CustomResourceDefinitions exist
√ control plane MutatingWebhookConfigurations exist
√ control plane ValidatingWebhookConfigurations exist
√ control plane PodSecurityPolicies exist

linkerd-existence
-----------------
√ 'linkerd-config' config map exists
√ heartbeat ServiceAccount exist
√ control plane replica sets are ready
√ no unschedulable pods
√ controller pod is running
√ can initialize the client
√ can query the control plane API

linkerd-api
-----------
√ control plane pods are ready
√ control plane self-check
√ [kubernetes] control plane can talk to Kubernetes
√ [prometheus] control plane can talk to Prometheus
√ no invalid service profiles

linkerd-version
---------------
√ can determine the latest version
√ cli is up-to-date

control-plane-version
---------------------
√ control plane is up-to-date
√ control plane and cli versions match

Status check results are √

Environment

  • Kubernetes Version: 1.15.3
  • Cluster Environment: Bare metal
  • Host OS: Ubuntu 16.04
  • Linkerd version: stable-2.6.0

Possible solution

Change dependencies to use relative URL.

davidcorbin avatar Nov 12 '19 04:11 davidcorbin

#1960 looks very similar

BartVanBerkel avatar Dec 05 '19 08:12 BartVanBerkel

This isn't isolated to traefik -- I can repro this behaviour with ingress-nginx as well.

dayglojesus avatar Jan 22 '20 21:01 dayglojesus

This is because the dashboard is an SPA and all its routing is based off that. It isn't ingress controller specific and will happen any time the dashboard is viewed at a non-root URL.

grampelberg avatar Jan 22 '20 22:01 grampelberg

Hi,

That's true, this is a common issue with SPA, but some SPA have a way to handle it.

Some application provide a parameter to set the root URL, like: Jenkins, Nexus, Sonarqube, Kibana (not extremely modern frontend but still)

Some application auto-detect the root URL, like: Portainer

I don't know if I have enough knowledge but I may try a PR....

Regards,

DidierHoarau avatar Mar 25 '20 07:03 DidierHoarau

I am having the same issue with loading all the javascript files related to linkerd dashboard when exposing a mapping with ambassador. my https://example.com/ route is routing to my angular web application - which is the default. when i do https://example.com/linkerd-dashboard as the prefix - it fails to load the linkerd index file - it stays on my web application’s index file. index-bundle.js is able to have linkerd html, but thatt’s it - not able to download any other javascript files that are needed to render the dashboard. i have followed the linkerd exposing dashboard for ambassador configurations. adding the mapping into linkerd svc yaml file, and changing the enforced host in linkerd deploy yaml file.

binnie268 avatar Sep 15 '20 00:09 binnie268

I am having the exact same issue. I wanted to have a look at it just in case if I can find a solution but no luck so far. I found some solutions like this. But this is basically about exposing the dashboard on a static domain and path. Have someone ever worked on this issue? Any ideas?

abarisuzuner avatar Jan 20 '21 15:01 abarisuzuner

react-router should have this functionality in the form of https://reactrouter.com/web/api/BrowserRouter/basename-string

justindriggers avatar Feb 05 '21 18:02 justindriggers

This functionality works for K8S dashboard but not Viz dashboard. You can get around JS not loading by updating HTML in ingress. Here's a sample:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: linkerd-ingress
  namespace: linkerd-viz
  annotations:
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    kubernetes.io/tls-acme-challenge-endpoints: "true"
    nginx.ingress.kubernetes.io/upstream-vhost: $service_name.$namespace.svc.cluster.local:8084
    nginx.ingress.kubernetes.io/configuration-snippet: |
      sub_filter_once off;
      sub_filter '<head>' '<head> <base href="dashboard/">';
      sub_filter 'src="/' 'src="/dashboard/';
      sub_filter 'href="/' 'href="/dashboard/';

      proxy_set_header Origin "";
      proxy_set_header Accept-Encoding "";
      proxy_hide_header l5d-remote-ip;
      proxy_hide_header l5d-server-id;

    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - host: example.com
    http:
      paths:
      - path: /dashboard(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 8084

All the JS files load correctly, but page still shows 404 (even though there's no 404 in network tab) This is enough to make K8S dashboard work:

      sub_filter_once on;
      sub_filter '<head>' '<head> <base href="dashboard/">';

karpikpl avatar Nov 02 '21 03:11 karpikpl

Hi, Did anyone find a solution for this issue, whereby we can't expose the linkerd dashboard via Ingress ? I tried exposing the grafana which is located as /grafana in linkerd dashboard, which i am able to expose.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
  namespace: linkerd-viz
  annotations:
    nginx.ingress.kubernetes.io/upstream-vhost: $service_name.$namespace.svc.cluster.local:8084
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header Origin "";
      proxy_hide_header l5d-remote-ip;
      proxy_hide_header l5d-server-id;      
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: web-ingress-auth
    nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /grafana
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 8084

kingalok avatar Jan 20 '22 16:01 kingalok

based on @karpikpl I extended sub_filter and get the viz dashboard working on subpath with nginx-ingress snippet. In the viz's react router there is specific handling of the path prefix, that is computed for some kind of a "proxy" scenario, using the regex match, see index.js, lines 38-42 . Substitution of that regex will make the trick. Here is Ingress resource:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: linkerd-viz
  namespace: linkerd-viz
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/use-regex: "true"
    # viz does not support base url, therefore substituting by nginx
    nginx.ingress.kubernetes.io/configuration-snippet: |
      sub_filter_once off;
      sub_filter '<head>' '<head> <base href="dashboard/">';
      sub_filter 'src="/' 'src="/dashboard/';
      sub_filter 'href="/' 'href="/dashboard/';
      sub_filter '/\/api\/v1\/namespaces\/.*\/proxy/g' '/\/dashboard/g';
      sub_filter_types text/html text/css text/javascript;
      proxy_set_header Origin "";
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /dashboard(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              name: http

This is pretty dirty hack, but seems working for link navigations, with exception of the drop-down button in the left pane, which still sets window.location to the absolute path. Possibility to control that prefix by other means may be worth of the PR, but will require to adapt also drop down button. .

milung avatar Feb 02 '22 18:02 milung

Nice hack @milung ! If you or anyone else would like to add this functionality natively into linkerd, I'd be happy to provide guidance. Sounds like most of the changes would occur in the web backend component (golang), adding a new root dir setting to values.yaml and propagating it through the routes in the backend code...

alpeb avatar Feb 11 '22 13:02 alpeb

Linkerd dashboard exposed via traefik ingress with a root path such as: linkerd.10.10.10.10.nip.io/ also reproduces the same problem.

I can see on the traefik log that the javascript bundle is the problem, throwing a 404 status:

GET /dist/index_bundle.js HTTP/1.1" 404 19 "-" "-" 19537 "-" "-" 0ms

Unfortunately not familiar with golang to fix this.

TaoVonQi avatar Jul 31 '22 21:07 TaoVonQi

Still experiencing the same behavior in 2.14

lieberlois avatar Nov 27 '23 08:11 lieberlois

For some reason I had to escape the backslashes in the ingress definition of @milung

This is what I have:

    nginx.ingress.kubernetes.io/configuration-snippet: |
      sub_filter_once off;
      sub_filter '<head>' '<head> <base href="/linkerd-viz/">';
      sub_filter 'src="/' 'src="/linkerd-viz/';
      sub_filter 'href="/' 'href="/linkerd-viz/';
      sub_filter '/\\/api\\/v1\\/namespaces\\/.*\\/proxy/g' '/\\/linkerd-viz/g';
      sub_filter_types text/html text/css text/javascript application/javascript;
      proxy_set_header Origin "";

It works but not very well. It would be great if someone can implement a root path setting.

lennartack avatar Jan 25 '24 17:01 lennartack