4. 쿠버네티스 Pod, Deployment, Service 와 예제.
쿠버네티스 Pod, Deployment, Service 와 예제.
쿠버네티스의 이해를 위해 핵심 요소인 Pod, Deployment, Service 에 대해서 먼저 알아보고 Docker Desktop 을 이용한 싱글노드기반의 쿠버네티스 클러스터를 구축하고 앱을 배포해보겠습니다.
Introduction
Pod, Deployment, Service 은 쿠버네티스의 핵심 개념으로 이 개념만으로도 테스트용 서버를 배포할 수 있습니다.
Pod(파드) 에 대한 이해
Pod 는 컨테이너들의 집합 으로 구성되며 쿠버네티스에서 가장 작고 기본적인 배포 가능한 단위입니다.
- Pod 는 하나 이상의 컨테이너로 구성되며 같은 Pod 의 컨테이너들간 공유 스토리지 및 네트워크, 실행방법 들을 정의할 수 있습니다.
- Pod 는 그 자체로 직접 배포가 가능하지만 일반적으로 자주 사용되는 방법은 아닙니다. 일반적으로 Pod 는 상위 개념인 Deployment 를 통해 배포됩니다.
Yaml 파일을 통한 Pod 배포
yaml 을 통한 pod 설정은 아래와 같습니다. 가장 기본적인 nginx 앱의 pod 를 배포하는 yaml 파일입니다.
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: web
spec:
containers:
- name: nginx-container
image: nginx:latest
ports:
- containerPort: 80
필드 설명
- apiVersion: 오브젝트를 생성하는 데 사용하는 쿠버네티스 API 의 버전을 지정합니다. 쿠버네티스에게 yaml 파일을 어떻게 해석할지에 대한 정보를 제공합니다.
- kind: 어떤 종류의 쿠버네티스 리소스인지 지정합니다. 여기서는 Pod 를 생성하므로 Pod 라고 지정합니다.
- metadata: Pod 를 고유하게 식별하는 데 도움이 되는 데이터를 포함합니다. 여기서는 이름이 nginx-pod 이고 레이블이 app: web 으로 지정되어 있습니다. 추후 이 Pod 를 찾거나 다른 리소스와 연결할 때 사용됩니다
- spec: Pod 의 구성을 지정한다.
- containers: pod 가 실행할 컨테이너의 정보를 포함합니다
- name: 컨테이너의 이름이며, pod 내에서 고유해야 한다.
- image: 컨테이너에 사용할 도커 이미지를 지정합니다.
- port: 컨테이너가 노출하는 네트워크 포트 정보를 포함합니다.
아래 명령어를 통해 Pod 를 배포할 수 있습니다.
kubectl apply -f nginx-pod.yaml
Deployment
Deployment 는 Pod 을 관리하는 상위 수준의 개념입니다. Deployment 는 Pod 의 개수, 업데이트 전략, 롤백 등을 관리합니다.
특징
- Replication: 지정된 수의 Pod 복제본이 항상 실행되도록 합니다.
- Update & Rollback: Pod 의 내용이 수정되면 롤링 업데이트를 통해 이전 Pod 를 업데이트하고 문제 발생시 이전 버전으로 롤백할 수 있습니다.
- Self-healing: Pod 실행이 중지되거나 비정상적으로 종료되면 자동으로 Pod 를 재생성합니다.
Yaml 파일을 통한 Deployment 배포
Deployment 는 Pod 의 내용을 spec.template 아래 포함합니다. 따라서 Pod 을 미리 만들어 배포할 필요는 없습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
필드 설명
- apiVersion: Deployment 리소스를 생성하는 데 사용하는 쿠버네티스 API 의 버전을 지정합니다.
- kind: Deployment 리소스를 지정합니다.
- metadata:
- name:: Deployment 의 이름입니다.
- labels: Key-Value 값으로, Deployment 의 하위 집합을 구성하고 리소스를 선택하는 데 사용됩니다.
- spec:
- replicas: Pod 의 복제본 개수를 지정합니다.
- selector.matchLabels: Deployment 가 관리하게 될 Pod 에 대한 레이블이며 template.metadata.labels 값과 일치해야합니다.
- template: Pod 에 대한 정보를 포함합니다.
- metadata.labels: Pod 의 레이블이며, Deployment 의 selector.matchLabels 와 일치해야합니다.
- spec: Deployment 에서 실행할 Pod 의 구성을 지정합니다.
- containers: Pod 에서 실행할 컨테이너의 정보를 포함합니다.
- name: 컨테이너의 이름이며, pod 내에서 고유해야 합니다.
- image: 컨테이너에 사용할 도커 이미지를 지정합니다.
- port: 컨테이너가 노출하는 네트워크 포트 정보를 포함합니다.
헷갈리기 쉬운 필드들이 있어 한 번더 설명하겠습니다.
- metadata.labels: Deployment 에 적용되는 값이며 쿠버네티스 리소스와 구성하거나 식별 할 때 사용됩니다.
- spec.selector.matchLabels: Deployment 가 관리하는 Pod 의 레이블이입니다.
- spec.template.metadata.labels: 구서앟려는 Pods 에 대한 레이블이며 spec.selector.matchLabels 값과 일치해야 합니다.
Service
Service 는 Deployment 를 통해 배포된 Pod 들을 대상으로 하는 통신에 대한 추상적인 방법을 제공합니다. 쿠버네티스 안에서 각 Pod 집합들에 대해 접근 가능한 방법을 제공하며 외부와의 통신을 위한 효율적인 관리 방법도 제공합니다.
특징
- Stable IP Address: 각 Service 는 고유한 IP 주소를 가지며 Pod 의 IP 주소가 변경되어도 Service 의 IP 주소는 변경되지 않습니다. 따라서 Pod 내용이 변경되더라도 동일한 Service 를 통해 Pod 에 접근할 수 있습니다.
- Load Balancing: Service 는 Pod 들의 IP 주소를 라운드 로빈 방식으로 분산시킵니다.
- Service Types: Service 는 ClusterIP, NodePort, LoadBalancer 등의 타입을 가질 수 있습니다.
Service Type
- ClusterIP: Service 를 클러스터 내부에 노출시킵니다. 이 타입은 기본값이며, 클러스터 내부에서만 접근할 수 있습니다.
- NodePort: Service 를 클러스터 외부에 노출시킵니다. 각 노드의 IP 주소와 지정된 포트를 통해 접근할 수 있습니다.
- LoadBalancer: 클라우드의 로드밸런서 예를 들면 AWS ALB 를 사용하여 Service 를 외부에 노출시킬 수 있습니다.
Yaml 파일을 통한 Service 배포
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
필드 설명
- apiVersion: Service 리소스를 생성하는 데 사용하는 쿠버네티스 API 의 버전을 지정합니다.
- kind: Service 리소스를 지정합니다.
- metadata: Service 의 이름과 같은 메타데이터를 지정합니다.
- spec:
- selector: Service 의 목적 Pod 의 레이블이며, Deployment 와 같이 쓴다면 selector.matchLabels 와 일치해야합니다.
- ports: Service 가 노출할 포트 정보를 포함합니다.
- protocol: 통신 프로토콜로 TCP 또는 UDP 를 지정합니다.
- port: 80: Service 가 노출할 포트입니다.
- targetPort: 80: Service 의 목적 Pod 의 포트입니다. Port 로 들어온 요청을 targetPort 로 전달합니다.
앱 통해 쿠버네티스 Pod, Deployment, Service 이해하기
간단한 Order, Price 앱을 배포하여 쿠버네티스 Pod, Deployment, Service 를 이해해보겠습니다. 아래 기능을 실행할 수 있는 환경이 필요합니다.
- Docker
- Kubernetes
Docker, Kubernetes 설치
노드(일반적으로 머신)가 하나인 쿠버네티스 클러스터를 구축하기 위해 가장 간단한 방법으로 Docker Desktop 을 사용하겠습니다. Docker Desktop 은 링크 에서 설치가이드를 확인할 수 있습니다. Docker Desktop 을 설치하여 설정 GUI 를 통해 Kubernetes 를 활성화하면 쿠버네티스 클러스터가 설치되고 실행됩니다.
터미널에서 아래 명령어를 통해 정상적으로 실행이 되었는지 확인해주세요.
kubectl get pods -A
앱 구성
Node.js 를 사용하여 간단한 서버를 구성하겠습니다. 편한 프레임워크와 언어로 같은 동작을 하는 서버를 구성해도 무방합니다.
- Order: 주문을 생성하고 주문 목록을 조회하는 서버입니다. 외부로 노출됩니다.
- Price: 주문에 대한 가격을 계산하는 서버입니다. 외부로 노출되지 않고 쿠버네티스 클러스터 안에서 실행됩니다.
앱간 api 를 간단하게 테스트하는 용도로
Client -> Order -> Price 서버에 대한 요청을 진행하여 응답을 확인하도록 하겠습니다.
Order 서버 코드 와 배포
Order 서버의 설명.
- 외부에서 접근하기 위해 Service 리소스 정의에서 접근할 외부 노출용도의 NodePort 지정합니다. 예를 들어 http://localhost:31000/create 로 접근하면 오더앱에 접근할 수 있습니다.
- /create POST 요청을 받으면 Price 서버에 GET 요청을 보내용 결과를 반환합니다.
- Price 서버에 대한 요청이 성공하면 “request for price server is successful” 메시지를 반환하고 실패하면 “request for price server failed” 메시지를 반환합니다.
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;
app.use(express.json());
// POST endpoint
app.post('/create', async (req, res) => {
const resultMessage = ["order server request received"];
try {
const response = await axios.get("http://price-service/calculate").then();
resultMessage.push("request for price server is successful");
} catch (error) {
resultMessage.push("request for price server failed");
}
res.json(resultMessage);
});
// Start the server
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
docker 이미지 생성
위 앱을 쿠버네티스로 실행하기 위해서는 도커이미지로 만들어야 합니다. 앱의 루트 디렉토리에 Dockerfile 을 생성하고 아래 내용을 추가합니다.
# 베이스 이미지 지정
FROM node:20
# 컨테이너 내에서 작업 디렉토리 설정
WORKDIR /usr/src/app
# package.json 및 package-lock.json 복사
COPY package*.json ./
# 의존성 설치
RUN npm install
# 앱 소스 복사
COPY . .
# 앱이 3000 포트에서 바인딩되므로, EXPOSE 를 사용하여 docker 실행시 컨테이너에 매핑되도록 함
EXPOSE 3000
# 앱 실행 명령어 정의
CMD [ "node", "app.js" ]
아래 명령어를 통해 이미지의 이름과 태그를 지정하여 이미지를 생성합니다. 이미지는 로컬에 저장됩니다.
docker build -t order:1.0 .
쿠버네티스 배포
Order 서비스를 Deployment 리소스를 통해 먼저 배포하고 Service 를 통해 이 앱에 대한 접근을 가능하도록 하겠습니다. Service 는 NodePort 타입으로 외부로 노출하겠습니다. 저는 31000 포트를 통해 외부에서 이 Pod 에게 접근할 수 있도록 하겠습니다. 예를들어 localhost:31000 으로 접근시 Order 앱에 접근할 수 있습니다.
# order-config.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-deployment
spec:
replicas: 2
selector:
matchLabels:
app: order
template:
metadata:
labels:
app: order
spec:
containers:
- name: order
image: order:1.0
imagePullPolicy: IfNotPresent # 이미지가 없다면 다시 다운받아서 사용하도록 함
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
type: NodePort
selector:
app: order
ports:
- port: 80
targetPort: 3000
nodePort: 31000 # 선택사항 (30000-32767) 범위 안에서 포트를 설정합니다. 포트를 설정하지 않으면 이 범위 안에서 랜덤으로 포트를 지정합니다.
아래 명령어를 통해 배포를 진행합니다.
kubectl apply -f order-config.yaml
아래 명령어로 배포가 정상적으로 되었는지 확인합니다.
kubectl get pods
Price 서버 코드
Price 서버의 개략설명.
- 외부에서 접근할 수 없으며 Service type 은 ClusterIP 로 설정하여 클러스터 내부에서만 접근가능 하도록 설정합니다.
- /calculate GET 요청을 받으면 정상적으로 응답받았다는 메시지를 반환합니다.
const express = require('express');
const app = express();
const port = 3000;
const axios = require('axios');
// Middleware to parse JSON bodies
app.use(express.json());
// POST endpoint
app.get('/calculate', async (req, res) => {
console.log("price server request received");
res.json("price server request received");
});
// Start the server
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
docker 이미지 생성
오더앱과 같은 docker 파일을 사용합니다
FROM node:20
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "node", "app.js" ]
이미지와 태그는 price:1.0 라는 이름으로 생성합니다.
아래 명령어를 통해 이미지의 이름과 태그를 지정하여 이미지를 생성합니다.
docker build -t price:1.0 .
쿠버네티스 배포
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: price-deployment
spec:
replicas: 2
selector:
matchLabels:
app: price
template:
metadata:
labels:
app: price
spec:
containers:
- name: price
image: price:1.0
imagePullPolicy: IfNotPresent # Specifying the image pull policy here
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: price-service
spec:
type: ClusterIP
selector:
app: price
ports:
- port: 80
targetPort: 3000
아래 명령어를 통해 배포를 진행합니다.
kubectl apply -f price-config.yaml
아래 명령어로 배포가 정상적으로 되었는지 확인합니다.
kubectl get pods
테스트
먼저 아래 명령어 모든 앱이 정상적으로 실행중인지 확인합니다. 각 pods 들의 STATUS 가 Running 인지 확인합니다.
kubectl get pods
NAME READY STATUS RESTARTS AGE
order-deployment-6c5696f949-2hkvr 1/1 Running 0 4m19s
order-deployment-6c5696f949-dp9nx 1/1 Running 0 4m20s
price-deployment-867d475869-6gzm2 1/1 Running 0 4m15s
price-deployment-867d475869-pf97h 1/1 Running 0 4m14s
여러분이 가지고 있는 http 클라이언트를 통해 요청을 시도합니다. 저는 httpie 를 사용하겠습니다.
http POST http://localhost:31000/create
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 74
Content-Type: application/json; charset=utf-8
Date: Sat, 03 Feb 2024 08:00:52 GMT
ETag: W/"4a-Wl4KhrKWi/LdLIyzkaXo0Nqwgu0"
Keep-Alive: timeout=5
X-Powered-By: Express
[
"order server request received",
"request for price server is successful"
]
위와 같은 응답을 받았다면 정상적으로 배포가 완료된 것입니다. 만약 정상적으로 실행이 되지 않는다면 아래 명령어를 통해 로그를 확인해보세요.
kubectl get pods # pods 이름을 확인한후
kubectl logs -f <팟식별값(NAME)>
마치며
쿠버네티스를 사용하여 Pod, Deployment, Service를 이해하고 실제 애플리케이션을 배포하는 과정을 단계별로 살펴보았습니다. 이를 통해 쿠버네티스의 핵심 개념과 운영 방법에 대해 깊이 있는 이해를 할 수 있었을 것입니다. 이번 실습을 통해 얻은 지식은 이후 글에서 다룰 쿠버네티스의 다양한 주제에 대한 기반으로 사용될 것입니다.