23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
안녕하세요, dragonpipe입니다.
이 번에는 지난 게시글에 이어 AKS(Azure Kubernetes Service) 상용 서비스에 적용한 후기 세 번째 내용으로,
서비스 안정성에 도움이 되는 k8s 리소스의 manifest yaml 파일에 대해 소개드리려고 합니다.
k8s의 큰 장점 중 하나가 오토스케일링 기능이라고 생각하는데요. HPA 리소스가 있으면 auto-scale 기능을 어렵지 않게 구현할 수 있습니다.
HPA 리소스를 생성하기 위한 manifest 파일을 어떻게 작성하였는지 공유 드리고, 추가로 HPA가 잘 동작하는지 테스트 해 본 결과를 공유드리겠습니다.
hpa.yaml 파일 작성
kind : HorizontalPodAutoscaler 추가
minReplicas : 최소 replica 개수 (hpa의 minReplicas와 deployment의 replica 값을 맞추는 것을 권장)
maxReplicas : 최대 replica 개수
metrics 설정
Memory 사용량 기준 autoscaling 설정
name : memory
target.type : Utilization
target.averageUtilization : 기준 사용량(평균)
CPU 사용량 기준 autoscaling 설정
name : cpu
target.type : Utilization
target.averageUtilization : 기준 사용량(평균)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: inspect-dna-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: inspect-dna-api
minReplicas: 1
maxReplicas: 3
metrics:
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 90
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80jmeter로 부하를 주어 HPA가 자동으로 오토스케일링 하는지 확인
CPU와 Memory에 부하를 주는 코드 작성
jmeter로 부하 테스트 수행
kubectl 명령어를 통해 HPA 상태와 replica 숫자 확인
replica : 1일 때 Memory가 제한 기준 90%를 상회
replica : 2로 증가하였지만 CPU 제한 기준 80%를 상회
replica: 3으로 증가하였지만 Memory, CPU 모두 제한 기준을 상회 → 하지만 maxReplicas가 3이므로 더 이상 replica는 증가하지 않음
k8s에서 pod는 언제든지 삭제되고 재생성 될 수 있습니다.
만약 파일을 업로드 하였을 때 local 서버에만 저장한다면 삭제 후 재생성되면 파일도 함께 삭제될 수 있습니다.
그렇기 때문에 영구적인 파일 저장 스토리지가 필요한데 이를 가능하게 해주는 리소스가 volume입니다.
pod에서 사용하려면 blob storage와 같은 저장소를 PV와 PVC로 프로비저닝을 해야 하는데, 해당 내용은 별도로 공유드리겠습니다.
우선 이번 게시글에서는 pv와 pvc가 생성되었다고 가정하고 deployment에서 어떻게 pvc를 적용할 수 있는지에 대해 설명드리겠습니다.
deployment.yaml에 pvc 설정 추가
volume을 mount하기 위해선 미리 PV와 PVC가 생성되어야 함 (PV와 PVC 생성 가이드는 추후 공유 예정)
deployment.yaml 파일에 미리 생성한 pvc를 volume으로 mount하는 부분 추가
volumeMounts : containers 밑에 생성
name : 바깥 volumes의 name 중 일치하는 volume을 mount 하겠다는 의미로 volumes의 name과 일치하면 되고 자율적으로 네이밍 가능
mountPath : 컨테이너에서 mount 할 경로
volumes : containers와 같은 레벨에 생성
name : volumeMounts의 name과 일치해야 함
persistentVolumeClaim.claimName : pvc name 추가
...
volumeMounts:
- name: azurefile
mountPath: /mnt/oavatarfiles
volumes:
- name: azurefile
persistentVolumeCl
claimName: azurefile-pvc
...제가 운영하고 있는 서비스는 아직 Rolling update 방식으로 배포하고 있습니다. 시간이 되면 Blue/Green 방식으로 배포하는 것도 진행해보려고 합니다.
Rolling update 방식으로 배포할 때는 반드시 Probe 설정을 해주어야 하는데요.
왜냐하면 배포할 때 하나 또는 여러개씩 pod가 생성되는 데 이 때 실제 컨테이너 내부 애플리케이션이 부팅 완료되기도 전에
k8s에서 해당 서비스가 살아있다고 인지하면 네트웍 트래픽을 전달할 수도 있기 때문입니다.
따라서 애플리케이션 부팅이 완료되어 서비스를 정상적으로 수행할 수 있는 상태인지 k8s에서 인지할 수 있게 Probe 설정을 해주어야 합니다.
이번에는 Probe에는 어떤 것들이 있고 어떻게 설정하는지에 대해 알아보겠습니다.
Probe 종류
livenessProbe : 컨테이너가 동작 중인지 여부를 나타냄. 활성 프로브(liveness probe)에 실패한다면, kubelet은 컨테이너를 죽이고, 해당 컨테이너는 재시작 정책의 대상이 됨.
만약 컨테이너가 활성 프로브를 제공하지 않는 경우, 기본 상태는 Success 이다.
readinessProbe : 컨테이너가 요청을 처리할 준비가 되었는지 여부를 나타냄.
준비성 프로브(readiness probe)가 실패한다면, 엔드포인트 컨트롤러는 파드에 연관된 모든 서비스들의 엔드포인트에서 파드의 IP 주소를 제거.
준비성 프로브의 초기 지연 이전의 기본 상태는 Failure. 만약 컨테이너가 준비성 프로브를 지원하지 않는다면, 기본 상태는 Success 이다.
startupProbe : 컨테이너 내의 애플리케이션이 시작되었는지를 나타냄.
스타트업 프로브(startup probe)가 주어진 경우, 성공할 때 까지 다른 나머지 프로브는 활성화 되지 않음.
만약 스타트업 프로브가 실패하면, kubelet이 컨테이너를 죽이고, 컨테이너는 재시작 정책에 따라 처리. 컨테이너에 스타트업 프로브가 없는 경우, 기본 상태는 Success 이다.
테스트 수행
비기능(성능) 테스트 결과 replica 개수가 2개 이상인 상황에서 readinessProbe을 설정하였을 경우 안정성 향상
jmeter 테스트 결과 readinessProbe 설정 없을 때 대비 Error 발생율이 10% 이하로 떨어짐 (12.3% → 1.1%)
사유 : 한 pod에서 문제가 발생하였을 때 해당 pod가 정상으로 돌아올 때 까지 다른 pod로 네트웍 트래픽을 고정해주므로 에러 발생율 낮아짐
단점 : 성능(throughput)이 다소 떨어짐 (5.5/sec → 5.1/sec)
HPA를 설정한 상태에서 startupProbe를 설정하였을 경우 안정성 향상
HPA를 설정할 경우 리소스 자원이 설정한 기준치 보다 상회할 경우 replica 개수를 늘리므로 pod가 중간에 생성될 수 있음
생성되는 중간에 (구동이 완료되지 않은 상태에서) ingress를 통해 요청이 들어오면 에러가 발생할 수 있음
startupProbe를 설정하면 컨테이너가 정상적으로 다 구동될 때 까지 트래픽을 전달하지 않으므로 안정성 향상
deployment.yaml 파일에서 Probe설정(readinessProbe, startupProbe) 추가 (containers 하위에 추가)
readinessProbe 설정
path : spring actuator 사용 시 "/actuator/health/readiness"로 고정
해당 경로로 테스트로 호출해 보면 json 결과 값 확인 가능 : {"status": "UP"}
initialDelaySeconds : 시작 후 readiness 상태를 확인할 때 까지 기다리는 시간 (어플리케이션 구동에 걸리는 평균 시간을 입력할 것)
periodSeconds : readiness 상태를 확인하는 주기
startupProbe 설정
path : spring actuator 사용 시 "/actuator/health/liveness"로 고정 (Spring Actuator에 startupProbe을 위한 API 없지만 liveness API로 대체 가능)
해당 경로로 테스트로 호출해 보면 json 결과 값 확인 가능 : {"status": "UP"}
initialDelaySeconds : 시작 후 readiness 상태를 확인할 때 까지 기다리는 시간 (어플리케이션 구동에 걸리는 평균 시간을 입력할 것)
periodSeconds : readiness 상태를 확인하는 주기
...
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 80
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 5
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 80
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 5
...Spring-boot project에서 health check를 위한 actuator 기능 추가
pom.xml: 애플리케이션에 readiness 체크를 위한 설정 추가 (Spring Actuator 사용)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>application.yaml: health.probe.enabled: true로 설정
management:
endpoint:
health:
probes:
enabled: true
Graceful Shutdown이란?
: 현재 들어온 요청을 모두 수행하고 우아하게 종료된다는 개념으로, 수행 중이던 요청을 미처 처리하지 못하고 종료되면 리소스가 서버에 좀비처럼 남아있거나 데이터 손실을 일으킬 수 있는 경우 필요
Probe 설정이 Pod가 생성될 때의 안정성을 보장해 준다면, preStop 설정은 Pod가 종료될 때의 안정성을 보장해줄 수 있습니다.
Pod가 다른 리소스와 커넥션이 되어 있을 때 Pod를 강제 종료하게 되면 Pod는 shutdown 되지만 연결되었던 리소스는 진행하던 프로세스를 계속 진행할 수 있습니다.
프로세스 이후 무언가 요청을 주었던 pod에 응답을 준다거나 인터렉션을 하는 과정에서 에러가 발생할 수 있으므로 우아하게(?) 종료될 수 있도록 preStop 설정을 추가하였습니다.
preStop으로 graceful shutdown을 수행해야 하는 이유
kube-proxy나 Ingress controller 등은 엔드포인트 변경에 대해 알림을 받기까지 다소 시간이 걸림
따라서 트래픽은 종료 된것으로 표시가 되어도 여전히 포드(Pod)로 트래픽이 흐를 수 있음
새로운 요청 수락을 중지하고 모든 연결이 종료되면 종료되어야 할 때 'lifecycle.preStop' 설정 고려
deployment.yaml 파일에 설정 추가
preStop 설정 (containers 하위에 추가)
lifecycle.preStop.exec.command에 확인이 필요한 사항을 sh로 작성 (예 : "while netstat -at -n -nn|grep 10000|grep ESTABLISHED; do sleep 1; done")
예시 상황: grpc 커넥션 맺고 있는 상황에서 pod가 삭제될 경우 커넥션이 끊어져서 target 서버에서 rpc 통신 할 때 에러 발생
조치 예시: 포트가 10000번 이므로 1초에 한번씩 10000번 포트로 ESTABLISHED 상태인지 확인 하고 없으면 done 처리
테스트 결과: 커넥션 맺고 있는 동안은 컨테이너가 종료되지 않고 모든 커넥션이 종료되면 컨테이너가 종료되고 pod가 삭제됨을 확인
terminationGracePeriodSeconds 설정 (containers와 동일 레벨에 추가)
초 단위로 몇 초까지 종료 조건을 확인할지 설정
만약 60이라고 설정하면 60초가 넘어가면 preStop 조건이 충족하지 않아도 종료 처리
...
terminationGracePeriodSeconds: 60
containers:
- name: {app명}
image: {acr domain url}/{app명}/{env}/{image명}:0.1.150
lifecycle:
preStop:
exec:
command: ["/bin/sh","-c","while netstat -at -n -nn|grep 10000|grep ESTABLISHED; do sleep 1; done"]
...이 번엔 서비스의 안정성을 높여주는 k8s 리소스 설정에 대해 알아보았습니다.
개인적으론 HPA의 metrics설정은 서비스 특성에 맞게 조심스럽게 설정해야 된다고 느꼈습니다.
우선 부하가 꾸준히 많은 경우에는 애초부터 Replica 수를 늘리면 되기 때문에 HPA 설정의 의미가 크지 않다고 생각합니다.
만약 서비스의 특성이 이벤트 성이 강하고 특정 기간에만 부하가 많이 몰린다고 한다면 HPA 설정이 큰 힘이 될 것 같습니다.
HPA 테스트를 해 본 결과, 60~70 정도로 메트릭을 설정하면 크게 부하가 없는데도 pod가 늘어나는 것을 보았습니다. 따라서 저는 90정도로 임계치를 높게 잡았습니다.
이 때 중요한 것은 애플리케이션 부팅 시간인데요. 부하를 빨리 분산시켜주려면 늘어나는 pod들이 빠르게 부팅되어야 합니다.
따라서 이런 특성을 가진 서비스는 최대한 가볍게 만들어서 부팅시간을 짧게 구현하면 큰 도움이 될 것 같습니다.
(Spring Boot 보다 부팅시간이 더 빠른 Node.js 등 다른 프레임워크를 사용하는 것도 추천)
지금까지 긴 글을 읽어주셔서 감사합니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.