본문 바로가기

코드스테이츠 SEB FE 41기/Section 별 내용 정리

section4/Unit10/[Deploy] CI/CD

반응형

CI/CD

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

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

 

일반적인 앱의 개발 및 유지보수 단계는 아래와 같다.여기서 지속적 통합 및 지속적 전달을 단계별로 꾀할 수 있다.

지속적 통합(Continuous Integration, CI)

개발자를 위한 자동화 프로세스라고 볼 수 있으며, Code - Build - Test 단계에서 꾀할 수 있다.

  • Code : 개발자가 코드를 원격 코드 저장소 (Ex. github repository)에 push하는 단계
  • Build : 원격 코드 저장소로부터 코드를 가져와 유닛 테스트 후 빌드하는 단계
  • Test : 코드 빌드의 결과물이 다른 컴포넌트와 잘 통합되는 지 확인하는 과정

이 과정에서 개발자는 코드를 잦게 원격 코드 저장소에 push하고, 테스트 및 빌드를 하며 빌드 결과를 통해 빌드가 성공했는지 실패했는지 확인을 하고, 통합 테스트 결과를 통해 개선 방안을 찾는다. 이 지속적인 통합 과정을 통해 개발자는 버그를 일찍 발견할 수 있고, 테스트가 완료된 코드에 대해 빠른 전달이 가능해지며 지속적인 배포가 가능해진다.

지속적 통합은 모든 코드 변화를 하나의 리포지토리에서 관리하는 것 부터 시작한다. 모든 개발팀이 코드의 변화를 확인할 수 있기 때문에, 투명하게 문제점을 파악할 수 있다. 그리고 잦은 풀 리퀘스트(pull request)와 머지(merge)로 코드를 자주 통합한다. 이 때, 기본적인 테스트도 작동시킬 수 있다. 이렇게 지속적 통합을 통해 개발팀은 각자 개발한 코드를 이른 시점에 자주 합치고 자주 테스트 해볼 수 있다.

지속적 통합으로 보안 이슈, 에러 등을 쉽게 파악할 수 있어 해당 이슈를 빠르게 개선할 수 있다. 이전에는 각자 개발자가 작성한 코드를 합치고 난 후, 모두 모여서 빌드를 시작하고 나서야 문제점을 파악할 수 있었다. 지속적 통합이 적용된 개발팀은 코드를 머지하기 전, 이미 빌드 오류나 테스트 오류를 확인하여 훨씬 더 효율적인 개발을 할 수 있게 된다.

 

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

지속적인 서비스 제공(Continuous Delivery) 및 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환적으로 사용된다. 이 부분은 Release - Deploy - Operate 단계에서 꾀할 수 있다.

  • Release : 배포 가능한 소프트웨어 패키지를 작성
  • Deploy : 프로비저닝을 실행하고 서비스를 사용자에게 노출.실질적인 배포 부분
  • Operate : 서비스 현황을 파악하고 생길 수 있는 문제를 감지

지속적 배포의 경우, 코드 변경 사항의 병합부터 프로덕션에 적합한 빌드 제공에 이르는 모든 단계로, 테스트 자동화와 코드 배포 자동화가 포함된다.

이 프로세스를 완료하면 프로덕션 준비가 완료된 빌드를 코드 리포지토리에 자동으로 배포할 수 있기 때문에 운영팀이 보다 빠르고 손쉽게 애플리케이션을 프로덕션으로 배포할 수 있게 된다.

최근에는 클라우드 기술 발전과 맞물려 지속적 통합과 지속적 배포가 빠른 속도로 진행되면서 CI/CD를 하나로 묶어서 다루는 경우가 점차 증가하고 있다. 예를 들어, 이전에는 배포 자체가 상당히 오래 걸리고 힘든 일이어서 배포 이전 단계에서 많은 고민을 하곤 했다. 서버를 전부 재시작해야 한다거나, 일부 기능을 제공하지 못하는 경우도 많았기 때문이다. 요즘은 고객의 피드백을 빨리 받기 위해서라도, 서비스를 중단하지 않기 위해서라도 릴리즈만 잘 기록해두고 바로바로 배포하는 사례가 증가하고 있다.


배포자동화

