Kubernetes - 효율적 운영을 위한 검토사항

466

Importants

Kubernetes Cluster를 테스트하고 운영하는 것과 관련해서 프로젝트를 진행하면서 겪었던 내용과 유사한 고민과 해결방법을 제시하는 글을 보고 고민했던 내용과 게시글을 혼합해서 일단 정리해 놓도록 한다. 어떤 부분은 실제 적용한 부분도 있고, 다른 부분은 실제 테스트를 못해 봤기 때문에 관련된 참고 게시글의 내용을 나름대로 유추해서 정리한 부분도 존재한다.

따라서 아래의 정리된 내용은 개인적인 판단과 이해를 기준으로 정리한 것이기 때문에 Kubernetes 에서 제시된 정보와 다를 수 있으며 혼선을 일으킬 수 있다. 따라서 Kubernetes에서 제시하는 모든 관련 문서들을 읽으신 후에 수 많은 케이스 중에 하나일 뿐이라는 생각으로 접근하는 것이 좋다.

AWS 상의 Kubernetes Cluster를 효율적으로 운영하기 위한 검토

프로젝트가 진행되는 동안 AWS 상에 Kubeadm 를 이용해서 Kubernetes를 설치하고 각종 테스트 및 데모를 진행 했었다.

그 과정에서 운영을 위한 몇 가지 문제점들에 직면 했었고 문제점들은 서로 연관성을 가지고 발생하는 것으로 보이기에 여러 가지 상황들을 검토 하고 적용해 보았다.

  • 리소스 기본 배정 (BestEffort) 방식으로 과부하가 걸리면 노드들이 사용불가 상태에 빠지는 문제가 빈번하게 발생한다.
  • 클러스터를 많은 리소스들을 가지는 인스턴스들(노드들)로 구성하는 것이 무조건 효율적이지는 않으며 비용 문제가 발생한다.
  • 일반적인 어플리케이션들과 데이터베이스 어플리케이션들의 리소스 배정의 기준이 필요하다.
  • 어플리케이션 운영을 위해서 어느 정도 규모의 인스턴스를 사용해야 하는지 결정을 위한 검토가 필요하다. (즉, 서비스에 비해 과도하게 AWS 비용이 발생할 수 있다)
  • Resource Qutoas 는 namespace 단위로 적용된다. 따라서 클러스터 내에 여러 개의 namespace를 운영 중이라면 각 namespace 별로 Resource Quota를 지정하는 형식이 되기 떄문에 클러스터를 구성하는 노드들의 총 리소스량과 Quotas에 배정된 양이 일치하지 않는 상태가 발생할 수 있고, 노드의 가용량을 측정하면서 Quota와 연계하는 방식이 아닌 수동 지정 방식이기 때문에 언제든지 문제가 발생할 소지는 남아있다.

Notes

리소스라는 것은 CPU, Memory, Disk, Network, … 등이 될 수 있지만 현재 버전에서는 대 부분 CPU, MEMORY를 기준으로 한다.

진행하면서 정말 다양하고 이해하기 애매한 상황들이 발생했지만, 결국 모든 것은 클러스터 내에서 어떻게 리소스를 배정하고, 서비스를 운영하기 위한 정책적인 결정과 제한 처리들을 구성하는냐? 의 문제를 정리하면 될 듯 하다.

고민하고 검토했던 내용들은 간략하게 정리하기에는 어렵고 광범위한 내용이지만 적용했던 결론(?)을 기준으로 하나씩 알아보도록 한다.

리소스 배정 검토

Kubernetes 기본 옵션으로 클러스터가 구성되면 각 노드들은 Pod 배정을 BestEffort 방식으로 처리한다. 즉, 배정되면 노드의 모든 리소스를 무제한으로 사용하려고 한다. 추가 배정이 이뤄지면 각각 분할해서 최대한 사용하려고 한다. 이런 상황에서 데이터베이스와 같이 리소스를 과다하게 사용하는 Pod 들이 리소스 사용에 대한 규칙없이 배정이 되기 시작하면 어느 순간부터 Pod 들이 종료와 재 배정의 상태에 빠지고 로그를 확인하면 OOM Killed 와 관련된 오류들을 자주 접하게 된다.

리소스에 관련된 것은 대부분의 문제를 야기했던 가장 중요한 부분이고 모든 것의 근원이 되는 것으로 서비스에 대한 요청 (requests) 량과 제한 량 (limits) 를 기준으로 리소스의 배정을 결정해야 한다.

