본문 바로가기
Development(Web, Server, Cloud)/22) LINUX - Cloud

클라우드 Runtime (k8s)

by tonyhan18 2022. 4. 6.
728x90

k8s 입문

yoonjeong-kwon/fastcampus-kubernetes: 쿠버네티스 입문 과정 (github.com)

 

GitHub - yoonjeong-kwon/fastcampus-kubernetes: 쿠버네티스 입문 과정

쿠버네티스 입문 과정. Contribute to yoonjeong-kwon/fastcampus-kubernetes development by creating an account on GitHub.

github.com

쿠버네티스 문서 | Kubernetes

 

쿠버네티스 문서

쿠버네티스는 컨테이너화된 애플리케이션의 배포, 확장 및 관리를 자동화하기 위한 오픈소스 컨테이너 오케스트레이션 엔진이다. 오픈소스 프로젝트는 Cloud Native Computing Foundation에서 주관한다.

kubernetes.io

 

---

 

쿠버네티스와 클러스터 동작 방식

서비스가 많아지면서 어떻게 배포를 빠르게 하고 관리할 것인가?

 

개발서버에서 이미지를 만들고

registry에 이미지를 올리고

운영서버에서 이를 작동

 

한두개 애플리케이션이면 이게 가능한데 수백개이면 이게 가능할까?

리소스는 어떻게 관리할 것인가?

 

내가 운영하고자 하는 서버와 마이크로서비스간의 리소스와 비용을 어떻게 계산할것인가?

 

마이크로서비스 아키텍처로 변환되면서 개발자가 신경쓸 부분들이 너무 많아졌고 이걸 어떻게 자동화할 것인가?

 

그래서 이러한 문제를 해결하기 위한 쿠버네티스는 위와같은 기능을 제공해준다.

 

자동화된 빈 패킹 : 각 컨테이너가 필요로 하는 CPU와 메모리를 쿠버네티스에게 지시, 쿠버네티스는 컨테이너를 노드에 맞추어서 리소스를 가장 잘 사용할 수 있도록 해줌

자동화된 복구 : 쿠버네티스는 실패한 컨테이너를 다시 시작, 컨테이너를 교체, 사용자 정의 상태 검사에 응답하지 않는 컨테이너는 죽인다.

자동화된 롤아웃과 롤백 : 작업자가 쿠버네티스에게 어떻게 롤아웃하고 롤백할지 서술해주면 그 상태에 따라서 쿠버네티스는 롤아웃과 롤백을 지원해준다.

 

 

서비스 디스커버리

MSA에서 수많은 애플리케이션의 IP를 예측하고 관리하기 어렵다. 그래서 중앙에 Service Registry를 두고 MSA의 애플리케이션 주소들을 관리하게 된다.

방법은 MSA 애플리케이션이 Service Registry 서버로 register, cancel 요청을 보내고 주기적으로 heartbeat를 확인한다.

 

낙관적 동시성 제어를 이용해서 리소스 충돌을 해결

 

리소스 버전을 통해서 쿠버네티스 상태의 제어를 하게 된다.

 

오메가는 상태 관리 저장소를 직접 접근하고 제어할 수 있는 구조 -> 쿠버네티스는 저장소를 제어하기 위한 API 서버를 앞에 두고 그 API 서버를 통해 저장소에 접근하도록 하는 추상화를 위한 REST API를 통해서만 접근할 수 있다.

 

=> 컨테이너로 향상된 리소스 활용의 이점을 누리면서도 복잡한 분산 시스템을 쉽게 배포하고 관리할 수 있게 만드는 것이다.

 

 

 

 

 

 

과거에는 Bare Metal에서는 애플리케이션별로 운영되는 것에 큰 문제가 없었다. 그런데 애플리케이션이 패키지 의존성의 문제가 생기었다. 그래서 이러한 문제를 해결하기 위해 애플리케이션을 샌드박식하기로 결정했다. 애플리케이션이 의존성이랑 sandbox로 묶여서 다른 애플리케이션이 접근하지 못하도록 만들었다.

 

