Github Actions로 JS/TS 파일 실행시키기(node.js) + 토이프로젝트 소개

Siwon Yoo·2023년 1월 3일
9
post-thumbnail

서론, 간단한 토이 프로젝트 소개

본 포스트는 간단하게 진행한 프로젝트에 대한 소개와,
Github Actions를 활용해 Typescript(혹은 Javascript) 파일을 실행하는 과정에 대해 설명합니다.

Github Actions와 Node.js를 활용해 Push 등의 액션이 발생하거나, 매일 원하는 시간에(Cron scheduler 활용) TS/JS 파일을 실행하도록 할 수 있습니다.

방학을 맞아 매일 할 일을 기록하는 간단한 웹페이지를 만들었다.

깃허브의 잔디를 가꾸는 것처럼, 크게 네 가지 주제에 대한 매일의 달성도를 표기할 수 있도록 만들었다. 달성한 항목은 초록색, 달성하지 못한 항목은 빨간색으로 표현된다.

주말의 경우 반투명하게 표현되고, 아직 실천이 가능한 항목(오늘 아직 완료하지 않은 항목)들은 검은색으로 표현된다.

사용한 기술들은 아래와 같다.

  • Next.js (Deploy/Vercel)
  • Node.js (Serverless APIs)
  • CSS Module
  • Typescript
  • MongoDB (Deploy/Atlas)
  • Github Actions

일주일간 조금씩 만들었고, Github Repository는 여기 있다.

Next.js에서 제공하는 API routing을 활용해 필요한 API는 Serverless로 구축했고(Node.js 기반), 타입 체킹을 위해 Typescript 기반으로 작성했다.

DB는 추후 오픈소스 혹은 다른 사용자들을 위한 서비스로 발전시킬 여지를 남겨두기 위해,
스키마를 편하게 수정할 수 있도록 NoSQL 기반인 MongoDB를 사용했다.

웹앱은 Vercel(Next.js 제작사라 연동이 편하다), DB는 Atlas(MongoDB에서 제공하는 Cloud DB)를 활용해 배포했다.

그날 한 일을 매일 했다고 새로 체크하기 위해 새 데이터가 매일 한 row씩 추가되어야 했고,
Github Actions를 활용해 이 과정을 자동화했다. Github Actions에서 cron scheduler(리눅스 기본 스케쥴러)를 활용해 원하는 시간에 작업을 반복하도록 구성할 수 있었다.

Why?

나의 경우 Next.js에서 제공하는 API routing을 활용해 Serverless로 구현했기 때문에, 매일 자정에 그날의 데이터를 추가하도록 DB를 업데이트하기 위한 scheduler가 필요했다.

AWS의 EC2 인스턴스를 하나 새로 띄우거나, GCP에서 제공하는 Free Tier를 사용해도 되었지만,
이왕 Serverless로 동작하는 서비스를 구축하기 시작한 김에 컨셉을 지키고 싶었다.
가볍게 돌아가고, 서버 관리 소요가 없는 방식으로 구현하고 싶었다.
서버 관리하는게 은근.. 해 보니까 혹시 요금이 발생하지는 않았는지 신경이 많이 쓰였기 때문이다. 또, 어차피 나 혼자 사용할 간단한 웹앱을 위해 24시간 동작하는 서버를 낭비하고 싶지 않았다.

Node.js <-> MongoDB Driver를 활용해 MongoDB에 DB 업데이트 요청을 보냈기 때문에,
스케쥴러가 Node.js 파일을 실행하도록 해야 했다.

Serverless 앱에 Cron job을 수행하는 방법은 AWS lambda를 사용해도 되었지만,
Github repo를 사용하고 있었기 때문에 Github Actions를 쓰는 게 통합 관리하기 더 쉽겠다는 판단이 들었다.

Github Action을 활용해 Cron Job으로 JS 파일을 실행하는 방법을 찾았는데,
이를 활용한 CI/CD 파이프라인을 구축하는 방법을 설명하는 글은 많으나, 자바스크립트 혹은 타입스크립트 파일 실행을 자동화하는 방법에 대해 설명한 글은 거의 없어 글을 작성한다.

How?

Github Actions 공식 문서 를 보면, Github actions가 뭔지, 어떻게/왜 쓰는지 확인할 수 있고, 대부분 CI/CD 용도로 많이 활용한다.(배포, 테스트 과정 자동화 등)

공식 문서의 Examples를 보면, 다음과 같이 Github actions를 활용해

  • 스크립트를 작성하고 Github actions Runner를 사용해 스크립트를 실행하는 방법
  • Runner에서 GitHub CLI를 사용하는 방법
  • 다양한 expressions, matrix 등을 사용하는 방법

Github actions에서 스크립트를 작성해 내 Github repo에서 원하는 script를 실행할 수 있는 예제도 제시한다.

