Add Jaeger links to the Linkerd dashboard (#4177)

* Add Jaeger reverse proxy
* add jaegerLink to the metrics table
* update MetricsTable tests
* Add optional jaeger link
* rename grafana_proxy to reverse_proxy

Signed-off-by: Tarun Pothulapati <tarunpothulapati@outlook.com>
This commit is contained in:
Tarun Pothulapati 2020-05-07 21:35:56 +00:00 committed by GitHub
parent 2a95d373c4
commit 2be43a5f9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2745 additions and 30 deletions

View File

@ -206,7 +206,9 @@ spec:
{{- include "partials.proxy.labels" .Values.global.proxy | nindent 8}}
spec:
containers:
- image: {{.Values.jaeger.image}}
- args:
- --query.base-path=/jaeger
image: {{.Values.jaeger.image}}
imagePullPolicy: {{.Values.global.imagePullPolicy}}
name: jaeger
ports:

View File

@ -64,6 +64,9 @@ spec:
- args:
- -api-addr=linkerd-controller-api.{{.Values.global.namespace}}.svc.{{.Values.global.clusterDomain}}:8085
- -grafana-addr=linkerd-grafana.{{.Values.global.namespace}}.svc.{{.Values.global.clusterDomain}}:3000
{{- if .Values.tracing.enabled }}
- -jaeger-addr={{.Values.tracing.jaeger.name}}.{{.Values.global.namespace}}.svc.{{.Values.global.clusterDomain}}:16686
{{- end}}
- -controller-namespace={{.Values.global.namespace}}
- -log-level={{.Values.controllerLogLevel}}
{{- if .Values.enforcedHostRegexp }}

View File

@ -801,6 +801,7 @@ spec:
- args:
- -api-addr=linkerd-controller-api.linkerd.svc.cluster.local:8085
- -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000
- -jaeger-addr=linkerd-jaeger.linkerd.svc.cluster.local:16686
- -controller-namespace=linkerd
- -log-level=info
- -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|linkerd-web\.linkerd\.svc|\[::1\])(:\d+)?$
@ -2615,7 +2616,9 @@ spec:
linkerd.io/proxy-deployment: linkerd-jaeger
spec:
containers:
- image: jaegertracing/all-in-one:1.17.1
- args:
- --query.base-path=/jaeger
image: jaegertracing/all-in-one:1.17.1
imagePullPolicy: IfNotPresent
name: jaeger
ports:

View File

@ -1714,6 +1714,7 @@ spec:
- args:
- -api-addr=linkerd-controller-api.linkerd.svc.cluster.local:8085
- -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000
- -jaeger-addr=linkerd-jaeger.linkerd.svc.cluster.local:16686
- -controller-namespace=linkerd
- -log-level=info
- -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|linkerd-web\.linkerd\.svc|\[::1\])(:\d+)?$
@ -3512,7 +3513,9 @@ spec:
linkerd.io/proxy-deployment: linkerd-jaeger
spec:
containers:
- image: jaegertracing/all-in-one:1.17.1
- args:
- --query.base-path=/jaeger
image: jaegertracing/all-in-one:1.17.1
imagePullPolicy: IfNotPresent
name: jaeger
ports:

View File

@ -1641,6 +1641,7 @@ spec:
- args:
- -api-addr=linkerd-controller-api.linkerd.svc.cluster.local:8085
- -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000
- -jaeger-addr=linkerd-jaeger.linkerd.svc.cluster.local:16686
- -controller-namespace=linkerd
- -log-level=info
- -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|linkerd-web\.linkerd\.svc|\[::1\])(:\d+)?$
@ -3481,7 +3482,9 @@ spec:
linkerd.io/proxy-deployment: linkerd-jaeger
spec:
containers:
- image: jaegertracing/all-in-one:1.17.1
- args:
- --query.base-path=/jaeger
image: jaegertracing/all-in-one:1.17.1
imagePullPolicy: IfNotPresent
name: jaeger
ports:

View File

@ -807,6 +807,7 @@ spec:
- args:
- -api-addr=linkerd-controller-api.linkerd.svc.cluster.local:8085
- -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000
- -jaeger-addr=linkerd-jaeger.linkerd.svc.cluster.local:16686
- -controller-namespace=linkerd
- -log-level=info
- -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|linkerd-web\.linkerd\.svc|\[::1\])(:\d+)?$
@ -2635,7 +2636,9 @@ spec:
linkerd.io/proxy-deployment: linkerd-jaeger
spec:
containers:
- image: jaegertracing/all-in-one:1.17.1
- args:
- --query.base-path=/jaeger
image: jaegertracing/all-in-one:1.17.1
imagePullPolicy: IfNotPresent
name: jaeger
ports:

