[S4]Chapter10. [Deploy] CI/CD

박현석·2022년 12월 7일
1

코드스테이츠

목록 보기
38/40
post-thumbnail

개발 프로세스

요구분석 및 시스템 명세 작성

  • 문제분석 단계라고도 하며, 개발할 소프트웨어의 기능과 제약조건, 목표 등을 사용자와 함께 정확히 정의하는 단계
  • 개발하고자 하는 소프트웨어의 성격을 정확히 이해하여 이를 토대로 개발 방법과 필요한 자원 및 예산 예측 후 요구명세를 작성

설계

  • 설계단계에서는 앞서 정의한 기능을 실제로 수행하기 위한 방법을 논리적으로 결정
  • 크게 시스템, 프로그램, UI(User Interface) 설계로 나뉘며, 시스템 구조설계는 시스템을 구성하는 내부 프로그램이나 모듈 간의 관계와 구조를 설계하고, 프로그램설계는 프로그램 내의 각 모듈에서의 처리 절차나 알고리즘을 설계

구현

  • 설계 단계에서 논리적으로 결정한 문제 해결 방법을 프로그래밍언어를 사용하여 실제 프로그램을 작성
  • 프로그래밍 기법은 구조화 프로그래밍과 모듈러 프로그래밍 두 개로 나눈다.
    - 구조화 프로그래밍
    - 조건문, 반복문을 사용하여 프로그램을 작성하고, 순차구조, 선택구조, 반복구조의 세 가지 제어구조로 표현하며, 구조가 명확하여 정확성 검증과 테스트 및 유지보수가 쉬운 장점
    • 모듈러 프로그래밍
      • 프로그램을 여러 개의 작은 모듈로 나누어 계층 관계로 구성하는 프로그래밍 기법으로, 모듈별로 개발과 테스트 및 유지보수 가능하며, 모듈의 재사용 가능하다는 장점

테스트

  • 테스트 단계에서는 개발한 시스템이 요구사항을 만족하는지, 실행 결과가 예상한 결과와 정확하게 맞는지를 검사하고 평가하는 일련의 과정
  • 미처 발견하지 못한 오류를 발견할 수 있기 때문에 매우 중요한 과정

배포 및 유지보수

  • 배포와 유지보수 단계는 시스템이 인수되고 설치된 후(배포) 일어나는 모든 활동을 지칭
  • 이후 일어나는 커스터마이징, 구현, 테스트 등 모두 이 단계에 포함되므로 소프트웨어 생명주기에서 가장 긴 기간을 차지
  • 유지보수의 유형에는 수정형, 적응형, 완전형, 예방형 총 네 가지가 있다.
    - 수정형 유지보수
    - 사용 중에 발견한 프로그램의 오류 수정 작업을 진행
    • 적응형 유지보수
      • 시스템과 관련한 환경적 변화에 적응하기 위한 재조정 작업
    • 완전형 유지보수
      • 시스템의 성능을 향상하기 위한 개선 작업
    • 예방형 유지보수
      • 앞으로 발생할지 모를 변경 사항을 수용하기 위한 대비 작업을 수행
  • 무엇을 개발할지 결정하고 요구사항들을 구현 작업에 적합하게 명확하고 조직화된 구조로 바꾸는 과정을 거쳐, 실제 개발에 착수하는 단계는 ‘구현’부터이다.
    실제 개발 환경에서는 환경에 따라 각각의 단계가 또 세밀하게 나뉘어져 있기도 하며, 각 단계가 계속해서 반복되기도 한다.

