블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

쿠버네티스 리소스(CPU/Memory)할당과 관리

조대협

리소스 관리


쿠버네티스에서 Pod를 어느 노드에 배포할지를 결정하는 것을 스케쥴링이라고 한다.

Pod에 대한 스케쥴링시에, Pod내의 애플리케이션이 동작할 수 있는 충분한 자원 (CPU,메모리 등)이 확보되어야 한다. 쿠버네티스 입장에서는 애플리케이션에서 필요한 자원의 양을 알아야, 그 만한 자원이 가용한 노드에 Pod를 배포할 수 있다.


쿠버네티스에서는 이런 컨셉을 지원하기 위해서 컨테이너에 필요한 리소스의 양을 명시할 수 있도록 지원하고 있다.  현재(1.9 버전) 지원되는 리소스 타입은 CPU와 메모리이며, 아직 까지는 네트워크 대역폭이나 다른 리소스 타입은 지원하고 있지 않다.

리소스 단위

리소스를 정의하는데 사용되는 단위는 CPU의 경우에는 ms(밀리 세컨드)를 사용한다. 해당 컨테이너에 얼마만큼의 CPU 자원을 할당할것인가인데, 대략 1000ms가 1 vCore (가상 CPU 코어) 정도가 된다. 클라우드 벤더에 따라 또는 쿠버네티스를 운영하는 인프라에 따라서 약간씩 차이가 있으니 참고하기 바란다.

메모리의 경우에는 Mb를 사용한다.

Request & Limit

컨테이너에 적용될 리소스의 양을 정의하는데 쿠버네티스에서는 request와 limit이라는 컨셉을 사용한다.

request는 컨테이너가 생성될때 요청하는 리소스 양이고, limit은 컨테이너가 생성된 후에 실행되다가 리소스가 더 필요한 경우 (CPU가 메모리가 더 필요한 경우) 추가로 더 사용할 수 있는 부분이다.


예를 들어 CPU request를 500ms로 하고, limit을 1000ms로 하면 해당 컨테이너는 처음에 생성될때 500ms를 사용할 수 있다. 그런데, 시스템 성능에 의해서 더 필요하다면 CPU가 추가로 더 할당되어 최대 1000ms 까지 할당될 수 있다.


리소스를 정의하는 방법은 아래와 같이 Pod spec 부분에서 개별 컨테이너 마다. Resources 파트에 request와 limit으로 필요한 리소스의 최소/최대양을 정의하면 된다.


apiVersion: v1

kind: Pod

metadata:

 name: frontend

spec:

 containers:

 - name: db

   image: mysql

   resources:

     requests:

       memory: "64Mi"

       cpu: "250m"

     limits:

       memory: "128Mi"

       cpu: "500m"

 - name: wp

   image: wordpress

   resources:

     requests:

       memory: "64Mi"

       cpu: "250m"

     limits:

       memory: "128Mi"

       cpu: "500m"


위의 예제에 따라서 정의된 Pod내의 컨테이너 CPU 리소스의 할당은 다음과 같이 된다.


db라는 이름과 wp라는 이름의 컨테이너는 생성시 250ms 만큼의 CPU 리소스를 사용할 수 있도록 생성이 되고, 필요시 최대 CPU를 500ms 까지 늘려서 사용할 수 있다.

모니터링 리소스

그러면 사용할 수 있는 리소스의 양과 현재 사용되고 있는 리소스의 양을 어떻게 모니터링할 수 있을까?

사용할 수 있는 리소스의 양은 쿠버네티스 클러스터를 생성하는데 사용된 node의 스펙을 보면 알 수 있다. 예를 들어 2 코어 VM 5대로 node를 만들었다면 그 총량은 10 코어 = 10,000ms가 된다.

그러나 이 자원을 모두 사용자 애플리케이션에 사용할 수 있는 것이 아니다. 쿠버네티스 클러스터를 유지하는 시스템 자원이나 또는 모니터링등에 자원이 소비되기 때문에 실제로 사용할 수 있는 자원의 양을 확인하는게 좋은데 “kubectl describe nodes” 명령을 이용하면 된다.

