AWS

🐮 Vue 프로젝트에 Jenkins로 CI를 적용해보자

ಠಿ_ಠ 💻 2021. 8. 22. 14:37

이번 프로젝트를 진행하면서 프론트와 백엔드 CI 과정을 다시 경험했다. 그리고 이전 프로젝트에서 적용하지 못해서 아쉬웠던 부분을 개선해서 적용하게 되었다! 하지만 이전에 좋았던 부분을 적용하지 못한 부분도 있다..ㅎㅎ 이건 보완해서 다시 추가해놓겠다.

 

목표는?
Vue로 만들어진 프로젝트를 EC2 서버에 올려보자! 근데 이제 CI를 곁들여서 ...

 

사용한 기술 스택

Docker 컨테이너로 Jenkins를 실행하고, 거기서 Vue 프로젝트를 빌드해 나온 결과물 dist 폴더를 scp로 EC2 서버에 전송할 것이다. 그리고 그 디레터리를 Docker 컨테이너에 Nginx 위에서 정적 파일을 제공할 수 있도록 한다. gitlab에 특정 브랜치에 푸쉬하면 해당 프로세스가 진행될 것이다.

 

- Vue

- EC2

- Jenkins

- Docker

- Nginx

 

(사전 작업으로 Jenkins를 도커에서 실행시킨 상태라고 생각하고 진행하겠다. + gitlab같은 필요한 플러그인을 받았다.)

Gitlab 푸쉬 이벤트는 Jenkins를 trigger해!

가장 먼저 Gitlab의 어떤 브랜치에 푸쉬 이벤트를 주면, Jenkins가 알 수 있도록 설정해야 한다. 이를 위해, Gitlab의 API 토큰을 발급받아 연결해주자!

 

1) Gitlab 레포지터리에서 Settings > Access Tokens 에 가서 토큰을 생성했다.

토큰 생성 결과
Jenkins의 Credentials

그리고 이를 젠킨스에 등록해준다. 현재 사진에서 위에 있는 키가 발급한 토큰을 말한다.

 

2) 파이프라인을 생성한다.

Jenkins 파이프라인 생성
BuildTrigger 설정

위 사진에서 Pipeline을 선택해서 아이템을 만든다. 그리고 Build Trigger에서 푸쉬가 일어났을 때 빌드하는 어쩌구 저쩌구 내용을 선택한다. 사진에서도 보면 알 수 있듯이 아래에 적힌 URL이 웹 훅 URL이다. 그리고 아마 쟤를 체크했을 때 무슨 비밀 키 값이 나왔을 것이다. 얘네를 이용해 gitlab에 등록해줘서 푸쉬했을 때 트리거하도록 설정할 것이다.

 

3) gitlab에서 웹 훅 등록한다.

gitlab webhook

다시 깃랩 레퍼지토리로 가서 Settings > Webhooks에 간다. 방금 받은 값을 알맞게 입력해서 어떤 브랜치에 적용할 것인지까지 작성한다. 그리고 저장 후 테스트 버튼을 눌러서 성공적으로 동작하는지 확인하자.

 

아무일도 안 일어나는데?!

성공적으로 푸쉬 이벤트가 일어나는 것을 확인했어야 한다. 하지만 아무 일도 안일어난다. 이상한것같을 수도 있겠지만 당연하다! 왜냐면 우리는 무슨 동작을 하라고 정의한 적이 없다. 이를 pipeline에 작성해주면 된다.

 

프론트엔드 프로젝트니까 git clone -> npm install -> npm run build 해서 최종 원하는 결과물인 dist 폴더까지 생성할 수 있겠다.

이러한 과정을 Jenkins에서 하도록 파이프라인을 작성해주자. 이 모든 과정은 Jenkins 컨테이너 내부의 /var/jenkins_home/workspace/해당파이프라인이름 의 위치에서 진행이 된다.

 

1) 깃랩 프로젝트를 Jenkins 서버에서 클론을 받아보자.

