전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]

티스토리 뷰

7. 파드 실행 및 통제를 위한 워크로드 컨트롤러

7장 부터는 쿠버테니스 리소스별로 추가적인 설명을 하겠습니다.

열심히 개발한 어플리케이션을 컨테이너 이미지로 만들었다면 쿠버네티스 클러스터에 파드로 배포를 해야 합니다.
물론 실무에서는 CI/CD파이프라인을 통해 자동으로 배포가 되기 때문에 개발자 여러분들이 직접 워크로드 컨트롤러 오브젝트와 서비스나 인그레스 같은 다른 오브젝트들을 야믈로 정의해서 직접 만들일은 없을 수 있습니다.
하지만 CI/CD 파이프라인을 직접 만들어야 한다면 각 쿠버네티스 리소스를 정의하는 방법에 대해 알고 있어야 합니다.
설사 CI/CD 파이프라인을 이용만 하더라도 생성된 쿠버네티스 오브젝트들을 이해하는 것이 컨테이너화 하는 어플리케이션을 만드는데 많은 도움이 됩니다.
그래서 조금 어려울 수도 있지만 인내심을 갖고 각 쿠버네티스 리소스에 대해 조금 더 학습하여 주시기 바랍니다.

워크로드 컨트롤러는 파드의 유형에 따라 아래와 같이 5가지로 나눌 수 있습니다.
대부분의 어플리케이션은 디플로이먼트 또는 스테이트풀셋으로 파드를 배포합니다. 나머지 리소스는 아주 간혹 사용될 것입니다. 그래서 디플로이먼트와 스테이트풀셋을 중점적으로 다루고 나머지는 간단한 예제로 이해만 하고 넘어 가겠습니다.


파드 유형에서 다른 유형은 쉽게 이해가 되겠지만 스테이트리스 서버와 스테이트풀 서버는 생소하실 겁니다.
우리말로 하면 ‘상태가 없는서버’와 ‘상태가 있는 서버’가 되고 여기서 ‘상태’란 ‘정보' 또는 ‘데이터'를 의미합니다.
그래서 스테이트리스 서버는 ‘고유의 데이터가 없는 서버'이고 스테이트풀 서버는 ‘고유의 데이터가 있는 서버'가 됩니다.
스테이트리스와 스테이트풀에 대해 좀 더 쉬운 이해를 위해 예를 하나 들겠습니다.
여러분이 콜센터에 전화로 상담하는 상황을 생각해 봅시다. 상담원A와 상담을 하다가 갑자기 전화가 끊겨서 다시 전화를 했더니 상담원B와 연결이 되었습니다. 상담원A가 본인의 노트에만 상담내용을 기록했다면, 상담원B는 다시 처음부터 상담을 시작해야 합니다.
각 상담원들을 파드라고 생각해 봅시다.
각 파드가 데이터를 자기 안에 갖고 있으면 다른 파드로 연결되었을 때 서비스에 문제가 발생합니다.



그렇다면 “독립적인 빌드/배포/스케일링을 위해서 컨테이너를 사용하는 건데 각 파드가 스테이트풀하게 고유의 데이터를 가지면 안되지 않습니까 ?”라고 생각하시는 분들이 있을까요 ? 그렇다면 정말 컨테이너 기술을 잘 이해하신 겁니다.
네 맞습니다. 일반적인 경우 파드안에 담기는 어플리케이션은 스테이트리스하게 개발되어야 합니다.
그래서 로그는 콘솔에 남겨 EFK스택으로 통합하고 인증 세션은 별도의 인증세션 저장소에서 관리되어야 합니다.

그럼 어떤 경우에 스테이트풀셋StatefulSet이라는 리소스로 스테이트풀Stateful한 파드를 만드는 걸까요 ?
파드가 고유의 데이터를 관리할 필요가 있을 경우에 필요합니다. 어플리케이션은 이런 경우가 없어야 합니다.
하지만 데이터베이스를 파드로 실행한다면 각 파드는 고유의 데이터를 관리해야 합니다.
예를 들어 MySQL의 ‘Primary-Secondary’ 아키텍처를 생각해 봅시다.
아래와 같이 각 파드는 고유의 데이터 저장소를 가지고 있어야 이벤트와 로그를 이용하여 데이터를 동기화 합니다.

따라서, 각 DB파드는 자기가 CRUD해야할 데이터 저장소에 대한 정보를 갖고 있어야 합니다. 

결론적으로 데이터베이스를 파드로 실행한다면 스테이트풀셋 리소스를 이용해야 합니다.

DB가 아닌 일반적인 어플리케이션도 스테이트풀셋으로 파드를 배포해야 하는 경우가 있습니다.
파드의 고유한 주소가 필요한 경우입니다.
대표적인 경우가 스프링클라우드의 유레카Eureka라는 서비스 디스커버리 서버를 이용할때입니다.
유레카는 서버가 실행될 때 그 서버의 주소인 HOST와 PORT정보를 저장하고 그 서버에 대한 주소 정보 요청이 오면 제공해 주는 서버입니다.
아래는 스프링클라우드 로드밸런서Spring Cloud LoadBalancer 와 유레카를 이용하여 특정 파드를 직접 연결하는 예시 입니다.


❶ 각 파드가 실행될 때 ‘EurekaClient’를 이용하여 유레카 서버에 주소 등록을 요청합니다. 이때 spring.application.name에 등록된 이름인 ‘member’, 파드명, server.port에 등록된 ‘3001’, 서비스 오브젝트명 ‘member’, 네임스페이스 ‘ott’가 전달됩니다. 스테이트풀셋으로 배포한 파드의 이름에는 일련번호가 붙습니다.
❷ 유레카는 어플리케이션명인 ‘member’ 하위에 3개 파드 주소를 등록 합니다. 등록되는 파드 주소 형식은 {파드명}.{서비스명}.{네임스페이스명}.svc.cluster.local:{서버 포트}가 됩니다. 예를 들어 ‘member-0.member.ott.svc.cluster.local:3001’와 같습니다.
❸ ‘member’ 서버를 접근하려는 클라이언트 파드의 스프링 클라우드 로드밸런서는 유레카에 ‘member’서버에 대한 주소를 요청 합니다.
❹ 유레카는 ‘member’서버에 등록된 3개 파드의 주소를 리턴 합니다.
❺ ‘Web Client’는 스프링 클라우드 로드밸런서가 로드밸런싱 정책에 따라 결정한 파드 주소를 이용하여 그 파드로 연결 합니다.

이와 같은 아키텍처대로 동작하려면 각 파드가 고유의 주소가 있어야 합니다.
이런 경우에는 스테이트리스한 어플리케이션도 스테이트풀셋으로 배포해야 합니다.

디플로이먼트와 스테이트풀셋의 차이를 요약하면 고유의 데이터 관리나 주소가 필요한 경우에는 스테이트풀셋으로 배포하고 그렇지 않은 경우는 디플로이먼트로 배포해야 한다는 것입니다.

그럼 각 워크로드 컨트롤러 리소스를 야믈로 정의하여 파드를 실행하고 통제하는 방법을 배워보도록 하겠습니다.
여러분들이 직접 워크로드 컨트롤러를 만들어야 한다면 어떤것들을 알아야 할까요 ?
생각해 보면 아래와 같은 지식이 필요 할 겁니다.


워크로드 컨트롤러의 공통 구조는 ‘7.1’장에서 파드 스케줄링은 ‘7.3’장에서 나머지는 ‘7.2’장에서 배워보도록 하겠습니다.

7.1 워크로드 컨트롤러 구조 이해

워크로드 컨트롤러의 야믈 구조를 이해하시면 야믈 파일을 만들거나 워크로드 오브젝트를 파악하는데 큰 도움이 됩니다.
워크로드 컨트롤러는 크게 워크로드 컨트롤러 명세, 파드 명세, 컨테이너 명세 부분으로 나뉩니다.
그리고 각 명세 부분에 그 리소스의 목적에 맞는 항목들을 정의하면 됩니다.


<팁>
각 리소스 명세내에서의 항목의 위치는 순서가 없습니다. 예를 들어 metadata.name이 matadata.labels 뒤에 와도 잘 동작 합니다.
</>

각 리소스 명세에서 공통적인 부분만 설명하겠습니다.