아래 예제는 kubectl describe nodes 명령으로 node들의 상세정보중에서 한 node의 자원 상태를 모니터링한 내용이다.



아래 붉은 박스를 보면 총 4 코어 머신으로 현재 request된 CPU는 1081m이고 limit으로 296m를 확보하고 있다. 메모리는 request 된것은 685M가 requested 되었고, 약 1G가 limit으로 확보되어 있다.

실제 사용량은 붉은 박스 위를 보면 되는데, default 네임 스페이스의 client-6bcxxx Pod는 현재 110m의 CPU를 request해서 사용중인것을 확인할 수 있다.


확보된 리소스와 현재 실제로 사용되는 리소스의 양은 다른데, “kubectl top nodes” 명령을 이용하면 실제로 사용되고 있는 리소스의 상태를 확인할 수 있다. 아래는 4개의 노드에서 실제로 사용되고 있는 리소스의 양이다. 붉은 색으로 표시된 노드가 위의 예제와 같은 노드인데, 위에서 requested 된 양은 1081m이었는데, 실제 사용된 cpu는 151m가 사용되고 있다.


Pod들의 리소스 사용량은 “kubectl top pods” 명령으로 확인이 가능하다.


ResourceQuota & LimitRange

이제까지 컨테이너 운영에 필요한 리소스의 양을 명시하여 요청하는 방법을 알아보았다.

만약에 어떤 개발자나 팀이 불필요하게 많은 리소스를 요청한다면, 쿠버네티스 클러스터를 운영하는 입장에서 자원이 낭비가 되고, 다른 팀이 피해를 볼 수 있는 상황이 될 수 있다. 그래서, 쿠버네티스에서는 네임스페이스별로 사용할 수 있는 리소스의 양을 정하고, 컨테이너마다 사용할 수 있는 리소스의 양을 지정할 수 있는 기능을 제공한다.

Resource Quota

Resource Quota는 네임스페이스별로 사용할 수 있는 리소스의 양을 정한다.

아래는 예는 demo 네임스페이스에, CPU 는 500m ~ 700m 까지, 메모리는 100M~500M까지 할당한 예제이다.



이 용량안에서 demo 네임스페이스내에 컨테이너를 자유롭게 만들어서 사용할 수 있다.

Limit Range

Resource Quota가 네임 스페이스 전체의 리소스양을 정의한다면, Limit Range는 컨테이너 개별 자원의 사용 가능 범위를 지정한다.

아래 예제를 보자.



  • default 로 정의된 부분은 컨테이너에 limit을 지정하지 않았을 경우 디폴트로 지정되는 limit이다. 여기서는 cpu 600m, 메모리 100m로 정의되었다.

  • defaultRequest 로 정의된 부분은 컨테이너의 request를 지정하지 않았을 경우 디폴트로 지정되는 request의 양이다.

  • max : 컨테이너에 limit을 지정할 경우, 지정할 수 있는 최대 크기이다.

  • min : 컨테이너에 limit을 지정할 경우, 지정할 수 있는 최소 크기이다.  

Overcommitted 상태

이  request와 limit의 개념이 있기 때문에 생기는 문제인데, request 된 양에 따라서 컨테이너를 만들었다고 하더라도, 컨테이너가 운영이되다가 자원이 모자르면 limit 에 정의된 양까지 계속해서 리소스를 요청하게 된다.

컨테이너의 총 Limit의 양이 실제 시스템이 가용한 resource의 양보다 많을 수 있는 경우가 발생한다. 이를 overcommitted 상태라고 한다.

Overcommitted 상태가 발생하면, CPU의 경우에는 실제 사용량을 requested 에 정의된 상태까지 낮춘다. 예를 들어 limit이 500, request가 100인 경우, 현재 500으로 가동되고 있는 컨테이너의 CPU할당량을 100으로 낮춘다. 그래도 Overcommitted 상태가 해결되지 않는 경우, 우선 순위에 따라서 운영중인 컨테이너를 강제 종료 시킨다.  

