프로젝트 개요
이번 프로젝트는 Naver Cloud Kubernetes Service(NKS)를 활용하여, 간단한 3-Tier 애플리케이션을 Kubernetes의 기본 리소스만으로 직접 배포하고, 이를 통해 Kubernetes 구성 요소에 대한 이해를 높이는 것에 중점을 두고 있다.
Frontend, Backend, Database 각 구성 요소는 개별적으로 컨테이너화되어 Kubernetes 클러스터에 배포되며, 서비스 간의 연결과 통신이 이루어지도록 하는것이 목표이다.
아키텍처

애플리케이션 기술 스택
- Frontend: React
- Backend: Node.js(Express)
- Database: MySQL
Kubernetes 리소스
- Deployment
- Service
- PersistentVolume (PV)
- PersistentVolumeClaim (PVC)
- Secret
- Ingress
NCP 사용 리소스
- Naver Cloud Kubernetes Service(NKS) - 1.29.9 version
- Container Registry
- Applcation Load Balancer
- Global DNS
아키텍처 흐름 및 주요 작업 단계
간단한 게시판 형태의 애플리케이션 소스코드를 Kubernetes 환경에 배포할 예정이다. 일반적으로는 관리형 데이터베이스를 사용하는 경우가 많지만, 본 프로젝트에서는 MySQL을 컨테이너로 직접 구동하여 Kubernetes 리소스와 함께 구성할 예정이다.
각 컴포넌트는 개별 Pod로 구성되며, 해당 Pod는 각각의 Service(SVC)와 연결된다. 이후, Ingress를 통해 외부와의 접점을 구성한다.
또한, PVC를 이용한 볼륨 관리, Secret을 통한 민감 정보 처리, DNS 설정을 통한 도메인 연결까지 진행하여, SSL 인증서를 제외한 모든 주요 구성 요소를 테스트해보는 것을 목표이다. Node는 하나만 띄워 테스트를 진행하였다.
- Docker Image build & Container Registry Push
→ 애플리케이션을 Docker 이미지 생성
→ 이미지 저장소인 Container Registry에 생성한 이미지 Push - Naver Cloud Kubernetes Service(NKS) 클러스터 생성
→ NCP에서 Kubernetes 클러스터 생성 - kubectl 설치 및 NKS 접속
→ 클러스터와 통신하기 위한 kubectl 설치 및 설정 - Kubernetes 매니페스트 설정
→ Deployment, Service, Ingress 등 리소스 정의 파일 작성 - 글로벌 DNS 도메인 연결
→ DNS매핑을 통한 도메인 연결
Github
1. Docker Image build & Container Registry Push
Docker 이미지를 저장할 Container Registry를 먼저 생성한 후, 빌드한 이미지를 해당 레지스트리에 Push해야 한다.
NCP 서버 또는 로컬 환경에서 Dockerfile을 사용해 이미지를 빌드한 뒤, 생성한 Container Registry 주소로 태그를 지정하고 docker push 명령어를 통해 업로드하면 된다.
<소스코드 설명은 오픈 소스코드를 가져온것이므로 생략하도록 하겠습니다>
Frontend Dockerfile
위 Dockerfile은 멀티 스테이지 빌드(Multi-stage Build) 방식을 사용하여 React 프론트엔드 애플리케이션의 이미지 크기를 줄이고 최적화하는 구조로 작성되어 있다.
첫 번째 단계에서는 node:21-alpine3.17 이미지를 기반으로 애플리케이션을 빌드한다. /app 디렉토리에 소스를 복사한 후, npm install, npm update, npm run build를 실행하여 정적 파일을 생성한다.
두 번째 단계에서는 동일한 이미지를 사용하되, 빌드된 결과물만 복사하여 실행 환경을 구성합니다. 이후 npm start 명령어를 통해 Node.js 서버를 실행한다.
이처럼 멀티 스테이지 빌드를 사용하면 이미지 크기를 줄이고 실행 환경을 구성할 수 있다.
### Frontend 구조 ###
root@dj-master:~/kubernetes/Frontend# ls
Dockerfile package.json package-lock.json public README.md src
### 멀티 스테이지 빌드를 통한 이미지 경량화 ###
cat Dockerfile
# 1단계: Node.js를 사용하여 애플리케이션 빌드
FROM node:21-alpine3.17 AS build
WORKDIR /app
COPY . .
ENV NODE_OPTIONS=--openssl-legacy-provider
RUN npm install
RUN npm update
RUN npm run build
# 2단계: 애플리케이션 실행
FROM node:21-alpine3.17
WORKDIR /app
COPY --from=build /app /app
ENV NODE_OPTIONS=--openssl-legacy-provider
CMD ["npm", "start"]
Backend Dockerfile
백엔드는 프론트엔드와 마찬가지로 Node.js 환경에서 동작하므로, node:14 이미지를 기반으로 구성된다. package*.json 파일을 먼저 복사한 뒤 npm install을 통해 필요한 모듈을 설치하고, 전체 소스를 복사한 후 node server.js 명령어를 통해 애플리케이션을 실행한다.
데이터베이스는 Docker Hub에서 제공하는 mysql:latest를 기반으로 하며, 기본 포트인 3306을 외부에 노출한다. MySQL 데이터는 /var/lib/mysql 경로에 저장되고, 해당 디렉토리를 VOLUME)으로 지정하여 컨테이너 재시작 시에도 데이터가 유지될 수 있도록 구성하였다.
### backend 구조 ###
root@dj-master:~/kubernetes/backend# ls
Dockerfile package.json package-lock.json server.js
### backend Dockerfile ###
root@dj-master:~/kubernetes/backend# cat Dockerfile
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD [ "node" , "server.js" ]
root@dj-master:~/kubernetes/Database# cat Dockerfile-Database
FROM mysql:latest
# Expose the MySQL port
EXPOSE 3306
# Define a named volume for MySQL data
VOLUME /var/lib/mysql
# Command to run the MySQL server
CMD ["mysqld"]
2. Docker Build & Container Registry Push
이미지 저장소로는 Naver Cloud의 Container Registry를 사용하고 있으며, 이는 AWS의 ECR(Elastic Container Registry)와 같다고 보면 된다. 해당 레지스트리는 외부에서 직접 접근할 수 없도록 Private Registry로 구성되어 있다.
Container Registry에 Docker 이미지를 Push하기 위해서는 먼저 해당 Registry 주소로 로그인해야 한다. 이때 Access Key ID와 Secret Key를 사용하여 인증 과정을 거쳐야 한다. 인증이 완료되면, 빌드한 Docker 이미지에 Registry 주소를 포함한 Tag를 지정하고, docker push 명령어를 통해 이미지를 업로드할 수 있다.
아래 사진은 프론트엔드, 백엔드, 데이터베이스(MySQL)를 각각 개별 이미지로 구분하여 관리하고 있는 모습이다.
root@dj-master:~/kubernetes/# docker login dj-kubetest-container.kr.ncr.ntruss.com
Authenticating with existing credentials...
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
Login Succeeded
root@dj-master:~/kubernetes/# cd frontend
root@dj-master:~/kubernetes/frontend/# docker build -t dj-kubetest-container.kr.ncr.ntruss.com/backend-image:latest .
root@dj-master:~/kubernetes/frontend# cd ..
root@dj-master:~/kubernetes/# cd backend/
root@dj-master:~/kubernetes/backend# docker build -t dj-kubetest-container.kr.ncr.ntruss.com/backend-image:latest .
root@dj-master:~/kubernetes/frontend# cd ..
root@dj-master:~/kubernetes/# cd database/
root@dj-master:~/kubernetes/database# docker build -t dj-kubetest-container.kr.ncr.ntruss.com/mysql-image:latest .
### 생성된 Docker Image ###
root@dj-master:~/kubernetes/database# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dj-kubetest-container.kr.ncr.ntruss.com/frontend-image latest e388be37cf5c 5 days ago 253MB
dj-kubetest-container.kr.ncr.ntruss.com/backend-image latest b488de0e29d6 5 days ago 694MB
dj-kubetest-container.kr.ncr.ntruss.com/mysql-image latest 6dca13361869 5 days ago 463MB
### Docker login ###
root@dj-master:~/kubernetes/database# docker login dj-kubetest-container.kr.ncr.ntruss.com
Authenticating with existing credentials...
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
Login Succeeded
API 인증키가 필요하며, Username에 Access Key Id를 Password에 Secret Key를 사용해주어야한다.
### Docker Push ###
root@dj-master:~/kubernetes/database# docker push dj-kubetest-container.kr.ncr.ntruss.com/frontend-image:latest
root@dj-master:~/kubernetes/database# docker push dj-kubetest-container.kr.ncr.ntruss.com/backend-image:latest
root@dj-master:~/kubernetes/database# docker push dj-kubetest-container.kr.ncr.ntruss.com/mysql-image:latest
Docker 빌드와 푸시 작업을 완료한 후, Container Registry에서 이미지를 확인해보니 정상적으로 업로드된 것을 확인할 수 있었다. 이제 이 이미지를 사용하여 Deployment를 통해 Kubernetes에 배포할 예정이다.