전통적인 개발 프로세스

  • 기존에 존재하고 있던 개발 프로세스는 워터폴(Waterfall) 방식
  • 개발된 앱이 배포되어 실제 사용하는 유저들에게 도달하기까지 시간이 걸린다.
  • 실제 디자인 또는 개발된 화면을 시각적으로 확인할 수 있는 단계는 이미 많은 단계가 지나온 시점이기 때문에 어떤 버그나 수정 사항이 생기면 다시 처음으로 돌아가 수정되기 때문에 일정과 비용 등 많은 부분에서 에로 사항이 생긴다.
  • 출시 시점에 소프트웨어의 신뢰성 및 안정성을 보장 받기가 힘들며, 배포 직후에도 수많은 버그를 마주하게 될 가능성이 있다.
  • 전통적인 소프트웨어 개발 프로세스에서는 소프트웨어의 안정성 개선을 위해 테스트 단계에 다양한 테스트들을 도입하기도 한다.
  • 시스템 테스트
    - 모든 모듈을 통합한 후 최종적으로 완성된 시스템이 요구사항을 만족하는지 확인한다.
    요구사항을 만족하지 않는다면 다시 요구분석 단계로 돌아가 새로 개발을 하기도 한다.
  • 알파 테스트
    - 완전히 개발된 시스템을 개발 현장에서 비공개로 테스트하는 것으로, 주문형 제품의 경우 개발진과 클라이언트 사이에서 동의가 이루어질 때까지 수행된다.
    대기업의 경우 이 업무를 주로 하는 전문 QA팀이 존재한다.
  • 베타 테스트
    - 고객의 실제 사용 환경에서 수행되는 테스트로, 미리 선별된 유저들이 해당 제품을 사용해 본다.
    이 과정에서 에러나 버그가 발견되면 수정하는 식으로 진행된다.

전통적인 개발 프로세스의 전달이 가지는 한계

  • 대부분의 모바일 애플리케이션이 사용하는 전달 방식의 한계이기도 하다.
    애플리케이션이 접속할 시, 이런 현상을 보이는 이유는 다양한 테스트 방식을 도입했음에도 사용자가 항상 최신 상태로 업데이트를 해줘야 하고, 버그 수정(patch)된 버전을 유저에게 즉각적으로 전달하기가 어렵기 때문이다.

모던 개발 프로세스

  • 전통적인 개발 프로세스에서 벗어나기 위해 만들어진 프로세스 중 하나가 애자일(Asile) 방식
  • 애자일 방식은 요구사항이 변하는 것을 당연한 전제로 두고 있다.
    따라서 계획에 의존하여 형식적인 절차를 끝까지 따라야 하고 중간에 뒤로 회귀할 수 없는 전통적인 개발 프로세스보다는 훨씬 효율적으로 개발에 착수할 수 있다.
    애자일 개발 프로세스를 적절히 사용하면 빠르게 문제를 해결해 하루에도 여러 번의 배포가 가능해진다.

SaaS란?

  • 클라우드 서비스의 한 방식으로, 브라우저에 접속하기만 해도 새 버전을 즉시 사용할 수 있는 서비스 방식
  • 애플리케이션부터 서버, 가상화, 스토리지, 네트워킹까지 전부 공급자 쪽에서 관리하기 때문에 고객이 제어하거나 관리할 부분이 거의 없게된다.
    따라서 사용자 업데이트에 대한 걱정에서 벗어나며, 하루에 여러 번의 배포도 가능하고, 빠른 배포 속도도 보장 받을 수 있다.

전통적인 개발 프로세스 VS 모던 개발 프로세스

워터폴애자일
장점- 프로세스가 길고 순서가 잡혀 있으므로 팀의 규모에 상관 없이 따르기 쉬움
- 개발 주기가 정해져 있으므로 새로운 프로젝트를 안정적으로 시작 가능
- 요구 사항이 확정되어 있으므로 프로젝트를 실행하기 수월하며, 개발 목표를 자주 변경하지 않아도 됨
- 프로젝트의 전 과정에 필요한 예산 및 자원이 초기에 확정되어 예상 결과와 리스크를 통제하기 훨씬 쉬움
- 빠르면서 유연한 개발 과정
- 짧고 반복적인 스프린트로 구성되어 있어 품질에 초점을 맞출 수 있으므로 빠르게 결함을 인지하고 수정 가능
- 스프린트를 통한 짧은 반복 과정으로 개발 과정 중에 신속히 제품 변경 가능
단점- 개발이 순차적으로 진행되므로 앞 단계가 완성되지 않으면 다음 단계로 넘어갈 수 없어 개발 속도가 느리고 유연성이 떨어짐
- 테스팅 단계에 이르러서 이슈가 발견되는 경우가 왕왕 있음
- 개발 요구 사항이 초기에 확정되므로 범위 변경이 자유롭지 못함
- 스프린트에 대한 경험이 있으며 빠른 반복 작업에 익숙한 스크럼 마스터가 필요함
- 고객이 수많은 변경 사항을 검토해야만 하는 번거로움 발생
- 팀원이 잘 조직되어 있지 않거나 자립성이 떨어지는 경우 애자일론을 채택할 시 문제가 발생