배포 자동화란 한번의 클릭 혹은 명령어 입력을 통해 전체 배포 과정을 자동으로 진행하는 것을 뜻한다. 

  • 먼저 수동적이고 반복적인 배포 과정을 자동화함으로써 시간이 절약된다.
  • 휴먼 에러(Human Error)를 방지할 수 있다. 여기서 휴먼 에러란 사람이 수동적으로 배포 과정을 진행하는 중에 생기는 실수들을 뜻한다. 그 전에 했던 배포 과정과 비교하여 특정 과정을 생략하거나 다르게 진행하여 오류가 발생하는 것이 휴먼 에러의 예로 볼 수 있다.

배포 자동화를 통해 전체 배포 과정을 매번 일관되게 진행하는 구조를 설계하여 휴먼 에러 발생 가능성을 낮출 수 있다.

 

CI/CD 파이프라인

사용자 업데이트에 대한 걱정에서도 벗어났고, 하루에 여러 번의 배포도 가능해졌다. 그렇다면 어떻게 빠른 배포 속도를 보장 받을 수 있을까? 개발자가 배포할 때마다 일일히 빌드하고 배포하는 과정을 진행하는 것은 한두 번이면 충분하겠지만, 이러한 과정이 수없이 진행된다면 일일히 이 과정을 수행하는 것이 번잡스럽고 지루할 것이다.

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

배포 과정

해당 그림은 배포 과정을 도식화한 것이다. 개발자가 코드를 원격 저장소에 올리면, 그 코드가 빌드 및 테스트와 릴리즈를 거쳐 배포 서버로 전달 된다. 배포 서버에 도달한 빌드된 코드는 애플리케이션 서버로 최종 배포가 완료 되고, 그 결과물을 유저가 직접 확인하게 되는 것이다.

여기서 자동화를 꾀하는 부분은 보통 코드가 빌드되면서 최종적으로 배포가 되는 단계까지이다. 이 부분을 지속적인 통합 및 배포를 위하여 일련의 자동화 단계로 만드는데, 이것을 파이프라인을 구축한다고 표현한다.

 

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

배포에서 파이프라인(Pipeline)이란 용어는 소스 코드의 관리부터 실제 서비스로의 배포 과정을 연결하는 구조를 뜻한다.

파이프라인은 전체 배포 과정을 여러 단계(Stages)로 분리한다. 각 단계는 파이프라인 안에서 순차적으로 실행되며, 각 단계마다 주어진 작업(Actions)들을 수행한다.

파이프라인을 여러 단계로 분리할 때, 대표적으로 쓰이는 세 가지 단계가 존재한다.

  • Source 단계: Source 단계에서는 원격 저장소에 관리되고 있는 소스 코드에 변경 사항이 일어날 경우, 이를 감지하고 다음 단계로 전달하는 작업을 수행한다.
  • Build 단계: Build 단계에서는 Source 단계에서 전달받은 코드를 컴파일, 빌드, 테스트하여 가공한다. 또한 Build 단계를 거쳐 생성된 결과물을 다음 단계로 전달하는 작업을 수행한다.
  • Deploy 단계: Deploy 단계에서는 Build 단계로부터 전달받은 결과물을 실제 서비스에 반영하는 작업을 수행한다.

파이프라인의 단계는 필요에 따라 더 세분화되거나 간소화될 수 있다. DevOps를 전문으로 학습하는 경우 아래와 같이 파이프라인의 단계를 세분화해서 나누기도 한다. 또한, 해당 툴을 소개하는 업체에 따라 용어를 미묘하게 다르게 사용하기도 한다.

 

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

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

로 이루어져 있으며, 이 과정이 실무에서는 반복적인 프로세스이기 때문에 이 부분을 일련의 자동화 단계로 만든다고 볼 수 있다.

이렇게 구축된 파이프라인은 최신 버전의 소프트웨어 애플리케이션을 업데이트하고 제공하려는 일련의 처리 단계에 걸리는 시간을 수동으로 하는 것보다 더 빠르고 안정적이며 효과적으로 줄여주고 CI/CD 인프라와의 호환성과 효율성을 높여준다.


Github Action

