Back to Notes

Notes

Continuous Integration 운영 원칙

Continuous Integration을 여러 개발자의 code change를 자주 shared repository에 통합하고 자동 build/test로 검증하는 DevOps practice로 정리한다.

Published
Updated
Area
Cloud Infrastructure
Type
concept
Series
DevOps Explained
Category
Notes
Continuous IntegrationCI/CDDevOpsPipelineAutomated TestingBuildGitArtifact

개요

Continuous Integration은 여러 개발자의 code change를 shared repository에 자주 통합하고, 그때마다 자동 build와 test를 실행해 integration 문제를 빠르게 발견하는 DevOps practice다.

짧게 정리하면 다음과 같다.

Continuous Integration:
  개발자가 code change를 자주 shared repository에 merge하고,
  merge 또는 pull request 시점마다
  자동 build와 test를 실행해
  문제를 빠르게 발견하는 방식

CI의 본질은 단순히 “자동 테스트를 돌린다”가 아니다. 더 정확히는 다음 세 가지가 결합된 방식이다.

1. 자주 통합한다.
2. 통합할 때마다 자동으로 build/test한다.
3. 문제가 생기면 빠르게 feedback을 준다.

즉, CI는 작은 변경을 자주 통합하고, 통합 시점마다 자동 검증을 수행해 main branch의 신뢰성을 유지하는 방식이다.


CI가 해결하려는 문제: Integration Hell

CI가 등장한 가장 큰 이유는 integration hell 또는 merge hell 문제 때문이다.

전통적인 개발 방식에서는 개발자가 각자 긴 시간 동안 branch나 local copy에서 작업한 뒤, release 직전에 한꺼번에 merge하는 경우가 많았다.

전통적 방식:
  개발자 A가 feature A를 2주 동안 작업
  개발자 B가 feature B를 2주 동안 작업
  개발자 C가 feature C를 2주 동안 작업

release 직전:
  모두 main branch에 merge
  conflict 발생
  test failure 발생
  build failure 발생
  원인 파악 어려움

이 방식의 문제는 변경사항이 너무 오래 분리되어 있다는 점이다.

문제:
  변경 단위가 큼
  merge 시점이 늦음
  conflict가 복잡함
  bug가 언제 들어왔는지 추적하기 어려움
  release 직전에 품질 문제가 폭발함

CI는 이 문제를 정반대로 해결한다.

CI 방식:
  작은 변경을 자주 merge
  merge할 때마다 자동 build/test
  문제가 생기면 즉시 feedback
  원인 commit을 빠르게 찾음

핵심은 문제를 늦게 크게 발견하는 대신, 일찍 작게 발견하는 것이다.


CI의 기본 흐름

CI의 기본 흐름은 다음과 같다.

1. 개발자가 code 변경
2. local에서 commit
3. shared repository에 push 또는 pull request 생성
4. CI system이 변경 감지
5. source code checkout
6. dependency install
7. build 또는 compile
8. unit test 실행
9. static analysis / lint 실행
10. integration test 일부 실행
11. 결과를 개발자에게 feedback
12. 성공하면 merge 가능 또는 artifact 생성

이를 그림처럼 표현하면 다음과 같다.

Developer Commit
  -> Git Repository
  -> CI Trigger
  -> Build
  -> Test
  -> Quality Check
  -> Feedback
  -> Merge / Artifact

CI에서 중요한 것은 이 흐름이 사람의 수동 판단 전에 자동으로 반복 실행된다는 점이다.

좋은 CI:
  code가 들어올 때마다 자동으로 검증
  실패 원인을 빠르게 보여줌
  main branch가 깨지는 것을 막음

나쁜 CI:
  pipeline은 있지만 실패를 무시
  main branch가 자주 broken 상태
  test 결과를 신뢰하지 않음

CI는 CI/CD Pipeline의 첫 번째 단계다

CI/CD에서 CI는 Continuous Integration, CD는 보통 Continuous Delivery 또는 Continuous Deployment를 의미한다.

