블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-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양으로 줄어들기 때문에 성능저하가 발생할 수 있다.  




쿠버네티스 보안 Best Practice


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


지금까지 여러가지 보안 기능에 대해서 알아보았다. 그러면 이러한 보안 기능을 어떻게 잘 사용할지 베스프 프렉틱스에 대해서 알아보자. 쿠버네티스 보안 베스트 프렉틱스는 쿠버네티스 공식 블로그 https://kubernetes.io/blog/2016/08/security-best-practices-kubernetes-deployment/ 에 2016년 8월에 포스팅과 https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/ 에 2018년 7월에 포스팅된 내용을 기반으로 한다. 쿠버네티스는 새버전 릴리즈가 빠른 편이고 버전마다 기능이나 아키텍쳐의 변화가 심하기 때문에, 항상 새로운 베스트 프렉틱스를 찾아서 참고하기 바란다.

이 글에서는 쉽지만 중요한 보안 정책을 위주로 설명한다.

Control plan security

TLS (SSL) 사용

쿠버네티스를 설치하면 디폴트로 API 통신은 TLS(SSL) 암호화를 이용하도록 되어 있으나, 일부 재배포판의 경우는 REST API통신을 HTTPS를 사용하지 않고, HTTP를 사용하는 경우가 있기 때문에, 이를 확인할 필요가 있다.


인증

쿠버네티스는 앞에서 설명한데로 여러가지 인증 방식을 제공하고 있는데, 그중에 BASIC_AUTH를 사용하는 방식등은, 비밀 번호가 그냥 네트워크를 통해서 전송되기 때문에, 중간에 패킷을 가로 채는 방식등으로 탈취가 가능하다.

쿠버네티스는 이외에도 Bootstrap token, static token, X509 인증서, Open ID 연동등 다양한 인증 방식이 있는데, 가급적이면 Open ID 인증 방식을 사용하는 것이 안전하다.

RBAC 사용

쿠버네티스의 기본 인증 방법은 ABAC(Attirbute-Based control) 이다.  사용자마다 기능에 대해서 권한을 배정하는 방법인데,

RBAC은 1.6에서 소개되었고, 1.8 부터는 디폴트이다. 1.6~1.8 버전은 RBAC 설정을 따로 하기 바라고, 1.6 이하 버전은 1.8 이상 버전으로 업그레이드 하는 것을 권장한다.

대쉬 보드 사용 금지

쿠버네티스 대쉬 보드는 편리하고 강력한 기능을 가지고 있지만, 별도의 접근 통제 기능이 디폴트로 탑재되어 있지 않다. 1.8 이전 버전에는 클러스터에 대한 모든 억세스가 가능한 서비스 어카운트가 바인딩되어 있기 때문에, 클러스터에 대한 모든 접근이나 보안 정보(토큰)등의 탈취가 가능하다.

실제로 테슬라의 경우에 쿠버네티스 대쉬보드 접근을 통해서, 해킹을 당한 사례가 유명하다. https://redlock.io/blog/cryptojacking-tesla

가급적이면 쿠버네티스 대쉬보드를 사용하지 않도록 하는 것이 좋고 (인스톨하지 않는다.), 사용한다고 해도, 계정 인증과, 내부 인터넷망을 통한 접근만을 허용하는등의 추가적인 보안 조치가 반드시 필요하다.

kubectl 억세스 통제 (마스터 노드 억세스 통제)

kubectl은 쿠버네티스를 통제할 수 있는 매우 강력한 툴이다. 일종의 어드민툴이기 때문에 접근 제어를 하는 것이 좋다. 쿠버네티스 방화벽 설정등을 해서 특정 머신에서만 오는 트래픽만 마스터 노드가 받아 드리도록 설정하는 방법이다.  태그 기반으로 k8s-controller로 가는 트래픽을 특정 머신에서 오는 트래픽만을 수용하게 하거나 또는 bastion을 놓고, bastion에서 들어온 API 호출만 수용하도록 하는 방법이 있다.

서비스 어카운트 토큰 마운트을 자동으로 마운트 하지 않게 한다.

Pod는 기본적으로 서비스 어카운트를 사용하게 되어 있다. 만약 서비스 어카운트를 지정하지 않으면 디폴트로 정의된 서비스 어카운트를 사용하게 되는데, 쿠버네티스에서는 디폴트로 서비스 어카운트를 사용하게 되면 서비스 어카운트의 API 토큰을 볼륨으로 마운트 한다.

서비스 어카운트 볼륨은 /var/run/secrets/kubernetes.io/serviceaccount 디렉토리에 마운트 되는데, 이 디렉토리 안에는 API인증을 위한 인증서와, 토큰이 들어 있다.

만약 이 토큰을 탈취당하게 되면, 토큰을 이용하여 쿠버네티스 API 접근이 가능하다.

일반적인 Pod의 경우에는 애플리케이션을 운영하기 위한 목적으로 사용될뿐, 쿠버네티스 API를 접근할 일이 없기 때문에 사용하지 않는 토큰을 마운트 하는 것은 위험하다. 해커가 컨테이너를 해킹해서  /var/run/secrets/kubernetes.io/serviceaccount 디렉토리를 접근한다면 토큰을 탈취할 수 있다.

이를 막기 위해서, 서비스 어카운트를 사용시 디폴트로 서비스 어카운트 토큰을 마운트하지 않게 하는 것이 좋다.


아래와 같이 서비스 어카운트를 생성할때, 자동으로 토큰을 마운트 하지 않는 옵션을 주거나,

apiVersion: v1

kind: ServiceAccount

metadata:

 name: nonroot-sa

automountServiceAccountToken: false


