improve podman support (#211)

This commit is contained in:
Henry Dollman
2024-11-12 11:59:56 -05:00
parent db092d2440
commit 3cd11d6bc4
5 changed files with 56 additions and 23 deletions

View File

@@ -73,7 +73,7 @@ func (a *Agent) Run(pubKey []byte, addr string) {
a.initializeSystemInfo() a.initializeSystemInfo()
a.initializeDiskInfo() a.initializeDiskInfo()
a.initializeNetIoStats() a.initializeNetIoStats()
a.dockerManager = newDockerManager() a.dockerManager = newDockerManager(a)
// initialize GPU manager // initialize GPU manager
if os.Getenv("GPU") == "true" { if os.Getenv("GPU") == "true" {

View File

@@ -25,18 +25,23 @@ type dockerManager struct {
apiContainerList *[]container.ApiInfo // List of containers from Docker API apiContainerList *[]container.ApiInfo // List of containers from Docker API
containerStatsMap map[string]*container.Stats // Keeps track of container stats containerStatsMap map[string]*container.Stats // Keeps track of container stats
validIds map[string]struct{} // Map of valid container ids, used to prune invalid containers from containerStatsMap validIds map[string]struct{} // Map of valid container ids, used to prune invalid containers from containerStatsMap
goodDockerVersion bool // Whether docker version is at least 25.0.0 (one-shot works correctly)
} }
// Add goroutine to the queue // Add goroutine to the queue
func (d *dockerManager) queue() { func (d *dockerManager) queue() {
d.sem <- struct{}{}
d.wg.Add(1) d.wg.Add(1)
if d.goodDockerVersion {
d.sem <- struct{}{}
}
} }
// Remove goroutine from the queue // Remove goroutine from the queue
func (d *dockerManager) dequeue() { func (d *dockerManager) dequeue() {
<-d.sem
d.wg.Done() d.wg.Done()
if d.goodDockerVersion {
<-d.sem
}
} }
// Returns stats for all running containers // Returns stats for all running containers
@@ -75,6 +80,7 @@ func (dm *dockerManager) getDockerStats() ([]*container.Stats, error) {
go func() { go func() {
defer dm.dequeue() defer dm.dequeue()
err := dm.updateContainerStats(ctr) err := dm.updateContainerStats(ctr)
// if error, delete from map and add to failed list to retry
if err != nil { if err != nil {
dm.containerStatsMutex.Lock() dm.containerStatsMutex.Lock()
delete(dm.containerStatsMap, ctr.IdShort) delete(dm.containerStatsMap, ctr.IdShort)
@@ -89,11 +95,10 @@ func (dm *dockerManager) getDockerStats() ([]*container.Stats, error) {
// retry failed containers separately so we can run them in parallel (docker 24 bug) // retry failed containers separately so we can run them in parallel (docker 24 bug)
if len(failedContainters) > 0 { if len(failedContainters) > 0 {
slog.Debug("Retrying failed containers", "count", len(failedContainters)) slog.Debug("Retrying failed containers", "count", len(failedContainters))
// time.Sleep(time.Millisecond * 1100)
for _, ctr := range failedContainters { for _, ctr := range failedContainters {
dm.wg.Add(1) dm.queue()
go func() { go func() {
defer dm.wg.Done() defer dm.dequeue()
err = dm.updateContainerStats(ctr) err = dm.updateContainerStats(ctr)
if err != nil { if err != nil {
slog.Error("Error getting container stats", "err", err) slog.Error("Error getting container stats", "err", err)
@@ -201,12 +206,13 @@ func (dm *dockerManager) deleteContainerStatsSync(id string) {
delete(dm.containerStatsMap, id) delete(dm.containerStatsMap, id)
} }
// Creates a new http client for Docker API // Creates a new http client for Docker or Podman API
func newDockerManager() *dockerManager { func newDockerManager(a *Agent) *dockerManager {
dockerHost := "unix:///var/run/docker.sock" dockerHost, exists := os.LookupEnv("DOCKER_HOST")
if dockerHostEnv, exists := os.LookupEnv("DOCKER_HOST"); exists { if exists {
slog.Info("DOCKER_HOST", "host", dockerHostEnv) slog.Info("DOCKER_HOST", "host", dockerHost)
dockerHost = dockerHostEnv } else {
dockerHost = getDockerHost()
} }
parsedURL, err := url.Parse(dockerHost) parsedURL, err := url.Parse(dockerHost)
@@ -251,11 +257,15 @@ func newDockerManager() *dockerManager {
Transport: transport, Transport: transport,
}, },
containerStatsMap: make(map[string]*container.Stats), containerStatsMap: make(map[string]*container.Stats),
sem: make(chan struct{}, 5),
} }
// Make sure sem is initialized // If using podman, return client
concurrency := 200 if strings.Contains(dockerHost, "podman") {
defer func() { dockerClient.sem = make(chan struct{}, concurrency) }() a.systemInfo.Podman = true
dockerClient.goodDockerVersion = true
return dockerClient
}
// Check docker version // Check docker version
// (versions before 25.0.0 have a bug with one-shot which requires all requests to be made in one batch) // (versions before 25.0.0 have a bug with one-shot which requires all requests to be made in one batch)
@@ -273,9 +283,22 @@ func newDockerManager() *dockerManager {
// if version > 24, one-shot works correctly and we can limit concurrent operations // if version > 24, one-shot works correctly and we can limit concurrent operations
if dockerVersion, err := semver.Parse(versionInfo.Version); err == nil && dockerVersion.Major > 24 { if dockerVersion, err := semver.Parse(versionInfo.Version); err == nil && dockerVersion.Major > 24 {
concurrency = 5 dockerClient.goodDockerVersion = true
} else {
slog.Info(fmt.Sprintf("Docker %s is outdated. Upgrade if possible. See https://github.com/henrygd/beszel/issues/58", versionInfo.Version))
} }
slog.Debug("Docker", "version", versionInfo.Version, "concurrency", concurrency)
return dockerClient return dockerClient
} }
// Test docker / podman sockets and return if one exists
func getDockerHost() string {
scheme := "unix://"
socks := []string{"/var/run/docker.sock", "/run/user/1000/podman/podman.sock", "/run/podman/podman.sock"}
for _, sock := range socks {
if _, err := os.Stat(sock); err == nil {
return scheme + sock
}
}
return scheme + socks[0]
}

View File

@@ -74,6 +74,7 @@ type Info struct {
DiskPct float64 `json:"dp"` DiskPct float64 `json:"dp"`
Bandwidth float64 `json:"b"` Bandwidth float64 `json:"b"`
AgentVersion string `json:"v"` AgentVersion string `json:"v"`
Podman bool `json:"p,omitempty"`
} }
// Final data structure to return to the hub // Final data structure to return to the hub

View File

@@ -86,6 +86,13 @@ async function getStats<T>(collection: string, system: SystemRecord, chartTime:
}) })
} }
function dockerOrPodman(str: string, system: SystemRecord) {
if (system.info.p) {
str = str.replace("docker", "podman").replace("Docker", "Podman")
}
return str
}
export default function SystemDetail({ name }: { name: string }) { export default function SystemDetail({ name }: { name: string }) {
const direction = useStore($direction) const direction = useStore($direction)
const { _ } = useLingui() const { _ } = useLingui()
@@ -385,7 +392,7 @@ export default function SystemDetail({ name }: { name: string }) {
<ChartCard <ChartCard
empty={dataEmpty} empty={dataEmpty}
grid={grid} grid={grid}
title={t`Docker CPU Usage`} title={dockerOrPodman(t`Docker CPU Usage`, system)}
description={t`Average CPU utilization of containers`} description={t`Average CPU utilization of containers`}
cornerEl={containerFilterBar} cornerEl={containerFilterBar}
> >
@@ -406,8 +413,8 @@ export default function SystemDetail({ name }: { name: string }) {
<ChartCard <ChartCard
empty={dataEmpty} empty={dataEmpty}
grid={grid} grid={grid}
title={t`Docker Memory Usage`} title={dockerOrPodman(t`Docker Memory Usage`, system)}
description={t`Memory usage of docker containers`} description={dockerOrPodman(t`Memory usage of docker containers`, system)}
cornerEl={containerFilterBar} cornerEl={containerFilterBar}
> >
<ContainerChart chartData={chartData} chartName="mem" dataKey="m" unit=" MB" /> <ContainerChart chartData={chartData} chartName="mem" dataKey="m" unit=" MB" />
@@ -447,8 +454,8 @@ export default function SystemDetail({ name }: { name: string }) {
> >
<ChartCard <ChartCard
empty={dataEmpty} empty={dataEmpty}
title={t`Docker Network I/O`} title={dockerOrPodman(t`Docker Network I/O`, system)}
description={t`Network traffic of docker containers`} description={dockerOrPodman(t`Network traffic of docker containers`, system)}
cornerEl={containerFilterBar} cornerEl={containerFilterBar}
> >
{/* @ts-ignore */} {/* @ts-ignore */}
@@ -649,7 +656,7 @@ function ChartCard({
<CardDescription>{description}</CardDescription> <CardDescription>{description}</CardDescription>
{cornerEl && <div className="relative py-1 block sm:w-44 sm:absolute sm:top-2.5 sm:end-3.5">{cornerEl}</div>} {cornerEl && <div className="relative py-1 block sm:w-44 sm:absolute sm:top-2.5 sm:end-3.5">{cornerEl}</div>}
</CardHeader> </CardHeader>
<div className="ps-0 w-[calc(100%-1.6em)] h-52 relative"> <div className="ps-0 w-[calc(100%-1.5em)] h-52 relative">
{<Spinner msg={empty ? t`Waiting for enough records to display` : undefined} />} {<Spinner msg={empty ? t`Waiting for enough records to display` : undefined} />}
{isIntersecting && children} {isIntersecting && children}
</div> </div>

View File

@@ -34,6 +34,8 @@ export interface SystemInfo {
b: number b: number
/** agent version */ /** agent version */
v: string v: string
/** system is using podman */
p?: boolean
} }
export interface SystemStats { export interface SystemStats {