최근에 기업 홈페이지를 구축하는 프로젝트를 진행했습니다.
프로젝트 1차 완성 기간이 끝나고 유지보수 기간으로 들어가게 됐는데, 유지보수 업무를 제가 도맡아 진행하게 되었습니다.
CI/CD 및 Test bed 환경 도입 계기
프로젝트 1차 기간 동안 개발하면서 제가 불편했던 사항들이 있었는데, 이번 유지보수 기간 때 기술을 도입하여 불편했던 점을 해소해보고 싶었습니다.
기존에 프로젝트를 진행했던 방식을 살펴보면, 아래와 같은 프로세스로 배포를 진행했습니다.
- front / back 레포가 존재하는 상태.
- 변경 사항을 master 브랜치에 푸쉬한 뒤, 각각의 인스턴스에 접속해서 명령어를 실행시켜 직접 배포 작업을 진행.
- 그리고 이런 적용사항들은 별도의 테스트 도메인 없이 작업한 내용이 바로 Publish 도메인에 배포되는 방식.
위 프로세스에서 아래와 같은 불편함을 겪었습니다.
- 일단 각각의 변경 사항들을 수동으로 명령어를 입력해서 배포시키는 것 자체가 비효율적인 작업임
- 배포 과정에서 본 서버에 적용시키기 전에 문제가 없는지 확인을 해보겠지만, 사람이 하는일인지라, 혹시라도 빠뜨려서 사이트에 영향이 가능 문제가 생긴다면? → 매우 크리티컬한 문제임. 해당 프로젝트를 진행하며 실제로 겪었던 문제...
저는 CI/CD를 통해 1번 문제를, Test Bed 환경 구축을 통해 2번 문제를 해결해고자 했습니다.
따라서 앞으로 3달 정도 간 진행될 유지보수 업무에서는 아래와 같은 프로세스를 도입하기로 결정했습니다.
유지보수 사항 요청(클라이언트) -> 작업 확인 요청(Me) -> 검수 완료(클라이언트) -> 배포 완료(Me)
즉, 제가 개발한 사항들을 클라이언트 측에서 테스트 환경에서 확인해볼 수 있고, 검수가 완료되었을 때 본 서버에 적용되는 프로세스를 도입해야 했습니다.
위 프로세스를 적용하기 위해 AWS CodePipeline을 활용하였습니다.
MSA(Micro Service Architecture)과 CI/CD
우선 해당 내용을 적용하기 위해 이것저것 찾아보며 공부하고, 현재 단계에서 필요한 MSA와 CI/CD의 개념에 대해 간단하게 정리해보았습니다.
Micro Service Architecture
작은 서비스가 여러 개가 모여서 하나의 시스템을 제공하는 아키텍처를 말합니다.
각 서비스는 작고 독립적이며, 느슨하게 결합되어 있습니다. 때문에 서비스들을 독립적으로 배포할 수 있으며, 전체 프로그램을 빌드한 뒤에 재배치하지 않고도 기존 서비스들을 업데이트 할 수 있습니다.
즉, 큰 문제(task, service)들을 작은 문제로 분해하여 해결하는 것이며, 작게 나뉘어진 서비스가 서로에게 영향을 미치지 않고 독립적으로 역할을 수행하게 만드는 것이 목표입니다.
따라서 특정 서비스에 에러가 발생해도 다른 서비스에게 영향을 미치지 않게 되며, 서비스가 실패할 경우 롤백 매커니즘을 두어, 이전 버전으로 쉽게 되돌릴 수 있습니다.
CI/CD
CI/CD는 지속적인 통합, 지속적인 제공, 지속적인 배포에 관한 것입니다.
- 지속적인 통합(Continuous integration)
- 코드 변경 사항은 보통 main branch에 merge 됩니다. 자동화된 빌드 및 테스트 프로세스는 main branch에 있는 코드가 항상 높은 생산성 품질을 유지하도록 보장합니다.
- 지속적 제공(Continuous delivery)
- CI 프로세스를 통과한 모든 코드 변경 사항은 자동적으로 프로덕션과 유사한 환경에 배포됩니다. 실제 프로덕션 환경에 배포하려면 수동 승인이 필요할 수 있지만, 그렇지 않으면 자동화됩니다. 목표는 코드가 항상 프로덕션에 배포될 준비가 되어 있어야 한다는 것입니다.
- 지속적 배포(Continuous deployment)
- 위 두 단계를 거친 코드의 변경 사항은 자동적으로 프로덕션 환경에 배포됩니다.
microservices을 위한 CI/CD 목표
저는 MSA와 CI/CD의 개념에 입각하여 해당 프로젝트에서 다음과 같이 활용해볼 수 있겠다고 생각이 들었습니다.
- 각 팀은 다른 팀에 영향을 주거나 방해하지 않고, 독립적으로 소유한 서비스를 빌드하고 배포할 수 있다 → 각 issue(유지보수 issue 하나에 해당하는) 별로 해결한 문제를 독립적으로 수정하고 배포할 수 있음
- 서비스의 새로운 버전이 프로덕션에 배포되기 전에, 검증을 위해서 개발/테스트/QA 환경에 배포된다 → 클라이언트 측에서 확인 후 승인 시 배포
- 서비스의 새로운 버전을 이전 버전과 함께 배포할 수 있다.
AWS CodePipeline이란?
AWS에서는 CodePipeline을 통해 CI/CD 환경을 구축할 수 있습니다.
파이프라인(Pipeline)이란 소스 코드의 관리부터 실제 서비스로의
배포 과정을 연결하는 구조로, 전체 배포 과정을 여러 단계(Stages)로 분리합니다.
파이프라인 안에서 순차적으로 실행되며, 각 단계마다 주어진 작업(Actions)들을
수행합니다.
파이프라인의 단계는 상황과 필요에 따라 더 세분화되거나 간소화될 수 있지만, 통상적으로는 Source - Build - Deploy
의 단계를 구성하며, 해당 파이프라인에도 다음 단계를 구성하고자 합니다.
- Source 단계: Source 단계에서는 원격 저장소에 관리되고 있는 소스 코드에 변경 사항이 일어날 경우, 이를 감지하고 다음 단계로 전달하는 작업을 수행합니다.
- Build 단계: Build 단계에서는 Source 단계에서 전달받은 코드를 컴파일, 빌드, 테스트하여 가공. 또한 Build 단계를 거쳐 생성된 결과물을 다음 단계로 전달하는 작업을 수행합니다.
- Deploy 단계: Deploy 단계에서는 Build 단계로부터 전달받은 결과물을 실제 서비스에 반영하는 작업을 수행합니다.
AWS에서 제공해주는 개발자 도구를 옵션들을 통해 CodePipeline을 수월하게 구축할 수 있습니다.
- CodeCommit
- Source 단계를 구성할 때 CodeCommit 서비스를 이용. CodeCommit은 GitHub과 유사한 서비스를 제공하는 버전 관리 도구
- 하지만 파이프라인에서 CodeCommit 말고 Github 레포로 직접 Source 단계를 연결할 수 있기 때문에 따로 만들지는 않아도 됨.
- CodeBuild
- Build 단계에서는 CodeBuild 서비스를 이용. CodeBuild 서비스를 통해 유닛 테스트, 컴파일, 빌드와 같은 빌드 단계에서 필수적으로 실행되어야 할 작업들을 명령어를 통해 실행
- CodeBuild 서비스는 사용자가 작성한 buildspec.yml 파일을 참조하여 작업을 수행
- CodeDeploy
- Deploy 단계에서 실행되고 있는 서버 애플리케이션에 실시간으로 변경 사항을 전달
- 사용자가 작성한 appspec.yml 파일을 참조하여 작업을 수행 (appspec.yml은 배포 자동화를 도와주는 CodeDeploy-Agent가 인식하는 파일)
- S3 서비스를 통해 S3 버킷을 통해 업로드된 정적 웹 사이트에 변경 사항을 실시간으로 전달하고 반영
- CodePipeline
- 각 단계를 연결하는 파이프라인을 구축할 때 CodePipeline 서비스를 이용
정리하자면, CodePipeline은 CodeCommit, CodeBuild, CodeDeploy를 하나의 프로세스로 통합시켜주는 CI/CD 도구입니다.
CodePipeline을 적용시키 전에, 기존에 배포했던 과정을 요약해보고 어떻게 적용시킬지 점검해보았습니다.
우선 현재 프로젝트에서 프론트엔드는 pm2를, 백엔드는 apache를 활용하여 배포하고 있습니다.
- Frontend: master 브랜치의 변경사항 pull → build → build된 폴더를 기반으로 pm2 start “yarn preivew” 명령어 실행
- Backend: master 브랜치의 변경사항 pull → apache2 restart 명령어 실행
위 기존의 과정을 살펴봤을 때, 프론트엔드는 Source → Build → Deploy
의 과정을, 백엔드는 Source → Deploy
의 과정만을 적용시키면 됐습니다. 이에 맞게 Pipeline을 구축해보았습니다.
우선 CodePipline을 적용시키기 위해, CodeBuild 빌드 프로젝트와 CodeDeploy 애플리케이션을 각각 생성해줘야합니다.
CodeBuild 빌드 프로젝트 생성


