Skip to content

Commit ba8d6f6

Browse files
author
Gianluca Arbezzano
committed
feat(kprofefe): bootstrap daemon
Fixed #7 This PR contains a first iteration for kprofefe the daemon that runs inside a kubernetes cluster as a cronjob and keeps collecting and persisting profiles in profefe. Signed-off-by: Gianluca Arbezzano <[email protected]>
1 parent c2490e2 commit ba8d6f6

File tree

6 files changed

+187
-2
lines changed

6 files changed

+187
-2
lines changed

.goreleaser.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ builds:
88
- id: "kubectl-profefe"
99
main: ./cmd/kubectl-profefe
1010
binary: kubectl-profefe
11+
env:
12+
- CGO_ENABLED=0
13+
- id: "kprofefe"
14+
main: ./cmd/kprofefe
15+
binary: kprofefe
16+
env:
17+
- CGO_ENABLED=0
1118
archives:
1219
- replacements:
1320
darwin: Darwin
@@ -25,3 +32,14 @@ changelog:
2532
exclude:
2633
- '^docs:'
2734
- '^test:'
35+
# .goreleaser.yml
36+
dockers:
37+
-
38+
binaries:
39+
- kprofefe
40+
goos: linux
41+
goarch: amd64
42+
image_templates:
43+
- "profefe/kprofefe:latest"
44+
- "profefe/kprofefe:v{{ .Tag }}"
45+
dockerfile: Dockerfile.gorelease

Dockerfile.gorelease

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM alpine
2+
3+
COPY kprofefe /
4+
5+
ENTRYPOINT ["/kprofefe"]

cmd/kprofefe/main.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/gianarb/kube-profefe/pkg/cmd"
7+
"k8s.io/cli-runtime/pkg/genericclioptions"
8+
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
9+
)
10+
11+
func main() {
12+
rootCmd := cmd.NewKProfefeCmd(genericclioptions.IOStreams{
13+
In: os.Stdin,
14+
Out: os.Stdout,
15+
ErrOut: os.Stderr,
16+
})
17+
18+
rootCmd.Execute()
19+
}

