본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2026. 2. 3. · 5 Views
CI/CD 파이프라인 통합 완벽 가이드
Jenkins, GitLab CI와 Ansible을 연동하여 자동화된 배포 파이프라인을 구축하는 방법을 다룹니다. Ansible Tower/AWX의 활용법과 실무에서 바로 적용 가능한 워크플로우 설계 패턴을 단계별로 설명합니다.
목차
- CI/CD_파이프라인_개요
- Jenkins와_Ansible_통합
- GitLab_CI에서_Ansible_실행
- Ansible_Tower와_AWX_소개
- 자동화_워크플로우_설계
- 배포_파이프라인_구축
1. CI/CD 파이프라인 개요
김개발 씨는 새벽 2시에 급한 전화를 받았습니다. "프로덕션 서버에 긴급 패치 좀 배포해줘요." 잠결에 SSH로 서버에 접속해서 수동으로 코드를 배포하다가 실수로 설정 파일을 덮어썼습니다.
다음 날 아침, 서비스 장애 보고서를 작성하며 김개발 씨는 생각했습니다. "이걸 자동화할 방법이 없을까?"
CI/CD 파이프라인은 코드 변경사항을 자동으로 빌드, 테스트, 배포하는 일련의 자동화된 프로세스입니다. 마치 자동차 조립 공장의 컨베이어 벨트처럼, 코드가 작성되는 순간부터 사용자에게 전달되기까지의 모든 과정을 자동화합니다.
여기에 Ansible을 통합하면 인프라 구성까지 코드로 관리할 수 있어 더욱 강력한 자동화가 가능합니다.
다음 코드를 살펴봅시다.
# CI/CD 파이프라인의 기본 구조를 표현한 Ansible Playbook
# deploy_pipeline.yml
---
- name: CI/CD 파이프라인 기본 구조
hosts: all
vars:
app_name: "myapp"
deploy_path: "/var/www/{{ app_name }}"
tasks:
# 1단계: 코드 가져오기 (CI - Continuous Integration)
- name: Git에서 최신 코드 가져오기
git:
repo: "https://github.com/company/{{ app_name }}.git"
dest: "{{ deploy_path }}"
version: "{{ branch | default('main') }}"
# 2단계: 의존성 설치 및 빌드
- name: 의존성 설치
pip:
requirements: "{{ deploy_path }}/requirements.txt"
virtualenv: "{{ deploy_path }}/venv"
# 3단계: 배포 (CD - Continuous Deployment)
- name: 애플리케이션 서비스 재시작
systemd:
name: "{{ app_name }}"
state: restarted
enabled: yes
김개발 씨의 새벽 배포 사고 이후, 팀에서는 긴급 회의가 열렸습니다. CTO인 이사장 씨가 화이트보드에 큰 글씨로 적었습니다.
"CI/CD 파이프라인 도입." 그렇다면 CI/CD란 정확히 무엇일까요? CI는 Continuous Integration, 즉 지속적 통합을 의미합니다.
여러 개발자가 작성한 코드를 자주, 그리고 자동으로 합치는 것입니다. 마치 여러 작가가 함께 쓰는 소설책에서 편집자가 매일 원고를 모아 전체 흐름을 확인하는 것과 같습니다.
CD는 두 가지 의미를 가집니다. Continuous Delivery는 지속적 전달로, 배포 가능한 상태를 항상 유지하는 것입니다.
Continuous Deployment는 지속적 배포로, 검증된 코드를 자동으로 프로덕션까지 배포하는 것입니다. 과거에는 어땠을까요?
개발자들은 몇 주 또는 몇 달에 한 번씩 코드를 합쳤습니다. 이를 빅뱅 통합이라고 불렀는데, 말 그대로 폭발적인 충돌이 발생하곤 했습니다.
서로 다른 방향으로 개발된 코드들이 한꺼번에 만나니 문제가 생기지 않을 수 없었습니다. CI/CD 파이프라인이 등장하면서 상황이 달라졌습니다.
코드가 커밋되는 즉시 자동으로 빌드되고, 테스트가 실행됩니다. 문제가 발견되면 몇 분 안에 알림이 옵니다.
수동 배포로 인한 실수도 사라졌습니다. 위의 코드를 살펴보겠습니다.
이 Ansible Playbook은 CI/CD 파이프라인의 핵심 단계를 보여줍니다. 첫 번째 태스크에서 Git 저장소로부터 최신 코드를 가져옵니다.
두 번째 태스크에서 필요한 라이브러리들을 설치합니다. 세 번째 태스크에서 애플리케이션을 재시작하여 변경사항을 반영합니다.
실제 현업에서는 이보다 훨씬 복잡한 파이프라인을 구성합니다. 단위 테스트, 통합 테스트, 보안 스캔, 성능 테스트 등 다양한 검증 단계가 추가됩니다.
하지만 기본 원리는 동일합니다. 자동화된 단계들이 순차적으로 또는 병렬로 실행되며, 모든 단계를 통과해야만 다음으로 진행됩니다.
Ansible이 CI/CD 파이프라인에서 특별히 빛나는 이유가 있습니다. 바로 멱등성입니다.
같은 Playbook을 여러 번 실행해도 결과가 동일하게 보장됩니다. 이 특성 덕분에 배포 과정에서 예기치 않은 부작용을 걱정하지 않아도 됩니다.
주의할 점도 있습니다. 파이프라인이 너무 복잡해지면 오히려 유지보수가 어려워집니다.
처음에는 간단하게 시작하여 필요에 따라 점진적으로 확장하는 것이 좋습니다. 김개발 씨 팀은 CI/CD 파이프라인 도입 후 배포 시간이 2시간에서 15분으로 단축되었습니다.
무엇보다 새벽에 전화받을 일이 없어졌습니다. 자동화된 파이프라인이 알아서 처리해주기 때문입니다.
실전 팁
💡 - 파이프라인은 작게 시작해서 점진적으로 확장하세요
- 모든 단계에서 실패 시 롤백 전략을 미리 수립해두세요
- 파이프라인 실행 로그는 최소 30일 이상 보관하세요
2. Jenkins와 Ansible 통합
박시니어 씨가 김개발 씨에게 물었습니다. "Jenkins는 써봤죠?" 김개발 씨가 고개를 끄덕이자, 박시니어 씨가 웃으며 말했습니다.
"그럼 Jenkins에서 Ansible을 실행하는 방법을 알려줄게요. 이 조합이면 거의 모든 배포 자동화가 가능해요."
Jenkins는 가장 널리 사용되는 CI/CD 도구 중 하나입니다. Jenkins Pipeline과 Ansible을 연동하면 빌드부터 배포까지 하나의 흐름으로 자동화할 수 있습니다.
마치 지휘자가 오케스트라를 이끌듯이, Jenkins가 전체 흐름을 제어하고 Ansible이 실제 서버 작업을 수행합니다.
다음 코드를 살펴봅시다.
// Jenkinsfile - Jenkins Pipeline에서 Ansible 실행
pipeline {
agent any
environment {
ANSIBLE_HOST_KEY_CHECKING = 'False'
DEPLOY_ENV = "${params.ENVIRONMENT ?: 'staging'}"
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/company/myapp.git'
}
}
stage('Build & Test') {
steps {
sh 'pip install -r requirements.txt'
sh 'pytest tests/ --junitxml=test-results.xml'
}
}
stage('Deploy with Ansible') {
steps {
// Ansible Playbook 실행
ansiblePlaybook(
playbook: 'deploy/playbook.yml',
inventory: "deploy/inventory/${DEPLOY_ENV}",
credentialsId: 'ansible-ssh-key',
extras: "-e app_version=${BUILD_NUMBER}"
)
}
}
stage('Health Check') {
steps {
sh 'ansible-playbook deploy/healthcheck.yml -i deploy/inventory/${DEPLOY_ENV}'
}
}
}
post {
failure {
// 실패 시 롤백 실행
ansiblePlaybook(
playbook: 'deploy/rollback.yml',
inventory: "deploy/inventory/${DEPLOY_ENV}"
)
}
}
}
박시니어 씨가 화면을 공유하며 설명을 시작했습니다. "Jenkins와 Ansible, 이 둘의 조합은 정말 강력해요." Jenkins는 CI/CD 세계의 맥가이버 칼과 같습니다.
무엇이든 할 수 있지만, 혼자서는 서버에 파일을 배포하거나 설정을 변경하는 일이 서툽니다. 반면 Ansible은 서버 관리의 달인이지만, 언제 실행해야 할지 스스로 판단하지 못합니다.
이 둘을 연결하면 완벽한 팀이 됩니다. Jenkins가 "지금 배포해!"라고 신호를 보내면, Ansible이 "알겠습니다!"하고 서버들을 일사불란하게 정리합니다.
Jenkins에서 Ansible을 사용하는 방법은 크게 두 가지입니다. 첫 번째는 Ansible Plugin을 설치하는 것입니다.
위 코드에서 사용한 ansiblePlaybook 스텝이 바로 이 플러그인이 제공하는 기능입니다. 두 번째는 단순히 sh 스텝에서 ansible-playbook 명령어를 직접 실행하는 것입니다.
위의 Jenkinsfile을 단계별로 살펴보겠습니다. 파이프라인은 네 개의 스테이지로 구성되어 있습니다.
Checkout 스테이지에서는 Git 저장소로부터 소스 코드를 가져옵니다. 이 단계가 없으면 배포할 코드 자체가 없으니 가장 기본이 되는 단계입니다.
Build & Test 스테이지에서는 의존성을 설치하고 테스트를 실행합니다. pytest를 사용하여 테스트 결과를 XML 파일로 저장합니다.
이 파일은 나중에 Jenkins가 테스트 결과를 시각화하는 데 사용됩니다. Deploy with Ansible 스테이지가 핵심입니다.
ansiblePlaybook 함수에 여러 파라미터를 전달합니다. playbook은 실행할 Playbook 파일 경로입니다.
inventory는 대상 서버 목록입니다. credentialsId는 Jenkins에 저장된 SSH 키를 참조합니다.
extras를 통해 빌드 번호를 Ansible 변수로 전달합니다. Health Check 스테이지에서는 배포가 성공했는지 확인합니다.
서버가 정상적으로 응답하는지, 애플리케이션이 제대로 동작하는지 검증합니다. post 블록의 failure 섹션도 중요합니다.
파이프라인이 실패하면 자동으로 롤백 Playbook이 실행됩니다. 이렇게 하면 실패한 배포로 인한 서비스 중단을 최소화할 수 있습니다.
환경 변수 설정에서 ANSIBLE_HOST_KEY_CHECKING을 False로 설정한 것을 눈여겨보세요. CI/CD 환경에서는 새로운 서버가 자주 추가되므로, SSH 호스트 키 확인을 비활성화하는 것이 일반적입니다.
물론 보안이 중요한 환경에서는 다른 방법을 고려해야 합니다. 실무에서 흔히 하는 실수 중 하나는 credentials 관리를 소홀히 하는 것입니다.
SSH 키나 API 토큰을 코드에 직접 넣으면 보안 사고로 이어집니다. 반드시 Jenkins의 Credentials 기능을 활용하세요.
김개발 씨가 이 구조를 적용한 후, 팀의 배포 프로세스가 크게 개선되었습니다. 더 이상 "누가 배포했어요?"라는 질문이 없어졌습니다.
Jenkins 빌드 히스토리에 모든 기록이 남기 때문입니다.
실전 팁
💡 - Jenkins Credentials에 SSH 키를 등록하여 보안을 강화하세요
- 스테이지별로 타임아웃을 설정하여 무한 대기를 방지하세요
- Blue Ocean 플러그인을 사용하면 파이프라인 시각화가 더 직관적입니다
3. GitLab CI에서 Ansible 실행
최근 회사가 GitLab으로 이전하면서 김개발 씨는 새로운 고민에 빠졌습니다. "Jenkins 파이프라인을 다 버려야 하나?" 박시니어 씨가 대답했습니다.
"GitLab CI도 Ansible과 잘 어울려요. 오히려 .gitlab-ci.yml 파일 하나로 모든 걸 관리할 수 있어서 더 깔끔할 수 있어요."
GitLab CI/CD는 GitLab에 내장된 CI/CD 도구입니다. 저장소 루트에 .gitlab-ci.yml 파일만 추가하면 바로 파이프라인이 작동합니다.
Ansible과 결합하면 코드 저장소와 배포 설정을 한 곳에서 관리할 수 있어 GitOps 패러다임을 실현할 수 있습니다.
다음 코드를 살펴봅시다.
# .gitlab-ci.yml - GitLab CI에서 Ansible 실행
stages:
- test
- build
- deploy
- verify
variables:
ANSIBLE_HOST_KEY_CHECKING: "False"
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
# Ansible 실행을 위한 기본 이미지 정의
.ansible_base:
image: python:3.11-slim
before_script:
- pip install ansible boto3 --quiet
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ansible --version
test:
stage: test
image: python:3.11
script:
- pip install -r requirements.txt
- pytest tests/ -v
artifacts:
reports:
junit: test-results.xml
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
deploy_staging:
extends: .ansible_base
stage: deploy
script:
- ansible-playbook deploy/playbook.yml
-i deploy/inventory/staging
-e "app_version=$CI_COMMIT_SHA"
-e "environment=staging"
environment:
name: staging
url: https://staging.example.com
only:
- develop
deploy_production:
extends: .ansible_base
stage: deploy
script:
- ansible-playbook deploy/playbook.yml
-i deploy/inventory/production
-e "app_version=$CI_COMMIT_SHA"
-e "environment=production"
environment:
name: production
url: https://example.com
when: manual
only:
- main
verify:
extends: .ansible_base
stage: verify
script:
- ansible-playbook deploy/healthcheck.yml -i deploy/inventory/$CI_ENVIRONMENT_NAME
김개발 씨는 GitLab CI의 첫인상에 감탄했습니다. "파일 하나만 추가하면 된다고요?" 그렇습니다.
GitLab CI/CD의 가장 큰 장점은 단순함입니다. 저장소에 .gitlab-ci.yml 파일을 추가하는 순간, GitLab이 자동으로 파이프라인을 인식하고 실행합니다.
별도의 서버 설정이나 플러그인 설치가 필요 없습니다. 이 구조가 GitOps라는 개념과 잘 맞아떨어집니다.
GitOps는 Git 저장소를 단일 진실의 원천으로 삼는 운영 방식입니다. 애플리케이션 코드, 인프라 설정, 배포 파이프라인이 모두 같은 저장소에 있으니 변경 이력을 추적하기 쉽습니다.
위의 .gitlab-ci.yml 파일을 살펴보겠습니다. 먼저 stages에서 파이프라인의 단계를 정의합니다.
test, build, deploy, verify 순서로 실행됩니다. .ansible_base라는 숨겨진 Job을 주목해주세요.
점(.)으로 시작하는 이름은 실제로 실행되지 않는 템플릿입니다. 이 템플릿에서 Ansible 실행에 필요한 공통 설정을 정의합니다.
Python 이미지를 사용하고, Ansible을 설치하고, SSH 키를 설정합니다. SSH_PRIVATE_KEY는 GitLab의 CI/CD Variables에 저장된 비밀 값입니다.
이렇게 하면 SSH 키가 코드에 노출되지 않습니다. GitLab 프로젝트 설정에서 Settings > CI/CD > Variables로 이동하여 등록할 수 있습니다.
deploy_staging과 deploy_production Job의 차이점을 살펴보세요. staging 배포는 develop 브랜치에 푸시할 때마다 자동으로 실행됩니다.
반면 production 배포는 when: manual 설정으로 인해 수동 승인이 필요합니다. main 브랜치에 머지해도 자동으로 배포되지 않고, GitLab UI에서 "Play" 버튼을 눌러야 합니다.
environment 키워드도 중요합니다. 이를 설정하면 GitLab의 Environments 페이지에서 각 환경의 배포 상태를 한눈에 확인할 수 있습니다.
어떤 버전이 어느 환경에 배포되어 있는지, 언제 배포되었는지 추적할 수 있습니다. extends 키워드는 코드 재사용의 핵심입니다.
deploy_staging과 deploy_production 모두 .ansible_base를 상속받아 중복을 줄입니다. 마치 객체지향 프로그래밍의 상속과 비슷합니다.
실무에서 자주 겪는 문제 중 하나는 GitLab Runner의 리소스 부족입니다. 공유 Runner를 사용하면 대기 시간이 길어질 수 있습니다.
가능하다면 전용 Runner를 설정하는 것이 좋습니다. 또 다른 팁은 artifacts와 cache를 적절히 활용하는 것입니다.
test 단계에서 생성된 테스트 결과를 artifacts로 저장하면 나중에 다운로드하거나 다른 Job에서 사용할 수 있습니다. 김개발 씨는 GitLab CI로 전환한 후 한 가지 더 마음에 드는 점을 발견했습니다.
Merge Request를 생성하면 자동으로 파이프라인이 실행되어 코드 리뷰 전에 테스트 결과를 확인할 수 있다는 것입니다.
실전 팁
💡 - SSH 키와 API 토큰은 반드시 GitLab CI/CD Variables에 저장하세요
- 프로덕션 배포는 when: manual로 설정하여 실수로 인한 배포를 방지하세요
- 전용 GitLab Runner를 구성하면 빌드 속도가 크게 향상됩니다
4. Ansible Tower와 AWX 소개
어느 날 김개발 씨가 Ansible Playbook을 실행하려는데 선배가 물었습니다. "그거 CLI로 매번 돌리는 거야?" 김개발 씨가 고개를 끄덕이자, 선배가 화면을 보여주었습니다.
웹 브라우저에서 Playbook 목록이 보이고, 버튼 하나로 실행할 수 있는 대시보드였습니다. "이게 Ansible Tower야.
오픈소스 버전은 AWX라고 해."
Ansible Tower는 Red Hat이 제공하는 상용 Ansible 관리 플랫폼이고, AWX는 그 오픈소스 버전입니다. 웹 UI를 통해 Playbook을 실행하고, 권한을 관리하고, 실행 이력을 추적할 수 있습니다.
마치 Ansible에 조종석을 달아준 것과 같습니다. REST API도 제공하여 다른 시스템과 쉽게 연동됩니다.
다음 코드를 살펴봅시다.
# AWX/Tower API를 활용한 Job Template 실행
# awx_integration.py
import requests
import time
class AWXClient:
def __init__(self, host, token):
self.host = host
self.headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
def launch_job_template(self, template_id, extra_vars=None):
"""Job Template 실행"""
url = f"{self.host}/api/v2/job_templates/{template_id}/launch/"
payload = {}
if extra_vars:
payload["extra_vars"] = extra_vars
response = requests.post(url, json=payload, headers=self.headers)
response.raise_for_status()
return response.json()
def wait_for_job(self, job_id, timeout=600):
"""Job 완료 대기"""
url = f"{self.host}/api/v2/jobs/{job_id}/"
start_time = time.time()
while time.time() - start_time < timeout:
response = requests.get(url, headers=self.headers)
job_status = response.json()["status"]
if job_status in ["successful", "failed", "error", "canceled"]:
return job_status
time.sleep(10)
raise TimeoutError(f"Job {job_id} did not complete within {timeout} seconds")
# 사용 예시
if __name__ == "__main__":
awx = AWXClient("https://awx.example.com", "your-api-token")
# 배포 Job Template 실행
result = awx.launch_job_template(
template_id=15,
extra_vars={"app_version": "v2.1.0", "environment": "production"}
)
job_id = result["id"]
print(f"Job {job_id} started")
# 완료 대기
final_status = awx.wait_for_job(job_id)
print(f"Job completed with status: {final_status}")
박시니어 씨가 AWX 대시보드를 보여주며 설명했습니다. "CLI로 Ansible을 실행하는 건 개인 작업에는 괜찮아요.
하지만 팀 단위로 운영하려면 한계가 있죠." 그 한계가 무엇일까요? 첫째, 권한 관리입니다.
CLI에서는 SSH 키를 가진 사람이면 누구나 어떤 서버에든 접근할 수 있습니다. 실수로 프로덕션 서버에 잘못된 명령을 실행할 수도 있습니다.
둘째, 실행 이력 추적입니다. 누가 언제 어떤 Playbook을 실행했는지 기록이 남지 않습니다.
문제가 발생했을 때 원인을 찾기 어렵습니다. 셋째, 일관성입니다.
개발자마다 다른 버전의 Ansible을 사용하거나, 다른 환경 변수를 설정하면 같은 Playbook도 다른 결과를 낼 수 있습니다. Ansible Tower와 AWX는 이 모든 문제를 해결합니다.
AWX는 Tower의 오픈소스 업스트림 프로젝트로, 기능은 거의 동일합니다. 다만 기업용 지원과 인증이 필요하다면 Tower를 선택하면 됩니다.
핵심 개념들을 살펴보겠습니다. Inventory는 관리 대상 서버 목록입니다.
정적으로 정의할 수도 있고, AWS나 Azure 같은 클라우드에서 동적으로 가져올 수도 있습니다. Credentials는 SSH 키, API 토큰 등 민감한 정보를 안전하게 저장합니다.
팀원들은 이 Credentials를 사용할 수 있지만, 실제 값을 볼 수는 없습니다. Project는 Playbook이 저장된 Git 저장소를 가리킵니다.
AWX가 자동으로 최신 버전을 가져옵니다. Job Template은 이 모든 것을 조합한 실행 단위입니다.
어떤 Playbook을, 어떤 Inventory에 대해, 어떤 Credentials로 실행할지 정의합니다. 위의 Python 코드는 AWX의 REST API를 활용하는 예시입니다.
launch_job_template 메서드로 Job을 실행하고, wait_for_job 메서드로 완료를 기다립니다. 이 API를 활용하면 Jenkins나 GitLab CI에서 AWX의 Job Template을 트리거할 수 있습니다.
CI/CD 파이프라인에서 직접 ansible-playbook 명령을 실행하는 대신 AWX를 거치면 여러 장점이 있습니다. 모든 실행 이력이 AWX에 기록됩니다.
Credentials가 CI/CD 시스템에 노출되지 않습니다. 동일한 Job Template을 여러 파이프라인에서 재사용할 수 있습니다.
주의할 점도 있습니다. AWX 자체도 인프라입니다.
고가용성을 위해 클러스터로 구성해야 하고, 데이터베이스 백업도 필요합니다. 작은 팀이라면 CLI로 충분할 수 있습니다.
김개발 씨 팀은 AWX 도입 후 배포 권한 관리가 훨씬 수월해졌습니다. 주니어 개발자는 staging 환경만 배포할 수 있고, 시니어 개발자만 production 배포 버튼을 누를 수 있게 설정했습니다.
실전 팁
💡 - AWX는 Kubernetes나 Docker Compose로 설치할 수 있습니다
- 중요한 Job Template에는 Survey 기능을 활용하여 실행 전 확인을 받으세요
- Webhook 기능을 사용하면 Git push 시 자동으로 Job을 실행할 수 있습니다
5. 자동화 워크플로우 설계
김개발 씨가 파이프라인을 구축하다가 막혔습니다. "웹 서버 배포하고 나서 DB 마이그레이션을 해야 하는데, 그 다음에 캐시 서버도 재시작해야 하고..." 박시니어 씨가 다가왔습니다.
"순서가 중요한 작업들이 많죠? 그럴 때는 워크플로우를 설계해야 해요.
어떤 작업이 먼저 실행되어야 하고, 어떤 작업은 병렬로 돌릴 수 있는지 정리하는 거예요."
자동화 워크플로우는 여러 작업들의 실행 순서와 의존 관계를 정의한 것입니다. 마치 요리 레시피에서 재료 손질, 조리, 플레이팅의 순서가 정해져 있듯이, 배포 과정에서도 어떤 작업이 먼저 완료되어야 다음 작업을 시작할 수 있는지가 중요합니다.
잘 설계된 워크플로우는 배포 시간을 단축하고 오류를 줄여줍니다.
다음 코드를 살펴봅시다.
# 자동화 워크플로우 설계 - 복합 배포 시나리오
# workflow_playbook.yml
---
- name: 사전 점검
hosts: all
gather_facts: yes
tasks:
- name: 디스크 공간 확인
assert:
that: ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 1073741824
fail_msg: "디스크 공간이 1GB 미만입니다"
- name: 현재 버전 백업
archive:
path: /var/www/app
dest: "/backup/app_{{ ansible_date_time.iso8601_basic_short }}.tar.gz"
- name: 데이터베이스 마이그레이션
hosts: db_servers
serial: 1 # 한 번에 하나씩 실행
tasks:
- name: 마이그레이션 스크립트 실행
command: /opt/app/migrate.sh
register: migration_result
- name: 마이그레이션 결과 확인
assert:
that: migration_result.rc == 0
fail_msg: "마이그레이션 실패"
- name: 웹 서버 배포 (롤링 업데이트)
hosts: web_servers
serial: "30%" # 전체의 30%씩 순차 배포
max_fail_percentage: 10
tasks:
- name: 로드밸런서에서 제거
uri:
url: "http://{{ lb_host }}/api/servers/{{ inventory_hostname }}/disable"
method: POST
delegate_to: localhost
- name: 애플리케이션 배포
include_role:
name: deploy_app
vars:
app_version: "{{ deploy_version }}"
- name: 헬스체크 대기
uri:
url: "http://{{ inventory_hostname }}:8080/health"
status_code: 200
register: health
until: health.status == 200
retries: 30
delay: 10
- name: 로드밸런서에 복귀
uri:
url: "http://{{ lb_host }}/api/servers/{{ inventory_hostname }}/enable"
method: POST
delegate_to: localhost
- name: 캐시 갱신
hosts: cache_servers
tasks:
- name: 캐시 무효화
command: redis-cli FLUSHDB
when: flush_cache | default(false)
- name: 캐시 워밍업
uri:
url: "http://localhost:8080/api/cache/warmup"
method: POST
박시니어 씨가 화이트보드에 배포 순서를 그리기 시작했습니다. "워크플로우 설계는 건축 설계도를 그리는 것과 비슷해요." 좋은 워크플로우에는 몇 가지 원칙이 있습니다.
첫 번째 원칙은 의존성 파악입니다. 어떤 작업이 다른 작업보다 먼저 완료되어야 하는지 명확히 해야 합니다.
데이터베이스 스키마 변경 없이 새 코드를 배포하면 오류가 발생할 수 있습니다. 따라서 DB 마이그레이션이 먼저 실행되어야 합니다.
두 번째 원칙은 병렬화 가능성 식별입니다. 서로 독립적인 작업은 동시에 실행할 수 있습니다.
예를 들어 정적 파일을 CDN에 업로드하는 작업과 API 서버를 배포하는 작업은 병렬로 진행해도 됩니다. 세 번째 원칙은 실패 처리 전략입니다.
어떤 단계에서 실패하면 어떻게 할 것인가? 롤백할 것인가, 멈출 것인가, 무시하고 진행할 것인가?
위의 Playbook을 살펴보겠습니다. 총 네 개의 Play로 구성되어 있고, 순서대로 실행됩니다.
첫 번째 Play인 사전 점검에서는 배포 전에 환경이 준비되었는지 확인합니다. 디스크 공간이 부족하면 배포가 실패할 수 있으니 미리 확인합니다.
또한 현재 버전을 백업하여 문제 발생 시 복구할 수 있도록 합니다. 두 번째 Play인 데이터베이스 마이그레이션에서 serial: 1을 주목하세요.
DB 서버가 여러 대라도 한 번에 하나씩만 마이그레이션을 실행합니다. 동시에 실행하면 충돌이 발생할 수 있기 때문입니다.
세 번째 Play인 웹 서버 배포가 가장 복잡합니다. **serial: "30%"**는 전체 서버의 30%씩 순차적으로 배포한다는 의미입니다.
10대의 웹 서버가 있다면 3대씩 배포됩니다. 이를 롤링 업데이트라고 합니다.
max_fail_percentage: 10은 10% 이상의 서버에서 실패하면 전체 배포를 중단한다는 의미입니다. 이렇게 하면 문제가 있는 배포가 모든 서버로 확산되는 것을 방지할 수 있습니다.
각 웹 서버 배포는 네 단계로 진행됩니다. 먼저 로드밸런서에서 해당 서버를 제거하여 새 트래픽을 받지 않게 합니다.
그 다음 새 버전을 배포합니다. 헬스체크가 통과할 때까지 기다립니다.
마지막으로 로드밸런서에 다시 등록합니다. until과 retries를 조합한 패턴은 매우 유용합니다.
헬스체크가 성공할 때까지 30번까지 10초 간격으로 재시도합니다. 애플리케이션이 시작되는 데 시간이 걸릴 수 있으니 충분한 여유를 둡니다.
네 번째 Play인 캐시 갱신은 조건부로 실행됩니다. **when: flush_cache | default(false)**는 flush_cache 변수가 true일 때만 캐시를 비웁니다.
항상 필요한 작업은 아니니까요. 실무에서 흔히 하는 실수는 모든 것을 순차적으로 실행하는 것입니다.
그러면 배포 시간이 불필요하게 길어집니다. 병렬화할 수 있는 작업을 찾아보세요.
김개발 씨는 이 워크플로우를 적용한 후 배포 시간이 40분에서 15분으로 줄었습니다. 무엇보다 배포 중에 서비스 중단 없이 사용자들이 계속 서비스를 이용할 수 있게 되었습니다.
실전 팁
💡 - 워크플로우를 다이어그램으로 시각화하면 팀원들과 소통하기 좋습니다
- 테스트 환경에서 충분히 검증한 후 프로덕션에 적용하세요
- 배포 시간이 30분을 넘으면 병렬화 기회를 찾아보세요
6. 배포 파이프라인 구축
드디어 모든 퍼즐 조각이 모였습니다. 김개발 씨는 지금까지 배운 CI/CD 개념, Jenkins/GitLab CI 연동, AWX 활용법, 워크플로우 설계를 하나로 엮어 완성된 배포 파이프라인을 구축하려 합니다.
박시니어 씨가 조언했습니다. "처음부터 완벽하게 만들려고 하지 마세요.
작동하는 최소한의 파이프라인부터 시작해서 점진적으로 개선하는 게 좋아요."
배포 파이프라인은 코드 커밋부터 프로덕션 배포까지의 전체 여정을 자동화한 것입니다. 마치 공장의 생산 라인처럼, 각 단계를 통과해야만 다음 단계로 진행됩니다.
완성된 파이프라인은 코드 품질 검사, 보안 스캔, 테스트, 스테이징 배포, 승인 프로세스, 프로덕션 배포를 모두 포함합니다.
다음 코드를 살펴봅시다.
# 완성된 배포 파이프라인 구성
# deploy/main_pipeline.yml
---
- name: 완전한 배포 파이프라인
hosts: localhost
gather_facts: no
vars:
app_name: "myapp"
deploy_version: "{{ lookup('env', 'BUILD_VERSION') }}"
tasks:
# 1단계: 배포 전 검증
- name: 배포 버전 검증
assert:
that:
- deploy_version is defined
- deploy_version | length > 0
fail_msg: "배포 버전이 지정되지 않았습니다"
# 2단계: Staging 배포
- name: Staging 환경 배포
include_tasks: deploy_to_environment.yml
vars:
target_env: staging
hosts_group: staging_servers
# 3단계: 통합 테스트
- name: Staging 통합 테스트 실행
command: pytest integration_tests/ --environment=staging
register: integration_test
failed_when: integration_test.rc != 0
# 4단계: Production 배포 (조건부)
- name: Production 배포 승인 대기
pause:
prompt: "Staging 테스트 통과. Production 배포를 진행하시겠습니까? (yes/no)"
register: approval
when: require_approval | default(true)
- name: Production 환경 배포
include_tasks: deploy_to_environment.yml
vars:
target_env: production
hosts_group: production_servers
when: not require_approval or approval.user_input == 'yes'
# deploy/deploy_to_environment.yml
---
- name: "{{ target_env }} 환경 배포 시작"
debug:
msg: "Deploying version {{ deploy_version }} to {{ target_env }}"
- name: 대상 서버에 배포
delegate_to: "{{ item }}"
loop: "{{ groups[hosts_group] }}"
block:
- name: 이전 버전 백업
archive:
path: /var/www/{{ app_name }}
dest: /backup/{{ app_name }}_{{ target_env }}_backup.tar.gz
- name: 새 버전 배포
unarchive:
src: "artifacts/{{ app_name }}-{{ deploy_version }}.tar.gz"
dest: /var/www/{{ app_name }}
- name: 서비스 재시작
systemd:
name: "{{ app_name }}"
state: restarted
- name: 헬스체크
uri:
url: "http://{{ item }}:8080/health"
status_code: 200
register: health
until: health.status == 200
retries: 30
delay: 5
- name: 배포 완료 알림
slack:
token: "{{ slack_token }}"
channel: "#deployments"
msg: "{{ app_name }} {{ deploy_version }} deployed to {{ target_env }}"
when: slack_token is defined
김개발 씨가 드디어 완성된 파이프라인을 앞에 두고 뿌듯해했습니다. 하지만 박시니어 씨는 한 가지 더 조언했습니다.
"파이프라인은 한 번 만들고 끝이 아니에요. 계속 개선해 나가야 해요." 완성된 배포 파이프라인의 구조를 살펴보겠습니다.
1단계: 배포 전 검증은 게이트키퍼 역할을 합니다. 배포 버전이 지정되지 않았거나, 필수 조건이 충족되지 않으면 파이프라인이 즉시 중단됩니다.
이렇게 하면 잘못된 배포가 진행되는 것을 사전에 방지할 수 있습니다. 2단계: Staging 배포는 프로덕션과 동일한 환경에서 새 버전을 먼저 테스트합니다.
Staging 환경은 프로덕션의 축소판이어야 합니다. 가능하면 같은 운영체제, 같은 설정, 비슷한 데이터를 사용해야 합니다.
3단계: 통합 테스트는 Staging에 배포된 애플리케이션이 제대로 동작하는지 확인합니다. 단위 테스트로는 발견하기 어려운 문제들, 예를 들어 서비스 간 통신 문제나 환경 설정 오류 등을 찾아낼 수 있습니다.
4단계: Production 배포는 가장 신중해야 하는 단계입니다. 위 코드에서 pause 모듈을 사용하여 수동 승인을 요구합니다.
자동화도 중요하지만, 프로덕션 배포 전에는 사람의 판단이 필요할 때가 있습니다. include_tasks로 분리한 deploy_to_environment.yml은 환경별 배포 로직을 재사용합니다.
Staging과 Production에서 같은 배포 절차를 사용하면 "Staging에서는 됐는데 Production에서 안 돼요"라는 문제를 줄일 수 있습니다. 배포 알림도 중요합니다.
Slack이나 Teams 같은 협업 도구로 배포 상태를 팀원들에게 알립니다. 누가 언제 어떤 버전을 배포했는지 모든 사람이 알 수 있습니다.
실무에서 파이프라인을 운영하다 보면 여러 개선 포인트가 보입니다. 배포 시간을 줄이기 위해 캐싱을 도입할 수 있습니다.
보안을 강화하기 위해 시크릿 스캔이나 취약점 검사를 추가할 수 있습니다. 품질을 높이기 위해 코드 커버리지 검사를 추가할 수 있습니다.
롤백 전략도 미리 준비해야 합니다. 위 코드에서 이전 버전을 백업하는 이유입니다.
문제가 발생하면 백업에서 빠르게 복구할 수 있습니다. 더 정교한 방법으로는 블루-그린 배포나 카나리 배포가 있습니다.
블루-그린 배포는 두 개의 동일한 환경을 운영하면서 새 버전을 배포할 때 트래픽을 한꺼번에 전환하는 방식입니다. 문제가 생기면 다시 이전 환경으로 전환하면 됩니다.
카나리 배포는 새 버전을 일부 사용자에게만 먼저 노출하는 방식입니다. 전체의 5%에게 새 버전을 보여주고, 문제가 없으면 점진적으로 비율을 높입니다.
김개발 씨 팀은 이 파이프라인을 도입한 후 많은 변화를 경험했습니다. 배포 주기가 월 1회에서 주 3회로 늘었습니다.
배포로 인한 장애는 80% 감소했습니다. 무엇보다 개발자들이 배포를 두려워하지 않게 되었습니다.
"자동화된 파이프라인이 있으니까 자신 있게 코드를 푸시할 수 있어요." 김개발 씨의 말입니다.
실전 팁
💡 - 파이프라인 실행 시간을 모니터링하고 병목 구간을 찾아 개선하세요
- 배포 실패율과 롤백 횟수를 지표로 관리하세요
- 정기적으로 재해 복구 훈련을 실시하여 롤백 절차를 검증하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
vLLM 통합 완벽 가이드
대규모 언어 모델 추론을 획기적으로 가속화하는 vLLM의 설치부터 실전 서비스 구축까지 다룹니다. PagedAttention과 연속 배칭 기술로 GPU 메모리를 효율적으로 활용하는 방법을 배웁니다.
Web UI Demo 구축 완벽 가이드
Gradio를 활용하여 머신러닝 모델과 AI 서비스를 위한 웹 인터페이스를 구축하는 방법을 다룹니다. 코드 몇 줄만으로 전문적인 데모 페이지를 만들고 배포하는 과정을 초급자도 쉽게 따라할 수 있도록 설명합니다.
Sandboxing & Execution Control 완벽 가이드
AI 에이전트가 코드를 실행할 때 반드시 필요한 보안 기술인 샌드박싱과 실행 제어에 대해 알아봅니다. 격리된 환경에서 안전하게 코드를 실행하고, 악성 동작을 탐지하는 방법을 단계별로 설명합니다.
빌드와 배포 자동화 완벽 가이드
Flutter 앱 개발에서 GitHub Actions를 활용한 CI/CD 파이프라인 구축부터 앱 스토어 자동 배포까지, 초급 개발자도 쉽게 따라할 수 있는 빌드 자동화의 모든 것을 다룹니다.
Voice Design then Clone 워크플로우 완벽 가이드
AI 음성 합성에서 일관된 캐릭터 음성을 만드는 Voice Design then Clone 워크플로우를 설명합니다. 참조 음성 생성부터 재사용 가능한 캐릭터 구축까지 실무 활용법을 다룹니다.