$ kubectl get pods -o wide -w -n exapi
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
oauth-b88bb75fb-cbpfb 1/1 Running 0 12m 10.1.19.219 fargate-ip-10-1-19-219.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-cbpfb 1/1 Running 0 12m 10.1.19.219 fargate-ip-10-1-19-219.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-zx68l 1/1 Running 0 9m47s 10.1.27.53 fargate-ip-10-1-27-53.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-zx68l 1/1 Running 0 9m47s 10.1.27.53 fargate-ip-10-1-27-53.ap-northeast-2.compute.internal <none> 1/1
oauth-6889b9d9d8-89f8d 0/1 Pending 0 0s <none> <none> <none> 0/1
oauth-6889b9d9d8-89f8d 0/1 Pending 0 1s <none> <none> 3e4a3b4284-f3a1e8e9245847e19d248aa203cec099 0/1
oauth-6889b9d9d8-89f8d 0/1 Pending 0 65s <none> fargate-ip-10-1-22-204.ap-northeast-2.compute.internal 3e4a3b4284-f3a1e8e9245847e19d248aa203cec099 0/1
oauth-6889b9d9d8-89f8d 0/1 ContainerCreating 0 65s <none> fargate-ip-10-1-22-204.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-89f8d 1/1 Running 0 100s 10.1.22.204 fargate-ip-10-1-22-204.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-89f8d 1/1 Running 0 101s 10.1.22.204 fargate-ip-10-1-22-204.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-89f8d 1/1 Running 0 102s 10.1.22.204 fargate-ip-10-1-22-204.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-89f8d 1/1 Running 0 2m11s 10.1.22.204 fargate-ip-10-1-22-204.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-89f8d 1/1 Running 0 3m34s 10.1.22.204 fargate-ip-10-1-22-204.ap-northeast-2.compute.internal <none> 1/1
oauth-6889b9d9d8-89f8d 1/1 Running 0 3m34s 10.1.22.204 fargate-ip-10-1-22-204.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-zx68l 1/1 Terminating 0 14m 10.1.27.53 fargate-ip-10-1-27-53.ap-northeast-2.compute.internal <none> 1/1
oauth-6889b9d9d8-89f8d 1/1 Running 0 3m34s 10.1.22.204 fargate-ip-10-1-22-204.ap-northeast-2.compute.internal <none> 1/1
oauth-6889b9d9d8-8n2mf 0/1 Pending 0 0s <none> <none> <none> 0/1
oauth-6889b9d9d8-8n2mf 0/1 Pending 0 1s <none> <none> 16625974aa-a093eb0abc954f958086de0c456c0ea2 0/1
oauth-b88bb75fb-zx68l 0/1 Terminating 0 14m 10.1.27.53 fargate-ip-10-1-27-53.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-zx68l 0/1 Terminating 0 14m 10.1.27.53 fargate-ip-10-1-27-53.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-zx68l 0/1 Terminating 0 14m 10.1.27.53 fargate-ip-10-1-27-53.ap-northeast-2.compute.internal <none> 1/1
oauth-6889b9d9d8-8n2mf 0/1 Pending 0 50s <none> fargate-ip-10-1-18-160.ap-northeast-2.compute.internal 16625974aa-a093eb0abc954f958086de0c456c0ea2 0/1
oauth-6889b9d9d8-8n2mf 0/1 ContainerCreating 0 50s <none> fargate-ip-10-1-18-160.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-8n2mf 1/1 Running 0 85s 10.1.18.160 fargate-ip-10-1-18-160.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-8n2mf 1/1 Running 0 86s 10.1.18.160 fargate-ip-10-1-18-160.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-8n2mf 1/1 Running 0 87s 10.1.18.160 fargate-ip-10-1-18-160.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-8n2mf 1/1 Running 0 116s 10.1.18.160 fargate-ip-10-1-18-160.ap-northeast-2.compute.internal <none> 0/1
oauth-6889b9d9d8-8n2mf 1/1 Running 0 3m19s 10.1.18.160 fargate-ip-10-1-18-160.ap-northeast-2.compute.internal <none> 1/1
oauth-6889b9d9d8-8n2mf 1/1 Running 0 3m19s 10.1.18.160 fargate-ip-10-1-18-160.ap-northeast-2.compute.internal <none> 1/1
oauth-6889b9d9d8-8n2mf 1/1 Running 0 3m19s 10.1.18.160 fargate-ip-10-1-18-160.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-cbpfb 1/1 Terminating 0 20m 10.1.19.219 fargate-ip-10-1-19-219.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-cbpfb 0/1 Terminating 0 20m 10.1.19.219 fargate-ip-10-1-19-219.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-cbpfb 0/1 Terminating 0 20m 10.1.19.219 fargate-ip-10-1-19-219.ap-northeast-2.compute.internal <none> 1/1
oauth-b88bb75fb-cbpfb 0/1 Terminating 0 20m 10.1.19.219 fargate-ip-10-1-19-219.ap-northeast-2.compute.internal <none> 1/1
자알 돌아간다~ 정상적인 것처럼 보이나...
Default 값을 믿고 열심히 삽질해 준 나에게 감사한다.
Rolling Update 시 가장 먼저 선행해야 할 것은 strategy 명시이다. 기본값으로 테스트하겠다고 이 부분을 생략한게 큰 타격이 됐다.
$ my-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: exapi
name: api
spec:
replicas: 2
selector:
matchLabels:
app: api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0 # 0 fast
maxUnavailable: 1 # 0 slow
maxSurge 와 maxUnavailable 값은 배포시 최대 생성 pod 수와 종료 수를 명시할 수 있다. 각각 0/1, 1/0 으로 설정할 경우 running 상태 이후에 terminating 을 시키느냐, pending 과 동시에 terminating 을 시키느냐의 차이이며 약 30초 정도의 차이를 확인했다.
겉보기에는 잘 작동하는 것처럼 보이나 배포시 약 5~10초간 502 Gateway Error 뒤에 504 Gateway Time-out 이 발생한다. 우선 502 에러가 발생하는 시점을 찾아봤다.
쿠버네티스에서 kubectl rollout restart 명령으로 배포할 경우, 새 pod 가 추가되고 Running 상태가 되면 기존 포드가 삭제되는 식이다. 확인 결과 pod 가 Terminating 되는 순간에 502 에러가 발생했다. ALB 의 Target 이 동시에 draining 되는 시점이기도 하다. 인터넷을 후벼파서 결과, 502 에러를 최소화 할 수 있는 방법을 찾아 결국 해냈다.
502 Gateway Error / 504 Gateway Time-out Error 최소화
1. ingress : connection-draining 설정
ALB 에 connection-draining 관련 옵션을 주어 기존 연결에 대한 처리를 유지한다. (이미 기본값으로 동작중. 효과없음.)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
...
annotations:
...
service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "30"
2. pod : readiness-gate 설정
readiness-gate 옵션을 활성화 하여 새로운 target 이 healthy 상태로 연결되기 전까지 기존 target 을 종료하지 않음. (마찬가지로 502 에러는 발생하나, ALB 내의 target 이 healthy 상태가 한개도 유지되지 않는 어이없는 상황은 막을 수 있음). 반드시 필요!
$ kubectl label namespace {mynamespace} elbv2.k8s.aws/pod-readiness-gate-inject=enabled
apiVersion: apps/v1
kind: Deployment
metadata:
spec:
template:
spec:
containers:
- name: api
readinessGates:
- conditionType: target-health.alb.ingress.k8s.aws/api-ingress_api-nodeport_80
readiness-gate 옵션을 활성화할 namespace 에 레이블을 설정하고, target-health.alb.ingress.k8s.aws/{ingress-name}_{service-name}_{port} 를 위처럼 삽입한다. 설정이 올바르지 않으면 ALB 에서 target 그룹을 인식하지 못할 수도 있으니 주의!
3. pod : preStop 설정
life-cycle hooks 에서 pod 가 중지되기 전에(preStop) 딜레이를 가지며 기존 연결을 마저 처리한다. 새로운 연결이 이루어지지 않는다. (인터넷에서 좋아요도 가장 많고, 실제로 preStop 설정만으로 502 에러를 해결하였음.)
kind: Deployment
spec:
...
template:
...
spec:
containers:
- name: api
lifecycle:
preStop:
exec:
command: ["sleep", "60"]
4. pod : terminationGracePeriodSeconds 설정
preStop 가 실패했을 때 대신 컨테이너를 종료시킨다. (preStop sleep 값보다 +10초 정도로 설정: default 값은 45초)
kind: Deployment
spec:
...
template:
...
spec:
containers:
- name: web-api
...
terminationGracePeriodSeconds: 70
5. pod livenessProbe / readinessProbe 설정
pod 의 활성화 상태를 나타내는 livenessProbe 가 실패하면 재시작 정책의 대상이 된다. pod 준비 상태를 나타내는 readinessProbe 가 실패하면 해당 pod 는 모든 엔드포인트에서 제거된다. (두 방법 모두 비정상 pod 에 연결을 못받게 하여 502 에러를 줄인다고는 하는데 효과는 잘 모르겠음.)
kind: Deployment
spec:
...
template:
...
spec:
containers:
- name: api
...
livenessProbe:
httpGet:
path: /
port: 80
periodSeconds: 4
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 80
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
정리
이 5가지 설정 중 3가지를 사용하여 무중단 배포를 완벽하게 해결했다.
preStop
readinessGates
terminationGracePeriodSeconds
readinessGates 설정을 가장 먼저 한 바람에 필수요소인지는 확실치 않다. (조만간 테스트 예정)
preStop 설정으로 무중단 배포를 해결했다. (본인의 서비스와 사양에 맞는 설정이 필요할 수 있다.)
terminationGracePeriodSeconds 설정을 하지 않아도 문제는 없었다. 내 경우 기본값이 45초 만으로 충분한 듯.
한가지 더... kubectl rollout 으로 테스트 할 때 기존의 pod 가 원치 않는 동작을 할 수도 있다. 항상 기존 pod 는 delete 로 삭제한 후에 새로 생성하여 테스트 하는 것이 원하는 결과를 얻는데 도움이 될 것 같다.
무중단 배포도 안된다고 며칠을 eks 욕하고 있었는데... 정말 다행이다. ^_______________^
WRITTEN BY
- 손가락귀신
정신 못차리면, 벌 받는다.