내 프로젝트의 script들은 package.json 파일에 정의되어 있는데, package.json 파일을 잘 살펴보면, scripts: {} 안에 스크립트들이 있는 것을 확인할 수 있다.
혹 기존에 아무 sciprt도 없다면, 만들면 된다.

실행하고 싶은 파일을 실행하는 코드를 새로운 script로 만들어 보자.

JS 파일의 경우, Node.js를 활용해 코드를 실행하려면 터미널에 node 파일명.js 라고 입력한다.

TS 파일의 경우, ts-node라는 npm에 출시된 패키지를 통해 타입스크립트 파일을 실행할 수 있다.
설치되어 있지 않다면, npm i ts-node를 통해 패키지를 설치한 후, npx ts-node 파일명.ts를 통해 TS 파일을 터미널에서 실행할 수 있다.

나의 경우 pages 디렉토리api 디렉토리batch_daily.ts 파일 을 실행하고 싶었기 때문에, 위와 같이 newday라는 script로 해당 파일을 실행하는 명령어를 입력해 주었다. "newday": "npx ts-node pages/api/batch_daily.ts라고 작성했다.

만약 src 디렉토리index.js 파일hello 라는 이름의 script로 실행하고자 한다면

"scripts: {
	"hello": node src/index.js
}

와 같이 script를 적어 줄 수 있겠다.

이제, 터미널에서 npm run script명을 쳐 보자. 나의 경우는 npm run newday이고, script가 hello였다면 npm run hello이다.
터마널 입력 시 JS/TS 파일이 정상적으로 실행되는 것을 확인할 수 있다.
혹 정상적으로 실행되지 않았다면, Node.js 혹은 ts-node 등이 제대로 설치되었는지 확인해 보자.

script를 제대로 작성했다면,

Github workflow를 만들어 보자.

우선, 내 디렉토리에 .github 디렉토리와, 그 안에 workflows 디렉토리를 만들어야 한다.
그리고 그 안에 .yml 확장자의 파일을 만들어야 한다.

yml 파일의 문법까지 모두 설명하려면 내용이 너무 길어지므로,
공부하고 싶으면 구글에 yml 문법을 검색해보도록 하자.

나는 add-newday-data 라는 이름의 파일을 만들었다. 파일명은 자유롭게 지어도 좋다.

Github Actions 공식 문서 를 보면, Github Action을 사용하는 Tutorial이 있다. 원하는 대로 Github Actions를 사용하고 싶다면 공식 문서를 탐독해 보자.
JS/TS 파일을 컴파일하고 싶다면 다음과 같이 따라하면 된다.

아래는 내가 작성한 add-newday-data.yml 파일이다.
on: 구문은 언제 이 action이 실행될지를 정하는 부분으로, Github Repository에 push 했을 경우에 실행되길 원한다면 on: push로, fork되었을 경우에서도 사용하고 싶으면 on: [push, fork]와 같이 사용할 수 있다.

name: Run newday data

on:
  schedule:
    # 시간이 UTC 기준이므로 15시가 한국의 자정!
    - cron: '15 0 * * *'

jobs:
  build:
    runs-on: ubuntu-latest
    name: Update daily data to DB
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: 'npm'

      - name: Generate env variables for DB update
        env:
          MONGODB_URI: ${{ secrets.MONGODB_URI }}
        run: echo "DBURI=$MONGODB_URI" >> .env

      - name: Install npm dependencies
        run: npm ci

      - name: Update daily data
        run: npm run newday

나처럼 매일 특정 시간에 사용하고 싶으면 scheduler을 사용하는데,
나의 경우 Ubuntu에서 돌아가는 runner을 사용했기 때문에, cron이라는 스케쥴러를 사용한다.
cron scheduler을 사용하면, 언제 runner가 action을 수행할지 정의할 수 있는데, 자세한 cron 문법은 위키백과를 참조하자. Crontab Guru라는 웹사이트를 활용하면, 작성한 cron expression이 올바른지 확인할 수 있다.

Quickstart를 위해 간단히 설명하면, 가장 앞에 두 개의 숫자가 를 나타낸다.
0 0 * * *을 실행하면 UTC+0 시간대 기준 자정에 액션이 수행된다. 한국은 UTC+9의 시간대에 있으므로 - cron:0 15 * * *로 정의하면 한국 기준 매일 자정에 액션이 수행된다.

그 이후 jobs: build: runs-on: 등의 구문으로 액션에 대한 정의를 하고,
steps: 를 통해 내 액션이 어떤 단계를 밟아 수행될지를 정한다.
여기서 대충 설명하는 모든 것들에 대한 자세한 문법은, 공식 문서에 아주 친절하게 설명되어 있다.

