문제점

AWS ECS 클러스터 인프라 인스턴스 유형을 amd64에서 arm64로 변경 후, GitActions에서 배포가 되지 않는 상황이 발생했다.

 

GitActions에서 제공하는 기본 러너 환경이 X86_64 우분투 서버라, 해당 서버에서 빌드한 이미지는 arm64 인스턴스에서 실행을 할 수가 없었다.

 

따라서 배포할 인스턴스 아키텍쳐에 맞게 GitActions에 여러가지 시도를 해보았다.

 

1.  워크플로우 yml 파일의 도커 이미지 빌드 과정에서 docker buildx로 멀티플랫폼 이미지 빌드

워크플로우 yml에 Docker buildx 환경 설정
하단 명령어에 --platform 플래그를 설정해주고 이미지 아키텍처를 설정

위의 방법으로 빌드 시점에 이미지 플랫폼을 설정해주었지만...

 

이상하게도 워크 플로우 내에서 이미지 빌드 자체가 되지 않는 문제가 발생했다..

 

다음 방법으로 넘어가보자.

 

2. GitActions 러너 환경 자체를 arm64로 변경하기

Git에서는 GitActions의 런타임 환경레포지토리 별로 다른 서버를 호스팅해 사용 가능하게 지원한다. (기본적으로는 Ubuntu)

 

실행 가능한 환경은 MacOS, Linux, Windows 셋 다 가능하며 설정법은 꽤나 간단하다.

 

먼저, 레포지토리의 Settings에 접근 후, 좌측 메뉴의 Actions - runners 탭에 들어간다.

리눅스 뿐 아니라 다른 OS도 지원하는 모습. macOS도 x64, arm64 지원.
필자의 경우는 배포할 서버의 환경에 맞게 설정해주었다.

위와 같이 설정을 하게 되면 (접근 가능한) 서버에서 실행할 명령어를 보여주게 된다.

 

런타임을 제공할 해당 서버에 접속 후, 하단에 나오는 명령어를 차례대로 입력 하면 된다.

( 본인은 배포 환경에 맞는 도커 이미지를 맞춰주기 위해 arm64 EC2 인스턴스를 사용했다. )

 

 

Download Step

# Create a folder
$ mkdir actions-runner && cd actions-runner

# Download the latest runner package
$ curl -o actions-runner-linux-arm-2.307.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.307.1/actions-runner-linux-arm-2.307.1.tar.gz

# Extract the installer
$ tar xzf ./actions-runner-linux-arm-2.307.1.tar.gz

각 명령어들이 성공 되었으면 actions-runner 디렉토리에 다음과 같은 파일들이 생성 되어 있는 모습을 볼 수 있다.

 

Configure Step

# Create the runner and start the configuration experience
$ ./config.sh --url https://github.com/USER_NAME/REPOSITORY_NAME --token TOKEN

# Last step, run it!
$ ./run.sh

참고로 Git에서 EC2 서버로 접속하는 방식이 아닌 EC2 서버에서 GitHub 저장소로 접속하는 방식이다.

 

그래서 /USER_NAME/REPOSITORY_NAME 필드는 해당 github 저장소 주소,

--token TOKEN 액세스 토큰으로 설정해줘야 한다.

 

또한 위의 config.sh 스크립트를 실행할 때

 

해당 에러를 마주칠 수 있다. 이 에러는 GitHub Actions Runner가 실행 중에

 

ICU (International Components for Unicode) 패키지를 찾지 못해 발생하는 문제이다.

 

ICU는 국제화 및 지역화를 지원하는 라이브러리로, GitHub Actions Runner가 이를 사용하지 못하면 오류가 발생 할 수도 있다.

 

호옹...그렇다고 한다...

어찌됐든 해당 에러는 ICU 패키지를 찾지 못해서 발생하는 문제이므로 ,

 

해당 라이브러리는 현재 configuration 과정에서 필요 없으므로 이를 무시하는 옵션을 넣고 실행하면 된다.

 

DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true ./config.sh --url [저장소 주소] --token [토큰]

위의 스크립트가 성공적으로 실행되면 , 아래와 같이 Connected to GitHub 메시지를 볼 수 있다.

 

 

 

