<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>namo_o36.log</title>
        <link>https://velog.io/</link>
        <description>백엔드 개발자 나무입니다</description>
        <lastBuildDate>Tue, 23 Sep 2025 15:28:04 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>namo_o36.log</title>
            <url>https://velog.velcdn.com/images/namo_o36/profile/9ead0bce-ce31-4ebb-9d3c-00e281948c8b/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. namo_o36.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/namo_o36" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[사용자 수에 따른 규모 확장성]]></title>
            <link>https://velog.io/@namo_o36/%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%88%98%EC%97%90-%EB%94%B0%EB%A5%B8-%EA%B7%9C%EB%AA%A8-%ED%99%95%EC%9E%A5%EC%84%B1</link>
            <guid>https://velog.io/@namo_o36/%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%88%98%EC%97%90-%EB%94%B0%EB%A5%B8-%EA%B7%9C%EB%AA%A8-%ED%99%95%EC%9E%A5%EC%84%B1</guid>
            <pubDate>Tue, 23 Sep 2025 15:28:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 <strong>*&quot;가상 면접 사례로 배우는 대규모 시스템 설계 기초&quot;*</strong> 를 읽고 정리한 글입니다.
<a href="https://product.kyobobook.co.kr/detail/S000001033116">https://product.kyobobook.co.kr/detail/S000001033116</a></p>
</blockquote>
<hr>
<h2 id="-서버">| 서버</h2>
<h3 id="웹-애플리케이션"><strong>웹 애플리케이션</strong></h3>
<ul>
<li>비즈니스  로직 + 데이터  처리 등을 위한 서버 구현용 언어 사용(Java, Python)</li>
<li>프레젠테이션 용으로 클라이언트 구현용 언어(HTML, JavaScript) 사용</li>
</ul>
<h3 id="모바일-앱"><strong>모바일 앱</strong></h3>
<ul>
<li>모바일 앱 ↔ 웹 서버<ul>
<li>HTTP 프로토콜 이용 → 반환  응답 데이터는 JSON 형태</li>
</ul>
</li>
</ul>
<br>

<hr>
<h2 id="-데이터베이스">| 데이터베이스</h2>
<ul>
<li><strong>웹 계층</strong>(웹/모바일 트래픽 처리)</li>
<li><strong>데이터 계층</strong>(데이터베이스 서버)</li>
</ul>
<br>

<h3 id="어떤-데이터-베이스-사용">어떤 데이터 베이스 사용?</h3>
<p><strong>⇒ 관계형 데이터베이스</strong></p>
<ul>
<li>MySQL, Oracle, PostgreSQL</li>
<li>자료를 테이블, 열, 칼럼으로 표현</li>
<li>테이블 관계 <code>JOIN</code>가능</li>
</ul>
<p><strong>⇒ 비-관계형 데이터베이스(NoSQL)</strong></p>
<ul>
<li>CouchDB, Neo4j, Cassandra, Amazon DynamoDB</li>
<li>일반적으로 JOIN 연산은 지원 x</li>
</ul>
<p><strong>분류</strong></p>
<ul>
<li>Key-value 저장소</li>
<li>그래프 저장소</li>
<li>칼럼 저장소</li>
<li>문서 저장소</li>
</ul>
<blockquote>
<p>💡 <strong>아래의 경우 비-관계형 데이터베이스가 더 좋음</strong></p>
</blockquote>
<ul>
<li>응답 지연 시간이 낮아야할 때</li>
<li>데이터가 비정형인 경우</li>
<li>데이터를 직렬화 하거나 역직렬화할 수 있기만 하면 됨</li>
<li>아주 많은 양의 데이터를 저장해야 함</li>
</ul>
<blockquote>
<p>💡<strong>데이터 직렬화</strong>
    - 객체, 자료구조, 메모리에 있는 데이터를 하나의 바이트 스트림(문자열, JSON, BSON 등)으로 변환하는 과정
    - ex) 자바 객체 → JSON 문자열 <br>
💡<strong>데이터 역직렬화</strong>
    - 직렬화된 데이터를 다시 원래의 객체나 자료구조 형태로 복원
    - ex) JSON 문자열 → 자바 객체</p>
</blockquote>
<br>

<hr>
<h2 id="-수직적-규모-확장-vs-수평적-규모-확장">| 수직적 규모 확장 vs 수평적 규모 확장</h2>
<h3 id="수직적-규모-확장">수직적 규모 확장</h3>
<ul>
<li>서버에 고사양 자원(CPU,  더 많은 RAM)을 추가</li>
<li>서버에 들어오는 트래픽이 적을 때 좋은 선택</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>한 대의 서버에 CPU나 메모리를 무한대로 증설할 수는 없음</li>
<li>장애  자동 복구나 다중화 방안을 제시하지 않음 → 서버 장애 시  웹/앱은 완전히 중단됨</li>
</ul>
<h3 id="수평적-규모-확장">수평적 규모 확장</h3>
<ul>
<li>더 많은 서버를 추가하여 성능 개선</li>
<li>대규모 애플리케이션 지원에 적절</li>
<li>대규모 사용자접속  → 부하 분산기, 로드 밸런서 도입이 최선</li>
</ul>
<p><strong>로드 밸런서</strong></p>
<ul>
<li>부하 분산  집합에 속한 웹 서버들에게 트래픽 부하를 고르게 분산하는 역할</li>
<li>사용자가 로드밸런서의 공개 IP로 접속 → 웹 서버는 클라이언트 접속을 직접 처리하지 않음</li>
<li>서버간 통신에는 private IP 주소(같은 네트워크 내에 속한 서버 사이 통신에만 사용할 수 있는 IP 주소) 사용, 인터넷 접속 불가</li>
<li>웹 계층 가용성 향상</li>
</ul>
<p><strong>데이터베이스 다중화</strong></p>
<ul>
<li><strong>master-slave 구조</strong><ul>
<li><strong>MasterDB</strong> - 쓰기 연산만 지원 (<code>insert</code>, <code>delete</code>, <code>update</code> 등)</li>
<li><strong>SlaveDB</strong> - 읽기 연산만 지원</li>
</ul>
</li>
<li>대부분의 DB는 쓰기 연산보다 <strong>읽기 연산</strong>이 더 많음 → 일반적으로 SlaveDB가 MasterDB보다 수가 더 많음</li>
<li><strong>성능 향상</strong> / <strong>안정성</strong> - 서버 일부 파괴 시에도 데이터 보존 / <strong>가용성</strong> - 한 서버의 장애에도 서비스 지속 가능</li>
</ul>
<hr>
<h2 id="-캐시">| 캐시</h2>
<ul>
<li>값 비싼 연산 결과나 자주 참조되는 데이터를  메모리 안에 두고 빨리 처리될 수 있도록 하는 저장소</li>
<li>웹 페이지의 새로 고침 →  매 번 데이터베이스 호출이 발생 → 애플리케이션 성능은 데이터 베이스를 얼마나 자주 호출하는지에 따라 크게 좌우됨</li>
</ul>
<h3 id="캐시-계층">캐시 계층</h3>
<ul>
<li><p>데이터가 잠시 보관되는 곳 - DB 보다 훨씬 빠름</p>
</li>
<li><p>ex) 웹 서버 ↔ 캐시 ↔ 데이터베이스</p>
<ul>
<li>요청 받은 웹 서버가 캐시에 응답을 찾음 → 저장되어 있으면 그대로 반환</li>
<li>없으면 DB에 가서 응답을 찾아서 반환</li>
<li><em>⇒ 읽기 주도형 캐시 전략(read-through caching strategy)*</em></li>
</ul>
</li>
<li><p>캐시 데이터 종류, 크기, 액세스 패턴에 맞는 캐시 전략을 선택해 사용하면 됨</p>
</li>
</ul>
<h3 id="캐시-사용-시-주의할-점">캐시 사용 시 주의할 점</h3>
<ul>
<li><p>캐시 서버는 일반적으로 <strong>프로그래밍 언어</strong>로 API를 제공</p>
</li>
<li><p>데이터 갱신이 자주 일어나지 않지만 <strong>참조가 빈번하게 일어나는 경우</strong></p>
</li>
<li><p>영속적으로 보관할 데이터는 캐시에 두지 않는 편이 나음. → 캐시는 <strong>휘발성 저장소</strong>이기 때문</p>
</li>
<li><p>캐시 데이터의 <strong>만료 정책</strong>도 마련해야 함. →  너무 짧으면 성능이 떨어지고 너무 길면 원본이랑 차이 나겠지</p>
</li>
<li><p><strong>데이터 원본과 캐시 사본이 동일한지</strong>도 확인해야 함 →  여러 지역에 걸쳐 시스템을 확장할 경우 캐시와 저장소 사이 일관성 유지는 매우 어려움</p>
</li>
<li><p><strong>캐시 서버의 장애</strong>에는 어떻게 대처할건지? → 서버가 한 대 있는 경우는 <strong>단일 장애 지점(Single Point of Failure, SPOF)</strong>가 될 수 있음</p>
</li>
<li><p><strong>캐시 메모리</strong>를 얼마나 크게 잡을지? → 너무 작으면 데이터가 캐시에서 밀려날 수도 있음 → 캐시 메모리의 과할당하면 됨
⇒ <strong>무조건적인 과할당</strong>은 운영 비용적인 문제나 캐시 만료에 있어 불필요한 오버헤드 발생 가능성이 있음
  ⇒ <strong>예상되는 워크로드 + 캐시 정책 + 모니터링</strong>으로 안정적으로 커버될 정도로만 크면 됨</p>
</li>
<li><p>캐시가 가득 차면 기존 캐시 데이터를 방출해야 함 → 뭘 내보낼건지?</p>
<ul>
<li><strong>LRU</strong> - 마지막으로 사용된 시점이 가장 오래된 데이터</li>
<li><strong>LFU</strong> - 사용 빈도가 가장  낮은 데이터</li>
<li><strong>FIFO</strong> - 가장 먼저 들어온 애 내보내기</li>
</ul>
</li>
</ul>
<br>

<hr>
<h2 id="-컨텐츠-전송-네트워크cdn">| 컨텐츠 전송 네트워크(CDN)</h2>
<ul>
<li>정적 컨텐츠 전송에 쓰이는 지리적으로 분산된 서버의 네트워크</li>
<li>이미지, 비디오, CSS, JavaScript 파일 캐시 가능</li>
</ul>
<p><strong>동작 과정</strong></p>
<ol>
<li>사용자가 CDN이 제공하는 URL로 이미지에 접근</li>
<li>이미지가 CDN에 없으면 CDN이 원본 서버에 요청</li>
<li>원본 서버 응답의 HTTP 헤더에 TTL(만료 기한) 값이 들어 있음</li>
<li>CDN 서버가 파일을 캐시하고 사용자에게 반환</li>
</ol>
<h3 id="cdn-사용-시-고려할-점">CDN 사용 시 고려할 점</h3>
<ul>
<li>비용 → CDN으로 들오가고 나가는 데이터 전송 양에 따라 요금을 냄. 자주 사용되지 않는 컨텐츠는 캐싱하지 않는 편이 비용적으로 이득</li>
<li>적절한  만료 기간 설정<ul>
<li>너무 짧으면 자주 원본 서버에 요청해야 함</li>
<li>너무 길면 컨텐츠 신선도 저하</li>
</ul>
</li>
<li>CDN 장애 발생<ul>
<li>CDN 서버 장애 발생 시 → 문제 감지 후 원본 서버로  직접 컨텐츠를 가져오도록 클라이언트 구성이 필요</li>
</ul>
</li>
<li>컨텐츠 무효화<ul>
<li>CDN 사업자 제공 API를 이용한 컨텐츠 무효화</li>
<li>새로운 버전을 서비스 하도록 오브젝트 버저닝(Object Versioning) 이용</li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<ul>
<li><strong>캐시</strong> = 데이터 접근 속도를 올리기 위한 임시 저장소</li>
<li><strong>CDN</strong> = 지리적 분산을 통해 정적 컨텐츠를 빠르게 전달하는 시스템 (결과적으로 캐시를 활용함)</li>
<li>⇒ CDN도 캐시를 사용하지만, 단순 캐시보다 범위가 넓고 지리적 분산이 있다는 점이 핵심 차이*</li>
</ul>
<br>

<hr>
<h2 id="-무상태-웹-계층">| 무상태 웹 계층</h2>
<ul>
<li>웹 계층의 수평적 확장 방법 → 더 많은 웹 서버 증설</li>
<li>상태 정보(사용자 세션 데이터)를 웹 계층에서 제거해야 함.  → 상태 정보를 관계형 DB나 NoSQl 등의 지속성 저장소에 보관하고 필요할 때 가져오는 것<ul>
<li>상태 정보가 한 서버에만 묶여 있으면 서버 증설 의미가 없음 → 한 서버에 묶여 있으면 서버를 늘려도 정보가 저장되어 있는 서버로만 접속해야 하잖아</li>
</ul>
</li>
<li>사용자의 HTTP 요청은 어떤 웹 서버로도 전송 가능하게 만들고, → 이걸 <strong>공유 저장소</strong>에서 데이터를 가져오도록 설정함</li>
<li>세션 데이터를 noSQL이나 캐시 시스템 등으로 분리 → 트래픽 양에 따라 웹 서버를 자동으로 추가하거나 삭제</li>
</ul>
<blockquote>
<p><strong>세션</strong> = 사용자 정보 파일을 <strong>서버</strong>에서 관리
<strong>쿠키</strong> = <strong>클라이언트</strong>에 저장되는 Key-Value 데이터</p>
</blockquote>
<br>


<hr>
<h2 id="-데이터-센터">| 데이터 센터</h2>
<ul>
<li>서버, 스토리지, 네트워크 장비, 보안 시스템 등</li>
<li>IT 시스템을 집중적으로 배치하고 운영하는 시설</li>
</ul>
<h3 id="지리적-라우팅"><strong>지리적 라우팅</strong></h3>
<ul>
<li>장애가 없는 상황에서 가장 가까운 데이터 센터로 안내됨</li>
<li>geoDNS - 사용자 위치에 따라 도메인명을 어떤 IP로 변환할지 결정하도록 돕는 DNS</li>
<li>데이터 센터 장애 발생 시 → 장애가 없는 다른 데이터 센터로 전송</li>
</ul>
<h3 id="다중-데이터-센터-아키텍처">다중 데이터 센터 아키텍처</h3>
<ul>
<li><strong>트래픽 우회</strong><ul>
<li>올바른 데이터 센터로 트래픽을 전송하는 효과적인 방법을 찾아야 함</li>
<li>geoDNS는 사용자의 가장 가까운 데이터 센터로 트래픽을 보낼 수 있도록 함</li>
</ul>
</li>
<li><strong>데이터 동기화</strong><ul>
<li>데이터를 여러 데이터 센터에 걸쳐 다중화</li>
</ul>
</li>
<li><strong>테스트와 배포</strong><ul>
<li>여러 데이터센터를 사용하도록 시스템이 구성되었다면 → 여러 위치에서 테스트해 보는 것이 중요</li>
<li>자동화 배포 도구는 모든 데이터 센터에 동일한 서비스가 설치되도록 해야함</li>
</ul>
</li>
</ul>
<p>→ 시스템의 확장을 위해서는 시스템을 컴포넌트로 분리 → 각각이 독립적으로 확장될 수 있도록 해야 함.</p>
<p><strong>→ 메시지 큐</strong>는 실제 분산 시스템의 핵심 전략 중 하나</p>
<br>

<hr>
<h2 id="-메시지-큐">| 메시지 큐</h2>
<ul>
<li><strong>비동기 통신</strong>을 지원하는 컴포넌트<ul>
<li>메시지 무손실 - 메시지 큐에 보관된 메시지는 소비자가 꺼내기 전까지 안전함</li>
</ul>
</li>
<li><strong>비동기적</strong>으로 전송</li>
<li><strong>기본 아키텍처</strong><ul>
<li><strong>생산자</strong>(입력 서비스) → 메시지 생성, 메시지 큐에 발행</li>
<li><strong>소비자</strong>(구독자) → 메시지를 받아 그에 맞는 동작을 수행</li>
</ul>
</li>
<li><strong>서비스 또는 서버 간 결합이 느슨해짐</strong><ul>
<li><strong>생산자 다운</strong> → 소비자는 메시지 수신 가능</li>
<li><strong>소비자 다운</strong> → 메시지 발행 가능</li>
</ul>
</li>
<li>요청을 받는 <strong>웹서버</strong>, 받은 요청을 작업하는  <strong>작업 서버</strong> 두 개로 물리적으로 나누고, 그 사이에서 연결 역할을 함. 일종의 <strong>편지함/대기열</strong></li>
</ul>
<br>

<hr>
<h2 id="-로그-메트릭-자동화">| 로그, 메트릭, 자동화</h2>
<h3 id="로그">로그</h3>
<ul>
<li>에러 로그 모니터링</li>
<li>서버 단위 / 서비스 단위</li>
</ul>
<h3 id="메트릭">메트릭</h3>
<p>시스템 상태를 수치화한 데이터</p>
<ul>
<li>호스트 단위 메트릭 - CPU, 메모리, 디스크 I/O에 관한 메트릭</li>
<li>종합 메트릭 - 데이터 베이스 계층 성능, 캐시 계층의 성능</li>
<li>핵심 비즈니스 메트릭 - 일별 능동 사용자, 수익, 재방문 등</li>
</ul>
<h3 id="자동화">자동화</h3>
<ul>
<li>생산성을 높이기 위한 자동화 도구 활용</li>
<li>CI 도구 → 검증 절차 자동 /  빌드, 테스트, 배포 등 절차 자동화 가능</li>
</ul>
<br>

<hr>
<h2 id="-데이터-베이스-규모-확장">| 데이터 베이스 규모 확장</h2>
<h3 id="수직적-확장scale-up">수직적 확장(Scale-up)</h3>
<ul>
<li>고성능 자원증설(CPU, RAM, 디스크 등)</li>
</ul>
<p><strong>약점</strong></p>
<ul>
<li>서버 HW는 한계가 있음 → CPU, RAM의 무한 증설 x</li>
<li>SPOF로 인한 위험성 증가</li>
<li>비용이 많이 듦</li>
</ul>
<h3 id="수평적-확장sharding">수평적 확장(Sharding)</h3>
<ul>
<li>서버 증설</li>
<li>대규모 DB를 <strong><em>Shard</em></strong>라는 작은 단위로 분할하는 기술</li>
</ul>
<p><strong>샤드(Shard)</strong></p>
<ul>
<li>샤드는 같은 스키마를 사용하지만, 보관되는 데이터 사이에는 중복이 없음<ul>
<li>ex) user_id 를 4로 나눈 나머지가 0이면 0번 샤드에,  1이면 1번 샤드에 …</li>
</ul>
</li>
<li>샤딩 키 전략(Sharding Key)<ul>
<li>= partition key</li>
<li>데이터 분산 전략을 정하는 하나 이상의 칼럼(ex. user_id 등)</li>
</ul>
</li>
</ul>
<p><strong>샤딩 문제점</strong></p>
<ul>
<li><strong>데이터 재 샤딩(Resharding)</strong><ul>
<li>하나의 샤드로 감당이 안될 때</li>
<li>샤드 간 데이터 불균형(샤드 소진 속도가 다른 경우)</li>
</ul>
</li>
<li><strong>유명 인사 문제(Celebrity, hotspot key)</strong><ul>
<li>특정샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제</li>
</ul>
</li>
<li><strong>조인과 비정규화</strong><ul>
<li>한 DB를  여러 샤드로 나누면 여러 샤드에 걸친 데이터를 조인하기 힘들 수 있음</li>
<li>데이터베이스 비정규화 → 한 테이블에서 질의가 수행되도록 해야 함</li>
</ul>
</li>
</ul>
<br> 

<hr>
<h2 id="-사용자-규모-확장">| 사용자 규모 확장</h2>
<ul>
<li>시스템 규모 확장은 지속적이고 반복적</li>
<li><strong>정리</strong><ul>
<li>웹 계층은 무상태</li>
<li>모든 계층의 다중화</li>
<li>가능한 한 많은 데이터 캐시</li>
<li>여러 데이터 센터 지원</li>
<li>정적 컨텐츠는 CDN을 통해 서비스</li>
<li>데이터 계층은  샤딩을 통한 규모 확장</li>
<li>계층은 독립적 서비스로분할</li>
<li>시스템의 지속적 모니터링, 자동화 도구 활용</li>
</ul>
</li>
</ul>
<br> 

<hr>
<h2 id="-개략적-규모-추정">| 개략적 규모 추정</h2>
<ul>
<li><p>얼마나 많은 요청을 처리해야 하는지, 얼마나 데이터를 저장해야 하는지, 얼마나 많은 서버가 필요한지를 개략적으로 추정하는 것 ⇒ 시스템 용량이나 성능 요구 사항을  개략적으로 추정</p>
</li>
<li><p>초기 설계 단계에서 <strong>과부하 방지</strong>, <strong>비용 효율적 설계</strong>, <strong>확장성 확보</strong>를 위해 수행 → 완벽한 값이 아닌 근사치 구하는게 목적</p>
</li>
<li><p><strong>개략적인 규모 추정</strong>은 보편적으로 통용되는 성능  수치에서 사고 실험을 행해 추정치를 계산</p>
<p>  ⇒ 어떤 설계가 요구사항에 부합할지 보기 위한 것</p>
</li>
</ul>
<h3 id="주요-지표">주요 지표</h3>
<ul>
<li><strong>QPS (Queries Per Second)</strong><ul>
<li>초당 요청 수</li>
</ul>
</li>
<li><strong>저장소 요구량</strong><ul>
<li>한 사용자당 데이터 크기 x 사용자 수 +(로그, 이미지, 백업 등 고려하여 여유율 추가)</li>
</ul>
</li>
<li><strong>캐시 요구량</strong><ul>
<li>전체 데이터 중 얼마나 자주 조회될지</li>
</ul>
</li>
<li><strong>서버 수</strong><ul>
<li>서버 수 = (최대 QPS / 서버 1대 처리 능력) × 여유율</li>
</ul>
</li>
</ul>
<h3 id="2의-제곱수">2의 제곱수</h3>
<ul>
<li>데이터 볼륨의 단위를 2의 제곱수로 표현할 경우</li>
<li><table>
<thead>
<tr>
<th><strong>2의 x 제곱</strong></th>
<th><strong>근사치</strong></th>
<th><strong>이름</strong></th>
<th><strong>축약형</strong></th>
</tr>
</thead>
<tbody><tr>
<td>10</td>
<td>1,000(천)</td>
<td>1킬로바이트</td>
<td>1KB</td>
</tr>
<tr>
<td>20</td>
<td>1000,000(백만)</td>
<td>1메가바이트</td>
<td>1MB</td>
</tr>
<tr>
<td>30</td>
<td>1000,000,000(10억)</td>
<td>1기가바이트</td>
<td>1GB</td>
</tr>
<tr>
<td>40</td>
<td>1조</td>
<td>1테라바이드</td>
<td>1TB</td>
</tr>
<tr>
<td>50</td>
<td>1000조</td>
<td>1페타바이트</td>
<td>1PB</td>
</tr>
</tbody></table>
</li>
</ul>
<br>