간략하게 내 yml 파일을 설명하자면,

  • Setup을 하고,
    • checkout은 Github actions가 내 레포지토리에 접근할 수 있도록,
    • setup-node는 Node.js를 사용할 수 있도록 버전 등을 명시한다.
  • Runner가 도는 환경에도 .env 파일을 만들어 내용을 채우고,
    • Github Repo에 등록한 secret을 불러와 Runner가 도는 리눅스 환경에도 .env 파일을 만든다.
  • npm dependency를 설치하고,
    • npm ci는 자동화 환경에서 npm install 역할을 수행한다.
  • 아까 작성했던 npm run newday 스크립트를 실행한다.

name:은 각 step에 대한 이름을 정의하는 부분이고, 아래와 같이 Runner가 작업을 수행하며 각 단계별 상황을 나타내 준다. 이름을 짓지 않으면 아래와 같이 Run actions/checkout@v3와 같이 어떤 작업을 수행했는지 표시해 준다.

작업 수행에 필요한 env 변수가 있다면, 잘 등록해 주자.

작업을 수행하며 노출되면 안되는 환경 변수들이 있다면, 이를 Github repository에 secrets로 등록한다. Runner가 작업을 수행하며 등록된 secret을 읽어와 의도대로 동작을 수행할 수 있다.

나의 경우 DB에 데이터를 추가하는 과정에서 DB에 접속할 수 있는 URI가 필요했기에, MONGODB_URI라는 이름의 Repository secrets를 생성해 작업 중 action이 이를 읽어올 수 있도록 처리해 주었다.

Github repository > Settings > Secrets > Actions로 가면, Repository secrets를 설정할 수 있다.
Environment secrets와, Repository secrets는 다르다. Repository secrets에 가서 사용할 env 변수를 등록해야 올바르게 Runner가 변수를 읽을 수 있다.


다시 강조한다. Repository secrets에 작성해야 한다.
나처럼 많은 실패 메일을 받아보고 싶지 않다면..

공식 문서에서 이 부분을 잘 설명해 주고 있으나,
Github Actions 문서가 너무 방대해 다 읽지 못하고 작업을 시작했다가
Environment secrets에만 변수를 등록하고 왜 안돼!!!를 2시간가량 외칠 수도 있으니 주의하자.

이 글과 공식 문서를 잘 참고해 env를 설정했다.

Runner가 수행되는 환경에서 echo>>를 사용해 .env 파일을 직접 만드는 과정이다.

내 코드를 보면 아래와 같은데,

      - name: Generate env variables for DB update
        env:
          MONGODB_URI: ${{ secrets.MONGODB_URI }}
        run: echo "DBURI=$MONGODB_URI" >> .env

Runner가 동작할 때 해당 환경(리눅스 쉘 환경)에 MONGODB_URI라는 변수로 깃헙 Repo에서 Secret으로 등록한 ${{ secrets.MONGODB_URI }}의 값을 읽어들여 저장하고,

echo>>를 활용해 해당 환경의 .env 파일에 "env변수=env값"의 값을 저장한다.
만약 당신의 로컬 .env 파일에 REACT_APP_어쩌구=1234라고 환경 변수가 적혀 있다면,

      - name: 짓고 싶은 이름
        env:
          변수: ${{ secrets.Github Repo scret에 저장한 KEY }}
        run: echo "REACT_APP_어쩌구=$변수" >> .env

라고 작성해 Runner의 환경에 .env 파일을 만들 수 있겠다.

잘 안된다면, 리눅스 환경에서 escape가 필요한 부분을 누락시키지는 않았는지 확인해 보자.
리눅스 환경에서 ABC="123"로 변수에 값을 넣으면, $ABC를 출력했을 때 쌍따옴표를 빼고 123만 들어간다. 만약 쌍따옴표를 넣고 싶다면 \"와 같이 escaping이 필요하고,
비슷하게 리눅스 환경에서의 escaping을 무시하고 값을 넣지는 않았는지 확인해 보라.

사실 공식 문서에 따르면, 이와 같이 secret을 command line에서 바로 불러내는 방법은 권장되는 방법은 아니다. 다른 유저가 탈취할 수 있는 방법이 존재하기 때문에, 보안이 중요한 프로젝트를 수행할 때에는, 다른 방법을 이용하는 것이 좋을 것 같다. 리눅스는 멀티 유저 시스템이고, 누구나 현재 실행되고 있는 프로세스 등을 쉽게 확인할 수 있다는 점을 명심하자.

간단하게 Github Actions를 사용해 node.js 로 JS/TS 파일을 실행하는 방법을 알아보았다.

Github Actions는 CI/CD를 위한 툴이지만, 다양하게 활용할 수 있다.
본 글에서 소개한 것과 같이 서버리스 앱을 구축할 때 Scheduler의 역할로도 사용할 수가 있고,
원하는 scripts를 실행할 수 있기에 이외에도 활용도가 높을 것으로 생각된다.

개발할 때 한글로 된 정보가 많지 않은 탓에 영어 실력이 나날이 늘고 있지만, 오히려 좋아
이 글로 도움을 받는 사람이 많으면 좋겠다😄

profile
세상은 넓고 배울 건 언제나 많다😃

0개의 댓글