그래서 가장 먼저 시도한것이 가상화 방식이다.

GuestOS를 만들어서 사용하는 방식인데 HostOS에서 CPU/RAM/HDD를 가상화하고 컴퓨터로 추상화해서 사용할 수 있게 하는 방식이다.문제는 이렇게해도 성능은 낮고 오버헤드는 자주 발생했다. 

 

그래서 나온것이 컨테이너화이다. 컨테이너 기술은 가상머신과 다르게 HostOS의 GuestOS를 띄우지 않고 GuestOS 처럼 동작하게 된다. HostOS의 커널을 공유하게 된다. 필요한건 시스템적인건 HostOS와 공유하고. HostOS 커널위에서 샌드박싱된 영역을 여러기술로 구현하게 된다.(chroot) App binary와 Lib만이 샌드박스 안에 들어가서 경량화 되고 오버헤드가 낮추고 성능을 높인다.

docker = container engine

 

k8s(container orchestration) 여러 컨테이너에 대해서 어떻게 스케쥴링하고 관리할지를 결정한다.

- 클러스터

클러스터 : 여러 개의 서버를 하나로 묶은 집합

쿠버네티스 클러스터 : 쿠버네티스가 관리하는 서버들의 집합

(여러개의 서버를 쿠버네티스가 관리할 수 있도록 묶어주고 이걸 쿠버네티스 클러스터라고 부름)

 

클러스터 내에 하나는 Master Node이다. Worker node는 실재로 실행시키는 것이다.

 

etcd : 클러스터에 배포된 애플리케이션 실행 정보를 저장

API Server : Rest API를 받는 서버. 외부/내부 요청을 수신

Scheduler : 노드 스케쥴링

Controller Managers : 컨테이너의 상태와 갯수의 상태를 확인하고 맞지 않으면 추가적으로 API 서버에 요청

 

Kubelet, Container Runtime

kube-proxy : worker 노드로 들어오는 트래픽을 pod를 전달하기 위해서 kube-proxy

 

k8s에 애플리케이션 컨테이너를 배포한다 == 배포하기 위한 스크립트를 k8s가 이해할 수 있는 형식으로 전달

 

쿠버네티스 오브젝트를 정의하기 위한 Manifest 파일을 작성

Manifest 파일에는 쿠버네티스 오브젝트를 생성하기 위한 필수 정보가 담긴다.

 

이 파일을 Master Node에 있는 API Server에 요청을 보내면 k8s에 배포하는 행위이다.

API Server가 어떻게 통신하길래 이게 가능한가?

 

요청이 올때마다 처리해주는 패턴일거라고 생각된다.

 

kubectl을 이용해서 작업.

HTTP POST Request를 API Server로 전송. API Server는 요청에 대해 etcd에 상태저장

Controller Managers 에게 신규 리소스 생성 Event 요청을 받게됨. Controller가 API 로 API 서버에 전달 그리고 etcd에 상태를 저장

Scheduler는 Pod의 생성 요구를 받아온다. 그럼 요구를 만족하는 노드를 선택해서 노드의 정보를 Pod 정보에 추가해서 API Server에 전달한다. etcd에 상태를 저장한다.

Worker node에 kubelet 프로세스에 내가 실행중인 A 노드에 배포해야할 Pod가 있다는 event 듣게 된다. 그럼 kubelet이 docker에게 실행 명령을 전달한다.

 

kubectl

kubelet

 

 

트래픽 전달방식

각각의 Worker 노드에는 kube-proxy가 동작중이다. API Server를 통해서 엔드포인트가 추가되었다. 어떤 pod에 ip들이 할당되고 kube-proxy가 그 정보를 감지해서 자신의 worker 노드에 있는 iptables를 업데이트 한다. worker 노드로 트래픽이 들어오면 클라이언트 요청을 목적지로 연결하기 위한 역활들을 제공한다. 그게 iptable을 관리하는 것이다. worker node1에서 10.76.1.52에 요청이 들어오면 첫번째 컨테이너에서 10.80.13.74에 대한 ip를 바꾸는 작업을 수행하게 된다.

 