마지막으로 runner를 실행 시키는 스크립트 까지 실행해보자

DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true ./run.sh

성공적으로 서버에서 GitHub로 연결된 모습이며,

이때 action - runner 프로세스는 Job(Actions에서 발생한 워크플로우)을 Listening 상태로 대기하게 된다.

다시 Actions - Runner 탭에 들어가서 확인해보면 해당 레포지토리에 self-hosted runner 잘 등록되어 있는 걸 볼 수 있다.

 

runner Status가 정상인 걸 확인했으면 워크플로우 yml 파일을 다음과 같이 수정해주자.

jobs:
  deploy:
    name: Deploy
    runs-on: self-hosted
    environment: production
  • runs-on : 요 부분을 위에서 등록한 서버의 태그를 달아주면 된다. (만약 등록한 러너가 하나라면 self-hosted만 달아줘도 된다)
  • 만약 여러 개의 러너를 등록한 경우에는 등록한 러너의 태그에 따라
  • runs-on : [ self-hosted, Other(러너 목록의 해당 태그) , Other(러너 목록의 해당 태그) ]
    이런 식으로 세부적으로 분리도 가능하다.

 

이 후 , GitActions에서 워크 플로우를 실행 시킨 후, 연결된 서버에 접속하면,

해당 job이 워크플로우 순서대로 성공적으로 실행되고, 성공하는 모습을 볼 수 있다.

(이거이거...깃액션에서 도커 레이어 캐시도 더 잘 될지도..?)

ref : 

https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners

웹 서버 프로젝트를 pm2 로 관리하던 중 생긴 문제.

 

프로젝트 요구 사항 중, 사용자가 업로드 한 유튜브 영상의 조회수를 하루마다 갱신하여 DB에 저장해야하는 기능이 필요했다.

따라서 DB에 저장된 영상의 고유 id값을 가져와 YouTube API를 통해 조회수를 수집 후, 이를 다시 DB에 Insert 하는 API를 개발 하였다.

 

key_value는 영상 id, runtime은 해당 영상의 조회수.

따로 크롤러는 만들지 않고, 해당 프로젝트는 pm2 프로세스로 관리 되던 터라 직접 cron 라이브러리를 통하여 함수를 작성하였다.

  1.  DB에 저장된 비디오의 정보를 가져와 해당 비디오의 id값만을 따로 리스트로 만들어 valuesOfContentList에 저장해준다.
  2. valuesOfContentList getVideoViewsCount 함수를 통해 유튜브에서 해당 영상의 조회수를 가져온다.
  3. 1에서 가져온 id값과, 조회수를 각각 key, value형태로 만들어 DB에 새롭게 Insert 한다.
  4. 해당 함수를 node-cron 라이브러리를 통해 주기적으로 실행할 수 있도록 설정하였다.

해당 함수를 로컬에서 테스트 후, 잘 실행되는 걸 확인하였고 이에 안심하고 배포를 하게 된다...

그러나, 예상과는 다르게 영상별로 조회수가 22시에 한번씩만 Insert 돼야 했지만 한 영상마다 수집한 조회수가 15건이 되어버렸다..

중복으로 수집된 데이터로 인해, 영상별 조회수 통계를 보여주는 페이지에서 대혼돈이 찾아왔다...(눈물)

 

왜 로컬에선 딱 한번만 수집되고, 배포 환경에선 안 되는 걸까..라는 원인 모를 상황에 약 이틀 동안 머리를 싸맸다..

처음엔 코드 문제인가 싶어 다시 찬찬히 훑어보았지만 매우 정상적으로 잘 작동 했다..

 

그러자, 불현듯 스치는 생각

https://yunja.tistory.com/12 

 

[ Jenkins + pm2 ] 트러블 슈팅

사내 프로젝트를 젠킨스를 통해 배포 자동화를 구축 후, 문제가 발생하게 된다. 상기의 이미지처럼, Jenkins Build Step을 쉘 스크립트로 비교적 간단하게 작성했다. NestJS로 프로젝트를 구성했던 터

yunja.tistory.com

