2023.07.27 코드스테이츠 76회차. ( 지속적 통합 )
지속적 통합 ( CI )
현대의 개발 업무는 단 한 사람에 의해 진행되는 것이 아니라 협업을 통해서 이루어진다, 이를 기반으로 Git과 같은 고도의 버전 관리 시스템이 등장하였고, 지속적 통합(CI) 이라는 개발 프로세스가 정립되었다
지속적 통합은 서비스의 전달에 앞서 선해오디어야 하는 과정이며, 사람의 실수를 줄이는 자동화 과정이기도 하다
빌드 ( Build )
소프트웨어 개발에서 빌드(build)란, 소스 코드 파일들을 컴퓨터가 이해할 수 있는 실행 가능한 소프트웨어 산출물로 변환하는 과정을 말한다
이 산출물은 일반적으로 아티팩트(Artifact)라고 부르며 바이너리 파일, 라이브러리, 패키지 등의 형태로 제공된다
빌드 과정에서는 소스 코드 파일들을 컴파일하고, 링크하고, 패키징하고, 배포할 수 있는 형태로 변환한다
빌드 과정은 매우 복잡하고 번거로운 일이며, 수동으로 이루어지면 매우 오래 걸리고 실수할 가능성이 높다
프로젝트가 커지면 커질수록 빌드 과정에서 필요한 작업들이 복잡해지고, 빌드 도구 없이는 이를 관리하기가 매우 어렵다.따라서 빌드 도구를 사용하여 빌드 과정을 자동화 하고, 효율적이고, 안정적인 소프트웨어 릴리스를 위한 과정을 간소화 할 수 있다.
빌드 도구
- Java : Maven, Gradle
- Python : (의존성 관리 및 빌드 스크립트 실행 도구) pip, setuptools
- JavaScript : (의존성 관리 및 빌드 스크립트 실행 도구) npm, yarn
- C++ : CMake, Makefile
빌드 도구 선택 시 고려사항
빌드 도구를 선택할 때는 라이브러리 관리, 빌드 스크립트 작성 용이성, 확장성 등을 고려해야 한다
라이브러리 관리는 빌드 도구에서 자동으로 의존성을 해결할 수 있는 기능을 제공하고, 빌드 스크립트 작성 용이성은 빌드 도구의 문법과 구성이 개발자에게 친숙한지 여부를 고려해야 한다
확장성은 빌드 도구가 대규모 프로젝트에서도 충분히 확장 가능한 구조를 가지고 있는지 여부를 고려해야 한다
## 지속적 통합(Continuous Integration)은 소프트웨어 개발에서 매우 중요한 개념 중 하나입니다. 이는 코드 변경 사항이 발생할 때마다 자동으로 빌드 및 테스트를 실행하여, 코드 변경 사항이 문제를 일으키지 않는지 확인하는 것을 말합니다.
만약 지속적 통합 과정이 자동으로 작동하도록 구성되어 있지 않다면, 개발자가 수동으로 빌드 및 테스트를 실행해야 합니다. 이는 시간이 오래 걸리고, 실수를 유발할 가능성이 높습니다. 또한, 수동으로 실행하면서 생기는 인적 오류(Human Error)로 인해 문제가 발생할 가능성도 높아집니다.
따라서 지속적 통합을 구성하면, 개발자는 자동으로 빌드 및 테스트를 실행하여 문제를 빨리 감지하고, 더욱 안정적인 소프트웨어를 개발할 수 있습니다. 또한, 지속적 통합을 구성하여, 여러 개발자가 동시에 작업하더라도, 각자의 코드 변경 사항이 다른 개발자들의 코드 변경 사항과 충돌하는 문제를 사전에 감지할 수 있습니다.
지속적 통합 (CI : Continuous Integration)
지속적 통합(CI)은 개발자들이 새로운 코드를 작성하고 이전 코드와 함께 자동으로 빌드하고 테스트를 수행하는 프로세스로 매우 중요한 개발 방법론 중 하나이다
지속적 통합의 이점
1. 코드 품질 향상 : CI는 코드를 자동으로 빌드하고 테스트 하므로 개발자들은 실수를 줄일 수 있다.
- 코드 품질 향상에 큰 도움이 된다
2. 더 빠른 피드백 : CI는 새로운 코드를 작성하고 이전 코드와 함께 자동으로 빌드하고 테스트 하므로 문제가 발생하면 빠르게 발견할 수 있다
- 더 빠른 피드백을 받을 수 있게 해준다
3. 더 높은 효율성 : CI는 빌드 및 테스트를 자동화 하므로 개발자들은 수정으로 작업하는 시간을 절약하고 더 많은 시간을 실제 개발에 할애할 수 있다
CI를 사용하면 개발자들은 더 많은 코드를 더 빠르게 개발할 수 있으며, 코드의 품질도 향상시킬 수 있습니다. 이러한 이유로 CI는 현재 많은 기업에서 사용되고 있으며, 개발자들에게 매우 중요한 개발 방법론 중 하나입니다.
지속적 통합의 원칙
지속적 통합은 개발자들이 코드를 작성하면 이전 코드와 함께 자동으로 빌드하고 테스트하는 방법입니다.
이를 통해 코드 품질을 향상하고 더 빠른 피드백을 받을 수 있으며, 더 높은 효율성을 얻을 수 있습니다.
지속적 통합의 원칙은 매우 간단합니다.
새로운 코드가 작성되면, 이전 코드와 함께 자동으로 빌드 및 테스트를 수행하고, 문제가 없으면 코드를 릴리스합니다. 이렇게 하면 개발자들은 더 많은 코드를 더 빠르게 개발할 수 있으며, 코드 품질도 향상할 수 있습니다.
- 버전 관리 시스템에서 새로운 코드가 등록되면, CI 서버에서 자동으로 빌드 및 테스트를 진행합니다.
- 빌드 및 테스트가 완료되면, 결과를 개발자들에게 알려줍니다.
- 문제가 발생한 경우, 개발자들은 이를 빠르게 수정할 수 있습니다.
- 문제가 없으면, 코드를 릴리스합니다.
지속적 통합 (CI : Continuous Integration) 도구
1. Jenkins
Jenkins는 소프트웨어 구축, 테스트, 제공 또는 배포와 관련된 모든 종류의 작업을 자동화하는 데 사용할 수 있는 독립형 오픈 소스 자동화 서버입니다.
Jenkins는 기본 시스템 패키지, Docker를 통해 설치하거나 JRE(Java Runtime Environment)가 설치된 시스템에서 독립 실행형으로 실행할 수도 있습니다.
특징
- 설치형: 별도의 서버가 필요합니다.
- 다양한 플러그인을 활용할 수 있습니다.
- 쿠버네티스, Docker 등과 호환됩니다.
- 다양한 운영체제에서 사용이 가능합니다.
2. Travis CI
Travis CI는 호스트형(hosted) 배포 자동화 서비스로, GitHub 및 Bitbucket 등에서 호스팅되는 소프트웨어 프로젝트를 빌드하고 테스트하는 데 사용됩니다.
특징
- 클라우드 서비스(SasS) 형태로 사용할 수 있습니다.
- Travis 자체에서 호스팅을 해주기 때문에 관리적인 측면에서 편리합니다.
- Clojure, Erlang, Groovy Haskell, Java, JavaScirpt, Node.js, Perl PHP, Rython, Ruby 등의 다양한 언어를 지원합니다.
3.GitHub Acrions
GitHub Actions는 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 지속적 통합 및 지속적 배포(CI/CD) 플랫폼입니다.
리포지토리에 대한 모든 풀 요청을 빌드 및 테스트하는 워크플로를 생성하거나 병합된 풀 요청을 프로덕션에 배포할 수 있습니다.
GitHub Actions는 DevOps를 넘어 리포지토리에서 다른 이벤트가 발생할 때 워크플로를 실행할 수 있습니다.
GitHub는 Linux, Windows 및 macOS 가상 머신을 제공하여 워크플로를 실행하거나 자체 데이터 센터 또는 클라우드 인프라에서 자체 호스팅 러너를 호스팅할 수 있습니다.
특징
- GitHub 저장소를 기반으로 소프트웨어 개발 Workflow를 자동화할 수 있는 툴입니다.
- GitHub 마켓 플레이스를 통해 여러 사람이 공유한 Workflow를 찾을 수 있으며, 자신이 직접 만들어 공유할 수도 있습니다.
- 공개 저장소(Github Public Repository)는 무료로 사용할 수 있으며, 비공개 저장소(Github Private Repository) 같은 경우 매달 일정량의 무료 사용량 이후에 요금이 부과됩니다.
Github Actions를 통한 컨테이너 지속적 통합
Github Actions를 통한 컨테이너 지속적 통합 실습 흐름
1.백엔드 개발자의 IDE(e.g. 로컬 환경의 IntelliJ)에서 코드 작업 후 연결된 Github 저장소로 Commit & Push합니다.
2. Github Repository에 변화가 감지되면 Github Actions가 작동합니다
3. 작성한 Github Actions Workflow의 순서대로 명령이 실행됩니다
3-1. Github Repository에 갱신된 코드에 테스트 및 빌드를 진행합니다. (./gradlew build를 명령한 것과 같습니다 )
3-2. Repository에 있는 Dockerfile을 통해 웹 애플리케이션을 도커 이미지로 만듭니다
4. Github Actions Workflow에 의해 (3-2)에서 생성된 이미지가 DockerHub에 업로드됩니다
깃허브에 공개적인 Owner 레포지토리를 만들어 준다
다음 Github Secret 설정에서
Github Actions는 중요한 정보를 NAME : VALUE 쌍으로 구성된 Github Secret이라는 환경변수에 별도 저장하여 관리할 수 있습니다. Github Secret에 도커 이미지 저장소에 접근하기 위한 계정 정보를 설정합니다.
IDE(e.g. 로컬 환경의 IntelliJ)에서 코드 작업 후 생성한 GitHub 레포지토리에 웹 애플이케이션을 업로드 해준다.
$ echo "# My Repository" >> README.md
$ git init
$ git add .
$ git commit -m "first commit"
$ git branch -M main
$ git remote add origin {웹 애플리케이션을 업로드 하려는 Github 리포지토리 url}
$ git push -u origin main
2.X.X 버전의 SpringBoot는 빌드(./gradlew build)할 때 {file_name}.jar와 {file_name}-plain.jar 두 개의 파일이 생성된다
두 개 파일이 생성되면 지속적 통합의 연속 작업 중 도커 이미지를 생성하는 과정에서 어떤 JAR 파일로 생성할지에 대한 문제가 발생하기 때문에 지속적 통합 작업 도중에 실패가 발생하기때문에 Gradle 설정에서 -plain이 붙은 JAR 파일 생성 기능을 비활성화하여 미리 방지해 준다
Gradle 빌드 다음 단계인 SpringBoot 웹 애플리케이션을 도커 컨테이너로 배포하기 위해 도커 이미지를 만드는 과정에 필요한 작업 DockerFile을 생성해준다
Dockerfile을 만들고 파일 안에 도커 이미지를 만드는 과정을 작성합니다. 도커 이미지를 빌드할 때 작성해 둔 Dockerfile의 내용을 바탕으로 생성합니다.
Dockerfile은 프로젝트 내 build.gradle파일이 위치한 경로인 프로젝트 최상위 경로에 생성해준다
FROM openjdk:11
Dockerfile로 이미지를 만들 땐 베이스가 되는 이미지를 기반으로 새로운 이미지를 만들게 된다
- 운영체제, 웹서버 등 다양한 이미지가 Docker Hub 공식 계정을 통해 제공되고 있으며 이미지를 만드는 환경이나 버전에 따라 다양한 베이스 이미지를 선택할 수 있습니다.
ARG JAR_FILE=build/libs/*.jar
ARG 명령어를 이용하면 Dockerfile 내에서 변수를 등록할 수 있다
Github Actions를 이용해 빌드한 결과물(jar파일)에 쉽게 접근하기 위해, 경로를 포함해 JAR_FILE이라는 이름의 변수에 저장해 준다
COPY ${JAR_FILE} app.jar
COPY A B 명령어는 A에서 B로 이동하는 리눅스의 cp명령어와 비슷합니다. Dockerfile에서는 A는 Dockerfile이 있는 환경에서의 경로 혹은 파일이 컨테이너의 경로 혹은 파일로 복사된다
( 사이사이 띄어쓰기를 꼭 유의해 주어야 한다 )
실습 코드의 COPY ${JAR_FILE} app.jar 는 JAR_FILE에 저장된 값을 적용하면 **COPY build/libs/*.jar app.jar** 로 작성할 수 있습니다. 즉 build/libs/*.jar를 컨테이너에 복사하되 app.jar라는 이름으로 복사하라는 명령어입니다.
위와 같이 작성하면 컨테이너로 옮겨진 jar 파일은 기존에 어떤 이름이더라도 컨테이너에선 app.jar라는 이름으로 확인할 수 있다
ENTRYPOINT ["java","-jar","/app.jar"]
ENTRYPOINT를 이용해 컨테이너가 시작된 후 어떤 동작을 할지 정해 놓을 수 있습니다. 명령은 JSON 배열(e.g. [”a”, “b”, “c”])의 형태로 작성해야 합니다.
즉, 이 Dockerfile로 만든 이미지를 컨테이너로 실행하면 java -jar /app.jar가 실행된다는 뜻입니다. 별도의 jar 파일 실행 명령어 없이 컨테이너를 실행하면서 바로 SpringBoot 웹 애플리케이션을 실행할 수 있게 되는 것이다
Github Actions Workflow 작성
프로젝트 최상위 경로에 .github/workflows 디렉터리를 생성합니다. 그리고 그 밑에 main.yml이란 파일을 생성합니다. 이 파일에 Github Actions에서 수행될 동작을 작성합니다.
- on: 이벤트를 트리거하는 조건을 지정합니다. 여기서는 push 이벤트가 main 브랜치에 발생할 때 작업이 실행됩니다.
- jobs: 하나 이상의 작업을 정의합니다.
- build: 작업의 이름입니다.
- runs-on: 작업을 실행하는 머신의 운영체제를 지정합니다.
- steps: 작업을 수행하기 위해 실행되는 일련의 단계를 정의합니다. 각 단계는 name과 run으로 구성됩니다.
- name: 작업에 대한 이름을 지정합니다.
- use: Github Actions의 마켓플레이스에 등록된 작업을 실행합니다. Github Actions에서 특정 작업을 일종의 라이브러리처럼 마켓플레이스에 등록해 놓을 수 있습니다.
- run: 해당 단계가 실행될 때 수행할 명령을 지정합니다.
steps의 하위 -name은 하나의 작업이라고 볼 수 있습니다. 각각 Set up JDK, Grant Execute permission for gradlew, Docker build 총 세 개의 작업으로 구성되어 있습니다. 작업은 작성된 순서대로 진행됩니다.
- Set up JDK
- 코드를 빌드하기 위해 JDK를 설정하고 점검합니다.
- Grant Execute permission for gradlew
- 코드를 gradle로 빌드합니다.
- Docker build
- 빌드 완료된 JAR 파일을 run에 기입된 명령대로 수행합니다.
- Dockerfile을 이용해 도커 이미지로 만든 후 이미지를 DockerHub에 배포(Push)합니다.
- DOCKER_HUB_USERNAME과 DOCKER_HUB_PASSWORD는 도커 이미지 저장소에 로그인하기 위해 필요한 계정 정보입니다. Github Secret에 등록한 환경변수를 읽어와 로그인합니다.
- DockerHub의 본인 계정에 spring-cicd라는 이름의 리포지토리가 없다면 자동으로 생성되어 업로드됩니다.
- 빌드 완료된 JAR 파일을 run에 기입된 명령대로 수행합니다.
지속적 통합 테스트
Commit & Push를 통해 Github에 반영되는 시점부터 코드를 검사하고 빌드하고 도커 이미지를 만든 후 생성된 도커 이미지를 도커 이미지 저장소에 등록하는 과정까지 자동으로 진행됩니다.
즉, 여러 단계의 과정을 지속적, 연속적으로 통합하는 셈입니다. 지속적 통합의 성공 여부를 확인하려면 아래 과정대로 테스트할 수 있습니다.
- 코드를 Github 저장소에 Commit & Push 합니다.
- Github 저장소에서 Actions 메뉴로 이동하면 아래 이미지처럼 진행 과정을 로그로 확인할 수 있습니다. 성공 실패 여부를 알 수 있습니다.
3. Github Actions의 수행 결과가 성공적이라면 도커 이미지 저장소로 이동하여 아래 이미지처럼 지속적 통합작업으로 생성된 도커 이미지가 등록되었는지 확인합니다.
GIthub Actions 다시 실행(Re-Run)하기
코드가 아닌 Github Secret의 설정 단계에서 문제가 발생한 경우, 리포지토리에 코드를 다시 업로드 할 필요 없이 Github Actions을 다시 실행합니다.
Re-run all jobs를 클릭하면 뜨는 작은 창에서 [Re-run jobs] 버튼을 클릭하면 Github Actions Workflow가 다시 실행됩니다.
클라우드 환경에서 컨테이너 배포
클라우드 환경에서 도커 컨테이너를 배포하기 위해선 개인 도커 허브 리포지토리에 하나 이상의 이미지가 업로드 되어있어야 합니다.
클라우드 환경에 도커 설치
AWS의 EC2에서 연결을 한후 인스턴스에 Docker를 설치해줍니다.
수동으로 도커 컨테이너 배포하기
이제 내려받은 도커 이미지의 태그를 변경하여 이 도커 이미지가 최신 버전의 도커 이미지라는 선언을 함과 동시에 도커 이미지 이름 또한 특정 도커 허브 계정을 붙이지 않은 상태로 만듭니다.
sudo docker tag {도커 유저네임}/spring-cicd:{TAG} spring-cicd
명령어가 정상적으로 작동하면 위 이미지처럼 spring-cicd의 latest 태그가 붙은 도커 이미지가 하나 더 추가됩니다. 서비스를 배포하는 것은 여러 문제점이 해결되고 신규 기능이 추가된 최신 버전이 배포된다는 의미이므로 배포될 최신 버전의 도커 이미지를 latest로 정의하여 배포합니다.
이제 최신 버전의 도커 이미지를 실행하여 컨테이너로 등록합니다.
sudo docker run -d --name server -p 8080:8080 spring-cicd
-d 옵션을 사용하여 백그라운드에서 컨테이너가 동작하도록 설정했고, 포트는 8080을 사용한다고 정의했습니다. 컨테이너 생성과 시작 후 실행 중인 도커 컨테이너를 확인합니다.
도커 컨테이너가 정상적으로 동작하고 있는 것이 확인된다면 서비스 배포가 되었다고 볼 수 있다
포스트맨에서 호출하여 내가 작성한 코드의 내용이 출력이 되는지 확인한다.