쿠버네티스 클러스터 구성

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

나중에 위와같이 기술을 더 배울 수도 있다.

 

---

 

docker, docker-compose 설치

환경 : ubuntu

 

fastcampus-devops/3-docker-kubernetes/env/ubuntu at main · tedilabs/fastcampus-devops (github.com)

 

GitHub - tedilabs/fastcampus-devops: 🚀 패스트캠퍼스 데브옵스 초격차 코스 자료

🚀 패스트캠퍼스 데브옵스 초격차 코스 자료. Contribute to tedilabs/fastcampus-devops development by creating an account on GitHub.

github.com

#!/usr/bin/env bash
## INFO: https://docs.docker.com/engine/install/ubuntu/

set -euf -o pipefail

DOCKER_USER=ubuntu

# Install dependencies
sudo apt-get update && sudo apt-get install -y \
  apt-transport-https \
  ca-certificates \
  curl \
  gnupg \
  lsb-release

# Add Docker’s official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# Set up the stable repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker CE
sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# Use Docker without root
sudo usermod -aG docker $DOCKER_USER

t3.small(2 core CPU, 2GB RAM)

 

위의 스크립트를 가져다가 sh 파일안에 복붙하고 실행시키어주자

 

 

#!/usr/bin/env bash
## INFO: https://docs.docker.com/compose/install/

set -euf -o pipefail

DOCKER_COMPOSE_VERSION=v2.1.1

# Download and install
sudo curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

docker-compose 설치

 

kubectl : k8s를 위한 커맨드라인 도구. k8s는 

k8s 는 클러스터 시스템이고 master과 worker로 나뉜다.

master에는 API Server가 존재. API server를 통해 k8s cluster에 명령어를 주고 받을 수 있다.

 

kubectl을 통해 api 서버에 인증하고 api 서버에 여러가지 k8s 명령어를 전달

 

#!/usr/bin/env bash
## INFO: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-using-native-package-management

set -euf -o pipefail

# Install dependencies
sudo apt-get update && sudo apt-get install -y \
  apt-transport-https \
  ca-certificates \
  curl \
  gnupg \
  lsb-release

# Add kubectl's official GPG key
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/kubernetes-archive-keyring.gpg

# Set up the repository
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

# Install kubectl
sudo apt-get update && sudo apt-get install -y kubectl

 

kustomize

kustomize는 쿠버네티스의 매니페스트 파일을 좀 더 효율적으로 관리할 수 있도록 도와주는 도구

매니페스트를 관리하는게 kubectl에서 할 수 있지만 관리하는 어플리케이션이 많아짐에 따라서 관리가 불편하게 된다.

이러한 불편함을 해결하기 위해서 나오는 것이 kustomize와 helm이다.

 

위의 이미지에서 긴게 base이고 오른쪽의 짧은게 patch(overlay)라고 해서 이걸 base에 적용해서 변태적인 무언가를 만들 수 있다.

 

#!/usr/bin/env bash
## INFO: https://kubectl.docs.kubernetes.io/installation/kustomize/binaries/

set -euf -o pipefail

KUSTOMIZE_VERSION=v4.4.1

# Download kustomize binary
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/kustomize/${KUSTOMIZE_VERSION}/hack/install_kustomize.sh"  | bash

# Install to /usr/local/bin
sudo install -o root -g root -m 0755 kustomize /usr/local/bin/kustomize

 

minikube

작은 쿠버네티스를 뜻함. k8s가 너무 어려움. k8s는 클러스터 시스템이다. 여러머신을 관리하는 시스템이다보니 k8s를 운영환경에 맞추기 위해서는 여러 머신 위에 k8s를 올려야 하지만 너무 무겁다.

그래서 이걸 학습용으로 사용할 수 있도록 만든 작은 k8s이다. 가상환경 드라이버를 제공해주기 때문에 minikube가 어떤 가상환경에서 클러스터 환경을 생성할지 결정할 수 있다.

 

드라이버(driver)를 선택하여 원하는 가상환경(docker, podman, virtualbox, parallels, vmware, hyperkit 등)에서 구성 가능 쿠버네티스 학습환경으로 활용하기 좋음

 

