배포 아키텍처 + Docker

Park sang woo·2024년 4월 3일
0

🔥 Docker

Docker는 컨테이너 기반 가상화 플랫폼으로 응용 프로그램과 그 종속성을 격리된 환경인 컨테이너로 패키징하여 실행하는 기술이다.
Docker는 환경의 일관성으로 JRE와 JDK를 하나로 묶고(일관된 버전으로) 다중 컨테이너(Side car pattern) 동작을 위해 사용한다. (Application, Library/Binarys까지 동일하게)
즉 Docker 사용의 중요성은 어플리케이션 동작의 일관성을 가져야 한다는 것이다.
"동작의 일관성은 어플리케이션의 버전, 설정 명령어의 존재 여부, 환경 변수, 실행 환경 등 환경과 어플리케이션과 함께 묶어 하나의 응용 프로그램을 다른 환경에서도 일관되게 실행할 수 있고, 개발 환경과 운영 환경 사이의 차이로 인한 문제를 해결해준다는 것이다."

&& 컨테이너는 격리된 공간.

VM은 OS도 포함. 근데 overload 발생 가능. Docker는 OS없이 단위인 애플리케이션과 Binary Library만 갈아 끼면 됨.



Dockerfile은 Docker 이미지를 만들기 위한 청사진(설계도)이다. (script 파일)
Dockerfile을 통해서 Image가 어떤 형식인지 알 수 있다.

이 파일을 사용하면 Docker 이미지를 구성하는 과정을 자동화하고, 이미지가 어떻게 빌드되어야 하는지의 세부 사항을 정의 가능.

Docker Image는 Dockerfile을 기반으로 Docker Build된 상태다.

  • 어플리케이션과 바이너리 파일 및 라이브러리(애플리케이션 구동 환경)을 묶어놓은 상태.
    Docker Image를 Run하면 바로 애플리케이션이 동작할 수 있다.
    Image에는 Binary/Library가 포함되어 있기 때문에 따로 애플리케이션을 구동할 때 환경 설정을 할 필요가 없다.

Docker Container는 Docker Image가 구동한 상태로 격리된 공간에서 프로세스를 동작시킨 것이다.

  • 쉽게 DockerImage가 프로그램, Container를 프로세스라 생각.

프로그램
-> 소스코드를 바탕으로 작성된 어떠한 파일로 아직 메모리나 CPU에 올라가지 않은 상태의 바이너리 파일
프로세스
-> 프로그램을 바탕으로 실행 중인 상태로 프로그램이 메모리에 올라가 CPU가 할당되어 동작.



Docker Compose는 여러 컨테이너를 가지는 애플리케이션을 통합적으로 관리하고, 각각의 컨테이너를 시작,중지하는 작업을 쉽게 해준다.
여러 개의 Dockerfile과 각 Dockerfile의 관계 등을 하나의 Docker Compose에 정의하여 하나의 큰 Container에 여러 개의 Container를 띄우는 Multiple Container의 개념.



Docker Daemon은 OS 위에 있다.

도커를 OS 위에 다운로드하는 것이 도커 데몬이다. 도커 엔진이라고도 하고 Docker Container를 구동시킬 수 있는 엔진이다.
Docker Image를 받아서 Container를 구동하는 역할을 한다. Container는 동적인 프로세스의 개념을 생각하면 됨.

Docker Daemon은 Linux Container로 Linux 명령만 가능하다.

Build Image하면 우리가 필요한 Docker Image로 JRE + 필요한 것과 JAR + 필요한 환경 설정 이 있는데 이 Docker Image는 Docker Registry에 저장한다.

Docker Registry는 공식 저장소인 Base Image와 개인 이미지인 Docker Image를 저장하는 곳이다.

JRE와 JAR 파일을 사용하여 Docker 이미지를 만드는 것은 Java 애플리케이션을 컨테이너화하는 효율적인 방법이다.

이 과정을 통해 애플리케이션을 어디서나 동일한 환경에서 실행할 수 있게 된다.