GitHub Actions는 Github가 공식적으로 제공하는 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 CI/CD 플랫폼이다.

레포지토리에서 Pull Request 나 push 같은 이벤트를 트리거로 GitHub 작업 워크플로(Workflow)를 구성할 수 있다. 워크플로는 하나 이상의 작업이 실행되는 자동화 프로세스로, 각 작업은 자체 가상 머신 또는 컨테이너 내부에서 실행된다.

워크플로는 .yml (혹은 .yaml ) 파일에 의해 구성되며, 테스트, 배포 등 기능에 따라 여러 개의 워크플로도 만들 수 있다. 생성된 워크플로.github/workflows 디렉토리 이하에 위치한다.

비공개 레포지토리의 경우 Github Actions가 작동할 때의 용량과 시간이 제한되어있으며 공개 레포지토리는 무료로 사용 가능하다. Github Actions에 대해 더 자세히 알고 싶은 경우 공식문서를 참고하자

Github Actions 공식문서 : https://docs.github.com/en/actions

 

[실습] 지속적 통합

이번 튜토리얼에서는 나만의 아고라 스테이츠 서버 레퍼런스와 Github Action을 이용하여 진행한다.
나만의 아고라 스테이츠 서버 레퍼런스

  1. 먼저, 자신의 깃허브 계정에 새로운 리포지토리를 만든다.
  2. 새로운 리포지토리에 나만의 아고라 스테이츠 서버 레퍼런스 코드를 push한다. drag & drop을 할 수도 있겠지만, 가능하면 기존 나만의 아고라 스테이츠 서버 레퍼런스를 클론받아서, 새로운 리포지토리를 원격 리포지토리로 등록하고, 코드를 push 해보자.
# 기존 나만의 아고라 스테이츠 서버 레퍼런스 클론
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
  1. 코드가 모두 push가 된 후 커밋 기록을 보면, 주황색 원을 확인할 수 있다.
  2. 주황색 원이 무엇인지 좀 더 자세히 파악하기 위해 Actions 탭으로 이동한다. Github Action을 코드를 설정해두면 이렇게 서버 테스트가 자동으로 작동하는 것을 확인할 수 있다.

Github Action은 Github의 특정 이벤트에 맞게 다양한 작업을 시킬 수 있는 CI/CD 플렛폼이다. EC2와 같은 하나의 가상 인스턴스를 실행시켜서 원하는 작업을 시킬 수 있다.

리포지토리를 push하기만 했는데, 왜 작동했을까? ./.github/workflows/pullRequest.yml 파일을 읽어보면, 언제 어떤 job을 할지 명시되어 있다.

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

npm install은 빌드를 위한 준비과정으로 볼 수 있다. Node.js로 만든 서버 애플리케이션은 npm으로 관련 오픈소스를 모두 깔끔하게 설치해야 작동하기 때문이다.
npm test는 유닛 테스트 과정이다. 작성한 코드가 요구사항 충족을 위한 최소한의 조건을 만족했는지 확인한다.

github actions 공식문서

 

YAML

Yet Another Markup Language의 약자로, 사람이 읽을 수 있는 데이터 직렬화 언어를 의미한다. 여기서 YAML을 YAML ain’t markup language(재귀 약어)로 생각하는 사람도 있다. 후자는 YAML이 문서가 아닌 데이터용임을 강조하는 말이라고 생각하면 된다. 파일로 작성시 확장자는 .yaml 혹은 .yml 확장자를 가진다.

YAML은 사람이 읽을 수 있고 이해하기 쉬워 프로그래밍 언어 중에서도 인기가 높다. 또한 다른 프로그래밍 언어와 함께 사용할 수도 있다. YAML은 그 유연성과 접근성으로 인해 자동화 프로세스를 생성하는 데에도 사용된다.

 

JSON vs YAML

name: Bare Minimum Requirements
on: [push, pull_request]
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

[코드] Github actions 설정을 위해 작성된 YAML 파일

위의 YAML 파일을 보면 다른 형식의 파일과는 조금 다른 점이 눈에 보인다. 이번에는 MDN에 게시된 JSON 파일을 보자.

{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    }
  ]
}

[코드] MDN의 JSON 파일

