2024.03.08(금)

🧩마이크로서비스 아키텍쳐 (Microservice Architecture; MSA)

  • 응용 시스템 개발 및 구성을 위한 아키텍처 스타일의 하나

  • 이에 비교하여 전통적인 방식의 아키텍처를 모놀리식(monolithic) 아키텍처라고 부르기도 함

  • 애플리케이션이 서비스 모음으로 개발되어 각 마이크로서비스는 특정한 기능을 수용하고 개별 작업을 처리, 이 서비스들이 서로 연결되어 전체 응용을 구성

  • 컨테이너 모델은 마이크로서비스를 구현하기에 적합

Kubernetes

  • 줄여서 k8s라고도 표기
  • 컨테이너 오케스트레이션 솔루션
    • 다수의 컨테이너들을 관리하면서 자동 배포, 배포된 컨테이너의 동작 보증, 부하에 따른 동적 확장 등의 기능을 담당
  • 도커와 잘 어울리는 실행 환경 구성 도구
    • 도커 컨테이너들을 클러스터 내에 실행하고 관리하는 데 적합
    • 지속적 통합과 인도(CI/CD)에 유효하게 적용 가능
    • 컨테이너는 포드(pod)라 불리는 k8s 오프젝트와 연관하여 실행(포드 위에서 실행한다고 대강 표현)

🏘️k8s 클러스터

🏠구성 요소

  • 하나 이상의 노드들로 구성
  • 마스터 노드(컨트롤 플레인)
    • kubectl (반드시 마스터 노드에 있어야 하는 것은 아님)
    • API 서버, etcd - 클러스터의 중심 역할을 하는 구성 요소들
    • 컨트롤러 매니저, 스케줄러
  • 워커 노드
    • 컨테이너 런타임(CRI; Container Runtime Interface) - 포드를 이루는 컨테이너의 실행을 담당
    • kubelet - 포드의 구성 내용을 받아 CRI에 전달하고 컨테이너들의 동작 상태를 모니터링

🗃️포드(pod)와 컨테이너(Container)

Container ⊂ Pod ⊂ Worker node

  • Pod의 생명 주기 (Life Cycle)
    • kubectl을 통해 API 서버에 포드의 생성을 요청
      • (업데이트가 있을 때마다) API 서버는 etcd에 기록하고 클러스터의 상태를 최신으로 유지(하려고 함)
    • 컨트롤러 매니저는 포드를 생성하고, 이 상태를 API 서버에 전달
      • 아직 어떤 워커 노드에 포드를 적용할지는 결정하지 않은 상태
    • 스케줄러는 포드가 생성되었다는 정보를 인지하고, 이 포드를 어떤 워커 노드에 적용할지를 결정해서 해당 노드에 포드의 실행을 요청
    • 해당 노드의 kubeletCRI에 요청하여 포드가 만들어지고 사용 가능한 상태가 됨
  • k8s는 절차적인 구조가 아닌 선언적인 구조를 가지고 있음
    • 각 요소가 추구하는 상태(desired state)를 선언하면 현재 상태(current state)와 비교하고 지속적으로 맞추어 가려고 노력하는 구조

💡k8s 오브젝트들

  • 기본 오브젝트
    • Pod
      • 한 개 이상의 컨테이너로 단일 목적의 일을 하기 위해서 모인 단위
      • 독립적인 공간과 사용 가능한 IP를 가지고 있음, 언제든지 죽을 수 있음
    • Namespace
      • k8s 클러스터에서 사용되는 리소스들을 구분해 관리하는 그룹
    • Volume
      • 포드가 생성될 때 포드에서 사용할 수 있는 디렉토리를 제공
    • Service
      • 유동적인 포드들에 대한 접속을 안정적으로 유지하도록 클러스터 내/외부에 연결하는 역할
  • 디플로이먼트 (Deployment)
    • 기본 오브젝트들을 보다 효율적으로 작동할 수 있도록 조합하고 추가로 구현한 것
    • 레플리카셋(replicaset) 오브젝트를 합쳐 놓은 형태로 단순하게 생각할 수 있음

