TIL

32일차 지속적 통합

김영재0412 2022. 5. 30. 13:48

CI/CD 파이프라인

 

waterfall model

과거에는 전통적인  소프트웨어 전달 방식인 폭포수(waterfall)모델을 주로 사용했다. 출시 기한을 미리 정해두고 소프트웨어를 완성 및 배포하였다. 많은 문제점이 있었는데 출시 시점에 소프트웨어의 신뢰성, 안정성을 보장할 수 없었고, 막상 열어보니 수많은 버그가 존재했다.

그래서 소프트웨어의 안정성을 위해 베타 버전등을 통한 테스트를 하였으며 사용자가 최신 상태로 업데이트를 해야했으며  업데이트를 하지않으면 버그 수정을 전달하기 매우 어려웠다. 아직 모바일 어플리케이션이 전달하는 방식이다.

ps. 몇몇 소프트웨어는 지속적 전달로 자동 업데이트를 도입했다 

 

 

현재는 클라우드 서비스 전달 방식인 애자일 모델을 사용한다. SaaS(Software as a Service)로 서비스로서의 소프트웨어이며 따로 업데이트 필요없이 브라우저에 접속하기만 해도 최신 버젼을 사용할 수 있다. 애자일 모델을 사용하면 사용자의 업데이트 걱정으로 부터 벗어날 수 있고 하루에도 여러번 릴리즈가 가능하여 빠른 문제해결이 가능하다. 다양한 배포 방식을 적용하거나 A/B 테스트가 가능하며 빠른 배포를 보장한다. 이를 달성하기 위해 전달 워크플로가 수립되어야 하며, 자동화가 필수적이다.

 

이러한 CI/CD라인(전달 배포 파이프라인)을 구성하는게 DevOps 영역이며 서비스 전달/배포 Workflow를 구성할 수 있어야 한다.

 

 

 

 

유용한 CI 도구들

 

 

Jenkins

 

모든 언어의 조합과 소스 코드 레포지토리에 대한 지속적인 통합과 지속적 배포환경을 구축하기 위한 도구다. 빌드, 테스트, 배포 프로세스를 자동화하여 소프트웨어 품질과 개발 생산성을 높일 수 있다.

 

편리한 설정

웹 기반의 콘솔로 다양한 인증 기반과 결합이 가능하며 권한 관리 기능을 통해 안전한 빌드/배포 환경을 구축할 수 있다. 수많은 플러그인을 사용하여 자동화 할 수 있어 반복되는 작업을 줄일 수 있다. 빌드/배포의 결과에 대해 통지 받을 수 있는 설정이 간편하고 다양한 채널을 통해 빠르게 피드백을 받을 수 있다.

 

안정적인 빌드/배포 환경

소스 버전 관리 툴과 연동하여 코드 변경을 감지하고, 자동화 테스트를 포함한 빌드를 수행하여 소프트웨어 품질을 향상시킬 수 있다. 자동화 테스트에는 코딩 표준 준수 여부 체크, 유닛 테스트, 통합 테스트 등을 설정할 수 있고 테스트 결과에 대한 피드백을 받아 잠재적인 오류를 사전에 예방할 수 있다. 빌드 결과물을 지속적으로 배포하도록 설정하여 개발 프로세스 전체를 자동화할 수 있다.

 

다양한 활용 및 손쉬운 확장

Jenkins는 많이 사용 되고 있는 오픈 소스 소프트웨어로 문서화가 잘 되어 있다. 빌드/배포 이외에도 스케쥴링을 이용한 배치 작업에도 활용되는 등 다양한 적용 사례들을 참고할 수 있다. 플러그 인을 직접 개발하여 기능을 확장하는 것도 가능하다.

 

 

기능

 

젠킨스와 같은 CI툴이 등장하기 전에는 일정시간마다 빌드를 실행하는 방법이 일반적이었는데, 젠킨스는 서브버전, Git과 같은 버전 관리 시스템과 연동해서 소스의 커밋을 감지하면 자동적으로 자동화 테스트가 포함된 빌드가 작동하도록 도와주게 되어 편의성이 증가되었다.

  