나의 경우 private 프로젝트여서 그냥 클론 받을 수 없다. 그래서 Jenkins에 GItlab credential을 등록해 클론 받을 수 있는 상태로 만들어야한다.

 

Jenkins의 Credentials

 아까 위에서 봤던 Jenkins의 Credentials를 다시 보자. 아까 등록한 Gitlab API token 아래에 Gitlab이라는 ID로 또 어떤 값이 등록되어있는걸 볼 수 있다. Jenkins에 Gitlab 아이디와 패스워드를 등록해준 것이다. 얘를 이용해서 우리는 private 레퍼지토리를 클론받을 수 있는 상황을 만들었다. 이를 파이프라인에 명시해줘서 해당 credential을 이용해서 클론받도록 해야 한다.

 

그리고 아래는 클론받는 과정까지의 파이프라인이다.

pipeline {
    agent any
    tools {
      git 'git'
    }
    
    stages {
        stage('prepare') {
            steps {
                sh "git --version"
                # 나의 경우 Gitlab이라는 이름으로 계정의 아이디와 비밀번호를 등록했기 때문에 credentialsId에 Gitlab이라고 작성했다.
                git branch: '클론받을 브랜치명', credentialsId: 'Gitlab', url: '클론 받을 프로젝트 URL'
            }
        }
    }
}

(사실 저 tools에 git은 무시하고 해도 될것이다. 아마도 컨테이너에 git이 설치되어있는듯!? 정확히 확인해보진 못했지만^^! 나중에 확인해보겠다.)

 

이렇게 한 후에 Jenkins에서 Build Now 버튼을 눌러서 제대로 클론 받아지는지 확인해보자.

성공적으로 클론받아졌다면, 위에서 말한 경로에 들어가서 확인해보자. 직접 눈으로 보는게 잘 느껴져서 들어가보는걸 추천한다!

gitlab에 등록되어 있는 프로젝트가 잘 받아졌음을 확인할 수 있었다. 이제 한단계를 넘었다.

 

2) 클론받고 나서 프로젝트를 빌드해보자.

우리가 일반적으로 로컬 환경에서 할 때와 똑같이 빌드하려면, npm install 명령어로 필요한 모듈을 설치한 후 npm run build해서 dist 폴더가 성공적으로 생기는 과정을 진행해야 한다.

 

이 때, 우리의 환경에는 node 가 설치 되어있어야 한다. 그래서 npm install 이런 명령어가 안 먹을 것이다. 이를 위해 우린 Jenkins에 해당하는 tool을 등록해줄 것이다.

 

Jenkins 관리 > Global Tool Configuration 에서 등록해줄 것이다.

NodeJS 툴 등록

나는 node14라는 이름으로 현재 가장 LTS 버전을 등록했다. 이제 파이프라인에서 tools 에 nodejs 를 등록해서 사용할 수 있다.

잘 되는지 확인하기 위해서, node --version 명령어를 입력해서 확인해보자. 아마 잘 설치가 됐다면, 버전이 출력 될 것이다. Jenkins에서 console output을 통해 확인해보자.

버전 확인 결과

그리고 npm install, npm run build 를 실행하는 파이프라인의 내용은 아래와 같다.

pipeline {
    agent any
    tools {
      git 'git'
      # nodejs 툴을 node14라고 등록한 걸 사용한다
      nodejs 'node14'
    }
    
    stages {
        stage('prepare') {
            steps {
                sh "git --version"
                sh "node --version"
                git branch: '클론받을 브랜치명', credentialsId: 'Gitlab', url: '클론 받을 프로젝트 URL'
                sh "pwd"
                # 파이프라인에서 위치를 옮기려면 dir 을 이용해야 한다.
                dir('frontend') {
                    sh "pwd"
                    sh "npm install"
                    sh "ls -al"
                }
            }
        }
        stage('build') {
            steps {
                echo 'build'
                sh "pwd"
                dir('frontend') {
                    sh "pwd"
                    sh "npm run build"
                }
            }
        }
    }
}

npm install으로 하려면, package.json이 있는 곳에서 npm install을 해줘야 한다.

