Skip to content

Commit

Permalink
Inject sidecar in properly annotated pods (jaegertracing#58)
Browse files Browse the repository at this point in the history
* Inject sidecar in properly annotated pods

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
  • Loading branch information
jpkrohling authored and andream16 committed Oct 17, 2018
1 parent 97ff4d6 commit a6f14b7
Show file tree
Hide file tree
Showing 15 changed files with 516 additions and 67 deletions.
29 changes: 29 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,35 @@ oc get routes

NOTE: make sure to use `https` with the hostname/port you get from the command above, otherwise you'll see a message like: "Application is not available".

== Auto injection of Jaeger Agent sidecars

The operator can also inject Jaeger Agent sidecars in `Deployment` workloads, provided that the deployment has the annotation `inject-jaeger-agent` with a suitable value. The values can be either `"true"` (as string), or the Jaeger instance name, as returned by `kubectl get jaegers`. When `"true"` is used, there should be exactly *one* Jaeger instance for the same namespace as the deployment, otherwise, the operator can't figure out automatically which Jaeger instance to use.

The following snippet shows a simple application that will get a sidecar injected, with the Jaeger Agent pointing to the single Jaeger instance available in the same namespace:

[source,yaml]
----
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
annotations:
inject-jaeger-agent: "true" # <1>
spec:
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: acme/myapp:myversion
----
<1> Either `"true"` (as string) or the Jaeger instance name

== Agent as DaemonSet

By default, the Operator expects the agents to be deployed as sidecars to the target applications. This is convenient for several purposes, like in a multi-tenant scenario or to have better load balancing, but there are scenarios where it's desirable to install the agent as a `DaemonSet`. In that case, specify the Agent's strategy to `DaemonSet`, as follows:
Expand Down
18 changes: 14 additions & 4 deletions pkg/cmd/start/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package start

import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
Expand All @@ -12,7 +13,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/jaegertracing/jaeger-operator/pkg/stub"
"github.com/jaegertracing/jaeger-operator/pkg/apis/io/v1alpha1"
stub "github.com/jaegertracing/jaeger-operator/pkg/stub"
"github.com/jaegertracing/jaeger-operator/pkg/version"
)

Expand Down Expand Up @@ -85,13 +87,16 @@ func start(cmd *cobra.Command, args []string) {

sdk.ExposeMetricsPort()

resyncPeriod := 5
namespace, err := k8sutil.GetWatchNamespace()
if err != nil {
logrus.Fatalf("failed to get watch namespace: %v", err)
}
resyncPeriod := 5
logrus.Infof("Watching %s, %s, %s, %d", resource, kind, namespace, resyncPeriod)
sdk.Watch(resource, kind, namespace, resyncPeriod)

apiVersion := fmt.Sprintf("%s/%s", v1alpha1.SchemeGroupVersion.Group, v1alpha1.SchemeGroupVersion.Version)
watch(apiVersion, "Jaeger", namespace, resyncPeriod)
watch("apps/v1", "Deployment", namespace, resyncPeriod)

sdk.Handle(stub.NewHandler())
go sdk.Run(ctx)

Expand All @@ -101,3 +106,8 @@ func start(cmd *cobra.Command, args []string) {
logrus.Info("Jaeger Operator finished")
}
}