🔗 https://ssyoni.tistory.com/22






집 모양을 봐보자.

1) BaseImage는 가장 근간이 되는 Bins/Libs로 언어에 따라 결정된다. (집에서 바닥을 생각.)

2) Docker Image로 Layer 총 집합이다. 캐싱 가능하고 Layer를 쌓다가 문제가 발생하면 Rebuild가 가능하다. 또한 명령어 순서대로 빌드가 된다

3) 이 만들어진 Docker Image로 배포를 한다.

Dockerfile을 Build하면 정의된 명령어에 따라 Docker 이미지가 생성된다. 하나의 이미지로 보이지만 내부적으로는 여러 개의 이미지가 층층히 쌓여있는 Layer구조다.

Docker Container : 도커 이미지로 생성할 수 있으며, 컨테이너를 생성하면 해당 이미지의 목적에 맞는 파일이 들어있는 호스트와 다른 컨테이너로부터 격리된 시스템 자원 및 네트워크를 사용할 수 있는 독립된 공간이 생성된다.

  • Build : DockerFile을 기반으로 명령어를 수행하는 것으로 Build가 끝나면 DockerImage가 생성된다.
    이미지를 만드는 과정은 해당 과정에서 끝남
    - 빌드 이미지를 가져옴
  • Push : 개인 이미지를 Registry에 저장. -> 즉 생성된 DockerImage를 AWS의 ECR 또는 Registry 같은 저장소에 올리는 행위.
  • Pull : Registry에 있는 것 컨테이너에 넣음. -> Docker가 구동될 EC2에서 Push한 DockerImage를 다운로드 받는 행위.
  • Run : Pull 받은 DockerImage를 구동시키는 행위로 Run을 통해 Docker Container가 구동된다.


Docker Build(CI)를 해서 Docker Image를 Push하면 Docker Registry에 저장되고 이 저장된 Docker

Pull과 Run은 우리가 실행하고자 하는 인스턴스에서 실행

Docker Image가 와서 Run하면 Container가 된다.

구동하는 인스턴스와 빌드하는 인스턴스를 따로 둔다. Run하는 인스턴스는 EC2(실제 서비스하는 것) , Docker Run을 하는 CI에 해당한다.

Docker Image는 반드시 Registry 사용. (모든 이미지들을 저장하는 저장소)

저장소는 중앙 Registry가 있고 개인별로 쓸 수 있는 저장소가 있다. AWS 또는 Docker에서 제공하는 자체 제공 나만의 저장소를 쓸 수 있다.

즉 BaseImage는 공식 저장소를 사용하고 개인인 Cumtom Image는 AWS . Docker 자체 제공 나만의 저장소를 사용한다.

Image를 Pull로 가져온 후 Run 을 해야 한다.

Build와 Push는 로컬 또는 Action일 수도 있음. → 이게 구동하는 인스턴스와 빌드하는 인스턴스를 따로 두는 것이다.



이미지를 만들지 않고 빌드하기 위해서는 가지고 있는 소스 파일을 Action 에 넘긴 다음에 빌드를 한다. 그리고 JRE와 합친다.

Dockerfile 실행하기 전에 Github 또는 Local에서 소스 코드를 가져온다.

Action에서 CI/CD를 할 때는 Action에게 build 명령어를 실행시키면, Dockerfile 실행시키면 Action 안에서 실행이 되고 그 결과물을 가져온다.

Action 디렉토리에 src있고 새로 생성된 app.jar 있고 Dockerfile이 있다. (소스 코드 빌드하면 app.jar가 된다.)

JDK 와 app.jar 가지고 “Boot Run” 하면 한 번에 이미지로 묶인다. 이미지 파일은 JDK 기반의 ap.jar 만 담기게 된다. 마지막에 gradlew boot run 하면 Docker 이미지가 생성된다. 이제 이 이미지를 push하면 Registry로 간다.