---

 

kubernetes

쿠버네티스는 컨테이너 오케스트레이션 시스템이다.

 

docker, docker-compose는 한 대의 머신에서 사용가능하다.

오케스트레이션 시스템은 여러대 머신에서 클러스터로 관리가 가능해진다.

 

이걸 하게 되면 Scaling이라는 컨테이너 갯수를 조절할 수 있게된다.

자원관리(Resource Allocation)도 가능해진다.

 

해당 컨테이너가 어떤 노드에 스케쥴링이 되어야 하는지(Scheduling)

 

overlay 네트워크를 이용해서 멀티노드 환경에서 멀리 떨어져 있어도 동일네트워크에 있게 해줄 수 있다. 그럼 자동으로 Load Balancing이 된다.

 

이제 어떤 서비스가 다른 서비스와 통신할때 다른 서비스의 위치를 자동으로 발견하는 서비스이다.(Service Discorvery) 이를 위해서는 internal DNS가 필요하다. 쿠버네티스는 이걸 etcd가 수행해준다. 해당 서비스가 재배포가 되고 스케일링이 다시 되더라도 그 서비스를 찾아내서 연결해준다.

 

문제가 생기어 컨테이너가 죽으면 자동으로 복구하는 Self Healing도 지원해준다.

컨테이너와 컨테이너의 설정, 기밀을 주입하는 Configuration Management도 제공해준다.

애플리케이션에서 영구적인 데이터 공간을 위한 Storage Orchestration 기능도 제공해준다.

 

---

 

컨테이너 오케스트레이션 시스템은 여러 머신으로 구성된 클러스터 상에서 컨테이너를 효율적으로 관리하기 위한 시스템

 

다양한 오케스트레이션 시스템이 존재한다.

 

docker swarm은 쉽지만 다양한 기능을 만족시키지 못하였다. 그래서 k8s 로 이동하게 되었다.

 

---

 

쿠버네티스 클러스터 구성요소

두개로 나뉘어서 불러진다.

 

Master Node는 홀수개로 만들어져서 각각은 etcd, contraoller manager, scheduler, kube apiserver, kubectl의 Master Node이다

 

Node는 Pods라는 단위로 구성된다.

 

API Server : 쿠버네티스 리소스와 클러스터 관리를 위한 API 제공, etcd를 데이터 저장소로 사용

etcd : 분산 Key-Value 저장소로 클러스터 상태 저장

Scheduler : 노드의 자원 사용 상태를 관리, 새로운 워크로드를 어디에 배포할지 관리

Controller Manager(중요) :  여러 컨트롤러 프로세스를 관리(KUBE, Cloud로 나뉜다.)

 

각각의 k8s res가 존재하는데 각각을 관리하는 컨트롤러가 존재한다. 예를 들어 Pod 컨트롤러가 있다면 그 컨트롤러는 계속해서 Pod의 상태를 점검한다.  이러한 작업을 reconcile이라고 부른다.

 

노드는 실재 애플리케이션 컨테이너가 띄어지는 환경이다.

kubelet과 kube-proxy가 핵심 컴포넌트이다.

 

kubelet은 모든 노드에 설치되는 컴포넌트이다. API 서버와 통신하며 노드의 리소스를 관리한다. 컨테이너 런타임과 통신하며 컨테이너 라이프사이클 관리

 

CRI(Container Runtime Interface) : 여러 Runtime과 통신할 때 사용하는 인터페이스이다.

 

kube-proxy : 클러스터 상에 오버레이 네트워크 구성, 네트워크 프록시 및 내부 로드밸런서 역활 수행

 

minikube -> 단일노드(master 노드/worker 노드)

 

---

 

API 리소스

API 리소스는 쿠버네티스가 관리할 수 있는 오브젝트의 종류(Pod, Service, ConfigMap, Secret/Node, ServiceAccount, Role)

 

오브넥트 : API 리소스를 인트선트화 한 것(API 리소스는 오브젝트의 명세이고 오브젝트가 실재로 APi를 이용해 만들어진 것이다)

 