이러한 기능을 수행하는 젠킨스는 컴파일 오류를 검출하고, 자동화 테스트를 수행하며, 정적 코드 분석으로 인한 코딩 규약 준수 여부를 체크하고 프로파일링 툴을 이용한 성능 변화 감시, 결합 테스트 환경에 대한 배포 작업의 큰 도움을 준다.

 

특징

  • 설치형: 별도의 서버가 필요하다.
  • 다양한 플러그인을 활용할 수 있다.
  • 쿠버네티스, Docker 등과 호환된다.
  • 다양한 운영체제에서 사용이 가능하다.

 

 

 

Travis CI

 

Travis에서 만든 지속적인 통합(CI)을 위한 툴입니다. 해당 소스가 지속적으로 잘 작동하는지 테스트하는 것을 말하며 github에 커밋이 될 때마다, 사용자가 따로 빌드 테스트를 실행시키지 않아도 자동으로 테스팅을 수행하고 결과를 알려준다.

Travis CI의 웹 사이트에서 github 계정으로 연동해 가입하면, 자신의 github에 있는 저장소들 중 관리하고 싶은 것을 선택하여 자동화 테스트를 수행할 수 있다.

.travis.yml

Travis CI에 등록한 저장소에 루트에 .travis.yml이라는 설정 파일을 넣어두면, 해당 설정에 정의되어 있는 방식대로 빌드테스트를 수행하게되며 다양한 언어별로, 그리고 프레임워크별로 설정의 내용이 상이할 수 있기 때문에, 구글검색 혹은 Travis CI Docs를 참고하여 설정해야 한다.

Build

저장소를 Travis 에서 추적하도록 등록하였고, .travis.yml을 작성하였다면, git push명령어를 실행하여 github에 올렸을 때, 자동으로 Travis는 빌드 테스트를 수행한다.

 

특징

  • 클라우드 서비스(SasS) 형태로 사용할 수 있다.
  • Travis 자체에서 호스팅을 해주기 때문에 관리적인 측면에서 편리하다.
  • Clojure, Erlang, Groovy Haskell, Java, JavaScirpt, Node.js, Perl PHP, Rython, Ruby 등의 다양한 언어를 지원한다.

 

 

GitHub Action

 

GitHub Actions는 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 CI/CD 플랫폼이다. repository에 대한 모든 pull request를 빌드 및 테스트하는 workflow를 생성하거나 병합된 pull request를 프로덕션에 배포할 수 있으며 단순한 DevOps를 넘어 repository에서 다른 이벤트가 발생할 때 workflow를 실행할 수 있도록 할 수 있다. 예를 들어, 누군가가 저장소에 새로운 이슈를 생성할 때마다 적절한 레이블을 자동으로 추가하는 workflow를 실행할 수 있다.

 

GitHub은 workflow를 실행하기 위한 Linux, Windows 및 macOS 가상 머신을 제공하거나 자체 데이터 센터 또는 클라우드 인프라에서 자체 호스팅 러너를 호스팅할 수 있다.

 

컴포넌트

pull request가 열리거나 이슈가 생성되는 것과 같은 이벤트가 repository에서 발생할 때 트리거되도록 GitHub workflow를 구성할 수 있다. workflow에는 순차적으로 또는 병렬로 실행할 수 있는 하나 이상의 job이 포함되어 있다. 각 job은 자체 가상 머신 runner 내부 또는 컨테이너 내부에서 실행되며, 사용자가 정의한 스크립트를 실행하거나 job을 실행하는 하나 이상의 단계(워크플로를 단순화할 수 있는 재사용 가능한 확장)가 있다.

 

 

workflows

workflow는 하나 이상의 job으로 구성되고 event에 의해 트리거될 수 있는 자동화된 프로세스이다. 가장 최상위 개념으로 YAML으로 작성되고 Github Repository의 .github/workflows 폴더 아래에 저장된다. repository에는 여러 workflow를 가질 수 있으며 workflow는 서로 다른 작업을 수행할 수 있다.

 

events

event는 workflow 실행을 트리거하는 특정 활동이나 규칙이다. 예를 들어, 누군가가 pull request를 생성하거나 issue를 열거나 특정 브랜치로 push하거나 특정 시간대에 반복(cron)하거나 webhook을 사용해 외부 이벤트를 통해 실행될 수 있다.

 

jobs

