도커란 무엇인지, 도커로 React 프로젝트 Github Actions으로 배포하기

dev_dam·2024년 4월 27일
9
post-thumbnail

들어가며

도커에 대한 얘기를 주변에서 많이 들었으면서 항상 관심 있게 알아보려고 생각하지 않았습니다.
그러다 이번에 배포 환경에 대한 스터디를 하려고 알아보다가 인프런의 따라 하며 배우는 도커와 CI환경 인강을 알게 되었고, 이전부터 궁금했던 도커와 CI환경에 대해서 가볍게 살펴볼 수 있을 것 같아 학습한 내용을 정리했습니다.

가상화의 등장

컴퓨터는 하나의 서버를 하나의 용도로만 사용해서 남는 서버 공간은 그대로 방치되었고 하나의 운영체제만 사용하고 하나의 프로그램만 운영했기에 안정적이었지만 비효율적이였습니다.
이러한 비효율적인 문제를 해결하기 위해 나온 기술이 가상화 기술이며, 물리 서버 1대 위에 여러대의 가상 서버를 만들어서 사용하는 기술로 용량을 늘리거나 줄이는 부분이 쉬워 효율적으로 사용할 수 있습니다.

하이퍼 바이저(Hypervisor) 기반의 가상화 출현

하이퍼 바이저는 논리적으로 공간을 분할하여 VM이라는 독립적인 가상 환경의 서버 이용이 가능한 기술입니다.

  • 네이티브 하이퍼 바이저
    • 하드웨어를 직접 제어해서 자원을 효율적으로 사용 가능하여 별도의 호스트OS가 없으므로 오버헤드가 적음. 하지만 여러 하드웨어 드라이버를 세팅해야 함으로 설치가 어려움.
      • 하드웨어 → 하이퍼바이저 → OS
  • 호스트형 하이퍼 바이저
    • 많이 사용하는 가상화 기술.
    • 일반적인 소프트웨어처럼 호스트 OS위에서 실행되며 하드웨어 자원을 VM 내부의 게스트 OS에 애뮬레이트 하는 방식으로 오버헤드가 큼.
    • 하지만 게스트 OS 종류에 대한 제약이 없고 구현이 다소 쉬운 특징이 있다.
      • 하드웨어 → OS → 하이퍼바이저 → OS(윈도우,리눅스등 다양한걸 사용해도됨)

하이퍼 바이저 기반의 VM 구조


하이퍼 바이저는 논리적으로 분리되어 있기 때문에 하나의 VM에 오류가 발생해도 다른 VM으로 퍼지지 않는다는 장점이 있습니다.

도커(Docker)

도커란 리눅스 컨테이너 기반으로 하는 가상화 플랫폼으로 쉽게 말하면, 데이터 또는 프로그램을 격리시키는 기능을 제공하는 소프트웨어입니다.

도커는 기존의 가상화 기술에서 나온 컨테이너 가상화 기술로서 기존 VM과 비교했을 때 하이퍼바이저와 게스트OS가 필요하지 않기 때문에 훨씬 가볍습니다.

  • 도커 컨테이너 방식
    • 애플리케이션을 실행할때 호스트 OS위에 어플리케이션의 실행 패키지인 이미지를 배포하기만 하면 되서 가볍다.
  • 하이퍼 기반 방식
    • 애플리케이션을 실행하기 위해 VM을 띄우고 자원을 할당한 후 게스트 OS를 부팅하여 어플리케이션을 실행하는 방식으로 훨씬 복잡하고 무겁다.

도커는 컨테이너를 어떻게 격리시킬 수 있을까?

도커의 컨테이너 격리에 대해 알기 위해 리눅스에서 쓰이는 Cgroup(control groups)과 네임스페이스(namespaces)에 대해 알아야 합니다.

  • C Group
    • CPU, 메모리, Network Bandwith, HD i/o 등 프로세스 그룹의 시스템 리소스 사용량을 관리.
    • 어떤 어플이 사용량이 너무 많다면 C group에 집어넣어서 CPU와 메모리 사용 제한이 가능하다.
  • 네임스페이스
    • 하나의 시스템에서 프로세스를 격리시킬 수 있는 가상화 기술.
    • 별개의 독립된 공간을 사용하는 것처럼 격리된 환경을 제공하는 경량 프로세스 가상화 기술

이런 기술들을 이용해서 컨테이너 기술을 사용할 수 있고, 리눅스 커널에 있는 기능을 컨테이너 기술로 가져온 것이라고 생각할 수 있습니다.

도커는 리눅스 운영체제에서 사용되는 프로그램입니다.