해당 프로젝트의 Github repository 기반으로 빌드 작업을 수행해야하므로 위와 같이
소스 공급자를 선택해줍니다.


중요한 부분이 다음 Buildspec 관련 내용입니다.
해당 설정으로 빌드 명령을 구성할 수 있습니다.
프로젝트 최상단에 buildspec.yml 파일을 만들고 다음과 같이 작성했습니다.
version: 0.2
phases:
build:
commands:
- BASE_URL=$BASE_URL yarn install
- yarn build
artifacts:
files:
- .output/**/*
- scripts/**/*
- appspec.yml
- package.json
yarn install을 통해 필요한 모듈들을 다운받고 build 명령어를 실행합니다.
여기서 BASE_URL은 api 주소가 들어가게 됩니다.
api도 dev/publish 버전이 각각 존재하기 때문에, 그에 맞는 BASE_URL을 넣어주기 위함입니다.
Code Pipeline에서 build 프로젝트를 등록할 때 환경변수로 넘겨줄 값을 설정해줄 수 있습니다. 뒷 내용에서, $BASE_URL에 맞는 값을 넣어줄 예정입니다.
artifacts files에 정의한 파일들을 빌드 결과물로 넘겨주게 됩니다. 해당 프로젝트는 Nuxt.js를 통해 개발을 진행했는데, Nuxt는 프로덕션용 애플리케이션을 구축할 때 .output/ 디렉토리를 생성하므로 해당 파일의 경로를 지정했습니다.