여러 step으로 구성되고 가상 환경의 인스턴스에서 실행되며 다른 job에 의존 관계를 가질 수 있고 독립적으로 병렬 실행도 가능하다.

  • Workflow는 다양한 Job으로 구성되는데 기본적으로 병렬로 실행

 

steps

step은 task들의 집합으로 커맨드를 날리거나 쉘 스크립트 실행하는 것처럼 action을 실행할 수 있다.

 

actions

workflow의 가장 작은 블럭으로 job을 만들기 위해 step들을 연결할 수 있다. 재사용이 가능한 컴포넌트로서 반복적인 코드의 양을 줄일 수 있고 git repository를 가져오거나 클라우드 공급자에게 인증을 설정할 수도 있다. 또한 개인적으로 만든 action을 작성할 수도 있고 github marketplace에 있는 공용 action을 사용할 수도 있다.

 

runners

runner는 workflow가 트리거될 때 실행하는 서버이다. 각 runner는 1번에 1개의 job을 실행할 수 있으며 Github에서 호스팅해주는 Github-hosted runner와 직접 호스팅하는 Self-hosted runner로 나뉜다. Github-hosted runner는 Azure의 Standard_DS2_v2로 vCPU 2, 메모리 7GB, 임시 스토리지 14GB이다.

 

특징

  • GitHub 저장소를 기반으로 소프트웨어 개발 Workflow를 자동화 할 수 있는 툴이다.
  • GitHub 마켓 플레이스를 통해 여러 사람이 공유한 Workflow를 찾을 수 있으며, 자신이 직접 만들어 공유 할 수도 있다.
  • 공개 저장소를 무료로 사용할 수 있으며, 비공개 저장소 같은 경우 무료 사용량 이후에 요금이 부과된다.
    • 한달에 500MB 스토리지와 실행 시간 2,000분(minute)까지 제공

Github Actions 레퍼런스

 

 

 

 

 

빌드와 언어별 빌드 도구

 

빌드

프로그램의 소스 코드를 독립적인 아티팩트(artifact)로 변환하는 과정이며 때로는 그 아티팩트 그 자체로도 실행이 가능하며, 대체로 런타임(소프트웨어 실행 환경)이 필요한 경우가 많다.

 

프레임워크

프레임워크는 소프트웨어 개발을 쉽게 만들어주기 위해 필요한 도구, 규약의 집합체입니다. 프레임워크 없이 밑바닥부터 모든 코드를 작성하는 것도 가능하지만, 프레임워크를 통해 만들고자 하는 소프트웨어의 기본 골격이 제공되기 때문에, 현대의 소프트웨어 개발에서는 많은 부분을 프레임워크에 의존합니다. 오로지 빌드만을 위한 도구도 많지만, 대부분의 경우 어떤 언어나 프레임워크를 선택하느냐에 따라 빌드 도구가 정해지기 마련입니다. 사실상 "언어별"이라고 하기 보다는 "프레임워크별"로 구분하는 것이 보다 편리한데, 이는 현대의 프레임워크는 대부분 빌드 도구를 지정하기 때문입니다. 목적에 따른 프레임워크의 종류로는 다음과 같은 것들이 있습니다.

 

백엔드 웹 애플리케이션 개발용 프레임워크

  • Spring (Java, Kotlin)
  • Django (Python)
  • Express (JavaScript)

프론트엔드 웹 애플리케이션 개발용 프레임워크

  • React 및 관련 라이브러리 (JavaScript)
  • Vue.js, Svelte (JavaScript)

모바일 및 데스크탑 애플리케이션 개발용 프레임워크

  • Flutter (Android, iOS 등)
  • .NET Framework (Windows)
  • Apple 운영체제 기본 Native 프레임워크 Cocoa (macOS), Cocoa Touch (iOS)
  • 안드로이드 기본 Native 프레임워크 (Android)

 

JavaScript 기반의 React 생태계

React 프레임워크를 사용하는 경우 create-react-app 또는 next.js 와 같은 프레임워크를 사용하며 의존성 설치 후 빌드하지 않고, 바로 애플리케이션을 실행하기 위해서는 npm run start 등의 명령어를 사용할 수 있다. 마찬가지로, 애플리케이션에 단위 테스트가 제공된다면, 애플리케이션의 테스트를 위해 npm run test 명령어를 사용한다. 각 프로젝트에서 사용하는 package.json 파일이 어떻게 구성되어 있느냐에 따라 달라질 수 있다.

 

 

 

