항해 3기 8주차 WIL 2021.11.01~2021.11.08

CH_Hwang·2021년 11월 4일
3

항해99

목록 보기
10/14

2021.11.01

아마존에서 https적용에 elb 부분에서 조금 어려운 부분이 있어서 lets encrypt를 이용한 https 인증과 nginx를 이용하는 방법을 찾아봤다.

# 뭔가를 설치하기전에는 패키지들을 항시 업데이트
sudo apt-get update
sudo apt-get upgrade
1. sudo add-apt-repository ppa:certbot/certbot  # 레퍼지토리를 생성하여 인증서 저장
2. sudo apt-get install python-certbot-nginx # certbot 설치
3. sudo certbot --nginx -d [특정 도메인] -d [www.특정 도메인]
# 예시
# sudo certbot --nginx -d example.com -d www.example.com
# 3번 진행 시 이메일을 입력하고 약관 동의 후 , Redirect 에 대한 문의가 나오는데 2번을 선택해준다.
# 그 이유는 http 로 요청이 들어왔을 때 https 로 방향전환해준다.
4. sudo certbot certificates  # 인증서가 제대로 발급되었는지 확인
5. sudo certbot renew --dry-run # 발급받은 인증서를 자동으로 갱신하고 nginx를 재시작해준다.

sudo vi /etc/nginx/nginx.conf 에 들어갔을때 아무 설정이 되어 있지 않다면

server {
    listen              443 ssl;
    server_name         <YOUR DOMAIN>;
    ssl_certificate     /etc/letsencrypt/live/<YOUR DOMAIN>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<YOUR DOMAIN>/privkey.pem;

    location / {
        proxy_pass http://[YOUR DOMAIN]:3000;
        proxy_http_version 1.1;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        fastcgi_buffers 8 16k; # increase the buffer size for PHP-FTP
        fastcgi_buffer_size 32k; # increase the buffer size for PHP-FTP
        fastcgi_connect_timeout 60;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
    }
}

쿠키가 저장되는 부분이 자꾸 프론트서버쪽 application-cookie에 저장되지 않아 조금 논리적으로 추측을 해봤다.
프론트 서버쪽에 저장이 안된다. 그러나 request cookie에 없나? => 있다.
그 전에 혼자했을때 res.cookie는 브라우저에 저장이 되었다. 그러면 서버쪽 브라우저에 있을 가능성은?
=> 확인결과 서버쪽 브라우저에 있는 application-cookie에 저장이 되어있다.

여기서 드는 궁금점은 서버쪽 브라우저에 있는데 프론트쪽 네트워크를 확인해보면 request cookie에 저장이 되어있다.. 이것은 어떤 통신과정에 의해 생기는지 궁금해졌다.

이 부분은 아마도 cookie 통신과정에서 도메인 설정을 해주지 않아 생기는 문제였던 것 같다.

2021.11.02

오늘은 nginx를 통해 부하분산과 윈스턴 로깅을 배웠다. nginx를 통해서 부하분산 하는 부분은 생각보다 쉬웠는데 upstream을 이용해서 분산할 포트들을 지정해주면 됐다.

Load balancing methods(부하 분산 규칙)
round-robin(디폴트) - 그냥 돌아가면서 분배한다.
hash - 해시한 값으로 분배한다 쓰려면 hash <키> 형태로 쓴다. ex)hash $remote_addr <- 이는 ip_hash와 같다.
ip_hash - 아이피로 해싱해서 분배한다.
random - 그냥 랜덤으로 분배한다.
least_conn - 연결수가 가장 적은 서버를 선택해서 분배, 근데 가중치를 고려함
least_time - 연결수가 가자 적으면서 평균 응답시간이 가장 적은 쪽을 선택해서분배

처음에 서버 3개로 돌리고 1번 포트에 가중치 3을 줘서 제일 부하가 많게 설정을 했었다.
그러나 3개를 돌리는 순간

Error: ENOSPC: System limit for number of file watchers reached, watch '/home/ubuntu/ant/node_modules/.bin/which'
PM2     |     at FSWatcher.start (internal/fs/watchers.js:210:26)
PM2     |     at Object.watch (fs.js:1444:11)
PM2     |     at createFsWatchInstance (/usr/lib/node_modules/pm2/node_modules/chokidar/lib/nodefs-handler.js:119:15)
PM2     |     at setFsWatchListener (/usr/lib/node_modules/pm2/node_modules/chokidar/lib/nodefs-handler.js:166:15)
PM2     |     at NodeFsHandler._watchWithNodeFs (/usr/lib/node_modules/pm2/node_modules/chokidar/lib/nodefs-handler.js:331:14)
PM2     |     at NodeFsHandler._handleFile (/usr/lib/node_modules/pm2/node_modules/chokidar/lib/nodefs-handler.js:395:23)
PM2     |     at NodeFsHandler._addToNodeFs (/usr/lib/node_modules/pm2/node_modules/chokidar/lib/nodefs-handler.js:629:21)
^C

