서론

클라우드 네이티브 아키텍처는 나도 생소한 개념이였는데, 간단하게 설명하면 여러 서비스를 유기적으로 연결해주는 기술인데, 프로젝트 구조를 보다 유연하고 확장 가능하게 만들어준다고 한다. 이번 포스팅에서는 클라우드 네이티브의 기본 개념부터 시작해서, Leafy라는 식물 관리 애플리케이션을 실제로 컨테이너 환경에서 구성했던 과정을 기록해봤다
본론
클라우드 네이티브
클라우드의 형태는 크게 두가지가 있다고 나오는데, 퍼블릭 클라우드는 AWS, GCP처럼 누구나 사용 가능한 형태고 프라이빗 클라우드는 조직 내부에서만 사용하는 방식인데, 보안성도 높고 비용 최적화에도 도움이 된다고 배웠다

서론에서도 간단하게 얘기했었지만, 클라우드를 사용하게 되면 가장 큰 장점은 유연성이라고 느꼈다. 그 이유는 아래와 같다
클라우드를 사용하는 이유
- 트래픽이 증가할때 빠르게 대처할 수 있음 (확장성)
현대 애플리케이션에서는 트래픽이 빠르게 증가했다가 빠르게 감소할 수 있기때문에, 클라우드를 사용하게 되면 해당 내용에 맞춰서 유연한 운영이 가능하게 해준다 - 장애 발생시 빠르게 복구할 수 있음 (복원력)
클라우드 서버를 운영하면 백업 및 복구가 빠르게 이루어질 수 있고, 전세계의 다수의 데이터 센터를 가지고 있어서, 효율적인 Disaster Recovery가 가능하다
반대로 클라우드를 사용할때 주의할 점도 존재하는데, 해당 내용은 아래와 같았다
클라우드 사용시 주의할점
- 클라우드 서버 가용량을 적절하게 구성해야함
- 꾸준히 비용 최적화를 진행해줘서 비용을 효율적으로 운영해줘야함
클라우드 네이티브 애플리케이션
다음으로 구체적으로 클라우드 네이티브 애플리케이션을 어떻게 사용하고, 활용할 수 있는지 다루어보겠다
1. MSA : 애플리케이션을 여러 단위로 분리해서, 트래픽 증가에 효율적으로 대처하기 위한 소프트웨어 아키텍처 기존 모놀리식(한 서버에 전체 모든 트래픽 수용)와 달리, 기능별로 여러개의 모듈로 분배해서 서버를 배포한다. 각각의 모듈이 크기가 작아져서 개발도 간편하고, 스케일링 시간도 감소한다

2. 컨테이너 : 이미지를 가지고 소프트웨어를 실행하기 위한 환경들이 모두 포함되어있다 (어떤 환경에서도 동일한 운영 가능)
3. 상태 비저장 : 애플리케이션 자체 서버는 상태를 가지지 않아야하고, 상태를 가지지 않는 애플리케이션은 어디에나 즉시 배포 가능
4. DevOps 및 CI/CD 사용 DevOps와 CI/CD를 사용해서 배포 자동화를 진행하고, 빠르게 릴리즈 진행
Leafy 실습
아래 내용부터는 실제 강의를 들으며 실습을 했던 내용인데, Leafy 애플리케이션 부분은 가독성이 좀 떨어지는거 같아서, 해당 방식으로 진행됐다 정도만 봐주면 될거같다 (실습을 진행하고 터미널 커스텀의 중요성을 느끼고 현재는 커스텀이 된 상태이다 🥲)
이번 파트에서 구성한 애플리케이션은 Leafy라는 식물관리 서비스인데, 프론트(Vue.js), 백엔드(Spring Boot), 데이터베이스(PostgreSQL)로 구성되어 있고, 전부 도커 기반으로 컨테이너화되어있었다
백엔드 빌드 프로세스 (Spring Boot)
Leafy 애플리케이션 구성