이 부분은 쉽게 결정할 수 있는 부분이 아니기 떄문에 문서를 읽어보고 신중하게 접근하기를 바란다. 간략하게 의미를 정리하면 다음과 같다.

  • requests: Pod 를 노드에 배정하기 위한 최소한의 요구량이라고 이해하면 된다. 즉, 이 정도의 리소스 요구를 받아줄 수 있는 노드를 검색하기 위한 것이다.
  • limits: Pod 가 노드에서 사용할 수 있는 최대치의 리소스량이라고 이해하면 된다.

Importants

만일 어플리케이션의 CPU 사용량이 설정된 limit 값을 넘어서는 경우는 CPU 할당에 따른 지연이 발생한다. 그러나 Memory의 경우는 limit 값을 넘어서는 순간 Pod 가 강제로 종료되는 문제가 있다. 종료가 되면 Replica 설정 때문에 다시 배정되는 상황이 발생하는데 노드들이 과부하가 걸려서 배정할 수 있는 노드가 없다면 배정될 수 있을 때까지 계속 배정 시도를 하는 문제가 발생하게 된다. (물론 재생성 금지 등의 옵션을 설정할 수도 있기는 하다)

대략적인 구성은 아래와 같으며 namespace 단위로 적용된다.

[Deployment에 request, limit 지정]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
cpu: 500m
memory: 600Gi
requests:
cpu: 100m
memory: 200Mi

위의 예는 nginx 어플리케이션의 CPU 와 Memory에 대한 request, limit 값을 설정해서 실행하는 것이다. CPU 는 최소 100m 에서 최대 500m (Milicores 단위) 까지 사용하고 Memory는 200Mi 에서 600Mi (MegaByte 단위) 까지 사용 하도록 설정한 것이며, Pod 가 배정이 되면 Dashboard 의 namespace 에서 Resource Quota 정보를 확인할 수 있다.

Importants

위의 정보는 Pod 의 Resource 설정에 대한 부분이므로 실제 배정될 노드의 가용 량과는 상관이 없다. 따라서 클러스터를 구성하고 있는 모든 노드들이 request 를 충족하지 못할 정도로 과부하가 걸린 상태라면 배정되지 못하고 계속 생성 대기 상태가 될 수도 있다.

문제는 리소스 사용량을 지정하는 것이 중요한 것이 아니라 정말 이 값이 적절한 것인가? 를 검토해야 한다.

딱히 정답이 없는 것이기 때문에 실제 구성할 떄는 다음과 같이 결정을 진행했었다.

  1. Heapster 설치 환경에서 제한 (request, limit) 이 없이 실행해서 초기 실행 상태의 resource 사용량을 Dashboard 에서 측정한다.
  2. 부하를 발생시켜서 리소스 사용량을 Dashboard 에서 측정한다.
  3. 위의 작업의 결과를 기준으로 초기 사용량을 request 로 설정하고 부하가 발생했던 사용량에 10% 정도를 추가한 사용량을 limit 로 설정한다. 10% 를 추가하는 경우는 과부하 발생을 위한 예비를 하는 것이다.
  4. 실제와 비슷하게 운영하면서 리소스 사용량을 측정하고 문제가 발생하는 경우는 리소스 할당량을 조정한다.

물론 아주 단순 무식한 방법이기는 하지만 대략적인 적용은 할 수 있다. 중요한 것은 지속적으로 모니터링을 통해서 변경되는 리소스량을 최대한 근접하게 조정해가는 것이다.

Loadbalancer 활용 검토

Kubernetes 에서 서비스를 생성할 떄 service.beta.kubernetes.io/aws-load-balancer-internal annotation을 지정하면 AWS Elastic Loadbalancer에 연결할 수 있다. 그러나 Kubernetes Cluster에서 운영하는 모든 서비스를 연결해서 운영하는 것은 비용적인 소비가 너무 많이 발생하게 된다.

Kubernetes에는 Ingress 가 존재하기 때문에 nginx 기반의 Ingress Controller를 구성 하고 서비스들을 여기에 연결하면 모든 서비스 요청은 Controller가 대상이 되는 Pod로 연결되도록 하기 때문에 AWS Loadbalancer를 Controller 당 하나씩만 유지하면 된다.

Ingress 작동 방식 을 잘 참고해서 처리하면 된다. Ingress와 Ingress Controller는 혼란스럽게 느껴지기는 하지만 Ingress는 정보의 집합이고 실제 분배는 Controller가 처리하는 것으로 개념을 잡으면 된다.

Idle Pod 관리

