[{"data":1,"prerenderedAt":615},["ShallowReactive",2],{"post-document:\u002Fci-cd-docker\u002Fkubernetes\u002Fpart-6\u002F":3},{"id":4,"title":5,"body":6,"categories":586,"date":589,"description":590,"extension":591,"image":592,"key_concepts":593,"last_modified_at":592,"legacyPath":598,"meta":599,"navigation":600,"part":601,"path":602,"published":600,"robots":592,"seo":603,"series":604,"stem":605,"strengths":592,"summary":606,"tags":607,"tradeoffs":592,"__hash__":614},"posts\u002Fposts\u002FCI-CD-Docker\u002Fkubernetes\u002F2026-09-07-kubernetes-part-6.md","6. 설정과 저장소, 앱을 운영할 수 있는 상태로 만들기",{"type":7,"value":8,"toc":575},"minimark",[9,14,22,30,34,40,43,131,134,138,144,211,214,217,221,227,288,294,297,301,307,315,321,334,337,341,347,418,440,443,446,450,456,462,465,468,472,478,550,553,557,571],[10,11,13],"h2",{"id":12},"실행된다는-것과-운영할-수-있다는-것은-다르다","실행된다는 것과 운영할 수 있다는 것은 다르다",[15,16,17,21],"p",{},[18,19,20],"strong",{},"운영 가능한 앱은 재시작과 교체를 견디도록 설정, 데이터, 자원과 종료 동작을 준비해야 한다."," Pod가 한 번 Running이 된 것만으로 이 조건이 검증되지는 않는다.",[15,23,24,29],{},[25,26,28],"a",{"href":27},"\u002Fci-cd-docker\u002Fkubernetes\u002Fpart-4\u002F","4편 실험실","은 Pod가 교체되어도 서비스가 이어지는 구조를 보여준다. 이 파트는 그 구조 바깥에서 앱이 지켜야 하는 조건을 정리한다. ConfigMap, Secret, 볼륨과 자원 제한은 본문의 학습 범위이며 모형이 실행하는 기능은 아니다.",[10,31,33],{"id":32},"_1-같은-이미지를-환경별-설정으로-실행한다","1. 같은 이미지를 환경별 설정으로 실행한다",[15,35,36,39],{},[18,37,38],{},"개발, 검증, 운영마다 이미지를 새로 만드는 것보다 검증한 이미지에 환경별 설정을 주입하면 무엇을 배포했는지 추적하기 쉽다."," 앱 코드와 환경에 따라 달라지는 값을 분리하는 이유다.",[15,41,42],{},"ConfigMap은 일반 설정을 키와 값 또는 파일 형태로 담는다. Secret은 민감한 값을 별도 객체로 다룬다. ConfigMap의 값은 문자열이므로 숫자처럼 보이는 값도 문자열로 의도했다면 따옴표를 붙여 표현한다.",[44,45,50],"pre",{"className":46,"code":47,"language":48,"meta":49,"style":49},"language-yaml shiki shiki-themes github-light github-dark","apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: shop-config\ndata:\n  APP_MODE: \"demo\"\n  ORDER_TIMEOUT_MS: \"3000\"\n","yaml","",[51,52,53,70,81,90,101,109,120],"code",{"__ignoreMap":49},[54,55,58,62,66],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"s9eBZ","apiVersion",[54,63,65],{"class":64},"sVt8B",": ",[54,67,69],{"class":68},"sZZnC","v1\n",[54,71,73,76,78],{"class":56,"line":72},2,[54,74,75],{"class":60},"kind",[54,77,65],{"class":64},[54,79,80],{"class":68},"ConfigMap\n",[54,82,84,87],{"class":56,"line":83},3,[54,85,86],{"class":60},"metadata",[54,88,89],{"class":64},":\n",[54,91,93,96,98],{"class":56,"line":92},4,[54,94,95],{"class":60},"  name",[54,97,65],{"class":64},[54,99,100],{"class":68},"shop-config\n",[54,102,104,107],{"class":56,"line":103},5,[54,105,106],{"class":60},"data",[54,108,89],{"class":64},[54,110,112,115,117],{"class":56,"line":111},6,[54,113,114],{"class":60},"  APP_MODE",[54,116,65],{"class":64},[54,118,119],{"class":68},"\"demo\"\n",[54,121,123,126,128],{"class":56,"line":122},7,[54,124,125],{"class":60},"  ORDER_TIMEOUT_MS",[54,127,65],{"class":64},[54,129,130],{"class":68},"\"3000\"\n",[15,132,133],{},"Secret의 base64 표현은 암호화가 아니다. 읽을 권한이 있는 사람은 원래 값을 복원할 수 있다. 저장 시 암호화와 접근 권한 등 실제 보호 설정은 클러스터 구성에 따라 별도로 확인한다. 이름이 Secret이라는 이유만으로 Git에 올려도 안전한 값이 되는 것은 아니다.",[10,135,137],{"id":136},"_2-설정-객체-변경과-앱-반영은-다른-사건이다","2. 설정 객체 변경과 앱 반영은 다른 사건이다",[15,139,140,143],{},[18,141,142],{},"ConfigMap을 수정했다고 이미 실행 중인 프로세스의 환경변수가 자동으로 바뀌지는 않는다."," 값을 어디에 주입했고 앱이 언제 읽는지까지 봐야 한다.",[145,146,147,163],"table",{},[148,149,150],"thead",{},[151,152,153,157,160],"tr",{},[154,155,156],"th",{},"주입 방식",[154,158,159],{},"값이 전달되는 시점",[154,161,162],{},"변경 뒤 필요한 확인",[164,165,166,178,189,200],"tbody",{},[151,167,168,172,175],{},[169,170,171],"td",{},"환경변수",[169,173,174],{},"컨테이너 시작 시",[169,176,177],{},"새 컨테이너가 새 값을 받았는가",[151,179,180,183,186],{},[169,181,182],{},"일반적인 ConfigMap 볼륨",[169,184,185],{},"파일로 마운트, 변경은 지연 후 반영 가능",[169,187,188],{},"파일 갱신과 앱의 재읽기가 모두 이루어졌는가",[151,190,191,194,197],{},[169,192,193],{},"subPath 파일 마운트",[169,195,196],{},"특정 경로를 마운트",[169,198,199],{},"일반 볼륨처럼 자동 갱신되지 않는 제약 확인",[151,201,202,205,208],{},[169,203,204],{},"앱의 외부 설정 조회",[169,206,207],{},"앱의 구현과 조회 정책에 따름",[169,209,210],{},"캐시와 갱신, 실패 처리",[15,212,213],{},"파일만 새로 바뀌고 앱이 시작할 때 한 번 읽은 값을 계속 사용한다면 동작은 그대로다. 반대로 환경변수를 갱신하려고 롤아웃을 했다면 새 Pod가 설정을 읽고 준비됐는지까지 확인한다.",[15,215,216],{},"Deployment는 Pod template이 바뀔 때 새 롤아웃을 만든다. 별도 ConfigMap의 내용만 바꾸는 것은 Pod template 변경과 다르다. 설정 버전이나 해시를 template에 반영하는 방식은 이 둘을 연결하려는 방법이다.",[10,218,220],{"id":219},"_3-데이터는-필요한-수명에-맞는-곳에-둔다","3. 데이터는 필요한 수명에 맞는 곳에 둔다",[15,222,223,226],{},[18,224,225],{},"남겨야 하는 데이터의 수명이 컨테이너나 Pod보다 길다면 저장소를 분리해야 한다."," 단순히 볼륨이 있다는 사실보다 어떤 삭제와 재시작을 견디는지가 중요하다.",[145,228,229,242],{},[148,230,231],{},[151,232,233,236,239],{},[154,234,235],{},"위치",[154,237,238],{},"일반적인 수명 기준",[154,240,241],{},"사용할 데이터의 성격",[164,243,244,255,266,277],{},[151,245,246,249,252],{},[169,247,248],{},"컨테이너의 쓰기 영역",[169,250,251],{},"해당 컨테이너 실행",[169,253,254],{},"사라져도 다시 만들 수 있는 임시 파일",[151,256,257,260,263],{},[169,258,259],{},"emptyDir",[169,261,262],{},"Pod 수명",[169,264,265],{},"같은 Pod의 컨테이너가 공유하는 임시 작업",[151,267,268,271,274],{},[169,269,270],{},"PVC로 연결한 저장소",[169,272,273],{},"볼륨과 반환 정책에 따름",[169,275,276],{},"Pod가 교체되어도 보존할 파일",[151,278,279,282,285],{},[169,280,281],{},"외부 DB, 객체 저장소",[169,283,284],{},"해당 외부 서비스의 정책",[169,286,287],{},"앱 복제본과 독립적으로 관리할 상태",[15,289,290,291,293],{},"컨테이너만 재시작되면 ",[51,292,259],{},"는 남아 있지만 Pod가 삭제되면 사라진다. PVC를 사용하더라도 삭제 정책에 따라 실제 저장소가 제거될 수 있다. 영속 저장소를 사용했다는 사실이 백업을 대신하지는 않는다.",[15,295,296],{},"로그를 stdout과 stderr로 출력하는 것과 로그가 장기 보존되는 것도 별개다. 별도의 수집과 저장 구성이 있어야 Pod가 사라진 뒤에도 조사할 근거가 남는다.",[10,298,300],{"id":299},"_4-pvc는-요청-pv는-볼륨을-나타내는-객체다","4. PVC는 요청, PV는 볼륨을 나타내는 객체다",[15,302,303,306],{},[18,304,305],{},"애플리케이션은 필요한 저장 공간을 PVC로 요청하고, 실제 저장소 제공 방식은 StorageClass와 프로비저너가 맡을 수 있다."," 앱이 모든 디스크 생성 절차를 직접 알 필요를 줄인다.",[44,308,313],{"className":309,"code":311,"language":312,"meta":49},[310],"language-text","Pod의 volume 참조\n  → PVC: 필요한 용량과 접근 모드\n  → StorageClass와 프로비저너: 제공 방식\n  → PV: 연결된 볼륨을 나타내는 객체\n  → 실제 저장소와 마운트\n","text",[51,314,311],{"__ignoreMap":49},[15,316,317,320],{},[51,318,319],{},"WaitForFirstConsumer"," 방식은 사용할 Pod의 배치 조건을 고려할 때까지 볼륨 바인딩이나 생성을 지연한다. 따라서 PVC가 Pending이라는 사실만으로 오류라고 단정하지 않는다. 반대로 계속 진행되지 않는다면 Events, StorageClass, 드라이버와 배치 조건을 확인한다.",[15,322,323,324,327,328],{},"접근 모드는 특히 정확히 구분해야 한다. ",[18,325,326],{},"ReadWriteOnce는 한 Pod만이 아니라 한 Node에서 읽고 쓸 수 있다는 의미다."," 같은 노드의 여러 Pod가 접근할 가능성과 저장소 자체의 제약을 함께 봐야 한다. 한 Pod로 제한하는 의미는 ReadWriteOncePod와 구분한다. ",[25,329,333],{"href":330,"rel":331},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fstorage\u002Fpersistent-volumes\u002F#access-modes",[332],"nofollow","공식 Persistent Volumes 문서",[15,335,336],{},"RWO 저장소를 쓰는 Pod가 서로 다른 Node로 배치되면 멀티 어태치 같은 문제가 생길 수 있다. 공유 파일, 개별 복제본의 데이터, 객체 저장소 중 무엇이 앱의 일관성 요구에 맞는지 먼저 정해야 한다.",[10,338,340],{"id":339},"_5-requests는-배치의-기준이고-limits는-실행의-제한이다","5. requests는 배치의 기준이고 limits는 실행의 제한이다",[15,342,343,346],{},[18,344,345],{},"Scheduler가 자리를 계산하는 값과 실행 중 자원 사용을 제한하는 값은 용도가 다르다."," 둘을 같은 “최대 사용량”으로 읽으면 배치와 장애를 혼동한다.",[44,348,350],{"className":46,"code":349,"language":48,"meta":49,"style":49},"# containers의 app 항목 아래에 들어가는 설정 조각\nresources:\n  requests:\n    cpu: \"250m\"\n    memory: \"512Mi\"\n  limits:\n    cpu: \"1\"\n    memory: \"1Gi\"\n",[51,351,352,358,365,372,382,392,399,408],{"__ignoreMap":49},[54,353,354],{"class":56,"line":57},[54,355,357],{"class":356},"sJ8bj","# containers의 app 항목 아래에 들어가는 설정 조각\n",[54,359,360,363],{"class":56,"line":72},[54,361,362],{"class":60},"resources",[54,364,89],{"class":64},[54,366,367,370],{"class":56,"line":83},[54,368,369],{"class":60},"  requests",[54,371,89],{"class":64},[54,373,374,377,379],{"class":56,"line":92},[54,375,376],{"class":60},"    cpu",[54,378,65],{"class":64},[54,380,381],{"class":68},"\"250m\"\n",[54,383,384,387,389],{"class":56,"line":103},[54,385,386],{"class":60},"    memory",[54,388,65],{"class":64},[54,390,391],{"class":68},"\"512Mi\"\n",[54,393,394,397],{"class":56,"line":111},[54,395,396],{"class":60},"  limits",[54,398,89],{"class":64},[54,400,401,403,405],{"class":56,"line":122},[54,402,376],{"class":60},[54,404,65],{"class":64},[54,406,407],{"class":68},"\"1\"\n",[54,409,411,413,415],{"class":56,"line":410},8,[54,412,386],{"class":60},[54,414,65],{"class":64},[54,416,417],{"class":68},"\"1Gi\"\n",[15,419,420,421,424,425,428,429,432,433,428,436,439],{},"CPU의 ",[51,422,423],{},"1000m","은 CPU 한 단위에 해당한다. ",[51,426,427],{},"Mi",", ",[51,430,431],{},"Gi","는 1024 기반이고 ",[51,434,435],{},"M",[51,437,438],{},"G","는 1000 기반이다. 같은 숫자라도 단위에 따라 다른 양이 된다.",[15,441,442],{},"CPU 제한은 사용 시간을 제한해 지연을 만들 수 있고, 메모리 제한에 도달하면 OOM 종료가 발생할 수 있다. 값은 앱의 기동 구간과 부하 변화, JVM 힙 밖 메모리까지 관찰해 정한다. 예제에 쓰인 수치를 모든 앱의 적정값으로 사용하지 않는다.",[15,444,445],{},"QoS는 자원 설정으로부터 결정된다. BestEffort, Burstable, Guaranteed 구분은 노드 압박 상황을 이해하는 단서지만, 실제 축출 판단은 우선순위와 요청 대비 사용량 등도 함께 고려하므로 QoS 이름 하나로 순서가 항상 고정된다고 보지는 않는다.",[10,447,449],{"id":448},"_6-종료도-정상-요청-처리의-일부다","6. 종료도 정상 요청 처리의 일부다",[15,451,452,455],{},[18,453,454],{},"새 Pod가 Ready가 되는 과정만큼, 기존 Pod가 처리 중 요청을 끝내고 종료하는 과정도 중요하다."," 롤링 업데이트 설정 하나로 모든 요청의 무중단을 보장할 수는 없다.",[44,457,460],{"className":458,"code":459,"language":312,"meta":49},[310],"삭제 요청\n  ├── 연결 대상의 준비\u002F종료 조건 갱신\n  └── kubelet 종료 처리\n        → preStop이 있으면 실행\n        → SIGTERM 전달\n        → 앱이 진행 중 작업을 정리\n        → 유예 시간이 끝나면 강제 종료 가능\n",[51,461,459],{"__ignoreMap":49},[15,463,464],{},"preStop에 쓴 시간도 종료 유예 예산 안에 들어간다. 예를 들어 훅에 5초, 앱 종료 작업에 최대 25초가 필요하다고 가정하면 유예를 20초로 두어서는 정리를 마칠 수 없다. 실제 종료 시간을 측정해 여유를 정해야 한다.",[15,466,467],{},"고정된 sleep 5초를 넣었다는 사실만으로 전파 지연이나 모든 긴 요청이 해결되는 것은 아니다. 요청 시간, keep-alive, 연결 해제, 프록시와 앱의 종료 동작을 함께 검증한다. 관리 포트의 헬스체크가 성공해도 서비스 포트가 정상인지 별도 확인이 필요할 수 있다.",[10,469,471],{"id":470},"_7-배포-성공은-단계별-결과로-확인한다","7. 배포 성공은 단계별 결과로 확인한다",[15,473,474,477],{},[18,475,476],{},"명령 성공, Pod 준비, 실제 사용자 응답을 서로 다른 증거로 기록해야 한다."," 그래야 “배포됐다”는 말이 무엇을 확인한 것인지 분명해진다.",[145,479,480,493],{},[148,481,482],{},[151,483,484,487,490],{},[154,485,486],{},"확인 수준",[154,488,489],{},"확인한 것",[154,491,492],{},"아직 남은 것",[164,494,495,506,517,528,539],{},[151,496,497,500,503],{},[169,498,499],{},"매니페스트 검증",[169,501,502],{},"필드와 일부 정책이 유효함",[169,504,505],{},"이미지 실행과 앱 동작",[151,507,508,511,514],{},[169,509,510],{},"변경 요청 성공",[169,512,513],{},"API가 선언을 받아들임",[169,515,516],{},"조정과 컨테이너 준비",[151,518,519,522,525],{},[169,520,521],{},"롤아웃 완료",[169,523,524],{},"새 template의 복제본이 준비됨",[169,526,527],{},"외부 연결과 업무 기능",[151,529,530,533,536],{},[169,531,532],{},"실제 엔드포인트 호출",[169,534,535],{},"해당 경로의 응답 확인",[169,537,538],{},"부하와 장애, 장시간 상태",[151,540,541,544,547],{},[169,542,543],{},"배포 중 요청 관찰",[169,545,546],{},"교체 구간의 오류와 지연 확인",[169,548,549],{},"다른 장애 상황과 복구 절차",[15,551,552],{},"문제가 생기면 영향 범위, 최근 변경, 복구 가능한 버전과 외부 데이터 호환성을 먼저 파악한다. 이전 앱으로 돌렸는데 DB 스키마가 맞지 않으면 다른 장애가 생길 수 있다. 복구와 함께 Events, 로그, 지표를 남기고 다음 실험과 재발 방지에 연결한다.",[10,554,556],{"id":555},"다시-실험할-지점","다시 실험할 지점",[15,558,559,562,563,565,566,570],{},[18,560,561],{},"다시 실험실로 돌아가면, Ready 수와 Endpoints 수가 운영 조건의 일부라는 점이 더 잘 보일 것이다."," ",[25,564,28],{"href":27},"에서 실패 배포와 롤백을 다시 관찰하거나, ",[25,567,569],{"href":568},"\u002Fci-cd-docker\u002Fkubernetes\u002Fpart-1\u002F","전체 목차","에서 필요한 파트를 골라 읽으면 된다.",[572,573,574],"style",{},"html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":49,"searchDepth":72,"depth":72,"links":576},[577,578,579,580,581,582,583,584,585],{"id":12,"depth":72,"text":13},{"id":32,"depth":72,"text":33},{"id":136,"depth":72,"text":137},{"id":219,"depth":72,"text":220},{"id":299,"depth":72,"text":300},{"id":339,"depth":72,"text":340},{"id":448,"depth":72,"text":449},{"id":470,"depth":72,"text":471},{"id":555,"depth":72,"text":556},[587,588],"CI-CD-Docker","kubernetes","2026-09-07 08:05:00 +0900","ConfigMap과 Secret의 반영 시점, 볼륨의 수명, requests와 limits, 프로브와 종료 처리를 하나의 배포 조건으로 연결한다.","md",null,[594,595,596,597],"ConfigMap \u002F Secret: 일반 설정 \u002F 민감한 값의 분리","PVC \u002F PV \u002F StorageClass: 저장소 요청 \u002F 볼륨 객체 \u002F 제공 방식","requests \u002F limits: 배치를 위한 자원 요청 \u002F 실행 자원 제한","Graceful shutdown: 처리 중 요청을 정리한 뒤 종료","\u002Fci-cd-docker\u002Fkubernetes\u002Fpart-6\u002F",{},true,"6","\u002Fposts\u002Fci-cd-docker\u002Fkubernetes\u002F2026-09-07-kubernetes-part-6",{"title":5,"description":590},"직접 실험하는 Kubernetes","posts\u002FCI-CD-Docker\u002Fkubernetes\u002F2026-09-07-kubernetes-part-6","운영 준비는 같은 이미지를 환경별 설정으로 실행하고, 필요한 데이터를 Pod 수명 밖에 보존하며, 자원과 종료 조건을 맞추는 일이다. 설정 객체가 바뀌는 시점과 앱이 새 값을 쓰는 시점은 다르다. 배포 성공은 실제 준비 상태와 요청 결과로 확인한다.",[608,609,610,611,612,613],"Kubernetes","ConfigMap","Secret","PVC","리소스","운영","zLNG03ZDBEd29Qg5H3lzLC_OjIJNSfL0b4RQqpP-eZs",1788744788275]