CI는 주로 다음을 다룬다.

CI:
  code를 통합한다.
  build한다.
  test한다.
  artifact로 만들 준비를 한다.

CD는 그 다음 단계다.

Continuous Delivery:
  검증된 artifact를 언제든 배포 가능한 상태로 준비한다.
  production 배포에는 수동 승인 단계가 있을 수 있다.

Continuous Deployment:
  검증된 변경사항을 production까지 자동 배포한다.

정리하면 다음과 같다.

구분핵심 질문주요 작업
CI변경사항이 기존 code와 잘 통합되는가?build, test, lint, static analysis
Continuous Delivery이 변경사항을 배포 가능한 상태로 만들었는가?packaging, staging deploy, approval 준비
Continuous Deployment검증된 변경을 production까지 자동 배포할 것인가?automated production deployment

CI가 제대로 되어 있지 않으면 CD는 위험해진다.

CI 없이 CD만 있음:
  검증되지 않은 변경을 빠르게 배포할 수 있음

좋은 CI + CD:
  작은 변경을 자주 검증하고,
  검증된 변경만 delivery/deployment로 넘김

CI는 CD, GitOps, DevSecOps의 기반이다. CI가 약하면 이후 delivery와 deployment automation도 신뢰하기 어렵다.


CI의 핵심 원칙

CI는 도구가 아니라 practice다. Jenkins, GitHub Actions, GitLab CI, Tekton, CircleCI 같은 도구는 CI를 구현하는 수단이다.

CI의 핵심 원칙은 다음과 같다.

1. 모든 code는 shared repository에서 관리한다.
2. 개발자는 변경사항을 자주 integrate한다.
3. 통합 시점마다 자동 build가 실행된다.
4. 자동 test가 빠르게 feedback을 준다.
5. main branch는 항상 deployable 또는 최소한 buildable 상태여야 한다.
6. 실패한 build는 즉시 수정한다.
7. 변경 단위는 작게 유지한다.

이 중 가장 중요한 것은 main branch의 신뢰성이다.

좋은 CI:
  main branch가 항상 green에 가깝다.
  실패하면 빠르게 고친다.
  실패 원인이 작고 명확하다.

나쁜 CI:
  main branch가 자주 broken 상태로 방치된다.
  test failure를 무시한다.
  pipeline은 있지만 신뢰하지 않는다.

CI pipeline이 있어도 팀이 실패를 무시하면 CI는 의미가 약해진다.


CI에서 Build가 의미하는 것

CI에서 build는 단순히 compile만 의미하지 않는다.

언어와 stack에 따라 build는 다음을 포함할 수 있다.

Java:
  compile
  unit test
  JAR/WAR 생성

Node.js:
  dependency install
  TypeScript compile
  bundle
  test

Python:
  dependency install
  lint
  test
  wheel/package 생성

Go:
  go test
  go build
  binary 생성

Container-based app:
  Dockerfile build
  image 생성

CI build의 목적은 다음이다.

이 codebase가 깨지지 않고
현재 dependency와 함께
실행 가능한 artifact로 만들어질 수 있는가?

Cloud-native 환경에서는 build 결과가 container image가 되는 경우가 많다.

source code
  -> CI build
  -> container image
  -> registry push

하지만 순수 CI 단계에서는 반드시 production 배포까지 가지 않아도 된다. CI의 핵심은 통합된 code가 build/test를 통과하는지 확인하는 것이다.


CI에서 Test가 중요한 이유

CI에서 test는 가장 중요한 feedback 장치다.

Code change
  -> 자동 test
  -> 실패 시 즉시 feedback
  -> 개발자가 빠르게 수정

CI에서 자주 사용하는 test 종류는 다음과 같다.