워터폴애자일
이런 상황에 적합- 높은 예측 가능성과, 순차적인 프로젝트 타임라인, 사전 확정 예산이 필요한 경우
- 프로젝트 팀의 경험이 적은 경우
- 요구사항이 간단하거나, 타임라인이 긴 프로젝트를 수행하는 경우
- 요구사항이 간단하거나, 타임라인이 긴 프로젝트를 수행하는 경우
- 프로젝트가 완벽히 수행될 때까지 결과물을 기다리는 것보다 결과물에 대해 빠른 피드백이 필요한 경우
이런 기업 및 팀에 적합- 개발상의 변경이나 리스크에 덜 민감한 기업 및 팀
- 제한적인 시간과 자원 탓에 협업이 자유롭지 못한 고객을 둔 기업 및 팀
- IBM, 시스코, AT&T, 마이크로소프트와 같이 크고 복잡한 회사들이 프로세스를 간소화해 변화에 더욱 신속하게 대응하고자 할 때
- 고객 및 외부 관계자와 정기적으로 긴밀한 협업을 수행하는 프로젝트 팀
### DevOps
Dev(개발팀)Ops(운영팀)
특징- 잦은 배포 및 업데이트
- 애플리케이션을 통해 새로운 기능(리소스)제공
- 프로덕션 앱의 안정성 확보
- 인프라 관리
- 모니터링 및 제어
- DevOps는 소프트웨어 개발(Development)과 IT 운영(Operations)의 합성어 - 소프트웨어를 자주, 빨리 그리고 안전하게 배포하는 것을 목표로 하며, 그렇기 때문에 애자일 개발 프로세스를 기반으로 한 것이라고 볼 수 있다. #### DevOps 문화 ![](https://user-images.githubusercontent.com/58800295/195049472-d35fe54b-10bf-4f07-b893-1bab3e059556.png) - DevOps는 특정한 업무라던지 부서가 아닌 일종의 개발 문화이다. 만약 서비스가 중단된다면, 누구든지 문제점을 진단하고 시스템을 복구하여 운영할 수 있는 절차를 알고 있어야 한다. 이를 위한 기술과 지식이 제공되기 위해서 훈련과 효과적인 협업체계를 마련하는 것이 매우 중요하다. #### DevOps 특징 - DevOps는 개발에서 운영까지 하나의 통합된 프로세스로 묶어내고 사용하는 툴과 시스템을 표준화하여 의사소통의 효율성을 확보하고 일련의 작업들을 자동화한다. 즉 코드 통합, 테스트, 배포 과정을 자동화 시키는 것이다. 이 부분은 지속적으로 유지되어야 할 필요가 있는데, 지속적 통합 및 배포(CI/CD)라고 하며 DevOps의 핵심 원칙이라고 해도 과언이 아니다. 잘 구축된 CI/CD는 애플리케이션의 배포 시간을 크게 단축시킨다.

CI/CD

CI

  • 개발자를 위한 자동화 프로세스인 지속적인 통합(Continuous Integration)을 의미한다.
    CI를 성공적으로 구현할 경우 애플리케이션에 대한 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 리포지토리에 통합되므로 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결할 수 있다.

CD

  • 지속적인 서비스 제공(Continuous Delivery) 및/또는 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환적으로 사용된다.
    두 가지 의미 모두 파이프라인의 추가 단계에 대한 자동화를 뜻하지만 때로는 얼마나 많은 자동화가 이루어지고 있는지를 설명하기 위해 별도로 사용되기도 한다.

CI/CD의 단계

지속적 통합(Continuous Integration, CI)

  • 개발자를 위한 자동화 프로세스라고 볼 수 있으며, Code - Build - Test 단계에서 꾀할 수 있다.
    - Code
    - 개발자가 코드를 원격 코드 저장소에 push 하는 단계
    - Build
    - 원격 코드 저장소로부터 코드를 가져와 유닛 테스트 후 빌드하는 단계
    • Test
      • 코드 빌드의 결과물이 다른 컴포넌트와 잘 통합되는 지 확인하는 과정
  • 개발자는 코드를 잦게 원격 코드 저장소에 push하고, 테스트 및 빌드를 하며 빌드 결과를 통해 빌드가 성공했는지 실패했는지 확인을 하고, 통합 테스트 결과를 통해 개선 방안을 찾는다.
    이 지속적인 통합 과정을 통해 개발자는 버그를 일찍 발견할 수 있고, 테스트가 완료된 코드에 대해 빠른 전달이 가능해지며 지속적인 배포가 가능해진다.
  • 지속적 통합은 모든 코드 변화를 하나의 리포지토리에서 관리하는 것 부터 시작한다.
    모든 개발팀이 코드의 변화를 확인할 수 있기 때문에, 투명하게 문제점을 파악할 수 있다.
    그리고 잦은 풀 리퀘스트 와 머지로 코드를 자주 통합한다.
    이 때, 기본적인 테스트도 작동시킬 수 있다.
    이렇게 지속적 통합을 통해 개발팀은 각자 개발한 코드를 이른 시점에 자주 합치고 자주 테스트 해볼 수 있다.
  • 지속적 통합으로 보안 이슈, 에러 등을 쉽게 파악할 수 있어 해당 이슈를 빠르게 개선할 수 있다.