<hr>
<h2 id="-시스템-설계">| 시스템 설계</h2>
<h3 id="요구사항-이해">요구사항 이해</h3>
<ul>
<li>구체적으로 어떤 기능을 만들어야 하는지?</li>
<li>제품 사용자 수가 얼마나 되는지?</li>
<li>회사 규모는 얼마나 빨리 커질지 예상하는지? 이후의 규모가 어떻게 될지 어떻게 예상하는지?</li>
<li>회사가 주로 사용하는 기술 스택은 무엇인지?</li>
<li>설계 단순화를 위해 활용할 수 있는 기존 서비스는 어떤 것들이 있는지?</li>
</ul>
<br>

]]></description>
        </item>
        <item>
            <title><![CDATA[Thread Pool & Executor Framework]]></title>
            <link>https://velog.io/@namo_o36/Thread-Pool-Executor-Framework</link>
            <guid>https://velog.io/@namo_o36/Thread-Pool-Executor-Framework</guid>
            <pubDate>Sun, 21 Sep 2025 14:31:19 GMT</pubDate>
            <description><![CDATA[<h2 id="스레드-풀thread-pool">스레드 풀(Thread Pool)</h2>
<hr>
<h3 id="스레드-직접-사용">스레드 직접 사용</h3>
<ul>
<li><p><strong>스레드 생성 비용으로 인한 성능 문제</strong> → 스레드는 매우 무거운 객체</p>
<ul>
<li>자신만의 호출 스택을 가지고 있어야 함 → 호출 스택을 위한 메모리 할당</li>
<li>운영체제 자원 사용 → CPU와 메모리 리소스를 소모하는 작업</li>
<li>운영체제 스케줄러 설정 → 스레드 관리, 실행 순서 조정에 추가 오버헤드 발생 가능함</li>
<li>가벼운 작업은 작업 실행 시간보다 스레드 생성 시간이 더 오래 걸릴 수 있음</li>
</ul>
</li>
<li><p><strong>스레드 관리 문제</strong></p>
<ul>
<li>메모리 자원, 서버 CPU가 한정되어 있음 → 스레드는 무한히 만들 수 없음</li>
<li>최대 스레드 수까지만 스레드를  생성하고 관리할 수 있어야 함</li>
<li>스레드 종료 시에도 남은 작업을 모두 수행하고 종료되어야 함 → 이 경우도 어딘가에서 스레드를 관리해줘야 함</li>
</ul>
</li>
<li><p><strong>Runnable 인터페이스의 불편함</strong></p>
<ul>
<li>반환 값이 없음</li>
<li>예외 처리 → 체크 예외의 처리는 메서드 내부에서 처리해야 함</li>
</ul>
</li>
</ul>
<h3 id="스레드-풀pool">스레드 풀(pool)</h3>
<ul>
<li>스레드를 필요한 만큼 미리 만들어둠 → 작업 요청 시 스레드를 하나 조회해 처리한 뒤, 스레드를 다시 반납</li>
<li>스레드 보관, 재사용  가능 + 스레드 생성 시간 절약</li>
<li><strong><code>Executor</code></strong> → 자바가 제공하는 프레임 워크 / 스레드 풀,  스레드 관리, 생산자 소비자 문제 해결</li>
</ul>
<h3 id="executor-프레임워크">Executor 프레임워크</h3>
<ul>
<li>멀티 스레딩 및 병렬 처리를 돕는 기능 모음</li>
<li>작업 실행 관리 및 풀 관리를 효율적으로 처리, 개발자가 직접 스레드 생성 및 관리까지</li>
</ul>
<br>

<h2 id="future">Future</h2>
<hr>
<h3 id="runnable-vs-callable">Runnable vs Callable</h3>
<p><strong>➡️ Runnable</strong> </p>
<ul>
<li>반환 타입 <code>void()</code></li>
<li>예외가 선언되어 있지 않음 → 체크 예외를 던질 수 없음 (런타임 예외는 제외)</li>
</ul>
<p><strong>➡️ Callable</strong></p>
<ul>
<li><code>concurrent</code>에서 제공되는 기능</li>
<li>반환 타입이 제네릭 <code>V</code> 임. → 값의 반환 가능</li>
<li><code>throws Exception</code> 예외가 선언되어 있음 → 해당  인터페이스를 구현하는 모든 메서드는 체크 예외인 <code>Exception</code>과 그 하위 예외를 모두 던질  수 있음</li>
</ul>
<h3 id="future-사용">Future 사용</h3>
<ul>
<li><code>ExecutorService es = new ThreadPoolExecutor(1,1,0, TimeUnit.MILLISECONDS, new
LinkedBlockingQueue&lt;&gt;());</code><ul>
<li><code>corePoolSize</code>: 스레드 풀에서 관리되는 기본 스레드 수</li>
<li><code>maximumPoolSize</code>: 스레드 풀에서 관리되는 최대 스레드 수</li>
<li><code>keepAliveTime</code>: 기본 스레드 수 초과해서 만들어진 스레드가 생존할 수 있는 대기 시간</li>
<li><code>BlockingQueue</code>: 작업을 보관 → 생산자, 소비자 문제를 해결 → 작업을 무한대로 저장 가능</li>
</ul>
</li>
<li><code>ExecutorService es = Executors.newFixedThreadPool(1);</code> : 편의 코드</li>
<li><code>Future</code>는 전달한 작업의 미래 결과를 담고 있다고 생각하면 됨</li>
</ul>
<blockquote>
<p>💡 <strong>왜 Future를 써야 할까?</strong></p>
</blockquote>
<ul>
<li>요청스레드가 작업을 요청한 뒤 다른 작업 수행 가능</li>
<li>요청 스레드가 필요한 요청을 전부 한 다음에 <code>get()</code>을 통해 블로킹 상태로 대기하면서 결과를 받으면 된다.</li>
</ul>
<br>

<h3 id="future-메소드">Future 메소드</h3>
<ul>
<li><code>boolean cancel(boolean mayInterruptIfRunning)</code><ul>
<li>작업이 실행중이 아니거나 시작 전인 경우 취소, 실행 중인 경우 <code>mayInterruptIfRunning</code>이 <code>true</code>면 중단</li>
<li>작업이 성공적으로 취소된 경우 <code>true</code>, 이미 완료되었거나 취소할 수 없으면 <code>false</code></li>
</ul>
</li>
<li><code>boolean isCancelled()</code><ul>
<li>작업이 취소되었는지 여부 확인</li>
</ul>
</li>
<li><code>boolean isDone()</code><ul>
<li>작업이 완료되었는지 여부 확인</li>
</ul>
</li>
<li><code>State state()</code><ul>
<li>상태 반환(<code>RUNNING</code> , <code>SUCCESS</code> , <code>FAILED</code> , <code>CANCELLED</code>)</li>
</ul>
</li>
<li><code>V get()</code><ul>
<li>작업 완료까지 대기, 완료되면 결과 반환</li>
<li><strong>예외</strong><ul>
<li><code>InterruptedException</code> : 대기 중인 스레드에 인터럽트 발생</li>
<li><code>ExecutionException</code> : 작업 계산 중 예외 발생</li>
</ul>
</li>
</ul>
</li>
<li><code>V get(long timeout, TimeUnit unit)</code><ul>
<li>시간 초과 시 예외 발생</li>
<li><strong>예외</strong><ul>
<li><code>InterruptedException</code> : 대기 중인 스레드에 인터럽트 발생</li>
<li><code>ExecutionException</code> : 작업 계산 중 예외 발생</li>
<li><code>TimeoutException</code> : 주어진 시간 내에 작업 완료되지 않음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="future-취소">Future 취소</h3>
<ul>
<li><code>future.cancel(true)</code> : Future를 취소 상태로 변경 → 작업이 실행중인 경우 인터럽트 발생, 작업 중단</li>
<li><code>future.cancel(false)</code> : Future를 취소 상태로 변경 → 이미 실행중인 작업은 중단하지 않음</li>
</ul>
<h3 id="future-예외">Future 예외</h3>
<ul>
<li><code>Future.get()</code> 는 작업의 결과를 받을 수도, 예외를  받을 수도 있음</li>
</ul>
<br>

<h2 id="executor">Executor</h2>
<hr>
<h3 id="executorservice">ExecutorService</h3>
<ul>
<li><code>Executor</code> 인터페이스 확장, 작업의 제출과 제어 기능 추가 제공</li>
<li><code>ExecutorService</code> 인터페이스의 기본 구현체는 <code>ThreadPoolExecutor</code></li>
<li>주요 메서드 : <code>execute()</code> - 반환값 x,  <code>submit()</code> - 반환값 o, <code>invokeAll()</code>, <code>invokeAny()</code>, <code>shutdown()</code>, <code>close()</code> - <code>ExecutorService</code> 종료</li>
</ul>
<h3 id="executorservice---작업-컬렉션-처리">ExecutorService - 작업 컬렉션 처리</h3>
<ul>
<li><code>invokeAll()</code> : 모든 <code>Callable</code> 작업을 제출, 모든 작업이 완료될 때까지  기다림</li>
<li><code>invokeAny()</code> : 가장 먼저 완료된 작업 결과 반환, 나머지는 전부 취소</li>
</ul>
<h3 id="executorservice--종료">ExecutorService- 종료</h3>
<ul>
<li><strong>서비스 종료</strong><ul>
<li><code>void shutdown()</code> : 새로운 작업을 받지 않고 제출된 작업을 모두 완료 후 종료</li>
<li><code>List&lt;Runnable&gt; shutdownNow()</code> : 실행 중인 작업 모두 중단(인터럽트  발생) / 큐를 비우면서 큐에 담긴 작업을 꺼내 컬렉션으로 반환</li>
</ul>
</li>
<li><strong>서비스 상태 확인</strong><ul>
<li><code>boolean isShutdown()</code></li>
<li><code>boolean isTerminated()</code></li>
</ul>
</li>
<li><strong>작업 완료 대기</strong><ul>
<li><code>boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException</code> : 서비스 종료 시 모든 작업이 완료될 때까지 대기</li>
</ul>
</li>
<li><code>close()</code><ul>
<li><code>shutdown()</code>을 호출하고, 작업이 완료되거나 인터럽트가 발생할 때 까지 무한정 반복 대기</li>
</ul>
</li>
</ul>
<h3 id="executor-스레드-풀-관리">Executor 스레드 풀 관리</h3>
<ul>
<li>작업 요청 시 core 사이즈 만큼 스레드 생성</li>
<li>core 사이즈 초과 시 작업을 큐에 넣음</li>
<li>큐를 초과할 경우 max  사이즈 만큼 스레드 생성 → <strong>“초과 스레드”</strong></li>
<li>max 사이즈도 초과할 경우 예외가 발생함</li>
</ul>
<br>

<h3 id="executor-전략">Executor 전략</h3>
<p>➡️ <strong>고정 스레드 풀 전략</strong></p>
<pre><code class="language-java">    ExecutorService es = Executors.newFixedThreadPool(2);
    ThreadPoolExecutor es = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue&lt;Runnable&gt;())</code></pre>
<ul>
<li><code>newFixedThreadPool(nThreads)</code></li>
<li>초과 스레드는 생성하지 않음</li>
<li>큐 사이즈에 제한이 없음</li>
<li>스레드 수 고정 → CPU, 메모리 리소스가 어느 정도 예측 가능한 안정적인 방식</li>
</ul>
<blockquote>
<p>💡 요청은 제한 없이 무한정 받을 수 있는데, 처리하는 스레드 수는 한정적이라 사용자가 늘어날 수록 응답 속도가 떨어지게 됨</p>
</blockquote>
<br>

<p>➡️ <strong>캐시 스레드 풀 전략</strong></p>
<pre><code class="language-java">     ExecutorService es = Executors.newCachedThreadPool();
     ThreadPoolExecutor es = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3, TimeUnit.SECONDS, new SynchronousQueue&lt;Runnable&gt;());</code></pre>
<ul>
<li><code>newCachedThreadPool();</code></li>
<li>기본 스레드  사용x , 60초 생존 주기를 갖는 초과 스레드만 사용</li>
<li>큐에 작업을 저장하지 않음(<code>SynchronousQueue</code> ) → <strong>생산자</strong>의 요청을 스레드 풀의 <strong>소비자</strong>가 직접 받아서 바로 처리</li>
<li>모든 요청이 대기하지 않고 스레드가 바로바로 처리함 → 빠른 처리 가능</li>
</ul>
<blockquote>
<p>💡사용자 증가에 따라 스레드 사용량도 늘어남 → CPU 메모리 사용량 증가 → 메모리 자원의 한계 때문에 적절한 시점에 시스템을 증설해야 함. 안그러면 다운됨</p>
</blockquote>
<br>


<p>➡️ <strong>사용자 정의 풀 전략</strong></p>
<pre><code class="language-java">    ExecutorService es = new ThreadPoolExecutor(100, 200, 60, TimeUnit.SECONDS, new ArrayBlockingQueue&lt;&gt;(1000));
</code></pre>
<ul>
<li><strong>일반</strong> - 일반적인 상황에는 고정 크기 스레드로 서비를 안정적으로 운영</li>
<li><strong>긴급</strong> - 사용자 요청이 증가 시 → 초과 스레드 투입, 작업 빠르게 처리</li>
<li><strong>거절</strong> - 사용자 요청의 폭증, 긴급 대응도 어려울 경우 → 요청 거절</li>
</ul>
<blockquote>
<p>💡큐가 가득 차야 긴급 상황으로 인지됨 → 큐를 무한대 사이즈로 사용하면 큐가 가득차지 않아 무한대의 작업을 처리해야 하는 상황 발생</p>
</blockquote>
<br>



<h3 id="executor-예외-정책">Executor 예외 정책</h3>
<p>➡️ <strong>AbortPolicy</strong></p>
<pre><code class="language-java">    ThreadPoolExecutor es = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue&lt;&gt;(), new ThreadPoolExecutor.AbortPolicy());</code></pre>
<ul>
<li>작업 거절, <code>RejectedExecutionException</code> 예외 던짐</li>
<li>기본 정책으로 생략 가능</li>
</ul>
<p>➡️ <strong>DiscardPolicy</strong></p>
<pre><code class="language-java">    ThreadPoolExecutor es = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue&lt;&gt;(), new ThreadPoolExecutor.DiscardPolicy());</code></pre>
<ul>
<li>거절된 작업 무시, 아무런 예외 발생 x</li>
</ul>
<p>➡️ <strong>CallerRunsPolicy</strong></p>
<pre><code class="language-java">    ThreadPoolExecutor es = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue&lt;&gt;(), new ThreadPoolExecutor.CallerRunsPolicy());</code></pre>
<ul>
<li>호출한 스레드가 직접 작업을 수행함</li>
<li>생산자 스레드가 대신 일을 수행하기 때문에 생산 자체가 느려짐 → 생산 속도의 조절이 가능함</li>
</ul>
<p>➡️ <strong>사용자 정의</strong></p>
<ul>
<li><code>RejectedExecutionHandler</code> 인터페이스 구현</li>
<li>자신만의 거절 처리 전략을 정의할 수 있음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[메모리 가시성, 동기화]]></title>
            <link>https://velog.io/@namo_o36/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B0%80%EC%8B%9C%EC%84%B1-%EB%8F%99%EA%B8%B0%ED%99%94</link>
            <guid>https://velog.io/@namo_o36/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B0%80%EC%8B%9C%EC%84%B1-%EB%8F%99%EA%B8%B0%ED%99%94</guid>
            <pubDate>Fri, 19 Sep 2025 16:45:42 GMT</pubDate>
            <description><![CDATA[<h2 id="volatile-메모리가시성">Volatile, 메모리가시성</h2>
<hr>
<h3 id="메모리-가시성">메모리 가시성</h3>
<ul>
<li>멀티 스레드 환경에서 한 스레드가 변경한 값 → 언제 보이는지에 대한  것</li>
<li>메모리에 변경한 값이 보이는지 안보이는지?</li>
</ul>
<h3 id="java-memory-model">Java Memory Model</h3>
<ul>
<li>자바 프로그램이 메모리에 접근하고 수정하는 방식 규정</li>
<li>멀티 스레드 프로그래밍에서 스레드 간 상호작용을 의미함</li>
<li>핵심은 작업 순서를 보장하는 happens-before 관계</li>
</ul>
<h3 id="happens-before">happens-before</h3>
<ul>
<li>“앞에 동작한 결과가 뒤따르는 동작에 반드시 반영됨”</li>
<li>스레드 간의 작업 순서를 정의하는 개념 - 메모리 가시성을 보장하는 규칙</li>
<li>한 동작이 다른 동작보다 먼저 발생</li>
<li>한 스레드에서 수행한  작업 → 다른 스레드가 참조할 때 최신 상태가 보장된다는 의미</li>
</ul>
<p><strong>발생하는 경우</strong></p>
<ul>
<li>프로그램 순서 규칙</li>
<li><code>volitile</code> 변수에 대한 쓰기 작업은 해당 변수를 읽는 모든 스레드에 보이도록 함</li>
<li>스레드 시작 규칙 → <code>start()</code> 호출 시 스레드 내 모든 작업은 <code>start()</code> 호출 이후에 실행된 작업 보다 happens-before 관계 성립</li>
<li>스레드 종료 규칙 → <code>join()</code>을 호출하면  <code>join</code>대상 스레드의 모든 작업은 <code>join</code>이 반환된 후의 작업보다 happens-before 관계</li>
<li>인터럽트 규칙 → <code>interrupt()</code>를 호출하는 작업이 인터럽트 된 스레드가 인터럽트 감지하는 시점의 작업보다 happens-before 관</li>
</ul>
<h3 id="volatile-키워드">Volatile 키워드</h3>
<ul>
<li>캐시 메모리에 적용하지 않고 바로 메인 메모리에 작성함</li>
<li>CPU에 붙어 있는 캐시 메모리는 가격이 비싸서 큰 용량을 구성하긴 어려움 → 일반적으로 코어 단위로 캐시 메모리를 각각 보유하고 있음</li>
<li>일반적으로 컨텍스트 스위칭이 되면서 메인 메모리 값이 갱신됨 → 갱신을 보장하는 것은 아님</li>
<li>이를 위해 성능을 포기하는 대신 <code>volatile</code>이라는 키워드를 사용해 메인 메모리에 직접 접근하면 된다.</li>
</ul>
<h3 id="공유-자원">공유 자원</h3>
<ul>
<li>같은 리소스에 여러 스레드가 동시에 접근할 때 발생하는 동시성 문제</li>
<li>여러 스레드가 접근하는 자원을 <strong>공유자원</strong>이라고 함</li>
<li>접근을 적절히 동기화해서 동시성 문제가 발생하지 않게 방지하는 것이 중요함</li>
</ul>
<h3 id="임계-영역">임계 영역</h3>
<ul>
<li>여러 스레드가 동시에 접근해서는 안되는 공유 자원을 접근하거나 수정하는 부분을 임계 영역이라고 함</li>
<li>여러 스레드가 공유 자원을 여러 단계로 나눠서 사용하면 문제가 발생함</li>
<li>ex) 출금 → (검증 → 출금)<ul>
<li>이 경우 검증에서 확인한 잔액은 출금 단계까지 유지가 되어야 함</li>
<li>중간에 다른 스레드가 잔액의 값을 변경하면 혼란  발생</li>
<li>어 그러면 한 번에 한 스레드만 실행할 수 있도록 제한하면? 중간에 다른 스레드가 값 변경 못하잖아!</li>
</ul>
</li>
<li>임계 영역에 한 번에 하나의 스레드만 접근할 수 있도록 안전하게 보호해야 함 → 이를 위해 <code>synchronized</code> 키워드를 이용함<br>

</li>
</ul>
<h2 id="synchronized">Synchronized</h2>
<hr>
<h3 id="synchronized-1">Synchronized</h3>
<ul>
<li>메서드 동기화 / 블록 동기화 가능</li>
<li>모든 인스턴스는 내부에 자신의 lock을 가지고 있음 ⇒ <code>monitor lock</code>이라고도 부름</li>
<li>synchronized 키워드가 있는 메서드에 진입하고자 하면 반드시 해당 인스턴스의 <code>lock</code>이 필요</li>
<li><code>lock</code>이 없으면 <code>BLOCKED</code> 상태로 대기</li>
</ul>
<p><strong>참고</strong></p>
<ul>
<li><code>volatile</code>사용 안해도 <code>synchronized</code>안에 접근하는 변수 메모리 가시성 문제는 해결됨</li>
<li>동기화 사용 시 경합 조건(두 스레드가 동일한 자원 수정)과  데이터 일관성 문제 해결 가능</li>
<li>자바의 모든 객체 인스턴스는 멀티 스레드 임계 영역을 다루기 위해 <strong>모니터 락</strong>, <strong>락 대기 집합</strong>, <strong>스레드 대기 집합</strong> 총 3가지 기본 요소를 지닌다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>한 번에 하나의 스레드만 실행 가능함 → 성능  저하</li>
<li>lock 획득  순서가 보장되지 않음</li>
<li><code>lock</code>을 얻기 위해 <code>BLOCKED</code>  상태가 되면 락을 얻기까지 무한 대기해야함</li>
</ul>
<br>

<h2 id="concurrentlock">Concurrent.Lock</h2>
<hr>
<h3 id="locksupport">LockSupport</h3>
<ul>
<li>스레드를 <code>WAITING</code>  상태로 변경 → 누가 깨워주기 전까지 계속 대기(CPU  실행 스케줄링에 들어가지 않음)</li>
<li><code>WAITING</code> 상태의 스레드는 인터럽트를 걸어 중간에 깨울 수 있음<ul>
<li><code>BLOCKED</code>상태는 인터럽트로 대기  상태를 빠져나올 수 없음(Synchronized에서만 사용하는 특별한 대기 상태)</li>
<li><code>WAITING</code>, <code>TIME_WAITING</code>상태는 인터럽트가 걸리면 대기 상태를 빠져나옴</li>
</ul>
</li>
</ul>
<p><strong>기능</strong></p>
<ul>
<li><code>park()</code> : 스레드를 <code>WAITING</code>상태로 변경</li>
<li><code>parkNanos(nanos)</code> : 스레드를 나노초 동안만 <code>TIMED_WAITING</code>으로 변경 → 이후 <code>RUNNABLE</code>로 변경</li>
<li><code>unpark(thread)</code> : <code>WAITING</code>상태의 대상 스레드를 <code>RUNNABLE</code>로 변경</li>
</ul>
<h3 id="reentrantlock">ReentrantLock</h3>
<ul>
<li>안전한 임계 영역을 위한 <code>lock</code>구현에 사용되는 <code>lock</code> 인터페이스 → 대표적인 구현체로 <code>ReentrantLock</code></li>
</ul>
<p><strong>기능</strong></p>
<ul>
<li><code>void lock()</code> : lock 획득 / 인터럽트에 응답하지 않음</li>
<li><code>void lockInterruptily()</code> : lock 획득 시도, 다른 스레드가 인터럽트할 수 있음 → 대기 중에 인터럽트가 발생하면 <code>InterruptedException</code>발생, <code>lock</code>획득 포기</li>
<li><code>boolean tryLock()</code> : <code>lock</code>획득  시도,  즉시 성공 여부 반환 → 다른  스레드가 이미 <code>lock</code>을 획득했다면 바로 포기</li>
<li><code>boolean tryLock(long time, TimeUnit unit)</code> : 주어진  시간 동안  락 획득  시도 → 주어진  시간 안에 획득하면 true, 못하면  그냥 false 반환, 포기</li>
<li><code>void unlock()</code> : <code>lock</code> 해제, 대기중인 스레드가 <code>lock</code> 획득</li>
<li><code>Condition newCondition()</code> : <code>Condition</code> 객체 생성, 반환 →</li>
</ul>
<h3 id="공정성">공정성</h3>
<ul>
<li><code>private final Lock nonFairLock = new ReentrantLock();</code> : 비공정  모드 락<ul>
<li><code>lock</code>획득 속도가 빠름</li>
<li>새로운 스레드가 다른 기존 스레드보다 빠르게 선점할  수 있음</li>
<li>기아  현상 가능</li>
</ul>
</li>
<li><code>private final Lock nonFairLock = new ReentrantLock(true);</code> : 공정  모드 락<ul>
<li>공정성 보장</li>
<li>기아 현상  방지</li>
<li>성능  저하</li>
</ul>
</li>
</ul>
<h3 id="synchronized-vs-reentrantlock-대기">Synchronized vs ReentrantLock 대기</h3>
<ul>
<li><code>Lock(ReentrantLock)</code>도 2가지 단계의 대기 상태가 존재</li>
</ul>
<p><strong>➡️ Synchronized 대기</strong></p>
<ul>
<li><strong>모니터 <code>lock</code> 획득 대기</strong><ul>
<li><code>BLOCKED</code> 상태로 “<strong><code>lock</code> 대기 집합”</strong>에서 관리</li>
<li>다른 스레드가 <code>synchronized</code> 를 빠져나갈 때 <code>lock</code> 획득 시도</li>
</ul>
</li>
<li><strong><code>wait()</code> 대기</strong><ul>
<li><code>wait()</code> 호출 시 “<strong>스레드 대기 집합”</strong>에서 대기</li>
<li>다른 스레드가 <code>notify()</code> 호출 시 빠져나감</li>
<li><code>WAITING</code> 상태로 대기</li>
</ul>
</li>
</ul>
<p><strong>➡️ ReentrantLock 대기</strong> </p>
<ul>
<li><strong><code>ReentrantLock</code> 락 획득 대기</strong><ul>
<li><code>lock.lock()</code>을 호출했을 때 <code>lock</code>이 없으면 대기</li>
<li><code>WAITING</code> 상태로 <code>lock</code> 획득 대기</li>
<li>다른 스레드가 <code>lock.unlock()</code>을 호출했을 떄 대기가 풀리며 <code>lock</code> 획득 시도</li>
</ul>
</li>
<li><strong><code>await()</code> 대기</strong><ul>
<li><code>condition</code>에서 <code>WAITING</code> 상태로 대기</li>
<li><code>wait()</code>를 호출했을 때 객체 내부의 스레드 대기 집합에서 관리</li>
<li>다른 스레드가 <code>notify()</code> 호출할 때 스레드 대기 집합 빠져나감</li>
</ul>
</li>
</ul>
<h3 id="생산자--소비자">생산자 &amp; 소비자</h3>
<ul>
<li><strong>생산자(Producer)</strong> : 데이터 생성 역할</li>
<li><strong>소비자(Consumer)</strong> : 생성된 데이터 사용 역할</li>
<li><strong>버퍼(Buffer)</strong> : 생산자가 생성한 데이터 일시 저장</li>
</ul>
<p><strong>➡️ 문제 상황(producer-consumer  problem / bounded-buffer problem)</strong></p>
<ul>
<li><strong>생산자가 너무 빠를 때</strong> → Buffer가 가득 참 → Buffer에 빈 공간이 생길 때까지 생산자는 기다려야 함</li>
<li><strong>소비자가 너무 빠를 때</strong> → Buffer가 비어있음 → Buffer에 새로운 데이터가 들어올 때까지 기다려야 함</li>
</ul>
<blockquote>
<p><strong><em>⇒ 결국  버퍼 크기가 한정되어 있고, 생산자와 소비자가 함께 생산하고 소비하기 때문에 발생하는 문제</em></strong></p>
</blockquote>
<br>

<h2 id="blockingqueue">BlockingQueue</h2>
<hr>
<h3 id="blockingqueue-1">BlockingQueue</h3>
<p>특정 조건이 만족될 때까지 스레드의 작업을 차단(Blocking)</p>
<ul>
<li><strong>데이터 추가 차단</strong><ul>
<li><code>add()</code>, <code>offer()</code>, <code>put()</code>, <code>offer(타임 아웃)</code></li>
</ul>
</li>
<li><strong>데이터 획득 차단</strong><ul>
<li><code>take()</code>, <code>poll(타임아웃)</code>, <code>remove()</code></li>
</ul>
</li>
<li><strong>인터페이스 대표 구현체</strong><ul>
<li><code>ArrayBlockingQueue</code></li>
<li><code>LinkedBlockingQueue</code></li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>➡️ 멀티 스레드 사용 시 응답성이 중요!</strong></p>
</blockquote>
<ul>
<li>대기 상태 → 고객이 중지 요청을 하거나, 너무 오래 대기한 경우 포기하고 빠져나갈 수 있는 방법 필요<ul>
<li>ex) 큐 한계가 1000개일 때, 순간적으로 1000개 넘는 주문이 들어오면 소비가 생산을 따라가지 못하고 가득차게 됨</li>
</ul>
</li>
<li>위의 경우 선택지는 4가지가 있음<ul>
<li><strong><em>예외 던지기</em></strong></li>
<li><strong><em>대기 안하고 false 반환</em></strong></li>
<li><strong><em>대기</em></strong></li>
<li><strong><em>특정 시간만큼 대기</em></strong></li>
</ul>
</li>
</ul>
<h3 id="1-throw-exception---대기시-예외">1. Throw Exception - 대기시 예외</h3>
<ul>
<li><strong><code>add(e)</code></strong> : 지정된 요소 큐에 추가 → 큐가 가득 차면 <code>IllegalStateException</code> 예외 던짐</li>
<li><strong><code>remove()</code></strong> : 큐에서 요소 제거, 반환 → 큐가 비어 있으면 <code>NoSuchElementException</code> 예외 던짐</li>
<li><strong><code>element()</code></strong> : 큐 머리 요소 반환, 요소 제거(x) → 큐가 비어 있으면 <code>NoSuchElementException</code>
예외 던짐</li>
</ul>
<h3 id="2-special-value---대기시-즉시-반환">2. Special Value - 대기시 즉시 반환</h3>
<ul>
<li><strong><code>offer(e)</code></strong> : 지정된 요소 큐에 추가 시도 → 큐가 가득 차면 <code>false</code> 반환</li>
<li><strong><code>poll()</code></strong> : 큐에서 요소 제거, 반환 → 큐가 비면 <code>null</code> 반환</li>
<li><strong><code>peek()</code></strong> : 큐의 머리 요소 반환, 요소 제거(x) → 큐가 비어 있으면 <code>null</code> 반환</li>
</ul>
<h3 id="3-blocks---대기">3. Blocks - 대기</h3>
<ul>
<li><strong><code>put(e)</code></strong> : 지정된 요소 큐에 추가할 때까지 대기 → 큐가 가득 차면 공간 생길 때까지 대기</li>
<li><strong><code>take()</code></strong> : 큐에서 요소 제거 후  반환 →  큐가 비어 있으면 요소가 준비될 때까지 대기</li>
<li><strong><code>Examine (관찰)</code></strong> : 해당 사항 없음.</li>
</ul>
<h3 id="4-times-out---시간-대기">4. Times Out - 시간 대기</h3>
<ul>
<li><strong><code>offer(e, time, unit)</code></strong>: 지정된 요소 큐에 추가 시도, 지정된 시간 동안 큐가 비워지기를 대기 → 시간 초과 시 <code>false</code> 반환</li>
<li><strong><code>poll(time, unit)</code></strong> : 큐에서 요소 제거 후 반환 → 큐에 요소가 없으면 지정된 시간 동안 요소 준비를
기다림 → 시간 초과 시 <code>null</code> 반환</li>
</ul>
<br>