1. 먼저 Leafy 저장소를 git clone해서 가져왔다
2. 그 이후로 leafy/leafy-postgresql 디렉토리로 이동 후
3. 00-init 브랜치에서 기본 구조 확인한 다음에 이후 01-dockerfile 브랜치로 이동해서 Dockerfile 및 구성 파일들을 확인했다

4. 다음으로 PostgreSQL 공식 이미지 내 초기화 스크립트를 실행할 수 있는 디렉토리인 /docker-entrypoint-initdb.d/ 내부 구조를 확인하였다

5. 해당 디렉토리에 SQL 파일을 넣어서, 컨테이너 실행 시 자동으로 반영되도록 하는 실습 내용이였다

6. PostgreSQL 이미지가 로컬에 존재하지 않아서 postgres:13 이미지를 pull하고, 환경 변수로 비밀번호를 설정해 컨테이너를 백그라운드 모드로 실행했다

7. 복사한 SQL 파일을 PostgreSQL 컨테이너 내에서 psql 명령어로 실행해서, 여러 개의 테이블이 생성되고, 각 테이블에 데이터가 삽입된 것도 확인할 수 있었고, \d 명령어를 사용해서 전체 테이블 목록을 확인하며 구조를 점검하며 마무리 했다
Leafy 애플리케이션 아키텍처

정리하자면 백엔드 컨테이너 실습은 SpringBoot + Gradle로 되어있었으며, Gradle 이미지로 새로운 컨테이너를 생성한 후, 해당 컨테이너 내부 /app 디렉토리에 프로젝트 복사를 해주고, 컨테이너로 gradle을 직접 빌드해보며 실습을 마무리 해주는 형태였다
PostgreSQL 컨테이너 구성
애플리케이션 구성 이후에는 postgres:13 이미지를 기반으로 새로운 컨테이너를 만들었고, Dockerfile 안에 다음과 같이 설정해줬다
FROM postgres:13
COPY ./init/init.sql /docker-entrypoint-initdb.d/
COPY ./config/postgresql.conf /etc/postgresql/custom.conf
ENV POSTGRES_USER=myuser
ENV POSTGRES_PASSWORD=mypassword
ENV POSTGRES_DB=mydb
EXPOSE 5432
CMD ["postgres", "-c", "config_file=/etc/postgresql/custom.conf"]
/docker-entrypoint-initdb.d/ 폴더에 SQL 파일을 넣으면, 컨테이너 최초 실행 시 자동 적용이 되는 구조였다
추가로 init 패키지 안에, init.sql 에는 테이블 생성 및 샘플 데이터 삽입 로직이 담겨있었는데,
CREATE TABLE users (user_id SERIAL PRIMARY KEY, ...);
CREATE TABLE plants (plant_id SERIAL PRIMARY KEY, ...);
CREATE TABLE user_plants (...);
CREATE TABLE plant_logs (...);
INSERT INTO users (name, email, ...) VALUES ('John', 'john123@gmail.com', ...);
INSERT INTO plants (plant_name, ...) VALUES ('아이비', '덩굴식물', ...);
INSERT INTO user_plants (user_id, plant_id, ...) VALUES (1, 1, '노을이');
INSERT INTO plant_logs (...) VALUES (1, '2023-03-22', '관리가 어려워서 죽었습니다', false);
위 내용처럼 입력하면, 테이블 생성 후 예시 데이터까지 미리 넣어줘서 실습 시 DB 내용까지 실제로 확인이 가능했다

Leafy 애플리케이션 Gradle 컨테이너 빌드
위 실습 내용은 Gradle 기반으로 도커 컨테이너에서 직접 빌드하고 실행해볼 수도 있었다

도커 컨테이너에서 애플리케이션을 빌드하고 실행하는 과정을 따라 쳐보면서, 뒤 세션에서 진행할 CI/CD 구성 부분에서도 해당 부분이 필요할거 같다고 생각했었다

프론트 엔드 실습 (Vue.js)
프론트엔드 실습은 아래 플로우로 진행됐는데,

