Controlla e monitora i tuoi container Docker con Python

Apr 14 2023
Usa Python per controllare i tuoi container docker! Ecco una procedura dettagliata su come iniziare. Si basa su un test di tre pacchetti popolari per la gestione dei dati tabulari.

Usa Python per controllare i tuoi container docker! Ecco una procedura dettagliata su come iniziare. Si basa su un test di tre pacchetti popolari per la gestione dei dati tabulari.

Deposito Git:https://github.com/martinkarlssonio/polars-pandas-spark

Se desideri leggere l'aspetto dei dati di questo test, c'è una storia separata per questo!
/@martinkarlsson.io/pandas-spark-and-polars-quando-usare-quale-f4e85d909c6f

Esplora il codice

L'immagine sotto illustra l'idea generale con il codice. L'immagine di build del codice main.py avvia, controlla e monitora 12 contenitori Docker.

Controlla le tue immagini e i tuoi contenitori

La star di questa soluzione è il pacchetto Docker Python. In un requirements.txt (installato con pip) specifichiamo il pacchetto docker.

docker==6.0.1

import docker
dockerClient = docker.DockerClient()

Ottenere un elenco di tutti i contenitori. Qui il codice filtra anche i contenitori creati con un determinato nome di immagine.

def getContainers():
    containersReturn = []
    containers = dockerClient.containers.list(all=True)
    for container in containers:
        if imageName in str(container.image):
            containersReturn.append(container)
    return containersReturn

Elimina un elenco di contenitori (sempre una buona idea prima di iniziare una nuova esecuzione del test).

def removeContainers():
    containers = getContainers()
    for container in containers:
        if imageName in str(container.image):
            print("################################################### Deleting old container {}".format(container.name))
            try:
                container.stop()
                container.remove()
            except Exception as e:
                print("################################################### Error deleting old container {}".format(container.name))
                print(e)

Avvia un nuovo contenitore da un'immagine esistente o crea l'immagine se non esiste già. Passiamo anche un set di variabili di ambiente all'avvio del contenitore.

def runContainer(testType,dataframeN):
    images = dockerClient.images.list(all=True)
    if imageName in ' '.join(map(str, images)):
        print("################################################### Image exist, starting container..")
        dockerClient.containers.run(imageName+":latest", environment = {"TEST_TYPE":testType,"DATAFRAME_N":dataframeN,"CALC_N":calcN})
    else:
        print("################################################### Image doesn't exist, need to create it!")
        dockerClient.images.build(path = "./", tag = imageName)
        dockerClient.containers.run(imageName+":latest", environment = {"TEST_TYPE":testType,"DATAFRAME_N":dataframeN,"CALC_N":calcN})

#UBUNTU AS BASE
FROM ubuntu:focal

##UPDATE
RUN apt-get update -y
RUN apt-get install nano
RUN apt-get update -y

##PYTHON
RUN apt-get install -y python3-pip python3-dev
RUN pip3 install --upgrade pip
COPY requirements-container.txt /requirements.txt
RUN pip3 install -r requirements.txt

##JAVA
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
RUN apt install -y openjdk-8-jdk

##COPY IN FILES
COPY test.py /test.py
CMD python3 /test.py

Ecco una funzione per monitorare la CPU, la memoria e il tempo di esecuzione utilizzando il comando 'stats' di Dockers.

Il calcolo dell'utilizzo della CPU potrebbe richiedere ulteriori spiegazioni. Confronta l'utilizzo della CPU dell'ultima lettura con quello corrente e lo confronta con l'utilizzo della CPU del sistema moltiplicato per la quantità di core.

Ecco la logica scritta in parole:

cpuDelta = Total CPU Usage (now) - Total Usage (last read)
systemDelta = System CPU Usage (now) - System CPU Usage (last read)
cpuPercent = (cpuDelta/systemDelta) * CPU Cores * 100

