Python으로 Docker 컨테이너 제어 및 모니터링

Apr 14 2023
Python을 사용하여 도커 컨테이너를 제어하세요! 다음은 시작하는 방법에 대한 연습입니다. 테이블 형식 데이터를 처리하기 위해 널리 사용되는 세 가지 패키지의 테스트를 기반으로 합니다.

Python을 사용하여 도커 컨테이너를 제어하세요! 다음은 시작하는 방법에 대한 연습입니다. 테이블 형식 데이터를 처리하기 위해 널리 사용되는 세 가지 패키지의 테스트를 기반으로 합니다.

힘내 레포:https://github.com/martinkarlssonio/polars-pandas-spark

이 테스트의 데이터 측면을 읽고 싶다면 별도의 이야기가 있습니다!
/@martinkarlsson.io/pandas-spark-and-polars-when-to-use-which-f4e85d909c6f

코드 살펴보기

아래 이미지는 코드로 전반적인 아이디어를 보여줍니다. main.py 코드는 이미지를 빌드하고 12개의 Docker 컨테이너를 시작, 제어 및 모니터링합니다.

이미지 및 컨테이너 제어

이 솔루션의 스타는 Docker Python 패키지입니다. requirements.txt(pip와 함께 설치됨)에서 docker 패키지를 지정합니다.

docker==6.0.1

import docker
dockerClient = docker.DockerClient()

모든 컨테이너 목록을 가져옵니다. 여기서 코드는 지정된 이미지 이름으로 빌드된 컨테이너도 필터링합니다.

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

컨테이너 목록을 삭제합니다(항상 새로운 테스트 실행을 시작하기 전에 하는 것이 좋습니다).

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)

기존 이미지에서 새 컨테이너를 시작하거나 이미 존재하지 않는 경우 이미지를 만듭니다. 또한 컨테이너를 시작할 때 환경 변수 집합을 전달합니다.

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

다음은 Dockers 'stats' 명령을 사용하여 CPU, 메모리 및 실행 시간을 모니터링하는 기능입니다.

CPU 사용량 계산에는 추가 설명이 필요할 수 있습니다. 마지막 읽기 CPU 사용량을 현재와 비교하고 이를 시스템 CPU 사용량과 코어 수를 곱한 값과 비교합니다.

다음은 단어로 작성된 논리입니다.

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")

별도의 스레드에서 dockerLog 함수를 실행하고 싶으므로 'threading' 패키지를 사용하여 함수를 시작합니다. 즉, 코드 실행을 차단하지 않고 동시에 컨테이너를 시작할 수 있습니다.

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()

'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 행

250.000행

2.500.000행

결론

그게 다야! 이제 Python을 사용하여 메트릭 로거와 함께 Docker 이미지 및 컨테이너를 제어할 수 있습니다.

즐거운 코딩하세요!