지속적 배포(Continuous Delivery/Deployment, CD)

  • 지속적인 서비스 제공(Continuous Delivery) 및 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환적으로 사용하고, Release - Deploy - Operate 단계에서 꾀할 수 있다.
    - Release
    - 배포 가능한 소프트웨어 패키지를 작성
    • Deploy
      • 프로비저닝을 실행하고 서비스를 사용자에게 노출한다. 실질적인 배포 부분.
    • Operate
      • 서비스 현황을 파악하고 생길 수 있는 문제를 감지

CI/CD 파이프라인

  • 수없이 진행되는 배포 과정을 자동화시키는 방법을 구축하게 되는데, 그것을 CI/CD 파이프라인이라고한다.

CI/CD 파이프라인을 구성하는 기본 단계와 수행 작업

  • 배포에서 파이프라인(Pipeline)이란 용어는 소스 코드의 관리부터 실제 서비스로의 배포 과정을 연결하는 구조를 뜻한다.
    파이프라인은 전체 배포 과정을 여러 단계(Stages)로 분리한다.
    각 단계는 파이프라인 안에서 순차적으로 실행되며, 각 단계마다 주어진 작업(Actions)들을 수행한다.

1. Source 단계

  • Source 단계에서는 원격 저장소에 관리되고 있는 소스 코드에 변경 사항이 일어날 경우, 이를 감지하고 다음 단계로 전달하는 작업을 수행

2. Build 단계

  • Build 단계에서는 Source 단계에서 전달받은 코드를 컴파일, 빌드, 테스트하여 가공한다.
    또한 Build 단계를 거쳐 생성된 결과물을 다음 단계로 전달하는 작업을 수행한다.

3. Deploy 단계

  • Deploy 단계에서는 Build 단계로부터 전달받은 결과물을 실제 서비스에 반영하는 작업을 수행한다.

CI/CD 파이프라인 구성 요소 및 장점

  • 빌드 (소프트웨어 컴파일)
  • 테스트 (호환성 및 오류 검사)
  • 릴리스 (버전 제어 저장소의 애플리케이션 업데이트)
  • 배포 (개발에서 프로덕션 환경으로의 변환)
  • 규정 준수 및 유효성 검사

Github Action

  • Github가 공식적으로 제공하는 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 CI/CD 플랫폼
  • 레포지토리에서 Pull Request 나 push 같은 이벤트를 트리거로 GitHub 작업 워크플로(Workflow)를 구성할 수 있다.
    워크플로는 하나 이상의 작업이 실행되는 자동화 프로세스로, 각 작업은 자체 가상 머신 또는 컨테이너 내부에서 실행한다.
  • 워크플로는 .yml (혹은 .yaml ) 파일에 의해 구성되며, 테스트, 배포 등 기능에 따라 여러개의 워크플로도 만들 수 있다.
    생성된 워크플로는 .github/workflows 디렉토리 이하에 위치한다.
  • 비공개 레포지토리의 경우 Github Actions가 작동할 때의 용량과 시간이 제한되어있으며 공개 레포지토리는 무료로 사용 가능하다.

사용법(예제)

  • 새로운 리포지토리를 만든다.
    Public 으로 만들어야 무료로 사용 가능하다.