View File

@ -1647,6 +1647,7 @@ spec:
- args:
- -api-addr=linkerd-controller-api.linkerd.svc.cluster.local:8085
- -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000
- -jaeger-addr=linkerd-jaeger.linkerd.svc.cluster.local:16686
- -controller-namespace=linkerd
- -log-level=info
- -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|linkerd-web\.linkerd\.svc|\[::1\])(:\d+)?$
@ -3499,7 +3500,9 @@ spec:
linkerd.io/proxy-deployment: linkerd-jaeger
spec:
containers:
- image: jaegertracing/all-in-one:1.17.1
- args:
- --query.base-path=/jaeger
image: jaegertracing/all-in-one:1.17.1
imagePullPolicy: IfNotPresent
name: jaeger
ports:

View File

@ -1647,6 +1647,7 @@ spec:
- args:
- -api-addr=linkerd-controller-api.linkerd.svc.cluster.local:8085
- -grafana-addr=linkerd-grafana.linkerd.svc.cluster.local:3000
- -jaeger-addr=linkerd-jaeger.linkerd.svc.cluster.local:16686
- -controller-namespace=linkerd
- -log-level=info
- -enforced-host=^(localhost|127\.0\.0\.1|linkerd-web\.linkerd\.svc\.cluster\.local|linkerd-web\.linkerd\.svc|\[::1\])(:\d+)?$
@ -3501,7 +3502,9 @@ spec:
linkerd.io/proxy-deployment: linkerd-jaeger
spec:
containers:
- image: jaegertracing/all-in-one:1.17.1
- args:
- --query.base-path=/jaeger
image: jaegertracing/all-in-one:1.17.1
imagePullPolicy: IfNotPresent
name: jaeger
ports:

View File

@ -0,0 +1,41 @@
import PropTypes from 'prop-types';
import React from 'react';
import _isEmpty from 'lodash/isEmpty';
import { jaegerIcon } from './util/SvgWrappers.jsx';
function jaegerQuery(name, namespace, resource) {
if (_isEmpty(namespace)) {
return `{"linkerd.io/workload-ns"%3A"${name}"}`;
} else if (resource === 'pod') {
return `{"hostname"%3A"${name}"%2C"linkerd.io/workload-ns"%3A"${namespace}"}`;
} else {
return `{"linkerd.io%2Fproxy-${resource}"%3A"${name}"%2C"linkerd.io/workload-ns"%3A"${namespace}"}`;
}
}
const JaegerLink = ({ PrefixedLink, name, namespace, resource }) => {
const link = `/jaeger/search?service=linkerd-proxy&tags=${jaegerQuery(name, namespace, resource)}`;
return (
<PrefixedLink
to={link}
targetBlank>
&nbsp;&nbsp;
{jaegerIcon}
</PrefixedLink>
);
};
JaegerLink.propTypes = {
name: PropTypes.string.isRequired,
namespace: PropTypes.string,
PrefixedLink: PropTypes.func.isRequired,
resource: PropTypes.string.isRequired,
};
JaegerLink.defaultProps = {
namespace: '',
};
export default JaegerLink;

View File

