Kubernetes 환경에서 argo workflow를 통해 파이프라인을 실행할 때는 자원 할당에 대한 고민이 필요하다. 그런데, 일반적인 방식으로는 의도대로 자원 할당이 되지 않는다. Kubernetes 환경의 안정적인 운영을 위해 argo workflow에 자원 설정을 하는 방법을 알아보자.
Argo workflow에 kubernetes resource request와 limit을 설정하는 방법
Argo workflow 자원 설정의 필요성
안적정인 운영과 정확한 진단을 위해 예민해지기
Kubernetes 환경에서 argo workflow를 통해 pipeline을 돌릴 때, resource request와 limit을 잘 설정해주어야 한다. Resource request와 limit을 제대로 설정하지 않으면 argo workflow 실행에 의해 배포되는 pod들이 무분별하게 자원을 점유할 수 있다. 경우에 따라 kubernetes 환경에 배포된 시스템 전체를 위험에 빠뜨릴 수도 있다. 이전 글에서 특정 pod가 과도하게 memory를 사용하면 kubernetes에 배포된 다른 pod들이 eviction 대상이 되거나 시스템 전체가 마비될 수 있음을 살펴봤다. 이와 별개로 자원 할당량이나 사용량 등을 모니터링하는 서비스에서 정확한 정보를 제공하기 어려워진다는 것도 한 가지 이유가 될 수 있다. 따라서, 안정적인 kubernetes 환경을 운영하기 위해서는 argo workflow에도 자원 할당 관련 설정을 정확하게 하는 방법을 꼭 숙지하고 있어야 한다.
Argo workflow 자원 설정 시 주의사항
Argo workflow는 일반적인 kubernetes의 pod나 deployment처럼 resources 부분에 값을 넣어도 의도대로 자원 설정이 되지 않는다. Argo workflow라는 것 자체가 argo에서 정의한 custom object이기 때문이다. 정확하게 말하자면, workflow가 생성하는 pod 하나에 container가 3개 포함되는데, 모든 container에 자원 설정이 적용되지는 않는다는 뜻이다.
예를 들어, argo workflow를 정의하는 yaml 파일의 spec.templates[n].container.resources
부분에 자원 설정을 하면 어떻게 될까?
# pipeline.yaml
...
spec:
templates:
- name: component1
container:
resources:
requests:
memory: "10Mi"
cpu: "20m"
limits:
memory: "100Mi"
cpu: "200m"
...
위 파일을 kubectl create -f pipeline.yaml
명령어를 통해 배포하면 workflow가 생성된다. 이 workflow가 pod를 자동으로 배포해주는데, 해당 pod의 상세 정보를 조회해보면 다음과 같이 main
이라는 이름의 container에만 자원 설정이 적용된 것을 확인할 수 있다.
...
name: wait
resources: {}
...
name: main
resources:
limits:
cpu: 200m
memory: 100Mi
requests:
cpu: 20m
memory: 10Mi
...
name: init
resources: {}
...
init
와 wait
container에 자원 관련 설정이 적용되지 않았기 때문에 앞서 언급했던 문제가 발생할 수 있다. 사실 이 두 가지 컨테이너들이 시스템 전체에 악영향을 미칠 정도로 자원을 과도하게 사용하지는 않을 것이다. 그래도 정확한 리소스 모니터링을 위해서는 누락되는 container가 없어야 한다. 세 가지 컨테이너 모두에 자원을 설정하는 방법은 없을까?
Argo workflow에 누락없이 자원을 설정하는 방법
PodSpecPatch를 통해 container 자원 설정하기
Argo workflow에 의해 생성되는 pod에 자원 설정을 하려면 podSpecPatch
를 수정하면 된다. 해당 영역에서 main
외에도 init
과 wait
컨테이너의 자원도 설정할 수 있다. 이때, yaml 파일에 입력하는 값의 위치에 따라 영향 받는 범위를 컨트롤할 수 있다. Workflow 전체에 일괄적으로 적용하려면 spec.podSpecPatch
에 입력하면 된다. 일부 특정 pod에만 자원 설정을 하고 싶다면 spec.templates[n].podSpecPatch
에 입력하면 된다.
Workflow가 생성하는 pod 전체에 일괄적으로 자원 설정하기
...
spec:
podSpecPatch: |
initContainers:
- name: init
resources:
requests:
memory: "10Mi"
cpu: "10m"
limits:
memory: "100Mi"
cpu: "100m"
containers:
- name: wait
resources:
requests:
memory: "20Mi"
cpu: "20m"
limits:
memory: "200Mi"
cpu: "200m"
- name: main
resources:
requests:
memory: "30Mi"
cpu: "30m"
limits:
memory: "300Mi"
cpu: "300m"
...
위 예시에서 주의할 점은 init
container의 경우 initContainers
로 감싸야 한다는 것이다. main
과 wait
은 containers
로 감싼다는 점에서 차이가 있다. 만약 init
컨테이너도 containers
로 감싸면 workflow가 배포되었을 때 pod를 생성하지 못하고 error 상태로 남아있게 된다. 해당 workflow에 kubectl describe
명령어를 실행해보면 kubernetes events 영역에서 다음과 같은 오류 메시지들을 확인할 수 있다.
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal WorkflowRunning 25s workflow-controller Workflow Running
Warning WorkflowFailed 25s workflow-controller error in entry template execution: failed to look-up entrypoint/cmd for image "", you must either explicitly specify the command, or list the image's command in the index: https://argoproj.github.io/argo-workflows/workflow-executors/#emissary-emissary: could not parse reference:
Normal WorkflowNodeRunning 25s workflow-controller Running node pipeline-7kc6k: failed to look-up entrypoint/cmd for image "", you must either explicitly specify the command, or list the image's command in the index: https://argoproj.github.io/argo-workflows/workflow-executors/#emissary-emissary: could not parse reference:
Warning WorkflowNodeError 25s workflow-controller Error node pipeline-7kc6k: failed to look-up entrypoint/cmd for image "", you must either explicitly specify the command, or list the image's command in the index: https://argoproj.github.io/argo-workflows/workflow-executors/#emissary-emissary: could not parse reference:
Workflow의 특정 pod에만 자원 설정하기
Workflow에서 실행할 여러 pod들 중에서 특정 pod에만 자원을 할당하려면, 다음과 같이 pipeline의 각 컴포넌트가 정의된 영역에 podSpecPatch
를 포함시켜주면 된다. 형식은 앞서 살펴본 일괄설정과 다르지 않다.
...
spec:
templates:
- name: component1
podSpecPatch: |
initContainers:
- name: init
resources:
requests:
memory: "19Mi"
cpu: "19m"
limits:
memory: "199Mi"
cpu: "199m"
containers:
- name: wait
resources:
requests:
memory: "29Mi"
cpu: "29m"
limits:
memory: "299Mi"
cpu: "299m"
- name: main
resources:
requests:
memory: "39Mi"
cpu: "39m"
limits:
memory: "399Mi"
cpu: "399m"
...
자원 설정 값 적용 우선순위
자원 설정 값이 적용되는 우선순위는 다음과 같다. 숫자가 작을수록 우선순위가 높다.
(1) spec.templates[n].podSpecPatch
(2) spec.podSpecPatch
(3) spec.templates[n].container.resources
숫자가 높은 쪽에 자원 설정이 되어 있는 상태에서 숫자가 작은 쪽에 자원 설정을 추가한다고 해서 값 전체를 덮어쓰는 것은 아니다. 일부 중복으로 설정된 값만 덮어쓰게 된다. 예를 들어, (2)에 cpu와 memory에 대한 설정을 해놓은 상태에서 (1)에 memory request만 설정했다면, 결과적으로 (2)의 설정 값들 중 memory request 값만 (1)에 설정한 값으로 변경된다. 다시 말해서, 우선순위가 낮은 쪽에 A, B를 설정한 상태에서 우선순위가 높은 쪽에 A만 설정하더라도, 결과가 A, null이 되지는 않는다는 뜻이다.
정리하자면, (3)처럼 익숙한 방식으로 자원 설정을 하는 것보다 podSpecPatch
에 설정하는 것이 더욱 우선순위가 높다. 그리고 일괄설정보다는 pod 개별 설정이 더욱 우선순위가 높다. 직접 다양한 위치에 설정된 값들을 바꿔가면서 테스트해보자!
Pipeline 실행 시점에 동적으로 자원 할당하기
Argo workflow가 배포되는 환경에 따라 필요한 자원 설정이 달라질 수도 있다. 그렇다면 배포 환경에 따라 yaml 파일을 매번 새로 작성해주어야 할까? 인프라적인 요소에 의해 코드가 변경되는 것은 지양해야 한다. 효율적으로 pipeline을 배포하고 관리려면 pipeline의 실행 시점에 배포 환경에 맞는 자원 설정 값을 전달할 수 있어야 한다.
Argo workflow의 parameter와 podSpecPatch
의 변수를 활용하면 동적 자원 할당을 가능하게 만들 수 있다. 다음과 같이 spec.arguments.parameters
와 {{}}
기호를 활용하면 된다. 이렇게 파일을 작성해두면 pipeline 실행 시점에 argument로 자원 필요량을 전달해서 pod에 동적으로 자원 설정을 할 수 있게 된다. 테스트는 공식 예제를 참고하여 직접 수행해보자.
...
spec:
entrypoint: hello-world
arguments:
parameters:
- name: mem-limit
value: 100Mi
podSpecPatch: |
containers:
- name: main
resources:
limits:
memory: "{{workflow.parameters.mem-limit}}"
...
Argo workflow 자원 설정 결과
앞서 살펴본 설정을 바탕으로 yaml 파일을 작성한 뒤 실제로 argo workflow를 배포해보면 다음과 같이 workflow가 생성하는 pod에 포함된 모든 container들에 자원 설정 값들이 잘 적용되어 있는 것을 확인할 수 있다.
...
name: wait
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 20m
memory: 20Mi
...
name: main
resources:
limits:
cpu: 300m
memory: 300Mi
requests:
cpu: 30m
memory: 30Mi
...
name: init
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 10m
memory: 10Mi
...
전체 내용 핵심 요약
이번 글에서 다룬 내용에서 핵심들만 뽑아서 요약하면 다음과 같다.
- Argo workflow에서 생성하는 pod에는
init
,main
,wait
세 가지 container들이 포함되어 있다. - 일반적인 방식으로 yaml 파일에 resources 부분을 설정하면
main
컨테이너에만 설정 값이 적용된다. init
과wait
컨테이너에도 자원 설정을 하려면podSpecPatch
를 활용해야 한다.podSpecPatch
를 통한 개별 pod 설정이 workflow 전체 일괄설정보다 우선순위가 높다.- Workflow 일괄설정이 개별 pod에 일반적인 방식으로 설정하는 것보다 우선순위가 높다.
- 우선순위에 의해 값을 덮어쓸 때는 전체를 덮어쓰는 것이 아니라, 실제 입력된 값만 부분적으로 덮어쓴다.
- Pipeline 실행 시점에 동적으로 자원을 할당하려면
spec.arguments.parameters
와{{}}
를 통해 변수를 활용하면 된다.
More Posts
Python으로 kubernetes 노드 선택 기능을 개발하는 방법
클러스터를 구성하면 목적에 따라 노드의 역할을 구분하는 경우가 많다. 따라서, kubernetes에서 pod를 특정 노드에만 배포해야 한다는 제약사항은 당연히 나올 수 밖에 없다. Python을 통해 이를 구현하는 방법과 고려해야 할 점들에 대해 알아보자.
안전하게 무한 루프 탈출하기 - Handling SIGTERM in kubernetes with python
프로그램의 유형에 따라 명확한 종료 시점 없이 반복적인 작업을 수행해야 하는 경우가 있다. 그런데, 영원한 것은 없지 않나! 언젠가는 종료를 시켜야 한다면, 어떻게 해야 안전하게 무한 루프를 빠져나올 수 있는지 알아보자.
Kubernetes legacy version들의 최후 - Kubernetes apt install GPG error
2024년 11월 초부터 ubuntu에 kubernetes 클러스터를 구성하기 위해 kubeadm, kubelet, kubectl 설치하려는데 GPG error가 발생하고 있다. Kubernetes의 package repository에서 이전 버전들을 더 이상 제공하지 않는 것일까? 어떤 문제인지 살펴보자.
Comments