# 기존 나만의 아고라 스테이츠 서버 레퍼런스 클론
git clone git@github.com:codestates-seb/fe-sprint-my-agora-states-server-reference.git
# 디렉터리 이동
cd fe-sprint-my-agora-states-server-reference
# 새로운 리포지토리를 원격 리포지토리로 등록
git remote add myRepo git@github.com:{여러분의 아이디}/{새로운 리포지토리 이름}.git
# 기존 레퍼런스 코드를 새로운 리포지토리로 push
git push myRepo reference
  • 새로운 리포지토리에 나만의 아고라 스테이츠 서버 레퍼런스 코드를 push 한다.
    rag & drop을 할 수도 있겠지만, 가능하면 기존 나만의 아고라 스테이츠 서버 레퍼런스를 클론받아서, 새로운 리포지토리를 원격 리포지토리로 등록하고, 코드를 push 해본다.
  • push 한 모습이다. 커밋 기록을 보시면, 주황색 원을 확인할 수 있다.
  • Actions 탭으로 이동하여 Github Action을 코드를 설정해두면 이렇게 서버 테스트가 자동으로 작동하는 것을 확인할 수 있다.

리포지토리를 push하기만 했는데, 왜 작동했을까요?

  • ./.github/workflows/pullRequest.yml 파일로 인해 가능
name: Bare Minimum Requirements
# 언제 job을 작동시킬지
on: [push, pull_request]
# 어떤 job을 할지
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Bare Minimum Requirements
        uses: actions/setup-node@v1
        with:
          node-version: '16'
      - run: npm install
      - run: npm test

YAML

  • Yet Another Markup Language의 약자로, 사람이 읽을 수 있는 데이터 직렬화 언어를 의미

JSON vs YAML

  • JSON 파일과 YAML 파일은 key-value 형태로 작성된 파일이며, 계층 구조를 가지는 것에는 동일하다.
    그러나 YAML 파일은 "" (큰따옴표, double quotation marks) 없이 문자열 작성이 가능해, 설정을 위한 스펙이나 프로퍼티 값 등이 JSON 파일에 비해 한 눈에 들어온다는 점이다.
    또한 JSON 파일처럼 {} 형태로 감싸줄 필요도 없기 때문에 스코프의 압박(잘못 쓰면 일일이 어디가 처음이고 끝인지 찾아야 하는 등)에서 벗어날 수도 있다.
  • JSON 파일은 주석을 작성할 수 없기 때문에 해당 파일 하나만 두고 커뮤니케이션 하기가 까다롭지만, YAML 파일은 애초에 파일 내에 주석을 작성할 수 있기 때문에 커뮤니케이션 하기가 훨씬 수월하다.
  • YAML은 JSON의 상위 호환 격이므로, 기존 json문서를 그대로 yaml파일로 사용하거나 원하는 부분만 손볼 수 있다.

YAML 문법

  • 해당 문법을 지켜 작성하지 않으면 YAML 파일로 읽지 못하기 때문에, 문법을 잘 지켜줘야 한다.

주석, 문서의 시작과 끝

  • : 주석

  • --- : 문서의 시작 (선택사항)
  • ... : 문서의 끝 (선택사항)
#이런 식으로 주석을 작성할 수 있습니다.
--- #문서 시작
#이 사이에 내용이 들어갑니다.
... #문서 끝
  • 들여쓰기 : 들여쓰기는 기본적으로 2칸 또는 4칸을 지원합니다. 보통 2칸씩 들여쓰는 것을 추천합니다. 탭 키가 아닌 스페이스 키로 들여써야 한다.

기본 표현

  • key: value 이며, : 다음에는 무조건 공백 문자가 와야합니다.
key: value

자료형

  • int, string, boolean, 리스트, 매핑을 지원합니다.
  • 여기서 int와 string 타입은 스칼라(Scalar)라 부르고, 배열 혹은 리스트는 시퀀스(Sequence)라 부릅니다.
    매핑에는 기본 표현인 key-value 쌍 및 hash, dictionary가 포함
#int(숫자)
int_type: 1
#string(문자열)
string_type: "1"
#blooean(참/거짓)
boolean_true_type: true
boolean_false_type: false
#이외에 yes, no로 작성하기도 합니다.
yaml_easy: yes
yaml_difficult: no
#리스트(배열 형태)
person:
  name: Chungsub Kim
  job: Developer
  skills: 
    - docker
    - kubernetes
  # JSON 형식의 "skill" : [docker, kubernetes]와 같습니다.

객체

  • 객체 표현은 key 작성 후 두 칸을 들여써서 key-value 형태로 작성을 해주거나, key를 작성 후 중괄호({})로 한 번 묶고 key-value 형태로 작성