1) 워크로드 컨트롤러 명세
워크로드 컨트롤러 명세에서 기본 정보는 리소스 종류, 리소스 API 버전, 이름, 레이블입니다.
사실 이건 모든 쿠버네티스 오브젝트를 정의할 때 공통적인 항목입니다.

항목 키 이름 설명
리소스 종류 kind: {리소스 종류} 워크로드 컨트롤러의 리소스 종류
리소스 API버전 apiVersion: {API 버전} 워크로드 컨트롤 리소스에 따른 API 버전
Deployment, StatefulSet, DaemonSet은 ‘apps/v1’이고 Job과 CronJob은 ‘batch/v1’임
이름 metadata:
  name: {오브젝트명}
생성할 파드 수에 따라 각 파드의 이름은 ‘{워크로드 컨트롤러 오브젝트명}-{랜덤문자 또는 일련번호}’와 같이 부여됨
레이블 metadata:
  labels:
    {키}: {값}
    {키}: {값}
    …
워크로드 컨트롤러 오브젝트의 레이블 지정
이 값은 레이블로 워크로드 컨트롤러를 찾을 때 사용


그리고 워크로드 컨트롤러의 역할이 파드를 실행하고 통제하는 것이므로 몇개의 파드를 실행하고 어떤 파드를 통제할지를 지정합니다.

항목 키 이름 설명
파드 수 spec: 
  replicas: {파드 수}
실행할 파드 수
디플로이먼트와 스테이트풀셋에만 지정 가능함. 데몬셋은 워커 노드 당 1개씩 만 배포되므로 지정이 필요 없고 잡과 크론잡은 다른 방법으로 파드 수를 지정함
통제할 파드 선택 조건 spec:
  selector:
    matchLabels:
      {레이블 키}:{레이블 값}
파드 명세에 지정한 파드 레이블 키와 값으로 통제할  대상 파드를 선택함



2) 파드 명세
워크로드 컨트롤러 오브젝트를 위해 파드의 레이블을 지정하고 파드를 실행할 계정, 컨테이너를 위한 볼륨, 컨테이너 이미지 다운로드를 위한 인증 시크릿을 정의 합니다. spec.template 밑에 지정합니다.

항목 키 이름 설명
파드 이름 metadata:
  name: {파드 명}
파드 이름
파드 레이블 metadata:
  labels:
    {키}: {값}
워크로드 컨트롤러가 통제할 대상 파드를 찾을 때 이 레이블을 이용함
파드 실행 계정 spec: 
  serviceAccount: {이름}
파드를 실행할 서비스 어카운트의 이름을 지정함
이 서비스 어카운트에 부여한 권한에 따라 컨테이너 실행이 제어됨
컨테이너를 위한 볼륨 spec: 
  volumes:
  - name: {볼륨명}
    persistentVolumeClaim
컨테이너에게 파드 외부의 스토리지 볼륨을 제공하기 위해 퍼시스턴트 볼륨 클레임PVC를 정의함 
‘{볼륨명}’은 컨테이너의 ‘volumeMounts’항목에 정의한 이름과 동일해야 함
이미지 풀 인증 시크릿 spec: 
  imagePullSecrets:
  - name: {시크릿명}
이미지 다운로드를 위한 인증 시크릿 오브젝트를 지정함. 프라이빗 리파지토리의 이미지를 다운로드 하려면 인증 시크릿 오브젝트를 만들고 여기에 정의해야 함 



3) 컨테이너 명세
컨테이너 명세를 정의하는 방법은 워크로드 컨트롤러와 상관 없이 동일합니다.
정의할 수 있는 항목은 컨테이너의 이름, 이미지, 이미지 다운로드 정책, 포트, 환경변수, 리소스 제한, 볼륨 마운트 위치, 레디니스 프로브, 라이브니스 프로브입니다.
spec.template.spec.containers 항목 밑에 정의 합니다.
레디니스 프로브와 라이브니스 프로브는 ‘12. 헬스 체크를 위한 레디니스 프로브와 라이브니스 프로브’에서 설명하겠습니다.

항목 키 이름 설명
컨테이너 이름 name: {컨테이너 이름}  
컨테이너 이미지 image: {이미지 경로} {레지스트리}/{오거니제이션}/{리포지토리}:{태그}형식으로 지정
이미지 다운로드 정책 imagePullPolicy: {정책} - IfNotPresent: 파드를 배포하는 노드에 이미지가 없으면 이미지 레지스트리에서 다운로드. 생략 시 기본값
- Always: 항상 이미지 레지스트리에서 다운로드
컨테이너 포트 ports: 
- name: {포트이름}
  protocol: TCP
  containerPort: {포트번호}
  hostPort: {포트번호}
컨테이너 간 통신시 내부적으로 사용하는 포트임.
서비스 오브젝트의 targetPort와 이 컨테이너 포트는 아무 상관이 없음
서비스의 targetPort는 어플리케이션에서 수신하는 포트를 지정해야 함
hostPort는 파드가 배포되는 노드에 노출할 포트번호임. ‘{노드IP}:{hostPort}’를 호출하면 ‘containerPort’로 연결됨
환경변수: 직접 지정 env:
  - name: {환경변수명}
    value: {환경변수값}
  - …
컨테이너 안에 환경변수 생성함
name과 value로 여러개의 환경변수를 지정할 수 있음. 
주의할것은 값이 숫자일때는 큰 따옴표로 감싸야 함
- name: serivice_port
  value: “3001”
환경변수: 컨피그맵 envFrom:
- configMapRef:
    name: {ConfigMap 명}
컨피그맵에 정의한 데이터가 컨테이너 안에 환경변수 로 생성됨
환경변수: 시크릿 envFrom:
- secretRef:
    name: {Secret 명}
시크릿에 정의한 데이터가 컨테이너 안에 환경변수로 생성됨. 시크릿에는 base64로 인코딩한 값으로 등록되어 있지만 컨테이너에는 디코딩되어 만들어짐
리소스 제한 resources:
  requests:
    cpu: {숫자}m
    memory: {숫자}Gi/Mib
  limits:
    cpu: {숫자}m
    memory: {숫자}Gi/Mib
컨테이너가 사용할 수 있는 CPU와 메모리를 정의
requests는 최초 할당 리소스 양이고 limits는 최대 사용 가능 리소스 양임
CPU는 밀리세컨드(ms)단위로 지정하고 약 1000m이 1Core임
메모리는 기가바이트나 메비바이트로 지정할 수 있음

※ 리소스 제한을 안하면 노드의 모든 리소스를 사용하여 다른 파드에 영향을 미칠 수 있으므로 정의하는 것이 좋음
볼륨 마운트 위치 volumeMounts:
- name: {볼륨명}
  mountPath: {마운트 경로}
파드 외부의 스토리지 볼륨을 어느 경로에 마운트할 것인지 정의
파드명세에 반드시 지정한 ‘볼륨명'으로 퍼시스턴트 볼륨 클레임PVC이 정의되어 있어야 함
또는 워크로드 컨트롤러 명세에 PVC를 정의한 volumeClaimTemplates항목이 있어야 함



7.2 워크로드 컨트롤러 디플로이먼트Deployment 와 스테이트풀셋StatefulSet

먼저 워크로드 컨트롤러가 파드를 어떻게 통제하는지 실습하겠습니다.
두번째로 디플로이먼트를 스테이트풀셋으로 변경해서 배포했을때 파드이름이 어떻게 달라지는지 확인하고 정말 파드가 고유의 주소를 가지게 되는지 테스트 해 보겠습니다.
세번째 스테이트풀 파드들을 동시에 병행으로 생성/삭제/스케일링하는 방법을 알아 보겠습니다.
네번째 프라이빗 이미지 리포지토리에서 이미지 풀 시크릿을 이용하여 이미지를 다운로드하는 방법을 배워 보겠습니다.


1) 워크로드 컨트롤러의 파드 통제 실습
워크로드 컨트롤러는 파드를 스케일링할 수 있습니다. 또한 파드 수가 지정된 수와 다를때 자동으로 맞춰 줍니다.

‘member’의 파드수를 스케일링 해 보겠습니다.
문법: kubectl scale {워크로드 컨트롤러 리소스 종류} {워크로드 컨트롤러 오브젝트명} --replicas={파드수}
아래와 같이 ‘member’파드 수를 1개에서 3개로 늘려 보십시오.