일반적으로 개발이 진행되는 동안에 검증을 위해서 서비스를 배포하고 테스트를 수행하고, 별 다른 문제가 없다면 다음 단계로 진행하게 된다. 그러나 배포된 서비스는 실제 사용되는 것과 상관없이 수행 중인 상태로 유지된다. 따라서 실제 사용이나 테스트 등과 상관없이 유휴상태가 되는 Pod 들을 종료시켜서 효율적으로 클러스터가 운영될 수 있도록 하는 것도 중요하다.

필요없는 서비스를 삭제한 후에 필요할 떄 다시 생성하는 것 보다는 Scale up/down 방식이 더 효율적인 것으로 판단해서 주기적으로 최근에 Ingress 설정을 통해서 접근되지 않았고 특정 시간 미만으로 동작하고 있는 서비스들을 제외한 서비스를 찾아서 Pod 를 제거 (Scale down to 0)해서 다른 작업에 클러스터의 리소스를 활용하는 운영을 하고, 향후에 필요할 때 Scale up 해서 사용하면 되는 방식을 적용하는 것도 좋다.

아래의 예제는 위의 처리 조건을 검증하는 것으로 1시간 동안에 Ingress를 통해서 접근이 발생했고, 30분 넘게 유지되고 있는 서비스를 골라서 제거하는 스크립트로 10분 단위로 수행될 수 있도록 crontab 에 등록해서 사용하면 된다.

[불필요한 서비스 Scale down 스크립트]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/bash
set -eo pipefail

# ingress-nginx controller 검색
ingress = "$(kubectl get pods --output=jsonpath='{.items[*].metadata.name}' | xargs -n1 | grep "ingress-nginx" | head -n1)"

# ingress에 지정된 모든 호스트 검색
hosts = "$(kubectl get ingress nginx-ingress --output=jsonpath='{.spec.rules[*].host}' | xargs -n1)"

# pods 검색
pods = "$(kubectl get pods)"

# 최근 1시간의 ingress 로그 검색
logs = "$(kubectl logs --since=60m "$ingress")"

# 모든 Deployments 검색
kubectl get deployment --output=jsonpath='{.items[*].metadata.name}' | xargs -n1 |
while read -r svc; do
# 동작하는 Pod가 없는 서비스 제외
echo "$pods" | grep -q "$svc" || {
echo "$svc: no pods running"
continue
}

# Ingress를 사용하지 않는 서비스 제외
echo "$hosts" | grep -q "$svc" || {
echo "$svc: not using ingress"
continue
}

# 30분 미만 동작 중인 Pods 제외
echo "$pods" | grep "$svc" | awk '{print $5}' | grep -q h || {
echo "$svc: pod running less than 30m"
continue
}

# Ingress 로그에 존재하는 서비스를 scale down
echo "$logs" | grep -q "default-$svc" || {
echo "$svc: scale down"
kubectl scale deployments "$svc" --replicas 0 --record || true
}
done

위의 예제는 실제 적용할 조건에 맞춰서 시간이나 namespace 이름 규칙등을 수정해서 사용하면 된다.

Cluster Auto-scaler 활용

Kubenetes 1.4 버전부터 Autoscaler 기능이 지원되고 있으며, AWS에서 실행하기 가이드도 확인할 수 있다. 물론 다른 클라우드 공급자 부분도 있지만 확인해 보지는 못했다.

주된 기능은 다음과 같은 상황에서 Cluster Size를 조정하는 것이다.

  • 리소스가 부족해서 클러스터 내에서 Pods 가 더 이상 배치할 수 없는 경우
  • 일정 시간 이상 활용할 수 없는 상태의 노드가 존재하는 경우 노드를 제거하고 다른 활용 가능한 노드로 Pods를 재 배치하는 경우

전체적인 부분을 모두 검토한 것은 아니지만 대략 Damons set 이 없거나, Mirror 상태가 아니거나 kube-system pod 들이 여러 노드에 분산되어 있는 경우등을 제외하면 아주 훌륭하게 적용할 수 있는 것으로 보인다.

Cluster-Autoscaler 는 계절적 또는 주기적으로 활용과 비 활용이 반복되는 경우에 적용할 수 있을 것으로 보인다. 즉, 평상시에는 잘 활용하다가 비 활용 시점이 되면 Cluster를 축소할 수 있도록 AutoScale Group에 예약된 작업 (특정한 시간이 되면 노드의 수를 줄이거나 늘리는 작업)을 등록해서 동적으로 운영될 수 있도록 조정하면 더욱 효율적으로 운영이 가능하다.

