1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 13:23:07 +01:00

feat: supports namespaces properly in k8s mode (#3631)

This commit is contained in:
Amir Raminfar
2025-02-15 09:35:32 -08:00
committed by GitHub
parent 65fa04c877
commit 21fe5b84f2
7 changed files with 53 additions and 58 deletions

View File

@@ -84,7 +84,7 @@ func (s *ContainerStore) checkConnectivity() error {
} }
running := lo.Filter(containers, func(item Container, index int) bool { running := lo.Filter(containers, func(item Container, index int) bool {
return item.State != "exited" return item.State != "exited" && !item.FullyLoaded
}) })
sem := semaphore.NewWeighted(maxFetchParallelism) sem := semaphore.NewWeighted(maxFetchParallelism)
@@ -168,8 +168,8 @@ func (s *ContainerStore) FindContainer(id string, labels ContainerLabels) (Conta
} }
if container, ok := s.containers.Load(id); ok { if container, ok := s.containers.Load(id); ok {
if container.StartedAt.IsZero() { if !container.FullyLoaded {
log.Debug().Str("id", id).Msg("container doesn't have detailed information, fetching it") log.Debug().Str("id", id).Msg("container is not fully loaded, fetching it")
if newContainer, ok := s.containers.Compute(id, func(c *Container, loaded bool) (*Container, bool) { if newContainer, ok := s.containers.Compute(id, func(c *Container, loaded bool) (*Container, bool) {
if loaded { if loaded {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)

View File

@@ -11,20 +11,21 @@ import (
// Container represents an internal representation of docker containers // Container represents an internal representation of docker containers
type Container struct { type Container struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Image string `json:"image"` Image string `json:"image"`
Command string `json:"command"` Command string `json:"command"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
StartedAt time.Time `json:"startedAt"` StartedAt time.Time `json:"startedAt"`
FinishedAt time.Time `json:"finishedAt"` FinishedAt time.Time `json:"finishedAt"`
State string `json:"state"` State string `json:"state"`
Health string `json:"health,omitempty"` Health string `json:"health,omitempty"`
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
Tty bool `json:"-"` Tty bool `json:"-"`
Labels map[string]string `json:"labels,omitempty"` Labels map[string]string `json:"labels,omitempty"`
Stats *utils.RingBuffer[ContainerStat] `json:"stats,omitempty"` Stats *utils.RingBuffer[ContainerStat] `json:"stats,omitempty"`
Group string `json:"group,omitempty"` Group string `json:"group,omitempty"`
FullyLoaded bool `json:"-,omitempty"`
} }
// ContainerStat represent stats instant for a container // ContainerStat represent stats instant for a container

View File

@@ -337,16 +337,17 @@ func newContainerFromJSON(c types.ContainerJSON, host string) container.Containe
} }
container := container.Container{ container := container.Container{
ID: c.ID[:12], ID: c.ID[:12],
Name: name, Name: name,
Image: c.Config.Image, Image: c.Config.Image,
Command: strings.Join(c.Config.Entrypoint, " ") + " " + strings.Join(c.Config.Cmd, " "), Command: strings.Join(c.Config.Entrypoint, " ") + " " + strings.Join(c.Config.Cmd, " "),
State: c.State.Status, State: c.State.Status,
Host: host, Host: host,
Labels: c.Config.Labels, Labels: c.Config.Labels,
Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats
Group: group, Group: group,
Tty: c.Config.Tty, Tty: c.Config.Tty,
FullyLoaded: true,
} }
if createdAt, err := time.Parse(time.RFC3339Nano, c.Created); err == nil { if createdAt, err := time.Parse(time.RFC3339Nano, c.Created); err == nil {

View File

@@ -85,16 +85,17 @@ func podToContainers(pod *corev1.Pod) []container.Container {
var containers []container.Container var containers []container.Container
for _, c := range pod.Spec.Containers { for _, c := range pod.Spec.Containers {
containers = append(containers, container.Container{ containers = append(containers, container.Container{
ID: pod.Name + ":" + c.Name, ID: pod.Namespace + ":" + pod.Name + ":" + c.Name,
Name: pod.Name + "/" + c.Name, Name: pod.Name + "/" + c.Name,
Image: c.Image, Image: c.Image,
Created: pod.CreationTimestamp.Time, Created: pod.CreationTimestamp.Time,
State: phaseToState(pod.Status.Phase), State: phaseToState(pod.Status.Phase),
StartedAt: started, StartedAt: started,
Command: strings.Join(c.Command, " "), Command: strings.Join(c.Command, " "),
Host: pod.Spec.NodeName, Host: pod.Spec.NodeName,
Tty: c.TTY, Tty: c.TTY,
Stats: utils.NewRingBuffer[container.ContainerStat](300), Stats: utils.NewRingBuffer[container.ContainerStat](300),
FullyLoaded: true,
}) })
} }
return containers return containers
@@ -108,16 +109,7 @@ func (k *K8sClient) ListContainers(ctx context.Context, labels container.Contain
var containers []container.Container var containers []container.Container
for _, pod := range pods.Items { for _, pod := range pods.Items {
for _, c := range pod.Spec.Containers { containers = append(containers, podToContainers(&pod)...)
containers = append(containers, container.Container{
ID: pod.Name + ":" + c.Name,
Name: pod.Name + "/" + c.Name,
Image: c.Image,
Created: pod.CreationTimestamp.Time,
State: phaseToState(pod.Status.Phase),
Host: pod.Spec.NodeName,
})
}
} }
return containers, nil return containers, nil
} }
@@ -141,9 +133,9 @@ func phaseToState(phase corev1.PodPhase) string {
func (k *K8sClient) FindContainer(ctx context.Context, id string) (container.Container, error) { func (k *K8sClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
log.Debug().Str("id", id).Msg("Finding container") log.Debug().Str("id", id).Msg("Finding container")
podName, containerName := parsePodContainerID(id) namespace, podName, containerName := parsePodContainerID(id)
pod, err := k.Clientset.CoreV1().Pods(k.namespace).Get(ctx, podName, metav1.GetOptions{}) pod, err := k.Clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil { if err != nil {
return container.Container{}, err return container.Container{}, err
} }
@@ -158,7 +150,7 @@ func (k *K8sClient) FindContainer(ctx context.Context, id string) (container.Con
} }
func (k *K8sClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) { func (k *K8sClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
podName, containerName := parsePodContainerID(id) namespace, podName, containerName := parsePodContainerID(id)
opts := &corev1.PodLogOptions{ opts := &corev1.PodLogOptions{
Container: containerName, Container: containerName,
Follow: true, Follow: true,
@@ -167,11 +159,11 @@ func (k *K8sClient) ContainerLogs(ctx context.Context, id string, since time.Tim
SinceTime: &metav1.Time{Time: since}, SinceTime: &metav1.Time{Time: since},
} }
return k.Clientset.CoreV1().Pods(k.namespace).GetLogs(podName, opts).Stream(ctx) return k.Clientset.CoreV1().Pods(namespace).GetLogs(podName, opts).Stream(ctx)
} }
func (k *K8sClient) ContainerLogsBetweenDates(ctx context.Context, id string, start time.Time, end time.Time, stdType container.StdType) (io.ReadCloser, error) { func (k *K8sClient) ContainerLogsBetweenDates(ctx context.Context, id string, start time.Time, end time.Time, stdType container.StdType) (io.ReadCloser, error) {
podName, containerName := parsePodContainerID(id) namespace, podName, containerName := parsePodContainerID(id)
opts := &corev1.PodLogOptions{ opts := &corev1.PodLogOptions{
Container: containerName, Container: containerName,
Follow: false, Follow: false,
@@ -179,7 +171,7 @@ func (k *K8sClient) ContainerLogsBetweenDates(ctx context.Context, id string, st
SinceTime: &metav1.Time{Time: start}, SinceTime: &metav1.Time{Time: start},
} }
return k.Clientset.CoreV1().Pods(k.namespace).GetLogs(podName, opts).Stream(ctx) return k.Clientset.CoreV1().Pods(namespace).GetLogs(podName, opts).Stream(ctx)
} }
func (k *K8sClient) ContainerEvents(ctx context.Context, ch chan<- container.ContainerEvent) error { func (k *K8sClient) ContainerEvents(ctx context.Context, ch chan<- container.ContainerEvent) error {
@@ -224,7 +216,7 @@ func (k *K8sClient) ContainerStats(ctx context.Context, id string, stats chan<-
} }
func (k *K8sClient) Ping(ctx context.Context) error { func (k *K8sClient) Ping(ctx context.Context) error {
_, err := k.Clientset.CoreV1().Pods(k.namespace).List(ctx, metav1.ListOptions{Limit: 1}) _, err := k.Clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{Limit: 1})
return err return err
} }
@@ -238,7 +230,7 @@ func (k *K8sClient) ContainerActions(ctx context.Context, action container.Conta
} }
// Helper function to parse pod and container names from container ID // Helper function to parse pod and container names from container ID
func parsePodContainerID(id string) (string, string) { func parsePodContainerID(id string) (string, string, string) {
parts := strings.Split(id, ":") parts := strings.Split(id, ":")
return parts[0], parts[1] return parts[0], parts[1], parts[2]
} }

View File

@@ -102,7 +102,7 @@ func (sc *K8sStatsCollector) Start(parentCtx context.Context) bool {
for _, pod := range metricList.Items { for _, pod := range metricList.Items {
for _, c := range pod.Containers { for _, c := range pod.Containers {
stat := container.ContainerStat{ stat := container.ContainerStat{
ID: pod.Name + ":" + c.Name, ID: pod.Namespace + ":" + pod.Name + ":" + c.Name,
CPUPercent: float64(c.Usage.Cpu().MilliValue()) / 1000 * 100, CPUPercent: float64(c.Usage.Cpu().MilliValue()) / 1000 * 100,
MemoryUsage: c.Usage.Memory().AsApproximateFloat64(), MemoryUsage: c.Usage.Memory().AsApproximateFloat64(),
} }

View File

@@ -29,6 +29,7 @@ type Args struct {
Mode string `arg:"env:DOZZLE_MODE" default:"server" help:"sets the mode to run in (server, swarm)"` Mode string `arg:"env:DOZZLE_MODE" default:"server" help:"sets the mode to run in (server, swarm)"`
TimeoutString string `arg:"--timeout,env:DOZZLE_TIMEOUT" default:"10s" help:"sets the timeout for docker client"` TimeoutString string `arg:"--timeout,env:DOZZLE_TIMEOUT" default:"10s" help:"sets the timeout for docker client"`
Timeout time.Duration `arg:"-"` Timeout time.Duration `arg:"-"`
Namespace string `arg:"env:DOZZLE_NAMESPACE" default:"" help:"sets the namespace to use in k8s"`
Healthcheck *HealthcheckCmd `arg:"subcommand:healthcheck" help:"checks if the server is running"` Healthcheck *HealthcheckCmd `arg:"subcommand:healthcheck" help:"checks if the server is running"`
Generate *GenerateCmd `arg:"subcommand:generate" help:"generates a configuration file for simple auth"` Generate *GenerateCmd `arg:"subcommand:generate" help:"generates a configuration file for simple auth"`
Agent *AgentCmd `arg:"subcommand:agent" help:"starts the agent"` Agent *AgentCmd `arg:"subcommand:agent" help:"starts the agent"`

View File

@@ -203,7 +203,7 @@ func main() {
} }
}() }()
} else if args.Mode == "k8s" { } else if args.Mode == "k8s" {
localClient, err := k8s.NewK8sClient("default") localClient, err := k8s.NewK8sClient(args.Namespace)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Could not create k8s client") log.Fatal().Err(err).Msg("Could not create k8s client")
} }