Java/Kotlin 기반의 Spring Boot 생태계 (Gradle)

Java/Kotlin 애플리케이션을 빌드하면 JVM(자바 런타임) 위에서 실행되는 war 파일이 아티팩트로 생성되며 빌드 도구를 이용해 이를 실행할 수도 있다. Spring 및 Spring Boot 생태계에서는 대표적인 빌드 도구가 두가지(maven, Gradle)가 있으며 그 중에서 여기서는 Gradle 위주로 간략하게 흐름을 설명한다.

  1. 자바 개발 환경(JDK, OpenJDK가 대표적) 준비
  2. gradle 설치
  3. 프로젝트 폴더로 이동
  4. 빌드
    • gradlew build 명령 입력

자바 애플리케이션은 실행을 위해서 빌드가 필수적이며 빌드 후에 실행을 위해서 gradlew bootRun 명령을 통해 애플리케이션의 실행이 가능하다. 그밖에 gradle tasks를 이용해 gradle에서 사용할 수 있는 다양한 태스크를 확인할 수 있다.

 

 

node.js나 python 같이 소스 코드를 그대로 런타임 실행할 수 있는 경우(다른 변환 작업 필요 X)에는 빌드 과정이 생략될 수 있다.

 

 

 

지속적 통합

 

지속적 통합의 원칙

  1. 단일 소스 레파지토리를 유지해야 합니다.
    • 프로젝트에서 제품을 빌드하기 위해 함께 조정해야 하는 수많은 파일이 포함되기 때문입니다.
    • 이 모든 파일이 단일 소스 레파지토리가 아닌 곳에 뿔뿔이 흩어져 있다면 추적하는 것이 굉장히 힘들어질 것입니다.
  2. 빌드를 자동화해야 합니다.
    • 사람들에게 이상한 명령을 입력하게 하거나 대화 상자를 클릭하게 하는 것은 시간 낭비입니다.
    • 빌드가 수동으로 진행된다면, 수 많은 실수를 낳을 수 있습니다.
  3. 셀프 테스팅 빌드를 만들어야 합니다.
    • 빌드 프로세스에 자동화된 테스트를 포함 함으로써 버그를 더 빠르고 효율적으로 파악 할 수 있습니다.
    • 지속적 통합의 일부인 테스트 주도 개발(TDD)을 통해 손상된 빌드를 즉시 확인, 수정 할 수 있습니다.
  4. 매일 메인라인에 커밋을 해야 합니다.
    • 각자의 진행 상황을 추적하는 데 도움이 됩니다.
    • 짧은 주기로 커밋을 하므로, 충돌이 발생한 후 빠르게 충돌 상황을 감지 할 수 있습니다.
      • 해당 시점에는 충돌이 많이 발생하지 않아 문제를 쉽게 해결 할 수 있습니다.
      • 만약 통합의 빈도가 길어 충돌을 늦게 발견하게 된다면 문제를 해결하기 매우 어려울 수 있습니다.
      • 메인라인: 시스템의 현재 상태를 의미합니다. 회사마다 어떤 브랜치를 메인라인으로 두는지는 조금씩 다르지만, 여기서는 master 브랜치를 메인라인으로 취급하도록 하겠습니다.
  5. 모든 팀원이 무슨 일이 일어나고 있는지 알아야 합니다.
    • 지속적 통합은 커뮤니케이션에 관한 것이므로 모든 사람이 시스템 상태와 시스템에 적용된 변경 사항을 쉽게 확인 할 수 있어야 합니다.

기타 원칙