🪄k8s가 제공하는 기능

  • 컨테이너 밸런싱 (container balancing)

    포드의 부하 균등화를 수행 - 몇 개의 응용을 복제할 것인지 알려주면 나머지는 k8s가 처리

  • 트래픽 로드 밸런싱 (traffic load balancing)

    응용의 복제본이 둘 이상 있다면 k8s가 트래픽 부하 균등화를 수행하여 클러스터 내부에 적절히 분배

  • 동적 수평 스케일링 (HPA; Horizontal Pod Autoscaling)

    인스턴스 수를 동적으로 확장하거나 감축하여 동적 요구사항에 대응하면서 시스템 자원을 효율적으로 활용

  • 오류 복구(error recovery)

    포드와 노드를 지속적으로 모니터링하고 장애가 발생하면 새 포드를 실행하여 지정된 복제본의 수를 유지

  • 롤링 업데이트 (rolling update)

    지연 시간을 적용하고 순차적으로 업데이트 배포함으로써 문제가 발생하더라도 서비스를 정상 유지 가능

  • 스토리지 오케스트레이션 (storage orchestration)

    원하는 응용에 다양한 스토리지 시스템(Amazon EBS, Google GCE Persistent Disk 등)을 마운트 가능

  • 서비스 디스커버리 (service discovery)

    태생적으로 수명이 짧은 포드의 동적 성질을 관리하기 위해 자체 DNS 기반으로 서비스를 동적 바인딩할 수 있는 기능 제공

🏗️k8s 인프라 구축

  • 로컬 환경
    • kubeadm, docker desktop 등을 설치, 운용함으로써 로컬 환경에 간단한 클러스터 구성 가능
    • 개발 단계에서의 테스트 등에 이용
  • Public clouds
    • Amazon의 AWS EKS (Elastic Kubernetes Services)
    • GCP (Google Cloud Platform)의 GKE (Google Kubernetes Engine)
    • Microsoft의 AKS (Azure Kubernetes Service)
  • On-prem 설치
    • SUSE의 Rancher
    • RedHat의 OpenShift

⌨️실습

🛠️준비

  • Docker Desktop을 이용한 로컬 환경 클러스터 구성
    • Settings > Kubernetes > Enable Kubernetes를 체크해서 Docker Desktop이 실행 시작할 때 단일 노드(내 컴퓨터)로 이루어진 k8s 클러스터를 운용하도록 지시
    • kubectl get nodes 명령어로 확인

🔍노드(Node)와 포드(Pod) 정보 조회

  • kubectl get
    • kubectl get nodes
    • kubectl get pods
    • -o wide option으로 자세히 보기

🏃컨테이너 이미지를 이용한 포드의 생성

🚀디플로이먼트

  • Deployment

    • 응용의 배포를 위하여 많이 이용되는 k8s의 오브젝트 형태
    • 동일한 모습의 포드들의 복제본 모음인 레플리카셋(replicaset)을 이용하는 것이 일반적
    • 단순한 레플리카셋에 비하여 동적 업데이트 및 롤백(rollback), 배포 버전의 관리 등이 유연하여 응용의 배포에 널리 이용됨
    • 보통은 상태가 없는(stateless) 응용의 배포에 이용
      • 포드는 언제라도 사멸할 수 있음
  • 동작 방식

📣클러스터 바깥으로 응용을 노출하기

  • k8s 서비스(service)
    • 클러스터 내부의 포드에 의하여 실행되는 응용을 외부에 접근 가능하도록 노출하는 기능을 하는 오브젝트
  • 노출하는 대상
    • 특정 포드(또는 포드들의 집합)에서 실행하는 컨테이너의 특정 포트(port)
  • 서비스 type
    • ClusterIP
    • NodePort
    • LoadBalancer
    • ExternalName
    • Ingress
  • kubectl expose
    • 특정 pod 노출

      svc는 service(s)의 약자
    • deployment 노출

      Deployment가 관리하는 3개의 Pod 중 하나와 연결됨

🗑️오브젝트 삭제

  • kubectl delete <object-type> <object-name>
    • service object 삭제
      • service object만 삭제되고 deployment와 pod는 여전히 존재
    • pod 삭제
    • deployment로 생성된 pods 삭제
      • 그냥 pod를 삭제하면 k8s가 바로 새로운 pod를 생성 → deployment에서 replicas를 3으로 선언했었기 때문에 이를 유지하기 위함
      • deployment를 지우면 속해있던 pod들이 모두 삭제됨

