From f6e75ec83a7c15e423ce21894f49841924da2987 Mon Sep 17 00:00:00 2001 From: Ivan Sim <1330522+ihcsim@users.noreply.github.com> Date: Fri, 8 Feb 2019 15:37:44 -0800 Subject: [PATCH] Add statefulsets to the dashboard and CLI (#2234) Fixes #1983 Signed-off-by: Ivan Sim --- chart/templates/base.yaml | 2 +- cli/cmd/stat.go | 12 +- cli/cmd/tap.go | 7 +- cli/cmd/testdata/install_default.golden | 2 +- cli/cmd/testdata/install_ha_output.golden | 2 +- .../install_ha_with_overrides_output.golden | 2 +- .../testdata/install_no_init_container.golden | 2 +- ...stall_no_init_container_auto_inject.golden | 2 +- cli/cmd/testdata/install_output.golden | 2 +- .../install_single_namespace_output.golden | 2 +- cli/cmd/top.go | 7 +- controller/api/public/stat_summary_test.go | 103 +- controller/api/public/top_routes_test.go | 64 + controller/api/util/api_utils.go | 10 +- controller/cmd/public-api/main.go | 2 +- controller/cmd/tap/main.go | 1 + controller/k8s/api.go | 48 + controller/k8s/api_test.go | 33 + controller/k8s/test_helper.go | 1 + grafana/dashboards/statefulset.json | 2241 +++++++++++++++++ pkg/k8s/k8s.go | 1 + web/app/js/components/Namespace.jsx | 3 +- web/app/js/components/NamespaceLanding.jsx | 3 +- web/app/js/components/NavigationResources.jsx | 1 + web/app/js/components/util/Utils.js | 3 + web/app/js/index.js | 6 + web/srv/server.go | 2 + 27 files changed, 2539 insertions(+), 25 deletions(-) create mode 100644 grafana/dashboards/statefulset.json diff --git a/chart/templates/base.yaml b/chart/templates/base.yaml index 9cc964f1eb478..dab426a67b8c1 100644 --- a/chart/templates/base.yaml +++ b/chart/templates/base.yaml @@ -29,7 +29,7 @@ metadata: {{- end}} rules: - apiGroups: ["extensions", "apps"] - resources: ["daemonsets", "deployments", "replicasets"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] verbs: ["list", "get", "watch"] - apiGroups: [""] resources: ["pods", "endpoints", "services", "replicationcontrollers"{{if not .Values.SingleNamespace}}, "namespaces"{{end}}] diff --git a/cli/cmd/stat.go b/cli/cmd/stat.go index cc5cd169433dd..416cf547e44ca 100644 --- a/cli/cmd/stat.go +++ b/cli/cmd/stat.go @@ -59,14 +59,15 @@ func newCmdStat() *cobra.Command { Examples: * deploy * deploy/my-deploy + * deploy/ po/ * ds/my-daemonset - * rc/my-replication-controller * ns/my-ns - * authority - * au/my-authority * po/mypod1 rc/my-replication-controller * po mypod1 mypod2 - * deploy/ po/ + * rc/my-replication-controller + * sts/my-statefulset + * authority + * au/my-authority * all Valid resource types include: @@ -75,9 +76,10 @@ func newCmdStat() *cobra.Command { * namespaces * pods * replicationcontrollers + * statefulsets * authorities (not supported in --from) - * services (only supported if a --from is also specified, or as a --to) * jobs (only supported as a --from or --to) + * services (only supported if a --from is also specified, or as a --to) * all (all resource types, not supported in --from or --to) This command will hide resources that have completed, such as pods that are in the Succeeded or Failed phases. diff --git a/cli/cmd/tap.go b/cli/cmd/tap.go index 42d094a659d28..333277d592a92 100644 --- a/cli/cmd/tap.go +++ b/cli/cmd/tap.go @@ -60,6 +60,8 @@ func newCmdTap() *cobra.Command { * deploy my-deploy * ds/my-daemonset * ns/my-ns + * sts + * sts/my-statefulset Valid resource types include: * daemonsets @@ -67,8 +69,9 @@ func newCmdTap() *cobra.Command { * namespaces * pods * replicationcontrollers - * services (only supported as a --to resource) - * jobs (only supported as a --to resource)`, + * statefulsets + * jobs (only supported as a --to resource) + * services (only supported as a --to resource)`, Example: ` # tap the web deployment in the default namespace linkerd tap deploy/web diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden index 4844a1f74ff42..6ab33d2821ee0 100644 --- a/cli/cmd/testdata/install_default.golden +++ b/cli/cmd/testdata/install_default.golden @@ -20,7 +20,7 @@ metadata: name: linkerd-linkerd-controller rules: - apiGroups: ["extensions", "apps"] - resources: ["daemonsets", "deployments", "replicasets"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] verbs: ["list", "get", "watch"] - apiGroups: [""] resources: ["pods", "endpoints", "services", "replicationcontrollers", "namespaces"] diff --git a/cli/cmd/testdata/install_ha_output.golden b/cli/cmd/testdata/install_ha_output.golden index d1dc46f95dc49..5a0c6cc94d837 100644 --- a/cli/cmd/testdata/install_ha_output.golden +++ b/cli/cmd/testdata/install_ha_output.golden @@ -20,7 +20,7 @@ metadata: name: linkerd-linkerd-controller rules: - apiGroups: ["extensions", "apps"] - resources: ["daemonsets", "deployments", "replicasets"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] verbs: ["list", "get", "watch"] - apiGroups: [""] resources: ["pods", "endpoints", "services", "replicationcontrollers", "namespaces"] diff --git a/cli/cmd/testdata/install_ha_with_overrides_output.golden b/cli/cmd/testdata/install_ha_with_overrides_output.golden index 802def4547f25..70b5121f0fdca 100644 --- a/cli/cmd/testdata/install_ha_with_overrides_output.golden +++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden @@ -20,7 +20,7 @@ metadata: name: linkerd-linkerd-controller rules: - apiGroups: ["extensions", "apps"] - resources: ["daemonsets", "deployments", "replicasets"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] verbs: ["list", "get", "watch"] - apiGroups: [""] resources: ["pods", "endpoints", "services", "replicationcontrollers", "namespaces"] diff --git a/cli/cmd/testdata/install_no_init_container.golden b/cli/cmd/testdata/install_no_init_container.golden index 87c581628726f..fbf854d8b697c 100644 --- a/cli/cmd/testdata/install_no_init_container.golden +++ b/cli/cmd/testdata/install_no_init_container.golden @@ -20,7 +20,7 @@ metadata: name: linkerd-linkerd-controller rules: - apiGroups: ["extensions", "apps"] - resources: ["daemonsets", "deployments", "replicasets"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] verbs: ["list", "get", "watch"] - apiGroups: [""] resources: ["pods", "endpoints", "services", "replicationcontrollers", "namespaces"] diff --git a/cli/cmd/testdata/install_no_init_container_auto_inject.golden b/cli/cmd/testdata/install_no_init_container_auto_inject.golden index c2a22edce8673..4bc911543d681 100644 --- a/cli/cmd/testdata/install_no_init_container_auto_inject.golden +++ b/cli/cmd/testdata/install_no_init_container_auto_inject.golden @@ -22,7 +22,7 @@ metadata: name: linkerd-linkerd-controller rules: - apiGroups: ["extensions", "apps"] - resources: ["daemonsets", "deployments", "replicasets"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] verbs: ["list", "get", "watch"] - apiGroups: [""] resources: ["pods", "endpoints", "services", "replicationcontrollers", "namespaces"] diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden index e5fa24875ce4e..c82774625fe22 100644 --- a/cli/cmd/testdata/install_output.golden +++ b/cli/cmd/testdata/install_output.golden @@ -22,7 +22,7 @@ metadata: name: linkerd-Namespace-controller rules: - apiGroups: ["extensions", "apps"] - resources: ["daemonsets", "deployments", "replicasets"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] verbs: ["list", "get", "watch"] - apiGroups: [""] resources: ["pods", "endpoints", "services", "replicationcontrollers", "namespaces"] diff --git a/cli/cmd/testdata/install_single_namespace_output.golden b/cli/cmd/testdata/install_single_namespace_output.golden index 3dc39ae75eac8..27aa2333a5f95 100644 --- a/cli/cmd/testdata/install_single_namespace_output.golden +++ b/cli/cmd/testdata/install_single_namespace_output.golden @@ -15,7 +15,7 @@ metadata: namespace: Namespace rules: - apiGroups: ["extensions", "apps"] - resources: ["daemonsets", "deployments", "replicasets"] + resources: ["daemonsets", "deployments", "replicasets", "statefulsets"] verbs: ["list", "get", "watch"] - apiGroups: [""] resources: ["pods", "endpoints", "services", "replicationcontrollers"] diff --git a/cli/cmd/top.go b/cli/cmd/top.go index 98e102ae15949..2a1ae4d4a6fe7 100644 --- a/cli/cmd/top.go +++ b/cli/cmd/top.go @@ -286,6 +286,8 @@ func newCmdTop() *cobra.Command { * deploy my-deploy * ds/my-daemonset * ns/my-ns + * sts + * sts/my-statefulset Valid resource types include: * daemonsets @@ -293,8 +295,9 @@ func newCmdTop() *cobra.Command { * namespaces * pods * replicationcontrollers - * services (only supported as a --to resource) - * jobs (only supported as a --to resource)`, + * statefulsets + * jobs (only supported as a --to resource), + * services (only supported as a --to resource)`, Example: ` # display traffic for the web deployment in the default namespace linkerd top deploy/web diff --git a/controller/api/public/stat_summary_test.go b/controller/api/public/stat_summary_test.go index 2b3d4abb1a1e3..50a94a7d37ee8 100644 --- a/controller/api/public/stat_summary_test.go +++ b/controller/api/public/stat_summary_test.go @@ -108,7 +108,6 @@ func testStatSummary(t *testing.T, expectations []statSumExpected) { if !proto.Equal(exp.expectedResponse.GetOk(), statOkRsp) { t.Fatalf("Expected: %+v\n Got: %+v", &exp.expectedResponse, rsp) } - } } @@ -286,6 +285,101 @@ status: testStatSummary(t, expectations) }) + t.Run("Successfully performs a query based on resource type StatefulSet", func(t *testing.T) { + expectations := []statSumExpected{ + statSumExpected{ + expectedStatRPC: expectedStatRPC{ + err: nil, + k8sConfigs: []string{` +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis + namespace: emojivoto + labels: + app: redis + linkerd.io/control-plane-ns: linkerd +spec: + replicas: 3 + serviceName: redis + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - image: redis + volumeMounts: + - name: data + mountPath: /var/lib/redis + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 10Gi +`, ` +apiVersion: v1 +kind: Pod +metadata: + name: redis-0 + namespace: emojivoto + labels: + app: redis + linkerd.io/control-plane-ns: linkerd +status: + phase: Running +`, ` +apiVersion: v1 +kind: Pod +metadata: + name: redis-1 + namespace: emojivoto + labels: + app: redis + linkerd.io/control-plane-ns: linkerd +status: + phase: Running +`, ` +apiVersion: v1 +kind: Pod +metadata: + name: redis-2 + namespace: emojivoto + labels: + app: redis + linkerd.io/control-plane-ns: linkerd +status: + phase: Running +`, + }, + mockPromResponse: prometheusMetric("redis", "statefulset", "emojivoto", "success", false), + }, + req: pb.StatSummaryRequest{ + Selector: &pb.ResourceSelection{ + Resource: &pb.Resource{ + Namespace: "emojivoto", + Type: pkgK8s.StatefulSet, + }, + }, + TimeWindow: "1m", + }, + expectedResponse: GenStatSummaryResponse("redis", pkgK8s.StatefulSet, []string{"emojivoto"}, &PodCounts{ + MeshedPods: 3, + RunningPods: 3, + FailedPods: 0, + }, true), + }, + } + + testStatSummary(t, expectations) + }) + t.Run("Queries prometheus for a specific resource if name is specified", func(t *testing.T) { expectations := []statSumExpected{ statSumExpected{ @@ -713,6 +807,13 @@ status: }, }, }, + &pb.StatTable{ + Table: &pb.StatTable_PodGroup_{ + PodGroup: &pb.StatTable_PodGroup{ + Rows: []*pb.StatTable_PodGroup_Row{}, + }, + }, + }, &pb.StatTable{ Table: &pb.StatTable_PodGroup_{ PodGroup: &pb.StatTable_PodGroup{ diff --git a/controller/api/public/top_routes_test.go b/controller/api/public/top_routes_test.go index 8aa5795fd1996..4bb833f56dae9 100644 --- a/controller/api/public/top_routes_test.go +++ b/controller/api/public/top_routes_test.go @@ -51,6 +51,36 @@ spec: containers: - image: buoyantio/booksapp:v0.0.2` +var booksStatefulsetConfig = `kind: StatefulSet +apiVersion: apps/v1 +metadata: + name: books + namespace: default +spec: + selector: + matchLabels: + app: books + template: + serviceName: books + metadata: + labels: + app: books + spec: + containers: + - image: buoyantio/booksapp:v0.0.2 + volumes: + - name: data + mountPath: /usr/src/app + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 10Gi +` + var booksServiceConfig = []string{ // service/books `apiVersion: v1 @@ -93,6 +123,7 @@ spec: var booksConfig = append(booksServiceConfig, booksDeployConfig) var booksDSConfig = append(booksServiceConfig, booksDaemonsetConfig) +var booksSSConfig = append(booksServiceConfig, booksStatefulsetConfig) type topRoutesExpected struct { expectedStatRPC @@ -292,6 +323,39 @@ func TestTopRoutes(t *testing.T) { testTopRoutes(t, expectations) }) + t.Run("Successfully performs a routes query for a statefulset", func(t *testing.T) { + routes := []string{"/a"} + counts := []uint64{123} + expectations := []topRoutesExpected{ + topRoutesExpected{ + expectedStatRPC: expectedStatRPC{ + err: nil, + mockPromResponse: routesMetric([]string{"/a"}), + expectedPrometheusQueries: []string{ + `histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{direction="inbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default", statefulset="books"}[1m])) by (le, dst, rt_route))`, + `histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{direction="inbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default", statefulset="books"}[1m])) by (le, dst, rt_route))`, + `histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{direction="inbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default", statefulset="books"}[1m])) by (le, dst, rt_route))`, + `sum(increase(route_response_total{direction="inbound", dst=~"(books.default.svc.cluster.local)(:\\d+)?", namespace="default", statefulset="books"}[1m])) by (rt_route, dst, classification)`, + }, + k8sConfigs: booksSSConfig, + }, + req: pb.TopRoutesRequest{ + Selector: &pb.ResourceSelection{ + Resource: &pb.Resource{ + Namespace: "default", + Type: pkgK8s.StatefulSet, + Name: "books", + }, + }, + TimeWindow: "1m", + }, + expectedResponse: GenTopRoutesResponse(routes, counts, false, "books"), + }, + } + + testTopRoutes(t, expectations) + }) + t.Run("Successfully performs an outbound routes query", func(t *testing.T) { routes := []string{"/a"} counts := []uint64{123} diff --git a/controller/api/util/api_utils.go b/controller/api/util/api_utils.go index 99039f3b6e59f..567e00aaa74a6 100644 --- a/controller/api/util/api_utils.go +++ b/controller/api/util/api_utils.go @@ -33,6 +33,7 @@ var ( k8s.Namespace, k8s.Pod, k8s.ReplicationController, + k8s.StatefulSet, } // ValidTapDestinations specifies resource types allowed as a tap destination: @@ -45,6 +46,7 @@ var ( k8s.Pod, k8s.ReplicationController, k8s.Service, + k8s.StatefulSet, } ) @@ -551,16 +553,16 @@ func K8sPodToPublicPod(pod v1.Pod, ownerKind string, ownerName string) pb.Pod { switch ownerKind { case k8s.Deployment: item.Owner = &pb.Pod_Deployment{Deployment: namespacedOwnerName} + case k8s.DaemonSet: + item.Owner = &pb.Pod_DaemonSet{DaemonSet: namespacedOwnerName} + case k8s.Job: + item.Owner = &pb.Pod_Job{Job: namespacedOwnerName} case k8s.ReplicaSet: item.Owner = &pb.Pod_ReplicaSet{ReplicaSet: namespacedOwnerName} case k8s.ReplicationController: item.Owner = &pb.Pod_ReplicationController{ReplicationController: namespacedOwnerName} case k8s.StatefulSet: item.Owner = &pb.Pod_StatefulSet{StatefulSet: namespacedOwnerName} - case k8s.DaemonSet: - item.Owner = &pb.Pod_DaemonSet{DaemonSet: namespacedOwnerName} - case k8s.Job: - item.Owner = &pb.Pod_Job{Job: namespacedOwnerName} } return item diff --git a/controller/cmd/public-api/main.go b/controller/cmd/public-api/main.go index ea1611897c3d4..b99195104d505 100644 --- a/controller/cmd/public-api/main.go +++ b/controller/cmd/public-api/main.go @@ -55,7 +55,7 @@ func main() { var spClient *spclient.Clientset restrictToNamespace := "" - resources := []k8s.APIResource{k8s.DS, k8s.Deploy, k8s.Pod, k8s.RC, k8s.RS, k8s.Svc} + resources := []k8s.APIResource{k8s.DS, k8s.Deploy, k8s.Pod, k8s.RC, k8s.RS, k8s.Svc, k8s.SS} if *singleNamespace { restrictToNamespace = *controllerNamespace diff --git a/controller/cmd/tap/main.go b/controller/cmd/tap/main.go index 4f0187682820f..31db0b6d52f47 100644 --- a/controller/cmd/tap/main.go +++ b/controller/cmd/tap/main.go @@ -38,6 +38,7 @@ func main() { nil, restrictToNamespace, k8s.DS, + k8s.SS, k8s.Deploy, k8s.Pod, k8s.RC, diff --git a/controller/k8s/api.go b/controller/k8s/api.go index 2b1f2608da7f5..ff275722e3a26 100644 --- a/controller/k8s/api.go +++ b/controller/k8s/api.go @@ -45,6 +45,7 @@ const ( RC RS SP + SS Svc ) @@ -61,6 +62,7 @@ type API struct { rc coreinformers.ReplicationControllerInformer rs appv1beta2informers.ReplicaSetInformer sp spinformers.ServiceProfileInformer + ss appv1informers.StatefulSetInformer svc coreinformers.ServiceInformer syncChecks []cache.InformerSynced @@ -128,6 +130,9 @@ func NewAPI(k8sClient kubernetes.Interface, spClient spclient.Interface, namespa case SP: api.sp = spSharedInformers.Linkerd().V1alpha1().ServiceProfiles() api.syncChecks = append(api.syncChecks, api.sp.Informer().HasSynced) + case SS: + api.ss = sharedInformers.Apps().V1().StatefulSets() + api.syncChecks = append(api.syncChecks, api.ss.Informer().HasSynced) case Svc: api.svc = sharedInformers.Core().V1().Services() api.syncChecks = append(api.syncChecks, api.svc.Informer().HasSynced) @@ -168,6 +173,14 @@ func (api *API) DS() appv1informers.DaemonSetInformer { return api.ds } +// SS provides access to a shared informer and lister for Statefulsets. +func (api *API) SS() appv1informers.StatefulSetInformer { + if api.ss == nil { + panic("SS informer not configured") + } + return api.ss +} + // RS provides access to a shared informer and lister for ReplicaSets. func (api *API) RS() appv1beta2informers.ReplicaSetInformer { if api.rs == nil { @@ -250,6 +263,8 @@ func (api *API) GetObjects(namespace, restype, name string) ([]runtime.Object, e return api.getRCs(namespace, name) case k8s.Service: return api.getServices(namespace, name) + case k8s.StatefulSet: + return api.getStatefulsets(namespace, name) default: // TODO: ReplicaSet return nil, status.Errorf(codes.Unimplemented, "unimplemented resource type: %s", restype) @@ -313,6 +328,10 @@ func (api *API) GetPodsFor(obj runtime.Object, includeFailed bool) ([]*apiv1.Pod namespace = typed.Namespace selector = labels.Set(typed.Spec.Selector).AsSelector() + case *appsv1.StatefulSet: + namespace = typed.Namespace + selector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector() + case *apiv1.Pod: // Special case for pods: // GetPodsFor a pod should just return the pod itself @@ -367,6 +386,9 @@ func GetNameAndNamespaceOf(obj runtime.Object) (string, string, error) { case *apiv1.Service: return typed.Name, typed.Namespace, nil + case *appsv1.StatefulSet: + return typed.Name, typed.Namespace, nil + case *apiv1.Pod: return typed.Name, typed.Namespace, nil @@ -536,6 +558,32 @@ func (api *API) getDaemonsets(namespace, name string) ([]runtime.Object, error) return objects, nil } +func (api *API) getStatefulsets(namespace, name string) ([]runtime.Object, error) { + var err error + var statefulsets []*appsv1.StatefulSet + + if namespace == "" { + statefulsets, err = api.SS().Lister().List(labels.Everything()) + } else if name == "" { + statefulsets, err = api.SS().Lister().StatefulSets(namespace).List(labels.Everything()) + } else { + var ss *appsv1.StatefulSet + ss, err = api.SS().Lister().StatefulSets(namespace).Get(name) + statefulsets = []*appsv1.StatefulSet{ss} + } + + if err != nil { + return nil, err + } + + objects := []runtime.Object{} + for _, ss := range statefulsets { + objects = append(objects, ss) + } + + return objects, nil +} + func (api *API) getServices(namespace, name string) ([]runtime.Object, error) { services, err := api.GetServices(namespace, name) diff --git a/controller/k8s/api_test.go b/controller/k8s/api_test.go index 34ea96af04efa..05ec2bb588ee8 100644 --- a/controller/k8s/api_test.go +++ b/controller/k8s/api_test.go @@ -163,6 +163,39 @@ apiVersion: apps/v1 kind: DaemonSet metadata: name: my-ds + namespace: not-my-ns`, + }, + }, + getObjectsExpected{ + err: nil, + namespace: "", + resType: k8s.StatefulSet, + name: "", + k8sResResults: []string{` +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: my-deploy + namespace: my-ns`, + }, + }, + getObjectsExpected{ + err: nil, + namespace: "my-ns", + resType: k8s.StatefulSet, + name: "my-ss", + k8sResResults: []string{` +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: my-ss + namespace: my-ns`, + }, + k8sResMisc: []string{` +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: my-ss namespace: not-my-ns`, }, }, diff --git a/controller/k8s/test_helper.go b/controller/k8s/test_helper.go index 68345693b0770..0cf0188c476b0 100644 --- a/controller/k8s/test_helper.go +++ b/controller/k8s/test_helper.go @@ -43,6 +43,7 @@ func NewFakeAPI(namespace string, configs ...string) (*API, error) { CM, Deploy, DS, + SS, Endpoint, Pod, RC, diff --git a/grafana/dashboards/statefulset.json b/grafana/dashboards/statefulset.json new file mode 100644 index 0000000000000..401f98665e9cc --- /dev/null +++ b/grafana/dashboards/statefulset.json @@ -0,0 +1,2241 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 1, + "id": null, + "iteration": 1531763681685, + "links": [], + "panels": [ + { + "content": "
\n  \n sts/$statefulset\n
", + "gridPos": { + "h": 2.4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 20, + "links": [], + "mode": "html", + "title": "", + "transparent": true, + "type": "text" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#d44a3a", + "rgba(237, 129, 40, 0.89)", + "#299c46" + ], + "datasource": "prometheus", + "decimals": null, + "format": "percentunit", + "gauge": { + "maxValue": 1, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 8, + "x": 0, + "y": 2.4 + }, + "id": 5, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(irate(response_total{classification=\"success\", namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}[30s])) / sum(irate(response_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}[30s]))", + "format": "time_series", + "instant": false, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": "0.9,.99", + "title": "SUCCESS RATE", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "prometheus", + "decimals": null, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 8, + "x": 8, + "y": 2.4 + }, + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": " RPS", + "postfixFontSize": "100%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(irate(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}[30s]))", + "format": "time_series", + "instant": false, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": "", + "title": "REQUEST RATE", + "transparent": true, + "type": "singlestat", + "valueFontSize": "100%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "prometheus", + "decimals": null, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 16, + "y": 2.4 + }, + "id": 11, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "100%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "count(count(request_total{dst_namespace=\"$namespace\", statefulset!=\"\", dst_statefulset!=\"\", dst_statefulset=\"$statefulset\", direction=\"outbound\"}) by (namespace, statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": "", + "title": "INBOUND STATEFULSETS", + "transparent": true, + "type": "singlestat", + "valueFontSize": "100%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 20, + "y": 2.4 + }, + "id": 15, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "100%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "count(count(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\"}) by (namespace, dst_statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": "", + "title": "OUTBOUND STATEFULSETS", + "transparent": true, + "type": "singlestat", + "valueFontSize": "100%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "content": "
\n INBOUND TRAFFIC\n
", + "gridPos": { + "h": 2.2, + "w": 24, + "x": 0, + "y": 6.4 + }, + "id": 17, + "links": [], + "mode": "html", + "title": "", + "transparent": true, + "type": "text" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 8.600000000000001 + }, + "id": 67, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(response_total{classification=\"success\", namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}[30s])) by (statefulset) / sum(irate(response_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}[30s])) by (statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "sts/{{statefulset}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "SUCCESS RATE", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "percentunit", + "label": "", + "logBase": 1, + "max": "1", + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 8.600000000000001 + }, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\", tls=\"true\"}[30s])) by (statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "๐Ÿ”’sts/{{statefulset}}", + "refId": "A" + }, + { + "expr": "sum(irate(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\", tls!=\"true\"}[30s])) by (statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "sts/{{statefulset}}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "REQUEST RATE", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "rps", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 8.600000000000001 + }, + "id": 68, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}[30s])) by (le, statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "p50 sts/{{statefulset}}", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}[30s])) by (le, statefulset))", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "p95 sts/{{statefulset}}", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}[30s])) by (le, statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "p99 sts/{{statefulset}}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "LATENCY", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "ms", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15.600000000000001 + }, + "id": 148, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 16.6 + }, + "id": 167, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "tcp_close_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\",errno!=\"\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{peer}} {{errno}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "TCP CONNECTION FAILURES", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "none", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 16.6 + }, + "id": 168, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "tcp_open_connections{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{peer}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "TCP CONNECTIONS OPEN", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "datasource": "prometheus", + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 16.6 + }, + "heatmap": {}, + "highlightCards": true, + "id": 169, + "legend": { + "show": false + }, + "links": [], + "targets": [ + { + "expr": "tcp_connection_duration_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"inbound\"}", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "TCP CONNECTION DURATION", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "dtdurationms", + "logBase": 1, + "max": null, + "min": "0", + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + } + ], + "title": "Inbound TCP Metrics", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16.6 + }, + "id": 152, + "panels": [], + "title": "", + "type": "row" + }, + { + "content": "
\n INBOUND STATEFULSETS\n
", + "gridPos": { + "h": 2.2, + "w": 24, + "x": 0, + "y": 17.6 + }, + "id": 76, + "links": [], + "mode": "html", + "title": "", + "transparent": true, + "type": "text" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 19.8 + }, + "id": 59, + "panels": [ + { + "content": "
\n  \n sts/$inbound\n
", + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 20.8 + }, + "id": 39, + "links": [], + "mode": "html", + "title": "", + "transparent": true, + "type": "text" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 22.8 + }, + "id": 36, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(response_total{classification=\"success\", statefulset!=\"\", statefulset=\"$inbound\", dst_namespace=\"$namespace\", dst_statefulset=\"$statefulset\", direction=\"outbound\"}[30s])) by (statefulset, pod) / sum(irate(response_total{statefulset!=\"\", statefulset=\"$inbound\", dst_namespace=\"$namespace\", dst_statefulset=\"$statefulset\", direction=\"outbound\"}[30s])) by (statefulset, pod)", + "format": "time_series", + "instant": false, + "intervalFactor": 1, + "legendFormat": "po/{{pod}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "SUCCESS RATE", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "percentunit", + "label": null, + "logBase": 1, + "max": "1", + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 22.8 + }, + "id": 22, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(request_total{statefulset!=\"\", statefulset=\"$inbound\", dst_namespace=\"$namespace\", dst_statefulset=\"$statefulset\", direction=\"outbound\", tls=\"true\"}[30s])) by (statefulset, pod)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "๐Ÿ”’po/{{pod}}", + "refId": "A" + }, + { + "expr": "sum(irate(request_total{statefulset!=\"\", statefulset=\"$inbound\", dst_namespace=\"$namespace\", dst_statefulset=\"$statefulset\", direction=\"outbound\", tls!=\"true\"}[30s])) by (statefulset, pod)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "po/{{pod}}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "REQUEST RATE", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "rps", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 22.8 + }, + "id": 29, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{statefulset!=\"\", statefulset=\"$inbound\", dst_namespace=\"$namespace\", dst_statefulset=\"$statefulset\", direction=\"outbound\"}[30s])) by (le, statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "P50 sts/{{statefulset}}", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{statefulset!=\"\", statefulset=\"$inbound\", dst_namespace=\"$namespace\", dst_statefulset=\"$statefulset\", direction=\"outbound\"}[30s])) by (le, statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "P95 sts/{{statefulset}}", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{statefulset!=\"\", statefulset=\"$inbound\", dst_namespace=\"$namespace\", dst_statefulset=\"$statefulset\", direction=\"outbound\"}[30s])) by (le, statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "P99 sts/{{statefulset}}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "LATENCY", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "repeat": "inbound", + "title": "sts/$inbound", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 20.8 + }, + "id": 34, + "panels": [], + "repeat": null, + "title": "", + "type": "row" + }, + { + "content": "
\n OUTBOUND TRAFFIC\n
", + "gridPos": { + "h": 2.2, + "w": 24, + "x": 0, + "y": 21.8 + }, + "id": 32, + "links": [], + "mode": "html", + "title": "", + "transparent": true, + "type": "text" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 24 + }, + "id": 77, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(response_total{classification=\"success\", namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\"}[30s])) by (dst_statefulset) / sum(irate(response_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\"}[30s])) by (dst_statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "sts/{{dst_statefulset}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "SUCCESS RATE", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "percentunit", + "label": "", + "logBase": 1, + "max": "1", + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 24 + }, + "id": 78, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\", tls=\"true\"}[30s])) by (dst_statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "๐Ÿ”’sts/{{dst_statefulset}}", + "refId": "A" + }, + { + "expr": "sum(irate(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\", tls!=\"true\"}[30s])) by (dst_statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "sts/{{dst_statefulset}}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "REQUEST RATE", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "rps", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 24 + }, + "id": 79, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\"}[30s])) by (le, dst_statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "P95 sts/{{dst_statefulset}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "P95 LATENCY", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 31 + }, + "id": 154, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 41 + }, + "id": 157, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "tcp_close_total{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\",errno!=\"\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{peer}} {{errno}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "TCP CONNECTION FAILURES", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "none", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 41 + }, + "id": 166, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "tcp_open_connections{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{peer}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "TCP CONNECTIONS OPEN", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "datasource": "prometheus", + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 41 + }, + "heatmap": {}, + "highlightCards": true, + "id": 160, + "legend": { + "show": false + }, + "links": [], + "targets": [ + { + "expr": "tcp_connection_duration_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", direction=\"outbound\"}", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "TCP CONNECTION DURATION", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "dtdurationms", + "logBase": 1, + "max": null, + "min": "0", + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + } + ], + "title": "Outbound TCP Metrics", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 32 + }, + "id": 156, + "panels": [], + "title": "", + "type": "row" + }, + { + "content": "
\n OUTBOUND STATEFULSETS\n
", + "gridPos": { + "h": 2.2, + "w": 24, + "x": 0, + "y": 33 + }, + "id": 80, + "links": [], + "mode": "html", + "title": "", + "transparent": true, + "type": "text" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 35.2 + }, + "id": 27, + "panels": [ + { + "content": "
\n  \n sts/$outbound\n
", + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 36.2 + }, + "id": 40, + "links": [], + "mode": "html", + "title": "", + "transparent": true, + "type": "text" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 38.2 + }, + "id": 28, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(response_total{classification=\"success\", namespace=\"$namespace\", statefulset=\"$statefulset\", dst_statefulset=\"$outbound\", direction=\"outbound\"}[30s])) by (dst_statefulset) / sum(irate(response_total{namespace=\"$namespace\", statefulset=\"$statefulset\", dst_statefulset=\"$outbound\", direction=\"outbound\"}[30s])) by (dst_statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "sts/{{dst_statefulset}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "SUCCESS RATE", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "percentunit", + "label": null, + "logBase": 1, + "max": "1", + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 38.2 + }, + "id": 35, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\", dst_statefulset=\"$outbound\", direction=\"outbound\", tls=\"true\"}[30s])) by (dst_statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "๐Ÿ”’sts/{{dst_statefulset}}", + "refId": "A" + }, + { + "expr": "sum(irate(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\", dst_statefulset=\"$outbound\", direction=\"outbound\", tls!=\"true\"}[30s])) by (dst_statefulset)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "sts/{{dst_statefulset}}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "REQUEST RATE", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "rps", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 38.2 + }, + "id": 41, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", dst_statefulset=\"$outbound\", direction=\"outbound\"}[30s])) by (le, dst_statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "P50 sts/{{dst_statefulset}}", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", dst_statefulset=\"$outbound\", direction=\"outbound\"}[30s])) by (le, dst_statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "P95 sts/{{dst_statefulset}}", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{namespace=\"$namespace\", statefulset=\"$statefulset\", dst_statefulset=\"$outbound\", direction=\"outbound\"}[30s])) by (le, dst_statefulset))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "P99 sts/{{dst_statefulset}}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "LATENCY", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "repeat": "outbound", + "title": "sts/$outbound", + "type": "row" + }, + { + "content": "
\n
\n \n
\n
\n
\n
\n", + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 36.2 + }, + "height": "1px", + "id": 171, + "links": [], + "mode": "html", + "title": "", + "transparent": true, + "type": "text" + } + ], + "refresh": "5s", + "schemaVersion": 16, + "style": "dark", + "tags": [ + "linkerd" + ], + "templating": { + "list": [ + { + "allValue": null, + "current": {}, + "datasource": "prometheus", + "hide": 0, + "includeAll": false, + "label": "Namespace", + "multi": false, + "name": "namespace", + "options": [], + "query": "label_values(request_total{statefulset!=\"\"}, namespace)", + "refresh": 2, + "regex": "", + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "prometheus", + "hide": 0, + "includeAll": false, + "label": "StatefulSet", + "multi": false, + "name": "statefulset", + "options": [], + "query": "label_values(request_total{namespace=\"$namespace\"}, statefulset)", + "refresh": 2, + "regex": "", + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "prometheus", + "hide": 2, + "includeAll": true, + "label": null, + "multi": false, + "name": "inbound", + "options": [], + "query": "label_values(request_total{dst_namespace=\"$namespace\", dst_statefulset=\"$statefulset\"}, statefulset)", + "refresh": 2, + "regex": "", + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "prometheus", + "hide": 2, + "includeAll": true, + "label": null, + "multi": false, + "name": "outbound", + "options": [], + "query": "label_values(request_total{namespace=\"$namespace\", statefulset=\"$statefulset\"}, dst_statefulset)", + "refresh": 2, + "regex": "", + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Linkerd StatefulSet", + "uid": "statefulset", + "version": 1 +} diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go index 8b919257df041..238f8e724562f 100644 --- a/pkg/k8s/k8s.go +++ b/pkg/k8s/k8s.go @@ -46,6 +46,7 @@ var AllResources = []string{ var StatAllResourceTypes = []string{ // TODO: add Namespace here to decrease queries from the web process DaemonSet, + StatefulSet, Deployment, ReplicationController, Pod, diff --git a/web/app/js/components/Namespace.jsx b/web/app/js/components/Namespace.jsx index a2a311349efcd..37235d89ad42f 100644 --- a/web/app/js/components/Namespace.jsx +++ b/web/app/js/components/Namespace.jsx @@ -143,8 +143,9 @@ class Namespaces extends React.Component { } {this.renderResourceSection("deployment", metrics.deployment)} {this.renderResourceSection("daemonset", metrics.daemonset)} - {this.renderResourceSection("replicationcontroller", metrics.replicationcontroller)} {this.renderResourceSection("pod", metrics.pod)} + {this.renderResourceSection("replicationcontroller", metrics.replicationcontroller)} + {this.renderResourceSection("statefulset", metrics.statefulset)} {this.renderResourceSection("authority", metrics.authority)} )} diff --git a/web/app/js/components/NamespaceLanding.jsx b/web/app/js/components/NamespaceLanding.jsx index 255b79f6f1e00..4a0d4ee81c8a3 100644 --- a/web/app/js/components/NamespaceLanding.jsx +++ b/web/app/js/components/NamespaceLanding.jsx @@ -156,8 +156,9 @@ class NamespaceLanding extends React.Component { {this.renderResourceSection("deployment", metrics.deployment)} {this.renderResourceSection("daemonset", metrics.daemonset)} - {this.renderResourceSection("replicationcontroller", metrics.replicationcontroller)} {this.renderResourceSection("pod", metrics.pod)} + {this.renderResourceSection("replicationcontroller", metrics.replicationcontroller)} + {this.renderResourceSection("statefulset", metrics.statefulset)} {this.renderResourceSection("authority", metrics.authority)} ); diff --git a/web/app/js/components/NavigationResources.jsx b/web/app/js/components/NavigationResources.jsx index c8631c32dc800..e9c2312ef3657 100644 --- a/web/app/js/components/NavigationResources.jsx +++ b/web/app/js/components/NavigationResources.jsx @@ -73,6 +73,7 @@ class NavigationResourcesBase extends React.Component { + ); } diff --git a/web/app/js/components/util/Utils.js b/web/app/js/components/util/Utils.js index 53a28123f10b7..a42dd4b46555a 100644 --- a/web/app/js/components/util/Utils.js +++ b/web/app/js/components/util/Utils.js @@ -129,7 +129,10 @@ export const friendlyTitle = singularOrPluralResource => { titleCase = _startCase("replication controller"); } else if (resource === "daemonset") { titleCase = _startCase("daemon set"); + } else if (resource === "statefulset") { + titleCase = _startCase("stateful set"); } + let titles = { singular: titleCase }; if (resource === "authority") { titles.plural = "Authorities"; diff --git a/web/app/js/index.js b/web/app/js/index.js index 4fd2f70fdf511..116b86db96f74 100644 --- a/web/app/js/index.js +++ b/web/app/js/index.js @@ -65,6 +65,9 @@ let applicationHtml = ( } /> + } /> } /> @@ -89,6 +92,9 @@ let applicationHtml = ( } /> + } /> } /> diff --git a/web/srv/server.go b/web/srv/server.go index c7edf8e09b69e..4a3547ba4d00b 100644 --- a/web/srv/server.go +++ b/web/srv/server.go @@ -95,12 +95,14 @@ func NewServer( server.router.GET("/namespaces", handler.handleIndex) server.router.GET("/namespaces/:namespace", handler.handleIndex) server.router.GET("/daemonsets", handler.handleIndex) + server.router.GET("/statefulsets", handler.handleIndex) server.router.GET("/deployments", handler.handleIndex) server.router.GET("/replicationcontrollers", handler.handleIndex) server.router.GET("/pods", handler.handleIndex) server.router.GET("/authorities", handler.handleIndex) server.router.GET("/namespaces/:namespace/pods/:pod", handler.handleIndex) server.router.GET("/namespaces/:namespace/daemonsets/:daemonset", handler.handleIndex) + server.router.GET("/namespaces/:namespace/statefulsets/:statefulset", handler.handleIndex) server.router.GET("/namespaces/:namespace/deployments/:deployment", handler.handleIndex) server.router.GET("/namespaces/:namespace/replicationcontrollers/:replicationcontroller", handler.handleIndex) server.router.GET("/tap", handler.handleIndex)