목적(간단한 투두리스트 앱 만들기)

간단한 투두 리스트 서비스를 만들고 이를 컨테이너화 해서 구현하려 합니다.

프론트, 백엔드, 디비를 모두 컨테이너로 만들고 같은 네트워크에서 동작시키는 테스트 과정입니다.

프론트

간단한 투두 리스트

프론트 기능은 하나의 페이지에서 투두 추가, 투두 리스트 보여주기 두 가지입니다.

간단한 리액트 앱을 노드 기반의 이미지로 만들고 미리 만들어둔 브릿지 네트워크로 넣어 실행하겠습니다.

dockerfile

FROM node:14
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
docker run -d -p 3000:3000 --network todo-network --name todo-front-container todo-front

백엔드

백엔드의 기능은 두 가지 입니다. 먼저 get 방식으로 /todos 를 호출하면 모든 투두를 줍니다. 그리고 post 방식으로 투두를 주면 이를 db에 저장합니다.

dockerfile

FROM gradle:jdk17 AS build
WORKDIR /app
COPY . /app
RUN gradle clean build --no-daemon
FROM openjdk:17
WORKDIR /app
COPY --from=build /app/build/libs/*.jar /app/todo.jar
EXPOSE 8080
ENTRYPOINT ["java"]
CMD ["-jar", "todo.jar"]

먼저 gradle이미지로 todo.jar을 만들고 이를 jdk 환경에서 복사해서 이미지로 만들었습니다(멀티 스테이징).

곧바로 gradle로 이미지를 만들 때보다 용량이 훨씬 적습니다.

문제발생

<aside> 🛠 개발환경 java 17

springboot 3.2.5

gradle 8.7

</aside>

위의 환경에서 docker image가 생성이 안되는 문제가 발생합니다.

#0 44.36 TodoListBackendApplicationTests > contextLoads() FAILED
#0 44.36     java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
#0 44.36         Caused by: org.springframework.beans.factory.BeanCreationException at AbstractAutowireCapableBeanFactory.java:1786
#0 44.36             Caused by: org.hibernate.service.spi.ServiceException at AbstractServiceRegistryImpl.java:276
#0 44.36                 Caused by: org.hibernate.HibernateException at DialectFactoryImpl.java:191
#0 44.43 
#0 44.43 1 test completed, 1 failed

처음에는 gradle 버전이 문젠가 해서 바꿔줘봤지만 여전히 실패했습니다.

검색을 해보니 main에는 application.properties 파일이 존재하지만 test에는 application.properties 파일이 없어서 발생하는 문제라고 합니다.

이번 실습에서는 테스트 코드를 사용하지 않았기 때문에 @SpringBootTest 주석처리해서 해결했습니다.

 docker run -d -p 8080:8080 --network todo-network \\
-e MYSQL_HOST=todo-db-container \\
-e MYSQL_USERNAME=root \\
-e MYSQL_PASSWORD=1234567890 \\
--name todo-back-container \\
todo-back

디비와 연동하기 위해 미리 설정해둔 환경변수 값을 넣어줬습니다.


DB

디비는 mysql을 사용하였습니다.

dockerfile

init.sql을 copy하여 스키마와 entity를 생성하도록 생성하도록 설정하였습니다.

FROM mysql

COPY init.sql /docker-entrypoint-initdb.d/
docker run -d -p 3306:3306 \\                                                              
--name todo-db-container \\
--network todo-network \\
-e MYSQL_ROOT_PASSWORD=1234567890 \\
todo-db

도커 컴포즈

연동돼서 정상 작동하는 투두리스트

잘 동작하게 하기 위해서 순서가 중요한데 db가 먼저 실행되고 서버가 나중에 실행되어야 합니다.

이번에는 개별적으로 실행해주어 문제가 없었습니다. 하지만 매번 하나씩 컨테이너를 실행하기에는 너무 번거롭습니다. 그래서 마지막으로 도커 컴포즈를 사용해서 한 번에 하는 방법을 시도해보겠습니다.

version: "3.8"

services:
  mysql:
    image: mysql:latest
    container_name: todo-db-container
    environment:
      MYSQL_ROOT_PASSWORD: 1234567890
      MYSQL_DATABASE: TodoList
    volumes:
      - ./mysql-init:/docker-entrypoint-initdb.d
      - ./todoDB/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p1234567890"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s
    command: ["--default-authentication-plugin=mysql_native_password"]

  spring-boot-app:
    build:
      context: ./todoList_backend
      dockerfile: dockerfile
    container_name: todo-back-container
    ports:
      - "8080:8080"
    depends_on:
      mysql:
        condition: service_healthy
        restart: true
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://todo-db-container:3306/TodoList
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: 1234567890

  react-app:
    build:
      context: ./todo-list
      dockerfile: dockerfile
    container_name: todo-front-container
    ports:
      - "3000:3000"

원래는 depend on에 mysql만 넣어줬는데, 도커 컴포즈로 실행해보니 backend서비스가 db가 설정되기 전에 실행이 돼서 문제가 발생합니다. healthcheck을 추가해서 정상적으로 작동하도록 수정하였습니다.

도커파일이란?

 

dockerfile은 도커 이미지를 만들기 위한 스크립트 파일입니다.

장점

  • 이미지 빌드에 필요한 모든 과정을 담고 있기 때문에 이미지 빌드를 재현 가능
  • 스크립트 파일이기 때문에 버전관리 가능
  • 하나의 파일에 모든 구성이 있기 때문에 관리하기 편함

과정

  1. 임시 컨테이너 생성
  2. 변경사항 저장 후 커밋
  3. 임시 컨테이너 삭제

리액트 도커 이미지 만들기

환경변수로 화면의 색상을 바꾸는 간단한 앱으로 실습해 보겠습니다.

리액트 파일은 다음과 같습니다.

Dockerfile

// 베이스 이미지 선택
FROM node:14

// 작업 디렉토리 설정
WORKDIR /app

// 의존 라이브러리 설치
RUN npm install

// 빌드 컨텍스트 전체를 워크 디렉토리로 이동
COPY ./ /app/

// 3000번 포트 열어주기
EXPOSE 3000

// 컨테이너 실행 시 명령어 실행
CMD ["npm", "start"]

도커 빌드 명령어입니다. 저는 이미지 명을 my-docker-app으로 해줬습니다.

docker build -t my-docker-app ./
// docker build -t<이미지 명> <dokerfile 경로>
// dockerfile명이 dockerfile이 아니면 -f<dockerfile명> 옵션을 추가로 넣어줘야한다.

생성된 이미지를 컨테이너로 만들고 실행해보겠습니다.

1234:3000으로 포트 포워딩 하였고, -e옵션으로 환경변수를 주입해 주었습니다.

docker run -d -p 1234:3000 -e REACT_APP_THEME_COLOR=red --name reactApp my-docker-app

결과

localhost:1234로 들어갔을때 앱이 잘 실행되는 것을 볼 수 있습니다.

환경변수에 값도 잘 들어갔습니다.

결과 화면

다음

다음은 프론트, 백엔드, db를 모두 컨테이너화 하고 구동하는 간단한 앱을 만들어보겠습니다.

 

 

도커

도커는 대표적인 대표적인 컨테이너 기반 오픈소스 가상화 플랫폼입니다.

이전 글에서 가상화 기술에 대해서 알아보았으니 도커를 사용해서 실제 동작을 확인해보겠습니다.

컨테이너

먼저 nginx컨테이너를 실행해 보겠습니다.

호스트 pc의 1234포트를 container의 80번 포트와 연결하고 nginx 컨테이너를 받아와 실행하겠습니다.

// nginx 컨테이너 실행
docker run -d -p 1234:80 --name nginx-container nginx
// 실행중인 컨테이너 확인
docker ps

nginx 컨테이너가 작동중인 것을 확인할 수 있습니다.

localhost:1234으로 들어가면 nginx 서버가 잘 동작하는 것을 확인할 수 있습니다.

도커 컨테이너를 실행하기 위해서는 컨테이너를 구성할 정보가 필요합니다.

OS → 프로그램 실행에 필요한 언어, 라이브러리, 설정 파일 → 프로그램

이런 구성의 정보가 필요한데 이를 저장한 파일이 이미지입니다.

이제 이미지에 대해서 알아보고 실습해보겠습니다.

이미지

이미지는 특정 시점의 파일 시스템을 저장한 파일입니다.

docker image ls 

nginx 이미지를 확인할 수 있습니다

// nginx 컨테이너 상세 보기
docker container inspect nginx-image

// nginx 이미지 상세 보기
docker image inspect nginx
 

위의 명령어로 컨테이너와 이미지의 상세 정보를 확인할 수 있습니다.

메타데이터 수정

docker run --name nginx-container nginx cat usr/share/nginx/html/index.html

요런식으로 하면 cmd(메타 데이터를 수정할 수 있다.)

환경변수 수정

docker run --name nginx-container --env hello=hiroo nginx 

—env 옵션으로 컨테이너의 환경변수를 수정할 수 있습니다.

도커 이미지 만들어보기

도커 이미지는 레이어 구조로 이루어져있습니다.

공통되는 부분은 공유해서 재활용에 유리합니다. 아까 실습한 nginx 같은 경우도 os와 nginx설치, 설정 파일은 공유하고 index.html 파일만 새로운 레이어로 만들어서 사용할 수 있습니다.

docker image inspect nginx

이렇게 레이어를 확인할 수 있습니다.

기존의 nginx 이미지에 index.html을 수정해서 새로운 레이어를 추가하는 실습을 해보겠습니다.

//bash 컨테이너 실행 후 bash 열기
docker run -it --name nginx-container nginx bin/bash
// index.html 수정
echo hello-hiroo > /usr/share/nginx/html/index.html

//다른 터미널에서
docker commit -m "edit index.html" -c 'CMD ["nginx", "-g", "daemon off;"]' nginx-container nginx-container2

이렇게 해주면 레이어를 추가한 이미지를 얻을 수 있습니다.

이걸 실행해주면 index.html이 바뀐 container가 실행됩니다.

 

 

 

가상화 기술

  1. 베어메탈
  2. 하이퍼바이저
  3. 컨테이너

베어메탈

과거에는 하나의 컴퓨터에 여러 프로세스를 실행했습니다. 이것을 베어메탈이라고 하는데, 하나의 프로세스만 잘못돼도 모든 프로세스가 영향을 받는 문제가 있었습니다. 하나의 프로세스에 문제가 생겨 컴퓨터의 모든 리소스를 잡아 먹는 상황이 발생하면 다른 프로세스는 실행이 안되는 것 같은 문제지요. 그래서 격리된 환경에서 프로세스를 실행시켜 각 프로세스를 독립적으로 운영할 필요성이 생깁니다.

독립적인 환경에서 프로세스를 실행시키기 위해서는 두 가지 방법이 존재합니다. 먼저 성능이 낮은 컴퓨터에 하나의 프로세스만을 실행하는 방법입니다. 이런 방법은 프로세스의 독립성을 유지할 수 있지만, 매번 프로세스의 성능에 알맞는 하드웨어로 운영하기는 쉽지 않겠죠. 두 번째는 성능이 좋은 컴퓨터에 독립적인 가상의 공간을 만들어 운영하는 방법입니다. 프로그램에 상관없이 컴퓨터에서 리소스를 적절히 할당할 수 있어 효율적입니다.

하이퍼바이저

하이퍼바이저는 가상 머신을 생성하고 구동하는 소프트웨어입니다. 즉 컴퓨터 위에 가상 머신을 돌려 프로세스를 독립적인 환경에서 실행하게 해줍니다. 서버 → 호스트OS커널 → 하이퍼바이저 → 가상머신 커널 → 프로세스 구조로 동작합니다. 이를 통해 프로세스는 자신만의 리소스를 할당받고 독립적으로 실행할 수 있습니다. 프로세스의 독립성 문제는 해결됐지만, 프로세스가 실행되기 위해 많은 과정을 거쳐야하고, 프로세스를 실행하기 위한 라이브러리나 설정 파일들도 모두 설치되어 있어야 하니 비효율적입니다.

컨테이너

하이퍼바이저의 비효율을 해결하기 위해 나온 것이 컨테이너 기술입니다. 컨테이너 기술은 눅스 자체 기술인 chroot, 네임스페이스, cgroup을 조합한 기술로 하이퍼바이저처럼 게스트OS를 설치할 필요 없이 동작합니다.

  • chroot: 특정 디렉터리를 최상위 디렉터리 root로 인식하게끔 설정하는 리눅스 명령
  • 네임스페이스: 프로세스 자원을 관리하는 기능으로, mnt, pid, ipc, user 등의 자원을 그룹화하여 할당하는 기능
  • cgroup: CPU, 메모리, 디스크 I/O, 네트워크 등의 자원

이런 기술로 인해 컨테이너는 하이퍼바이저보다 가볍고 빠르게 실행할 수 있습니다.

+ Recent posts