4차 스프린트에서는 프로젝트의 목표였던 "개발 환경 개선"에 집중할 수 있었습니다. 저희 팀의 프론트엔드 인원이 다른 팀에 비해 한 명이 적다 보니 개발 환경의 효율화와 생산성 향상이 꼭 필요했습니다. 그래서 프로젝트를 하면서 제가 어떤 부분에 기여할 수 있을지 고민하다, 개발 환경을 개선시켜 생산성을 높여보자는 목표를 정했습니다.
1. 자동 배포 (CD)
개발 환경을 개선하기 위한 방법 중 첫 번째로는 자동 배포(CD)에 도전했습니다. 프론트엔드의 CI는 이미 구축되어 있었지만, CD는 구축되지 않은 상태였습니다. 그래서 배포 환경에서 테스트를 하려면 pem 키를 가진 크루에게 부탁해서, 우분투에 접속한 후 수동으로 배포를 해야 했습니다. 환경 변수가 추가되거나 변경 사항이 생길 때에도 우분투 CLI 환경에서 직접 입력해야 했습니다. 이러한 과정에서 휴먼 에러가 발생하기도 했고, 매번 부탁을 드리는 것이 번거로웠습니다. 그래서 자동 배포에 도전하게 되었습니다!
1-1. 첫 번째 시도, EC2 + Nginx + Github Actions 조합
처음에는 EC2에 설치된 Nginx를 기반으로 페어와 함께 Github Actions 스크립트를 작성했습니다. .yml 파일에 node 관련 명령어를 입력하고, 배포할 파일을 찾아 옮기는 설정까지 완료했습니다. 하지만 EC2 + Nginx + Github Actions 조합에 대한 자료가 부족해 디버깅에 어려움을 겪었습니다.
특히 경로 설정 부분이 어려웠습니다. jobs의 build 과정에서 사용할 working-directory 경로를 './frontend'로 지정해 두었기 때문에, root 경로가 './frontend'로 지정된 것으로 생각했습니다. 그래서 빌드 결과물을 업로드할 경로를 지정하는 환경 변수인 ARTIFACT_DIRECTORY를 './dist'로 할당한 상태였습니다.
name: Frontend CD
on:
workflow_dispatch:
push:
branches:
- main
paths:
- frontend/**
env:
ARTIFACT_DIRECTORY: ./dist/ # 이곳!
jobs:
build:
name: Build Test
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend # 이곳!
steps:
# ... npm install, build 과정 등등 ... (생략)
- name: 📤 Upload Artifact File
uses: actions/upload-artifact@v4
with:
name: react-dist-files
path: ${{ env.ARTIFACT_DIRECTORY }} # 이곳에서 환경변수가 쓰여요!
deploy:
# ...
steps:
- name: 📥 Download Artifact File
uses: actions/download-artifact@v4
with:
name: react-dist-files
path: ./frontend/dist/
merge-multiple: true
# ...
하지만 ARTIFACT_DIRECTORY에 working-directory의 root 경로가 설정되지 않아 에러가 발생했습니다. 파일은 './dist' 경로에 업로드되어 있었기 때문에, Artifact를 다운로드하기 위해 './frontend/dist' 경로를 방문했을 때 파일이 존재하지 않았습니다.
ls와 같은 명령어를 입력하고 디버깅하면서 배포만 10번을 넘게 시도했던 것 같아요.
따라서 ARTIFACT_DIRECTORY에도 './frontend' 경로를 추가하여 './frontend/dist'로 설정한 뒤 다시 배포했더니 성공!
name: Frontend CD
on:
workflow_dispatch:
push:
branches:
- main
paths:
- frontend/**
env:
ARTIFACT_DIRECTORY: ./frontend/dist # 이 부분을 수정
jobs:
build:
name: Build Test
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- uses: actions/checkout@v4
- name: 🏗️ Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20.15.1
- name: 🏗️ Install Dependencies
run: npm install
- name: 🏗️ Insert Environment Variables
run: |
touch .env
cat << EOF >> .env
${{ secrets.FRONTEND_ENV }}
touch .env.sentry-build-plugin
cat << EOF >> .env.sentry-build-plugin
${{ secrets.SENTRY_AUTH_ENV }}
- name: 🏗️ Build
run: npm run build
- name: 📤 Upload Artifact File
uses: actions/upload-artifact@v4
with:
name: react-dist-files
path: ${{ env.ARTIFACT_DIRECTORY }}
deploy:
name: 🚀 Web Deployment
needs: build
runs-on: [self-hosted, develup-front]
steps:
- name: 📥 Download Artifact File
uses: actions/download-artifact@v4
with:
name: react-dist-files
path: ./frontend/dist/
merge-multiple: true
- name: Move Build File
run: sudo cp ./frontend/dist/* /var/www/html
- name: 🟢 Nginx Restart
run: sudo service nginx restart
이렇게 결국 해결하여 CD가 성공적으로 구현되었지만... 어딘가 찝찝한 느낌이 들었습니다.
1-2. 두 번째 시도, S3 + CDN (CloudFront) + AWS Pipeline 조합
이후 어느 날 CD가 작동하지 않는다는 소식을 들었습니다. 원인을 확인해 보니, CD 스크립트에서 파일을 재귀적으로 탐색하는 -r 옵션이 빠져있어 문제가 발생한 것이었습니다.
소개 페이지에 사용할 사진은 변경이 거의 일어나지 않는다고 판단하여 코드 상에서 이미지를 처리하려 했습니다. 이미지를 추가한 뒤 배포를 진행했으나, CD 스크립트에서는 ./frontend/dist/* 경로의 파일만 복사하기 때문에 ./frontend/dist/assets 경로의 파일들은 복사되지 않았습니다.
sudo cp ./frontend/dist/* /var/www/html
그래서 -r을 추가하여 재귀적으로 dist 내부의 모든 파일을 탐색하도록 수정했더니 배포 성공!
그렇지만 이번에도 디버깅에 애를 썼던 것 같아요.
sudo cp -r ./frontend/dist/* /var/www/html
어떻게 하면 이 방식을 개선할 수 있을지 주변 크루들에게 물어보고, 나름대로 방법을 찾아보다가 두 번째 배포 방식으로 S3와 CDN 조합을 사용하기로 결정했습니다. 이때 우려했던 부분은, 교육생 신분으로 S3 Access Key를 발급받지 못해 Github Actions를 사용할 수 없다는 점이었습니다. 그런데 AWS Pipeline을 사용하면 Access Key 없이도 바로 AWS CDN에 접근할 수 있어 이 문제가 해결된다고 들었습니다. (크루들 최고👍)
S3에 정적 파일을 업로드하고, CDN을 통해서만 S3에 접근할 수 있도록 설정했습니다. 이후 AWS Pipeline을 통해 배포를 진행할 수 있었습니다. 이번에도 슬래시('/')와 같은 경로 설정 문제로 디버깅에 시간을 쏟긴 했지만, 확실히 눈으로 확인할 수 있는 GUI로 구성되어 있어서 첫 번째 방법보다는 훨씬 간단했습니다. 덕분에 CD 구축에 필요한 시간도 줄일 수 있었습니다.
2. 스타일 전역 변수 지정
코드 리뷰를 진행하면서 가장 번거로운 부분은 디자인 관련 코드였습니다. 디자인이 코드에서 어떻게 구현되었는지 파악하려면 npm run dev로 실행해야만 확인할 수 있었고, 이는 리뷰 과정을 복잡하게 만들었습니다. PR에 첨부된 캡처 화면을 참고할 수 있긴 했지만, 이를 통해 코드의 세부 사항을 확인하는 데는 한계가 있었습니다.
스타일 전역 변수는 :root에 --{color}-{number} 형식으로 색상만 지정되어 있었고, 그림자와 폰트는 따로 관리되지 않았습니다. 이로 인해 코드 리뷰 중 관련 코드를 놓치는 일이 자주 발생했습니다.
예를 들어, 그림자는 색상을 확인하기 힘들었고
box-shadow: 0 0.4rem 0.4rem rgba(0, 0, 0, 0.12);
폰트도 세부 사항을 확인하기 어려웠으며, 다음과 같이 세 줄로 작성해야 했습니다.
font-size: 1.6rem;
font-weight: bold;
font-family: inherit;
이 문제를 해결하기 위해 styled-components의 DefaultTheme을 활용해 테마를 새로 설정했습니다. 기존에는 색상이 이미 :root에 지정되어 있었지만, DefaultTheme을 사용한 이유는 자동 완성 기능을 제공하기 때문이었습니다. :root에 변수를 지정하면 var(--primary-50) 형식으로 사용할 수 있었지만, 자동 완성이 지원되지 않아 오탈자나 휴먼 에러가 발생할 가능성이 있었습니다.
DefaultTheme을 사용하면서 색상뿐만 아니라 그림자와 폰트 스타일도 일관된 변수로 관리할 수 있도록 추가했습니다. 기존 코드를 새롭게 설계한 스타일 변수로 변경하는 과정을 거쳤고, 만약 미처 변경하지 못한 코드가 있더라도 서비스는 잘 동작할 수 있도록 :root에 변수들은 임시로 남겨놓은 상태입니다.
이를 통해 개발 경험이 개선되었고, 휴먼 에러가 감소할 수 있었습니다. 또한, 코드 리뷰의 효율성과 정확성이 높아질 수 있었고, 나아가 디자인 관련 코드 관리와 협업이 편리해짐을 느낄 수 있었습니다. 당장은 귀찮은 일일지라도 장기적으로는 생산성을 향상시킬 수 있음을 배웠어요🤗
// theme.ts
import type { DefaultTheme } from 'styled-components';
export const theme: DefaultTheme = {
colors: {
primary50: '#E7E9F8',
primary100: '#C4C9ED',
// ... 생략
},
font: {
heading1: `
font-size: 2.8rem;
font-weight: bold;
font-family: inherit;
`,
// ... 생략
},
boxShadow: {
shadow04: `0 0.4rem 0.4rem rgba(0, 0, 0, 0.12)`,
// ... 생략
},
};
// styled.d.ts
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme {
colors: {
primary50: string;
primary100: string;
// ... 생략
};
font: {
heading1: string;
// ... 생략
};
boxShadow: {
shadow04: string;
// ... 생략
};
}
}
그런데 이렇게 스타일 변수를 지정하려다 보니, 모든 스타일 파일에서 변경이 불가피해졌고, 팀원들의 작업물에 큰 충돌이 발생할 것 같았습니다. 어떻게 해야 할지 팀원들과 논의해보았는데, 알고 보니 팀원들 역시 같은 불편함을 느끼고 있었다고 합니다. 귀찮은 일이 될 수도 있었지만, 나서서 개선해 주어서 고맙다는 이야기도 들었습니다. ㅎㅎ
// 변경 후
// 색상
${(props) => props.theme.color.primary50};
// 그림자
${(props) => props.theme.boxShadow.shadow04};
// 폰트
${(props) => props.theme.font.heading1}
'우아한테크코스' 카테고리의 다른 글
S3, AWS CloudFront(CDN) 캐시 설정의 차이점 (0) | 2024.09.17 |
---|---|
웹 사이트의 성능을 높여보자 (1) (요청 크기 줄이기, 필요한 것만 요청하기) (2) | 2024.09.15 |
레벨 3 런칭 페스티벌 & 4차 스프린트 회고 (0) | 2024.09.08 |
페어 프로그래밍 - 해시태그 및 필터링 구현 (React + Typescript) (0) | 2024.08.24 |
협업을 잘 하기 위한 방법 (0) | 2024.08.23 |