Skip to content

Commit ca4c379

Browse files
committed
feat(tracerunner): trace runner initial implementation
Signed-off-by: Lorenzo Fontana <[email protected]>
1 parent 7b55816 commit ca4c379

File tree

6 files changed

+235
-10
lines changed

6 files changed

+235
-10
lines changed

cmd/trace-runner/root.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/fntlnz/kubectl-trace/pkg/cmd"
7+
"github.com/spf13/pflag"
8+
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
9+
)
10+
11+
func main() {
12+
flags := pflag.NewFlagSet("trace-runner", pflag.ExitOnError)
13+
pflag.CommandLine = flags
14+
15+
root := cmd.NewTraceRunnerCommand()
16+
if err := root.Execute(); err != nil {
17+
os.Exit(1)
18+
}
19+
}

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ module github.com/fntlnz/kubectl-trace
33
require (
44
cloud.google.com/go v0.34.0 // indirect
55
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
6+
github.com/davecgh/go-spew v1.1.1
67
github.com/docker/distribution v2.6.2+incompatible // indirect
78
github.com/docker/docker v0.7.3-0.20181124105010-0b7cb16dde4a // indirect
89
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 // indirect
910
github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a // indirect
1011
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
1112
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
13+
github.com/fntlnz/mountinfo v0.0.0-20171106231217-40cb42681fad
1214
github.com/ghodss/yaml v1.0.0 // indirect
1315
github.com/go-openapi/spec v0.17.2 // indirect
1416
github.com/gogo/protobuf v1.1.1 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7Vpz
2020
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
2121
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
2222
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
23+
github.com/fntlnz/mountinfo v0.0.0-20171106231217-40cb42681fad h1:7dkG+DBBIETkv0nraI5oMvN4M5X3i75q7xq68eIq5Ag=
24+
github.com/fntlnz/mountinfo v0.0.0-20171106231217-40cb42681fad/go.mod h1:OJmEqKcMeJq0teE8CysGMs/5Ulch9FogT/MmOzE1U9o=
2325
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
2426
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
2527
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=

pkg/cmd/tracerunner.go

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package cmd
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
"io"
7+
"math/rand"
8+
"os"
9+
"os/exec"
10+
"path"
11+
"path/filepath"
12+
"strings"
13+
"syscall"
14+
15+
"github.com/fntlnz/mountinfo"
16+
"github.com/spf13/cobra"
17+
"golang.org/x/sys/unix"
18+
)
19+
20+
const runFolder = "/var/run"
21+
22+
type TraceRunnerOptions struct {
23+
podUID string
24+
containerName string
25+
inPod bool
26+
programPath string
27+
bpftraceBinaryPath string
28+
}
29+
30+
func NewTraceRunnerOptions() *TraceRunnerOptions {
31+
return &TraceRunnerOptions{}
32+
}
33+
34+
func NewTraceRunnerCommand() *cobra.Command {
35+
o := NewTraceRunnerOptions()
36+
cmd := &cobra.Command{
37+
PreRunE: func(c *cobra.Command, args []string) error {
38+
return o.Validate(c, args)
39+
},
40+
RunE: func(c *cobra.Command, args []string) error {
41+
if err := o.Complete(c, args); err != nil {
42+
return err
43+
}
44+
if err := o.Run(); err != nil {
45+
fmt.Fprintln(os.Stdout, err.Error())
46+
return nil
47+
}
48+
return nil
49+
},
50+
}
51+
52+
cmd.Flags().StringVarP(&o.containerName, "container", "c", o.containerName, "Specify the container")
53+
cmd.Flags().StringVarP(&o.podUID, "poduid", "p", o.podUID, "Specify the pod UID")
54+
cmd.Flags().StringVarP(&o.programPath, "program", "f", "program.bt", "Specify the bpftrace program path")
55+
cmd.Flags().StringVarP(&o.bpftraceBinaryPath, "bpftracebinary", "b", "/bin/bpftrace", "Specify the bpftrace binary path")
56+
cmd.Flags().BoolVar(&o.inPod, "inpod", false, "Wether or not run this bpftrace in a pod's container process namespace")
57+
return cmd
58+
}
59+
60+
func (o *TraceRunnerOptions) Validate(cmd *cobra.Command, args []string) error {
61+
// TODO(fntlnz): do some more meaningful validation here, for now just checking if they are there
62+
if o.inPod == true && (len(o.containerName) == 0 || len(o.podUID) == 0) {
63+
return fmt.Errorf("poduid and container must be specified when inpod=true")
64+
}
65+
return nil
66+
}
67+
68+
func (o *TraceRunnerOptions) Complete(cmd *cobra.Command, args []string) error {
69+
return nil
70+
}
71+
72+
func (o *TraceRunnerOptions) Run() error {
73+
if o.inPod == false {
74+
c := exec.Command(o.bpftraceBinaryPath, o.programPath)
75+
c.Stdout = os.Stdout
76+
c.Stdin = os.Stdin
77+
c.Stderr = os.Stderr
78+
return c.Run()
79+
}
80+
81+
pid, err := findPidByPodContainer(o.podUID, o.containerName)
82+
if err != nil {
83+
return err
84+
}
85+
if pid == nil {
86+
return fmt.Errorf("pid not found")
87+
}
88+
if len(*pid) == 0 {
89+
return fmt.Errorf("invalid pid found")
90+
}
91+
92+
// pid found, enter its process namespace
93+
pidns := path.Join("/proc", *pid, "/ns/pid")
94+
pidnsfd, err := syscall.Open(pidns, syscall.O_RDONLY, 0666)
95+
if err != nil {
96+
return fmt.Errorf("error retrieving process namespace %s %v", pidns, err)
97+
}
98+
defer syscall.Close(pidnsfd)
99+
syscall.RawSyscall(unix.SYS_SETNS, uintptr(pidnsfd), 0, 0)
100+
101+
rootfs := path.Join("/proc", *pid, "root")
102+
bpftracebinaryName, err := temporaryFileName("bpftrace")
103+
if err != nil {
104+
return err
105+
}
106+
temporaryProgramName := fmt.Sprintf("%s-%s", bpftracebinaryName, "program.bt")
107+
108+
binaryPathProcRootfs := path.Join(rootfs, bpftracebinaryName)
109+
if err := copyFile(o.bpftraceBinaryPath, binaryPathProcRootfs, 0755); err != nil {
110+
return err
111+
}
112+
113+
programPathProcRootfs := path.Join(rootfs, temporaryProgramName)
114+
if err := copyFile(o.programPath, programPathProcRootfs, 0644); err != nil {
115+
return err
116+
}
117+
118+
if err := syscall.Chroot(rootfs); err != nil {
119+
os.Remove(binaryPathProcRootfs)
120+
return err
121+
}
122+
123+
defer os.Remove(bpftracebinaryName)
124+
125+
c := exec.Command(bpftracebinaryName, temporaryProgramName)
126+
127+
c.Stdout = os.Stdout
128+
c.Stdin = os.Stdin
129+
c.Stderr = os.Stderr
130+
131+
return c.Run()
132+
}
133+
134+
func copyFile(src, dest string, mode os.FileMode) error {
135+
in, err := os.Open(src)
136+
if err != nil {
137+
return fmt.Errorf("bpftrace binary not found in host: %v", err)
138+
}
139+
defer in.Close()
140+
141+
out, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
142+
143+
if err != nil {
144+
return fmt.Errorf("unable to create file in destination: %v", err)
145+
}
146+
defer out.Close()
147+
148+
if _, err = io.Copy(out, in); err != nil {
149+
return fmt.Errorf("unable to copy file to destination: %v", err)
150+
}
151+
152+
err = out.Sync()
153+
154+
if err != nil {
155+
return err
156+
}
157+
return nil
158+
}
159+
160+
func findPidByPodContainer(podUID, containerName string) (*string, error) {
161+
d, err := os.Open("/proc")
162+
163+
if err != nil {
164+
return nil, err
165+
}
166+
167+
defer d.Close()
168+
169+
for {
170+
dirs, err := d.Readdir(10)
171+
if err == io.EOF {
172+
break
173+
}
174+
if err != nil {
175+
return nil, err
176+
}
177+
178+
for _, di := range dirs {
179+
if !di.IsDir() {
180+
continue
181+
}
182+
dname := di.Name()
183+
if dname[0] < '0' || dname[0] > '9' {
184+
continue
185+
}
186+
187+
mi, err := mountinfo.GetMountInfo(path.Join("/proc", dname, "mountinfo"))
188+
if err != nil {
189+
continue
190+
}
191+
192+
for _, m := range mi {
193+
root := m.Root
194+
if strings.Contains(root, podUID) && strings.Contains(root, containerName) {
195+
return &dname, nil
196+
}
197+
}
198+
}
199+
}
200+
201+
return nil, fmt.Errorf("no process found for specified pod and container")
202+
}
203+
204+
func temporaryFileName(prefix string) (string, error) {
205+
randBytes := make([]byte, 16)
206+
rand.Read(randBytes)
207+
return filepath.Join(runFolder, prefix+hex.EncodeToString(randBytes)), nil
208+
}