key: 
  key: value
  key: value
#또는 이렇게도 작성합니다. 가독성을 위해 사용합니다.
key: {
  key: value,
  key: value
}

Text

  • 줄바꿈 표현(|)과 줄바꿈 무시 표현(>)이 있다.
# |는 줄바꿈 표현입니다.
# JSON 형식의 "comment_line_break": "Hello codestates.\nIm kimcoding.\n"과 같습니다.
comment_line_break: |
  Hello codestates.
  Im kimcoding.
# >는 줄바꿈 무시 표현입니다.
# JSON 형식의 "comment_single_line": "Hello world my first coding."과 같습니다.
comment_single_line: >
  Hello world
  my first coding.

문자열 따옴표

  • key-value 쌍에서 value에 :가 들어간 경우는 반드시 따옴표가 필요하다.
# error가 납니다.
windows_drive: c:
# 이렇게 써야 합니다.
windows_drive: "c:"
windows_drive: 'c:'

Github Action 실습

  • Source: Github reference 브랜치에 코드가 커밋되면
  • Build: github acitons의 YAML 파일에 적힌 명령어를 토대로 Webpack을 이용해 빌드를 하고
  • Deploy: github acitons의 YAML 파일에 적힌 명령어를 토대로 s3로 빌드 결과를 업로드합니다.

name: client
on:
  push:
    branches:
      - reference
jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout source code.
        uses: actions/checkout@v2
      - name: Install dependencies
        run: npm install
        working-directory: ./my-agora-states-client
      - name: Build
        run: npm run build
        working-directory: ./my-agora-states-client
      - name: SHOW AWS CLI VERSION
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_EC2_METADATA_DISABLED: true
        run: |
          aws --version
      - name: Sync Bucket
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_EC2_METADATA_DISABLED: true
        run: |
          aws s3 sync \
            --region ap-northeast-2 \
            build s3://fe-55-ssseok-s3 \
            --delete
        working-directory: ./my-agora-states-client
  • .github/workflows/client.yml 만들어서 위와 같이 설정
  • secrets 을 설정해줘야하는데 실습하고 있는 리포지토리에 들어가서 setting -> secrets/Actions -> New repository secret 으로 KEY 설정하면 된다.

Proxy

CORS 정책이 필요한 이유

  • 브라우저에서 기본적으로 API를 요청 할 때에, 브라우저의 현재 주소와 API 의 주소의 도메인이 일치해야만 데이터를 접근 할 수 있게 되어있다.
    만약 다른 도메인에서 API를 요청해서 사용 할 수 있게 해주려면 CORS 설정이 필요하다.

CORS

  • 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제

출처

  • 웹 콘텐츠의 출처(origin)는 접근할 때 사용하는 URL의 스킴(프로토콜), 호스트(도메인), 포트로 정의된다.
    두 객체의 스킴, 호스트, 포트가 모두 일치하는 경우 같은 출처를 가졌다고 말한다.
    일부 작업은 동일 출처 콘텐츠로 제한되나, CORS를 통해 제한을 해제할 수 있다.

예시

  • 로컬 환경에서 개발한 앱은 기본적으로 localhost:3000 도메인으로 시작한다.
    그러나 나중에 로컬 환경에서 개발한 실제 서비스 및 프로젝트의 클라이언트에서 서버의 API로 요청하게 되면, 이 포트로 요청하는 것이 차단되는 것 또한 경험해 봤을 것이다.
    개발할 당시에는 이 현상이 굉장히 귀찮은 일로 느껴질 수 있겠지만, 실제로 개발한 서비스 및 프로젝트가 모든 출처의 접근을 허락한다면 이는 후에 큰 문제로 이야기될 수 있다.
  • 만일 여러분이 실제 서비스가 되는 상용 앱을 운영 중이라면, 여러분들이 구축한 클라이언트 뒤의 서버와 연결되어 있는 DB에는 라이브 데이터(live data)가 쌓일 것

라이브 데이터(live data)

  • 실제 서비스되고 있는 앱의 데이터베이스(Data Base, DB)에 적재되고 있는 데이터를 의미합니다. 유저 및 상품, 결제 등 다양한 정보들을 예로 들 수 있다.
  • 여러분들은 모든 도메인을 허용해서는 안 되고, 특정 도메인을 허용하도록 구현해야한다.
    프론트엔드 개발자가 백엔드 개발자에게 프론트엔드 개발 서버 도메인을 허용해달라고 요청을 해야하고, 백엔드 개발자는 응답 헤더에 필요한 값들을 담아서 전달을 해줘야한다.
    서버에서 적절한 응답 헤더를 받지 못하면 브라우저에서 에러가 발생하기 때문이다.