[root@osboxes yaml]# kubens ott
Context "kubernetes-admin@kubernetes" modified.
Active namespace is "ott".
[root@osboxes yaml]# k get po | grep member
member-59d75b86ff-kpvvx      1/1     Running   0          6s
[root@osboxes yaml]# k scale deploy member --replicas=3
deployment.apps/member scaled

[root@osboxes yaml]# k get po | grep member
member-59d75b86ff-5fs9w      1/1     Running   0          48s
member-59d75b86ff-95s8x      1/1     Running   0          48s
member-59d75b86ff-kpvvx      1/1     Running   0          77s


파드 1개를 지워보겠습니다. 위에서 구한 ‘member’파드명을 복사해서 지워 보십시오.

[root@osboxes yaml]# k delete po member-59d75b86ff-5fs9w
pod "member-59d75b86ff-5fs9w" deleted


기존 3개에서 1개를 지웠으니 2개가 남아야겠지요 ? 과연 그럴까요 ?

[root@osboxes yaml]# k get po | grep member
member-59d75b86ff-445gc      1/1     Running   0          73s
member-59d75b86ff-95s8x      1/1     Running   0          3m38s
member-59d75b86ff-kpvvx      1/1     Running   0          4m7s


여전히 3개가 나오네요. 자세히 보면 파드명이 바뀐것을 볼 수 있습니다.
이렇게 워크로드 컨트롤러는 지정한 파드수가 항상 유지되는것을 보장 합니다.


2) 디플로이먼트와 스테이트풀셋의 차이
디플로이먼트를 스테이트풀셋으로 바꿔서 그 차이를 이해해 보겠습니다.
디플로이먼트 야믈을 스테이트풀셋 야믈로 바꾸는 건 매우 쉽습니다. ‘member’ 파드를 스테이트풀셋으로 바꿔 보겠습니다.

실습을 위해 배천 노드에 작업 디렉토리를 만들고 디플로먼트 ‘member’의 야믈을 다운로드 합니다.

[root@osboxes ~]# mkdir -p ~/k8s/yaml && cd ~/k8s/yaml
[root@osboxes yaml]# wget -O sts-member.yaml https://hiondal.github.io/k8s-yaml/3.4/deploy-member.yaml


sts-member.yaml의 내용을 수정합니다.

[root@osboxes yaml]# vim sts-member.yaml


변경할 내용은 딱 두가지 입니다.
❶ 리소스 종류를 ‘StatefulSet’으로 변경하고 ❷ ‘spec.serviceName’항목만 추가해 주면 됩니다.
❸ 파드 수는 테스트를 위해 3개로 늘려 주십시오.


기존 ‘member’파드를 모두 지웁니다. 파드 오브젝트들을 지워야 할까요 ? 아니면 디플로이먼트 오브젝트를 지워야 할까요 ? 파드를 지워 봤자 디플로이먼트가 다시 살려 놓으니까 당연히 디플로이먼트 오브젝트를 지워야 합니다.

[root@osboxes yaml]# k delete deploy member
deployment.apps "member" deleted
[root@osboxes yaml]# k get po | grep member

모든 ‘member’파드가 지워졌습니다. 당연히 디플로이먼트 오브젝트 ‘member’도 삭제 되었구요.

이제 위에서 만든 sts-member.yaml로 스테이트풀셋 오브젝트 ‘member’와 스테이트풀셋 파드 3개를 배포하겠습니다.

[root@osboxes yaml]# k apply -f sts-member.yaml
statefulset.apps/member created

[root@osboxes yaml]# k get po | grep member
member-0                     1/1     Running   0          2m30s
member-1                     1/1     Running   0          2m25s
member-2                     1/1     Running   0          2m20s


뭔가 디플로이먼트로 배포한 스테이트리스한 파드와 달라졌죠 ? 파드 이름이 달라졌습니다. 지정한 파드 이름에 일련번호가 붙어서 생성이 되었습니다.

정말 파드가 고유의 주소를 가지는지 테스트 해 보겠습니다.
‘nslookup’이라는 DNS에서 주소를 찾는 프로그램이 필요 합니다. 이 프로그램도 파드로 배포해서 테스트 하겠습니다.

[root@osboxes yaml]# k run curl -it --image curlimages/curl -- sh
If you don't see a command prompt, try pressing enter.
/ $

‘kubectl run ‘명령은 파드를 간편하게 실행해 주는 명령어입니다. ‘--image’옵션에 이미지 경로를 지정해 주면 그 이미지를 다운로드하여 실행까지 해 줍니다. ‘-it’ 옵션을 이용해서 파드 내에 진입하는 방법은 이미 배우셨고요.

‘curl’ 파드 안에서 ‘nslookup {주소}’형식으로 주소의 IP를 찾을 수 있습니다.
예를 들어 아래와 같이 ‘google.com’을 찾을 수가 있습니다.

/ $ nslookup google.com
Server:        10.96.0.10
Address:    10.96.0.10:53

Non-authoritative answer:
Name:    google.com
Address: 216.58.197.206

Non-authoritative answer:
Name:    google.com
Address: 2404:6800:4004:801::200e


그럼 각 파드의 IP를 nslookup으로 찾아 볼까요 ?
파드 주소의 형식은 ‘{파드명}.{서비스명}.{네임스페이스명}.svc.cluster.local’입니다. 좀 길지만 논리적으로 네임스페이스 안에 서비스가 있고 서비스가 파드를 연결해 준다는 걸 이해하면 기억하기 쉬울겁니다.
한번 ‘member-0’의 IP를 찾아 보겠습니다. 아래와 같이 잘 찾아 집니다. 다른 파드의 IP도 한번 찾아 보십시오.

/ $ nslookup member-0.member.ott.svc.cluster.local
Server:        10.96.0.10
Address:    10.96.0.10:53


Name:    member-0.member.ott.svc.cluster.local
Address: 192.168.235.174

<팁>
스테이트풀한 파드의 주소는 쿠버네티스 컴포넌트 중 CoreDNS에 등록됩니다.
</>

다 테스트 하셨으면 ‘exit’를 입력하여 파드를 빠져 나가십시오.

/ $ exit
Session ended, resume using 'kubectl attach curl -c curl -i -t' command when the pod is running
[root@osboxes yaml]# k get po
NAME                         READY   STATUS    RESTARTS      AGE
curl                         1/1     Running   1 (10s ago)   10m

<팁>
다시 curl 파드안으로 들어가서 nslookup을 테스트 하려면 ‘kubectl exec ‘명령을 이용하세요.
k exec -it curl -- sh
</>
디플로이먼트로 배포한 파드는 주소가 없을까요 ? 네 ! nslookup으로 찾아지지 않습니다.
‘sts-member.yaml’을 복사하여 디플로이먼트 야믈 파일을 만들어서 직접 한번 해 보시기 바랍니다.
리소스종류를 ‘Deployment’로 바꾸고 ‘spec.serviceName’만 지우고 배포하면 됩니다.

디플로이먼트를 스테이트풀셋으로 바꿀때는 리소스 종류와 ‘serviceName’만 추가해 주면 되고 스테이트풀셋으로 배포된 파드는 고유의 주소를 가지는걸 실습해 봤습니다.
스테이트풀셋의 더 중요한 목적은 각 파드가 고유의 데이터를 관리하는 것입니다.
이건 ‘11. 데이터 저장소 사용을 위한 PV/PVC’에서 MySQL을 스테이트풀셋으로 배포하면서 실습하겠습니다.

스테이트풀 파드의 이름에는 일련번호가 붙는 걸 바로 전에 확인했습니다.
그런데 스테이트풀 파드는 기본적으로 동시에 생성/삭제/스케일링 되지 않고 이 일련번호대로 처리됩니다.
즉 생성 시에는 member-0 → member-1 → member-2 순으로 생성됩니다.
삭제 시에는 member-2 → member-1 → member-0 순으로 삭제됩니다.
스케일링은 늘어날때는 생성시와 똑같고 줄어들때는 삭제시와 똑같이 역순으로 처리 됩니다.
데이터베이스를 스테이트풀 파드로 배포할 때는 이렇게 순서대로 처리하는게 좋을 겁니다.
하지만 고유의 파드 주소를 이용하기 위해서 스테이트풀셋으로 배포할 때는 괜히 시간만 걸립니다.
동시에 병행적으로 파드가 생성/삭제/스케일링 하게 하는 방법을 이어서 배워보도록 하겠습니다.

디플로먼트와 스테이트풀셋의 차이를 요약하면 다음과 같습니다.

