실환경에서는 수동으로 명령어를 입려하며 파드를 관리하지 않고, 자동으로 실행되고 안정적인 상태를 유지하도록 한다.
그러기 위해서 Replication Controller 또는 Deployement 와 같은 유형의 리소스를 생성해 실제 파드를 생성하고 관리한다.
이번엔 쿠버네티스가 컨테이너를 모니터링하고 실패하면 자동으로 다시 시작하는 방법을 살펴볼 것이다.
노드 전체에 장애가 발생하면 노드에 있는 파드는 유실되며 이전에 언급한 레플리케이션 컨트롤러나 그와 유사한 기능을 하는 컨트롤러가 해당 파드를 관리하지 않는 한 새로운 파드로 대체되지 않는다.
쿠버네티스의 장점 중 하나는 쿠버네티스에 컨테이너 목록을 제공하면 해당 컨테이너를 클러스터 어딘가에서 계속 실행되도록 할 수 있다는 것이다.
파드가 노드에 스케줄링되는 즉시, 해당 노드의 Kubelet 은 파드의 컨테이너를 실행하고 파드가 존재하는 한 컨테이너가 계속 실행되도록 할 것이다. 컨테이너의 주 프로세스가 죽게 되면 Kubelete 은 컨테이너를 다시 시작한다.
그렇다면 프로세스의 크래시가 없이 중단되는 경우, 예를 들어 메모리 반환이 되지 않아 더이상 애플리케이션이 더 이상 제대로 동작하지 않게되면 어떨까. 또 애플리케이션이 무한 루프나 교착 상태에 빠져서 응답을 하지 않는 상황이라면 어떨까. 컨테이너 내부의 애플리케이션의 상태를 체크해야 할 필요가 있을 것이다.
쿠버네티스는 라이브니스 프로브를 통해 컨테이너가 살아 있는지 확인할 수 있다. 쿠버네티스는 주기적으로 컨테이너에 지정된 라이브니스 프로브를 실행하고 실패할 경우 컨테이너를 다시 시작한다.
쿠버네티스는 세 가지 메커니즘을 사용해 컨테이너에 프로브를 실행한다.
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveness
spec:
containers:
- image: luksa/kubia-unhealthy
name: kubia
livenessProbe:
httpGet:
path: /
port: 8080
위와 같은 방식으로 아주 기본적인 HTTP liveness probe를 설정할 수 있다.
kubectl describe
를 통해 라이브니스 프로브에 관한 추가적인 정보를 볼 수 있다.
http-get http://:8080/ delay=0s timeout=1s period=10s #success=1 #failure=3
delay
: 컨테이너가 시작된 후 바로 프로브가 시작된다는 것을 의미
timeout
: 제한 시간. 컨테이너가 제한 시간 안에 응답해야 한다.
period
: 몇 초 간격으로 프로브를 수행할 것인가?
failure
: 해당 횟수만큼 실패하면 다시 시작
위의 추가적인 매개변수들은 프로브를 정의할 때 지정할 수 있다. 또한 일반적인 경우 초기 지연을 설정하지 않게 되면 컨테이너가 시작하자마자 요청을 받을 준비가 안 되어 있는 상태에서 프로브가 실행되어 실패하는 경우가 생긴다.
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 15
효과적으로 라이브니스 프로브를 구성하기 위해서는 특정 URL(엔드포인트의 생사 유무를 확인할 수 있는)에 요청을 보내 애플리케이션이 살아 있는지를 파악할 수 있을 것이다.
외부 환경에 영향을 받지 않도록 해야 한다.
예를 들어, 마이크로서비스의 경우 특히 다른 서버의 요청을 받아 애플리케이션의 요청을 처리하는 경우가 있다면 해당 엔드포인트는 애플리케이션 자체의 문제 이외에도 요청에 실패할 경우가 있다. 따라서 이런 경우에는 애플리케이션 재시작이 해결책이 되지 않는다.
프로브를 가볍게 유지해야 한다.
라이브니스 프로브는 자주 실행되어야 하고, 대부분 1초 내에 빠른 속도로 완료되어야 한다. 따라서 너무 많은 연산 리소스를 사용해서는 안 되고, 최대한 가볍게 유지해야 한다. 더불어 컨테이너의 CPU 가동 시간을 제한하는 경우엔 프로브가 무거울 경우, 실제 애플리케이션 프로세스에서 사용할 수 있는 CPU 사용 시간이 줄어들게 될 수도 있다.
프로브 자체에 재시도를 구현하지 않는다.
라이브니스 프로브는 컨테이너에 문제가 생겼을 경우 재시작을 할 수 있다는 측면에서 매우 유용하게 사용될 수 있다.
하지만 이 프로브는 노드의 Kubelet 에서 수행되는 것으로 만약 노드 자체가 중단될 경우 작동하지 않게 된다.
이때 중단된 모든 파드의 대체 파드를 생성해야 하는 것은 컨트롤 플레인의 몫이다.
애플리케이션이 다른 노드에서 다시 시작되도록 하려면 Replication Controller 또는 이와 유사한 메커니즘으로 파드를 관리해야 한다.
레플리케이션컨트롤러는 쿠버네티스 리소스 중 하나로, 파드가 항상 실행되도록 보장한다.
클러스터에서 노드가 사라지거나, 노드에서 파드가 제거된 경우 레플리케이션컨트롤러는 항상 사라진 파드를 감지해 교체 파드를 생성한다.
먼저 레플리케이션컨트롤러가 시작되면 실행 중인 파드 목록을 모니터링하기 시작한다.
위에서 보면 레플리케이션컨트롤러의 핵심적인 3가지 요소가 등장하는데
레플리케이션컨트롤러의 핵심 요소 중에 레이블 셀렉터와 파드 템플릿은 변경해도 기존 파드에는 영향을 미치지 않는다.
레이블 셀렉터를 변경하게 되면 기존 파드가 레플리케이션컨트롤러의 범위를 벗어나므로 컨트롤러가 더이상 해당 파드를 관리하지 않는다.
또한 파드를 생성한 뒤에는 파드 내의 이미지, 환경변수 및 기타 콘텐츠에 신경을 쓰지 않기 때문에 파드 템플릿을 변경하게 되면 새로운 파드를 생성할 때만 영향이 있게 된다.
레플리케이션컨트롤러를 사용했을 때의 장점은 다음과 같다.
apiVersion: v1
kind: ReplicationController
metadata:
name: kubia
spec:
replicas: 3
selector:
app: kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: onikss793/kubia
ports:
- containerPort: 8080
기본적인 Replication Controller 생성 yaml 이다.
파일을 API 서버에 게시하면, 쿠버네티스는 레이블 셀렉터 app: kubia
와 일치하는 파드 인스턴스가 3개가 되도록 유지하는 레플리케이션컨트롤러를 생성한다.
만약 셀렉터를 지정하지 않으면 템플릿의 레이블로 자동 설정된다. 이와 같은 방법이 더욱 간결하게 yaml을 유지하는 방법이다.
특정 파드에 어떤 장애가 있어 디버깅을 해야 한다고 가정해보자. 파드의 오작동을 파악했다면 디버깅을 위해 해당 파드의 레이블 셀렉터를 변경하여 레플리케이션컨트롤러가 새 파드로 교체하도록 한 다음, 충분히 디버깅을 진행해볼 수 있을 것이다.
반대로 레플리케이션컨트롤러의 레이블 셀렉터를 변경했다면 어떻게 될까? 변경된 레이블 셀렉터로 새로운 파드를 생성하게 될 것이다. 그리고 기존에 사용하던 파드들은 레플리케이션컨트롤러의 관리에서 벗어나게 된다.
파드 템플릿은 언제든지 수정할 수 있다. 바뀐 템플릿을 적용하려면 기존 파드를 삭제하고 레플리케이션컨트롤러가 새로운 템플릿 기반으로 파드를 생성하도록 해야 한다.
kubectl edit rc {ReplicationControllerName}
을 활용하면 기본 에디터로 yaml를 수정할 수 있다.
스케일업의 원리는 매우 간단한데, replica의 수를 늘리는 것이 그 방법이다.
kubectl edit rc ...
를 통해 replicas의 수를 늘리거나, kubectl scale rc {ReplicationControllerName} --replicas={number}
로 명령할 수도 있다.
kubectl delete rc {ReplicationControllerName}
을 이용해 레플리케이션컨트롤러를 삭제할 수 있다. 기본적으로 해당 RC의 관리 하에 있는 파드들은 모두 같이 삭제되지만 --cascade=false
옵션을 이용하면 파드들은 살릴 수도 있다.
초기에는 레플리케이션컨트롤러가 파드를 복제하고 노드 장애가 발생했을 때 reschedule 할 수 있는 유일한 수단이었다. 하지만 차세대 레플리케이션컨트롤러인 레플리카셋이 등장하면서 앞선 기능들을 모두 대체할 것이다.
일반적으로 레플리카셋을 직접 생성하지는 않고, 디플로이먼트 리소스를 생성할 때 자동으로 생성되게 한다.
레플리카셋은 레플리케이션컨트롤러와 똑같이 동작하지만 좀 더 풍부한 표현식을 사용하는 파드 셀렉터를 갖고 있다.
기본적으로 레플리카셋은 특정 레이블이 없는 파드나 레이블의 값과 상관없이 특정 레이블의 키를 갖는 파드를 매칭시킬 수 있다(예를 들어, env 라는 키를 갖고 있는 레이블을 모두 그룹으로 묶는다, env=*
).
또 레플리카셋은 하나의 레플리카셋으로 두 개의 파드 세트를 모두 매칭시켜 하나의 그룹으로 취급할 수 있다. (예를 들어, env=production, env=dev 이 두 개의 label을 하나의 그룹으로 취급할 수 있다)
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: onikss793/kubia
추가적으로
matchExpressions:
- keys: app
operator: In
values:
- kubia
처럼 다양한 방식으로 표현식을 구성할 수 있다는 것이 레플리카셋의 장점이다.
기본적인 연산자는 다음과 같다.
레플리케이션컨트롤러, 레플리카셋은 쿠버네티스 클러스터 내 어딘가에 지정된 수만큼의 파드를 실행하는데 사용되는데, 만약 클러스터의 모든 노드에 노드 당 하나의 파드만 실행되길 원하는 경우엔 어떻게 할까?
예를 들면, 모든 노드에서 로그 수집기와 리소스 모니터를 실행하려는 경우가 있을 것이다. 혹은 kube-proxy 프로세스와 같은 경우도 있을 것이다.
모든 클러스터 노드마다 파드를 하나만 실행시키길 원할 경우 데몬셋 오브젝트를 생성해야 한다.
데몬셋에 의해 생성되는 파드는 타깃 노드가 이미 지정돼 있고, 쿠버네티스 스케줄러를 건너뛰는 것을 제외하면 레플리케이션컨트롤러, 레플리카셋과 매우 유사하다.
하지만 데몬셋은 각 노드마다 의도된 파드의 복제본 수가 정해진 것이 아니라 파드 셀렉터와 일치하는 파드가 각 노드에서 실행 중인지 확인하는 역할을 한다.
그렇기 때문에 노드가 다운되면 데몬셋은 다른 곳에서 파드를 생성하는 것이 아니라 새 노드가 클러스터에 추가되면 그때 새 파드 인스턴스를 새 노드에 배포한다.
이는 수동으로 노드 내의 데몬셋의 파드를 지워도 동일하다.
더불어 데몬셋도 파드 템플릿으로 파드를 생성한다.
데몬셋을 설정할 때 특정 노드를 지정하지 않으면 클러스터의 모든 노드에 파드를 배포한다.
만약 노드를 지정하고 싶다면 파드 템플릿에서 node-selector
속성을 지정하면 된다.
예시) SSD를 갖는 모든 노드에 `ssd-monitor`라는 데몬이 있다고 가정할때 SSD를 갖는 노드에 disk=ssd 라는 레이블을 추가하여 해당 노드만 선택해 모니터링 파드를 배포할 수 있다.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLabels:
app: ssd-monitor
template:
metadata:
labels:
app: ssd-monitor
spec:
nodeSelector:
disk: ssd
containers:
- name: main
image: luksa/ssd-monitor
지금까지 계속해서 실행되어야 하는 파드에 대해서 알아보았다면 만약 실행된 후 종료되어야하는 파드의 경우에는 어떻게 해야 할까.
쿠버네티시는 이럴 경우를 위해 잡 리소스를 지원한다.
컨테이너 내부에서 실행 중인 프로세스가 성공적으로 완료되면 컨테이너를 다시 시작하지 않는 파드를 실행할 수 있다.
만약 노드에 장애가 생겼다면 해당 노드에 있던 잡이 관리하는 파드는 레클리카셋 파드와 같은 방식으로 다른 노드로 다시 스케줄링된다.
만약 컨테이너 내의 프로세스에 장애가 발생할 경우엔 잡에서 컨테이너를 다시 시작할 것인지 설정할 수 있다.
apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
template:
metadata:
labels:
app: batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job
이외에도
spec:
completions: {number}
template:
...
위처럼 지정한 갯수만큼의 파드를 순차적으로 실행할 수도 있다. 파드가 완료되면 그 다음 파드가 생성되는 방식이다.
spec:
completions: {number}
parallelism: {number}
template:
...
반면 다음과 같이 parallelism
을 지정해주면 원하는 만큼 같은 파드를 병렬로 실행할 수 있다. 또한 잡이 실행하는 동안에 병렬로 실행될 파드의 갯수를 늘릴 수도 있다.
만약 잡에서 설정한 파드가 일정 시간이 지나도 종료되지 않고, 정체 불명의 상태에 빠질 경우엔 어떻게 할까?
이럴 경우를 위해 activeDeadlineSeconds
속성을 통해 파드의 실행 시간을 제한할 수 있다. 파드가 이 지정 시간보다 오래 실행되면 시스템이 종료를 시도하고 잡을 실패한 것으로 간주한다.
리눅스의 크론잡을 동일하게 쿠버네티스에서도 지원한다. 이를 잘 활용하면 정해진 시간마다 주기적으로 어떠한 작업이 실행되도록 할 수 있을 것이다.
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: batcdh-job-every-fifteen-minutes
spec:
schedule: "0,15,30,45 * * * *"
jobTemplate:
spec:
template:
metadata:
labels:
app: periodic-batch-job
spec:
restartPolicy: OnFailure
containers:
- name: main
image: luksa/batch-job