[K8s] CronJob으로 크롤링한 데이터 저장하기

우노·2024년 8월 12일
0

Practice & Trouble Shooting

목록 보기
10/18
post-custom-banner

여러 노드에서 사용할 수 있게 곧 PV, PVC를 추가할 예정입니다!

최근 쿠버네티스를 써야하는 프로젝트 두개에서 동시에 같은 문제가 발생했습니다. 둘 다 주기적으로 어떤 페이지를 크롤링해서 가공한 데이터를 파일로 저장해두고 써야했습니다. 주기적인 작업이라서 크론잡을 생각해냈고 크론잡으로 [크롤링→추출 및 가공] 을 수행하는 컨테이너를 주기적으로 실행하고 종료할 계획을 세웠습니다. 크론잡이 끝나면 데이터가 사라질 것을 방지하여 볼륨에 데이터를 저장했습니다! 그래서 두 프로젝트 중 하나에서 쓸 크론잡 수행을 통해 로컬에 데이터 저장하기를 성공해서 신나게 적어보려고 합니다!

이 글은 쿠버네티스 클러스터 환경이 구축되어 있다는 가정 하에 읽어주시면 감사하겠습니다. 😊

[Python] 작업 수행

이 프로젝트 해야할 일은 HTTP GET 요청으로 .csv와 .json 파일을 가져와서 로컬에 저장하고 해당 파일을 통해 어떠한 검증 과정을 거치는 것이었습니다. 가져올 파일은 주기적으로 업데이트되어 하루에 한 번 정도 GET 요청을 날려 로컬의 파일을 업데이트 하고자 했습니다.

해당 코드는 간단하게 python으로 작성했습니다.

import requests
import os

url_csv = "<url>.csv"
url_json = "<url>.json"

response_csv = requests.get(url_csv)
response_json = requests.get(url_json)

# /data 폴더 존재 확인
os.makedirs('/data', exist_ok=True)

# csv, json 파일 GET 요청에 에러가 없다면 파일 업데이트
if response_csv.status_code == 404 or response_csv.status_code == 429:
#    with open('/data/verify_list.csv', 'w', encoding='utf-8') as f:
#        f.write('nothing')
    print("CSV file NOT Updated")
elif response_csv.status_code == 200:
    with open('/data/verify_list.csv', 'w', encoding='utf-8') as f:
        f.write(response_csv.text)
    print("CSV file Updated")

if response_json.status_code == 404 or response_json.status_code == 429:
#    with open('/data/verify_list.json', 'w', encoding='utf-8') as f:
#        f.write('nothing')
    print("JSON file NOT Updated")
elif response_json.status_code == 200:
    with open('/data/verify_list.json', 'w', encoding='utf-8') as f:
        f.write(response_json.text)
    print("JSON file Updated")

print(response_csv.status_code)
print(response_json.status_code)

해당 코드는 정말 간단하게 파일을 GET 요청으로 가져와서 로컬에 작성하는 일을 수행합니다.

확인

python <file_name>.py 로 해당 코드의 동작을 확인합니다.

[Dockerfile] 작업 수행 환경 제공

쿠버네티스의 CronJob을 실행하려면 실행 코드를 컨테이너 이미지로 전달해야합니다. 그래서 이미지로 컨테이너를 실행하면 위의 파이썬 코드를 실행할 수 있게 Dockerfile을 작성했습니다.

FROM --platform=linux/amd64 python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY get-verify-list.py .

ENTRYPOINT ["python", "get-verify-list.py"]

--platform=linux/amd64

처음 Dockerfile을 작성했을 때 실행하면 아래와 같은 문제가 발생했습니다.

WARNING: The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64/v2) and no specific platform was requested
exec /usr/local/bin/python: exec format error

stackoverflow를 참고했을 때 제가 이미지를 빌드한 환경을 arm이고 실행하는 환경이 amd라서 CPU 플랫폼의 차이로 인해 python을 인식하지 못한다는 것을 알게 되었습니다.
[Docker ]Multi-platform images

그래서 FROM에 --platform=linux/amd64를 추가하여 amd 환경에서 실행할 때 문제가 없게끔 처리했습니다.

그런데 뒤늦게 생각해보니.. 그냥 ubuntu 환경에서 빌드해서 이미지를 만드는 건 어떨까 싶네요.. 하하

확인

docker build -t <이미지_이름>:<태그> --push . 로 이미지를 빌드합니다. 외부 환경에서 해당 이미지를 활용하기 위해 DockerHub에 push하는 작업을 추가했습니다.

이미지가 빌드, 업로드 되었다면
docker run -it --rm -v <로컬_폴더_위치>:/data <이미지_이름>:<태그> 를 통해 해당 로컬 폴더 위치에 파일이 생성되는지 확인합니다.
위의 파이썬 코드에서 404 또는 429가 발생하면 파일이 생성되지 않기 때문에 일시적으로 주석을 해제하여 파일에 'nothing'을 작성하여 생성할 수 있습니다.

[Kubernetes] 주기적인 수행

위에서 파이썬 코드와 이미지 실행에 문제가 없는 것을 확인했다면 cronjob을 설정하도록 yaml 파일을 작성합니다.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: file-updater
spec:
  schedule: "0 0 * * *"
  successfulJobsHistoryLimit: 3 
  failedJobsHistoryLimit: 1 
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: file-updater
              image: <이미지_이름>:<태그>
              volumeMounts:
                - name: file-storage
                  mountPath: /data  # 파드 내부 경로
          restartPolicy: OnFailure
          volumes:
            - name: file-storage
              hostPath:
                path: /usr/data  # 로컬 디렉토리 경로
  • spec.schedule: 하루에 한 번 실행
  • 주기적인 실행 관리
    • spec.successfulJobsHistoryLimit: 성공한 작업의 최대 기록 수
    • spec.failedJobsHistoryLimit: 실패한 작업의 최대 기록 수
  • spec.containers.volumeMounts.mountPath: 파드 내부에서 생성되는 경로
    • 파이썬 코드에서 /data에 생성
  • volumes.hostPath.path: 파드에서 생성한 파일을 저장할 로컬 디렉토리 경로

확인

kubectl create -f <yaml_파일_이름>.yaml로 크론잡을 생성합니다.

CronJob 확인

저는 빠른 테스트를 위해 spec.schedule"* * * * *" 로 바꾸어 1분에 한 번 수행하도록 수정했습니다.

먼저 크론잡을 생성하고 해당 잡이 수행될 때까지 kubectl get jobs --watch 로 기다립니다. 잡이 생성되면 파드가 생성되어 작업을 수행하고 있는 것을 확인할 수 있습니다.

kubectl get po를 통해 파드 상태를 확인했을 때 다음과 같이 Completed 상태가 된 것을 통해 작업을 수행하고 컨테이너가 종료되었음을 확인할 수 있습니다. 상태가 Error라면 describe, logs 등을 통해 추가적인 트러블슈팅이 필요합니다.

이제 로컬의 /usr/data로 이동해서 파일이 생성되었는지 확인할 수 있습니다.

404, 429 에러가 많이나는 데이터 링크라 nothing이 뜨면 캡쳐하려 했는데 데이터가 잘 들어와서 데이터 캡쳐는 생략하도록 하겠습니다..! 😂

가벼운 작업인데 requirements.txt를 놓치거나 플랫폼이나 볼륨에서 작은 문제가 이어져 시간이 꽤 오래 걸렸던 것 같습니다.
언제나 잘못된 점이 있다면 조언, 지적은 환영입니다!

profile
기록하는 감자
post-custom-banner

0개의 댓글