<h2 id="원자적-연산">원자적 연산</h2>
<hr>
<ul>
<li>더 나눌 수 없는 단위로 수행되는 연산</li>
<li>멀티 스레드 상황에서 다른 스레드 간섭 없이  안전히 처리되는 연산<ul>
<li>ex_1 ) <code>i = 1</code> : 원자적 연산(o)</li>
<li>ex_2 ) <code>i = i + 1</code> : 원자적 연산(x)</li>
</ul>
</li>
<li>원자적이지 않은 연산을 멀티 스레드 환경에서 실행할 경우 문제가 발생할 수 있음</li>
</ul>
<h3 id="atomicinteger">AtomicInteger</h3>
<ul>
<li><code>AtomicInteger</code> : 멀티  스레드 환경에서 안전한 증가 연산 수행을 돕는 클래스</li>
<li><code>synchronized</code> 연산과 달리 락을 사용하지 않고 원자적 연산을 만들어 냄</li>
</ul>
<h3 id="cascompare-and-set">CAS(Compare-And-Set)</h3>
<ul>
<li>락 프리(lock-free) 기법</li>
<li>락을 걸지 않고 원자적 연산을 수행하는 방법(완전히 안거는건 아니고 작은  단위에 검)</li>
<li>원자적이지 않은 두 개의 연산을 CPU 하드웨어 차원에서 특별히 하나의 원자적인 연산으로 묶어서 제공하는 기능 → <code>SW</code>가 아닌 <code>HW</code>가 제공하는 기능</li>
</ul>
<h3 id="cas-연산-vs-lock-방식">CAS 연산 vs Lock 방식</h3>
<h3 id="lock-방식">Lock 방식</h3>
<ul>
<li>비관적 방식 → 다른 스레드가 방해할 것이라고 가정</li>
<li>Data 접근 전 lock 획득 필수</li>
<li>다른 스레드 접근을 막음</li>
<li>락을 대기하는 스레드는 CPU를 거의 사용하지 않음</li>
<li>락 사용 시 획득 시점, 대기 시점의 상태  변경  시 컨텍스트 스위칭이 발생, 오버헤드가 증가할 수 있음</li>
</ul>
<h3 id="cas-방식">CAS 방식</h3>
<ul>
<li>낙관적 방식 → “어지간해선 충돌 없겠지”</li>
<li>lock을 사용하지 않고 데이터에 바로 접근</li>
<li>충돌이 발생하면 재시도</li>
<li>스핀락과 유사한 오버헤드 → 충돌 빈도가 높으면 성능 저하가 발생함</li>
</ul>
<blockquote>
<p>💡 충돌이 많이 없는 경우는 CAS가 빠름 
→ ex) 간단한 CPU 연산의 경우
→ 오래 기다리는 작업을 요청할 경우 CPU를 계속 사용하며 기다리게 됨</p>
</blockquote>
<p>⇒ 일반적으로 동기화 락을 사용, 특별한 경우에 한해서 CAS 사용해야 함</p>
<p>⇒ 자바의 동시성 라이브러리들은 일반적으로 CAS 연산을 활용함. 우리가 직접 CAS 연산을 활용하는  경우는 드뭄</p>
</aside>

