[{"data":1,"prerenderedAt":650},["ShallowReactive",2],{"post-document:\u002Fdevops\u002Fci-cd\u002F2026\u002F08\u002F17\u002Fci-cd-04-deployment-operations\u002F":3},{"id":4,"title":5,"body":6,"categories":628,"date":631,"description":632,"extension":633,"image":634,"key_concepts":634,"last_modified_at":634,"legacyPath":635,"meta":636,"navigation":638,"part":639,"path":640,"published":638,"robots":634,"seo":641,"series":642,"stem":643,"strengths":634,"summary":634,"tags":644,"tradeoffs":634,"__hash__":649},"posts\u002Fposts\u002FCI-CD-Docker\u002FCI-CD\u002F2026-08-17-ci-cd-04-deployment-operations.md","CI\u002FCD 기초 4편: 배포 전략과 운영",{"type":7,"value":8,"toc":610},"minimark",[9,14,23,33,36,41,48,84,87,93,96,102,105,109,112,118,121,125,128,131,158,161,167,175,179,182,188,191,195,198,204,207,211,214,220,223,229,232,236,239,245,252,255,259,262,268,271,275,278,364,367,373,376,380,383,399,402,408,412,418,421,425,431,434,527,531,534,540,543,549,553,600,606],[10,11,13],"h2",{"id":12},"배포-후에도-끝은-아니다","배포 후에도 끝은 아니다",[15,16,17,18,22],"p",{},"CI\u002FCD를 처음 접하면 ",[19,20,21],"code",{},"docker run","이 성공한 것을 배포 성공으로 생각하기 쉽다. 하지만 컨테이너 프로세스가 실행됐더라도 Spring Boot 애플리케이션은 정상적으로 동작하지 않을 수 있다.",[24,25,31],"pre",{"className":26,"code":28,"language":29,"meta":30},[27],"language-text","Deploy\n→ Container Start\n→ Health Check\n→ Application 정상 확인\n→ Traffic 연결\n","text","",[19,32,28],{"__ignoreMap":30},[15,34,35],{},"배포 이후에는 위와 같은 확인 과정이 필요하다.",[37,38,40],"h3",{"id":39},"_1-health-check","1. Health Check",[15,42,43,44,47],{},"Spring Boot Actuator를 사용한다면 ",[19,45,46],{},"\u002Factuator\u002Fhealth"," 엔드포인트로 애플리케이션 상태를 확인할 수 있다.",[24,49,53],{"className":50,"code":51,"language":52,"meta":30,"style":30},"language-json shiki shiki-themes github-light github-dark","{\n  \"status\": \"UP\"\n}\n","json",[19,54,55,64,78],{"__ignoreMap":30},[56,57,60],"span",{"class":58,"line":59},"line",1,[56,61,63],{"class":62},"sVt8B","{\n",[56,65,67,71,74],{"class":58,"line":66},2,[56,68,70],{"class":69},"sj4cs","  \"status\"",[56,72,73],{"class":62},": ",[56,75,77],{"class":76},"sZZnC","\"UP\"\n",[56,79,81],{"class":58,"line":80},3,[56,82,83],{"class":62},"}\n",[15,85,86],{},"컨테이너에는 크게 두 가지 상태가 있다.",[24,88,91],{"className":89,"code":90,"language":29,"meta":30},[27],"프로세스가 살아 있음\n애플리케이션이 정상 작동함\n",[19,92,90],{"__ignoreMap":30},[15,94,95],{},"이 둘은 항상 같지 않다. Java 프로세스는 살아 있지만 DB 연결에 실패한 상태일 수도 있다.",[24,97,100],{"className":98,"code":99,"language":29,"meta":30},[27],"HTTP 요청\n→ \u002Factuator\u002Fhealth\n→ DB 연결 등 상태 확인\n→ HTTP 200 OK\n",[19,101,99],{"__ignoreMap":30},[15,103,104],{},"따라서 단순히 컨테이너가 실행 중인지 확인하는 것보다 애플리케이션이 실제 요청을 처리할 수 있는지 검사하는 것이 중요하다.",[37,106,108],{"id":107},"_2-rollback","2. Rollback",[15,110,111],{},"Health Check에서 문제가 발견되면 이전 정상 버전으로 되돌려야 한다. 이때 Docker 이미지의 버전 관리가 도움이 된다.",[24,113,116],{"className":114,"code":115,"language":29,"meta":30},[27],"기존 버전: myapp:1.4.1  → GOOD\n새 버전:   myapp:1.4.2  → ERROR\n\nmyapp:1.4.2 중지\n→ myapp:1.4.1 실행\n→ Health Check\n",[19,117,115],{"__ignoreMap":30},[15,119,120],{},"Registry에 이전 이미지가 남아 있으면 빠르게 Rollback할 수 있다. 따라서 운영 정책과 보관 기간을 정하지 않은 상태에서 이전 이미지를 무조건 삭제하면 안 된다.",[37,122,124],{"id":123},"_3-컨테이너는-수정하지-않고-교체한다","3. 컨테이너는 수정하지 않고 교체한다",[15,126,127],{},"Docker의 중요한 철학 중 하나는 고장 난 컨테이너 내부를 직접 고치는 대신 새 이미지로 컨테이너를 교체하는 것이다.",[15,129,130],{},"컨테이너에 접속해 다음과 같은 작업을 하면 변경 사항을 재현하기 어렵다.",[24,132,136],{"className":133,"code":134,"language":135,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark","vi config\napt install some-package\n","bash",[19,137,138,147],{"__ignoreMap":30},[56,139,140,144],{"class":58,"line":59},[56,141,143],{"class":142},"sScJk","vi",[56,145,146],{"class":76}," config\n",[56,148,149,152,155],{"class":58,"line":66},[56,150,151],{"class":142},"apt",[56,153,154],{"class":76}," install",[56,156,157],{"class":76}," some-package\n",[15,159,160],{},"대신 원인이 되는 설정이나 Dockerfile을 수정하고 새 이미지를 만든다.",[24,162,165],{"className":163,"code":164,"language":29,"meta":30},[27],"Dockerfile 또는 설정 수정\n→ 새 Image 생성\n→ myapp:1.1\n→ 새 Container 실행\n→ 검증 후 기존 Container 제거\n",[19,166,164],{"__ignoreMap":30},[15,168,169,170,174],{},"실행 중인 서버를 계속 수정하는 Mutable Server 방식보다, 변경 사항을 이미지에 반영해 통째로 교체하는 ",[171,172,173],"strong",{},"Immutable Infrastructure"," 방식에 가깝다.",[37,176,178],{"id":177},"_4-production에서-컨테이너-교체하기","4. Production에서 컨테이너 교체하기",[15,180,181],{},"컨테이너 한 개만 운영한다면 다음 교체 과정에서 잠깐 서비스가 끊길 수 있다.",[24,183,186],{"className":184,"code":185,"language":29,"meta":30},[27],"Old Container Stop\n→ New Container Start\n",[19,187,185],{"__ignoreMap":30},[15,189,190],{},"서비스 중단을 줄이기 위해 Rolling, Blue-Green, Canary 같은 배포 전략을 사용한다.",[37,192,194],{"id":193},"_5-rolling-deployment","5. Rolling Deployment",[15,196,197],{},"여러 서버 또는 컨테이너를 하나씩 새 버전으로 교체하는 방식이다.",[24,199,202],{"className":200,"code":201,"language":29,"meta":30},[27],"1단계: A=v1, B=v1, C=v1\n2단계: A=v2, B=v1, C=v1\n3단계: A=v2, B=v2, C=v1\n4단계: A=v2, B=v2, C=v2\n",[19,203,201],{"__ignoreMap":30},[15,205,206],{},"기존 인스턴스를 순차적으로 교체하므로 서비스 중단을 줄이고 리소스를 효율적으로 사용할 수 있다. Kubernetes에서 흔히 사용하는 방식이다.",[37,208,210],{"id":209},"_6-blue-green-deployment","6. Blue-Green Deployment",[15,212,213],{},"현재 운영 중인 Blue 환경 옆에 새 버전인 Green 환경을 별도로 만든다.",[24,215,218],{"className":216,"code":217,"language":29,"meta":30},[27],"BLUE                    GREEN\nv1                      v2\n현재 서비스             새 버전 검증\n",[19,219,217],{"__ignoreMap":30},[15,221,222],{},"Green 검증이 끝나면 Load Balancer가 보낼 대상을 바꾼다.",[24,224,227],{"className":225,"code":226,"language":29,"meta":30},[27],"Users\n→ Load Balancer\n→ GREEN v2\n",[19,228,226],{"__ignoreMap":30},[15,230,231],{},"문제가 발생하면 트래픽을 다시 Blue로 돌릴 수 있어 Rollback이 빠르다. 다만 두 환경을 동시에 유지할 리소스가 필요하다.",[37,233,235],{"id":234},"_7-canary-deployment","7. Canary Deployment",[15,237,238],{},"새 버전을 일부 사용자에게 먼저 제공하는 방식이다.",[24,240,243],{"className":241,"code":242,"language":29,"meta":30},[27],"초기: v1 90%, v2 10%\n확대: v1 50%, v2 50%\n완료:          v2 100%\n",[19,244,242],{"__ignoreMap":30},[15,246,247,248,251],{},"Canary 배포를 사용하는 이유는 모든 서버의 가용성을 높이기 위해서라기보다 ",[171,249,250],{},"새 버전의 위험을 일부 트래픽으로 제한하기 위해서","다. 오류율이나 지연 시간이 나빠지면 전체 사용자에게 영향을 주기 전에 배포를 중단하거나 되돌릴 수 있다.",[15,253,254],{},"대규모 서비스에서는 사용자 그룹, 지역 또는 트래픽 비율을 기준으로 점진적으로 범위를 넓힌다.",[37,256,258],{"id":257},"_8-cicd-pipeline의-stage","8. CI\u002FCD Pipeline의 Stage",[15,260,261],{},"실무에서는 Pipeline을 Stage 단위로 생각하면 이해하기 쉽다.",[24,263,266],{"className":264,"code":265,"language":29,"meta":30},[27],"Source\n  ↓\nTest\n  ↓\nBuild\n  ↓\nImage Build\n  ↓\nImage Push\n  ↓\nDeploy\n  ↓\nHealth Check\n  ↓\nMonitoring\n",[19,267,265],{"__ignoreMap":30},[15,269,270],{},"이것이 Docker 기반 CI\u002FCD의 전체 뼈대다.",[37,272,274],{"id":273},"_9-배포-시스템에서-흐르는-것","9. 배포 시스템에서 흐르는 것",[15,276,277],{},"CI\u002FCD 시스템에서는 다음 항목들이 각 단계를 거쳐 이동하거나 참조된다.",[279,280,281,294],"table",{},[282,283,284],"thead",{},[285,286,287,291],"tr",{},[288,289,290],"th",{},"항목",[288,292,293],{},"예시",[295,296,297,306,316,324,332,340,348,356],"tbody",{},[285,298,299,303],{},[300,301,302],"td",{},"Source Code",[300,304,305],{},"GitHub Repository",[285,307,308,311],{},[300,309,310],{},"Build Artifact",[300,312,313],{},[19,314,315],{},"app.jar",[285,317,318,321],{},[300,319,320],{},"Container Artifact",[300,322,323],{},"Docker Image",[285,325,326,329],{},[300,327,328],{},"Configuration",[300,330,331],{},"Environment Variables",[285,333,334,337],{},[300,335,336],{},"Secrets",[300,338,339],{},"Password, API Key",[285,341,342,345],{},[300,343,344],{},"Runtime Data",[300,346,347],{},"Database Data",[285,349,350,353],{},[300,351,352],{},"Logs",[300,354,355],{},"Application Log",[285,357,358,361],{},[300,359,360],{},"Metrics",[300,362,363],{},"CPU, Memory, Latency, Error Rate",[15,365,366],{},"이들은 성격에 맞게 분리해서 관리해야 한다. 특히 Docker 이미지와 데이터베이스 데이터는 서로 다르다.",[24,368,371],{"className":369,"code":370,"language":29,"meta":30},[27],"Container 삭제\n      │\n      └─ Database Data는 유지\n",[19,372,370],{"__ignoreMap":30},[15,374,375],{},"컨테이너를 삭제해도 데이터가 사라지지 않도록 DB는 Docker Volume이나 외부 데이터베이스를 사용한다.",[37,377,379],{"id":378},"_10-운영에서는-observability가-중요하다","10. 운영에서는 Observability가 중요하다",[15,381,382],{},"배포 이후에는 서비스가 정상적으로 동작하는지 지속해서 관찰해야 한다. 최소한 다음 정보를 확인할 필요가 있다.",[384,385,386,390,393,396],"ul",{},[387,388,389],"li",{},"CPU, Memory, Disk, Network",[387,391,392],{},"Request Count, Latency, Error Rate",[387,394,395],{},"Application Log, Database Connection",[387,397,398],{},"Container Restart Count",[15,400,401],{},"대표적인 도구로 Prometheus, Grafana, ELK, Loki, Datadog, CloudWatch 등이 있다.",[24,403,406],{"className":404,"code":405,"language":29,"meta":30},[27],"CI        → Build\nCD        → Deploy\nOperation → Observe\nProblem   → Fix\ngit push  → 다시 CI\n",[19,407,405],{"__ignoreMap":30},[37,409,411],{"id":410},"_11-devops는-하나의-loop다","11. DevOps는 하나의 Loop다",[24,413,416],{"className":414,"code":415,"language":29,"meta":30},[27],"PLAN\n→ CODE\n→ BUILD\n→ TEST\n→ RELEASE\n→ DEPLOY\n→ OPERATE\n→ MONITOR\n→ PLAN\n",[19,417,415],{"__ignoreMap":30},[15,419,420],{},"Docker는 이 과정에서 Build, Release, Deploy, Operate를 연결해 주는 핵심 기술이다.",[37,422,424],{"id":423},"_12-spring-boot-프로젝트에-적용한다면","12. Spring Boot 프로젝트에 적용한다면",[24,426,429],{"className":427,"code":428,"language":29,"meta":30},[27],"Spring Boot Source Code\n→ git push\n→ GitHub\n→ GitHub Actions Runner\n   ├─ JDK 설치\n   ├─ Gradle Test\n   ├─ bootJar\n   └─ docker build\n→ Docker Image: myapp:a8df329\n→ docker push\n→ Container Registry\n→ Production Server에서 docker pull\n→ Container 실행\n→ Spring Boot\n   ├─ Redis\n   └─ PostgreSQL\n→ Nginx\n→ Internet\n→ User\n",[19,430,428],{"__ignoreMap":30},[15,432,433],{},"핵심 개념을 다시 정리하면 다음과 같다.",[279,435,436,446],{},[282,437,438],{},[285,439,440,443],{},[288,441,442],{},"개념",[288,444,445],{},"핵심 의미",[295,447,448,456,464,472,479,487,495,503,511,519],{},[285,449,450,453],{},[300,451,452],{},"CI",[300,454,455],{},"코드를 지속적으로 통합하고 자동 검증",[285,457,458,461],{},[300,459,460],{},"CD",[300,462,463],{},"검증된 결과물을 서버에 배포",[285,465,466,469],{},[300,467,468],{},"Artifact",[300,470,471],{},"Build 결과물",[285,473,474,476],{},[300,475,323],{},[300,477,478],{},"실행 환경까지 포함한 배포 Artifact",[285,480,481,484],{},[300,482,483],{},"Container",[300,485,486],{},"Image를 실행한 Runtime Instance",[285,488,489,492],{},[300,490,491],{},"Registry",[300,493,494],{},"Docker Image 저장소",[285,496,497,500],{},[300,498,499],{},"Image Tag",[300,501,502],{},"Image 버전 식별자",[285,504,505,508],{},[300,506,507],{},"Immutable",[300,509,510],{},"실행 중인 서버를 수정하지 않고 교체하는 방식",[285,512,513,516],{},[300,514,515],{},"Health Check",[300,517,518],{},"실제 서비스가 정상인지 검사",[285,520,521,524],{},[300,522,523],{},"Rollback",[300,525,526],{},"이전 정상 버전으로 복귀",[37,528,530],{"id":529},"_13-cicd를-공부할-때의-질문","13. CI\u002FCD를 공부할 때의 질문",[15,532,533],{},"Workflow 문법만 외우기보다 다음 흐름을 이해하는 것이 중요하다.",[24,535,538],{"className":536,"code":537,"language":29,"meta":30},[27],"누가 Trigger하는가?\n→ 무엇을 Test하는가?\n→ 무엇을 Build하는가?\n→ 어떤 Artifact가 만들어지는가?\n→ Artifact는 어디에 저장되는가?\n→ 서버는 무엇을 가져오는가?\n→ 어떻게 실행하는가?\n→ 정상인지 어떻게 판단하는가?\n→ 실패하면 어떻게 되돌리는가?\n",[19,539,537],{"__ignoreMap":30},[15,541,542],{},"헷갈린다면 각 도구의 역할을 다음처럼 구분하면 된다.",[24,544,547],{"className":545,"code":546,"language":29,"meta":30},[27],"GitHub          = 코드를 저장\nDocker Registry = 이미지를 저장\nDocker          = 이미지를 Container로 실행\nCI\u002FCD           = 이 전체 이동과 검증을 자동화\n",[19,548,546],{"__ignoreMap":30},[37,550,552],{"id":551},"_14-다음-실습에서-확인할-것","14. 다음 실습에서 확인할 것",[554,555,556,559,565,568,576,579,582,588,594,597],"ol",{},[387,557,558],{},"Spring Boot 애플리케이션 작성",[387,560,561,564],{},[19,562,563],{},".\u002Fgradlew bootJar"," 실행",[387,566,567],{},"Dockerfile 작성",[387,569,570,573,574,564],{},[19,571,572],{},"docker build","와 ",[19,575,21],{},[387,577,578],{},"Docker Hub 또는 GHCR에 Push",[387,580,581],{},"GitHub Actions로 Test, Build, Docker Build, Docker Push 자동화",[387,583,584,585,564],{},"Linux 서버에서 ",[19,586,587],{},"docker pull",[387,589,590,591,593],{},"컨테이너 실행 후 ",[19,592,46],{}," 확인",[387,595,596],{},"v2 이미지 배포",[387,598,599],{},"오류를 발생시킨 뒤 v1으로 Rollback",[24,601,604],{"className":602,"code":603,"language":29,"meta":30},[27],"Git\n→ CI\n→ Artifact\n→ Docker\n→ Registry\n→ Server\n→ Container\n→ Monitoring\n→ Rollback\n",[19,605,603],{"__ignoreMap":30},[607,608,609],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":30,"searchDepth":66,"depth":66,"links":611},[612],{"id":12,"depth":66,"text":13,"children":613},[614,615,616,617,618,619,620,621,622,623,624,625,626,627],{"id":39,"depth":80,"text":40},{"id":107,"depth":80,"text":108},{"id":123,"depth":80,"text":124},{"id":177,"depth":80,"text":178},{"id":193,"depth":80,"text":194},{"id":209,"depth":80,"text":210},{"id":234,"depth":80,"text":235},{"id":257,"depth":80,"text":258},{"id":273,"depth":80,"text":274},{"id":378,"depth":80,"text":379},{"id":410,"depth":80,"text":411},{"id":423,"depth":80,"text":424},{"id":529,"depth":80,"text":530},{"id":551,"depth":80,"text":552},[629,630],"DevOps","CI-CD","2026-08-17 15:00:00 +0900","컨테이너 배포 이후 필요한 Health Check, Rollback, 무중단 배포 전략과 모니터링을 정리한다.","md",null,"\u002Fdevops\u002Fci-cd\u002F2026\u002F08\u002F17\u002Fci-cd-04-deployment-operations\u002F",{"layout":637},"post",true,"4","\u002Fposts\u002Fci-cd-docker\u002Fci-cd\u002F2026-08-17-ci-cd-04-deployment-operations",{"title":5,"description":632},"Docker 기반 CI\u002FCD","posts\u002FCI-CD-Docker\u002FCI-CD\u002F2026-08-17-ci-cd-04-deployment-operations",[645,646,515,523,647,648],"CI\u002FCD","Docker","Deployment","Observability","J3ItcOxMXvyijw_VX3Vtn2DvHOeLQU28RzZHUbG_4nQ",1788744787448]