```

kubectl api-resources  # 현재 쿠버네티스 클러스터가 지원하는 API 리소스 목록 출력

kubectl explain pod  # 특정 API 리소스에 대한 간단한 설명확인

```

 

```

minikube start

minikube status

``` 

```

kubectl get node

kubectl api-resources

```

pods를 가장 많이 사용할 것인데 po 라는 짧은 명령어를 사용할 수 있다.

 

```

kubectl get pod

kubectl get pod --all-namespaces

```

pod 목록을 볼 수 있다.

모든 pod를 볼 수 있다.

```

kubectl explain pod

```

으로 API의 정보를 확인할 수 있다.

 

쿠버네티스는 오브젝트를 YAML 기반 매니페스트 파일로 관리된다.

 

apiVersion(필) : 어떤 API 그룹에 속하고 API 버전은 몇인가?

kind(필) : 오브젝트가 어떤 API 리소스인가?

metadata(필) : 오브젝트를 식별하기 위한 정보(이름, 네임스페이스, 레이블 등)

spec : 오브젝트가 가지고자 하는 데이터

* API 리소스에 따라 spec 대신 data(configmap, secret), rules(role), subjects등 다른 속성 사용

 

모든 쿠버네티스 오브젝트는 Labels과 Annotaions 메타데이터를 가질 수 있음(선택적인 데이터)

 

Labels는 오브젝트를 식별하기 위한 목적

- Label Selector기능을 통해서 특정 오브젝트들을 필터링하기 위한 목적으로 사용된다.

- 검색 / 분류 / 필터링 등의 목적으로 사용

 

Annotations

- 식별이 아닌 다른 목적으로 사용

- 쿠버네티스의 앤드온 서비스들의 설정용도이다.

- Log Agent 역활의 애드온을 클러스터에 설치시 각각의 pod의 로그 수집을 어떻게 할지등을 설정할 수 있다.

 

kubectl 명령형과 선언형 방식

명령형 방식은 수행하고자 하는 액션을 지시

 

여러 명령어를 모두 알고 있어야하기 때문에 힘듬

적은 리소스에 대해서 빠르게 처리 간으

```

# ubuntu:focal 이미지로 ubuntu 파드 생성 (bash 명령어)

kubectl run -it ubuntu --image ubuntu:focal bash

 

# grafana Deployment 오브젝트에 대해 NodePort 타입의 Service 오브젝트 생성 (노드에 포트 개방)

kubectl expose deployment grafana --type=NodePort --port=80 --target-port=3000

 

# frontend Deployment의 www 컨테이너 이미지를 image:v2로 변경

kubectl set image deployment/frontend www=image:v2

 

# frontend Deployment를 리비전 2로 롤백

kubectl rollout undo deployment/frontend --to-revision=2

```

 

선언형방식

상태를 정의하면 특정시스템이 원하는 상태를 정의한 것이기 때문에 시스템이 알아서 그 환경에 맞게 시스템 구성해준다. 코드로 관리 가능 = gitOps 활용가능

변경사항에 대한 감사 용이, 코드리뷰를 통한 협업

멱등성 보장

코드로 관리하기 때문에 리소스에 대해서도 매니페스트 관리 방법에 따라 빠르게 처리 가능

알아야 할 명령어 수가 적음

 

```

# deployment.yaml에 정의된 쿠버네티스 오브젝트 클러스터에 반영

kubectl apply -f deployment.yaml

 

# deployment.yaml에 정의된 쿠버네티스 오브젝트 제거

kubectl delete -f deployment.yaml

 

# 현재 디렉토리의 kustomization.yaml 파일을 쿠버네티스 오브젝트 클러스터에 반영 (추후 강의)

kubectl apply -k ./

```

 

```

cd ~/fastcampus-devops/3-docker-kubernetes/5-k8s-start

vi imperative.sh

```

실행해보니 위와같이 뜬다.

```

./clean.sh

```

 

```

kubectl get all

```

 

CRUD : 선언형 방식

