마지막 강의이다.

 

실습 전 설정

실습 디렉토리 : last (강의에서는 그대로 ex09에서하심)

  • db
  • product
  • docker-compose.yml
  • my-react-app-auto-build

실습 전에 해당 파일들을 ex09에서 last로 복사해놓자.

 

[강사님 GIT에서 nginx.conf 퍼오기]

https://github.com/codingspecialist/docker-study/blob/main/ex09/my-app/nginx/nginx.conf

 

docker-study/ex09/my-app/nginx/nginx.conf at main · codingspecialist/docker-study

Contribute to codingspecialist/docker-study development by creating an account on GitHub.

github.com

여기서 nginx.conf 퍼오자. 설명은 나중에 해준다고 하심.

 

[Nginx 설정하기 및 설명] 

위치 : last / my-react-app-auto-build / nginx 생성!

방금 퍼온 nginx.conf를 방금 생성한 nginx디렉토리에 넣어주자.

 

upstream backend {
	server backend: 8080;
}

 

upstream backend -> 변수를 하나 지정한다고 생각하자.

backend에 요청이 들어오면 backend:8080으로 가게 하겠다.

여기서 server backend:8080; 에서 backend는 도커 컴포즈의 서비스명이다. 이는 10.10.1.1로 치환된다고 생각하면된다.

 

#server_name metacoding.site www.metacoding.site;

실제로 도메인이 있으면 써야하는데 도메인이 없기 때문에 주석처리한다.

 