항목 디플로이먼트 스테이트풀셋
목적 고유의 주소나 데이터 관리가 필요 없는 스테리스한 파드 배포 고유의 주소나 데이터 관리가 필요한 스테이트풀한 파드 배포
리소스 종류 Deployment StatefulSet
serviceName 요구 serviceName 정의 불가 serviceName 정의 필수
파드 생성/삭제/스케일링 병행 처리 순차 처리와 병행 처리 모두 가능
(podManagementPolicy로 설정)
파드 이름 {워크로드 컨트롤러 이름}-{랜덤문자} {워크로드 컨트롤러 이름}-{일련번호}
파드 주소 없음 {파드}.{서비스}.{네임스페이스}.svc.cluster.local로 CoreDNS에 등록됨
고유 데이터 관리 불가 각 파드별로 PVC 바인딩하여 가능
(volumeClaimTemplates에 정의하여 각 파드별 PVC를 자동 생성함)



3) 스테이트풀 파드 동시에 생성/삭제/스케일링 하기
스테이트풀한 파드가 동시에 병행으로 생성/삭제/스케일링 되게 하려면 파드 관리 정책podManagementPolicy을 변경해야 합니다.
sts-member.yaml을 열고 워크로드 컨트롤러 명세 부분에 ‘spec.podManagementPolicy: Parallel’을 추가 하십시오.

파드관리 정책의 기본값은 OrderedReady 입니다.

<팁>
파드 고유 주소가 필요하여 스테이트풀셋으로 파드를 배포하는 경우 파드 관리 정책은 ‘Parallel’로 바꾸는게 좋습니다.
그래야 생성/삭제/스케일링이 동시에 처리 되니까요.
</>

spec.podManagementPolicy가 없을때와 ‘Parallel’일때를 직접 비교해 보시기 바랍니다.

4) 이미지 다운로드 권한 통제
실무에서는 컨테이너 이미지를 아무나 다운로드 하지 못하도록 통제해야 합니다.
설사 기업 내부에서만 공유하는 컨테이너 이미지라도 민감한 업무를 취급하는 컨테이너 이미지는 권한자만 접근할 수 있도록 해야 합니다.
그렇게 하기 위해서는 이미지 리포지토리를 퍼블릭에서 프라이빗으로 변경하고 권한자를 리포지토리에 등록해야 합니다. 또한 워크로드 컨트롤러 정의 시 프라이빗 이미지 리포지토리를 접근할 수 있는 인증 토큰을 제공해야 합니다.

도커 허브에서 이미지 리포지토리 ‘member’를 프라이빗으로 변경하고 테스트 해 보겠습니다.
도커 허브를 로그인 하시고 이미지 리포지토리 ‘member’를 여신 후 ❶ ‘Settings’를 누르십시오.
❷ [Make Private]버튼을 눌러 리포지토리를 프라이빗으로 변경 합니다.
참고로 이 리포지토리를 접근할 수 있는 권한자는 ❸ ‘Collaborators’탭에서 지정할 수 있습니다.


프라이빗 이미지 리포지토리에서 이미지를 다운로드 받을 수 있는지 테스트 해 보겠습니다.
‘sts-member.yaml’파일을 열어 ‘imagePullPolicy’가 ‘Always’인지 체크 합니다.
왜냐하면 이 값이 ‘IfNotPresent’로 되어 있으면 이미 워커노드에 다운로드 받은 이미지로 배포하기 때문입니다.

기존의 ‘member’파드들을 지우고 다시 배포해 봅니다.
이젠 지울때 워크로드 컨트롤러 리소스를 ‘statefulset’으로 지정해야 합니다. 줄여서 ‘sts’를 쓰셔도 됩니다.

[root@osboxes yaml]# k delete sts member
statefulset.apps "member" deleted


파드가 잘 배포되었을까요 ? 아래와 같이 ‘ImagePullBackOff’라는 에러가 날겁니다.

[root@osboxes yaml]# k apply -f sts-member.yaml
statefulset.apps/member created

[root@osboxes yaml]# k get po
NAME                         READY   STATUS         RESTARTS      AGE
curl                         1/1     Running        1 (43m ago)   53m
member-0                     0/1     ErrImagePull   0             14s
member-1                     0/1     ErrImagePull   0             14s
member-2                     0/1     ErrImagePull   0             14s


이 문제를 이미지 풀 시크릿으로 해결해 보겠습니다.
도커 허브의 사용자명과 로그인 암호를 이용하여 시크릿 오브젝트를 만듭니다. 시크릿은 보안 환경변수의 역할도 하지만 이미지 레지스트리 자격 증명의 역할도 할 수 있습니다.
이미지 레지스트리 자격 증명을 위한 시크릿 오브젝트는 아래와 같이 생성합니다.
kubectl create secret docker-registry {시크릿명} --docker-server={이미지 레지스트리 주소} --docker-username={이미지 레지스트리 로그인 사용자명} --docker-password={이미지 레지스트리 로그인 사용자 암호} -n ${네임스페이스명}

아래와 같이 이미지 풀 시크릿 ‘dockerhub’를 만듭니다. 사용자명과 암호는 본인걸로 바꾸셔야 합니다.

[root@osboxes yaml]# k create secret docker-registry dockerhub --docker-server=docker.io --docker-username=hiondal --docker-password=11111
secret/dockerhub created


‘sts-member.yaml’을 열고 파드 명세 부분에 ‘spec.template.spec.imagePullSecrets’을 추가 하십시오.


기존 파드들을 지우고 다시 배포해 봅니다.

[root@osboxes yaml]# k delete sts member
statefulset.apps "member" deleted

[root@osboxes yaml]# k apply -f sts-member.yaml
statefulset.apps/member created

파드 리스트를 확인해 보면 이제 잘 배포된것을 확인할 수 있습니다.

[root@osboxes yaml]# k get po
NAME                         READY   STATUS    RESTARTS      AGE
curl                         1/1     Running   1 (58m ago)   68m
member-0                     1/1     Running   0             3m43s
member-1                     1/1     Running   0             3m43s
member-2                     1/1     Running   0             3m43s


또 다른 방법이 있습니다.
워크로드 컨트롤러는 ‘serviceAccount’에 지정한 ‘sa-ott’라는 서비스 어카운트의 권한으로 수행이 됩니다.
이 서비스 어카운트에 ‘imagePullSecrets’을 지정해줘도 됩니다.
이렇게 하면 워크로드 컨트롤러에 ‘serviceAccount’만 지정해 주면 각 워크로드 컨트롤러에는 ‘imagePullSecrets’을 지정하지 않아도 됩니다.

아래와 같이 서비스 어카운트 ‘sa-ott’에 ‘imagePullSecrets’을 정의하십시오.

[root@osboxes ~]# k edit sa sa-ott
apiVersion: v1
kind: ServiceAccount
...
secrets:
- name: sa-ott-token-rlbgj
imagePullSecrets:
- name: dockerhub


‘sts-member.yaml’을 열고 ‘imagePullSecrets’항목을 지우십시오.
기존 파드를 지우고 다시 ‘sts-member.yaml’파일을 이용하여 다시 배포 하십시오.
‘member’파드가 잘 실행되는 것을 확인할 수 있습니다.



<팁>
이미지 풀 시크릿이 정의되어 있어도 퍼블릿 이미지 레포지토리의 이미지를 다운로드 받는데는 아무 문제가 없습니다.
따라서 항상 이미지 풀 시크릿을 정의하는 습관을 들이는것도 좋습니다.
</>
<팁>
도커 허브가 아닌 다른 이미지 레지스트리를 사용해도 이미지 풀 시크릿을 만드는 방법은 동일 합니다.
</>

7.3 파드 배포 대상 노드 정의

파드를 배포할 노드를 정의하는 방법의 종류와 그 개념을 먼저 이해하겠습니다.
그리고 배포 대상 노드를 정의할 때 사용하는 노드의 레이블 추가/수정/삭제 방법을 배우고 각 방법별 정의 방법과 실습을 해 보겠습니다.