나머지는 kubectl이 다양한 명령어를 지원(ssh 접속, log)해주기에 이때는 선언형이 불가능해서 명령형을 사용해주자

 

---

 

API 리소스 - Pod

 

- 파드

Pod는 쿠버네티스가 컨테이너를 다루는 기본 단위

docker, docker-compose에서는 컨테이너가 기본단위였는데 k8s는 컨테이너를 직접 생성할 수 없다. 그래서 pod를 사용해서 제작해야 한다. pod는 여러개의 컨테이너로 구성될 수 있음. 동일 파드 내 컨테이너는 여러 리눅스 네임스페이스를 공유한다. => 네트워크 네임스페이스 공유(동일 Pod내의 컨테이너는 동일 IP 사용). 네트워크 IP는 하나만 가진다.

 

사용자가 파드를 직접 관리하는 경우는 거의 없음

파드를 관리하는 API 리소스들을 이용하게 된다.

 

```

# 클러스터 안에 파드 목록 확인

kubectl get pod

 

# 특정 파드 상태 확인

kubectl describe pod hello

 

# 특정 파드에 명령어 전달

kubectl exec -it hello bash

 

# 특정 파드 로그 확인

kubectl logs pod/hello

```

 

```

cd ~/fastcampus-devops/3-docker-kubernetes/6-k8s-pod

```

kubectl api-resources | grep pod

 

yaml로 실행시킨 pod는 똑같이 yaml 파일을 이용해서 지워야 하나보다.

```

kubectl delete -f service.yaml

```

실행중인 pod보기

```

kubectl get pod

```

 

```

kubectl apply -f pod.yaml

```

```

# 더 많은 pod의 정보를 볼 수 있다.

kubectl get pod -o wide

# 아예 모든 정보를 가져올 수 있기 때문에 포트번호도 알 수 있지만 접근은 불가능하다.

kubectl get all

```

하지만 위의 명령어로는 나온 pod의 ip로 직접 접근이 불가능한게 우리가 해당 pod에 위치해있지 않기 때문이다.

구조상 위와같이 있다. 그래서 우리가 접근을 못하는 것이다.

 

이를 접근하기 위해

```

minikube ssh

curl 172.17.0.3

```

결과 위와같이 정보를 가지고 온것을 확인할 수 있다.

 

```

# 특정 파드로 접근

kubectl run -it debug --image=posquit0/doraemon bash

```

 

생성된 이미지는 같은 클러스터 안에 있기에 여전히 172.17.0.3의 정보를 가지고 올 수 있다.

 

```

kubectl exec -it hello sh

kubectl logs hello

```

로그를 확인할 수 있다.

 

- 멀티 컨테이너 파드와 사이드카 패턴

동일 파드 내 컨테이너는 모두 같은 노드에서 실행

 

```

# 특정 파드 로그 확인(-c : 컨테이너 지정)

kubectl logs pod/hello -c debug

 

# 특정 파드에 명령어 전달

kubectl exec -it hello -c debug bash

```

 

사이드카 패턴 : 메인 컨테이너를 보조하는 컨테이너와 같이 실행하는 구조

- Filebeat와 같은 로그 에이전트로 파드 로그 수집

- Envoy 와 같은 프록시 서버로 서비스메시 구성

- Vault Agent와 같은 기밀 데이터 전달 목적

- Nginx의 설정 리로드 역활 에이전트 구성

 

```

cat multi.yaml

```

기존의 것을 삭제해주자

 

pod가 두개 생긴것을 확인할 수 있다.

 

그리고 조금더 자세히보면 컨테이너 중에 nginx와 debug 컨테이너가 실행중인것을 확인할 수 있다.

 

```

kubectl exec -it hello sh

```

 

```

# 아래와 같이 pod를 직접 지정해주지 않으면 default pod로 이동하게 된다.

kubectl exec -it hello -c debug sh

```

 

위와같이 nginx로 접근이 가능한데 debug와 nginx가 같은 네트워크에 묶이어 nginx가 80번 포트를 열었기 때문에 가능한 일이다.

 

---

 

API 리소스 - ReplicaSet

728x90