def dockerLog(event,testType,dataframeN):
    cpuLog = []
    memLog = []
    print("################################################### dockerLog started")
    startFlag = True
    while True:
        try:
            containers = getContainers()
            for container in containers:
                status = container.stats(decode=None, stream = False)
                try:
                    # Calculate the change for the cpu usage of the container in between readings
                    # Taking in to account the amount of cores the CPU has
                    cpuDelta = status["cpu_stats"]["cpu_usage"]["total_usage"] - status["precpu_stats"]["cpu_usage"]["total_usage"]
                    systemDelta = status["cpu_stats"]["system_cpu_usage"] - status["precpu_stats"]["system_cpu_usage"]
                    #print("systemDelta: "+str(systemDelta)+" cpuDelta: "+str(cpuDelta))
                    cpuPercent = (cpuDelta / systemDelta) * (status["cpu_stats"]["online_cpus"]) * 100
                    cpuPercent = int(cpuPercent)
                    #print("cpuPercent: "+str(cpuPercent)+"%")
                    #Fetch the memory consumption for the container
                    mem = status["memory_stats"]["usage"]
                    mem = int(mem/1000000)
                    if startFlag == True and cpuPercent == 0: #Not logging CPU increase during startup of container, before test code executes.
                        startFlag = False
                        startEpoch = int(time.time()*1000)
                        print("Startflag set to False - let's go!")
                    if startFlag == False:
                        cpuLog.append(cpuPercent)
                        memLog.append(mem)
                except Exception as e:
                    print("Error: "+str(e))
                    status = container.stats(decode=None, stream = False)
                    break
        except Exception as e:
            print("Error: "+str(e))
            pass
        if event.is_set():
            break
    # Write the logging to a file
    endEpoch = int(time.time()*1000)
    with open("output/"+str(dataframeN)+"_"+testType+"_"+containerStatsName, "w") as f:
        json.dump({"mem":memLog,"cpu":cpuLog,"timeSpent":float((endEpoch-startEpoch)/1000)}, f)
    print("################################################### dockerLog ended")

Vorremmo eseguire la funzione dockerLog in un thread separato, quindi avviamo la funzione utilizzando il pacchetto 'threading'. Ciò significa che non bloccheremo l'esecuzione del codice e potremo in parallelo avviare il contenitore.

from threading import Thread
from threading import Event
event = Event()
dockerLogThread = Thread(target=dockerLog, args=(event,testType,dataframeN,))
dockerLogThread.start()

runContainer(testType,dataframeN)
event.set()
dockerLogThread.join()

Visualizziamo i log del docker con il pacchetto 'matplotlib'!

##Visualize Mem & CPU
def visMemCpu(testType,timeSpent,dataframeN,visAll=False):
    if visAll==True:
        from os import listdir
        from os.path import isfile, join
        outputFiles = [f for f in listdir("output/") if isfile(join("output/", f))]
        combDict = {}
        for file in outputFiles:
            if "containerStats.json" in file:
                with open("output/"+file, "r") as fp:
                    data = json.load(fp)
                fileSplit = file.split("_")
                rows = fileSplit[0]
                lib = fileSplit[1]
                try:
                    combDict[rows][lib] = {}
                    combDict[rows][lib]["mem"] = data["mem"]
                    combDict[rows][lib]["cpu"] = data["cpu"]
                    combDict[rows][lib]["timeSpent"] = int(data["timeSpent"])
                except:
                    combDict[rows] = {}
                    combDict[rows][lib] = {}
                    combDict[rows][lib]["mem"] = data["mem"]
                    combDict[rows][lib]["cpu"] = data["cpu"]
                    combDict[rows][lib]["timeSpent"] = int(data["timeSpent"])
        with open("output/all.json", "w") as f:
            json.dump(combDict, f)

        for rows in combDict.keys():
            textString = " | "
            plt.clf()
            for lib in combDict[rows].keys():
                plt.plot(combDict[rows][lib]["mem"], label=lib)
                textString += lib + " " + str(combDict[rows][lib]["timeSpent"]) + "s | "
            plt.xticks([], [])
            plt.ylabel("Memory MB")
            plt.title(textString)
            plt.xlabel("Dataframe rows : " + rows)
            plt.legend(framealpha=1, frameon=True)
            plt.savefig("output/"+"{}_mem.png".format(str(rows)), dpi=300)
            plt.clf()
            for lib in combDict[rows].keys():
                plt.plot(combDict[rows][lib]["cpu"], label=lib)
            plt.xticks([], [])
            plt.ylabel("CPU %")
            plt.xlabel("Dataframe rows : " + rows)
            plt.title(textString)
            plt.legend(framealpha=1, frameon=True)
            plt.savefig("output/"+"{}_cpu.png".format(str(rows)), dpi=300)
        plt.yticks([], [])

25.000 righe

250.000 righe

2.500.000 righe

Conclusione

Questo è tutto! Ora puoi utilizzare Python per controllare le immagini e i contenitori Docker, insieme a un logger di metriche.

Buona programmazione!