Proxy

  • 위의 정석적인 과정 없이 React 라이브러리, 혹은 Webpack Dev Server에서 제공하는 proxy 기능을 사용하면 CORS 정책을 우회할 수 있다.

Proxy 적용 전 흐름

  • 프론트엔드, 즉 여러분이 개발한 React 앱에서 브라우저 쪽으로 요청을 보낸다.
    그러면 브라우저는 백엔드, 즉 서버 쪽으로 리소스를 요청하게된다.
    이때 접근 권한이 있는지, 즉 출처가 같은지 확인하는데 이때 백엔드 서버는 정상적으로 200 OK 응답을 브라우저에게 보낸다.
    마지막으로 브라우저는 받은 리소스 및 응답과 함께 출처가 같은지 아닌지 확인하게 되는데, 이때 출처가 다르다면 응답을 파기(CORS Error) 하고, 출처가 같다면 응답을 파기하지 않고 다시 프론트엔드 쪽으로 응답을 보내주는 것이다.

Proxy 적용 후 흐름

  • React 앱에서 브라우저를 통해 API를 요청할 때, proxy를 통해 백엔드 서버로 요청을 우회하여 보내게된다.
    그러면 백엔드 서버는 응답을 React 앱으로 보내고, React 앱은 받은 응답을 백엔드 서버 대신 브라우저에게 전달한다.
    이렇게 되면 출처가 같아지기 때문에 브라우저는 이 사실을 눈치 채지 못하고 허용하게 된다.

proxy 사용법

webpack dev server proxy

  • webpack dev server proxy 사용하게 되면, 브라우저 API를 요청할 때 백엔드 서버에 직접적으로 요청을 하지 않고, 현재 개발서버의 주소로 우회 요청을 하게된다.
    그러면 웹팩 개발 서버에서 해당 요청을 받아 그대로 백엔드 서버로 전달하고, 백엔드 서버에서 응답한 내용을 다시 브라우저쪽으로 반환한다.
...
"browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
	"proxy" : "우회할 API 주소"
}
  • 웹팩 개발서버의 proxy 설정은 원래 웹팩 설정을 통해서 적용을 하지만, CRA 를 통해 만든 리액트 프로젝트에서는 package.json 에서 "proxy" 값을 설정하여 쉽게 적용할 수 있도록 구성이 되어있다.
# 변경전
export async function getAllfetch() {
    const response = await fetch('우회할 api주소/params');
    .then(() => {
			...
		})
}
# 변경후
export async function getAllfetch() {
    const response = await fetch('/params');
    .then(() => {
			...
		})
}
  • 기존의 fetch, 혹은 axios를 통해 요청하던 부분에서 도메인 부분을 제거

React Proxy 사용법

  • webpack dev server 에서 제공하는 proxy는 전역적인 설정이기 때문에, 종종 해당 방법이 충분히 적용되지 않는 경우가 생기기도 한다.
    수동으로 proxy를 적용해줘야 하는 경우가 있는데, 이때는 http-proxy-middleware 라이브러리를 사용한다.
npm install http-proxy-middleware --save
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
  app.use(
    '/api', //proxy가 필요한 path prameter를 입력합니다.
    createProxyMiddleware({
      target: 'http://localhost:5000', //타겟이 되는 api url를 입력합니다.
      changeOrigin: true, //대상 서버 구성에 따라 호스트 헤더가 변경되도록 설정하는 부분입니다.
    })
  );
};
  • React App의 src 파일 안에서 setupProxy.js 파일을 생성하고, 안에서 설치한 라이브러리 파일을 불러온 다음, 아래와 같이 작성
# 변경전
export async function getAllfetch() {
    const response = await fetch('우회할 api주소/params');
    .then(() => {
			...
		})
}
# 변경후
export async function getAllfetch() {
    const response = await fetch('/params');
    .then(() => {
			...
		})
}
  • 기존의 fetch, 혹은 axios를 통해 요청하던 부분에서 도메인 부분을 제거
profile
선한 영향력을 주는 사람

0개의 댓글