Nginx로 하나의 EC2에서 두 개의 React 돌리기

김기영·2024년 11월 24일

트러블 슈팅

목록 보기
1/2
post-thumbnail

사건의 발단

외주를 진행하던 중에 배포 관련해서 요청 사항이 들어왔다.

일단, 저희 쪽에서 EC2는 만들어 드릴께요~
EC2 두개 드릴테니까 하나는 서버 용으로, 그리고 나머지 하나는 프런트 배포용으로 사용해주세요.

음... 일단 배포 환경을 만들어 주신다니... 땡큐지~
그럼 일단 서버부터 해볼까?

해서 정말 어렵지 않게 서버(spring boot) 배포까지는 끝냈다.

하지만... 문제가 있었으니...
바로 외주사 측의 추가 요청사항들이었다...

아 근데 저희 로드 벨런서는 안쓸꺼에요
그리고 프런트는 배포해야 하는데, 하나의 메인 루트 도메인에, 서브 도메인을 나눠서 두 개의 페이지를 운영해주세요
아 그리고 Route 53으로 ec2에 연결하는 것은 안될꺼에요
DNS 서비스 업체랑 컨택하셔서 A레코드 연결 해주시고, DB는 RDS로 사용할께요

원래 항상 하던 방식으로 로드벨런서 설정하고, EC2 인스턴스 생성 후에 연결하는 것이 불가능한 상황이 생겨버린 것이다!

하지만 개발은 끝났고 배포한 하면 되는데 그만한다고 할 수 없는 노릇이니... 일단은 방법을 찾아보기로 했다.

외주사 측에서 바라는 것은 https://test.com 이 메인 도메인이면, https://test.com/phone 을 서브 도메인으로 하여 두 개의 React 코드를 하나의 ec2 인스턴스에서 돌리는 것이었다.

이를 해결하기 위해 여러개의 방법을 찾아보았는데,

  1. 로드 밸런서를 사용한 트래픽 분배를 시켰으면 금방 끝났겠지만 못쓴다고 하여서 기각
  2. 그냥 포트를 다르게 해서 react 서버를 각각 시행하는 것은, http 기반으로 실행되는 한계 때문에 프로덕트 환경에서는 사용할 수 없을 것 같았다.
    따라서 기각
  3. 서브도메인을 사용해서 도메인 기반 분리 배포는 동일한 도메인 내에서 동일한 경로에 여러개의 앱을 넣어야 하는 경우에 적용이 불가능해서 기각
  4. Node.js서버를 사용한 프록시 설정방식은 좋지만, 정적 파일 서빙에서는 Nginx보다 성능이 떨어질 수 있다는 단점이 있기에 보류
  5. Docker를 이용한 컨테이너화의 경우에는 우리가 사용할 EC2 인스턴스의 사용이 낮아서 성능 문제가 발생할 수 있을 것 같았기에 보류
  6. Nginx의 경우에는 실시간 데이터 교환에는 적합하지 않고, 대규모 트래픽 처리에 로드 밸런싱이 필요했지만, 서비스가 정적 파일 제공에 최적화된 서비스였고, 백엔드 기능은 별도 서리에서 처리하고 있었으며, 추후에 사용자가 크게 늘어날 일이 없는 구조의 로드 밸런싱이 필요 없는 서비스였기 때문에 적합하다고 판단되어 선택

그렇게 Nginx를 기반으로 한 프런트 배포를 하게 되었다!


그럼 진행을 해보자!

일단 Nginx를 사용하기 위해서 nginx를 설치했고, nginx.conf 파일을 아래처럼 기본적인 세팅을 진행했다.

    listen 443 ssl;
    server_name '도메인 주소';

    ssl_certificate '.pem 파일 위치';
    ssl_certificate_key 'privatekey 파일 위치';

    location / {
        root 'build 폴더 위치';
        try_files $uri $uri/ /index.html;
    }

    location /phone {
        alias '서브도메인 build 폴더 위치';
        try_files $uri $uri/ /phone/index.html;
    }
}

이렇게 설정하고 바로 되면 얼마나 좋았겠을까...

