재생성되는 Go 백엔드를 위한 CI/CD: 빌드, 테스트, 마이그레이션, 배포
Go 백엔드를 위한 CI/CD: 빌드, 테스트, 마이그레이션, Kubernetes 또는 VM에 안전하게 배포하는 실용적 파이프라인 단계와 예측 가능한 환경 설정.

Why CI/CD matters for Go backends you regenerate
수동 배포는 지루하고 반복적인 방식으로 실패합니다. 누군가 로컬에서 다른 Go 버전으로 빌드하거나 환경 변수를 빼먹고 마이그레이션을 건너뛰거나 잘못된 서비스를 재시작합니다. 릴리스는 “내 환경에서는 작동하는데” 프로덕션에서는 동작하지 않고, 사용자가 문제를 겪고 나서야 알게 됩니다.
코드를 생성(재생성)한다고 해도 릴리스 규율이 필요 없어지지는 않습니다. 요구사항을 업데이트한 뒤 백엔드를 재생성하면, 수작업으로 코드를 만지지 않아도 새로운 엔드포인트나 데이터 형태, 의존성이 생길 수 있습니다. 바로 그때 파이프라인이 안전장치처럼 작동해야 합니다: 모든 변경은 매번 동일한 검사 과정을 거칩니다.
예측 가능한 환경은 빌드와 배포 단계가 이름 붙일 수 있고 반복 가능한 조건에서 실행된다는 뜻입니다. 몇 가지 규칙만 지켜도 대부분 해결됩니다:
- 버전 고정(Go 툴체인, 베이스 이미지, OS 패키지).
- 한 번 빌드해서 동일한 아티팩트를 모든 곳에 배포.
- 설정은 바이너리 밖에 두기(환경 변수 또는 환경별 설정 파일).
- 모든 환경에서 같은 마이그레이션 도구와 프로세스 사용.
- 롤백을 현실적으로 만들기: 이전 아티팩트를 보관하고 데이터베이스에 무슨 일이 일어나는지 알기.
Go 백엔드를 위한 CI/CD의 목적은 자동화 자체가 아닙니다. 재현 가능한 릴리스로 스트레스를 줄이는 것입니다: 재생성하고 파이프라인을 실행하면, 나오는 결과물을 신뢰하고 배포할 수 있게 되는 겁니다.
AppMaster처럼 Go 백엔드를 생성하는 도구를 쓴다면 이 점은 더 중요합니다. 재생성은 편리한 기능이지만 변경에서 프로덕션까지 가는 경로가 일관되고 테스트되며 예측 가능할 때만 안전하게 느껴집니다.
Pick your runtime and define “predictable” upfront
“예측 가능”하다는 것은 같은 입력이 어디서 실행하든 같은 결과를 만든다는 뜻입니다. Go 백엔드를 위한 CI/CD에서는 개발, 스테이징, 프로덕션 사이에 무엇을 동일하게 유지할지 미리 합의하는 것에서 시작합니다.
보통 비타협 항목은 Go 버전, 베이스 OS 이미지, 빌드 플래그, 설정이 로드되는 방식입니다. 이 중 하나라도 환경마다 달라지면 TLS 동작 차이, 누락된 시스템 패키지, 프로덕션에서만 드러나는 버그 같은 깜짝 문제가 생깁니다.
환경 간 드리프트는 보통 다음 지점에서 드러납니다:
- OS와 시스템 라이브러리(다른 배포판 버전, 누락된 CA 인증서, 시간대 차이)
- 설정 값(기능 플래그, 타임아웃, 허용된 오리진, 외부 서비스 주소)
- 데이터베이스 형태와 설정(마이그레이션, 확장 기능, 정렬법, 연결 제한)
- 시크릿 처리(저장 위치, 회전 방식, 누가 읽을 수 있는지)
- 네트워크 가정(DNS, 방화벽, 서비스 디스커버리)
Kubernetes와 VM 중 무엇을 선택할지는 “무엇이 더 좋다”가 아니라 팀이 차분하게 운영할 수 있는지에 관한 문제입니다.
Kubernetes는 오토스케일링, 롤링 업데이트, 여러 서비스를 표준화된 방식으로 운영해야 할 때 적합합니다. 파드가 동일한 이미지에서 실행되므로 일관성 유지에도 도움이 됩니다. VM은 서비스가 하나 또는 소수이고, 작은 팀이며 구성 요소를 적게 유지하고 싶을 때 맞을 수 있습니다.
런타임이 달라도 아티팩트와 그 계약을 표준화하면 파이프라인을 동일하게 유지할 수 있습니다. 예를 들어: CI에서 항상 동일한 컨테이너 이미지를 빌드하고 같은 테스트 단계를 실행하며 같은 마이그레이션 번들을 공개합니다. 그러면 배포 단계만 달라집니다: Kubernetes는 새 이미지 태그를 적용하고, VM은 이미지를 풀링해 서비스를 재시작합니다.
실용적 예: 팀이 AppMaster에서 Go 백엔드를 재생성하고 스테이징은 Kubernetes에, 프로덕션은 당분간 VM에 배포한다고 합시다. 둘 다 정확히 같은 이미지를 풀링하고 같은 종류의 시크릿 스토어에서 설정을 로드하면 “다른 런타임”은 버그의 원인이 아니라 배포의 세부사항이 됩니다. AppMaster(appmaster.io)를 사용하면 관리형 클라우드 대상으로 배포하거나 소스 코드를 내보내 동일한 파이프라인을 자체 인프라에서 실행할 수 있어 이 모델에 잘 맞습니다.
A simple pipeline map you can explain to anyone
예측 가능한 파이프라인은 설명하기 쉽습니다: 코드를 확인하고, 빌드하고, 작동을 증명하고, 테스트한 정확한 것을 배포하고, 매번 같은 방식으로 배포합니다. 이 명료성은 백엔드가 재생성되는 경우 더 중요합니다(예: AppMaster로부터 재생성) — 변경이 많은 파일에 영향을 줄 수 있으므로 빠르고 일관된 피드백이 필요합니다.
단순한 Go 백엔드용 CI/CD 흐름은 다음과 같습니다:
- 린트와 기본 검사
- 빌드
- 단위 테스트
- 통합 체크
- 패키징(불변 아티팩트)
- 마이그레이션(제어된 단계)
- 배포
실패는 초기에 멈추도록 구조화하세요. 린트가 실패하면 다른 단계가 실행되지 않아야 합니다. 빌드가 실패하면 통합 검사용 데이터베이스를 올리는 데 시간을 낭비하면 안 됩니다. 이렇게 하면 비용을 줄이고 파이프라인이 빠르게 느껴집니다.
모든 단계가 모든 커밋에서 실행될 필요는 없습니다. 일반적인 분할:
- 모든 커밋/PR: 린트, 빌드, 단위 테스트
- main 브랜치: 통합 검사, 패키징
- 릴리스 태그: 마이그레이션, 배포
어떤 것을 아티팩트로 보관할지 결정하세요. 보통 컴파일된 바이너리 또는 컨테이너 이미지(배포하는 대상), 마이그레이션 로그, 테스트 리포트 등을 보관합니다. 이렇게 하면 롤백과 감사 시 정확히 무엇이 테스트되고 승격되었는지 지목하기 쉽습니다.
Step by step: build stage that is stable and repeatable
빌드 단계는 한 가지 질문에 답해야 합니다: 오늘, 내일, 다른 러너에서 같은 바이너리를 만들 수 있는가? 그렇지 않다면 이후 단계(테스트, 마이그레이션, 배포)는 신뢰하기 어려워집니다.
환경을 고정하는 것부터 시작하세요. 고정된 Go 버전(예: 1.22.x)과 고정된 러너 이미지(리눅스 배포판 및 패키지 버전)를 사용하세요. latest 태그를 피하세요. libc, Git 또는 Go 툴체인의 작은 변화가 “내 머신에서는 된다”는 실패를 만들 수 있으며 디버그가 어렵습니다.
모듈 캐시는 속도를 높이지만 진실의 근원으로 취급하면 안 됩니다. Go 빌드 캐시와 모듈 다운로드 캐시를 캐시하되, go.sum으로 키를 걸어 의존성이 바뀌면 깔끔히 다시 다운로드되게 하세요.
컴파일 전에 빠른 게이트를 추가하세요. 개발자가 우회하지 않도록 빠르고 경량이어야 합니다. 일반적으로 gofmt 검사, go vet, 그리고(빠르다면) staticcheck를 포함합니다. 또한 누락되었거나 오래된 생성 파일에서 실패하게 하세요 — 재생성된 코드베이스에서 흔한 문제입니다.
재현 가능한 방식으로 컴파일하고 버전 정보를 포함하세요. -trimpath 같은 플래그를 사용하고 -ldflags로 커밋 SHA와 빌드 시간을 주입할 수 있습니다. 서비스당 하나의 명명된 아티팩트를 생성하세요. 그러면 Kubernetes나 VM에서 무엇이 실행되는지 추적하기 쉬워집니다. 특히 백엔드를 재생성하는 경우 유용합니다.
Step by step: tests that catch issues before deploy
테스트는 매번 같은 방식으로 실행될 때만 도움이 됩니다. 먼저 빠른 피드백을 목표로 하고, 그 다음 예측 가능한 시간 내에 끝나는 더 깊은 검사를 추가하세요.
모든 커밋에서 단위 테스트를 실행하세요. 멈춰버리는 테스트를 방지하려면 패키지별 타임아웃과 전체 잡 타임아웃을 설정하세요. 팀에 따라 충분한 커버리지 기준을 정하세요. 커버리지는 트로피가 아니라 품질 유지를 위한 최소 기준입니다.
안정적인 테스트 단계에 포함되는 항목:
go test ./...를 패키지별 타임아웃과 전역 잡 타임아웃으로 실행- 타임아웃에 걸린 테스트는 플라키가 아니라 실제 버그로 간주
- 중요한 패키지(auth, billing, permissions)에 대한 커버리지 기대치 설정
- 동시성 관련 코드(큐, 캐시, 팬아웃 작업자)에 대해 레이스 디텍터 사용
레이스 디텍터는 유용하지만 빌드를 크게 느리게 할 수 있습니다. 타협 방법으로는 풀 리퀘스트와 야간 빌드에서만 실행하거나 선택된 패키지에서만 실행하는 것이 있습니다.
플래키 테스트는 빌드를 실패시키세요. 격리해야 하는 테스트가 있다면 별도 잡으로 옮기고 계속 레드로 리포트되게 하며, 소유자와 기한을 요구하세요.
디버깅을 위해 테스트 출력을 저장하세요. 원시 로그와 간단한 리포트(성공/실패, 소요시간, 느린 테스트 목록)를 보관하면 재생성된 변경이 많은 경우 회귀를 찾기 쉽습니다.
Integration checks with real dependencies, without slow builds
단위 테스트는 코드가 격리된 상태에서 작동하는지 알려줍니다. 통합 검사는 서비스가 실제로 부트하고, 실제 서비스에 연결하며, 실제 요청을 처리할 때 올바르게 동작하는지를 확인합니다. 이것이 모든 것이 연결되었을 때만 드러나는 문제를 잡아주는 안전망입니다.
필요한 경우 일시적인 의존성을 띄우세요. 테스트 전용 PostgreSQL(및 Redis 사용 시 Redis)을 잠깐 띄우는 정도면 대개 충분합니다. 버전은 프로덕션과 가깝게 유지하되 모든 프로덕션 세부사항을 복사하려 들지는 마세요.
좋은 통합 단계는 의도적으로 작게 구성합니다:
- 프로덕션과 유사한 환경 변수로 서비스를 시작(단, 테스트용 시크릿 사용)
- 헬스 체크 검증(예:
/health가 200 반환) - 핵심 엔드포인트 한두 개 호출해 상태 코드와 응답 형태 확인
- PostgreSQL(및 필요 시 Redis)에 연결 가능한지 확인
API 계약 검증은 깨졌을 때 가장 피해가 큰 엔드포인트에 집중하세요. 전체 E2E가 아니라 몇 가지 요청/응답 규칙이면 충분합니다: 필수 필드 누락 시 400, 인증 필요시 401, 정상 흐름은 200과 예상 JSON 키 반환 등.
통합 테스트를 자주 돌릴 수 있게 하려면 범위를 제한하고 시간을 제어하세요. 작은 데이터셋을 가진 하나의 데이터베이스, 몇 개의 요청, 부팅이 멈추면 몇 초 내로 실패하도록 하는 하드 타임아웃을 권장합니다.
AppMaster로 백엔드를 재생성하는 경우 이런 검증은 더 큰 의미를 가집니다. 재생성된 서비스가 여전히 정상적으로 시작하고 웹/모바일 앱이 기대하는 API를 제공하는지 확인해 줍니다.
Database migrations: safe ordering, gates, and rollback reality
먼저 마이그레이션을 어디서 실행할지 결정하세요. CI에서 실행하면 초기에 오류를 잡기 좋지만, 보통 CI가 프로덕션을 직접 건드려선 안 됩니다. 대부분 팀은 배포 중에(전용 단계로) 또는 새로운 버전이 시작되기 전에 완료되어야 하는 별도의 “migrate” 잡으로 실행합니다.
실용적 규칙: CI에서 빌드하고 테스트한 뒤 가능한 프로덕션에 가깝게 마이그레이션을 실행하세요. Kubernetes에서는 보통 일회성 Job으로, VM에서는 릴리스 단계의 스크립트 명령으로 실행합니다.
순서(ordering)는 사람들이 생각하는 것보다 중요합니다. 타임스탬프 파일(또는 순번)과 “정확한 순서로 한 번만 적용”을 강제하세요. 재시도해도 중복을 만들거나 도중에 크래시 나지 않도록 마이그레이션을 가능하면 멱등하게 만드세요.
마이그레이션 전략은 단순하게 유지하세요:
- 먼저 추가적 변경 선호(새 테이블/컬럼, nullable 컬럼, 새 인덱스)
- 하나의 릴리스 동안 코드가 옛 스키마와 새 스키마 모두를 처리할 수 있게 배포
- 그 다음에 제약을 제거하거나 강화(컬럼 삭제, NOT NULL로 변경)
- 오래 걸리는 작업은 안전하게 수행(가능하면 인덱스를 concurrent로 생성)
실행 전에 안전 장치를 추가하세요. 예를 들어 하나의 마이그레이션만 실행되게 하는 DB 락과 “파괴적 변경은 승인 필요” 같은 정책입니다. 예: 마이그레이션에 DROP TABLE 또는 DROP COLUMN이 포함되면 수동 게이트가 승인되지 않는 한 파이프라인을 실패하게 만드세요.
롤백은 어려운 현실입니다: 많은 스키마 변경은 되돌릴 수 없습니다. 컬럼을 삭제하면 데이터를 복구할 수 없습니다. 롤백 계획은 전향적 수정(forward fix)에 맞춰 계획하세요: 안전할 때만 다운 마이그레이션을 두고, 그렇지 않으면 백업과 전향 마이그레이션으로 복구하세요.
각 마이그레이션에는 복구 계획을 붙이세요: 도중 실패 시 어떻게 복구할지, 앱을 롤백해야 할 때 어떻게 행동할지. AppMaster로 Go 백엔드를 생성하는 경우 마이그레이션을 릴리스 계약의 일부로 다루어 생성된 코드와 스키마가 동기화되도록 하세요.
Packaging and configuration: artifacts you can trust
파이프라인이 예측 가능하다고 느껴지려면 배포하는 것이 항상 테스트한 것과 동일해야 합니다. 이는 패키징과 설정 관리에 달려 있습니다. 빌드 결과물을 봉인된 아티팩트로 취급하고 환경 차이는 전부 밖으로 빼세요.
패키징은 보통 두 가지 길 중 하나입니다. Kubernetes에 배포한다면 컨테이너 이미지가 기본입니다. OS 계층을 고정하고 롤아웃을 일관되게 하기 때문입니다. VM용 번들도 신뢰할 수 있는데, 컴파일된 바이너리와 런타임에 필요한 소수의 파일(예: CA 인증서, 템플릿, 정적 자산)을 포함하고 매번 같은 방식으로 배포하면 됩니다.
설정은 바이너리에 구워 넣지 마세요. 대부분 설정(포트, DB 호스트, 기능 플래그)은 환경 변수로 처리하고, 값이 길거나 구조화되어 있으면 환경별 설정 파일을 사용하세요. 설정 서비스가 있다면 의존성으로 취급하세요: 권한 잠금, 감사 로그, 명확한 폴백 플랜을 둡니다.
시크릿은 넘지 말아야 할 선입니다. 레포지토리나 이미지, CI 로그에 절대 넣지 마세요. 시작 시 연결 문자열을 출력하지 마세요. CI 시크릿 저장소에 두고 배포 시 주입하세요.
아티팩트 추적성을 위해 각 빌드에 식별자를 넣으세요: 커밋 해시와 버전으로 태그, 빌드 메타데이터(버전, 커밋, 빌드 시간)를 정보 엔드포인트에 포함, 배포 로그에 아티팩트 태그 기록. 한 명령이나 대시보드로 “무엇이 실행 중인지” 답할 수 있게 하세요.
AppMaster로 Go 백엔드를 생성한다면 이 규율은 더 중요합니다: 재생성이 안전하려면 아티팩트 네이밍과 설정 규칙이 매 릴리스를 재현 가능하게 만들어야 합니다.
Deploying to Kubernetes or VMs without surprises
대부분의 배포 실패는 “나쁜 코드” 때문이 아닙니다. 설정 불일치, 누락된 시크릿, 시작은 하지만 실제로 준비되지 않은 서비스 때문입니다. 목표는 단순합니다: 동일한 아티팩트를 모든 곳에 배포하고, 환경별 차이는 설정으로만 제어하세요.
Kubernetes: treat deploys as controlled rollouts
Kubernetes에서는 통제된 롤아웃을 목표로 하세요. 롤링 업데이트를 사용해 파드를 점진적으로 교체하고, readiness와 liveness 체크를 추가해 플랫폼이 트래픽 전송 시기와 정지 시기를 알게 하세요. 리소스 요청과 제한도 중요합니다. CI 러너에서는 통과했지만 작은 노드에서 OOM으로 죽을 수 있습니다.
이미지와 시크릿을 분리하세요. 커밋마다 하나의 이미지를 빌드하고 배포 시 환경별 설정(ConfigMaps, Secrets 또는 시크릿 매니저)을 주입하면 스테이징과 프로덕션은 동일한 비트로 실행됩니다.
VMs: systemd gives you most of what you need
가상 머신에 배포할 때는 systemd가 작은 오케스트레이터 역할을 합니다. 작업 디렉터리, 환경 파일, 재시작 정책을 명확히 한 유닛 파일을 만드세요. stdout/stderr를 로그 수집기나 journald로 보내면 인시던트가 SSH 뒤지기 수색전이 되는 일을 줄일 수 있습니다.
클러스터 없이도 안전한 롤아웃이 가능합니다. 블루/그린: 두 디렉터리(또는 두 VM)를 유지하고 로드밸런서를 스위치하며 이전 버전을 빠른 롤백용으로 준비합니다. 카나리도 비슷합니다: 소량의 트래픽만 새 버전으로 보내 상태를 확인한 뒤 확장합니다.
배포를 “완료”로 표시하기 전에 어디서나 같은 포스트-디플로이 스모크 체크를 실행하세요:
- 헬스 엔드포인트가 OK를 반환하고 의존성이 접근 가능한지 확인
- 작은 실제 동작 실행(예: 테스트 레코드 생성 및 조회)
- 서비스 버전/빌드 ID가 커밋과 일치하는지 검증
- 실패하면 롤백하고 알림 발송
백엔드를 재생성하는 경우(예: AppMaster Go 백엔드)에도 이 접근법은 안정적입니다: 한 번 빌드하고 아티팩트를 배포한 뒤 환경 설정으로 차이를 제어하세요.
Common mistakes that make pipelines unreliable
대부분의 망가진 릴리스는 “나쁜 코드” 때문이 아닙니다. 파이프라인이 실행마다 다르게 행동할 때 발생합니다. Go 백엔드용 CI/CD를 차분하고 예측 가능하게 만들고 싶다면 다음 패턴을 조심하세요.
Mistake patterns that cause surprise failures
데이터베이스 마이그레이션을 아무런 가드레일 없이 자동으로 모든 배포에서 실행하는 것은 전형적인 실수입니다. 테이블을 잠그는 마이그레이션은 바쁜 서비스 전체를 다운시킬 수 있습니다. 프로덕션에서는 명시적 단계 뒤에 두고 승인 절차를 요구하세요.
latest 태그나 고정되지 않은 베이스 이미지를 사용하는 것도 원인입니다. 도커 이미지와 Go 버전을 고정해 빌드 환경이 서서히 변하지 않게 하세요.
“임시”로 하나의 데이터베이스를 여러 환경에서 공유하면 영구화되는 경우가 많고, 테스트 데이터가 스테이징으로 유입되거나 스테이징 스크립트가 프로덕션을 건드리는 일이 생깁니다. 환경별로 데이터베이스(및 자격증명)를 분리하세요.
헬스 체크와 레디니스 체크가 없으면 배포는 “성공”으로 표시되지만 서비스가 깨져 있을 수 있습니다. 애플리케이션이 시작하고 DB에 연결하며 요청을 처리할 수 있는지를 검사하는 체크를 추가하세요.
마지막으로 시크릿, 설정, 접근권에 대한 소유권이 불명확하면 릴리스가 추측으로 변합니다. 누가 시크릿을 생성하고 회전하며 주입하는지 소유자를 정하세요.
현실적인 실패 예: 팀이 변경을 머지하고 파이프라인이 배포를 시작합니다. 자동 마이그레이션은 스테이징에서는 작동했지만(데이터 적음) 프로덕션에서는 타임아웃합니다(데이터 많음). 이미지가 고정되어 있고 환경 분리가 되어 있으며 마이그레이션 단계가 게이트로 보호되어 있었다면 배포는 안전하게 중단되었을 것입니다.
AppMaster로 Go 백엔드를 생성하면 재생성이 많은 파일을 한 번에 건드릴 수 있기 때문에 이런 규칙은 더 중요합니다. 예측 가능한 입력과 명시적 게이트는 큰 변경이 위험한 릴리스로 이어지는 것을 막아줍니다.
Quick checklist for a predictable CI/CD setup
Go 백엔드용 CI/CD에 대한 직감 점검표입니다. 각 항목에 명확한 “예”로 답할 수 있다면 릴리스는 훨씬 쉬워집니다.
- 코드만이 아니라 환경을 잠금. Go 버전과 빌드 컨테이너 이미지를 고정하고 로컬과 CI에 동일한 설정 사용.
- 파이프라인을 3개의 간단한 명령으로 실행. 하나는 빌드, 하나는 테스트, 하나는 배포 가능한 아티팩트 생성.
- 마이그레이션을 프로덕션 코드처럼 취급. 모든 마이그레이션 실행 로그 보관, 롤백의 의미 문서화.
- 추적 가능한 불변 아티팩트 생성. 한 번 빌드하고 커밋 SHA로 태그해 환경 간 재빌드 없이 승격.
- 빠르게 실패하는 체크로 배포. 레디니스/라이브니스 헬스 체크와 모든 배포에서 실행되는 짧은 스모크 테스트 추가.
프로덕션 접근은 제한하고 감사 가능하게 유지하세요. CI는 전용 서비스 계정으로 배포하고, 시크릿은 중앙에서 관리하며 수동 프로덕션 작업은 누가, 무엇을, 언제 했는지 명확히 남겨야 합니다.
A realistic example and next steps you can start this week
작은 운영팀(4명)이 주 1회 배포합니다. 제품팀이 워크플로를 계속 다듬기 때문에 Go 백엔드를 자주 재생성합니다. 목표는 간단합니다: 새벽에 울리는 고장 수를 줄이고 아무도 놀라지 않는 릴리스.
금요일의 Typical 변경: customers에 새 필드를 추가(스키마 변경)하고 그 필드를 쓰는 API를 업데이트(코드 변경). 파이프라인은 이들을 하나의 릴리스로 처리합니다. 하나의 아티팩트를 빌드하고 그 정확한 아티팩트로 테스트를 실행한 뒤에만 마이그레이션을 적용하고 배포합니다. 이렇게 하면 데이터베이스가 코드를 기대보다 앞서 나가거나, 반대로 코드가 매칭되는 스키마 없이 배포되는 일이 없습니다.
스키마 변경이 포함되면 파이프라인은 안전 게이트를 추가합니다. 마이그레이션이 추가적 변경인지(예: nullable 컬럼 추가) 확인하고, 컬럼 삭제나 대규모 테이블 재작성 같은 위험 요소를 표시합니다. 위험하면 프로덕션 이전에 릴리스를 중단합니다. 팀은 마이그레이션을 더 안전하게 고치거나 계획된 윈도우를 잡습니다.
테스트가 실패하면 아무것도 앞으로 나아가지 않습니다. 마찬가지로 프리프로덕션 환경에서 마이그레이션이 실패하면 진행하지 않습니다. 파이프라인은 “이번 한 번만 밀어넣자”는 시도를 해선 안 됩니다.
대부분 팀에 잘 통하는 간단한 다음 단계:
- 하나의 환경부터 시작(리셋하기 쉬운 단일 개발 배포).
- 파이프라인이 항상 하나의 버전화된 빌드 아티팩트를 생성하게 하기.
- 개발 환경에서는 마이그레이션을 자동으로 실행하되, 프로덕션에서는 승인을 요구.
- 개발이 몇 주 안정되면 스테이징 추가.
- 프로덕션 게이트에 그린 테스트와 성공적인 스테이징 배포를 요구.
AppMaster로 백엔드를 생성한다면 재생성을 동일한 파이프라인 단계 안에 유지하세요: regenerate, build, test, migrate(안전한 환경에서), 그리고 deploy. 생성된 소스도 일반 소스처럼 취급하세요. 모든 릴리스는 태그된 버전으로부터 동일한 단계로 재현 가능해야 합니다.
자주 묻는 질문
Go 버전과 빌드 환경을 고정하세요. 같은 입력이 항상 같은 바이너리나 이미지를 만들도록 하면 “내 환경에서는 작동” 문제를 제거하고 실패를 재현하기 쉬워집니다.
재생성(regeneration)은 엔드포인트, 데이터 모델, 의존성을 바꿀 수 있습니다. 파이프라인은 이런 변경을 항상 동일한 검증 절차로 통과시키므로 재생성이 위험이 아닌 안전한 작업이 됩니다.
한 번 빌드한 아티팩트를 dev, staging, prod로 그대로 승격(promote)하세요. 환경별로 다시 빌드하면 동일 커밋이라도 테스트하지 않은 것이 배포될 수 있습니다.
포맷 검사, 기본 정적 검사, 빌드, 그리고 타임아웃이 있는 단위 테스트 같은 빠른 게이트를 풀 리퀘스트마다 실행하세요. 빠르면서도 엄격하게 해서 깨진 변경은 초기에 멈추게 합니다.
서비스를 실제와 비슷한 설정으로 띄우고 PostgreSQL 같은 실제 의존성과 연동하는 작은 통합 단계만 추가하세요. 목표는 “컴파일은 되는데 시작하지 못한다” 같은 문제를 잡는 것입니다. 전체 E2E를 매번 돌릴 필요는 없습니다.
마이그레이션은 자동으로 아무 환경에나 실행되는 단계가 되어선 안 됩니다. 단일 실행 락과 명확한 로그가 있는 제어된 릴리스 단계로 실행하세요. 많은 스키마 변경은 간단히 되돌릴 수 없다는 것을 인정해야 합니다.
Readiness 체크를 추가해 새 파드가 실제로 준비된 뒤에만 트래픽이 가도록 하세요. 또한 리소스 요청/제한을 현실적으로 설정해 CI에서 통과한 서비스가 프로덕션에서 OOM으로 죽지 않게 해야 합니다.
간단한 systemd 유닛 파일과 일관된 릴리스 스크립트면 VM 환경에서도 안정적인 배포가 가능합니다. 가능한 한 컨테이너과 동일한 아티팩트 모델을 유지하고, 배포 후 간단한 스모크 체크를 추가하세요.
비밀은 레포지토리나 빌드 아티팩트, 로그에 절대 포함시키지 마세요. 배포 시에만 시크릿 스토어에서 주입하고, 읽을 수 있는 권한을 제한하며 정기적으로 로테이션하세요.
재생성을 파이프라인 안에 두고 다른 변경과 같은 단계로 처리하세요: regenerate, build, test, package, 그리고 안전 장치가 있는 migrate 및 deploy. AppMaster로 생성하는 경우에도 이 흐름을 따르면 변경 내용을 빠르고 안전하게 배포할 수 있습니다.