도커 컨테이너 기술도 리눅스에서 쓰이는 기술을 사용하여 컨테이너를 사용할 수 있다고 했습니다.
즉, 도커는 리눅스 운영체제에서 사용되는 프로그램입니다.
하지만 윈도우 , macOS를 사용하는 사용자도 도커를 사용할 수 있는데, 어떻게 리눅스 운영체제에서 돌아가는 도커를 사용할 수 있을까요?
윈도우 , macOS를 사용하는 사용자는 도커를 설치해서 사용할 수 있는데, 이 때 도커 패키지안에는 리눅스 운영체제등의 실행 환경에 필요한 것들을 함께 묶어서 패키지로 설치하기 때문에 가능합니다.
가상의 리눅스 환경을 만들고 이 환경에서 도커 엔진을 구동하게 하는 것입니다.
즉, mac용 도커를 설치하면 그 안에 가상환경(Hyper-Kit)과 리눅스 운영체제와 도커엔진이 한번에 패키지로 다운받게 되어 가상화 소프트웨어나 리눅스 운영체제의 존재를 신경쓰지 않고 사용할 수 있습니다.

도커 컨테이너안에는 운영체제 비슷한 것이 들어있습니다.

앞서, 도커 패키지를 설치할때 가상환경, 리눅스 운영체제, 도커 엔진을 한번에 패키지로 받는다고 설명했습니다.
하이퍼바이저의 단점이 게스트OS의 무거움이라고 했는데 도커를 설치할때 리눅스 운영체제를 설치하면 무겁지 않을까요?
그래서 실제 도커 컨테이너 안에는 운영체제가 들어있는 것이 아니라, 운영체제 비슷한 무언가가 들어가 있을 뿐입니다.
도커는 컨테이너가 완전히 분리되어 있음으로, 컨테이너 속에 운영체제의 주변 부분이 들어있어서 프로그램의 명령을 전달받고 이를 밑바탕이 되는 커널에 전달하는 구조로 되어있습니다.
즉, 리눅스 운영체제 전체를 컨테이너에 넣지 않음으로써 도커는 가볍게 가상화를 구현할 수 있습니다.

도커 이미지, 컨테이너, 허브

도커 컨테이너 기술로 격리된 가상화 공간을 만들 수 있으며, 도커의 컨테이너는 도커 이미지를 통해 만들 수 있습니다.
도커 이미지란, 템플릿과 같으며 컨테이너의 설계도 역할을 합니다.
예를들어 로봇 장난감을 만들 수 있는 금형 틀을 이미지라고 할 수 있으며, 금형 틀로 만들어내는 로봇 인형은 컨테이너로 비유할 수 있습니다.
이렇듯 이미지 하나로 동일한 컨테이너를 여러개 만들 수 있습니다.
즉, 이미지는 코드, 런타임, 시스템 도구, 시스템 라이브러리 설정과 같은 프로그램을 실행하는데 필요한 모든 것을 포함합니다.
컨테이너는 이미지로 생성된 인스턴스이며 각각의 독립된 환경에서 프로그램을 실행합니다.
도커 허브는, 도커 이미지를 저장하고 공유할 수 있는 서비스로 직접 만든 이미지를 업로드할 수도 있고 다른사람들이 만들어둔 이미지를 다운로드할 수 있습니다.

도커의 특징

  • 독립된 환경 제공 : 도커의 핵심은 독립된 환경으로 여러개의 컨테이너를 띄울 수 있습니다.
  • 도커 허브에서 이미지를 만들고 다운받을 수 있다. : 이미지를 직접 만들지 않아도 도커 허브에서 이미지를 내려받으면 컨테이너를 사용할 수 있습니다.
  • 컨테이너에 커널을 포함시키지 않습니다 : 컨테이너에는 커널이 포함되지 않음으로 가볍다는 특징이 있습니다.

도커의 장점

  • 격리된 환경
    • 도커는 격리된 환경을 제공함으로 이들이 각각 독립적으로 안전한 상태로 실행되고 조합도 가능하기 때문에 애플리케이션 간의 충돌을 방지합니다.
  • 경량성
    • 도커 컨테이너는 가상 머신(VM)과 달리 하이퍼바이저와 게스트 OS를 필요로 하지 않기 때문에 훨씬 경량이며, 여러 컨테이너가 동일한 호스트에서 동일한 OS 커널을 공유할 수 있으며, 각각은 격리된 프로세스로 실행됨으로 자원 사용을 최소화 할 수 있습니다.
  • 동일한 작동
    • 도커 컨테이너는 모든 도커가 설치된 환경에서 실행될 수 있기 때문에 개발, 테스트, 생산 환경 간의 일관성을 보장하며, "내 컴퓨터에서는 작동하는데"라는 문제를 해결합니다.
    • 즉, 소프트웨어가 컨테이너 내에 포함되어 있기 때문에 OS나 기반 인프라에 관계없이 동일하게 작동합니다.