우리의 package.json은 기본위치인 /var/jenkins_home/workspace/해당파이프라인이름 에서 frontend 위치로 들어가야 한다.

 

위치를 변경하기 위해서 dir을 이용했다. dir('frontend') 해서 그 안에서 할 내용을 입력했다.

그 결과를 확인해보면 다음과 같이 node_modules가와 dist 폴더가 잘 생성된걸 확인했다.

 

3) 만들어진 dist 폴더를 EC2 서버로 보내보자. 

현재는 Jenkins 컨테이너 안에서 모든 상황이 이루어졌다. 나는 Nginx 컨테이너를 실행시켜서 웹 서버가 정적 페이지를 전달하도록 환경을 설정하고 싶다. 따라서 해당 dist 폴더에 담긴 모든 빌드 파일들을 우선 EC2 서버로 보낼 것이다. (왜 굳이 이렇게 복잡한 과정을 볼륨을 공유했는지 지금 생각해보니까 이해가 잘 안된다. 더 좋은 방법을 적용해서 보완하는 글을 다음에 작성해보겠다.)

 

우선 해당 dist 파일들을 EC2 서버에 전달하려면 scp 명령어를 통해 파일을 전송해야 한다. scp를 통해 Jenkins 컨테이너에서 EC2로 파일을 전송하기 위해서는 ssh 키가 필요하다.

 

마치 우리가 EC2 서버를 AWS에 만들어서, ssh 키를 받아 그 키만을 가지고 EC2 서버에 접속할 수 있도록 하는 상황과 똑같다. Jenkins 서버에서 해당 EC2 서버에 접근하기 위해 ssh 키가 필요하다. 그래서 우리는 Jenkins 서버에서 ssh 키를 만들고 public key를 EC2에 등록해서 접근이 가능한 상황을 만들어 줄 것이다.

 

Jenkins 컨테이너에 접속해서 다음과 같이 명령어를 입력한다.

ssh-keygen

그러면 여러가지를 물어보는 게 있을텐데 나는 아무값도 주지 않고 그냥 일단 생성했다. 필요하면 잘 읽어보고 등록하면 좋겠다.

그러면 ssh key가 생성되었을 것이다. 나는 다음과 같은 경로에 생성되었다.

key가 생성된 결과

 

그리고 Jenkins 컨테이너에서 다음과 같은 명령어를 통해 pulbic key 의 내용을 확인할 수 있다. 이제 이 퍼블릭 키를 EC2에 등록해서 접근할 수 있도록 환경을 설정해 줄 것이다.

cat ~/.ssh/id_rsa.pub

 

EC2 서버에서 다음과 같이 확인할 수 있다.

EC2 authorized key 확인 결과

기존에 있던 위에 있는 키에 이어서 방금 위에서 확인한 public key도 이어서 작성해주면 된다. 나도 원래는 위에만 있었는데 방금 추가해준 내용이다. 그럼 이제 Jenkins 컨테이너에서 EC2로 scp 를 통해 파일을 전송할 수 있게 되었다.

최종 파이프라인

pipeline {
    agent any
    tools {
      git 'git'
      nodejs 'node14'
    }
    
    stages {
        stage('prepare') {
            steps {
                sh "git --version"
                sh "node --version"
                git branch: '클론받을 브랜치명', credentialsId: 'Gitlab', url: '클론 받을 프로젝트 URL'
                sh "pwd"
                dir('frontend') {
                    sh "pwd"
                    sh "npm install"
                    sh "ls -al"
                }
            }
        }
        stage('build') {
            steps {
                echo 'build'
                sh "pwd"
                dir('frontend') {
                    sh "pwd"
                    sh "npm run build"
                }
            }
            post {
                success {
                    echo "success"
                    sh "pwd"
                    dir('frontend') {
                    	# scp로 dist 폴더를 해당 경로로 전달하겠다.
                        sh "scp -r dist ubuntu@123.456.789.10:/home/ubuntu"
                    }
                }
            }
        }
    }
}