JSON 파일과 YAML 파일은 key-value 형태로 작성된 파일이며, 계층 구조를 가지는 것은 동일하다.

그러나 YAML 파일은 "" (큰따옴표, double quotation marks) 없이 문자열 작성이 가능해, 설정을 위한 스펙이나 프로퍼티 값 등이 JSON 파일에 비해 한 눈에 들어온다는 점이 다르다. 또한 JSON 파일처럼 {} 형태로 감싸줄 필요도 없기 때문에 스코프의 압박(잘못 쓰면 일일이 어디가 처음이고 끝인지 찾아야 하는 등)에서 벗어날 수도 있다.

게다가 YAML 파일은 JSON 파일과 다르게 주석을 작성할 수 있다는 점도 굉장한 이점으로 작용한다. JSON 파일은 주석을 작성할 수 없기 때문에 해당 파일 하나만 두고 커뮤니케이션 하기가 까다롭지만, YAML 파일은 애초에 파일 내에 주석을 작성할 수 있기 때문에 커뮤니케이션 하기가 훨씬 수월하다.

그리고 YAML은 JSON의 상위 호환 격이므로, 기존 json문서를 그대로 yaml파일로 사용하거나 원하는 부분만 손볼 수 있다. 반대로 yaml을 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:'

 

YAML 사용법 및 실제 사용 사례

YAML은 일반적으로 설정 파일(configure file 등)에 사용하기에 좋다. 따라서 spring boot, github action 등 다양한 CI/CD 툴이나 프레임워크에서 사용되고 있다. YAML을 실제로 사용하고 있는 프레임워크 중 대표적으로 쿠버네티스가 있다. 기본적인 팟, 레플리카, 디플로이먼트 등 모든 내부 오브젝트가 yml문서로 작성되어 있으며, yaml 고유 기능 중 하나인 문서 스트림을 사용해 클러스터 전체의 설정을 파일 하나로 관리하기도 한다.

YAML을 사용하여 오브젝트를 기술하고 있는 쿠버네티스


Github Action 실습

먼저 .github의 workflows 폴더 안에 'client.yml' 파일을 추가해주고 해당 코드를 넣어준다.

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-버킷이름-s3 \
            --delete
        working-directory: ./my-agora-states-client

  yml 파일에 코드를 적어줄 때에는 빈칸을 무지 조심해야한다.각 문장의 끝 쪽에 빈칸이 있으면 테스트가 통과되지 않는다.

이상한 곳에 빈칸이 없도록 매우 주의해야 한다!!!!! 나는 버킷 이름 옆에 빈 칸이 하나 있어 계속 안됐다...^^ 아메리카노 원샷 ^^

※ yml 문법에서는 줄바꿈을 문자열로 인식한다고 해서 'run: |' 이렇게 작성 한다.

  sync vs cp

  • cp :  모든 파일을 복사하기 때문에 파일이 이미 존재하는 경우에도 그 파일을 복사
  • sync : 파일을 복사하기 전에 경로를 확인하고 새 파일과 업데이트 된 파일만 복사

 

git add . 했을 때 client.yml 파일이 인식이 안됐고, 인식이 안되니 레포지토리에 변경사항이 전혀 안올라가서 git add 옆에 파일 경로를 따로 적어줬다. 그랬더니 올라갔다. ㅠㅠ너까지 왜그래 add야..

git add ../.github/workflows/client.yml

이걸 몰라서 된건지 안된건지 모른채 한시간을 허비했다^^!!!!

아까 실습에서 원격 리포지토리 이름을 myRepo로 해줬으므로 평소처럼 git push origin master가 아닌 

git push myRepo reference

이 형태로 push를 해준다.

통과됐을 때 진짜 행복했다

push를 한 후 'client.yml'에 대한 테스트가 잘 통과되면 자동으로 aws에 적용되어 배포가 완료된다(어제처럼 직접 파일 드래그 앤 드롭을 해주지 않아도 됨).

약 두시간 반 만에 본 결과 화면

오늘 과제의 빌런: 스페이스 한 칸, git add 

그래도 잘 된게 어디냐..ㅠㅠ 행복하다...해결 못했으면 오늘 하루종일 찝찝했을 것 같다.

반응형