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:
Alex Leong 2020-01-16 10:57:15 -08:00 committed by GitHub
parent 65aad4e373
commit cabe2a13e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 715 additions and 0 deletions

201
test/tracing/testdata/emojivoto.yaml vendored Normal file
View File

@ -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: {}
---

193
test/tracing/testdata/ingress.yaml vendored Normal file
View File

@ -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

147
test/tracing/testdata/tracing.yaml vendored Normal file
View File

@ -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

View File

@ -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
}