테스트목적CI에서의 위치
Unit test작은 함수/모듈 검증거의 모든 commit마다
Integration testDB, API, 외부 service 연동 검증PR 또는 merge 전
Static analysiscode smell, type error, unsafe pattern 탐지빠른 feedback
Lint/formatcoding convention 유지빠른 feedback
Security scansecret, dependency, SAST 일부 검사DevSecOps와 결합
Container testimage build와 startup 검증cloud-native CI
Contract testservice 간 API 계약 검증microservices 환경

CI에서는 test 속도도 중요하다.

너무 느린 CI:
  개발자가 feedback을 늦게 받음
  pipeline을 우회하려고 함
  작은 변경도 부담이 됨

좋은 CI:
  빠른 test는 PR마다 실행
  느린 test는 stage를 나눔
  중요한 regression은 반드시 차단

현실적인 구조는 다음과 같다.

PR마다:
  lint
  unit test
  type check
  빠른 security scan

merge 후:
  integration test
  image build
  artifact scan

nightly:
  full regression
  E2E test
  deep security scan

CI의 목적은 모든 검사를 한 번에 무겁게 실행하는 것이 아니라, 변경 시점에 적절한 수준의 빠른 feedback을 제공하는 것이다.


CI와 Branching Strategy

CI가 잘 작동하려면 branching strategy가 중요하다.

CI의 기본 철학은 변경사항을 오래 분리하지 않는 것이다.

대표적인 방식은 다음과 같다.

Trunk-based development:
  main/trunk에 자주 작은 변경을 통합
  feature branch는 짧게 유지
  feature flag로 미완성 기능을 숨김

Git flow:
  develop, release, hotfix branch 등 여러 장기 branch 사용
  release 관리에는 유용하지만 branch가 길어지면 integration cost 증가

CI 관점에서는 짧은 branch가 유리하다.

짧은 branch:
  conflict가 작음
  test feedback이 빠름
  main과 차이가 적음

긴 branch:
  conflict가 커짐
  통합 시점에 문제 폭발
  feature 간 상호작용을 늦게 발견

CI의 목표는 “각자 오래 작업한 뒤 마지막에 통합”이 아니라 “계속 통합하면서 계속 검증”이다.


CI와 Pull Request

현대적인 CI는 보통 pull request 또는 merge request와 결합된다.

Developer:
  feature branch push
  pull request 생성

CI:
  build
  test
  lint
  scan
  preview build

Reviewer:
  code review
  CI result 확인
  merge 승인

좋은 PR 기반 CI는 다음 조건을 만족해야 한다.

- PR마다 자동 pipeline 실행
- 실패한 check가 있으면 merge 차단
- test 결과가 PR에 직접 표시
- code coverage 변화 확인 가능
- security issue가 PR comment로 제공
- main branch merge 전 conflict 확인

이 구조에서 CI는 reviewer의 부담을 줄여준다.

Reviewer가 직접 확인할 것:
  설계
  가독성
  business logic
  maintainability

CI가 자동 확인할 것:
  build 가능 여부
  test 통과 여부
  lint/format
  basic security
  dependency issue

CI는 code review를 대체하지 않는다. 대신 사람이 할 필요 없는 반복 검증을 자동화한다.


CI와 Artifact

CI의 결과물은 단순히 “성공/실패”만이 아니다. 많은 경우 artifact가 생성된다.

예시는 다음과 같다.

Java:
  .jar
  .war

Node:
  bundled static assets
  npm package

Python:
  wheel
  source distribution

Go:
  binary

Cloud-native:
  container image

Cloud-native 환경에서는 다음 흐름이 자연스럽다.

CI:
  code checkout
  test
  image build
  image scan
  registry push

CD/GitOps:
  image tag를 manifest에 반영
  Argo CD/Flux가 cluster에 sync

여기서 중요한 것은 artifact가 immutable해야 한다는 것이다.

나쁜 예:
  my-app:latest

좋은 예:
  my-app:a1b2c3d
  my-app@sha256:...

CI에서 생성된 artifact와 source commit이 명확히 연결되어야 한다.

source commit:
  a1b2c3d

image:
  registry.example.com/my-app:a1b2c3d