📋메니페스트(Manifest)

  • k8s 오브젝트에 대한 명세를 파일로 기록한 것
    • YAML(Yet Another Markup Language 또는 YAML Ain’t Markup Language) 형태 이용
  • 파일에 각 오브젝트에 의도하는 상태(desired state)를 기술 = 오브젝트 스펙(object spec)
  • 이 파일을 이용해
    • 오브젝트를 생성할 수 있고
    • (파일의 내용을 변경하여) 오브젝트의 상태를 변경 가능
      • 물론, 파일의 내용을 변경하지 않고 다른 파일을 이용하여 오브젝트 상태를 변경하는 것도 가능
  • 자동화가 필요한 환경에서 (당연히) 명령어 라인에 일일이 입력하는 것보다 많이 이용됨
  • 앞서 kubectl create deployment 명령을 이용한 것과 동일한 형태의 디플로이먼트를 생성하는 데 이용할 수 있는 파일 예시
    # deployment.yaml
    
    apiVersion: apps/v1
    kind: Deployment  # 디플로이먼트를 만들 것을 지시
    
    metadata:
    	name: dpy-nginx # 디플로이먼트 이름은 dpy-nginx
    	labels:
    		apps: nginx
    spec:
    	replicas: 3     # 세 개의 포드로 구성된 레플리카셋을 생성
    	selector:
    		matchLabels:
    			app: nginx
      # 포드 템플릿
    	template:
    		metadata:
    			labels:
    				app: nginx
    		spec:
    			containers:
    			- name: nginx
    				image: nginx
    				ports:
    				- containerPort: 80
  • 앞서 kubectl expose deployment 명령을 이용한 것과 동일한 서비스를 생성하는 데 이용할 수 있는 파일 예시
    # service.yaml
    
    apiVersion: v1
    kind: Service			# 서비스를 만들 것을 지시
    
    metadata:
    	name: nginx-svc	# 서비스 이름은 nginx-svc
    spec:
    	type: NodePort
    	selector:
    			app: nginx	# 이 셀렉터에 의해 발견되는 오브젝트에 대해
    		# 80/TCP (http) 포트를 노드의 30000번 포트로 노출 → localhost:30000으로 접속 가능
    		ports:
    		- name: http
    			protocol: TCP
    			port: 80
    			targetPort: 80
    			nodePort: 30000
  • 명령어

🧐클러스터에서 수신한 요청에 대해 k8s가 반응하는 방식 알아보기

  • hostname:latest라는 이름:태그의 이미지를 로컬에 build
    docker build -t hostname:latest .
  • 태그 지정해서 docker hub에 push
    docker tag hostname:latest <docker_hub-id>/hostname:latest
    docker push <docker_hub-id>/hostname:latest
  • deployment.yaml에 지정된 대로 포드를 생성해서 컨테이너 실행
    kubectl apply -f deployment.yaml
  • service.yaml로 포트 노출
    kubectl apply -f service.yaml
  • 접속해보기
    $ curl localhost:30000
    <p>Hostname: dpy-hname-747d7bbcd8-sq9j7</p><p>IPv4 Address: 10.1.0.12</p>
    • 접속할 때마다 응답하는 pod가 다름! ⇒ k8s의 container balancing
  • 주기적인 요청을 이용해 테스트
    • Linux/MacOS: bash script (check.sh)
      i=1
      
      while true
      do
      	sleep 1
      	echo $((i++)) `curl --silent --connect-timeout 0.1 localhost:30000`
      done
    • Windows: PowerShell script (check.ps1)
      $i=0
      while($true)
      {
      	% { $i++; write-host -NoNewline "$i $_" }
      	(Invoke-RestMethod "http://localhost:30000")-replace '\n', " "
      	Start-Sleep -Seconds 1
      }

💥디플로이먼트의 레플리카셋에 포함된 포드가 사멸하는 경우

  • deployment.yaml에서 replicas: 1로 수정한 후 스케일 다운
    kubectl apply -f deployment.yaml
    • 3개의 pod들 중 2개는 종료됨 → 남은 pod 하나만 응답
  • script를 실행해 1초마다 요청을 보내기
  • 남은 pod를 삭제하면 k8s는 새로운 이름과 ip 주소를 가진 pod(이미지 동일)를 생성하는 것을 확인할 수 있음!

