mirror of https://github.com/linkerd/linkerd2.git
Add distributed tracing integration test (#3920)
This integration test roughly follows the [Linkerd guide to distributed tracing](https://linkerd.io/2019/10/07/a-guide-to-distributed-tracing-with-linkerd/). We deploy the tracing components (oc-collector and jaeger), emojivoto, and nginx as an ingress to do span initiation. We then watch the jaeger API and check that a trace is eventually created that includes traces from all of the data plane components: nginx, linkerd-proxy, web, voting, and emoji. Signed-off-by: Alex Leong <alex@buoyant.io>
This commit is contained in:
parent
65aad4e373
commit
cabe2a13e6
|
@ -0,0 +1,201 @@
|
|||
---
|
||||
kind: ServiceAccount
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: emoji
|
||||
---
|
||||
kind: ServiceAccount
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: voting
|
||||
---
|
||||
kind: ServiceAccount
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: web
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: emoji
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: emoji-svc
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: emoji-svc
|
||||
annotations:
|
||||
config.linkerd.io/trace-collector: "oc-collector.___TRACING_NS___:55678"
|
||||
spec:
|
||||
serviceAccountName: emoji
|
||||
containers:
|
||||
- env:
|
||||
- name: GRPC_PORT
|
||||
value: "8080"
|
||||
- name: OC_AGENT_HOST
|
||||
value: "oc-collector.___TRACING_NS___:55678"
|
||||
image: buoyantio/emojivoto-emoji-svc:v8-tracing
|
||||
name: emoji-svc
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: grpc
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
status: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: emoji-svc
|
||||
spec:
|
||||
selector:
|
||||
app: emoji-svc
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: grpc
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: voting
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: voting-svc
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: voting-svc
|
||||
annotations:
|
||||
config.linkerd.io/trace-collector: "oc-collector.___TRACING_NS___:55678"
|
||||
spec:
|
||||
serviceAccountName: voting
|
||||
containers:
|
||||
- env:
|
||||
- name: GRPC_PORT
|
||||
value: "8080"
|
||||
- name: OC_AGENT_HOST
|
||||
value: "oc-collector.___TRACING_NS___:55678"
|
||||
image: buoyantio/emojivoto-voting-svc:v8-tracing
|
||||
name: voting-svc
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: grpc
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
status: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: voting-svc
|
||||
spec:
|
||||
selector:
|
||||
app: voting-svc
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: grpc
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: web
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: web-svc
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: web-svc
|
||||
annotations:
|
||||
config.linkerd.io/trace-collector: "oc-collector.___TRACING_NS___:55678"
|
||||
spec:
|
||||
serviceAccountName: web
|
||||
containers:
|
||||
- env:
|
||||
- name: WEB_PORT
|
||||
value: "80"
|
||||
- name: OC_AGENT_HOST
|
||||
value: "oc-collector.___TRACING_NS___:55678"
|
||||
- name: EMOJISVC_HOST
|
||||
value: emoji-svc:8080
|
||||
- name: VOTINGSVC_HOST
|
||||
value: voting-svc:8080
|
||||
- name: INDEX_BUNDLE
|
||||
value: dist/index_bundle.js
|
||||
image: buoyantio/emojivoto-web:v8-tracing
|
||||
name: web-svc
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
status: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: web-svc
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
app: web-svc
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: vote-bot
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: vote-bot
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: vote-bot
|
||||
annotations:
|
||||
config.linkerd.io/trace-collector: "oc-collector.___TRACING_NS___:55678"
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- emojivoto-vote-bot
|
||||
env:
|
||||
- name: WEB_HOST
|
||||
value: nginx-ingress:80
|
||||
image: buoyantio/emojivoto-web:v8
|
||||
name: vote-bot
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
status: {}
|
||||
---
|
|
@ -0,0 +1,193 @@
|
|||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: nginx-ingress
|
||||
labels:
|
||||
app: nginx-ingress
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- endpoints
|
||||
- nodes
|
||||
- pods
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- list
|
||||
- watch
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- update
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: nginx-ingress
|
||||
labels:
|
||||
app: nginx-ingress
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: nginx-ingress
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: default
|
||||
namespace: ___INGRESS_NAMESPACE___
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: web-nginx
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
proxy_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:80;
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: web-svc
|
||||
servicePort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: nginx-ingress-controller
|
||||
labels:
|
||||
app: nginx-ingress
|
||||
data:
|
||||
ssl-redirect: "false"
|
||||
enable-opentracing: "true"
|
||||
enable-vts-status: "false"
|
||||
zipkin-collector-host: oc-collector.___TRACING_NS___
|
||||
zipkin-sample-rate: "0.5"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-ingress
|
||||
name: nginx-ingress
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx-ingress
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-ingress
|
||||
annotations:
|
||||
config.linkerd.io/trace-collector: "oc-collector.___TRACING_NS___:55678"
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- /nginx-ingress-controller
|
||||
- --default-backend-service=
|
||||
- --ingress-class=nginx
|
||||
- --configmap=___INGRESS_NAMESPACE___/nginx-ingress-controller
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.22.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10254
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
name: nginx-ingress-controller
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
name: https
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10254
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
resources: {}
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
drop:
|
||||
- ALL
|
||||
procMount: Default
|
||||
runAsUser: 33
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx-ingress
|
||||
labels:
|
||||
app: nginx-ingress
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: http
|
||||
selector:
|
||||
app: nginx-ingress
|
|
@ -0,0 +1,147 @@
|
|||
---
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: jaeger
|
||||
labels:
|
||||
app: jaeger
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: jaeger
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: jaeger
|
||||
spec:
|
||||
dnsPolicy: ClusterFirst
|
||||
containers:
|
||||
- name: jaeger
|
||||
image: jaegertracing/all-in-one:1.8
|
||||
ports:
|
||||
- name: collection
|
||||
containerPort: 14268
|
||||
- name: ui
|
||||
containerPort: 16686
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: jaeger
|
||||
labels:
|
||||
app: jaeger
|
||||
spec:
|
||||
selector:
|
||||
app: jaeger
|
||||
ports:
|
||||
- name: collection
|
||||
port: 14268
|
||||
- name: ui
|
||||
port: 16686
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: oc-collector-conf
|
||||
labels:
|
||||
app: opencensus
|
||||
component: oc-collector-conf
|
||||
data:
|
||||
oc-collector-config: |
|
||||
receivers:
|
||||
opencensus:
|
||||
port: 55678
|
||||
zipkin:
|
||||
port: 9411
|
||||
queued-exporters:
|
||||
jaeger-all-in-one:
|
||||
num-workers: 4
|
||||
queue-size: 100
|
||||
retry-on-failure: true
|
||||
sender-type: jaeger-thrift-http
|
||||
jaeger-thrift-http:
|
||||
collector-endpoint: http://jaeger:14268/api/traces
|
||||
timeout: 5s
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: oc-collector
|
||||
labels:
|
||||
app: opencesus
|
||||
component: oc-collector
|
||||
spec:
|
||||
ports:
|
||||
- name: opencensus
|
||||
port: 55678
|
||||
protocol: TCP
|
||||
targetPort: 55678
|
||||
- name: zipkin
|
||||
port: 9411
|
||||
protocol: TCP
|
||||
targetPort: 9411
|
||||
selector:
|
||||
component: oc-collector
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: oc-collector
|
||||
labels:
|
||||
app: opencensus
|
||||
component: oc-collector
|
||||
spec:
|
||||
minReadySeconds: 5
|
||||
progressDeadlineSeconds: 120
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: opencensus
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/path: "/metrics"
|
||||
prometheus.io/port: "8888"
|
||||
prometheus.io/scrape: "true"
|
||||
labels:
|
||||
app: opencensus
|
||||
component: oc-collector
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- "/occollector_linux"
|
||||
- "--config=/conf/oc-collector-config.yaml"
|
||||
env:
|
||||
- name: GOGC
|
||||
value: "80"
|
||||
image: omnition/opencensus-collector:0.1.10
|
||||
name: oc-collector
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 400Mi
|
||||
ports:
|
||||
- containerPort: 55678
|
||||
- containerPort: 9411
|
||||
volumeMounts:
|
||||
- name: oc-collector-config-vol
|
||||
mountPath: /conf
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 13133
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 13133
|
||||
volumes:
|
||||
- configMap:
|
||||
name: oc-collector-conf
|
||||
items:
|
||||
- key: oc-collector-config
|
||||
path: oc-collector-config.yaml
|
||||
name: oc-collector-config-vol
|
|
@ -0,0 +1,174 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/linkerd/linkerd2/testutil"
|
||||
)
|
||||
|
||||
type (
|
||||
traces struct {
|
||||
Data []trace `json:"data"`
|
||||
}
|
||||
|
||||
trace struct {
|
||||
Processes map[string]process `json:"processes"`
|
||||
}
|
||||
|
||||
process struct {
|
||||
ServiceName string `json:"serviceName"`
|
||||
}
|
||||
)
|
||||
|
||||
//////////////////////
|
||||
/// TEST SETUP ///
|
||||
//////////////////////
|
||||
|
||||
var TestHelper *testutil.TestHelper
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
TestHelper = testutil.NewTestHelper()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
/// TEST EXECUTION ///
|
||||
//////////////////////
|
||||
|
||||
func TestTracing(t *testing.T) {
|
||||
|
||||
// Tracing Components
|
||||
out, stderr, err := TestHelper.LinkerdRun("inject", "testdata/tracing.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("linkerd inject command failed\n%s\n%s", out, stderr)
|
||||
}
|
||||
|
||||
tracingNs := TestHelper.GetTestNamespace("tracing")
|
||||
err = TestHelper.CreateDataPlaneNamespaceIfNotExists(tracingNs, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create %s namespace: %s", tracingNs, err)
|
||||
}
|
||||
out, err = TestHelper.KubectlApply(out, tracingNs)
|
||||
if err != nil {
|
||||
t.Fatalf("kubectl apply command failed\n%s", out)
|
||||
}
|
||||
|
||||
// Emojivoto components
|
||||
emojivotoNs := TestHelper.GetTestNamespace("emojivoto")
|
||||
err = TestHelper.CreateDataPlaneNamespaceIfNotExists(emojivotoNs, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create %s namespace: %s", emojivotoNs, err)
|
||||
}
|
||||
|
||||
emojivotoYaml, err := testutil.ReadFile("testdata/emojivoto.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read emojivoto yaml\n%s\n", err)
|
||||
}
|
||||
emojivotoYaml = strings.ReplaceAll(emojivotoYaml, "___TRACING_NS___", tracingNs)
|
||||
out, stderr, err = TestHelper.PipeToLinkerdRun(emojivotoYaml, "inject", "-")
|
||||
if err != nil {
|
||||
t.Fatalf("linkerd inject command failed\n%s\n%s", out, stderr)
|
||||
}
|
||||
|
||||
out, err = TestHelper.KubectlApply(out, emojivotoNs)
|
||||
if err != nil {
|
||||
t.Fatalf("kubectl apply command failed\n%s", out)
|
||||
}
|
||||
|
||||
// Ingress components
|
||||
// Ingress must run in the same namespace as the service it routes to (web)
|
||||
ingressNs := emojivotoNs
|
||||
err = TestHelper.CreateDataPlaneNamespaceIfNotExists(ingressNs, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create %s namespace: %s", ingressNs, err)
|
||||
}
|
||||
|
||||
ingressYaml, err := testutil.ReadFile("testdata/ingress.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read ingress yaml\n%s\n", err)
|
||||
}
|
||||
ingressYaml = strings.ReplaceAll(ingressYaml, "___INGRESS_NAMESPACE___", ingressNs)
|
||||
ingressYaml = strings.ReplaceAll(ingressYaml, "___TRACING_NS___", tracingNs)
|
||||
out, stderr, err = TestHelper.PipeToLinkerdRun(ingressYaml, "inject", "-")
|
||||
if err != nil {
|
||||
t.Fatalf("linkerd inject command failed\n%s\n%s", out, stderr)
|
||||
}
|
||||
|
||||
out, err = TestHelper.KubectlApply(out, ingressNs)
|
||||
if err != nil {
|
||||
t.Fatalf("kubectl apply command failed\n%s", out)
|
||||
}
|
||||
|
||||
// wait for deployments to start
|
||||
for ns, deploy := range map[string]string{
|
||||
emojivotoNs: "vote-bot",
|
||||
emojivotoNs: "web",
|
||||
emojivotoNs: "emoji",
|
||||
emojivotoNs: "voting",
|
||||
ingressNs: "nginx-ingress",
|
||||
tracingNs: "oc-collector",
|
||||
tracingNs: "jaeger",
|
||||
} {
|
||||
if err := TestHelper.CheckPods(ns, deploy, 1); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := TestHelper.CheckDeployment(ns, deploy, 1); err != nil {
|
||||
t.Error(fmt.Errorf("Error validating deployment [%s]:\n%s", deploy, err))
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("expect full trace", func(t *testing.T) {
|
||||
|
||||
url, err := TestHelper.URLFor(tracingNs, "jaeger", 16686)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = TestHelper.RetryFor(120*time.Second, func() error {
|
||||
tracesJSON, err := TestHelper.HTTPGetURL(url + "/api/traces?lookback=1h&service=nginx")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
traces := traces{}
|
||||
|
||||
err = json.Unmarshal([]byte(tracesJSON), &traces)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
processes := []string{"nginx", "web", "emoji", "voting", "linkerd-proxy"}
|
||||
if !hasTraceWithProcesses(&traces, processes) {
|
||||
return fmt.Errorf("No trace found with processes: %s", processes)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func hasTraceWithProcesses(traces *traces, ps []string) bool {
|
||||
for _, trace := range traces.Data {
|
||||
if containsProcesses(trace, ps) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsProcesses(trace trace, ps []string) bool {
|
||||
toFind := make(map[string]struct{})
|
||||
for _, p := range ps {
|
||||
toFind[p] = struct{}{}
|
||||
}
|
||||
for _, p := range trace.Processes {
|
||||
delete(toFind, p.ServiceName)
|
||||
}
|
||||
return len(toFind) == 0
|
||||
}
|
Loading…
Reference in New Issue