func watch(apiVersion, kind, namespace string, resyncPeriod int) {
logrus.Infof("Watching %s, %s, %s, %d", apiVersion, kind, namespace, resyncPeriod)
sdk.Watch(apiVersion, kind, namespace, resyncPeriod)
}
2 changes: 1 addition & 1 deletion pkg/controller/production.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (c *productionController) Create() []sdk.Object {

components := []sdk.Object{
collector.Get(),
agent.InjectSidecar(*query.Get()),
query.Get(),
}

ds := agent.Get()
Expand Down
19 changes: 0 additions & 19 deletions pkg/controller/production_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/operator-framework/operator-sdk/pkg/sdk"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/jaegertracing/jaeger-operator/pkg/apis/io/v1alpha1"
Expand Down Expand Up @@ -43,24 +42,6 @@ func TestUpdateProductionDeployment(t *testing.T) {
assert.Len(t, c.Update(), 0)
}

func TestAgentIsInjectedIntoQuery(t *testing.T) {
name := "TestAgentIsInjectedIntoQuery"
c := newProductionController(context.TODO(), v1alpha1.NewJaeger(name))
objs := c.Create()
var dep *appsv1.Deployment

for _, obj := range objs {
switch obj.(type) {
case *appsv1.Deployment:
dep = obj.(*appsv1.Deployment)
break
}
}

assert.Len(t, dep.Spec.Template.Spec.Containers, 2)
assert.Contains(t, dep.Spec.Template.Spec.Containers[1].Image, "jaeger-agent")
}

func TestOptionsArePassed(t *testing.T) {
jaeger := &v1alpha1.Jaeger{
TypeMeta: metav1.TypeMeta{
Expand Down
33 changes: 0 additions & 33 deletions pkg/deployment/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,39 +137,6 @@ func (a *Agent) Get() *appsv1.DaemonSet {
}
}

// InjectSidecar adds a new container to the deployment, containing Jaeger's agent
func (a *Agent) InjectSidecar(dep appsv1.Deployment) *appsv1.Deployment {
sidecar := v1.Container{
Image: a.jaeger.Spec.Agent.Image,
Name: agent,
Args: []string{fmt.Sprintf(format, service.GetNameForCollectorService(a.jaeger))},
Ports: []v1.ContainerPort{
{
ContainerPort: zkCompactTrftPort,
Name: zkCompactTrft,
Protocol: v1.ProtocolUDP,
},
{
ContainerPort: configRestPort,
Name: configRest,
},
{
ContainerPort: jgCompactTrftPort,
Name: jgCompactTrft,
Protocol: v1.ProtocolUDP,
},
{
ContainerPort: jgBinaryTrftPort,
Name: jgBinaryTrft,
Protocol: v1.ProtocolUDP,
},
},
}

dep.Spec.Template.Spec.Containers = append(dep.Spec.Template.Spec.Containers, sidecar)
return &dep
}

func (a *Agent) selector() map[string]string {
return map[string]string{app: jaeger, jaeger: a.jaeger.Name, jaegerComponent: "agent-daemonset"}
}
16 changes: 7 additions & 9 deletions pkg/deployment/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"github.com/jaegertracing/jaeger-operator/pkg/apis/io/v1alpha1"
)

const testNewAgent = "TestNewAgent"

func setDefaults() {
viper.SetDefault(versionKey, versionValue)
viper.SetDefault(agentImageKey, "jaegertracing/jaeger-agent")
Expand All @@ -26,7 +24,7 @@ func reset() {
}

func TestNewAgent(t *testing.T) {
jaeger := v1alpha1.NewJaeger(testNewAgent)
jaeger := v1alpha1.NewJaeger("TestNewAgent")
NewAgent(jaeger)
assert.Contains(t, jaeger.Spec.Agent.Image, agent)
}
Expand All @@ -36,33 +34,33 @@ func TestDefaultAgentImage(t *testing.T) {
viper.Set(versionKey, "123")
defer reset()

jaeger := v1alpha1.NewJaeger(testNewAgent)
jaeger := v1alpha1.NewJaeger("TestDefaultAgentImage")
NewAgent(jaeger)
assert.Equal(t, "org/custom-agent-image:123", jaeger.Spec.Agent.Image)
}

func TestGetDefaultAgentDeployment(t *testing.T) {
jaeger := v1alpha1.NewJaeger(testNewAgent)
jaeger := v1alpha1.NewJaeger("TestGetDefaultAgentDeployment")
agent := NewAgent(jaeger)
assert.Nil(t, agent.Get()) // it's not implemented yet
}

func TestGetSicedarDeployment(t *testing.T) {
jaeger := v1alpha1.NewJaeger(testNewAgent)
func TestGetSidecarDeployment(t *testing.T) {
jaeger := v1alpha1.NewJaeger("TestGetSidecarDeployment")
jaeger.Spec.Agent.Strategy = "sidecar"
agent := NewAgent(jaeger)
assert.Nil(t, agent.Get()) // it's not implemented yet
}

func TestGetDaemonSetDeployment(t *testing.T) {
jaeger := v1alpha1.NewJaeger(testNewAgent)
jaeger := v1alpha1.NewJaeger("TestGetDaemonSetDeployment")
jaeger.Spec.Agent.Strategy = daemonSetStrategy
agent := NewAgent(jaeger)
assert.NotNil(t, agent.Get())
}

func TestInjectSidecar(t *testing.T) {
jaeger := v1alpha1.NewJaeger(testNewAgent)
jaeger := v1alpha1.NewJaeger("TestInjectSidecar")
dep := NewQuery(jaeger).Get()
agent := NewAgent(jaeger)

Expand Down
7 changes: 7 additions & 0 deletions pkg/deployment/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ func (q *Query) Get() *appsv1.Deployment {
annotations := map[string]string{
prometheusScrapeKey: prometheusScrapeValue,
prometheusPortKey: "16686",

// note that we are explicitly using a string here, not the value from `inject.Annotation`
// this has two reasons:
// 1) as it is, it would cause a circular dependency, so, we'd have to extract that constant to somewhere else
// 2) this specific string is part of the "public API" of the operator: we should not change
// it at will. So, we leave this configured just like any other application would
"inject-jaeger-agent": q.jaeger.Name,
}

return &appsv1.Deployment{
Expand Down
98 changes: 98 additions & 0 deletions pkg/inject/sidecar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package inject

import (
"fmt"
"strings"

"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"

"github.com/jaegertracing/jaeger-operator/pkg/apis/io/v1alpha1"
"github.com/jaegertracing/jaeger-operator/pkg/deployment"
"github.com/jaegertracing/jaeger-operator/pkg/service"
)

var (
// Annotation is the annotation name to look for when deciding whether or not to inject
Annotation = "inject-jaeger-agent"
)

// Sidecar adds a new container to the deployment, connecting to the given jaeger instance
func Sidecar(dep *appsv1.Deployment, jaeger *v1alpha1.Jaeger) {
deployment.NewAgent(jaeger) // we need some initialization from that, but we don't actually need the agent's instance here

if jaeger == nil || dep.Annotations[Annotation] != jaeger.Name {
logrus.Debugf("Skipping sidecar injection for deployment %v", dep.Name)
} else {
logrus.Debugf("Injecting sidecar for pod %v", dep.Name)
dep.Spec.Template.Spec.Containers = append(dep.Spec.Template.Spec.Containers, container(jaeger))
}
}

// Needed determines whether a pod needs to get a sidecar injected or not
func Needed(dep *appsv1.Deployment) bool {
if dep.Annotations[Annotation] == "" {
logrus.Debugf("Not needed, annotation not present for %v", dep.Name)
return false
}

// this pod is annotated, it should have a sidecar
// but does it already have one?
for _, container := range dep.Spec.Template.Spec.Containers {
if container.Name == "jaeger-agent" { // we don't labels/annotations on containers, so, we rely on its name
return false
}
}

return true
}

// Select a suitable Jaeger from the JaegerList for the given Pod, or nil of none is suitable
func Select(target *appsv1.Deployment, availableJaegerPods *v1alpha1.JaegerList) *v1alpha1.Jaeger {
jaegerName := target.Annotations[Annotation]
if strings.ToLower(jaegerName) == "true" && len(availableJaegerPods.Items) == 1 {
// if there's only *one* jaeger within this namespace, then that's what
// we'll use -- otherwise, we should just not inject, as it's not clear which
// jaeger instance to use!
// first, we make sure we normalize the name:
jaeger := &availableJaegerPods.Items[0]
target.Annotations[Annotation] = jaeger.Name
return jaeger
}

for _, p := range availableJaegerPods.Items {
if p.Name == jaegerName {
// matched the name!
return &p
}
}
return nil
}

func container(jaeger *v1alpha1.Jaeger) v1.Container {
args := append(jaeger.Spec.Agent.Options.ToArgs(), fmt.Sprintf("--collector.host-port=%s:14267", service.GetNameForCollectorService(jaeger)))
return v1.Container{
Image: jaeger.Spec.Agent.Image,
Name: "jaeger-agent",
Args: args,
Ports: []v1.ContainerPort{
{
ContainerPort: 5775,
Name: "zk-compact-trft",
},
{
ContainerPort: 5778,
Name: "config-rest",
},
{
ContainerPort: 6831,
Name: "jg-compact-trft",
},
{
ContainerPort: 6832,
Name: "jg-binary-trft",
},
},
}
}
Loading

0 comments on commit a6f14b7

Please sign in to comment.