또는 아래와 같이 Pod 정의 부분에서 서비스 어카운트에 디폴트로 토큰을 마운트 하지 않게 정의하면 된다.


apiVersion: apps/v1

kind: Deployment

metadata:

 name: nonroot-deploy

spec:

 replicas: 3

 selector:

   matchLabels:

     app: nonroot

 template:

   metadata:

     name: nonroot-pod

     labels:

       app: nonroot

   spec:

     serviceAccountName: nonroot-sa

     automountServiceAccountToken: false

     securityContext:

       runAsUser: 1001

       fsGroup: 2001

     containers:

     - name: nonroot

       image: gcr.io/terrycho-sandbox/security-context:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


반대로 토큰을 사용하고 싶을때는 Pod 정의 부분에서 automountServiceAccountToken: true 옵션을 주면된다.


감사 로깅 (Audit)

쿠버네티스 클러스터에 대한 각종 명령어 (Pod 생성, Service 생성 삭제 등)에 대한 내용을 추적하기 위해서 모든 로그를 남겨서 정상적인 접근 여부를 판단하고, 비정상적인 접근이 발생하였을때, 이를 감지하고 추적할 수 있는 기반을 마련해야 한다. 모니터링/로깅 시스템 구축시 감사 로깅은 별도로 분리해서 감사에 대한 내용만을 따로 추적할 수 있도록 하는것이 좋다.

노드 시큐리티

컨테이너를 호스팅하는 노드에 대해서도 보안 조치가 필요하다. 다음 항목은 필수적으로 적용하기를 권장되는 항목이다.

노드에 Private IP 만 사용 (Public IP 사용 금지)

노드 서버의 IP를  Private IP만을 사용하여, 외부 인터넷으로 부터 노드 서버를 접속할 수 있는 경로를 원천적으로 차단한다.

Minimal OS 사용

노드 서버의 OS는 필요한 기능만을 가지고 있는 OS만을 설치하는 것이 좋다. OS에 따라서 디폴트로 메일 서버, FTP등 사용하지 않는 서비스가 디폴트로 제공됨으로써, 노드 서버로 접근할 수 있는 채널을 제공할 수 있다.

정기적인 패치

또한 노드 OS에 대해서 정기적인 보안 패치를 적용함으로써, 새롭게 발견되는 보안 위협에 대해 사전 봉쇄 조치를 취해야 한다

컨테이너 시큐리티

마지막으로 컨테이너에 대한 보안을 강화하는 방법이다.

컨테이너 이미지 관리

가장 중요한 것중의 하나가 컨테이너 이미지를 잘 관리하는 것인데, 일반적으로 도커로 이미지를 만들때, 베이스 이미지 (OS등이 깔려있는)를 외부 컨테이너 레파지토리에서 가져다가 사용하는 경우가 많다. 이 경우 공인되지 않는 이미지등을 사용해서, 해킹 프로그램이 깔려있는 이미지가 베이스 이미지로 사용되거나, 또는 최신 보안 패치가 되어 있지 않은 이미지로 전환하지 않고 계속 오래된 이미지를 사용해서, 보안에 헛점을 들어내는 경우가  많다.

베이스 이미지 사용은 반드시, 보안이 검증된 이미지를 사용하되, 지속적으로 최신 OS 패치를 적용한 이미지를 사용해야 한다.

베이스 이미지를 포함하여 실제 쿠버네티스에 배포되는 애플리케이션 컨테이너 이미지는 신뢰할 수 있는 이미지 저장 서비스를 이용하고, 해당 클러스터 및 허가된 IP나 사용자만 접근할 수 있도록 하는 것이 좋다.


또는 상용 서비스 중에는 컨테이너 저장소에 저장된 이미지를 스캔해서 보안에 위협이 되는 항목을 자동으로 검출하여 알려주는 서비스들이 있다.

아래 그림은 구글 클라우드의 컨테이너 저장소 서비스로, 컨테이너에 저장된 이미지에 대해서 보안 위협을 자동으로 스캔해서 리포팅 해주는 기능이다.




Security Context 사용

Pod를 정의할때, 불필요한 root나 커널 접근 권한을 최대한 제외하는 것이 좋다.

Security context를 이용해서 이런 권한을 통제할 수 있다.

  • 컨테이너는 꼭 필요하지 않는 이상 root 사용자 권한이 아니라 일반 사용자로 실행하도록 한다. securityContext에서 runAsUser와 fsGroup을 이용해서 사용자와 그룹을 지정할 수 있다.

  • Root 권한으로 실행할 수 없도록 securityContext에서 runAsNonRoot 를 true로 설정한다.

  • 꼭 필요한 경우가 아니라면 root 권한으로 생성된 파일이나 디렉토리에 대해서는 읽기만을 할 수 있도록 SecurityContext에서 readOnlyRootFilesystem 를 true로 설정한다.

  • 마지막으로 필요한 경우가 아니라면 호스트 커널에 대한 접근을 막기 위해서 securityContext에서 privileged를 false로 설정한다.

PodSecurityPolicy를 이용한 Pod Security Context 통제

SecurityContext를 위와 같이 설정하도록 권고하지만, 이를 빼먹을 수 있기 때문에,  PodSecurityPolicy (PSP)를 정의하여, Security context를 강제할 필요가 있다.

Network Policy를 이용한 트래픽 통제

마지막으로 Network policy를 정의해서, Pod로의 네트워크 접근을 통제하여 불필요한 접근을 막는다. 예를 들어 MySQL DB서버로의 접근은 label이 app=apiserver인 서버들만 3306으로 inbound 트래픽만 들어올 수 있도록 통제하는 등의 예를 들 수 있다.