넘겨준 아티팩트의 결과물을 저장할 버킷 정보를 작성합니다. 기존에 미리 생성한 S3 버킷
정보를 입력했습니다.
위 정보들을 입력하고 프로젝트 생성을 누르면 빌드 프로젝트가 생성됩니다.

Pipeline에 적용시키 전에 상세 페이지에서 빌드가 잘 되는지 테스트
CodeDeploy 애플리케이션 생성

배포할 플랫폼이 EC2이므로 선택 후 애플리케이션을 생성했습니다.
애플리케이션 생성 후 배포 그룹을 생성해줘야 하는데, 사전 작업으로 IAM에 CodeDeploy 관련 정책이 부여된 역할을 생성해야합니다.

그러고 나서 배포 그룹을 생성 과정을 수행하면 됩니다.



front-dev 버전의 해당 인스턴스를 배포 대상으로 설정해줍니다.
그러고 배포 그룹을 생성하면 됩니다.
배포 그룹 설정 뒤에 추가적으로 두 가지 작업을 진행해야 합니다.
AppSpec File (EC2/온프레미스 컴퓨팅 플랫폼을 사용하는 경우) 작성
AppSpec file은 appspec.yml이라는 YAML 형식의 파일이어야 하며, 애플리케이션 소스 코드의 루트에 위치 시켜야합니다다. 저는 아래와 같이 appspect.yml 파일을 프로젝트 최상단 경로에 작성했습니다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/build/
overwrite: yes
file_exists_behavior: OVERWRITE
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
ApplicationStart:
- location: scripts/deploy.sh
runas: ubuntu
- source : 인스턴스에 복사할 업데이트 파일 또는 디렉터리를 식별합니다.
- destination : 인스턴스에서 파일이 복사되어야 하는 위치를 식별합니다.
- file_exists_behavior: OVERWRITE → 기존에 같은 파일이 존재하면 overwrite 합니다.
그리고 hooks를 통해 원하는 deploy 단계에서 특정 명령을 실행시킬 수 있습니다.
저는 Application이 시작 됐을 때 실행시킬 deploy.sh를 정의했습니다. 해당 파일에서는 변경된 빌드 파일을 기반으로 하여 프로젝트를 배포하도록 하는 코드를 작성했습니다.
#!/bin/bash deploy.sh
REPOSITORY=/home/ubuntu/build
HOME=/home/ubuntu
pm2 start "yarn preview"
EC2에 CodeDeploy Agent 설치
아래 명령어들을 차례대로 실행시키면 CodeDeploy Agent를 설치할 수 있습니다.
sudo apt-get update
sudo apt-get install ruby
sudo apt-get install wget
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
sudo service codedeploy-agent status
sudo service codedeploy-agent start
해당 과정까지 마치면 CodeDeploy를 위한 모든 작업은 완료됐습니다!
이제 CodePipeline에 만든 CodeBuild 빌드 프로젝트와 CodeDeploy 프로젝트를 적용시켜주면 됩니다.
CodePipeline 생성
소스 스테이지 추가

