쿠버네티스의 Deployment와 Service

김태훈·2023년 11월 16일
0
post-thumbnail

성균관대학교 [SKKUDING] 동아리에서 진행한 쿠버네티스 스터디 발표 내용입니다.

💡 https://subicura.com/k8s 의 내용과, 책 ‘시작하세요 도커, 쿠버네티스' 를 참고하여 발표하였습니다.

1. Replica Set과 Pod의 배포를 담당하는 'Deployment'

우리가 저번시간까지는 Replica Set과 Pod의 개념과 활용에 대해 공부했었습니다.
하지만, 실제로 배포의 단계에서는 replica set 그 자체로 배포하기엔 무리가 있습니다. 왜냐하면 replica set과 pod를 정의하고 관리하는 'Deployment' 라는 녀석이 있기 때문입니다.

1) Deployment라는 오브젝트에 대해서

Deployment는 Replica Set의 상위 오브젝트입니다. 따라서, Deployment를 생성하게 되면 해당 Deployment에 정의되어 있는 Replica Set도 함께 생성됩니다. 실제 yaml파일 예시를 가져왔습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example
spec:
  replicas: 3
  selector:
    matchLabels:
      app: goat
  template:
    metadata:
      labels:
        app: goat
    spec:
      containers:
        - name: example
          image: nginx:latest
          ports:
          - containerPort: 80

apply를 진행하고, kubectl get po,rs,deploy 명령어로 pod, replica set, deployment 오브젝트 정보를 가져와봅시다.

실제 replica set과 pod까지 같이 생성된 것을 볼 수 있습니다.
yaml파일 형식도 replica set을 정의한 yaml파일 형식과 비교해보면, 'kind' 빼고 다 동일합니다.
또한 당연하게도, deployment resource를 삭제한다면 모든 resource들이 삭제될 것입니다.

💡 빨간색 부분은 뭔가요?
Deployment로부터 생성되어진 pod와 replica set은 같은 빨간색과 같은 동일한 해시값이 부여됩니다.
저희가 yaml파일을 정의할 때에, template을 활용했죠? 이로부터 생성되어진 해시값이라고 할 수 있습니다. 이 해시값은 Deployment를 사용하는 이유이기도 한데, 추후에 설명드리겠습니다.

2) Deployment를 왜 사용해야 하는가

Replica Set만으로도 충분히 pod를 같은 개수로 유지할 수 있으니, 굳이 Deployment를 사용하는 이유가 무엇인지 궁금하신 분도 계실겁니다.
그럼 그 이유를 살펴보겠습니다.

1. 어플리케이션의 업데이트와 배포를 '더 쉽게'

Deployment object를 활용해 배포하게 된다면, 매 배포마다, revision을 찍어서 rollback이 가능해집니다.
예를들어서 방금만든 deployment object에 변경이 필요해서, 도커 이미지를 교체했다고 합시다. nginx:latest에서 nginx:1.20.1로 변경시켜봅시다.

deployment.yaml파일의 이미지를 직접 변경해도 되고, kubectl set image명령어로 변경할 수도 있습니다.

kubectl set image deployment <metadata.name> <container name>=nginx:1.20.1 --record
명령어를 사용해봅시다.
저의 경우에는
kubectl set image deployment example example=nginx:1.20.1 --record
가 되겠군요.

그럼 변경이 되고 있는지 확인해볼까요?

위 사진은 container 가 creating 되고 있는 상황입니다.
아래의 replica set을 보게되면, 두가지가 있는 것을 확인할 수 있는데요. example-675c65447d replica set에 현재 1개의 pod가 띄워져있고, example-58c~~ 친구가 desired가 3개 current가 3개 ready가 1개인 pod 상태임을 알 수 있습니다. 이 상황은 pod의 상황과 동일합니다.

그 후에는 원래 존재했던 pod가 종료되다가.. (아래사진)

이렇게 완벽히 종료되고 새롭게 deployment로 정의한 pod와 replica set이 띄워지게 됩니다. (아래사진)

아까 빨간색 박스를 기억하시나요?
우리는 이 해시값을 통해서 언제 생성한 replica set임을 알 수 있고, 또한 새로운 deployment를 정의하고 생성했음에도 불구하고 replica set에는 이전버전의 replica set을 보존합니다.

이러한 리비전 정보들은 kubectl rollout history deployment <metadat-name>
으로 확인 가능합니다.

이 때, deployment를 생성하고 업데이트할 때, --record 옵션을 주지 않았다면, 위 명령어를 사용할 시에는

이렇게 뜰 것이니 주의 바랍니다. (물론, --record 옵션을 주지 않아도 replica set은 보존됩니다)

그럼 당연히, revision이 있다는 사실은 rollback도 가능하다는 것이겠죠?
저 위의 'REVISION' 칼럼에 존재하는 번호로 rollback할 수 있습니다.
첫번째 배포한 nginx:latest 버전 컨테이너로 돌아가고 싶다면
kubectl rollout undo deployment example --to-revision=1

성공적으로 rollback할 수 있습니다.

또한 rollback 전에 해당 revision의 내용을 자세히 보고싶다면
kubectl rollout history deployment example --revision=1
으로 확인할 수 있습니다.

아래 사진은 describe로 deploy 오브젝트를 자세히 들여다본 결과입니다.

2. 롤링업데이트 전략을 수립할 수 있다.

다양한 포드의 롤링 업데이트 정책을 사용할 수 있다고 합니다. (좀 후에 자세히 배울듯 하네요 ㅎㅎ.. 아직 뭔말인지는 모름..)

요약 : Deployment는 ReplicaSet의 상위 오브젝트이며, Deployment로 Pod를 생성하는 방식이 일반적이다.