3. Naver Cloud Kubernetes Service(NKS) 클러스터 생성
NKS를 생성하기 위해서는 NKS 클러스터를 배치할 Private Subnet, 로드밸런서를 위한 Public Subnet, 그리고 애플리케이션이 위치할 또 다른 Private Subnet이 각각 하나씩 필요하다. 이번 구성에서는 ALB를 Public Subnet에 배치하고, Ingress를 통해 Private Subnet에 위치한 애플리케이션과 외부 간의 통신을 처리할 예정입니다. 테스트 목적이기 때문에 ACG는 모든 포트를 오픈해주었지만, 추후에는 필요한 포트만 선택적으로 오픈하여 보안에 신경써야한다.

kubectl 설치 및 NKS 접속
NKS 클러스터에 접속하려면 먼저 kubectl을 설치하고, kubeconfig를 설정한 다음 NKS의 UUID 경로를 통해 접속해야 한다.
이 작업은 NCP 서버에 해도 되고, 로컬 환경에 해도 상관없다. 현재 편의성을 고려해 로컬 환경에서 설정을 진행하였다.
kubectl 설치 방법은 쿠버네티스 공식 문서에 운영체제별로 정리되어 있으니 참고하면 된다.
ncp-iam-authenticator 설치
NKS는 ncp-iam-authenticator를 통해 AWS와 비슷한 구조로 IAM 기반 인증을 제공한다.
kubectl 명령어를 사용하려면 ncp-iam-authenticator를 먼저 설치하고, 이를 인증에 사용할 수 있도록 kubeconfig를 수정해줘야 한다.
# ncp-iam-authenticator다운로드
$ curl -o ncp-iam-authenticator -L https://github.com/NaverCloudPlatform/ncp-iam-authenticator/releases/latest/download/ncp-iam-authenticator_linux_amd64
# SHA-256 다운로드
$ curl -o ncp-iam-authenticator.sha256 -L https://github.com/NaverCloudPlatform/ncp-iam-authenticator/releases/latest/download/ncp-iam-authenticator_SHA256SUMS
# 권한 수정
$ chmod +x ./ncp-iam-authenticator
# PATH 추가 (예시)
$ export PATH=$PATH:$HOME/bin
# 동작 테스트
root@dj-master:~/kubernetes# ncp-iam-authenticator
cli written to authenticate with iam in ncloud kubernetes service
Usage:
ncp-iam-authenticator [command]
Available Commands:
create-kubeconfig Get Kubeconfig to access kubernetes
help Help about any command
token Authenticate using SubAccount and get token for Kubernetes
update-kubeconfig update Kubeconfig to access kubernetes
version Show the version info of the ncp-iam-authenticator
Flags:
--credentialConfig string credential config path (default : ~/.ncloud/configure)
--debug debug option
-h, --help help for ncp-iam-authenticator
--profile string profile
Use "ncp-iam-authenticator [command] --help" for more information about a command.
root@dj-master:~/kubernetes/MERN-Stack-Project/DevOps/docker#
Kubeconfig 설정
ncp-iam-authenticator를 사용하면 IAM 인증이 적용된 kubeconfig를 통해 클러스터에 접근할 수 있다.
Access Key와 Secret Key는 마이페이지 → 인증키 관리 화면에서 확인 가능하다.
# OS 환경변수나 configure파일에 API 키를 설정.
$ export NCLOUD_ACCESS_KEY=ACCESSKEYIDACCESSKEY
$ export NCLOUD_SECRET_KEY=SECRETACCESSKEYSECRETACCESSKEYSECRETACCE
$ export NCLOUD_API_GW=https://ncloud.apigw.ntruss.com
Cluster 접속
생성된 kubeconfig 파일을 이용해 NKS 클러스터 UUID를 기반으로 클러스터에 접속할 수 있다. 이때 클러스터에 접속할 때마다 해당 명령어를 반복해서 입력해야 하므로, 편의상 alias로 등록해두는 것이 좋다.
# kubeconfig파일 생성
$ ncp-iam-authenticator create-kubeconfig --region KR --clusterUuid <cluster-uuid> --output kubeconfig.yaml
정상적으로 Node가 확인되는 것을 볼 수 있다.
root@dj-master:~/kubernetes# kubectl get nodes
NAME STATUS ROLES AGE VERSION
kdj-w-5xni Ready <none> 12d v1.29.9
4. Database Kubernetes Manifest
namespace.yaml
애플리케이션 리소스를 논리적으로 격리하기 위해 별도의 namespace를 구성하였다. (mern stack)
현재는 프론트엔드, 백엔드, 데이터베이스를 모두 하나의 네임스페이스에 배포했지만, 프로젝트를 마무리하고 보니 각 구성 요소를 별도의 네임스페이스에 배포하는 방식이 관리 측면에서 더 효율적일 것 같다는 생각이 들었다.
apiVersion: v1
kind: Namespace
metadata:
name: mern
labels:
name: mern
pv.yaml
PersistentVolume은 Kubernetes에서 데이터를 지속적으로 저장할 수 있도록 해주는 리소스로, 파드(Pod)가 삭제되더라도 데이터를 유지할 수 있게 해준다. 하지만 단독으로는 사용할 수 없으며, 반드시 PersistentVolumeClaim(PVC)와 함께 사용해야 한다. PVC는 사용자가 필요한 저장소 용량과 접근 모드를 정의하여 PV에 요청을 보내고, 실제 저장소를 바인딩해 사용할 수 있게 해준다.
/mnt/data 경로를 실제 디스크 위치로 사용하는 hostPath 타입이다.
- accessModes: ReadWriteOnce는 하나의 노드에서만 읽기/쓰기가 가능하다는 의미이다.
- persistentVolumeReclaimPolicy: Retain은 PVC가 삭제되더라도 PV 자체는 삭제하지 않고 보존하도록 설정하는 정책이다.
이렇게 구성된 PV는 이후 PVC에 의해 바인딩되어 데이터 저장에 활용된다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: db-pv
namespace: mern
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: standard
hostPath:
path: /mnt/data
pvc.yaml
앞서 설명한 것처럼, PVC는 위에서 정의한 PV에 저장소를 요청하는 리소스이다. 파드가 필요로 하는 저장소의 사양을 정의하면, 해당 조건에 맞는 PV가 존재할 경우 자동으로 바인딩되어 연결된다. 이번에는 테스트 목적으로 최소 용량인 1Gi로 설정하여 테스트를 진행하였다.
또한, storageClassName은 PV와 PVC가 동일한 스토리지 클래스를 사용할 수 있도록 반드시 일치시켜야 한다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-pvc
namespace: mern
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: standard
Secret.yml
Secret은 민감한 정보를 안전하게 저장하고 관리하는 데 사용되는 Kubernetes 리소스이다. 주로 데이터베이스의 사용자명, 비밀번호, 데이터베이스 이름 등의 정보에 대해 사용할 수 있다. 아래는 DB 설정에 필요한 변수 값들을 base64로 인코딩하여 저장한 것이다.
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: mern
type: Opaque
data:
host: ZGF0YWJhc2U= #database (base64 인코딩된 값)
user: cm9vdA== #root (base64 인코딩된 값)
password: a2ltZG9uZ2p1MTIz #kimdongju123 (base64 인코딩된 새 비밀번호)
database: a2ltZG9uZ2p1 #kimdongju (base64 인코딩된 값)
또한, DB뿐만 아니라 Container Registry에서 이미지를 가져올 때도 인증 정보(Access Key / Secret Key)가 필요하기 때문에,
다음과 같이 별도의 Secret을 하나 더 생성해주었다.
kubectl create secret docker-registry myregistrykey \
--docker-server=<"자신의 컨테이너 Registry 주소" \
--docker-username= <"자신의 Accessekey"> \
--docker-password=<"자신의 Secretekey"> \
--namespace=mern
Deployment.yaml
Deployment는 Kubernetes에서 애플리케이션의 배포, 업데이트, 파드 복제 및 자동 복구 등을 관리하는 핵심 리소스이다.
아래는 데이터베이스를 위한 Deployment manifest이다.
먼저, 데이터베이스의 비밀번호와 이름은 db-credentials라는 Secret을 통해 관리되며, 해당 값들은 환경 변수로 주입된다.
또한, Private Container Registry에서 이미지를 가져오기 위해 myregistrykey라는 시크릿을 imagePullSecrets에 등록하였다.
데이터는 db-pvc라는 PersistentVolumeClaim을 통해 /var/lib/mysql 경로에 영구적으로 저장되도록 구성하였다.
이러한 PV/PVC 설정을 통해 파드가 재시작되더라도 데이터가 유지될 수 있다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: database
namespace: mern
labels:
role: database
env: dev
spec:
replicas: 1
selector:
matchLabels:
role: database
template:
metadata:
labels:
role: database
spec:
imagePullSecrets:
- name: myregistrykey # NCP 인증 Secret 값
containers:
- name: database
image: dj-kubetest-container.kr.ncr.ntruss.com/mysql-image:latest
imagePullPolicy: Always
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: db-credentials
key: database
volumeMounts:
- name: db-storage
mountPath: /var/lib/mysql
volumes:
- name: db-storage
persistentVolumeClaim:
claimName: db-pvc
Service.yaml
Service 리소스는 Kubernetes에서 파드 간의 네트워크 통신을 관리하는 역할을 한다.
클러스터 내에서 파드들이 서로 쉽게 연결될 수 있도록 도와주며, 여러 파드를 하나의 고정된 IP 주소와 DNS 이름으로 접근할 수 있게 해준다.
백엔드와 프론트엔드 서비스는 외부 접근이 하도록 NodePort 타입을 사용하였고, 데이터베이스 서비스는 ClusterIP 타입을 사용하였다. 데이터베이스가 외부에서 직접 접근될 필요가 없는 내부 전용 서비스이기 때문에, 클러스터 내부에서만 접근 가능하도록 제한하였다.
apiVersion: v1
kind: Service
metadata:
name: database
namespace: mern
spec:
ports:
- port: 3306
protocol: TCP
type: ClusterIP
selector:
role: database
5. Backend Kubernetes Manifest
Deployment.yaml
데이터베이스와 마찬가지로 백엔드에서도 Secret 리소스를 통해 데이터베이스 접속에 필요한 환경 변수를 주입받아 DB와 연결되도록 구성되어 있다. 또한, 3500번 포트를 외부에 노출시켜 외부 요청을 받을 수 있도록 설정하였다.
애플리케이션의 상태를 지속적으로 점검하기 위해 livenessProbe를 설정하였으며, /backend/ 경로에 HTTP 요청을 주기적으로 보내 컨테이너의 정상 동작 여부를 확인한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: mern
labels:
role: backend
env: dev
spec:
replicas: 1
selector:
matchLabels:
role: backend
template:
metadata:
labels:
role: backend
spec:
imagePullSecrets:
- name: myregistrykey # NCP 레지스트리에 대한 시크릿 이름
containers:
- name: backend
image: dj-kubetest-container.kr.ncr.ntruss.com/backend-image:latest # 이미지 경로 변경
imagePullPolicy: Always
ports:
- containerPort: 3500
livenessProbe:
httpGet:
path: backend/
port: 3500
initialDelaySeconds: 3
periodSeconds: 10
env:
- name: host
valueFrom:
secretKeyRef:
name: db-credentials
key: host
- name: user
valueFrom:
secretKeyRef:
name: db-credentials
key: user
- name: password
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: database
valueFrom:
secretKeyRef:
name: db-credentials
key: database
service.yaml
외부에서 백엔드 서비스에 접근할 수 있도록 NodePort 타입의 Service를 구성하였다. 사용자가 80번 포트로 요청을 보내면 Ingress Controller가 이를 수신하고, 내부적으로 NodePort(31001)로 전달한 뒤 해당 요청은 백엔드 컨테이너의 3500번 포트로 연결된다.
Service는 role: backend 레이블을 가진 파드를 선택하고, 해당 파드의 3500번 포트와 연결되어 통신이 이루어진다.
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: mern
spec:
type: NodePort
ports:
- name: backend-port
port: 3500
targetPort: 3500
nodePort: 31001
protocol: TCP
selector:
role: backend
6. Frontend Kubernetes Manifest
Deployment.yaml
Frontend는 3000번 포트를 통해 외부와 통신하며, 환경 변수로 API 서버 URL을 설정하여 백엔드와의 연결을 가능하게 하였다.
REACT_APP_API_BASE_URL 환경 변수에 http://kimdongju.site/backend 값을 설정함으로써, 프론트엔드 애플리케이션은 해당 백엔드 주소를 통해 API 요청을 보낼 수 있다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: mern
labels:
role: frontend
env: dev
spec:
replicas: 1
selector:
matchLabels:
role: frontend
template:
metadata:
labels:
role: frontend
spec:
imagePullSecrets:
- name: myregistrykey
containers:
- name: frontend
image: dj-kubetest-container.kr.ncr.ntruss.com/frontend-image:latest # 이미지 경로
imagePullPolicy: Always
ports:
- containerPort: 3000
env:
- name: REACT_APP_API_BASE_URL
value: "http://kimdongju.site/backend" # 자신의 도메인이름 적어준다 API 경로를 명세해주는 작업
- name: NODE_OPTIONS
value: "--openssl-legacy-provider"
service.yaml
프론트엔드 서비스는 백엔드와 마찬가지로 NodePort 방식을 사용하였다. 외부에서 80번 포트로 들어온 요청은 Ingress Controller를 통해 NodePort(31000)로 전달되며, 이는 다시 프론트엔드 컨테이너의 3000번 포트로 연결된다.
Service는 role: frontend 레이블을 가진 파드를 선택하여, 해당 파드의 3000번 포트로 트래픽을 안정적으로 전달하도록 구성되어 있다.
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: mern
spec:
type: NodePort
ports:
- port: 3000 # 클러스터 내부에서 사용할 포트
targetPort: 3000 # 컨테이너에서 사용할 포트
nodePort: 31000 # 외부에서 접근할 포트 (예시)
protocol: TCP
selector:
role: frontend
Ingress.yaml
Ingress 리소스는 kimdongju.site 도메인으로 들어오는 HTTP 요청을 처리하며, ALB를 통해 외부에서 접근할 수 있도록 설정되어 있다. 사용자가 브라우저에서 http://kimdongju.site로 요청을 보내면, ALB가 80번 포트로 이를 수신하고 Ingress Controller에 전달한다.
Ingress는 두 가지 경로 규칙을 사용한다. /backend* 경로는 backend 서비스로, 그 외 모든 경로(/*)는 frontend 서비스로 전달된다. 두 서비스 모두 NodePort 타입으로 구성되어 있으며,
- backend는 NodePort 31001 → 내부 포트 3500
- frontend는 NodePort 31000 → 내부 포트 3000
으로 각각 매핑된다.
Ingress 설정에서는 NodePort 번호를 직접 명시하지 않지만, Ingress Controller는 Kubernetes API를 통해 이를 자동으로 인식하고 ALB Target Group을 생성하여 트래픽을 라우팅한다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mern-ingress
namespace: mern
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
alb.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: kimdongju.site # 사용할 도메인
http:
paths:
- path: /backend*
pathType: Prefix
backend:
service:
name: backend
port:
number: 3500 # backend 서비스의 포트
- path: /*
pathType: Prefix
backend:
service:
name: frontend
port:
number: 3000 # frontend 서비스의 포트
7. Kubernetes manifest 배포 결과
모든 Kubernetes 리소스는 정상적으로 배포되었으며, 실무에서는 Argo CD와 같은 GitOps 도구를 사용하는 것이 바람직하지만, 이번에는 직접 명령어를 이용하여 수동으로 배포를 진행하였다.
### Deployment, Service 정상 배포 확인 ###
root@dj-master:~/kubernetes/Kubernetes-Manifests# kubectl get all -n mern
NAME READY STATUS RESTARTS AGE
pod/backend-5888cb94b5-wmk4r 1/1 Running 0 5d6h
pod/database-58d656c7ff-n2sp5 1/1 Running 0 12d
pod/frontend-85785769f8-vj4n7 1/1 Running 0 18m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend NodePort 198.19.185.88 <none> 3500:31001/TCP 10d
service/database ClusterIP 198.19.189.238 <none> 3306/TCP 12d
service/frontend NodePort 198.19.204.218 <none> 3000:31000/TCP 10d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend 1/1 1 1 12d
deployment.apps/database 1/1 1 1 12d
deployment.apps/frontend 1/1 1 1 18m
NAME DESIRED CURRENT READY AGE
replicaset.apps/backend-5888cb94b5 1 1 1 10d
replicaset.apps/database-58d656c7ff 1 1 1 12d
replicaset.apps/frontend-85785769f8 1 1 1 18m
### Secret 정상 배포 확인 ###
root@dj-master:~/kubernetes/Kubernetes-Manifests# kubectl get secret -n mern
NAME TYPE DATA AGE
db-credentials Opaque 4 12d
myregistrykey kubernetes.io/dockerconfigjson 1 12d
### PV, PVC 정상 배포 확인 ###
root@dj-master:~/kubernetes/Kubernetes-Manifests# kubectl get pv -n mern
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
db-pv 1Gi RWO Retain Bound mern/db-pvc standard <unset> - 12d
root@dj-master:~/kubernetes/Kubernetes-Manifests# kubectl get pvc -n mern
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
db-pvc Bound db-pv 1Gi RWO standard <unset> 12d
### ingress 정상 배포 확인 ###
root@dj-master:~/kubernetes/Kubernetes-Manifests# kubectl get ingress -n mern
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress alb kimdongju.site ing-mern-merningress-27dd8-100409304-3669ec287cc4.kr.lb.naverncp.com 80 17m
root@dj-master:~/kubernetes/DevOps/Kubernetes-Manifests# kubectl describe ingress mern-ingress -n mern
Name: ingress
Labels: <none>
Namespace: mern
Address: ing-mern-merningress-27dd8-100409304-3669ec287cc4.kr.lb.naverncp.com
Ingress Class: alb
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
kimdongju.site
/backend* backend:3500 (198.18.0.69:3500)
/* frontend:3000 (198.18.0.87:3000)
Annotations: alb.ingress.kubernetes.io/listen-ports: [{"HTTP": 80}]
alb.ingress.kubernetes.io/rewrite-target: /$2
alb.ingress.kubernetes.io/scheme: internet-facing
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CreateLoadBalancer 18m ingress no load balancer found, load balancer created (ingress: mern-ingress, instanceNo: 100409304)
Normal UpdateLoadBalancer 17m ingress load balancer updated (ingress: mern-ingress, instanceNo: 100409304)
Normal CreateTargetGroup 17m ingress target group created (no: 2919545, name: svc-mern-backend-22ad5, nodePort: 31001)
Normal CreateTargetGroup 17m ingress target group created (no: 2919546, name: svc-mern-frontend-85211, nodePort: 31000)
Normal CreateRule 17m ingress rule created (listenerPort: 80, rulePriority: 1)
Normal CreateRule 17m ingress rule created (listenerPort: 80, rulePriority: 2)
Ingress와 Service 구성을 기반으로 로드 밸런서와 타겟 그룹이 정상적으로 생성된 모습을 확인할 수 있다.


백엔드의 Pod에 livenessProbe 경로를 /backend/로 지정하였기 때문에, ALB에서 생성된 백엔드 Target Group의 헬스 체크 경로 또한 동일하게 /backend/로 설정해야 정상적으로 통신이 이루어진다.

8. Global DNS Domain Mapping
마지막으로 Global DNS를 통해 도메인과 ALB 주소를 매핑시켜주시면 된다.

도메인 접속
애플리케이션이 외부에서 정상적으로 접속되고 있는 것을 확인할 수 있다.


개선할 점 & 느낀점
- 현재는 GitOps 도구를 사용하지 않고 수동으로 배포를 진행하였기 때문에, 테스트나 반복적인 배포 과정에서 시간이 소요되었다. 따라서 향후에는 CI/CD 기반의 GitOps 방식을 도입하여 배포 자동화와 관리 효율성을 높일 필요가 있다.
- 직접 애플리케이션을 배포하는 과정을 경험하며 Kubernetes 리소스에 대해 전반적인 이해도가 높아진 것 같다. 기존에는 Deployment에서 직접 변수를 지정했지만, ConfigMap 리소스를 활용해 설정 값을 분리하여야겠다.
'kubernetes' 카테고리의 다른 글
| [Kubernetes] Karpenter (0) | 2025.04.25 |
|---|---|
| [Kubernetes] EKS(Elastic Kubernetes Service) (0) | 2025.04.24 |
| [kubernetes] Security Context (0) | 2024.12.16 |
| [kubernetes] CSR (0) | 2024.12.14 |
| [Kubernetes] etcd member (0) | 2024.12.14 |