7월 개발은 지난달에 개발하고 완료된 상태가 배포도 안 된 상태에서 다시 새로운 기능개발 인 투표/설문 개발 을하게되었다.
새로운 신규개발과 더불어 지난달 개발된 항목에 대해 지속적인 API 지원요청 이 있어 로그확인등 하는데 생각보다 작업하는 일정이 늘어졌다.
1. https 오류 지원
현재 개발의 부모페이지 즉 프런트 부모페이지는 외부협력사에서 담당하고 있다. 해당 개발사에서 는 특정 테스트 서버환경을 제공해주지 않고 단순 우리의 API를 호출하면서 "안된다"라는 메일을 받으면 정말 당황스럽다.
지난 6월 리버스 프락시 를 적용해서 openssl -x509 를 사설 인증서를 만들어 프록시 서버를 오픈하여 포트를 공유하였다. 이에 대한 피드백은 지지난주 금요일 퇴근 5시 전에 단순 안된다 라는 메일 이 왔고, 이를 해결하기 위해 let's encrypt를 활용해 3개월짜리 무료 ca 인증을 받을 수 있다.
[certbot 인증과정]
lets encrypt 사이트를 접속하게 되면, certbot acme 클라이언트를 이용해 발급받는 방법을 추천하는데 이 방법을 이용해서 인증서 발급을 받았다.
acme 란 웹보안인증서를 자동으로 발급, 갱신을 위한 프로토콜이다. certbot 웹 페이지를 실제 접근하게 되면 정말 설명이 잘되어 있다.
설명서를 읽기 위해 우선 리눅스의 버전을 알아야 한다.
확실히 리눅스 쪽에서 작업이 어색하다 보니 자주 사용하는 명령어인 scp mv cp ltr grep 등의 명령어만 사용할 줄 알지 다른 거는 정말 모른다.
리눅스 버전확인 : cat /etc/os-release
centos - 7을 활용하고 있다. 해당 버전을 certbot에 기입을 하게 되면 설치하는 방법에 대해 설명해 준다.
1번 스텝 : ssh를 이용해 서버에 접속한다
2번 스텝 : snapd를 설치한다.(https://snapcraft.io/docs/installing-snap-on-centos)
리눅스 에는 os에 맞는 버전관리 툴? 이 존재한다 우분투는 apt centos는 yum 등이 있는데 이를 통합해서 관리하는 snapd라는 툴이 새로 나온 것이다. 당연히 우리 서버에는 존재하지 않기에 설치해주어야 했다.
3번 스텝: certbot을 설치한다.
sudo snap install --classic certbot
4번 스탭 : certbot 명령어를 활성화시켜준다.
sudo ln -s /snap/bin/certbot /usr/bin/certbot
ln 링크의 약자로 파일 또는 디렉터리 연결을 할 때 사용하는 것이다. -s와 조합하여 사용하는 것으로
/snap/bin/cerbot을 /usr/bin/certbot으로 심벌링 링크 즉 원본파일을 가리키는 링크를 생성하는 것이다.
이에 따라 /usr/bin 은 환경변수에 포함되어 있기에 리눅스 내에서 certbot 커맨드를 사용할 수 있는 것이다.
5번 스탭: certbot을 이용해 어떻게 인증받을지 정하는 것인데 2가지 선택지가 있다.
실제로 해당 웹페이지에 가면 더 다양한 방법을 알려준다. (https://eff-certbot.readthedocs.io/en/stable/using.html)
- 웹서버가 현재 실행 중이 아니라면?
sudo certbot certonly --standalone
- 웹서버가 계속 실행되는 것이 유지되어야 한다면?
sudo certbot certonly --webroot
1번의 경우는 80번 포트를 바인딩해서 도메인 인증 저을 한다고 한다. 따라서 웹서버가 진행 중이라면 모두 정지해주어야 한다.
실제로 회사에서는 80번 포트를 운영하는 것 이 nginx 가 있어서 해당 경우는 사용할 수 없었다.
2번의 경우는 계속 인증에 실패했다.
2번의 인증 동작방식 중 하나인 /. well-known/acme-challenge 패스에 접근이 되어야 하는데 그것이 불가능해서 발생되는 오류였다.
팀장님께서는 휴가 중이시고 이 리버스 프락시 서버를 만들게 된 계기도 nginx 설정을 만지지 말라고 하셔서 생긴 것이기 때문에 nginx에 추가적인 설정으로 해당 주소를 바인딩하기로 결정했으나 잉? 이게 무슨 일인가 nginx는 서버에서 돌아가고 있지 않은 게 아닌가...
http {
include mime.types;
server {
listen 80;
listen [::]:80;
location ^~/.well-known/acme-challenge/{
default_type "text/plain";
root /var/www/letsencrypt;
}
}
}
바로 설정 추가해서 돌려주었다. 잉 웬걸 서버 실행에 자꾸 실패한다. 확인해 보니. apache httpd 서버가 돌아가고 있는 게 아닌가?
httpd라는 웹서버가 있다는 것도 이때 처음 알게 되었는데 검색을 해보니 nginx와 같은 웹서버 역할을 해준다. 그렇다면 config를 해줄 수 있는 무언가가 있다고 생각해 찾아보니
/etc/httpd/conf/httpd.conf라는 파일이 존재한다. 해당 폴더에 웹루트에 대해 적혀있고 해당 루트를 기준으로 인증에 필요한 폴더 패스 f를 생성해 주었다.
띠용 계속 실패한다. 이사님에게 여쭤보니 우리의 도메인은 route53을 이용하고 있다고 하신다. route53 의설정을 찾아보니 (https://certbot-dns-route53.readthedocs.io/en/stable/)
aws의 키파일들이 /. aws/config 에 존재해야 한다. 이사님도 계시지 않고, 팀장님도 휴가 중이셔서 다른 방법을 찾아봐야 할 것 같다.
httpd 서버를 통해 특정 포트 혹은 실행 중인 포트와 연동되는 것이 없어 httpd를 일시중지 시키고 1번의 경우를 이용해 certbot을 인증을 받게 되었다. 만약 중지되어서 lsof -i :80에 아무것도 나오면 안 되는 것이니 이중체크 하기 바란다.
인증을 받게 되면 /etc/letsencrypt/live/도메인으로 cert.pem, chain.pem, fullchai.pem, privkey.pem READM를 파일들을 준다.
리버스 프락시에 제공된 cert.pem과 privkey.pem을 이용해 서버를 재기동하고 확인하니 정상적으로 동작하고 있다.
이렇게 완료되는 줄 알았으나. 다시 받게 된 "안된다 메일"과 함께 온 에러메시지
curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it.
To learn more about this situation and how to fix it, please visit the web page mentioned above.
너무 답답해서 전화를 해서 확인을 해보니 다른 컴퓨터에서는 정상접근이 가능하나 해당 작업하는 서버에서는 curl 요청 시 위와 같은 에러가 발생한다는 거였다...
문제의 원인은 저 cert.pem 이 문제였다. 해당. pem 은 자체적인 서버인증 만을 가지고 있는 것이다. 다시 말해 웹스에서는 정상접근이 가능하다 그러나 curl을 이용해서 서버의 인증서를 이용해 요청을 하게 될 때 몇몇 서버에서는 해당. pem을 인증할 수 없는 문제가 발생된다.
README에 작성되어 있는데 한 번이라도 봤더라면 fullchain.pem을 적용했을 텐데 참 아쉽다...
이에 따라 fullchain을 적용해서 아래 적용되었는지 확인을 하게 되면 아래와 같이 chain 이 형성된다.
openssl s_client -connect "리버스 프록시 주소"
Certificate chain
0 s:/CN=im.plea.kr
i:/C=US/O=Let's Encrypt/CN=R3
1 s:/C=US/O=Let's Encrypt/CN=R3
i:/C=US/O=Internet Security Research Group/CN=ISRG Root X1
2 s:/C=US/O=Internet Security Research Group/CN=ISRG Root X1
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
이후 더 이상의 메일은 없었다고 한다.
2. Log 파일 읽기
특정 API 호출 시 데이터가 없다는 메일을 수신했다. 제발 이런 메일을 보내면 언제 주로 테스트를 했고 어떻게 했는지에 대해 첨부 좀 해주었으면 좋겠다. 모든 걸 다 확인해야 하기 때문에 생각보다 시간이 오래 걸린다.
scp로 메일 받기 전날짜의 로그를 로컬로 가져와 sublime Text를 이용해 검색했다.
특정 IP 기준으로 확인을 해보니 진짜 데이터가 안 내려간다. 로직상 에서 데이터가 안 내려가는 경우는 jwt 토큰 인증이 안된경우만 안내려가는 것인데 무엇이 문제인가 로그를 위아래 확인해 보니 세상에 jwt 토큰을 헤더에 잘못 넣어주고 있었다.
우리는 A라고 API 명세서 상에 적어주었으나, B라고 작성해 요청을 하는 것이 아닌가? 이거는 다시 말해서 저 외부협력사는 6월 중순부터 토큰을 넣어서 api 요청을 해본 적이 없다는 소리 아니겠는가?
이런 협력사와 일을 하는 게 너무 스트레스받는다.
3. 투표/설문조사 기능 개발
투표/설문조사라는 새로운 기능개발 건이 들어왔다. 기존에 없는 신규 기능이기에 처음부터 작성해야 해서 정말 재밌었다.
ERD 설계, API 명세서 작성 등 4일 동안 팀장님한테 와장창 깨졌다.
ERD와 API 명세서를 작성하는데 왜 동시성에 대한 문제와 쿼리에 대해 고민하는지 사적지식을 그만 탐구하라고 하셨다.
저런 거를 고민 안 하고 어떻게 ERD, API 명세서를 작성하는지 이해가 안 간다. 동시성을 고민해야 했던 이유는 라이브 방송에서 진행되는 투표/설문조사 의 경우 어떻게 통계 테이블에 데이터를 밀어 넣어주고 해당 고 루틴 관리에 대해 생각보다 많은 고민을 했고,
이렇게 ERD를 작성했을 때 어떤 걸 기준으로 조회가 많이 발생할 것 같은데? 복합 PK 가 좋을까? 이런 고민은 필요한 것이 아닌가에 대해 팀장님과 의논을 했고 팀장님이 원하던 것은 완벽한 ERD, API 가 아닌 빠르게 먼저 기초 틀을 잡길 원하셨던건데 나는 다른 방향을 가고 있었다고 하셨다. 단순 자전거 수리점에서 체인만 갈면되는것을 , 이것저것 핸들 안장 바퀴 등을 다손보는 작업을 하고 있어 팀장님 입장에서는 답답하셨다고 한다.
테이블의 설계와 pk fk 전략은 기존 데이터 베이스와 동일하게 가져갔다. fk는 없이 id 칼럼을 추가하고 인덱스를 걸어주기로 했다.
지난 프로젝트에서 이렇게 설정했던 이유는 fk에 따른 조인 시 오버헤드 연산 그리고 msa 가 적용되어 있어 id 하나의 값을 가지고 레디스, mysql 등 한 번에 조회하는 이점 등이 있어 이렇게 설정했었다. 이에 대해 추후 어떻게 하면 이런 방식이 좋을 수 있는지에 대해 작성해 보겠다.
그외 테이블 설계의 특별한것은 없었던것 같다.
ERD,API 명세서 작성이 완료된 이후 코드작성을 하는데 너무 재밌었다. 새로운 신규기능에 대해 개발 을 하는 것이 너무 새로웠다. 물론 프로젝트 구조상 api 핸들러는 템플릿 메서드 패턴이 적용되어 필요한 인터페이스를 구현하기만 하면 되지만 이런 api 가 아닌 라이브 채팅 서버와 채팅 매니저 관리 쪽은 고 루틴과 채널을 이용해야 했다.
이번에 배운 동시성 프로그래밍 의 규칙에 맞춰 잘 작성했다고 생각한다. 라이브 채팅 서버 혹은 vod에서 투표가 진행된다면 해당 결과를 통계테이블에 옮겨주는 작업이다. 물론 배치를 통해서 작성하면 손쉽게 작성할 수 있겠지만 투표진행이 없음에도 배치가 도는 비효율적인 코드를 작성하기 싫어 고 루틴과 채널을 활용해서 작성해 보았다.
캡슐화를 통해 고 루틴을 생성하는 함수에서 채널을 생성해 해당 채널을 반환하는 함수를 작성했다. 이에 따라 단방향 채널의 반환값이 결정되고, 함수의 호출 부는 해당 단방향 함수에서 값을 읽어오기만 하면 된다.
이렇게 작성된 코드는 파이프라인 의 패턴으로 확장하기도 쉬울뿐더러 단방향 채널의 반환값이 주는 매력에 빠졌다.
이에 따라 함수 호출자는 동시성에 대한 고려 없이 그냥 함수를 호출만 하면 되고, 함수 의 내부로직에서 동시성의 관리만 해주면 된다.
또한 해당 고 루틴 들은 각자 투표의 아이디 값을 기준으로 map을 통해 관리되고 map에서 삭제되는 순간에는 mutex lock을 걸어 동시성 이슈를 제거하고자 했다. 이렇게 하다 보니 차라리 투표의 핸들러? 를 만들어서 구조체 하나에서 관리하는 것이 더 좋다고 생각해 apiHandler의 구조체에 추가해 주었다.
최근 동시성 프로그래밍 책을 읽고 막 책에서 소개해주는 복잡하고, 어려운 패턴을 적용한 것이 아닌 동시성 프로그래밍에서 강조하는 컨택스트의 관리를 왜 함수 내부에서 관리하는 것이 좋은지 코드를 작성하면서 알게 되었다. 정말 코드의 작성이 간결하고 로직의 구현이 쉬웠다.
그러나 한 가지 고민되는 부분은 통계 테이블로 옮기는 과정이 upsert이다.
나의 설계에 따르면 투표가 진행 중이라면 1초에 한 번씩 고 루틴 신호가 발생해 upsert를 진행하는데, mysql 유후 커넥션 풀이 10개 필요시 20개까지 늘어나게 된다.
그렇다면 20개 모두 사용 중이라면 해당 선전됨 고 루틴은 하나의 upsert 가 진행되는 동안 나머지는 모두 대기상태에 빠져 생각보다 성능이 나쁘지 않을까 생각된다.
하지만 우리의 투표시스템이 막 100~200 개처럼 엄청 크다고 생각하지는 않는다. 만약 실제로 이렇게 진행되는 투표가 많이 존재한다면 차라리 배치 프로세스를 도입하는 것이 훨씬 이득인 것처럼 보인다.
또한 실시간 채팅 의 경우 투표의 시작 메시지에 대해 브로드 캐스팅이 필요하다 현재 투표가 진행되었다는 브로드캐스팅이 필요하다.
우리의 서버는 채팅 매니저- 나츠 - 채팅 이 존재하는데 나츠는 고로 만들어진 메시지큐 이다. 어드민 에서 발생되는 메세지 들은 고 루틴 의버퍼링 채널을 통해 5~10개씩 나츠로 밀어 넣어주고 나츠에서는 이를 브로드캐스팅으로 채팅 접속자에게 나눠주는 구조로 되어있다.
이에 우리 채팅매니저 쪽에서는 밀어 넣어주는 메시지에 대해서 시그널에 대해 정의가 되어있는데 기존 것을 활용하기 에 적합한 곳이 없어 투표라는 시그널을 뚫어주었다.
추후 나츠를 이용한 채팅을 간략하게 구현하는 것을 포스팅해보겠다.
확실히 CRUD 쪽에 있어서는 4~5월 프로젝트할 때보다 상당히 많은 시간이 줄어든 것 같다. orm에 익숙해지고 어떤 쿼리가 나갈지 알고 있으니 코드작성에 막힘없이 작성했으며, 사실 테스트 코드는 작성하지 않았다. 테스트 코드라기보다는 그냥 저 채팅 시뮬레이터 같은 것을 만들어서 신호에 따라 쿼리가 나가고 데이터 업데이트가 잘 되는지에 대해서 만 시뮬레이션을 돌렸다.
이번 개발 역시 시간이 부족했다. 여름휴가 기간이다 보니 다들 휴가를 가고, 휴가 복귀 후에 테스트를 원하시니... 적어도 8월 초에는 마무리를 지어야 하지 않겠는가?...
실제 개발한 API는 17개 이고 현재 진행 중인 고 루틴에 대해 확인하는 테스트용 api까지 총 18개를 작성했다. 3개를 제외한 나머지는 단순 CRUD 여서 코드가 많았지 생각보다 고민시간 없이 수월하게 작성했으며 3개는 라이브와 고 루틴의 생성과 삭제에 연관되어 있다 보니 생각보다 고민을 많이 하면서 작성했다.