<h3 id="동시성-컬렉션">동시성 컬렉션</h3>
<ul>
<li><p>컬렉션 프레임 워크가 제공하는 대부분의 연산은 원자적 연산이 아님 → 스레드 세이프 하지 않음</p>
</li>
<li><p>여러 스레드가 접근해야할 경우 <code>synchronized</code>, <code>Lock</code> 등과 같은 안전한 임계영역을 만들어 해결</p>
<p>  → 코드를 모두 복사해서 synchronized 기능 추가해야 할까? → 성능과 트레이드 오프 발생</p>
</li>
</ul>
<h3 id="proxy">Proxy</h3>
<ul>
<li>프록시가 대신 동기화 기능을 처리</li>
<li>어떤 객체에 대한 접근을 제어하기 위해 해당 객체의 대리인 또는 인터페이스 역할을 하는 객체 제공 패턴</li>
<li><strong>주요 목적</strong><ul>
<li>접근 제어 -  실제 객체에 대한 접근 제한</li>
<li>성능 향상 - 실제 객체 생성 지연 or 캐싱 → 성능 최적화</li>
<li>부가 기능 제공 - 실제 객체에 추가적인 기능 제공 가능</li>
</ul>
</li>
</ul>
<h3 id="자바-동시성-컬렉션---synchronized">자바 동시성 컬렉션 - synchronized</h3>
<ul>
<li><code>List&lt;String&gt; list = Collections.synchronizedList(new ArrayList&lt;&gt;());</code><ul>
<li><code>synchronized</code>를 추가하는 프록시 역할 → 동기화 프록시를 만들어냄</li>
</ul>
</li>
<li><code>Collections</code> 가 제공하는 동기화 프록시 기능 덕분에 스레드가 안전한 컬렉션으로 변경해서 사용할 수 있음</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>동기화 오버헤드가 발생함 → 호출 시마다 동기화 비용이 추가됨</li>
<li>전체 컬렉션에 대해 동기화 발생 → 병렬처리 효율 저하</li>
<li>정교환 동기화 불가 → 특정 부분이나 메서드에 대한 선택적인 동기화 적용은 어려움</li>
</ul>
<h3 id="자바-동시성-컬렉션---동시성-컬렉션">자바 동시성 컬렉션 - 동시성 컬렉션</h3>
<ul>
<li><code>ConcurrentHashMap</code>, <code>CopyOnWriteArrayList</code>, <code>BlockingQueue</code> (생산자, 소비자)</li>
<li>일부 메서드에만 동기화 적용</li>
<li>정교한  동기화 구현 및 성능 최적화</li>
</ul>
<h3 id="동시성-컬렉션-종류">동시성 컬렉션 종류</h3>
<ul>
<li><code>List</code> : <code>CopyOnWriteArrayList</code> → <code>ArrayList</code> 대안</li>
<li><code>Set</code> : <code>CopyOnWriteArraySet</code>, <code>ConcurrentSkipListSet</code> (정렬 순서 유지, <code>comparator</code> 사용)</li>
<li><code>Map</code> : <code>ConcurrentHashMap</code> , <code>ConcurrentSkipListMap</code> (정렬 순서 유지, <code>comparator</code> 사용)</li>
<li><code>Queue</code> : <code>ConcurrentLinkedQueue</code></li>
<li><code>Deque</code> : <code>ConcurrentLinkedDeque</code></li>
<li><code>BlockingQueue</code><ul>
<li><code>ArrayBlockingQueue</code> : 크기 고정 블로킹 큐, 공정 보드 사용(성능 저하)</li>
<li><code>LinkedBlockingQueue</code> : 크기 고정, 무한 블로킹 큐</li>
<li><code>PriorityBlockingQueue</code> : 우선순위 블로킹 큐</li>
<li><code>SynchronousQueue</code> : 중간 큐 없이 생산자, 소비자 직접 거래</li>
<li><code>DelayQueue</code> : 지정된 지연 시간이 지난 후에 소비됨</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1202번(Java)]]></title>
            <link>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-1202%EB%B2%88Java</link>
            <guid>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-1202%EB%B2%88Java</guid>
            <pubDate>Thu, 11 Sep 2025 13:28:50 GMT</pubDate>
            <description><![CDATA[<h3 id="📖-요세푸스-문제-0">📖 요세푸스 문제 0</h3>
<blockquote>
<p>__[ 문제 ] __
세계적인 도둑 상덕이는 보석점을 털기로 결심했다.
상덕이가 털 보석점에는 보석이 총 $$N$$개 있다. 각 보석은 무게 $$M_i$$와 가격 $$V_i$$를 가지고 있다. 상덕이는 가방을 $$K$$개 가지고 있고, 각 가방에 담을 수 있는 최대 무게는 $$C_i$$이다. 가방에는 최대 한 개의 보석만 넣을 수 있다.
상덕이가 훔칠 수 있는 보석의 최대 가격을 구하는 프로그램을 작성하시오.</p>
</blockquote>
<hr>
<h4 id="💡풀이">💡풀이</h4>
<ul>
<li>보석을 무게의 오름차순으로 우선 정렬</li>
<li>가방을 최대 무게 오름차순으로 정렬</li>
<li><code>PriorityQueue</code> 하나 선언</li>
<li>가방 무게를 하나하나 방문하면서 가방 무게보다 낮은 무게의 보석들을 하나씩 <code>PriorityQueue</code>에 담음</li>
<li>다 담았으면 그 중 제일 값어치가 비싼 무게 하나를 꺼내서 넣음</li>
<li>만약 방문한 가방 무게에서 <code>PriorityQueue</code>에 추가할 보석이 없다면, 이전에 추가한 보석들 중 제일 값어치가 비싼 보석을 추가하면 됨</li>
<li>로직은 너무 단순한데... DFS 써보고 난리도 아니었던 문제</li>
</ul>
<hr>
<pre><code class="language-java">    package priority_queue;

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.*;

    public class bj1202_1 {
        static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        static int N, K;
        static ArrayList&lt;int[]&gt; arr;
        static ArrayList&lt;Integer&gt; backpack;

        public static void main(String[] args) throws IOException {
            StringTokenizer st = new StringTokenizer(br.readLine());
            N = Integer.parseInt(st.nextToken());
            K = Integer.parseInt(st.nextToken());

            arr = new ArrayList&lt;int[]&gt;();
            backpack = new ArrayList&lt;Integer&gt;();

            for(int i = 0 ; i &lt; N ; i ++){
                StringTokenizer st1 = new StringTokenizer(br.readLine());
                int M = Integer.parseInt(st1.nextToken());
                int V = Integer.parseInt(st1.nextToken());
                arr.add(new int[] {M, V});
            }

            for(int j = 0 ; j &lt; K ; j ++){
                int c = Integer.parseInt(br.readLine());
                backpack.add(c);
            }

            arr.sort((a, b) -&gt; a[0] - b[0]);
            backpack.sort(Comparator.naturalOrder());

            int cur = 0;
            long maxRob = 0;

            PriorityQueue&lt;Integer&gt; pq = new PriorityQueue&lt;&gt;(Collections.reverseOrder());
            for(int a = 0 ; a &lt; K; a ++){

                while(cur &lt; arr.size() &amp;&amp; arr.get(cur)[0] &lt;= backpack.get(a)){
                    pq.add(arr.get(cur)[1]);
                    cur ++;
                }

                if(!pq.isEmpty()){
                    maxRob += pq.poll();
                }
            }
            System.out.println(maxRob);
        }
    }
</code></pre>
</br>

<hr>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/1202">https://www.acmicpc.net/problem/1202</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 12015번(Java)]]></title>
            <link>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-12015%EB%B2%88Java</link>
            <guid>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-12015%EB%B2%88Java</guid>
            <pubDate>Thu, 11 Sep 2025 05:50:00 GMT</pubDate>
            <description><![CDATA[<h3 id="📖-요세푸스-문제-0">📖 요세푸스 문제 0</h3>
<blockquote>
<p>__[ 문제 ] __
수열 $$A$$가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.
예를 들어, 수열 $$A$$ = { $$10, 20, 10, 30, 20, 50$$ } 인 경우에 가장 긴 증가하는 부분 수열은 $$A$$ = { $$10, 20, 10, 30, 20, 50$$ } 이고, 길이는 4이다.</p>
</blockquote>
<hr>
<h4 id="💡풀이">💡풀이</h4>
<ul>
<li>가장 긴 부분 수열 구하기는 이전 다른 백준 문제에서 풀었기 때문에 동일한 방식으로 풀어도 될 줄 알고 풀었더니 시간 초과가 났다.</li>
<li>가장 작은 수부터 하나씩 차례로 스택처럼 쌓다가, 다음에 오는 숫자가 제일 마지막 숫자보다 작은 경우, 내부에 해당 숫자보다 크되, 개중에 제일 작은 숫자와 대치하는 방식으로 풀이했다. </li>
<li>이 방식은 어차피 최대 개수를 세는 것이 메인이고 이들의 배열을 요구하지 않기 때문에 가능한 방식이다.</li>
<li>ex) $$A$$ = { $$10, 20, 30, 15, 20, 50$$ } 인 경우,<pre><code>  - $$tmp$$ = { $$10, 20, 30$$ } 까지 차례대로 넣다가, 그 다음에 오는 숫자가 15인 경우, 
     - *15* 보다 크되, 그 중에 제일 작은 숫자인 *20*과 *15*를 *&quot;대치&quot;*
  - $$tmp$$ = { $$10, 15, 30$$ }
  - 대치한다 해도 여전히 증가 수열의 최대 길이가 3임은 변하지 않음 </code></pre></li>
</ul>
<hr>
<pre><code class="language-java">package binary_search;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class bj12015 {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static int N;
    static int [] A;
    static int [] tmp;

    public static void main(String[] args) throws IOException {
        N = Integer.parseInt(br.readLine());
        A = new int[N];
        tmp = new int[N];

        StringTokenizer st = new StringTokenizer(br.readLine());
        for(int i = 0 ; i &lt; N ; i ++){
            A[i] = Integer.parseInt(st.nextToken());
        }


        int flag = 0;
        tmp[flag] = A[0];
        for(int i = 1 ; i &lt; N ; i ++){
            if(tmp[flag] &lt; A[i]){
                tmp[++flag] = A[i];
            } else {
                int curloc = findloc(flag, A[i]);
                tmp[curloc] = A[i];
            }
        }
        System.out.println(flag + 1);

    }
    private static int findloc(int end, int n){
        int lo = 0;

        while(lo &lt; end){
            int mid = (lo + end) / 2;

            if(tmp[mid] &gt;= n){
                end = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo;
    }

}
</code></pre>
</br>

<hr>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/12015">https://www.acmicpc.net/problem/12015</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1300번(Java)]]></title>
            <link>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-1300%EB%B2%88Java</link>
            <guid>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-1300%EB%B2%88Java</guid>
            <pubDate>Thu, 11 Sep 2025 05:40:33 GMT</pubDate>
            <description><![CDATA[<h3 id="📖-요세푸스-문제-0">📖 요세푸스 문제 0</h3>
<blockquote>
<p>__[ 문제 ] __
세준이는 크기가 $$N×N$$인 배열 $$A$$를 만들었다. 배열에 들어있는 수 $$A[i][j] = i×j$$ 이다. 이 수를 일차원 배열 $$B$$에 넣으면 $$B$$의 크기는 $$N×N$$이 된다. $$B$$를 오름차순 정렬했을 때, $$B[k]$$를 구해보자.
배열 $$A$$와 $$B$$의 인덱스는 1부터 시작한다.</p>
</blockquote>
<hr>
<h4 id="💡풀이">💡풀이</h4>
<ul>
<li>처음엔 일반 배열을 만들어서 풀이했다. 당연하게도 메모리 초과가 났다. </li>
<li>문제의 조건은 배열을 생성하지 않고 $$k$$ 번째 수를 구하는 것. </li>
<li>조건을 바꾸어 직접 수를 계산하는 식으로 생각했으나 이것 또한 시간 초과가 났다.</li>
<li>풀이의 접근을 바꾸어 2차원 배열의 한 행에 들어있는 수들 중에서 임의로 지정한 수 $$n$$보다 작은 숫자들의 수를 구하기로 했다.</li>
<li>보통 $$1×i$$, $$2×i$$, $$3×i$$ ... 의 규칙으로 늘어나기 때문에, 이것보다 작은 수들의 개수는 임의의 수 $$n$$을 $$i$$로 나눈 몫이 된다.</li>
</ul>
<hr>
<pre><code class="language-java">package binary_search;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class bj1300 {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static int N, k;
    static long[][] A;
    static long[] B;

    public static void main(String[] args) throws IOException {
        N = Integer.parseInt(br.readLine());
        k = Integer.parseInt(br.readLine());

        long answer = 0;
        long low = 1;
        long hi = N * N;

        while(low &lt; hi){
            long mid = (low + hi) / 2;

            if(counting(mid) &lt; k){
                low = mid + 1;
            }
            else {
                hi = mid;
            }
        }

        System.out.println(low);
    }

    private static long counting(long n){
        long res = 0;
        for(int i = 1 ; i &lt;= N ; i ++){
            res += Math.min(n/i, N);
        }
        return res;
    }
}
</code></pre>
</br>

<hr>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/1300">https://www.acmicpc.net/problem/1300</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 6549번(Java)]]></title>
            <link>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-6549%EB%B2%88Java</link>
            <guid>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-6549%EB%B2%88Java</guid>
            <pubDate>Wed, 10 Sep 2025 07:08:30 GMT</pubDate>
            <description><![CDATA[<h3 id="📖-히스토그램에서-가장-큰-직사각형">📖 히스토그램에서 가장 큰 직사각형</h3>
<blockquote>
<p>__[ 문제 ] __
히스토그램은 직사각형 여러 개가 아래쪽으로 정렬되어 있는 도형이다. 각 직사각형은 같은 너비를 가지고 있지만, 높이는 서로 다를 수도 있다. 예를 들어, 왼쪽 그림은 높이가 2, 1, 4, 5, 1, 3, 3이고 너비가 1인 직사각형으로 이루어진 히스토그램이다.
히스토그램에서 가장 넓이가 큰 직사각형을 구하는 프로그램을 작성하시오.</p>
</blockquote>
<hr>
<h4 id="💡풀이">💡풀이</h4>
<ul>
<li>분할 정복 카테고리에 있는 문제니 분할 정복으로 풀어보고자 백방으로 노력했다. </li>
<li>히스토그램을 절반 나누어서 각 반쪽 도형 내에 포함된 최대 넓이의 사각형과, 가운데 기준선을 포함한 사각형의 넓이를 비교해 최대를 구하는 방식으로 진행했다. </li>
<li>분할 정복의 적용 자체는 어렵지 않았는데 아이디어 떠올리는게 어려웠다... </li>
</ul>
<hr>
<pre><code class="language-java">package divconqur;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class bj6549 {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static int n;
    public static void main(String[] args) throws IOException {
        while(true){
            StringTokenizer st = new StringTokenizer(br.readLine());

            n = Integer.parseInt(st.nextToken());
            if(n == 0) break;

            long[] histogram = new long[n];

            for(int i = 0 ; i &lt; n ; i ++){
                histogram[i] = Integer.parseInt(st.nextToken());
            }
            long answer = divcon(0, n - 1, histogram);
            System.out.println(answer);
        }
    }
    private static long divcon(int start, int end, long[] arr){
        // 히스토그램 막대기 한 개일 경우 그대로 반환
        if(start == end){
            return arr[start];
        } else{
            int mid = (start + end) / 2;

            // 왼쪽 히스토그램 절반에서의 최대 사각형 vs 오른쪽 히스토그램의 최대 사각형 비교
            long lrmax = Math.max(divcon(start, mid, arr), divcon(mid+1, end, arr));

            int ts = mid;
            int te = mid;
            long base = arr[mid];
            long mmax = 0;

            // 가운데 기준선을 포함하는 사각형 큰 거 하나 확인
            while(start &lt; ts &amp;&amp; te &lt; end){
                if(arr[te + 1] &gt;= arr[ts - 1]){
                    te += 1;
                    base = Math.min(base, arr[te]);
                } else {
                    ts -= 1;
                    base = Math.min(base, arr[ts]);
                }
                mmax = Math.max(mmax, base * (te - ts + 1));
            }

            // 위의 while문에서 한 쪽 끝에만 도달했으니, 남은 부분을 탐색하는 과정 필요
            while(ts &gt; start){
                ts -= 1;
                base = Math.min(base, arr[ts]);
                mmax = Math.max(mmax, base * (te - ts + 1));
            }

            while(te &lt; end){
                te += 1;
                base = Math.min(base, arr[te]);
                mmax = Math.max(mmax, base * (te - ts + 1));
            }

            return Math.max(mmax, lrmax);
        }
    }

}

</code></pre>
</br>

<hr>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/6549">https://www.acmicpc.net/problem/6549</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DevOps] Jenkins, ArgoCD 설치]]></title>
            <link>https://velog.io/@namo_o36/DevOps-Jenkins-ArgoCD-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@namo_o36/DevOps-Jenkins-ArgoCD-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Thu, 04 Sep 2025 03:36:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="💡cicd">💡CI/CD</h3>
<p><em>“개발부터 배포까지 모든 단계를 자동화하는 것”</em></p>
</blockquote>
<ul>
<li><strong>CI : Continuous Integration (지속적 통합)</strong> ⇒ 새로운 코드들이 자동으로 빌드 및 테스트 되어 레포지토리에 통합되는 것을 의미<pre><code>- 개발자들은 최대한 작은 단위로 개발하며, 빈번하게 merge해야 함.
- 애플리케이션들의 빌드, 테스트, 병합 과정을 주기적으로 자동화해주어야 함</code></pre></li>
<li><strong>CD : Continuous Delivery/Deployment(지속적 제공/배포)</strong> ⇒ 빌드, 테스트가 완료되어 배포 준비가 된 애플리케이션을 개발자가 자동/수동으로 배포를 진행하는 것<ul>
<li>수동 : Continuous Delivery</li>
<li>자동 : Continuous Deployment</li>
</ul>
</li>
<li><strong>CI/CD 파이프라인</strong><ul>
<li>개발자가 작은 단위로 코드 짜고 메인 레포에 merge → 자동 빌드 → 자동 테스트 → 릴리즈 → 배포</li>
</ul>
</li>
</ul>
<h3 id="jenkins">Jenkins</h3>
<ul>
<li>CI/CD 파이프라인을 구축하고 관리하는데 사용되는 도구 중 하나</li>
<li>개발자 요청을 받아 빌드, 배포, 테스트를 관리하는 오픈 소스 자동화 도구이다. 개발자들은 코드 변경 사항을 통합하고, 자동으로 테스트하며 배포하는 과정을 효율적으로 관리할 수 있음</li>
</ul>
<h3 id="jenknis-작동-원리">Jenknis 작동 원리</h3>
<ul>
<li>Git, SVN 등의 버전 관리 시스템과 연동 → 코드 변경 감지</li>
<li>코드 변경 감지 시 자동으로 빌드 시작</li>
<li>빌드 결과물에 대한 테스트 실행</li>
<li>테스트 성공 시 배포 작업 실행</li>
<li>빌드, 테스트, 배포 결과를 보고서 형태로 제공</li>
</ul>
<hr>
<h3 id="jenkins-설치">Jenkins 설치</h3>
<h4 id="0-chocolatey--helm-설치">0. Chocolatey &amp; Helm 설치</h4>
<ul>
<li>Kubernetes 클러스터 안에 Jenkins 설치를 위해 네임 스페이스 생성 후 설치 진행<pre><code class="language-bash"># powershell 관리자 권한으로 실행
Set-ExecutionPolicy Bypass -Scope Process -Force; `
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; `
iex ((New-Object System.Net.WebClient).DownloadString(&#39;https://community.chocolatey.org/install.ps1&#39;))
</code></pre>
</li>
</ul>
<h1 id="버전-확인">버전 확인</h1>
<p>choco --version</p>
<h1 id="helm-설치">Helm 설치</h1>
<p>choco install kubernetes-helm</p>
<h1 id="버전-확인-1">버전 확인</h1>
<p>helm version</p>
<pre><code>
#### 1. Jenkins Helm 설치
- 관리자 권한으로 PowerShell 실행 후 진행

```bash
# 젠킨스 실행할 네임스페이스 생성
&gt; kubectl create namespace jenkins

# 젠킨스 저장소 추가, 업데이트
&gt; helm repo add jenkins https://charts.jenkins.io
&gt; helm repo update

# 젠킨스 설치
helm install jenkins jenkins/jenkins -n jenkins

# 설치 후 확인
kubectl get pods -n jenkins</code></pre><h4 id="1-1-jenkins-초기-설정-시-지정한-port가-아닌-내가-지정한-port로-접속하기">1-1) Jenkins 초기 설정 시 지정한 Port가 아닌 내가 지정한 port로 접속하기</h4>
<pre><code class="language-bash"># 텍스트 편집기 열기
notepad jenkins-values.yaml</code></pre>
<pre><code class="language-yaml">  # For minikube, set this to NodePort, elsewhere uses LoadBalancer
  # Use ClusterIP if your setup includes ingress controller
  # -- k8s service type
  serviceType: NodePort    # ClusterIP -&gt; NodePort로 변경

  # -- k8s service clusterIP. Only used if serviceType is ClusterIP
  clusterIp:
  # -- k8s service port
  servicePort: 8080
  # -- k8s target port
  targetPort: 8080
  # -- k8s node port. Only used if serviceType is NodePort
  nodePort: &quot;&lt;내가 쓸 포트번호&gt;&quot;</code></pre>
<ul>
<li><strong>ServiceType</strong> : ClusterIP -&gt; NodePort
  ➡️ 쿠버네티스 외부(PC 브라우저)에서 Jenkins에 접속하기 위해서는 ClusterIP가 아닌 NodePort 방식이어야 함<pre><code> ➡️ ClusterIP(기본값)은 클러스터 내부에서만 접근이 가능하기 때문</code></pre></li>
<li><strong>nodePort</strong> : 내가 쓸 포트 지정</li>
<li>지정한 port 번호로 localhost:포트번호 접속해서 확인</li>
</ul>
<p><img src="https://velog.velcdn.com/images/namo_o36/post/5999bf55-2306-4c1d-b8e7-c92aac196bd2/image.png" alt=""></p>
<p>예쁘게 잘 뜨는걸 확인할 수 있다!</p>
<br>

<h4 id="3-웹훅webhook">3. 웹훅(WebHook)</h4>
<ul>
<li>이벤트 발생 시 서버나 애플리케이션으로 데이터 전송</li>
</ul>
<h4 id="젠킨스-컨테이너-접속-ssh-키-생성">젠킨스 컨테이너 접속, SSH 키 생성</h4>
<pre><code class="language-bash">&gt; kubectl exec -it jenkins-0 -n jenkins -c jenkins -- /bin/bash
&gt; ssh-keygen -t ed25519 -C &quot;&lt;이메일 주소&gt;&quot;</code></pre>
<ul>
<li><p>생성한 SSH 공개키를 Git 저장소에 등록</p>
<ul>
<li><code>Settings</code>에서 좌측 메뉴의 <code>Deploy keys</code>을 선택한다.</li>
<li><code>Add deploy key</code> 버튼을 클릭하고 SSH 공개 키를 등록한다.</li>
</ul>
</li>
<li><p>Jenkins에도 SSH 키를 등록한다</p>
<ul>
<li><code>Jenkins 관리</code> -&gt; <code>Credentials</code> &gt; <code>System의 global</code> &gt; <code>Add Credentials</code><ul>
<li><code>New credentials</code> 페이지에서 kind로 <code>SSH Username with private key</code>를 선택하고 내용을 작성</li>
<li><code>Private Key</code> 항목에 <code>Enter directly</code> → SSH 개인 키를 복붙</li>
</ul>
</li>
</ul>
</li>
<li><p>GitHub 웹훅 설정 </p>
<ul>
<li><code>Settings</code>에서 좌측 메뉴의 <code>Webhooks</code>을 선택</li>
<li><code>Add webhook</code> 버튼을 클릭하고 새로운 웹훅을 설정</li>
<li><code>Payload URL</code>에는 <code>http://젠킨스 주소/github-webhook/</code>를 입력</li>
<li><code>Content type</code>에는 <code>application/json</code>를 선택해 주고 <code>Add webhook</code> 버튼을 클릭해 웹훅을 생성</li>
</ul>
</li>
</ul>
<hr>
<h3 id="argocd-설치">ArgoCD 설치</h3>
<pre><code class="language-bash"># namespace 생성
kubectl create namespace argocd
# 공식 홈페이지에서 제공하는 yaml파일 이용
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 확인
kubectl get po -n argocd  -w

# 초기 비밀번호 확인
kubectl get secret -n argocd argocd-initial-admin-secret -o jsonpath=&quot;{.data.password}&quot; | base64 -d

# 초기 비밀번호 변경
kubectl exec -it -n argocd deployment/argocd-server -- /bin/bash
argocd login localhost:8080
Username: admin
Password: &quot;&lt;확인한 비밀번호&gt;&quot;

argocd account update-password
*** Enter password of currently logged in user (admin): &quot;&lt;초기 비밀번호&gt;&quot;
*** Enter new password for user admin: &quot;&lt;초기 비밀번호&gt;&quot;
*** Confirm new password for user admin: &quot;&lt;새 비밀번호&gt;&quot;
Password updated
</code></pre>
<h4 id="포트포워딩">포트포워딩</h4>
<ul>
<li>간단한 테스트<pre><code class="language-bash"># 새 포트 번호로 접속하고 싶을 경우
kubectl -n argocd port-forward svc/argocd-server &quot;&lt;설정할 포트 번호&gt;&quot;:443
</code></pre>
</li>
</ul>
<pre><code>
- 포트 영구 설정
```bash
# argocd-server 서비스 편집
kubectl -n argocd edit svc argocd-server
</code></pre><ul>
<li>메모장이 뜨면 편집 화면에서 포트 변경
```yaml
ipFamilies:</li>
<li>IPv4
ipFamilyPolicy: SingleStack
ports:</li>
<li>name: http
port: 80             # Service 포트
protocol: TCP
targetPort: 8080     # Pod containerPort
nodePort: &quot;&lt;새 포트번호&gt;&quot;      # NodePort 지정 (생략하면 자동 할당)</li>
<li>name: https
port: 443
protocol: TCP
targetPort: 8080
nodePort: &quot;&lt;새 포트번호&gt;&quot;      # NodePort 지정
selector:
app.kubernetes.io/name: argocd-server
sessionAffinity: None
type: NodePort          # &lt;- ClusterIP에서 NodePort로 변경
status:
loadBalancer: {}</li>
</ul>
<p>```</p>
<p><img src="https://velog.velcdn.com/images/namo_o36/post/77097a7e-9971-40fe-8f27-c415d0ba0101/image.png" alt=""></p>
<ul>
<li>예쁘게 잘 뜨는걸 확인할 수 있다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[바이브 코딩(Vibe Coding)과 MCP]]></title>
            <link>https://velog.io/@namo_o36/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9Vibe-Coding%EA%B3%BC-MCP</link>
            <guid>https://velog.io/@namo_o36/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9Vibe-Coding%EA%B3%BC-MCP</guid>
            <pubDate>Sun, 31 Aug 2025 07:53:23 GMT</pubDate>
            <description><![CDATA[<h3 id="바이브코딩vibe-coding">바이브코딩(Vibe Coding)</h3>
<ul>
<li>자연어로 의도 설명 <strong>→</strong> AI가 코드를 생성해주는 개발 방법</li>
<li>직접 코드를 작성한다기보다 <strong>→</strong>  AI에게 요구사항 설명 <strong>→</strong> AI가 코드 생성 <strong>→</strong> 개발자는 이를 검토, 수정, 채택</li>
<li>단기, 실험적 프로젝트에서는 효과적</li>
<li>장기, 대규모 프로젝트에는 위험 요소가 큼</li>
</ul>
<p><img src="https://velog.velcdn.com/images/namo_o36/post/7e937e9c-7f02-4b83-a67f-c8c20094e6ab/image.png" alt=""></p>
<h3 id="프롬프트-엔지니어링prompt-engineering--ai가-원하는-코드를-잘-뽑아내는-질문법">프롬프트 엔지니어링(Prompt Engineering) = “AI가 원하는 코드를 잘 뽑아내는 질문법”</h3>
<ul>
<li>프롬프트는 LLM 모델에게 질문할 때 좋은 답변을 얻기 위한 질문의 최적화 방식임<ul>
<li><strong>LLM(Large Language Model)</strong> - 생성형 AI 중 하나, 자연어 처리 가능 머신러닝 모델</li>
</ul>
</li>
<li>생성형 인공 지능 솔루션을 안내하여 원하는 결과를 내는 솔루션</li>
<li>쉽게 말해 원하는 결과를 위해 질문/명령을 조금 더 체계적이고 똑똑하게 하는 방법<ul>
<li><strong>“이거 어떻게 수정해?”</strong> ❌</li>
<li><strong>“이 Method를 O(n log n) 복잡도로 최적화하고, 주석 달아줘”</strong> ✅</li>
</ul>
</li>
</ul>
<h3 id="프롬프트-최적화-구조">프롬프트 최적화 구조</h3>
<blockquote>
<ul>
<li><strong>Input - 질문</strong><ul>
<li>질문형(<del>는 뭐야?) / 명령형(</del> 해줘) / 롤(너는 ~이다. ~해줘) / 분류 / …</li>
</ul>
</li>
</ul>
</blockquote>
<ul>
<li><strong>Context</strong> - 부가적인 상황(Optional)</li>
<li><strong>Example</strong> - 답변 예제들(Optional)</li>
</ul>
<p><strong>📖 예시 1 - 질문형</strong></p>
<ul>
<li><strong>Input</strong>: “바이브 코딩이 뭐야?”</li>
<li><strong>Context</strong>: &quot;개발자가 AI를 활용해 코드를 작성하는 방식에 대해 이해하고 싶음. 초보자도 쉽게 이해할 수 있도록 설명.”</li>
<li><strong>Example</strong>: &quot;바이브 코딩 = [정의], [핵심 개념1], [핵심 개념2], [핵심 개념3]”<blockquote>
<p><strong>도출된 답 ✅</strong>
&quot;바이브 코딩은 개발자가 AI와 협업하여 자연어로 코드를 작성하고, 반복 작업을 자동화하는 코딩 방식입니다.
핵심 개념은 </p>
</blockquote>
1) 프롬프트 엔지니어링: AI에게 정확한 지시를 주는 방법,
2) AI Pair Programming: AI와 페어 프로그래머처럼 함께 작업,
3) Natural Language Coding: 자연어로 코드 요청 및 생성입니다.&quot;</li>
</ul>
<p><strong>📖 예시 2 - 명령형</strong></p>
<ul>
<li><strong>Input</strong>: &quot;간단한 To-do 앱을 HTML+JS로 만들어줘”</li>
<li><strong>Context</strong>: &quot;프론트엔드 초보자 실습용. 기능: 할 일 추가, 삭제, 완료 체크”</li>
<li><strong>Example</strong>: “HTML input, 버튼, ul 리스트를 이용한 기본 구조 코드”<blockquote>
<p><strong>도출된 답 ✅</strong></p>
<pre><code>  &lt;input id=&quot;taskInput&quot; placeholder=&quot;할 일 입력&quot;&gt;
  &lt;button onclick=&quot;addTask()&quot;&gt;추가&lt;/button&gt;
  &lt;ul id=&quot;taskList&quot;&gt;&lt;/ul&gt;
  &lt;script&gt;
  function addTask() {
      const input = document.getElementById(&#39;taskInput&#39;);
      const li = document.createElement(&#39;li&#39;);
      li.textContent = input.value;
      document.getElementById(&#39;taskList&#39;).appendChild(li);
      input.value = &#39;&#39;;
  }
  &lt;/script&gt;</code></pre></blockquote>
</li>
</ul>
<h3 id="ai-pair-programming--ai를-내-페어-프로그래머처럼-활용">AI Pair Programming = “AI를 내 페어 프로그래머처럼 활용”</h3>
<ul>
<li>AI를 동료 프로그래머로 활용하는 방식</li>
<li>반복적 코드 작성, 테스트 코드 생성, 리팩토링, 문서화 등을 AI가 도와주므로 생산성 향상</li>
<li>사람이 전체 코드를 작성하는 대신, AI와 협업하는 구조<ul>
<li>개발자가 주요 로직 개발 → AI가 반복문 최적화 + 테스트 코드 자동 생성 + 코드 리뷰 &amp; 개선 사항 제안</li>
</ul>
</li>
</ul>
<h3 id="natural-language-coding--코드를-직접-쓰는-대신-말로-설명해서-생성">Natural Language Coding = “코드를 직접 쓰는 대신 말로 설명해서 생성”</h3>
<ul>
<li>자연어로 코드 요청 / 작성하는 방식</li>
<li>기존 코딩은 문법 중심으로 작성 → 자연어로 기능 설명만으로 코드 생성 가능<ul>
<li>“사용자 입력을 받아 이름을 출력하는 HTML 페이지 만들어줘” → AI가 HTML+JS 코드 생성</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡<strong>프롬프트 엔지니어링, AI Pair Programming, Natural Language Coding</strong> 세 개 전부 바이브 코딩의 핵심 요소 → 쉽게 말해 <strong>“AI와 개발자가 함께 일하는 새로운 코딩 방식”</strong></p>
</blockquote>
<p>이렇게만 보면 개발 생산성이 엄청 늘어서 원하는 서비스를 공장처럼 찍어낼 수 있을 것 같고, 개발자들은 워라벨 훌륭한 직장을 다니게 되겠지만 실상은 다르다..</p>
<h3 id="바이브-코딩-도입">바이브 코딩 도입</h3>
<ul>
<li>AI를 활용한 코드 도입 → 사내 데이터와 연결해 업무를 자동화한다면 얼마나 좋을까?</li>
<li>그렇지만 직원 PC나 AI가 직접 사내 DB에 접속한다면 보안 문제가 발생할 수 있다 → 공격 경로가  너무 많아지고, 누가 언제 무엇에 접근했는지 추적이 어려워진다.<ul>
<li>회사에도 사원증 찍어야 출입이 되고 기록이 남는다. 사원증 없으면 온갖 인간군상이 회사에 들락날락 거리게 되고, 누가 회사 컴퓨터, 의자를 들고 갔는지 알 수 없게된다.</li>
</ul>
</li>
<li>여기서 중요하게 대두되는 것이 <strong>“MCP”</strong>이다.</li>
</ul>
<hr>
<h3 id="mcpmodel-context-protocol">MCP(Model Context Protocol)</h3>
<ul>
<li>Anthropic에서 발표한 LLM 모델과 외부 애플리케이션의 연동 스펙</li>
<li>위의 문제 해결을 위해 나온 개념</li>
<li>여러 도구(Agent/Tool)를 안전하고 효율적으로 연결하기 위해 등장한 표준 프로토콜/프레임워크를 <strong>MCP</strong></li>
</ul>
<h3 id="llm-⇒-agent-tool">LLM ⇒ Agent, Tool</h3>
<blockquote>
<p>💡 <strong>“Agent”</strong>는 어떤 정보를 어떻게 답변할지에 대한 판단을 담당 
    → 이후 질문에 맞는 적절한 “<strong>Tool”</strong>을 호출해서 답변에 도달</p>
</blockquote>
<ul>
<li><strong>LLM</strong>은 학습된 지식을 바탕으로 질문에 대답하는 방식. 학습되지 않은  정보는 검색으로 정보를 얻을 수 없다. ⇒ 이를 위해 나온 개념이 <strong>Agent</strong> / <strong>Tools</strong></li>
<li>외부 도구를 통합하고 연결 → <strong>LLM</strong>의 기능을 확장시키는 구조</li>
<li><strong>Agent</strong><ul>
<li><strong>LLM 추론 능력을 바탕으로 애플리케이션 전체 흐름을 제어, 상황에 맞는 작업을 처리하는 애플리케이</strong></li>
<li><strong>LLM</strong> 도구(<strong>Tool</strong>) 호출</li>
<li>다른 <strong>LLM</strong> 호출 - 특화된 다른 <strong>LLM</strong>에 해당 작업을 위임</li>
<li>외부 도구 활용 - <strong>API</strong> 호출</li>
</ul>
</li>
<li><strong>Tool</strong><ul>
<li><strong>외부 세계와 상호작용하기 위해 사용하는 인터페이스</strong></li>
<li>특정 함수/작업을 수행하는 형태로 제공됨</li>
</ul>
</li>
</ul>
<h3 id="agent-framework">Agent Framework</h3>
<ul>
<li><strong>Agent</strong>를 직접 구현하려면 어떤 <strong>Tool</strong>을 선택할지나, <strong>Tool</strong>이 반환한 대답을 다듬는 처리에 관련된 로직을 구현하기엔 너무 힘들지..
  ⇒ 이걸 위해 미리 구현되어 있는 <strong>Framework</strong>를 사용할거야!
  ⇒ ex) LangChain, Crew AI, AutoGen, Llama Index, Anthropic의 Agent framework</li>
</ul>
<p>근데 <strong>Agent Framework</strong>가 <strong>Tool</strong>들과 연동하기 위해 <strong>SDK</strong>가 필요함</p>
<ul>
<li><strong><em>Agent/Tool 프레임워크</em></strong> = 모든 <strong>Tool</strong>과 <strong>Agent</strong>를 연결할 수 있는 <strong><em>공통 뼈대</em></strong></li>
<li><strong><em>SDK</em></strong> = 특정 <strong>Tool</strong>이나 언어 환경에 맞춰 뼈대를 실제로 구현할 수 있게 도와줌</li>
<li>어 근데 그러면 <strong>Tool</strong> 개발자들은 <strong>Agent Framework</strong>랑 협업해서 연동되게 만들어야 하고, <strong>Agent Framework</strong> 개발자들은 <strong>Tool</strong> 별 <strong>SDK</strong>를 또 따로 만들어야 하는거 아니야??</li>
</ul>
<p>⇒ 아 귀찮다 그냥 우리 통합하자… 표준화 해놓고 <strong>Agent Framework</strong>랑 <strong>Tool</strong>이랑 뭘 써도 연동 되게 만들자! ⇒ “<strong><em>MCP”</em></strong></p>
<h3 id="mcp-동작-과정">MCP 동작 과정</h3>
<ol>
<li><strong>Agent(LLM) → MCP : List Tools 요청</strong><ul>
<li><em>Agent</em>가 어떤 <em>Tool</em>이 있고 어떤 기능이 있는지 요청</li>
<li><em>JSON-RPC/HTTP</em> 형태로 요청 전달</li>
</ul>
</li>
<li><strong>MCP → Tool : 기능 정보 요청</strong><ul>
<li><em>MCP</em>가 <em>Tool</em>에게 기능 요청 → <em>Tool</em>은 기능 목록 반환</li>
</ul>
</li>
<li><strong>MCP → Agent : Tool 기능 목록 전달</strong><ul>
<li><em>Tool*에게 받은 응답을 *JSON</em> 구조로 <em>Agent</em>에게 전달</li>
<li><em>MCP</em>가 권한 체크 후 <em>Agent</em>가 호출할 수 있는 <em>Tool</em>만 보이게 제한</li>
</ul>
</li>
<li><strong>Agent 판단 → MCP를 통해 Tool 호출</strong><ul>
<li><em>Agent<em>가 어떤 *tool</em>을 쓸지판단 → *MCP</em> 가 권한 확인, 요청 기록 → Tool 에 전달</li>
</ul>
</li>
<li><strong>Tool 실행 → MCP → Agent 응답 반환</strong><ul>
<li><em>Tool</em>이 결과를 <em>MCP</em>에  반환 → 표준화 된 형태로 <em>Agent</em>에게 반환</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DevOps] Kubernetes]]></title>
            <link>https://velog.io/@namo_o36/DevOps-Kubernetes</link>
            <guid>https://velog.io/@namo_o36/DevOps-Kubernetes</guid>
            <pubDate>Fri, 29 Aug 2025 09:23:02 GMT</pubDate>
            <description><![CDATA[<h3 id="인프라-발전">인프라 발전</h3>
<blockquote>
<table>
<thead>
<tr>
<th>구분</th>
<th>전통 배포</th>
<th>가상머신 기반 배포</th>
<th>컨테이너 중심 배포</th>
</tr>
</thead>
<tbody><tr>
<td><strong>컴퓨터</strong></td>
<td>물리 컴퓨터 1 대</td>
<td>1대의 컴퓨터 + 다수의 가상 머신</td>
<td>컴퓨터 형태에 영향 x</td>
</tr>
<tr>
<td><strong>운영체제(OS)</strong></td>
<td>OS 1개 설치</td>
<td>가상머신 각각에 OS 설치</td>
<td>형태와 관련 없이 설치된 OS 1개</td>
</tr>
<tr>
<td><strong>리소스</strong></td>
<td>1대의 자원 나눠씀</td>
<td>하이퍼바이저로 가상머신 별 개별 자원 할당</td>
<td>OS에서 프로그램 별 자원 할당 관리</td>
</tr>
<tr>
<td><strong>격리 수준</strong></td>
<td>격리되지 않음, 프로그램 간섭 발생 가능성</td>
<td>프로그램 별 완전 격리</td>
<td>실행 환경은 격리, OS 자원은 공유</td>
</tr>
<tr>
<td><strong>문제 전이 가능성</strong></td>
<td>한 서비스 문제 발생 시 전체 영향</td>
<td>VM 단위로 격리</td>
<td>프로그램이 OS에 문제를 줄 경우 시스템 중단 가능성</td>
</tr>
</tbody></table>
</blockquote>
<h4 id="컨테이너는-커널을-공유함">컨테이너는 커널을 공유함</h4>
<ul>
<li>가상 머신의 경우 OS 전체를 포함, 자신만의 커널을 가지기 때문에 완전한 독립된  환경</li>
<li>컨테이너의경우 Host의 커널을 함께 씀 -&gt; 애플리케이션, 라이브러리만 포함하기 때문에 훨씬 가볍고 빠름</li>
</ul>
<p>🎒 <strong>컨테이너 = 가방</strong>
🏠 <strong>가상 머신 = 집 또는 창고</strong></p>
<ul>
<li>가상머신의 경우 <strong>집 안에 가전, 전기, 수도 전부 포함되어 있음(자체 커널 존재)</strong> vs 가방만 챙긴 경우  <strong>가전, 전기, 수도는 외부에서 얻어서 써야 함</strong></li>
<li>가상머신은 집 하나 통째로 쓰기 때문에 <strong>문제 생겨도 다른 집에 영향 없음</strong> vs 컨테이너는 어느 정도 독립적이지만 커널(밖의 기반 시설)은 공유하므로 <strong>일부 영향 받을 수 있음</strong></li>
<li>가상머신은 <strong>무거워서 옮기기 어려움</strong> vs 컨테이너는 <strong>가볍고 어디든 쉽게 이동 가능</strong></li>
<li>가상머신은 <strong>전기·수도·공간 모두 많이 사용</strong> vs 컨테이너는 <strong>꼭 필요한 것만 챙겨서 리소스 효율적 사용</strong></li>
</ul>
<blockquote>
<p>📌 <strong>가방 하나</strong> 들고 여행 가는 게 컨테이너, <strong>집 한 채</strong> 짓고 들어가는 게 가상머신
📌 <strong>가방</strong>(컨테이너)는 빠르고 가볍지만, 완전한 독립이 필요하다면 <strong>집</strong>(가상머신)이 더 적합</p>
</blockquote>
<hr>
<h3 id="쿠버네티스kubernetes">쿠버네티스(kubernetes)</h3>
<ul>
<li>K8s라고도 하는데 쿠버네티스에서(Kubernetes)에서 K와 s 사이에 8글자를 나타내는 약식 표기</li>
<li>컨테이너 오케스트레이션 도구</li>
<li>쿠버네티스는 여러 대의 서버(호스트)를 하나의 클러스터로 만들어줌 + 타 오케스트레이션 도구와 비교해 세부적인 기능을 더욱 폭넓게 제공</li>
<li>도커는 한 대의 물리적 서버에서 실행 &lt;-&gt;  쿠버네티스는 <strong><em>여러 대의 물리적 서버</em></strong> + <strong><em>각 서버 별 여러 개의 컨테이너 실행</em></strong></li>
<li>쿠버네티스에서 <strong><em>docker</em></strong>를 사용해서 컨테이너를 관리하는 셈</li>
</ul>
<h4 id="특징">특징</h4>
<ul>
<li>모든 리소스를 <strong><em>오브젝트 형태</em></strong>로 관리함 <ul>
<li>ex) Node, Pod, Replica Set</li>
</ul>
</li>
<li>모든 리소스의 경우 <code>YAML</code> 파일로 작성 됨</li>
<li>서버마다 정해진 역할이 있을것 → 여러 개의 컴포넌트로 구성</li>
</ul>
<h4 id="장점">장점</h4>
<ul>
<li>여러 대의 서버를 하나의 <strong><em>클러스터</em></strong> 로 만들어줌</li>
<li>마이크로 서비스 구조의 컨테이너 배포, 서비스 장애 복구 등 컨테이너 기반 서비스 운영에 필요한 대부분의 오케스트레이션 기능을 지원</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>구조가 복잡, 사용법이 다양 → 학습 비용이 높음</li>
<li>소규모 조직 및 회사에서는 쿠버네티스 기능이 오버 엔지니어링일 수 있음</li>
</ul>
<hr>
<h3 id="쿠버네티스-→-바람직한-상태를-유지">쿠버네티스 → ‘바람직한’ 상태를 유지</h3>
<ul>
<li>쿠버네티스가 컨테이너 생성 및 삭제 시 명령어를 일일이 입력하는 방식을 사용하지는 않음</li>
<li>어떤 <em>‘바람직한’</em> 상태를 <code>YAML</code> 파일에 정의한 뒤 자동으로 컨테이너를 생성하거나 삭제하면서 이 상태를 만들고 유지하는 것이 쿠버네티스 기본 아이디어이다.<ul>
<li>ex) 컨테이너가 하나 망가질 경우 → 해당 컨테이너를 자동 삭제, 새 컨테이너 대체</li>
<li>ex) 바람직한 상태가 바뀔 경우 → 컨테이너 한 개 삭제</li>
</ul>
</li>
</ul>
<h3 id="클러스터--마스터-노드--워커-노드">클러스터 = 마스터 노드 + 워커 노드</h3>
<ul>
<li><strong>마스터 노드</strong><ul>
<li>전체적인 제어 담당</li>
<li>컨테이너를 실행하지 않으며 워커 노드에서 실행되는 컨테이너들 관리 역할을 하기 때문에 도커 엔진 같은 컨테이너 엔진도 x</li>
<li>컨테이너 상태 관리를 위한 <code>etcd</code> 라는 데이터 베이스가 설치되어야 함</li>
</ul>
</li>
<li><strong>워커 노드</strong><ul>
<li>실제 동작 담당</li>
<li>컨테이너가 동작해야 하기 때문에 컨테이너 엔진이 설치되어 있어야 함</li>
</ul>
</li>
<li><strong>노드(Node)</strong><ul>
<li>물리적 서버와 일치하는 개념</li>
<li>실제 애플리케이션을 실행하는 물리적 또는 가상 머신 </li>
</ul>
</li>
</ul>
<h3 id="etcd">Etcd</h3>
<ul>
<li><code>Key</code>:<code>value</code> 형태의 데이터를 저장하는 저장소</li>
<li>분산 시스템에서 중요한 데이터를 저장할 때 사용</li>
<li>클러스터 관련 정보 전반을 관리하는 데이터 베이스</li>
</ul>
<hr>
<h3 id="쿠버네티스-구성-요소">쿠버네티스 구성 요소</h3>
<p><img src="https://velog.velcdn.com/images/namo_o36/post/cca4f8c7-83c0-4d11-822b-8817dd8c6f7d/image.jpg" alt=""></p>
<h4 id="➡️-파드-pod">➡️ 파드 (Pod)</h4>
<ul>
<li><strong>컨테이너</strong>(여러 개 가능) + <strong>볼륨</strong>(하나도 없는 경우도 가능)</li>
<li><strong><em>애플리케이션의 기본 단위</em></strong> → 각각 기능을 담는 컨테이너들이 1개 이상 모여 있는 묶음을 <strong>포드</strong>라고 함.</li>
<li>하나의 컨테이너라도 제대로 작동하지 않으면 애플리케이션이 실행되지 않음 → 컨테이너가 정상적으로 실행될 때까지 컨테이너를 계속 restart 시킴</li>
<li>내부 컨테이너들은 <code>Pod</code>의 IP를 <code>ocalhost</code>로 공유함</li>
<li><code>Pod</code>는 따로 포트번호를 갖지 않음 → 내부 컨테이너들이 포트 번호를 가짐</li>
</ul>
<h4 id="➡️-pod-생성을-위한-yaml-파일">➡️ Pod 생성을 위한 <code>YAML</code> 파일</h4>
<pre><code class="language-YAML">apiVersion: v1
kind: Pod
metadata:
  name: &lt;pod 이름&gt;
spec:
  containers:
  - name: &lt;컨테이너 이름&gt;
    image: &lt;기본 이미지&gt;
    ports:
    - containerPort: &lt;컨테이너가 노출할 Port&gt;</code></pre>
<br>

<h4 id="➡️-서비스">➡️ 서비스</h4>
<ul>
<li>위의 <code>pod</code>들이 모인 것 → 클러스터 외부로부터 요청을 받을 수 있게 IP를 노출하는 역할을 하는 리소스</li>
<li><code>pod</code>를 외부에 노출해 사용자들이 접근하거나 다른 <code>Deployment</code>의 <code>pod</code>들이 내부에 접근하려면 <code>Service</code>를 별도로 생성해야 한다.</li>
<li>여기서 말하는 <code>service</code>는 여러 <code>pod</code>들을 이끄는 반장 정도로 생각</li>
<li>특정 <code>service</code>가 관리하는 <code>pod</code>들은 모두 동일한 구성을 가짐</li>
<li>로드 밸런서(부하 분산장치) 역할 → 각 서비스는 자동으로 고정된 IP 주소를 부여 받음 → 이 주소로 들어오는 통신 처리</li>
<li>내부에 여러 개의 <code>pod</code>가 있어도 밖에는 하나의 IP 주소만 볼 수 있음 → 해당 주소로 접근할 경우 <code>service</code>가 통신을 적절히 분배해줌</li>
</ul>
<h4 id="➡️-service-타입">➡️ Service 타입</h4>
<ul>
<li><strong>ClusterIP</strong><ul>
<li>가장 기본이 되는 Service 타입</li>
<li>클러스터 내부 통신만 가능, 외부 트래픽은 받을 수 없음</li>
</ul>
</li>
<li><strong>NodePort</strong><ul>
<li>클러스터 내부 및 외부 통신이 가능한 Service 타입</li>
<li>외부 트래픽을 전달받을 수 있고 노드의 포트를 사용함(30000~32767)</li>
<li>Cluster를 구성하는 각 Node에 동일한 port를 열고, 열린 포트를 통해 각 Node 별로 외부 트래픽을 받은 뒤 ClusterIP로 모인 뒤 분산됨</li>
</ul>
</li>
<li><strong>LoadBalancer</strong><ul>
<li>외부 트래픽을 받는 역할</li>
<li>LoadBalancer → NodePort → ClusterIP 순서</li>
<li>AWS, GCP 등과 같은 클라우드 플랫폼 환경에서만 사용 가능</li>
</ul>
</li>
<li><strong>ExternalName</strong><ul>
<li>외부로 나가는 트래픽을 변환하기 위한 용도</li>
</ul>
</li>
</ul>
<br>

<h4 id="➡️-레플리카-세트replicaset">➡️ 레플리카 세트(ReplicaSet)</h4>
<ul>
<li>정해진 수의 동일한 <code>pod</code>가 항상 실행되도록 관리하는 역할을 하는 오브젝트</li>
<li>서비스가 요청을 배분하는 반장이라면, <code>replica set</code>은 <code>pod</code> 수를 관리하는 반장임</li>
<li>모자라는 <code>pod</code>를 보충 혹은 정의 파일에 정의된 <code>pod</code> 수 감소 시 <code>pod</code> 수를 실제로 감소시킴</li>
<li><code>replica set</code>가 관리하는 동일한 구성의 <code>pod</code> ⇒ 레플리카</li>
<li><code>Replica set</code> 생성 <code>YAML</code> 파일</li>
<li>어 근데 동일한 <code>pod</code> 생성이면 → 각 <code>pod</code> 속 컨테이너들이 갖는 포트 번호가 전부 동일하지 않나..? → 그럼 어떤 <code>pod</code> 로 요청을 보내려나? → 이 역할을 하는 것이 <strong><em><code>Service</code></em></strong><pre><code class="language-YAML">apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: &lt;레플리카셋 이름&gt;
spec:
replicas: &lt;Pod 유지 개수&gt;
selector:
  matchLabels:
    app: &lt;관리할 Pod를 찾는 선택기&gt;
template:
  metadata:
    name: &lt;포드 이름&gt;
    labels:
      app: &lt;레플리카셋의 matchLabels의 항목과 동일하게 작성&gt;
  spec:
    containers:
    - name: &lt;컨테이너 이름&gt;
      image: &lt;기본 이미지&gt;
      ports:
      - containerPort: &lt;컨테이너가 노출할 Port&gt;</code></pre>
</li>
</ul>
<br>

<h4 id="➡️-deployment">➡️ Deployment</h4>
<ul>
<li>여러 개의 <code>Replica Set</code>을 관리하기 위한 상위 오브젝트 → 컨테이너 애플리케이션 배포하고 관리하는 역할을 담당함</li>
<li>애플리케이션 업데이트 시 <code>Replica Set</code>의 변경사항을 저장하는 리비전(Rivision)을 남겨놓고 롤백 가능</li>
</ul>
<h4 id="➡️-deployment-strategy">➡️ Deployment Strategy</h4>
<ul>
<li><strong>Recreate</strong><ul>
<li>배포된 모든 앱 제거 후 새로운 버전 앱 한 번에 생성</li>
<li>모든 배포 앱을 다운시킨 뒤 새로운 앱이 업로드될 때까지 사용자 접근이 불가 → 무중단 배포(Zero Downtime Deployment)가 불가능</li>
<li>ex) 5개를 지운 뒤 새로운 앱 5개 새로 띄우기</li>
</ul>
</li>
<li><strong>Rolling Update</strong><ul>
<li>현재 배포된 pod를 새로운 pod로 점진적으로 교체함</li>
<li>구 버전을 부분적으로 내린 뒤, 그 수만큼의 새 버전을 배포 → 모두 새 버전이 될 때까지 반복함</li>
</ul>
</li>
</ul>
<br>

<h4 id="➡️-cluster-autoscaling">➡️ Cluster AutoScaling</h4>
<ul>
<li>쿠버네티스에서 <strong>Cluster Autoscaler</strong>는 클러스터의 리소스 사용량에 따라  <strong>노드의 수를 자동으로 늘리거나 줄이는 역할</strong>을 한다.  </li>
</ul>
<ul>
<li><strong>Cluster AutoScaling 동작 과정</strong><ol>
<li><strong>Pending 상태인 Pod 발생</strong><ul>
<li>새로 생성된 Pod가 리소스를 충분히 할당받지 못하면 <code>Pending</code> 상태로 대기함</li>
<li>Cluster Autoscaler는 주기적으로 클러스터 상태를 확인하여 <strong>스케줄링되지 않은 <code>Pending Pod</code>를 탐지</strong></li>
</ul>
</li>
<li><strong>리소스 부족 판단</strong><ul>
<li>Autoscaler는 쿠버네티스 API 서버와 통신하여 현재 스케줄되지 못한 Pod들의 목록을 가져옴</li>
<li>현재 노드들의 자원을 고려했을 때해당 Pod들을 수용할 수 없다고 판단</li>
</ul>
</li>
<li><strong>필요한 노드 수 계산 및 확장 요청</strong><ul>
<li>Cluster Autoscaler는 얼마나 많은 노드를 추가해야 하는지 계산<ul>
<li>해당 값 기반 <strong>Auto Scaling Group (ASG)</strong>의 Desired Capacity를 증가시킴</li>
<li>새로운 노드가 자동으로 생성됨</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<br>

<h4 id="➡️-hpahorizontal-pod-autoscaler">➡️ HPA(Horizontal Pod Autoscaler)</h4>
<ul>
<li>Pod 수를 스케일아웃하여 서비스 요청을 여러 pod로 분산시키는 방법</li>
<li>쿠버네티스의 HPA는 주기적으로 MetricStore로부터 Pod의 metric을 얻고, 이를 기반으로 pod 수를 계산해 pod 수 경</li>
<li>명령어 → Helm 이용<ul>
<li><strong>Helm</strong> : 쿠버네티스 용 소프트웨어 검색, 사용을 위한 패키지 관리자<pre><code>helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm repo update
helm show values metrics-server/metrics-server &gt; values.yaml</code></pre></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DevOps] Docker 실습]]></title>
            <link>https://velog.io/@namo_o36/DevOps-Docker-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@namo_o36/DevOps-Docker-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Fri, 29 Aug 2025 02:44:03 GMT</pubDate>
            <description><![CDATA[<h3 id="1-도커-설치">1. 도커 설치</h3>
<ul>
<li>도커 사용 시 → 시스템 &gt; 정보  에서 윈도우 버전 확인하기(WSL 2 기능 지원하는지?)</li>
<li>WSL(Window Subsystem for Linux)<ul>
<li>윈도우즈에서 경량 가상화 기술을 써서 리눅스</li>
</ul>
</li>
<li>가상화 ‘사용’으로 되어 있는지 확인</li>
<li><a href="https://www.docker.com/products/docker-desktop/">https://www.docker.com/products/docker-desktop/</a> 에서 도커 다운</li>
</ul>
<blockquote>
<p><strong>WSL(Window Subsystem for Linux)</strong>
    - 윈도우에서 경량 가상화 기술을 이용해 리눅스 구동을 돕는 기능
    - 가상 머신을 사용하는 것보다 빠르게 윈도우 환경에서 리눅스 환경을 구축할 수 있음</p>
</blockquote>
<hr>
<h3 id="2-이미지-실습">2. 이미지 실습</h3>
<h4 id="2-1-이미지-검색">2-1) 이미지 검색</h4>
<ul>
<li>컨테이너 생성 전 필요한 이미지 확인</li>
<li><a href="https://hub.docker.com">https://hub.docker.com</a> 에서 확인</li>
<li><code>docker search [키워드]</code> 명령어로 이미지 조회 가능</li>
<li>필자는 <code>tomcat</code> 이미지를 Docker 홈페이지에서 다운 받을 예정</li>
<li>공식 사이트에서 올린 이미지는 <code>OFFICIAL</code>이라는 키워드에 <code>[OK]</code>라고 붙어 있음<img src="https://velog.velcdn.com/images/namo_o36/post/ee822d49-1386-4d43-97f2-f7af224661cc/image.png" width="600"/>



</li>
</ul>
<br>

<h4 id="2-2-이미지-다운">2-2) 이미지 다운</h4>
<ul>
<li><code>docker pull [이름]</code> 으로 도커 이미지 다운</li>
<li><code>tag</code>를 추가하지 않으면 자동으로 latest image가 다운됨<img src="https://velog.velcdn.com/images/namo_o36/post/4cb94e5c-0620-40df-86c1-b30ed19973e3/image.png" width="700"/></li>
<li><code>docker images</code> → 다운 받은 이미지들 확인<img src="https://velog.velcdn.com/images/namo_o36/post/e541da49-ee85-4c69-939b-5000c085deb2/image.png" width="700"/></li>
<li><code>docker image inspect [이미지명] [Tag]</code> or <code>docker image inspect [이미지 ID]</code> → 특정 이미지에 대한 상세 정보 확인<img src="https://velog.velcdn.com/images/namo_o36/post/797e57ed-dd4d-407a-b099-3d58a4e0df09/image.png" width="700"/></li>
<li><code>Cmd</code> : 명령어 → 이미지를 가지고 컨테이너 생성 시 가장 먼저 실행될 명령어 <code>catalina.sh run</code> 방식으로 실행됨<img src="https://velog.velcdn.com/images/namo_o36/post/0c93e7f1-6589-4b25-9f6c-4ef6340a3a9e/image.png" width="300"/>

</li>
</ul>
<br>

<h4 id="2-3-이미지-저장">2-3) 이미지 저장</h4>
<ul>
<li><code>docker save -o ./[저장할 이름] [저장할 이미지]</code> → 도커 이미지 파일 로컬에 저장<img src="https://velog.velcdn.com/images/namo_o36/post/9a4ff6ea-7b86-4335-9bb8-3051cbc4743b/image.png" width="700"/></li>
<li><code>docker load -i .\redis_image</code> → 파일로 저장된 이미지를 load 하기<img src="https://velog.velcdn.com/images/namo_o36/post/11884020-0f4b-4fd7-8836-bf9a4892d919/image.png" width="700"/>




</li>
</ul>
<br>

<h4 id="2-4-이미지-삭제">2-4) 이미지 삭제</h4>
<ul>
<li><code>docker rmi [이미지 이름]</code> → 도커 이미지 삭제<img src="https://velog.velcdn.com/images/namo_o36/post/0ce5eb50-171b-4faf-9fde-b63eca636957/image.png" width="700"/></li>
<li><code>docker tag [기존 이미지명] [새로운 이름]</code>  → 이미지 아이디는 동일하지만 부르는 이름만 다른 셈<img src="https://velog.velcdn.com/images/namo_o36/post/79273574-7a30-43bf-9ae8-6fe8d27ea8af/image.png" width="700"/></li>
<li><code>tag</code>로 이름 붙인 이미지는 ID로 삭제할 수 없음. 이름으로 따로 따로 삭제해주어야 함<img src="https://velog.velcdn.com/images/namo_o36/post/5d16712a-d735-4530-8d47-f8a3b547f938/image.png" width="700"/>

</li>
</ul>
<hr>
<br> 

<h3 id="3-컨테이너-실습">3. 컨테이너 실습</h3>
<h4 id="3-1-컨테이너-생성--실행">3-1) 컨테이너 생성 &amp; 실행</h4>
<ul>
<li><code>docker run</code> 으로 컨테이너 생성 → 실행, 이미지가 없으면 먼저 다운 받은 다음에 실행함<img src="https://velog.velcdn.com/images/namo_o36/post/14cdfdc6-c230-4de1-be1f-715b6d0e71f0/image.png" width="700"/></li>
<li><code>docker ps</code> : 현재 실행 중인 컨테이너 확인</li>
<li><code>Tomcat</code>을 종료할 경우, 컨테이너도 같이 종료됨. → 컨테이너는 애플리케이션 단위로 가상화 되기 때문<img src="https://velog.velcdn.com/images/namo_o36/post/3b0fd152-eda5-4b2e-aa26-382d67a437f9/image.png" width="700"/></li>
<li><code>docker run --name [원하는 이름] [이미지 이름]</code> : 컨테이너 실행
<img src="https://velog.velcdn.com/images/namo_o36/post/1130eeda-c0b1-476b-acfa-ed007d09fcbc/image.png" width="700"/><img src="https://velog.velcdn.com/images/namo_o36/post/b770a3f0-1380-4312-9e22-3e21199b2a16/image.png" width="700"/></li>
</ul>
<br>

