1. 개요
반쪽짜리 결과물이 탄생한 거 같아서 기분은 좀 별로지만, 그래도 간만에 개발을 하면서 몰입했던 것 같다.
세상에 없는 기능을 내 손으로 구현한다...애초에 이게 재밌어서 코딩을 시작한 건데, 요새 내가 뭘하고 있었던 걸까.
혹시나 더 나은 아이디어가 있거나, 직접 구현해볼 분들이 있다면 해당 기록을 참고하여 시행착오를 줄일 수 있었으면 좋겠습니다.
애석하게도 현재 버전은 온전히 나만을 위한 세팅이라, 아마 다른 사람이 사용하기엔 무리가 있을 것이다.
그리고 Github Action을 이 당시 처음 다뤄본 거라 이상한 점도 많을 것이다. (일단 지금은 이걸로 대만족)
우선 현재 구현한 부분에 대해서 기능과 사용법을 요약하자면 이렇다.
🟡 기능
- github에 Event가 발생하면 Kakao Developer에 등록된 팀원과 나에게 알림을 전송한다.
🟡 흐름
- github action으로 event 감지
- 실행 중인 self-hosted runners 탐색
- runner에 chrome 설치
- macOS와 Windows OS 식별하여 event type 별로 TITLE, DESC 작성하는 쉘 스크립트 실행
- node.js로 구성한 애플리케이션을 실행하여 환경 변수 주입
- Kakao Developer에 등록한 Application에 OAuth 인증을 통해 팀원 및 '나'에게 메시지 전송
2. 아이디어 및 플로우 차트
사실 이 서비스를 쓸 바에 slack이나 discord에 web-hook을 걸어버리면 훨씬 다양한 기능을 지원받을 수 있다.
그럼에도 왜 이런 기능을 개발했는가.
팀원이 디스코드나 슬랙을 자주 확인할 것 같지 않았기 때문이다. ^^
아무래도 프로젝트 경험이 적거나, 학부생이라면 해당 서비스에 대한 친숙도가 적을 것이고
그만큼 자주 확인하지 않게 되므로, 결국 카톡으로 확인 문자를 주고 받게 될 것이다.
그러다 문득 '이럴 바엔 그냥 카카오톡으로 바로 메시지를 주면 되지 않을까?'라는 생각이 스쳐 지나갔고 이런 서비스를 구현하게 되었다.
하면서 '포기할까', '내가 이딴 걸 왜 만들고 있지' 싶다가도 재밌어서 끝까지 만들게 되었다.
원래 이걸 사용하면 되지 않을까 했는데, access token이 만료될 때마다 내가 수동으로 갱신해줘야 하는 시스템이길래 바로 갖다 버렸다.
그래도 아이디어를 채용하면 어찌저찌 만들 수 있을 거 같아서 시작된 우당탕탕 카카오톡 챗 ci 액션 개발기.
1️⃣ github action으로 event 감지
2️⃣ 실행 중인 self-hosted runners 탐색
3️⃣ runner에 chrome 설치
4️⃣ macOS와 Windows OS 식별하여 event type 별로 TITLE, DESC 작성하는 쉘 스크립트 실행)
5️⃣ node.js로 구성한 애플리케이션을 실행하여 환경 변수 주입
6️⃣ Kakao Developer에 등록한 Application에 OAuth 인증을 통해 팀원 및 '나'에게 메시지 전송
3. 시행 착오
1️⃣ 카카오 OAuth 인증을 위한 여정
내가 만드는 건 백그라운드에서 동작하는 node.js 애플리케이션이므로, 지금처럼 친절하게 카카오 로그인 페이지를 볼 수 있는 환경이 아니다.
처음에는 로그인 url만 따와서 아이디와 패스워드 정보를 보내주면 되지 않을까 했는데, 얘네 무슨 packet으로 보낼 때부터 암호화를 하고 있었다.
그러면 방법은 크롤링을 해서 id, pw를 주입한 다음 로그인 버튼을 누르는 매크로 기능을 구현해야 했는데, 여기서부터가 악의 근원이었다.
puppeteer 라이브러리를 끌고 와서 어찌저찌 로그인을 시켜줬더니 2차 인증을 해줘야 한다고 한다.
이걸 범용적인 CI로 만들려면, 2차 인증 설정이 되어 있느냐 아니냐에 따라 또 분기를 나눠야 한다.
문제는 2차 인증을 하면, 확인을 누르고 페이지가 하나 더 나오길래 화나서 그냥 내 2차 인증을 풀어버렸더니 그냥 통과했다. (보안성 감점..)
2️⃣ 친구 목록 불러오기
다음은 Kakao Developer 멤버로 등록된 유저 목록을 불러와야 하는데, 이게 안 뜨길래 의아했다.
그래서 내가 설정을 잘못했나 싶었는데, 여기서 이상하다 싶었던 건 에러가 아니가 아예 빈 값을 돌려주는 것.
그 말은 요청이 잘못된 게 아니라 뭔가 요구 사항을 충족시키지 못 했다는 의미일 확률이 높았다.
그런데 아무리 설정값을 바꿔도 안 뜨길래 혼자 골머리를 앓고 있었는데, 어처구니 없는 이유였다.
요약: 팀원도 access token을 발급받아야 한다. (미친 거 아냐?)
솔직히 여기서 프로젝트 관두려고 했다.
왜냐하면, 그럼 이 기능을 사용할 때마다 '나'와 그룹 내 모든 유저의 OAuth 인증을 통해 accessToken을 발급 받아야 하는데, accessToken은 기본적으로 유효 기간이 짧지 않은가?
그럼 모든 멤버의 카카오톡 아이디, 비밀번호를 Github secret에 올려놓고 action이 돌 때마다 전부 다 로그인 크롤링을 해야 한다는 건데 장난 하냐?
심지어 로그인 할 때마다 다른 기기에서 로그인 했다고 알림 뜨는데, 이럴 거면 그냥 로그인 알림뜨면 '누가 PR 올렸나보다~' 이렇게 생각해도 무방한 수준.
그런데 그나마 다행인 점은 한 번 accessToken 발급 받아두면, 시간 제한의 제약이 거의 없다고 봐도 무방하다는 것이었다.
혹시나 해서 24시간 뒤에 보내봤는데도 잘 보내지고, 나중에는 며칠 씩 지나서 보내도 잘 보내지는 것을 확인했다.
(다만, 멤버가 2명이면 그 중 한 명만 장시간 유지되고 다른 한 명은 또 유지가 안 된다. 뭐 이딴 시스템을 만들어놨는지 도저히 이해가 안 감.)
3️⃣ Self-hosted runner로 전환
puppeteer로 크롤링하는 환경은 백그라운드의 self-hosted runner이므로, `headless: false` 옵션을 걸어서 작동한다.
그런데 자꾸 local은 잘 되는데, Gihub action에서 실행하면 timeout error가 나면서 실패했다.
분명히 중간에 또 이상한 페이지가 떴겠거니 싶어서 크롤링 해주려고 했는데, 로컬에선 안 보이니 받아온 html을 아예 로그로 전부 뿌려봤더니
...그렇다.
Gihub action을 실행하기 위해 ubuntu-latest를 실행시키면, 해당 OS가 실행되는 서버가 해외에서 카카오톡으로 내 계정의 로그인 요청을 보내게 되고,
카카오에서는 비정상적인 접근으로 간주하여 내게 로그인할 것인지를 물어보는 거였다.
솔직히 처음에는 2차 보안 해제한 것처럼, 해외에서 로그인 차단하는 기능도 풀어버리려고 했는데, 웃긴 게 그렇게 하려면 내가 그 위치에 가있어야 해제가 된다는 거다. ^^
예를 들어, 미국에서 로그인 요청하는 거 제한 해제하려면 내가 미국에서 직접 등록을 해줘야 한다.
그렇다고 기기를 특정해서 풀어주자니 로그인 이력을 조회했을 때, 매번 IP 주소가 바뀌어버려서 그렇게 하지도 못 한다.
일정 시간이 지나면 추가 인증이 필요 없다길래, 페이지 대기 시간을 늘려보기도 했는데 실패했다.
그러다 찾은 것이 self-hosted runner를 등록하는 것이었다.
Github action을 실행시키는 환경을 랜덤하게 띄우는 게 아니라, 내 PC의 OS 환경을 사용하는 방법이었다.
Host에서 Github을 listen하고 있다가, 이벤트가 감지되면 action을 수행하는 방식이다.
다만 이 방식의 문제점은 runner가 24시간 풀가동한 상태로 유지되어야 하는 것이었다.
심지어 몇 시간 이상 켜져 있으면 접속이 끊긴다고 해서, 중간중간 cron으로 재부팅까지 해줘야 했다.
결국 서버로 사용할 PC가 필요하기에 집에서 서버로 굴리고 있던 맥북을 서버로 가공하던지,
스크립트를 작성해서 PC가 켜질 때마다 자동으로 백그라운드에 runner을 실행시켜서 cron으로 정상 실행이 되도록 만들던지 선택을 해야 했었다.
그러나 정말정말정말 다행히도 Github에선 이런 경우를 위해, self-hosted runner를 서비스로 등록해주는 방법을 제시해주고 있었다.
windows, macOS, linux마다 설치 방법을 알려주고 있고, 매우 간단하게 세팅할 수 있다.
다만 이 방식으로 해도 한계점이 서버로 돌아가는 PC 하나가 언제나 실행 중이어야 한다는 조건이 필요했다.
그런데 서버로만 돌리던 내 맥북으로 요새 iOS를 취미로 공부한다고 들고 다니다보니, 전제 조건이 성립할 수가 없어 고민해봤는데
그냥 귀찮아서 팀원 PC로 내 카톡 로그인 한 번 로그인 해주고, 팀원 PC에도 runner를 돌려버렸다. (runner가 여러 개 실행되고 있으면, 네트워크 분산 처리하는 거 마냥 아무거나 하나 골라서 action이 실행된다.)
4️⃣ Chrome 설치
이건 진짜 광기의 클라이막스 정도 되는 파트였는데, 이젠 local 환경, runner를 실행했을 때 모두 잘 되다가
이전에 언급한 runner를 service로 등록해서 실행하면 실패하는 골 때리는 상황이 벌어졌다.
아니, 이건 대체 무슨 수로 디버깅하는데?
라고 말하고 프로젝트를 버리기엔 너무 먼 길을 와버려서, 이젠 진짜 어떻게든 구현을 하지 않으면 잠을 이루지 못하는 지경에 이르렀었다.
로그를 유심하게 읽다보니, 28번 째 줄의 node.js 실행 경로가 눈에 띄었다.
action-runner는 내가 서비스로 등록한 runner가 등록되어 있는 디렉토리고, _work는 뭐지?
하고 디렉토리를 열어 뒤져보니, self-hosted runner의 동작 방식은 대충 이런 식인 듯 했다.
- action이 실행되면서 listen 중인 runner 탐색
- runner에다가 node.js 애플리케이션을 통채로 올림
- 그 안에서 node.js 애플리케이션 실행
그래서 예외 로그를 하나 더 추가해서 다시 action을 돌려봤더니 다음과 같은 문제가 발생했다.
에러 원인은 즉, 크롤링하려고 puppeteer 쓰려고 보는데 Chrome을 못 찾는다는 이유였다.
service로 등록된 runner가 정확히 어떤 원리로 돌아가는지 파악할 수 없는 와중에,
로컬의 크롬 경로를 지정해주기엔 너무 이식성이 바닥까지 떨어지는 것 같아 차마 그렇게 하고 싶지는 않았다.
내 갤럭시북, 맥북, 팀원의 맥북 총 3대의 PC에 runner를 등록해놨는데, 그럼 PC 별로 chrome 경로를 지정해줘야 한다? 이건 내 자존심의 문제였다.
그래서 chrome을 설치하는 action을 끌고 와서 돌려 보니, 예상 대로 _work 경로 내에 크롬 실행 파일이 생성된 것을 확인할 수 있었다.
어차피 service 입장에선 작업 파일이 _work 디렉토리 내부이므로, 상대 경로를 잘 사용해서 PC 의존도를 떨어트리면서 chrome 실행 경로를 잡아줄 수 있을 것 같았다.
다만 여기서 한 가지 또 문제가 발생했었는데, Windows와 MacOS에서 실행하는 Chrome 경로가 다르다는 것이었다.
처음에는 Windows는 puppeteer, MacOS는 puppeteer-core를 따로 불러와야 하는 줄 알고 동적 임포트를 했는데, 사실 상관 없었다.
4. 결과
그래도 나름 고생한 만큼 잘 써먹고 있다.
특히 git push 할 때마다 카톡으로 알림 뜨는 거 보면 흐뭇함 그 자체.
그러나 개선해야 할 사항이 너무너무너무 많아서, 차마 오픈 소스로 올리기엔 한계점이 명확하다는 게 단점이다.
- 메시지를 보내는 주체는 '나에게 메시지 전송'으로 카톡이 와서 알림이 안 뜬다. (난 어차피 Github 자주 확인해서 딱히 불편함을 못 느꼈다.)
- runner로 등록된 모든 PC에 '나' 계정으로 로그인 하여 기기 인증을 모두 수행해줘야 한다. (보안 취약점)
- 2차 보안 인증 걸 수 없다. (보안 취약점)
- 크롬 설치가 생각보다 오래 걸려서 action 한 사이클 도는데 거의 3분이 소요된다. 너무 느리다. 도중에 PC 꺼버리면 action은 실패한다.
이런 단점에도 불구하고, 아무도 만들지 않은 나만의 프로젝트에 적용하기 위한 기능을 만들어 볼 수 있어서 너무 즐거웠던 경험이었다.
물론 두 번 하고 싶은 경험은 아니었다.