1) 파드 배포 대상 노드 정의 방법 이해
파드가 배포될 노드를 결정하는 스케줄링은 쿠버네티스 아키텍처에서 어느 컴포넌트가 하는걸까요 ?
답은 컨트롤 플레인의 스케줄러Scheduler입니다.
그런데 스케줄러는 워크로드 컨트롤러가 노드 스케줄링에 대한 조건을 주지 않으면 리소스 여유가 있는 노드에 파드를 할당합니다. 잘못하면 한 노드에 파드가 몰릴 수가 있고 높은 컴퓨팅 처리 능력이 필요한 파드가 저사양의 노드에 배포될 수도 있습니다.
이 문제를 해결하기 위해 워크로드 컨트롤러는 파드들을 어떤 노드에 스케줄링 해 달라고 지정할 수 있습니다.
노드 스케줄링 조건을 지정하는 방법에는 4가지가 있습니다. 노드네임nodeName, 노드 셀렉터nodeSelector, 노드 어피니티nodeAffity, 파드 어피니티podAffinity가 그것입니다.

파드를 노드에 스케줄링하는 4가지 방법을 예를 들어 설명하겠습니다.

당신이 교보문고와 같은 대형 서점의 진열 담당자라고 생각해 보십시오.
새 국내 소설 책이 들어왔고 그걸 어디에 진열하라고 직원에게 지시하려고 합니다.
진열공간을 지시하는 방법에는 아래와 같이 4가지가 있을겁니다.
- 방법 1: 정확한 진열 공간을 지시
예) 문학구역의 한국소설 2번공간에 배치해라.
- 방법 2) 진열 공간 속성으로 지시
예) 분류가 한국소설인 공간에 배치해라. 한국소설의 1번이나 2번공간에 배치됨
- 방법 3) 진열 공간 속성들을 조합한 조건으로 지시
예) 분류가 한국소설이거나 베스트셀러인 공간에 배치해라.
- 방법 4) 책의 속성들을 조합한 조건으로 지시
예) 분류가 한국소설이고 키워드에 로맨스가 포함된 책들과 같은 공간에 배치 해라
단, 인기구분이 베스트셀러라고 되어 있는 책들과는 다른 공간에 배치해라

첫번째 방법이 노드네임, 두번째 방법이 노드 셀렉터, 세번째 방법이 노드 어피니티, 네번째 방법이 파드 어피니티입니다.
네번째 방법의 예외 조건으로 지정하는 방법은 파드 안티 어피니티입니다.

각 스케줄링 방법을 기술적인 용어로 다시 설명해 보겠습니다. 위 예에서 진열공간이 노드이고 책이 파드라고 생각하시고 이해해 주십시오.


파드 스케줄링의 개념은 이해 했으니 실제 어떻게 정의하는지 배워 보겠습니다.

2) 노드 레이블 추가/수정/삭제
노드네임을 제외하고 나머지 방법들은 모두 노드의 레이블을 조건에 이용합니다.
그래서 노드 레이블을 추가/수정/삭제하는 방법부터 알아야 합니다.
노드명과 레이블 확인
kubectl get node --show-labels

노드 레이블 추가
kubectl label node {노드명} {레이블 키}={레이블 값}

노드 레이블 수정
kubectl label node {노드명} {레이블 키}={레이블 값} --overwrite=true

노드 레이블 삭제
kubectl label node {노드명} {레이블 키}-

여러분 클러스터의 노드명과 레이블을 확인해 보십시오.
아래 예시와 같이 기본으로도 많은 레이블이 보일겁니다. 노드의 OS(kubernetes.io/os), CPU 아키텍처(kubernetes.io/arch), 노드 역할(node-role.kubernetes.io/control-plane 또는 worker), 노드 호스트명(kubernetes.io/hostname) 등이 있습니다.
클러스터가 구성되는 환경에 따라 구역zone이나 지역region이 노드 레이블로 부여될 수 있습니다.

[root@osboxes ~]# k get node --show-labels
NAME      STATUS   ROLES                  AGE   VERSION   LABELS
master    Ready    control-plane,master   12d   v1.23.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
worker1   Ready    worker                 12d   v1.23.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker1,kubernetes.io/os=linux,node-role.kubernetes.io/worker=worker


노드 레이블을 추가해 보겠습니다. 워커노드에 ‘kubernetes.io/zone=public-a’라고 추가해 보십시오.

[root@osboxes ~]# k label node worker1 kubernetes.io/zone=public-a
node/worker1 labeled
[root@osboxes ~]# k get node worker1 --show-labels
NAME      STATUS   ROLES    AGE   VERSION   LABELS
worker1   Ready    worker   12d   v1.23.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker1,kubernetes.io/os=linux,kubernetes.io/zone=public-a,node-role.kubernetes.io/worker=worker


레이블 ‘beta.kubernetes.io/arch’를 지워 보겠습니다.

[root@osboxes ~]# k label node worker1 beta.kubernetes.io/arch-
node/worker1 labeled
[root@osboxes ~]# k get node worker1 --show-labels
NAME      STATUS   ROLES    AGE   VERSION   LABELS
worker1   Ready    worker   12d   v1.23.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker1,kubernetes.io/os=linux,kubernetes.io/zone=public-a,node-role.kubernetes.io/worker=worker



3) 노드네임
노드네임은 제일 단순합니다. 워크로드 컨트롤러의 파드 명세 부분에 ‘nodeName: {노드명}’으로 정의 하면 됩니다.
노드네임을 엉뚱하게 줘서 파드가 배포 안되는 걸 해보겠습니다.
배천 노드의 ‘~/k8s/yaml’로 이동하고 ‘sts-member.yaml’파일을 여십시오.
아래와 같이 파드 명세 부분인 ‘spec.template.spec’하위에 ‘nodeName: worker2’를 추가 하십시오.

배포를 다시 하고 그 결과를 확인 하십시오. 아래와 같이 ‘worker2’라는 노드가 없으므로 파드가 배포되지 않습니다.

[root@osboxes yaml]# k delete sts member
statefulset.apps "member" deleted
[root@osboxes yaml]# k get po
NAME                         READY   STATUS    RESTARTS      AGE
curl                         1/1     Running   1 (11h ago)   12h
recommend-67445ccb4f-qw47c   1/1     Running   0             3d4h
[root@osboxes yaml]# k apply -f sts-member.yaml
statefulset.apps/member created

[root@osboxes yaml]# k get po
NAME                         READY   STATUS    RESTARTS      AGE
curl                         1/1     Running   1 (11h ago)   12h
member-0                     0/1     Pending   0             16s
member-1                     0/1     Pending   0             16s
member-2                     0/1     Pending   0             16s


<팁>
터미널을 하나 더 띄우고 파드가 생성되고 삭제되는 것을 ‘watch k get po‘ 명령으로 보시면 편합니다.
</>

4) 노드 셀렉터
지정한 노드 레이블과 일치하는 노드에 배포하는 것입니다.
위에서 워커노드에 ‘kubernetes.io/zone=public-a’이라는 레이블을 추가했으니 이 레이블을 이용한 노드 셀렉터를 지정해 보겠습니다.
‘sts-member.yaml’을 다시 엽니다. 역시 파드 명세 부분에 아래와 같이 노드 셀렉터를 추가 합니다.
기존에 nodeName은 삭제하거나 리마크 하십시오.

기존 파드들을 지우고 재 배포하면 잘 실행되는것을 확인할 수 있습니다.

5) 노드 어피니티
진열 공간 분류가 한국소설이거나 베스트셀러인 공간에 책을 배치하듯이 노드 어피니티는 노드의 레이블들을 조합한 조건을 지정하여 노드를 할당할 수 있습니다.
Affinity라는 단어의 뜻이 ‘친밀감'이니 친한 노드로 할당해 달라는 요청이라 이해할 수 있습니다.

노드 어피니티의 분류는 크게 필수 조건과 선호 조건으로 나뉩니다.
필수 조건과 선호 조건을 노드 레이블을 조합하여 지정 합니다.
RequiredDuringExecution 과 IgnoredDuringExecution은 노드 레이블이 바뀌었을때 실행중인 파드의 재배치 여부를 지정하는 것입니다.


<팁>
RequiredDuringExecution 옵션은 아직 지원하지 않습니다. 향후에 지원하기 위해 미리 만들어 놓은 겁니다.
</>

실제 예를 갖고 어떻게 정의하는지 이해해 봅시다.
여러분이 만든 member파드는 반드시 worker1 또는 worker2에 배포되어야 합니다.
둘중에 가급적 컴퓨팅 파워가 높은 노드에 배치하고 싶습니다.
이걸 노드 어피니티로 정의 하면 아래와 같습니다.