처음에 Repository에서 소스 코드 넣고 Action에 가져와서 build하면 jar나오고 생긴 jar를 가지고 이미지를 만든다. Image는 Dockerfile에다가 수행해 달라고 명령어를 넣어줘서 이미지를 만든다. run은 Dockerfile을 실행하는 것이 아니라 Dockerfile로 만들어진 이미지를 구동시킨 것이다.

Dockerfile이 사용되는 시점은 빌드가 끝이다. (청사진이어서) 더 이상 사용 X

Sidecar는 이미지가 컨테이너로써 동작한다고 가정했을 때 로그들을 하나씩 하나씩 파일로 사용하는 것이다. Host에 저장한다. 로그 파일을 적으면 sidecar가 읽어서 외부에 보내준다.(pulling, pushing) (다른 인스턴스로)






🔥 정확하게

첫 번째 방식은 Local에서 Java Build를 하고, Action에서 Docker Build를 하는 것이다.

그러면 Local에서 미리 빌드된 JAR, Java 코드를 Action으로 가져와서 Docker Build를 한다.

.Java 소스 코드를 gradle build하면 JAR가 생성되는데 이것을 같이 Repository로 올린다.

Local에서 미리 빌드된 JAR + Dockerfile 을 Docker Build하면 Docker Image가 완성됨.

두 번째 방식은 Java만 가져와서 Action에서 Java 빌드와 Docker Build를 한꺼번에 하느냐 이다. (이상적)

java 소스 코드만 을 가져와서 Repository에 올리고 Action에서 gradle 빌드와 Docker 빌드를 한 번에 한다.

💧 Java Build 와 Docker Build 를 나눠서 생각.

정리
이상적인 T2 방식은 모든 CI/CD를 개발자가 직접하는 것이 아닌 자동화 프로세스가 있는 것들을 사용한 것이다.

1. Local에서 모든 개발이 완료된 소스코드를 개발자가 GitHub Repository에 업로드 한다.

2. GitHub Repository에서 설정하여 GitHub Actions와 연결되어 GitHub Actions에서 자동으로 java build를 수행한다 -> CI 작업

3. GitHub Actions에서 build를 통해 .jar 파일이 생성된다

4. Github Action에서 .jar파일과 소스코드 내 Dockerfile을 기반으로 Docker build를 통해 Docker Image가 생성된다.

5. 생성된 Docker Image는 Docker Registory에 Push되어 저장된다.

6. EC2 내에 Docker Engine을 설치하고 Docker Registory에서 Docker Image Pull을 진행한다.

7. Pull을 통해 다운로드 받은 Docker Image를 Docker Run을 통해 독립된 Docker Container를 구동시킨다. -> CD 작업


DockerImage는 Registry로 올린다.

Registry에는 공식 Docker Hub와 사내 설치형 Docker Hub(회사에서 등장), 공식 개인 Docker Hub, AWS ECR이 있다. (우리는 개인 Docker Hub와 AWS ECR 사용)

저장되면 EC2에서 다운을 받아서 사용한다.

EC2에서 Docker Image를 받아서 Daemon 이 Docker Container를 구동






🔥 Spring에서의 Docker 활용

Spring boot는 .java 파일들의 모듬을 build해서 JAR 파일로 변경한 뒤 JRE를 통하여 구동만 시키면 된다.

✅ Docker를 활용하지 않는다면

Local에서 모든 개발이 완료된 파일을 build를 통한 .jar 파일을 생성한다.
그리고 애플리케이션이 구동될 서버에 사용할 EC2에 JRE를 설치한 뒤, JAR 파일을 업로드 한다.
모든 다운로드가 끝나면 JRE를 바탕으로 JAR파일을 구동시키면 Spring에서 서버가 배포된다.

CI 과정을 로컬이 아닌 불필요한 Computing 자원을 아끼기 위해 Action이나 Jeckins를 사용할 수 있다.
Action은 개발자가 Local에서 빌드를 진행하며 Compution 자원을 사용하지 않고 CI를 이관하는 것이라 할 수 있다.



