레플리케이션 컨트롤러와 레플리카셋은 쿠버네티스 클러스 내 어딘가에 지정된 수만큼의 파드를 실행하는 데 사용됩니다. 그러나 클러스터의 모든 노드에, 노드당 하나의 파드만 실행되길 원하는 경우가 있을 수 있습니다.
시스템 수준의 작업을 수행하는 인프라 관련 파드가 이런 경우인데, 예를 들면 모든 노드에서 로그 수집기와 리소스 모니터를 실행하려는 경우가 좋은 예입니다. 또 다른 좋은 예는 쿠버네티스의 kube-proxy 프로세스이며, 서비스를 작동시키기 위해 모든 노드에서 실행돼야 합니다.
쿠버네티스를 사용하지 않는 환경에서는 일반적으로 노드가 부팅되는 동안에 시스템 초기화 스크립트(init script)나 systemd 데몬을 통해 시작됩니다. 쿠버네티스 노드에서도 여전히 systemd를 사용해 시스템 프로세스를 실행할 수도 있지만, 그렇게 하면 쿠버네티스가 제공하는 모든 기능을 최대한 활용할 수가 없습니다.
모든 클러스터 노드마다 파드를 하나만 실행하려면 데몬셋 오브젝트를 생성해야 합니다. 데몬셋에 의해 생성되는 파드는 타깃 노드가 이미 지정돼 있고 쿠버네티스 스케줄러를 건너뛰는 것을 제외하면 이 오브젝트는 레플리케이션 컨트롤러 또는 레플리카셋과 매우 유사합니다. 파드가 클러스터 내에 무작위로 흩어져 배포되지는 않습니다.
데몬셋은 위 그림과 같이 노드 수만큼 파드를 만들고 각 노드에 배포됩니다. 레플리카셋이 클러스터에 원하는 수의 파드 복제본이 존재하는지 확인하는 반면, 데몬셋에는 원하는 복제본 수라는 개념이 없습니다. 파드 셀렉터와 일치하는 파드 하나가 각 노드에서 실행 중인지 확인하는 것이 데몬셋이 수행해야 하는 역할이기 때문에 복제본 개념이 필요하지 않습니다.
노드가 다운되면 데몬셋은 다른 곳에서 파드를 생성하지 않습니다. 그러나 새 노드가 클러스터에 추가되면 데몬셋은 즉시 새 파드 인스턴스를 새 노드에 배포합니다. 또한 누군가 실수로 파드 중 하나를 삭제해 노드에 데몬셋의 파드가 없는 경우에도 마찬가지입니다. 레플리카셋과 마찬가지로 데몬셋은 그 안에 구성된 파드 템플릿으로 파드를 생성합니다.
특정 노드에서만 파드를 실행하기
파드가 노드의 일부에서만 실행되도록 지정하지 않으면 데몬셋은 클러스터의 모든 노드에 파드를 배포합니다. 데몬셋 정의의 일부인 파드 템플릿에서 nnode-Selector 속성을 지정하면 됩니다. 이는 이전에 다루었던 레플리카셋이나 레플리케이션 컨트롤러의 파드 템플릿과 유사합니다. 또한 데몬셋의 노드 셀렉터도 유사하며 데몬셋이 파드를 배포해야 하는 노드를 정의합니다.
SSD를 갖는 모든 노드에서 실행되야 하는 ssd-moniter라는 데몬이 있다고 가정해봅시다. SSD를 갖고 있다고 표시된 모든 노드에서 이 데몬을 실행하는 데몬셋을 만듭니다. 클러스터 관리자가 이런 모든 노드에 disk=ssd 레이블을 추가했으므로 다음 그림과 같이 해당 레이블이 있는 노드만 선택하는 노드 셀렉터를 사용해 데몬셋을 작성합니다.
데몬셋 YAML 파일 작성하기
5초마다 표준 출력으로 "SSD OK"를 출력하는 모의 ssd-monitor 프로세스를 싱행하는 데몬셋을 생성합니다. 이미 모의 컨테이너 이미지를 준비해서 도커 허브로 푸시했기 때문에 직접 빌드하는 대신 이 이미지를 사용할 수 있습니다. 다음 예제와 같이 데몬셋에 대한 YAML을 작성합니다.
apiVersion: apps/v1beta2 > 데몬셋의 API 그룹은 apps의 v1beta2 입니다. kind: DeamonSet metadata: name: ssd-monitor spec: selector: matchLabels: app: ssd-monitor template: metadata: labels: app: ssd-monitor spec: nodeSelector: > 파드 템플릿은 disk=ssd 레이블이 있는 노드를 선택하는 노드 셀렉터를 갖습니다. disk: ssd containers: - name: main image: luksa/ssd-monitor
이 파드의 인스턴스는 disk=ssd 레이블이 있는 각 노드에 생성될 것입니다.
이전 포스팅에서도 여러번 설명한 바 있지만, YAML 파일을 작성할 때에는 항상 대소문자, 들여쓰기를 철저히 해주어야합니다. 종종 대소문자 하나 잘못써서 작동이 안되는 경우가 있다고 합니다. 그래서 현직에 있을 때도 "복사, 붙여넣기"를 자주 활용한다고 합니다. 복사, 붙여넣기하는게 농땡이가 아니라는 뜻이죠.
실제로 실습을 해보면 복사 붙여넣기를 잘 활용하면, 오히려 휴먼 리스크도 감소하고, 속도와 정확성도 증가해서 허탈했었던 적이 있습니다. 혹은 쿠버네티스 명령어들을 yaml 파일로 만드는 < ~~~ -o yaml > XXX.yaml> 을 활용해 명령어를 YAML파일로 만들어 활용하는 것도 많은 도움이 됩니다.
초기에는 레플리케이션 컨트롤러가 파드를 복제하고 노드 장애가 발생했을 때 재스케줄링하는 유일한 쿠버네티스 구성 요소 였습니다. 후에 레플리카셋 이라는 유사한 리소스가 도입됐습니다. 이는 비교적 차세대 레플리케이션 컨트롤러이며, 레플리케이션 컨트롤러를 완전히 대체할 수 있게 되었습니다.
이전에 다루었던 레플리케이션 컨트롤러 대신 레플리카셋을 다루는 것으로 진행해도 좋았겠지만, 발전한 순서를 알면 더 좋지 않을까 했습니다. 또한 현장에서는 여전히 레플리케이션 컨트롤러가 계속 사용되기도 함으로 이를 잘 이해하는 것이 좋습니다.
레플리케이션 컨트롤러와 레플리카셋은 거의 동일하기 때문에 대신해서 사용하는 데 아무 문제가 없을 것입니다. 일반적으로 레플리카셋을 직접 생성하지 않고, 다음에 배울 상위 수준의 디플로이먼트 리소스를 생성할 때 자동으로 생성되게 합니다. 어쨌든 레플리카셋을 이해해야 하므로 레플리케이션 컨트롤러와 어떻게 다른지 확인해봅시다.
레플리카셋과 레플리케이션 컨트롤러 비교
레플리카셋은 레플리케이션 컨트롤러와 똑같이 동작하지만 좀 더 풍부한 표현식을 사용하는 파드 셀렉터를 갖고 있습니다. 레플리케이션 컨트롤러의 레이블 셀렉터는 특정 레이블이 있는 파드만을 매칭시킬 수 있는 반면, 레플리카셋의 셀렉터는 특정 레이블이 없는 파드나 레이블의 값과 상관없이 특정 레이블의 키를 갖는 파드를 매칭시킬 수 있습니다.
또한 예를 들어 하나의 레플리케이션 컨트롤러는 레이블이 env=production인 파드와 레이블이 env-deve1인 파드를 동시에 매칭시킬 수 없습니다. 레이블이 env=production인 파드 또는 레이블이 env=deve1인 파드만 매칭시킬 수 있습니다. 그러나 레플리카셋은 하나의 레플리카셋으로 두 파드 세트를 모두 매칭시켜 하나의 그룹으로 취급할 수 있습니다.
마찬가지로 레플리케이션 컨트롤러는 값에 상관없이 레이블 키의 존재만으로 파드를 매칭ㅎ시킬 수 없지만, 레플리카셋은 가능합니다. 예를 들어 레플리카셋은 실제 값이 무엇이든 env 키가 있는 레이블을 갖는 모든 파드를 매칭시킬 수 있습니다.
레플리카셋 정의하기
이제 레플리카셋을 생성해 이전에 레플리케이션컨트롤러에서 생성했다가 버려져서 혼자 남겨진 파드를 레플리카셋이 어떻게 취하는지 살펴보겠습니다. 먼저 다음 예제처럼 새로운 YAML 파일을 만들어 레플리케이션컨트롤러를 레플리카셋으로 다시 작성합니다.
apiVersion: apps/v1beta2 > 레플리카셋은 레플리케이션 컨트롤러와 다르게 이 버전 그룹에 속합니다. kind: ReplicaSet metadata: name: kubia spec: relicas: 3 selector: matchLabels: > 레플리케이션컨트롤러와 유사하지만 matchLabels가 추가됩니다. app: kubia template: metadata: labels: apps: kubia spec: container: - name: kubia image: luksa/kubia
가장 먼저 주목할 점은 레플리카셋이 v1 API의 일부가 아니기 떄문에 리소스를 생성할 때 적절한 apiVersion을 지정해야 한다는 것입니다. 앞에서 만든 레플리케이션 컨트롤러와 내용이 동일한 레플리카셋 유형의 리소스를 생성합니다.
유일한 차이점은 셀렉터에 있습니다. 파드가 가져야 하는 레이블은 selector 속성 바로 아래 나열하는 대신 selector.matchLables 아래에 저장합니다. 이는 레플리카셋에서 레이블 셀렉터를 정의하는 가장 단순한 방법입니다.
app=kubia 셀렉터와 매칭되는 이전에 실해 세 개의 파드가 여전히 실행 중이기 때문에 이 레플리카셋을 생성해도 새 파드가 생성되지는 않습니다. 레플리카셋은 기존 세 개의 파드를 자신의 관리하에 둡니다.
더욱 섬세하게 레플리카셋 사용하기
레플리케이션 컨트롤러에 비해 레플리카셋의 주요 개선 사항은 좀 더 섬세한 레이블 셀렉터입니다. 첫 번째 레플리카셋 예제에서는 의도적으로 단순한 matchLabels 셀렉터를 사용해 레플리카셋이 레플리카컨트롤러와 다르지 않다는 것을 확인했습니다. 이제 더 강력한 matchExpressions를 사용해 셀렉터를 작성합니다.
selector: matchExpressions: - key: app > 라벨 키 operator: IN > 비교 연산자 values: - kubia > 비교할 값
셀렉터에 표현식을 추가할 수 있습니다. 예제와 같이 각 표현식은 키, 연산자, 가능한 값이 포함돼야 합니다. 이 중 유효한 연산자의 예시를 가져왔습니다.
In: 라벨 값이 values 목록에 있는 경우 일치합니다.
NotIn: 라벨 값이 values 목록에 없는 경우 일치합니다.
Exists: 라벨 값이 존재하는 경우 일치합니다.
DoesNotExist: 라벨 값이 존재하지 않는 경우 일치합니다.
Gt: 라벨 값이 주어진 값보다 큰 경우 일치합니다. (문자열은 ASCII 값으로 비교됩니다.)
Lt: 라벨 값이 주어진 값보다 작은 경우 일치합니다. (문자열은 ASCII 값으로 비교됩니다.)
Gte: 라벨 값이 주어진 값보다 크거나 같은 경우 일치합니다. (문자열은 ASCII 값으로 비교됩니다.)
Lte: 라벨 값이 주어진 값보다 작거나 같은 경우 일치합니다. (문자열은 ASCII 값으로 비교됩니다.)
여러 표현식을 지정한 경우 셀렉터가 파드와 매칭되기 위해서는, 모든 표현식이 true여야 합니다. matchLabels와 matchExpressions를 모두 지정하면, 셀렉터가 파드를 매칭하기 위해서는, 모든 레이블이 일치하고, 모든 표현식이 true로 평가 돼야합니다.
레플리케이션 컨트롤러는 쿠버네티스 리소스로서 파드가 항상 실행되도록 보장해야 합니다. 어떤 이유에서든 파드가 사라지면, 쉽게 말해 클러스터에서 노드가 사라지거나 노드에서 파드가 제거된 경우, 레플리케이션 컨트롤러는 사라진 파드를 감지해 교체 파드를 생성합니다. 다음 그림은 파드가 두 개 있는 노드가 다운될 때 어떤 일이 일어나는지 보여줍니다.
파드 A는 직접 생성해 관리되지 않는 파드인 반면, 파드 B는 레플리케이션 컨트롤러에 의해 관리됩니다. 노드에 장애가 발생한 후 레플리케이션 컨트롤러는 사라진 파드 B를 교체하기 위해 새로운 파드를 생성하지만 기존의 파드 A는 완전히 유실됩니다.
이 그림에서 레플리케이션 컨트롤러는 하나의 파드만 관리하지만 일반적으로 레플리케이션 컨트롤러는 파드의 여러 레플리카를 작성하고 관리하기 위한 것입니다. 이것이 '레플리케이션 컨트롤러'라고 불리는 이유입니다.
레이플리케이션 컨트롤러의 파드 생성 방식
레플리케이션 컨트롤러는 실행 중인 파드 목록을 지속적으로 모니터링하고, 특정 레이블 셀렉터의 실제 파드 수가 의도하는 수와 일치하는지 항상 확인합니다. 이런 파드가 너무 적게 실행 중인 경우 파드 템플릿에서 새 복제본을 만들고, 너무 많은 파드가 실행 중이면 초과 복제본이 제거됩니다.
지정한 복제본 숫자보다 적거나 많을 수 있는지 궁금할 수 있을 것 같아 몇 가지 내용을 정리해 보았습니다.
누군가 같은 유형의 파드를 수동으로 만든다.
누군가 기존 파드의 유형을 변경한다.
누군가 의도하는 파드 수를 줄인다.
컨트롤러 조정 루프
레플리케이션 컨트롤러의 역할은 정확한 수의 파드가 항상 레이블 셀렉터와 일치하는지 확인하는 것입니다. 그렇지 않은 경우 레플리케이션 컨트롤러는 의도하는 파드 수와 실제 파드 수를 일치시키기 위한 적절한 조취를 취합니다. 레플리케이션 컨트롤러의 작동 방식은 바로 위에서 설명한 바 있습니다.
레플리케이션 컨트롤러의 세가지 요소
레이블 셀렉터: 레플리케이션 컨트롤러의 범위에 있는 파드를 결정합니다.
레플리카 수: 실행할 파드의 의도하는 수를 지정합니다.
파드 템플릿: 새로운 레플리카를 만들 때 사용합니다.
레플리케이션 컨트롤러의 레플리카 수, 레이블 셀렉터, 심지어 파드 템플릿은 언제든지 수정할 수 있지만 레플리카 수의 변경만 기존 파드에 영향을 끼칩니다.
레플리케이션 컨트롤러가 레이블 셀렉터 혹은 파드 템플릿에 미치는 영향
레이블 셀렉터와 파드 템플릿을 변경해도 기존 파드에는 영향을 미치지 않습니다. 레이블 셀렉터를 변경하면 기존 파드가 레플리케이션 컨트롤러의 범위를 벗어나므로 컨트롤러가 해당 파드에 대한 관리를 중지합니다 . 또한 레플리케이션 컨트롤러는 파드를 생서한 후에는 파드의 실제 "콘텐츠"에 신경을 쓰지 않습니다. 따라서 템플릿은 이 레플리케이션컨트롤러로 새 파드를 생성할 때만 영향을 미칩니다. 템플릿을 새 파드를 만들기 위한 쿠키 커터라고 생각할 수 있습니다.
레플리케이션 컨트롤러 사용 시 이점은 다음과 같이 정리할 수 있습니다.
기존 파드가 사라지면 새 파드를 시작해 파드가 항상 실행되도록 합니다.
클러스터 노드에 장애가 발생하면 장애가 발생한 노드에서 실행 중인 모든 파드에 관한 교체 복제본이 생성됩니다.
수동 또는 자동으로 파드를 쉽게 수평으로 확장할 수 있게 합니다.
레플리케이션 컨트롤러 생성 및 관리
레플리케이션 컨트롤러 생성
레플리케이션 컨트롤러를 생성하는 방법과 이 컨트롤러가 파드를 유지하는 방법을 살펴봅시다. 파드를 비롯해 기타 다른 쿠버네티스 리소스와 마찬가지로 쿠버네티스 API 서버에 JSON 또는 YAML 디스크립터를 게시해 레플리케이션 컨트롤러를 만듭니다.
apiVersion: v1 kind: ReplicationController metadata: name: kubia < 레플리케이션 컨트롤러 이름 spec: relicas: 3 < 복제 및 유지할 컨테이너 수 selector: < 파드 셀렉터로 레플리케이션 컨트롤러가 관리하는 파드 선택 app: kubia template: < 새 파드에 사용할 파드 템플릿 metadata: labels: app: kubia spec: containerL - name: kubia image: luksa/kubia ports: - containerPort: 8080
파일을 API 서버에 게시하면, 쿠버네티스는 레이블 셀렉터 app=kubia와 일치하는 파드 인스턴스가 세 개를 유지하도록 하는 kubia라는 이름의 새로운 레플리케이션 컨트롤러를 생성합니다. 파드가 충분하지 않으면 제공된 파드 템플릿에서 새 파드가 만들어질 것입니다. 템플릿의 내용은 이전에 다루었던 파드의 정의와 거의 동일합니다.
템플릿의 파드레이블은 레플리케이션컨트롤러의 레이블 셀렉터와 완전히 일치해야 합니다. 그렇지 않으면 컨트롤러에서 새 파드를 무한정 생성할 수 있습니다. 이는 새로운 파드를 가동시키더라도 실제 복제본 수가 의도하는 복제본 수와 일치하지 않기 때문입니다. 이런 경우를 방지하기 위해 API 서버는 레플리케이션 컨트롤러의 정의를 검증하고 잘못 구성된 경우 이를 받아 들이지 않습니다.
셀렉터를 지정하지 않는 것도 선택 가능한 옵션입니다. 셀렉터를 지정하지 않으면 템플릿의 레이블로 자동 설정됩니다. 레플리케이션 컨트롤러릉 정의할 때 파드 셀렉터를 지정하면 안됩니다. 쿠버네티스가 파드 템플릿에서 이를 추출하도록 하는 것이 YAML을 좀 더 간결하고 단순하게 유지할 수 있습니다.
레플리케이션컨트롤러 작동 확인
app=kubia인 레이블을 가진 파드가 없으므로 레플리케이션컨트롤러는 파드 템플릿에서 세 개의 새로운 파드를 가동 시켜야 합니다. 파드를 조회해 레플리케이션컨트롤러가 해야 할 작업을 수행했는지 확인해봅시다. kubectl get pods 명령어를 사용하면 실제로 파드가 생성된 것을 확인할 수 있을 것입니다. 여러분은 세개의 파드를 원한다고 YAML에 작성하였고, 레플리케이션 컨트롤러가 이에 따라 세 개의 파들을 생성합니다. 레플리케이션 컨트롤러는 이제 이 세개의 파드를 관리하게 됩니다. 다음은 레플리케이션 컨트롤러가 어떻게 반응하지 보기 위해 파드를 약간 망가뜨려볼 수 있습니다.
파드가 살제될 경우 먼저 파드 중 하나를 수동으로 삭제해서, 레플리케이션 컨트롤러가 어떻게 새로운 파드를 즉시 기동해 파드의 수를 세 개로 되돌리는지 확인합니다. 파드를 삭제하고 다시 조회하면 하나는 제거가 되고 있고, 이미 새로운 파드가 생성되어 총 4개가 가동중일 것 입니다. 이렇게 작동한다면 레플리케이션 컨트롤러가 제 역할을 잘 수행하준 것 입니다.
명령어: kubectl delete pod <pod이름>
컨트롤러가 새로운 파드를 생성한 원리 컨트롤러가 새 교체 파드를 만들어 파드 삭제에 대응합니다. 엄밀히 말하면 그 것은 삭제 그 자체에 대한 대응이 아니라 결과적인 상태(부족한 파드 수)에 대응하는 것입니다.
레플리케이션컨트롤러는 삭제되는 파드에 대해 즉시 통지를 받지만(API 서버는 클라이언트가 리소스 및 리소스 목록의 변경을 감지하는 것을 허용해 줍니다.), 이 통지 자체가 대체 파드를 생성하게 하는 것은 아닙니다. 이 통지는 컨트롤러가 실제 파드 수를 확인하고, 적절한 조치를 취하도록 하는 트리거 역할을 합니다.
노드 장애 대응
쿠버네티스 클러스터에 노드가 세 개가 있다고 가정해 봅시다. 노드 장애를 시뮬레이션하기 위해 노드 중 하나의 네트워크 연결을 끊는 것입니다. 해당 노드에서는 더 이상 파드가 잘동할 수 없게 됩니다.
쿠버네티스를 사용하지 않는 환경에서 노드에 장애가 발생하면 운영 팀은 해당 노드에서 실행 중인 애플리케이션을 수동으로 다른 시스템에 마이그레이션해야 할 것입니다. 반면 쿠버네티스는 이를 자동으로 수행합니다. 레플리케이션 컨트롤러는 노드의 파드가 다운됐음을 감지하자마자 파드를 대체하기 위해 새 파드를 가동합니다.
레이블 혹은 레이블 셀렉터를 변경된다면?
레플리케이션컨트롤러가 생성한 파드는 어떤 식으로든 이 레플리케이션컨트롤러와 묶이지 않습니다. 레플리케이션컨트롤러는 레이블 셀렉터와 일치하는 파드만을 관리합니다. 파드의 레이블을 변경하면 레플리케이션컨트롤러의 범위에서 제거되거나 추가될 수 있습니다. 이러한 방식으로 레플리케이션 컨트롤러에서 다른 레플리케이션 컨트롤러로 이동할 수도 있습니다.
파드의 레이블을 변경해 더 이상 레플리케이션컨트롤러의 레이블 셀렉터와 일치하지 않게 만들면 해당 파드는 수동으로 만든 다른 파드처럼 됩니다. 더 이상 아무도 이 파드를 관리하지 않습니다. 파드를 실행하는 노드에 장애가 발생하면, 파드는 당연히 다시 스케줄링되지 않습니다. 그러나 파드의 레이블을 변경하면 파드가 하나 사라진 것을 레플리케이션컨트롤러가 감지하고 사라진 파드를 대체하기 위해 새로운 파드를 기동함을 명심해야 합니다.
반대로 파드의 레이블을 변경하는 대신 레플리케이션컨트롤러의 레이블 셀렉터를 수정하면 어떻게 될까요? 모든 파드가 레플리케이션컨트롤러의 범위를 벗어나게 되기 때문에 세 개의 새로운 파드를 생성하게 될 것이 정답입니다.
쿠버네티스는 레플리케이션컨트롤러의 레이블 셀렉터를 변경하도록 허용하지만 다른 리소스들의 경우는 그렇지 않습니다. 여러분이 이런 컨트롤러의 레이블 셀렉터를 변경할 수는 없겠지만, 일반적으로 해당 파드 템플릿은 변경되게 될 것입니다.
파드 템플릿 변경
레플리케이션컨트롤러의 파드 템플릿은 언제든지 수정할 수 있습니다. 파드 템플릿을 변경하는 것은 쿠키 커터를 다른 것으로 교체하는 것과 같습니다. 나중에 잘라낼 쿠키에만 영향을 줄 뿐 이미 잘라낸 쿠키에는 아무런 영향을 미치지 않습니다. 기존 파드를 수정하려면 해당 파드를 삭제하고 레플리케이션컨트롤러가 새 템플릿을 기반으로 새 파드로 교체하도록 해야 합니다.
파드 스케일링
지금까지 레플리케이션컨트롤러가 특정 수의 파드 인스턴스를 항상 실행하도록 보장하는 방법을 살펴보았습니다. 원하는 복제본 수를 변경하는 것은 매우 간단한 일이며, 이는 파드를 수평으로 스케일링(확장)하는 것이 무척 쉬운 일임을 의미합니다.
파드 수를 늘리거나 줄이는 것은 레플리케이션 컨트롤러 리소스의 replicas 필드 값을 변경하기만 하면 됩니다. 변경하면 레플리케이션 컨트롤러가 파드가 많을 때, 일부를 삭제하거나, 너무 적으면 파드를 추가로 생성합니다.
즉, vi 등 편집기를 이용해 레플리케이션 컨트롤러 생성에서 보여주었던 예시에서 replicas 숫자를 변경해주거나 kubectl scale 명령어를 이용해 줍니다.
스케일링에 대한 선언적 접근의 원리
쿠버네티스에서 파드를 수평으로 확장한다는 것은 "x개의 인스턴스가 실행되게 하고 싶다."와 같이 의도하는 바를 언급하는 것입니다. 쿠버네티스에게 무엇을 어떻게 하라고 말하는게 아니라 의도하는 상태를 지정할 뿐입니다.
이 선언적 접근 방식을 통해 쿠버네티스 클러스터와 쉽게 상호작용할 수 있습니다. 현재 실행 중인 인스턴스 수를 수동으로 판단해서 추가로 실행해야 할 인스턴스 수를 쿠버네티스에게 명시적으로 알려줘야 한다고 상상해봅시다. 이는 더 많은 작업이 필요하며 오류 발생 가능성이 훨씬 높습니다. 간단히 숫자를 변경하는 것이 훨씬 쉽습니다. 이것을 자동으로 스케일링 해줄 수 있는 쿠버네티스 명령어가 있는데 차후 다뤄보도록 하겠습니다.
쿠버네티스를 사용하면 얻을 수 있는 주요 이점은 쿠버네티스에 컨테이너 목록을 제공해 해당 컨테이너를 클러스터 어딘가에서 계속 실행되도록 할 수 있다는 것입니다.
파드 리소스를 생성하고 쿠버네티스가 이 파드를 실행할 워커 노드를 지정하며 해당 노드에서 파드의 컨테이너가 싱행되도록 함으로써 이 작업을 수행합니다. 그러나 그 컨테이너 중 하나 또는 파드 안에 있는 모든 컨테이너가 죽으면 어떻게 될까요?
파드가 노드에 스케줄링되는 즉시, 해당 노드의 Kubelet은 파드의 컨테이너를 실행하고 파드가 존재하는 한 컨테이너가 계속 실행되도록 할 것입니다. 컨테이너의 주 프로세스에 크래시(Crash)가 발생했다면 kubelet이 컨테이너를 다시 시작합니다.
만약 여러분의 애플리케이션에 버그가 있어 가끔씩 크래시가 발생하는 경우에는 쿠버네티스가 애플리케이션을 자동으로 다시 시작하므로, 애플리케이션에서 특별한 작업을 하지 않더라도 쿠버네티스에서 애플리케이션을 실행하는 것만으로도 자동으로 치유할 수 있는 능력이 주어집니다.
그러나 때때로 애플리케이션은 프로세스의 크래시 없이도 작동이 중단되는 경우가 있습니다. 일례로 자바 애플리케이션이 메모리 누수가 있어서 OutofMemoryErrors를 발생시키기 시작하더라도 JVM 프로세스는 계속 실행될 것 입니다. 애플리케이션이 더 이상 제대로 동작하지 않는다는 신호를 쿠버네티스에 보내서, 쿠버네티스가 애플리케이션을 다시 시작하도록 록 도와준다면 좋을 것입니다.
크래시가 발생한 컨테이너는 자동으로 다시 시작한다고 했으므로 이러한 유형의 오류를 캐치해서 프로세스를 종료할 수도 있습니다. 하지만 이 방법이 모든 문제를 해결할 수는 없습니다.
예를 들어 애플리케이션이 무한 루프나 교착 상태에 빠져서 응답을 하지 않는 상황이라면 어떨까요? 이런 경우 애플리케이션이 다시 시작되도록 하려면 애플리케이션 내부의 기능에 의존하지 말고 외부에서 애플리케이션의 상태를 체크해야 합니다.
JVM : Java Virtual Machine
라이브니스 프로브란
쿠버네티스는 라이브니스 프로브(Liveness probe)를 통해 컨테이너가 살아 있는지 확인할 수 있습니다. 파드의 스펙(specification)에 각 컨테이너의 라이브니스 프로브를 지정할 수 있습니다. 쿠버네티스는 주기적으로 프로브를 실행하고 프로브가 실패할 경우 컨테이너를 다시 시작합니다. 이와 비슷하게 레디니스 프로브(Reainess probe)도 있는데 이는 쓰임새가 다르기 때문에 혼동하지 않도록 주의해야 합니다.
쿠버네티스는 다음 세가지 메커니즘을 통해 컨테이너에 프로브를 실행합니다.
HTTP GET 프로브 지정한 IP 주소, 포트, 경로에 HTTP GET 요청을 수행합니다. 프로브가 응답을 수신하고 응답 코드가 오류를 나타내지 않는 경우(즉, HTTP 응답 코드가 200~399인 경우)에 프로브가 성공했다고 간주됩니다. 서버가 오류 응답 코드를 반환하거나 전혀 응답하지 않으면 프로브가 실패한 것으로 간주돼 컨테이너를 다시 시작합니다.
TCP 소켓 프로브 컨테이너의 지정된 포트에 TCP 연결을 시도합니다.연결에 성공하면 프로브가 성공한 것이고, 그렇지 않으면 컨테이너가 다시 시작합니다.
Exec 프로브 컨테이너 내의 임의 명령을 실행하고 명령의 종료 상태 코드를 확인합니다. 상태 코드가 0이면 프로브가 성공한 것입니다. 모든 다른 코드는 실패로 간주됩니다.
HTTP 기반 라이브니스 프로브
Node.js 애플리케이션에 라이브니스 프로브를 추가하는 방법을 살펴봅시다. 웹 애플리케이션이므로 웹 서버가 요청을 처리하는지 체크하는 라이브니스 프로브를 추가하는 것이 좋을 것입니다. 그러나 이 Node.js 애플리케이션은 너무 단순해서 쉽게 실패가 발생하지 않으므로 인위적으로 실패하게 만들어야 합니다.
이 프로브를 사용하면 처음 다섯 번째까지는 적절히 처리하고 이후의 모든 요청은 오류를 반환합니다. 라이브니스 프로브 덕분에 이런 현상이 발생하면 컨테이너가 다시 시작돼 클라이언트의 요청을 다시 적절히 처리하게 됩니다.
해당 컨테이너 이미지는 감사하게도 도커 허브에 푸시했주었기 때문에 직접 이미지를 빌드할 필요는 없고 바로 테스트 해보실 수 있을 것입니다. pod를 생성하는 YAML 파일에 다음 HTTP GET 라이브니스 프로브가 포함된 새 파드를 생성합니다. 예시는 다음과 같습니다.
apiVersion: v1 kind: pod metadata: name: kubia-liveness spec: container: - image: luksa/kubia-unhealthy < 약간의 문제가 있는 이미지 파일 name: kubia livenessProbe: < HTTP GET을 수행하는 라이브니스 프로브 httpGet: path: / < HTTP 요청 자료 port: 8080 < 프로브가 연결해야 하는 네트워크 포트
이 파드 디스크립터는 쿠버네티스가 주기적으로 "/" 경로와 8080포드에 HTTP GET을 요청해 컨테이너가 정상 작동되는지 확인하도록 httpGet 라이브니스 프로브를 정의합니다. 이 요청은 컨테이너가 실행되는 즉시 시작됩니다.
다섯 번의 요청 후에 애플리케이션은 HTTP 상태 코드 500을 반환하기 시작하고 쿠버네티스가 프로브를 실패한 것으로 간주해 컨테이너를 다시 시작합니다.
Node.js는 Chrome의 V8 JavaScript 엔진을 기반으로 구축된 오픈 소스 교차 플랫폼 런타임 환경으로 개발자가 JavaScript를 사용하여 서버 측 애플리케이션을 만들 수 있습니다.
이외의 속성 설정
kubectl describe는 라이브니스 프로브에 관한 추가적인 정보도 표시되는 것을 알 수 있습니다. 명시적으로 지정한 라이브니스 프로브 옵션 외에도 지연(delay), 제한 시간(timeout), 기간(period) 등과 같은 추가 속성을 볼 수도 있습니다. 다음 예시의 라이브니스 프로브 옵션에 대해 살펴보겠습니다.
kubectl describe pod kubia-liveness
이전에 작성했던 YAML 파일을 kubectl apply 명령어로 싱행한 후 위 명령어를 작성하면 출력되는 정보에서 컨테이너가 현재 실행 중이지만 오류로 인해 이전에 종료된 것을 확인할 수 있습니다. 종료 코드는 137일텐데 이는 프로세스가 외부 신호에 의해 종료되었음을 나타냅니다.
이 숫자는 128+n이고 n은 프로세스에 전송된 시그널 번호이고, 이 시그널로 인해 컨테이너가 종료됐음을 의미합니다. 이 예시에서는 신호를 죽이는 코드인 9이고, 이에 따라 강제 종료됐다는 것을 알 수 있습니다.
이외에도 initialDelaySeconds 속성을 사용해 라이브니스 프로브에 추가해 초기 지연 시간을 설정할수도 있습니다.
livenessProbe: httpGet: path: / port: 8080 initialDelaySeconds: 15 < 시간의 기준은 초(s) 입니다. 즉, 첫 프로브 실행까지 15초를 대기하겠다는 뜻입니다.
초기 지연을 설정하지 않으면 프로브는 컨테이너가 시작되자마자 프로브를 시작합니다. 이 경우 대부분 애플리케이션이 요청을 받을 준비가 돼 있지 않기 때문에 프로브가 실패하게 됩니다. 실패 횟수가 실패 임곗값을 초과하면 요청을 올바르게 응답하기 전에 컨테이너가 다시작됩니다. 때문에 초기 지연을 필수적으로 설정해줘야 하는 것이죠.
효율적으로 프로브 생성하기
운영 환경에서 실행 중인 파드는 반드시 라이브니스 프로브를 정의해야 합니다. 정의하지 않으면 쿠버네티스가 애플리케이션이 살아 있는지를 알 수 있는 방법이 없습니다. 프로세스가 실행되는 한 쿠버네티스는 컨테이너가 정상적이라고 간주하게 될 것입니다. 이제 효율적으로 프로브를 생성하기 위한 예시를 알아보겠습니다.
라이브니스 프로브가 확인해야 하는 사항 위에서 만든 간단한 라이브니스 프로브는 단순히 서버가 응답하는지만 검사합니다. 지나치게 단순해 보이지만, 이런 라이브니스 프로브는 엄청난 일을 합니다. 컨테이너 내에서 실행 중인 웹 서버가 HTTP 요청에 응답하지 않으면 컨테이너가 다시 시작되기 때문입니다. 라이브니스 프로브가 없는 것에 비하면 커다란 개선 사항이며, 대부분 이것으로 충분합니다.
그러나 더 나은 라이브니스 프로브를 위해 특정 URL 경로에 요청하도록 브로브를 구성해 애플리케이션 내에서 실행 중인 모든 주요 구성 요소가 살아 있는지 또는 응답이 없는지 확인하도록 구성할 수 있습니다.
라이브니스 프로브는 애플리케이션의 내부만 체크하고, 외부 요인의 영향을 받지 않도록 해야합니다. 예를 들어 프론트엔드 웹서버의 라이브니스 프로브는 웹서버가 백엔드 데이터베이스에 연결할 수 없을 때 실패를 반환해서는 안 됩니다. 근본적인 원인이 데이터베이스 자체에 있을 경우, 웹 서버 컨테이너를 재시작한다 하더라도 문제가 해결되지는 않습니다.
프로브 가볍게 유지하기 라이브니스 프로브는 너무 많은 연산 리소스를 사용해서는 안 되며, 완료하는 데 너무 오래 걸리지 않아야 합니다. 기본적으로 프로브는 비교적 자주 실행되며 1초 내에 완료돼야 합니다. 너무 많은 일을 하는 프로브는 컨테이너의 속도를 상당히 느려지게 만듭니다.
그래서 컨테이너가 사용할 수 있는 CPU 시간과 할당량을 제한하는 방법이 있는데, 이는 나중에 다뤄보도록 하겠습니다. 프로브의 CPU 사용 시간은 컨테이너의 CPU 시간 할당량으로 계산되므로, 라이브니스 프로브를 무겁게 만들면 메인 애플리케이션 프로세스에서 사용할 수 있는 CPU 시간이 줄어들게 됩니다.
프로브에 재시도 루프를 구연하지 않기 프로브의 실패 임곗값을 설정할 수 있으며, 컨테이너가 강제 종료되려면 프로브가 여러 번 실패해야 함을 알게 되었을 것입니다. 그러나 실패 임곗값을 1로 설정하더라도, 쿠버네티스는 실패를 한 번 했다고 간주하기 전에 프로브를 여러 번 재시도 합니다. 따라서 프로브에 자체적인 재시도 루프를 구현하는 것은 헛수고 입니다.
Kubernetes annotations(주석)은 파드, 배포, 서비스 등과 같은 Kubernetes 개체에 추가할 수 있는 메타데이터입니다. 개체를 식별하는 데 사용되지 않고 컨텍스트 또는 구성 옵션을 제공하는 데 사용되는 개체에 대한 추가 정보를 제공합니다. 어노테이션은 키-값 쌍으로 표시되며 Kubernetes 도구 또는 기타 애플리케이션에서 특정 작업을 수행하거나 추가 기능을 제공하는 데 사용할 수 있습니다.
이 예에서는 배포 메타데이터에 app-type 및 app-version이라는 두 개의 어노테이션을 추가했습니다. 이러한 어노테이션은 배포 중인 애플리케이션 유형 및 해당 버전과 같은 배포에 대한 추가 정보를 제공합니다. 이 정보는 배포 및 관련 리소스를 더 잘 이해하기 위해 모니터링 도구 또는 기타 응용 프로그램에서 사용할 수 있습니다.
어노테이션을 사용하여 Kubernetes의 특정 기능이나 동작을 구성할 수도 있습니다. 예를 들어 kubectl 명령줄 도구는 특정 명령의 동작을 사용자 지정하는 데 사용할 수 있는 여러 어노테이션을 지원합니다. 다음은 어노테이션을 사용하여 kubectl expose 명령을 구성하는 방법의 예입니다.
이 예에서는 example-deployment 배포를 위한 새 서비스를 생성하고 annotations 플래그를 사용하여 로드 밸런서에 대한 AWS SSL 인증서를 지정합니다. 이 어노테이션은 이 서비스와 연결된 로드 밸런서에 대해 지정된 SSL 인증서를 사용하도록 Kubernetes에 지시합니다.
어노테이션 특징
파드 및 다른 오브젝트는 레이블 외에 어노테이션을 가질 수 있습니다. 어노테이션은 키-값 쌍으로 레이블과 거의 비슷하지만 식별 정보를 갖지 않습니다. 레이블은 오브젝트를 묶는 데 사용할 수 있지만, 어노테이션은 그렇게 할 수 없습니다. 레이블 셀렉터를 통해서 오브젝트를 선택하는 것이 가능하지만 어노테이션 셀렉터와 같은 것은 없습니다.
반면 어노테이션은 훨씬 더 많은 정보를 보유할 수 있습니다. 이는 주로 명령어들에서 사용됩니다. 특정 어노테이션은 쿠버네티스에 의해 자동으로 오브젝트에 추가되지만, 나머지 어노테이션은 사용자에 의해 수동으로 추가됩니다.
어노테이션은 쿠버네티스에 새로운 기능을 추가할 때 흔히 사용됩니다. 일반적으로 새로운 기능의 알파 혹은 베타 버전은 API 오브젝트에 새로운 필드를 바로 도입하지 않습니다. 필드 대신 어노테이션을 사용하고, 필요한 API 변경이 명확해지고 쿠버네티스 개발자가 이에 동의하면 새로운 필드가 도입됩니다. 그리고 관련된 어노테이션은 사용이 중단되게 됩니다.
어노테이션이 유용하게 사용되는 경우는 파드나 다른 API 오브젝트에 설명을 추가해두는 것입니다. 이렇게 하면 클러스터를 사용하는 모든 사람이 개별 오브젝트에 관한 정보를 신속하게 찾아볼 수 있습니다. 예를 들어 오브젝트를 만든 사람 이름을 어노테이션으로 지정해두면, 클러스터에서 작업하는 사람들이 좀 더 쉽게 협업할 수 있습니다.
네임스페이스의 특징
이전에 레이블을 이용해 파드와 다른 오브젝트를 그룹으로 묶는 것을 다뤘었습니다. 각 오브젝트는 여러 레이블을 가질 수 있기 때문에, 오브젝트 그룹은 서로 겹쳐질 수 있습니다. 또한 클러스터에서 작업을 수행할 때 (예를 들어 kubectl 명령을 통해) 레이블 셀렉터를 명시적으로 지정하지 않으면 항상 모든 오브젝트를 보게 됩니다.
오브젝트를 겹치지 않는 그룹으로 분할하고자 할 때에는 어떻게 해야 할까요? 또 다른 클러스터를 만들어 완전히 별개의 작업공간을 만들 수도 있지만, 사실 한 번에 하나의 그룹 안에서만 작업하고 싶은것이 사람 마음입니다. 이러한 이유로 쿠버네티스는 오브젝트를 네임 스페이스로 그룹화합니다.
여기서 네임스페이스는 이전에 다루었던 프로세스를 격리하는데 사용했던 리눅스 네임스페이스가 아닙니다. 쿠버네티스 네임스페이스는 오브젝트 이름의 범위를 제공합니다. 모든 리소스를 하나의 단일 네임스페이스에 두지 않고 여러 네임스페이스로 분할할 수 있으며, 이렇게 분리된 네임스페이스는 같은 리소스 이름을 다른 네임스페이스에 걸쳐 여러번 사용할 수 있게 해줍니다.
여러 네임스페이스를 사용하면 많은 구성 요소를 가진 복잡한 시스템을 좀 더 작은 개별 그룹으로 분리할 수 있습니다. 또한 멀티테넌트 환경처럼 리소스를 분리하는 데 사용됩니다. 리소스를 프로덕션, 개발, QA 환경 혹은 원하는 다른 방법으로 나누어 사용할 수 있습니다. 리소스 이름은 네임스페이스 안에서만 고유하면 됩니다. 서로 다른 두 네임스페이스는 동일한 이름의 리소스를 가질 수 있습니다. 대부분의 리소스 유형은 네임스페이스 안에 속하지만 일부는 그렇지 않습니다. 그 가운데 하나는 노드 리소스인데, 이 리소스는 전역(global)이며 단일 네임스페이스에 얽매이지 않습니다.
네임스페이스를 사용한 격리 이해하기
네임스페이스를 사용하면 오브젝트를 별도 그룹으로 분리해 특정한 네임스페이스 안에 속한 리소스를 대상으로 작업할 수 있게 해주지만, 실행 중인 오브젝트에 대한 격리는 제공하지 않습니다.
예를 들어 다른 사용자들이 서로 다른 네임스페이스에 파드를 배포할 때 해당 파드가 서로 격리돼 통신할 수 없다고 생각할 수 있지만, 반드시 그런 것은 아닙니다. 네임스페이스에서 네트워크 격리를 제공하는지는 쿠버네티스와 함께 배포하는 네트워킹 솔루션에 따라 다릅니다.
네트워크 솔루션이 네임스페이스 간 격리를 제공하지 않는 경우 foo 네임스페이스 안에 있는 파드가 bar 네임스페이스 안에 있는 파드의 IP 주소를 알고 있었다면, HTTP 요청과 같은 트레픽을 다른 파드로 보내는 것에 아무런 제약 사항이 없습니다.
실제 애플리케이션을 배포할 때 대부분의 사용자는 더 많은 파드를 실행하게 될 것입니다. 파드 수가 증가함에 따라 파드를 부분 집합으로 분류할 필요가 있습니다.
예를 들어 마이크로서비스 아키텍처의 경우 배포된 마이크로서비스의 수는 20개를 매우 쉽게 초과합니다. 이러한 구성 요소는 복제돼(동일한 구성 요소의 여러 복사본이 배포됩니다.) 여러 버전 혹은 릴리스 (안정, 베타, 카나리 등)가 동시에 실행됩니다. 이로 인해 시스템에 수백 개 파드가 생길 수 있다. 파드를 정리하는 메커니즘이 없다면, 크고 이해하기 어려운 난장판이 됩니다. 그림은 여러 개 레플리카를 실행하는 여러 마이크로서비스에 속해 있는 파드와 동일한 마이크로 서비스의 다른 릴리스에 속한 파드를 보여줍니다.
모든 개발자와 시스템 관리자는 어떤 파드가 어떤 것인지 쉽게 알 수 있도록 임의의 기준에 따라 작은 그룹으로 조직하는 방법이 필요합니다. 각 파드에 대해 개별적으로 작업을 수행하기보다 특정 그룹에 속한 모든 파드에 관해 한 번에 작업하기를 원할 것 입니다. 레이블을 통해 파드와 기타 다른 쿠버네티스 오브젝트의 조직화가 이뤄집니다.
레이블이란
레이블은 파드와 모든 다른 쿠버네티스 리소스를 조직화할 수 있는 단순하면서 강력한 쿠버네티스 기능입니다. 레이블은리소스에 첨부하는 키-값 쌍으로, 이 쌍은 레이블 셀렉터를 사용해 리소스를 선택할 때 활용됩니다.(리소스는 셀렉터에 지정된 레이블을 포함하는지 여부에 따라 필터링된다.) 레이블 키가 해당 리소스 내에서 고유하다면, 하나 이상 원하는 만큼 레이블을 가질 수 있다. 일반적으로 리소스를 생성할 때 레이블을 붙이지만, 나중에 레이블을 추가하거나 기존 레이블 값을 수정할 수도 있습니다.
위 그림의 마이크로서비스 예제로 돌아가봅시다. 파드에 레이블을 붙여 누구나 쉽게 이해할 수 있는 훨씬 체계적인 시스템을 구성할 수 있습니다. 각 파드에는 레이블 두개를 붙였습니다. 이 외에도 다양한 구성이 가능합니다.
* app: 파드가 속한 애플리케이션, 구성 요소 혹은 마이크로서비스를 지정합니다.
* rel : 파드에서 실행 중인 애플리케이션이 안정(stabl), 베타 혹은 카나리 릴리스인지 보여줍니다.
두 레이블을 추가해 위 그림의 아래처럼 파드를 2차원으로 구성이 가능합니다. (애플리케이션은 수평으로, 릴리스는 수직으로) 클러스터에 접속할 수 있는 개발자와 운영자는 이제 파드 레이블을 보고 시스템 구조와 각 파드가 적합한 위치에 있는지 볼 수 있게 됩니다.
레이블 셀렉터 이용하기
리소스를 조회할 때 각 리소스 옆에 부착된 레이블을 같이 표시하는 것은 그다지 흥미로운 일이 아닙니다. 중요한 것은 레이블 셀렉터와 함께 사용된다는 점입니다. 레이블 셀렉터는 특정 레이블로 태그된 파드의 부분 집합을 선택해 원하는 작업을 수행합니다. 레이블 셀렉터는 특정 값과 레이블을 갖는지 여부에 따라 리소스를 필터링하는 기준이 됩니다.
레이블 셀렉터는 리소스 중에서 다음 기준에 따라 리소스를 선택합니다.
특정한 키를 포함하거나 포함하지 않는 레이블
정한 키와 값을 가진 레이블
특정한 키를 갖고 있지만, 다른 값을 가진 레이블
명령어 kubectl get pod 에 옵션을 부가해 원하는 부분만 뽑아낼 수 있습니다. --selector 또는 -l 로 사용되며 구조는 다음과 같습니다.
kubectl get pod -l <label>
레이블 셀렉터 편의 기능
셀렉터는 쉼표를 사용해 여러 기준을 포함하는 것도 가능합니다. 셀렉터를 통해 선택하기 위해서는 리소스가 모든 기준을 만족해야 합니다. 위 사진에서 예를 들어 제품 카탈로그 마이크로서비스의 베타 릴리스인 파드를 선택하기 위해서는 app=pc, rel=beta 셀렉터를 사용합니다.
레이블 셀렉터는 파드 목록을 나열하는 것 뿐만 아니라, 파드 부분 집합에 작업을 수행할 때도 유용합니다. 예를 들어 레이블 셀렉터를 이용해 여러 파드를 한번에 삭제할 수 있습니다. 또한 레이블 셀렉터는 kubectl에서만 사용하는 것이 아니고 내부적으로도 사용됩니다.
레이블과 셀렉터로 파드 스케줄링 제한
지금까지 생성한 모든 파드는 워커 노드 전체에 걸쳐 무작위로 스케줄링됐고 이것이 쿠버네티스에서 동작하는 방식입니다. 쿠버네티스는 모든 노드를 하나의 대규모 배포 플랫폼으롤 노출하기 때문에, 파드가 어느 노드에 스케줄링됐느냐는 중요하지 않습니다. 각 파드는 요청한 만큼의 정확한 컴퓨팅 리소스(GPU, 메모리 등)를 할당받습니다. 그리고 다른 파드에서 해당 파드로 접근하는 것은 파드가 스케줄링된 노드에 아무런 영향을 받지 않습니다. 그렇기 때문에 쿠버네티스에게 파드를 어디에 스케줄링할지 알려줄 필요는 없습니다.
하지만 예를 들어 하드웨어 인프라가 동일하지 않은 경우를 들 수 있습니다. 워커 노드 일부는 HDD를 가지고 있고 나머지에게 SSD를 가지고 있는 경우, 특정 파드를 한 그룹에 나머지 파드는 다른 그룹에 스케줄링되도록 할 수 있습니다. 또 다른 예는 GPU 가속을 제공하는 노드에만 GPU 계산이 필요한 파드를 스케줄링하는 것을 들 수 있습니다.
쿠버네티스의 전체적인 아이디어는 그 위에 실행되는 애플리케이션으로부터 실제 인프라스트럭처를 숨기는 것에 있기에 파드가 어떤 노드에 스케줄링돼야 하는지 구체적으로 지정하고 싶지는 않을 것입니다. 그로 인해 애플리케이션이 인프라스트럭처에 결합되기 때문입니다.
즉, 애플리케이션이 인프라 스트럭처에 너무 세밀하게 연결되어 쿠버네티스의 목적성에 맞지 않다는 것을 뜻합니다. 그러나 정확한 노드를 지정하는 대신 필요한 노드 요구 사항을 기술하고 쿠버네티스가 요구 사항을 만족하는 노드를 선택하도록 해야합니다. 이는 노드 레이블과 레이블 셀렉터를 통해 할 수 있습니다.
인프라 스트럭처(인프라) : 물리적 기반 시설
파드 이외에도 레이블 사용하기
앞에서 확인한 바와 같이 파드는 레이블을 부착할 수 있는 유일한 쿠버네티스 리소스가 아닙니다. 노드를 포함한 모든 쿠버네티스 오브젝트에 레이블을 부착할 수 있습니다. 일반적으로 ops 팀은 새 노드를 클러스터에 추가할 때, 노드가 제공하는 하드웨어나 파드를 스케줄링할 때 유용하게 사용할 수 있는 기타 사항을 레이블로 지정해 노드를 분류합니다.
클러스터에 범용 GPU 컴퓨팅에 사용할 수 있는 GPU를 가지고 있는 노드가 있다고 가정해봅시다. 이 기능을 가지고 레이블을 노드에 추가하려고 합니다. gpu=true 레이블을 노드 중 하나에 추가합니다. 이제 파드를 나열할 때처럼 노드를 나열할 때 레이블 셀렉터를 사용할 수 있습니다. 노드 중에 gpu=true 레이블을 가진 노드를 나열해봅시다. 이러한 방식으로 kubectl을 이용해 모든 노드를 나열하고 각 노드의 gpu 레이블 값을 보여주는추가 열을 표시하도록 하는 것도 가능합니다.
특정 노드에 파드 스케줄링
이제 GPU를 필요로 하는 새로운 파드를 배포해야 한다고 가정해봅시다. 스케줄러가 GPU를 제공하는 노드를 선택하도록 요청하려면, 파드의 yaml 파일에 노드 셀렉터를 추가해야 합니다. 파드를 생성하는 yaml 파일에 다음 내용을 추가하고 kubectl create -f <수정한 yaml 파일> 명령을 실행합니다.
spec: nodeSeloector: gpu: "true"
보통 해당 내용은 yaml파일의 container가 정의 되어있는 부분 근처에 'spec: '이 있을텐데 거기에 추가해주면 됩니다.
하나의 특정 노드로 스케줄링
마찬가지로 각 노드에는 키를 kubernetes.io/hostname으로 하고 값에는 호스트 이름이 설정돼 있는 고유한 레이블이 있기 때문에, 파드를 특정한 노드로 스케줄링하는 것도 가능합니다. 그러나 nodeSelector에 실제 호스트 이름을 지정할 경우에 해당 노드가 오프라인 상태인 경우 파드가 스케줄링되지 않을 수 있습니다. 따라서 개별 노드로 생각하기 보다 레이블 셀렉터를 통해 지정한 특정 기준을 만족하는 노드의 논리적인 그룹을 생각해야 합니다.
레이블 셀렉터의 중요성은 이후에 다뤄볼 레플리케이션 컨트롤러와 서비스를 이야기 할 때 다시 한번 강조될 것으로 생각 됩니다. 내용은 순차적으로 차근차근 다뤄보도록 하겠습니다.