GitOps repo:
  image tag: a1b2c3d

이 연결이 명확해야 rollback, audit, incident analysis가 쉬워진다.


CI Pipeline 예시

가장 단순한 CI pipeline은 다음과 같다.

trigger:
  push 또는 pull request

steps:
  checkout
  install dependencies
  lint
  unit test
  build
  upload artifact

GitHub Actions 스타일로 보면 개념적으로 다음과 같다.

name: ci

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Test
        run: npm test

      - name: Build
        run: npm run build

Cloud-native app이라면 image build까지 들어갈 수 있다.

name: ci

on:
  push:
    branches:
      - main

jobs:
  container-build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Test
        run: npm test

      - name: Build image
        run: docker build -t registry.example.com/my-app:${{ github.sha }} .

      - name: Push image
        run: docker push registry.example.com/my-app:${{ github.sha }}

다만 실제 운영에서는 registry credential, secret masking, image scan, artifact signing, branch protection을 함께 고려해야 한다.


CI와 DevSecOps

CI는 DevSecOps와도 강하게 연결된다.

DevSecOps 관점에서 CI는 security feedback을 빠르게 제공하는 위치다.

CI에서 수행할 수 있는 security check:
  secret scanning
  SAST
  dependency scan / SCA
  license scan
  container image scan
  Dockerfile lint
  IaC scan
  Kubernetes manifest policy check

중요한 것은 모든 security scan을 무조건 모든 commit마다 full로 돌리는 것이 아니다.

좋은 구조:
  PR마다 빠른 scan
  merge 후 image/dependency scan
  nightly deep scan
  release 전 full scan

CI에 security check를 넣을 때는 false positive와 pipeline 속도를 고려해야 한다.

나쁜 CI security:
  결과가 너무 많음
  무엇을 고쳐야 할지 모름
  pipeline이 너무 느림
  개발자가 우회함

좋은 CI security:
  PR에서 바로 feedback
  severity와 fix suggestion 제공
  critical issue는 blocking
  low-risk issue는 backlog로 관리

CI는 보안을 release 직전 gate가 아니라 개발 흐름 안의 빠른 feedback으로 바꿔준다.


CI와 GitOps의 관계

CI와 GitOps는 역할이 다르다.

CI:
  code를 검증하고 artifact를 만든다.

GitOps:
  배포 desired state를 Git에 저장하고,
  controller가 cluster 상태를 맞춘다.

둘을 연결하면 다음 workflow가 된다.

1. Developer가 code push
2. CI가 test/build/image scan 수행
3. CI가 container image를 registry에 push
4. CI가 GitOps repo의 image tag를 update
5. Argo CD 또는 Flux가 GitOps repo 변경 감지
6. cluster 상태를 desired state와 sync

즉, CI가 production cluster에 직접 kubectl apply를 하지 않아도 된다.

CI:
  artifact 생산

GitOps repo:
  deployment desired state

Argo CD:
  cluster reconciliation

이 구조는 DevOps, DevSecOps, GitOps를 자연스럽게 연결한다.


CI에서 자주 발생하는 문제

Pipeline이 너무 느림

CI가 너무 느리면 개발자는 feedback을 늦게 받고, 결국 CI를 부담으로 느낀다.

원인:
  모든 test를 매번 실행
  dependency cache 없음
  병렬화 없음
  slow integration test가 PR마다 실행
  container image build가 비효율적

개선 방향은 다음과 같다.

- dependency cache
- test split
- parallel job
- 변경 파일 기반 selective test
- unit/integration/E2E test stage 분리
- Docker layer cache

Flaky Test

Flaky test는 code가 잘못되지 않았는데도 가끔 실패하는 test다.

문제:
  CI 신뢰도 하락
  개발자가 실패를 무시
  실제 bug와 noise 구분 어려움

원인은 다음과 같다.

- 시간 의존성
- network 의존성
- 외부 API 의존성
- test 간 shared state
- race condition
- resource 부족