pkg/tracejob/job.go

+3-10
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,10 @@ func (t *TraceJobClient) DeleteJobs(nf TraceJobFilter) error {
170170
return nil
171171
}
172172

173-
// todo(fntlnz): deal with programs that needs the user to send a signal to complete,
174-
// like how the hist() function does
175-
// Will likely need to allocate a TTY for this one thing.
176173
func (t *TraceJobClient) CreateJob(nj TraceJob) (*batchv1.Job, error) {
177174
bpfTraceCmd := []string{
178-
"bpftrace",
179-
"/programs/program.bt",
175+
"/bin/trace-runner",
176+
"--program=/programs/program.bt",
180177
}
181178

182179
commonMeta := metav1.ObjectMeta{
@@ -205,11 +202,7 @@ func (t *TraceJobClient) CreateJob(nj TraceJob) (*batchv1.Job, error) {
205202
TTLSecondsAfterFinished: int32Ptr(5),
206203
Parallelism: int32Ptr(1),
207204
Completions: int32Ptr(1),
208-
// This is why your tracing job is being killed after 100 seconds,
209-
// someone should work on it to make it configurable and let it run
210-
// indefinitely by default.
211-
ActiveDeadlineSeconds: int64Ptr(100), // TODO(fntlnz): allow canceling from kubectl and increase this,
212-
BackoffLimit: int32Ptr(1),
205+
BackoffLimit: int32Ptr(1),
213206
Template: apiv1.PodTemplateSpec{
214207
ObjectMeta: commonMeta,
215208
Spec: apiv1.PodSpec{

program.bt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uretprobe:/caturday:"main.counterValue" { printf("%d\n", retval) }'

0 commit comments

Comments
 (0)