<h4 id="3-2-컨테이너-삭제">3-2) 컨테이너 삭제</h4>
<ul>
<li><code>docker stop [컨테이너 이름]</code> : 현재 실행중인 컨테이너는 삭제 불가 -&gt; 어지간해선 종료하고  지우자 </li>
<li><code>force remove</code>로 삭제할 수는 있음(<code>-f</code>)
<img src="https://velog.velcdn.com/images/namo_o36/post/6838beb2-4d3d-42a6-b9f6-9a6b562bd2b4/image.png" width="700"/><img src="https://velog.velcdn.com/images/namo_o36/post/d79d2164-5fba-4587-8ae0-3fc7064c1720/image.png" width="700"/></li>
</ul>
<br>

<h4 id="3-3-컨테이너-실행-모드">3-3) 컨테이너 실행 모드</h4>
<ul>
<li><strong>attach 모드</strong>
  <strong>→</strong> foreGround 에서 실행</li>
<li><strong>detach 모드</strong> 
  <strong>→</strong> 우리에게 보이지 않게 백그라운드에서 실행 → 현재 터미널에서 분리되어 실행
  <strong>→</strong> 터미널에서 애플리케이션 실행하는 Attach 모드(Foreground 실행)과 달리 프롬프트가 바로 돌아옴 → <code>docker ps -a</code> 로 확인할 경우 실행중임을 확인할 수 있음</li>
</ul>
<br>

<h4 id="3-4-포트-포워딩-설정">3-4) 포트 포워딩 설정</h4>
<ul>
<li>외부 접속을 위해선 포트 포워딩을 해주어야 함</li>
<li><code>localhost:8080</code>으로 접속하기 위해선 호스트의 특정 포트를 열어서 컨테이너에 연결해주어야 한다.</li>
<li><code>docker run -p [호스트포트]:[컨테이너포트] 이미지이름</code></li>
<li>ex) <code>8080:80</code> 인 경우, 호스트(내 컴퓨터)의 <code>8080</code>번 포트로 접속 시 → <code>Docker</code>가 이 요청을 컨테이너 내부의 <code>80</code>번 포트로 전달해줌<img src="https://velog.velcdn.com/images/namo_o36/post/be7ce451-9fe3-494b-8ad3-88ea13538ff0/image.png" width="700"/>

</li>
</ul>
<br>

<h4 id="3-5-컨테이너-실행">3-5) 컨테이너 실행</h4>
<ul>
<li><code>docker exec [컨테이너 이름] [명령어]</code> → 컨테이너에 접근 / 이미 실행중인 컨테이너에 새로운 명령어 실행</li>
<li>사용자와 컨테이너 사이 상호 입출력 → <code>-i</code></li>
<li>상호 입출력은 터미널을 통해서 사용 → <code>-t</code></li>
<li><code>docker exec -it tomcat_10.1.39 /bin/bash</code> → 가상의 터미널을 생성해 컨테이너와 상호 입출력 하겠다 → 터미널 지정 했으니 컨테이너 내부에서 셀 프로그램 실행하겠다 → <code>bin/bash shell</code>로 입력하겠다-
<img src="https://velog.velcdn.com/images/namo_o36/post/30bab43e-4031-41ef-8631-f9db1b499c3a/image.png" width="700"/><img src="https://velog.velcdn.com/images/namo_o36/post/1705636e-1818-4db4-a210-3d3092104df9/image.png" width="700"/></li>
<li>톰캣 서버 시작 시 <code>webapps</code>디렉토리에 있는 모든 앱을 자동으로 배포, 실행함</li>
<li>기존의 <code>webapps</code>를 삭제한 뒤, <code>webapps.dist</code> 이름을 <code>webapps</code>으로 변경<img src="https://velog.velcdn.com/images/namo_o36/post/1f3a5a77-96e4-46f5-ad77-953810f63065/image.png" width="700"/></li>
<li><code>docker run -it --name alpine-linux alpine:latest</code> → <code>alpine 리눅스</code> 실행</li>
<li>실행 시 바로 <code>shell</code>이 터미널과 연결되도록 <code>-it</code>로 실행.</li>
<li><code>shell</code> 실행 되어도 터미널과 연결되어 있지 않을 경우 컨테이너가 죽음 → 터미널이 종료된 상태로 간주, 프로그램 종료 → 컨테이너 종료<img src="https://velog.velcdn.com/images/namo_o36/post/d172037c-3cec-4f30-bd3c-0092700d2a77/image.png" width="700"/>


</li>
</ul>
<hr>
<br>

<h3 id="4-로그-확인">4. 로그 확인</h3>
<ul>
<li><code>docker logs [컨테이너 이름]</code></li>
<li><code>docker logs -t [컨테이너 이름]</code> → timestamp도 찍힘</li>
<li><code>docker logs -t --tail 20 [컨테이너 이름]</code> → 마지막 20줄만 출력</li>
<li><code>docker logs -t -f --tail 20 [컨테이너 이름]</code> → 실시간 로그 확인</li>
</ul>
<hr>
<br> 

<h3 id="5-도커-볼륨-설정">5. 도커 볼륨 설정</h3>
<ul>
<li><code>docker volume create [생성할 볼륨 이름]</code> : 도커 볼륨 생성 명령어 -&gt; 볼륨은 호스트에 저장되는 저장소</li>
<li><code>docker run -v [볼륨 이름]:[컨테이너 내부 디렉토리 경로] --name [컨테이너 이름] [이미지 이름]</code> : 생성한 볼륨을 컨테이너 내부에 있는 디렉토리랑 연결 -&gt; 컨테이너에서 해당 디렉토리에 저장된 내용은 볼륨에 저장됨 -&gt; 컨테이너 삭제 후 해당 볼륨 내 데이터는 남아있음</li>
<li><code>docker volume ls</code> : 현재 생성된 볼륨 목록 확인</li>
<li><code>docker volume inspect [볼륨 이름]</code> :    볼륨 상세 정보 확인 (실제 경로 포함)</li>
<li><code>docker volume rm [볼륨 이름]</code> : 볼륨 삭제</li>
<li><code>docker volume prune</code> : 사용하지 않는 볼륨 모두 삭제 </li>
</ul>
<hr>
<br>

<h3 id="6-도커-네트워크">6. 도커 네트워크</h3>
<ul>
<li>Docker는 기본적으로 <code>Bridge</code> 네트워크 모드를 사용해 컨테이너들 간 통신을 가능하게 해준다.</li>
<li><code>Bridge</code> 네트워크는 같은 네트워크에 속한 컨테이너들끼리 서로의 컨테이너 이름으로 통신이 가능하도록 설정해준다. 
  → 간단히 말해 <strong>*&quot;같은 Docker 네트워크 안에 있는 컨테이너들은 내부 IP나 컨테이너 이름을 통해 자유롭게 통신할 수 있다.&quot;*</strong></li>
<li>직접 Docker 네트워크 이름, DNS, 구조화 기능 지정도 가능</li>
</ul>
<blockquote>
<p>** 네트워크 타입 **</p>
</blockquote>
<ul>
<li><code>bridge</code> : 기본 네트워크, 컨테이너 간 통신 가능</li>
<li><code>host</code> : 호스트 네트워크 공유 (포트 포워딩 없이 사용)</li>
<li><code>none</code> : 네트워크 없음 (완전 격리)</li>
</ul>
<br>

<h4 id="6-1-도커-네트워크-실습">6-1) 도커 네트워크 실습</h4>
<ul>
<li><code>docker network create university</code> : 컨테이너 전용 네트워크(university) 생성 / 애플리케이션 배포할 네트워크를 하나 만들 예정</li>
<li>컨테이너들을 한 네트워크에 포함시킬 예정 → 같은 네트워크 내에서 컨테이너 간 통신 가능하도록 설정</li>
<li><code>docker-root</code>, <code>docker-beyond</code> 두 개의 컨테이너를 포트 <code>3307</code>에 연결</li>
<li>MariaDB 컨테이너에 볼륨과 네트워크 설정 적용</li>
<li>재부팅해도 자동 실행되도록 <code>--restart=always</code> 사용
<img src="https://velog.velcdn.com/images/namo_o36/post/f346fdb8-008d-4347-932f-26dbce4574cb/image.png" width="700"/><img src="https://velog.velcdn.com/images/namo_o36/post/9d2ae454-4f9a-4043-928d-fa1ea681a224/image.png" width="700"/></li>
</ul>
<br>

<h4 id="6-2-명령어">6-2) 명령어</h4>
<ul>
<li><code>docker network ls</code> : 네트워크 목록 확인</li>
<li><code>docker network inspect [네트워크 이름]</code> : 네트워크 상세 정보 확인 </li>
<li><code>docker network create [네트워크 이름]</code> : 사용자 정의 네트워크 생성</li>
<li><code>docker run --network [네트워크 이름] ...</code> : 네트워크에 컨테이너 연결 - 컨테이너 생성 시 바로 연결</li>
<li><code>docker network connect [네트워크 이름] [컨테이너 이름]</code> : 이미 생성된 컨테이너 연결</li>
<li><code>docker network disconnect [네트워크 이름] [컨테이너 이름]</code> : 네트워크에서 컨테이너 분리</li>
<li><code>docker network rm [네트워크 이름]</code> : 네트워크 삭제</li>
<li><code>docker network prune</code> : 사용되지 않는 네트워크 삭제 </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DevOps] Docker]]></title>
            <link>https://velog.io/@namo_o36/DevOps-Docker</link>
            <guid>https://velog.io/@namo_o36/DevOps-Docker</guid>
            <pubDate>Fri, 29 Aug 2025 01:26:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>가상화(Virtualization)</strong>
    - 하드웨어 기능을 소프트웨어로 분리하고 추상화하는 기술
    - 하나의 물리적 시스템에서 여러 개의 가상 시스템 실행 가능</p>
</blockquote>
<h3 id="가상머신vm-virtual-machine">가상머신(VM, Virtual Machine)</h3>
<ul>
<li>가상 머신은 컴퓨터상에 논리적으로 만들어낸 <em><strong>&quot;가상 컴퓨터&quot;</strong></em></li>
</ul>
<p><strong>동작 원리</strong></p>
<ul>
<li>하나의 물리 자원(서버)에 가상화 계층(→ Hypervisor)을 설치</li>
<li>Hypervisor 위에 <strong>여러 개의 운영체제(Guest OS)</strong>를 설치하여 독립적으로 운영</li>
<li>각 VM은 자체 운영체제, 커널, 드라이버 등을 포함함</li>
</ul>
<hr>
<h3 id="도커docker">도커(Docker)</h3>
<ul>
<li>2013년 3월 출시된 오픈 소스 컨테이너 프로젝트</li>
<li>Go 프로그래밍 언어로 작성되어 리눅스 컨테이너에 여러 기능을 추가해 애플리케이션을 개발, 실행하기 위한 플랫폼</li>
<li>가상 머신과는 다르게 도커 컨테이너는 게스트 OS를 설치하지 않아 가상 머신보다 이미지 용량이 크게 줄어 서비스 개발 및 운영 환경에 도커를 많이 사용함</li>
</ul>
<br>

<h3 id="도커-구성-요소">도커 구성 요소</h3>
<p><img src="https://velog.velcdn.com/images/namo_o36/post/8671b011-0647-48fd-a9c0-d335c0d254f7/image.png" alt=""></p>
<ul>
<li><strong><em>클라이언트-서버 아키텍처</em></strong> 사용</li>
<li><strong><em>Docker Client</em></strong> : 도커 설치하면 그것이 Client → <code>pull</code>, <code>build</code>, <code>run</code> 등의 명령어 사용</li>
<li><strong><em>Docker host</em></strong> : 도커가 띄워져 있는 서버를 의미, 컨테이너와 이미지를 관리함</li>
<li><strong><em>Docker daemon</em></strong> : 도커 엔진</li>
<li><strong><em>Registry</em></strong> : 외부 이미지 저장소 → 도커 host에 pull할 수 있음 → 가져온 이미지 run</li>
<li><strong><em>Docker Image</em></strong> : 컨테이너를 만들고 실행하기 위한 읽기 전용 파일</li>
</ul>
<blockquote>
<p><strong><em>도커는 리눅스 컴퓨터에서 사용</em></strong>
    - 종류와 상관 없이 리눅스 운영체제가 사용됨.
    - 윈도우, macOS에서도 도커  구동은 가능하지만, 내부적으로 리눅스가 사용됨 <br>
<strong><em>WSL(Window Subsystem for Linux)</em></strong>
    - 별도의 가상머신 또는 이중 부팅 없이 윈도우에서 리눅스를 사용할 수 있게 하는 Window 기능</p>
</blockquote>
<hr>
<h3 id="docker-image">Docker Image</h3>
<ul>
<li>모든 컨테이너는 이미지를 기반으로 생성</li>
<li>컨테이너 생성을 위한 ‘읽기 전용 파일’</li>
<li>컨테이너 생성을 위한 템플릿, 즉 일종의 “틀” → 애플리케이션을 실행할 수 있는 최소한의 환경만 제공하기 때문에 경량화된 OS 환경 같은 느낌임</li>
<li>도커 컨테이너는 도커 이미지를 실행한 인스턴스 → 컨테이너 상태를 기반으로 새로운 이미지를 생성할 수도 있음</li>
<li>이미지는 컨테이너 실행에 필요한 파일과 설정값 등을 포함하고 있다.</li>
</ul>
<p><strong>도커 허브(Docker Hub)</strong></p>
<ul>
<li>도커에서 공식적으로 제공하는 이미지 저장소</li>
<li>태그는 일반적으로 버전을 명시, 버전 생략 시 latest로 인식</li>
</ul>
<p><strong>명령어 정리</strong></p>
<ul>
<li><code>docker search [키워드]</code> : 이미지 검색</li>
<li><code>docker pull [이미지 이름]:[태그]</code> : 이미지 내려받기</li>
<li><code>docker images [이미지 이름]</code> : 도커 허브에서 내려받은 이미지 목록 확인</li>
<li><code>docker image inspect [이미지 ID]</code> : 이미지 세부 정보 출력</li>
<li><code>docker save -o [추출할 파일 이름] [이미지 이름]:[태그]</code> : 이미지를 파일로 추출</li>
<li><code>docker load -i [추출된 파일 이름]</code> : 추출한 이미지 로드</li>
<li><code>docker rmi [이미지 이름, 이미지 ID]</code> : 이미지 삭제</li>
<li><code>docker commit [옵션] [컨테이너 이름] [이미지 이름]:[태그]</code> : 이미지 생성</li>
</ul>
<hr>
<h3 id="docker-container">Docker Container</h3>
<ul>
<li>도커 이미지를 실행한 상태</li>
<li>컨테이너는 리눅스 커널 기능을 활용하여, 프로세스 단위로 격리된 실행 환경을 제공하는 기술 ⇒ 컨테이너 안에는 애플리케이션 구동에 필요한 라이브러리 및 실행 파일만 존재 ⇒ 용량이 작음</li>
<li>기존의 가상머신처럼 운영체제 전체를 가상화하는 것이 아니라, 애플리케이션이 실행되는 환경만을 가상화 함 ⇒ 컨테이너에서 실행되는 애플리케이션이 종료되면 컨테이너도 같이 종료됨</li>
</ul>
<br>