해결 방향은 다음과 같다.

- test isolation
- mock/stub 사용
- deterministic test data
- retry는 제한적으로 사용
- flaky test owner 지정
- flaky test quarantine 후 수정

Main Branch가 자주 깨짐

CI가 있어도 main branch가 자주 깨진다면 branch protection과 merge policy가 필요하다.

필요한 것:
  required checks
  PR review
  merge queue
  protected branch
  실패한 pipeline merge 금지

Test가 부족함

Pipeline은 green인데 production에서 장애가 발생할 수 있다.

원인:
  unit test만 있음
  integration test 부족
  contract test 없음
  migration test 없음
  configuration test 없음

CI는 test가 있어야 의미가 있다. CI 도구만 도입한다고 품질이 자동으로 좋아지지는 않는다.


CI와 Kubernetes/Homelab 관점

k3s, Nexus, Argo CD, Tekton 같은 환경에서는 CI를 다음 흐름으로 적용할 수 있다.

app repo:
  source code
  Dockerfile
  tests

CI:
  test
  build image
  image scan
  push to Nexus registry

GitOps repo:
  Helm values 또는 Kustomize overlay
  image tag update

Argo CD:
  k3s cluster에 sync

Runtime:
  Traefik, Longhorn, MetalLB, observability stack

예시 흐름은 다음과 같다.

1. app repo에 commit push
2. CI pipeline 실행
3. unit test와 lint 실행
4. Docker image build
5. image를 Nexus registry에 push
6. GitOps repo의 image tag update
7. Argo CD가 sync
8. k3s에서 rollout
9. Grafana/Loki로 runtime 확인

Homelab에서 CI를 붙일 때 주의할 점은 다음이다.

- CI runner가 homelab resource를 과도하게 사용하지 않는가?
- Nexus registry credential을 안전하게 관리하는가?
- image tag를 latest로 쓰지 않는가?
- Longhorn PVC를 쓰는 service와 CI build workload가 resource 경쟁하지 않는가?
- internal IP, password, token이 pipeline log에 노출되지 않는가?

작게 시작한다면 다음 정도가 현실적이다.

1. app repo에 GitHub Actions 또는 Tekton pipeline 추가
2. unit test/lint 실행
3. Docker image build
4. Nexus registry push
5. image tag를 commit SHA로 관리
6. GitOps repo update는 수동 또는 PR로 시작
7. 이후 Argo CD auto-sync 검토

CI가 잘 되어 있는지 확인하는 기준

CI가 잘 작동하는지는 단순히 pipeline이 존재하는지로 판단하면 안 된다.

검증 기준은 다음과 같다.

검증 포인트확인 질문
Integration frequency개발자가 자주 merge하는가?
Pipeline speedfeedback이 충분히 빠른가?
Build reliabilitymain branch가 자주 깨지지 않는가?
Test quality실제 regression을 잡는 test가 있는가?
Flaky test실패가 noise로 무시되지 않는가?
Branch protection실패한 pipeline의 merge가 차단되는가?
Artifact traceabilityartifact와 source commit이 연결되는가?
Security checksecret/dependency/image scan이 포함되는가?
Developer feedback실패 원인을 개발자가 빠르게 이해할 수 있는가?
Reproducibilitylocal과 CI 결과가 크게 다르지 않은가?
Scalability팀과 repo가 커져도 pipeline이 유지 가능한가?

좋은 CI는 “초록불이 많이 보이는 dashboard”가 아니라, 작은 변경을 빠르게 통합하고 신뢰할 수 있는 feedback을 주는 system이다.


자칫 실수하기 쉬운 부분

CI를 단순 자동 빌드로만 이해하는 경우

CI는 자동 build만 의미하지 않는다.

자동 build:
  code를 compile한다.

CI:
  자주 통합하고,
  자동 build/test로 통합 문제를 빠르게 발견한다.

핵심은 “continuous integration”이다. 통합이 자주 일어나지 않으면 build tool이 있어도 CI 효과는 제한된다.

