<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>duck-ach.log</title>
        <link>https://velog.io/</link>
        <description>자몽 허니 블랙티와 아메리카노 사이 그 어딘가</description>
        <lastBuildDate>Tue, 01 Jul 2025 02:12:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>duck-ach.log</title>
            <url>https://velog.velcdn.com/images/duck-ach/profile/f87d9757-77ff-418c-9479-e3340d763778/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. duck-ach.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/duck-ach" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Prometheus] PromQL, HTTP API 데이터 쿼리 방법 정리]]></title>
            <link>https://velog.io/@duck-ach/Prometheus-PromQL-HTTP-API-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BF%BC%EB%A6%AC-%EB%B0%A9%EB%B2%95-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@duck-ach/Prometheus-PromQL-HTTP-API-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BF%BC%EB%A6%AC-%EB%B0%A9%EB%B2%95-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 01 Jul 2025 02:12:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/50cc67f6-1346-4f13-9553-7aa26059067e/image.png" alt=""></p>
<h1 id="prometheus-데이터-쿼리-방법">Prometheus 데이터 쿼리 방법</h1>
<p>Prometheus에서 데이터를 쿼리하는 방식으로는 크게 두 가지가 있다.</p>
<ul>
<li>PromQL(Prometheus Query Language)</li>
<li>HTTP API</li>
</ul>
<p>이 두가지 방식에 대해 공부해보자.</p>
<table>
<thead>
<tr>
<th></th>
<th>PromQL</th>
<th>HTTP API</th>
</tr>
</thead>
<tbody><tr>
<td>문법</td>
<td>metric + 함수 + 레이블</td>
<td>URL 파라미터로 PromQL 전달</td>
</tr>
<tr>
<td>출력</td>
<td>UI 또는 텍스트</td>
<td>JSON</td>
</tr>
<tr>
<td>사용</td>
<td>웹 UI, Grafana 등</td>
<td>외부코드, 자동화, Custom Tool 등</td>
</tr>
</tbody></table>
<h1 id="promqa">PromQA</h1>
<p>PromQL(Prometheus Query Language)은 Prometheus의 내장 쿼리 언어이다.
시계열데이터(Time Series Data)를 질의, 집계, 필터링, 변환 등의 작업을 수행할 수 있다.</p>
<p><span style="color: grey">※ 시계열 데이터(Time Series Data) : 시간의 흐름에 따라 수집된 데이터로, 특정 시간 간격을 두고 연속적으로 관측된 값을 의미</span></p>
<p><code>sum()</code>, <code>rate()</code>, <code>avg()</code>, <code>max()</code>, <code>min()</code> 등 다양한 함수 및 연산자를 사용해서 복잡한 질의를 구성할 수 있다.</p>
<h2 id="promqa-예제">PromQA 예제</h2>
<h3 id="단일-메트릭-조회">단일 메트릭 조회</h3>
<p><span style="color: grey">http_request_total 메트릭의 현재 시계열을 모두 가져옴</span></p>
<pre><code class="language-java">http_requests_total</code></pre>
<h3 id="레이블-필터링">레이블 필터링</h3>
<p><span style="color: grey">최근 5분동안 api-server에서 발생한 HTTP 요청수의 GET 요청 계산</span></p>
<pre><code class="language-java">http_requests_total{job=&quot;api-server&quot;, method=&quot;GET&quot;}</code></pre>
<h3 id="범위-선택--함수적용">범위 선택 + 함수적용</h3>
<p><span style="color: grey">최근 5분 동안의 HTTP 요청 수의 초당 증가율을 계산</span></p>
<pre><code class="language-java">rate(http_requests_total{job=&quot;api-server&quot;}[5m])</code></pre>
<h3 id="집계-함수">집계 함수</h3>
<p><span style="color: grey">job 별 HTTP 요청수의 증가율을 합산</span></p>
<pre><code class="language-java">sum(rate(http_requests_total[5m])) by (job)</code></pre>
<h3 id="수학-연산">수학 연산</h3>
<p><span style="color: grey">메모리 사용률 계산(백분율)</span></p>
<pre><code class="language-java">node_memory_MemFree / node_memory_MemTotal * 100</code></pre>
<h3 id="조건-필터링">조건 필터링</h3>
<p><span style="color: grey">up 메트릭이 0인 (즉 다운된 대상만 선택)</span></p>
<pre><code class="language-java">up == 0</code></pre>
<h1 id="http-api">HTTP API</h1>
<p>Prometheus가 제공하는 REST API를 사용하여 데이터를 조회하거나 자동화할 때 사용한다.</p>
<h2 id="promqa-예제-1">PromQA 예제</h2>
<h3 id="현재-상태조회-쿼리">현재 상태조회 쿼리</h3>
<p><span style="color: grey">현재 Target이 살아있는지(UP 상태인지) 확인아있는지(UP 상태인지) 확인</span></p>
<pre><code class="language-java">GET /api/v1/query?query=up</code></pre>
<p><strong>응답예시</strong></p>
<pre><code class="language-json">{
  &quot;status&quot;: &quot;success&quot;, // 쿼리성공여부
  &quot;data&quot;: {
    &quot;resultType&quot;: &quot;vector&quot;, // 단일 시점의 시계열 결과 (스냅샷)
    &quot;result&quot;: [
      {
        &quot;metric&quot;: {&quot;job&quot;: &quot;node&quot;, &quot;instance&quot;: &quot;localhost:9100&quot;}, // 해당 시계열의 메타정보{prometheus에서 수집한 job 이름, 해당 타깃의 주소와 포트}
        &quot;value&quot;: [1627645327.182, &quot;1&quot;] // 이 시계열 데이터가 수집된 시점의 타임스탬프, up 상태(1:정상, 0:비정상)
      }
    ]
  }
}</code></pre>
<h3 id="http-api-예시">HTTP API 예시</h3>
<table>
<thead>
<tr>
<th>Endpoint</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>/api/v1/query</td>
<td>현재 시점의 상태 쿼리</td>
</tr>
<tr>
<td>/api/v1/query_range</td>
<td>범위 데이터(시간 간격) 쿼리</td>
</tr>
<tr>
<td>/api/v1/series</td>
<td>존재하는 시계열 메타데이터 조회</td>
</tr>
<tr>
<td>/api/v1/label/{name}/values</td>
<td>특정 레이블의 모든 값 조회</td>
</tr>
<tr>
<td><span style="color: grey">최근 5분동안 api-server에서 발생한 HTTP 요청수의 증가율 계산</span></td>
<td></td>
</tr>
<tr>
<td>```java</td>
<td></td>
</tr>
<tr>
<td>GET /api/v1/query?query=rate(http_requests_total{job=&quot;api-server&quot;}[5m])</td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Apache JMeter] JMeter 사용법 - JMeter란?, 테스트 방법]]></title>
            <link>https://velog.io/@duck-ach/Apache-JMeter-JMeter-%EC%82%AC%EC%9A%A9%EB%B2%95-JMeter%EB%9E%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@duck-ach/Apache-JMeter-JMeter-%EC%82%AC%EC%9A%A9%EB%B2%95-JMeter%EB%9E%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 23 Jun 2025 07:40:41 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/2590e2ef-7927-4c7d-a4d1-9ddb9a047c0c/image.png" alt=""></p>
<p>성능테스트 테스트 도구에는 <code>k6</code>, <code>JMeter</code>, <code>Locust</code>, <code>Gatling</code> 등이 있다.</p>
<p>이 중, 자사 SSO 를 개발하고나서 성능테스트도 추후에 진행하겠지만 지금은 우선 모니터링 Prometheus + Grafana를 연동하고 있는데 수집 데이터가 많지 않아 시각화를 하는데 무리가 있어 요청데이터를 늘려 모니터링이 정상적으로 잘 되는지 테스트해보기 위해 회사에서 많이 사용하는 JMeter를 학습하고자 정리해본다.</p>
<h1 id="🚀-jmeter-란">🚀 JMeter 란?</h1>
<p>JMeter는 웹 애플리케이션과 서버의 성능을 테스트하기 위한 오픈소스 부하 테스트 도구이다.
HTTP, REST API, 데이터베이스, FTP, WebSocket 등 다양한 시스템에 대한 부하/스트레스 테스트를 자동화할 수 있다.</p>
<ul>
<li><code>다양한 프로토콜 지원</code> : HTTP, HTTPS, SOAP, REST, JDBC, FTP 등 지원</li>
<li><code>GUI 제공</code> : 테스트 시나리오를 시각적으로 구성이 가능</li>
<li><code>부하테스트</code> : 수백~수천명의 동시 사용자 시뮬레이션 가능</li>
<li><code>리포트/그래프 제공</code> : 요청속도, 응답시간, 성공률 등 시각화</li>
<li><code>확장성</code> : 플러그인, 스크립트(Javascript, BeanShell 등) 지원</li>
<li><code>CI/CD 연동 가능</code> : Jenkins, GitHub Action </li>
</ul>
<h1 id="🚀-jmeter-다운로드-및-실행">🚀 JMeter 다운로드 및 실행</h1>
<p>우선 유의사항으로는 성능테스트 시 같은 리소스를 사용하면 정확한 수치를 잴 수 없어 애플리케이션 서버와 테스트(JMeter)서버는 달라야 한다.</p>
<p><a href="https://jmeter.apache.org/download_jmeter.cgi">다운로드 링크 Apache-JMeter</a></p>
<p>zpi 파일 다운로드 후 압축해제
<img src="https://velog.velcdn.com/images/duck-ach/post/bbf56c53-fb32-4116-851d-22e0e5b87f77/image.png" alt=""></p>
<p>apache-jmeter-{version}/bin 폴더 아래에 
<img src="https://velog.velcdn.com/images/duck-ach/post/bb83554f-4ff7-4c92-8da8-48fe26ea36be/image.png" alt="">
jmeter.bat 파일을 더블클릭하여 실행
<img src="https://velog.velcdn.com/images/duck-ach/post/d657d056-927d-47e9-b0fb-6c63d0961996/image.png" alt=""></p>
<p><strong>실행 완료</strong>
실행을 완료하면 아래와 같은 GUI 화면이 나온다.
<img src="https://velog.velcdn.com/images/duck-ach/post/8bf2736d-6a0b-45b3-9387-e505ed5ed3a4/image.png" alt=""></p>
<h1 id="🚀-jmeter-테스트-작성">🚀 JMeter 테스트 작성</h1>
<h2 id="✅-jmeter-테스트-용어-정리">✅ JMeter 테스트 용어 정리</h2>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Test Plan</strong></td>
<td>테스트 전반의 구조와 흐름을 정의하는 최상위 단위</td>
<td>로그인 → API 호출 → 로그아웃 순서 구성</td>
</tr>
<tr>
<td><strong>Thread Group</strong></td>
<td>동시 사용자 수, 반복 횟수 등을 설정하는 실행 단위</td>
<td>100명의 가상 사용자를 10분 동안 테스트</td>
</tr>
<tr>
<td><strong>Sampler</strong></td>
<td>실제 요청을 전송하는 구성 요소</td>
<td>HTTP Request, JDBC Request 등</td>
</tr>
<tr>
<td><strong>HTTP Request</strong></td>
<td>HTTP/HTTPS 요청을 전송하는 샘플러</td>
<td><code>/login</code>, <code>/api/data</code> 요청 등</td>
</tr>
<tr>
<td><strong>Logic Controller</strong></td>
<td>요청 흐름을 제어하는 로직 블록</td>
<td>조건문(if), 반복(loop), switch 등</td>
</tr>
<tr>
<td><strong>Loop Controller</strong></td>
<td>하위 요소를 반복 실행하게 만드는 컨트롤러</td>
<td>로그인 요청 5번 반복</td>
</tr>
<tr>
<td><strong>If Controller</strong></td>
<td>특정 조건일 때만 실행되는 컨트롤러</td>
<td>응답값이 200일 때만 다음 요청 실행</td>
</tr>
<tr>
<td><strong>Assertion</strong></td>
<td>응답 결과의 유효성을 검증</td>
<td>응답 코드가 200인지 확인, 본문에 &quot;success&quot; 포함 여부 등</td>
</tr>
<tr>
<td><strong>Listener</strong></td>
<td>테스트 결과를 수집하거나 시각화하는 구성 요소</td>
<td>View Results Tree, Summary Report, Aggregate Graph 등</td>
</tr>
<tr>
<td><strong>Timer</strong></td>
<td>요청 간 지연을 추가</td>
<td>사용자 간 1초씩 지연</td>
</tr>
<tr>
<td><strong>PreProcessor</strong></td>
<td>요청 전에 실행되는 설정    변수</td>
<td>초기화, 동적 파라미터 생성 등</td>
</tr>
<tr>
<td><strong>PostProcessor</strong></td>
<td>요청 후 결과 처리에 사용</td>
<td>JSON 응답에서 토큰 추출</td>
</tr>
<tr>
<td><strong>Regular Expression Extractor</strong></td>
<td>응답에서 값을 정규표현식으로 추출</td>
<td><code>&lt;token&gt;(.*?)&lt;/token&gt;</code> 등</td>
</tr>
<tr>
<td><strong>CSV Data Set Config</strong></td>
<td>외부 CSV 파일로부터 데이터 로드</td>
<td>사용자 계정 목록 불러오기</td>
</tr>
<tr>
<td><strong>Variables</strong></td>
<td>테스트 중 사용하는 변수</td>
<td><code>${username}</code>, <code>${access_token}</code> 등</td>
</tr>
<tr>
<td><strong>Functions</strong></td>
<td>변수 값 생성, 날짜 계산 등을 위한 내장 함수</td>
<td><code>${__time(YMD)}</code>, <code>${__UUID}</code> 등</td>
</tr>
<tr>
<td><strong>Assertion Result</strong></td>
<td>Assertion의 성공/실패 결과</td>
<td>실패 시 로그에 기록됨</td>
</tr>
<tr>
<td><strong>Throughput</strong></td>
<td>일정 시간 내 처리한 요청 수</td>
<td>초당 500건의 로그인 처리</td>
</tr>
<tr>
<td><strong>Latency</strong></td>
<td>요청 전송 후 최초 응답까지 걸린 시간</td>
<td>평균 200ms</td>
</tr>
<tr>
<td><strong>Response Time</strong></td>
<td>전체 응답을 받는 데 걸린 시간</td>
<td>평균 450ms</td>
</tr>
<tr>
<td><strong>Error Rate</strong></td>
<td>전체 요청 중 실패한 요청의 비율</td>
<td>0.5% 오류 발생률 등</td>
</tr>
</tbody></table>
<h2 id="✅-jmeter-테스트-작성">✅ JMeter 테스트 작성</h2>
<h3 id="1-테스트-생성">1. 테스트 생성</h3>
<p><code>File</code> - <code>New</code> - <code>Test Plan Name 작성</code>
<img src="https://velog.velcdn.com/images/duck-ach/post/f474eee8-be0e-4cac-bde1-60d73e3a9af4/image.png" alt=""></p>
<h3 id="2-thread-group유저설정">2. Thread Group(유저설정)</h3>
<p>테스트 할 유저를 설정해준다.
<code>Test Plan 마우스 우클릭</code> - <code>Add</code> - <code>Threads (Users)</code> - <code>Thread Group</code>
<img src="https://velog.velcdn.com/images/duck-ach/post/1444c078-472d-43d6-b376-d3c3a23bc5a6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/8af18ff6-8d53-4526-a0f4-30ffa18a5a56/image.png" alt=""></p>
<p><span style="color: grey;">10명의 유저(Number of Threads (users))가 1초(Ramp-up period (seconds))만에 2번(Loop Count) 반복해서 에러가 발생해도 계속 요청을 보낸다고 설정</span></p>
<p><strong>옵션 설명</strong></p>
<ul>
<li><code>Name</code> : Thread Group의 이름</li>
<li><code>Action to be taken after a Sampler error</code> : Sampler로 테스트 중 에러 시 Action 설정</li>
<li><code>Number of Threads (users)</code> : 유저(Thread) 수. 동시에 몇개의 Thread를 발생시킬 것인지에 대한 옵션</li>
<li><code>Ramp-up period (seconds)</code> : Thread를 발생시킬 시간</li>
<li><code>Loop Count</code> : Thread의 반복 시간. n 으로 값을 설정할 수 있으며 설정된 값에 따라 <span style="background-color: #FAFAD2">Number of Threads X Ramp-up period</span> 만큼 요청을 다시 보냄</li>
</ul>
<h3 id="3-sampler유저-action-설정">3. Sampler(유저 Action 설정)</h3>
<p>요청을 보낼 Request를 작성해준다.
나는 HTTP 기반 요청을 보낼것이기 때문에 <code>HTTP Request</code>를 추가해주었다.
<code>Thread Group 마우스 우클릭</code> - <code>New</code> - <code>Sampler</code> - <code>HTTP Request</code> 를 선택한다.
<img src="https://velog.velcdn.com/images/duck-ach/post/822e524f-ff27-40f9-9c97-a33441d8c9a3/image.png" alt=""></p>
<p>x-www-form-urlencoded 형태로 파라미터를 전달을 해야해서 <code>Body data</code> 를 열어서 파라미터를 담아주었다.
<img src="https://velog.velcdn.com/images/duck-ach/post/e4563580-703e-457e-8ceb-86cba3c094ad/image.png" alt=""></p>
<h3 id="4-header-manager-header-설정">4. Header Manager (Header 설정)</h3>
<p>위에서 말한 x-www-form-urlencoded 형태로 파라미터를 전달하기 위해 헤더에<code>Content-Type=application/x-www-form-urlencoded</code> 파라미터를 담아주어야 한다.</p>
<p><code>Thread Group 마우스 우클릭</code> - <code>Add</code> - <code>Config Element</code> - <code>HTTP Header Manager</code> 를 선택한다.
<img src="https://velog.velcdn.com/images/duck-ach/post/ac18c739-618e-4346-b4ec-fe7a86071806/image.png" alt=""></p>
<p>헤더추가</p>
<ul>
<li>Content-Type: application/x-www-form-urlencoded</li>
<li>KEYCLOAK_LOCALE: ko
<img src="https://velog.velcdn.com/images/duck-ach/post/62304ecc-6545-4656-9dc5-aadf8710c3cd/image.png" alt=""></li>
</ul>
<h3 id="5-listener">5. Listener</h3>
<p>결과를 모니터링 하기위해 Listener를 추가해준다.</p>
<ul>
<li>view Results Tree</li>
<li>Summary Report</li>
<li>View Results in Table
세개를 추가해주었다.</li>
</ul>
<p><code>HTTP Request 마우스 우클릭</code> - <code>Add</code> - <code>Listener</code> - <code>View Result Tree</code>, <code>Summary Report</code>, <code>View Results in Table</code></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/e5d60fa9-7935-49a5-8e8f-8c223de2d329/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/8894e4d1-496a-4a73-8ba8-ce7d73a1d902/image.png" alt=""></p>
<h3 id="6-assertion">6. Assertion</h3>
<p>응답 검증을 위해 Assertion을 추가해준다.
<code>HTTP Request 우클릭</code> - <code>Assertions</code> - <code>Response Assertion</code></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/c2feb2ac-39b5-4d52-bfa8-6b3f91bde3a1/image.png" alt=""></p>
<p>응답 텍스트에 <code>&quot;access_token&quot;</code> 이 담겨있으면 성공으로 응답 Assertion을 작성해주었다.
<img src="https://velog.velcdn.com/images/duck-ach/post/492a6144-a7fe-4034-b6fd-d2993154266e/image.png" alt=""></p>
<h2 id="jmeter-테스트-실행">JMeter 테스트 실행</h2>
<h3 id="1-테스트-실행">1. 테스트 실행</h3>
<p>▶️버튼을 누르면 실행
🧹빗자루 모양 버튼을 누르면 응답 결과를 clear 할 수 있다.
<img src="https://velog.velcdn.com/images/duck-ach/post/0f430e9a-35d8-4a1c-a3f7-acc6b7289c89/image.png" alt=""></p>
<h3 id="2-테스트-결과">2. 테스트 결과</h3>
<p>View Results Tree 페이지를 본 결과 응답 데이터에 <code>access_token</code>을 받아오는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/duck-ach/post/a0183c93-e310-4e88-a45c-cead23d594bd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] 04. Container, Pod, Node]]></title>
            <link>https://velog.io/@duck-ach/Kubernetes-04.-Container-Pod-Node</link>
            <guid>https://velog.io/@duck-ach/Kubernetes-04.-Container-Pod-Node</guid>
            <pubDate>Mon, 02 Jun 2025 13:39:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/b721e813-2d82-4709-82b7-f8ad02876019/image.png" alt="">
Kubernetes의 구성요소로는 Container, Pod, Node 등 구성요소가 다양하다.
각 구성요소는 어떤 역할을 수행하며, 구성요소가 어떻게 합쳐져 구성될 수 있을지 정리해보겠다.</p>
<h1 id="🚀-kubernetes의-구성요소">🚀 Kubernetes의 구성요소</h1>
<p>Kubernetes는 컨테이너화 된 Application을 자동으로 배포, 스케일링, 운영 하는 오픈 소스 플랫폼 이며, 아래 이미지와 같이 구조가 계층적으로 구성되어 있다.
<img src="https://velog.velcdn.com/images/duck-ach/post/f2f402bf-486d-4f3d-b4c6-99f313071a06/image.png" alt=""></p>
<h2 id="☸️-kubernetes-cluster">☸️ Kubernetes Cluster</h2>
<p>Kubernetes의 <span style="color: red;">전체 시스템</span>을 뜻하며, 여러 개의 Node로 구성된 집합체이다.</p>
<p><strong>구성요소</strong></p>
<ul>
<li><strong>Control Plane :</strong> Kubernetes 클러스터를 전체적으로 제어하는 논리적 개념. 물리적인 실행 호스트는 Node(Master Node)가 담당</li>
<li><strong>Node :</strong> Application(Workload)를 실제 실행하는 서버</li>
</ul>
<h2 id="☸️-node">☸️ Node</h2>
<p>Kubernetes 클러스터 내에서 <span style="color: red;">컨테이너가 실제로 실행되는 물리적/가상 서버</span></p>
<p><strong>구성요소</strong></p>
<ul>
<li><strong>Master Node :</strong> Master Node는 Control Plane의 물리적 실행 호스트</li>
<li><strong>Worker Node :</strong> 실제로 Application(Container)을 실행하는 노드. 일반적으로 Node라고 하면 Worker Node를 의미하는 문맥이 많다.(반대로 Master Node는 특별히 구분해서 지칭함)</li>
</ul>
<h2 id="☸️-pod">☸️ Pod</h2>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/72393d97-3e3b-4e4a-a62d-53187093f720/image.png" alt=""></p>
<p>Kubernetes에서 배포 가능한 가장 작은 단위이며, 하나 이상의 컨테이너 그룹을 칭하기도 한다.
하나의 Pod는 하나 이상의 컨테이너를 포함할 수 있으며, 이들은 같은 네트워크를 공유한다.
일반적으로 하나의 Application 단위를 실행한다.</p>
<p>Pod에 Container 가 여러개 있을 순 있지만, 보통은 다른 Application 모음을 한 Pod 내에 배치.</p>
<p>Kubernetes에서 Application의 사용자가 많아지고 트래픽이 증가한다면 Pod를 생성하여 복제하고, 다시 규모를 줄일 때에는 기존의 Pod를 없앤다.</p>
<h2 id="☸️-container">☸️ Container</h2>
<p>애플리케이션 실행에 필요한 코드, 라이브러리, 설정 파일 등이 포함된 독립적인 실행 환경
Docker, containerd 등의 Container Runtime을 통해 실행된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] 03. ETCD]]></title>
            <link>https://velog.io/@duck-ach/Kubernetes-03.-ETCD</link>
            <guid>https://velog.io/@duck-ach/Kubernetes-03.-ETCD</guid>
            <pubDate>Thu, 29 May 2025 13:03:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/0a032cf5-7ee2-410b-a6c4-c339edd116be/image.png" alt=""></p>
<h1 id="🚀-etcd란">🚀 ETCD란?</h1>
<p>ETCD란 분산된 시스템이나 클러스터의 중요한 데이터를 저장하고 관리하기 위해 사용되는 분산 키-값 저장소이다.
특히, Kubernetes와 같은 컨테이너 오케스트레이션 플랫폼에서 Cluster의 구성데이터, 상태데이터, 메타데이터를 관리하는 데 핵심적인 역할을 수행한다.</p>
<h1 id="🚀-etcd에-들어있는-정보">🚀 ETCD에 들어있는 정보</h1>
<ul>
<li>Node에 관한 데이터</li>
<li>POD에 관한 데이터</li>
<li>Config 설정 데이터</li>
<li>Secret 데이터</li>
<li>Account 데이터</li>
<li>권한 데이터</li>
<li>바인딩 데이터</li>
<li>Other,,</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] 02. Container Runtime]]></title>
            <link>https://velog.io/@duck-ach/Kubernetes-02.-Container-Runtime</link>
            <guid>https://velog.io/@duck-ach/Kubernetes-02.-Container-Runtime</guid>
            <pubDate>Tue, 27 May 2025 12:28:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/aac7d00a-7366-4c26-abdd-2276e6fb0c80/image.png" alt=""></p>
<h1 id="🚀-container-runtime-interface">🚀 Container Runtime Interface</h1>
<p><strong>Container Runtime Interface(CRI)</strong>는 어떤 Container Runtime 공급업체던지 <span style="color: blue"><strong>Open Container Initiative(OCI)</strong></span>표준을 준수한다면 쿠버네티스의 Container Runtime으로 작업할 수 있게 만들어준다.</p>
<blockquote>
<p><span style="color: blue"><strong>Open Container Initiative(OCI)</strong></span> 는 <strong>imagespec</strong>과 <strong>runtimespec</strong>으로 구성되어 있다. </p>
</blockquote>
<ul>
<li><strong>imagespec</strong> : 이 이미지가 어떻게 생겼는지 정의 (정적 정보)</li>
<li><strong>runtimespec</strong> : 컨테이너를 실행(run)할 때 어떻게 동작해야 하는지에 대한 기준 정의 (동적 실행정보)</li>
</ul>
<h1 id="🚀-주요-kubernetes-container-runtime">🚀 주요 Kubernetes Container Runtime</h1>
<p>Kubernetes에서 사용할 수 있는 Container Runtime들은 여러 종류가 있고, 각자 다른 기술 스택, 성능, 보안성, 유지보수 등을 기준으로 선택받는다.</p>
<h2 id="🐳-docker-dockershim">🐳 Docker (dockershim)</h2>
<p>Kubernetes 초기 버전에서 기본으로 사용하던 런타임. CRI표준이 나오기 전에 출시되어 Kubernetes는 dockershim을 이용한 지원을 하고 있었음. 
<span style="color: grey">dockershim은 도커와 쿠버네티스의 중간에 번역하는 역할을 수행.</span>
하지만 docker만을 위한 dockershim 지원의 어려움으로 인해 <strong>Kubernetes v1.24부터 공식적으로 지원 중단</strong>되었으며, 현재는 개발환경이나 학습 용도로 사용한 후, 운영환경 전환 시 컨테이너 런타임만 다른것으로 변경하여 동일한 환경에서 Kubernetes를 사용한다.</p>
<p><span style="color: blue"><strong>장점</strong></span></p>
<ul>
<li>사용하기 쉬움 (GUI 도구, Docker CLI 등)</li>
<li>풍부한 에코시스템과 튜토리얼</li>
</ul>
<p><span style="color: red"><strong>단점</strong></span></p>
<ul>
<li>Kubernetes에서 공식적으로 제거됨</li>
<li>성능, 보안 측면에서 현대적 런타임에 비해 뒤처짐</li>
</ul>
<h2 id="⛴️-containerd">⛴️ containerd</h2>
<p>Docker에서 분리된 경량화 컨테이너 런타임이며, CNCF(Cloud Native Computing Foundation)에서 관리하는 프로젝트다. Kubernetes와의 궁합이 매우 좋아 현재 주류 런타임으로 자리잡았으며 GKE, AKS, EKS 등 대부분의 대형 클라우드 서비스에서 기본 런타임으로 사용한다.</p>
<p><span style="color: blue"><strong>장점</strong></span></p>
<ul>
<li>경량성과 안정성 우수</li>
<li>Kubernetes와의 통합이 뛰어남 (CRI plugin 내장)</li>
<li>Docker 이미지와의 높은 호환성</li>
</ul>
<p><span style="color: red"><strong>단점</strong></span></p>
<ul>
<li>자체 CLI가 불편함</li>
<li>일부 고급 기능 부족 (ex. 이미지 빌드, 로컬 개발 편의 기능 등)</li>
</ul>
<h2 id="🛡-cri-o">🛡 CRI-O</h2>
<p>Red Hat 주도로 개발된 Kubernetes 전용 컨테이너 런타임. Docker 없이도 Kubernetes에서 컨테이너를 실행할 수 있도록 설계되었으며, 보안과 경량화에 초점이 맞춰져 있다. OpenShift에서 기본 런타임으로 채택되어 사용된다.</p>
<blockquote>
<p><strong>☁️ OpenShift</strong> : Red Hat이 만든 Kubernetes 플랫폼
단순한 클러스터 관리 도구를 넘어서 CI/CD, 모니터링, 보안, UI까지 모두 갖춘 엔터프라이즈급 PaaS (Platform as a Service)</p>
</blockquote>
<p><span style="color: blue"><strong>장점</strong></span></p>
<ul>
<li>Kubernetes에 최적화되어 있음</li>
<li>Podman과의 연동 우수</li>
<li>보안 기능 강화 (seccomp, AppArmor, SELinux 지원 등)</li>
</ul>
<p><span style="color: red"><strong>단점</strong></span></p>
<ul>
<li>범용성이 낮고 Kubernetes 외 환경에선 사용하기 어려움</li>
<li>사용자 및 커뮤니티 규모가 상대적으로 작음</li>
</ul>
<h2 id="🔐-gvisor">🔐 gVisor</h2>
<p>Google에서 개발한 보안 중심의 사용자 공간(User-space) 컨테이너 런타임. 컨테이너를 커널로부터 격리시키는 샌드박스 기술을 사용한다. 주로 민감한 데이터를 다루는 보안 중심 서비스에서 사용한다.</p>
<p><span style="color: blue"><strong>장점</strong></span></p>
<ul>
<li>매우 강력한 샌드박싱 제공</li>
<li>높은 격리 수준 (호스트에 대한 공격면 축소)</li>
</ul>
<p><span style="color: red"><strong>단점</strong></span></p>
<ul>
<li>성능 저하가 있음</li>
<li>일부 시스템 호출이 제한되어 호환성 문제가 발생할 수 있음</li>
</ul>
<h2 id="🧱-kata-containers">🧱 Kata Containers</h2>
<p>하드웨어 가상화 기술을 기반으로 한 경량 컨테이너 런타임. 컨테이너를 실제 가상 머신처럼 격리해서 실행하며 보안성을 극대화하며 금융, 공공기관, 보안 업계 등 고신뢰 환경에서 사용한다.</p>
<p><span style="color: blue"><strong>장점</strong></span></p>
<ul>
<li>가상 머신 수준의 높은 격리와 보안성</li>
<li>컨테이너 기술과 가상화 기술의 장점을 결합</li>
</ul>
<p><span style="color: red"><strong>단점</strong></span></p>
<ul>
<li>VM을 띄우기 때문에 리소스 소모가 큼</li>
<li>컨테이너보다 느린 부팅 속도</li>
</ul>
<p><span style="color: grey"><strong>📝 Container vs VM(가상머신)</strong></span></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>컨테이너</th>
<th>VM(가상머신)</th>
</tr>
</thead>
<tbody><tr>
<td>커널공유</td>
<td>Host OS 커널 공유</td>
<td>독립된 커널 사용</td>
</tr>
<tr>
<td>속도</td>
<td>빠름 (초 단위로 기동)</td>
<td>느림 (수십 초~분 소요)</td>
</tr>
<tr>
<td>리소스 사용</td>
<td>적음</td>
<td>많음</td>
</tr>
<tr>
<td>격리성</td>
<td>중간(보안 위험 존재)</td>
<td>완전 분리</td>
</tr>
<tr>
<td>실행단위</td>
<td>프로세스 수준</td>
<td>운영체제 전체</td>
</tr>
</tbody></table>
<h2 id="⚙️-runc">⚙️ runC</h2>
<p>OCI(Open Container Initiative) 표준을 구현한 저수준 런타임. containerd나 CRI-O 같은 상위 런타임이 내부적으로 사용하는 핵심 실행 엔진이다.</p>
<p><span style="color: blue"><strong>장점</strong></span></p>
<ul>
<li>매우 경량</li>
<li>표준을 정확히 준수</li>
</ul>
<p><span style="color: red"><strong>단점</strong></span></p>
<ul>
<li>저수준 런타임이기 때문에 단독 사용 시 불편하며, 보통 다른 런타임과 함께 사용됨</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] 01. Kubernetes Architecture]]></title>
            <link>https://velog.io/@duck-ach/Kubernetes-01.-Cluster-Architecture</link>
            <guid>https://velog.io/@duck-ach/Kubernetes-01.-Cluster-Architecture</guid>
            <pubDate>Wed, 21 May 2025 15:03:45 GMT</pubDate>
            <description><![CDATA[<h1 id="🚀-kubernetes-란">🚀 Kubernetes 란?</h1>
<p>Kubernetes는 <strong>컨테이너 관리 자동화(Orchestration) 시스템</strong>이다.
쉽게말해 컨테이너들을 똑똑하게 띄워주고, 필요하면 옮기고, 고장나면 다시 띄우고, Load Balancing도 수행해준다. 이 모든것을 자동으로 해주는 시스템이다.</p>
<h1 id="🚀-kubernetes-유형">🚀 Kubernetes 유형</h1>
<h2 id="☸️-관리형-쿠버네티스managed-kubernetes">☸️ 관리형 쿠버네티스(Managed Kubernetes)</h2>
<p><span style="background-color: pink;"><strong>대표 예시</strong></span></p>
<ul>
<li>AWS EKS</li>
<li>Azure AKS</li>
<li>Google GKE</li>
</ul>
<p><span style="background-color: pink;"><strong>특징</strong></span></p>
<ul>
<li>클러스터의 Control Plane 및 인프라 자원을 관리해주며, 사용자는 Application만 배포/운영</li>
<li>유지보수, 업그레이드, 고가용성 등을 클라우드에서 자동으로 제공</li>
<li>비용을 줄이고 빠르게 시작하고 싶을 때 사용</li>
</ul>
<p><span style="background-color: pink;"><strong>장점</strong></span></p>
<ul>
<li>빠른 구축, 높은 안정성</li>
<li>인프라 운영 부담 최소화</li>
</ul>
<p><span style="background-color: pink;"><strong>단점</strong></span></p>
<ul>
<li>세세한 커스터마이징 제한</li>
<li>클라우드 종속성</li>
<li>사용된 만큼 비용 발생</li>
</ul>
<h2 id="☸️-설치형-쿠버네티스packaged-kubernetes-platform">☸️ 설치형 쿠버네티스(Packaged Kubernetes Platform)</h2>
<p><span style="background-color: pink;"><strong>대표 예시</strong></span></p>
<ul>
<li>Red Hat OpenShift</li>
<li>Rancher</li>
<li>VMware Tanzu</li>
</ul>
<p><span style="background-color: pink;"><strong>특징</strong></span></p>
<ul>
<li>쿠버네티스에 다양한 운영/보안/관리 기능이 포함된 일체형 플랫폼</li>
<li>GUI, 멀티 클러스터 관리, CI/CD, 인증 연동 등 기능을 패키지 형태로 제공</li>
<li>기업용 기능과 지원이 필요한 경우 사용</li>
</ul>
<p><span style="background-color: pink;"><strong>장점</strong></span></p>
<ul>
<li>엔터프라이즈 환경에 적합한 기능 제공</li>
<li>운영 자동화 및 보안 기능 강화</li>
</ul>
<p><span style="background-color: pink;"><strong>단점</strong></span></p>
<ul>
<li>상대적으로 복잡한 구조</li>
<li>초기비용이 크고 유지비도 높은 편</li>
</ul>
<h2 id="☸️-구성형-쿠버네티스diy-kubernetes--provisioning-tool-based">☸️ 구성형 쿠버네티스(DIY Kubernetes / Provisioning Tool-based)</h2>
<p><span style="background-color: pink;"><strong>대표 예시</strong></span></p>
<ul>
<li>kubeadm</li>
<li>kops</li>
<li>Kubespray</li>
</ul>
<p><span style="background-color: pink;"><strong>특징</strong></span></p>
<ul>
<li>사용자가 직접 구성 요소를 선택하고 설정하여 쿠버네티스 클러스터를 구축한다.</li>
<li>자유도와 유연성이 높음</li>
<li>베어메탈 서버, 온프레미스 환경, 커스텀 클라우드 환경 등에 적합
<span style="color: lightgrey;">베어메탈 서버 : 물리 서버를 가상화 없이 통째로 하나의 사용자(또는 조직)가 단독으로 사용하는 서버</span></li>
<li>오픈소스 기반이라 소프트웨어 자체는 무료지만, 설정/모니터링/업그레이드까지 모두 직접해야하므로 DevOps나 인프라 전문가가 필요하다.</li>
</ul>
<p><span style="background-color: pink;"><strong>장점</strong></span></p>
<ul>
<li>학습 및 교육용으로 매우 유용</li>
<li>세밀한 설정 가능 (예: 네트워크 플러그인, 저장소 구성 등)</li>
</ul>
<p><span style="background-color: pink;"><strong>단점</strong></span></p>
<ul>
<li>설치 및 유지보수에 대한 운영 부담이 큼</li>
<li>관리 자동화 기능 부족</li>
</ul>
<h1 id="🚀-kubernetes-아키텍처">🚀 Kubernetes 아키텍처</h1>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/065b79d7-7316-40bc-882b-f69b1f2cd41f/image.svg" alt="">
이미지 출처 : Linux Foundation - 쿠버네티스 아키텍처 소개</p>
<h1 id="🚀-아키텍처-구성요소">🚀 아키텍처 구성요소</h1>
<h2 id="🧠-control-plane">🧠 Control Plane</h2>
<p>Kubernetes의 두뇌역할을 담당하고있으며 클러스터의 신경 중추라고도 표현한다. 여기에는 클러스터를 제어하는 쿠버네티스 구성 요소와 상태 및 구성에 관한 데이터가 함께 있다.
Manage, Plan, Schedule, Monitor Nodes 등을 수행한다.</p>
<h3 id="kube-api">kube-api</h3>
<p>클러스터 내에서 모든 작업은 여기를 통해 들어오고 나간다.
Control Plane 에서 Worker Node에게 지시를 할 때도 kube-api를 지난다.</p>
<h3 id="kube-scheduler">kube-scheduler</h3>
<p>지금 사용 가능한 컨테이너와 기타 조건들을 기준으로 스케줄을 조정해주는 모듈
CPU, Memory상황등을 보고 이 Pod는 Node2에 올리자 등 이런식으로 판단해준다.</p>
<p>더 자세히 말하자면, Kubernetes는 두 단계를 거쳐 Pod에 가장 적합한 Node를 찾아준다.</p>
<ul>
<li><code>Fileter Nodes</code> :  CPU, Memory 요구사항이 맞지 않는 Node를 걸러낸다.</li>
<li><code>Rank Nodes</code> : 우선순위 함수를 이용하여 Node마다 점수를 매겨, 가장 효율적이고 리소스 누수가 없는 적합한 Node를 찾는다.</li>
</ul>
<h3 id="kube-controller-manager">kube-controller-manager</h3>
<p>kubernetes에서 controller는 시스템 내 다양한 구성 요소의 상태를 지속적으로 모니터링하고 시스템 전체를 원하는 상태로 만드는 것을 의미한다.
controller는 scheduler를 참고하여 정확한 수의 Pod가 실행되도록 컨트롤 한다.
Pod가 3개 있어야 함 -&gt; 하나 죽으면 새로 만들어줌</p>
<p><span style="color: lightgrey">Kuber-Controller-Manager 안에는 
  Node Controller, Job Controller, Deployment Controller, Namespace Controller, Endpoint Controller, PV-Protection Controller, Replication Controller, Replicaset Controller, Stateful-Set Controller, CronJob Controller 등 다양한 Controller가 포함되어 있다.</span></p>
<h3 id="etcd-cluster">ETCD Cluster</h3>
<p>어떤 배에 어떤 컨테이너가 들어있고, 몇시에 적재되었는지 등 쿠버네티스의 모든 설정, 상태, 위치 정보 저장 정보를 담고있는 고가용 키-값 데이터베이스.
모든 정보를 담고 있기 때문에 클러스터의 &quot;진짜 뇌&quot; 라고도 부른다.</p>
<h2 id="⛴️-compute-machinesworker-node">⛴️ Compute machines(Worker Node)</h2>
<p>Compute Machine == Worker Node == 일꾼 노드
쿠버네티스에서 실제로 컨테이너(Pod)가 돌아가는 서버를 말한다.
일반적으로 하나의 클러스터에는 여러개의 Worker Node가 있고, 각각이 Container를 실행한다.</p>
<h3 id="kubelet">kubelet</h3>
<p>클러스터의 각 노드에서 실행되는 에이전트.
컨테이너를 띄우고 Control Plane(kube-apiserver)랑 소통하여 컨테이너를 실행/모니터링 등
관리한다.
kube-api(선장)의 지시를 듣고 kubelet(선원)이 필요한 대로 노드에서 컨테이너를 배포하거나 파괴함
kube-api(선장)은 주기적으로 kubelet(선원)에게 상태보고서를 받음(컨테이너 상태 모니터)
<span style="color: lightgrey">kubeadm으로 설치 시 kubelet을 수동으로 따로 설치해주어야 한다.</span></p>
<h3 id="kube-proxy">kube-proxy</h3>
<p>네트워크 관리자 역할을 수행한다.
외부 트래픽이 올 때 적절한 Pod로 라우팅 하는 역할을 수행한다.
클러스터 내 작업자 노드에 필요한 네트워크 규칙을 설정하고 시행되도록 한다.</p>
<h3 id="container-runtime">Container runtime</h3>
<p>컨테이너를 실제로 실행하는 컨테이너 Runtime을 의미한다.
docker, containerD, rkt 등 종류가 있다.</p>
<h3 id="pod">Pod</h3>
<p>컨테이너를 담는 상자이다.
하나 이상의 컨테이너를 담는 최소 실행 단위를 뜻하는데, 보통 Pod라고 하면 하나의 컨테이너 + 부가적인 설정 정도로 생각하면 된다고 한다.</p>
<h3 id="containers">Containers</h3>
<p>nginx, mysql, node.js 등 애플리케이션이 실제로 실행되는 단위이다.</p>
<h2 id="💾-persistant-storage">💾 Persistant storage</h2>
<p>데이터를 유지하는 저장소이다.
Pod가 죽거나 이동해도 데이터가 남아 있어야 할 때 사용한다.
종류에는 AWS EBS, NFS, GCP Persistent Disk 등이 있다.</p>
<h2 id="🏠-container-registry">🏠 Container registry</h2>
<p>이미지를 저장하고 가져오는 창고이다.
DockerHub, Google Container Registry(GCR), private registry 등의 종류가 있다.
<code>kubectl apply</code> 명령어를 요청하면 여기서 이미지를  받아와서 컨테이너로 실행한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Flutter ] 01. Splash 화면(앱 실행화면) 구현하기]]></title>
            <link>https://velog.io/@duck-ach/Flutter-01.-Splash%ED%99%94%EB%A9%B4%EC%95%B1-%EC%8B%A4%ED%96%89%ED%99%94%EB%A9%B4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@duck-ach/Flutter-01.-Splash%ED%99%94%EB%A9%B4%EC%95%B1-%EC%8B%A4%ED%96%89%ED%99%94%EB%A9%B4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 10 Feb 2025 11:55:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/866c8062-92c3-4fde-ba20-6db08fc7f75c/image.png" alt=""></p>
<h2 id="개요">개요</h2>
<p>휴대폰에서 앱을 실행하면 보통 로고나 앱 이름이 박힌 로딩화면을 거쳐 메인화면으로 앱이 켜질 것이다.
이 로딩화면을 Splash 화면이라고 명칭한다.</p>
<p>오늘은 이 Splash 화면을 구현해볼 것이다.</p>
<p><a href="https://pub.dev/packages/flutter_native_splash">참고 문서 - Flutter Splash</a></p>
<h2 id="1-패키지-dependency-설정">1. 패키지 dependency 설정</h2>
<p>먼저, 프로젝트의 <code>pubspec.yaml</code> 파일에 dependency를 추가한다.</p>
<pre><code class="language-yaml">dependencies:
  flutter_native_splash: ^2.4.4</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/3bc66c16-744a-4725-a42a-b7c52e5455c8/image.png" alt=""></p>
<p>그리고 <code>pubspec.yaml</code> 경로로 이동하여 <code>flutter pub get</code> 명령어를 실행시켜 dependency를 적용시켜 준다. </p>
<pre><code class="language-bash">$ flutter pub get</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/be71c0e6-ce1a-4c4e-b2e8-12e60b99d150/image.png" alt=""></p>
<h2 id="2-splash-설정">2. splash 설정</h2>
<p><code>pubspec.yaml</code> 파일에 바로 적용을 할 수 있는 방법도 있고, <code>flutter_native_splash.yaml</code> 파일을 루트 디렉토리에 생성하여 관리를 하는 방법도 있다.</p>
<p>나는 <code>flutter_native_splash.yaml</code> 파일을 생성하여 따로 적용해보도록 하겠다.</p>
<p>우선 루트 디렉토리에 <code>flutter_native_splash.yaml</code> 이름을 가진 파일을 하나 생성해준다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/667a8bc6-be06-4bf1-8502-94a1312f2aaf/image.png" alt=""></p>
<p><a href="https://pub.dev/packages/flutter_native_splash">참고 문서 - Flutter Splash</a> 에 나오는 설정 yaml 파일을 복사해서 붙여넣는다.</p>
<p>복사해서 붙여넣으면 아래와같이 많은 옵션들이 적힌 파일을 가져올 수 있다.
<img src="https://velog.velcdn.com/images/duck-ach/post/21063512-848c-426e-8c22-8ca2594c631b/image.png" alt=""></p>
<p>나는 로고 이미지와 배경색상(background-color)를 변경할 것이다.</p>
<p>우선 사용할 로고 이미지를 assets 경로에 넣어준다. 
<code>flutter_native_splash.yaml</code> 자체에서 로고의 크기를 조절하는 기능은 아직 지원하지 않는다고 해서 크기별로 로고 이미지를 추출하여 저장해주었다.
<img src="https://velog.velcdn.com/images/duck-ach/post/fa61f12b-39e9-4970-a733-b4fa1b037a5a/image.png" alt=""></p>
<p>아래와 같이 설정 정보를 저장해준다.</p>
<pre><code class="language-yaml">flutter_native_splash:
  color: &quot;#31363F&quot;
  image: assets/logo/YEONTA_logo_80.png
  fullscreen: true</code></pre>
<h2 id="3-splash-실행">3. splash 실행</h2>
<p>이제 설정한 splash를 실행시켜 볼것이다.</p>
<pre><code class="language-bash">$ dart run flutter_native_splash:create</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/559532db-cbe0-45db-b6a6-c607cbaa43b4/image.png" alt=""></p>
<p>그리고 나서, 프로젝트를 실행해 보면 splash를 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/c333c949-33d0-4a20-8bcd-d8dd51eddba8/image.gif" alt=""></p>
<p>지금은 데모앱으로 실행중이라 조금 빠르게 지나가지만, 분명히 splash가 정상적으로 적용된 것을 확인할 수 있다.</p>
<blockquote>
<p>splash는 flutter가 실행되는동안 유지된다고 한다.</p>
</blockquote>
<p>이상 splash를 알아보았다! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Dart ] Class 문법]]></title>
            <link>https://velog.io/@duck-ach/Dart-Class-%EB%AC%B8%EB%B2%95</link>
            <guid>https://velog.io/@duck-ach/Dart-Class-%EB%AC%B8%EB%B2%95</guid>
            <pubDate>Sun, 27 Oct 2024 14:41:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/c223acd1-bcd1-4605-99a5-1d9021200807/image.png" alt=""></p>
<h1 id="class">Class</h1>
<h2 id="class란">Class란?</h2>
<p>Dart는 객체지향 언어이다. 모든 객체는 Class의 Instance이다.</p>
<p>Class는 Member를 가진다. Member 안에는 Member Method(멤버 함수)과 Member Variable(멤버 변수, 인스턴스 변수)를 가진다. </p>
<blockquote>
<p>*<em>🔥 참고 : *</em></p>
</blockquote>
<ul>
<li>클래스 밖에서 하나의 기능을 하는 것은 <code>Function</code> 이라고 하고, 클래스 내부에 있는 함수는 <code>Method</code> 라고 한다. </li>
<li>멤버 변수는 객체가 생성되면 <code>인스턴스 변수</code>라고한다.</li>
</ul>
<h2 id="class의-기본형태">Class의 기본형태</h2>
<pre><code class="language-dart">class 클래스명 {
    멤버변수
    멤버함수
}</code></pre>
<p><strong>예시</strong></p>
<pre><code class="language-dart">class Player {
    // 멤버변수
      final String name; // final은 수정할 수 없음
      int xp, age;
      String team;

    // 멤버함수
    void sayHello() {
        print(&quot;hi my name is $name $age $team $xp&quot;); 
      }
}</code></pre>
<h2 id="생성자">생성자</h2>
<p>Class 객체를 생성하여 인스턴스를 생성할 때 사용하는 것이다.
선언된 멤버변수에 어떻게 값을 대입할지에 대해 선언하는 함수이다.
생성자 Method의 이름은 Class의 이름과 같아야한다.</p>
<h3 id="basic-constructor">basic Constructor</h3>
<p>기본적인 Dart의 생성자를 살펴보자.</p>
<pre><code class="language-dart">class Player {
    // 멤버변수
      late String name; // final은 수정할 수 없음
      late xp,;

    // 생성자(Constructor)
      Player(String name, int xp) {
        this.name = name;
        this.xp = xp;
      };
}

void main () {
    print player = Player(&quot;hee&quot;, 15000);
}</code></pre>
<ul>
<li><code>late</code> 선언을 통해 객체가 생성되면서 값을 할당 할 수 있도록 한다.</li>
<li>객체를 생성할 때 C++ 이나 Java의 경우 <code>new</code>를 통해 생성하지만, dart는 그냥 해당 클래스를 호출하면서 생성자에 값을 담아주면 된다.</li>
</ul>
<h3 id="positioning-constructor">Positioning Constructor</h3>
<p>Positioning Constructor는 생성자를 호출할 때 선언해 놓은 순서대로 변수를 할당하는 생성자이다.</p>
<pre><code class="language-dart">class Player {
      final String name; // final은 수정할 수 없음
      int xp, age;
      String team;

      // positioning parameter
     Player.createRedPlayer(String name, int age) : 
        this.age = age,
        this.name = name,
        this.team = &#39;grey&#39;,
        this.xp = 0;
}

void main () {
    print player = Player(&quot;hee&quot;, 26);
}</code></pre>
<h3 id="named-constructor-parameters">Named Constructor Parameters</h3>
<p>생성자를 key값인 변수 이름을 가지고 해당 멤버변수에 값을 대입하는 방법이다.
멤버 변수가 많아질 경우 생성자에 값을 하나 하나 담아서 호출해야 하므로 dart 언어는 named constructor를 지원한다.</p>
<pre><code class="language-dart">class Player {
      final String name; // final은 수정할 수 없음
      int xp, age;
      String team;

      // named parameter
      Player.createBluePlayer({
        required String name, 
        required int age,
      }) :  this.age = age,
        this.name = name,
        this.xp = 0,
        this.team = &#39;grey&#39;;
}

void main () {
    print player = Player(&quot;hee&quot;, 26);
}</code></pre>
<p><code>required</code> 를 사용하여 값이 비어있다면 호출하지 못하도록 막고, 필수값이 아니어도 되는 변수의 경우 <code>:</code>(콜론)을 사용하여 값을 할당받지 못했을 때 default 값을 설정할 수 있다.</p>
<h2 id="apijson-읽어와서-class에-할당하기">API(Json) 읽어와서 Class에 할당하기</h2>
<p>Map타입의 생성자를 만들어서 Json 파일에 담긴 데이터를 멤버변수에 담으면 된다.</p>
<pre><code class="language-dart">class Player {
  final String name; // final은 수정할 수 없음
  int xp, age;
  String team;

  Player.formJson(Map&lt;String, dynamic&gt; playerJson)
    : name = playerJson[&#39;name&#39;],
      age = playerJson[&#39;age&#39;],
      xp = playerJson[&#39;xp&#39;],
      team = playerJson[&#39;team&#39;];

  void sayHello() {
    print(&quot;hi my name is $name $age $team $xp&quot;); // $name = ${this.name}
  }
}

main() {

  /* API */
  var apiData = [
    {
      &#39;name&#39; : &#39;heera&#39;,
      &#39;team&#39; : &#39;red&#39;,
      &#39;xp&#39; : 12000,
      &#39;age&#39; : 15,
    },
    {
      &#39;name&#39; : &#39;amily&#39;,
      &#39;team&#39; : &#39;red&#39;,
      &#39;xp&#39; : 15000,
      &#39;age&#39; : 20,
    },
    {
      &#39;name&#39; : &#39;juno&#39;,
      &#39;team&#39; : &#39;red&#39;,
      &#39;xp&#39; : 16000,
      &#39;age&#39; : 18,
    },
  ];

  apiData.forEach((playerJson){
    var player = Player.formJson(playerJson);
    player.sayHello();
  });
}</code></pre>
<h2 id="cascade-notation객체-생성-문법">Cascade Notation(객체 생성 문법)</h2>
<p>객체를 생성할 때 basic 하게는 아래와 같은 방식으로 호출한다.</p>
<pre><code class="language-dart">var duck = Member(name: &#39;duck&#39;, age: 26);</code></pre>
<p>하지만 <code>Cascade Notation[..]</code>을 사용하면 하나의 오브젝트에 함수호출, 필드접근을 순차적으로 할 수 있다. 자바의 build()와 유사하다.</p>
<pre><code class="language-dart">var duck = Member
  ..name = &#39;duck_ach&#39;
  ..age = 26
  ..phoneNum = &#39;010-0000-0000&#39;
  ..getUserInfo();</code></pre>
<h2 id="abstract-class추상-클래스">Abstract Class(추상 클래스)</h2>
<p>Abstract Class(추상클래스)는 이를 상속받는 모든 클래스가 가지고 있어야 할 메소드를 정의한 클래스이다. (구현 X)</p>
<pre><code class="language-dart">abstract class Human {
  void walk();
}

class Duck extends Human {
  @override
  void walk() {
    print(&#39;Duck is walk&#39;);
  }
}</code></pre>
<h2 id="inheritance상속">Inheritance(상속)</h2>
<p>상속은 <code>super</code> 라는 선언을 통해 부모클래스의 생성자를 호출할 수 있다.</p>
<p>Human 클래스를 부모클래스로 선언하고, Player 클래스에서 extends를 사용하여 상속받는 코드이다.</p>
<p>필요한 개별부분들은 자식클래스에서 선언하고, 공통적인 요소나 메소드는 부모클래스에서 상속받아 코드를 좀 더 통일성 있게 짤 수 있다.</p>
<pre><code class="language-dart">class Human {
  final String name;
  Human(this.name);
  void sayHello() {
    print(&#39;Hi. my name is $name&#39;);
  }
}

enum Team {blue, red}

class Player extends Human {
  final Team team;
  Player({
    required this.team,
    required String name
  }) : super(name);

  // 부모클래스가 정의한 메소드말고 커스텀하고싶을 때
  @override
  void sayHello() {
    //print(&#39;Hi. my name is $name 1111&#39;);
    super.sayHello();
    print(&#39;and I play for ${team}&#39;);
  }

}
void main() {
  var player = Player(
    team: Team.red,
    name : &#39;heera&#39;,
    );
    player.sayHello();
}</code></pre>
<h2 id="mixin생성자가-없는-클래스">Mixin(생성자가 없는 클래스)</h2>
<p>Mixin는 생성자가 없는 클래스를 의미한다.
클래스에 필터나 메소드를 뺏어올 때 사용한다.</p>
<p>Inheritance(상속) 이랑은 개념이 다른게, 상속은 <code>super</code> 를 통해 접근해서 부모가 가진것을 자식이 물려받아 사용한다는 개념이지만, mixin은 그저 <code>with</code> 으로 class를 생성할 때 선언을 함께 해주며, 그 안에 있는 필드값이나 메소드를 공유하며 사용한다.</p>
<p>생성자가 없는 것이 가장 큰 특징이다.</p>
<p>mixin을 생성할 땐 <code>mixin</code> 을 사용한다. (원래는 class 의 한 형태여서 class를 사용하여 만들었지만 최근에 업데이트가 된 모양이다.)</p>
<pre><code class="language-dart">// mixin타입으로 선언해줌 필드 가지고 있고, 생성자 안가지고 있음
mixin Strong {
  final double strengthLevel = 1500.99;
}

// 클래스고, 메소드 가지고 있고, 생성자 안가지고 있음
mixin PlayBaseball {
  void runQuick() {
    print(&quot;homer!!&quot;);
  }
}

mixin Tall {
  final double height = 162.49;
}
enum Team {blue, red}

class Player with Strong, PlayBaseball, Tall {
  final Team team;
  Player({
    required this.team,
    required String name
  });
}
void main() {
  var player = Player(
    team: Team.red,
    name : &#39;heera&#39;,
    );

  player.runQuick();

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Dart ] 07. typedef]]></title>
            <link>https://velog.io/@duck-ach/Dart-07.-typedef</link>
            <guid>https://velog.io/@duck-ach/Dart-07.-typedef</guid>
            <pubDate>Mon, 21 Oct 2024 13:38:03 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/22fc0e11-efa1-4189-89db-ee05f8e089f8/image.png" alt=""></p>
<h1 id="typedef">typedef</h1>
<p>Dart에서는 함수의 타입을 <code>typedef</code>를 이용하여 정의할 수 있다.
특히 Map, List, Set 등의 자료구조로 함수의 값을 반환할 때도 미리 함수의 타입을 정의하여 변수로 정의해두고 사용할 수 있다.</p>
<p>typedef를 사용했을 때 장점으로는</p>
<ul>
<li>가독성이 높아진다.</li>
<li>복잡한 함수가 많을 경우 코드가 간결해진다.</li>
</ul>
<p>문법</p>
<pre><code class="language-dart">typedef 변수 = 함수타입;</code></pre>
<p><strong>예시</strong></p>
<pre><code class="language-dart">// list를 ListOfInts 라는 변수에 담기
typedef ListOfInts = List&lt;int&gt;;

// ListOfInts 반환
ListOfInts reverseListOfNumbers(ListOfInts list) {
  var reversed = list.reversed;
  return reversed.toList();
}</code></pre>
<hr>
<p>복잡한 함수를 사용해야하는 경우는 위와 같이 typedef를 잘 활용하면 좋지만, dart는 강력한 제네릭 기능을 갖추고있어 타입을 명시적으로 선언하지 않아도 되기 때문에 상황에 따라 고려하여 선언하여 사용하는 것이 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Dart ] 06. 함수와 파라미터(feat. QQ Operator)]]></title>
            <link>https://velog.io/@duck-ach/Dart-06.-%ED%95%A8%EC%88%98%EC%99%80-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0</link>
            <guid>https://velog.io/@duck-ach/Dart-06.-%ED%95%A8%EC%88%98%EC%99%80-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0</guid>
            <pubDate>Thu, 17 Oct 2024 12:04:13 GMT</pubDate>
            <description><![CDATA[<h1 id="함수function">함수(function)</h1>
<p>Dart 언어의 함수는 만드는 방법이 간단하다.</p>
<pre><code>반환타입 메소드이름(파라미터) {
    동작
}</code></pre><p>위와 같은 형식으로 만들어주면 된다.</p>
<pre><code class="language-dart">bool haveMoney(int money) {
    return money != null;
}

// void == 반환 값 X
void sayHello(String name) {
      print(&quot;Hello $name nice to meet you!&quot;);
}</code></pre>
<p>또는 짧고 간단한 함수의 경우 Syntax Return 을 사용하여 간단하게 <code>=&gt;</code> 를 활용하여 한줄로도 표현할 수 있다.</p>
<pre><code class="language-dart">// Syntax return 사용
String sayHelloSyntaxReturn(String name) =&gt; &quot;Hello $name nice to meet you!&quot;;

num plus(num a, num b) =&gt; a + b;</code></pre>
<h1 id="파라미터parameter">파라미터(Parameter)</h1>
<p>파라미터는 2가지 방법으로 전달할 수 있는데
하나는 position 기반으로 파라미터를 읽는 <code>position parameter</code>, 하나는 직접 name을 파라미터와 함께 지정하여 사용하는 <code>named parameter</code> 이다.
<code>named parameter</code>의 경우 json 통신이랑 매우 비슷하다고 느꼈다.</p>
<h2 id="position-parameter">position Parameter</h2>
<p>position Parameter의 경우 Java처럼 파라미터에 어떤값이 들어올지, 지정하고 호출할 때도 파라미터를 그냥 그 자리에 맞춰 호출하면 된다.</p>
<pre><code class="language-dart">void sayHello(String name, String greeting) {
  print(&quot;Hello $name $greeting&quot;);
}

int pow(int n) =&gt; n * n;

main() {
    sayHello(&#39;duck&#39;, &#39;nice to meet you!&#39;);
    pow(3);
}</code></pre>
<p>또는 특정 파라미터만 Optional 하게 지정할 수도 있다.
nullable variables 를 이용한 방법이다.</p>
<pre><code class="language-dart">String sayHello(String name, int age, [String? country = &#39;korea&#39;]) =&gt; &#39;Hello $name, you are $age years old from $country&#39;;
main() {
  print(sayHello(&#39;duck&#39;, 25));
  // Hello duck, you are 25 years old from korea

}</code></pre>
<p>또 다른 비슷한 방법으로 연산자 QQ Operator <code>??=</code> 를 활용하는 방법이 있다.</p>
<p>만약 null 값일 수도 있는 파라미터를 넣는다고 가정했을 때 아래와 같이 if문을 넣어 null인지 체크한 후 함수를 호출해야 오류가 안난다.(함수 호출을 하는 곳에서 오류)</p>
<pre><code class="language-dart">// 변경전
String capitalizeName(String? name) {
  if(name != null) {
    return name.toUpperCase();
  }
  return &#39;ANON&#39;;
}

// 변경후
String capitalizeName2(String? name) =&gt; name != null ? name.toUpperCase() : &#39;ANON&#39;;

main() {
    String? name;
      name ??= &#39;heera&#39;;
      capitalizeName2(name); // HEERA
}</code></pre>
<h2 id="named-parameter">named parameter</h2>
<p>named parameter는 key와 value를 호출할 때 명시해준다.</p>
<p>하지만 이 named parameter 방법의 경우 만약 파라미터가 3개라면 3개의 파라미터를 다 받아야하는데 누락이 될 수 있는 문제가 있다.
그래서 default 값을 명시해준다.
호출을 할 때 해당 파라미터는 전달하지 않고 호출했다면 default 값이 할당된다.</p>
<pre><code class="language-dart">String namedParameter1({String name = &#39;anon&#39;, int age = 0, String country = &#39;hello world&#39;}) {
  return &quot;Hello $name, you are $age, and you come from $country&quot;;
}
main() {
    print(namedParameter1(
        name: &#39;duck&#39;, 
        age: 25, 
      ));
    // Hello duck, you are 25, and you come from hello world

    print(namedParameter1());
    // Hello anon, you are 0, and you come from hello world

}</code></pre>
<p>만약 모든 파라미터가 꼭 들어와야한다면 <code>required</code> 선언을 해주면 된다.</p>
<pre><code class="language-dart">String namedParameter2({
    required String name, 
    required int age,
    required String country}) {
  return &quot;Hello $name, you are $age, and you come from $country&quot;;
}
main() {
    print(namedParameter2(
    name: &#39;duck&#39;, 
    age: 25, 
    country: &#39;japan&#39;, 
  ));
  // Hello duck, you are 25, and you come from japan![](https://velog.velcdn.com/images/duck-ach/post/a358600d-595e-4226-b945-caaa29080a64/image.png)

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Dart ] 05. String Interpolation]]></title>
            <link>https://velog.io/@duck-ach/Dart-String-Interpolation</link>
            <guid>https://velog.io/@duck-ach/Dart-String-Interpolation</guid>
            <pubDate>Wed, 16 Oct 2024 12:39:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/d5b87163-f67a-4b7b-a10c-202716a05428/image.png" alt="">
Dart 언어는 string interpolation 기능을 사용하여 문자열에 변수나 상수를 삽입할 수 있고, 데이터 타입에 따라 중괄호를 포함한 표현식 또한 삽입할 수 있다.</p>
<p>자바스크립트의 <code>$</code> 기호와 비슷한 역할을 한다.</p>
<hr>
<h2 id="예시">예시</h2>
<p>아래 사용 예시처럼 변수를 담아 결과를 도출할 수 있다.</p>
<pre><code class="language-dart">var name = &#39;Duck&#39;;
print(&quot;Hello, $name&quot;);
print(&quot;$name님 환영합니다.&quot;);</code></pre>
<p>여러개의 변수를 담을 수 있다.</p>
<pre><code class="language-dart">var firstName = &#39;Duck&#39;;
var lastName = &#39;Ach&#39;;
print(&quot;Hello, $firstName $lastName&quot;);
print(&quot;$firstName $lastName 님 환영합니다.&quot;);</code></pre>
<p>또는 꼭 문자열이 아니더라도 변수에 다른 값을 담아 연산하여 결과를 도출할 수도 있다.</p>
<pre><code class="language-dart">var adult = 20;
print(&#39;${adult + 5}살 입니다.&#39;);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Dart ] 03. 자료형과 자료구조]]></title>
            <link>https://velog.io/@duck-ach/Dart-03.-%EC%9E%90%EB%A3%8C%ED%98%95%EA%B3%BC-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@duck-ach/Dart-03.-%EC%9E%90%EB%A3%8C%ED%98%95%EA%B3%BC-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sun, 13 Oct 2024 11:25:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/e1518899-164d-497e-bcc4-3e04a2b8aabd/image.png" alt=""></p>
<h1 id="🦋-자료형data-type">🦋 자료형(Data Type)</h1>
<p>Dart 언어는 다음과 같은 자료형을 지원한다.</p>
<h2 id="1-numbersint-double">1. Numbers(int, double)</h2>
<p>숫자(Number)를 나타내는 자료형에는 <code>int</code>와 <code>double</code>을 지원하며, int와 double의 부모 클래스인 <code>num</code> 도 지원한다.</p>
<h3 id="int">int</h3>
<p>int 형은 <code>정수</code>를 나타낼 때 쓰이며, 사용하는 플랫폼에 따라서 정수 값은 64비트 이하로 표현된다.</p>
<ul>
<li>네이티브 플랫폼에서는 -263 ~ 263 - 1 까지 표현된다.</li>
<li>웹에서는 Javascript numbers (가수부가 없는 64-bits 부동소수점 표현) -253 to 253 - 1 사이의 수로 표현된다.</li>
</ul>
<p><strong>예시</strong></p>
<pre><code class="language-dart">var num = 2345834532;
int one = 1;
int two = 2;
int age = 25;</code></pre>
<h3 id="double">double</h3>
<p>double 형은 <code>소수</code>를 나타낼 때 쓰이며 IEEE 754 standard를 따라 64-bit (배정도) 부동 소수점 표현을 사용한다.</p>
<p>숫자가 소수점을 가지고 있다면 double 형이라고 생각하면 된다.
<strong>예시</strong></p>
<pre><code class="language-dart">var percent = 35.22222222;
double price = 25.99;
double average = 97.8;</code></pre>
<h3 id="num">num</h3>
<p>num 형은 <code>int</code>형과 <code>double</code>형의 부모 클래스로써 정수가 될 수도, 소수가 될 수도 있는 자료형태이다.</p>
<p><strong>예시</strong></p>
<pre><code class="language-dart">num x = 1; // x는 int, double 둘 다 가능합니다.
x += 2.5;</code></pre>
<h2 id="2-stringsstring">2. Strings(String)</h2>
<h3 id="string">String</h3>
<p>String 형은 <code>문자열</code>을 만들 때 사용되며 작은 따옴표, 큰 따옴표 모두 사용이 가능하다.</p>
<p><strong>예시</strong></p>
<pre><code class="language-dart">var hello = &quot;안녕하세요&quot;;
var name = &#39;duck&#39;;
String fruit = &quot;mango&quot;;</code></pre>
<h2 id="3-booleansbool">3. Booleans(bool)</h2>
<h3 id="bool">bool</h3>
<p>Dart는 Boolean 타입을 <code>bool</code> 로 명명했다.
bool은 true(참) 또는 false(거짓)을 나타낼 때 사용된다.</p>
<p><strong>예시</strong></p>
<pre><code class="language-dart">// 빈 문자열인지 확인합니다.
var fullName = &#39;&#39;;
assert(fullName.isEmpty);

// 0인지 확인합니다.
var hitPoints = 0;
assert(hitPoints &lt;= 0);

// null인지 확인합니다.
var unicorn = null;
assert(unicorn == null);

// NaN인지 확인합니다.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);</code></pre>
<h1 id="🦋-자료구조data-structure">🦋 자료구조(Data Structure)</h1>
<h2 id="1-list">1. List</h2>
<p>Java의 List와 별 다른점이 없다.
중복값을 추가할 수 있으며, 그냥 add(값) 을 하면 맨 뒤에 데이터가 추가되고, add(index)를 하면 해당 index에 값을 추가할 수도 있다.
<code>[] 대괄호</code> 로 표현한다.
var 로 선언해도 <code>[]</code> 를 통해 컴파일러가 List로 인식한다.</p>
<p><strong>예시</strong></p>
<pre><code class="language-dart">  var numbers = [1, 2, 3, 4]; // var 키워드 사용해도되고
  List&lt;int&gt; numbers2 = [
    1,
    2,
    3,
    4,
    if (true) 5,
  ]; // Type을 명시해줘도됨
  print(numbers); // [1, 2, 3, 4]
  print(numbers2); // [1, 2, 3, 4, 5]
</code></pre>
<h2 id="2-map">2. Map</h2>
<p>Map도 Java의 Map이랑 다른점이 없다.
<code>key:value</code> 의 형태이며, key값은 고유하다.
var로 선언해도 <code>key:value</code> 형태를 통해 컴파일러가 Map으로 인식한다.</p>
<p><strong>예시</strong></p>
<pre><code class="language-dart">  var player = {
    &#39;name&#39;: &#39;nico&#39;,
    &#39;xp&#39; : 19.99,
    &#39;superpower&#39; : false,
  };
  Map&lt;List&lt;int&gt;, bool&gt; compEx = {
    [1,2,3,5]: true,
  };

  print(player); // {name: nico, xp: 19.99, superpower: false}
  print(compEx); // {[1, 2, 3, 5]: true}

</code></pre>
<h2 id="3-set">3. Set</h2>
<p>Set도 Java의 Set이랑 다른점이 없다.
Set을 통해 데이터를 취합하면 <code>중복값</code>은 없어져서 중복값이 없어진 데이터 Set이 된다.
<code>{} 중괄호</code> 로 표현한다.
var로 선언해도 <code>{}</code>를 통해 컴파일러가 Set으로 인식한다.</p>
<p><strong>예시</strong></p>
<pre><code class="language-dart">var numbered = {1,2,3,4};
  numbered.add(1);
  numbered.add(4);
  numbered.add(3);
  numbered.add(1);
  numbered.add(1);
  print(numbered); // {1,2,3,4}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Dart ] 02. Null Safety]]></title>
            <link>https://velog.io/@duck-ach/Dart-02.-Null-Safety</link>
            <guid>https://velog.io/@duck-ach/Dart-02.-Null-Safety</guid>
            <pubDate>Fri, 11 Oct 2024 17:35:43 GMT</pubDate>
            <description><![CDATA[<p>Dart는 친절하게도 <code>Null Safety</code>를 지원한다고 한다.
<code>Null Safety</code>는 개발자가 null값을 참조할 수 없도록 하는 것이다.</p>
<p>기존에는 null값을 참조하면 런타임 에러가 발생할 수 있었지만, Null Safety를 통해 컴파일 전에 이러한 오류를 방지하기 위해 만들어졌다.</p>
<p>이는 사용자가 앱을 사용하는 동안 발생하는 런타임 에러를 최소화하여 더 안정적인 코드를 작성할 수 있도록 도와준다.</p>
<hr>
<h2 id="null-safety-예시">Null Safety 예시</h2>
<p>Dart에서 Null Safety가 적용된 예시를 확인해보자.</p>
<p>String타입인 변수 string의 값이 0이라면 true, 아니라면 false를 반환하는 함수이다.</p>
<pre><code class="language-dart">bool isEmpty(String string) =&gt; string.length == 0;

main() {
    isEmpty(null);
}</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/8d19f7c6-7231-4fa4-a532-4fb03362eb69/image.png" alt=""></p>
<p>함수를 호출할 때 null을 파라미터로 보냈기 때문에 벌써 null에 빨간줄이 뜨고, 실행을 하더라도 에러가 뜰 것이다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/079a6cc9-210d-4a58-b419-01d09df489d6/image.png" alt=""></p>
<hr>
<h2 id="nullable">Nullable</h2>
<p>하지만 개발을 하다보면 API 요청을 해서 불러온 데이터가 Null 일 수 도있고, Null을 사용해야 하는 경우가 분명 존재한다.
그럴 땐 Nullable이 되도록 바꿔보자.</p>
<p>변수 뒤에 <code>?</code> 를 붙여 Nullable로 변경할 수 있다.
Nullable이 된다면 변수가 Null값이면 참조하지 않고, Null이 아니라면 함수가 실행될 것이다.</p>
<pre><code class="language-dart">bool isEmpty(String? string) =&gt; string?.length == 0;

main() {
  isEmpty(null);
}</code></pre>
<p>변수 선언을 할 때 타입에도 <code>?</code>를 붙여주어야 하고, 해당 변수 뒤에도 <code>?</code>를 붙여 함수를 호출한다.</p>
<p>실행결과 - null 값을 파라미터로 보냈기 때문에 참조하지 않아 결과가 없다.
<img src="https://velog.velcdn.com/images/duck-ach/post/648d6d5c-e384-4640-b88b-43c627343313/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Dart ] 01. 변수/상수]]></title>
            <link>https://velog.io/@duck-ach/Dart-01.-%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8</link>
            <guid>https://velog.io/@duck-ach/Dart-01.-%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8</guid>
            <pubDate>Fri, 11 Oct 2024 16:48:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/46a6f880-a0f2-42c1-9e55-95519276db63/image.png" alt=""></p>
<h1 id="🦋-변수">🦋 변수</h1>
<p>Dart에서 변수를 선언할 때 3가지 방법으로 할 수 있다.</p>
<h2 id="var">var</h2>
<blockquote>
<p>주로 함수나 메소드 내에서 지역변수를 사용할 때 사용한다.</p>
</blockquote>
<p>만약 var를 사용하여 문자열을 선언했다면,</p>
<pre><code class="language-dart">var food = &#39;pasta&#39;;</code></pre>
<p>아래에서 다른 데이터 타입의 변수를 해당 변수에 넣지 못한다.</p>
<pre><code class="language-dart">var food = &#39;pasta&#39;;
food = 11; // error
food = true; // error</code></pre>
<h2 id="type">Type</h2>
<blockquote>
<p>주로 Class 내 지역변수나 properties 를 선언할 때 사용한다.</p>
</blockquote>
<p>문법이 Java랑 되게 비슷하다는 생각을 했다.
해당 문법도 String이라고 지정해주었기 때문에 다른 데이터 타입은 변수에 할당되지 않고 에러가난다.</p>
<pre><code class="language-dart">String food = &#39;pasta&#39;;
food = 11; // error
food = true; // error</code></pre>
<hr>
<p>var 를 사용하던 구체적인 타입을 선언해서 사용하던 컴파일러가 데이터 타입을 인식하는 것은 똑같다.</p>
<h2 id="dynamic">Dynamic</h2>
<blockquote>
<p>주로 데이터 타입을 알 수 없거나, flutter에서 json이랑 사용할 때 동적으로 데이터 타입 변환이 필요한 경우, 또는 데이터 타입의 확인이 필요할 경우 사용한다.</p>
</blockquote>
<p>보통은 Dynamic 변수를 선언을 많이 사용하지 않게 권장하고 있지만, 특정의 경우에는 사용할 수 있도록 Dart에서 지원하고 있다고 한다.</p>
<p>변수를 var로 선언한 후, 초기화 하지않고 그 아래에서 변수 값을 할당한다면 변수의 타입이 Dynamic으로 지정되기 때문에, 아래처럼 여러 데이터타입의 변수 선언이 다 가능하다.</p>
<pre><code class="language-dart">var food;
food = &#39;fasta&#39;;
food = 11;
food = true;</code></pre>
<p>또는 dynamic으로 선언할 수 있다.</p>
<pre><code class="language-dart">dynamic food;
food = &#39;fasta&#39;;
food = 11;
food = true;</code></pre>
<p>Dynamic 변수의 또 다른 특징은 Dynamic 변수를 선언한 후, String 값을 할당했더라도 타입을 특정하지 못하기 때문에 관련 함수가 조회되지 않는다.</p>
<pre><code class="language-dart">var food;
food = &#39;fasta&#39;;</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/2ea3d02b-bad0-4936-ab1a-2106aac818a4/image.png" alt=""></p>
<p>하지만 Dynamic 변수에 String 이라면, int 라면 등등 조건을 달아준 후 그 해당 단락 내에서는 해당 데이터 타입의 함수를 사용 할 수 있게 된다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/20d98a04-c97a-4e2a-8e8d-25e06c194fae/image.png" alt=""></p>
<h1 id="🦋-상수">🦋 상수</h1>
<p>값이 변하는 변수와 다르게 값이 변하지 않으면 안되는 상수 선언은 간단하다.</p>
<h2 id="const">const</h2>
<p>const는 compile 단계부터 값을 알고 있는 것이다.</p>
<ul>
<li>API를 통해 받아오는 값 X</li>
<li>사용자가 입력하는 값 X</li>
</ul>
<pre><code class="language-dart">const userId = &#39;duck-ach&#39;;</code></pre>
<h2 id="final">final</h2>
<p>동적인 초기화를 한 후 값이 변하지 않는 상수를 유지하고 싶다면 <code>final</code> 을 이용하여 선언해주면 된다.</p>
<pre><code class="language-dart">final userName = &#39;duck&#39;;</code></pre>
<p>만약 밑에서 값을 바꾸려고 하면 에러가 난다.</p>
<pre><code class="language-dart">final userName = &#39;duck&#39;;
userName = &#39;ach&#39;; // error</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/00f43de5-1dbb-45d3-b240-4da9d8554074/image.png" alt=""></p>
<h1 id="🦋-수식어">🦋 수식어</h1>
<h2 id="late">late</h2>
<p>late 수식어는 주로 변수나 상수 앞에 붙여준다.
먼저 변수/상수 앞에 선언해 놓고, api를 통해 나중에 값을 할당하거나 다른 작업을 한 후 값을 할당 할 경우 사용한다.</p>
<pre><code class="language-dart">late final String name;</code></pre>
<p>사용을 할때에는 값을 꼭 할당하고 사용을 할 수 있다.</p>
<pre><code class="language-dart">late final String name;
print(name); // error</code></pre>
<pre><code class="language-dart">late final String name;
name = &#39;duck&#39;;
print(name); // duck</code></pre>
<hr>
<h1 id="결론-정리">결론 정리</h1>
<ul>
<li><p>dart는 기본적으로 <code>var</code> 선언을 권장하며, class나 property 를 작성할 때는 <code>Type</code> 선언을 권장한다.</p>
</li>
<li><p>변수를 단 한번 할당한 후 값을 바꾸고 싶지 않다면 <code>final</code> 선언을 하면된다.</p>
</li>
<li><p><code>dynamic</code> 선언의 경우 가장 조심스럽게 사용하도록 권장된다. 타입에 대해 조건을 걸어주어야 그 타입에 대한 함수를 사용 할 수 있다.</p>
</li>
<li><p><code>const</code> 는 이미 값을 알고있고, 타입을 알고 있는 경우 사용한다. 컴파일단계부터 이미 값을 다 알고 실행되기 때문에 API를 통해 값을 받아오거나 사용자가 입력한 값을 할당할 수는 없다.</p>
</li>
<li><p><code>late</code> 는 변수 선언 후 나중에 값을 할당할 때 사용된다. 정의 되지 않은 late 변수는 사용할 수 없다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Dart ] 00. 프로젝트 세팅 (feat. VSCode)]]></title>
            <link>https://velog.io/@duck-ach/dart-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%B8%ED%8C%85-feat.-VSCode</link>
            <guid>https://velog.io/@duck-ach/dart-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%B8%ED%8C%85-feat.-VSCode</guid>
            <pubDate>Fri, 11 Oct 2024 16:26:52 GMT</pubDate>
            <description><![CDATA[<p>이번에 기획한 사이드 프로젝트가 사용자들이 모바일 앱 또는 웹 앱을 많이 사용할만한 프로젝트여서 iOS, Android, Web App 등 크로스 플랫폼을 지원하는 flutter를 채택하기로 했다.</p>
<p>flutter는 dart 라는 언어 기반으로 컴파일 되는데 dart가 flutter 맞춤형 언어라고 할 만큼 fit이 좋고 빠르다고 한다.
react-native도 고려했지만 여러 앱을 혼자 만드는 데 있어서 학습하는 시간과 개발 시간이 많이 들것이라고 예상했다.</p>
<p>그럼 본격적으로 dart 언어부터 배워보기 위해 프로젝트 세팅을 해보자!</p>
<h1 id="1-별다른-설치-없이-하는-방법">1. 별다른 설치 없이 하는 방법</h1>
<p>dart는 정말 친절하게도 웹 브라우저에서 개발하고 실행하고 화면을 확인할 수 있도록 지원한다. 맨날 새로운 프로젝트를 할 때마다 새로운 언어를 쓰거나 프레임워크가 바뀌면 프로젝트 세팅 하고 익히느라 한세월씩 가는데 사실 이 사이트 보고 좀 놀랐다.</p>
<p><a href="https://dartpad.dev/">dartpad.dev</a> 라는 사이트로 이동하면 된다.
<img src="https://velog.velcdn.com/images/duck-ach/post/b7f36892-0c79-416e-9b78-d2f104aa79e0/image.png" alt=""></p>
<h1 id="2-vscode-기반으로-프로젝트-세팅하는-방법">2. VSCode 기반으로 프로젝트 세팅하는 방법</h1>
<p>사실 웹 브라우저 에서 별다른 설치 없이 지원을 해주니깐 1번의 방법으로 사용해보고 싶었는데 내 2015년식 💩 Mac은 자꾸 버벅거리고 렉걸린다. 가끔 버튼도 안눌려서 VSCode로 설치해보기로 했다.</p>
<h2 id="1-우선-vscode를-다운로드-받는다">1. 우선 VSCode를 다운로드 받는다.</h2>
<h2 id="2-flutter-확장프로그램-설치">2. Flutter 확장프로그램 설치</h2>
<p>Flutter를 설치하면 dart 확장프로그램도 자동으로 다운로드 받아진다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/fc8d9ce1-a602-460a-ba1b-d62c2f523fe4/image.png" alt=""></p>
<p>확장프로그램 다운받고 나서, VSCode 재부팅 한번 해준다.</p>
<h2 id="3-프로젝트-생성">3. 프로젝트 생성</h2>
<p><code>⌘(Command)</code> + <code>⇧(Shift)</code> + <code>p</code>  단축키를 눌러 Dart: New Project 를 선택하고 만들어주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/edd57ca4-98f8-4a5d-916a-b3a90bd6dbf3/image.png" alt=""></p>
<p>Console Application을 선택해준다.
<img src="https://velog.velcdn.com/images/duck-ach/post/ef55765c-b641-4ae3-bc65-e3c9407b9d0a/image.png" alt=""></p>
<p>프로젝트 경로를 선택해주고, 이름을 입력한다.
<img src="https://velog.velcdn.com/images/duck-ach/post/a9423edb-3e0c-4582-8b73-77fdf7267952/image.png" alt=""></p>
<p>프로젝트 생성 완료
<img src="https://velog.velcdn.com/images/duck-ach/post/8a362252-9983-4c96-a1b3-0f93d6199946/image.png" alt=""></p>
<h2 id="4-실행run">4. 실행(run)</h2>
<p><code>F5</code>를 누르면 debug 모드로 실행이 가능하다.
<img src="https://velog.velcdn.com/images/duck-ach/post/d98e4c08-21b0-4099-b55e-1610e4290d38/image.png" alt="">!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Apache Struts ] Apache Struts 2 프레임워크 보안취약점 조치 전 간단 톮아보기]]></title>
            <link>https://velog.io/@duck-ach/Apache-Struts-Apache-Struts-2-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EB%B3%B4%EC%95%88%EC%B7%A8%EC%95%BD%EC%A0%90-%EA%B0%84%EB%8B%A8-%ED%86%AE%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@duck-ach/Apache-Struts-Apache-Struts-2-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EB%B3%B4%EC%95%88%EC%B7%A8%EC%95%BD%EC%A0%90-%EA%B0%84%EB%8B%A8-%ED%86%AE%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 09 Oct 2024 10:42:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/66627e22-3283-426c-be38-43d5c62b0794/image.png" alt=""></p>
<p>회사에서 다음주부터 Apache Struts2 프레임워크에서 발견된 원격코드 실행 관련 보안취약점을 해결해야해서 프로젝트 투입 전 간단히 Apache Struts 프레임워크에서 사용하는 문법과 종속 라이브러리 정보 등을 톮아보자.</p>
<p><em>※ 톮아보다 : 샅샅히 톮아 나가면서 살피다. (순우리말)</em></p>
<h1 id="apache-struts">Apache Struts</h1>
<p>Apache Struts는 Apache 사에서 만들어진 Java EE 애플리케이션을 개발하기 위한 MVC 아키텍처 기반 오픈 소스 프레임워크다.</p>
<p>Apache Struts의 특징으로는 POJO(Plain Old Java Object) 기반의 Actions를 사용하여 HTML Form 이나 다른 리소스들에 대한 요청들을 처리한다.</p>
<h1 id="보안취약점">보안취약점</h1>
<ul>
<li><p><strong>CVE-2019-0230</strong>
설명 : 사용자 입력 데이터 검증이 미흡하여 발생하는 원격코드 실행 취약점
영향 받는 버전 : Struts 2.0.0 ~ 2.5.20</p>
</li>
<li><p><strong>CVE-2020-17530</strong>
설명 : CVE-2019-0230의 보안패치를 우회한 원격코드 실행 취약점
영향 받는 버전 : Struts 2.0.0 ~ 2.5.25</p>
</li>
</ul>
<p>해결책으로는 권고사항인 Apache Struts 프레임워크를 업그레이드 하는 방안으로 진행하기로 했다.</p>
<h1 id="문법-및-용어">문법 및 용어</h1>
<h2 id="actions">Actions</h2>
<p>Struts 2 Web Application에서 하이퍼링크를 클릭하거나 HTML Form을 Submit하면 Struts 내의 자바 클래스로 전송된다. 이런 요청을 받는 클래스를 <code>Actions</code>라고 한다.
Action이 실행된 후, Result는 응답을 렌더링할 리소스(Java, html, jsp, file 등)를 선택할 수 있고, 실행 결과에 따라 <code>SUCCESS</code>, <code>ERROR</code>, <code>INPUT</code> 과 같은 result-name 을 반환할 수 있다.</p>
<h2 id="strutsxml-파일">struts.xml 파일</h2>
<p><code>struts.xml</code> 이라는 xml 파일을 통해 모든 연결관계를 확인할 수 있다.</p>
<p><strong>struts.xml</strong></p>
<pre><code>&lt;action name=&quot;index&quot; class=&quot;com.struts.MainAction&quot;&gt;
    &lt;result name=&quot;index&quot;&gt;/index.jsp&lt;/result&gt;
&lt;/action&gt;
&lt;action name=&quot;login&quot; class=&quot;com.struts.LoginAction&quot;&gt;
    &lt;result name=&quot;login&quot;&gt;/login/login.jsp&lt;/result&gt;
    &lt;result name=&quot;success&quot; type=&quot;redirect&quot;&gt;index&lt;/result&gt;
&lt;/action&gt;</code></pre><p><strong>LoginAction.java</strong></p>
<pre><code class="language-java">public class LoginAction extends ActionSupport {
    public String execute() {
        return SUCCESS;
    }
}</code></pre>
<p>사용자가 만약 <em><a href="http://www.myStruts.com/strutsproject/login">http://www.myStruts.com/strutsproject/login</a></em> 로 요청을 시도하면 Struts에 의해 액션클래스로 지정해놓은 <code>LoginAction</code> 의 execute() 메소드가 호출된다.</p>
<p>excute()의 return 값은 String 형태로 되어있으며, 실행결과에 문제가 없다면 &quot;success&quot;를 돌려주며, index 페이지로 지정된 <code>MainAction</code>을 호출하여 index.jsp로 보낸다.</p>
<p>위와 같이 각각의 동작에 해당되는 자바 클래스를 별도로 작성하고, 웹 페이지와의 관계를 xml로 지정하여 Struts 가 애플리케이션의 동작을 통제한다.</p>
<h2 id="html-jsp-에서의-데이터-렌더링">HTML, jsp 에서의 데이터 렌더링</h2>
<p>Apache Struts에서는 Java단에서 Getter/Setter를 작성한 후 이에 대한 변수 이름을 Form 필드에 지정만 해주면 HTML이나 jsp의 Form 에서 값을 넘겨줄 때나 받아올 때 추가 코드를 작성할 필요가 없이 Struts가 자동으로 이 메소드들을 호출해서 값을 받아오거나 넘겨준다.</p>
<p><strong>Users.java</strong></p>
<pre><code class="language-java">@Getter
@Setter
public class Users {
    private String id;
    private String name;
    private int age;</code></pre>
<p><strong>UsersInfoAction.java</strong></p>
<pre><code class="language-java">public class UsersInfoAction extends ActionSupport {
    private Users users;

    public String execute() {
        users = new Users() ;

        return SUCCESS;
    }

    public Users getUsers() {
        return users;
    }
}</code></pre>
<p><strong>userinfo.jsp</strong>
여기서 중요한점은 3번째 line의 Tag 라이브러리 <code>&lt;%@ taglib prefix=&quot;s&quot; uri=&quot;/struts-tags&quot; %&gt;</code> 를 지정해주는 것이다.</p>
<pre><code class="language-jsp">&lt;!DOCTYPE html&gt;
&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot; %&gt;
&lt;%@ taglib prefix=&quot;s&quot; uri=&quot;/struts-tags&quot; %&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;&gt;
    &lt;title&gt;User Info&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;h1&gt;사용자 정보&lt;/h1&gt;
      &lt;h3&gt;사용자 아이디&lt;/h3&gt;
    &lt;li&gt;&lt;s:property value=&quot;users.id&quot; /&gt;&lt;/li&gt;
    &lt;h3&gt;사용자 이름&lt;/h3&gt;
    &lt;li&gt;&lt;s:property value=&quot;users.name&quot; /&gt;&lt;/li&gt;
    &lt;h3&gt;사용자 나이&lt;/h3&gt;
    &lt;li&gt;&lt;s:property value=&quot;users.age&quot; /&gt;&lt;/li&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<h2 id="url-태그-forward-redirect">URL 태그 (Forward, Redirect)</h2>
<p>HTML은 하이퍼링크를 만드는 간단한 a 태그를 제공하지만, HTML은 종종 중복된 정보를 포함해야하거나 프레임워크에서 제공하는 동적 데이터에 쉽게 Access 하지 못한다.</p>
<p>Struts에서는 이를 해결하기 위해 Struts 2 url 태그를 제공한다.</p>
<p>사용자가 버튼 또는 링크를 클릭하면 Struts 프레임워크 클래스의 실행 메소드를 실행하고 뷰를 렌더링하는 예제를 작성해보았다.</p>
<h3 id="매개변수가-없는-url-태그">매개변수가 없는 URL 태그</h3>
<p><strong>jsp 맨 위에 taglib 지시문 추가</strong>
<code>&lt;@ taglib prefix=&quot;s&quot; uri=&quot;/struts-tags&quot;%&gt;</code></p>
<p><strong>예제용 jsp 파일 작성</strong></p>
<pre><code class="language-jsp">&lt;!DOCTYPE html&gt;
&lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot; %&gt;
&lt;%@ taglib prefix=&quot;s&quot; uri=&quot;/struts-tags&quot; %&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;&gt;
        &lt;title&gt;Basic Struts 2 Application - Welcome&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;Welcome To Struts 2 - Let&#39;s Join&lt;/h1&gt;
        &lt;p&gt;&lt;a href=&quot;&lt;s:url action=&#39;join&#39;/&gt;&quot;&gt;Sign Up&lt;/a&gt;&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><strong>struts.xml</strong></p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE struts PUBLIC
        &quot;-//Apache Software Foundation//DTD Struts Configuration 2.5//EN&quot;
        &quot;http://struts.apache.org/dtds/struts-2.5.dtd&quot;&gt;
&lt;struts&gt;
    &lt;constant name=&quot;struts.devMode&quot; value=&quot;true&quot; /&gt;

    &lt;package name=&quot;basicstruts2&quot; extends=&quot;struts-default&quot;&gt;
        &lt;action name=&quot;index&quot;&gt;
            &lt;result&gt;/index.jsp&lt;/result&gt;
        &lt;/action&gt;

        &lt;action name=&quot;join&quot; class=&quot;org.apache.struts.strutsproject.action.JoinAction&quot; method=&quot;execute&quot;&gt;
            &lt;result name=&quot;success&quot;&gt;/join.jsp&lt;/result&gt;
        &lt;/action&gt;
    &lt;/package&gt;
&lt;/struts&gt;
</code></pre><p>Struts url 태그는 <code>join</code> Action으로 URL을 생성한다. 사용자가 &quot;Sign Up&quot; 링크를 클릭하면 <code>join</code> Action은 클래스 <code>JoinAction</code>의 execute() 메소드를 호출하고, success 문자열을 반환하고, join.jsp 파일이 렌더링된다.</p>
<h3 id="매개변수가-있는-url-태그">매개변수가 있는 URL 태그</h3>
<pre><code class="language-jsp">&lt;s:url action=&quot;hello&quot; var=&quot;helloLink&quot;&gt;
  &lt;s:param name=&quot;userName&quot;&gt;Heera&lt;/s:param&gt;
&lt;/s:url&gt;

&lt;p&gt;&lt;a href=&quot;${helloLink}&quot;&gt;Click me! Heera&lt;/a&gt;&lt;/p&gt;</code></pre>
<p>url 태그를 <code>&lt;a&gt;</code> 태그의 href 속성의 값으로 넣는 대신, <code>&lt;s:url&gt;</code> 태그를 별도의 코드 블록으로 분리했고, 내부에 <code>&lt;param&gt;</code> 태그를 지정함으로써 파라미터를 지정해줄 수 있다.</p>
<hr>
<p>문법은 대충 전체적으로 JSTL이나 Thymeleaf 처럼 jsp에 taglib 들을 지정해서 사용하는 문법이 많은 것같고, 문법에 대한 내용이 너무 많아서 더 깊고 자세한 내용들은 그때그때 필요한것 위주로 문서를 찾아봐야겠다.</p>
<p>혹시나 이 글을 보고 struts를 공부하고 있는 사람이 있다면 struts 공식문서를 보고 따라해보길 바란다. 생각보다 쉽고 간단하게 설명이 되어있어 기존에 다른 프레임워크를 사용해본 사람이라면 금방 따라할 것 같다.</p>
<p><a href="https://struts.apache.org/getting-started/index.html">Struts 문서 링크 - Getting Started</a></p>
<h1 id="핵심-라이브러리-dependency">핵심 라이브러리 Dependency</h1>
<p>보통 Maven 기준으로 Apache Struts 버전을 바꿔주면 자동으로 모두 Depedency 된다고 한다.
하지만 기업내부에 배포되어 있는 소스라 알 수 없어 정보 볼 수 있는 곳 링크 걸어놓기
<a href="https://struts.apache.org/maven/struts2-core/dependencies.html">핵심 라이브러리 Dependency 정보</a></p>
<p>Struts 2 필수 라이브러리 로는</p>
<ul>
<li>commons-logging-1.0.4.jar</li>
<li>freemarker-2.3.8.jar</li>
<li>ognl-2.6.11.jar</li>
<li>struts2-core-2.0.11.jar</li>
<li>xwork-2.0.4.jar</li>
</ul>
<p>다섯가지가 있고,
버전마다 다르게 추가적으로 에러문구를 보면서 아래 라이브러리도 점검해주고 교체해주면 될 것같다.</p>
<ul>
<li>commons.beanutils.1.x.x.jar</li>
<li>commons.fileupload-1.2.x.jar</li>
<li>commons.io-1.x.x..jar</li>
<li>commons-lang3-....jar</li>
<li>javassist-3....GA.jar</li>
<li>struts.core1.....jar</li>
</ul>
<p>다른 블로그를 참고해보니 기존버전의 라이브러리와 업그레이드 할 버전의 라이브러리를 비교하여 이름이 같은것들만 버전을 덮어쓰기하여 맞춰주었더니 해결했다는 글도 있었다. 한번 시도해보면서 trace 해봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Jenkins ] Jenkins Pipeline 구문]]></title>
            <link>https://velog.io/@duck-ach/DevOps-Jenkins-Pipeline-%EA%B5%AC%EB%AC%B8</link>
            <guid>https://velog.io/@duck-ach/DevOps-Jenkins-Pipeline-%EA%B5%AC%EB%AC%B8</guid>
            <pubDate>Tue, 12 Mar 2024 06:13:08 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/e18df779-3a4b-4599-8da2-6dcbde557b88/image.png" alt=""></p>
<hr>
<h1 id="🥸-jenkins-pipeline-구문">🥸 Jenkins Pipeline 구문</h1>
<h2 id="jenkinsfile과-pipeline-구문">JenkinsFile과 Pipeline 구문</h2>
<p>Pipeline을 사용하기 위해서는 프로젝트 저장소(git, svn 등)의 <strong><code>JenkinsFile</code></strong>이 작성되어야 한다. 이 <strong><code>JenkinsFile</code></strong> 이 작성되기 위해  <strong><code>선언형 Pipeline 구문</code></strong> 과 <strong><code>스크립트형 Pipeline 구문</code></strong> 2가지를 사용할 수 있다.</p>
<p><strong><code>선언형 Pipeline 구문</code></strong>이 <strong><code>스크립트형 Pipeline 구문</code></strong>보다 최근에 더 많이 사용되는 방법 이라고 한다.</p>
<h3 id="jenkinsfile">JenkinsFile</h3>
<p>Jenkins 파이프라인은 <code>JenkinsFile</code> 이라는 텍스트 파일을 사용하여 파이프라인을 실행하는 데 필요한 단계를 정의할수있다.   </p>
<h3 id="pipeline-구문">Pipeline 구문</h3>
<ul>
<li>JenkinsFile을 정의하는 데 사용되는 파이프라인 구문은 <strong>선언적 파이프라인 구문</strong>과 <strong>스크립트 파이프라인 구문</strong> 두 가지로 나뉜다. (서로 호환 X)</li>
<li>이 두 파이프라인은 모두 Groovy DSL을 기반으로 하지만 스크립트 파이프라인은 조금 더 전통적인 파이프라인이기 때문에 조금 더 엄격한 Groovy 기반 구문을 사용한다.</li>
</ul>
<hr>
<h3 id="pipeline-주요-항목">Pipeline 주요 항목</h3>
<ul>
<li>pipeline</li>
<li>node</li>
<li>stage</li>
<li>step</li>
</ul>
<blockquote>
<p><strong>pipeline</strong></p>
</blockquote>
<ul>
<li><code>pipeline</code> 항목은 애플리케이션의 빌드, 테스트 및 배포 단계를 포함하는 전체 빌드 프로세스를 정의한다.</li>
<li><code>pipeline</code>의 블록은 선언형 파이프라인 구문의 핵심 부분이다.</li>
<li><code>JenkinsFile</code>의 시작지점에 선언해야 한다.</li>
</ul>
<pre><code class="language-javascript">pipeline { /* 내용 */ }</code></pre>
<blockquote>
<p><strong>node</strong></p>
</blockquote>
<ul>
<li><code>node</code> 항목은 스크립트형 파이프라인 구문의 핵심 부분이다.</li>
<li><code>JenkinsFile</code>의 시작 지점에 선언해야 한다.</li>
</ul>
<pre><code class="language-javascript">node { /* 내용 */ }</code></pre>
<blockquote>
<p><strong>stage</strong></p>
</blockquote>
<ul>
<li><code>stage</code> 항목은 전체 파이프라인 단계를 통해 수행되는 작업의 하위 집합을 나타낸다. (ex. jenkins를 통해 테스트 -&gt; 빌드 -&gt; 배포 -&gt; 서버 재시작을 수행해야 한다고 가정했을 때 테스트, 빌드, 배포, 서버재시작이 각각 하나의 stage가 된다.)</li>
<li><code>stages</code> 항목은 하나 이상의 <code>stage</code> 모음을 정의한다. </li>
<li>전체 파이프라인의 흐름에서 특정한 시점에 실행이 필요한 것들을 묶을 수 있다.</li>
</ul>
<pre><code class="language-javascript">stage(&#39;build-application-test&#39;) {
    steps {
        echo &#39;Build Start &quot;${TEST_ADMIN}&quot;&#39;
        sh &#39;./gradlew --gradle-user-home ${GRADLE_HOME} ${TEST_ADMIN}:clea ${TEST_ADMIN}:build -x test&#39;
        echo &#39;Build End &quot;${TEST_ADMIN}&quot;&#39;
    }
}</code></pre>
<blockquote>
<p><strong>step</strong></p>
</blockquote>
<ul>
<li><code>step</code> 항목은 <code>stage</code> 내에서 해야 할 단일 작업을 나타내며, 특정 시점에서 수행할 작업을 알려준다.</li>
</ul>
<pre><code>stage(&#39;build-application-test&#39;) {
    steps {
        echo &#39;Build Start &quot;${TEST_ADMIN}&quot;&#39;
        sh &#39;./gradlew --gradle-user-home ${GRADLE_HOME} ${TEST_ADMIN}:clea ${TEST_ADMIN}:build -x test&#39;
        echo &#39;Build End &quot;${TEST_ADMIN}&quot;&#39;
    }
}</code></pre><hr>
<h3 id="pipeline-구문-종류">Pipeline 구문 종류</h3>
<p>선언형 파이프라인 구문과 스크립트형 파이프라인 구문에 대해 알아보자</p>
<blockquote>
<p><strong>선언형 파이프라인 구문</strong></p>
</blockquote>
<ul>
<li>파이프라인 코드를 더 쉽게 읽고 쓸 수 있다.</li>
<li>이 코드는 git과 같은 소스 제어 관리 시스템에 체크인 할 수 있는 JenkinsFile로 작성된다.</li>
</ul>
<p><strong>선언형 파이프라인 구문 예시</strong></p>
<pre><code class="language-javascript">// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any // 사용 가능한 agent 에서 이 pipeline 또는 해당 stages를 수행
    stages {
        stage(&#39;Build&#39;) { // build 단계 정의
            steps {
                echo &#39;building&#39; // build 단계와 관련된 몇가지 작업 수행
            }
        }
        stage(&#39;Test&#39;) { // test 단계 정의
            steps {
                echo &#39;testing&#39; // test 단계와 관련된 몇가지 작업 수행
            }
        }
        stage(&#39;Deploy&#39;) { // deploy 단계 정의
            steps {
                echo &#39;deploying&#39; //  deploy 단계와 관련된 몇가지 작업 수행
            }
        }
    }
}</code></pre>
<blockquote>
<p><strong>스크립트형 파이프라인 구문</strong></p>
</blockquote>
<ul>
<li>선언적 파이프라인 구문보다는 전통적인 방법이다.</li>
<li>스크립트 파이프라인 구문에서 JenkinsFile은 Jenkins UI 인스턴스에 작성된다.</li>
<li>여기서 <code>stage</code> 블록은 script pipeline 구문에서 선택사항이다. 하지만 <code>stage</code> 블록을 구현하면 Jenkins UI에서 각 작업/단계의 하위 집합을 더 명확하게 시각화 할 수 있다.</li>
</ul>
<p><strong>스크립트형 파이프라인 구문 예시</strong></p>
<pre><code class="language-javascript">// Jenkinsfile (Scripted Pipeline)
node {  // 사용 가능한 agent 에서 이 pipeline 또는 해당 stages를 수행
    stage(&#39;Build&#39;) { // build 단계를 정의
        echo &#39;building&#39;// build 단계와 관련된 몇가지 작업 수행
    }
    stage(&#39;Test&#39;) { // test 단계를 정의
        echo &#39;testing&#39;// test 단계와 관련된 몇가지 작업 수행
    }
    stage(&#39;Deploy&#39;) { // deploy 단계를 정의
        echo &#39;deploying&#39;// deploy 단계와 관련된 몇가지 작업 수행 
    }
}</code></pre>
<hr>
<h1 id="🥸-선언형-파이프라인declarative-pipeline-구문-문법">🥸 선언형 파이프라인(Declarative Pipeline) 구문 문법</h1>
<p>선언형 파이프라인을 이용해서 <code>JenkinsFile</code>을 작성하기 위한 문법에 대해 알아보자.</p>
<p>선언형 파이프라인 구문 문법은 <code>Groovy</code> 구문과 동일한 규칙을 따르지만 몇 가지 예외 사항이 있다.</p>
<ul>
<li>시작은 <code>pipeline {   }</code> 으로 해야한다.</li>
<li>명령문 구분 기호로 세미콜론<code>;</code> 이 없다.</li>
<li>블록은 <code>sections</code>, <code>directives</code>, <code>steps</code> 등 할당문으로 구성되어야 한다.</li>
<li>속성 참조문은 인자값이 없다. <code>input()</code> </li>
</ul>
<p>각 옵션에 대한 자세한 정보는 아래 문서를 참고하자
<a href="https://www.jenkins.io/doc/book/pipeline/syntax/">jenkins - pipeline syntax</a></p>
<h2 id="sections">sections</h2>
<ul>
<li><code>sections</code>는 <code>directives</code> 와 <code>steps</code> 를 1개 이상 포함하는 코드로 구성되어 있다.</li>
<li>sections에 포함된 항목 : <code>agent</code>, <code>post</code>, <code>stages</code>, <code>steps</code></li>
</ul>
<h3 id="agent">agent</h3>
<ul>
<li><code>pipeline</code> 또는 <code>stage</code> 에서 조건부로 사용할 수 있는 하나의 구간이다.</li>
<li>옵션에는 <code>any</code>, <code>none</code>, <code>label</code>, <code>docker</code>, <code>kubernetes</code> 가 있다.</li>
</ul>
<h3 id="post">post</h3>
<ul>
<li><code>pipeline</code> 또는 <code>stage</code> 에서 조건부로 사용할 수 있는 하나의 구간이다.</li>
<li><strong>Job의 빌드 이후 처리 동작</strong>에 대해 상세 설정을 할 수 있다.</li>
<li>옵션에는 <code>always</code>, <code>changed</code>, <code>fixed</code>, <code>regression</code>, <code>aborted</code>, <code>failure</code>, <code>success</code>, <code>unstable</code>, <code>unsuccessful</code>, <code>cleanup</code>가 있다.</li>
</ul>
<h3 id="stages">stages</h3>
<ul>
<li><code>pipeline</code> 또는 <code>stage</code> 에서 조건부로 사용할 수 있는 하나의 구간이다.</li>
<li>하나 이상의 <code>stage</code>를 포함한다.</li>
<li><code>stage</code> 안에는 여러개의 <code>step</code>이 포함될 수 있다.</li>
</ul>
<h3 id="steps">steps</h3>
<ul>
<li><code>pipeline</code> 또는 <code>stage</code>에서 조건부로 사용할 수 있는 하나의 구간이다.</li>
<li>지정된 <code>stage</code> 에서 하나 이상의 <code>step</code>을 정의한다.</li>
<li>단일 작업을 나타낸다.</li>
</ul>
<p><strong>선언형 파이프라인 예시 1</strong></p>
<pre><code class="language-javascript">pipeline {
    agent any
    stages {
        stage(&#39;Example&#39;) {
            steps {
                echo &#39;Hello World&#39;
                sh &#39;&#39;
            }         
        }     
    }     
    post {
        always {
            echo &#39;I say always Hello, after perform stages!!&#39;
        }     
       } 
}</code></pre>
<p><strong>선언형 파이프라인 예시 2</strong> - 출처 Jenkins Document</p>
<pre><code class="language-javascript">pipeline {
    parameters {
        choice(name: &#39;PLATFORM_FILTER&#39;, choices: [&#39;all&#39;, &#39;linux&#39;, &#39;windows&#39;, &#39;mac&#39;], description: &#39;Run on specific platform&#39;)
    }
    agent none
    stages {
        stage(&#39;BuildAndTest&#39;) {
            matrix {
                agent {
                    label &quot;${PLATFORM}-agent&quot;
                }
                when { anyOf {
                    expression { params.PLATFORM_FILTER == &#39;all&#39; }
                    expression { params.PLATFORM_FILTER == env.PLATFORM }
                } }
                axes {
                    axis {
                        name &#39;PLATFORM&#39;
                        values &#39;linux&#39;, &#39;windows&#39;, &#39;mac&#39;
                    }
                    axis {
                        name &#39;BROWSER&#39;
                        values &#39;firefox&#39;, &#39;chrome&#39;, &#39;safari&#39;, &#39;edge&#39;
                    }
                }
                excludes {
                    exclude {
                        axis {
                            name &#39;PLATFORM&#39;
                            values &#39;linux&#39;
                        }
                        axis {
                            name &#39;BROWSER&#39;
                            values &#39;safari&#39;
                        }
                    }
                    exclude {
                        axis {
                            name &#39;PLATFORM&#39;
                            notValues &#39;windows&#39;
                        }
                        axis {
                            name &#39;BROWSER&#39;
                            values &#39;edge&#39;
                        }
                    }
                }
                stages {
                    stage(&#39;Build&#39;) {
                        steps {
                            echo &quot;Do Build for ${PLATFORM} - ${BROWSER}&quot;
                        }
                    }
                    stage(&#39;Test&#39;) {
                        steps {
                            echo &quot;Do Test for ${PLATFORM} - ${BROWSER}&quot;
                        }
                    }
                }
            }
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ DevOps ] Jenkins 기반의 CI/CD 환경 구축 (5) - Jenkins 기초 설정 및 기초 가이드]]></title>
            <link>https://velog.io/@duck-ach/DevOps-Jenkins-%EA%B8%B0%EB%B0%98%EC%9D%98-CICD-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95-5-Jenkins-%EA%B8%B0%EC%B4%88-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@duck-ach/DevOps-Jenkins-%EA%B8%B0%EB%B0%98%EC%9D%98-CICD-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95-5-Jenkins-%EA%B8%B0%EC%B4%88-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 11 Mar 2024 04:26:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/duck-ach/post/9d125361-50c7-4f27-8454-eaa48c463188/image.png" alt=""></p>
<h1 id="jenkins-기초-설정">Jenkins 기초 설정</h1>
<h2 id="unlock-jenkins">Unlock Jenkins</h2>
<p>우선 Jenkins의 첫 화면이 이렇게 나올것인데, 설치 시 받았던 Hash 값을 넣어주면 된다.
<img src="https://velog.velcdn.com/images/duck-ach/post/fce4db20-12ef-474f-9c31-1b0e2d5e145e/image.png" alt=""></p>
<p><strong>최초 비밀키 알아내는 방법</strong></p>
<p>admin을 발급받기 위한 임시 비밀번호이다. 이 key를 입력해주면 된다.</p>
<pre><code class="language-shell">$ sudo docker logs -f jenkins</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/3f4ad411-1872-4580-a85c-ad36edbb2088/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/7859e410-cc7d-4053-a6e1-17624edf9e33/image.png" alt=""></p>
<hr>
<h2 id="jenkins-설치-시-제안되는-플러그인-설치">Jenkins 설치 시 제안되는 플러그인 설치</h2>
<p>Jenkins를 설치할 때 Jenkins가 사용자에게 추천하는 플러그인 목록이다. Jenkins를 시작하고 기본적인 작업을 수행하는 데 도움이 되는 플러그인이 포함된다.
<code>Install suggested plugins</code> 를 누른다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/39fa64ee-faca-495e-95c3-13c93c7b0f03/image.png" alt=""></p>
<p>바로 자동으로 설치가 된다.
<img src="https://velog.velcdn.com/images/duck-ach/post/92af5077-05bb-478c-8620-f33ec29e4bde/image.png" alt=""></p>
<hr>
<h2 id="admin-계정-설정">Admin 계정 설정</h2>
<p>해당 이메일 주소는 봇같은 것을 연동하거나 할땐 그 이메일주소를 넣어주는 것이 좋다.
<img src="https://velog.velcdn.com/images/duck-ach/post/5f7c3f3b-4945-445b-a206-3402ba680ab0/image.png" alt=""></p>
<hr>
<h2 id="jenkins-url-설정">Jenkins URL 설정</h2>
<p>이 주소가 맞냐고 확인하는 과정이다. Save를 해준다.
<img src="https://velog.velcdn.com/images/duck-ach/post/fbc01b7e-8458-4fa4-92d4-a349b7212083/image.png" alt=""></p>
<hr>
<h2 id="완료">완료</h2>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/d61af788-1210-4edf-bed7-d1ea5eeacaba/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/4292ec91-eb95-4d2d-88d5-a4c196b0b8fb/image.png" alt=""></p>
<hr>
<h1 id="기초-가이드">기초 가이드</h1>
<h2 id="ubuntu-shell에서-jenkins-shell로-접속하기">Ubuntu Shell에서 Jenkins Shell로 접속하기</h2>
<pre><code class="language-shell">$ sudo docker exec -it jenkins /bin/bash</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/dad8b67a-476d-434b-91b4-291952d603c1/image.png" alt=""></p>
<p>나올때는 exit 명령어로 하면된다.</p>
<pre><code class="language-shell">$ exit</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/7d245add-a2cd-436f-82e7-c547ba367168/image.png" alt=""></p>
<h2 id="ubuntu-shell에서-jenkins-shell로-파일-복사하기">Ubuntu Shell에서 Jenkins Shell로 파일 복사하기</h2>
<pre><code class="language-shell">$ sudo docker cp script.sh jenkins:/tmp/script.sh</code></pre>
<p>docker cp를 이용하여 script.sh 파일을 jenkins라는 컨테이너의 /tmp/script.sh 경로로 파일을 복사하겠다는 뜻이다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/7b5534fb-c819-41dc-9137-eed4ec9675ee/image.png" alt=""></p>
<p>권한 정보까지 복사가 잘 된것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/duck-ach/post/a11d18d3-729c-4ff0-8e27-c69f86d67fe6/image.png" alt=""></p>
<h2 id="dsl로-job-생성하기">DSL로 Job 생성하기</h2>
<p>DSL을 이용하여 Job을 생성하는 것을 <code>Seed Job</code> 이라고 한다.
일반 프로젝트와 큰 차이점은 없으나 이 Job에서 작성한 템플릿을 통해 다른 Job들이 생성되는 것이 특징이다.</p>
<blockquote>
<p><strong>1. Jenkins 관리 &gt; Plugins 탭으로 가서 Job DSL Plugin을 설치해준다.</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/c38c42bb-369a-4c51-8f11-2f2c983b2bed/image.png" alt=""></p>
<blockquote>
<p><strong>2. New Item 으로 가서 Freestyle project를 생성해준다.</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/05a55b9e-9815-4e98-a58d-bc87b4705958/image.png" alt=""></p>
<blockquote>
<p><strong>3. github 정보 입력</strong></p>
</blockquote>
<p>간단히 index 페이지를 띄워주는 샘플 소스를 생성해서 git에 올려두었다.
<img src="https://velog.velcdn.com/images/duck-ach/post/fd823d45-9ceb-45fc-9b4c-c65de324b96d/image.png" alt=""></p>
<p>branch 정보를 넣어준다.
<img src="https://velog.velcdn.com/images/duck-ach/post/14ceb2fe-11c1-43f9-b623-7086a88d39b2/image.png" alt=""></p>
<blockquote>
<p><strong>4. build step 추가</strong></p>
</blockquote>
<p>Process Job DSLs 를 선택하여 추가하여준다.
<img src="https://velog.velcdn.com/images/duck-ach/post/a4d21a44-2abf-4b14-8643-9d82f5b0b8f8/image.png" alt=""></p>
<blockquote>
<p><strong>5. dsl samplescript 추가</strong></p>
</blockquote>
<p>테스트 용이니 샘플로 추가해주었다.
<a href="https://plugins.jenkins.io/job-dsl/">jenkins dsl-job</a> 여기 젠킨스 문서를 참고하자!</p>
<p><code>Use the provided DSL script</code> 는 DSL script를 추가하겠다는 의미이다.</p>
<p>steps는 빌드 단계를 의미한다. 빌드를 할때마다 echo Hello world! 를 하겠다는 의미이다.</p>
<p>이 단계에서 스케줄링을 설정할 수도 있다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/a002505f-7b8d-448b-a3ed-ccc2417d9dc8/image.png" alt=""></p>
<p>저장을 해준다.</p>
<blockquote>
<p><strong>6. dsl script 보안을 비활성화 해준다.</strong></p>
</blockquote>
<p>Jenkins 관리 &gt; Security &gt; Enable script security for Job DSL script 체크박스를 해제해준다.
<img src="https://velog.velcdn.com/images/duck-ach/post/d9a65224-36fc-4804-b437-e4b40e909502/image.png" alt=""></p>
<blockquote>
<p><strong>7. 빌드를 해준다.</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/f26e68e7-c54c-4f3c-be23-569ffb878520/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/627e9e94-7edf-412f-9431-51ff8805779c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/30819d6a-7476-4798-bb5f-b97dad53a2f8/image.png" alt=""></p>
<p>script가 적용된 모습을 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ DevOps ] Jenkins 기반의 CI/CD 환경 구축 (4) - Jenkins Image 구성]]></title>
            <link>https://velog.io/@duck-ach/DevOps</link>
            <guid>https://velog.io/@duck-ach/DevOps</guid>
            <pubDate>Thu, 07 Mar 2024 06:31:21 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>저번 포스팅에서 AWS를 이용한 Linux를 구성했고, Ubuntu에서 Docker와 Docker Compose를 구성해보았다.
이번에는 파이프라인(pipeline)의 핵심인 Jenkins를 구성 해보도록 하자.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/e0e7059f-2333-4a60-9048-42c4e729eda6/image.png" alt=""></p>
<h1 id="🥸-install-jenkins">🥸 Install Jenkins</h1>
<p>우선 Docker Hub에 접속하여 Jenkins Image 파일을 찾아보도록 하자.</p>
<p><a href="https://hub.docker.com/">Docker Hub</a></p>
<h2 id="jenkins-image-파일">Jenkins Image 파일</h2>
<p>맨 위에 있는 Jenkins는 이미 DEPRECATED(End of Service)가 되어 있으며 6년전 이 마지막 업데이트 이기 때문에 두번째의 jenkins/jenkins를 선택해주었다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/80dfeae4-dd16-4daa-bc23-68fd80d409df/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/e0cf2234-e1f0-4137-9fb5-3eaf41e13929/image.png" alt="">
Docker Pull Command로 간단하게 실행해서 컨테이너 이미지를 불러오는 방법이 있고, docker-compose를 활용하여 image에 대한 정보를 작성 후에 실행시켜 컨테이너 이미지를 띄우는 방법이 있다.</p>
<p>Docker Pull 방법은 간편하지만 실행할 때마다 port와 같은 정보들을 하나하나 입력해주어야 하는 불편함이 있다. Docker Compose 방식은 나중에 다른 시스템과 통합하기에도 편하고 유지보수에도 더 도움이 되기 때문에 docker-compose를 이용한 방식이 조금 더 권장된다.</p>
<p>나는 docker-compose를 활용한 방식을 사용해보겠다.</p>
<blockquote>
<p><strong>1. docker compose 작성 및 image 구동을 위한 폴더 생성</strong></p>
</blockquote>
<p><code>/</code> root 경로로 가서, docker라는 폴더를 생성해줄것이다.
그리고 그 안에 /docker/jenkins/jenkins_home/ 이라는 폴더도 생성해줄 것이다.</p>
<pre><code class="language-shell">$ cd /
$ sudo mkdir docker
$ cd docker
$ sudo mkdir jenkins
$ cd jenkins
$ sudo mkdir jenkins_home
$ cd /
$ sudo chown 1000:1000 docker/ -R</code></pre>
<blockquote>
<p><strong>2. docker-compose.yml 작성</strong></p>
</blockquote>
<pre><code class="language-shell">$ cd /docker/jenkins/jenkins_home
$ vi docker_compose.yml</code></pre>
<pre><code class="language-yaml">version: &#39;3&#39;
services:
    jenkins:
        container_name: jenkins # process name
        image: jenkins/jenkins  # jenkins/jenkins packaging version을 받겠다.
        ports:
          - &quot;8080:8080&quot; # port forwading
        volumes:
          - &quot;/docker/jenkins/jenkins_home:/var/jenkins_home&quot; # ubuntu 경로와 docker 경로
        networks:
          - net
networks:
    net:
</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/68ab1aec-9f92-41ac-8f7e-6302930f4ff1/image.png" alt=""></p>
<blockquote>
<p><strong>3. docker-compose로 docker-compose.yml up 하기</strong></p>
</blockquote>
<p>이 과정을 통해 Docker Compose는 <code>docker-compose.yml</code>안에 정의된 모든 서비스를 시작한다.</p>
<pre><code class="language-shell">$ sudo docker compose up -d</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/92c24dfc-47d6-4c5e-b8d6-6150cae59bd6/image.png" alt=""></p>
<blockquote>
<p>*<em>4. 최초 비밀키 알아내기 *</em></p>
</blockquote>
<p>admin을 발급받기 위한 임시 비밀번호이다. 확인하고 어딘가에 잘 복붙해 둔다.</p>
<pre><code class="language-shell">$ sudo docker logs -f jenkins</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/f15c76b5-4c97-43ce-8bce-9b1443eb52ab/image.png" alt=""></p>
<blockquote>
<p><strong>Docker에 image가 정상적으로 실행 되었는지 확인</strong></p>
</blockquote>
<p>docker ps는 실행중인 image를 나열하는 것이고, <code>-a</code>(all) 옵션이 붙으면 중지된 컨테이너 까지 모두 확인하는 명령어다.</p>
<pre><code class="language-shell">$ docker ps</code></pre>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/5263ec92-8358-4ebf-bf53-a2bd721e67af/image.png" alt=""></p>
<h2 id="jenkins-image-접속하기">Jenkins Image 접속하기</h2>
<h3 id="방화벽-체크">방화벽 체크</h3>
<p>docker를 구동시킨 8080에 접속을 하면 되어야 하는데 아무것도 연결되지 않는다. 이것은 방화벽을 체크하고 열어주어야 한다.
<img src="https://velog.velcdn.com/images/duck-ach/post/f6611fca-6a4f-4e41-a9f5-2849860a4be2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/77028a3e-63c1-416a-88d2-dab435bbe518/image.png" alt=""></p>
<blockquote>
<p><strong>1. AWS EC2 Instance 관리에 간다.</strong></p>
</blockquote>
<p>Instance를 체크하고, 보안 탭에서 보안그룹으로 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/599f0bf5-863c-49f4-aba7-7a39501b34da/image.png" alt=""></p>
<blockquote>
<p><strong>2. 인바운드 규칙을 설정해준다.</strong></p>
</blockquote>
<ul>
<li>인바운드(Inbound)란? 머신으로 들어오는 트래픽</li>
<li>아웃바운드(Outbound)란? 머신에서 나가는 트래픽</li>
</ul>
<p>인바운드 규칙 편집을 눌러준다.
<img src="https://velog.velcdn.com/images/duck-ach/post/7cee75c2-d2d2-4f84-a999-22a3d64be77f/image.png" alt=""></p>
<p>SSH는 그대로 두고, 8080포트를 허용하기 위해 <code>사용자지정 TCP</code>를 선택해준다.</p>
<p>포트는 <code>8080</code>으로 하고, 소스는 DB와 같은 보안이 중요한 리소스를 보호하기 위해 접속할 수 있는 IP들을 제한할 수 있는 것인데 나는 어떤 IPv4든 접속이 가능하도록 <code>Anywhere-IPv4</code>를 선택 해 주었다. 그리고 규칙 저장을 눌러준다.
<img src="https://velog.velcdn.com/images/duck-ach/post/7a413631-2656-4198-b236-040efea224c6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/duck-ach/post/55600352-2bac-40fd-a90c-c554f1de7014/image.png" alt=""></p>
<hr>
<p><strong>결과</strong>
<img src="https://velog.velcdn.com/images/duck-ach/post/481b113b-2c0f-49e2-9b3a-fcb1935652d7/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ Spring ] @ComponentScan Annotation에 대해서]]></title>
            <link>https://velog.io/@duck-ach/Spring-ComponentScan-Annotation%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C</link>
            <guid>https://velog.io/@duck-ach/Spring-ComponentScan-Annotation%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C</guid>
            <pubDate>Wed, 06 Mar 2024 13:54:00 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>기존 Spring에서는 자바 코드의 <code>@Bean</code>이나, xml파일의 <code>&lt;bean&gt;</code> 태그 등을 통해서 직접 등록할 스프링 빈을 나열했다.
실무에서는 코드가 수십, 수백개가 될텐데 이 Bean들을 모두 일일히 등록해주려면 귀찮기도하고 실제로 누락되는 정보도 많을 것이다.</p>
<p>그래서 Spring은 설정 정보가 없어도 자동으로 Spring Bean을 등록해주는 <code>@ComponentScan</code> 기능을 제공한다.</p>
<p><code>@ComponentScan</code> Annotation은<code>@Component</code> 및 <code>@Controller</code>, <code>@Service</code>, <code>@Repository</code>, <code>@Configuration</code> 등 과 같은 Annotation을 스캔하여 해당 Annotation이 부여된 Class(객체)들을 자동으로 Scan하여 스프링 빈(Bean)에 등록 해주는 역할을 한다.</p>
<p>그리고 <code>@Autowired</code>를 통해 의존 관계도 자동으로 주입(Dependency Injection)해준다.</p>
<hr>
<h2 id="componentscan">@ComponentScan</h2>
<h3 id="componentscan-을-지정할-파일-위치">@ComponentScan 을 지정할 파일 위치</h3>
<p><code>@ComponentScan</code> Annotation은 따로 <strong>basePackages</strong>를 지정해 주지 않는 이상 자기가 위치한 곳부터 패키지 아래의 모든 <code>@Component</code>와 <code>@Controller</code>, <code>@Service</code>, <code>@Repository</code>, <code>@Configuration</code>와 같은 Annotation 들을 Scan하여 객체를 <code>@Bean</code>으로 등록해준다.</p>
<p>그렇기 때문에 패키지의 가장 바깥쪽에 위치하는 곳에 두고 Scan을 한다.
<img src="https://velog.velcdn.com/images/duck-ach/post/6deb8b50-24ba-442f-bd03-3b931f7a50cb/image.png" alt=""></p>
<p><strong><code>@ComponentScan</code>을 적용한 예시</strong></p>
<pre><code class="language-java">@Configuration
@ComponentScan
public class AppConfig {

}</code></pre>
<hr>
<h4 id="componentscan-내-scan-패키지-범위-지정하기"><code>@ComponentScan</code> 내 Scan 패키지 범위 지정하기</h4>
<p><strong>basePackages</strong> 를 이용하면 탐색할 패키지의 시작 위치를 지정할 수 있다. basePackages에 지정한 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.</p>
<p><strong>com.kyh.heera.member 패키지와 그 하위 패키지 내 <code>@Component</code> 객체 Scan하기</strong></p>
<pre><code class="language-java">@ComponentScan(
          basePackages = &quot;com.kyh.heera.member&quot;,
}</code></pre>
<hr>
<h3 id="component와-같은-annotation을-scan-할-수-있는-이유">@Component와 같은 Annotation을 Scan 할 수 있는 이유</h3>
<p><code>@ComponentScan</code>은 말 그대로 <code>@Component</code> Annotation이 붙은 Class를 Scan해서 스프링에 Bean으로 등록한다. </p>
<p><code>@Controller</code>, <code>@Service</code>, <code>@Repository</code>, <code>@Configuration</code>와 같은 Annotation들을 들어가보면 <strong><code>@Component</code>가 등록</strong>되어 있는 것을 확인할 수 있다.</p>
<p><strong><code>@Controller</code> Annotation 내부</strong></p>
<pre><code class="language-java">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

    /**
     * Alias for {@link Component#value}.
     */
    @AliasFor(annotation = Component.class)
    String value() default &quot;&quot;;

}</code></pre>
<p><strong><code>@Service</code> Annotation 내부</strong></p>
<pre><code class="language-java">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

    /**
     * Alias for {@link Component#value}.
     */
    @AliasFor(annotation = Component.class)
    String value() default &quot;&quot;;

}</code></pre>
<p><strong><code>@Repository</code> Annotation 내부</strong></p>
<pre><code class="language-java">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

    /**
     * Alias for {@link Component#value}.
     */
    @AliasFor(annotation = Component.class)
    String value() default &quot;&quot;;

}</code></pre>
<p><strong><code>@Configuration</code> Annotation 내부</strong></p>
<pre><code class="language-java">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

    @AliasFor(annotation = Component.class)
    String value() default &quot;&quot;;

    boolean proxyBeanMethods() default true;
    boolean enforceUniqueMethods() default true;
}</code></pre>
<p><strong>Bean 등록 테스트</strong></p>
<pre><code class="language-java">package com.kyh.heera.scan;

import com.kyh.heera.AutoAppConfig;
import com.kyh.heera.member.MemberRepository;
import com.kyh.heera.member.MemberService;
import com.kyh.heera.order.OrderServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;

public class AppConfigTest {

    @Test
    void basicScan() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);

        OrderServiceImpl bean = ac.getBean(OrderServiceImpl.class);
        MemberRepository memberRepository = bean.getMemberRepository();

        System.out.println(&quot;memberRepository = &quot; + memberRepository);
    }

}</code></pre>
<p><strong>Test 결과</strong>
<img src="https://velog.velcdn.com/images/duck-ach/post/d17f2409-5f01-4a83-8ce3-2b827c0de0bc/image.png" alt=""></p>
<h2 id="autowired">@Autowired</h2>
<p><code>@Autowired</code>를 지정하면, Spring Container가 해당 스프링 빈을 찾아서 주입한다.
참고로 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 Bean이어야 동작한다. Member와 같은 DTO에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다.<img src="https://velog.velcdn.com/images/duck-ach/post/fc1a7fec-adc2-4715-b6e2-2349fd56b2c0/image.png" alt="">
<img src="https://velog.velcdn.com/images/duck-ach/post/6a1201de-7b50-4042-94e8-3b808c425699/image.png" alt=""></p>
<p>기본적으로 Type 기반으로 조회를 하며, 여러 빈이 있다면 필드이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.</p>
<p><code>@Autowired</code>는 4가지의 경우에 적용할 수 있다.</p>
<ul>
<li>Constructor(생성자)</li>
<li>setter(수정자)</li>
<li>field(필드)</li>
<li>method(메소드)</li>
</ul>
<h3 id="constructor생성자">Constructor(생성자)</h3>
<p>생성자 주입은 생성자에 의존성 주입을 받고자 하는 field를 나열하는 방법으로, 3가지 경우 중 가장 권고되는 방법이다.</p>
<p><strong>특징</strong></p>
<ul>
<li>생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.</li>
<li>불변이 보장된다.</li>
<li>생성자를 호출(=객체를 호출) 하는 시점에 Bean이 생성되기 때문에 필수적인 의존관계에 사용된다.</li>
<li>생성자가 딱 1개 있다면 @Autowired를 생략해도 자동 주입된다.</li>
</ul>
<p><strong>Constructor(생성자) 주입 예시</strong></p>
<pre><code class="language-java">@Component
  public class OrderServiceImpl implements OrderService {

      private final MemberRepository memberRepository;
      private final DiscountPolicy discountPolicy;

      @Autowired // 이렇게 생성자가 딱 하나만 있다면 생략 가능
      public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
  discountPolicy) {
          this.memberRepository = memberRepository;
          this.discountPolicy = discountPolicy;
      }
}</code></pre>
<h3 id="setter수정자">setter(수정자)</h3>
<p>수정자 주입은 setter라 불리는 field의 값을 변경하는 수정자 Method를 통해 의존관계를 주입하는 방법이다.</p>
<p><strong>특징</strong></p>
<ul>
<li>선택적으로 의존성을 주입해야 하는 경우 사용한다.</li>
<li>변경 가능성이 있는 의존관계에 사용한다.</li>
</ul>
<p><strong>참고</strong>
_선택적으로 의존성을 주입할 경우에 @Autowired의 기본동작은 주입할 대상이 없으면 오류가 발생하기 때문에 <strong>@Autowired(required=false)</strong> 와 같은 옵션을 추가해주어야 한다.
_</p>
<p><strong>setter(수정자) 주입 예시</strong></p>
<pre><code class="language-java">@Component
  public class OrderServiceImpl implements OrderService {

      private MemberRepository memberRepository;
      private DiscountPolicy discountPolicy;

        @Autowired
        public void setMemberRepository(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
        @Autowired
        public void setDiscountPolicy(DiscountPolicy discountPolicy) {
            this.discountPolicy = discountPolicy;
        }

}</code></pre>
<h3 id="field필드">field(필드)</h3>
<p>필드에 바로 주입하는 방식이다. 예전에 나도 많이 사용했었지만, 지금은 권장하지 않는다고 한다.</p>
<p><strong>특징</strong></p>
<ul>
<li>코드가 간결해서 많이 사용되었다.</li>
<li>외부에서 변경이 불가능(private keyword 등)하기 때문에 테스트하기 힘들다는 치명적인 단점이 있다.</li>
<li>DI(Dependency Injection) 프레임워크가 없으면 아무것도 할 수 없다.</li>
</ul>
<p><strong>field(필드) 주입 예시</strong></p>
<pre><code class="language-java">@Component
  public class OrderServiceImpl implements OrderService {

        @Autowired
        private MemberRepository memberRepository;

        @Autowired
        private DiscountPolicy discountPolicy;

}</code></pre>
<h3 id="method메소드">method(메소드)</h3>
<p>일반 method(메소드)를 통해서 주입 받을 수 있다.</p>
<p><strong>특징</strong></p>
<ul>
<li>한번에 여러 필드를 주입 받을 수 있다.</li>
<li>일반적으로 잘 사용하지 않는다.</li>
</ul>
<p><strong>method(메소드) 주입 예시</strong></p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy
    discountPolicy) {
            this.memberRepository = memberRepository;
            this.discountPolicy = discountPolicy;
        }
}</code></pre>
<hr>
]]></description>
        </item>
    </channel>
</rss>