linkerd2
linkerd2 copied to clipboard
View linkerd dashboard at non-root URL
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.

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.
#1960 looks very similar
This isn't isolated to traefik -- I can repro this behaviour with ingress-nginx
as well.
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.
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,
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.
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?
react-router
should have this functionality in the form of https://reactrouter.com/web/api/BrowserRouter/basename-string
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/">';
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
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. .
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...
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.
Still experiencing the same behavior in 2.14
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.