도커의 단점

  • 리눅스 운영체제에서만 사용할 수 있습니다.
  • 물리 서버 한대에 여러대의 서버를 띄움으로 서버에 문제가 생기면 모든 컨테이너에 영향이 갑니다.
  • 컨테이너를 여러개 사용하지 않고 하나면 사용한다면 큰 장점을 느끼기 어렵습니다.
    • 도커를 사용하기 위해 도커 엔진을 구동하는데 컨테이너를 하나만 사용한다면 오버헤드이기 때문입니다.

도커의 사용 용도

  • 팀원 모두에게 동일한 개발 환경을 제공
    • 애플리케이션을 실행하는데 필요한 모든 의존성을 도커 컨테이너가 갖고 있음으로 각 팀원이 동일한 도커 이미지를 사용하면 같은 소프트웨어 버전과 설정으로 작업할 수 있게 됨으로 각 팀원의 로컬 환경에 관계없이 동일한 개발 환경을 구축할 수 있습니다.
    • 즉 Node.js와 React 같은 특정 소프트웨어의 버전 차이로 인한 문제를 방지할 수 있다.
    • 만약 새로운 팀원이 프로젝트에 참여할 때 팀원이 Node.js 나 React 버전을 업데이트하더라도 다른 팀원의 환경에는 영향을 미치지 않음으로 개발자들은 개발 환경에 대한 걱정 없이 코드의 작성과 기능 개발에만 집중할 수 있습니다.
  • 새로운 버전의 테스트
    • 운영체제나 라이브러리등의 새로운 버전을 개발환경에서 테스트한 후 운영환경에 적용할 때 컨테이너를 활용할 수 있습니다.
  • 동일한 서버가 여러대 필요한 경우
    • 컨테이너를 이용해 한대의 물리 서버에 똑같은 서버를 여러개 만들 수 있습니다.
    • 이렇게 하면 간편하게 물리 서버를 여러개의 컨테이너가 공유함으로 비용을 절약할 수 있고 관리도 편합니다.

도커로 React 프로젝트 Github Actions으로 배포하기

전체 소스 코드는 여기서 확인 가능합니다.

  1. React 프로젝트 생성 (npx create-react-app docker-react-npm)
  2. 개발환경과 운영환경으로 Dockerfile 작성하기
    • Dockerfile.dev , Dockerfile
  3. 도커파일에 작성한 내용으로 도커 이미지 만들기
    • docker build ./ : Dockerfile을 찾아서 실행합니다.
    • docker build -f Dockerfile.dev -t <image-name> ./ : 개발서버의 도커 환경을 실행하려면 -f 옵션으로 도커파일을 직접 지정합니다.
    • -t 옵션은 도커 이미지를 기억하기 쉬운 이름으로 만듭니다.
  4. 포트 매핑하기
    • docker run <image-name> 로 도커 이미지로 컨테이너를 생성 후 실행하게 되는데 http://localhost:3000 로 접속해도 리액트 프로젝트가 제대로 나오지 않는 문제가 발생합니다.
    • 도커 컨테이너는 격리된 환경에서 실행되기 때문에 컨테이너 내에서 실행되는 어플리케이션이 호스트 머신의 네트워크와 직접 연결되어 있지 않기 때문에 리액트 앱이 사용하는 포트(예: 3000)를 호스트 머신의 포트에 명시적으로 연결해 주어야 외부에서 접근할 수 있기 때문에 포트 설정을 해줘야 합니다.
    • 사용자가 브라우저로 접속 요청 -> 호스트 머신의 3000번 포트로 전달 -> 호스트 머신에서 설정된 포트 매핑에 따라 도커 컨테이너 내부의 3000번 포트로 연결 -> 컨테이너 내의 어플리케이션으로 전달되어 사용자에게 웹 페이지 제공
    • docker run -it -p 3000:3000 <image-name> : 로 포트 매핑
  5. Docker Volume 설정하기
    • 리액트 어플리케이션이 잘 노출되지만, 리액트의 수정한 코드가 바로 반영되지 않는 문제를 해결하기 위해 도커 볼륨을 설정해줍니다.
    • 도커 볼륨이란, 도커 컨테이너의 데이터를 보존하고 관리하는데 사용하며, 컨테이너간의 공유가 가능합니다.
    • 도커 볼륨을 이용해서 로컬 환경과 도커 컨테이너 파일을 매핑하여 코드 변경사항이 실시간으로 컨테이너에 반영되도록 설정합니다.
  6. Docker Compose 설정하기
    • 도커 컴포즈란 여러 컨테이너로 구성된 애플리케이션을 정의하고 실행하기 위한 도구로, 멀티 컨테이너 애플리케이션을 쉽게 관리하고 자동화하기 위해 사용합니다. 개발, 스테이징 환경에서의 애플리케이션 배포와 관리를 간소화하는데 사용되기도 합니다.
    • 즉, 여러 독립된 컨테이너들이 서로 통신하기 위해 설정을 해주는 것이 도커 컴포즈라고 이해할 수 있습니다.
    • docker-compose.yml , docker-compose-dev.yml 작성
  7. 도커 환경에서 리액트 앱 테스트
    • docker run -it <image-name> npm run test
  8. 운영환경을 위한 Nginx 를 Dockerfile에 작성하기
    • Nginx란, 웹 서버로 개발되었지만 리버스 프록시, HTTP 캐시, 로드 밸런서 등 다양한 용도로 사용하며 현재 프로젝트에서는 운영환경을 위해 Nginx를 사용합니다.
    • 개발환경 서버와 운영환경 서버를 다르게 사용하는 이유는, 개발 서버에서는 코드를 수정하면 바로 앱에 반영되어야 하는 과정이 필요하지만, 운영환경에서는 코드를 변경한 것이 바로 앱에 반영되면 안되기 때문에 개발에 필요한 기능들이 필요하지 않습니다. 그래서 더 가볍고 빠른 Nginx를 웹 서버로 사용합니다.
    • 운영환경을 위한 Dockerfile 수정한 뒤, 운영 환경의 도커 파일로 이미지 생성하기
    • docker build -t <image-name> .
    • docker run -p 8080:80 <image-name>
  9. Github Actions 으로 배포하기 위해 deploy.yaml 파일 작성
    • .github 폴더 생성 → workflows 폴더 생성 → deploy.yaml 파일 작성
  10. AWS 설정하기
    • IAM Role 설정 및 IAM User 생성하기
    • Elastic Beanstalk 환경 설정하기
  11. 환경변수 설정
  12. Github push 후 Actions에서 CI 확인하기