물론 이 작업이 진행되는 동안에 축소의 경우는 Pods 가 종료되고 재 배치되는 과정이 존재하기 떄문에 일부 서비스가 중지되는 상황이 발생할 수는 있지만 시간대를 잘 선택하면 외부의 접근이 발생하기 이전에 처리할 수 있으므로 큰 문제가 되지는 않을 것으로 보인다.

Instance Type 과 리소스 검토

실제 데모를 위해서 사용한 마이크로 서비스 예제에서 Java로 구성된 어플리케이션이 많아서 CPU 는 별로 사용하지 않지만 메모리를 많이 사용 (대략 700M 언저리)하는 문제가 존재 헀었다.

이 문제 때문에 m2.small 등의 인스턴스를 사용했을 떄 메모리 병목현상과 limit 초과 문제로 인해서 Pods 가 종료되고 재 배치되는 등의 부작용을 많이 겪었다.

Importants

실제 운영과 테스트를 해 보면 CPU 가 부족한 경우보다는 Memory가 부족해서 문제가 생기게 된다. Master 서버가 아닌 경우라면 CPU에 큰 제한이 없기 때문에 차라리 Memory를 충족하게 사용할 수 있는 인스턴스를 선택하는 것이 좋다. 물론 CPU를 과도하게 사용해야 하는 어플리케이션의 경우라면 모두 잘 검토해야 한다.

디스크의 경우도 기본 값인 8G 를 사용해서 테스트를 진행했지만 너무 부족한 상태에서 문제가 발생했었고, 30G (IOPS 100)로 설정했을 때는 디스크 부족 문제의 발생보다는 성능의 문제가 존재하기 때문에 IOPS 가 높게 선택하면 실제 사용량대비 비용이 과다하게 나오는 문제가 발생한다. 따라서 어느 정도의 타협으로 100G (IOPS 300) 정도로 타협하면 충분할 것으로 예상해서 적용했다.

물론 이 부분도 어떤 어플리케이션을 실행할 것인지가 중요하기 때문에 상황에 따라서 선택하면 된다.

또한 AWS 에서 Spot Instance가 아닌 Reserved Instance를 사용하면 좀 더 비용을 줄일 수 있는 것으로 알고 있으므로 이를 잘 활용하면 된다.

운영 클러스터 검토

지금까지 알아본 것은 실제 대외 서비스를 위한 환경이 아닌 내부 개발 및 데모를 위한 과정에서 검토한 내용들이다. 그러나 언젠가는 대외 서비스를 위한 프로덕션 환경으로 이전해야 할 것이기 때문에 다음과 같은 상황들을 검토해 봐야 한다.

  • 대외 서비스를 하고 있는 시스템에 주기적으로 Pods 를 Scale down 하는 것은 적절하지 않기 때문에 Kubernetes에서 지원하고 있는 Horizontal Auto Scaler 를 활용하는 것으로 검토해야 한다. 기준은 CPU 사용량에 따라서 확장 또는 축소를 처리하게 된다. (단, CPU 사용량 기준이라는 것이 애매해서 좀 꺼려지기는 하다)
  • 계절적이나 주기적인 상황이라고 해도 프로덕션 환경에서는 단일 노드를 활용하는 것이 바람직하지도 않고, 안전하지도 않기 때문에 (단일 노드마저 장애가 생기면 문제가 더 커진다) Cluster Scale down도 처리하지 않는 것이 좋다.
  • 비용적인 문제 때문에 Spot Instance를 사용하다가 클러스터내에 노드가 존재하지 않는 상황이 발생할 수도 있기 떄문에 높은 입찰가를 제시하거나 아니면 on-demand node 와 Spot node 들을 섞어서 구성하는 것이 좋다. 물론 Spot Instance가 제외되면서 재 배치가 발생할 수 있기 때문에 서비스의 안정성 측면에서도 권장되지 않는다.

결론

데모를 위해서 AWS, Azure, GCE 등을 이용했지만 실제 데모 횟수보다는 운영되는 기간대비 비용이 과다하게 발생했다. 물론 서비스의 안정성은 더욱 중요하지만 좀 더 다양한 측면에서 기준을 세운다면 Kubernetes Cluster를 위에서 검토한 것 이상으로 서비스와 운영비용 절감 등의 방법을 찾게 될 것이라고 생각한다.


References

  • https://carlosbecker.com/posts/k8s-sandbox-costs/?utm_content=SoAmpli&utm_medium=Social&utm_source=Twitter&utm_campaign=errm

Written by Morris (ccambo@gmail.com - MSFL)


공유하기