위에서 언급했듯이, 레포지토리의 master 변경사항을 기반으로 파이프라인을 실행시킬 것입니다.
따라서 소스 공급자를 Github로 하고 해당 레포지토리를 연결해주면 됩니다.

트리거에 master 브랜치를 명시해줍니다.
빌드 스테이지 추가

기존에 생성했던 CodeBuild 빌드 프로젝트를 선택해주면 됩니다.
그리고 아까 언급했던 환경변수를 여기서 설정해줄 수 있습니다.
배포 스테이지 추가


위에 추가로 승인 후 본 도메인에 변경사항이 배포되는 Pipeline을 추가로 달아줬다.
백엔드에 해당하는 파이프라인도 위의 과정과 동일하게 적용하였습니다.
다른 점은 Github 레포지토리만 back에 해당하는 코드로 지정해주면 되는 것, build 과정이 빠지는 것, deploy.sh 코드가 백엔드에 맞게 바뀌는 것 뿐이었습니다.
이렇게 CI/CD를 적용하여 Test bed 환경을 구축해볼 수 있었습니다.
이제 각 pr에서 처리된 유지보수 사항들이 master branch에 merge되면, 자동으로 테스트 도메인에 변경사항이 적용됩니다. 이것을 클라이언트 측에서 검수하고, 문제가 없으면 승인을 하고 본 도메인에 변경사항이 배포되는 프로세스가 구축되었습니다.
물론 위 사항들을 도입하기 위해서 선임분과 클라이언트 측에서의 동의가 필요했지만, 흔쾌히 허락해주셔서 감사할 따름입니다.
불편한 점을 기술을 도입하여 해결해볼 수 있는 정말 갚진 경험이 아니었나 생각합니다.
더 나아진 개발 환경에서 진행될 유지보수가 기대됩니다☺️