<p><strong>컨테이너의 핵심 기술 요소</strong>
가상화 공간 생성을 위해 리눅스 자체 기능인 <code>chroot</code>, <code>namespace</code>, <code>cgroup</code>을 사용함으로써 프로세스 단위의 격리 환경을 만듦 → 성능 손실 x</p>
<ul>
<li><code>chroot</code>: 파일 시스템 루트 변경 → 격리된 디렉토리 환경 제공</li>
<li><code>namespace</code>: 프로세스, 네트워크, 사용자 ID 등을 격리</li>
<li><code>cgroup</code>: 리소스(CPU, 메모리 등) 사용 제한 및 할당 관리</li>
</ul>
<p>**  명령어 정리 **</p>
<ul>
<li><code>docker run [옵션] [이미지 이름]</code> : 컨테이너 생성 및 실행</li>
<li><code>docker ps [옵션]</code> : 컨테이너 목록 조회(실행 중)</li>
<li><code>docker ps [옵션] - a</code> : 컨테이너 목록 조회(모든 컨테이너)</li>
<li><code>docker stop [컨테이너 이름 / 컨테이너 ID]</code> : 컨테이너 종료</li>
<li><code>docker exec [컨테이너 이름 / 컨테이너 ID] [명령]</code> : 테이너에서 특정 명령을 실행</li>
<li><code>docker rm [옵션] [컨테이너 이름]</code> : 컨테이너 삭제 / 실행 중인 컨테이너는 삭제할 수 없음(중지 후 삭제 or <code>-f</code> 추가</li>
<li><code>docker logs [컨테이너 이름]</code> : 컨테이너 로그 출력 / <code>--tail</code>(마지막 로그 줄부터 출력할 줄의 수를 설정) / <code>-f</code>(컨테이너에서 실시간으로 출력되는 로그를 확인 가능) / <code>-t</code>(날짜 시간)</li>
</ul>
<hr>
<h3 id="docker-volume">Docker Volume</h3>
<ul>
<li>컨테이너를 삭제하게 될 경우 컨테이너에서 작업했던 데이터는 모두 삭제됨 → 컨테이너는 삭제되어도 데이터는 유지하고 싶은 경우에 <em><strong>&quot;볼륨&quot;</strong></em> 사용</li>
<li>내부 컨테이너 데이터를 외부로 링크를 걸어주는 기능 → 컨테이너 내부에서 데이터가 수정된 즉시 볼륨 걸린 외부 데이터도 같이 수정됨 → 이후 컨테이너 삭제 시 외부 볼륨은 남게 됨.</li>
<li>실제 물리 저장소를 쓰는 것처럼 컨테이너 내부로 해당 볼륨을 마운트해서 사용
  → <strong><em>마운트</em></strong> : 외부 저장소에 저장된 데이터를 현재 시스템의 파일 시스템 구조에 연결해서 사용
  → <strong><em>도커에서 마운트</em></strong> : 호스트 시스템의 파일 또는 디렉토리를 도커 컨테이너의 파일 시스템에 연결하는 작업을 의미.</li>
</ul>
<p>** 명령어 **</p>
<ul>
<li><code>docker -v [호스트 디렉토리] : [컨테이너 디렉토리]</code> : 컨테이너 생성 시 호스트와 볼륨 공유</li>
<li><code>docker volume create --name 볼륨 이름</code> : 도커 볼륨 생성</li>
</ul>
<hr>
<h3 id="dockerfile">Dockerfile</h3>
<ul>
<li>도커파일(Dockerfile)은 도커 이미지를 생성하는 설정 파일</li>
<li>컨테이너 생성 후 이미지 커밋 작업 자동화</li>
</ul>
<p><strong>도커파일 작성</strong></p>
<ul>
<li><code>[명령어] [매개값]</code> 의 형태로 작성<ul>
<li>작성된 명령어들은 위에서 아래로 작성된 순서대로 실행</li>
</ul>
</li>
<li><code>Dockerfile</code> 작성 후 빌드할 경우 Docker는 나열된 명령문을 차례로 수행해서 <code>DockerImage</code>를 생성함.</li>
<li><code>FROM ubuntu:latest</code> : ubuntu 이미지에서 새 계층 생성</li>
<li><code>RUN apt-get update</code> : Ubuntu 패키지 관리자인 apt를 이용해 패키지 업데이트</li>
<li><code>RUN apt-get install sudo</code> : 시스템에 sudo 명령어 설치 -&gt; 일반 사용자에게 임시로 관리자 권한 부여</li>
<li><code>RUN sudo apt-get install net-tools</code> :  net-tools : 네트워크 관리 도구 모음 -&gt; ifconfig 명령어 사용 가능</li>
<li><code>CMD ifconfig</code> : CMD : 컨테이너 시작 시 실행될 기본 명령어 지정 -&gt; ifconfig 실행</li>
</ul>
<p>** 도커 파일 빌드**</p>
<ul>
<li><code>docker build --no-cache -f [도커파일 이름] -t [생성할 이미지 이름]:[태그] [도커파일 경로]</code> : 파일 빌드 → 이미지 생성<ul>
<li><code>-f</code> : 생략할 경우 지정한 경로에서 DockerFile 이란 이름의 파일을 찾음</li>
<li><code>-t</code> : 생략할 경우 16진수 형태의 이름의 이미지가 생성됨</li>
<li><code>--no-cache</code> : 빌드 시 캐시 사용하지 않도록 설정</li>
</ul>
</li>
<li>이미지를 생성할 때 지정하는 도커 파일 경로에 해당하는 디렉토리 → <code>컨텍스트</code></li>
</ul>
<p><strong>명령어</strong></p>
<ul>
<li><code>FROM</code> : 도커 파일로 생성할 이미지 기반 이미지를 설정</li>
<li><code>LABEL</code> : 생성 이미지에 메타 데이터 추가</li>
<li><code>RUN</code> : 생성되는 컨테이너 내부에서 명령어 실행</li>
<li><code>ADD</code> : 이미지에 파일 추가</li>
<li><code>COPY</code> : 이미지에 파일 추가</li>
<li><code>WORKDIR</code> : 작업 디렉토리 설정 → cd 명령어와 동일한 기능</li>
<li><code>EXPOSE</code> : 생성된 이미지로 컨테이너 실행 시 노출할 포트</li>
<li><code>CMD</code> : 컨테이너 시작 시 실행할 명령어 설정</li>
<li><code>ENTRYPOINT</code> : 컨테이너 시작 시 실행할 명령어 설정<ul>
<li><code>CMD</code>, <code>ENTRYPOINT</code> 둘 중 하나는 반드시 설정</li>
<li>둘을 같이 사용할 경우 CDM 명령을 인자로 ENTRYPOINT 명령어 출력</li>
</ul>
</li>
<li><code>VOLUME</code> : 볼륨을 사용해 빌드된 이미지로 컨테이너를 생성했을 때 호스트와 공유할 컨테이너 디렉토리를 설정</li>
<li><code>ENV</code> : 생성된 이미지에서 사용할 수 있는 환경 변수 설정</li>
<li><code>ARG</code> : 내부로 값을 전달받을 수 있는 변수를 선언</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DevOps] DevOps & CI/CD]]></title>
            <link>https://velog.io/@namo_o36/DevOps-DevOps-CICD</link>
            <guid>https://velog.io/@namo_o36/DevOps-DevOps-CICD</guid>
            <pubDate>Fri, 29 Aug 2025 00:09:27 GMT</pubDate>
            <description><![CDATA[<h3 id="데브옵스devops">데브옵스(DevOps)</h3>
<blockquote>
<ul>
<li>데브옵스는 <strong>소프트웨어 개발(Development)</strong> + <strong>운영(Operations)</strong>의 합성어로 개발과 운영을 통합하여 소통, 협업, 통합을 강조하는 방법론이다.</li>
</ul>
</blockquote>
<ul>
<li>정보기술 전문가 간의 협업을 통해 소프트웨어 개발과 배포 속도를 높이고, 품질을 개선하는 것을 목표로 한다.</li>
<li>기존엔 개발팀 / 운영팀의 독립 작업이 많았음 -&gt; 두 팀이 소통 + 협업을 바탕으로 문제 해결, 고품질 소프트웨어의 효율적, 안정적인 제공</li>
</ul>
<h3 id="devops-주요-목표">DevOps 주요 목표</h3>
<ul>
<li><strong>자동화 도구 활용</strong>을 통해 반복 작업을 줄이고, <strong>배포 속도를 향상</strong>시키며 <strong>인적 오류를 최소화</strong>하여 시스템의 안정성을 확보한다.</li>
<li><strong>지속적인 통합(CI, Continuous Integration)</strong>과 <strong>지속적인 배포(CD, Continuous Delivery)</strong>를 구현하여, 개발부터 운영까지의 흐름을 자동화하고 신속하게 전달한다.</li>
<li><strong>개발팀과 운영팀 간의 협력과 소통을 강화</strong>하여, 책임을 공유하고 더 나은 결과를 만들어내는 협업 문화를 조성한다.</li>
<li><strong>자동화 및 실시간 모니터링 도구</strong>를 활용하여 시스템 상태를 상시 파악하고, <strong>문제를 조기에 발견하고 신속히 대응</strong>할 수 있도록 한다.</li>
</ul>
<h3 id="devops-tools">DevOps Tools</h3>
<p><strong>버전 관리 도구</strong>
    - GitHub, GitLab, Bitbucket
<strong>CI/CD 도구</strong>
    - Jenkins, GitLab CI/CD, CircleCI, Travis CI, ArgoCD, Spinnaker
<strong>컨테이너 및 오케스트레이션 도구</strong>
    - Docker(애플리케이션을 컨테이너에 패키징), Kubernetes(z컨테이너화된 애플리케이션을 오케스트레이션)
<strong>인프라 관리 도구</strong>
    - Terraform, Ansible
<strong>모니터링 및 로깅 도구</strong>
    - Grafana, ELK 스택 (Elasticsearch, Logstash, Kibana), Nagios</p>
<hr>
<h3 id="cicd">CI/CD</h3>
<p><strong>지속적인 통합(CI, Continuous Integration)</strong></p>
<ul>
<li>코드를 주기적으로 <em><strong>빌드+ 테스트 + 병합</strong></em> 하는 과정</li>
<li>긴 기간 작업한 이후 <code>merge</code>할 경우 코드 충돌 병합에 시간이 오래 걸리게 됨. → 작은 단위로 개발해 주기적으로 빈번하게 <code>merge</code> 해야 함</li>
</ul>
<p><strong>지속적인 배포(CD, Continuous Delivery)</strong></p>
<ul>
<li><em><strong>&quot;지속적인 제공(수동 배포)&quot;</strong></em> / <em><strong>&quot;지속적인 배포(자동 배포)&quot;</strong></em> 라는 2개의 뜻으로 불림</li>
<li><em><strong>지속적인 제공</strong></em> : 배포 준비 상태가 확인되면 개발자, 검증팀이 승인</li>
<li><em><strong>지속적인 배포</strong></em> : CI 단계에서 빌드, 테스트 완료 후 준비 상태 확인 후 자동으로 배포까지</li>
</ul>
<blockquote>
<ul>
<li><em><strong>A 회사</strong></em> : test 코드 자동으로 해도 디자인은 자동 확인이 어렵지! → QA 검수 후 최종 배포 진행 (지속적 제공)</li>
</ul>
</blockquote>
<ul>
<li><em><strong>B 회사</strong></em> : 디자인이 이슈되어도 수정 시 빠르게 배포하도록 합시다! → QA팀은 상시 Product 서버 기준 테스트 해주고 배포는 DevOps 팀에서 맡도록 (지속적 배포)</li>
</ul>
<h3 id="cicd-프로세스">CI/CD 프로세스</h3>
<p><strong>1. 코드 작성</strong>
    - 코드 변경사항 -&gt; Git과 같은 버전 관리 시스템에 Commit &amp; Push
<strong>2. 자동 빌드 &amp; 테스트</strong>
    - 자동 빌드 시스템 -&gt; 코드 빌딩 수행(의존성 설치, 컴파일링, 빌드 시 문제 발생 여부 확인) -&gt; 빌드 후 테스트 진행
<strong>3. 배포 준비 **
    - 코드의 성공적인 통합 -&gt; 자동 패키징, 배포 가능 상태로 유지
**4. 자동 배포</strong>
    - CD 도구를 바탕으로 운영 환경에 배포됨 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 11401번(Java)]]></title>
            <link>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-11401%EB%B2%88Java</link>
            <guid>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-11401%EB%B2%88Java</guid>
            <pubDate>Mon, 18 Aug 2025 06:57:56 GMT</pubDate>
            <description><![CDATA[<h3 id="📖-이항계수-3">📖 이항계수 3</h3>
<blockquote>
<p>__[ 문제 ] __
자연수 $$N$$과 정수 $$K$$가 주어졌을 때 이항 계수 $$\binom{N}{K}$$를 $$1,000,000,007$$로 나눈 나머지를 구하는 프로그램을 작성하시오.</p>
</blockquote>
<hr>
<h4 id="💡풀이">💡풀이</h4>
<ul>
<li>쉽게 생각해서 문제 접근했더니 당연하게도 시간 초과가 나버렸다.</li>
<li>이 문제는 페르마의 소정리를 이용해서 풀어야 한다.</li>
</ul>
<h4 id="페르마의-소정리">페르마의 소정리</h4>
<ul>
<li>$${\displaystyle p}$$가 소수이고, $${\displaystyle a}$$가 정수라고 하자.  $${\displaystyle p\nmid a}$$일 경우(배수 관계가 아닌 경우)
   $${\displaystyle a^{p}\equiv a{\pmod {p}}\qquad (a\neq 0)}$$
   $${\displaystyle a^{p-1}\equiv 1{\pmod {p}}\qquad (a\neq 0)}$$ 
   $${\displaystyle a × a^{p-2}\equiv 1{\pmod {p}}\qquad (a\neq 0)}$$ </li>
<li>따라서 $$a$$ 의 역원은 $$a^{p-2}$$ 임을 알 수 있다.</li>
</ul>
<br>
위 정리를 바탕으로 문제를 다시 접근해보자

<ul>
<li>$$\binom{N}{K} = \frac{N!}{K! \cdot (N - K)!} = 
({N!}\cdot({{K!}\cdot(N - K)!})^{-1}) \mod P = ({N!}\cdot({{K!}\cdot(N - K)!})^{1,000,000,007 - 2}) \mod P$$</li>
<li>수식이 정리를 하면 할 수록 복잡해지는  기분이 드는건  왜일까?</li>
<li>필자는 $${N!} \mod P$$ 와 $${{K!}\cdot(N - K)!})^{1,000,000,007 - 2} \mod P$$로 나누어 각각 계산한 뒤, 이를 합쳐서 $$mod$$ 연산을 진행했다.</li>
<li>처음엔 굉장히 악질 문제처럼 보였는데 막상 정리해서 코드 적어보니 생각보다 그렇게까지 악질은 아니라는 생각이 들었다...</li>
</ul>
<br>
--------

<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static int N, K;
    static int P = 1000000007;

    public static void main(String[] args) throws IOException {
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());
        long num = factorial(N, P);
        long denom = factorial(N - K, P) * factorial(K, P);
        System.out.println((num * div(denom, P - 2, P)) % P);
    }
    // 팩토리얼 계산
    private static long factorial(int n, int p){
        if(n == 0) return 1;
        if(n == 1) return 1 % p;
        else return ((n % p) * pib(n - 1, p) % p) % p;
    }
    // 모듈러 연산 공식 이용
    private static long div(long n, long m, long p){
        if(m == 0) return 1 % p;
        if(m == 1) return n % p;
        else{
            long tmp = div(n, m/2, p);
            if(m % 2 == 1){
                return (((tmp * tmp) % p) * (n % p)) % p;
            } else{
                return (tmp * tmp) % p;
            }
        }
    }

}

</code></pre>
</br>

