Skip to content

Commit

Permalink
feat(cmd/delete): Initial implementation for delete command
Browse files Browse the repository at this point in the history
Signed-off-by: Lorenzo Fontana <lo@linux.com>
  • Loading branch information
fntlnz committed Nov 22, 2018
1 parent 20c6f71 commit 79625b3
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 55 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ To consider this project (ready) the goals are:

- [x] basic program run and attach
- [ ] list command to list running traces - command: `kubectl trace ls`
- [x] delete running traces
- [ ] run without attach
- [ ] attach command to attach only - command: `kubectl trace attach <program>`
- allow sending signals (probably requires a TTY), so that bpftrace commands can be notified to stop by the user before deletion and give back results
Expand Down
58 changes: 58 additions & 0 deletions cmd/kubectl-trace/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"github.com/fntlnz/kubectl-trace/pkg/tracejob"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

var deleteCmd = &cobra.Command{
Use: "delete TRACEID",
Short: "",
Long: "",
Run: delete,
}

func delete(cmd *cobra.Command, args []string) {
log, _ := zap.NewProduction()
defer log.Sync()

uuid := args[0]
if len(args) == 0 {
log.Fatal("TRACEID not provided")
}
kubeconfig := viper.GetString("kubeconfig")
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)

if err != nil {
log.Fatal("cannot create kubernetes client from provider KUBECONFIG", zap.Error(err))
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Fatal("cannot create kubernetes config from provider KUBECONFIG", zap.Error(err))
}

jobsClient := clientset.BatchV1().Jobs(namespace)

tc := &tracejob.TraceJobClient{
JobClient: jobsClient,
ConfigClient: clientset.CoreV1().ConfigMaps(namespace),
}

tj := tracejob.TraceJob{
ID: uuid,
}

err = tc.DeleteJob(tj)

if err != nil {
log.Fatal("error deleting trace execution from cluster", zap.Error(err))
}

log.Info("trace execution deleted")

}
1 change: 1 addition & 0 deletions cmd/kubectl-trace/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func init() {
viper.BindPFlag("kubeconfig", rootCmd.PersistentFlags().Lookup("kubeconfig"))
viper.BindEnv("kubeconfig", "KUBECONFIG")
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(deleteCmd)
}

// initConfig reads in config file and ENV variables if set.
Expand Down
37 changes: 20 additions & 17 deletions cmd/kubectl-trace/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,24 @@ func init() {
}