pkg/cmd/capture.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ profefe.`)
168168

169169
func writeProfiles(ctx context.Context, pClient *profefe.Client, profiles map[pprofutil.Profile]*profile.Profile, target corev1.Pod) error {
170170
for profileType, profile := range profiles {
171-
profefeType := profefe.NewProfileTypeFromString(profile.PeriodType.Type)
171+
profefeType := profefe.NewProfileTypeFromString(profileType.String())
172172
if profefeType == profefe.UnknownProfile {
173173
println("unknown profile type: :" + profile.PeriodType.Type)
174174
continue

pkg/cmd/kprofefe.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"os"
8+
9+
"github.com/gianarb/kube-profefe/pkg/kubeutil"
10+
"github.com/gianarb/kube-profefe/pkg/pprofutil"
11+
"github.com/gianarb/kube-profefe/pkg/profefe"
12+
"github.com/spf13/cobra"
13+
"github.com/spf13/pflag"
14+
corev1 "k8s.io/api/core/v1"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/cli-runtime/pkg/genericclioptions"
17+
"k8s.io/client-go/kubernetes"
18+
"k8s.io/client-go/rest"
19+
)
20+
21+
func NewKProfefeCmd(streams genericclioptions.IOStreams) *cobra.Command {
22+
flags := pflag.NewFlagSet("kubectl-profefe", pflag.ExitOnError)
23+
pflag.CommandLine = flags
24+
25+
kubeConfigFlags := genericclioptions.NewConfigFlags(false)
26+
kubeResouceBuilderFlags := genericclioptions.NewResourceBuilderFlags()
27+
28+
cmd := &cobra.Command{
29+
Use: "kprofefe",
30+
Short: "kprofefe collects profiles from inside a kubernetes cluster",
31+
PersistentPreRun: func(c *cobra.Command, args []string) {
32+
c.SetOutput(streams.ErrOut)
33+
},
34+
Run: func(cmd *cobra.Command, args []string) {
35+
var config *rest.Config
36+
var err error
37+
38+
config, err = rest.InClusterConfig()
39+
if err != nil {
40+
config, err = kubeConfigFlags.ToRESTConfig()
41+
}
42+
if err != nil {
43+
panic(err)
44+
}
45+
if config == nil {
46+
panic("woww")
47+
}
48+
// creates the clientset
49+
clientset, err := kubernetes.NewForConfig(config)
50+
if err != nil {
51+
panic(err.Error())
52+
}
53+
54+
// Contains the pool of pods that we need to gather profiles from
55+
selectedPods := []corev1.Pod{}
56+
57+
namespace := kubeutil.GetNamespaceFromKubernetesFlags(kubeConfigFlags, kubeResouceBuilderFlags)
58+
59+
// If the arguments are more than zero we should check by pod name
60+
// (args == resourceName)
61+
if len(args) > 0 {
62+
for _, podName := range args {
63+
pod, err := kubeutil.GetPodByName(clientset, namespace, podName, metav1.GetOptions{})
64+
if err != nil {
65+
println(err.Error())
66+
continue
67+
}
68+
selectedPods = append(selectedPods, *pod)
69+
}
70+
} else {
71+
selectedPods, err = kubeutil.GetSelectedPods(clientset, namespace, metav1.ListOptions{
72+
LabelSelector: *kubeResouceBuilderFlags.LabelSelector,
73+
})
74+
if err != nil {
75+
println(err.Error())
76+
os.Exit(1)
77+
}
78+
}
79+
80+
// If the selectedPods are zero there is nothing to do.
81+
if len(selectedPods) == 0 {
82+
println("there are not pod that matches your research")
83+
os.Exit(1)
84+
}
85+
86+
println(fmt.Sprintf("selected %d pod/s", len(selectedPods)))
87+
88+
pClient := profefe.NewClient(profefe.Config{
89+
HostPort: ProfefeHostPort,
90+
}, http.Client{})
91+
92+
for _, target := range selectedPods {
93+
targetPort := pprofutil.GetProfefePortByPod(target)
94+
profiles, err := pprofutil.GatherAllByPod(context.Background(), fmt.Sprintf("http://%s", target.Status.PodIP), target, targetPort)
95+
if err != nil {
96+
panic(err)
97+
}
98+
for profileType, profile := range profiles {
99+
profefeType := profefe.NewProfileTypeFromString(profileType.String())
100+
if profefeType == profefe.UnknownProfile {
101+
println("unknown profile type: :" + profileType.String())
102+
continue
103+
}
104+
saved, err := pClient.SavePprof(context.Background(), profefe.SavePprofRequest{
105+
Profile: profile,
106+
Service: target.Name,
107+
InstanceID: target.Status.HostIP,
108+
Type: profefeType,
109+
Labels: map[string]string{
110+
"namespace": target.Namespace,
111+
"from": "kube-profefe",
112+
},
113+
})
114+
if err != nil {
115+
println(fmt.Sprintf("%s type=%s profile_type=%s", err.Error(), profefeType, profile.PeriodType.Type))
116+
} else {
117+
println(fmt.Sprintf("%s/api/0/profiles/%s type=%s", ProfefeHostPort, saved.Body.ID, profefeType))
118+
}
119+
}
120+
}
121+
},
122+
}
123+
124+
flags.AddFlagSet(cmd.PersistentFlags())
125+
flags.StringVar(&ProfefeHostPort, "profefe-hostport", "http://localhost:10100", `where profefe is located`)
126+
kubeConfigFlags.AddFlags(flags)
127+
kubeResouceBuilderFlags.WithLabelSelector("")
128+
kubeResouceBuilderFlags.AddFlags(flags)
129+
130+
return cmd
131+
}

pkg/pprofutil/pprof.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package pprofutil
33
import (
44
"context"
55
"fmt"
6+
"strconv"
67

78
corev1 "k8s.io/api/core/v1"
89

@@ -11,14 +12,15 @@ import (
1112

1213
const (
1314
DefaultProfilePath = "/debug/pprof"
15+
DefaultProfilePort = 6060
1416
)
1517

1618
func GatherAllByPod(ctx context.Context, host string, pod corev1.Pod, forwardedPort int) (map[Profile]*profile.Profile, error) {
1719
path := DefaultProfilePath
1820
if rawPath, ok := pod.Annotations["profefe.com/path"]; ok && rawPath != "" {
1921
path = rawPath
2022
}
21-
return GatherAll(ctx, fmt.Sprintf("%s:%d/%s", host, forwardedPort, path))
23+
return GatherAll(ctx, fmt.Sprintf("%s:%d%s", host, forwardedPort, path))
2224
}
2325

2426
// GatherAll downloads all profile types from address.
@@ -51,3 +53,13 @@ func GatherAll(ctx context.Context, addr string) (map[Profile]*profile.Profile,
5153

5254
return profs, err
5355
}
56+
57+
func GetProfefePortByPod(pod corev1.Pod) int {
58+
port := DefaultProfilePort
59+
if rawPort, ok := pod.Annotations["profefe.com/port"]; ok && rawPort != "" {
60+
if i, err := strconv.Atoi(rawPort); err == nil {
61+
port = i
62+
}
63+
}
64+
return port
65+
}

0 commit comments

Comments
 (0)