@ -3,6 +3,7 @@ import BaseTable from './BaseTable.jsx';
import ErrorModal from './ErrorModal.jsx';
import GrafanaLink from './GrafanaLink.jsx';
import Grid from '@material-ui/core/Grid';
import JaegerLink from './JaegerLink.jsx';
import PropTypes from 'prop-types';
import React from 'react';
import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';
@ -110,7 +111,7 @@ const trafficSplitDetailColumns = [
},
];
const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, PrefixedLink, isTcpTable, grafana) => {
const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, PrefixedLink, isTcpTable, grafana, jaeger) => {
const isAuthorityTable = resource === 'authority';
const isTrafficSplitTable = resource === 'trafficsplit';
const isMultiResourceTable = resource === 'multi_resource';
@ -154,6 +155,26 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix
},
};
const jaegerColumn = {
title: 'Jaeger',
key: 'JaegerDashboard',
isNumeric: true,
render: row => {
if (!isAuthorityTable && (!row.added || _get(row, 'pods.totalPods') === '0')) {
return null;
}
return (
<JaegerLink
name={row.name}
namespace={row.namespace}
resource={row.type}
PrefixedLink={PrefixedLink} />
);
},
};
const nameColumn = {
title: isMultiResourceTable ? 'Resource' : friendlyTitle(resource).singular,
dataIndex: 'name',
@ -200,8 +221,13 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix
columns.splice(1, 0, meshedColumn);
}
if (!isTrafficSplitTable && grafana !== '') {
columns = columns.concat(grafanaColumn);
if (!isTrafficSplitTable) {
if (grafana !== '') {
columns = columns.concat(grafanaColumn);
}
if (jaeger !== '') {
columns = columns.concat(jaegerColumn);
}
}
if (!showNamespaceColumn) {
@ -224,12 +250,12 @@ const preprocessMetrics = metrics => {
return tableData;
};
const MetricsTable = ({ metrics, resource, showNamespaceColumn, showName, title, api, isTcpTable, selectedNamespace, grafana }) => {
const MetricsTable = ({ metrics, resource, showNamespaceColumn, showName, title, api, isTcpTable, selectedNamespace, grafana, jaeger }) => {
const showNsColumn = resource === 'namespace' || selectedNamespace !== '_all' ? false : showNamespaceColumn;
const showNameColumn = resource !== 'trafficsplit' ? true : showName;
let orderBy = 'name';
if (resource === 'trafficsplit' && !showNameColumn) { orderBy = 'leaf'; }
const columns = columnDefinitions(resource, showNsColumn, showNameColumn, api.PrefixedLink, isTcpTable, grafana);
const columns = columnDefinitions(resource, showNsColumn, showNameColumn, api.PrefixedLink, isTcpTable, grafana, jaeger);
const rows = preprocessMetrics(metrics);
return (
<BaseTable
@ -251,6 +277,7 @@ MetricsTable.propTypes = {
metrics: PropTypes.arrayOf(processedMetricsPropType),
resource: PropTypes.string.isRequired,
selectedNamespace: PropTypes.string.isRequired,
jaeger: PropTypes.string,
showName: PropTypes.bool,
showNamespaceColumn: PropTypes.bool,
title: PropTypes.string,
@ -261,6 +288,7 @@ MetricsTable.defaultProps = {
showNamespaceColumn: true,
showName: true,
title: '',
jaeger: '',
isTcpTable: false,
metrics: [],
};

View File

@ -84,6 +84,20 @@ describe('Tests for <MetricsTable>', () => {
expect(table.props().tableColumns).toHaveLength(8);
});
it('render table with all columens including jaeger', () => {
let extraProps = _merge({}, defaultProps, {
metrics: [],
resource: "deployment",
showNamespaceColumn: false,
jaeger: 'jaeger.xyz'
});
const component = mount(routerWrap(MetricsTable, extraProps));
const table = component.find("BaseTable");
expect(table).toBeDefined();
expect(table.props().tableColumns).toHaveLength(9);
});
it('adds apex, leaf and weight columns, and omits meshed and grafana column, for a trafficsplit resource', () => {
let extraProps = _merge({}, defaultProps, { metrics: [], resource: "trafficsplit"});
const component = mount(routerWrap(MetricsTable, extraProps));

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,7 @@ func main() {
metricsAddr := cmd.String("metrics-addr", ":9994", "address to serve scrapable metrics on")
apiAddr := cmd.String("api-addr", "127.0.0.1:8085", "address of the linkerd-controller-api service")
grafanaAddr := cmd.String("grafana-addr", "", "address of the linkerd-grafana service")
jaegerAddr := cmd.String("jaeger-addr", "", "address of the jaeger service")
templateDir := cmd.String("template-dir", "templates", "directory to search for template files")
staticDir := cmd.String("static-dir", "app/dist", "directory to search for static files")
reload := cmd.Bool("reload", true, "reloading set to true or false")
@ -97,7 +98,7 @@ func main() {
log.Fatalf("invalid --enforced-host parameter: %s", err)
}
server := srv.NewServer(*addr, *grafanaAddr, *templateDir, *staticDir, uuid,
server := srv.NewServer(*addr, *grafanaAddr, *jaegerAddr, *templateDir, *staticDir, uuid,
*controllerNamespace, clusterDomain, *reload, reHost, client, k8sAPI, hc)
go func() {

View File

@ -28,7 +28,9 @@ type (
controllerNamespace string
clusterDomain string
grafana string
grafanaProxy *grafanaProxy
jaeger string
grafanaProxy *reverseProxy
jaegerProxy *reverseProxy
hc healthChecker
statCache *cache.Cache
}
@ -46,6 +48,7 @@ func (h *handler) handleIndex(w http.ResponseWriter, req *http.Request, p httpro
ControllerNamespace: h.controllerNamespace,
PathPrefix: pathPfx,
Grafana: h.grafana,
Jaeger: h.jaeger,
}
version, err := h.apiClient.Version(req.Context(), &pb.Empty{}) // TODO: remove and call /api/version from web app
@ -95,3 +98,7 @@ func (h *handler) handleProfileDownload(w http.ResponseWriter, req *http.Request
func (h *handler) handleGrafana(w http.ResponseWriter, req *http.Request, p httprouter.Params) {
h.grafanaProxy.ServeHTTP(w, req)
}
func (h *handler) handleJaeger(w http.ResponseWriter, req *http.Request, p httprouter.Params) {
h.jaegerProxy.ServeHTTP(w, req)
}

View File

@ -6,19 +6,19 @@ import (
"strings"
)
// grafanaProxy is an HTTP reverse proxy that forwards all web requests
// containing paths prefixed with "/grafana" to the grafana service. The proxy
// strips the "/grafana" prefix and rewrites the Host header before sending.
type grafanaProxy struct {
// reverseProxy is an HTTP reverse proxy that forwards all web requests
// containing paths prefixed to the corresponding service. The proxy
// strips the prefix and rewrites the Host header before sending.
type reverseProxy struct {
*httputil.ReverseProxy
}
func newGrafanaProxy(addr string) *grafanaProxy {
func newReverseProxy(addr string, prefix string) *reverseProxy {
director := func(req *http.Request) {
req.Host = addr
req.URL.Host = addr
req.URL.Scheme = "http"
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/grafana")
req.URL.Path = strings.TrimPrefix(req.URL.Path, prefix)
// the default director implementation does this, so we will too
if _, ok := req.Header["User-Agent"]; !ok {
@ -27,7 +27,7 @@ func newGrafanaProxy(addr string) *grafanaProxy {
}
}
return &grafanaProxy{
return &reverseProxy{
ReverseProxy: &httputil.ReverseProxy{Director: director},
}
}

View File

@ -52,6 +52,7 @@ type (
Error bool
ErrorMessage string
PathPrefix string
Jaeger string
Grafana string
}
@ -84,6 +85,7 @@ Please see https://linkerd.io/dns-rebinding for an explanation of what is happen
func NewServer(
addr string,
grafanaAddr string,
jaegerAddr string,
templateDir string,
staticDir string,
uuid string,
@ -115,8 +117,10 @@ func NewServer(
uuid: uuid,
controllerNamespace: controllerNamespace,
clusterDomain: clusterDomain,
grafanaProxy: newReverseProxy(grafanaAddr, "/grafana"),
jaegerProxy: newReverseProxy(jaegerAddr, ""),
grafana: grafanaAddr,
grafanaProxy: newGrafanaProxy(grafanaAddr),
jaeger: jaegerAddr,
hc: hc,
statCache: cache.New(statExpiration, statCleanupInterval),
}
@ -191,13 +195,10 @@ func NewServer(
server.router.GET("/api/resource-definition", handler.handleAPIResourceDefinition)
// grafana proxy
server.router.DELETE("/grafana/*grafanapath", handler.handleGrafana)
server.router.GET("/grafana/*grafanapath", handler.handleGrafana)
server.router.HEAD("/grafana/*grafanapath", handler.handleGrafana)
server.router.OPTIONS("/grafana/*grafanapath", handler.handleGrafana)
server.router.PATCH("/grafana/*grafanapath", handler.handleGrafana)
server.router.POST("/grafana/*grafanapath", handler.handleGrafana)
server.router.PUT("/grafana/*grafanapath", handler.handleGrafana)
server.handleAllOperationsForPath("/grafana/*grafanapath", handler.handleGrafana)
// jaeger proxy
server.handleAllOperationsForPath("/jaeger/*jaegerpath", handler.handleJaeger)
return httpServer
}
@ -243,6 +244,16 @@ func (s *Server) loadTemplate(templateFile string) (template *template.Template,
return template, err
}
func (s *Server) handleAllOperationsForPath(path string, handle httprouter.Handle) {
s.router.DELETE(path, handle)
s.router.GET(path, handle)
s.router.HEAD(path, handle)
s.router.OPTIONS(path, handle)
s.router.PATCH(path, handle)
s.router.POST(path, handle)
s.router.PUT(path, handle)
}
func safelyJoinPath(rootPath, userPath string) string {
return filepath.Join(rootPath, path.Clean("/"+userPath))
}

View File

@ -4,7 +4,8 @@
data-go-version="{{.Data.GoVersion}}"
data-controller-namespace="{{.ControllerNamespace}}"
data-uuid="{{.UUID}}"
data-grafana="{{.Grafana}}">
data-grafana="{{.Grafana}}"
data-jaeger="{{.Jaeger}}">
{{ if .Error }}
<p>Failed to call public API: {{ .ErrorMessage }}</p>
{{ end }}