이번 글에서는 CI/CD가 무엇인지, 어떤 DevOps 플랫폼이 있는지, 동작을 이해할 때 도움되는 용어들을 알아보겠습니다. 저도 1년 반 전에만 해도 CI 개념을 잘 몰랐지만 SDK 자동화 도입하면서 CircleCI, Github Action을 다룰 기회가 많았습니다. 개발팀에서 본인이 DevOps 작업을 많이 하지 않더라도 작동 원리를 알아두면 도움이 될 거라 생각합니다. 아무래도 외부 환경에 메세지를 보내거나 빌드하는 과정에서 언젠가 에러가 발생할 수 있습니다. 제 글을 통해 개념을 익혀뒀다가 필요할 때 앞장서서 문제를 해결할 수 있으면 좋겠습니다.
CI(지속적인 통합)는 코드를 공유 리포지토리의 선택된 분기에 초기에 자주 통합하는 방식입니다. 독립적으로 기능을 구축하고 개발 주기가 끝날 때 기능을 통합하는 대신 개발자가 공유 메인라인에 매일 커밋하여 하루 종일 여러 번 코드를 공유 리포지토리와 통합합니다. 모든 커밋은 자동화된 테스트 및 빌드를 트리거합니다. 실패하면 신속하게 수리할 수 있습니다.
출처: https://circleci.com/docs/about-circleci/#what-is-ci-cd
협업 경험이 별로 없을 때는 작업한 것을 저장해서 압축파일로 만들고, 그 압축파일들을 열고 코드를 수동으로 합쳤던 기억이 있습니다. git을 사용해서 그런 협업 경험을 개선하게 되었습니다. 코드를 하나로 합칠 때 conflict가 발생하긴 하지만, 3-way merge를 통해서 비교적 편리하게 합칠 수 있습니다. 그런데 기존에는 잘 작동하던 기능이 코드를 합치고 나면 기대하는 대로 작동하지 않는 경우가 꽤 있습니다.
코드가 통합되고 나서도 기능이 작동하는 것을 검증해주면 좋겠죠? 이런 검증을 위해서 merge 후에 빌드를 해보고, 테스트 코드 결과를 검증해보고, 배포가 가능한지도 확인해보면 좋습니다. 그런데 끊임없이 최신 코드들이 통합되는 상황에서 매번 테스트를 하기 어렵습니다.
지겹고 여러번 반복되는 일은 자동화하면 좋은데요. 자동화된 테스트를 로컬 환경에서도 작동할 수 있습니다. 하지만 로컬에서 테스트를 실행하면 로컬 환경의 리소스를 점유하게되는 단점이 있습니다. 그래서 대부분의 개발팀에서는 CI 서버에서 자동으로 테스트를 실행합니다.
자동으로 테스트를 실행한다라고 표현했는데 사실 CI 동작이 언제 실행될지는 정하기 나름입니다. 어떤 조건을 만족하면 동작을 실행할지(trigger), 선행 조건은 무엇인지(requires), 어떤 환경에서 실행할지(enviornment), 병렬로 실행할지(parallelism) 설정해둘 수 있습니다. 다양한 조건과 환경으로 테스트를 실행하고 결과를 리포트로 받아볼 수 있습니다.
CI에 비해서 CD라는 용어는 일상에서는 별로 들어보지 못했습니다. 평소에는 CI 서버라고 부르는게 전부였습니다. 용어를 자세히 찾아보니 CI 과정 이후 -> 버그테스트 -> staging 릴리즈 -> production 배포 하는 과정을 CD 라고 부릅니다. 앱 개발 입장에서보면 버그 테스트 -> 앱 릴리즈 -> AppStore 심사 등록이 CD에 포함될 것 같네요.
Continuous Delivery와 Continuous Deployment 앞 글자만 따면 동일하게 CD 이지만 차이점이 존재합니다. 한 걸음 나아가서 production 배포까지 자동화 하면 Continuous Deployment 라고 부를 수 있습니다.
CI/CD 플랫폼은 엄청나게 많습니다. 이 중에 제가 사용해본 플랫폼은 Jenkins, Github Action, CircleCI가 있습니다.
플랫폼 마다 인터페이스가 조금씩 다르고 가격 정책도 달라서 선택하기 전에 고민이 필요합니다. 플랫폼을 관리하는 방식에서 크게 다른 점이 있다면 On-premise 방식과 Cloud 방식이 있습니다.
On-premise는 직접 설치해서 관리하는 방식입니다. 대표적으로 On-premise 방식에서 사용되는 플랫폼은 Jenkins 입니다. 오픈소스이고 무료라서 소규모 팀에서 많은 선택을 받는 것 같습니다. 몇 가지 단점과 장점이 있습니다.
많은 곳에서 클라우드 방식을 사용하고 있습니다.
플랫폼 마다 용어가 조금씩 다를 수 있지만, 전체적인 흐름은 비슷합니다. CircleCI, Github Action를 예시로 어떤 개념들이 있는지 알아봅시다. 최근에 가장 많이 사용했던 플랫폼들이고 Docs도 잘 되어 있어서 이걸 예시로 들겠습니다.
CI/CD가 작동할 조건, 환경, 명령어들을 관리하는 파일입니다. CI/CD를 처음 세팅할 때 이 파일들을 생성하게 됩니다.
config.yml
파일 하나만 두고 관리합니다. 파일을 쪼갤 수 없는건 상당히 불편합니다..github/workflows/
폴더 아래에 yml 파일을 생성해서 workflow를 관리합니다.https://circleci.com/docs/concepts/#workflows
CircleCI:
version: 2.1
jobs:
build1:
docker:
- image: cimg/ruby:2.4-node
- image: cimg/postgres:9.4.12
steps:
- checkout
- save_cache: # Caches dependencies with a cache key
key: v1-repo-{{ .Environment.CIRCLE_SHA1 }}
paths:
- ~/circleci-demo-workflows
#...
workflows:
build_and_test: # name of your workflow
jobs:
- build1
- build2:
requires:
- build1 # wait for build1 job to complete successfully before starting
# see circleci.com/docs/workflows/ for more examples.
- build3:
requires:
- build1 # wait for build1 job to complete successfully before starting
# run build2 and build3 concurrently to save time.
Github Action:
name: learn-github-actions
run-name: ${{ github.actor }} is learning GitHub Actions
on: [push]
jobs:
check-bats-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '14'
- run: npm install -g bats
- run: bats -v
두 플랫폼 모두 workflows 라고 부르는 작업 단위가 있습니다. repository에 event가 발생하면 job들을 실행합니다. 순차적으로 실행할 수도 있고, 병렬로 실행할 수도 있습니다.
위에서 소개했던 YAML 파일에 어떻게 작동할지 정의합니다. 하나의 repository는 여러개의 workflow를 가질 수 있습니다.
github action 예시: event 발생 후 순차적으로 작업 실행합니다.
CircleCI 예시: event 발생 후 4개의 job은 build_config_builder, build_config_ref, build_api_docs, js_build 각자 실행시키고, 4개가 모두 완료되었을 때 build job을 실행시키는 예시입니다.
하나의 workflow 안에서 사용되는 steps의 모음 입니다. 각 step은 shell script 명령어가 될 수 있고, 아니면 action을 실행할 수 있습니다.
Job은 어떤 환경에서 실행될지 지정할 수 있습니다. 예를 들어 docker
, machine
, windows
, macos
이런 것들을 지정합니다.
CircleCI의 macOS 환경을 사용하면 xcode 버전을 지정할 수 있습니다. 여러 버전의 Xcode 위에서 테스트하거나 아카이빙할 수 있어서 좋습니다. https://circleci.com/docs/executor-intro/#macos
jobs:
build: # name of your job
macos: # executor type
xcode: 14.2.0
steps:
# Commands run in a macOS virtual machine environment
# with Xcode 14.2.0 installed
Action은 명령어 라이브러리 같은 느낌인데, 자주 사용되고 유용한 명령어들을 라이브러리 형태로 만들어둘 수 있습니다. Github Action을 만들고 싶다면 다음 문서를 참고하세요. https://docs.github.com/en/actions/creating-actions
CircleCI에서는 비슷한 역할을 하는 것을 Orbs라고 부릅니다.
name: learn-github-actions # workflow 이름 (class 이름 같은 느낌)
run-name: ${{ github.actor }} is learning GitHub Actions # 실행되는 workflow 이름 (instance 이름 같은 느낌)
on: [push] # event 트리거 조건 지정
jobs:
check-bats-version: # job 이름
runs-on: ubuntu-latest # runner 지정. 작동할 환경 세팅
steps:
- uses: actions/checkout@v3 # action 사용 예시
- uses: actions/setup-node@v3
with:
node-version: '14' # action에 파라미터 넣을 수 있음
- run: npm install -g bats # shell script 실행
- run: bats -v
version: 2.1 # circleci yml 버전 이걸 바꿀 일은 아직 없었습니다
jobs: # 여기서 job들을 선언해둡니다
build1: # job 이름을 붙여줍니다
docker:
- image: cimg/ruby:2.4-node # 어떤 도커 이미지 위에서 실행시킬지 지정
- image: cimg/postgres:9.4.12
steps:
- checkout # 기본 repo checkout 하는 예약어 입니다.
- save_cache: # 캐시도 사용할 수 있습니다. 캐시 폴더를 수동으로 초기화할 방법은 없다고 알고 있습니다.
key: v1-repo-{{ .Environment.CIRCLE_SHA1 }}
paths:
- ~/circleci-demo-workflows
build2:
docker:
- image: cimg/ruby:2.4-node
- image: cimg/postgres:9.4.12
steps:
- restore_cache: # Restores the cached dependency.
key: v1-repo-{{ .Environment.CIRCLE_SHA1 }}
- run:
name: Running tests
command: make test
#...
workflows: # workflow 들을 정의합니다
build_and_test: # workflow 이름 정의
jobs: # 어떤 job을 실행시킬지 정의
- build1
- build2:
requires:
- build1 # build1 끝나고 build2 실행되게 할 때 사용
# see circleci.com/docs/workflows/ for more examples.
https://circleci.com/docs/concepts/#workflows
한 번에 2개의 플랫폼을 섞어서 설명했는데, 이해하는데 도움이 되면 좋겠습니다. 기본 개념들만 설명해뒀는데 자동화를 구현하다 보면 Secret 값 관리, 트리거 조건 관리 같은 것들도 꽤나 많이 들여다보게 되더라구요. 적용하면서 해당 플랫폼의 Docs 펼쳐두고 적용하는게 좋을 것 같습니다. 그리고 각 플랫폼을 위한 VSCode Extension 들이 있으니 설치해서 config 파일들을 수정하는 것을 추천합니다. YAML 파일을 다루다보니 들여쓰기가 맞아야하고, 플랫폼에서 정의해둔 예약어도 있어서 기본 텍스트 에디터로 작업하면 CI에서 에러를 출력하는 경우가 많았습니다. 결국 직접 부딪혀보면서 실행해보는게 제일 좋은 것 같습니다. 모두들 화이팅입니다 🙌
큰 도움이 되었습니다, 감사합니다.