✅ Docker를 활용한 Spring의 배포

Docker를 사용하면 C2에 JRE를 설치한 뒤, JAR 파일을 업로드하는 것을 개별적으로 진행하는 것이 아닌 DockerImage 안에 담아 놓는 것이라 생각할 수 있다.

1) Local에서 모든 개발이 완료된 파일을 빌드하여 .jar 파일을 생성.

2) 해당 프로젝트 내에 BaseImage(기본 환경) 및 여러 가지 명령(.jar 파일 copy, 구동될 때 동작할 명령어 등)을 통해 Dockerfile을 생성한다.

3) Dockerfile이 위치한 곳에서 Docker Build를 통해 DockerImage를 생성하고 Docker Registory에 Push를 한다.

  • Dockerfile을 기반으로 생성되는 Image이기 때문에 모든 환경설정(bins/libs)이 담겨 있다.
  • DockerImage와 Docker Engine을 설치하였다면 추가적인 JRE를 설치하거나 버전을 맞추는 등의 환경 세팅이 필요없다.

4) 애플리케이션이 구동될 곳(EC2)에 Docker Engine을 설치하고 DockerImage를 Pull한다.

5) Docker run을 통하여 DockerImage를 구동하면 독립된 공간에 Docker Container가 구동된다.






🔥 Next.js와 Spring 연결 아키텍처

Next.js에는 Backend(Node를 기반으로 한 서버)와 BFF 2개로 구성되어 있는 것이다.

Next.js에서 제공하는 것은 SSR 을 제공하기 위한 페이지 정의, API 라우터(API로써).

React.js 로 Backend와 연결하면 CORS 문제가 있었다.

BFF가 존재하는 이유는 Spring에서 제공하는 API에 반환하려는 JSON이 있는데 필요한 것들만 노출해야 하는데 BFF가 은닉을 해주고 API를 통해서 필요한 페이지들을 만들어서 바로 반환해주기 위해 존재한다.

Next.js에서는 page.tsx 로 페이지를 쓰는 CSR와 API를 쓰는 방식 2가지로 나누어지게 되므로 하나로 제약을 둬서 EndPoint 갖는 것이 나음.

그래서 Spring을 연결하려면 Route 핸들러와 연결하는 것이 베스트.

백엔드는 하나의 DTO에 모두 몰아줄테니까 프론트에서 필요한 것 알아서 가져다 써라 라고 하는 방식

10개의 API 만들 필요 없이 단 1개의 모든 정보를 반환하는 DTO를 만들어서 사용하는 쪽에서 선택하도록 하는 것이 BFF 요지.



Docker Compose를 이용한 CORS 해결 방법

  1. 리버스 프록시 사용.
  • Nginx를 리버스 프록시로 설정 : Docker Compose 환경에서 Nginx를 리버스 프록시로 설정하여 CORS 문제를 해결.
  • 프론트와 백엔드 서비스가 동일한 도메인에서 서비스되도록 구성하여, 브라우저의 동일 출처 정책(Same-Origin-Policy)을 우회한다.
  1. 백엔드 서버 CORS 설정 변경
  • 백엔드 서버에서 CORS 허용 : 서버 설정에서 직접 CORS를 허용하도록 설정 가능.
  • Flask 같은 백엔드 프레임워크에서는 CORS 관련 미들웨어나 라이브러리를 추가하여 CORS 정책을 완화 가능.





Reference

🔗 [CICD] Docker + Github Action + Spring Boot 자동배포환경 만들기 (velog.io)
🔗 https://rnclf1005.tistory.com/entry/ASAC04Docker-Docker-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-%EB%B0%8F-Spring-%ED%99%98%EA%B2%BD-%EB%B0%B0%ED%8F%AC-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4

profile
일상의 인연에 감사하라. 기적은 의외로 가까운 곳에 있을지도 모른다.

0개의 댓글