<hr>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/11401">https://www.acmicpc.net/problem/11401</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1629번(Java)]]></title>
            <link>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-1629%EB%B2%88Java</link>
            <guid>https://velog.io/@namo_o36/%EB%B0%B1%EC%A4%80-1629%EB%B2%88Java</guid>
            <pubDate>Mon, 18 Aug 2025 05:05:20 GMT</pubDate>
            <description><![CDATA[<h3 id="📖-곱셈">📖 곱셈</h3>
<blockquote>
<p>__[ 문제 ] __
자연수 A를 B번 곱한 수를 알고 싶다. 단 구하려는 수가 매우 커질 수 있으므로 이를 C로 나눈 나머지를 구하는 프로그램을 작성하시오.</p>
</blockquote>
<hr>
<h4 id="💡풀이">💡풀이</h4>
<ul>
<li>지수 법칙 + 모듈러 성질을 이용한 문제 풀이 방식</li>
<li>지수 곱을 부분으로 나누어 나머지를 구한 뒤 이를 곱한 값들의 나머지를 구하는 방식으로도 나머지 구하는게 가능함.</li>
<li>쉽게 말해 절반으로 나눈 수들의 나머지를 곱한 값의 나머지를 구하는 방식. 필자가 말로 적어놓고도 이해가 안되니 아래 수식을 참고하면 좋을 것 같다.. </li>
</ul>
<p><strong>모듈러 공식</strong>
    $$(A \times B) \bmod C =((A \bmod C) \times (B \bmod C))\ bmod C$$
    $(A \times B) \bmod C = ((A \bmod C) \times (B \bmod C)) \bmod C$</p>
<ul>
<li>만일 지수가 홀수일 경우 지수를 반으로 나눈 값으로 곱한 수 두 개와 A 하나를 C로 나눈 나머지를 곱한 전체 값을 C로 나누어 구하면 된다.</li>
<li><strong>짝수일 경우</strong> : $$A^B \bmod C = ((A^b \bmod C) * (A^b \bmod C))\bmod C$$<ul>
<li>$$B = b + b$$</li>
</ul>
</li>
<li><strong>홀수일 경우</strong> : $$A^B \bmod C = (((A^b \bmod C) * (A^b \bmod C))\bmod C * (A \bmod C))\bmod C$$<ul>
<li>$$B = b + b + 1$$</li>
</ul>
</li>
</ul>
<hr>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static int A, B, C;
    public static void main(String[] args) throws IOException {
        // 지수 법칙, 모듈러 성질
        StringTokenizer st = new StringTokenizer(br.readLine());
        A = Integer.parseInt(st.nextToken());
        B = Integer.parseInt(st.nextToken());
        C = Integer.parseInt(st.nextToken());

        // 모듈러 성질을 이용하는 문제 -&gt; A^B % C -&gt; (A%C)^B
        long ans = div(A, B, C);
        System.out.println(ans);

    }

    private static long div(int a, int b, int c){
        if(b == 0) return 1 % c;
        if(b == 1) return a % c;
        else {
            long tmp = div(a, b/2, c);
            if(b % 2 == 0) return (tmp * tmp) % c;
            else return (((tmp * tmp) % c) * (a % c)) % c;
        }
    }
}

</code></pre>
</br>

<hr>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/1629">https://www.acmicpc.net/problem/1629</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BONBON] TROUBLE SHOOTING(1) - 비용 제약 내 대규모 데이터 처리 및 배포 환경 구성]]></title>
            <link>https://velog.io/@namo_o36/BONBON-TROUBLE-SHOOTING1-%EB%B9%84%EC%9A%A9-%EC%A0%9C%EC%95%BD-%EB%82%B4-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC-%EB%B0%8F-%EB%B0%B0%ED%8F%AC-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@namo_o36/BONBON-TROUBLE-SHOOTING1-%EB%B9%84%EC%9A%A9-%EC%A0%9C%EC%95%BD-%EB%82%B4-%EB%8C%80%EA%B7%9C%EB%AA%A8-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC-%EB%B0%8F-%EB%B0%B0%ED%8F%AC-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Sun, 10 Aug 2025 03:20:28 GMT</pubDate>
            <description><![CDATA[<h2 id="📖-상황">📖 상황</h2>
<h3 id="1-데이터-수집">1. 데이터 수집</h3>
<ul>
<li>필자가 진행하는 프로젝트의 경우 각 가맹점 별로 어떤 메뉴가 얼마만큼의 수량으로 언제 판매가 되었고, 그래서 얼마만큼의 수익이 났는지에 대한 데이터를 바탕으로 매출 예측 및 분석을 진행한다. </li>
<li>매출 분석 및 예측의 정확도를 높이기 위해서는 이전에 누적된 데이터들의 양이 많았어야 했기 때문에 우리는 매출 데이터 수집에 열을 올렸다. </li>
<li>실제 현장에서 판매되는 매출 데이터와 유사한 데이터를 얻기 위해서 다음과 같은 요소들을 고려하면서 <strong><em>데이터 다양성 + 품질 + 보조변수 확보</em></strong>를 병행하였다.</li>
</ul>
<h3 id="2-데이터-수집-고려-요소">2. 데이터 수집 고려 요소</h3>
<ul>
<li><strong>기간 별 균형 맞추기</strong><ul>
<li>특정 시즌이나 월에 데이터가 몰려있지 않도록 월별 분기별로 고루 데이터가 확보되도록 기간 분산 수집하기</li>
</ul>
</li>
<li><strong>메뉴 별 균형 맞추기</strong><ul>
<li>인기 메뉴, 비인기 메뉴 데이터 확보해야 메뉴 별 차이를 제대로 학습함</li>
</ul>
</li>
<li><strong>데이터 품질 관리</strong> <ul>
<li>누락, 중복, 이상 데이터 등은 편향 위험 증가!</li>
</ul>
</li>
<li><strong>외부 영향 고려</strong><ul>
<li>날씨, 공휴일 등 외부 요인 고려해서 데이터 수집</li>
</ul>
</li>
</ul>
<p>위의 고려 요소들을 산정해서 시뮬레이션 데이터를 만들었다.</p>
<pre><code class="language-python">import pandas as pd
import numpy as np

# 1) 각 메뉴들의 단가 설정
menu_prices = {
    1:3000, 2:4000, 3:4500, 4:5000, 5:5500,
    6:5000, 7:4500, 8:3000, 9:4000, 10:4500,
    11:4800, 12:5200, 13:5500, 14:5000, 15:5500,
    16:5500, 17:5800, 18:4700, 19:4800, 20:4500
}

# 가맹점들 아이디, 기간 설정
franchise_ids = list(range(62, 72))
dates = pd.date_range(&#39;2023-01-01&#39;, &#39;2023-12-31&#39;, freq=&#39;D&#39;)

# 2) 한국 공휴일 직접 지정
holidays = pd.to_datetime([
    &#39;2024-01-01&#39;,                  # 신정
    &#39;2024-01-28&#39;, &#39;2024-01-29&#39;, &#39;2024-01-30&#39;,  # 설날
    &#39;2024-03-01&#39;,                  # 삼일절
    &#39;2024-05-05&#39;,                  # 어린이날
    &#39;2024-06-06&#39;,                  # 현충일
    &#39;2024-08-15&#39;,                  # 광복절
    &#39;2024-10-03&#39;,                  # 개천절
    &#39;2024-10-05&#39;, &#39;2024-10-06&#39;, &#39;2024-10-07&#39;,  # 추석
    &#39;2024-10-09&#39;,                  # 한글날
    &#39;2024-12-25&#39;,                  # 성탄절
    &#39;2025-01-01&#39;,                  # 신정(익년)
    &#39;2025-02-09&#39;, &#39;2025-02-10&#39;, &#39;2025-02-11&#39;,  # 설날(2025년)
    &#39;2025-03-01&#39;,                  # 삼일절
    &#39;2025-05-05&#39;                   # 어린이날
])

# 3) 요일·공휴일 팩터 계산
df_dates = pd.DataFrame({&#39;sales_date&#39;: dates})
df_dates[&#39;is_weekend&#39;] = df_dates[&#39;sales_date&#39;].dt.weekday &gt;= 5

# 주말 판매 건수 30% 감소 
df_dates[&#39;weekly_factor&#39;] = np.where(df_dates[&#39;is_weekend&#39;], 0.7, 1.0)

# 공휴일 판매 건수 50% 감소
df_dates[&#39;is_holiday&#39;] = df_dates[&#39;sales_date&#39;].isin(holidays)
df_dates[&#39;holiday_factor&#39;] = np.where(df_dates[&#39;is_holiday&#39;], 0.5, 1.0)

# 4) 더미 판매 내역 생성
np.random.seed(42)
records = []
target_rows = 150_000
base_rows = target_rows / len(dates)

for _, row in df_dates.iterrows():
    lam = base_rows * row[&#39;weekly_factor&#39;] * row[&#39;holiday_factor&#39;]
    n = np.random.poisson(lam)
    for _ in range(n):
        menu_id = np.random.choice(list(menu_prices.keys()))
        franchise_id = np.random.choice(franchise_ids)
        count = np.random.randint(1, 6)
        amount = menu_prices[menu_id] * count
        records.append({
            &#39;menu_id&#39;: menu_id,
            &#39;franchise_id&#39;: franchise_id,
            &#39;product_count&#39;: count,
            &#39;amount&#39;: amount,
            &#39;sales_date&#39;: row[&#39;sales_date&#39;].strftime(&#39;%Y-%m-%d&#39;)
        })

df_sales = pd.DataFrame(records)

# 5) CSV로 저장
df_sales.to_csv(&#39;sales_detail_2023.csv&#39;, index=False, encoding=&#39;utf-8-sig&#39;)</code></pre>
<br>

<h3 id="3-비용-제약-발생">3. 비용 제약 발생</h3>
<ul>
<li>데이터를 다 만들고 나니, 읽고 쓰는게 문제가 됐다.</li>
<li>저사양 t2 micro EC2에 MariaDB를 설치해 거기에 100만개 데이터를 넣자니... 데이터 50개만 읽고 쓰는데도 한나절이 걸리는데 전체 다 넣고 쿼리 돌리면 아주 죽겠구나 싶었다.</li>
<li>프로젝트를 진행했던 한화BEYOND SW 캠프에서는 최종 프로젝트 지원금으로 AWS 운영 비용 30만원을 지원해줬는데, 이미 프론트엔드와 백엔드 서버를 돌리는 중이었어서 하루에 만원 만오천원씩은 이미 고정적으로 나가고 있는 상황이어서 RDS를 운영하거나 EC2를 고사양으로 운영하며 인스턴스 위에 DB를 설치하기에는 조금 부족한 금액이었다. </li>
<li>비용 제약 환경에서 내가 생각해볼 수 있는 여러 가지 방법 중 두 가지를 선택해 적용해보기로 했다. 경량화된 DB나 데이터 분할 방식도 고려했으나 프로젝트 특성상 적용하기 어렵거나 효과가 미비한 것들이었다.<br>
</li>
</ul>
<hr>
<h2 id="✍️-접근-방법">✍️ 접근 방법</h2>
<h3 id="➡️-1-데이터-베이스-이원화">➡️ 1. 데이터 베이스 이원화</h3>
<img src="https://velog.velcdn.com/images/namo_o36/post/1ff8e689-a211-487b-8b5f-6f07da3622be/image.png" width="500">

<ul>
<li>말 그대로 데이터 베이스를 나누어서 쓰겠다는 심산!</li>
<li>솔직히 일반 프로젝트에 있어서 조금 오버엔지니어링일까 싶었는데 실무에서 쓰는 데이터베이스 아키텍처 공부도 할 겸 구현해보자 싶었다.</li>
<li>프로젝트 특성상 쓰기보다 읽기 트래픽이 더 많기 때문에 해당 구조를 선택한 것도 있다.</li>
<li>필자는 시간 관계 상 EC2 3개를 만들어 각각 MasterDB 서버, Slave DB1 서버, SlaveDB2 서버로 만들어 사용했다. 쿠버네티스 환경에서 배포해서 트래픽 증가 시 자동으로 Pod 생성해보는 것도 해보고 싶지만... 시간이 너무 부족하니까... 아무래도.. 일단 이렇게라도 만들어보자는 마음으로..<br>
### 계층 분리 전략</li>
<li><strong><em>Master DB</em></strong> - 쓰기 전용 (애플리케이션의 모든 쓰기 요청 처리, - <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>)</li>
<li><strong><em>Slave DB 1</em></strong> - 읽기 전용 (매출 데이터 조회 등의 읽기 부하 분산 - <code>SELECT</code>)</li>
<li><strong><em>Slave DB 2</em></strong> - 백업 전용 (주기적 백업 및 장애 발생 시 복구 대상)<br>
### 시연 - Slave DB</li>
<li>Swagger에서 <code>Get</code> 방식의 API 요청</li>
<li>등록한 가맹점주 계정의 전체 조회 요청을 보내봤다. </li>
<li>SlaveDB가 있는 EC2로 연결, 데이터 읽기 요청 처리한다.</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/4c30db3d-8604-4c30-9694-0883079b155b/image.png" width="800">


<ul>
<li><code>[SlaveDB]</code>가 있는 EC2로 자동으로 라우팅 된다.</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/3baf7c29-8c9d-4db3-8786-0a6bae31eb89/image.png" width="800">

<img src="https://velog.velcdn.com/images/namo_o36/post/e57cb150-256c-4af1-9648-6231ec194d22/image.png" width="800">

<br>

<h3 id="시연---master-db">시연 - Master DB</h3>
<ul>
<li>Swagger에서 <code>Post</code> 방식(쓰기)의 API 요청</li>
<li>가맹점주 계정을 새롭게 등록하는 요청을 보내봤다.</li>
<li>Master DB가 있는 EC에 연결, 데이터 쓰기 요청을 처리한다.</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/39fdc27a-53a9-4e55-82ad-a91a6553a809/image.png" width="800">

<ul>
<li><code>[Master]</code> DB가 있는 인스턴스로 자동 Routing 됨</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/168dc48c-f0cc-4b66-a4fc-56a59e1a6105/image.png" width="800">

<img src="https://velog.velcdn.com/images/namo_o36/post/f146b4be-6a6e-45e1-ab90-8eaa6073462b/image.png" width="800">

<br>

<h3 id="한계점">한계점</h3>
<ul>
<li>읽기 트래픽 분산되는 것까지는 좋다. 읽기 트래픽 분산, Master 장애 시 Slave 승격, 그리고 Slave 수 증가를 통한 수평 확장성까지 고려가능하다는 점 등에서 Master-Slave 구조의 의도와 장점은 분명하다.</li>
<li>그러나 Master, Slave 서버로 이용한 EC2들도 여러 개의 인스턴스를 운영하다보니 비용 부담 때문에 저사양 인스턴스로 사용하게 되면서 Master-Slave 구조를 도입하기 전보다 큰 성능 개선 효과를 보지는 못했다... <br>

</li>
</ul>
<h3 id="➡️-2-홈서버-구축">➡️ 2. 홈서버 구축</h3>
<img src="https://velog.velcdn.com/images/namo_o36/post/27e87b39-8b17-483c-8e59-edd30bedb421/image.png" width="500">

<ul>
<li><p>조금 더 심플하게 접근하기로 했다.</p>
</li>
<li><p>유휴 PC 한 대를 활용해 그 위에 데이터베이스를 설치하고, 100만 건의 데이터를 CSV 파일로 넣어 읽고 쓰는 방식을 선택했다.</p>
</li>
<li><p>이 방법은 물리적 하드웨어 성능이 EC2의 저사양 인스턴스보다 훨씬 우수하고, CPU나 메모리, I/O 자원을 다른 인스턴스와 공유하지 않아 일관된 성능을 낼 수 있다는 점에서 효과적이었다.</p>
</li>
<li><p>위 과정에서 한정된 인프라 자원 내에서 문제 해결 능력과 대안 설계 능력을 키웠고, 인프라 안정성과 확장성의 중요성을 체감했다.</p>
</li>
<li><p>홈서버와 클라우드 간 네트워크 연동을 통해 시스템 아키텍처에 대한 이해를 높였고, 데이터 처리와 서비스 운영 역할 분리를 통해 효율적인 시스템 구성과 Master-Slave DB 구조로 부하 분산하는 과정에 대해 경험했다. 결국 돌고 돌아 홈서버 구축이었지만.. 그래도 뭐..</p>
</li>
</ul>
<hr>
<img src="https://velog.velcdn.com/images/namo_o36/post/530cd3ce-2ed3-4300-b5c0-7157c4fee243/image.jpg" width="150">

]]></description>
        </item>
        <item>
            <title><![CDATA[[BONBON] CI/CD 파이프라인 설계, 구축, 시연 (Flask)]]></title>
            <link>https://velog.io/@namo_o36/BONBON-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%84%A4%EA%B3%84-%EA%B5%AC%EC%B6%95-%EC%8B%9C%EC%97%B0-Flask</link>
            <guid>https://velog.io/@namo_o36/BONBON-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%84%A4%EA%B3%84-%EA%B5%AC%EC%B6%95-%EC%8B%9C%EC%97%B0-Flask</guid>
            <pubDate>Fri, 08 Aug 2025 02:10:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="aws-사양">AWS 사양</h3>
</blockquote>
<ul>
<li><strong>Front End</strong> : Route53, ACM, S3, CloudFront</li>
<li><strong>Back End</strong> : S3, EC2, AutoScaling Group, CodeDeploy(Blue-Green)</li>
<li><strong>Machin Learning</strong> : S3, EC2, CodeDeploy</li>
</ul>
<hr>
<h2 id="시스템-아키텍처">시스템 아키텍처</h2>
<img src="https://velog.velcdn.com/images/namo_o36/post/5de6c530-cd87-49c5-84c5-4e30af38b468/image.png" width="800">

<ul>
<li>백엔드랑 프론트엔드 끝났으니까 이제 머신러닝 서버 배포도 빼놓을 수 없겠지? 얘는 좀 단순하게 구현했다. </li>
<li>이거 하고 나면 다음엔 데이터베이스 이원화 과정도 간단히 포스팅할 예정이다</li>
<li>후론트라라 후론트라라 후론트루루라라라~~ </li>
</ul>
<hr>
<h2 id="ml-cicd">ML CI/CD</h2>
<img src="https://velog.velcdn.com/images/namo_o36/post/eccf6101-9dcb-4d1e-be99-c045323b68f8/image.png" width="800">

<h4 id="배포-프로세스">배포 프로세스</h4>
<p>1️⃣ <code>GitHub</code>에 코드 commit &amp; push(main 브랜치) → <code>GitHub Actions</code> CI 실행
2️⃣ Flask 프로젝트 zip 압축 후 <code>S3</code> 버킷에 업로드
3️⃣ <code>CodeDeploy</code>를 통해 <code>EC2</code>에 배포
4️⃣ <code>appspec.yml</code>과 <code>scripts</code>로 Flask 애플리케이션 실행</p>
<br>

<hr>
<h2 id="github-actions-cicd-구축">GitHub Actions CI/CD 구축</h2>
<h3 id="githubworkflowsdeployyml-파일-생성">.github/workflows/deploy.yml 파일 생성</h3>
<blockquote>
<p><strong>🗂️ 디렉토리 구조</strong>
├─.<strong>github</strong>
│  ├─ <strong>workflows/</strong>
│  │  └─ <strong>deploy.yml/</strong></p>
</blockquote>
<ul>
<li>프론트엔드에서 만든 ci-cd.yaml과 동일한 구조로 deploy.yml 파일을 만들어준다.</li>
<li>원리도 프론트엔드와 유사하다<br>

</li>
</ul>
<h3 id="스크립트-흐름">스크립트 흐름</h3>
<ul>
<li>python 환경 설정</li>
<li>가상환경(venv) 생성, <code>requirements.txt</code>에 정의된 Python 패키지를 설치</li>
<li>Flask 애플리케이션 파일을 zip 파일로 압축</li>
<li>AWS 자격증명 구성 및 AWS CLI 설치</li>
<li>S3에 zip 파일 업로드</li>
<li>CodeDeploy를 이용해 EC2 인스턴스에 배포 -&gt; S3에 업로드된 파일을 가져와서 실행시킴</li>
</ul>
<pre><code class="language-yml">name: BonBon Flask to AWS EC2

# main 브랜치에 push 발생 시
on:
  push:
    branches: [ main ]

# 환경변수 지정
env:
  PROJECT_NAME: bonbon-ml
  BUCKET_NAME: bonbon-ml-bucket
  CODE_DEPLOY_APP_NAME: bonbon-dev
  DEPLOYMENT_GROUP_NAME: bonbon-ml-dev
  AWS_REGION: ap-northeast-2

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # 1. 기본 체크아웃
      - name: Checkout
        uses: actions/checkout@v2

      # python 환경 설정
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.8

      # 가상환경(venv) 생성, requirements.txt에 정의된 Python 패키지를 설치
      - name: Install dependencies
        run: |
          python -m venv venv
          . venv/bin/activate
          pip install -r venus/requirements.txt

      # Flask 애플리케이션 파일을 zip 파일로 압축
      - name: Make Zip File
        run: |
          cd venus
          zip -qq -r $GITHUB_SHA.zip . -x &quot;*.git*&quot; -x &quot;venv/*&quot;
        shell: bash

      # AWS 자격증명 구성 및 AWS CLI 설치
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{secrets.AWS_SECRET_KEY_ID}}
          aws-region: ${{env.AWS_REGION}}

      # S3에 업로드
      - name: Upload to S3
        run: |
          aws s3 cp --region ap-northeast-2 venus/$GITHUB_SHA.zip s3://$BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip

      # CodeDeploy를 이용해 EC2 인스턴스에 배포 -&gt; S3에 업로드된 파일을 가져와서 실행시킴
      - name: Code Deploy To EC2 instance
        run: |
          aws deploy create-deployment \
            --application-name ${{ env.CODE_DEPLOY_APP_NAME }} \
            --deployment-config-name CodeDeployDefault.AllAtOnce \
            --deployment-group-name ${{ env.DEPLOYMENT_GROUP_NAME }} \
            --s3-location bucket=$BUCKET_NAME,key=$PROJECT_NAME/$GITHUB_SHA.zip,bundleType=zip
</code></pre>
<br>

<hr>
<h2 id="ml-cicd-파이프라인-시연">ML CI/CD 파이프라인 시연</h2>
<h4 id="1-변경-내용-작업-후-github에-commit--push-→-main-branch로-pr--merge">1. 변경 내용 작업 후 Github에 Commit &amp; push → main branch로 PR &amp; Merge</h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/489a290f-f644-4d63-8648-cc91bc032179/image.png" width="800">

<br>

<h4 id="2-main-브랜치-업데이트-시-webhook-발생-→-git-action-workflows-실행">2. main 브랜치 업데이트 시 webhook 발생 → git action workflows 실행</h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/efea1078-ea4b-4905-94af-9a2429afc618/image.png" width="800">

<br>

<h4 id="3-git-action에서-build--deploy-완료-s3에-zip-파일-업로드-→-이후-codedeploy에서-배포-시작">3. Git Action에서 Build &amp; Deploy 완료, S3에 zip 파일 업로드 → 이후 CodeDeploy에서 배포 시작</h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/18113259-729d-441d-bfcd-7012e624133b/image.png" width="800">
<img src="https://velog.velcdn.com/images/namo_o36/post/7ea7e966-72fd-489d-aa7a-8d1a72cbccc3/image.png" width="800">


<br>

<h4 id="4-codedeploy-배포-완료">4. CodeDeploy 배포 완료</h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/008e835b-e4ea-4290-bdab-4c9dff1e44ee/image.png" width="800">

<br>

<h4 id="5-배포-확인">5. 배포 확인</h4>
<ul>
<li>curl 명령어를 이용해 데이터 예측을 위한 요청 전송 → 정상 응답 확인</li>
<li>CodeDeploy 로그에서 <code>Succeeded</code> 확인</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/d820562c-1ee2-4c2f-a833-6bebab46017a/image.png" width="800">

<img src="https://velog.velcdn.com/images/namo_o36/post/4f40db54-cbde-4d85-922a-2552814c799c/image.png" width="800">
<br> 

<hr>
<p>끝!
<img src="https://velog.velcdn.com/images/namo_o36/post/530cd3ce-2ed3-4300-b5c0-7157c4fee243/image.jpg" width="150"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BONBON] CI/CD 파이프라인 설계, 구축, 시연 (BE)]]></title>
            <link>https://velog.io/@namo_o36/BONBON-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%84%A4%EA%B3%84-%EA%B5%AC%EC%B6%95-%EC%8B%9C%EC%97%B0-BackEnd</link>
            <guid>https://velog.io/@namo_o36/BONBON-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%84%A4%EA%B3%84-%EA%B5%AC%EC%B6%95-%EC%8B%9C%EC%97%B0-BackEnd</guid>
            <pubDate>Thu, 07 Aug 2025 17:24:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="aws-사양">AWS 사양</h3>
</blockquote>
<ul>
<li><strong>Front End</strong> : Route53, ACM, S3, CloudFront</li>
<li><strong>Back End</strong> : S3, EC2, AutoScaling Group, CodeDeploy(Blue-Green)</li>
<li><strong>Machin Learning</strong> : S3, EC2, CodeDeploy</li>
</ul>
<hr>
<h2 id="시스템-아키텍처">시스템 아키텍처</h2>
<img src="https://velog.velcdn.com/images/namo_o36/post/5de6c530-cd87-49c5-84c5-4e30af38b468/image.png" width="800">

<ul>
<li>이번 포스트에선 BackEnd CI/CD 쪽을 구현해볼 예정이다.</li>
<li>정리할 내용이 생각보다 많다..  </li>
</ul>
<hr>
<h2 id="backend-cicd">BackEnd CI/CD</h2>
<img src="https://velog.velcdn.com/images/namo_o36/post/4c0b5764-5b96-41d2-89c8-a1da74e9ba73/image.png" width="800">



<h3 id="배포-프로세스">배포 프로세스</h3>
<p>1️⃣ <code>GitHub</code>에 코드 commit &amp; push(main 브랜치) → <code>GitHub Actions</code> CI 실행
2️⃣ 빌드 결과물 (<code>.jar</code>, <code>appspec.yml</code>, <code>scripts</code>) 압축 후 <code>S3</code>에 업로드
3️⃣ <code>CodeDeploy</code>가 <code>Auto Scaling Group</code>을 이용해 <code>Green</code> 환경에 배포
4️⃣ <code>Green 인스턴스</code> Health 체크 완료 시, <code>Blue</code> → <code>Green</code>으로 트래픽 전환
5️⃣ 배포 완료 후 <code>Blue 인스턴스</code> 자동 종료</p>
<br>

<hr>
<h2 id="github-actions-cicd-구축">GitHub Actions CI/CD 구축</h2>
<blockquote>
<p><strong>🗂️ 디렉토리 구조</strong>
├─.<strong>github</strong>
│  ├─ <strong>workflows/</strong>
│  │  └─ <strong>deploy.yaml/</strong>
├─ bonbon/
│  ├─ src/
│  │  └─ main/
│  │     └─ resources/
│  ├─ build/
│  ├─ gradlew
│  ├─ build.gradle
├─ <strong>scripts/</strong>
│  └─<strong>start.sh / stop.sh / init-dir.sh</strong> (배포 스크립트)
├─ <strong>appspec.yml</strong></p>
</blockquote>
<h3 id="백엔드-cicd-파이프라인">백엔드 CI/CD 파이프라인</h3>
<ul>
<li><code>appspec.yml</code><strong>(CD)</strong> - AWS CodeDeploy가 배포 작업 중 어떤 스크립트를 어떤 타이밍에 실행할지 정의하는 설정 파일 </li>
<li><code>start.sh</code><strong>(CD)</strong>  - 애플리케이션 실행</li>
<li><code>stop.sh</code><strong>(CD)</strong>  - 기존에 실행중이던 애플리케이션 중지 </li>
<li><code>deploy.yaml</code><strong>(CI)</strong>  - 배포 전 작업을 수행하는 사용자 정의 스크립트</li>
<li><code>init-dir.sh</code><strong>(CD)</strong>  - EC2 인스턴스 초기 배포 시 초기 디렉토리 및 권한 설정</li>
</ul>
<br>

<h3 id="전체-흐름"><strong>전체 흐름</strong></h3>
<p>1️⃣ <strong>[ CI 단계 ]</strong></p>
<ul>
<li><code>deploy.yaml</code> 
→ 코드 빌드 
→ .jar 파일 생성 , zip 압축 
→ 산출물 <code>S3</code> 업로드</li>
</ul>
<p>1️⃣ <strong>[ CD 단계 ]</strong></p>
<ul>
<li><code>appspec.yml</code><br>→ <code>BeforeInstall: init-dir.sh</code> 실행 (초기 디렉토리 준비)
→ <code>ApplicationStop: stop.sh</code> 실행 (기존 프로세스 종료)
→ <code>ApplicationStart: start.sh</code> 실행 (zip 다운로드, 압축 해제, .jar 실행)<br>

</li>
</ul>
<h3 id="deployyaml">deploy.yaml</h3>
<ul>
<li>이 과정 지나면 빌드, 테스트 후에 압축 파일이 S3로 올라감</li>
<li>생성되는  파일 구조는 아래와 같음</li>
</ul>
<blockquote>
<p><strong>🗂️ 디렉토리 구조</strong>
bonbon/
├─ 5f6c33d.zip/
│ ├─ build/
│ ├─ appspec.yml
│ └─ scripts/
│ │ ├─ start.sh
│ │ ├─ stop.sh 
│ │ ├─ init-dir.sh
│ │ └─ sha.txt </p>
</blockquote>
<pre><code class="language-yaml">name: BonBon Back-end CI&amp;CD

on:
  push:
    branches: main
env:
  AWS_REGION: ap-northeast-2 #리전
  S3_BUCKET_NAME: bonbon-back-bucket #버킷 이름
  CODE_DEPLOY_APPLICATION_NAME: bonbon-dev  #CodeDeploy 애플리케이션 이름
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: bonbon-dev #CodeDeploy 배포 그룹 이름

permissions:
  contents: read

jobs:
  build:
    # job이 돌아갈 환경
    runs-on: ubuntu-latest
    steps:
      # 기본 체크아웃
      - name: Checkout
        uses: actions/checkout@v3

      # resources 디렉토리 생성
      - name: Create resources directory (for ignored files)
        run: mkdir -p ./bonbon/bonbon/src/main/resources

      # properties들 생성
      - name: Set application.properties
        run: |
          echo &quot;${{ secrets.APPLICATION_PROPERTIES }}&quot; &gt; ./bonbon/bonbon/src/main/resources/application.properties
          echo &quot;${{ secrets.APPLICATION_JWT_PROPERTIES }}&quot; &gt; ./bonbon/bonbon/src/main/resources/application-jwt.properties

      # gradlew 권한 추가
      - name: Make gradlew executable
        run: chmod +x ./bonbon/bonbon/gradlew

      # 빌드, 테스트
      - name: Build and Test
        run: |
          cd ./bonbon/bonbon
          ./gradlew clean build test --info

      # GitHub 커밋의 고유한 SHA 값을 scripts/sha.txt 파일에 저장 -&gt; 어떤 커밋 배포했는지 확인할라고
      - name: Save SHA to file
        run: echo $GITHUB_SHA &gt; ./bonbon/bonbon/scripts/sha.txt

      # zip 파일 압축(scripts, appspec.yml, build 포함)
      - name: Make deployment zip
        run: |
          cd ./bonbon/bonbon
          echo $GITHUB_SHA &gt; scripts/sha.txt
          zip -r $GITHUB_SHA.zip build appspec.yml scripts
          mv $GITHUB_SHA.zip ../../

      # JDK 23 세팅
      - name: Set up JDK 23
        uses: actions/setup-java@v3
        with:
          distribution: &#39;temurin&#39;
          java-version: &#39;23&#39;

      # AWS 인증
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY_ID }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Upload to S3
        run: |
          aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ env.S3_BUCKET_NAME }}/$GITHUB_SHA.zip

      # CodeDeploy로 배포 실행
      - name: Deploy to EC2
        run: |
          aws deploy create-deployment \
            --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
            --deployment-config-name CodeDeployDefault.AllAtOnce \
            --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
            --s3-location bucket=${{ env.S3_BUCKET_NAME }},key=${{ github.sha }}.zip,bundleType=zip</code></pre>
<br>

<h3 id="init-dirsh">init-dir.sh</h3>
<ul>
<li>디렉토리 최초 생성 및 초기화<pre><code class="language-sh">#!/bin/bash
</code></pre>
</li>
</ul>
<h1 id="프로젝트-루트-디렉토리-경로">프로젝트 루트 디렉토리 경로</h1>
<p>PROJECT_ROOT=&quot;/home/ec2-user/app&quot;</p>
<h1 id="디렉토리-확인-후-생성">디렉토리 확인 후 생성</h1>
<p>if [ ! -d &quot;$PROJECT_ROOT&quot; ]; then
  echo &quot;$(date &#39;+%Y-%m-%d %H:%M:%S&#39;) &gt; $PROJECT_ROOT 디렉토리 없음. 생성 중...&quot; &gt;&gt; /home/ec2-user/deploy.log
  mkdir -p $PROJECT_ROOT
  echo &quot;$(date &#39;+%Y-%m-%d %H:%M:%S&#39;) &gt; $PROJECT_ROOT 디렉토리 생성 완료.&quot; &gt;&gt; /home/ec2-user/deploy.log
else
  echo &quot;$(date &#39;+%Y-%m-%d %H:%M:%S&#39;) &gt; $PROJECT_ROOT 디렉토리가 이미 존재합니다.&quot; &gt;&gt; /home/ec2-user/deploy.log
fi</p>
<h1 id="디렉토리-권한-설정-ec2-user에게-소유권-부여">디렉토리 권한 설정 (ec2-user에게 소유권 부여)</h1>
<p>echo &quot;$(date &#39;+%Y-%m-%d %H:%M:%S&#39;) &gt; $PROJECT_ROOT 디렉토리 권한 변경 중...&quot; &gt;&gt; /home/ec2-user/deploy.log
chown -R ec2-user:ec2-user $PROJECT_ROOT
chmod -R 755 $PROJECT_ROOT
echo &quot;$(date &#39;+%Y-%m-%d %H:%M:%S&#39;) &gt; $PROJECT_ROOT 디렉토리 권한 설정 완료.&quot; &gt;&gt; /home/ec2-user/deploy.log</p>
<pre><code>
&lt;br&gt;

### start.sh
- 막상 코드를 작성하다 보니` start.sh`에 `stop.sh`가 하는 일까지 포함시켰다.. 
- CodeDeploy에서 공식적으로 분리 사용을 권장하니 분리된 구조로 추후에 바꿔야겠다

```sh
#!/usr/bin/env bash

# 프로젝트 루트 디렉토리 설정
PROJECT_ROOT=&quot;/home/ec2-user/app/build/libs&quot;
JAR_NAME=&quot;bonbon-0.0.1-SNAPSHOT.jar&quot;
JAR_FILE=&quot;$PROJECT_ROOT/$JAR_NAME&quot;

# 로그 파일 경로
APP_LOG=&quot;$PROJECT_ROOT/application.log&quot;
ERROR_LOG=&quot;$PROJECT_ROOT/error.log&quot;
DEPLOY_LOG=&quot;$PROJECT_ROOT/deploy.log&quot;

# 현재 시간 저장
TIME_NOW=$(date &#39;+%Y-%m-%d %H:%M:%S&#39;)


# sha.txt에서 SHA 읽기
SHA=$(cat /home/ec2-user/app/scripts/sha.txt)

# 다운로드
aws s3 cp s3://bonbon-back-bucket/${SHA}.zip /home/ec2-user/app/${SHA}.zip

# S3에서 ZIP 파일 다운로드
echo &quot;$TIME_NOW &gt; S3에서 ZIP 파일 다운로드 시작&quot; &gt;&gt; $DEPLOY_LOG
aws s3 cp s3://bonbon-back-bucket/${SHA}.zip $PROJECT_ROOT/${SHA}.zip &gt;&gt; $DEPLOY_LOG 2&gt;&amp;1
if [ $? -ne 0 ]; then
  echo &quot;$TIME_NOW &gt; 오류: S3에서 ZIP 파일 다운로드 실패&quot; &gt;&gt; $DEPLOY_LOG
  exit 1
fi
echo &quot;$TIME_NOW &gt; ZIP 파일 다운로드 완료&quot; &gt;&gt; $DEPLOY_LOG

# ZIP 파일 압축 해제
echo &quot;$TIME_NOW &gt; ZIP 파일 압축 해제 시작&quot; &gt;&gt; $DEPLOY_LOG
unzip -o $PROJECT_ROOT/${SHA}.zip -d $PROJECT_ROOT &gt;&gt; $DEPLOY_LOG 2&gt;&amp;1
if [ $? -ne 0 ]; then
  echo &quot;$TIME_NOW &gt; 오류: ZIP 파일 압축 해제 실패&quot; &gt;&gt; $DEPLOY_LOG
  exit 1
fi
echo &quot;$TIME_NOW &gt; ZIP 파일 압축 해제 완료&quot; &gt;&gt; $DEPLOY_LOG

# 기존 프로세스 종료 (있을 경우)
EXISTING_PID=$(pgrep -f &quot;$JAR_FILE&quot;)
if [ -n &quot;$EXISTING_PID&quot; ]; then
  echo &quot;$TIME_NOW &gt; 기존 프로세스 종료 (PID: $EXISTING_PID)&quot; &gt;&gt; $DEPLOY_LOG
  kill -9 $EXISTING_PID
fi

# JAR 파일 실행
echo &quot;$TIME_NOW &gt; $JAR_FILE 파일 실행 시작&quot; &gt;&gt; $DEPLOY_LOG
nohup /opt/jdk-23/bin/java -jar $JAR_FILE &gt; $APP_LOG 2&gt; $ERROR_LOG &amp;

echo &quot;$TIME_NOW &gt; 실행된 프로세스 아이디 $CURRENT_PID 입니다.&quot; &gt;&gt; $DEPLOY_LOG</code></pre><br>

<h3 id="stopsh">stop.sh</h3>
<pre><code class="language-sh">#!/usr/bin/env bash

PROJECT_ROOT=&quot;/home/ec2-user/app&quot;
JAR_FILE=&quot;$PROJECT_ROOT/spring-webapp.jar&quot;

DEPLOY_LOG=&quot;$PROJECT_ROOT/deploy.log&quot;

TIME_NOW=$(date &#39;+%Y-%m-%d %H:%M:%S&#39;)

# 현재 구동 중인 애플리케이션 pid 확인
CURRENT_PID=$(pgrep -f $JAR_FILE)

# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
  echo &quot;$TIME_NOW &gt; 현재 실행중인 애플리케이션이 없습니다&quot; &gt;&gt; $DEPLOY_LOG
else
  echo &quot;$TIME_NOW &gt; 실행중인 $CURRENT_PID 애플리케이션 종료 &quot; &gt;&gt; $DEPLOY_LOG
  kill -15 $CURRENT_PID
fi</code></pre>
<br>

<h3 id="appspecyml">appspec.yml</h3>
<pre><code class="language-yml">version: 0.0
os: linux

files:
  - source:  /
    destination: /home/ec2-user/app
    overwrite: yes

permissions:
  - object: /
    pattern: &quot;**&quot;
    owner: ec2-user
    group: ec2-user

hooks:
  BeforeInstall:
    - location: scripts/init-dir.sh
      timeout: 30
      runas: ec2-user
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
      runas: ec2-user
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ec2-user</code></pre>
<hr>
<h2 id="backend-cicd-파이프라인-시연">BackEnd CI/CD 파이프라인 시연</h2>
<ul>
<li>스크립트도 다썼으니 잘 배포 되는지 확인해보는게 좋지 않을까? 시연해보자<br>

</li>
</ul>
<h4 id="0-변경-사항-반영-전-사이트-→-bonbon-project-ver_120">0. 변경 사항 반영 전 사이트 → <em><code>“bonbon project ver_1.2.0”</code></em></h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/52ad46b5-0203-4f30-8cd4-157152436c30/image.png" width="800">

<br>

<h4 id="1-변경-내용-작업-후-github에-commit--push-→-main-branch로-pr--merge">1. <strong>변경 내용 작업 후 Github에 Commit &amp; push → <code>main</code> branch로 PR &amp; Merge</strong></h4>
<ul>
<li><strong><em><code>“bonbon project ver_1.2.2”</code></em></strong>로 변경한 후 Commit &amp; push</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/6bc8550c-678a-4e62-aabc-c6580cfdc0ea/image.png" width="800">

<br>

<h4 id="2-main-브랜치-업데이트-시-webhook-발생-→-git-action-workflows-실행">2. main 브랜치 업데이트 시 webhook 발생 → git action workflows 실행</h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/396bd6cd-a0bc-4b7c-a22b-4dc6378c1fa6/image.png" width="800">

<br>

<h4 id="3-git-action에서-build--deploy-완료-s3에-zip-파일-업로드">3. Git Action에서 Build &amp; Deploy 완료, S3에 zip 파일 업로드</h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/7b8c9fff-7cec-4257-b9c6-99f59c9ef812/image.png" width="800">

<img src="https://velog.velcdn.com/images/namo_o36/post/66a3c174-4934-4404-ab9d-79ff9b5aa097/image.png" width="800">




<br>

<h4 id="4-codedeploy에서-배포-시작">4. CodeDeploy에서 배포 시작</h4>
<ul>
<li><code>AutoScaling Group</code> + <code>Launch Template</code>에 의해 새로운 EC2 인스턴스 2개 생성 → 대체 인스턴스에 <code>.jar</code> 파일 배포</li>
<li><code>AutoScaling Group</code> 을 통해 배포된 인스턴스들에게 트래픽을 분산시킴</li>
<li>대체 인스턴스에 배포 후 기존에 있던 원본 인스턴스(2개) traffic block 됨</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/61f981ed-e42e-4bc7-9b02-3805c76c851b/image.png" width="800">

<img src="https://velog.velcdn.com/images/namo_o36/post/09c8cad1-cf20-4c92-ab48-04d3726b0659/image.png" width="800">

<br>

<h4 id="5-원본-인스턴스-종료-및-배포-종료">5. 원본 인스턴스 종료 및 배포 종료</h4>
<ul>
<li>새로 생성된 인스턴스들 상태 확인 → <code>healthy</code></li>
<li>기존의 원본 인스턴스 2개는 삭제된 것을 확인</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/62c76891-d19e-45ee-a217-5a7a26903e36/image.png" width="800">

<img src="https://velog.velcdn.com/images/namo_o36/post/f915959f-ba8d-428f-8cba-c09978b44854/image.png" width="800">

<br>

<h4 id="6-swagger에서-변경-사항-확인">6. Swagger에서 변경 사항 확인</h4>
<ul>
<li><strong><em><code>“bonbon project ver_1.2.0”</code></em></strong> → <strong><em><code>“bonbon project ver_1.2.2”</code></em></strong> 
로 바뀐 것을 확인할 수 있다.</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/da7a9af6-21ba-4bf0-b9e7-bd8cf2244b78/image.png" width="800">

<hr>
<p>BackEnd도 끝!
<img src="https://velog.velcdn.com/images/namo_o36/post/530cd3ce-2ed3-4300-b5c0-7157c4fee243/image.jpg" width="150"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BONBON] CI/CD 파이프라인 설계, 구축, 시연 (FE)]]></title>
            <link>https://velog.io/@namo_o36/BONBON-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%84%A4%EA%B3%84-%EA%B5%AC%EC%B6%95-%EC%8B%9C%EC%97%B0-FE</link>
            <guid>https://velog.io/@namo_o36/BONBON-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%84%A4%EA%B3%84-%EA%B5%AC%EC%B6%95-%EC%8B%9C%EC%97%B0-FE</guid>
            <pubDate>Thu, 07 Aug 2025 16:36:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="aws-사양">AWS 사양</h3>
</blockquote>
<ul>
<li><strong>Front End</strong> : Route53, ACM, S3, CloudFront</li>
<li><strong>Back End</strong> : S3, EC2, AutoScaling Group, CodeDeploy(Blue-Green)</li>
<li><strong>Machin Learning</strong> : S3, EC2, CodeDeploy</li>
</ul>
<hr>
<h2 id="시스템-아키텍처">시스템 아키텍처</h2>
<img src="https://velog.velcdn.com/images/namo_o36/post/5de6c530-cd87-49c5-84c5-4e30af38b468/image.png" width="800">

<ul>
<li>프로젝트 전체의 시스템 아키텍처이다.</li>
<li><code>BackEnd</code>, <code>FrontEnd</code>, <code>Flask</code> 각각을 따로따로 나눠서 설명할 예정이다.</li>
<li><code>draw.io</code>를 이용해 그렸는데 생각보다 툴이 손에 잘 익지 않았다. 다이어그램 그릴 때 좀 더 써버릇 해봐야겠다. </li>
</ul>
<hr>
<h2 id="frontend-cicd">FrontEnd CI/CD</h2>
<img src="https://velog.velcdn.com/images/namo_o36/post/61085173-5302-46c9-a163-097beea09748/image.png" width="800">

<h4 id="배포-프로세스">배포 프로세스</h4>
<p>1️⃣ <code>GitHub</code>에 코드 commit &amp; push(main 브랜치) → <code>GitHub Actions</code> CI 실행
2️⃣ 빌드된 .dist 파일을 .zip 압축 후 <code>S3 버킷</code>에 업로드
3️⃣ <code>S3 버킷</code> 파일 업로드 및 <code>CloudFront</code> 캐시 무효화로 최신 버전 반영
<br></p>
<hr>
<h2 id="github-actions-cicd-구축">GitHub Actions CI/CD 구축</h2>
<h3 id="githubworkflowsci-cdyaml-파일-생성">.github/workflows/ci-cd.yaml 파일 생성</h3>
<blockquote>
<p><strong>🗂️ 디렉토리 구조</strong>
├─.<strong>github</strong>
│  ├─ <strong>workflows/</strong>
│  │  └─ <strong>ci-cd.yaml/</strong></p>
</blockquote>
<ul>
<li>위와 같은 구조로 <code>ci-cd.yaml</code> 파일을 생성한 뒤 아래와 같이 스크립트를 적는다.</li>
<li>github 폴더명 앞에 <code>.</code> 찍어야 해.. 안찍어서 에러 떴어..<br>

</li>
</ul>
<h3 id="스크립트-흐름">스크립트 흐름</h3>
<ul>
<li>깃허브 리포지토리 접근</li>
<li><code>node_modules</code> 캐싱</li>
<li>AWS 자격증명 구성 및 <code>AWS CLI</code> 설치</li>
<li>프로젝트 관련 종속성을 설치할 노드 설치</li>
<li>종속성 설치(<code>node_modules</code>)</li>
<li>Vite 빌드 -&gt; <code>bonbonCafe-page/dist</code>에 정적 파일 생성</li>
<li><code>AWS CLI</code> 를 사용하여 <code>S3</code> 에 배포 파일을 업로드</li>
<li>캐시 무효화<br>

</li>
</ul>
<pre><code class="language-yaml">name: BonBon Front-end CI&amp;CD   # workflow 이름
on: 
    push:
        branches: 
          - main    # main 브랜치에 push 발생 시 실행
jobs:
    build-and-deploy:
        name: Build And Deploy    # Job 정의
        runs-on: ubuntu-latest    # 깃허브 액션을 실행할 운영체제(기본 우분투)
        env: # 환경변수 설정
            BUCKET: bonbon-front-end-bucket    # 버켓이름
            DIST: bonbonCafe-page/dist/    # 빌드 파일 이름
            REGION: ap-northeast-2    # S3 배포 지역이름
            CLOUDFRONT_ID: E255E3VZI3VDE5     # Cloud Front 배포 파일의 식별자 

        # 작업 순서
        steps: 

        # 1. 단계 | 깃허브 리포지토리 접근 -&gt; CI 서버로 가져옴
        - name: Checkout Repository # 작업 이름
          uses: actions/checkout@v4 # 워크 플로 작업을 위한 저장소 액세스를 위한 도구

        # 2. 단계 | node_modules 캐싱
        - name: Cache node modules
          uses: actions/cache@v3
          with:
            path: node_modules
            key: ${{ runner.OS }}-build-${{ hashFiles(&#39;**/package-lock.json&#39;) }}
            restore-keys: |
              ${{ runner.OS }}-build-
              ${{ runner.OS }}-

        # 3. 단계 | AWS 자격증명 구성 및 AWS CLI 설치
        - name: Configure AWS Credentials
          uses: aws-actions/configure-aws-credentials@v4
          with:
            aws-access-key-id: ${{secrets.AWS_ACCESS_KEY_ID}} 
            aws-secret-access-key: ${{secrets.AWS_SECRET_KEY_ID}}
            aws-region: ${{env.REGION}}

        #.4. 단계 | 프로젝트 관련 종속성을 설치할 노드 설치
        - name: Set up Node.js
          uses: actions/setup-node@v4
          with:
           node-version: &#39;20&#39;

        # 5. 단계 | 종속성 설치(node_modules)
        - name: Install Dependencies
          run: |
              cd bonbonCafe-page
              npm ci

        # 5.5 .env 파일 추가 -&gt; KakaoMap 키를 환경 변수로 주입
        - name: Create .env file
          run: |
              echo &quot;VITE_KAKAO_MAP_KEY=${{ secrets.VITE_KAKAO_MAP_KEY }}&quot; &gt; bonbonCafe-page/.env

        # 6. 단계 | Vite 빌드 -&gt; bonbonCafe-page/dist에 정적 파일 생성
        - name: Build 
          run: |
              cd bonbonCafe-page
              npm run build

        # 7. 단계 | AWS CLI 를 사용하여 S3 에 배포 파일을 업로드
        - name : Sync files to S3
          run: |
            aws s3 sync ${{env.DIST}} s3://${{env.BUCKET}}

        # 8. 단계 | 캐시 무효화
        - name : Clear Cash
          run: |
            aws cloudfront create-invalidation \
              --distribution-id ${{ env.CLOUDFRONT_ID}} \
              --paths &quot;/*&quot;
</code></pre>
<br>

<hr>
<h2 id="frontend-cicd-파이프라인-시연">FrontEnd CI/CD 파이프라인 시연</h2>
<h4 id="0-변경-사항-반영-전-사이트-→-bonbon1-ver1">0. 변경 사항 반영 전 사이트 → <em><code>“BonBon1 ver.1”</code></em></h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/ab784e91-8892-4a66-b166-9234b5411cba/image.png" width="800">
<br>

<h4 id="1-변경-내용-작업-후-github에-commit--push-→-main-branch로-pr--merge">1. 변경 내용 작업 후 Github에 Commit &amp; push → main branch로 PR &amp; Merge</h4>
<ul>
<li><strong><em><code>“BonBon ver.2”</code></em></strong> 로 변경한 후 Commit &amp; push</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/e69fdcd9-457e-4113-8f80-026c2e53e1e5/image.png" width="800">
<img src="https://velog.velcdn.com/images/namo_o36/post/f582d5f7-8aa5-470a-bdd2-fc433355ea40/image.png" width="800">
<br>

<h4 id="2-main-브랜치-업데이트-시-webhook-발생-→-git-action-workflows-실행">2. <code>main</code> 브랜치 업데이트 시 webhook 발생 → git action workflows 실행</h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/af72296c-5811-43f4-9345-cd02bbe7dc29/image.png" width="800">

<br>

<h4 id="3-git-action에서-build--deploy-완료-s3에-zip-파일-업로드-→-이후-cloudfront에서-배포-시작">3. Git Action에서 Build &amp; Deploy 완료, S3에 zip 파일 업로드 → 이후 CloudFront에서 배포 시작</h4>
<img src="https://velog.velcdn.com/images/namo_o36/post/0e2be67b-adf6-4ae6-9eb5-e227950aa38e/image.png" width="800">
<img src="https://velog.velcdn.com/images/namo_o36/post/263b1758-f37a-440c-9a67-a29e20073d89/image.png" width="800">

<br> 

<h4 id="4-웹-페이지에서-변경-사항-반영-확인">4. 웹 페이지에서 변경 사항 반영 확인</h4>
<ul>
<li>웹 페이지를 Route53 + ACM을 이용해 구입한 도메인에 연결했기 때문에 해당 도메인으로 접속이 가능하다.</li>
<li><strong><em><code>“BonBon1 ver.1“</code></em></strong> → <strong><em><code>”BonBon1 ver.2“</code></em></strong>로 바뀐 것을 확인할 수 있다.</li>
</ul>
<img src="https://velog.velcdn.com/images/namo_o36/post/655fe860-22d6-415f-83bf-db96f673dfe5/image.png" width="800">

<img src="https://velog.velcdn.com/images/namo_o36/post/ed998ea3-b773-440a-9670-fd91a8283a8d/image.png" width="800">

<hr>
<p>FrontEnd 끝!
<img src="https://velog.velcdn.com/images/namo_o36/post/530cd3ce-2ed3-4300-b5c0-7157c4fee243/image.jpg" width="150"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BONBON] Vue.js 화면 구성]]></title>
            <link>https://velog.io/@namo_o36/BONBON-Vue.js-%ED%99%94%EB%A9%B4-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@namo_o36/BONBON-Vue.js-%ED%99%94%EB%A9%B4-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Thu, 07 Aug 2025 16:05:44 GMT</pubDate>
            <description><![CDATA[<h3 id="vuejs-화면-설계">Vue.js 화면 설계</h3>
<img src="https://velog.velcdn.com/images/namo_o36/post/b075e44d-24bf-4ea3-ad54-fb7a25b3a24c/image.jpg" width="800">

<ul>
<li>이해를 위해 그린 필자의 미숙한 그림과 악필은 못 본 척 해주길 바란다.. (<del>노력이 가상하다고 생각해주었으면 한다</del>)</li>
<li>User가 들고 온 URL (ex. <code>/franchisee-accounts/:id</code>)을 Router에서 보고 그에 맞는 Component를 연결해준다.</li>
<li>필자는 Router에서 바로 기능들이 다닥다닥 붙어 있는 페이지로 연결하지 않고, 하위 컴포넌트를 import 해서 쓰는 상위 컴포넌트들을 연결했다.</li>
<li>위 그림을 토대로 짧게 비유하면, 고객(<code>User</code>)이 원하는 어트랙션을 타기 위해 어트랙션 주소(<code>URL</code>)가 적힌 지도를 들고 가이드(<code>Router</code>)에게 물으면, 가이드가 어트랙션 지역(<code>상위 컴포넌트</code>) 안내하고 해당 컴포넌트 내에 들어 있는 신나는 어트랙션(<code>하위 어트랙션</code>)을 마주하게 되는 구조이다.</li>
<li>어떻게 돌아가는지 직접 코드를 봐보자.</li>
</ul>
<hr>
<h3 id="🧙♀️router-놀이공원-가이드">🧙‍♀️Router (놀이공원 가이드)</h3>
<ul>
<li>놀이공원 가이드는 user들이 들고 오는 지도에 대한 컴포넌트를 바로바로 소개할 수 있도록 정보를 저장하고 있어야 한다.</li>
<li>Router는 URL 경로와 컴포넌트를 매핑하여, 특정 URL에 해당하는 Vue 컴포넌트를 렌더링하도록 설정한다. </li>
</ul>
<pre><code class="language-ts">import { useAuthStore } from &#39;@/stores/auth&#39;
import { createRouter, createWebHistory } from &#39;vue-router&#39;

const AuthLayout = () =&gt; import(&#39;@/layout/AuthLayout.vue&#39;);
const BaseLayout = () =&gt; import(&#39;@/layout/BaseLayout.vue&#39;);
const Login = () =&gt; import(&#39;@/views/auth/Login.vue&#39;);
const NotFound = () =&gt; import(&#39;@/views/common/NotFound.vue&#39;);
const MainView = () =&gt; import(&#39;@/views/MainView.vue&#39;)

const FranchiseeAccount = () =&gt; import(&#39;@/views/accounts/FranchiseeAccount.vue&#39;)
const FranchiseeAccountListView = () =&gt; import(&#39;@/views/accounts/FranchiseeAccountListView.vue&#39;)

...

// createRouter : Vue Router 설정 함수 
const router = createRouter({
  // 라우터가 사용할 라우팅 모드 지정 (HTML 5 모드)
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: &#39;/&#39;,
      name: &#39;base&#39;,
      component: BaseLayout,
      children: [
        {
          path:&#39;&#39;,
          name: &#39;main&#39;,
          component: MainView,
        },
        {
          path:&#39;user-account&#39;,
          name: &#39;user-account&#39;,
          component: UserAccount,
        },
        {
          path:&#39;user-account-modify&#39;,
          name: &#39;user-account-modify&#39;,
          component: UserAccountModifyView,
        },
        {
          path:&#39;password-modify&#39;,
          name: &#39;password-modify&#39;,
          component: PasswordModifyView,
        },
        {
          path:&#39;franchisee-accounts/:userId&#39;,
          name: &#39;franchisee-accounts&#39;,
          component: FranchiseeAccount,
        },

        ...

   ],
});

router.beforeEach((to, from, next) =&gt; {
  // 라우트가 변경되기 전에 실행되는 가드 -&gt; 라우팅 제어 및 조건에 맞지 않는 경로의 이동을 막기 위함
  const authStore = useAuthStore(); 

  if(to.name === &#39;login&#39; &amp;&amp; authStore.isLoggedIn){
    // 이미 로그인 했는데 로그인 페이지로 이동하는 경우 
    next({name: &#39;base&#39;});
  }

  // 로그인 페이지가 아니고, 로그인 상태가 아니면 로그인 페이지로 리다이렉트한다.
  // 로그인하지 않은 상태에서 로그인 페이지가 아닌 다른 페이지로 가는 경우, 로그인페이지로 리다이렉트 되도록
  if(to.name !== &#39;login&#39; &amp;&amp; to.name !== &#39;franchisee-join&#39; &amp;&amp; to.name !== &#39;manager-join&#39; &amp;&amp; !authStore.isLoggedIn) {
    next({name: &#39;login&#39;});
  } else {
    next();
  }
});

export default router
</code></pre>
<hr>
<h3 id="🪧-상위-컴포넌트-안내-받은-놀이공원-구역">🪧 상위 컴포넌트 (안내 받은 놀이공원 구역)</h3>
<ul>
<li>가이드 (Router)가 안내해준 대로 내가 원하는 놀이공원 구역에 도착했다.</li>
<li>페이지 진입점 역할로, 화면에 자식 컴포넌트를 렌더링하는 Wrapper 역할을 한다.</li>
<li>상위 컴포넌트는 단순히 구조와 진입만 담당하고 하위 컴포넌트에서 데이터 로직 및 렌더링 처리를 담당한다.</li>
<li>역할 분리, 재사용성 및 유지보수와 가독성 면에서 강점을 가지기 때문에 이러한 구조를 선택했다. 내가 작성한 하위 컴포넌트를 다른 페이지에서도 쓸 수 있으니까</li>
<li>구현한 기능 중 <code>가맹점주 계정 관리 화면</code>을 예시로 들겠다.</li>
</ul>
<pre><code class="language-ts">&lt;template&gt;
    &lt;v-container&gt;
        &lt;FranchiseeDetailForm /&gt;
    &lt;/v-container&gt;
&lt;/template&gt;

&lt;script setup&gt;
import FranchiseeDetailForm from &#39;@/components/forms/userform/FranchiseeDetailForm.vue&#39;;
import { reactive } from &#39;vue&#39;;
import apiClient from &#39;@/api&#39;;
import { useRouter } from &#39;vue-router&#39;;

const router = useRouter();

import { defineProps } from &#39;vue&#39;;

const props = defineProps({
  id: {
    type: String,
    required: true
  }
});

...</code></pre>
<ul>
<li>해당 상위 컴포넌트인 <code>FranchiseeAccount.vue</code>는 하위 컴포넌트인 <code>FranchiseeDetailForm.vue</code>를 가져와 진입점을 만들어준다.</li>
</ul>
<hr>
<h3 id="🎢-하위-컴포넌트-타고-싶었던-어트랙션">🎢 하위 컴포넌트 (타고 싶었던 어트랙션)</h3>
<ul>
<li>여기까지 왔다. 이제 어트랙션 타기만 하면 된다!</li>
<li>상위 컴포넌트가 감싸던 하위 컴포넌트들의 내용에 대한 얘기다.</li>
<li>실제 기능들이 여기 안에 들어있다.</li>
</ul>
<pre><code class="language-ts">&lt;template&gt;
  &lt;v-container class=&quot;py-4 hei&quot;  fluid&gt;
    &lt;!-- 상단 타이틀 --&gt;

    &lt;!-- 두 카드 나란히 배치 --&gt;
    &lt;v-row dense&gt;
      &lt;!-- 회원 정보 카드 --&gt;
      &lt;v-col cols=&quot;12&quot; md=&quot;6&quot;&gt;
        &lt;v-card class=&quot;pa-6&quot; elevation=&quot;2&quot; style=&quot;width: 100%; height: 650px;&quot;&gt;

            &lt;v-typography class=&quot;list&quot;  align=&quot;center&quot;&gt;
            계정 관리 / 
            &lt;/v-typography&gt;
            &lt;v-typography class=&quot;title&quot;  align=&quot;center&quot;&gt;
            가맹점주 정보 수정
            &lt;/v-typography&gt;

            &lt;br&gt;
            &lt;br&gt;

          &lt;div class=&quot;d-flex justify-center mb-6&quot;&gt;
            &lt;v-avatar size=&quot;120&quot;&gt;
              &lt;v-img :src=&quot;franchiseeInfo.userImage || &#39;https://bonbon-file-bucket.s3.ap-northeast-2.amazonaws.com/profile-default.jpg&#39;&quot;
              cover/&gt;
            &lt;/v-avatar&gt;
          &lt;/div&gt;

          &lt;v-row dense&gt;
            &lt;v-col cols=&quot;12&quot; md=&quot;12&quot; class=&quot;mb-3&quot;&gt;
              &lt;div class=&quot;info-label&quot;&gt;이메일&lt;/div&gt;
              &lt;div class=&quot;info-value&quot;&gt;{{ franchiseeInfo.email }}&lt;/div&gt;
            &lt;/v-col&gt;
            &lt;v-col cols=&quot;12&quot; md=&quot;12&quot; class=&quot;mb-3&quot;&gt;
              &lt;div class=&quot;info-label&quot;&gt;가맹점주 이름&lt;/div&gt;
              &lt;div class=&quot;info-value&quot;&gt;{{ franchiseeInfo.name }}&lt;/div&gt;
            &lt;/v-col&gt;
            &lt;!-- &lt;v-col cols=&quot;12&quot; md=&quot;12&quot; class=&quot;mb-3&quot;&gt;
              &lt;div class=&quot;info-label&quot;&gt;비밀번호&lt;/div&gt;
              &lt;div class=&quot;d-flex align-center gap-2&quot;&gt;
                &lt;div class=&quot;info-value&quot;&gt;************&lt;/div&gt;
                &lt;v-btn size=&quot;small&quot; variant=&quot;outlined&quot; color=&quot;primary&quot;&gt;비밀번호 확인&lt;/v-btn&gt;
              &lt;/div&gt;
            &lt;/v-col&gt; --&gt;
            &lt;v-col cols=&quot;12&quot; md=&quot;6&quot; class=&quot;mb-3&quot;&gt;
              &lt;div class=&quot;info-label&quot;&gt;전화번호&lt;/div&gt;
              &lt;div class=&quot;info-value&quot;&gt;{{ franchiseeInfo.phone }}&lt;/div&gt;
            &lt;/v-col&gt;
            &lt;v-col cols=&quot;12&quot; md=&quot;6&quot; class=&quot;mb-3&quot;&gt;
              &lt;div class=&quot;info-label&quot;&gt;가맹점&lt;/div&gt;
              &lt;!-- &lt;div class=&quot;info-value&quot;&gt;{{ franchiseeInfo.franchiseId }}&lt;/div&gt; --&gt;
              &lt;div class=&quot;info-value&quot;&gt;{{ franchiseeInfo.franchiseName }}&lt;/div&gt;
            &lt;/v-col&gt;
            &lt;v-col cols=&quot;12&quot; md=&quot;6&quot; class=&quot;mb-3&quot;&gt;
              &lt;div class=&quot;info-label&quot;&gt;계정상태&lt;/div&gt;
              &lt;v-chip :color=&quot;getStatusColor(franchiseeInfo.status)&quot; text-color=&quot;white&quot; variant=&quot;elevated&quot; size=&quot;small&quot;&gt;{{ franchiseeInfo.status }}&lt;/v-chip&gt;
            &lt;/v-col&gt;

            &lt;br&gt;
            &lt;v-divider&gt;&lt;/v-divider&gt;

            &lt;v-col cols=&quot;12&quot; class=&quot;d-flex justify-center mt-4&quot; style=&quot;gap: 10px;&quot;&gt;
              &lt;v-btn color=&quot;secondary&quot; @click=&quot;goToList&quot;&gt;목록으로&lt;/v-btn&gt;
              &lt;v-btn color=&quot;primary&quot; @click=&quot;goToEdit&quot;&gt;수정하기&lt;/v-btn&gt;
            &lt;/v-col&gt;

          &lt;/v-row&gt;
        &lt;/v-card&gt;
      &lt;/v-col&gt;

      &lt;!-- 추가 카드 --&gt;
      &lt;v-col cols=&quot;12&quot; md=&quot;6&quot;&gt;
        &lt;v-card class=&quot;pa-6&quot; elevation=&quot;2&quot; style=&quot;width: 100%; height: 650px;&quot;&gt;
          &lt;div&gt;
            &lt;v-typography class=&quot;title2&quot;  align=&quot;center&quot;&gt;
              가맹점 위치 확인
            &lt;/v-typography&gt;
          &lt;/div&gt;
          &lt;KakaoMap class=&quot;kakao-map&quot; /&gt;
        &lt;/v-card&gt;
      &lt;/v-col&gt;
    &lt;/v-row&gt;
  &lt;/v-container&gt;
&lt;/template&gt;

&lt;script setup&gt;
// 추후 props 또는 store 연결 가능
import KakaoMap from &#39;@/components/map/KakaoMap.vue&#39;;
import { onMounted, ref } from &#39;vue&#39;;
import { useRoute, useRouter } from &#39;vue-router&#39;;
import apiClient from &#39;@/api&#39;;

const route = useRoute();
const router = useRouter();
const userId = route.params.userId;
const franchiseeInfo = ref({});

onMounted(() =&gt; {
  // KakaoMap이 로드된 후 높이를 동적으로 설정하는 코드 추가 가능
  const kakaoMapElement = document.querySelector(&#39;.kakao-map&#39;);
  const parentHeight = kakaoMapElement.parentElement.clientHeight;

  // 카드 높이에 맞춰서 카카오맵 높이 설정
  kakaoMapElement.style.height = `${parentHeight}px`;
  kakaoMapElement.style.width = &#39;100%&#39;;
});

const goToList = () =&gt; {
  router.push({name : &#39;franchisee-accounts-list&#39;}); // 원하는 목록 페이지 경로로 변경
};

const goToEdit = () =&gt; {
  router.push(`/franchisee-accounts/${userId}/edit`);
};

const getStatusColor = (status) =&gt; {
  switch (status) {
    case &#39;ACTIVE&#39;:
      return &#39;green&#39;;
    case &#39;INACTIVE&#39;:
      return &#39;grey&#39;;
    case &#39;PENDING&#39;:
      return &#39;orange&#39;;
    case &#39;EXPIRED&#39;:
      return &#39;grey&#39;;
    case &#39;DELETED&#39;:
      return &#39;red&#39;;
    default:
      return &#39;blue&#39;;
  }
};

onMounted(async () =&gt; {
  try {
    const accessToken = localStorage.getItem(&#39;accessToken&#39;);
    console.log(&#39;userId:&#39;, userId); // userId 확인
    console.log(&#39;accessToken:&#39;, accessToken); // 토큰 확인

    const response = await apiClient.get(`/bonbon/user/franchisee/${userId}`, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    console.log(&#39;API 응답:&#39;, response);
    franchiseeInfo.value = response.data;
    console.log(franchiseeInfo.value);

    const kakaoMapElement = document.querySelector(&#39;.kakao-map&#39;);
    if (kakaoMapElement &amp;&amp; kakaoMapElement.parentElement) {
      kakaoMapElement.style.height = `${kakaoMapElement.parentElement.clientHeight}px`;
      kakaoMapElement.style.width = &#39;100%&#39;;
    }
  } catch (error) {
    console.error(&#39;가맹점주 정보 로드 실패:&#39;, error);
    if (error.response) {
      console.error(&#39;응답 데이터:&#39;, error.response.data);
      console.error(&#39;응답 상태:&#39;, error.response.status);
    } else {
      console.error(&#39;요청 실패:&#39;, error.message);
    }
  }
});
</code></pre>
<hr>
<h3 id="vuejs-구현">Vue.js 구현</h3>
<ul>
<li>위에 소개한 방식으로 화면을 구현했다.</li>
<li>Components와 view들을 나눠서 구현하고, 이를 Router에 등록하는 흐름으로 구현했다. </li>
<li>큰 구조적 흐름이 익으니 개발에 속도가 붙는... 듯 했다가 디테일한 화면 구성이나 기능적인 부분에서 뭔갈 붙이고 빼고 붙이고 빼고 ... 를 무한 반복하다 보니 눈이 빠질 것 같았다. 디자이너 분들은 여분의 눈을 들고다니시는게 분명하다.</li>
</ul>
<div align="center">
  <img src="https://velog.velcdn.com/images/namo_o36/post/f43049da-b4d3-4f35-b45d-fc20e71bedb9/image.png" width="300">
</div>


]]></description>
        </item>
    </channel>
</rss>