좀 복잡해 보이지만 설명과 같이 보면 그렇게 이해하기 어렵지 않을 겁니다.
자 그럼 실제로 member 파드에 적용해 봅시다.
먼저 노드 어피니티가 적용된 야믈파일을 다운로드 합니다.

[root@osboxes yaml]# cd ~/k8s/yaml
[root@osboxes yaml]# wget https://hiondal.github.io/k8s-yaml/3.7/deploy-member-nodeaffinity.yaml


현재 노드에는 ‘compute.power’라는 레이블이 없습니다. 위 정의라면 파드가 배포될 수가 없겠지요.
진짜 배포가 안되는 지 테스트해 봅시다.
기존 스테이트풀셋으로 배포된 ‘member’파드를 지우고 배포 합니다.
아래와 같이 ‘Pending’상태가 되고 배포가 안됩니다.

[root@osboxes yaml]# k delete sts member
[root@osboxes yaml]# k apply -f deploy-member-nodeaffinity.yaml

[root@osboxes yaml]# k get po
NAME                         READY   STATUS    RESTARTS      AGE
curl                         1/1     Running   1 (14h ago)   14h
member-86657f5c84-2pnx5      0/1     Pending   0             16s


제대로 동작하려면 ‘worker1’노드에 ‘compute.power=high’라고 레이블을 추가해야 합니다.

[root@osboxes yaml]# k label node worker1 compute.power=high
node/worker1 labeled


위 레이블을 추가하자 마자 파드가 배포되는 것을 볼 수 있습니다.

<팁>
어피니티에서 사용할 수 있는 Operator의 종류에는 In, NotIn, Exists, DoesNotExist, Lt, Gt가 있습니다.
NotIn이나 DoesNotExist를 이용하여 배포하지 말아야할 노드 조건을 지정할 수도 있습니다.
</>
6) 파드 어피니티
이제 파드 스케줄링의 제일 어렵지만 실무에 가장 유용한 파드 어피니티를 배워볼 차례입니다.
파드 어피니티는 노드 레이블이 아닌 파드 레이블들을 조합한 조건으로 노드를 할당하는 방법입니다.
Affinity라는 단어의 뜻이 ‘친밀감'이니 친한 파드가 있는 노드에 할당해 달라는 요청이라 이해할 수 있습니다.

파드 어피니티가 왜 유용한지 예를 들어 설명해 보겠습니다.

여러분의 클러스터에 워커노드가 3개 있다고 가정해 봅시다.
위 노드 어피니티로 ‘member’파드를 2개 배포하면 어떻게 될까요 ?
다행스럽게 ‘worker1’과 ‘worker2’노드에 1개씩 배포될 수도 있겠지만 ‘worker1’이나 ‘worker2’에 몰려서 한 노드에 2개가 배포될 수도 있습니다.
파드 어피니티가 이런 상황을 방지할 수 있습니다. 어쨋튼 파드 어피니티를 이용해서 ‘worker1’과 ‘worker2’에 ‘member’파드를 하나씩 배포했다고 합시다.
이번에는 ‘recommend’ 파드를 2개 배포하는데 한 노드에 몰리지 않게 하면서 ‘member’파드와 동일한 노드에 1개씩 실행해야 한다고 가정합시다.
이걸 노드 어피니티로는 할 방법은 없습니다.

한 노드에 파드가 몰려서 배포되지 않아야 하는건 당연하고 관련 있는 파드는 같은 노드에 배포해야 하는 경우도 종종 있습니다.
대표적인 경우가 아래와 같이 어플리케이션과 클러스터링된 레디스Redis 데이터베이스를 모두 파드로 배포하는 경우입니다. 이러한 아키텍처에서 Redis 파드는 반드시 ‘member’ 파드가 설치된 노드에 배포 되어야 합니다.


왜 필요한지는 알았으니까 이제 어떻게 파드 어피니트를 정의해야 하는지 학습해 보겠습니다.

파드 어피니티의 분류도 노드 어피니티와 동일하게 필수 조건이냐 선호 조건이냐로 나뉩니다.
필수 조건과 선호 조건을 파드 레이블을 조합하여 지정 합니다.
RequiredDuringExecution 과 IgnoredDuringExecution은 파드 레이블이 바뀐 경우 이미 실행되고 있는 파드의 재배치 여부를 지정하는 것입니다.
‘topologyKey’는 노드의 레이블 키 이름을 지정 합니다.
지정된 조건을 만족하는 파드가 어떤 노드에 있는지를 찾을 때 이 레이블 키의 값을 이용하겠다는 것입니다.
예를 들어 topologyKey가 ‘kubernetes.io/zone’이고 파드 레이블 조건을 만족하는 파드가 ‘member’파드이며 ‘member’ 파드는 ‘worker1’에 있다고 가정 합시다.
그럼 ‘worker1’의 ‘kubernetes.io/zone’의 값은 ‘public-a’이므로 새로 배포되는 파드의 노드는 ‘kubernetes.io/zone=public-a’인 노드가 되는 것입니다.

파드 어피니티가 왜 필요한지 설명할때의 예를 갖고 어떻게 정의하는지 이해해 봅시다.
요구 사항은 아래와 같습니다.
- ‘member’파드 2개를 각 워커 노드에 1개씩만 배포해야 한다.
- ‘recommend’파드 2개도 각 워커 노드에 1개씩만 배포해야 한다.
- ‘recommend’파드는 반드시 ‘member’파드와 동일한 노드에 배포해야 한다.
- ‘recommend’파드는 가급적 ‘member’파드가 배포된 노드의 ‘compute.power’값과 동일한 노드에 배포하고 싶다.

이걸 파드 어피니티로 정의 하면 아래와 같습니다.

먼저 ‘member’파드의 파드 어피니티 정의 입니다.
‘member’파드 2개가 어떻게 노드가 결정되는지 생각해 봅시다.
첫번째 파드는 ❶ 번 조건에 해당하는 파드가 없으므로 파드 어피니티와 상관 없이 스케줄러가 할당한 노드에 배포 됩니다. 즉 ‘worker1’에 배포 됩니다.
두번째 파드는 ❶ 번 조건에 해당하는 파드를 1개 찾을테고 ❷ 그 파드가 속한 노드의 ‘kubernetes.io/hostname’의 값인 ‘worker1’ 을 읽습니다. ❸ 파드 안티 오피니티이므로 ‘worker1’에는 할당 하지 않습니다. 그 외의 다른 워커노드로 배정됩니다. 우리는 워커노드가 1개밖에 없으므로 두번째 파드는 배포되지 못합니다.


실제로 위 파드 어피니티가 정의된 디플로이먼트 ‘member’를 배포해서 그 결과를 확인해 보겠습니다.
배천 노드의 ‘~/k8s/yaml’로 이동하여 아래와 같이 디플로이먼트 야믈 파일을 다운로드 하십시오.

기존에 배포된 ‘member’파드와 ‘recommend’파드를 삭제합니다. 아래와 같이 한꺼번에 삭제할 수 있습니다.

[root@osboxes yaml]# k delete deploy member recommend




위 야믈 파일을 이용하여 ‘member’파드를 다시 배포 합니다. 예상대로 1개는 배포가 되지 못합니다.

[root@osboxes yaml]# k apply -f deploy-member-podaffinity.yaml
deployment.apps/member created
[root@osboxes yaml]# k get po
NAME                     READY   STATUS    RESTARTS      AGE
curl                     1/1     Running   1 (17h ago)   17h
member-8dfbb76f4-ctqk5   1/1     Running   0             15s
member-8dfbb76f4-xx24x   0/1     Pending   0             15s




다음으로 ‘recommend’파드의 파드 어피니티 정의를 봅시다.
이해를 돕기 위해 워커노드가 3개이고 ‘member’파드는 ‘worker1’과 ‘worker3’에 배포되어 있다고 가정 합시다.
또한 ‘worker1’의 ‘compute.power=high’이고 나머지 노드들은 ‘compute.power=medium’이라고 가정 합시다.

이 상황에서 ‘recommend’파드 2개의 노드는 아래와 같이 결정 됩니다.
첫번째 파드:
❶, ❷, ❸번 과정을 통해 ‘member’파드가 배포된 ‘worker1’과 ‘worker3’가 후보 노드가 됩니다.
❹ 번 조건에 해당하는 노드는 아직 없으므로 여전히 ‘worker1’과 ‘worker3’가 계속 후보 노드가 됩니다.
❺ 번에서 ‘member’파드가 배포된 ‘worker1’과 ‘worker3’중 ‘compute.power=high’인 노드는 ‘worker1’이므로 첫번째 ‘recommend’파드는 ‘worker1’에 배정 됩니다.