해당 포스팅에서 잠깐 언급이 되었는데, 잠깐 설명하자면 배포할 프로젝트는 pm2로 관리되며 이 pm2는 프로젝트 루트 경로의 ecosystem.config.js 파일을 토대로 인스턴스 갯수를 정하여 그에 맞게 프로세스가 실행된다.

 

module.exports = {
  apps: [
    {
      name: 'app',
      script: './dist/app.js',
      instances: 15,
      exec_mode: 'cluster',
    },
  ],
};

배포 환경에선 서버 인스턴스가 15개나 되어, 위에서 작성한 영상 조회수 수집 함수가 무려 15번이나 실행되었던 것이다 !!

 

따라서 나는 한 인스턴스에서만 조회수 수집 함수가 실행 되게 하고 싶었으며, 해당 방법을 모색해보았다.

module.exports = {
  apps: [
    {
      name: 'primary',
      script: './dist/app.js',
      instances: 1,
      exec_mode: 'cluster',
    },
    {
      name: 'replica',
      script: './dist/app.js',
      instances: 13,
      exec_mode: 'cluster',
    },
  ],
};

기존에는 동일한 이름의 인스턴스로 15개를 실행하였지만, 수정한 config.js는 

하나의 인스턴스에는 primary라고 이름을 지정해주고, 다른 인스턴스에는 replica라고 이름을 명시해주었다.

이렇게 되면, 배포 서버의 노드 프로세스는 primary 이름의 프로세스 1개, replica이름의 프로세스 13개가 실행되어 진다.

 

이에 맞게, node-cron 에서 실행되어지는 getVideoViewsCount 함수에도 현재 실행되는 프로세스 인스턴스의 이름이 primary 일 경우에만, 함수를 실행하도록 하였다.

 

함수 처음 부분에 process.env.name을 가져와 primary인지 검사하여 , 맞을 경우에만 함수를 실행하도록 변경하였다.

 

해당 함수까지 변경 후 ,처음 원했던 대로 정상적으로 22시에 함수가 정상적으로 primary 인스턴스에서만 실행되었다 !!!

'Trouble Shooting' 카테고리의 다른 글

GitActions 러너 환경을 arm64 EC2로 변경하기  (0) 2023.08.04
[ Jenkins + pm2 ] 트러블 슈팅  (0) 2023.07.15

사내 프로젝트를 젠킨스를 통해 배포 자동화를 구축 후, 문제가 발생하게 된다.

상기의 이미지처럼, Jenkins Build Step을 쉘 스크립트로 비교적 간단하게 작성했다.

 

NestJS로 프로젝트를 구성했던 터라,

  1. npm run build 후(TypeScript compile to JavaScript)
  2. 생성된 dist 디렉토리의 main.js 실행이 필요했기에 pm2 기본 config 파일을 생성(ecosystem.config.js)
  3. 해당 config.js 파일 내부에 필요한 인스턴스 갯수와, 실행 파일의 경로를 작성해주었다.
// root/ecosystem.config.js

module.exports = {
  apps: [
    {
      name: 'primary',
      script: './dist/main.js',
      instances: 1,
      exec_mode: 'cluster',
    },
    {
      name: 'replica',
      script: './dist/main.js',
      instances: 13,
      exec_mode: 'cluster',
    },
  ],
};

위의 단계를 통해 프로젝트 레포지토리에 푸쉬 이벤트 발생 시, 레포지토리에서 웹 훅을 발생시켜 Jenkins 서버에서 자동으로

위 첫번째 사진의 Build Step들이 실행되도록 구축하였다.

 

요약

  • Jenkins Build Step에서 노드 프로세스 매니저인 Pm2를 config.js 실행 시키도록 설정
  • 위의 Jenkins jobs 이 끝난 후, 배포 성공 여부를 슬렉 알림으로 받음
  • 배포 서버에 정상적으로 변경 사항이 반영된 것까지 확인 후, 모든 것이 끝났다고 생각했다.