초기 빌드 프로세스는 백엔드 로직과 동일하게, 기초 세팅을 해준 다음 웹서버를 실행해주었다
요청 URL과 응답할 파일 지정
프론트엔드 구성에서 cd ../leafy-frontend로 소스코드를 다운받으면 Leafy Frontend가 불러와지는데,
아래는 요청 URL에 따라 화면을 분기시켜주는 router.js 설정 내용이였다
const routes = [
{
path: '/',
name: 'HomePage',
component: () => import('@/views/HomePage.vue')
},
{
path: '/plants',
name: 'PlantList',
component: () => import('@/views/PlantList.vue')
},
{
path: '/plants/add',
name: 'PlantAdd',
component: () => import('@/components/modals/PlantAddModal.vue')
},
{
path: '/plantlogs',
name: 'PlantlogList',
component: () => import('@/views/PlantlogList.vue')
},
{
path: '/setting',
name: 'UserCard',
component: () => import('@/views/UserCard.vue')
},
{
path: '/edituser',
name: 'UserEdit',
component: () => import('@/views/UserEdit.vue')
},
{
path: '/login',
name: 'UserLogin',
component: () => import('@/views/UserLogin.vue')
}
]
백엔드에서는 @GetMapping으로 API 경로를 정의했다면, 프론트는 path를 통해 화면에서 라우팅 경로를 정해주고, 어떤 컴포넌트를 연결할지 component로 지정하여 각 페이지마다 뷰 컴포넌트를 분리해서 관리할 수 있었고, 화면 전환 시에도 URL이 명확하게 보일 수 있다고 생각했다
HomePage.vue 코드
<h3>최근 일기</h3>
<div class="log-container">
<v-card v-for="log in logs" :key="log.plantLogId" class="log-card" variant="outlined">
...
</v-card>
</div>
또 위 코드처럼, 프론트 단에서 하는 역할은 백엔드 API로부터 받은 데이터를 HTML 페이지로 사용자에게 띄워주는 역할을 한다고 생각했었다

다음 실습 과정으론 컨테이너를 띄워준 다음에 도커파일로 빌드를 하고 끝이 났다. 나머지 진행과정은 이전 백엔드 로직과 매우 동일해서 생략하려한다
마치며
전체 플로우를 요약해보자면, 클라이언트단인 router.js에서 접속 경로를 잡아주고, API 호출을 통해 데이터를 요청하는 구조였다

호출된 API는 백엔드 서버에서 비즈니스 로직 처리 후, PostgreSQL에서 데이터를 조회해서 응답을 보내준다. 프론트는 받은 데이터를 화면에 렌더링해서 클라이언트에게 전달하는 구조로 실습이 진행됐다고 생각해주면 될거같다
/docker-entrypoint-initdb.d/ 디렉토리를 통해 초기화 SQL을 적용하는 과정, 그리고 psql 명령어를 통해 실제로 데이터가 잘 들어갔는지 확인하는 과정이 컨테이너 기반 DB 설정에서 중요한 부분이었다고 생각했었다
또 마지막에 정리한 Leafy 아키텍처 이미지를 보면, 클라이언트에서 접속해 DB까지 쿼리되는 모든 흐름이 잘 표현되어 있어서, 실습하면서 전체 흐름을 한 눈에 이해하는 데 큰 도움이 될 수 있을거같다
참고한 자료
https://github.com/daintree-henry/leafy
GitHub - daintree-henry/leafy: Leafy 애플리케이션입니다. Vue3, Spring Boot 2.7, PostgreSQL
Leafy 애플리케이션입니다. Vue3, Spring Boot 2.7, PostgreSQL - daintree-henry/leafy
github.com
'DevOps > Docker' 카테고리의 다른 글
| 도커 스토리지와 볼륨 (DevOps) (1) | 2025.06.13 |
|---|---|
| 도커 네트워크, 실습 (DevOps) (0) | 2025.05.26 |
| 이미지 레지스트리와 빌드 (DevOps) (0) | 2025.05.11 |
| Docker 이미지와 컨테이너 (DevOps) (1) | 2025.05.11 |
| 가상화 기술과 컨테이너 (DevOps) (0) | 2025.05.11 |