두번째 파드:
❶, ❷, ❸번 과정을 통해 ‘member’파드가 배포된 ‘worker1’과 ‘worker3’가 후보 노드가 됩니다.
❹ 번 조건에 해당하는 노드는 ‘worker1’이므로 ‘worker3’가 후보 노드로 남습니다.
❺ 번에서 ‘member’파드가 배포된 ‘worker1’과 ‘worker3’중 ‘compute.power=high’인 노드는 ‘worker1’입니다.
❺ 번 조건은 선호 조건이므로 해당되는 노드가 없다고 배포가 안되는건 아닙니다. 따라서 후보 노드로 남은 ‘worker3’가 두번째 ‘recommend’파드의 최종 노드로 결정 됩니다.

실제로 위 파드 어피니티가 정의된 디플로이먼트 ‘recommend’를 배포해서 그 결과를 확인해 보겠습니다.
배천 노드의 ‘~/k8s/yaml’로 이동하여 아래와 같이 디플로이먼트 야믈 파일을 다운로드 하십시오.


위 야믈 파일을 이용하여 ‘recommend’파드를 다시 배포 합니다.
‘member’파드와 동일한 노드에 배포되었고 1개만 배포된 것을 확인할 수 있습니다.

[root@osboxes yaml]# k get po -o wide
NAME                       …  STATUS  …   NODE
member-8dfbb76f4-ctqk5     …  Running …   worker1
member-8dfbb76f4-xx24x     …  Pending …   <none>
recommend-cd5454dc4-dnljb  …  Pending …   <none>
recommend-cd5454dc4-khkm5  …  Running …   worker1

<팁>
‘-o wide’ 옵션을 추가하면 오브젝트의 더 많은 정보를 볼 수 있습니다.
</>



최대한 쉽게 설명하려 했지만 워낙 내용 자체가 복잡하여 어려우실 수 있습니다.
잘 이해가 되었다면 다행이고요. 어쨌든 어려운 내용 공부하시느라 고생 하셨습니다.
이제 워크로드 컨트롤러의 나머지 리소스들 데몬셋, 잡, 크론잡을 배워 보겠습니다.
이 리소스들은 사용할 경우가 그리 많지 않기 때문에 개념과 예제 위주로만 설명 하겠습니다.

이후 과정을 진행하기 위해 비정상적인 파드를 없앱니다.
아래와 같이 ‘kubectl scale’ 명령으로 파드 수를 1개로 줄입니다.

[root@osboxes yaml]# k scale deploy member recommend --replicas=1
deployment.apps/member scaled
deployment.apps/recommend scaled



7.4 워크로드 컨트롤러 데몬셋DamonSet, 잡Job, 크론잡CronJob

데몬 파드 배포를 위한 데몬셋DaemonSet, 일회성 수행 파드 배포를 위한 잡Job, 주기적 수행 파드 배포를 위한 크론잡에 대해서 배우도록 하겠습니다.

1) 데몬셋DaemonSet
데몬셋은 백그라운 프로세스인 데몬 프로그램으로 파드를 배포할 때 사용하는 리소스입니다.
데몬 프로그램의 특성은 사용자와의 직접 인터페이스 없이 백그라운드에서 서비스 요청을 처리한다는 것입니다.
쿠버네티스에서 데몬 프로그램이 사용되는 대표적인 경우는 로그 수집입니다.

데몬셋 야믈 구조는 디플로이먼트와 완전히 똑같고 리소스 종류가 ‘DaemonSet’이고 파드수를 지정하는 ‘spec.replicas’가 없다는 것만 다릅니다.
파드수를 지정 못하는 이유는 데몬 파드는 노드 당 무조건 1개씩 실행되기 때문입니다.
물론 파드 스케줄러로 배포할 노드를 제한할 수는 있습니다.

EFK스택에서 로그를 수집하는 플루언트디Fluentd를 데몬셋으로 배포해 보겠습니다.
배천 노드의 ‘~/k8s/yaml’로 이동하여 아래와 같이 예제 야믈 파일을 다운로드 하십시오.

[root@osboxes install]# cd ~/k8s/yaml
[root@osboxes yaml]# wget https://hiondal.github.io/k8s-yaml/3.7/ds-fluentd.yaml


파드 리스트를 확인해 보면 컨트롤 플레인과 워커노드에 1개씩 배포된 것을 볼 수 있습니다.

[root@osboxes yaml]# k get po -o custom-columns=Name:.metadata.name,Node:.spec.nodeName
Name                        Node
curl                        worker1
fluentd-drb56               master
fluentd-vthjd               worker1

<팁>
‘-o custom-columns={타이틀}:{값이 있는 항목 경로}’옵션을 이용하여 필요한 컬럼만 볼 수 있습니다.
</>

야믈 내용을 보면 디플로이먼트와 거의 유사한 구조인것을 볼 수 있습니다.

‘tolerations’라는 항목은 처음 보실텐데 파드가 배포 안되게 설정한 노드에도 파드를 배포하게 하는 방법이라는 정도만 이해하시면 되겠습니다.


2) 잡Job
잡은 어떤 처리를 하고 종료가 되는 파드를 배포할 때 사용하는 워크로드 컨트롤러입니다.

❶ 잡과 크론잡은 API 버전으로 ‘batch/v1’을 사용합니다.
❷ 리소스 종류는 ‘Job’입니다.

위 두가지야 당연한 차이이고 잡의 가장 큰 차이라면 ❸번 completions와 ❹번 parallelism입니다.
❸ completions는 지정된 작업을 총 몇번 할지를 의미 합니다. 지정된 작업은 ❻ command 에서 지정합니다.
아래 예에서는 파이값을 2000자리까지 계산하는 작업을 5번 하겠다는 의미가 됩니다.
결국 생성되는 파드 수는 5개가 됩니다.
❹ parallelism은 동시에 수행할 작업수를 지정하는 겁니다. 한번에 2개씩 하겠다고 했으니 처음에 파드가 2개 생성되고 모두 완료되면 또 2개가 생성될 겁니다. 마지막으로 파드 1개가 생성되어 총 5개의 파드가 실행됩니다.
❼ restartPolicy는 다른 워크로드 컨트롤러에도 지정할 수 있는 값입니다. 이 예에서는 파드 시작 시 실패 해도 재시작 안하겠다는 의미 입니다.
- Always(기본값): 파드 실행 결과의 성공/실패 여부와 상관 없이 항상 재시작
- OnFailure: 파드 실행이 실패 했을때만 재시작
- Never: 파드 실행이 실패해도 재시작 안함
※ Always로 되어 있어도 파드 실행이 성공한 경우는 재시작 안합니다. 단, 성공적으로 실행 후 어떠한 이유로 인해 정상적인 실행이 되지 못하는 경우 재시작 하겠다는 의미입니다.
❺ ttlSecondsAfterFinished는 각 파드의 작업이 완료된 후 파드를 얼마 후에 정리할 지를 정의합니다.
각 파드는 작업이 완료되면 ‘Running’에서 ‘Completed’로 상태가 바뀝니다. 하지만 자동으로 삭제되지는 않습니다.
이 옵션을 지정하여 불필요한 파드가 남지 않도록 할 수 있습니다.


자 그럼 실제 잡 파드를 실행해 보겠습니다.
배천 노드의 ‘~/k8s/yaml’로 이동하여 아래와 같이 예제 야믈 파일을 다운로드 하십시오.

[root@osboxes install]# cd ~/k8s/yaml
[root@osboxes yaml]# wget https://hiondal.github.io/k8s-yaml/3.7/job-pi.yaml


파드가 순차적으로 생성되는걸 보기 위해 새로운 터미널을 띄우고 파드 리스트를 모니터링 합니다.

[root@osboxes ~]# watch k get po


‘job-pi.yaml’ 파일로 잡 파드를 배포합니다.
배포 후 모니터링하는 탭에서 파드가 어떻게 생성되는지 보십시오.
처음에는 parallelism에 지정한대로 2개가 동시에 생성됩니다.

NAME                        READY   STATUS              RESTARTS   AGE
...
pi-m8frv                    0/1     ContainerCreating   0         6s
pi-pgwjg                    0/1     ContainerCreating   0          6s