func run(cmd *cobra.Command, args []string) {
logger, _ := zap.NewProduction()
defer logger.Sync()
log, _ := zap.NewProduction()
defer log.Sync()

if len(programfile) > 0 {
b, err := ioutil.ReadFile(programfile)
if err != nil {
logger.Fatal("error opening program file", zap.Error(err))
log.Fatal("error opening program file", zap.Error(err))
}
program = string(b)
}
if len(program) == 0 {
logger.Fatal("program not provided")
log.Fatal("program not provided")
}

node := ""
if len(args) == 0 {
logger.Fatal("node not provided")
log.Fatal("node not provided")
}
node = args[0]
node := args[0]

ctx := context.Background()
ctx = signals.WithStandardSignals(ctx)
Expand All @@ -71,32 +70,36 @@ func run(cmd *cobra.Command, args []string) {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)

if err != nil {
logger.Fatal("cannot create kubernetes client from provider KUBECONFIG", zap.Error(err))
log.Fatal("cannot create kubernetes client from provider KUBECONFIG", zap.Error(err))
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
logger.Fatal("cannot create kubernetes config from provider KUBECONFIG", zap.Error(err))
log.Fatal("cannot create kubernetes config from provider KUBECONFIG", zap.Error(err))
}

jobsClient := clientset.BatchV1().Jobs(namespace)

juid := uuid.NewUUID()
tj := &tracejob.TraceJob{
Name: fmt.Sprintf("kubectl-trace-%s", string(juid)),
Namespace: namespace,
ID: string(juid),
Hostname: node,
tc := &tracejob.TraceJobClient{
JobClient: jobsClient,
ConfigClient: clientset.CoreV1().ConfigMaps(namespace),
}
job, err := tj.CreateJob(program)

tj := tracejob.TraceJob{
Name: fmt.Sprintf("kubectl-trace-%s", string(juid)),
Namespace: namespace,
ID: string(juid),
Hostname: node,
Program: program,
}
job, err := tc.CreateJob(tj)
if err != nil {
logger.Fatal("cannot create kubernetes job client", zap.Error(err))
log.Fatal("cannot create kubernetes job client", zap.Error(err))
}

a := attacher.NewAttacher(clientset.CoreV1(), config)
a.WithLogger(logger)
a.WithLogger(log)
a.WithContext(ctx)

a.AttachJob(job.Name, job.Namespace)
Expand Down
107 changes: 69 additions & 38 deletions pkg/tracejob/job.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,102 @@
package tracejob

import (
"fmt"

batchv1 "k8s.io/api/batch/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
batchv1typed "k8s.io/client-go/kubernetes/typed/batch/v1"
corev1typed "k8s.io/client-go/kubernetes/typed/core/v1"
)

type TraceJob struct {
Name string
ID string
Namespace string
Hostname string
type TraceJobClient struct {
JobClient batchv1typed.JobInterface
ConfigClient corev1typed.ConfigMapInterface
}

type TraceJob struct {
Name string
ID string
Namespace string
Hostname string
Program string
}

func (t *TraceJobClient) DeleteJob(nj TraceJob) error {
selectorOptions := metav1.ListOptions{
LabelSelector: fmt.Sprintf("fntlnz.wtf/kubectl-trace-id=%s", nj.ID),
}
jl, err := t.JobClient.List(selectorOptions)

if err != nil {
return err
}

for _, j := range jl.Items {
err := t.JobClient.Delete(j.Name, nil)
if err != nil {
return err
}
}

cl, err := t.ConfigClient.List(selectorOptions)

if err != nil {
return err
}

for _, c := range cl.Items {
err := t.ConfigClient.Delete(c.Name, nil)
if err != nil {
return err
}
}
return nil
}

// todo(fntlnz): deal with programs that needs the user to send a signal to complete,
// like how the hist() function does
// Will likely need to allocate a TTY for this one thing.
func (t *TraceJob) CreateJob(program string) (*batchv1.Job, error) {
func (t *TraceJobClient) CreateJob(nj TraceJob) (*batchv1.Job, error) {
bpfTraceCmd := []string{
"bpftrace",
"/programs/program.bt",
}

cm := &apiv1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: t.Name,
Namespace: t.Namespace,
commonMeta := metav1.ObjectMeta{
Name: nj.Name,
Namespace: nj.Namespace,
Labels: map[string]string{
"fntlnz.wtf/kubectl-trace": nj.Name,
"fntlnz.wtf/kubectl-trace-id": nj.ID,
},
Annotations: map[string]string{
"fntlnz.wtf/kubectl-trace": nj.Name,
"fntlnz.wtf/kubectl-trace-id": nj.ID,
},
}

cm := &apiv1.ConfigMap{
ObjectMeta: commonMeta,
Data: map[string]string{
"program.bt": program,
"program.bt": nj.Program,
},
}

job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: t.Name,
Labels: map[string]string{
"fntlnz.wtf/kubectl-trace": t.Name,
"fntlnz.wtf/kubectl-trace-id": t.ID,
},
Annotations: map[string]string{
"fntlnz.wtf/kubectl-trace": t.Name,
"fntlnz.wtf/kubectl-trace-id": t.ID,
},
},
ObjectMeta: commonMeta,
Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: int32Ptr(5),
Parallelism: int32Ptr(1),
Completions: int32Ptr(1),
ActiveDeadlineSeconds: int64Ptr(100), // TODO(fntlnz): allow canceling from kubectl and increase this,
BackoffLimit: int32Ptr(1),
// This is why your tracing job is being killed after 100 seconds,
// someone should work on it to make it configurable and let it run
// indefinitely by default.
ActiveDeadlineSeconds: int64Ptr(100), // TODO(fntlnz): allow canceling from kubectl and increase this,
BackoffLimit: int32Ptr(1),
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: t.Name,
Labels: map[string]string{
"fntlnz.wtf/kubectl-trace": t.Name,
"fntlnz.wtf/kubectl-trace-id": t.ID,
},
Annotations: map[string]string{
"fntlnz.wtf/kubectl-trace": t.Name,
"fntlnz.wtf/kubectl-trace-id": t.ID,
},
},
ObjectMeta: commonMeta,
Spec: apiv1.PodSpec{
Volumes: []apiv1.Volume{
apiv1.Volume{
Expand Down Expand Up @@ -97,8 +128,8 @@ func (t *TraceJob) CreateJob(program string) (*batchv1.Job, error) {
},
Containers: []apiv1.Container{
apiv1.Container{
Name: t.Name,
Image: "quay.io/fntlnz/kubectl-trace-bpftrace:master",
Name: nj.Name,
Image: "quay.io/fntlnz/kubectl-trace-bpftrace:master", //TODO(fntlnz): yes this should be configurable!
Command: bpfTraceCmd,
VolumeMounts: []apiv1.VolumeMount{
apiv1.VolumeMount{
Expand Down Expand Up @@ -132,7 +163,7 @@ func (t *TraceJob) CreateJob(program string) (*batchv1.Job, error) {
apiv1.NodeSelectorRequirement{
Key: "kubernetes.io/hostname",
Operator: apiv1.NodeSelectorOpIn,
Values: []string{t.Hostname},
Values: []string{nj.Hostname},
},
},
},
Expand Down

0 comments on commit 79625b3

Please sign in to comment.