도대체가 메인 도메인(https://test.com) 화면과, 서브 도메인(https://test.com/phone) 화면이 흰화면으로 뜨고 제대로 작동하지 않는 것이었다...

문제를 찾기위해...

일단 내가 했던 방법은, nginx.conf파일 코드에 문제가 있는지 확인해보는 것이었다.

일단 아래 코드를 사용해서 nginx 프로세스의 사용자를 확인해보았다.

ps aux | grep nginx

그 결과 디렉토리 사용자가 ec2-user로 설정되어 있는 것을 확인했고, Nginx가 디렉토리 에 접근하지 못하는 것을 해결하기 위해 아래와 같이 권한을 주었다.

sudo chown -R nginx:nginx /path/to/directory
sudo chmod -R 755 /path/to/directory

그 다음에 nginx 설정 파일 테스트 후에 재시작을 해주었다.

sudo nginx -t   # 설정 파일 테스트
sudo systemctl restart nginx

여기까지 진행하니 일단 https://test.com 까지는 제대로 떴다!

근데 여기부터가 진짜 지옥이었다...

https://test.com/phone 은 무슨 짓을 해도 당최 화면이 뜨지 않는 것이었다...

페이지에서 network log도 확인해보니 아무런 문제도 없고 200을 띄우고 있었다, 그래서 조금 더 원초적인 문제일 것으로 생각했다.
그래서 SELinux가 켜져 있을 가능성을 생각하여, SELinux를 완전히 종료시키고 서버를 돌려보았음에도 동일하게 흰 화면밖에 뜨지 않았다.

그러면 build 파일에 문제가 있는 것 같다...!

라는 생각에 바로 /phone 도메인을 배포할 때 사용했던 build 파일의 디렉토리를 main 도메인의 배포 디렉토리로 옮겨서 테스트 해보았다.

그런데... 왠걸...
/phone 도메인에서는 흰 화면을 띄우던 build 파일이 파일 디렉토리만 마꿔 메인 도메인에서 배포를 하니 너무나도 잘 돌아가는 것이었다...


그렇다면 build 파일에는 문제가 없다...

이렇게 단언해서 판단한것이 나의 가장 큰 실책이었다.

이 시점부터 build 파일에는 문제가 없다고 가정하고, 에러 로그를 계속하며 확인하며 config 파일을 수정하는 것에만 시간을 쏟았다.

그런데 진짜 암만 봐도 config에는 문제가 없는 것 같았다.


그래서 처음부터 차근차근 짚고 넘어갔다.

내가 배포하고 있었던 방식은, npm run build를 사용해서 react의 build 파일을 메인 도메인 react 코드와, 서브 도메인 react 코드에서 각각 만들고, filezilla로 ec2에 접근해 파일을 옮긴 후에 nginx로 돌리는 형식이었다.

그렇다면, nginx에서 해볼 수 있는 것은 다 해보았고, ec2 설정 또한 확인해보았다.
react 코드에서 자체적으로 라우팅 해주는 것은 제대로 한 것을 확인했으니...
그렇다면 남은 것은 filezilla에서 ec2로 넘기는 과정에서 이슈가 생겼을 가능성과 build 과정에서의 문제인데

일단 filezilla에서 넘어오는 파일의 무결성은 build 파일의 디렉토리 스왑으로 이미 확인한 바가 있었다.

그렇다면 남은것은 build인데...

본인은 백엔드 개발을 하고 있고, npm에 대해 깊게 알고 있지는 않았다.
분명히 build 과정에서 문제가 생겼을 것이 자명했기에... npm run build에 대해서 조금 더 찾아보았다.

코드 번들링 하고, 코드 압축 후에 환경설정하고 정적파일을 생성한다...

흠... 여기까지는 알고 있는 것과 다른 부분이 없었다.

그렇게 생성되는 파일은 아래와 같았다.

build/
├── index.html           # 앱의 진입점 HTML 파일
├── static/
│   ├── css/             # 최적화된 CSS 파일
│   ├── js/              # 번들링된 JavaScript 파일
│   ├── media/           # 이미지, 폰트 등 정적 리소스
└── asset-manifest.json  # 리소스 매니페스트 (파일 경로 정보)

자주 봤던 그 파일 구조인데, 평소에는 보이지 않았던 거슬리는 부분이 하나 있었다.

그것은 바로 asset-manifest.json

이 파일에 담겨있는 정보들을 확인해 보니 정적 파일 경로 정보를 제공하고 동적으로 로드될 청크 파일들의 경로를 관리하는 역할을 하고 있었다.

그리고 이 파일의 내부를 확인하니...

{
  "files": {
    "main.js": "/static/js/main.abcdef.js",
    "main.css": "/static/css/main.123456.css",
    "runtime-main.js": "/static/js/runtime-main.654321.js",
    "index.html": "/index.html"
  },
  "entrypoints": [
    "/static/js/runtime-main.654321.js",
    "/static/js/main.abcdef.js",
    "/static/css/main.123456.css"
  ]
}

여기서 문제점을 발견했다.

무엇이 문제였던 것 같은가?


문제해결

문제점은 바로 asset-manifest.json 파일에서 Public url 경로를
"main.js": "/static/js/main.abcdef.js" 로 설정하고 있었다는 점이었다.

우리는 서브 도메인을 /phone으로 설정했으니, 정적 파일의 제공 또한 /phone으로 가야함이 맞다.
즉 이 부분이 "main.js": "/affiliate/static/js/main.abcdef.js"으로 수정되야 했던 것이었다.

이를 확인하고, 나는 바로 public url을 반영하여 build를 진행했다.

PUBLIC_URL=/phone npm run build

우리가 nginx.confing에서 하위 경로를 /phone으로 설정했으니 build 시에 public url을 /phone으로 지정해주어야 정적 리소스들을 로드할 때의 기본 경로를 /phone으로 지정해줄 수 있는 것이었다.

설레이는 마음으로 이를 인스턴스에 올렸고, 페이지의 캐시를 지우고 다시 시도하니...

성공했다

정말 손발이 발발 떨렸던 것 같다...

정말 배포를 해면서 너무 많은 것을 느꼈는데...
깊은 공부의 필요성을 느끼면서 동시에 넓은 공부의 필요성을 느꼈던 것 같다.

백엔드라고 npm run build를 몰라도 되는 것이 아니고, 그렇다고 npm run build가 어떻게 돌아가는지 모르고 남발하면 안되었듯이, 깊고 넓은 공부를 해야겠다고 다짐하게 되는 시간이었다.

그리고 무엇보다 크게 배운 것은, 눈에 보이는 현상만으로 섣불리 원인을 판단하지 말자는 것이었다.

나의 경우에 build 파일이 잘 돌아가는 것을 확인하고 환경의 문제일 것이라고 단언했던 부분이 잘못이었다.
그보다 더 근본적인 문제가 있을것이라고 생각하기까지는 무려 하루라는 긴 시간이 있었다.
좀 더 시야를 넓혔다면 더 쉽게 문제를 해결할 수 있지 않았을까?

profile
달리는 개발자

0개의 댓글