Pipeline은 있는데 실패를 무시하는 경우

CI는 실패를 신뢰하고 즉시 고칠 때 의미가 있다.

나쁜 상태:
  pipeline failed지만 merge
  flaky test라며 무시
  main branch가 broken 상태로 방치

좋은 상태:
  실패한 check는 merge 차단
  main branch green 유지
  flaky test는 따로 관리하고 수정

너무 많은 일을 PR마다 실행하는 경우

모든 test와 scan을 PR마다 실행하면 feedback이 느려질 수 있다.

권장:
  빠른 test는 PR마다
  느린 test는 merge 후 또는 nightly
  release 전 full validation

Integration test 없이 Unit test만 믿는 경우

Unit test가 통과해도 service 간 연동, DB migration, configuration 문제는 남을 수 있다.

필요한 추가 검증:
  integration test
  contract test
  migration test
  container startup test
  manifest validation

latest tag로 artifact를 관리하는 경우

CI가 image를 build하더라도 latest만 쓰면 추적성이 약해진다.

비추천:
  my-app:latest

권장:
  my-app:a1b2c3d
  my-app@sha256:...

CI credential을 과도하게 주는 경우

CI는 source code, registry, GitOps repo, secret에 접근할 수 있다. 따라서 최소 권한이 중요하다.

주의:
  registry push token
  GitOps repo write token
  cloud credential
  kubeconfig
  deployment token

Credential은 필요한 범위로 제한하고, pipeline log에 secret이 노출되지 않도록 해야 한다.


Mental Model

Continuous Integration은 다음 mental model로 이해할 수 있다.

작은 변경:
  feature branch 또는 local commit

자주 통합:
  shared repository 또는 main branch에 빠르게 merge

자동 검증:
  build
  test
  lint
  static analysis
  security scan

빠른 feedback:
  PR status
  pipeline log
  failure report

신뢰 가능한 main branch:
  merge 가능한 code만 통과
  broken state를 빠르게 복구

더 짧게 정리하면 다음과 같다.

CI는 code를 자주 합치고,
합칠 때마다 자동으로 검증하는 방식이다.

정리

Continuous Integration은 여러 개발자의 code change를 shared repository에 자주 통합하고, 그때마다 자동 build와 test를 실행해 integration 문제를 빠르게 발견함으로써 main branch를 안정적으로 유지하는 DevOps practice다.

핵심은 다음과 같다.

  • CI는 자동 build만 의미하지 않는다.
  • CI의 핵심은 작은 변경을 자주 통합하고 자동으로 검증하는 것이다.
  • CI는 integration hell을 줄이기 위한 practice다.
  • CI는 CI/CD pipeline의 첫 번째 단계다.
  • CI가 약하면 Continuous Delivery와 Continuous Deployment도 위험해진다.
  • Build는 언어별 compile/package뿐 아니라 container image build까지 포함할 수 있다.
  • Test는 unit test, integration test, static analysis, lint, security scan 등으로 나뉜다.
  • CI에서는 빠른 feedback이 중요하므로 test stage를 적절히 나눠야 한다.
  • Branch는 짧게 유지할수록 CI 효과가 커진다.
  • Pull request와 CI를 결합하면 merge 전 자동 검증이 가능하다.
  • CI artifact는 source commit과 명확히 연결되어야 한다.
  • latest tag보다 commit SHA나 digest 기반 image tag가 더 안전하다.
  • CI는 DevSecOps, GitOps와 자연스럽게 연결된다.
  • CI pipeline이 느리거나 flaky test가 많으면 개발자가 pipeline을 신뢰하지 않게 된다.
  • Homelab/k3s 환경에서도 CI는 image build, Nexus push, GitOps repo update, Argo CD sync 흐름의 출발점이 된다.

운영 관점에서는 이렇게 볼 수 있다.

CI:
  작은 변경
  잦은 통합
  자동 build
  자동 test
  빠른 feedback
  안정적인 main branch