Search
🔨

운영서버와 개발 서버 분리하기

태그
cloud
nginx
AWS
분류
Cloud/Infra
목차

1차 배포 끝. 그 다음은?

1차 배포가 끝나고 그 다음 배포를 하기 위한 준비를 해야했습니다. 포트폴리오로 사용하기 위해 1차 배포 후 사이트를 공개해 두었는데요, 공개 이후 저에겐 여러 고민이 생겼습니다.
2차 배포를 위해 개발 시 서버에 jar 파일을 올렸을 때 서버 주소는 하나인데 들어오는 요청을 어떻게 구분하여 알맞은 프로젝트로 돌릴것인가?
운영 디비와 서버 디비를 분리해야 하는데 RDS 를 하나 더 파야 하는걸까?

하나의 인스턴스 두개의 프로젝트

포트번호로 요청 분리하기

인스턴스를 하나 더 생성하기엔 요금 부과가 무서워 일단 인스턴스 하나에 프로젝트 포트번호를 달리 두어 요청을 분리해보자! 라는 생각을 하였습니다. 그래서 운영서버의 경우 포트번호를 8080 으로 두고, 개발 서버의 경우 8085 로 두어 각각의 jar 파일을 서버에 배포, 실행해보았습니다. (DB 의 경우 새로운 RDS 를 연결하기 보다는 개발용 스키마를 하나 더 생성하였습니다.)
물론 이를 위해 선행되어야 할 작업들이 있습니다. 먼저, 서버 주소는 travel-planner.xyz 하나입니다. 이곳으로 요청이 들어왔을 때 이 요청이 어떤 서버로 가야 하는지를 알려주기 위해 nginx.conf 를 수정해야 합니다.

nginx.conf

# For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { map $http_x_route_to $backend_port { default 8080; "8085" 8085; // 분리할 포트를 넣어줍니다. } log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 65; types_hash_max_size 4096; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { server_name travel-planner.xyz; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } location / { proxy_pass http://127.0.0.1:$backend_port; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } listen [::]:443 ssl ipv6only=on; # managed by Certbot listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/travel-planner.xyz/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/travel-planner.xyz/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { if ($host = travel-planner.xyz) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; listen [::]:80; server_name travel-planner.xyz; return 404; # managed by Certbot } # HTTPS 요청 처리 server { listen 443 ssl; listen [::]:443 ssl; server_name travel-planner.xyz; root /user/share/nignx/html; ssl_certificate "/etc/letsencrypt/live/travel-planner.xyz/fullchain.pem"; ssl_certificate_key "/etc/letsencrypt/live/travel-planner.xyz/privkey.pem"; # SSL sessions ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; include /etc/nginx/default.d/*.conf; location /ws { proxy_pass http://localhost:$backend_port; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; } } } 153,1 Bot
Plain Text
복사
위와 같이 작성하면 이제 nginx 가 헤더에 있는 포트 번호에 따라 알맞게 요청을 돌릴 수 있습니다. 디폴트 값이 8080 이기 때문에 만약 8085 프로젝트로 요청을 전달하고 싶다면 “X-Route-To” 헤더에 8085 를 값으로 넣어주면 됩니다.
요청의 경우는 프론트가 일괄적으로 헤더에 넣어주면 되지만 응답의 경우는 어떻게 해야하나요? 응답에 “X-Route-To”:8085 를 넣어주지 않으면 8085 요청에 대한 응답은 8080 서버로 가고 맙니다. 이를 위해 백엔드에서도 일괄적으로 응답에 “X-Route-To”:8085 를 넣어주어야 합니다. 이를 위해 필터를 만들어주었습니다.

CustomHeaderFilter.java

public class CustomHeaderFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.setHeader("X-Route-To", "8085"); chain.doFilter(request, response); } }
Java
복사
그리고 특정 헤더를 노출시켜주어야 프론트에서 읽을 수 있기 때문에 시큐리티 cors 필터 설정에 아래와 같이 노출할 헤더를 추가시켜 주었습니다.