메모리의 경우에는 할당되어 사용중인 메모리의 크기를 줄일 수 는 없기 때문에, 우선 순위에 따라서 운영 중인 컨테이너를 강제 종료 시킨다.  Deployment,RS/RC에 의해 관리되고 있는 컨테이너는 다시 리스타트가 되고 초기 requested 상태의 만큼만 자원 (메모리/CPU)를 요청해서 사용하기 때문에, overcommitted  상태가 해제된다.

Best practice

구글 문서에 따르면 데이타 베이스등 아주 무거운 애플리케이션이 아니면, 일반적인 경우에는 CPU request를 100m 이하로 사용하기를 권장한다.

또한 세밀하게 클러스터를 운영하기 어려운 경우에는 request와 limit의 사이즈를 같게 하는 것을 권장한다. limit이 request보다 클 경우 overcommitted 상태가 발생할 수 있는데, 이때 CPU가 throttle down 되면, 실제 필요한 CPU양 보다 작은 CPU양으로 줄어들기 때문에 성능저하가 발생할 수 있다.  



Heroku에서 Metrics 메뉴를 이용하여 애플리케이션 모니터링 하기


조대협 (http://bcho.tistory.com)


heroku에서는 Metrics이라는 메뉴를 통해서 기본적인 시스템 모니터링을 지원한다. 기본 지원 항목으로는

응답시간 (Response time), 메모리 사용률 (Memory Usage), 분당 처리량 (Throughput-TPM), CPU 사용률 (Dyno Load)을 볼 수 있다. 이 항목들은 Heroku 대쉬보드에서 모니터링하고자 하는 애플리케이션을 선택한 후, 상단의 Metrics라는 탭을 선택하면 된다.  

 

아래는 Metrics 메뉴를 통해서 helloherokuterry 애플리케이션의 주요 지표를 모니터링 한 화면이다.



한가지 주의할점은 Metrics 모니터링 기능은 무료 dyno로는 사용이 불가능하며 최소 standard-1x dyno 부터 지원이 된다.


Apache Spark(스파크) - RDD Persistence (스토리지 옵션에 대해서)


조대협 (http://bcho.tistory.com)


Spark Persistence에 대해서


앞에 글에서 Spark RDD가 메모리에 상주 되는 방법에 대해서 간략하게 언급했는데, 다시 되 짚어 보면 Spark의 RDD는 filter() 등. 여러  Transformation Operation을 실행하더라도  Transformation  단계가 아니라 Action이 수행되는 단계에 로드된다고 설명하였다.


그리고, 매번 해당 RDD가 Action으로 수행될 때마다 다시금 소스에서 부터 다시 로드되서 수행된다고 했는데, 그렇다면 매번 로드 해서 계산하여 사용하는 것이 아니라, 저장해놓고 사용 하는 방법이 무엇이 있을까?


스파크에서는 RDD 를 저장해놓고 사용하는 기능으로 persist()와 cache() 라는 두 가지 오퍼레이션을 지원한다.

스파크는 RDD를 저장함에 있어서,  메모리와 디스크 두 가지 영역을 사용하며, 옵션에 따라서 RDD의 저장 영역을 지정할 수 있다.


기본 디폴트는 메모리에 저장하고, 옵션으로 디스크를 지정할 수 있다. 디스크를 지정하면, 메모리에서 모지란 부분을 마치 swapping 하듯이 디스크에 데이타를 저장한다.


아래 옵션 참고 




출처 : https://spark.apache.org/docs/latest/programming-guide.html#rdd-persistence


여기에 특이한 점은, 메모리나 디스크에 저장할때, RDD를 RAW (원본 형식)으로 저장할 것인지 자바의 Serialized 된 형태로 저장할 지를 선택할 수 있다. ( Serealized 된 형태로 저장하기 MEMORY_ONLY_SER, MEMORY_AND_DISK_SER) 이렇게 저장하면, 메모리 사용량은 더 줄일 수 있지만, 반대로 저장시 Serizalied하는 오버로드와, 읽을때 De-Seriazlie 하는 오버로드가 더 붙어서 CPU 사용량은 오히려 증가하게 된다.


아래 데이타는 http://sujee.net/2015/01/22/understanding-spark-caching/#.VWcOh1ntlBc 의 데이타 긴데,

Serialized 로 저장하는 경우, 최대 약 4배 정도의 메모리 용량을 절약할 수 있으나, 반대로, 처리 시간은 400배 이상이 더 들어간다.

 









 


다음으로, 특이한 옵션중에 하나가 OFF_HEAP 이라는 옵션인데, 스파크는 JVM 상에서 동작하기 때문에, 스파크가 저장하는 메모리란 JVM 상의 메모리를 뜻한다. JVM  특성상 Garbage collection 에 의한 성능 제약을 받을 수 있으며 또한 별도로 서로 복제가 되지 않기 때문에, (기본 옵션의 경우에만), 안정적인 서비스를 원할 경우에는 별도의 복제 옵션을 선택해야 한다.

이런 문제를 해결하기 위한 다른 옵션으로는 JVM 내에 데이타를 저장하는 것이 아니라, 별도의 JVM 외의 메모리 공간에 데이타를 저장하는 방식이 OFF_HEAP 이라는 옵션이다. 아직 안정화 되지는 않았지만, http://tachyon-project.org/ 이라는 메모리 클러스터를 이용하여, 서로 복제가 가능한 외부 메모리 클러스터에 저장하는 방식으로, JVM  상 메모리 보다는 성능이 약간 떨어지지만, 디스크보다는 빠르며, 큰 메모리 공간을 장애 대응에 대한 상관 없이 (자체 적으로 HA  기능을 제공함) 사용이 가능하다. 

 cf. Redis나  Infinispan등과 같은 메모리 기반의 데이타 그리드 솔루션의 하나인 Hazelcast 역시 JVM 밖의 네이티브 메모리 공간에 데이타를 저장하는 유사한 방식을 사용한다.


Persist vs Cache


그렇다면, persist()와 cache()의 차이점은 무엇인가? cache()는  persist() 에서 저장 옵션을 MEMORY_ONLY로 한 옵션과 동일하다.


저장된 RDD는 메모리나 디스크에서 언제 삭제 되는가?

RDD가 메모리나 디스크에 로드되었다고 항상 로드된 상태로 있는 것이 아니다. 기본적으로 LRU (Least Recently Used)  알고리즘 (가장 근래에 사용되지 않은 데이타가 삭제되는 방식)에 의해서 삭제가 되가나, 또는 RDD.unpersiste() 함수를 호출하면 명시적으로 메모리나 디스크에서 삭제할 수 있다.


언제 어떤 타입의 Peristence옵션을 사용해야 하는가?


가장 좋은 옵션은 디폴트 옵션인  MEMORY_ONLY  옵션이다. 가장 빠르다.

다음으로 메모리가 모자를 경우에는  MEMORY_ONLY_SER 옵션을 이용하면, Seriazlied 된 형태로 저장하기 때문에 메모리 공간은 줄일 수 있으나, 대신 CPU 사용률이 올라간다. 그래도 여전히 빠른 방식이다.

데이타 양이 많을 경우에는 DISK에 저장하는 옵션보다는 차라리 Persist 를 하지 않고, 필요할때 마다 재계산 하는 것이 더 빠를 수 있다.

빠른 응답이 필요한 경우에 Persist 된 데이타에 대한 유실을 방지하려면, replicated storage 옵션을 사용하는 것이 좋다. (MEMORY_ONLY2 등).  다른 스토리지 타입 역시, 장애로 인해서 데이타가 유실되더라도 재계산을 통하여 복구가 가능하지만, 재계산 하는 것 보다는 RDD 의 복제본을 저장해 놓고, 장애시 페일오버 하는 것이 빠르기 때문에, 빠른 응답시간을 요구로 하는 웹 애플리케이션의 경우 이 스토리지 타입이 유리하다. (단, 메모리 사용량은 복제본을 저장하는데도 사용되기 때문에 상대적으로 일반 스토리지 옵션에 비해서 메모리 여유가 적다.)



참고 

Learning Spark

Spark document - https://spark.apache.org/docs/latest/programming-guide.html#rdd-persistence


참고 - 이 글은 제가 스파크를 혼자 공부하면서 문서만을 통해서 정리한 글이기 때문에, 실무적인 경험이 많이 녹아 들어 있지 않습니다. 

MongoDB를 구성할때 보면, 가장 많이 권장 받는 부분 중의 하나가, 메모리량과 디스크 성능이다.

메모리 크기가 아주 sensitive한 요인이 되는데, 어떤 부분이 문제가 되는지 내부 저장 구조를 살펴 봄으로써 이해를 돕고자 한다.


저장 구조

mongodb는 기본적으로 memory mapped file (OS에서 제공되는 mmap을 사용) 을 사용한다. mongodb는 데이타를 write할때, 논리적으로 memory 공간에 write하고, 일정 주기에 따라서, 이 메모리 block들을 주기적으로 disk로 write하는데, 이 디스크 writing 작업은 OS에 의해서 이루어 진다.


OS에 의해서 제공되는 Virtual Memory를 사용하게 되는데, Pysical Memory 양이 작더라도 Virtual Memory는 훨씬 큰 공간을 가질 수 있다. Virtual Memory는 page라는 블럭 단위로 나뉘어 지고, 이 Block들은 Disk의 block에 mapping이되고, 이 block들의 집합이 하나의 데이타 파일이 된다. (아래 그림 참조)




참고 http://www.polyspot.com/en/blog/2012/understanding-mongodb-storage/


메모리에 저장되는 내용

메모리에 저장되는 내용은 실제 데이타 블록과, Index 자체가 저장이 된다. mongodb에서 index를 남용하지 말라는 이야기가 있는데, 이는 index를 생성 및 업데이트 하는 데 자원이 들어갈뿐더러, index가 메모리에 상주하고 있어야 제대로 된 성능을 낼 수 있기 때문이기도 하다.


만약에 Physical Memory에 해당 데이타 블록이 없다면, page fault가 발생하게 되고, disk에서 그 데이타 블록을 loading하게 된다. 물론 그 데이타 블록을 loading하기 위해서는 다른 데이타 블록을 disk에 써야 한다.

즉, page fault가 발생하면, page를 memory와 disk 사이에 switching하는 현상이 일어나기 때문에, disk io가 발생하고, 성능 저하를 유발하게 된다.


즉 메모리 용량을 최대한 크게 해서 이 page fault를 예방하라는 이야기이다.

그러나, page fault가 안 발생할 수 는 없고, (1TB 저장하려고, 메모리를 진짜 1TB를 저장할 수 없는 노릇이니...). page fault를 줄이는 전략으로 접근 하는 것이 옳은 방법인데..


page fault시 disk로 write되는 데이타는 LRU 로직에 의해서 결정이 된다. 그래서, 자주 안쓰는 데이타가 disk로 out되는데, 일반적인 application에서 자주 쓰는 데이타 (최근 데이타)의 비율은 그리 크지 않다. 예를 들어 게시판이나 블로그만을 생각해보더라도, 앞의 1~10 페이지 정도가 많이 보게 되지 뒤의 데이타를 잘 안보게 된다. 


이렇게 자주 억세스 되는 데이타를 Hot Data라고 하는데,이 Hot Data 들이 집중되서 메모리에 올라가도록 key 설계를 하는 것이 핵심이다.

쓸떼 없이 전체 데이타를 scan하는 등의 작업을 하게 되면, 100% page fault가 발생하기 때문에, table scan등이 필요한 시나리오는 별도의 index table(summary table)을 만들어서 사용하는 등의 전략이 필요하다.


note

Physical memory < Virtual Memory (=Physical memory + Swapping) < mmap = total file size < disk size



Do I need to configure swap space?

Always configure systems to have swap space. Without swap, your system may not be reliant in some situations with extreme memory constraints, memory leaks, or multiple programs using the same memory. Think of the swap space as something like a steam release valve that allows the system to release extra pressure without affecting the overall functioning of the system.

Nevertheless, systems running MongoDB do not need swap for routine operation. Database files are memory-mapped and should constitute most of your MongoDB memory use. Therefore, it is unlikely that mongod will ever use any swap space in normal operation. The operating system will release memory from the memory mapped files without needing swap and MongoDB can write data to the data files without needing the swap system.


http://docs.mongodb.org/manual/faq/diagnostics/#faq-memory