최종 파이프라인은 다음과 같이 작성되었다. 파이프라인을 실행시켜서 제대로 동작하는지 확인해보자.

dist 확인 결과

내가 보낸 위치인 /home/ubuntu 경로에 dist 폴더가 잘 전달된 것을 확인할 수 있다.

 

4) dist 폴더를 volume으로 설정해서 Nginx 컨테이너를 실행시켜보자.

 

(EC2에 mkdir로 dist라는 폴더를 생성한 후)

docker run --name nginx -d -p 8080:80 -v /home/ubuntu/dist:/usr/share/nginx/html nginx

다음과 같은 명령어를 입력해서 nginx 컨테이너를 실행시켰다.

 

docker run

도커 컨테이너를 실행하겠다.

 

--name nginx

이름은 nginx라고 하겠다.

 

-d

백그라운드로 실행하겠다.

 

-p 8080:80

EC2의 8080번 포트를 통해서 Nginx 컨테이너의 80번 포트를 연결했다.

 

-v /home/ubuntu/dist:/usr/share/nginx/html

EC2의 /home/ubuntu/dist 와 Nginx 컨테이너 내부에 /usr/share/nginx/html 을 공유하도록 설정했다.

실제 컨테이너 내부에 들어가서 해당 경로를 보면, EC2에 공유하고 있는 경로에 파일이 존재함을 볼 수 있다.

nginx

nginx 이미지를 기반으로 컨테이너를 실행한다.

 

최종적으로 해당 포트로 접속시 화면이 잘 나오는 것을 볼 수 있다.

 

하지만 이때, Nginx에서는 파일을 기준으로 경로를 찾는다 Vue는 SPA이다 보니까 하위 경로로 접속하면 ex) http://도메인주소:8080/home 404에러가 나타나는 것을 볼 수 있을 것이다.

이는 다음 글에서 해결방법을 적어보겠다.

 

이전 프로젝트 진행했을 때와 다르게 보완한 점

1) 웹서버와 WAS의 분리

이전에는 하나의 WAS에서 정적/동적 페이지 모두를 전달하도록 했다. 지난번 스터디를 통해 공부하면서, 분리를 통한 부하 방지 그리고 웹서버로 인한 속도 개선과 같은 장점을 볼 수 있었다. 이번에 Nginx를 통해 정적 페이지를 전달하도록 환경을 구성하면서 공부한 내용을 적용할 수 있었다.

 

아쉬웠던 점 그래서 다음에 적용해보기

1) 직접 명령어를 입력해서 컨테이너를 실행했던 점

docker compose, Dockerfile을 작성하여 좀 더 효율적인 방법으로 컨테이너를 만들 수 있었을텐데, 그렇게 하지 못했던 점이 아쉽다. 

 

2) Jenkins 컨테이너를 실행할 때 설치된 도커 경로를 볼륨으로 지정하지 못했던 점

Jenkins 컨테이너에는 docker가 설치되어있지 않았다. 그래서 직접설치해도 되겠지만, EC2에 설치된 도커 경로를 볼륨으로 공유하면 더 좋았을 것 같다.

 

3) 이미지화 하지 못한 점

프로젝트를 도커 이미지로 만들어서 배포 환경을 만들지 않았어서 오류가 생겼을 때 롤백한다던가 하는 상황을 만들지 못하지 않았나 생각했다. 이 부분은 좀 더 공부를 해봐야 알 것 같다.

 

아쉬웠던 점들은 모두 보완해서 추가 작성하겠다.

 

결과

CI 파이프라인을 적용해서 git push 만 해도 자동으로 EC2 위에 배포되는 환경을 만들 수 있었다.

이 과정까지 직접 git clone 받아서 필요파일을 등록해주는 아주 귀찮고 번거로운 작업을 반복했다.

직접 겪어보니까 다시 또 CI의 중요성을 느낄 수 있었다.

더 나은 방향을 고려해볼 수 있었던 것이 좋았다. 다음에는 이번에 설정하면서 아쉬웠던 점을 보완하겠다.