최종 Dockerfile

# Node.js 16 버전을 사용하는 Alpine 이미지 선택
FROM node:16-alpine as builder

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

# .package.json 복사
# 의존성 설치 전에 package.json을 먼저 복사하는 이유는 package.json 의 파일들이 변경되지 않는 한, Docker가 이전에 캐시된 의존성 설치 단계를 재사용하게 하기 위함입니다.
COPY package.json .

# 의존성 설치
RUN npm install

# 전체 애플리케이션 코드 복사
COPY . .

# React 애플리케이션 빌드
RUN npm run build

# 빌드된 결과물을 Nginx로 복사 
FROM nginx
COPY --from=builder /app/build /usr/share/nginx/html

도커 자주 사용하는 명령어

docker ps , docker ps -a : 도커 상태보기
stop : 컨테이너 중지, 만약 메시지를 보내고 있었다면 다 보내고 이후 중지된다.
kill : 메시지를 다 보내지 않았더라도 즉시 컨테이너 중지
rm : docker rm 컨테이너 아이디. 삭제할때 사용
docker rm `docker ps -a -q` : 모든 컨테이너 삭제
docker build -t <image-name> : 도커 이미지 생성
docker run <image-name> : 도커 이미지로 컨테이너 생성 후 실행
docker-compose up : 이미지가 없을 때 이미지를 빌드하고 컨테이너 시작
docker compse up --build : 이미지가 있든 없든 이미지 빌드하고 컨테이너 시작
docker compose down : 중지시키기

마무리

이전 회사에서 리액트 프로젝트를 할 때 팀원의 노드 버전이나 다른 패키지의 버전이 다를 경우 다른 팀원들이 다 같이 버전을 맞춰야 하는 번거로움이 있었습니다.
이번 도커를 공부할 때 흥미로웠던 점은 이전 회사의 경험이랑 비교해 보면서 도커를 사용했으면 편하게 개발할 수 있었겠다는 라는 체감이 느껴져서였던 것 같습니다.
하지만 도커는 이론으로 이러이러한 것이 도커구나 라고 머리로는 이해해도 실습을 따라 하면서 도커 플로우를 이해한다는 것이 생각보다 어려웠습니다.
이번에는 도커와 배포 환경에 대해 가볍게 훑어보는 과정이었지만, 앞으로 여러 번 더 해보면서 플로우를 익혀야 할 것 같다는 생각을 했습니다.

profile
병아리에서 닭이 될 때까지

0개의 댓글