위에서 언급한 원칙 외에도, 그밖에 Martin Fowler가 제시한 나머지 원칙도 함께 소개합니다. 다음은 천천히 이해해도 좋습니다.

  1. 모든 커밋은 통합 서버의 메인라인에서 빌드 돼야 합니다.
    • 개발자간 개발 환경에 차이가 있기 때문입니다.
  2. 빌드의 오류를 즉시 수정 할 수 있어야 합니다.
    • 빌드가 중단되는 것이 나쁜 것은 아니지만, 이러한 상황이 매번 항상 발생하는 경우 사람들이 커밋 전에 로컬에서 업데이트 및 빌드에 대해 충분히 주의하지 않고 있음을 시사합니다.
    • 지속적 통합 도구를 사용하면 빌드의 오류를 즉시 확인 할 수 있습니다.
  3. 빌드가 빨리 되도록 유지해야 합니다.
    • 지속적 통합의 요점은 신속한 피드백을 제공하는 것입니다.
    • 오랜 시간이 걸리는 빌드는 지속적 통합의 가장 큰 장애물입니다.
  4. 운영 환경과 동일한 환경에서 테스트가 진행돼야 합니다.
    • 다른 환경에서 테스트하는 경우 환경의 차이로 인해 테스트에서 발생하는 일이 프로덕션에서 발생하지 않을 위험이 있습니다.
  5. 누구나 최신 실행 파일을 쉽게 얻을 수 있어야 합니다.
  6. 배포 자동화가 이루어져야 합니다.
    • 지속적 통합을 수행하려면 커밋 테스트를 실행하기 위한 환경과 2차 테스트를 실행하기 위한 하나 이상의 환경이 필요합니다. 이러한 환경 간에 실행 파일을 하루에 여러 번 이동하기 때문에 이 작업을 자동으로 수행하고 싶을 것입니다. 따라서 응용 프로그램을 모든 환경에 쉽게 배포할 수 있는 스크립트를 갖는 것이 중요합니다.
    • 매일 프로덕션에 배포하지 않을 수도 있지만, 자동 배포는 프로세스 속도를 높이고 오류를 줄이는 데 도움이 됩니다. 또한 테스트 환경에 배포하는 데 사용하는 것과 동일한 기능을 사용하기 때문에 저렴한 옵션이 될 수 있습니다.

 

지속적 통합에서 테스트가 가지는 장점

 

  • 테스트를 통해 결함과 버그를 조기에 발견 할 수 있으며, 이는 개발자의 생산성을 향상할 수 있다.
  • 제품의 결함과 버그를 발견하고 수정하는 것은 소프트웨어의 품질을 보증하고, 더 안정적이고 사용하기 쉽게 만든다.

 

 

 

테스트 주도 개발(Test Driven Development, TDD)

 

테스트 주도 개발이란 반복 테스트를 이용한 소프트웨어 방법론으로 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현하는 것을 뜻한다.

 

짧은 개발 주기의 반복에 의존하는 개발 프로세스이며, 애자일 방법론 중 하나인 eXtream Programming(XP)의 ‘Test-First’ 개념에 기반을 둔 단순한 설계를 중요시한다.

 

eXtream Programming(XP)란?

미래에 대한 예측을 최대한 하지 않고 지속적으로 프로토타입을 완성하는 애자일 기방법론 중 하나이다.

이 방법론은 추가 요구사항이 생기더라도 실시간으로 반영할 수 있다.

 

 

 

 

테스트 주도 개발의 필요성

 

 

기존 개발 프로세스

기존 개발 프로세스

기존 개발 프로세스는 아래와 같다.

  1. 요구사항 분석
  2. 요구 사항을 토대로 디자인(설계)을 진행
  3. 설계에 맞추어 기능을 개발.
  4. 구현 완료 후 수동으로 기능을 테스트
  5. 원하는 대로 동작하지 않거나 문제가 발생하면 디버깅을 통해 원인을 파악하고 수정
  6. 3 - 4의 과정을 개발이 완료될 때까지 반복

 

 

테스트 주도 개발 프로세스

 

테스트 주도 개발

테스트 주도 개발(TDD)의 프로세스는 아래와 같다.

  1. 요구사항 분석
  2. 요구 사항을 토대로 디자인(설계)을 진행
  3. 설계를 기반으로 기능 테스트 진행 -> 실패 시 다시 설계
  4. 테스트가 성공했다면 개발 진행
  5. 3 - 4의 과정을 개발이 완료될 때까지 반복

 

 

테스트 주도 개발의 테스트는 큰 단위의 문제를 작은 단위로 나누어, 지속적인 피드백을 통해 목표를 개선해나가는 방향으로 진행된다.

 

 