두개의 파드가 수행을 종료하면 또 2개의 파드가 생성됩니다.

NAME                        READY   STATUS              RESTARTS   AGE
...
pi-bdnqt                    1/1     Running             0          13s
pi-cs7qv                    0/1     ContainerCreating   0          5s
pi-m8frv                    0/1     Completed           0         30s
pi-pgwjg                    0/1     Completed           0          30s


마지막으로 1개의 파드가 생성됩니다.

NAME                        READY   STATUS              RESTARTS   AGE
...

pi-bdnqt                    0/1     Completed   0          27s
pi-cs7qv                    0/1     Completed   0          19s
pi-f4phw                    1/1     Running     0          12s
pi-m8frv                    0/1     Completed   0          44s
pi-pgwjg                    0/1     Completed   0          44s




마지막 파드가 수행을 완료하면 더 이상 파드가 생성되지 않습니다.
최종적으로 completions에서 지정한대로 5개의 파드가 생성되었습니다.
다시 말해 지정된 작업을 5번 하고 끝난 겁니다.

NAME                        READY   STATUS              RESTARTS   AGE
...

pi-bdnqt                    0/1     Completed   0          40s
pi-cs7qv                    0/1     Completed   0          32s
pi-f4phw                    0/1     Completed   0          25s
pi-m8frv                    0/1     Completed   0          57s
pi-pgwjg                    0/1     Completed   0          57s




‘ttlSecondsAfterFinished’에 지정한대로 30초 후에 자동으로 파드가 사라지는지 기다려 보십시오.
약 30초 후 5개의 파드가 자동으로 사라지는 걸 확인할 수 있을겁니다.

NAME                        READY   STATUS    RESTARTS   AGE
curl                        1/1     Running   0      15m
fluentd-drb56               1/1     Running   0          4h54m
fluentd-vthjd               1/1     Running   0          4h54m
member-8dfbb76f4-ctqk5      1/1     Running   0         12h
recommend-cd5454dc4-khkm5   1/1     Running   0          12h



3) 크론잡CronJob
크론잡은 주기적인 작업을 하는 파드를 배포할 때 사용하는 워크로드 컨트롤러입니다.

❶ 잡과 크론잡은 API 버전으로 ‘batch/v1’을 사용합니다.
❷ 리소스 종류는 ‘CronJob’입니다.

잡 오브젝트와 마찬가지로 위 두가지는 당연한 차이이고 크론잡의 가장 큰 차이라면 ❸번 schedule과 ❹번 jobTemplate입니다.
❸ schedule는 지정된 작업의 수행 주기를 정의하는 것입니다. 정의 하는 형식은 조금 이따 설명하겠습니다.
❹ jobTemplate은 수행할 잡의 명세를 정의 하는 부분입니다. 잡 오브젝트를 정의할 때와 동일합니다.
크론잡은 크론잡 파드가 생성되는 것이 아니라 jobTemplate에 정의한 대로 잡 파드를 생성하는 방식으로 동작 합니다.

❸ schedule을 정의하는 문법은 리눅스의 크론탭Crontab과 동일 합니다.
스페이스로 구분된 5가지의 값으로 정의 하며 ‘{분} {시} {일} {월} {요일}’을 의미 합니다.
요일은 일요일인 0부터 토요일인 6까지 지정합니다. 일부 시스템에서는 7을 일요일로 사용할 수도 있습니다.

특정 시간을 지정하지 않을 때는 ‘*’로 표시합니다. 예를 들어 매주 일요일만 수행한다면 ‘* * * * 0‘과 같이 ’{요일}’부분 외에는 별표로 채우면 됩니다.
특정 시간을 지정하는 방법에는 아래와 같이 단일값, 복수값, 반복값, 범위값이 있습니다.
- 단일값: 예) 매일 13시에 수행 → * 13 * * *
- 복수값: 예) 매일 1시, 3시, 5시에 수행 → * 1,3,5 * * *
- 반복값: 예) 매일 2시간 간격으로 수행 → * */2 * * *
- 범위값: 예) 매일 0시에서 5시까지만 수행 → * 0-5 * * *

자 그럼 실제 크론잡을 실습해 보겠습니다.
배천 노드의 ‘~/k8s/yaml’ㅏ 로 이동하여 아래와 같이 예제 야믈 파일을 다운로드 하십시오.

[root@osboxes install]# cd ~/k8s/yaml
[root@osboxes yaml]# wget https://hiondal.github.io/k8s-yaml/3.7/cronjob-pi.yaml


파드가 순차적으로 생성되는걸 보기 위해 새로운 터미널을 띄우고 파드 리스트를 모니터링 합니다.

[root@osboxes ~]# watch k get po


‘cronjob-pi.yaml’ 파일로 크론잡을 배포합니다.
배포 후 모니터링하는 탭에서 파드가 어떻게 생성되는지 보십시오.
이 크론잡은 2분 마다 파이를 계산하는 작업을 수행 합니다.
jobTemplate에 정의한대로 2분 마다 잡 파드 2개가 순서대로 생성되면서 작업을 수행 합니다.

배포 후 잠시 기다리면 아래와 같이 잡 파드가 1개 생깁니다.

NAME                        READY   STATUS              RESTARTS   AGE
...
pi-27367876-zqkxm           0/1     ContainerCreating   0         5s


그 파드가 수행을 완료하면 2번째 파드가 실행 됩니다.

NAME                        READY   STATUS              RESTARTS   AGE
...
pi-27367876-vxcmx           1/1     Running     0          10s
pi-27367876-zqkxm           0/1     Completed   0      25


2개 파드가 모두 작업을 완료하면 약 30초 후에 파드가 자동으로 사라집니다.

확인을 다 하셨으면 불필요하게 주기적으로 파드가 생기지 않도록 크론잡을 삭제하여 주십시오.

[root@osboxes yaml]# k get cronjob
NAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
pi     */2 * * * *   False     0        74s             62m
[root@osboxes yaml]# k delete cronjob pi
cronjob.batch "pi" deleted




이상으로 워크로드 컨트롤러 디플로이먼트, 스테이트풀셋, 데몬셋, 잡, 크론잡에 대한 설명을 모두 마쳤습니다.

이후에 설명할 주제들은 지금까지 과정 동안 이미 어느정도는 맛을 본것들이라 조금 더 수월할것입니다.
사실 지금까지 배운것들만 잘 이해해도 개발, 빌드, 배포는 큰 문제 없이 하실 수 있을 겁니다.
그래도 이왕 쿠버네티스를 시작했으니 한발 더 들어가는것도 나쁘지 않습니다.

 

쿠버네티스 쉽게 이해하기 시리즈 목차

[쿠버네티스 쉽게 이해하기 1] 쿠버네티스 설치하기
[쿠버네티스 쉽게 이해하기 2] 쿠버네티스 아키텍처
[쿠버네티스 쉽게 이해하기 3] 한장으로 이해하는 쿠버네티스 리소스
[쿠버네티스 쉽게 이해하기 4] 쿠버네티스 개발에서 배포까지 실습
[쿠버네티스 쉽게 이해하기 5] 쿠버네티스 오브젝트 정의 파일 쉽게 만들기
[쿠버네티스 쉽게 이해하기 6] 꼭 알아야 할 쿠버네티스 주요 명령어
[쿠버네티스 쉽게 이해하기 7] 파드 실행 및 통제를 위한 워크로드 컨트롤러
[쿠버네티스 쉽게 이해하기 8] 파드 로드 밸런서 서비스
[쿠버네티스 쉽게 이해하기 9] 서비스 로드 밸런서 인그레스
[쿠버네티스 쉽게 이해하기 10] 환경변수 컨피그맵과 시크릿
[쿠버네티스 쉽게 이해하기 11] 데이터 저장소 사용을 위한 PV/PVC
[쿠버네티스 쉽게 이해하기 12] 헬스 체크를 위한 스타트업 프로브, 라이브니스 프로브, 레디니스 프로브
[쿠버네티스 쉽게 이해하기 13] 통합 로깅을 위한 EFK 스택
[쿠버네티스 쉽게 이해하기 14] 인증Authentication과 알백RBAC 방식의 인가Authorization
[쿠버네티스 쉽게 이해하기 15] 더 알면 좋을 주제들: 무중단 배포, 모니터링, HPA

댓글

전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]