이라는 에러가 뜨는데
이 에러를 검색해봤더니 watch 옵션을 너무 많이 사용해서 파일와쳐가 limit에 달해서 그렇다고 한다.

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

이 명령어를 통해 와쳐 리밋을 조금 늘려주고 포트도 2개만 사용했더니 정상 작동이 되고 부하도 골고루 나눠가진다.

현재 맘에안드는 점은

이렇게 로그가 조금 난잡하게 찍힌다는 것과 에러시 에러스택이 로그파일에 남지 않는다는 점이 있어서 에러스택도 로그파일에 남기기 위해서 에러 핸들러에 에러스텍을 로깅했다.

그리고 위에 있는 초록색으로 뜨는 정보도 너무 난잡하고 현재로서는 쓸 정보가 많지 않은 것 같아 기존에 했던 방식으로 짧게 남기기로 하여 morgan('dev') 옵션을 사용했고, 이때 레벨 선정 로직에서 오류가 나서 이부분을 해결하는 과정이 있었다.

먼저, morgan('dev') 옵션의 경우 콘솔에서 status가 색깔이 입혀져서 나온다는 점이 가장 큰 문제였다.

첫번째 해결방법은 위와 같이 split을 두번해서 붙혀주는 방법을 사용했었다. 그러나 이방법은 너무 원시적인 방법이라고 생각해서 다시한번 머리를 굴렸을때 정규표현식으로 해서 replace를 하여 저 색깔코드를 소각하는 방법을 찾았다.
https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
여기서 보면 대부분의 색상코드가 \x1b[숫자m으로 나오는 것을 볼 수 있다.

그래서

const state = message.split(' ')[2].replace(/\x1b\[[0-9;]*m/g, '');

이렇게 replace와 정규표현식을 사용하여 소각하는 방법을 택했다.

이렇게 해결해서 결과적으로

위 사진과 같이 깔끔하게 코드가 보이게 돼서 기분이 좋다.

내일은 팀원들에게 access token과 refresh token을 이용해서 보안을 조금 더 강화시키는 방법을 제안할 것인데
잠깐 생각해본 로직은

사용자 인증 미들웨어에서 쿠키를 받아왔을때 access token이 만료가 되어있으면 디비에서 refresh token을 가져와서 refresh token이 만료가 되어있으면 로그인 인증 실패, 만료되지 않으면 access token을 재발급 해줘서 계속 진행하게 만들면 어떨까 생각중이다.

2021.11.03

백엔드의 대략적인 계획이 완성된 것 같다.
일단 현재까지 한 것들을 정리해보자면

  • api 구성완료
  • https 환경
  • nginx를 이용한 부하분산

정도이며 앞으로 해나갈 부분은

  • github actions를 이용한 CI/CD 구현
  • 부하테스트
  • rds를 이용한 데이터베이스 서버 구축 (현재는 ec2에 데이터베이스를 깔아서 사용중이다)
  • redis 이용
  • 도커 vs 타입스크립트

현재 도커와 타입스크립트는 위에 것들을 다 한 후에 추가로 할 예정인데 시간상으로 둘중 하나정도만 할 수 있을 것 같아(다 할 수 있을수도..) 우선순위를 정하자고만 말을 해놓은 상태이다.

오늘은 대부분의 시간을 프론트와의 통신중에 빠진 부분과 개선해야할 부분을 코드적으로 정리하는 시간을 가졌다. 이를테면 프론트에서 하다보니 닉네임이 필요하다고 해서 기존 쿼리를 수정하거나 시퀄라이즈문을 수정해서 닉네임을 뽑아내는 알고리즘을 만들어낸다거나 하는 부분이 있어서 테스트코드도 조금 수정이 되었다.

이때 조금 의문이 드는 것이 사실 테스트코드는 한번 제대로 짜면 바꿀일이 거의 없다고 했는데 이부분에 대해서 입출력이 바꿔졌기 때문에 테스트코드를 바꾼다는 것이 기존 설계가 부족하다는 반증같아서 조금 실망스러운 하루였다고 생각이 든다.

2021.11.07

11월 4일~7일까지 썻던 모든 수정사항이 사라졌다... 임시저장을 했던 벨로그가 사라지다니... 굉장히 굉장히 분하네ㅡㅡ..

일단 이번주에 11월 3일 이후에 했던 것들을 정리해보자면 github actions를 이용하여 ci/cd를 구현하려던 생각을 하고 3일정도 github actions에 대해서 공부를 조금 해봤지만 많이 힘들었다. github actions 자체가 젠킨스와 트래비스에 비해 래퍼런스가 그렇게 많지도 않고 대부분 s3를 이용하기 때문에 그다지 효용성이 없다고 판단하여 github actions를 이용한 ci/cd를 일단은 접고 다른 문제들 부터 해결하자고 했다.

해결할 문제는 첫번째로 부하테스트를 해보니 메인뷰를 보여주는 get method에서 굉장히 많은 부하가 걸린다는 걸 알아냈다. 포스팅 넘버를 계산하는 count에서 문제가 있었는데 sequelize의 기능인 count를 사용했던게 문제인가? 라는 생각이 들어 raw query를 사용하여 count를 해보기도 하고, sub query를 이용하여 사용해보기도 하고(아이러니하게도 서브쿼리를 이용한 카운트가 가장 빨랐다..;) union all을 이용해보기도 했다. 하지만 유의미한 속도개선이 되지 않아(서브쿼리를 이용한 카운트 역시 빠르긴 했지만 원래 median이 3000ms이던게 600ms가 되봐야... 느린건 느린거다..) 계속 찾아보던 중, db에서 계산을 하는것 보다 서버에서 계산을 하는 것이 더 빠르다는 글을 보고나서 count를 그럼 서버에서 해주자!라고 생각이 들어 findAll로 다 찾은 후 그 length를 세는 것으로 했다. 일단 서버의 성능이 크게 개선되었지만.. 이게 데이터가 많아질 수록 서버에 부하가 많이 걸릴것이 뻔하여 나중에 더미데이터를 많이 넣어보고 실험해보도록 할 것 같다.

쿼리를 이용한 카운트 방식들..

기존 방식과 개선한 방식

쿼리, sequelize.count를 이용했을때 부하테스트

개선한 방식으로 했을때 부하테스트
개선한 방식으로 했을때 부하를 더 줘봤더니 60초간 초당 150번까지는 버틸 수 있는것으로 확인되었다.

그리고 로그인 문제로 프론트와 통신방법에 대해서 조금 논의를 했었다. 현재 로그인을 하면 우리조는 토큰을 프론트로 주지않고 서버에서 직접 쿠키에 담아서 보내고 프론트는 건들 수 없도록 했다(httpOnly 옵션). 이때문에 프론트에서는 로그인성공시 보내주는 닉네임을 이용하여 세션에 그것을 담아서 로그인 상태를 확인하는 방법으로 진행을 하고있는데, 이것보다는 서버에서 로그인을 확인하는 api를 따로 만들어서 페이지 이동마다 get요청으로 로그인 상태를 확인하는 것이 어떻냐는 제안이 있었다.

현재는 우리끼리만은 판단이 되지 않아 멘토님들한테 이런상황이니 어떻게 하는것이 조금 더 좋은 통신인지를 여쭤봤는데 한분밖에 답이오진 않았지만 일단은 보안을 위해서 httpOnly를 한 만큼 로그인상태를 페이지 이동마다 서버에서 받는것이 좋을 것 같다는 말씀을 해줬다( 대신 API요청이 훨씬 많아지니 유지 비용이 올라간다고 하셨다..)

일단은 현재상태에서는 기존의 방식대로 로그인을 하되 멘토님들의 답변을 더 받고나서 api 형태를 바꿀 것 같고, 또 기존방식에서 accessToken과 refresh token으로 방식이 바뀔 가능성도 있어 (redis 활용) 다음주 쯤 조금 더 구체화 될 것 같다.

이번주는 조금 서버쪽으로 생각도 많이해보고 코드의 구현방식, 서버 성능개선에 대해서 많이 생각해본 일주일이 되었던 것 같다. 아마도 다음주에는 이것들을 마무리하고 타입스크립트를 공부해서 강타입언어인 타입스크립트를 써볼 것 같다.

ci/cd는 오늘 젠킨스를 이용하여 ci/cd를 구현하는 테스트를 해봤고 성공적으로 테스트가 되어 내일부터 jenkins를 이용한 ci/cd를 본 서버에 적용시킬 것 같다.

타입스크립트를 적용하는 이유는 따로 있는데 이번에 상세페이지에서 뒤로가기 시 메인뷰가 보일때 보던 것을 계속해서 보도록 해야되는 경우가 있어서 알고리즘을 짜는데 파라미터가 undefined로 올 때가 있었다(뒤로가기가 아닌 메인뷰를 처음 볼때는 파라미터가 없이 undefined로 오도록 한다). 그때 우리는 당연히 undefined라서 if문에 (req.params)를 했고 이게 먹히지 않는 현상이 일어나서 혹시나 하고 typeof를 했더니 undefined가 문자열로 오는것을 확인했다... 이부분을 보고 타입스크립트를 사용했더라면 조금 더 일찍 문제해결을 할 수 있지 않았을까라는 생각이 들어 타입스크립트를 적용하면 좋겠다라는 생각이 들었다.

1개의 댓글

comment-user-thumbnail
2021년 11월 4일

좋은 정보 감사합니다 ㅎㅎ

답글 달기