💥특정 포드에서 실행하는 컨테이너에 오류가 발생하는 경우

  • script를 실행해 1초마다 요청을 보내기
  • 컨테이너에 셸 접속을 얻어 flask 프로세스의 PID를 알아내고 kill을 이용해 프로세스를 강제 종료
    [kubectl exec -it <pod-name> -- /bin/bash](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#exec)
    # ps ax | grep flask
    # kill -9 [PID]
  • 디플로이먼트에 의해 배포된 pod의 컨테이너 실행에 문제가 생기면, k8s는 이 pod에서 실행하던 컨테이너 삭제하고 다시 생성해 응용을 지속 실행(동일한 IP 주소 유지) = 재시작(RESTART)

⇒ pod를 삭제하면 pod를 재생성 (새로운 이름 & IP 주소가 할당됨), pod 내의 컨테이너에 문제가 생기면 컨테이너 재시작(동일한 IP 주소 유지): pod마다 고유한 IP 주소가 할당되고 수명 동안 유지되기 때문

pod를 삭제하면 pod를 재생성 (새로운 이름 & IP 주소가 할당됨)
pod 내의 컨테이너에 문제가 생기면 컨테이너 재시작(동일한 IP 주소 유지)
⇒ pod마다 고유한 IP 주소가 할당되고 수명 동안 유지되기 때문

🔙배포된 소프트웨어의 업데이트와 복구

  • 소프트웨어 업데이트가 발생함에 따라 (매번 새로 배포하는 것이 아니라) 동적 업데이트 필요
  • 소프트웨어 업데이트는 실패할 수 있으므로, 빠르고 유연한 복구는 필수 기능
    • 업데이트 자체가 실패하는 경우 → 복구(rollback)
    • 새 버전의 소프트웨어가 오동작하는 경우 → 민첩한 복구 조치 필요

🔍롤아웃 정보와 이력 조회

  • rollout.yaml은 기존 deployment.yaml 파일에서 image의 버전을 지정하도록 수정 2-04.tgz
  • kubectl apply -f rollout.yaml & kubectl apply -f service.yaml (기존 service는 모두 삭제)
  • rollout.yaml의 이미지 버전을 1.17.0으로 수정하고 다시 apply
    • --record option 적용 시 이력 기록
      • deprecated 경고가 뜨기 때문에 다른 방법 사용
  • kubectl rollout status deployment : 롤아웃 상태 조회
  • kubectl rollout history deployment : 롤아웃 이력 조회

📼배포 이력의 기록: kubectl annotate

  • kubectl delete deployment dpy-nginx
  • rollout.yaml의 이미지 버전을 다시 1.16.0으로 수정
  • kubectl apply -f rollout.yaml & kubectl apply -f service.yaml
    $ curl -I --silent localhost:30000 | grep Server
    Server: nginx/**1.16.0**
  • kubectl set image deployment dpy-nginx nginx=nginx:1.17.0 → apply를 하지 않아도 변경 사항이 바로 적용됨
  • kubectl annotate deployment dpy-nginx kubernetes.io/change-cause="update nginx image from 1.16.0 to 1.17.0"
    • 가장 최근 버전의 리비전을 대상
    • 이미 존재하는 주석을 변경하려면 --overwrite flag 사용
  • kubectl rollout history deployment dpy-nginx
    deployment.apps/dpy-nginx 
    REVISION  CHANGE-CAUSE
    1         <none>
    2         update nginx image from 1.16.0 to 1.17.0
    $ curl -I --silent localhost:30000 | grep Server
    Server: nginx/**1.17.0**
  • 존재하지 않는 nginx 버전으로 image를 수정하면 annotate도 되고 history에도 남지만 status가 실패로 뜸 → kubectl rollout undo deployment dpy-nginx로 직전 버전으로 roll back
    • kubectl rollout undo deployment <deployment> --to-revision=<revision-num>으로 특정 리비전으로 복구 가능

🔗쿠버네티스 서비스와 볼륨

🤝서비스 종류

  • ClusterIP
    • 클러스터 내부에서만 접근할 수 있는 IP를 할당
    • 포트 포워딩(port forwarding) 또는 프록시(proxy)를 통해 클러스터 외부로부터 접근 가능
    • 테스트, 디커깅 등의 목적에 제한적으로 이용
    • 서비스의 type을 명시적으로 지정하지 않았을 때의 기본값
  • NodePort
    • 모든 워커 노드의 특정 포트(NodePort)를 열고 여기로 오는 모든 요청을 노드포트 서비스에 전달
    • 노드포트 서비스는 해당 요청을 처리할 수 있는 포드로 요청을 전달
  • LoadBalancer
    • 클러스터 외부의 로드밸런서(public cloud들은 공히 제공)를 이용하여 부하 균등화 수행
    • 노드포트와 달리 특정 노드가 접근 불가한 경우에도 서비스 제공 가능
  • Ingress
    • k8s의 한 종류는 아니고, 복수의 서비스에 대해 목적에 따라 트래픽을 연결하는 도구

⚖️동적 수평 오토스케일링

  • HPA(Horizontal Pod Autoscaler)
    • 부하량에 따라 디플로이먼트의 포드 수를 유동적으로 관리하는 k8s의 기능
      • (보통의 경우) 고려되는 부하량: CPU 및 메모리 사용량
    • 메트릭스 서버(metrics server)로부터 부하 계측값을 전달받아 동일한 기능을 제공하는 포드의 수를 동적으로 조절
  • 스케일링 기준이 되는 값과 최소/최대 포드 수를 지정

🎛️메트릭스 서버의 설치 (배포)

  • https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
  • 원본 파일에서 TLS(Transport Layer Security) 인증을 무시하도록 --kubelet-insecure-tls 추가
          containers:
          - args:
            - --cert-dir=/tmp
            - --kubelet-insecure-tls
            - --secure-port=10250
            - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
            - --kubelet-use-node-status-port
            - --metric-resolution=15s
            image: registry.k8s.io/metrics-server/metrics-server:v0.7.0
  • kubectl apply -f metrics.yaml로 설치 (배포)
  • 응용(웹 서버) 배포 (k8s 예제 파일 사용: 포드 1개, ClusterIP로 클러스터 내부에만 노출)
    kubectl apply -f php-apache.yaml
  • HPA: CPU 사용률 50%를 기준으로 동적 스케일링(최소 1개, 최대 10개의 포드 운용)
    kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
  • 0.01초마다 php-apache로 HTTP GET 요청을 보내는 간단한 부하 테스트
    kubectl run -it load-gen --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
    • http://php-apache: k8s에서 서비스 디스커버리 기능이 있어서 서비스 이름으로 요청을 보낼 수 있음
    • 부하에 따라 pod가 늘어난 것을 확인 할 수 있음
    • 부하 코드 종료 시 pod는 다시 줄어듦

🔗볼륨(Volumes)

  • k8s는 클러스터 내에서 이용할 수 있는 저장장치(storage)의 추상화된 객체로 볼륨을 정의

  • PV(PersistentVolume)

    • 포드에서 바인드 마운트(bind moount)로 이용할 수 있는 저장장치
    • 클러스터 내에 존재하는 스토리지를 추상화한 것
    • 클러스터 내의 노드에 존재하는 물리적 저장장치를 이용할 수도 있으며, 다양한 원격 저장소 및 클라우드 서비스들도 이용 가능
  • PVC(PersistentVolumeClaim)

    • (사용자에 의한) PV를 이용하기 위한 요청
    • Pod와 Node의 관계는 PVC와 PV의 관계와 비슷
    • 저장 공간의 크기와 접근 모드(읽기/쓰기, …)를 지정
  • PV, PVC, Deployment, Service 생성 & volume 연동 확인 (2-05/pv 폴더)

    ⚠️ Docker Desktop의 Kubernetes를 사용 중이라면 내부적으로 Linux 컨테이너를 사용하기 때문에 윈도우 경로가 호환되지 않음

    • pv-volume.yaml 파일에서 hostPath의 path 입력 시 /run/desktop/mnt/host/<your-path>와 같이 수정
      • 연동 확인을 위해 해당 path에 미리 파일 만들어두기 (index.html)
      <p>Hello from k8s Storage!</p>
    • 변경 전 (pod 생성 시 CreateContainerError 발생: Error response from daemon: invalid mode: /var/lib/mysql)
      hostPath:
        path: "D:/PROGRAMMERS/수업/CI-CD/K8S/2-05/2-05/data"
    • 변경 후
      hostPath:
      	path: "**/run/desktop/mnt/host/**d/PROGRAMMERS/수업/CI-CD/K8S/2-05/2-05/data"

profile
이것저것 관심 많은 개발자👩‍💻

0개의 댓글