배포 자동화는 처음 접하면 까다로운 개념이고 복잡해보이지만 실제로 작업해보면 그리 어렵지 않게 작업할 수 있다. 그리고 배포에 드는 수고를 크게 줄여줄 수 있다. Cloud 서버를 비롯해서 많은 서비스에서 이와 같은 서비스를 제공한다. 이번 글에서는 Github Actions를 이용해 Dedicated Server (Cloud 서비스, 호스팅 서비스가 아닌 서버 하나를 임대 또는 구축하여 사용하는 경우)에서 배포하는 방식에 대해서 소개하겠다.
이전에 다른 글에서 소개했다시피, 나는 토이 서버로 Odroid 를 이용한 서버를 가지고 있다. 간단한 어플리케이션을 구동하는 데에 주로 사용했지만, 이번에는 이것으로 웹 어플리케이션을 배포하게 되었고, 배포 자동화를 구현할 필요성을 느껴서 작업하게 되었다.
다양한 언어, 프레임워크, 서비스를 통해서 배포 자동화를 구현할 수 있지만, 기본적으로 배포 자동화는 다음과 같은 방식으로 구현된다.
어떤 트리거를 감지했을 때, 코드 저장소에서 코드를 모두 가져온다(clone). 보통은 특정 브랜치에 commit 이 이뤄졌을 때가 될 것이다. AWS의 CodeCommit 이 이와 같은 일을 한다.
(1)에서 가져온 코드를 빌드용 서버(프로덕션 환경과 동일한 환경을 가져야 한다)로 가져온 뒤, 절차에 따라 빌드를 실행한다. 자동화 테스트가 있다면 테스트도 실행한다. 이 때 언어, 프레임워크마다 빌드를 하는 방식이 다르기 때문에, 빌드용 스크립트를 작성해야 한다. AWS의 CodeBuild가 이 일을 작업한다.
빌드 결과물을 production 서버로 전달하고 재시작한다. 클라우드 서버에서는 블루/그린(무중단 배포 방식) 배포를 위해서 기존 서버 인스턴스를 유지한 상태로 새 인스턴스를 생성하여 배포를 진행하고, 라우팅을 변경하는 식으로 진행하기도 한다. AWS의 CodeDeploy가 이 작업을 진행해준다.
깃허브에서는 Github Actions 라고 하는 서비스를 제공하는데, 커밋이 실행되었을 때 특정한 순서로 스크립트를 실행할 수 있는 기능이다. 이 때 Github Hosted Runner 라고 하는 클라우드 인스턴스를 제공하고 사용자는 이 인스턴스를 이용해서 자동화 테스트나 빌드를 진행할 수 있다.
위의 코드 클론 / 빌드 / 배포의 세 개의 과정을 실행하기 위해선 Dedicated Server로 코드의 변경 또는 빌드 결과물을 Dedicated Server로 전달할 필요가 있다. 문제는 어떻게 하느냐는 것이다.
Github Hosted Runner가 Dedicated Server로 SSH 접속을 해, 코드 클론 / 빌드 / 배포를 실행한다. 이 방법은 가장 단순하고 구현하기도 쉬운 방식이다. 빌드 환경과 프로덕션 환경을 동일한 환경으로 유지하기도 쉬운데, 프로덕션 환경이 곧 빌드 환경이 되기 때문이다.
그러나 이 방법은 사용하지 않기로 했다. 커밋과 동시에 생성되는 Github Hosted Runner의 SSH 접속을 허용한다는 것은 내 네트워크에 임의의 SSH 접속을 허용한다는 이야기가 된다. 네트워크의 폐쇄성을 유지하는 게 보안상 훨씬 유리했다.
커밋이 실행된 시점에서 커밋이 실행됐다는 정보만을 담은 리퀘스트를 실행하고, 해당 리퀘스트를 CI/CD 툴이나 웹훅 클라이언트를 통해서 감지하고 코드 클론 / 빌드 / 배포 순서로 실행하는 방법이 있었다.
그러나 아주 단순한 프로젝트를 진행하는만큼 이는 배보다 배꼽이 더 커질 염려가 있었다.
다행히 Github는 Self-hosted Runner 라는 기능을 지원했다. 커밋이 실행됐을 때, 클라우드 인스턴스 대신, 특정한 서버에 설치된 Github Runner 가 대신 작업을 수행하게 하는 기능이다.
Production Server 에 Self-hosted Runner를 설치 후, 스크립트를 Self-hosted Runner 가 실행하게 하는 것으로 위의 문제를 쉽게 해결할 수 있었다. 코드 클론 / 빌드 / 배포 모두 프로덕션 서버 내에서 실행하되, Github Actions 가 커밋이 되었다는 정보만 전달하게 하는 것이다.
다음은 실제로 사용한 github actions 용 스크립트다.
name: Node.js CI
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: odroid # self-hosted runner의 tag 값이 들어간다.
strategy:
matrix:
node-version: [22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- name: build tailwindcss
run : npm run tailwind-once
- name: Deploy
run:
cp -r $GITHUB_WORKSPACE/* /home/pjc1991/myfirstnodejs
- name: Reload node
run: pm2 restart myfirstnodejs