location / {
              root   /usr/share/nginx/html;
              index  index.html index.htm;

 

'/' 로 요청이 오면 /usr/share/nginx/html 안에 있는 index.html 파일을 전달해줄 것이다.

여기에서 index.html은 react가 build된 파일이다.

이는 도커 입문 30강 - 도커컴포즈 Dockerfile로 빌드와 nginx실행 한번에 하기

 

도커 입문 30강 - 도커컴포즈 Dockerfile로 빌드와 nginx실행 한번에 하기

[실습목표]이번 강의는 이전강의와 같이 React를 Nginx에서 실행하도록하는 목표를 공유하고있다.차이점은 이전강의에서는 직접 build해줬지만 이번 강의에서는 build조차 자동으로 되도록 하고자

ilikecoding.tistory.com

여기에서 한번 했었다.

 

저기에서 사용한 Dockerfile의 내용을 보면 

FROM node:alpine as build
WORKDIR /app
COPY package.json /app
RUN npm install --silent
COPY . /app
RUN npm run build

FROM nginx
#--from=build는 위에있는 FROM으로 부터 이미지가 구워지는데 그 결과로 나온 것을 의미한다.
# 위에서 나온 결과물인 /app/build 를 /usr/share/nginx/html로 복사하라는 의미이다.
COPY --from=build /app/build /usr/share/nginx/html
ENTRYPOINT ["nginx","-g","daemon off;"]

여기에서 마지막에서 2번째 줄에 있는 것을 보면 build한 것을 /usr/share/nginx/html로 넣어주게 되는 것을 볼 수 있다.

 

location /api/ {
              proxy_pass http://backend;
              rewrite ^/api(/.*)$ $1 break; # /api/ 제거가 되요!!

/api로 왔을 때의 설정인데 가장 중요한 것을 proxy_pass이다. http://, https:// 는 고정인데 backend 이부분이 docker 내부 IP는 컨테이너가 실행될 때 동적으로 바뀌게 되니까 미리 적어 놓을 수가 없다. 그래서 저렇게 backend라고 적어놓으면 upstream backend {

    server backend:8080; 

}

여기 upstream의 backend를 찾아간다. 근데 여기 적힌 server backend:8080은 도커 서비스명이다.

 

2번째 줄인 

rewrite ^/api(/.*)$ $1 break; # /api/ 제거가 되요!!

이 부분은 스프링에 Controller는 /api/ 로 시작되는 부분이 없으니까 nginx단에서 제거 해주는 것이다.

밑에 proxy_http_version~ 헤더 들은 다음 시간에 따로 알려주신다고 하심...

 

이제 해당 설정파일을 가진 nginx가 올라가서 nginx와 통신을 하면 [ 도커 입문 31강 - 도커컴포즈 React와 SpringDB포함 직접연결하기]  강의에서 설정한 cross origin은 필요가 없어진다.

왜냐면,
React에서 /api/ ---▶ nginx (/api를 없애주고 Spring에게 전달)  ---▶ Spring

이렇게 React에서 직접적으로 Spring에 요청한 것이 아니고 프록시 서버를 경유하는 요청이기 때문에 Cross Origin 에러에 걸리지 않게 된다.

 

[my-react-app-auto-build]

my-react-app-auto-build 디렉토리에 있는 Dockerfile에서 nginx를 띄우는데 그것을 수정해야한다.

FROM node:alpine as build
WORKDIR /app
COPY package.json /app
RUN npm install --silent
COPY . /app

RUN npm run build

FROM nginx
#--from=build는 위에있는 FROM으로 부터 이미지가 구워지는데 그 결과로 나온 것을 의미한다.
# 위에서 나온 결과물인 /app/build 를 /usr/share/nginx/html로 복사하라는 의미이다.
COPY --from=build /app/build /usr/share/nginx/html
#내가 커스텀한 nginx.conf파일로 넢어씌운다.
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
ENTRYPOINT ["nginx","-g","daemon off;"]

모두 똑같은데 마지막 COPY 하는 부분에서 위에서 설정한 nginx.conf 파일을 실제로 가동될 nginx 컨테이너로 덮어 씌우는 부분을 추가했다.

 

그리고 소스코드도 변경해야하는데 onLoad 함수의 fetch에서 요청하는 주소만 변경하면된다.

async function onLoad(){
       //let response = await fetch('http://localhost:8080/products');
       let response = await fetch('/api/products');
       let responseBody = await response.json();
       console.log("onLoad",responseBody);
       setProducts(responseBody);
}

http://192.168.0.3/products ----▶ /api/products 로 변경하면 된다.

 

[product]  : 딱히 변경점이 없다. 굳이 있다면 cross origin 설정 코드를 지우는 것?

[db]  : 딱히 변경점이 없다. 

 

※우리는 db의 데이터 파일들을 모두 외부 볼륨(store 디렉토리)로 연결시켜놨기 때문에 컨터이너 종료와 상관없이 계속 누적된다.

※ docker-compose.yml은 실제 배포할 때는 필요가 없다. 로컬에서 테스트 용으로만 사용하고, AWS같은 곳에 배포할 때는 Dockerfile만 잘 써두면 된다.

 

[docker-compose.yml]

server -> backend로 변경

 

frontend 추가

  backend:
    build:
      context: ./product
      dockerfile: Dockerfile
    restart: always
    ports:
      - 8080:8080
    depends_on:
      - db #db가 먼저 구축되고 나서 다 되면 server를 실행시키는 것을 의미한다.
    environment: #해당 값들은 application-prod.yml에서 ${SPRING_DATASOURCE_URL} 에서 사용한다.
      SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/metadb?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true
      SPRING_DATASOURCE_DRIVER: com.mysql.cj.jdbc.Driver
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: root1234
    networks: #동일 네트워크로 잡아주어야 datasource url에서 jdbc:mysql://db 로 쓸수있는 것이다. ip로 안쓰고..
      - network
  frontend:
    build:
      context: ./my-react-app-auto-build
      dockerfile: Dockerfile
    restart: always
    ports:
      - 80:80
    depends_on:
      - backend
    networks:
      - network

db, netowork 부분은 변경점이 없다.

 

 

docker-compose.yml 전체코드

version: '3'

services:
  db:
    build:
      context: ./db #Dockerfile의 위치-이걸 안잡아주면 COPY할 때 init.sql위치를 ex08(docker-compose.yml이 있는 곳)으로 봐버리는 문제가 생긴다.
      dockerfile: Dockerfile
    ports:
      - 3306:3306
    volumes:
      - ./db/store:/var/lib/mysql
    networks:
      - network

  backend:
    build:
      context: ./product
      dockerfile: Dockerfile
    restart: always
    ports:
      - 8080:8080
    depends_on:
      - db #db가 먼저 구축되고 나서 다 되면 server를 실행시키는 것을 의미한다.
    environment: #해당 값들은 application-prod.yml에서 ${SPRING_DATASOURCE_URL} 에서 사용한다.
      SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/metadb?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true
      SPRING_DATASOURCE_DRIVER: com.mysql.cj.jdbc.Driver
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: root1234
    networks: #동일 네트워크로 잡아주어야 datasource url에서 jdbc:mysql://db 로 쓸수있는 것이다. ip로 안쓰고..
      - network
  frontend:
    build:
      context: ./my-react-app-auto-build
      dockerfile: Dockerfile
    restart: always
    ports:
      - 80:80
    depends_on:
      - backend
    networks:
      - network
networks:
  network:

 

 

[product(backend)] Dockerfile 전체코드

FROM openjdk:11-jdk-slim

WORKDIR /app

# COPY만 docker-compose 파일의 위치를 기반으로 작동함
COPY . .

# 개행문자 오류 해결 [unix와 window 시스템 차이]
RUN sed -i 's/\r$//' gradlew

# RUN은 현재 파일을 위치를 기반으로 작동함
RUN chmod +x ./gradlew
RUN ./gradlew clean build

ENV JAR_PATH=/app/build/libs
RUN mv ${JAR_PATH}/*.jar /app/app.jar

ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "app.jar"]

 

 

[db] Dockerfile전체코드

FROM mysql:8.0

COPY init.sql /docker-entrypoint-initdb.d

ENV MYSQL_ROOT_PASSWORD=root1234
ENV MYSQL_DATABASE=metadb
ENV MYSQL_HOST=%

CMD ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]

 

[React] Dockerfile전체코드

FROM node:alpine as build
WORKDIR /app
COPY package.json /app
RUN npm install --silent
COPY . /app

RUN npm run build

FROM nginx
#--from=build는 위에있는 FROM으로 부터 이미지가 구워지는데 그 결과로 나온 것을 의미한다.
# 위에서 나온 결과물인 /app/build 를 /usr/share/nginx/html로 복사하라는 의미이다.
COPY --from=build /app/build /usr/share/nginx/html
#내가 커스텀한 nginx.conf파일로 넢어씌운다.
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
ENTRYPOINT ["nginx","-g","daemon off;"]

구성도

 

[DNS의 활용]

Spring은 Datasource를 연결할 때 DB의 IP를 알 수 없어서 환경변수를 이용한다고 함.

환경변수는 말그대로 변수이므로 S_D_URL = 10.10.1.1로 들어가면 그것을 Spring이 참조하게 된다. 그 과정에서 DNS 레지스트리가 필요한 거라고 하심.

 

리버스 프록시 (Nginx)를 따로 둘것이다.

리버스 프록시는 모든 사용자 요청을 받아서 HTTP Header, HTTP Body 등을 받아서 처리한다.

리버스 프록시는 URL을 분석하여 / 로 요청오면 React로 보내주고, /api 로 요청이 들어오면 거부(Block) 하도록 구현할 것이다. 외부에서는 /api 를 사용하지 못하도록 하기 위함이다.

리버스 프록시는 /api로 요청이 오면 모두 block할 예정이지만 예외사항으로 React에서 들어오는 /api 요청은 Backend로 연결 시켜 주는 역할 까지 겸한다.

 

 

React에서는 /api/product 로 하는데 Spring에는 @RequestMapping이 /product 처럼 만들 예정이라서 /api/product로 오면 redirect하여 /api를 삭제하고 /product로 가도록 구현할 것이다. (왜 굳이 이렇게 하는지는 아직 모르겠다..)

[실습목표]

Spring과 React를 연결하기 

 

[React 코딩하기 및 Cors 해결하기]

실습목표 성취를 위해 React 코딩이 조금 필요하다.

25강에서 만들었던 my-app으로 서버와 통신하는 것이 끝이다.

 

App.js

import './App.css';
import Card from "./components/Card";
import {useEffect, useState} from "react";

function App() {
  const [products, setProducts] = useState([]);

  async function onLoad(){
      //let response = await fetch('http://localhost:8080/products');
      let response = await fetch('http://192.168.0.3:8080/products');
      let responseBody = response.json();
      console.log("onLoad",responseBody);
      setProducts(responseBody);
  }

  //해당 페이지가 열릴 때 한번 실행된다.
  //두 번째 인수는 어떤 값이 변경될 떄 다시 그릴거야?
  useEffect(() => {
      onLoad();
  }, []);

  return (
    <>
      <h1>상품목록 페이지</h1>
      <hr/>
      <div>
          {
              products.map((product)=> <Card id={product.id} product={product}/>)
          }
      </div>
    </>
  );
}

export default App;

 

Card.js

import React from 'react';
import '../App.css'

const Card = ({product}) => {
    return (
        <div className='product-card'>
            {product.id} : {product.name}, {product.price}원
        </div>
    );
};

export default Card;

 

 

일단 윈도우에서 코딩을 했다. IDE가 로컬컴퓨터에 있어서.. 코딩된 폴더 자체를 H2로 옮기는 작업을 해줘야한다..

React 코딩 완료 후 잘 되는지 확인하기 위해 npm start를 하고 브라우저창을 켰는데 작업 결과가 제대로 나오지 않는다.

네트워크 탭을 열어보니 cross-origin 에러가 나온다.

localhost:3000 -> 192.168.0.3으로 요청하는걸 열어줘야한다.

 

// 스프링 서버 전역적으로 CORS 설정
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*") // 허용할 출처
                .allowedMethods("GET", "POST") // 허용할 HTTP method
                .allowCredentials(true) // 쿠키 인증 요청 허용
                .maxAge(3000); // 원하는 시간만큼 pre-flight 리퀘스트를 캐싱
    }
}

전에 받아놨던 강의교재(?) SpringBoot프로젝트인 product 에 저렇게 코딩 해두고 H2서버에 있던 product를 지운 다음 윈도우에서 코딩한 product를 통째로 옮겨주었다.

 

1. ~ex09 / product 디렉토리 삭제 및 윈도우에서 코딩한 것 옮겨주기.

2. 코드를 변경하였기 때문에 기존에 올라가 있던 서버를 내린다.

docker compose down

 

2. SpringBoot & MySQL 컨테이너 재실행

docker compose up -d --build

이미지까지 재생성하기 위해 --build 옵션을 사용하자.

 

재실행이 완료되면 이렇게 React가 SpringBoot와 통신하여 데이터를 제대로 가져오는 것을 볼 수 있다.

 

[이전 강의에서 실습했던 내용 덮어쓰기]

이전강의에서 실습했던 my-react-app-auto-build 를 모두 삭제하고, 지금 Spring과 통신할 수 있는 코드들을 다시 덮어 씌운 후 docker build를 해본 결과 아주 잘 작동한다!

저렇게 5개의 파일들을 ex09/my-react-app-auto-build 에 복사한 후 이전강의 에서 만들었던 Dockerfile을 이용해서

docker build -t react-app ./ 

docker run ~  

한 결과 제대로 작동하는 것을 확인했다.

 

 

강의를 쭉 들으면 강사님도 cors 에러를 겪는 것이 나오고 cosr에 대해 설명해주신다.

교차출처 방지 에러는 서로 도메인(포트가 달라도 도메인이 다른것이라네?)이 다른 요청에 대해서 서버 측에서 보안을 위해 요청하는 것을 거부하는 것을 의미한다고 하심.

cross origin : 서로 도메인이 다른 것.

 

강사님은 메서드에 어노테이션을 붙여서 해결하심.

 

다음 강의에서는 굳이 cors 설정을 안해도 되도록 리버스 프록시 라는 것을 사용해본다고 하심.

+ Recent posts