2. Pod를 연결하고 외부에 노출해주는 'Service'

혹시 지난주에, 외부에서 pod로 직접 요청을 보낼 수 없어서, 클러스터 내부에 pod를 만들고, 해당 pod로 들어가서 같은 클러스터내에 존재하는 다른 pod로 요청을 보냈었던 것을 기억하시나요?
이제는 그러지 않아도 됩니다 ! 바로 Service가 있기 때문입니다.

1. 도커와의 차이점으로부터 Service의 역할을 알기

docker run -p를 통해 도커에서는 외부 포트번호를 외부에 고의적으로 노출시켜 왔습니다.
하지만 쿠버네티스에서는 '내부'에서만 사용할 포트번호를 정의합니다. 아까 yaml파일에서의 'containerPort'가 내부포트번호를 정의한 부분입니다.

그렇다면, 이 포트를 외부에서 어떻게 접근하게 할 수 있을까요?
이를 해주는 것이 바로 Service입니다. Pod를 연결해주고 외부에 노출해주는 것이 Service의 존재 이유입니다.

서비스의 역할

  1. 여러 개의 포드에 접근할 수 있도록 서비스 별 도메인 이름 부여
  2. 여러 개의 포드에 접근시 로드 밸런서 역할
  3. 클라우드 플랫폼과 연동하여 외부로 포드를 노출

2. 서비스의 종류

1) Cluster IP 타입 (쿠버네티스 내부에서만 포드에 접근 가능)

이 서비스는 외부에 포드를 노출시키지 않으므로 클러스터 내부에서만 Pod를 사용할 경우에 해당 서비스 타입을 사용합니다.
예시를 가져왔습니다.

  • service
apiVersion: v1
kind: Service
metadata:
  name: hostname-clusterip
spec:
  ports:
    - name: web-port
      port: 8080
      targetPort: 80
  selector:
    app: webserver
  type: ClusterIP
  • spec.selector : 서비스에서 어떠한 라벨을 가지는 pod에 접근할 수 있게 할 것인가
  • spec.ports.port: 쿠버네티스 내부에서'만' 사용할 수 있는 고유한 IP를 할당
  • spec.ports.targetPort: selector 항목에서 정의한 label에 의해 접근 가능한 pod들이 내부적으로 사용하고 있는 port를 입력. (생략할경우 port와 동일합니다)
  • spec.type : 서비스 타입

이는 hostname-clusterip라는 이름의 service를 만들고 selector로 추후에 label과 함께 deployment할 webserver를 지정해주었습니다.

여기에서 신기한 부분이 service/kubernetes 는 생성한적이 없는데 왜있는 걸까요?
이 서비스는 포드 내부에서 쿠버네티스 API에 접근하기 위한 서비스라고 합니다. (일단 이정도로만 알고 넘어갑시다)
위 터미널 결과를 보면 ClusterIP로 생성된 redis가 보이시죠? 해당 Cluster-IP와 PORT를 이용하여 요청을 보내면 됩니다.

  • deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostname-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webserver
  template:
    metadata:
      name: my-webserver
      labels:
        app: webserver
    spec:
      containers:
      - name: my-webserver
        image: alicek106/rr-test:echo-hostname
        ports:
        - containerPort: 80

해당 webserver는 nginx이미지를 기반으로 하였으며, hostname을 반환하는 단순한 구조입니다.
service에 정의된 selector에 맞게 labeling 해주면, service에서 해당 Pod에 접근할 수 있게 됩니다.

  • 접근해보기
    이전과 마찬가지로 임시 pod를 만들어서 클러스터 내부에서 요청을 보내봅시다.
    방법은 여기로 ->https://velog.io/@goat_hoon/j5js7tqj

    신기한점이 있습니다!
    hostname의 hash값들이 매 요청마다 다릅니다.
    이는 곧, service가 알아서 로드밸런싱한다는 의미와 같습니다.

또한, 서비스 이름 그자체로도 접근할 수 있다고 말씀드렸듯이 이름으로도 가능합니다.

이렇게 포드가 클러스터 내부에서 서비스와 연결할 때에는 서비스의 이름만으로도 간단히 접근할 수 있습니다.

아래는 요약의 그림입니다.

2) 외부에 노출시키기 위한 서비스 NodePort

이름에서 알 수 있듯이, Node 별로 Port번호를 부여하는 방식으로 외부에 Port번호를 개방합니다.

apiVersion: v1
kind: Service
metadata:
  name: hostname-nodeport
spec:
  ports:
    - name: web-port
      port: 8080
      targetPort: 80
  selector:
    app: webserver
  type: NodePort


놀랍게도 port번호가 8080 to 32128로 매핑되어있는 것을 확인할 수 있습니다. 이때 매핑되어있는 32128번호는 모든 노드에서 동일하게 접근가능합니다.
또한 NodePort는 ClusterIP의 역할도 포함하고 있습니다 !

3) LoadBalancer

그러나.. 노드의 IP를 모르면..? 그리고 그 노드가 살아있는지도 모르면..?
그래서 LoadBalancer가 필요합니다.
이는 클라우드 플랫폼으로부터 도메인 이름과 IP를 할당받아 사용하므로, Node IP가 필요하지 않습니다.
즉 로드밸런서에 붙어있는 IP나 도메인네임으로 알아서 pod에 요청을 꽂아줍니다 !

그 과정중에는 무작위의 Port번호가 열리게 되는데, 이 숫자는 각 노드에서 동일하게 접근할 수 있는 포트번호입니다. NodePort와 비슷하다면 비슷하다고 생각할 수 있습니다만, 차이점은 'Loadbalancer의 Ip 혹은 도메인 네임만으로 접근이 가능하다' 입니다.

profile
기록하고, 공유합시다

0개의 댓글