테스트 주도 개발의 장점

  1. 더욱 명확한 기능과 구조를 설계할 수 있다.
  2. 재사용성이 고려된, 모듈화된 코드를 작성할 수 있다.
  3. 설계 수정 시간과 디버깅 시간의 단축
    • 단위 테스트 기반의 테스트 코드를 작성하기 때문에 추후 프로그램에 문제가 발생할 경우, 각각의 모듈별로 테스트를 진행하면서 문제 지점을 쉽게 찾아낼 수 있다.
  4. 완성도 높은 설계
    • 코드의 기능, 정의 등 구조적인 문제에 대하여 명확하게 접근할 수 있으며 다양한 예외상황에 대해서도 고려하게 되므로 이는 완성도 높은 설계로 이루어진다.
  5. 유지 보수의 용이성
    • 프로젝트에 어떤 기능을 추가하는 등의 유지 보수를 해야 하는 상황이라면 항상 기존 코드들에 끼칠 영향에 대해 생각해야한다.
    • TDD 이전의 개발 방식에선 단순한 기능이라도 수정되거나, 추가되는 경우에는 많은 코드에 대하여 테스트를 진행해야 했으나, TDD를 진행한다면 변경 점에 따른 테스트를 진행해야 하는 상황에 대한 부담이 줄어들 수 있다.

 

테스트의 종류

 

단위 테스트(Unit Test)

  • 말 그대로 작은 단위의 테스트
  • 검증이 필요한 코드에 대해 테스트 케이스를 작성하는 절차 혹은 프로세스
  • 유닛 테스트의 장점으로는 즉각적인 피드백이 나오는 것을 들 수 있으나 하나의 메서드가 잘 작동함은 보장할 수 있지만 그들이 결합하는 시점에서도 잘 작동 하는지에 대해서는 보장할 수 없기 때문에 염두해야한다.

 

it('sum에 두 수를 인자로 입력하면, 두 수의 합이 리턴됩니다', () => {
  expect(sum(1,1)).to.be.equal(2)    // sum(1,1)의 리턴값은 2가 될 것이라고 기대한다
  }

 

통합 테스트(Integration Test)

  • 모듈을 통합하는 과정에서 모듈 간 호환성의 문제를 찾아내기 위해 수행되는 테스트
    • 단위 테스트에서 찾지 못하는 통합시 발생하는 버그 등을 찾을 수 있다.

 

// 서버에 API 요청을 보냈을 때, 정확한 응답이 오리라고 기대하는 경우
// 아래의 코드가 백엔드 입장에서는 단순 유닛 테스트라고 볼 수도 있지만
// 서버와 클라이언트 코드가 별도로 작성되어 있고, 서버-클라이언트의 통합을 테스트해야 한다면, 이는 통합 테스트라고 할 수 있습니다.

it('서버에 POST /upper 요청에 body를 실어 보내면 응답은 대문자로 돌려줍니다', () => {
  return request(API서버)
    .post('/upper')
    .send('"coDeStaTes"')
    .set('Content-Type', 'application/json')
    .then(res => {
      expect(res.body).to.be.equal('CODESTATES')
    })
})

단위 테스트 및 통합 테스트시 사용하는 도구

  • mocha, chai (JavaScript)
  • JUnit (Java)

 

 

 

E2E 테스트 (End To End Test)

  • 전체 시스템이 제대로 동작하는지 확인하기 위한 테스트
  • 사용자의 입장에서 사용자가 사용하는 상황을 가정하고 시뮬레이션을 진행한다.

장점

  • 실제 상황에서 발생할 수 있는 에러를 사전에 발견 할 수 있다.

단점

  • 테스트 작성 시 들어가는 비용이 너무 크다.
  • 수행 속도가 느리다.
  • "실패했다"라는 결과만 있기 때문에 피드백의 질이 낮다.

E2E 테스트시 사용하는 도구

  • Cypress
  • Nightwatch
  • TestCafe

'TIL' 카테고리의 다른 글

34일차 지속적 통합 && 배포 자동화  (0) 2022.06.02
33일차 지속적 통합  (0) 2022.05.31
31일차 yaml  (0) 2022.05.30
30일차 docker, yaml  (0) 2022.05.30
29일차 docker  (0) 2022.05.30