그러나, 문제는 여기서 발생하게 된다.

  • 배포 서버의 pm2 로그 확인을 위해 ssh로 접속 후, pm2 logs를 확인 해보았지만 .. 아무것도 보이지 않았다
  • 뭔가 잘못된 것을 직감 하고 배포 된 웹서버에 접속해보았지만...이게 웬일 ? 실제로 웹은 정상적으로 동작하고 있었다. (ㄷㄷㄷ...)
  • 다시 말하자면, 젠킨스에서 pm2 start와 reload가 잘 일어 났지만, 서버의 pm2 list엔 전혀 찾아 볼 수가 없었다.

왜 이런일이 발생한걸까? 처음부터 접근 방법이 잘못 되었던 것이다.

 

Jenkins 잡에서 실행되는 pm2 프로세스는 서버 로컬 환경이 아닌 젠킨스 내의 새로운 세션에서 실행된다.

따라서 이 새로운 세션에서 pm2를 실행한 것이니, 로컬 환경에서 직접 프로세스의 목록을 확인이 불가능 했던 것이다.

 

또한 pm2는 현재 사용자 세션과 관련된 프로세스 목록만 표시하기 때문에, 젠킨스 내부의 새로운 세션에서 실행되는 프로세스는 해당 세션에 대한 정보를 표시 할 수 없으며, pm2 list에서 보이지 않았던 것이다.

 

서버 로컬 환경에서 pm2 list가 나오지 않았던 이유를 알게됐다.. 하지만 왜? 젠킨스는 이렇게 동작하는 걸까 ?

기존 처음 젠킨스를 접하고, 쉘 스크립트로 파이프라인을 구축했을 때, 해당 서버의 로컬 환경에서 정의된 쉘 스크립트를 그대로 실행하는 줄 알았다. 

그러나, 젠킨스 잡은 젠킨스에서 실행되는 작업 단위로, 일반적으로 빌드, 배포, 테스트 등의 작업만을 수행 한다.

 

위에 설명한 바와 같이 젠킨스 잡은 새로운 세션에서 실행되며, 이는 젠킨스 잡이 독립된 환경에서 실행되도록 하는 데 목적이 있다.

  1. 환경 분리 : 젠킨스 잡은 여러 개의 빌드 또는 배포를 병렬로 실행할 수 있다. 이를 위해 각 Job은 독립된 환경에서 실행되어야 한다.
    즉, 각 Job은 자체적인 프로세스 그룹 또는 세션에서 실행되어 서로의 작업에 영향을 주지 않는다.
  2. 격리와 안정성 : 새로운 세션에서 Jenkins Job을 실행함으로써, 현재 사용자 세션 또는 기존의 실행 중인 프로세스에 영향을 주지 않고 독립적으로 실행될 수 있다. 이렇게 함으로써 잡의 실행 중에 발생하는 예외나 에러가 다른 프로세스에 영향을 주지 않고 안정적으로 처리 될 수 있다.
  3. 자원 관리: 젠킨스 잡이 별도의 세션에서 실행되면, 해당 세션에 할당된 자원(메모리, CPU등) 을 더 효율적으로 관리가 가능하다.

따라서 젠킨스 잡은 새로운 세션에서 실행되며, 젠킨스에서 실행되는 pm2 프로세스는 새로운 세션에서 실행되어 pm2 list를 통해 직접 빌드 스크립트에서 확인을 해야한다.

 

젠킨스 pm2가 새로운 세션에서 동작한다는 것을 인지한 후, 해당 빌드 스크립트(젠킨스 잡)에서 프로세스 로그를 표현하기엔 보기가 상당히 불편했기 때문에 다른 방법을 모색 해보았다.

 

자동 업데이트 스크립트 구성하기

젠킨스에 의해 실행될 스크립트를 먼저 작성.

#!/bin/bash
sudo npm install # yarn이나 npm 둘 다 하나만
npm run build
pm2 restart ecosystem.config.js

해당 파일을 프로젝트 루트 경로에 생성 해주고, Jenkins 잡 빌드 스크립트에서 해당 파일을 실행 시켜준다면,

처음 원했던 대로 새로운 세션에서 실행되지 않고 서버 로컬 환경에서 pm2 프로세스로 실행이 되며 서버 로그를 쉽게 확인이 가능했다.. !

 

+ Recent posts