SecurityConfig.java

config.addExposedHeader("X-Route-To");
Java
복사
또한 이 필터를 시큐리티에 빈으로 등록해주어야 작동할 수 있기 때문에 아래와 같이 작성해주었습니다.
@Bean public FilterRegistrationBean<CustomHeaderFilter> customHeaderFilter() { FilterRegistrationBean<CustomHeaderFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new CustomHeaderFilter()); registrationBean.addUrlPatterns("/*"); return registrationBean; }
Java
복사

하나의 레디스 서버에 값이 혼동되지 않도록 저장하기

각각의 프로젝트에서 리프레시 토큰을 하나의 레디스 서버에 저장해야 했습니다. 유저 이메일이 다르다면 상관 없지만 만약 같다면 어떻게 될까요? 이를 방지하기 위해 아래와 같이 개발 서버에서 토큰 저장 방식을 변경하였습니다. 아래와 같이 키에 dev: 를 넣어서 구분하는 방식으로 코드를 작성했습니다.

RedisUtil.java

RequiredArgsConstructor @Component public class RedisUtil { // 배포시에는 devKey 를 제거해주세요 private final StringRedisTemplate stringRedisTemplate; // 레디스에서 특정 키를 가진 값 얻기 public String getData(String key) { ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue(); String devKey = "dev:" + key; return valueOperations.get(devKey); } // 키:값 저장 시 만료기한 설정 public void setDataExpire(String key, String value, Duration duration) { ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue(); String devKey = "dev:" + key; valueOperations.set(devKey, value, duration); } // 레디스에서 특정 키를 가진 값 삭제 public void deleteData(String key) { String devKey = "dev:" + key; stringRedisTemplate.delete(devKey); } }
Java
복사

분리는 잘 되지만…

이렇게 작성하고 실행을 하였으나 새로운 문제점이 생겼습니다. 서버가 지나치게 느리고 버벅인다는 점 이였습니다. 아무래도 프리티어다 보니 한계가 있는게 분명해보였습니다. 그래서 결국 다른 방식을 선택하였습니다.

EC2 생성과 탄력적 IP 할당

결국 인스턴스를 하나 더 생성하였습니다. 그리고 탄력적 IP 를 할당 받고 이를 Route53 에 등록하였습니다. 같은 서버이름을 사용하면 안되므로 서브 도메인을 하나 더 두었고, 탄력적 IP 를 넣어주었습니다.
운영서버주소 travel-planner.xyz
운영프론트주소 go.travel-planner.xyz
개발서버주소 dev.travel-planner.xyz

인스턴스 복제하기

인스턴스를 하나 더 생성하기 싫었던 이유는 지금 운영하고 있는 서버와 같이 설치해야할것을 또 반복해야 하는것 때문이였습니다. 그래서 인스턴스를 복제하였습니다. 복제 과정을 참고한 블로그를 북마크 해 둘 테니 필요할 때 보시면 좋을 것 같습니다.

SSL 인증서 받기

복제로 인해 생성된 인스턴스는 이전 서버를 토대로 SSL 인증을 받았습니다. 즉, travel-planner.xyz 에 대하여 인증을 받았다는 뜻입니다. 새로운 인스턴스의 경우 서버 주소가 dev.travel-planner.xyz 이므로 이에 대하여 인증서를 발급받아야 합니다.

결과

각각의 상황에 따라 알맞은 서버와 스키마를 사용하게 되었습니다. 이번에 분리과정을 거치면서 도메인에 대한 이해도가 증가했고, nginx 의 역할에 대해서도 잘 알게 된 것 같습니다.
백엔드 서버 주소가 일반적으로 /api/v1/ … 형식을 가지는데 왜 이렇게 작명을 하는지 알게 되었습니다. 초반에 rest api 와 서버에 대한 이해도가 낮아 프론트와 맞춰야 한다는 생각을 가지고 있었는데 다음 프로젝트를 할 때는 일반적인 형식을 토대로 구현을 해야겠다는 생각이 들었습니다.