<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>garden-y.log</title>
        <link>https://velog.io/</link>
        <description>https://garden-ying.tistory.com/</description>
        <lastBuildDate>Thu, 06 Jun 2024 14:27:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>garden-y.log</title>
            <url>https://velog.velcdn.com/images/garden-y/profile/891a3ae3-bd1b-4b32-8ab7-7451811ba2c1/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. garden-y.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/garden-y" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Docker] 도커 실무 (레이어 관리, 캐싱을 활용한 빌드, Nignx 설정 커스터마이징, 3Tier 아키텍처 구성,  동적 서버 구성, 이중화 DB 구성, 컨테이너 애플리케이션 최적화)]]></title>
            <link>https://velog.io/@garden-y/Docker-%EB%8F%84%EC%BB%A4-%EC%8B%A4%EB%AC%B4-%EB%A0%88%EC%9D%B4%EC%96%B4-%EA%B4%80%EB%A6%AC-%EC%BA%90%EC%8B%B1%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%B9%8C%EB%93%9C-Nignx-%EC%84%A4%EC%A0%95-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95-3Tier-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B5%AC%EC%84%B1-%EB%8F%99%EC%A0%81-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%84%B1-%EC%9D%B4%EC%A4%91%ED%99%94-DB-%EA%B5%AC%EC%84%B1-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@garden-y/Docker-%EB%8F%84%EC%BB%A4-%EC%8B%A4%EB%AC%B4-%EB%A0%88%EC%9D%B4%EC%96%B4-%EA%B4%80%EB%A6%AC-%EC%BA%90%EC%8B%B1%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%B9%8C%EB%93%9C-Nignx-%EC%84%A4%EC%A0%95-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95-3Tier-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B5%AC%EC%84%B1-%EB%8F%99%EC%A0%81-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%84%B1-%EC%9D%B4%EC%A4%91%ED%99%94-DB-%EA%B5%AC%EC%84%B1-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Thu, 06 Jun 2024 14:27:35 GMT</pubDate>
            <description><![CDATA[<h1 id="part8-도커-실무">Part8. 도커 실무</h1>
<h2 id="레이어-관리">레이어 관리</h2>
<p>이미지의 개수를 작게 유지하는 방법과 이미지의 크기를 작게 만드는 방법</p>
<h4 id="도커-파일의-레이어-구조">도커 파일의 레이어 구조</h4>
<ul>
<li>도커 이미지는 레이어로 구성되어 있다.</li>
<li>Dockerfile에 작성된 지시어 (대부분)하나 당 레이어가 한 개 추가된다.</li>
<li><strong>불필요한 레이어가 많아지면 이미지의 크기가 늘어나고 빌드 속도가 느려질 수 있다.</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/cc083880-3a38-408f-bb34-64d49521a5c2/image.png" alt=""></p>
<p>node 14 이미지에 총 5개의 지시어가 추가, CMD를 제외하고 4개의 이미지가 추가되었다. 기존의 14개 레이어에서 새로운 4개의 레이어를 추가해서 총 18개의 레이어로 빌드된다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/99eca31a-ef5e-45e5-9bd4-d5afd14b6001/image.png" alt=""></p>
<ul>
<li>RUN 지시어는 컨테이너 레이어 안에서 특정 명령을 실행하는 지시어이다.</li>
<li>RUN 지시어는 &amp;&amp;을 활용해 최대한 하나로 처리한다.</li>
<li>불필요한 명령어를 추가해서 레이어의 개수를 늘리지 않는다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/f7d6457e-c6a6-483b-8a48-8304bcc60200/image.png" alt=""></p>
<p>현업에서는 보통 이미지를 빌드하고 푸시하고 배포하는 과정을 모두 다른 기기에서 진행하는 경우가 많다. 새로운 버전으로 빌드한 이미지를 레지스트리에 푸시하고, 각각의 환경에서 이미지를 레지스트리로부터 다운받아 컨테이너를 업그레이드 한다. </p>
<p>이 과정에서 <strong>이미지는 네트워크를 통해 업로드되고, 다운로드 되기 때문에 이미지의 크기가 작은 것이 배포 속도와 네트워크 사용량에 유리</strong>하다 볼 수있다. </p>
<h4 id="이미지-크기-줄이는-방법">이미지 크기 줄이는 방법</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/0a34bbee-fc00-4844-9cde-ce267e0a1fdb/image.png" alt=""></p>
<ul>
<li><p>애플리케이션의 <strong>크기를 가능한 작게 관리</strong>한다.</p>
<p>  → 애플리케이션 소스의 불필요한 기능들을 줄이고 하나의 큰 모듈을 여러 모듈로 분리해서 애플리케이션 크기를 줄일 수 있다.</p>
<p>  → 애플리케이션 크기가 줄어들면 이미지의 크기도 줄어든다. </p>
</li>
<li><p><strong>베이스 이미지는 가능한 작은 이미지</strong>를 사용한다. alpine OS를 사용하는 것이 좋다.</p>
<p>  → 극단적으로 사이즈를 줄이려면 모든 이미지의 뿌리가 되는 스크래치 이미지를 활용할 수도 있다. 스크래치 이미지는 이미지를 빌드하기 위한 가장 최소한의 파일만 포함한 이미지이다. </p>
</li>
<li><p><strong>.dockerignore 파일을 사용</strong>해서 불필요한 파일을 제거한다.</p>
<p>  → 불필요한 파일이 이미지로 들어가지 않도록 한다. (copy 지시어 사용 시)</p>
</li>
</ul>
<h3 id="실습">실습</h3>
<p>이미지 빌드</p>
<pre><code class="language-docker">docker build -t helloworld .</code></pre>
<pre><code class="language-docker">docker run -d -p 8080:8080 --name go-helloworld helloworld</code></pre>
<p>→ 이미지 크기를 작게 구성하는데 있어서 정적 바이너리 파일로 빌드할 수 있는 GO언어를 사용하는 것은 좋은 방법이다. 실제로 하나의 컨테이너의 크기를 줄이는 것이 중요한 미션 중의 하나인 MSA 아키텍처에서는 GO언어를 많이 사용한다.</p>
<p><br><br></p>
<h2 id="캐싱을-활용한-빌드">캐싱을 활용한 빌드</h2>
<p>캐싱은 <strong>빌드 속도를 크게 증가시킬 수 있는(빠르게 만들어주는) 기술</strong>이다. 시간이 걸리는 작업의 결과물을 미리 저장해두고, 동일한 작업이 발생했을 경우 다시 계산하지 않고 결과를 빠르게 제공해준다. </p>
<p>레이어를 어떻게 구성하는지에 따라서 캐싱을 활용하는 빈도가 달라질 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/2ff0c784-7cc7-4bf3-9b3e-77bf137ce7ea/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/31fea3e3-aba7-4f74-8d7e-243215e317c3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/78c29686-5157-4e47-9cbe-1b5462399007/image.png" alt=""></p>
<p>도커 빌드 명령어를 실행하면 결과적으로 4개의 이미지 레이어를 추가한다. 이 과정에서 레이어들은 도커가 캐시로 저장해둔다. </p>
<p>그 다음빌드에서 동일한 지시어를 사용하면 이미지 레이어를 새롭게 만들지 않고, 캐시에 저장되어 있는 레이어를 그대로 사용한다. 완전히 동일한 소스코드로 다시 빌드할 경우에는 빠르게 이미지를 빌드할 수 있다. </p>
<p>도커는 지시어를 똑같이 작성한 경우에는 캐시에 저장되어 있는 레이어를 그대로 사용한다. (지시어만 같다고 X, 지시어로 처리하는 내용까지 같아야 한다.) </p>
<ul>
<li>Dockerfile에 작성된 순서대로 결과 이미지의 레이어가 쌓입니다.</li>
<li>Docker는 각 단계의 결과 레이어를 캐시처리합니다. 지시어가 변경되지 않으면 다음 빌드에서 레이어를 재사용합니다.</li>
<li>COPY, ADD 명령의 경우 빌드 컨텍스트의 파일 내용이 변경되어도 캐시를 사용하지 않습니다.</li>
<li><strong>레이어가 변경되면 그 레이어와 이후의 모든 레이어는 캐시를 사용하지 않고 새로운 레이어가 만들어집니다.</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/4d12a29a-0f30-4f0d-ae1d-98e968435655/image.png" alt=""></p>
<ul>
<li><strong>잘 변경되지 않는 파일들을 아래 레이어에 배치하면, 캐시를 활용하는 빈도를 높일 수 있습니다.</strong></li>
<li>package.json, package-lock.json 파일은 소스 코드가 의존하는 외부 라이브러리 정보가 있습니다. 개발 시 자주 변경되지 않습니다.</li>
<li>package.json, package-lock.json 파일이 변경되지 않을 경우 npm ci 단계(의존 라이브러리 설치)까지 캐시를 활용할 수 있습니다.</li>
</ul>
<p><strong>라이브러리 설치와 같은 자주 변경되지 않는 부분을 이미지 빌드 초기 단계에 구성해놓으면 캐싱을 잘 활용할 수 있다.</strong>
기존 도커 파일은 빌드 컨텍스트에서 전체 파일을 복사 → 라이브러리 설치하는 부분을 별도로 분리한다. 
<br></p>
<h3 id="실습-1">실습</h3>
<pre><code class="language-docker">1. easydocker/leafy/leafy-frontend 디렉터리 이동 및 소스코드 상태 변경
git reset --hard HEAD &amp;&amp; git clean -fd
git switch 01-dockerfile

2. 코드 변경 없이 2번 빌드 시도, 2번째 빌드에서 캐시 사용 확인
docker build -t leafy-front:2.0.0 . --no-cache
docker build -t leafy-front:2.0.1 .

3. 코드 일부분 수정 후 다시 빌드 시도, 시간 체크 (src/App.vue)
docker build -t leafy-front:2.0.2 .

4. 다음 페이지의 도커파일을 참고해 도커파일 수정 및 빌드
docker build -t leafy-front:2.0.3 . --no-cache

5. 코드 일부분 수정 후 다시 빌드 시도, 캐시 활용 한 시간 체크
docker build -t leafy-front:2.0.4 .</code></pre>
<pre><code class="language-docker">FROM node:14 AS build

WORKDIR /app

COPY package.json . // ---------------- 라이브러리 설치에 필요한 package.json 파일과 package-lock.json 파일만 복사
COPY package-lock.json .

RUN npm ci // --------------------------------- 의존 라이브러리 설치

COPY . /app //--------------------------------- 전체 소스코드 복사 RUN npm run build ----------------------------- 애플리케이션 빌드

FROM nginx:1.21.4-alpine

COPY --from=build /app/dist /usr/share/nginx/html

EXPOSE 80
ENTRYPOINT [&quot;nginx&quot;]
CMD [&quot;-g&quot;, &quot;daemon off;&quot;]
</code></pre>
<pre><code class="language-docker">1. easydocker/leafy/leafy-backend 이동 및 다음 파일의 도커파일 수정 후 빌드
cd ../leafy-backend
docker build -t leafy-backend:2.0.0 . --no-cache

2. 소스파일 변경 후 재빌드, (src/main/java/com/devwiki/leafy/controller/plant/PlantController.java) 캐시 활용하여 단축된 시간 확인
docker build -t leafy-backend:2.0.1 .</code></pre>
<pre><code class="language-docker">FROM gradle7.6.1-jdk11 AS build

WORKDIR /app

COPY build.gradle settings.gradle ./ // ---------------- 라이브러리 설치에 필요한 build.gradle파일과 settingsgradle 파일만 복사

RUN gradle dependencies --no-daemon //----------------- 의존 라이브러리 설치

COPY . /app //--------------------------------- 전체 소스코드 복사

RUN gradle clean build --no-daemon //-------------- 애플리케이션 빌드

FROM openjdk:11-jre-slim

WORKDIR /app
COPY --from=build /app/build/libs/*.jar /app/leafy.jar

EXPOSE 8080
ENTRYPOINT [&quot;java&quot;]
CMD [&quot;-jar&quot;, &quot;leafy.jar&quot;]</code></pre>
<p>→ 이미지 빌드 시 캐싱을 활용해서 할 수 있다. 캐시를 활용하기 위해서는 도커 파일을 작성할 때 자주 수정되지 않는 부분을 먼저 작성하는 것이 유리하다. </p>
<p><br><br><br></p>
<h2 id="nignx-설정-커스터마이징-3tier-아키텍처-구성">Nignx 설정 커스터마이징, 3Tier 아키텍처 구성</h2>
<h3 id="3tier-아키텍처">3Tier 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/adb52123-37e9-4972-ad10-3411ea7e0b32/image.png" alt=""></p>
<h4 id="엔터프라이즈-웹-애플리케이션-서버-구성">엔터프라이즈 웹 애플리케이션 서버 구성</h4>
<ul>
<li>프론트엔드 소스 제공하는 웹 서버</li>
<li>비즈니스 로직을 수행하는 웹 애플리케이션</li>
<li>데이터 저장하는 데이터베이스 서버</li>
</ul>
<p>→ 이 세가지 종류의 서버가 유기적으로 상호작용하면서 하나의 애플리케이션으로 구성되는 것을 3Tier 아키텍처라고 한다. (Tier는 하나의 단계를 의미)</p>
<p>많은 엔터프라이즈 웹 애플리케이션이 <strong>3Tier 아키텍처를 기반으로 구성</strong>되어 있다.</p>
<p>여기서 가장 먼저 클라이언트의 첫번째 진입점 역할을 하는 서버가 웹 서버이다. 클라이언트가 주소창에 어떤 요청을 보냈을 때 요청한 경로에 해당하는 html, JS와 같은 정적 파일을 사용자에게 제공해준다. 정적 파일이라는 것은 어떤 사용자든 동일한 경로로 보낸 요청은 동일한 내용의 페이지를 제공한다는 의미이다. </p>
<p>사용자마다 다른 데이터를 제공하기 위해서는 프론트엔드에서 제공받은 파일을 활용해 클라이언트가 다시 백엔드 애플리케이션으로 요청을 보내야 한다. 백엔드 애플리케이션은 DB에 요청을 보내 영속성이 필요한 데이터를 조회하고, 저장할 수 있다. 
<br></p>
<h3 id="proxy">Proxy</h3>
<p>화면의 구조를 보면 클라이언트가 백엔드 서버에 요청을 직접한다. 하지만 일반적으로 백엔드 애플리케이션은 시스템과 실제 데이터에 밀접한 연관이 있기 때문에 보안에 주의해야 하므로 <strong>사용자가 직접 접근하지 않도록 설정하는 것</strong>이 좋다.</p>
<p>우리가 구성한 아키텍쳐는 웹 애플리케이션 서버가 클라이언트에 그대로 노출되기 때문에 보안상 뛰어나다고 볼 수 없다. 따라서 <strong>Nginx의 Proxy 기능을 활용해 백엔드 애플리케이션으로의 접근을 제한</strong>할 것이다. </p>
<p>Proxy 설정을 하기 위해서는 Nginx 이미지를 빌드할 때 Nginx 서버의 설정을 변경해야 하기 때문에 이미지를 빌드할 때 서버의 설정을 변경하는 방법을 실습한다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/c27484b0-4d2a-4c68-8768-ecd4cf5cf60b/image.png" alt=""></p>
<p><strong>개선된 3Tier 구조</strong>이다. 이 구조에서는 백엔드 애플리케이션에 대한 요청은 <strong>웹서버를 통해서만 접근이 가능</strong>하다. 웹 서버는 클라이언트와 백엔드 애플리케이션의 다리역할을 하기 위해서 Proxy라는 기술을 활용한다. </p>
<p><strong>Proxy는 요청을 전달해준다</strong>는 의미로, Nginx의 Proxy 기술을 활용하면 <strong>특정 경로로 접근하는 요청은 원하는 곳으로 다시 전달해 줄 수 있다.</strong> 그래서 Proxy를 활용해서 Nginx 서버의 /api로 시작되는 요청들은 일반적인 요청처럼 정적파일을 제공해주는 것이 아니라 <strong>백엔드 애플리케이션으로 다시 전달</strong>해준다.</p>
<p>프록시 설정을 하기 위해서는 Nginx의 설정파일에 아래를 추가하면 Nginx 서버로 전달되는 요청 중에 /api로 시작되는 모든 요청은 이 proxy pass에 적혀있는 http 프로토콜의 leafy:8080으로 요청을 다시 전달할 수 있다. </p>
<pre><code class="language-docker">Nginx 서버 설정 변경
location /api/ {
    proxy_pass http://leafy:8080;
}</code></pre>
<ul>
<li>Nginx의 프록시 기술을 활용해 보안에 뛰어난 3Tier 아키텍처를 구성할 수 있습니다.</li>
<li>Nginx는 특정 경로로 온 요청(/api로 시작하는 경로)를 지정한 서버로 전달합니다.</li>
<li>Nginx를 프록시 서버로 활용하여 보안 향상, 부하 관리 및 API 응답 캐싱을 활용할 수 있습니다.</li>
</ul>
<p><a href="http://localhost/index.html">http://localhost/index.html</a> → 웹서버의 정적 파일을 응답</p>
<p><a href="http://localhost/api/">http://localhost/api/</a>~ → 애플리케이션으로 요청 전달</p>
<p>Proxy는 특정 경로로 온 요청을 정해진 경로로 전달해준다.</p>
<p>이렇게 Nginx의 Proxy 기술을 활용하면 /api 이외의 경로로 접근하는 백엔드 애플리케이션으로의 접근을 물리적으로 차단할 수 있다. </p>
<p>추가적으로 api의 응답을 캐싱하거나 부하를 관리하고 접근 로그를 관리하는 것처럼 다양한 추가 기능을 Proxy를 동해 활용할 수 있다. </p>
<h3 id="실습-2">실습</h3>
<pre><code class="language-docker">import axios from &#39;axios&#39;; 

const api = axios.create({
    ~~baseURL: process.env.VUE_APP_API_BASE_URL || &#39;http://localhost:8080&#39;~~
});

export default api;</code></pre>
<p>→ 웹 브라우저에서 백엔드 애플리케이션에 대한 요청을 <a href="http://localhost:8080%EC%9C%BC%EB%A1%9C">http://localhost:8080으로</a> 보내는 설정, 해당 라인을 제거 후 저장한다. </p>
<p>easydocker/leafy/leafy-frontend/nginx.conf 파일 생성</p>
<pre><code class="language-python">server {
    listen       80;
    server_name  _;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        }
        # / 경로에 왔을 경우 usr/share/nignx/html에 있는 index.html을 제공해준다.

    location /api/ {
        proxy_pass http://leafy:8080;
        }
        # /api 경로에 왔을 경우 proxy 기능을 활용해 repeat 8080 포드로 요청을 전달한다. 

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}</code></pre>
<p>→ 웹 서버로 오는 요청 중 /api/로 시작하는 경로의 요청을 모두 <a href="http://leafy:8080%EC%9C%BC%EB%A1%9C">http://leafy:8080으로</a> 전달</p>
<p>새롭게 작성한 nignx 웹서버 설정파일인 nginx.conf 파일을 기존의 nginx 이미지에 덮어쓰기 하는 부분을 도커파일에 추가한다. </p>
<pre><code class="language-docker">COPY nginx.conf /etc/nginx/conf.d/default.conf</code></pre>
<p>→ 소스코드의 nginx.conf 파일을 이미지 빌드 시 nginx 설정으로 복사</p>
<pre><code class="language-docker">1. 이미지 빌드
docker build -t leafy-front:3.0.0-proxy .

2. network 가 제대로 생성되어 있는지 확인
docker network create leafy-network

3. 애플리케이션 구동 및 테스트, 백엔드 애플리케이션을 포트포워딩 하지 않고 내부 통신만 사용하도록 설정
# postgres 컨테이너 실행
(MacOS) docker run -d --name leafy-postgres -v mydata:/var/lib/postgresql/data --network leafy-network devwikirepo/leafy-postgres:1.0.0 
(Windows) docker run -d --name leafy-postgres -v mydata://var/lib/postgresql/data --network leafy-network devwikirepo/leafy-postgres:1.0.0
# leafy 컨테이너 실행, DB_URL옵션으로 db서버 url을 db컨테이너 명으로 설정
# 기존엔 포트포워딩을 통해 8080 포트를 외부로 열었지만, Nginx의 Proxy를 통해 접근할 예정으로 백엔드 애플리케이션을 외부에 오픈할 필요가 없다. 
docker run -d -e DB_URL=leafy-postgres --name leafy --network leafy-network devwikirepo/leafy-backend:1.0.0
# 프론트엔드 컨테이너 실행
docker run -d -p 80:80 --name leafy-front --network leafy-network leafy-front:3.0.0-proxy

docker ps
# 3개 컨테이너 확인 가능
# 포트포워딩 되어있는 서버는 웹서버뿐이기 때문에 실습PC에서 접근할 수 있는 서버는 웹서버로 제한 

4. localhost:80 접속하여 정상 접속 확인, curl 명령으로 백엔드 접근 테스트(응답이 오지 않아야 정상)
curl http://localhost:8080/api/v1/users

5. 환경 정리
docker rm -f leafy-postgres leafy leafy-front
# Postgres에서 생성한 mydata 볼륨은 남아있기 때문에 볼륨도 삭제
docker volume rm mydata</code></pre>
<p>웹 서버의 Proxy기능을 활용해 웹 애플리케이션으로 클라이언트가 직접 접근하는 것을 막았다. 클라이언트는 오로지 웹 서버만으로 요청을 수행하고, 웹 서버는 정적인 자료를 요청할 경우 바로 응답, api 요청이 전달될 경우에는 웹 애플리케이션으로 Proxy한다. </p>
<p>nginx 웹 서버 설정을 통해 /api 경로는 모두 전달되도록 설정했다.</p>
<p>백엔드 애플리케이션은 기존과 동일하게 요청을 받아서 데이터베이스 서버와 상호작용한 뒤 결과를 웹서버로 전달한다. 그러면 웹 서버는 결과를 클라에게 전달해준다. </p>
<p>→ proxy 기능을 활용해 클라이언트와 백엔드 서버가 연결되는 부분을 완전히 제거했다. 
→ 네트워크 구성을 보면 기존과 차이점은 leafy container의 <strong>포트 포워딩이 삭제</strong>된 것이다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/f2fc8052-7758-4d3e-83cf-d68df2912cff/image.png" alt=""></p>
<p>기존엔 호스트의 8080주소를 활용해 백엔드 컨테이너와 상호작용 했다면, 프록시를 활용해 리피 컨테이너의 <strong>포트 포워딩을 제거하고, Nginx 웹서버에서만 리피컨테이너로 접근되도록 구성</strong>했다. 따라서 프론트엔드 컨테이너는 HostOS의 80포트로 접근하고, 백엔드 컨테이너는 HostOS의 80포트의 /api로 접근했을 때 Nginx 서버에서 프록시되어 접근할 수 있다. DB 컨테이너도 마찬가지로 기존과 동일하게 포트포워딩이 없기 때문에 여전히 <strong>외부에서 접근 불가능하고, 백엔드 애플리케이션을 통해서만 접근할 수 있는 구조</strong>이다. </p>
<p><br><br></p>
<h2 id="환경-변수를-활용한-동적-서버-설정">환경 변수를 활용한 동적 서버 설정</h2>
<p><img src="https://velog.velcdn.com/images/garden-y/post/a112debd-5860-42ff-ab30-f205b3f621d1/image.png" alt=""></p>
<ul>
<li>Nginx 서버 설정에 백엔드 애플리케이션의 주소가 고정되어 있다.</li>
<li>환경 별로 Nginx가 프록시 해야 하는 주소가 바뀔 수 있다. (leafy:8080 → leafy-backend:8080으로 변경될 경우)</li>
<li>프록시 설정의 주소를 바꾸기 위해 설정 파일 수정 후, 이미지 빌드를 다시 해야 한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/76428bed-af4e-4e4b-8ebe-97db0403bbdb/image.png" alt=""></p>
<p>→ <strong>배포가 되는 환경 별로 달라지는 정보는 시스템 환경 변수로 처리</strong>하면 컨테이너 실행 시 결정할 수 있다.</p>
<p>SpringBoot의 경우, 이 프레임워크가 자체적으로 환경 변수를 찾아서 값을 결정해준다. 하지만 Nginx이미지 같은 경우는 이런 기능이 존재하지 않기 때문에 직접 nginx.conf 파일의 값을 수정해야 한다. 
<br></p>
<h3 id="실습-3">실습</h3>
<pre><code class="language-docker">1. easydocker/leafy/leafy-frontend 디렉터리 이동 및 소스코드 상태 변경
git reset --hard HEAD &amp;&amp; git clean -fd 
git switch 03-proxy
# 또는 git switch 04-dynamicconfig</code></pre>
<pre><code class="language-docker">         location /api/ {
        ~~proxy_pass http://leafy:8080;~~
        proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT};
        }</code></pre>
<p>→ 기존 nginx.conf 파일 수정</p>
<pre><code class="language-docker">FROM node:14 AS build
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.21.4-alpine
# 소스코드의 nginx.conf 파일을 template 파일로 복사 기본 환경 변수 지정
COPY nginx.conf /etc/nginx/conf.d/default.conf.template 
ENV BACKEND_HOST leafy
ENV BACKEND_PORT 8080

# 컨테이너 실행시 자동으로 실행될 스크립트 지정
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

COPY --from=build /app/dist /usr/share/nginx/html

EXPOSE 80
ENTRYPOINT [&quot;docker-entrypoint.sh&quot;] # 스크립트 실행 처리
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;] # docker-entrypoint.sh의 옵션 값으로 제공됨 </code></pre>
<p>/etc/nginx/conf.d/default.conf.template 에 복사된 파일은 바로 사용되진 않고, 환경 변수의 내용을 변경한 후 설정파일로 저장하기 위한 템플릿 역할을 하는 파일이다.</p>
<p>ENV BACKEND_HOST leafy
ENV BACKEND_PORT 8080</p>
<p>-e 옵션으로 지정할 수 있지만, -e를 주지 않았을 때 사용되는 기본 값을 설정해준다. </p>
<p>→ 실제 실행되는 명령어는 <a href="http://docker-entrypoint.sh">docker-entrypoint.sh</a> nginx -g daemon off; </p>
<p><a href="http://docker-entrypoint.sh">docker-entrypoint.sh</a> 생성</p>
<pre><code class="language-docker">#!/bin/sh
# 오류가 발생했을 때 스크립트를 중단하도록 설정 
set -e

# default.conf.template 파일에서 환경 변수를 대체하고 결과를 default.conf에 저장
# 템플릿 파일과 환경 변수를 읽은 뒤 nginx.conf 파일을 구성하여 nginx 설정으로 복사
envsubst &#39;${BACKEND_HOST} ${BACKEND_PORT}&#39; &lt; /etc/nginx/conf.d/default.conf.template &gt; /etc/nginx/conf.d/default.conf

# 다음 명령어를 실행
# nginx -g daemon off 명령을 이 엔트리 포인트에 옵션으로 줬는데, &quot;$@&quot;가 옵션으로 제공받은 값 실행하는 부분이다. 
exec &quot;$@&quot;</code></pre>
<p>→ <a href="http://docker-entrypoint.sh">docker-entrypoint.sh</a> 파일에 nginx-demonoff 라는 값을 옵션으로 제공하면 먼저 envsubst 명령을 사용해 환경 변수를 읽어서 환경 설정파일을 만든 다음에 옵션으로 제공받은 명령어를 통해서 웹 서버를 실행시킨다. </p>
<pre><code class="language-docker">1. 이미지 빌드
docker build -t leafy-front:4.0.0-env .

2. network 가 제대로 생성되어 있는지 확인
docker network create leafy-network

3. 애플리케이션 구동 및 테스트, 백엔드 애플리케이션의 접속 도메인 변경
localhost:80 접속하여 정상 접속 확인
(MacOS) docker run -d --name leafy-postgres -v mydata:/var/lib/postgresql/data --network leafy-network devwikirepo/leafy-postgres:1.0.0 
(Windows) docker run -d --name leafy-postgres -v mydata://var/lib/postgresql/data --network leafy-network devwikirepo/leafy-postgres:1.0.0 
docker run -d -e DB_URL=leafy-postgres --name leafy-backend --network leafy-network devwikirepo/leafy-backend:1.0.0
docker run -d -e BACKEND_HOST=leafy-backend -p 80:80 --name leafy-front --network leafy-network leafy-front:4.0.0-env

4. 설정 파일에 환경 변수가 적용되었는지 확인
docker exec leafy-front cat etc/nginx/conf.d/default.conf

5. 환경 정리
docker rm -f leafy-postgres leafy-backend leafy-front</code></pre>
<p>→ 이전과 달라진 점은 컨테이너명이 leafy가 아니라 leafy-backend로 수정된 것이다. 환경변수 처리가 되지 않았다면 proxy 설정에는 leafy가 되어있기 때문에 소스코드에서 이 파일의 내용을 수정하고 새로운 이미지를 빌드해야한다. 하지만 환경변수 처리를 했기 때문에 환경변수 값에 백엔드 애플리케이션의 HOST를 옵션으로 지정해주면 된다. </p>
<p><br><br><br></p>
<h2 id="postgresql-이중화-db-구성">PostgreSQL 이중화 DB 구성</h2>
<h4 id="서버-이중화다중화">서버 이중화(다중화)</h4>
<ul>
<li>단일 서버 구성 시 단일 서버에 장애가 생기면 전체 서비스의 장애로 이어진다.</li>
<li>같은 역할을 하는 서버가 두 대 이상 있다는 것을 의미한다.</li>
<li>서버 이중화(Redundancy) 구성 시 하나의 서버가 실패해도 다른 서버가 동일한 역할을 수행하여 고가용성(가용성이 높다)을 보장한다.</li>
</ul>
<p>웹서버나 WAS 서버 같은 경우에 서버의 상태가 없게 관리할 수 있기 때문에 이중화를 하지만 DB 서버는 데이터의 상태가 있기 때문에 이중화 서버를 구성하는 것이 까다롭다. </p>
<h4 id="db-이중화-구성">DB 이중화 구성</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/cdd75cae-f481-48d6-ae2a-1836fffcfd98/image.png" alt=""></p>
<ul>
<li><strong>동시에 같은 볼륨을 사용</strong>하거나, <strong>각각의 컨테이너에 별도의 볼륨을 연결</strong>할 수 있다.</li>
<li>동시에 하나의 볼륨을 사용하는 경우는 하나의 볼륨에 마운트해서 동시에 같은 스토리지를 바라보도록 구성한다.</li>
<li>동시에 같은 볼륨을 사용하면 구성이 간단하지만 볼륨에 문제가 생길 경우 대처가 어렵다.</li>
<li>동시에 같은 볼륨을 사용하면 볼륨의 성능에 부하가 생길 수 있다.</li>
<li>각각의 컨테이너에 별도의 볼륨을 연결하면 안정적이지만 볼륨이 독립적으로 존재하기 때문에 데이터의 싱크를 맞추는 처리(동기화)를 별도로 해야 한다.<br>

</li>
</ul>
<h4 id="db서버에서-각-볼륨을-가진-컨테이너가-데이터를-동기화하는-방법">DB서버에서 각 볼륨을 가진 컨테이너가 데이터를 동기화하는 방법</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/3927027b-54ab-450f-8fb8-d098f8958433/image.png" alt=""></p>
<ul>
<li><strong>프라이머리-스탠바이</strong>의 경우 하나의 프라이머리 서버에 여러 개의 스탠바이 서버를 연결할 수 있다. 새로운 데이터를 입력하는 쓰기 작업은 프라이머리 서버에서만 가능하다. 쓰기 작업을 수행하면, 새롭게 생성한 데이터들은 즉시 스탠바이 서버의 볼륨으로 복제된다. 프라이머리 서버에 연결된 스탠바이 서버는 읽기 전용으로만 사용되며, 읽기 전용 스탠바이 서버를 여러 대 사용할 수 있다.</li>
<li><strong>프라이머리- 프라이머리</strong> 복제 구조의 경우 모든 서버에 읽기/쓰기 작업을 수행한다. 여러 서버에서 동시에 쓰기작업이 일어나기 때문에 동기화 구성 작업이 복잡하다.</li>
</ul>
<h3 id="실습-4">실습</h3>
<p>프라이머리-스탠바이로 실습한다.</p>
<pre><code class="language-docker">1. easydocker/leafy/leafy-postgres 디렉터리 이동 및 소스코드 상태 변경
git reset --hard HEAD &amp;&amp; git clean -fd
git switch 05-redundancy

2. 테스트용 네트워크 생성
docker network create postgres</code></pre>
<pre><code class="language-docker">3. 프라이머리 노드 실행
docker run -d \
  --name postgres-primary-0 \
    --network postgres \ # Windows는 //bitnami/postgresql
    -v postgres_primary_data:/bitnami/postgresql \
    -e POSTGRESQL_POSTGRES_PASSWORD=adminpassword \ ---------------- postgres 사용자(슈퍼유저)의 비밀번호 
    -e POSTGRESQL_USERNAME=myuser \ ------------------데이터베이스 사용자 이름
    -e POSTGRESQL_PASSWORD=mypassword \ ------------------ 데이터베이스 사용자 패스워드 
    -e POSTGRESQL_DATABASE=mydb \ ------------------데이터베이스 명
    # 프라이머리 스탠바이 구조를 구성하기 위한 환경변수 추가 지정
    -e REPMGR_PASSWORD=repmgrpassword \ --------- Repmgr 패스워드(레플리케이션 관리용) 
    -e REPMGR_PRIMARY_HOST=postgres-primary-0 \ ----------------- 프라이머리 노드의 호스트 명 
    -e REPMGR_PRIMARY_PORT=5432 \ ------------ 프라이머리 노드의 포트 
    -e REPMGR_PARTNER_NODES=postgres-primary-0,postgres-standby-1:5432 \ ---------- 통신할 노드 목록 
    -e REPMGR_NODE_NAME=postgres-primary-0 \ ---------------- 현재 노드의 이름 
    -e REPMGR_NODE_NETWORK_NAME=postgres-primary-0 \ ------- 현재 노드의 도메인명 
    -e REPMGR_PORT_NUMBER=5432 \ ------------------------- 현재 노드의 포트 
    bitnami/postgresql-repmgr:15 # 이미지는 bitnami에서 제공하는 postgresql 15버전 사용</code></pre>
<p>→ 프라이머리의 호스트와 포트를 지정하고 파트너 노드 쪽에 자신의 컨테이너 명뿐만 아니라 PostgreSQL 스탠바이 컨테이너도 지정한다. </p>
<pre><code class="language-docker">4. 스탠바이 노드(postgres-standby-1) 실행
docker run -d \
  --name postgres-standby-1 \
    --network postgres \  # Windows는 //bitnami/postgresql
    -v postgres_standby_data:/bitnami/postgresql \
    -e POSTGRESQL_POSTGRES_PASSWORD=adminpassword \
    -e POSTGRESQL_USERNAME=myuser \
    -e POSTGRESQL_PASSWORD=mypassword \
    -e POSTGRESQL_DATABASE=mydb \
    -e REPMGR_PASSWORD=repmgrpassword \
    -e REPMGR_PRIMARY_HOST=postgres-primary-0 \
    -e REPMGR_PRIMARY_PORT=5432 \
    -e REPMGR_PARTNER_NODES=postgres-primary-0,postgres-standby-1:5432 \
    -e REPMGR_NODE_NAME=postgres-standby-1 \
    -e REPMGR_NODE_NETWORK_NAME=postgres-standby-1 \
    -e REPMGR_PORT_NUMBER=5432 \
    bitnami/postgresql-repmgr:15 # 이미지는 bitnami에서 제공하는 postgresql 15버전 사용</code></pre>
<p>→ 옵셥 값이나 사용하는 이미지는 이전의 프라이머리 컨테이너와 동일하지만 컨테이너의 이름, 볼륨 명이 다르다. </p>
<p>쓰기가 가능한 프라이머리 노드에서 테이블을 생성하고 데이터 삽입</p>
<pre><code class="language-docker">5. SHELL1, SHELL2 각 컨테이너의 로그 확인
docker logs -f postgres-primary-0
docker logs -f postgres-standby-1

6. 프라이머리 노드에 테이블 생성 및 데이터 삽입
docker exec -it -e PGPASSWORD=mypassword postgres-primary-0 psql -U myuser -d mydb -c &quot;CREATE TABLE sample (id SERIAL PRIMARY KEY, name
VARCHAR(255));&quot;
docker exec -it -e PGPASSWORD=mypassword postgres-primary-0 psql -U myuser -d mydb -c &quot;INSERT INTO sample (name) VALUES (&#39;John&#39;), (&#39;Jane&#39;),
(&#39;Alice&#39;);&quot;

7. 스탠바이 노드에 데이터가 동기화되어 있는지 확인
docker exec -it -e PGPASSWORD=mypassword postgres-standby-1 psql -U myuser -d mydb -c &quot;SELECT * FROM sample;&quot;

8. 환경 정리
docker rm -f postgres-primary-0 postgres-standby-1
docker volume rm postgres_primary_data postgres_standby_data
docker network rm postgres</code></pre>
<p><br><br><br></p>
<h2 id="컨테이너-애플리케이션-최적화">컨테이너 애플리케이션 최적화</h2>
<p>모든 애플리케이션은 CPU와 메모리를 사용한다. 일반적인 프로세스는 사용량이 별도로 제한되어 있지 않기 때문에 프로세스를 실행시키면 하드웨어의 모든 리소스를 사용할 수 있다. 하지만 도커는 가상화 기술이고, 컨테이너를 격리된 공간에서 실행하기 때문에 <strong>프로세스가 사용 가능한 리소스를 제한할 수 있다.</strong></p>
<h4 id="컨테이너-리소스를-제한하는-방법">컨테이너 리소스를 제한하는 방법</h4>
<p>docker run</p>
<ul>
<li><p>--cpus={CPUcore수}</p>
<p>  컨테이너가 사용할 최대 CPU코어 수 정의 (호스트 머신의 CPU 성능에 상대적)</p>
</li>
<li><p>--memory={메모리용량}</p>
<p>  컨테이너가 사용할 최대 메모리 정의 (b, k, m, g 단위로 지정 가능)</p>
</li>
</ul>
<br>

<p>컨테이너의 리소스(CPU, 메모리, 네트워크 디스크) 사용량 조회</p>
<pre><code class="language-docker">docker stats (컨테이너명/ID)</code></pre>
<p>HOSTOS에서 발생하는 이벤트(생성, 종료 등) 로그 조회</p>
<pre><code class="language-docker">docker events</code></pre>
<br>

<h3 id="실습-5">실습</h3>
<pre><code class="language-docker">1.리소스 제약이 없는 상태로 컨테이너 실행
docker run --help
# 일반 컨테이너 실행
docker run -d --name no-limit nginx

2.컨테이너의 메타데이터 확인
# inspect 중 Memory Cpus 필터링 -&gt; 0으로 나옴 (제한 없음 의미)
docker inspect no-limit | grep -e Memory -e Cpus

3.리소스 제약이 있는 상태로 컨테이너 실행 (0.5 Core / 256M Memory)
docker run -d --name with-limit --cpus=0.5 --memory=256M nginx

4.컨테이너의 메타데이터 확인
docker inspect no-limit | grep -e Memory -e Cpus

5.실습 컨테이너 삭제
docker rm -f no-limit with-limit</code></pre>
<h4 id="limit에-지정한-cpu보다-사용량이-초과할-경우-cpu-스로틀링-발생">LIMIT에 지정한 CPU보다 사용량이 초과할 경우 CPU 스로틀링 발생</h4>
<ul>
<li>컨테이너에 설정된 CPU LIMIT을 초과하는 CPU 사용이 감지되면, 시스템은 컨테이너의 CPU 사용을 제한</li>
<li>애플리케이션의 성능 저하 발생</li>
</ul>
<h4 id="limit에-지정한-memory보다-사용량이-초과할-경우">LIMIT에 지정한 MEMORY보다 사용량이 초과할 경우</h4>
<ul>
<li>OOM(Out Of Memory) Killer 프로세스가 실행되고 컨테이너가 강제로 종료</li>
</ul>
<h4 id="자바-가상-머신jvm-튜닝">자바 가상 머신(JVM) 튜닝</h4>
<ul>
<li>JVM(Java Virtual Machine)은 자바를 실행할 수 있는 환경이다.</li>
<li>자바 애플리케이션이 사용할 수 있는 메모리 영역인 힙(Heap) 메모리를 별도로 관리해야 한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/b0178a1f-14b4-486a-9f02-f801e8ea117b/image.png" alt=""></p>
<p>자바 애플리케이션이 실행될 때 자바가 동적으로 할당되는 공간을 힙 메모리라한다. 객체가 많이 사용될수록 힙메모리 영역이 많이 사용된다. 이 힙메모리 사용량은 보통 애플리케이션이 실행될 때 지정하는데, 전체 서버 메모리의 50~80%를 설정하는 것이 일반적이다. </p>
<p>컨테이너 리미트 값이 16GB이고 JVM의 힙메모리 지정된 25%인 4GB면 애플리케이션은 자기가 실행되는 서버의 환경을 충분하게 활용하지 못하는 것이다. 하지만 컨테이너에 지정된 리미트 값은 환경마다 다를 수 있고, 사용 중에 늘릴 수도 있다. 따라서 자바의 힙 메모리를 지정할 때는 컨테이너의 리미트 값도 함께 고려해야 한다.</p>
<p>자바에서는 자바 애플리케이션을 실행할 때 -Xmx라는 옵션으로 힙메모리의 최대 값을 지정할 수 있다. 하지만 이렇게 고정할 경우, 컨테이너의 리미트 값이 변경될 경우에 자바의 실행 명령도 함께 수정해야 한다.</p>
<pre><code class="language-docker">FROM openjdk:8-jre-alpine

# JVM 튜닝을 위한 환경 변수 추가
ENV JAVA_OPTS=&quot;-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap&quot;
JVM의 Heap 메모리를 컨테이너에 할당된 메모리에 맞추어 자동으로 조절합니다.
(Java 8u131 부터 지원, Java 10 이상에서는 기본 활성화)

WORKDIR /app 
COPY --from=build /app/build/libs/*.jar /app/leafy.jar

EXPOSE 8080
ENTRYPOINT [&quot;java&quot;]
CMD [&quot;-jar&quot;, &quot;leafy.jar&quot;]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 스토리지와 볼륨(컨테이너의 Stateless 와 Mount)]]></title>
            <link>https://velog.io/@garden-y/Docker-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80%EC%99%80-%EB%B3%BC%EB%A5%A8</link>
            <guid>https://velog.io/@garden-y/Docker-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80%EC%99%80-%EB%B3%BC%EB%A5%A8</guid>
            <pubDate>Mon, 27 May 2024 04:02:51 GMT</pubDate>
            <description><![CDATA[<h1 id="part7-스토리지와-볼륨">Part7. 스토리지와 볼륨</h1>
<h2 id="컨테이너의-상태state">컨테이너의 상태(State)</h2>
<p><img src="https://velog.velcdn.com/images/garden-y/post/26cc3dc7-534d-4619-88cb-57ec2b15fc78/image.png" alt=""></p>
<ul>
<li><strong>컨테이너는 상태 없음(Stateless)</strong> 이다. 컨테이너가 실행된 후 발생하는 모든 변경 사항은 컨테이너 레이어에만 있으며 컨테이너가 종료되면 변경 사항이 모두 사라진다.</li>
<li>컨테이너는 Stateless하기 때문에 쉽게 개수를 증가시킬 수 있고, 다른 환경에서도 빠르게 배포할 수 있다.</li>
<li>소프트웨어의 버전 등 컨테이너의 상태 변경이 필요한 경우 새로운 버전의 이미지를 만들어서 배포한다.</li>
<li>컨테이너는 상태가 없기 때문에 여러 대의 컨테이너를 여러 곳에 빠르게 배포할 수 있다.</li>
</ul>
<p>이미지는 <strong>읽기 전용 레이어</strong>이고, 이미지 실행 시 읽기 쓰기 레이어인 컨테이너가 추가된다. 컨테이너가 <strong>실행된 뒤 발생하는 모든 변경 사항은 추가된 컨테이너에 쌓이게 된다</strong>. 
<strong>컨테이너가 삭제될 경우 컨테이너 레이어도 삭제된다.</strong> 
기존 컨테이너를 실행 중인 상태에서 이미지를 변경할 경우도 실행 중인 컨테이너를 삭제하고, 다시 실행시켜야 한다.</p>
<p>→ 컨테이너를 실행할 때 한 번 지정한 이미지는 변경할 수 없고, 변경 사항이 적용된 새로운 이미지를 통해 새로운 버전의 컨테이너를 실행해야 한다. 
→ <strong>컨테이너 자체는 상태를 가지지 않고, 모든 상태는 이미지에 기록된다.</strong> 
→ 이미지에 컨테이너 레이어만 추가하면 바로 컨테이너를 실행시킬 수 있어서 컨테이너의 개수가 늘어나는 것이 작은 공간만 차지하며, 실행 속도가 빨라진다.
→ 트래픽이 증가해도 유연하게 대처할 수 있다. 
<br></p>
<h3 id="pet--cattle">Pet &amp; Cattle</h3>
<p><strong>클라우드 네이티브 구조에서 서버를 다루는 방법론</strong></p>
<ul>
<li><p>클라우드 네이티브 환경에서는 MSA 아키텍처에 따라 서버의 개수가 매우 많아진다.
→ 모던 애플리케이션의 요구사항을 충족시키기 위해 서버를 바라보는 관점도(서버 관리 방법론) 변화했다. </p>
</li>
<li><p>전통적인 서버 방법론은 <strong>서버 한 대를 중요하게 생각하는 Pet 방식</strong>이다.
  → 서버 한 대, 한 대를 소중하게 케어한다.(직접 관리)
  → 서버에 문제가 생기거나 종료되는 것은 서비스의 장애라 인식해 서버의 상태를 주시하고 서버를 관리한다. </p>
</li>
<li><p><strong>컨테이너를 활용한 서버 방법론은 Cattle 방식</strong>이다.
  → 서버를 빠르게 교체할 수 있으며 서버의 상태를 최대한 제거한다. 
  → 서버는 소모품이며, 서버 문제는 가능할 수 있기에 종료 시 새로운 서버를 빠르게 실행시켜 기존 서버를 대체한다.
  → 빠르게 서버를 늘리거나, 새 서버로 대체 시키려면 서버가 stateless 해야한다. </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/ed175b89-7a84-4713-b4f4-b16e66d9a89c/image.png" alt=""></p>
<p>→ Cattle에서 사용하는 외부 마운트가 바로 도커 볼륨
<br><br></p>
<h3 id="실습">실습</h3>
<p>mkdir index &amp;&amp; cd index → index 폴더 만들고 이동</p>
<p>echo Hello Volume! &gt; index.html → index.html 파일 생성</p>
<p>cat index.html , ls → 파일 확인</p>
<p>docker run -d --name my-nginx nginx → nginx 컨테이너 실행</p>
<p>docker cp index.html my-nginx:usr/share/nginx/html/index.html → 실습 폴더의 index.html 파일을 my-nginx 컨테이너로 복사</p>
<p>→ my-nginx 파일은 새로 작성한 index.html 파일로 대체되었을 것 </p>
<p>docker rm -f my-nginx → 실습용 컨테이너 삭제 </p>
<p>docker run -d --name my-nginx nginx → nginx 컨테이너 실행</p>
<p>docker cp my-nginx:usr/share/nginx/html/index.html indexfromcontainer.html → 재생성된 my-nginx 컨테이너의 index.html 파일을 실습폴더의  → 재생성된 my-nginx 컨테이너의 index.html 파일을 로 복사 </p>
<p>cat indexfromcontainer.html → 파일 확인</p>
<p>→Hello Volume이 아닌 default 내용을 확인할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/540dde14-56d0-4612-ae96-2112fdcd786b/image.png" alt=""></p>
<ol>
<li>컨테이너의 아이디도 다른 것을 확인할 수 있다.</li>
<li>컨테이너 삭제 시점엔 컨테이너가 가지고 있는 컨테이너의 모든 내용은 제거된다. </li>
<li>제거 후 다시 실행하면 기존 nginx 이미지 위에 새로운 읽기쓰기 레이어인 컨테이너 레이어가 추가된다. </li>
</ol>
<p>→ 컨테이너는 stateless 하기 때문에 삭제된다는 것은 그동안 가진 컨테이너의 모든 변경사항을 잃어버린다는 것을 의미한다. 
→ 실행한다는 것은 이미지에 저장된 상태를 그대로 출발한다는 것을 의미한다.
<br></p>
<h3 id="컨테이너의-stateless">컨테이너의 Stateless</h3>
<ul>
<li><p>컨테이너의 이미지는 한 번 지정된 후 변경되지 않는다. (불변성, Immutability)
  → 새로운 설정이나 패치가 필요한 경우 새로운 이미지를 만들어야 한다. </p>
</li>
<li><p>컨테이너는 언제든지 새로운 컨테이너로 대체할 수 있다.
  → 컨테이너는 이미지 기반으로 실행되고, 실제 상태는 이미지에 저장되어 있기 때문에 컨테이너를 실행하고 종료하는 것이 자유롭다. </p>
</li>
<li><p>컨테이너는 어떤 호스트에서든 컨테이너를 실행할 수 있다.
  → 호스트는 배포되는 환경을 의미한다. 개발, QA, 운영 서버 처럼 하나의 애플리케이션이 배포되는 여러가지 환경을 호스트라 할 수 있다.
  → 즉, 이미지만 있으면 환경의 제약없이 컨테이너를 실행할 수 있다. </p>
</li>
<li><p>컨테이너는 동일한 컨테이너를 여러 개 쉽게 생성해서 트래픽에 대응할 수 있다.</p>
</li>
<li><p>장애가 발생한 경우 새로운 컨테이너를 빠르게 시작할 수 있다.</p>
<br>

</li>
</ul>
<h4 id="컨테이너의-stateless-제약">컨테이너의 Stateless 제약</h4>
<ul>
<li><p>데이터를 영구적으로 저장하기 위해서는 데이터베이스 서버 사용이 필수이다.
  → 상태가 없기 때문에 저장 및 공유가 필요한 데이터는 무조건 외부에 저장해야 한다.</p>
</li>
<li><p>사용자 세션 정보나 캐시 같은 정보를 캐시 서버나 쿠키를 통해 관리한다. 파일이나 메모리에 저장하지 않아야 한다.</p>
</li>
<li><p>동일한 요청은 항상 동일한 결과를 제공해야한다. 서버마다 다른 응답을 제공하면 안된다.</p>
</li>
<li><p>환경 변수나 구성 파일을 통해 설정을 외부에서 주입할 수 있어야 한다.
   → 컨테이너를 구성할 때 컨테이너를 실행하는 시점에서 메타데이터의 값을 조정하면서 환경에 맞게 컨테이너를 구성할 수 있다. 이런 방식을 통해 이미지는 하나로 유지하면서 다양한 환경에서 이 이미지를 컨테이너로 활용할 수 있다. </p>
</li>
</ul>
<h2 id="도커-볼륨docker-volume">도커 볼륨(Docker Volume)</h2>
<h4 id="컨테이너의-영속성persistence">컨테이너의 영속성(Persistence)</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/1b3f11dc-11b9-4627-aa18-56613006f93f/image.png" alt=""></p>
<ul>
<li><p>컨테이너가 삭제되거나 재생성될 경우 컨테이너레이어가 초기화된다.</p>
</li>
<li><p>but 서버를 운영하다보면 <strong>데이터를 유지해야하는 경우</strong>가 있고, 이것은 IT 환경에서는 <strong>영속성이 있다</strong>고 한다.</p>
</li>
<li><p>컨테이너 환경에서는 같은 서버의 대수가 여러 개 존재한다. (서버가 이중화되어 있다.)</p>
<p>  → 트래픽이 증가할 때 마다 컨테이너의 대수 증가로 트래픽을 처리한다. </p>
</li>
</ul>
<p>DB이미지로 실행된 컨테이너 1,2,3이 있고, 클라이언트의 요청이 로드밸런싱이라는 네트워크 처리를 거쳐서 데이터베이스 1,2,3 컨테이너에게 전달된다.</p>
<p>로드밸런싱은 한 서비스의 트래픽을 여러 대의 서버로 분산해주는 네트워크 기술이다. 여기서 A라는 상품의 금액을 여러 번 조회하면 요청이 로드밸런싱 되어서 여러 개의 컨테이너에 상품조회 요청이 된다. 이때, 컨테이너마다 다르면 문제가 된다. 따라서 영속성이 필요한 데이터는 같은 종류의 모든 컨테이너가 함께 공유하고 있어야 한다. </p>
<p>도커는 <strong>영속성이 필요한 데이터를 위해서 도커 볼륨</strong>이라는 기능을 제공한다. <strong>컨테이너 자체는 상태를 가지지 않지만 상태가 필요한 데이터는 외부 공유 저장소에 저장해둔다.</strong> 도커의 볼륨 기능을 사용하면 <strong>컨테이너가 데이터를 외부에 저장하고 다른 컨테이너들과 공유할 수 있다.</strong> 컨테이너의 모든 데이터를 저장하는 것이 아닌, <strong>컨테이너의 특정 폴더를 공유용 폴더</strong>로 만들 수 있다. </p>
<p>이때, <strong>컨테이너의 폴더를 볼륨에 마운트한다</strong>고 표현한다.
<br></p>
<h3 id="마운트-mount">마운트 (Mount)</h3>
<p>USB를 PC에 연결하면 새로운 드라이브가 만들어진다. 이를 사용해서, 데이터를 저장할 수 있고, 다른 PC와 공유할 수 있다.</p>
<p>USB는 물리적으로 연결하는 장치이고, 동일한 역할을 하는 것이 네트워크 파일시스템 NFS이다. NFS만 사용하면 네트워크에만 연결되어 있으면 USB와 동일하게 데이터를 외부에 저장할 수 있다. </p>
<p>한 번에 한대만 연결가능한 USB와 다르게 네트워크 <strong>파일 시스템에는 동시에 많은 PC를 연결할 수 있다.</strong></p>
<p>NFS는 PC의 특정 폴더를 이 NFS에 마운트 시킬 수 있다. 스토리지의 종류에 따라 마운트되는 단위가 달라진다. 이렇게 연결할 때마다 새로운 드라이브가 추가되는 것처럼 드라이브 단위로 마운트가 될 수 있고, 특정한 디렉토리만 마운트 시킬 수도 있다. </p>
<h4 id="마운트란">마운트란?</h4>
<ul>
<li>컴퓨터의 특정 디렉터리를 외부 저장소와 연결한다는 것을 말한다.</li>
</ul>
<h4 id="도커-볼륨은">도커 볼륨은?</h4>
<ul>
<li>컨테이너 실행 시 볼륨을 컨테이너의 내부 경로에 마운트할 수 있다.</li>
<li>USB처럼 데이터를 외부에 보관하는 기능을 제공한다.</li>
</ul>
<p>컨테이너들은 도커 볼륨을 컨테이너의 특정 경로에 마운트해서 사용한다. DB 컨테이너를 실행하면, var/lib/postgresql/data에 실제 저장된다. 이 경로를 도커의 볼륨에 마운트한다고 하면, 컨테이너가 실행된 후 이 경로에 저장하는 파일들은 <strong>컨테이너 레이어에 저장되는 것이 아니라 마운트되어 있는 외부 볼륨에 저장</strong>된다. </p>
<p>여러 개의 컨테이너가 <strong>동일한 경로에 마운트(같은 볼륨)했을 경우 모두 동일한 데이터를 제공</strong>한다. <strong>컨테이너가 삭제, 생성 되어도 볼륨의 데이터는 그대로 남아있기 때문에 데이터 영속성을 보장</strong>할 수 있다.</p>
<p>도커의 볼륨을 컨테이너의 디렉터리로 마운트해서 실행</p>
<pre><code class="language-docker">docker run -v {도커의 볼륨명}:{컨테이너의 내부 경로}
docker run -v volume1:/var/lib/postgresql/data</code></pre>
<p><img src="https://velog.velcdn.com/images/garden-y/post/3c778c3a-b04b-451a-8ebb-2101d36efd15/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/f3504a2b-eff1-400f-867f-310fb49937ae/image.png" alt=""></p>
<ul>
<li>컨테이너가 삭제되어도 볼륨은 남아 있다.</li>
<li>컨테이너가 실행 시 다시 마운트할 수 있다.</li>
<li>하나의 컨테이너가 여러 개의 볼륨을 마운트(사용)할 수 있다. (하나의 컴퓨터에 여러개의 USB를 꽂을 수 있는 것)</li>
<li>여러 개의 컨테이너가 하나의 볼륨을 공유할 수도 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/1db63610-2043-4dad-89ab-d99e2a8dae9e/image.png" alt=""></p>
<p>도커 볼륨을 마운트할 때 일반적으로 <strong>도커 볼륨의 이름을 지정</strong>한다. 이 볼륨의 Host OS의 데이터를 저장하는 경로를 volumes/볼륨이름 이다. 하지만 실제로 도커가 경로를 자동으로 관리하고, 도커가 실행되는 가상 머신 안에서 되기때문에 이 경로에 사용자가 직접 접근하기 어렵다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/e7ead6a9-d732-47da-bbf5-5ef6b0eee6f9/image.png" alt=""></p>
<p>Host OS에서 직접 데이터를 관찰하고 싶은 경우엔 <strong>Host OS의 경로를 직접 지정할 수 있는 Bind Mounts</strong> 기능을 사용하면 된다.</p>
<p>바인드 마운트</p>
<pre><code class="language-docker">-v 마운트할 경로:컨테이너 내부 경로
-v /data/mypostgres:/var/lib/postgresql/data</code></pre>
<ul>
<li>Host OS에서 볼륨의 <strong>데이터에 더 쉽게 접근</strong>할 수 있다.</li>
<li><strong>볼륨이 별도로 만들어지지 않는다.</strong></li>
<li><strong>볼륨을 통해서 관리하는 것이 좀 더 일반적</strong>이기 때문에 Bind Mounts 기능은 <strong>직접 디렉터리 내용을 관찰해야 하는 디버깅 같은 상황에서 활용</strong> 가능하다.<br>

</li>
</ul>
<h3 id="도커-볼륨-관련-명령어">도커 볼륨 관련 명령어</h3>
<p>볼륨 리스트 조회</p>
<pre><code class="language-docker">docker volume ls</code></pre>
<p>볼륨 상세 정보 조회</p>
<pre><code class="language-docker">docker volume inspect 볼륨명</code></pre>
<p>볼륨 생성</p>
<pre><code class="language-docker">docker volume create 볼륨명</code></pre>
<p>볼륨 삭제</p>
<pre><code class="language-docker">docker volume rm 볼륨명</code></pre>
<p>→ 컨테이너에 마운트되어 있는 볼륨은 삭제할 수 없다. </p>
<h3 id="실습-1">실습</h3>
<ol>
<li><p>데이터 저장 및 공유 Postgres</p>
<ul>
<li><p>volume1 생성 후 PostgreSQL 컨테이너 실행하고, PostgreSQL 데이터 경로와 마운트 시킨다.</p>
</li>
<li><p>데이터 삽입 후 PostgreSQL 컨테이너를 삭제한다.</p>
</li>
<li><p>새로운 컨테이너 생성 시 기존 데이터 영역을 재활용할 수 있다.</p>
<p>docker volume create mydata → 볼륨 생성</p>
<p>docker volume ls → 볼륨 확인</p>
<p>docker volume inspect mydata → 생성된 날짜, 볼륨  드라이버, 마운트 포인트와 이름 등.. 을 확인. Driver가 local이면 실제 데이터가 호스트 OS에 저장됨을 의미, Mountpoint는 저장되는 실제 경로이고, 리눅스에서 관찰 가능하다. (맥, 윈도우는 가상 머신형태로 실행되기 때문)</p>
<p>docker run -d --name my-postgres -e POSTGRES_PASSWORD=password -v mvdata:/var/lib/postgresql/data postgres:13 → 볼륨을 마운트한 postgres 컨테이너 실행</p>
<p>docker container inspect my-postgres → 컨테이너 상세 출력, Mounts에서 확인할 수 있음</p>
<p>docker exec -it my-postgres psql -U postgres -c “CREATE DATABASE mydb;” → SQL문 실행으로 mydb(db server) 데이터 베이스 생성 </p>
<p>docker exec -it my-postgres psql -U postgres -c “\list” → DB 확인 </p>
<p>docker rm -f my-postgres → 컨테이너 삭제</p>
<p>docker volume ls → 도커 볼륨 확인, 볼륨은 그대로 남아있음 </p>
<p>docker run -d --name my-postgres2 -e POSTGRES_PASSWORD=password -v mvdata:/var/lib/postgresql/data postgres:13 → 볼륨을 다시 마운트해서 postgres2 컨테이너 실행 </p>
<p>docker exec -it my-postgres2 psql -U postgres -c “\list” → DB 확인, mydb를 확인할 수 있음</p>
<p>→ 도커의 볼륨을 사용해서 컨테이너의 라이프사이클에 영향을 받지 않고 데이터의 영속성을 유지할 수 있다. </p>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p>Nginx index.html 공유 (바인드 마운트)</p>
<ul>
<li><p>호스트 OS의 디렉터리를 2개의 컨테이너가 마운트하여 공유한다.</p>
</li>
<li><p>nginxA에서 변경한 파일이 nginxB도 변경된다.</p>
</li>
<li><p>바인드마운트는 별도의 볼륨이 만들어지지 않는다.</p>
</li>
<li><p>호스트 OS에서 도커가 마운트한 경로를 직접 관찰하고 수정할 수 있다.</p>
<p>mkdir index, cd index, pwd → index 폴더 생성 후 경로 확인</p>
<p>docker run -d -p 8000:80 --name my-nginx-a -v {pwd결과경로}:/usr/share/nginx/html nginx → node.js 임시 컨테이너 실행 </p>
<p>docker run -d -p 8001:80 --name my-nginx-b -v {pwd결과경로}:/usr/share/nginx/html nginx → node.js 임시 컨테이너 실행 </p>
<p>docker volume ls → bind mounts 했기 때문에 실제 조회되는 볼륨 없음 </p>
<p>ls 하면 아무것도 없기 때문에 8000, 8001 접속 시 403에러</p>
<p>echo Hello Volume! &gt; index.html → 하면 두포트 접속시 해당 화면에 출력됨</p>
<p>docker exec -it my-nginx-a /bin/bash → my-nginx-a 컨테이너 shell 접근 </p>
<p>컨테이너 셸에서 ls /usr/share/nginx/html → index.html 을 확인가능 </p>
<p>echo Bye Volume! &gt; /usr/share/nginx/html/index.html 컨테이너 안에서 index.html 파일 덮어쓰기</p>
<p>cat /usr/share/nginx/html/index.html → 변경된 것 확인 </p>
<p>exit으로 컨테이너 shell 나와서 cat index.html → 여기도 변경된 것을 확인 </p>
<p>→ 8000, 8001 모두 변경된 것 확인</p>
</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 도커 가상 네트워크]]></title>
            <link>https://velog.io/@garden-y/Docker-%EB%8F%84%EC%BB%A4-%EA%B0%80%EC%83%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@garden-y/Docker-%EB%8F%84%EC%BB%A4-%EA%B0%80%EC%83%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Fri, 24 May 2024 18:45:45 GMT</pubDate>
            <description><![CDATA[<h1 id="part6-네트워크">Part6. 네트워크</h1>
<h2 id="네트워크-기본">네트워크 기본</h2>
<h4 id="네트워크란">네트워크란?</h4>
<p><strong>여러 개의 장치들이 서로 연결되어서 정보를 주고받을 수 있는 시스템</strong>이다.인터넷에서 정보를 검색할 때도 네트워크를 통해서 정보를 주고받을 수 있다. <strong>네트워크는 기계와 기계 사이를 랜선이라는 케이블을 통해 물리적으로 연결한다.</strong> 이 물리적인 케이블로 전기신호를 통해서 정보를 주고받을 수 있다. (like 집과 도로)</p>
<p>전기 신호로 만들어진 정보가 인터넷 선을 타고 기기들 사이를 이동할 수 있다. 실제 바다 건너 미국에 있는 컴퓨터의 정보를 받아올 때도 아시아와 미국을 연결하는 해저 케이블을 통해 정보를 받아온다. </p>
<p>즉, <strong>전 세계의 전자기기들은 물리적인 인터넷 선으로 연결되어 있고, 이 선들이 그물망처럼 촘촘히 짜여져 있다고 해서(net + work) 네트워크</strong>라고 부르게 되었다. </p>
<p>네트워크에서 어떤 정보를 보내기 위해서는 목적지의 주소가 필요하다. IP 주소로 장치들의 위치를 관리한다. 따라서 컴퓨터가 인터넷에 연결될 때 IP주소를 할당 받는다. 이 IP 주소를 활용해서 컴퓨터나 기계들이 인터넷에서 서로를 찾고 통신할 수 있다. IP 주소는 8바이트에서 3자리 숫자 4개 조합으로 만들어진 주소이다. (IPv4)</p>
<p>범위의 고유한 값으로는 0.0.0.0 ~ 255.255.255.255 (이로 조합할 수 있는 IP주소는 43억개, 하지만 현재 거의 고갈된 상태.. IPv6가 나옴)이 있다. 주소 역할을 하려면 고유해야하기 떄문에 <strong>다른 IP와 중복될 수 없다.</strong> IP주소는 각각의 통신사가 관리한다. 고정적인 IP를 받아서 사용할 수 있으며, 변경될 수 도 있다. 일반적으로 가정용 인터넷은 동적 IP를 사용하기 때문에 시간이 지나면서 집에 할당된 IP가 바뀔 수도 있다. </p>
<p>보통 통신사의 인터넷에 가입하면 회선당 하나의 IP를 할당받아 사용한다. 본인 PC에 할당된 IP를 확인해보고 ping 명령어를 통해 다른 서버로 인터넷 신호를 보낼 수 있다. (터미널에서 ping 1.1.1.1 하면 상대방 서버에서 응답이 있는지 확인하는 명령이다.) </p>
<p>→ 네트워크는 기기 간의 정보를 주고받기 위한 통신망이다. 
→ 전 세계의 네트워크 신호는 IP 주소 체계를 사용해서 원하는 목적지로 찾아갈 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/630c7cb6-bbf3-4cfa-a157-6b3b5d6b5816/image.png" alt=""></p>
<p>회선당 공인IP가 하나씩 할당되는데, 하나의 인터넷 회선으로 스마트폰, 노트북, TV등 다양한 기기로 인터넷을 사용하고 있다. 이처럼 하나의 IP로 여러 기기가 인터넷을 사용하려면 사설 IP를 활용해야한다. </p>
<p>*<em>IP는 공인IP와 사설IP가 있다. *</em></p>
<p><strong>공인 IP는 집 주소처럼 인터넷 상에서 고유하다.</strong> 이 IP주소를 통해서 전세계의 기기들과 데이터를 주고받을 수 있다. </p>
<p><strong>사설 IP는 집 내부에서 방 번호를 나누는 것처럼 하나의 공인 IP를 나눠서 정의된다. 이 공인 IP안에서 사설 IP로 나눌 때는 네트워크 장비가 필요하다. 가정에서 일반적으로 공유기를 사용한다.</strong> (공유기의 1개의 WAN 포트는 외부에서 들어오는 인터넷 신호를 연결하고, 여러개의 LAN 포트를 통해 사설 IP(인터넷 신호)를 분배한다.) 사설IP는 공유기처럼 하나의 네트워크 장비 안에서만 고유하다. </p>
<p>→ IP는 외부에서 사용하는 공인 IP와 내부에서만 사용하는 사설 IP로 관리할 수 있다. 공인 IP를 공유기에 연결해서 여러 개의 사설 IP로 분리했고, 이 사설 IP들은 공유기에 연결되어 있는 TV, 컴퓨터, 스마트폰이 하나씩 받아서 사용할 수 있다. 이렇게 사설 IP를 사용하면 하나의 공인 IP만 가지고도 여러 기기들로 인터넷을 나눠 사용할 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/ec4e0aa5-73ce-48ef-9863-29e19a11d9d5/image.png" alt=""></p>
<ul>
<li>공인망 : 공인 IP를 사용하는 네트워크 통신망 (공인 IP끼리 통신)</li>
<li>사설망 : 기업 안에서 사설 IP를 사용하는 통신망 (공인망에서 라우터를 사용해서 만들어진 내부 네트워크망)</li>
<li>라우터는 기업의 공인 IP 주소를 각각의 사내 서버로 분배해주는 장비이다. (공유기와 비슷한 역할) 실제 기업의 네트워크 환경은 더 복잡한 계층 구조로 되어 있지만 이런 네트워크 장비들을 통해서 맨 아래 있는 실제 서버에 인터넷 선이 연결되고 사설망의 사설 IP가 할당된다. (가정보다 복잡할 뿐, 구조는 동일)
<br><br></li>
</ul>
<h3 id="네트워크의-인터페이스와-포트">네트워크의 인터페이스와 포트</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/71726c39-69d4-4348-9da1-77068703f372/image.png" alt=""></p>
<h4 id="네트워크-인터페이스란">네트워크 인터페이스란?</h4>
<ul>
<li>인터넷에 연결하기 위해서 컴퓨터에 장착하는 부품 중 하나이다.</li>
<li>네트워크 인터페이스는 IP를 가질 수 있다.</li>
<li>네트워크 인터페이스의 IP를 할당 받고, 인터넷에 연결하려면 네트워크 장비와 랜선으로 연결되어 있어야 한다. (무선 기능 지원시 연결하지 않고, 와이파이 연결로 가능)</li>
<li>하나의 기기는 한 개 이상의 네트워크 인터페이스를 장착할 수 있다. (유선과 무선이 다 되는 노트북의 경우 무선, 유선 인터페이스 두 개가 장착되어 있다.)</li>
</ul>
<p>→ 서버에 네트워크 인터페이스가 장착되어 있고, 이 네트워크 인터페이스에 할당된 IP로 데이터를 주고 받는다. </p>
<h4 id="포트란">포트란?</h4>
<p>하지만 서버는 여러 개의 소프트웨어가 실행된다. 이 중 어떤 소프트웨어로 데이터를 전달할지 지정하는 것이 바로 포트 넘버이다. IP뒤에 :포트번호 로 소프트웨어를 지정할 수 있다. 포트는 물리적으로 존재하진 않고, 서버 안에서 정의해서 사용할 수 있다. 네트워크를 사용하는 프로그램은 실행될 때 자신이 사용할 포트를 지정할 수 있다. </p>
<p>서버에서 실행중인 웹서버는 80(http), 443(https), SSH 통신은 22, FTP (파일 통신) 21 포트를 지정해야 한다. 이처럼 사전에 약속된 포트를 Well-Known Port라 한다.</p>
<p>애플리케이션 8080 포트처럼 개발자가 자신의 소프트웨어가 사용할 포트를 직접 지정할 수도 있다. </p>
<h3 id="아웃바운드와-인바운드">아웃바운드와 인바운드</h3>
<p>네트워크 통신은 크게 <strong>OutBound, InBound</strong>로 나눌 수 있다. </p>
<ul>
<li>아웃바운드 : 하나의 서버를 기준으로 했을 때 서버에서 출발하는 신호</li>
<li>인바운드 : 다른 클라이언트나 외부의 서버에서 출발해서 자신의 서버로 들어오는 통신</li>
</ul>
<p>외부서버와 내부서버의 통신</p>
<ul>
<li>외부 서버는 전 세계에서 유일한 공인 IP를 가지고 있는데, 내부 서버는 사설망안에서만 식별 가능한 사설 IP 주소를 가지고 있다.<br>

</li>
</ul>
<p>외부의 공인 IP와 내부의 사설 IP의 통신은 어떻게 될까?
→ *<em>NAT와 포트포워딩 기술 *</em></p>
<ul>
<li>네트워크 신호를 보낼 때 출발지와 목적지에 정보가 있어야 한다.</li>
<li>아웃바운드처럼 내부에서 시작돼 외부로 나가는 통신의 경우, 외부의 공인 IP가 유일한 주소이기 때문에 네트워크 안에서 목적지에 문제없이 도착할 수 있다.</li>
<li>외부서버에서 출발지로 응답을 보낼 경우, 내부에서만 사용하는 사설IP가 아닌 공인 IP로 와야한다. 이때 사설 IP까지 찾아오려면 NAT(Network Address Transration)라는 기술이다.<br>

</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/177c6016-8765-4c31-9a11-0e3249afc2bc/image.png" alt=""></p>
<h4 id="nat란">NAT란?</h4>
<ul>
<li>NAT는 <strong>공인 IP와 사설 IP를 Mapping해주는 기술</strong>이다. (자동 설정됨)</li>
<li><strong>공인 IP와 사설 IP의 매핑 테이블을 만들고 어떤 서버로 전달할지에 대한 정보를 기록</strong>한다.</li>
<li>사설망을 구성하는 라우터 장비는 모두 이 기능을 가지고 있다.</li>
<li>사설 IP마다 공인포트번호(랜덤 숫자)를 지정해 출발지를 공인IP:공인포트번호 로 보낸다.</li>
</ul>
<p>위의 경우, 사설망의 안쪽에 있는 서버에서 외부서버로 요청이 전달되었다가 외부 서버에서 응답이 돌아올 때의 트래픽 처리이다.</p>
<p>바깥쪽 서버에서 사설 IP로 직접 요청을 보내는 인바운드 요청의 경우엔 요청을 보낼 때 NAT 테이블의 규칙에 맞게 자동생성되는 공인포트번호로 지정해서 요청을 전달하는 방법도 있다. 하지만 보통은 외부에서 사내망으로 요청을 보내는 경우에는 포트를 랜덤으로 사용하는 것보다 미리 포트를 지정해 두는 것이 자연스럽다. </p>
<p>외부에서 사내 서버를 접근한다는 것은 사내 서버에서 제공하는 특정 서비스를 사용한다는 것을 의미한다. 내부 서버1이 웹서비스를 제공하면 80포트, 내부 서버 2번이 데이터베이스를 제공하면 5432포트로 접근하는 것처럼 랜덤한 포트가 아니라 사전에 정의되어 있는 포트를 제공하는 것이 더 자연스럽다.</p>
<p>NAT 테이블은 동적으로 정보를 관리하기 때문에 외부에서 서버를 접근하는데 혼란이 생길 수도 있다. </p>
<p>따라서 <strong>외부에서 사내망으로 접근할 경우 포트포워딩이라는 기술을 사용</strong>한다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/9996ea9b-645f-4176-9031-745d102a13df/image.png" alt=""></p>
<h4 id="포트포워딩이란">포트포워딩이란?</h4>
<ul>
<li><strong>사용자가 직접 NAT 테이블과 같은 맵핑 정보를 관리</strong>하는 것이다.</li>
<li>포트포워딩을 설정해두면 외부에서 공인 IP의 포트에 접근할 때, <strong>포트포워딩 룰에 맞는 사설 IP로 변환해서 트래픽을 전달</strong>한다.</li>
<li><strong>사용자가 지정해서 사용</strong>해야 한다.</li>
</ul>
<h3 id="dns">DNS</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/1f59cc4c-8ee6-42e0-bf1a-569863e926f4/image.png" alt=""></p>
<ul>
<li>Domain Name System</li>
<li>웹 브라우저에서 네이버, 구글 같은 웹사이트 접속 시 주소창에 접속할 주소를 입력한다. https:<a href="http://www.naver.com">www.naver.com</a> 을 도메인 주소라 한다.</li>
<li>도메인 주소를 사용하지 않아도, 웹 서버 IP주소를 입력하면 웹서버에 접속할 수 있다. 하지만 의미없는 숫자의 조합인 IP 주소보다는 의미를 가지고 있는 영어이름이 쉽고, 직관적이다.</li>
<li>사용자가 의미있는 도메인 주소를 사용해서 접근할 수 있도록 도메인 주소와 IP주소를 Mapping 해주는 매핑정보가 있다.</li>
<li>DNS 서버는 도메인과 IP 주소 데이터를 가지고 있는 시스템이다.</li>
<li>도메인 주소  ↔ IP 주소 , 이 과정은 PC에서 자동으로 처리된다.</li>
<li>실제 통신은 IP 주소로 이루어지지만 클라이언트는 사람이기 때문에 사람이 사용하기 편한 도메인 주소를 사용하는 것이 일반적이다.<br>


</li>
</ul>
<p>DNS 서버로 주소의 IP검색</p>
<pre><code class="language-docker">nslookup dns주소</code></pre>
<p>nslookup google.com
<br></p>
<h4 id="사내망-환경에서의-dns-서버">사내망 환경에서의 DNS 서버</h4>
<ul>
<li>일반적으로 엔터프라이즈 환경에서는 사내망에서 별도로 DNS 서버를 운영한다.</li>
<li>기존 DNS와 동일하나, 정보를 주고 받는 주체들이 사설망에 있는 내부 서버들로 한정되어 있다.</li>
<li>사설 IP와 도메인을 저장한 내부 DNS 서버는 사내 서버들끼리 도메인으로 통신할 수 있도록 정보를 제공한다.</li>
<li>외부 DNS 서버들끼리는 온라인 상에서 연동되고 동기화되기 때문에 어떤 DNS 서버를 사용해도 동일한 정보를 받을 수 있다. 하지만 내부 DNS 서버에서는 내부 DNS 서버에 등록된 정보만 얻을 수 있다.</li>
</ul>
<h4 id="내부-dns-서버에-없는-도메인은-아예-접속-불가">내부 DNS 서버에 없는 도메인은 아예 접속 불가?</h4>
<p>→ 내부 DNS에 <a href="http://google.com">google.com</a> 도메인 정보가 없으면 사내망 서버에서는 <a href="http://google.com">google.com</a> 도메인 정보를 사용할 수 없다. 하지만 이 부분은 내부 DNS 설정에 따라 다르게 작동한다. 내부 DNS 서버에 데이터가 없으면 내부 DNS 서버가 직접 외부 DNS 서버로 자기한테 없는 값을 물어볼 수 있다. (외부 연동 설정 여부에 따라 다르다.)</p>
<p><br><br></p>
<h2 id="도커-가상네트워크1">도커 가상네트워크(1)</h2>
<h3 id="가상-네트워크">가상 네트워크</h3>
<h4 id="컨테이너-가상화란">컨테이너 가상화란?</h4>
<p>→ <strong>서버 한 대를 여러 컨테이너로 격리하는 기술</strong>이다. 
<br></p>
<h4 id="가상-네트워크란">가상 네트워크란?</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/9f337997-3f94-4d31-94ff-554954eea2ed/image.png" alt=""></p>
<p>→ <strong>서버 한 대 안에서 여러 네트워크를 구성하는 기술</strong>이다.
→ 네트워크 망 안에서 컨테이너들이 서로 통신할 수 있고, 내부의 컨테이너와 바깥의 서버와도 통신할 수 있다.
따라서 가상 네트워크에 대해서 얘기할 때는 서버 한 대 안에 초점을 맞춰야 한다. 실습하는 PC도 가상화 기술을 사용하는 서버 한 대의 역할을 하기 때문에 구조도에 표시된 서버를 실습용 PC라 생각하면 된다. </p>
<p>가상 네트워크 아키텍처에서 바깥에 물리 네트워크가 어떻게 구성되어 있는지는 중요하지 않다. 어쨌든 물리 서버에서는 네트워크 인터페이스를 통해 공인 IP나 사설 IP가 할당되어 있을 것이다.</p>
<p>도커는 <strong>가상네트워크 기술을 활용해 컨테이너의 네트워크를 구성</strong>한다. 가상 네트워크는 실제로 인터넷 선이나 공유기가 없이 오직 한 대의 서버 내에서 논리적으로 정의되어 있는 네트워크이다.
<br></p>
<h4 id="가상네트워크의-구성">가상네트워크의 구성</h4>
<ol>
<li><p><strong>가상의 네트워크 브릿지 네트워크</strong>와 <strong>가상의 공유기인 도커 제로</strong>를 생성한다.</p>
<ul>
<li><p><strong>가상의 공유기 도커 제로를 도커에서는 브릿지</strong>라고 부른다.</p>
</li>
<li><p><strong>브릿지는 가상의 IP 주소를 할당</strong> 받는다. 일반적으로 172.17.0.1이다.</p>
</li>
<li><p>가상의 IP주소란 실제 존재하는 IP가 아닌 <strong>서버나 PC안에서 논리적으로 정의되어 있는 가상의 IP</strong>이다.</p>
<p>→ 도커의 브릿지 네트워크는 <strong>컨테이너들에게 IP를 할당해주고 컨테이너들끼리 통신할 수 있도록 만들어 준다</strong>.</p>
</li>
</ul>
</li>
<li><p>도커에서 컨테이너 실행하면 도커는 <strong>브릿지 네트워크의 IP주소 범위 내에서 컨테이너에 가상의 IP를 할당</strong>한다.</p>
<ul>
<li>공유기에 연결된 기기 한 대에 사설 IP가 할당되는 것과 동일하다.</li>
<li>차이는 물리적인 기계나 인터넷 선 없이 오직 소프트웨어적으로만 가상의 네트워크가 구성된다는 것이다.</li>
</ul>
</li>
<li><p><strong>같은 브릿지에서 생성된 네트워크는 브릿지를 통해서 통신할 수 있다.</strong> (컨테이너간의 통신 전달)</p>
</li>
</ol>
<p>→ <strong>논리적으로 네트워크를 구성하는 기술을 SDN</strong>(Software Defined Network) 라 한다. </p>
<br>

<p><img src="https://velog.velcdn.com/images/garden-y/post/f83cad3e-5afc-4310-a967-981a5236edd3/image.png" alt=""></p>
<h4 id="가상-네트워크에서-네트워크-신호의-전달">가상 네트워크에서 네트워크 신호의 전달</h4>
<ul>
<li>일반적 PC나 서버를 설치하면 물리적인 네트워크 인터페이스에 인터넷 선을 꽂아서 사용한다. 물리 인터페이스에 인터넷 선을 연결하면 기기의 공인 IP나 사설 IP를 할당받는다.</li>
<li>PC 위에서 도커를 설치하고 실행하면 도커는 <strong>가상의 인터페이스를 한 개 생성</strong>한다. 그리고 가상의 인터페이스는 호스트 OS에 만들어진다.</li>
<li>각 컨테이너를 실행할 때 컨테이너에 해당하는 <strong>가상 인터페이스들이 Veth라는 접두어로 호스트 OS에 만들어진다</strong>. 이때 Veth 뒤에 랜덤하게 고유번호가 할당된다.</li>
</ul>
<p>→ 호스트 OS에는 실제 존재하는 하드웨어인 물리 인터페이스 1개와 소프트웨어로 만들어진 가상의 인터페이스가 5개 만들어진 상태이다. </p>
<p>→ 이때 가상의 인터페이스로 전달되는 네트워크 신호는 각각의 인터페이스로 해당되는 컨테이너로 전달된다. ex) OS에서 Veth2 인터페이스로 신호를 보내면 Veth2 인터페이스는 컨테이너 2번으로 네트워크 신호를 전달한다.</p>
<p>→ 컨테이너끼리 요청을 보낼 때는 Veth1 → Veth2 인터페이스 여야 한다. 물리네트워크의 경우 도착지 IP에 맞게 네트워크를 통해 기기에 전달하는 것을 네트워크 장비들이 해준다. 하지만 가상 네트워크의 경우 별도의 장비가 없기에 소프트웨어적으로 트래픽을 전달해줘야 한다.</p>
<p>→ 가상의 인터페이스 간의 네트워크 패킷을 전달하는 규칙은 호스트OS의 커널 소프트웨어인 iptables에 정의된다. 
<br></p>
<h4 id="iptables란">iptables란?</h4>
<ul>
<li>Linux OS의 패킷 필터링 시스템으로 내부의 네트워크 트래픽 관리 및 제어한다.</li>
<li>서버 내부에서 네트워크 트래픽을 제어하고 라우팅 규칙을 정의한다.</li>
<li>내부에서 발생하는 네트워크 트래픽은 iptables에 정의된 대로 흘러간다.</li>
<li>도커는 컨테이너가 생성되거나 삭제될 때마다 호스트 OS의 iptables 규칙을 업데이트해서 Veth 인터페이스의 트패픽을 제어한다.</li>
</ul>
<p>→ 도커는 컨테이너 통신을 위해 브릿지 네트워크를 정의하고, 호스트 OS의 가상 인터페이스를 생성한다. 그리고 호스트 OS의 iptables 규칙을 관리하면서 가상 인터페이스들 간의 통신 규칙을 만든다. 따라서 사용자는 별도의 설정을 하지 않아도 같은 브릿지 네트워크에서 생성된 컨테이너들은 서로 통신할 수 있다. </p>
<p>→ 또한, 도커는 가상 네트워크 내부에서 여러 개의 브릿지 네트워크를 관리할 수 있다. 브릿지 네트워크 단위로 분리해 특정 컨테이너들끼리는 통신이 되지 않도록 구성할 수도 있다.</p>
<br>

<h3 id="실습">실습</h3>
<p>네트워크 리스트 조회</p>
<pre><code class="language-docker">docker network ls</code></pre>
<p>네트워크 상세 정보 조회</p>
<pre><code class="language-docker">docker network inspect 네트워크명</code></pre>
<p>네트워크 생성</p>
<pre><code class="language-docker">docker network create 네트워크명</code></pre>
<p>네트워크 삭제</p>
<pre><code class="language-docker">docker network rm 네트워크명</code></pre>
<p><img src="https://velog.velcdn.com/images/garden-y/post/bb711e25-d83d-44c7-bbc2-d1292576d23c/image.png" alt=""></p>
<p>도커를 설치하면 기본적으로 하나의 브릿지 네트워크가 있다.</p>
<p>docker network ls </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/53cf047d-e757-409f-8a5f-56ae45a0cfa2/image.png" alt=""></p>
<ul>
<li>기본적으로 도커를 사용하면 3개의 네트워크가 만들어진다.</li>
<li>DRIVER는 네트워크의 종류를 의미한다.</li>
</ul>
<p>docker network inspect bridge</p>
<ul>
<li>네트워크 이름과 아이디 생성일을 확인할 수 있다.</li>
<li>Subnet, Gateway를 확인할 수 있다.</li>
<li>Subnet은 네트워크 안에서 생성되는 컨테이너들이 할당받는 IP의 범위를 나타낸다. 172.17.0.0/16 → IP 범위를 숫자로 나타내는 CIDR 방식 (172.17.0.0~172.17.255.255)</li>
<li>Gateway는 도커 네트워크에 해당하는 도커 브릿지의 IP주소이다.</li>
</ul>
<p>docker network create --driver bridge --subnet 10.0.0.0/24 --gateway 10.0.0.1 second-bridge </p>
<ul>
<li>--driver 옵션으로 브릿지 네트워크임을 지정한다.</li>
<li>--subnet 옵션으로 네트워크가 사용할 IP 대역대를 지정한다. (10.0.0.0~10.0.0.255)</li>
<li>--gateway 옵션으로 bridge 주소를 10.0.0.1로 지정한다.</li>
</ul>
<p>docker run -d --name ubuntuA devwikirepo/pingbuntu</p>
<ul>
<li>실습에 사용할 ubuntuA 컨테이너를 생성한다.</li>
</ul>
<p>docker run -it --name ubuntuB devwikirepo/pingbuntu bin/bash</p>
<ul>
<li>ubuntuB 컨테이너 생성 후 shell에 접근한다.</li>
</ul>
<p>docker run -it --network second-bridge --name ubuntuC devwikirepo/pingbuntu bin/bash</p>
<ul>
<li>second-bridge 네트워크에 ubuntuC 컨테이너를 생성 후 shell에 접근한다.</li>
</ul>
<p>docker ps 하면 셋다 실행 중임을 알 수 있다.</p>
<p>docker container inspect ubuntuA → 컨테이너의 네트워크 정보를 조회할 수 있다.</p>
<p>ubuntuA, ubuntuB는 Gateway는 동일, IPAddress만 다르다.</p>
<p>ubuntuC는 Gateway, IPAddress가 다르다.</p>
<p>ping 명령어를 통해 확인해보면 ubuntuA, ubuntuB끼리는 통신 가능, ubuntuC는 불가능하다.</p>
<br>

<h2 id="도커-가상네트워크2">도커 가상네트워크(2)</h2>
<p>HostOS로의 네트워크 접근을 컨테이너로 포트포워딩</p>
<pre><code class="language-docker">docker run --name 컨테이너이름 -p 호스트OS포트:컨테이너포트 이미지이름</code></pre>
<p>→ 왼쪽은 호스트 OS 포트, 오른쪽은 컨테이너가 사용하는 포트를 지정한다.</p>
<p>→ 도커가 실행 중인 서버의 포트를 사용해서 컨테이너로 접근할 수 있다.</p>
<p>가상 네트워크 안에서 컨테이너를 실행하면 컨테이너들끼리는 통신이 가능하다. 하지만 사용자 PC인 호스트OS나 가상 네트워크 바깥에 있는 서버에서 이 컨테이너로 접근하려면 포트포워딩 기술을 사용해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/ab34f6dd-6f55-46d1-9ec7-6cd8f83d7485/image.png" alt=""></p>
<p>Nginx와 DB 컨테이너가 실행 중이고, 172.17.0.2, 172.17.0.3 IP로 실행 중이라고 하면, 공인망에서 사설망으로 네트워크 신호를 보낼 땐 NAT와 포트포워딩 기술을 활용하듯 여기도 동일하게 사용된다. </p>
<p>따라서 Nginx와 DB 컨테이너는 바깥쪽 192.168.0.20, 192.168.0.30로 네트워크 신호를 보낼 수 있다. NAT은 가상 네트워크가 알아서 설정하기 때문에 특별히 지정하지 않아도 내부의 컨테이너는 바쪽 외부망을 통해 다른 서버로 접근할 수 있다. 하지만 다른 서버로 접근하려면 실제로 도커가 실행 중인 물리 서버나 실습용 PC가 물리적인 네트워크로 연결되어 있어야지만 접근이 가능하다. </p>
<p>외부에서 접근할 경우 192.168.0.10에서 포트포워딩을 해서 특정 포트로 접근했을 때 사용자가 지정한 컨테이너로 전달하도록 포트포워딩을 등록해야 한다.  PC의 IP인 192.168.0.10의 8001 포트로 요청을 보내면 Nginx 컨테이너의 80포트로 요청이 전달된다. 호스트OS의 포트는 아무거나 지정해도 상관 없지만 중복은 안된다.
<br></p>
<h3 id="실습-1">실습</h3>
<pre><code class="language-docker">docker run -d --name nginx nginx
docker container inspect nginx</code></pre>
<ul>
<li>포트포워딩 없이 nginx 실행</li>
<li>nginx 컨테이너의 네트워크 정보 확인</li>
</ul>
<p>→ 컨테이너 IP는 172.17.0.2, 포트포워딩을 지정하지 않았기 때문에 컨테이너 IP를 치면 아무 응답이 없다. 172.17.0.2는 가상 네트워크망의 IP 대역이기 때문에 실제로 물리 네트워크에 속해 있는 호스트OS나 다른 서버에서는 접근할 수 없다. </p>
<p>→ 172.17.0.2는 내부의 가상 네트워크에서만 사용되는 IP이고, 실제로 물리적인 네트워크에서 접근하기 위해서 포트포워딩기술을 사용하는 것이다. </p>
<p>포트포워딩을 설정한 nginx 실행 </p>
<pre><code class="language-docker">docker run -d -p 8001:80 --name nginx2 nginx</code></pre>
<ul>
<li>nginx는 80포트여서 80으로 지정</li>
<li>8001 포트에 nginx 실행 및 <a href="http://localhost:8001">localhost:8001</a> 접속</li>
<li>nginx2는 컨테이너의 80포트로 포트포워딩 되어있어 호스트OS의 8001로 접근하면 내부 nginx2 컨테이너의 80포트로 전달된 것이다.</li>
</ul>
<pre><code class="language-docker">docker run -d -p 8002:3000 --name redColorApp --env COLOR=red devwikirepo/envnodecolorapp</code></pre>
<ul>
<li>8002 포트에 redapp실행 및 127.0.0.1:8002 접속</li>
<li><a href="http://localhost">localhost</a> == 127.0.0.1 완전히 동일한 주소를 의미한다.</li>
</ul>
<pre><code class="language-docker">docker run -d -p 8003:3000 --name blueColorApp --env COLOR=blue devwikirepo/envnodecolorapp</code></pre>
<ul>
<li>8003 포트에 blueapp실행 및 IP주소:8003 접속</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/45234936-4041-47ee-870a-bdece6ed4f2b/image.png" alt=""></p>
<p>로컬 호스트 말고 PC의 IP를 사용해서 접근해도 된다. </p>
<p>ifconfig en0 → 본인의 IP확인 </p>
<p>본인IP:포트번호 로 접속해도 들어가진다. </p>
<p>이 IP는 사설 IP이기 때문에 사설망의 IP를 사용하는 기기들은 본인IP:포트번호로 접근했을 때 이 웹서버 컨테이너로 접속할 수 있다.</p>
<pre><code class="language-docker">docker run -d -p 8003:3000 --name yellowColorApp --env COLOR=yellow devwikirepo/envnodecolorapp</code></pre>
<ul>
<li>8003 포트에 yelloapp 실행 시도</li>
<li>이미 8003으로 포트포워딩되어 있어서 에러남</li>
</ul>
<pre><code class="language-docker">docker run -d -p 8004:3030 --name greenColorApp --env COLOR=green devwikirepo/envnodecolorapp</code></pre>
<ul>
<li>8004 포트에 greenapp 실행 및 접속(애플리케이션과 다른 포트)</li>
<li>애플리케이션이 사용하지 않는 포트를 지정했을 경우는 컨테이너는 정상적으로 실행되나, 8004번으로 접근했을 때 이 포트포워딩된 주소는 컬러앱의 3030 포트로 전달되기 때문에 실제로 애플리케이션이 사용되지 않는 포트로 접근되어 정상적인 응답이 돌아오지 않는다.</li>
</ul>
<p>docker rm -f nginx redColorApp blueColorApp yellowColorApp greenColorApp → 실습 컨테이너 모두 삭제</p>
<p>정리</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/2690df53-d6ad-4e98-87dd-e24e2dc66158/image.png" alt=""></p>
<ul>
<li>포트포워딩을 설정하지 않으면 본인 서버나, 다른 서버 모두 접근 불가하다.</li>
<li>8001는 80포트로 지정됐기에 localhost나 127.0.0.1, 본인IP의 8001포트로 접근했을 때 nginx2의 응답이 가능하다</li>
<li>실습용 PC가 아니라 같은 사설망에 있는 다른 PC, 기기에서 접근하려면 본인IP:포트 로만 접근가능하다.
<br><br></li>
</ul>
<h3 id="가상-네트워크와-dns">가상 네트워크와 DNS</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/a8c54d5a-7457-4be0-8bc0-ea930323d608/image.png" alt=""></p>
<p>토커는 컨테이너들이 기본적으로 사용할 수 있는 DNS 서버를 제공한다. DNS 서버에는 IP주소와 도메인명이 저장되어 있다. 여기서 <strong>Domain은 컨테이너 이름으로 자동 저장</strong>된다. 따라서 컨테이너는 기본적으로 컨테이너의 IP가 아닌 컨테이너의 이름으로 서로 통신할 수 있다. </p>
<p>이 DNS서버는 외부의 DNS 서버와 연동되어 있어 구글같은 외부 도메인도 사용할 수 있다. 하지만 <strong>기본으로 생성되는 브릿지 네트워크는 DNS 기능이 제공되지 않고, 사용자가 직접 생성한 브릿지만 컨테이너의 이름을 통해서 통신할 수 있다</strong>.</p>
<p>→ <strong>도메인 주소를 사용하면 IP주소가 바뀌는 환경에서 다른 서버들이 영향받지 않고 연결이 일관적으로 이뤄진다</strong>는 장점이 있다. 
<br></p>
<h3 id="실습-2">실습</h3>
<pre><code class="language-docker">docker network create --driver bridge --subnet 10.0.0.0/24 second-bridge</code></pre>
<ul>
<li>새로운 네트워크 생성</li>
<li>docker network ls → second-bridge 생성 확인 가능</li>
</ul>
<pre><code class="language-docker">docker run -it --network second-bridge --name containerA devwikirepo/pingbuntu bin/bash</code></pre>
<ul>
<li>second-bridge에서 containerA 실행 및 shell 접근</li>
</ul>
<pre><code class="language-docker">cat /etc/resolv.conf</code></pre>
<ul>
<li>DNS서버 주소 확인</li>
<li>같은 네트워크의 컨테이너들은 IP가 아닌 컨테이너 이름으로 통신 가능</li>
</ul>
<pre><code class="language-docker">ping containerB</code></pre>
<ul>
<li>핑테스트 → 도메인 없음, 아직 containerB 생성 전</li>
</ul>
<pre><code class="language-docker">docker run -d --network second-bridge --name containerB devwikirepo/pingbuntu
docker inspect containerB</code></pre>
<ul>
<li>second-bridge에서 containerB 실행 및 containerB 상세 정보 확인 (새로운 셸에서 실행 할 것)</li>
</ul>
<pre><code class="language-docker">ping containerB</code></pre>
<ul>
<li>핑테스트 → 정상적 연결 , containerB IP주소로 요청됨</li>
</ul>
<pre><code class="language-docker">docker rm -f containerB</code></pre>
<ul>
<li>containerB 삭제</li>
</ul>
<pre><code class="language-docker">ping containerB</code></pre>
<ul>
<li>핑테스트 → 도메인 없음</li>
</ul>
<p>docker rm -f containerA
docker network rm second-bridge</p>
<p>→ 실습 마치기</p>
<h3 id="토커의-네트워크-드라이버">토커의 네트워크 드라이버</h3>
<ul>
<li><p>브릿지 네트워크(Bridge) : 도커 브릿지를 활용해 컨테이너간 통신, NAT 및 포트포워딩 기술을 활용해 외부 통신 지원</p>
<p>  → 대부분 브릿지 네트워크 활용, 컨테이너는 서로 통신할 수 있고, 외부의 서버에서 접근하려면 포트포워딩 사용해야한다. 각각의 컨테이너들은 NAT을 통해 외부로 요청보낼 수 있다. </p>
</li>
<li><p>호스트 네트워크(Host) : 호스트OS의 네트워크를 공유, 모든 컨테이너는 호스트 머신과 동일한 IP를 사용, 호스트 OS가 사용하는 포트와 중복 불가능</p>
</li>
<li><p>오버레이 네트워크(Overlay) : Kubernetes에서 사용, 호스트 머신이 다수일 때 네트워크 관리 기술</p>
<p>  → 여러 개의 호스트 머신이 있을 때, 이 호스트 머신들을 하나의 네트워크처럼 동작하게 만들어 준다. 쿠버네티스에서 주로 사용하는 네트워크 드라이버 방식이다. </p>
</li>
<li><p>Macvlan 네트워크 : 컨테이너에 MAC 주소를 할당하여 물리 네트워크 인터페이스에 직접 연결</p>
</li>
</ul>
<h2 id="leafy-네트워크">Leafy 네트워크</h2>
<p>docker network create leafy-network</p>
<p>→ leafy-network라는 브릿지 네트워크 생성, 새롭게 네트워크 만든 이유는 기본 네트워크는 DNS 기능을 제공하지 않기 때문이다. </p>
<p>→ DNS 사용하지 않으면 SpringBoot에서 DB에 접근할 때 name(도메인)이 아닌 IP를 통해 접근해야 한다. but IP는 자동으로 할당되기 때문에 DB 컨테이너가 재시작되면 IP도 변경될 수 있다. </p>
<p>docker run -d --name leafy-postgres --network leafy-network devwikirepo/leafy-postgres:1.0.0</p>
<p>→  leafy-network로 네트워크 지정
→ leafy-postgres가 도메인 명인 컨테이너 실행 </p>
<p>docker run -d -p 8080:8080 -e DB_URL=leafy-postgres --network leafy-network --name leafy devwikirepo/leafy-backend:1.0.0</p>
<p>→ 8080포트 포트포워딩</p>
<p>→ DB접속 URL을 postgres 컨테이너의 이름으로 지정</p>
<p>docker run -d -p 80:80 --network leafy-network --name leafy-front devwikirepo/leafy-frontend:1.0.0</p>
<p>→  leafy-network로 네트워크 지정</p>
<p>→ 프론트엔드 실행할 때는 80포트 포트포워딩</p>
<p>포트포워딩이 되어 있지 않은 leafy-postgres는 사용자의 PC에서 접근 불가이다. 포트포워딩이 되어있는 leafy, leafy-frontend는 실습PC의 IP의 80, 8080 으로 접근가능하다. 실습 PC는 localhost, 127.0.0.1, PC의 IP에 포트 추가하면 접근 가능하다. 실습 PC와 같은 네트워크 망에 있는 다른 기기들은 PC의 IP에 포트를 추가하면 접근 가능하다. </p>
<p>이렇게 외부에 접근이 필요한 컨테이너는 포트포워딩을 통해서 호스트PC의 IP를 컨테이너와 연결할 수 있고, DB같이 직접 연결할 필요가 없는 컨테이너는 포트포워딩을 설정하지 않음으로써 컨테이너들끼리만 통신할 수 있도록 설정한다. </p>
<p>docker ps
2. 구성 환경 삭제
docker rm -f leafy-front leafy leafy-postgres</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 클라우드 네이티브 애플리케이션과 애플리케이션 실습(Vue.js, SpringBoot, Postgres)]]></title>
            <link>https://velog.io/@garden-y/Docker-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EA%B3%BC-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%8B%A4%EC%8A%B5Vue.js-SpringBoot-Postgres</link>
            <guid>https://velog.io/@garden-y/Docker-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EA%B3%BC-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%8B%A4%EC%8A%B5Vue.js-SpringBoot-Postgres</guid>
            <pubDate>Wed, 15 May 2024 04:24:23 GMT</pubDate>
            <description><![CDATA[<h1 id="part5-컨테이너-애플리케이션-구성">Part5. 컨테이너 애플리케이션 구성</h1>
<p>애플리케이션을 이미지로 빌드하고, 컨테이너로 배포해보자. </p>
<p>우선  컨테이너 애플리케이션의 특징을 이해하기 위해 클라우드의 개념을 학습하고 Cloud Native Application 특징과 MSA 아키텍처에 대해 간단히 배워보자.</p>
<p>실습은 애플리케이션을 컨테이너로 빌드하는 것이다! (1. Vue.js → Nginx 2. Springboot → Java 컨테이너. + Postgres 컨테이너로 빌드)</p>
<h2 id="클라우드-네이티브-애플리케이션">클라우드 네이티브 애플리케이션</h2>
<h3 id="클라우드">클라우드</h3>
<p>클라우드는 스토리지 저장소를 의미한다. </p>
<p>Naver Cloud, Google Cloud.. 등 과 같은 클라우드 서비스를 사용하면 내가 원하는 파일을 클라우드에 저장해 놓을 수 있다. 사용한 만큼만 비용을 지불하고, 더 많은 파일을 저장하고 싶을 경우는 저장한 만큼 추가 비용을 지불하면 된다.</p>
<p>업로드한 파일은 실제 어디에 저장될까?</p>
<p>각각의 클라우드 업체들은 자체적으로 서버들을 가지고 있으며, 여기에 모든 사용자들의 파일이 물리적으로 섞여 있는 상태로 저장된다.  즉, <strong>하나의 물리 드라이브에 여러 사용자의 파일이 저장될 수 있는 것</strong>이다. </p>
<p>클라우드 서비스를 사용한다는 것은 이런 <strong>인프라적인 부분을 신경 쓰지 않고 실제 서비스의 기능만 사용하는 것</strong>을 뜻한다. </p>
<p>극단적으로 클라우드 스토리지를 사용하지 않는다고 생각하면 당장 USB부터 들고 다녀야 한다. 따라서 클라우드를 사용하면 물리적인 디바이스에서 자유로워지고, 필요할 때 마다 빠르게 확장할 수 있는 편리함을 누릴 수 있다. (공유 경제와 비슷한 원리이다. 그린카나 쏘카, 에어비엔비 같이 직접 구매하지 않고도 시간당 요금으로 빌리는 원리라고 생각하면 된다.) </p>
<p>클라우드 컴퓨팅은 단순히 스토리지에 국한되지 않고, 전반적인 서버 컴퓨터로 개념이 확장된다. 예를 들어 2코어의 4GB 메모리를 가진 컴퓨터를 사용하고 싶은 경우, 클라우드 사업자의 계정을 만들어 서버를 구매하면 5~10분내로 이 서버에 접속할 수 있다. 클라우드 사업자는 대형 데이터 센터를 지역별로 구축해 놓고 가상화 기술을 활용한다. 사용자가 서버를 구매할 때마다 VM을 한 대 만들어서 접속 정보를 제공해준다. </p>
<p>일반 기업이 서버를 새로 한 대 구매하려면 서버 구매 과정부터 배송, 설치, 유지보수까지 많은 비용과 시간을 소비하게 된다. 더불어 서버 기계 자체의 감가상삭, 유지하기 위한 인력, 전기, 폐기 비용까지 모두 서버 소유자의 몫이 된다. 그래서 기업들은 이런 비용들을 없애고 클라우드 서비스를 사용한다. 
<br></p>
<p>따라서 클라우드는 다른 회사 서버를 빌려서 운영하는 것이며, </p>
<p><strong>다른 회사가 모두에게 서버를 빌려줄 경우 → Public Cloud</strong></p>
<ul>
<li>마이크로소프트 Azure, 아마존 AWS, 구글 GCP가 있다.</li>
<li>어떤 사용자든 계정만 만들면 서비스를 사용할 수 있다.</li>
</ul>
<p><strong>다른 회사가 특정 조직에게만 서버를 빌려줄 경우 → Private Cloud</strong></p>
<ul>
<li>조직 내에서 IT 계열사가 제공하는 경우이다.</li>
<li>계열사가 제공하는 서비스는 같은 조직 내에 속해 있는 회사에만 서버를 만들어서 제공한다.</li>
<li>비교적 보안도 뛰어나고, 비용적으로 효율적이다.<br>

</li>
</ul>
<h4 id="클라우드-특징">클라우드 특징</h4>
<ul>
<li>사용 요청 즉시 서버를 생성해준다. (Provisioning)</li>
<li>실제 사용한 시간 만큼만 비용을 지불한다.<br>

</li>
</ul>
<h4 id="실제-클라우드를-사용하는-핵심적인-이유">실제 클라우드를 사용하는 핵심적인 이유</h4>
<p>→ 현대 애플리케이션이 겪는 다양한 문제들을 클라우드 환경 구성을 통해 해결할 수 있기 때문이다. </p>
<ol>
<li><p>트래픽이 증가할 때 빠르게 대처할 수 있는가? (확장성, Scalability)</p>
<ul>
<li><p>현대 애플리케이션은 사용자의 수요 변동이 크다.(커머스, 할인 등 사용자의 요청이 급증할 수 있으며, 회계 시스템 경우 특정 기간 아닐 때는 트래픽이 아예 없을 수 있다.) 이런 트래픽 변동에 유연하게 대처할 수 있는지 중요하다.</p>
</li>
<li><p>트래픽 증가 시 서버를 늘리고, 감소 시 줄여야 하는데 사내에 직섭 서버를 구성할 경우 서버의 용량을 증가하는 것이 쉽지 않다. 따라서 최댓값 예상에 맞춰 구성한다. (서버 다운과 낭비..)</p>
<p>→ 클라우드 환경에서는 서버 추가가 10분내로 이뤄진다. 온프레스미스에서는 서버가 미리 준비되어 있지 않은 경우 새로운 서버를 증가하는데 오랜 시간이 소요된다. (주문, 배송, 설치 등..)
→ 클라우드를 사용하면 확장성을 가질 수 있고 트래픽 증가에 빠르게 대처할 수 있다.</p>
</li>
</ul>
</li>
<li><p>장애 발생 시 빠르게 복구할 수 있는가? (복원력, Resilience)</p>
<ul>
<li><p>클라우드 서버를 운영한다해서 모든 것이 해결되진 않지만, 전 세계의 다수의 데이터 센터를 가지고 있기 때문에 비교적 안전하다.</p>
<p>→ 클라우드 환경에서는 백업 및 복구가 빠르게 이뤄질 수 있다.(Disaster Recovery) 장애에 대응하기 위한 다양한 지역의 서버를 구축할 수 있다. </p>
</li>
</ul>
</li>
<li><p>운영 비용을 효율적으로 운영할 수 있는가?</p>
<ul>
<li><p>전문 아키텍트가 서버 가용량을 적절하게 구성해야 하며, 비용 최적화를 지속적으로 수행해야 한다.</p>
<p>→ 사용한 만큼만 지불할 수 있기 때문에 운영 비용이 더 효율적이다. </p>
</li>
</ul>
</li>
</ol>
<br>

<h3 id="클라우드-네이티브">클라우드 네이티브</h3>
<ul>
<li>클라우드 : 복잡한 대형 애플리케이션이 겪는 다양한 문제들을 클라우드 환경 구성을 통해 해결한다.</li>
<li>클라우드 네이티브 애플리케이션 : 클라우드 환경을 더 잘 활용할 수 있는 애플리케이션 구조</li>
</ul>
<ol>
<li><p>MSA</p>
<ul>
<li><p>트래픽 증가에 빠르게 대처하기 위해선 애플리케이션이 MSA 구조로 개발되어야 한다.</p>
<p>→ 애플리캐이션을 여러 단위로 분리해서 트래픽 증가에 효율적으로 대처하기 위한 소프트웨어 아키텍처이다.</p>
</li>
</ul>
</li>
<li><p>컨테이너 Container</p>
<ul>
<li><p>컨테이너를 활용해 실행 환경에 종속되지 않는 동작이 보장되어야 한다.</p>
<p>→ 컨테이너의 이미지에는 소프트웨어가 실행하기 위한 환경들이 모두 포함되어 있다. 따라서 이미지를 가지고 있으면 어떤 환경에서든 동일한 실행 동작을 보일 수 있다. 
→ 컨테이너를 사용하지 않을 경우 각각의 서버의 프로그램을 별도로 운영해야 하고 결국 환경 불일치 문제가 생길 가능성이 크다.</p>
</li>
</ul>
</li>
<li><p>상태비저장 Stateless</p>
<ul>
<li><p>애플리케이션 서버는 상태를 가지지 않아야 한다.</p>
</li>
<li><p>상태를 가지지 않는 애플리케이션은 어디에나 즉시 배포될 수 있고, 여러 대의 서버가 동시에 같은 역할을 하도록 운영할 수 있다.</p>
<p>→ 상태를 가질 경우에는 외부의 이 상태가 분리되어있어야 한다.
→ 상태를 가진다는 것은 결국 각각의 서버가 다르게 동작할 수 있다는 것을 의미한다. 
→ 이미지를 컨테이너로 실행할 때 컨테이너에 읽기쓰기 레이어가 생기는 것, 삭제하면 이 읽기쓰기 레이어도 함께 삭제되는 것처럼 컨테이너는 태생적으로 비저장의 특징을 가진다.</p>
</li>
</ul>
</li>
<li><p>DevOps 및 CI/CD</p>
<ul>
<li><p>배포가 자동화되어야 하고 빠르게 릴리즈가 수행되어야 한다.</p>
<p>→ <a href="http://12factor.net/ko/">12factor.net/ko/</a> : 클라우드 환경에서 운영하는 애플리케이션의 요구 사항에 대해 잘 정리해놨다. </p>
</li>
</ul>
</li>
</ol>
<br>

<h3 id="msa">MSA</h3>
<p>MicroService Architecture
<img src="https://velog.velcdn.com/images/garden-y/post/1267432e-7de6-472d-bca2-370cd1fd343a/image.png" alt=""></p>
<p>모놀리식(Monolithic)</p>
<ul>
<li>애플리케이션을 개발할 때 모든 기능을 하나의 애플리케이션에 구성한다.</li>
<li>하나의 애플리케이션 크기가 커지고 애플리케이션 실행 시간이 오래 걸린다.</li>
<li>트레픽이 증가할 때는 이 트래픽을 받을 수 있는 새로운 서버를 증가시켜야 하는데 서버의 실행시간이 길어지면 트래픽 대처 능력도 떨어진다.</li>
<li>애플리케이션이 크면 클수록 개발에 들어가는 빌드, 배포 시간이 오래걸린다.</li>
<li>서버 증가 시 전체 기능을 늘려야 한다.</li>
</ul>
<p>마이크로서비스아키텍처(MSA)</p>
<ul>
<li>각각의 모듈을 나눠서 구성한다.</li>
<li>모듈 크기가 작아졌기 때문에 개발도 간편해지고 서버를 스케일아웃하는 시간도 빨라진다.<ul>
<li>스케일아웃 : 서버의 대수를 늘려서 트래픽에 대처한다.</li>
<li>스케일인 : 서버의 대수를 줄이는 것</li>
</ul>
</li>
<li>모듈 별로 다른 언어로 개발 가능하다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/df4fdcd9-56fc-406b-9c51-e6c650a20230/image.png" alt=""></p>
<p>→ MSA의 단점은 복잡성이 높다는 것이다. 하지만 MSA는 클라우드 네이티브에 잘 어울리는 소프트웨어 구조이다. MSA 아키텍처의 장점을 잘 이용하기 위해서 컨테이너를 활용하는 것이 필수이다. </p>
<h2 id="leafy-애플리케이션-구성">Leafy 애플리케이션 구성</h2>
<p>LEAFY : 식물 관리 웹 애플리케이션</p>
<ul>
<li>식물 정보 제공</li>
<li>나의 식물 리스트</li>
<li>식물 다이어리</li>
<li>물 주기 계산</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/6392479c-9f6c-4d18-a013-da626dd82a3e/image.png" alt=""></p>
<ul>
<li>Vue.js 프론트 프레임워크를 사용해 개발, 빌드 시 html, css, js 파일들을 얻을 수 있다.</li>
<li>패키징된 파일들을 Nginx 웹서버에 배포해서 사용자가 웹서버에 접속했을 때 페이지를 제공해준다.</li>
<li>SpringBoot 백엔드 프레임워크를 사용해 개발, Tomcat이라는 웹애플리케이션 서버로 실행된다.</li>
<li>PostgreSQL는 실제 DB서버이다.</li>
</ul>
<p>→ DB서버를 구성하고 실행한 뒤, 백엔드애플리케이션, Nginx 서버 순으로 이미지 빌드하고, 컨테이너를 실행시킬 것이다. (Web, WAS, DB 세개의 컨테이너로 애플리케이션이 정상적으로 동작하도록 한다.) </p>
<p>각 모듈을 이미지로 빌드하기전 Leafy 애플리케이션이 잘 실행되는지 확인한다.
이미지는 지금까지 사용한 devwiki에 저장된 Leafy 관련 이미지를 사용할 예정이다.
컨테이너들이 사용할 네트워크를 정의해야 한다. 
<br></p>
<p>leafy 애플리케이션이 사용할 네트워크 생성</p>
<pre><code class="language-docker">docker network create leafy-network </code></pre>
<br>

<p>leafy-postgres 컨테이너 생성</p>
<pre><code class="language-docker">docker run -d --name leafy-postgres --network leafy-network devwikirepo/leafy-postgres:1.0.0</code></pre>
<br>

<p>leafy-postgres 컨테이너</p>
<pre><code class="language-docker">docker logs -f leafy-postgres
# ctrl + c -&gt; 로그 탈출</code></pre>
<p>→ database system is ready가 나와야 정상적으로 실행된 것
→ DB 실행 전 SpringBoot 애플리케이션 실행 시 에러가 발생한다.
<br></p>
<p>leafy-postgres 컨테이너 생성</p>
<pre><code class="language-docker">docker run -d -p 8080:8080 -e DB_URL=leafy-postgres --network leafy-network --name leafy devwikirepo/leafy-backend:1.0.0</code></pre>
<br>

<p>백엔드 컨테이너 로그 조회</p>
<pre><code class="language-docker">docker logs -f leafy</code></pre>
<p>→ Started leafyApplication이 나오면 정상적으로 백엔드 애플리케이션이 실행된 것
→ 애플리케이션이 잘 실행된 것은 DB와 잘 연결되었다는 것
<br></p>
<p>leafy-front 컨테이너 생성</p>
<pre><code class="language-docker">docker run -d -p 80:80 --network leafy-network --name leafy-front devwikirepo/leafy-frontend:1.0.0</code></pre>
<p>→ 세가지 종류의 컨테이너를 실행했다. 프론트엔드 웹서버로 접속해서 파일을 다운로드 받은 후, 이 파일을 출력하고 브라우저를 출력하는 과정에서 필요한 데이터들을 백엔드 애플리케이션의 API 요청을 통해 불러왔다. 
→ 백엔드 애플리케이션은 요청을 받을 경우 PostgreSQL로 요청을 보내 저장되어 있는 데이터를 불러오거나, 저장한 후 JSON 응답으로 보내준다. 
→ 3가지 모듈들이 유기적으로 상호작용한다.<br><br></p>
<p>실습 컨테이너 삭제</p>
<pre><code class="language-docker">docker rm -f leafy-front leafy leafy-postgres</code></pre>
<p>→ 컨테이너를 사용하면 소프트웨어를 실행하기 위해 필요한 것들을 컴퓨터에 설치하지 않고도 애플리케이션을 구성할 수 있다. 컨테이너만 삭제하면 완전히 실행 전과 동일한 컴퓨터 상태로 돌릴 수 있다. 
→ 기존에 빌드하고 푸시해놓은 이미지로 애플리케이션을 구성했다면, 직접 컨테이너 이미지를 빌드해볼 것
<br><br></p>
<h2 id="postgresql-컨테이너-구성">PostgreSQL 컨테이너 구성</h2>
<h3 id="leafy-postgresql-구성-프로세스">Leafy PostgreSQL 구성 프로세스</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/442c5368-5f94-447e-a92b-dadf8293e42d/image.png" alt=""></p>
<ol>
<li>OS 구성 및 PostgreSQL 설치<ul>
<li>PostgreSQL은 데이터를 저장하고 조회할 수 있는 DB이다.</li>
<li>Nginx처럼 이미지에 소프트웨어가 포함되어 있기 때문에 별도의 설정없이 PostgreSQL만 실행해도 DB를 사용할 수 있다.</li>
<li>하지만 기본 이미지에는 아무 데이도 없기 때문에 초기 데이터를 구성하는 SQL을 작성해서 PostgreSQL에 전달한다.</li>
<li>postgres 13버전으로 베이스를 사용하면 이 안에 기본 OS 파일 시스템 위에 PostgreSQL DB를 설치한 상태로 이미지를 시작할 수 있다.</li>
</ul>
</li>
<li>환경 설정 파일 작성<ul>
<li>설정 파일은 ETC의 PostgreSQL의 Custom.conf 파일로 복사해서 서버를 실행할 때 여기에 있는 설정 파일을 사용하도록 지정할 것이다.</li>
</ul>
</li>
<li>SQL문 작성<ul>
<li>DB 데이터 초기 세팅</li>
<li>SQL문을 실행해서 테이블을 생성하고 데이터를 삽입할 수 있다.</li>
<li>빌드 컨텍스트에 내가 실행하고 싶은 SQL문을 작성한 다음 SQL문을 postgres 이미지 안에 docker-entrypoint-initdb.d 폴더에 넣으면 postgres 이미지가 컨테이너 실행될 때 자동으로 이 폴더 안에 있는 SQL문을 실행한다.</li>
</ul>
</li>
<li>데이터베이스 실행(postgres)<ul>
<li>컨테이너를 실행할 때 postgres를 실행시키기 위해 실행 명령은 cmd에 작성해야 한다. (-f옵션을 통해서 config 파일을 etc/postgresql에 파일로 저장한다.)</li>
</ul>
</li>
</ol>
<h3 id="실습">실습</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/7abccc1e-b948-4a5c-9239-7629d33df63b/image.png" alt=""></p>
<p>git clone <a href="https://github.com/daintree-henry/leafy.git">https://github.com/daintree-henry/leafy.git</a>
cd leafy/leafy-postgresql
git switch 00-init → 도커파일 작성 직접 할 경우
git switch 01-dockerfile → 도커파일 작성 되어있음</p>
<pre><code class="language-docker"># POSTGRESQL.CONF FILE
# ---------------------

# CONNECTIONS AND AUTHENTICATION
listen_addresses = &#39;*&#39;        # IP 주소, 호스트명 또는 &#39;*&#39;로 모든 IP에 대한 연결을 허용합니다.
max_connections = 100         # 동시 접속자 수 제한
authentication_timeout = 5min   # 인증 시간 초과 시간 (5분)
password_encryption = md5     # 패스워드 암호화 방식

# QUERY TUNING
work_mem = 64MB              # 개별 연결에서 사용 가능한 메모리 양
shared_buffers = 256MB       # 공유 메모리 버퍼 크기
effective_cache_size = 2GB   # 임시 파일 및 인덱스 생성 시 사용할 메모리 크기

# ERROR REPORTING AND LOGGING
log_destination = &#39;stderr&#39;   # 로그 파일 출력 대상 설정
logging_collector = on        # 로그 수집기를 사용하도록 설정
log_directory = &#39;pg_log&#39;     # 로그 파일이 저장될 디렉토리 경로
log_filename = &#39;postgresql-%a.log&#39;  # 로그 파일 이름 지정

# REPLICATION
wal_level = replica          # 스트리밍 복제 구성
max_wal_senders = 5          # 스트리밍 복제 전송자의 최대 수

# PERFORMANCE
effective_io_concurrency = 200   # 파일 I/O 수행을 위해 사용할 동시성 레벨
random_page_cost = 1.1           # 임의 액세스 비용 인덱스 스캔시 고려</code></pre>
<p>→ 원하는 설정 파일을 기본 이미지에 덮어 쓰게 함으로써 원하는 상태의 서버 설정을 만들 수 있다. 
<br></p>
<pre><code class="language-sql">-- Users table creation
CREATE TABLE users (
    user_id SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    gender VARCHAR(1) NOT NULL,
    birth_date DATE,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP
);

-- Plants table creation
CREATE TABLE plants (
    plant_id SERIAL PRIMARY KEY,
    plant_name VARCHAR(50) NOT NULL,
    plant_type VARCHAR(50) NOT NULL,
    plant_desc VARCHAR(255),
    image_url VARCHAR(255),
    temperature_low FLOAT NOT NULL,
    temperature_high FLOAT NOT NULL,
    humidity_low FLOAT NOT NULL,
    humidity_high FLOAT NOT NULL,
    watering_interval INT NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP
);

-- User_Plants table creation
CREATE TABLE user_plants (
    user_plant_id SERIAL PRIMARY KEY,
    user_id INT NOT NULL,
    plant_id INT NOT NULL,
    plant_nickname VARCHAR(50) NOT NULL,
    FOREIGN KEY (user_id) REFERENCES Users(user_id),
    FOREIGN KEY (plant_id) REFERENCES Plants(plant_id) ON DELETE SET NULL,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP
);

-- Plant_Logs table creation
CREATE TABLE plant_logs (
    plant_log_id SERIAL PRIMARY KEY,
    user_plant_id INT NOT NULL,
    log_date DATE NOT NULL,
    note VARCHAR(255),
    watered BOOLEAN,
    FOREIGN KEY (user_plant_id) REFERENCES User_Plants(user_plant_id),
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP
);

-- Users 테이블에 데이터 삽입
-- $2a$10$vYR4pPQqR/oZcUDZfXrahecEejQHY0kLkDB5s.FctPRMcEMh1PYhG = password123
-- $2a$10$Vqx3VUuB8gy9NvtKHQARWOOYB2wG4wV2WXy1sdQHIoY8TivSHZ3sC = password456
-- $2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q = password789
INSERT INTO users (name, email, password, gender, birth_date) VALUES
(&#39;John&#39;, &#39;john123@qmail.com&#39;, &#39;$2a$10$vYR4pPQqR/oZcUDZfXrahecEejQHY0kLkDB5s.FctPRMcEMh1PYhG&#39;, &#39;M&#39;, &#39;1988-05-01&#39;),
(&#39;Jane&#39;, &#39;jane456@qmail.com&#39;, &#39;$2a$10$vYR4pPQqR/oZcUDZfXrahecEejQHY0kLkDB5s.FctPRMcEMh1PYhG&#39;, &#39;F&#39;, &#39;1995-08-15&#39;),
(&#39;Peter&#39;, &#39;peter789@qmail.com&#39;, &#39;$2a$10$vYR4pPQqR/oZcUDZfXrahecEejQHY0kLkDB5s.FctPRMcEMh1PYhG&#39;, &#39;M&#39;, &#39;1981-12-25&#39;),
(&#39;Susan&#39;, &#39;susan321@qmail.com&#39;, &#39;$2a$10$vYR4pPQqR/oZcUDZfXrahecEejQHY0kLkDB5s.FctPRMcEMh1PYhG&#39;, &#39;F&#39;, &#39;1990-06-02&#39;),
(&#39;David&#39;, &#39;david654@qmail.com&#39;, &#39;$2a$10$vYR4pPQqR/oZcUDZfXrahecEejQHY0kLkDB5s.FctPRMcEMh1PYhG&#39;, &#39;M&#39;, &#39;1992-03-11&#39;),
(&#39;Judy&#39;, &#39;judy987@qmail.com&#39;, &#39;$2a$10$vYR4pPQqR/oZcUDZfXrahecEejQHY0kLkDB5s.FctPRMcEMh1PYhG&#39;, &#39;F&#39;, &#39;1983-10-19&#39;),
(&#39;Timothy&#39;, &#39;timothy012@qmail.com&#39;, &#39;$2a$10$Vqx3VUuB8gy9NvtKHQARWOOYB2wG4wV2WXy1sdQHIoY8TivSHZ3sC&#39;, &#39;M&#39;, &#39;1996-11-30&#39;),
(&#39;Lisa&#39;, &#39;lisa345@qmail.com&#39;, &#39;$2a$10$Vqx3VUuB8gy9NvtKHQARWOOYB2wG4wV2WXy1sdQHIoY8TivSHZ3sC&#39;, &#39;F&#39;, &#39;1988-07-20&#39;),
(&#39;Steve&#39;, &#39;steve678@qmail.com&#39;, &#39;$2a$10$Vqx3VUuB8gy9NvtKHQARWOOYB2wG4wV2WXy1sdQHIoY8TivSHZ3sC&#39;, &#39;M&#39;, &#39;1977-01-05&#39;),
(&#39;Emily&#39;, &#39;emily321@qmail.com&#39;, &#39;$2a$10$Vqx3VUuB8gy9NvtKHQARWOOYB2wG4wV2WXy1sdQHIoY8TivSHZ3sC&#39;, &#39;F&#39;, &#39;1994-09-23&#39;),
(&#39;Henry&#39;, &#39;henry654@qmail.com&#39;, &#39;$2a$10$Vqx3VUuB8gy9NvtKHQARWOOYB2wG4wV2WXy1sdQHIoY8TivSHZ3sC&#39;, &#39;M&#39;, &#39;1989-06-14&#39;),
(&#39;Grace&#39;, &#39;grace987@qmail.com&#39;, &#39;$2a$10$Vqx3VUuB8gy9NvtKHQARWOOYB2wG4wV2WXy1sdQHIoY8TivSHZ3sC&#39;, &#39;F&#39;, &#39;1982-04-28&#39;),
(&#39;Mike&#39;, &#39;mike012@qmail.com&#39;, &#39;$2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q&#39;, &#39;M&#39;, &#39;1998-02-08&#39;),
(&#39;Sophie&#39;, &#39;sophie345@qmail.com&#39;, &#39;$2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q&#39;, &#39;F&#39;, &#39;1991-12-12&#39;),
(&#39;Daniel&#39;, &#39;daniel678@qmail.com&#39;, &#39;$2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q&#39;, &#39;M&#39;, &#39;1980-07-01&#39;),
(&#39;Olivia&#39;, &#39;olivia321@qmail.com&#39;, &#39;$2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q&#39;, &#39;F&#39;, &#39;1992-05-28&#39;),
(&#39;Jackson&#39;, &#39;jackson654@qmail.com&#39;, &#39;$2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q&#39;, &#39;M&#39;, &#39;1985-02-18&#39;),
(&#39;Amelia&#39;, &#39;amelia987@qmail.com&#39;, &#39;$2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q&#39;, &#39;F&#39;, &#39;1995-01-10&#39;),
(&#39;Tom&#39;, &#39;tom012@qmail.com&#39;, &#39;$2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q&#39;, &#39;M&#39;, &#39;1987-08-03&#39;),
(&#39;Sarah&#39;, &#39;sarah345@qmail.com&#39;, &#39;$2a$10$ke3IM6noeWfQtX6POjZHl.49gSolYbqfrSTIn8sOQubdwjP2IT94q&#39;, &#39;F&#39;, &#39;1984-03-09&#39;);

-- Plants 테이블에 데이터 삽입
INSERT INTO plants (plant_name, plant_type, plant_desc, image_url, temperature_low, temperature_high, humidity_low, humidity_high, watering_interval)
VALUES 
(&#39;아이비&#39;, &#39;덩굴식물&#39;, &#39;아이비는 빠르게 성장하는 인기 있는 덩굴식물로, 공기 정화 능력이 뛰어납니다. 벽이나 거치대에 올려두면 빠르게 뻗어나가 아름다운 모습을 연출합니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/아이비.jpg&#39;, 12, 28, 40, 70, 7),
(&#39;스투키&#39;, &#39;선인장&#39;, &#39;스투키는 독특한 모양의 선인장으로, 견고하고 건조한 환경에도 잘 적응할 수 있습니다. 물을 적게 주어도 건강하게 자라며 관리가 쉽습니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/스투키.jpg&#39;, 10, 30, 10, 50, 21),
(&#39;로즈마리&#39;, &#39;허브&#39;, &#39;로즈마리는 향긋한 향기를 가진 허브로, 요리에 활용되기도 합니다. 건조한 환경에도 잘 적응하며, 햇빛을 좋아하는 식물입니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/로즈마리.jpg&#39;, 10, 30, 30, 50, 14),
(&#39;자스민&#39;, &#39;꽃&#39;, &#39;자스민은 아름다운 꽃과 달콤한 향기로 사랑받는 식물입니다. 화분이나 정원에서 재배할 수 있으며, 온화한 기후를 선호합니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/자스민.jpg&#39;, 15, 30, 40, 70, 7),
(&#39;스파티필럼&#39;, &#39;실내식물&#39;, &#39;스파티필럼은 큰 잎과 화이트 꽃이 특징인 실내 장식용 식물로, 공기 정화 능력이 높아 인기가 많습니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/스파티필럼.jpg&#39;, 18, 28, 50, 70, 10),
(&#39;스킨답서스&#39;, &#39;실내식물&#39;, &#39;스킨답서스는 작은 크기의 초록색 잎과 긴 줄기가 특징인 실내 장식용 식물입니다. 건조한 환경과 낮은 빛 조건에서도 잘 자라며, 관리가 쉽습니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/스킨답서스.jpg&#39;, 15, 30, 30, 50, 10),
(&#39;페퍼민트&#39;, &#39;허브&#39;, &#39;페퍼민트는 상쾌한 향기를 가진 허브로, 차나 요리에 활용되기도 합니다. 물이 잘 공급되는 환경을 선호하며, 햇빛을 좋아하는 식물입니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/페퍼민트.jpg&#39;, 15, 28, 40, 60, 7),
(&#39;산세베리아&#39;, &#39;실내식물&#39;, &#39;산세베리아는 긴 검은 잎이 특징인 실내 장식용 식물로, 공기 정화 능력이 뛰어납니다. 건조한 환경과 낮은 빛 조건에서도 잘 자라며, 관리가 쉽습니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/산세베리아.jpg&#39;, 15, 30, 20, 50, 21),
(&#39;식물성이끼&#39;, &#39;이끼&#39;, &#39;식물성이끼는 물에 잘 적응한 식물로, 습한 환경에서 자라는데 적합합니다. 실내 정원이나 수초 양식에서 인기가 많으며, 공기 정화에도 도움이 됩니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/식물성이끼.jpg&#39;, 10, 25, 70, 100, 5),
(&#39;올리브&#39;, &#39;나무&#39;, &#39;올리브는 과실과 나무로 인기가 있는 식물로, 지중해 기후를 선호합니다. 정원이나 화분에서 재배할 수 있으며, 올리브 오일이나 식재료로 사용됩니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/올리브.jpg&#39;, 10, 30, 30, 50, 14),
(&#39;바질&#39;, &#39;허브&#39;, &#39;바질은 향긋한 향기를 가진 허브로, 토마토 요리에 자주 사용됩니다. 햇빛을 좋아하며, 다소 습한 환경에서 잘 자라는 식물입니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/바질.jpg&#39;, 18, 30, 40, 60, 7),
(&#39;방울토마토&#39;, &#39;채소&#39;, &#39;방울토마토는 작고 맛있는 과실이 특징인 채소로, 화분이나 정원에서 쉽게 재배할 수 있습니다. 햇빛을 좋아하며, 꾸준한 물 공급이 필요합니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/방울토마토.jpg&#39;, 18, 30, 40, 70, 5),
(&#39;히야신스&#39;, &#39;꽃&#39;, &#39;히야신스는 다양한 색상의 아름다운 꽃과 향기로 봄의 대표적인 꽃입니다. 온화한 기후를 선호하며, 화분이나 정원에서 재배할 수 있습니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/히야신스.jpg&#39;, 10, 25, 40, 60, 7),
(&#39;해바라기&#39;, &#39;꽃&#39;, &#39;해바라기는 거대한 꽃과 높이가 특징인 식물로, 햇빛을 매우 좋아합니다. 정원이나 대형 화분에서 재배할 수 있으며, 씨앗이 간식이나 새의 먹이로 사용됩니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/해바라기.jpg&#39;, 15, 30, 30, 60, 7),
(&#39;아레카야자&#39;, &#39;야자&#39;, &#39;아레카야자는 열대 실내 장식용 식물로 유명하며, 큰 잎과 세련된 모습으로 인기가 많습니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/아레카야자.jpg&#39;, 18, 27, 40, 60, 7),
(&#39;파키라&#39;, &#39;실내식물&#39;, &#39;파키라는 견고하고 관리하기 쉬운 식물로, 두꺼운 줄기와 큰 둥글둥글한 잎이 특징입니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/파키라.jpg&#39;, 15, 28, 40, 60, 10),
(&#39;유칼립투스&#39;, &#39;나무&#39;, &#39;유칼립투스는 상쾌한 향기와 아름다운 잎 모양으로 많은 사랑을 받는 식물입니다. 특히 건조한 공간에서도 잘 자라기 때문에 인기가 높습니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/유칼립투스.jpg&#39;, 10, 25, 30, 55, 14),
(&#39;피나타&#39;, &#39;실내식물&#39;, &#39;피나타는 높은 곳에서 뻗어 나오는 날렵한 잎으로 장식성이 높은 식물입니다. 거실이나 베란다 같은 잘 통풍되는 장소에 두기 좋습니다.&#39;, &#39;https://leafyapplicationfiles.blob.core.windows.net/plantimages/피나타.jpg&#39;, 15, 30, 40, 70, 12);

-- User_Plants 테이블에 데이터 삽입
INSERT INTO user_plants (user_id, plant_id, plant_nickname) VALUES
(1, 1, &#39;노을이&#39;), (1, 2, &#39;햇님&#39;), (2, 1, &#39;별빛&#39;), (2, 4, &#39;새벽&#39;), (2, 6, &#39;향기&#39;), (3, 7, &#39;구름&#39;), (3, 9, &#39;바람&#39;), (4, 10, &#39;무지개&#39;), (4, 12, &#39;햇살&#39;), (5, 14, &#39;노을&#39;),
(5, 15, &#39;풀밭&#39;), (6, 16, &#39;물방울&#39;), (6, 18,&#39;풀풀이&#39;), (7, 15, &#39;뾰족이&#39;), (8, 1, &#39;여름&#39;), (8, 3, &#39;쑥쑥이&#39;), (8, 5, &#39;검정&#39;), (9, 2, &#39;커피&#39;), (9, 6, &#39;봄이&#39;), (10, 4, &#39;초록이&#39;),
(11, 7, &#39;딸기&#39;), (12, 8, &#39;노랑&#39;), (13, 11, &#39;바다&#39;), (14, 13, &#39;우주&#39;), (15, 17, &#39;하양&#39;);

-- Plant_Logs 테이블에 데이터 삽입
INSERT INTO plant_logs (user_plant_id, log_date, note, watered) VALUES
(1, &#39;2023-03-22&#39;, &#39;관리가 어려워서 죽었습니다&#39;, false),
(1, &#39;2023-03-23&#39;, &#39;새로운 아이비를 구입했습니다&#39;, true),
(1, &#39;2023-03-24&#39;, &#39;1주일에 한 번 비료를 주기로 했습니다&#39;, false),
(5, &#39;2023-03-22&#39;, &#39;근처에 이끼가 생겼습니다&#39;, false),
(5, &#39;2023-03-23&#39;, &#39;이끼를 제거하고 화분을 청소했습니다&#39;, true),
(5, &#39;2023-03-24&#39;, &#39;2일마다 스프레이로 적신 흔적이 있습니다&#39;, true),
(6, &#39;2023-03-22&#39;, &#39;잎이 말라서 살짝 노랗게 변했습니다&#39;, false),
(6, &#39;2023-03-23&#39;, &#39;조금 더 많이 관수하도록 조절했습니다&#39;, true),
(6, &#39;2023-03-24&#39;, &#39;잎을 분무기로 적신 흔적이 있습니다&#39;, true),
(7, &#39;2023-03-22&#39;, &#39;물을 주지 않아 꽃이 시들었습니다&#39;, false),
(7, &#39;2023-03-23&#39;, &#39;좀 더 자주 물을 주도록 조절했습니다&#39;, true),
(7, &#39;2023-03-24&#39;, &#39;잎에 먼지가 쌓여서 닦아주었습니다&#39;, true),
(8, &#39;2023-03-22&#39;, &#39;나뭇잎이 말라서 살짝 물을 주었습니다&#39;, true),
(8, &#39;2023-03-23&#39;, &#39;조금 더 어둡고 습한 곳으로 옮겼습니다&#39;, true),
(8, &#39;2023-03-24&#39;, &#39;이전보다 잎이 좀 더 생기기 시작했습니다&#39;, false),
(9, &#39;2023-03-22&#39;, &#39;가지고 있는 토양이 마르고 흙이 헐렁했습니다&#39;, false),
(9, &#39;2023-03-23&#39;, &#39;새로운 토양으로 교체하여 옮겼습니다&#39;, true),
(9, &#39;2023-03-24&#39;, &#39;이전보다 잎색이 좀 더 진해졌습니다&#39;, false),
(10, &#39;2023-03-22&#39;, &#39;낮은 온도로 인해 성장이 늦어졌습니다&#39;, false),
(10, &#39;2023-03-23&#39;, &#39;조금 더 따뜻한 곳으로 옮겼습니다&#39;, true),
(10, &#39;2023-03-24&#39;, &#39;새로운 잎이 조금씩 나오기 시작했습니다&#39;, false),
(11, &#39;2023-03-22&#39;, &#39;물을 너무 많이 주어 뿌리가 부패되었습니다&#39;, false),
(11, &#39;2023-03-23&#39;, &#39;새로운 화분으로 옮겨서 치료 중입니다&#39;, true),
(11, &#39;2023-03-24&#39;, &#39;뿌리 상태가 좋아지기 시작했습니다&#39;, false),
(12, &#39;2023-03-22&#39;, &#39;잎이 말라 색이 바래졌습니다&#39;, false),
(12, &#39;2023-03-23&#39;, &#39;분무기로 물을 주고 조금 더 어두운 곳으로 옮겼습니다&#39;, true),
(12, &#39;2023-03-24&#39;, &#39;잎의 색이 조금씩 회복되기 시작했습니다&#39;, false),
(13, &#39;2023-03-22&#39;, &#39;잎이 말라 피부가 매우 건조해졌습니다&#39;, false),
(13, &#39;2023-03-23&#39;, &#39;조금 더 습한 곳으로 옮기고 분무기로 물을 주었습니다&#39;, true),
(13, &#39;2023-03-24&#39;, &#39;잎과 피부 상태가 조금씩 개선되기 시작했습니다&#39;, false),
(14, &#39;2023-03-22&#39;, &#39;잎이 너무 습해서 흰색 곰팡이가 생겼습니다&#39;, false),
(25, &#39;2023-03-22&#39;, &#39;잎에 먼지가 많이 쌓여서 닦아주었습니다&#39;, false),
(25, &#39;2023-03-23&#39;, &#39;새로운 잎이 많이 나오기 시작했습니다&#39;, true),
(25, &#39;2023-03-24&#39;, &#39;잎의 상태가 좋아지고 성장하는 모습이 보입니다&#39;, false);</code></pre>
<p>→ 테이블은 user, plant, userplant, plantlog 총 4개의 테이블을 생성한다. 
→ 각 테이블에는 개발에 사용할 샘플 데이터를 INSERT 하고 있다. 
<br></p>
<pre><code class="language-docker">#PostgreSQL 13 버전을 베이스 이미지로 사용
FROM postgres:13

#init.sql파일을 /docker-entrypoint-initdb.d/ 로 복사, /docker-entrypoint-initdb.d/에 있는 sql문은 컨테이너가 처음 실행 시 자동실행됨
COPY ./init/init.sql /docker-entrypoint-initdb.d/

#postgresql.conf파일을 /etc/postgresql/postgresql.conf 로 복사, 기본 설정 파일을 덮어쓰기하여 새로운 설정 적용
COPY ./config/postgresql.conf /etc/postgresql/custom.conf

#PostgreSQL 계정정보 설정
ENV POSTGRES_USER=myuser
ENV POSTGRES_PASSWORD=mypassword
ENV POSTGRES_DB=mydb

#PostgreSQL 포트
EXPOSE 5432

CMD [&quot;postgres&quot;, &quot;-c&quot;, &quot;config_file=/etc/postgresql/custom.conf&quot;]
</code></pre>
<p>→ FROM에 postgres 13버전으로 지정. 이 이미지를 실행하면 기본적인 데이터베이스 서버를 실행할 수 있는 상태로 제공된다. 
→ 하지만 서버에는 데이터가 없어서 init.sql파일을 이미지내에서 실행시켜야 한다. 
→ init.sql파일을 /docker-entrypoint-initdb.d/ 로 복사한다. (해당 폴더는 postgres 이미지가 실행될 때 자동으로 실행하는 SQL을 저장하기로 약속된 폴더이다. 
→ -c를 통해 config 파일을 지정해서 이미지 안에 있는 기본 설정파일을 사용하지 않고 빌드를 통해 주입한 설정파일을 사용하게 해준다. 
<br></p>
<p>네트워크 리스트 확인</p>
<pre><code class="language-docker">docker network ls</code></pre>
<br>

<p>새로운 네트워크 생성(leafy-network가 없을 경우에만)</p>
<pre><code class="language-docker">docker network create leafy-network</code></pre>
<br>

<p>도커파일을 사용해 postgres 이미지 빌드</p>
<pre><code class="language-docker">docker build -t 레지스트리계정명/leafy-postgres:1.0.0 .</code></pre>
<br>

<p>빌드한 이미지 push</p>
<pre><code class="language-docker">docker push yoonjeongwon/leafy-postgres:1.0.0</code></pre>
<br>

<p>빌드한 이미지를 사용해 leafy-postgres 컨테이너 실행</p>
<pre><code class="language-docker">docker run -d --name leafy-postgres --network leafy-network 레지스트리계정명/leafy-postgres:1.0.0</code></pre>
<p>→ 이 이름은 나중에 Springboot에서 접근하기 때문에 꼭 기억해놓고 설정해야함 
<br></p>
<p>leafy-postgres 컨테이너의 로그 확인</p>
<pre><code class="language-docker">docker logs leafy-postgres</code></pre>
<p>→ init 문과 서버가 잘 실행된 것을 볼 수 있다.
<br></p>
<p>leafy-postgres 컨테이너 내에서 명령어 실행 후 결과 출력 (postgres의 터미널로 접근 가능)</p>
<pre><code class="language-docker">docker exec -it leafy-postgres su postgres bash -c &quot;psql --username=myuser --dbname=mydb&quot;</code></pre>
<p>→ SELECT * FROM USER; 가능 
→ q 누르면 나옴
→ exit 해서 터미널 나옴
<br></p>
<p>leafy-postgres 이미지의 레이어 확인</p>
<pre><code class="language-docker">docker image history 레지스트리계정명/leafy-postgres:1.0.0</code></pre>
<p><img src="https://velog.velcdn.com/images/garden-y/post/91c9ffe9-f159-4b07-9648-e8dc4398d779/image.png" alt=""></p>
<p>→ 0~24는 PostgreSQL의 기본 이미지인 postgres 13 버전의 레이어
→ PostgreSQL 이미지는 데비안 11이라는 OS 이미지를 베이스 이미지로 한다.
→ Postgres 이미지는 데비안이라는 OS 이미지가 베이스로 빌드
→ 우리는 Postgres 이미지를 베이스로 repeat Postgres 이미지를 빌드
→ 이처럼 레이어의 중첩관계를 가질 수 있다. (25부턴 커스텀)</p>
<br>
<br>

<h4 id="이미지-레지스트리-실습">이미지 레지스트리 실습</h4>
<p>→ 위의 dockerfile이 해주는 역할을 실제로 dockerfile을 사용하지않고 직접할 경우</p>
<ul>
<li>cp 명령어 : 실행 중인 컨테이너로 특정 파일을 복사할 수 있다. 컨테이너 내부에 있는 파일도 호스트머신으로 복사해 올 수 있다.</li>
</ul>
<p>컨테이너와 호스트 머신 간 파일 복사</p>
<pre><code class="language-docker">docker cp 원본위치 복사위치</code></pre>
<p>컨테이너 → 호스트머신으로 파일 복사</p>
<pre><code class="language-docker">docker cp 컨테이너명:원본위치 복사위치</code></pre>
<p>호스트머신 → 컨테이너로 파일 복사</p>
<pre><code class="language-docker">docker cp 원본위치 컨테이너명:복사위치</code></pre>
<p><br><br></p>
<h2 id="springboot-백엔드-컨테이너-구성">SpringBoot 백엔드 컨테이너 구성</h2>
<h3 id="백엔드-빌드-프로세스spring-boot">백엔드 빌드 프로세스(Spring Boot)</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/f60b48af-61eb-49af-8de3-b7c36e858e69/image.png" alt=""></p>
<ol>
<li>OS구성 및 Java Runtime 설치</li>
</ol>
<ul>
<li>자바로 개발된 소스코드는 jar, war 파일로 프로그램을 빌드할 수 있다.</li>
<li>jar, war 파일을 실행시키려면 OS에 자바 런타임이 설치되어 있어야 한다.</li>
</ul>
<ol start="2">
<li>빌드 도구 설치</li>
</ol>
<ul>
<li>소스코드를 애플리케이션으로 빌드하려면 Maven이나 Gradle이라는 빌드 프로그램이 필요하다.</li>
</ul>
<ol start="3">
<li>소스코드 다운로드</li>
</ol>
<ul>
<li>git clone …</li>
</ul>
<ol start="4">
<li>의존성 라이브러리 설치 및 빌드</li>
</ol>
<ul>
<li>gradle clean build 명령어 사용해서 애플리케이션으로 빌드한다.</li>
<li>빌드가 완료되면 jar 파일이 생성된다.</li>
</ul>
<ol start="5">
<li>애플리케이션 실행</li>
</ol>
<ul>
<li>java -jar leafy.jar</li>
<li>애플리케이션을 실행할 때 이렇게 자바 명령과 jar 파일의 경로를 지정해서 애플리케이션을 실행할 수 있다.</li>
</ul>
<p>→ 백엔드 애플리케이션에서는 소스코드 빌드 과정이 포함되어 있기 때문에 멀티 스테이징 빌드 기술을 활용한다.
→ 애플리케이션 빌드에 사용되는 이미지는 Gradle 이미지를 사용하고,
→ 이 gradle 이미지에서 빌드를 실행해서 만들어진 jar 파일을 이 자바 애플리케이션 실행 기능만 가지고 있는 OpenJDK 이미지로 복사한다.
→ OpenJDK 이미지에서는 이 컨테이너를 실행할 때 복사한 jar 파일 애플리케이션으로 실행할 것이다. </p>
<br>
<br>

<h3 id="실습-1">실습</h3>
<pre><code class="language-docker"># 빌드 이미지로 OpenJDK 11 &amp; Gradle을 지정
FROM gradle:7.6.1-jdk11 AS build

# 소스코드를 복사할 작업 디렉토리를 생성
WORKDIR /app

# 라이브러리 설치에 필요한 파일만 복사
COPY build.gradle settings.gradle ./

RUN gradle dependencies --no-daemon

# 호스트 머신의 소스코드를 작업 디렉토리로 복사
COPY . /app

# Gradle 빌드를 실행하여 JAR 파일 생성
RUN gradle clean build --no-daemon

# 런타임 이미지로 OpenJDK 11-jre-slim 지정
FROM openjdk:11-jre-slim

# 애플리케이션을 실행할 작업 디렉토리를 생성
WORKDIR /app

# 빌드 이미지에서 생성된 JAR 파일을 런타임 이미지로 복사
COPY --from=build /app/build/libs/*.jar /app/leafy.jar

EXPOSE 8080 
ENTRYPOINT [&quot;java&quot;] 
CMD [&quot;-jar&quot;, &quot;leafy.jar&quot;]
</code></pre>
<p>→ 도커파일을 사용해 이미지로 빌드하기</p>
<br>

<p>도커파일을 사용해 leafy-backend로 이미지 빌드</p>
<pre><code class="language-docker">docker build -t 레지스트리계정명/leafy-backend:1.0.0 .</code></pre>
<br>

<p>이미지 push</p>
<pre><code class="language-docker">docker push 레지스트리계정명/leafy-backend:1.0.0</code></pre>
<br>

<p>도커파일을 사용해 leafy-backend 이미지 빌드(컨테이너 실행)</p>
<pre><code class="language-docker">docker run -d -p 8080:8080 -e DB_URL=leafy-postgres --name leafy --network leafy-network 레지스트리계정명/leafy-backend:1.0.0</code></pre>
<br>

<p>leafy 컨테이너 로그 확인</p>
<pre><code class="language-docker">docker logs leafy</code></pre>
<br>

<p>curl <a href="http://localhost:8080/api/v1/users">http://localhost:8080/api/v1/users</a> → leafy 컨테이너의 로그 확인 </p>
<p><br><br></p>
<h2 id="vuejs-프론트엔드-컨테이너-구성">Vue.js 프론트엔드 컨테이너 구성</h2>
<p><img src="https://velog.velcdn.com/images/garden-y/post/21b133ca-4b11-4d30-b5ea-725e6b7c8f19/image.png" alt=""></p>
<ul>
<li>프레임워크 상관없이 프론트엔드는 html, css, js 파일들로 결과물을 빌드할 수 있다.</li>
<li>빌드한 파일은 root dir에 dist라는 폴더에 만들어진다.</li>
<li>이 파일들을 Nginx와 같은 웹서버의 특정 경로에 업로드하면 클라이언트가 브라우저를 통해서 웹서버에 접속했을 때 개발자가 개발한 웹페이지를 응답할 수 있다.<br>

</li>
</ul>
<h3 id="프론트엔드-빌드-프로세스vuejs">프론트엔드 빌드 프로세스(Vue.js)</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/e9f9a22a-ac01-450e-9e0e-170dee063ae7/image.png" alt=""></p>
<ol>
<li>OS구성 및 Nginx 웹 서버 설치<ul>
<li>OS에 Nginx 웹서버가 설치되어 있어야 한다.</li>
</ul>
</li>
<li>빌드 도구 설치<ul>
<li>소스코드를 빌드하기 위해서 node.js와 npm이 설치되어 있어야 한다.</li>
</ul>
</li>
<li>소스코드 다운로드<ul>
<li>git clone …</li>
</ul>
</li>
<li>의존성 라이브러리 설치 및 빌드<ul>
<li>외부 라이브러리는 npm ci 명령어로 다운 가능하다.</li>
<li>소스코드를 빌드하는 명령어는 npm run build 이다.  → dist 폴더에 결과 생성</li>
</ul>
</li>
<li>빌드 결과 폴더를 /usr/share/nginx/html 폴더로 복사<ul>
<li>cp ./dist /usr/share/nginx/html</li>
</ul>
</li>
<li>웹서버 실행<ul>
<li>nginx -g daemon off;</li>
</ul>
</li>
</ol>
<p>→ 멀티스테이징 빌드 기술을 활용해 불필요한 파일 크기를 최소화 한다.
→ 빌드과정에서 생성되는 파일은 웹서버에서는 사용되지 않는 파일들이기 때문에 빌드 과정을 node:14 이미지에서 실행시키고 결과파일인 dist 디렉터리만 실행 이미지 nginx로 복사해준다.
<br></p>
<h3 id="실습-2">실습</h3>
<pre><code class="language-docker"># 빌드 이미지로 node:14 지정 
FROM node:14 AS build

WORKDIR /app

# 라이브러리 설치에 필요한 파일만 복사
COPY package.json .
COPY package-lock.json .

# 라이브러리 설치
RUN npm ci

# 소스코드 복사
COPY . /app

# 소스코드 빌드
RUN npm run build

# 프로덕션 스테이지
FROM nginx:1.21.4-alpine 
COPY nginx.conf /etc/nginx/conf.d/default.conf.template
ENV BACKEND_HOST leafy
ENV BACKEND_PORT 8080

COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# 빌드 이미지에서 생성된 dist 폴더를 nginx 이미지로 복사
COPY --from=build /app/dist /usr/share/nginx/html

EXPOSE 80
ENTRYPOINT [&quot;docker-entrypoint.sh&quot;]
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]
</code></pre>
<p>leafy-frontend 이미지 빌드</p>
<pre><code class="language-docker">docker build -t 레지스트리계정명/leafy-frontend:1.0.0 .</code></pre>
<p>leafy-frontend 이미지 푸시</p>
<pre><code class="language-docker">docker push 레지스트리계정명/leafy-frontend:1.0.0 </code></pre>
<p>leafy-frontend 컨테이너 실행</p>
<pre><code class="language-docker">docker run -d -p 80:80 --name leafy-frontend --network leafy-network 레지스트리계정명/leafy-frontend:1.0.0 </code></pre>
<p><br><br></p>
<h2 id="leafy-애플리케이션-테스트">Leafy 애플리케이션 테스트</h2>
<h3 id="leafy-애플리케이션-아키텍처">Leafy 애플리케이션 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/097ef5a3-2723-4489-b27b-2885170730ca/image.png" alt=""></p>
<p>정상 실행 확인 후 구성 환경 삭제</p>
<pre><code class="language-docker">docker rm -f leafy-frontend leafy leafy-postgres</code></pre>
<p>→ 네트워크를 생략하고 진행한 내용, 네트워크는 다음 장에서..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 이미지 빌드 (Layer, Commit, Build, Build Context, Dockerfile 지시어, Multi-Stage Build)]]></title>
            <link>https://velog.io/@garden-y/Docker-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B9%8C%EB%93%9C-Layer-Commit-Build-Build-Context-Dockerfile-%EC%A7%80%EC%8B%9C%EC%96%B4-Multi-Stage-Build</link>
            <guid>https://velog.io/@garden-y/Docker-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B9%8C%EB%93%9C-Layer-Commit-Build-Build-Context-Dockerfile-%EC%A7%80%EC%8B%9C%EC%96%B4-Multi-Stage-Build</guid>
            <pubDate>Wed, 01 May 2024 00:33:38 GMT</pubDate>
            <description><![CDATA[<h1 id="part4-이미지-빌드">Part4. 이미지 빌드</h1>
<p>이미지가 어떻게 저장되는지 알아보고, 이미지를 효율적으로 저장하는 레이어드 구조에 대해서 알아보자. 이미지가 저장되는 원리를 알아보며, 이미지를 만드는 방법인 커밋과 빌드 두 가지 방식을 알아보자.</p>
<p>또한, 이미지를 빌드하려면 도커파일이라는 명세서를 작성해야 하는데, 도커파일을 작성하기 위해 필요한 문법들을 알아보고, 실제로 소스 코드를 애플리케이션 이미지로 빌드하자.</p>
<p>마지막으로 더 효율적인 빌드 방식인 멀티 스테이징을 알아보자. </p>
<br>

<h2 id="이미지와-레이어">이미지와 레이어</h2>
<h3 id="이미지의-구조---layer">이미지의 구조 - Layer</h3>
<p>이미지는 컨테이너를 실행하기 위한 읽기 전용 파일이다.
도커 이미지는 저장소를 효율적으로 사용하기 위해 레이어드 파일 시스템으로 구성되어있다. 레이어는 하나의 층을 의미하며, 여러 개의 층으로 구성되어 있는 것에서 하나의 층을 레이어라고 표현한다.  </p>
<p>이미지는 여러 개의 레이어로 구성되어 있다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/d9854f13-c887-4563-9943-77109c005604/image.png" alt=""></p>
<ul>
<li>nginx를 실행하면, 먼저 로컬 저장소에 이미지가 없는 것을 확인하고 도커 허브에서 이미지를 다운로드 받는다.</li>
<li>nginx라는 하나의 이미지를 다운받는 과정에서 pull 이 여러 단계에 걸쳐 되는 것을 확인할 수 있다. 여기서 한 줄이 하나의 레이어이며, 레이어들이 모여서 하나의 이미지를 구성한다.</li>
</ul>
<p>각각의 레이어는 이미지의 일부분을 나타낸다. 
<br></p>
<h4 id="하나의-이미지를-여러-개의-레이어로-구성한-이유">하나의 이미지를 여러 개의 레이어로 구성한 이유?</h4>
<ul>
<li>레이어드 구조가 재사용하기 유리한 구조이다.</li>
<li>공간을 효율적으로 사용할 수 있다.</li>
<li>이미지 저장하고 전송할 때 스토리지와 네트워크 사용량을 절약할 수 있다.</li>
</ul>
<h4 id="건축도면의-예시">건축도면의 예시</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/b88c863e-d3d3-496f-917d-9b2f60eede7b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/2e4f0831-389a-47f7-8197-499b71bc200b/image.png" alt=""></p>
<p>이를 위해 건축도면의 예시로 이해해보자. (똑같은 것은 아니지만 유사하다.)
→ 수정할 때 전체 도면이 영향을 받지 않고, 레이어마다 변경해주면 된다. 즉, 변경사항에 있어 재활용이 유리한 구조이다.
→ 겹치는 레이어를 재사용할 수 있다. 사용량도 줄이고, 보관하기에도 효율적이다. 즉, 데이터 전송에도 유리한 구조이다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/garden-y/post/ca93d58a-b560-450b-a3e4-c917dd1e922a/image.png" alt=""></p>
<p>이미지 레이어는 바로 직전 단계의 레이어에서 변경된 내용들만 저장된다. </p>
<p>서버의 간단한 페이지를 출력하는 Nginx를 설치한다 해보자. Nginx를 구성하는 단계는 먼저 OS를 설치하고 Nginx 소프트웨어를 설치 한 뒤, Nginx 설정 파일을 수정하고 브라우저에 표시되는 index.html 파일을 사용자에게 응답할 내용으로 수정해야 한다. </p>
<p>이 순서대로 이미지 레이어를 구성한다고 생각해보면 먼저 OS를 준비한 다음에 이 OS위에서 Nginx 소프트웨어를 설치한다. 소프트웨어를 설치하면 OS의 특정 폴더에 Nginx 소프트웨어 관련 파일들이 추가된다. 그래서 Nginx를 설치한다는 것은 기존 OS 파일 시스템에서 추가가 되는 부분이 있는 것이다. 기존 레이어에서 변경되는 것들은 기존 레이어를 수정하는 것이 아니라, 기존 레이터 위에 변경된 내용들이 새로운 레이어로 저장된다. </p>
<p>그래서 두번째 Nginx 설치 레이어에는 이전 레이어인 OS 레이어에서 Nginx 소프트웨어가 추가된 부분만 따로 가지고 있다.</p>
<p>→ 이전 레이어와 비교해서 추가되거나 변경된 파일들이 다음 레이어로 저장된다. 이미지에서 한 번 저장된 레이어는 변경할 수 없다. 따라서 변경 사항이 있다면 새로운 레이어로 저장해야 한다. </p>
<br>

<p>Nginx 설정 파일인 nginx.conf파일을 작성하나거나 index.html 파일의 내용을 수정하는 것도 각각 새로운 레이어로 만들어 진다. </p>
<p>ImageA를 완성한 뒤 이전과 똑같은 순서지만 마지막에서 index.html 파일의 내용을 custom한 ImageB를 만든다면, 3.Nginx 설정까지는 A,B 모두 같은 레이어를 재사용할 수 있다. 마지막만 다르기 때문에 마지막 레이어만 새롭게 추가되어 ImageB가 완성된다.</p>
<p>따라서 ImageA와 B는 총 3개의 레이어를 공유하고, 하나의 레이어를 별도로 사용하는 구조가 된다. 
<br></p>
<p><strong>→ 이미지의 레이어는 순차적으로 쌓이고, 각각의 레이어는 이전 레이어에서 변경된 부분을 저장한다. 같은 변경이 일어난 레이어는 공유해서 재사용할 수 있다.</strong></p>
<p><br><br></p>
<h3 id="이미지-레이어와-컨테이너-레이어">이미지 레이어와 컨테이너 레이어</h3>
<p>docker run 으로 컨테이너 실행 시, 이미지의 가장 마지막 레이어 위에서 새로운 <strong>읽기, 쓰기 전용 레이어인 ‘컨테이너 레이어’가 추가</strong>된다. </p>
<p>애플리케이션에서 로그가 쌓이거나 컨테이너가 실행 중에 생기는 모든 변경 사항들은 이 새로운 레이어에 저장된다.</p>
<p><strong>이미지의 레이어</strong>는 변경 불가능하기에 수정 불가능한 읽기 전용 레이어이다. 이미지 레이어는 컨테이너를 실행하기 위한 세이브 포인트 역할을 한다.</p>
<p><strong>컨테이너 레이어</strong>는 실제로 이 이미지를 컨테이너로 실행한 다음에 프로세스가 변경하는 내용을 기록하는 레이어이다. 컨테이너 레이어는 읽기 쓰기 모두 가능하기 때문에 컨테이너 레이어 한 장이 읽기 전용 레이어들인 이미지의 상단에 추가돼서 컨테이너 실행 중에 변경되는 내용만 기록하는 것이다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/ecc440c1-45c6-42b7-9249-e19f4e039672/image.png" alt=""></p>
<ul>
<li><p>ImageA로 Container2를 실행하면 컨테이너에 읽기, 쓰기 레이어가 생성된다.</p>
</li>
<li><p>Container1,2는 이미지가 동일하기 때문에 동일한 읽기 전용 레이어를 공유한다.
→ 실제 컨테이너를 실행할 때 전체 레이어를 복사하는 것이 아닌 읽기 전용 레이어인 이미지 위에 새로운 컨테이너 레이어만 하나씩 추가하는 것이다. (실제로 하나의 이미지를 공유해서 읽어오는 것이다.)</p>
</li>
<li><p>Container3의 경우 세번째 레이어까지만 공유한다. 따라서 이 컨테이너로 접근하면 B의 index.html 파일의 내용인 custom index가 출력된다.
→ 모든 컨테이너는 각자 자기만의 읽기 쓰기 레이어 한장을 가진다. 컨테이너를 만들 때 사용된 이미지에 따라서 이미지의 읽기 전용 레이어 전체를 공유할 수도, 일부만 공유할 수도 있다.
→ 이미지의 읽기전용 레이어를 활용하면 컨테이너를 실행할 때 전체 공간을 복사하지 않아도 돼서 빠르게 컨테이너를 실행할 수 있다. 
→ 컨테이너가 늘어나면서 사용하는 공간을 최대한 작게 관리할 수 있다.</p>
</li>
</ul>
<br>

<h4 id="이미지의-레이어-이력-조회">이미지의 레이어 이력 조회</h4>
<pre><code class="language-docker">docker image history 이미지명</code></pre>
<p>해당 명령어를 통해 3개의 이미지 레이어 이력을 조회하고, 레이어의 특징을 알아보자.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/0a1626ed-4efd-4985-be51-3e8ecb446ae2/image.png" alt=""></p>
<p>→ hello-nginx는 Nginx 오피셜 이미지 다운로드 후 index.html을 hello-nginx가 있는 파일로 덮어쓰게 했다.(COPY: 내가 가지고 있는 파일을 기존 레이어로 붙여넣기하는 방법)</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/0ffbc40b-2f5a-49af-9c54-e98ad5c8ea94/image.png" alt=""></p>
<p>→ 이전과 유사한데 한 줄 추가됐다. config-nginx의 경우 index.html을 덮어쓰기하고, 그 다음에 nginx.conf(nignx의 서버 설정 가능 파일)도 덮어쓰기 했다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/195d0473-ce9e-4122-aded-9ebcf3e9ee73/image.png" alt=""></p>
<p>→ 이전과 비슷하지만 순서가 다르다. pre-config-nginx이미지에선 nginx.conf를 먼저 수정하고, index.html를 수정했다. </p>
<br>

<pre><code class="language-docker">docker image inspect 이미지명</code></pre>
<p>→ 다음을 통해 이미지의 메타데이터를 확인해보면 “Layers”를 확인할 수 있다. 이때 각 레이어의 고유 해시값을 알 수 있는데 해시값은 SHA256 알고리즘으로 생성되고, 각각의 레이어의 변경사항을 암호화한 값이다. 
→ 이미지 레이어의 고유한 해시 값을 통해 다른 레이어와 구분할 수 있다. 즉, <strong>레이어마다 고유한 ID값이 있다.</strong> </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/49efc5d7-1e6d-4150-978e-06f52245e03a/image.png" alt=""></p>
<ul>
<li>hello-nginx와 config-nginx는 nginx.conf 빼고 동일하지만 pre-config-nginx는 nginx.conf와 index.html 수정이 다르다.</li>
</ul>
<p>→ 이미지의 레이어는 *<em>이전 파일에서 변경된 사항을 저장하기 때문에 결과적으로 두 개가 같은 내용이더라도 구성한 순서가 다르면 완전히 다른 레이어로 구성된다. *</em></p>
<br>

<h3 id="이미지-레이어-정리">이미지 레이어 정리</h3>
<ul>
<li><p><strong>Layering:</strong> 각 레이어는 이전 레이어 위에 쌓이며, 여러 이미지 간에 공유될 수 있다. 이미지는 레이어 방식을 통해 중복된 데이터를 최소화하고, 빌드 속도를 높이며, 저장소를 효율적으로 관리한다.</p>
</li>
<li><p><strong>Copy-on-Write(CoW)전략:</strong> 다음 레이어에서 이전 레이어의 특정 파일을 수정 할 때, 해당 파일의 복사본을 만들어서 변경 사항을 적용한다. 따라서 원래 레이어는 수정되지 않고, 그대로 유지된다. 새로운 레이어는 이전 레이어에 영향을 주지 않고, 자신의 레이어에 수정할 내용을 카피하고 수정해서 사용한다.
  → 읽기 쓰기 전용인 컨테이너 레이어를 생각하면 된다. 실행된 컨테이너에서 변경된 모든 것은 이미지가 아닌 컨테이너 레이어에 저장된다. </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/787fbd79-288a-4583-b704-57419eaace07/image.png" alt=""></p>
<p>→ nginx 컨테이너를 실행한 뒤 컨테이너 안에서 index.html를 수정한다 하면, 이미지 위에 있는 index.html 파일의 내용을 수정하는 것이 아니라, 컨테이너 레이어에 수정할 index.html 파일을 복사(Copy)해 온 다음 이 파일을 수정(Write)한다. 
→ 이때 Layer에서 index.html 파일은 총 3개가 되며, 이 중 가장 최근에 변경한 index.html이 실제 파일의 내용으로 사용된다. </p>
<ul>
<li><p><strong>Immutable Layers (불변 레이어):</strong> 이미지의 각 레이어는 불변으로, 한 번 생성되면 변경되지 않는다. 이렇게함으로써 이미지의 일관성을 유지하고, 여러 컨테이너에서 안전하게 공유할 수 있다.
  → CoW를 사용하는 목적이 Immutable Layers를 위해서이다.<br>  → 한 번 만들어진 이미지는 각 레이어가 해시로 암호화된 고유 값을 가지기 때문에 어떤 방식으로도 변경될 수 없다. 변경하기 위해선 CoW를 통해 새로운 레이어를 추가해야한다.<br>  → 이미지는 일관적이며, 동일한 이미지를 사용하는 컨테이너는 모두 동일한 파일 시스템 상태로 사용되는 것이 보장된다. </p>
</li>
<li><p><strong>Caching (캐싱):</strong> 레이어를 캐싱하여, 이미 빌드된 레이어를 재사용할 수 있다. 이는 이미지 빌드 시간을 크게 줄여주며, 같은 레이어를 사용하는 여러 이미지에서 효율적으로 작동한다.<br>  → 레이어를 재사용하는 기술이다. 이미지를 만들 때 레이어를 캐싱해두면 이미지 만드는 속도를 절약할 수 있다.</p>
</li>
</ul>
<p><br><br></p>
<h2 id="이미지-커밋">이미지 커밋</h2>
<p>이미지의 커밋 방식을 알아보고 커밋을 통해 이미지를 만들어 보자.</p>
<h3 id="이미지-생성-방법">이미지 생성 방법</h3>
<ol>
<li>실행 중인 컨테이너를 그 상태로 이미지를 만들어내는 <strong>커밋 방식</strong></li>
<li>도커 파일이라는 명세서를 통해 이미지를 만드는 <strong>빌드 방식</strong></li>
</ol>
<p>→ 대부분 빌드 방식을 사용하지만 빌드 방식이 커밋을 기반으로 동작하기에 커밋방식도 알아두는 것이 좋다.
<br></p>
<h4 id="commit-방식으로-nginx-웹-서버-이미지-만들기">Commit 방식으로 Nginx 웹 서버 이미지 만들기</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/b28001b6-b1e8-42ed-a07e-7000e6192382/image.png" alt=""></p>
<ol>
<li><p>공식 Nginx 이미지를 컨테이너로 실행시킨다.<br> → 이미 Nginx가 설치되어 있고, 기본으로 제공되는 default index.html 도 있다.</p>
</li>
<li><p>컨테이너의 내부 파일을 변경한다.<br> → docker run을 통해 nginx 이미지를 실행하면, Nginx 이미지 위에 읽기쓰기 전용 Contatiner 레이어가 만들어진다. 실행중인 컨테이너에서 index.html → helllo.html로 수정하면 이미지에 원래있던 파일을 컨테이너 레이어에 Copy 해온 뒤 수정된 파일의 내용을 Write 한다. </p>
</li>
<li><p>Commit 으로 새로운 이미지를 저장한다.<br> → 마지막으로 변경된 상태에서 도커의 커밋 기능을 사용해 컨테이너 레이어까지 포함한 모든 레이어의 상태를 이미지로 저장할 수 있다. </p>
</li>
</ol>
<h4 id="컨테이너-실행과-동시에-터미널-접속">컨테이너 실행과 동시에 터미널 접속</h4>
<pre><code class="language-docker">docker run -it --name 컨테이너명 이미지명 bin/bash</code></pre>
<ul>
<li>-it : 커맨드 창을 통해서 실행할 컨테이너와 직접 상호작용할 수 있다. (ex. Nginx 컨테이너 안에 직접 접속해서 명령어를 실행할 수 있다.)</li>
<li>직접 명령어를 실행하려면 cmd에 들어가는 실행명령어에 리눅스 shell을 명령어로 지정해줘야 하므로 -it에서 bin/bash를 해주면 cmd 대신 shell을 통해 사용자가 터미널로 접근할 수 있다.</li>
</ul>
<p>→ 이미지 내부의 파일 시스템을 확인해보거나 디버깅하는 용도로 많이 사용되는 방법이다. 
<br></p>
<h4 id="실행-중인-컨테이너를-이미지로-생성">실행 중인 컨테이너를 이미지로 생성</h4>
<pre><code class="language-docker">docker commit -m 커밋명 실행중인컨테이너명 생성할이미지명</code></pre>
<h3 id="실습---이미지-커밋-방식으로-새로운-이미지-만들기">실습 - 이미지 커밋 방식으로 새로운 이미지 만들기</h3>
<p>(1),(2)는 각각의 터미널을 의미한다.</p>
<ul>
<li><p>(1) docker run -it --name officialNginx nginx bin/bash
  → 컨테이너 안의 shell로 접근할 수 있다. 여기서 ls를 하면 내 pc가 아닌 컨테이너의 ls가 보여진다.</p>
</li>
<li><p>(2) docker ps 
  → 정상적으로 officialNginx가 실행된 것을 확인할 수 있다.</p>
</li>
<li><p>(1) echo hello-my-nginx &gt; usr/share/nginx/html/index.html
  → 실행되는 컨테이너에서 내용 변경한다. </p>
</li>
<li><p>(2) docker commit -m “edited index.html by devwiki” -c ‘CMD[”nginx”, “-g”, “deamon off”]’ officialNginx garden/commitnginx
  → 실행 중인 officialNginx 컨테이너를 이미지로 커밋한다.</p>
</li>
<li><p>(2) docker image ls (개인레지스트리명)/commitnginx<br>  → 이미지가 생성된 것 확인 가능하다. </p>
</li>
<li><p>(2) docker image history (개인레지스트리명)/commitnginx
  → 맨 위로 가면 새로운 코멘트로 새로운 이미지 레이어가 추가된 것을 확인할 수 있다. </p>
</li>
<li><p>(2) docker run -d -p 80:80 --name my-nginx (개인레지스트리명)/commitnginx
  → 변경한 새로운 이미지로 컨테이너를 실행한다.
  → 도커허브계정명에 commitnginx로 이미지명을 지정해서 컨테이너를 실행한다.</p>
</li>
<li><p>(2) docker rm -f ofiicialNginx my-nginx
  → 실행한 모든 컨테이너를 삭제한다. </p>
</li>
<li><p>(2) docker push 레지스트리계정명/commitnginx
  → 실습에서 제작한 이미지를 개인 레지스트리에 푸시한다.</p>
</li>
</ul>
<p>→ <strong>커밋 방식을 사용하면 기존 레이어에 새로운 레이어를 한장 더 추가할 수 있다.</strong> 이런 식으로 컨테이너의 이미지는 기존 이미지의 레이어에 새로운 레이어를 쌓아가면서 이미지를 만드는 것이다.
<br><br></p>
<h2 id="이미지-빌드">이미지 빌드</h2>
<h4 id="iac란">IaC란?</h4>
<ul>
<li>현대 인프라 구성에서 가장 중요한 개념 중 하나로 IaC는 방법론이다.</li>
<li>Infrastructure as Code, 인프라 상태를 코드로 관리하는 의미이다.</li>
<li>도커에서도 IaC를 활용해 코드로 이미지를 관리하는 방식이 이미지 빌드이다.</li>
</ul>
<p>기업에서 휴먼에러(사람이 직접 인프라를 컨트롤 하다 보면 실수가 나올 수 있는 것들을 의미한다.) 가 발생하기도 하며, 개인이 인프라를 관리하면 인프라의 상태를 변경한 기록들을 관리하기 쉽지 않다.</p>
<p>그래서 IaC 방법을 사용해 코드로 인프라의 상태를 관리할 수 있다. </p>
<p>코드에 상세 작업 내용이 기재되어 있고, 이 작업을 사람이 아닌 프로그램이 대신 수행해 주는 것이다. 프로그램이 작업을 하기 때문에 작업들을 더 빠르고 안전하게 실행할 수 있다. </p>
<p>코드에 들어가는 내용은 프로그램이 작업하기 위한 일련의 작업 명세서로 사용된다. 이런 명세서를 Github 같은 소스 코드 레포지터리에 저장하면 인프라의 상태도 소스코드처럼 버전관리를 할 수 있다. </p>
<p>IaC 방법론을 활용하는 많은 소프트웨어들이 시중에 있고, Docker도 그 중 하나이다. 
Docker는 Dockerfile이라는 소스코드를 사용해서 인프라의 상태를 저장하는 이미지를 만들 수 있다. 
<br></p>
<h3 id="커밋-vs-빌드">커밋 vs 빌드</h3>
<h4 id="커밋">커밋</h4>
<ul>
<li>이미지를 만들 때마다 컨테이너를 실행해야 한다.</li>
<li>사용자가 명령어를 직접 입력해야 한다.</li>
<li>커밋 하나 당 이미지의 레이어가 하나 추가되기 때문에 여러 개의 레이어를 추가하고 싶을 때는 여러번의 커밋을 추가해야 한다.</li>
</ul>
<p>→ 복잡하기 때문에 사람이 직접 작업하면 문제가 발생할 가능성이 크다.</p>
<h4 id="빌드">빌드</h4>
<ul>
<li>컨테이너를 생성하고 커밋하는 것을 도커가 대신 수행한다.</li>
<li>도커에게 어떤 작업을 수행할 지를 코드로 작성한 것이 도커 파일이다.</li>
</ul>
<p>이미지 제작자가 도커가 이해할 수 있는 문법에 따라 도커 파일을 작성하면, 도커는 임시로 컨테이너를 실행하고 사용자가 정의한 작업을 수행한 뒤 커밋을 실행한다.</p>
<p>따라서 도커 빌드를 활용하면 여러 개의 레이어도 쉽게 추가할 수 있다. </p>
<p>레이어 한 장을 추가한 다음에 다시 그 레이어로 만든 이미지를 컨테이너로 만들고, 그 컨테이너 위에서 변경 작업을 한 뒤 추가로 커밋하는 것을 도커가 알아서 반복해준다. </p>
<p>→ 도커는 IaC 방식에 따라 이미지를 도커 파일이라는 소스코드로 관리할 수 있다. 코드이기 때문에 애플리케이션 소스 코드와도 함께 관리할 수 있고, 버전 관리도 할 수 있다.
→ 도커파일에는 이미지를 어떻게 만드는지에 대한 세부 내용이 기재되어 있고, 도커는 이 도커파일을 해석해서 이미지를 제작해준다. </p>
<p>이미지 빌드는 도커에서 가장 많이 사용되는 기능 중 하나로, 컨테이너 애플리케이션 개발에 있어서 가장 핵심 기능이라 볼 수 있다. </p>
<p>이미지는 도커 빌드 명령어를 통해 빌드할 수 있다.</p>
<pre><code class="language-docker">docker build -t 이미지명 Dockerfile경로</code></pre>
<br>

<h4 id="도커-파일-문법">도커 파일 문법</h4>
<p>: 도커 파일 문법은 지시어와 지시어의 옵션으로 구성된다. </p>
<ul>
<li><p>FROM 이미지명
  → 새롭게 빌드할 이미지에 기반이 되는 베이스 이미지를 지정한다. 
  → 필수로 있어야 하며, 파일 시스템이 있는 이미지를 이미지 베이스로 지정하는 것이 좋다. 
  → Java 애플리케이션을 실행하려면 Java 런타임이 설치된 이미지를 베이스로.. Node.js는 Node.js가 설치된 이미지로 하는 것이 좋다. 
  (실습에선 Nginx 이미지(Nginx 웹서버를 실행하기 위한 모든 것이 이미 준비되어 있기에)를 베이스로 웹서버 컨테이너를 제작할 예정이다.)</p>
</li>
<li><p>COPY 파일경로 복사할경로
  → 파일을 레이어에 복사한다.</p>
</li>
<li><p>CMD [”명령어”]
  → 이미지를 컨테이너 실행 시 컨테이너 안에서 프로그램을 실행할 명령어를 지정한다.
  → CMD 지시어에 작성한 명령어는 메타데이터의 CMD 필드에 저장된다. </p>
</li>
</ul>
<br>

<h3 id="정리">정리</h3>
<p>커밋은 사용자가 직접 새로운 이미지를 만드는 방법이다.</p>
<p>빌드는 도커 데몬이 도커 파일에 적힌 지시어를 사용해서 이미지를 자동으로 만들어주는 방법이다.</p>
<p>도커 빌드는 IaC 개념으로 인프라의 상태를 도커 파일이라는 코드로 관리할 수 있다. 커밋 빌드에서 사용자가 직접 수행했던 일들을 Dockerfile의 지시어로 기록해놓고 필요할 때마다 도커 데몬에게 이미지 빌드를 맡긴다. </p>
<p>하나의 커밋은 기존 레이어에 하나의 새로운 레이어를 추가하며, 여러 레이어를 쌓으려면 커밋을 완료한 이미지를 새로운 컨테이너로 만들고 다시 두번째 레이어를 커밋하는 순서대로 여러 차례 커밋을 수행해야 한다. but Dockerfile을 사용하면 도커가 직접 이 작업들을 반복해주기 때문에 여러 개의 레이어 구조를 편리하게 활용할 수 있다.</p>
<p><br><br></p>
<h2 id="이미지-컨텍스트">이미지 컨텍스트</h2>
<h4 id="빌드-컨텍스트란">빌드 컨텍스트란?</h4>
<p>이미지를 빌드할 때 사용되는 폴더이다.</p>
<p>이미지 빌드 방식은 도커 데몬이 임시 컨테이너를 실행시키면서 레이어드를 하나씩 추가한다. 그래서 도커 데몬에게 도커 파일과 빌드에 사용되는 파일들을 전달해줘야 한다. </p>
<p>이렇게 도커 데몬에게 전달해주는 폴더를 빌드 컨텍스트라고 한다.
(이전 실습에선 Dockerfile을 작성한 01.buildnginx 폴더가 빌드 컨텍스트이다.)</p>
<p>도커 빌드 명령 시 빌드 컨텍스트가 도커 데몬에게 통째로 전달된다.
그래서 이 컨텍스트 안에 있는 도커 파일로 도커 데몬이 이미지를 빌드하는 것이다. 그리고 도커 파일에서 COPY 지시어를 사용하면 빌드 컨텍스트에 있는 파일이 빌드에 사용되는 컨테이너로 복사된다.
도커 데몬은 빌드 컨텍스트에 있는 파일만 COPY 명령으로 복사할 수 있다.</p>
<p>→ 빌드 컨텍스트란, 도커 데몬이 이미지를 빌드할 때 전달되는 폴더이고, 이 폴더 안에 도커 파일과 COPY에 사용할 파일들이 모두 있어야 한다. </p>
<p>빌드 컨텍스트의 .dockerignore 파일을 통해 빌드 컨텍스트로 전달될 파일을 관리할 수 있다. 예를 들어 이미지 빌드에 필요하지 않은 3GB 크기의 파일이 있다면, 이런 라지정크를 도커 데몬에 전달하는 것은 비효율적이다. 따라서 이 파일명을 .dockerignore 파일에 적어 놓으면 이를 제외하고 전달할 수 있다. </p>
<p>빌드 컨텍스트는 도커 데모에게 전달돼야 하기 때문에 빌드 컨텍스트의 크기가 커질수록 전송시간이 길어지고, 폴더의 크기가 비정상적으로 커지면 빌드에 문제가 생길 수 있다. 그래서 도커 파일과 빌드에 사용되는 파일만 별도의 폴더로 관리해야 한다. </p>
<p><br><br></p>
<h2 id="도커파일-지시어">도커파일 지시어</h2>
<p>Node.js로 개발된 소스코드를 애플리케이션으로 빌드하려면,</p>
<ol>
<li>Node.js가 설치된 OS 환경이 필요하다.</li>
<li>Node.js 개발된 소스 코드가 필요하다. 보통 이 코드는 Git을 통해 다운 받는다. </li>
<li>의존 라이브러리를 설치(application build)해야한다. (여기선 npm install)</li>
<li>애플리케이션을 실행시킨다. (npm start)<br>

</li>
</ol>
<h3 id="애플리케이션-빌드-vs-도커-이미지-빌드">애플리케이션 빌드 VS 도커 이미지 빌드</h3>
<h4 id="애플리케이션-빌드">애플리케이션 빌드</h4>
<ul>
<li>소스코드를 실행 가능한 상태로 만드는 것이다.</li>
<li>소스코드만 가지고 있는 상태로는 프로그램이라 부를 수 없고, 소스코드가 실행되기 위해 필요한 라이브러리를 설치하거나 소스코드를 실행 가능한 프로그램으로 만드는 과정을 통틀어 말한다.</li>
<li>애플리케이션 빌드를 통해 만들어낸 결과물을 애플리케이션(Application), 프로그램(Program), 아티팩트(Artifact)라 한다.</li>
</ul>
<h4 id="도커-이미지-빌드">도커 이미지 빌드</h4>
<ul>
<li>컨테이너로 바로 실행할 수 있는 이미지를 만드는 것이다.</li>
<li>컨테이너로 실행하는 소프트웨어 중에서는 사용자가 개발한 애플리케이션을 실행할 수도 있지만 DB같은 일반적인 소프트웨어를 실행시키는 경우도 많다. 이런 프로그램을 실행시키는 컨테이너는 별도의 소스코드가 필요하지 않기 때문에 이미지를 빌드하는 과정이 포함되어 있지 않다.</li>
<li>but 개발자가 개발한 소스코드를 애플리케이션 이미지로 빌드할 때는 이 이미지를 빌드하는 과정 안에서 보통은 소스 코드를 다운 받아서 실제로 실행할 수 있는 아티팩트로 만드는 애플리케이션 빌드 과정이 포함되어 있다.</li>
</ul>
<p>→ 도커 이미지를 빌드할 때 일반적인 소프트웨어만 실행할 때는 실행 환경을 준비하고 실행할 프로그램만 준비하면 된다. 하지만 개발한 소스코드를 이미지로 빌드하려면 먼저 소스 코드 애플리케이션으로 빌드하고, 그 애플리케이션을 실행할 수 있는 환경을 준비하면 된다. 따라서 후자의 경우엔 컨테이너 이미지를 빌드하는 과정 안에서 애플리케이션 빌드 과정이 포함되어 있다. 
<br></p>
<h3 id="기본-dockerfile-지시어">기본 Dockerfile 지시어</h3>
<pre><code class="language-docker">FROM 이미지명
COPY 빌드컨텍스트경로 레이어경로
RUN 명령어
CMD[&quot;명령어&quot;]</code></pre>
<ul>
<li>FROM은 새롭게 빌드할 이미지에 기반이 되는 베이스 이미지를 지정한다.</li>
<li>COPY는 빌드 컨텍스트로 전달된 파일의 기존 레이어에 복사한다. 그리고 복사하면서 새로운 레이어를 만든다.</li>
<li>RUN은 컨테이너 안에서 명령어를 실행하고 결과를 새로운 레이어로 저장한다.</li>
<li>CMD는 컨테이너가 실행될 때 기본 명령어를 지정한다. 컨테이너의 실행 때만 사용되기 때문에 별도의 이미지 레이어를 추가하지 않는다.</li>
</ul>
<p>→ 새로운 레이어를 추가하는 지시어와 추가하지 않는 지시어가 있다. 파일 시스템의 내용을 변경하는 부분이 있으면 일반적으로 레이어를 추가하고, 메타데이터에만 영향을 주는 부분은 레이어가 추가되지 않는다. 그래서 지시어들을 잘 활용해서 이미지의 레이어 개수를 관리할 수 있다. 
<br></p>
<h4 id="도커파일명이-dockerfile이-아닌-경우-별도-지정">도커파일명이 Dockerfile이 아닌 경우 별도 지정</h4>
<pre><code class="language-docker">docker build -f 도커파일명 -t 이미지명 Dockerfile 경로</code></pre>
<ul>
<li>일반적으로 도커파일명을 Dockerfile이라 지정하는데 종종 다르게 저장하는 경우가 있다.</li>
<li>예를 들어 케이스별로 다른 도커 파일 빌드가 필요한 경우다.</li>
<li>그래서 도커파일의 이름이 Dockerfile이 아닌 경우 -f 옵션을 줘서 실제로 빌드에 사용할 도커 파일의 이름을 지정해줘야 한다.</li>
<li>docker build -f Dockerfile-basic -t buildap:basic .</li>
</ul>
<p>→ 이렇게 도커를 활용하면 Node.js가 컴퓨터에 설치되어 있지 않아도 컨테이너 이미지를 통해 Node.js 애플리케이션을 빌드하고 실행할 수 있다. 
<br></p>
<h3 id="기본-dockerfile-지시어-1">기본 Dockerfile 지시어</h3>
<pre><code class="language-docker">WORKDIR 폴더명
USER 유저명
EXPOSE 포트번호</code></pre>
<ul>
<li><p>명령어들이 작업할 디렉토리를 지정할 수 있다.(cd) WORKDIR 다음에 나오는 지시어들은 이 명령어에서 지정한 디렉토리를 기준으로 명령어를 수행한다.
  → 이후 사용되는 지시어들에게 영향을 줄 수 있어서 가능한 FROM 다음에 바로 작성하는 것이 좋다. 특정 경로를 지정해 놓으면 기존의 Node.js 이미지에 있던 파일시스템과 섞이지 않고 별도의 폴더를 만들어서 관리할 수 있어 초반에 경로를 지정하는 것이 좋다. </p>
</li>
<li><p>명령을 실행할 때 명령어를 사용할 기본 사용자를 지정한다.(su) 도커 컨테이너가 실행될 때는 기본적으로 명령어들이 루트 사용자로 실행되기 때문에 실행된 프로세스가 굉장히 많은 권한을 가질 수 있다. 그리고 이 점은 보안에 취약할 수 있기 때문에 컨테이너가 필요 이상의 권할을 가지지 않도록 유저 지시어로 조절할 수 있다.</p>
</li>
<li><p>컨테이너가 실행될 때 사용할 네트워크의 포트를 기재한다. 이 명령어를 사용하지 않아도 기본적으로 컨테이너가 모든 포트를 사용할 수 있다. 그래도 이 명령어를 사용하는 이유는 이 도커파일을 읽는 다른 사람에게 애플리케이션이 사용하는 포트를 문서처럼 기재하기 위해서이다.
  보통은 애플리케이션 소스 코드 안에 애플리케이션이 사용할 포트를 지정한다. 이렇게 도커파일 안에도 EXPOSE로 애플리케이션이 사용할 포트를 명시 해놓으면 굳이 소스코드를 보지 않아도 Dockerfile을 통해 포트를 확인할 수 있다.</p>
</li>
</ul>
<br>

<h3 id="환경변수-설정-지시어">환경변수 설정 지시어</h3>
<pre><code class="language-docker">ARG 변수명 변수값
ENV 변수명 변수값</code></pre>
<p>→ 시스템에서 환경변수는 다양한 방법으로 활용할 수 있다.(ex. 환경변수에 따라 다른 색깔의 웹페이지, DB에 접속하기 위해 DB접속 정보를 환경변수에 저장..)
→ ARG와 ENV의 차이는 컨테이너를 실행할 때 환경변수가 유지되는지이다.</p>
<ul>
<li><p>ARG로 지정한 환경변수는 도커 빌드 명령으로 이미지를 빌드할 때만 사용된다. 
  → --build-arg 옵션으로 덮어쓰기할 수 있다. </p>
</li>
<li><p>ENV로 지정한 환경변수는 도커 빌드뿐만 아니라 이 이미지를 컨테이너로 실행할 때까지 지속적으로 유지된다.
  → -e 옵션으로 덮어쓰기할 수 있다. </p>
</li>
</ul>
<p>→ 기본적으로 특수한 경우를 제외하고는 애플리케이션에서 환경변수를 설정하고 싶으면 ENV로 설정해주면 된다. 
<br></p>
<h3 id="프로세스-실행-지시어">프로세스 실행 지시어</h3>
<pre><code class="language-docker">ENTRYPOINT[&quot;명령어&quot;]
CMD[&quot;명령어&quot;]</code></pre>
<ul>
<li>CMD는 띄어쓰기 기준으로 여러 명령어를 넣을 수 있다.</li>
<li>CMD로 관리하는 명령어 중에서는 일반적으로 고정되는 값들이 많이 있다.(ex. npm install, npm start..)</li>
<li>중복되는 명령어는 도커파일의 ENTRYPOINT로 지정해서 관리할 수 있다.</li>
</ul>
<p>docker build -f Dockerfile-entrypoint -t buildapp:entrypoint . 
→ Dockerfile-entrypoint 도커파일을 사용해 실습 이미지 빌드</p>
<p>docker run --name buildapp-entrypoint-list buildapp:entrypoint list
→ buildapp:entrypoint 이미지를 CMD를 list로 덮어씌우며 컨테이너로 실행
→ 실제 entrypoint에 npm이 있기때문에 실제로 실행되는 것은 npm list가 실행 될 것이다. </p>
<p>docker run -it --name buildapp-entrypoint-bash buildapp:entrypoint /bin/bash
→ bash로도 접근, 원래 이렇게 덮어쓰기하면 shell로 접근되었는데 엔트리 포인트에 npm이라는 명령어가 있기 때문에 실제 실행되는 명령어는 npm /bin/bash가 실행돼서 에러가 발생한다. </p>
<br>

<p>→ <strong>ENTRYPOINT를 사용해 사용자가 의도하지 않은 명령어로 접근하는 것을 1차적으로 막을 수 있다.</strong> (but ENTRYPOINT도 ENV처럼 컨테이너로 실행할 때 덮어쓰기가 가능하기 때문에 100% 보안적으로 안전하진 않다.)</p>
<p><br><br></p>
<h2 id="멀티-스테이지-빌드">멀티 스테이지 빌드</h2>
<h4 id="멀티-스테이징-빌드란">멀티 스테이징 빌드란?</h4>
<p>→ 도커 파일에서 두 개의 베이스 이미지를 활용하는 방법이다.</p>
<p>보통 애플리케이션을 빌드하는 과정에서 만들어지는 파일들이 용량을 많이 차지한다. 이 파일들은 실제로 애플리케이션이 실행되는데 사용되지 않기 때문에 이미지를 빌드에 사용하는 이미지와 실행에 사용하는 이미지로 나누는 것이다. </p>
<p>→ 장점은 애플리케이션이 실행되는 <strong>이미지의 크기를 줄일 수 있다</strong>. </p>
<br>

<h4 id="백엔드-springboot-application-이미지-구성">백엔드 SpringBoot Application 이미지 구성</h4>
<ol>
<li>OS 구성 및 Java Runtime 설치(jar 파일 실행시키기 위해 필요)</li>
<li>빌드 도구 설치(Maven, Gradle이 필요)</li>
<li>소스코드 다운로드 (git clone ..)</li>
<li>의존성 라이브러리 설치 및 빌드 (mvn clean package)</li>
<li>애플리케이션 실행(java -jar app.jar)</li>
</ol>
<pre><code class="language-docker"># 빌드 환경 설정
FROM maven:3.6-jdk-11 
WORKDIR /app

# pom.xml과 src/ 디렉토리 복사
COPY pom.xml .
COPY src ./src

# 애플리케이션 빌드(jar 파일 생성)
RUN mvn clean package

# 빌드된 JAR 파일을 실행 환경으로 복사
RUN cp /app/target/*.jar ./app.jar

# 애플리케이션 실행
EXPOSE 8080
CMD [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code></pre>
<ul>
<li>빌드 컨텍스트에 있는 java 소스코드를 java 애플리케이션을 실행하는 컨테이너 이미지로 빌드할 수 있다.</li>
<li>하지만 이 과정에서 실제 애플리케이션 실행에 필요하지 않은 파일들이 많이 생성된다.  (ex. 메이븐 도구들은 애플리케이션이 빌드될 때만 사용되지 애플리케이션이 실행되는 데는 필요하지 않다. → 실제 애플리케이션 이미지의 사이즈도 커진다.)
→ 멀티스테이징 기술을 활용해서 애플리케이션을 빌드할 때는 메이븐 이미지를 사용하고, 메이븐 이미지가 만들어낸 jar 파일만 가지고 애플리케이션을 실행하는 이미지를 별도로 만들 수 있다. </li>
</ul>
<p>이미지 빌드 과정을 두가지로 나눠보자.
→ 애플리케이션 빌드 &amp; 애플리케이션 실행 </p>
<ul>
<li>애플리케이션 빌드 : Maven 도구, 소스 코드</li>
<li>애플리케이션 실행 : Java Runtime, jar 파일</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/3fed143f-6653-4d06-825b-369fd97b950b/image.png" alt=""></p>
<p>→ 첫번째 단계에서는 소스코드를 복사한 뒤, mvn 패키지 명령을 통해 jar 파일을 만든 후, 실제 애플리케이션을 실행하는 이미지에는 Java Runtime만 있는 이미지를 가져온 다음 앞선 결과의 jar파일만 복사해와서 애플리케이션 실행에 사용한다.</p>
<p>빌드 스테이지와 실행 스테이지. 두개를 나눠서 빌드하는 방식이 멀티 스테이지 빌드이다. </p>
<pre><code class="language-docker"># 첫번째 단계: 빌드 환경 설정
FROM maven:3.6 AS build
WORKDIR /app

# pom.xml과 src/ 디렉토리 복사
COPY pom.xml .
COPY src ./src

# 애플리케이션 빌드
RUN mvn clean package

# 두번째 단계: 실행 환경 설정
FROM openjdk:11-jre-slim
WORKDIR /app

# 빌드 단계에서 생성된 JAR 파일 복사
COPY --from=build /app/target/*.jar ./app.jar

# 애플리케이션 실행
EXPOSE 8080
CMD [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]
</code></pre>
<ul>
<li>AS build를 통해 maven 이미지를 사용한 스테이지에 이름을 지정해준다.</li>
<li>openjdk:11-jre-slim은 Java 애플리케이션 실행에 필요한 Java Runtime만 가지고 있는 이미지이다.</li>
<li>RUN mvn clean package를 통해  maven이미지 target 폴더에 jar 파일이 생성된다.</li>
<li>COPY --from=build /app/target/*.jar ./app.jar를 통해 jar파일을 복사해온다. --from 옵션으로 다른 스테이지 이름을 쓰고, 다른 스테이지에서 파일을 가져온다.</li>
<li>FROM이 두 개이면 도커가 두 개의 컨테이너를 동시에 생성하고, 첫 번째 컨테이너에서 만들어진 파일을 두 번째 컨테이너로 복사해올 수 있다.</li>
</ul>
<p>→ 첫 번째 컨테이너는 maven 컨테이너이고, 이 컨테이너를 통해 만들어낸 jar 파일을 jdk 컨테이너에서 복사해 와서 사용하는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/d630837d-a3d5-433f-b47b-eb0dd2bb5aa6/image.png" alt=""></p>
<p>→ 멀티 스테이징 기술을 잘 활용하면 애플리케이션의 이미지 크기를 크게 줄일 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective Java] Item 1-4]]></title>
            <link>https://velog.io/@garden-y/Effective-Java-Item-1-4</link>
            <guid>https://velog.io/@garden-y/Effective-Java-Item-1-4</guid>
            <pubDate>Sat, 27 Apr 2024 03:04:03 GMT</pubDate>
            <description><![CDATA[<h2 id="item1-생성자-대신-정적-패터리-메서드를-고려하라">Item1. 생성자 대신 정적 패터리 메서드를 고려하라</h2>
<h4 id="클라이언트가-클래스의-인스턴스를-얻는-방법">클라이언트가 클래스의 인스턴스를 얻는 방법</h4>
<ul>
<li>public 생성자</li>
<li>정적 팩터리 메서드(static factory method)
  → 클래스의 인스턴스를 반환하는 단순한 정적 메서드</li>
</ul>
<h4 id="정적-팩터리-메서드static-factory-method의-장점">정적 팩터리 메서드(static factory method)의 장점</h4>
<ul>
<li>이름을 가질 수 있다.
  → 생성자에 넘기는 매개변수와 생성자 자체만으로는 반환된 객체의 특성을 제대로 설명하지 못하는 반면, 정적 팩터리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다. </li>
<li>호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
  → 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다. 따라서 (특히 생성 비용이 큰) 같은 객체가 자주 요청되는 상황이라면 성능을 상당히 끌어올려 준다. (like Flyweight pattern)
  (대표 적인 예 Boolean.valueOf(boolean))
  → 언제 어느 인스턴스를 살아 있게 할지 철저히 통제할 수 있다. : 인스턴스 통제 클래스    </li>
<li>반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.<br>  → 반환할 객체의 클래스를 자유롭게 선택할 수 있게 하는 엄청난 유연성을 가진다.    </li>
<li>입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.</li>
<li>정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.</li>
</ul>
<h4 id="인스턴스를-통제하는-이유">인스턴스를 통제하는 이유?</h4>
<ul>
<li>클래스를 싱글턴(singletone)으로 만들 수 있다.</li>
<li>인스턴스화 불가로 만들 수 잇다.</li>
<li>불변 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있다.</li>
</ul>
<h4 id="정적-팩터리-메서드static-factory-method의-단점">정적 팩터리 메서드(static factory method)의 단점</h4>
<ul>
<li>상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.<br>  → 상속보다 컴포지션을 사용하도록 유도하고 불변 타입으로 만들려면 이 제약을 지켜야한다는 점에서 오히려 장점일수도..    </li>
<li>정적 팩터리 메서드는 프로그래머가 찾기 어렵다.<br>  → 문서를 잘 작성하고 알려진 규약을 따라 짓는 식으로 문제를 완화해줘야 한다. (from, of, valueOf, instance, create, getType, newType, type …)</li>
</ul>
<h3 id="핵심-정리">핵심 정리</h3>
<p>→ 인스턴스를 생성하는 두가지 방법의 상대적인 장단점을 이해하고 사용하자. 정적 팩터리를 사용하는게 유리한 경우가 더 많기에 무작정 public 생성자를 제공하지 말자.</p>
<p><br><br></p>
<h2 id="item2-생성자에-매개변수가-많다면-빌더를-고려하라">Item2. 생성자에 매개변수가 많다면 빌더를 고려하라</h2>
<h4 id="정적-팩터리와-생성자에게-공통된-제약">정적 팩터리와 생성자에게 공통된 제약</h4>
<p>→ 선택적 매개변수가 많을 때 적절히 대응하기 어렵다. </p>
<h4 id="점층적-생성자-패턴telescoping-constructor-pattern">점층적 생성자 패턴(telescoping constructor pattern)</h4>
<ul>
<li><p>필수 매개 변수만 받는 생성자부터 .. 선택 매개 변수를 다 받는 생성자까지 늘려가는 방식이다.</p>
<pre><code class="language-java">  public class NutritionFacts {
      private final int servingSize; // （ml, 1회 제공량） 필수
      private final int servings; // （회, 총 n회 제공량） 필수
      private final int calories; // （1회 제공량당） 선택
      private final int fat; // （g/1 회 제공량） 선택
      private final int sodium; // （mg/1 회 제공량） 선택
      private final int carbohydrate; // （g/1 회 제공량） 선택

      public NutritionFacts（int servingSize, int servings） {
          this（servingSize, servings, 0）;
      }
      public NutritionFacts（int servingSize, int servings, int calories） {
          this（servingSize, servings, calories, 0）;
      }
      public NutritionFacts(int servingSize, int servings,int calories, int fat) {
          this(servingSize, servings, calories, fat, 0);
      }
      public NutritionFacts(int servingSize； int servings, int calories, int fat, int sodium) {
          this(servingSize, servings, calories, fat, sodium, 0);
      }
      public NutritionFactsdnt servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
          this.servingSize = servingSize;
          this.servings = servings;
          this.calories = calories;
          this.fat = fat;
          this.sodium = sodium;
          this.carbohydrate = carbohydrate;
      }
  }</code></pre>
</li>
<li><p>단점</p>
<ul>
<li>사용자가 설정하길 원치 않는 매개변수까지 포함하기 쉬운데 그런 매개변수에도 값을 지정해줘야 한다.</li>
<li>매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.</li>
</ul>
</li>
</ul>
<h4 id="자바빈즈-패턴javabeans-pattern">자바빈즈 패턴(JavaBeans pattern)</h4>
<ul>
<li><p>매개변수가 없는 생성자로 객체를 만든 후, setter 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식이다.</p>
<pre><code class="language-java">  public class NutritionFacts {
      //매개변수들은 （기본값이 있다면） 기본값으로 초기화된다
      private int servingSize = -1; // 필수; 기본값 없음
      private int servings = -1; //필수; 기본값 없음
      private int calories = 0;
      private int fat = 0;
      private int sodium = 0;
      private int carbohydrate = 0;

      public NutritionFacts() { }

      // 세터 메서드들
      public void setServingSize(int val) { servingSize = val; }
      public void setServings(int val) { servings = val; }
      public void setCalories (int val) { calories = val; }
      public void setFat(int val) { fat = val; }
      public void setSodium(int val) { sodium = val; }
      public void setCarbohydrate(int val) { carbohydrate = val; }
  }</code></pre>
</li>
<li><p>단점</p>
<ul>
<li>객체 하나를 만들기 위해서 메서드 여러 개를 호출해야한다.</li>
<li>객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.
  → 일관성이 깨진 객체가 만들어지면, 버그를 심은 코드와 그 버그 때문에 런타임에 문제를 겪는 코드가 물리적으로 멀리 떨어져 있을 것이므로 디버깅이 힘들다.</li>
</ul>
</li>
</ul>
<h4 id="빌더-패턴builder-pattern">빌더 패턴(Builder pattern)</h4>
<ul>
<li><p>클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(혹은 정적 팩터리)를 호출해 빌더 객체를 얻는다.</p>
</li>
<li><p>빌더는 보통 생성할 클래스 안에 정적 멤버 클래스로 만들어 둔다.</p>
<pre><code class="language-java">  public class NutritionFacts {
      private final int servingSize;
      private final int servings;
      private final int calories;
      private final int fat;
      private final int sodium;
      private final int carbohydrate;

      public static class Builder {
          // 필수 매개변수
          private final int servingSize;
          private final int servings;
          // 선택 매개변수 - 기본값으로 초기화한다
          private int calories = 0;
          private int fat = 0;
          private int sodium = 0;
          private int carbohydrate = 0;

          public Builder(int servingSize, int servings) {
              this.servingSize = servingSize;
              this.servings = servings;
          }
          public Builder calories(int val) { 
              calories = val; return this; 
          }
          public Builder fat(int val) { 
              fat = val; return this; 
          }
          public Builder sodium(int val) {
              sodium = val; return this; 
          }
          public Builder carbohydrate(int val) {
              carbohydrate = val; return this; 
          }
          public NutritionFacts build() {
              return new NutritionFacts(this);
          }
      }

      private NutritionFacts(Builder builder) {
          servingSize = builder.servingSize;
          servings = builder.servings;
          calories = builder.calories;
          fat = builder.fat;
          sodium = builder.sodium;
          carbohydrate = builder.carbohydrate;
      }
  }</code></pre>
</li>
<li><p>빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출 될 수 있다. 이런 방식을 메서드 호출이 흐르듯 연결된다 라는 뜻으로 플루언트 API(fluent API), 메서드 연쇄(method chaining) 라 한다.</p>
</li>
<li><p>빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.</p>
</li>
<li><p>빌더 패턴은 유연하다</p>
<ul>
<li>빌더 하나로 여러 객체를 순회하면서 만들 수 잇다</li>
<li>빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수 있다.</li>
<li>객체마다 부여되는 일련번호와 같은 특정 필드는 빌더가 알아서 채우도록 할 수 있다.</li>
</ul>
</li>
<li><p>단점</p>
<ul>
<li>객체를 만들기 위해서는 그 전에 빌더부터 만들어야 한다. 빌더 생성 비용이 크진 않지만 성능에 민감할 경우 문제가 될 수 있다.</li>
<li>점층적 생성자 패턴보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 한다. but API는 시간이 지날수록 매개변수가 많아지는 경향이 있다.</li>
</ul>
</li>
<li><p>안정적인 빌더패턴을 위해</p>
<ul>
<li>잘못된 매개변수를 일찍 발견하기 위해선 빌더의 생성자와 메서드에서 입력된 매개변수를 검사하고, build 메서드가 호출하는 생성자에서 여러 매개변수에 걸친 불변식(invariant)을 검사하자.</li>
<li>불변식을 보장하려면 빌더로부터 매개변수를 복사한 후 해당 객체 필드들도 검사해야 한다.</li>
</ul>
</li>
</ul>
<h4 id="핵심-정리-1">핵심 정리</h4>
<p>→ 생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 더 그렇다. 빌드는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.</p>
<p><br><br></p>
<h2 id="item3-private-생성자나-열거-타입으로-싱글턴임을-보증하라">Item3. private 생성자나 열거 타입으로 싱글턴임을 보증하라</h2>
<h4 id="싱글턴singleton">싱글턴(singleton)</h4>
<ul>
<li>인스턴스를 오직 하나만 생성할 수 있는 클래스</li>
<li>예로는 함수와 같은 무상태 객체나 설계상 유일해야하는 시스템 컴포넌트가 있다.</li>
<li>단점<ul>
<li>타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱클턴이 아니면 싱글턴 인스턴스를 가짜(mock) 구현으로 대체할 수 없으므로 테스트하기가 어려워질 수 있다.</li>
</ul>
</li>
</ul>
<h4 id="싱글턴-만드는-방식">싱글턴 만드는 방식</h4>
<ul>
<li><p>public static final 필드 방식</p>
<pre><code class="language-java">  public class Elvis {
      public static final Elvis INSTANCE = new Elvis();
      private Elvis() { ... }

      public void leaveTheBuilding() { ... }
  }</code></pre>
<ul>
<li>생성자는 private으로 감춰두고, 유일한 인스턴스에 접근할 수 있는 수단으로 public static 멤버를 마련한다.</li>
<li>private 생성자는 public static final 필드를 초기화 할 때 한 번만 호출된다.</li>
<li>public이나 protected 생성자가 없으므로 Elvis 클래스가 초기화될 때 만들어진 인스턴스가 전체 시스템에서 하나뿐임이 보장된다.</li>
<li>클라이언트는 리플렉션 API를 통해 private 생성자를 호출할 수 있다. 이를 막기위해 생성자를 수정해 두번째 객체 생성에 예외를 던지면 된다.
→ 클래스가 싱글턴임이 API에 명백하게 드러난다.
→ 간결하다.  </li>
</ul>
</li>
<li><p>정적 팩터리 방식</p>
<pre><code class="language-java">  public class Elvis {
      private static final Elvis INSTANCE = new ElvisO;
      private Elvis（） { ... }
      public static Elvis getlnstance（） { return INSTANCE; }

      public void leaveTheBuildingO { ... }
  }</code></pre>
<ul>
<li>Elvis.getlnstance는 항상 같은 객체의 참조를 반환하므로 제2의 Elvis 인스턴스란 결코 만들어지지 않는다.<br>→ API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.(호출하는 스레드별로 다른 인스턴스를 넘겨주게 할 수 있다.)<br>→ 정적 팩터리를 제네릭 싱글턴 팩터 리로 만들 수 있다.<br>→ 정적 팩터 리의 메서드 참조를 공급자（supplier）로 사용할 수 있다.<br>⇒ 이러한 장점이 굳이 필요하지 않다면 public 필드 방식이 좋다.  </li>
</ul>
</li>
<li><p>열거 타입 방식</p>
<ul>
<li><p>위 두 방식으로 만든 싱글턴 클래스를 직렬화하려면 인스턴스 필드를 일시적이라고 선언하고 readResolve 메서드를 제공해야한다. 이렇게 하지 않으면 역직렬화할 때마다 새로운 인스턴스가 만들어진다.</p>
<pre><code class="language-java">// 싱글턴임울 보장해주는 readResolve 메서드
private Object readResolve() {
  // &#39;진짜‘ Elvis를 반환하고,가짜 Elvis는 가비지 컬렉터에 맡긴다.
  return INSTANCE;
}</code></pre>
<pre><code class="language-java">public enum Elvis {
  INSTANCE;
  public void leaveTheBuilding() { ... }
}</code></pre>
<p>→ 더 간결하고 추가 노력없이 직렬화 할 수 있다.
→ 아주 복잡한 직렬화 상황이나 리플렉션 공격에서도 제 2의 인스턴스가 생기는 일을 완벽히 막아준다.</p>
</li>
</ul>
</li>
</ul>
<h4 id="핵심-정리-2">핵심 정리</h4>
<p>→ 대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다. 단, 만들려는 싱글턴이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.</p>
<p><br><br></p>
<h2 id="item4-인스턴스화를-막으려거든-private-생성자를-사용하라">Item4. 인스턴스화를 막으려거든 private 생성자를 사용하라</h2>
<h4 id="정적-메서드와-정적-필드만을-담을-클래스">정적 메서드와 정적 필드만을 담을 클래스</h4>
<ul>
<li>객체 지향적이지 않으나 나름의 쓰임새가 있다.</li>
<li>java.lang.Math와 java.util.Arrays처럼 기본 타입 값이나 배열 관련 메서드들을 모아 놓을 수 있다.</li>
<li>java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩터리)를 모아놓을 수도 있다.</li>
<li>final 클래스와 관련한 메서드들을 모아놓을 때도 사용한다.
→ 정적 멤버만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 게 아니다. 하지만 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만들어준다.</li>
</ul>
<h4 id="private-생성자를-추가">private 생성자를 추가</h4>
<ul>
<li><p>컴파일러가 기본 생성자를 만드는 경우는 오직 명시된 생성자가 없을 때뿐이니 private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.</p>
<pre><code class="language-java">  public class Utilityclass {
      // 기본 생성자가 만들어지는 것을 막는다（인스턴스화 방지용)
      private UtilityClass() {
          throw new AssertionError();
      }
      ... // 나머지 코드는 생략
  }</code></pre>
</li>
<li><p>명시적 생성자가 private이니 클래스 바깥에서는 접근할 수 없다.</p>
</li>
<li><p>꼭 Assertion Error를 던질 필요는 없지만, 클래스 안에서 실수로라도 생성자를 호출하지 않도록 해준다.</p>
</li>
<li><p>상속을 불가능하게 한다. 모든 생성자는 상위 클래스의 생성자를 호출하는데 private으로 선언했기에 하위 클래스가 상위 클래스의 생성자에 접근할 길이 막힌다.</p>
</li>
<li><p>이 코드는 어떤 환경에서도 클래스가 인스턴스화 되는 것을 막아준다.</p>
</li>
<li><p>하지만 생성자가 존재하는데 호출 할 수 없는 것이 직관적이지는 않으니 적절한 주석을 달아주자.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 이미지 레지스트리(Image Registry)와 Docker Hub 실습]]></title>
            <link>https://velog.io/@garden-y/Docker-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A0%88%EC%A7%80%EC%8A%A4%ED%8A%B8%EB%A6%ACImage-Registry%EC%99%80-Docker-Hub-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@garden-y/Docker-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A0%88%EC%A7%80%EC%8A%A4%ED%8A%B8%EB%A6%ACImage-Registry%EC%99%80-Docker-Hub-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Fri, 19 Apr 2024 10:33:20 GMT</pubDate>
            <description><![CDATA[<h1 id="part3-이미지-레지스트리">Part3. 이미지 레지스트리</h1>
<h2 id="이미지-레지스트리">이미지 레지스트리</h2>
<p>이미지 레지스트리는 <strong>도커 이미지를 저장하기 위한 저장소</strong>이다. 이미지 레지스트리를 통해 개인이나 팀 이미지를 다른 사람과 공유하거나 필요한 이미지를 다운받을 수 있다. 
(마치 Github에서 자신의 소스 코드를 보관하고 쉽게 다른 개발자들과 공유할 수 있는 것처럼 이미지 레지스트리도 매우 비슷하다.)</p>
<p>가장 많이 사용되는 Public Image Registry는 DockerHub가 있다. </p>
<p>Github엔 소스코드만 보관하고, Dockerhub엔 이 소스 코드를 사용해서 만들어진 애플리케이션과 그 애플리케이션을 실행할 수 있는 환경이 모두 포함되어 있는 이미지를 저장한다. 
<br></p>
<h4 id="이미지-레지스트리가-제공하는-기능들">이미지 레지스트리가 제공하는 기능들</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/05590f28-f359-4186-8ae2-897eff76959b/image.png" alt=""></p>
<ul>
<li>이미지 레지스트리는 기본적으로 이미지를 다운로드하고 업로드하는 기능을 제공한다.</li>
<li>이미지 레지스트리에 있는 이미지들을 검색하고 필요한 이미지를 찾아볼 수 있다.</li>
<li>이미지 레지스트리는 이미지의 버전을 관리하기 때문에 사용자는 이미지를 다운로드할 때 특정 버전의 이미지를 지정해서 다운받을 수 있다. </li>
<li>원하는 사용자만 이미지를 다운받을 수 있도록 인증 처리와 권한 관리 기능도 제공한다. </li>
<li>안전한 이미지를 받을 수 있도록 업로드된 이미지의 보안을 검증해주며, DevOps 파이프라인 기능과 연계해서 이미지를 업로드했을 때 자동으로 배포가 이뤄질 수 있도록 연계 기능이나 알림 기능도 제공한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/garden-y/post/f6bcbb20-a7ed-4b33-af9a-89af07311a1c/image.png" alt=""></p>
<p>이미지가 저장되는 공간은 크게 세가지로 나눌 수 있다.</p>
<p>먼저, 도커가 설치되어 있는 호스트 머신의 <strong>Local Storage</strong>가 있고, 온라인 저장소엔 <strong>Private Registry, Public Registry</strong>로 크게 두가지로 총 3가지이다.</p>
<p>컨테이너를 실행할 때 이미지의 이름을 입력했는데, docker run 명령어에서 이미지의 이름을 지정하면 먼저 로컬 스토리지에서 해당 이미지가 있는지 검색한다.</p>
<p>로컬 스토리지는 도커를 실행하는 호스트 OS의 특정 폴더를 의미한다. (실습 환경에선 내 PC에 특정 폴더를 의미한다.) 그래서 이 로컬 스토리지에 이미지가 있으면 바로 실행된다. 없을 경우 호스트 외부의 온라인 레지스트리에서 이미지를 로컬 스토리지로 다운 받는다. 그리고 다운 받은 로컬 스토리지의 이미지를 사용해서 컨테이너를 실행한다. 
한 번 다운 받아 온 경우 로컬 스토리지에 이미 다운로드 받아놓은 이미지가 있기 때문에 온라인 레지스트리를 검색하지 않고 바로 컨테이너로 실행된다. </p>
<p>온라인 저장소는 기업에서 많이 사용하는 Private Registry와 Dockerhub 같은 Public Registry로 두 종류가 있다. 특정한 네트워크에만 접근이 가능하면 Private Registry, 모든 네트워크에서 접근이 가능하면 Public Registry이다. </p>
<p>도커 허브와 같은 Public Registry는 모두가 접근할 수 있는 레지스트리이기 때문에 이런 공간에 이미지를 저장하는 것은 보안상 문제가 될 수 있다. 그래서 도커 허브 같은 서비스를 사용하지 않고, 본인만의 레지스트리를 사용하는 방법도 있다.</p>
<ol>
<li>직접 우리 서버에 레지스트리를 설치해서 사용한다.</li>
<li>퍼블릭 클라우드의 서비스를 사용한다.</li>
</ol>
<p>서버에 설치하는 레지스트리 소프트웨어는 Harbor, Docker Private Registry같은 제품이 있으며, Public Cloud의 서비스로는 AWS ECR, Azure ACR 같은 서비스를 사용할 수 있다. </p>
<p>설치형 레지스트리는 사용자가 직접 레지스트리를 서버에 설치해야 하며, 퍼블릭 클라우드의 레지스트리를 사용하면 시간당 사용 요금을 지불하고 레지스트리를 사용할 수 있다. 
<br></p>
<h3 id="이미지의-네이밍-규칙">이미지의 네이밍 규칙</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/2cc1af4e-f1ce-4ad1-a839-6151acffa4bd/image.png" alt=""></p>
<p>이미지의 네이밍 규칙은 이미지를 만들고 공유하는 데 있어서 꼭 필요한 정보이다.
이미지 이름에는 이미지를 어디서 다운 받는지, 어떤 버전을 다운 받는지에 대한 정보가 모두 포함되어 있어야 한다. 
이미지 이름은 크게 레지스트리 주소, 프로젝트명, 이미지명, 이미지 태그로 구성된다.</p>
<p>레지스트리 주소는 어떤 레지스트리를 사용할 지 지정한다. 도커 허브 말고도 레지스트리가 다양하기 때문에 이미지를 어떤 레지스트리에서 다운로드하고 업로드 할 지를 지정해준다.  비어있을 경우, 기본 값으로 지정된 레지스트리 주소가 사용된다. 도커를 사용하면 기본 레지스토리 값은 도커 허브의 주소인 docker.io가 default 값이다. 만약 개인 레지스트리 주소가 devwiki.com이고, 이 레지스트리에서 이미지를 다운 받거나 업로드하고 싶으면 맨 앞에 devwiki.com라는 레지스트리 주소를 적어야 한다. </p>
<p>프로젝트명은 이미지를 보관하는 폴더 같은 개념이다. 레지스트리마다 프로젝트를 정의하는 방식이 조금 다르기도 하다. 도커 허브의 경우 가입한 사용자의 계정명이 프로젝트 명이 된다.  (도커는 도커사가 직접 검증한 이미지는 오피셜 이미지로 제공하고 있다. 이 오피셜 이미지를 library 라는 프로젝트에서 관리하기 때문에 별도로 계정명을 입력하지 않을 경우 library가 기본 값으로 적용된다.)</p>
<p>이미지명은 다운로드 받을 이미지의 이름이다. 이미지 태그는 이미지의 버전이며, 숫자와 영문을 모두 사용할 수 있다. 기본 값은 lastest이므로, 이미지 태그를 생략할 경우 lastest로 가장 최근 버전이 사용된다.<br><br><br></p>
<h2 id="도커-허브이미지-레지스트리-실습">도커 허브(이미지 레지스트리) 실습</h2>
<p><a href="http://hub.docker.com">hub.docker.com</a> 에서 가입 할 때 Username이 프로젝트명이 된다. (구글 회원가입하니까 알아서 자동으로 이름이 입력됐다.)
<img src="https://velog.velcdn.com/images/garden-y/post/8720eabf-488f-4e8f-87af-ce8225165e0f/image.png" alt=""></p>
<p>nginx를 검색해보면 맨 위에 뜨는 것이 library 프로젝트에서 제공하는 도커 오피셜 nginx이다. 그리고 오피셜 이미지 말고 Verified Publisher 라는 태그가 붙은 이미지들이 있다.  이 이미지들은 도커에서 직접 관리하는 이미지는 아니지만 어느정도 규모가 있는 회사에서 자체적으로 인증한 이미지여서 다른 이미지보다는 신뢰할 수 있다.
하나의 이미지를 클릭하면 상세페이지에서 다운로드 수, 좋아요 수를 확인할 수 있다.
Tags 탭을 클릭하면 이미지의 버전 정보도 확인할 수 있는데 nginx의 경우 stable이 붙어있는 이미지들은 안정적인 버전이라고 생각하면 된다. 추가로 alpine 부분은 nginx 이미지를 만들기 위해서 베이스 이미지로 사용했던 OS의 버전을 확인할 수 있다.
상단의 Repositories가 나의 이미지 저장소라 생각하면 된다. 이미지를 푸시하면 이 곳에 저장된다. 
<br><br></p>
<p>다음은 관련된 명령어이다. </p>
<h4 id="로컬-스토리지로-이미지-다운로드">로컬 스토리지로 이미지 다운로드</h4>
<pre><code class="language-docker">docker pull 이미지명</code></pre>
<ul>
<li>이미지의 네이밍 규칙에 따라 이미지명을 작성해주면 된다.<br>

</li>
</ul>
<h4 id="로컬-스토리지의-이미지명-추가">로컬 스토리지의 이미지명 추가</h4>
<pre><code class="language-docker">docker tag 기존이미지명 추가할이미지명</code></pre>
<ul>
<li>새로운 이미지명을 만드는 명령어이다.</li>
<li>새로운 이미지명을 만들기 위해서는 새로운 이름을 붙일 원래의 이미지가 있어야 한다.</li>
<li>원래 있던 이미지는 그대로 둔 상태에서 내가 원하는 이미지명으로 새로운 이름을 하나 만든다고 생각하면 된다.</li>
</ul>
<h4 id="이미지-레지스트리에-이미지-업로드">이미지 레지스트리에 이미지 업로드</h4>
<pre><code class="language-docker">docker push 이미지명</code></pre>
<br>

<p><img src="https://velog.velcdn.com/images/garden-y/post/47a7c2dc-c300-4c6d-a1e5-dbcbdc659826/image.png" alt=""></p>
<p>기존 devwikirepo 프로젝트의 이미지를 다운 받아 이 이미지를 기반으로 새로운 이미지명을 만든 다음에 이미지를 푸시하는 실습이다. (여기서 이미지명을 바꾸는 이유는 프로젝트의 이름을 바꾸기 위해서 라고 한다.  여기서 본인의 프로젝트명(도커의 계정)으로 변경해서 본인의 레지스트리에 올라간다.)
<br></p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/5bcfffc0-955b-49e9-aa93-1474c3c77653/image.png" alt="">
<br></p>
<p>simple-web은 이름만 my-simple-web으로 변경된 것이기에 IMAGE ID가 같은 것을 확인할 수 있다. 이미지의 아이디가 같다는 것은 실제로 파일이 하나만 있다는 것을 의미한다. 하나의 이미지에 여러 개의 이름을 추가할 수 있는 것이다.</p>
<p>이름을 추가하는 이유는 같은 파일이더라도 이름을 어떻게 주는지에 따라서 어디에 푸시되는지가 결정되기 때문이다. 파일은 그대로 있고, 이미지명에 따라서 업로드하는 곳이 달라지는 것이다. </p>
<p>이미지를 원하는 곳에 업로드 하고 싶으면 위처럼 이미지의 네이밍 규칙을 활용해서 업로드할 곳의 이미지 이름으로 변경하면 된다. 또한, 레지스트리 명과 프로젝트 명을 어떻게 설정하냐에 따라서 다양한 곳에 다양한 버전으로 배포할 수 있다. 
<br></p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/262b8eda-22b2-457b-a460-304a0e603be5/image.png" alt=""></p>
<p>원래 denied: requestd access to the resource is denied 오류가 나야하는데 … 난 그냥 바로 됐다.
저 에러가 나는 이유는 도커 허브의 이미지를 푸시할 때는 업로드할 계정의 인증 정보가 필요한데, 로그인이 되어있지 않기 때문이다.</p>
<h4 id="이미지-레지스트리-인증-정보-생성">이미지 레지스트리 인증 정보 생성</h4>
<pre><code class="language-docker">docker login</code></pre>
<br>

<h4 id="이미지-레지스토리-인증-정보-삭제">이미지 레지스토리 인증 정보 삭제</h4>
<pre><code class="language-docker">docker logout</code></pre>
<br>

<h4 id="로컬-스토리지의-이미지-삭제">로컬 스토리지의 이미지 삭제</h4>
<pre><code class="language-docker">docker image rm 이미지명 </code></pre>
<br>

<p><img src="https://velog.velcdn.com/images/garden-y/post/c2d1b739-7a39-4416-abde-369516a81b4e/image.png" alt=""></p>
<p>실제 실습을 하고 삭제할 때 이름 변경한 이미지와 원본 이미지를 모두 삭제한다. 처음 삭제는 Untagged만 되고, 두번째 삭제할 때 Untagged와 Deleted까지 되는 걸 볼 수 있다. </p>
<p>하나의 이미지를 받아서 태그 명령어를 사용해서 새로운 이미지명을 추가했기 때문에 하나의 이미지로 두 개의 이미지 태그가 있는 상태였다. 그래서 이미지 태그를 하나만 삭제할 때는 이 이미지를 참조하고 있는 다른 이미지명이 있기 때문에 Untagged가 되고 삭제되지 않았지만 두번째 이미지를 삭제할 때는 더이상 이 이미지를 참조하고 있는 이미지명이 없기 때문에 아예 파일 자체가 삭제되는 것을 볼 수 있다. </p>
<p>이렇게 이미지와 이미지명의 관계는 실제 파일과 참조 링크처럼 생각하면 된다. 물리적인 파일에 여러 개의 링크 형태로 이미지에 이름을 붙일 수 있고, 더이상 참조하는 이미지명이 없을 경우에 물리적인 파일도 삭제되는 것이다.
<br></p>
<p>번외로 참조라는 단어를 써서 혹시 처음에 만들어진 이미지가 생성되면 아예 삭제되는 것인가? 라는 생각이 문득 들었다. 그래서 실습과 반대로 삭제해봤다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/0e3779d3-635c-44f2-b7fb-7da057a66e03/image.png" alt=""></p>
<p>이미지명을 추가해서 만든 것보다 기존 이미지인 devwikirepo/simple-web:1.0을 먼저 지워봤다. Untagged만 뜨고, image 목록에도 추가로 생성한 yoonjeongwon/my-simple-web:0.1은 존재하는 것을 알 수 있었다. 그리고 그 후에 yoonjeongwon/my-simple-web:0.1를 삭제하니 Untagged와 Delete가 같이 발생했다.</p>
<p>따라서 <strong>“더이상 참조하는 이미지명이 없을 경우에 물리적인 파일도 삭제”</strong> 에 집중해서 이해하면 좋을 것 같다. </p>
<p>삭제 후 본인 레포에서 다시 다운받으면 이미지가 로컬스토리지에 없기 때문에 다운받아진다. (이때 도커 허브로 다운로드 수가 증가함을 볼 수 있다.) 
<br></p>
<p>이제 이미지의 이름을 다른 사람들에게 전달해주면 어떤 환경에서든 도커만 설치되어 있으면 완벽하게 일치하는 서버를 구성할 수 있다. 
도커를 사용하기 전에는 이미지가 아닌 소스 코드나 애플리케이션을 파일로 공유했다. 이렇게 공유하면 실제로 애플리케이션을 실행할 때 OS나 라이브러리가 PC별로 차이가 있기 때문에 개발자의 개발용 PC에서는 잘 실행되던 애플리케이션도 운영 환경이나 다른 PC에서는 제대로 동작하지 않는 문제가 종종 생긴다. 그래서 운영 환경을 테스트할 수 있는 QA환경을 만들고 운영 환경과 완벽하게 일치시키는데 많은 리소스를 투자한다. 그럼에도 불구하고 이렇게 환경의 불일치로 문제들이 발생한다. </p>
<p>컨테이너를 사용하면 위처럼 <strong>환경의 차이에서 발생하는 문제들을 근본적으로 해결</strong>할 수 있다. 간단한 웹서버부터 복잡한 애플리케이션 서버 구성까지 이미지에 실행 가능한 형태로 저장해서 공유하면 서버 운영비를 줄이고 새로운 서버를 구성하는 시간을 크게 단축시킬 수 있다.</p>
<p><br><br><br><br></p>
<h3 id="레퍼런스">레퍼런스</h3>
<ul>
<li>[2024 NEW] 개발자를 위한 쉬운 도커</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 이미지와 컨테이너(이미지 
 메타 데이터부터 컨테이너의 생명주기까지..)]]></title>
            <link>https://velog.io/@garden-y/Docker-%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%99%80-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</link>
            <guid>https://velog.io/@garden-y/Docker-%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%99%80-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</guid>
            <pubDate>Fri, 19 Apr 2024 07:02:34 GMT</pubDate>
            <description><![CDATA[<h1 id="part2-이미지와-컨테이너">Part2. 이미지와 컨테이너</h1>
<p>이미지는 컨테이너를 만드는 재료이다. 이미지가 정확하게 무엇인지, 이미지와 컨테이너의 관계가 어떻게 되는지에 알아보자. 추가로 컨테이너의 라이프 사이클(실행부터 삭제)까지 알아보자.</p>
<h2 id="이미지">이미지</h2>
<h4 id="서버에서-프로그램-실행">서버에서 프로그램 실행</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/2ccdb2aa-b24f-4ca9-aea7-2852fedcf887/image.png" alt=""></p>
<p>서버에서 프로그램을 실행하기 위한 과정에선 우선 하드웨어가 필요하고, 하드웨어에서 실행할 소프트웨어가 필요하다.  소프트웨어는 Nginx 웹서버처럼 소프트웨어를 다운받아 실행할수도, 직접 개발한 애플리케이션을 실행할수도 있다. 
but 이러한 소프트웨어는 소프트웨어 하나만 가지고 실행할 수 없다. 소프트웨어가 하드웨어의 자원을 사용할 수 있도록 필수 기능을 제공해주는 OS가 필요하다. 일반적으로 소프트웨어는 특정 패키지나 라이브러리의 의존성을 가지고 있으며, 실행시키기 위해 런타임 언어를 설치해야하기도 한다. (우리의 예시로는 OS에 자바 소프트웨어를 실행하기 위한 자바 런타임이 설치되어야 한다.)
<br></p>
<h4 id="프로그램-실행을-위해선">프로그램 실행을 위해선</h4>
<ol>
<li>기본 OS</li>
<li>필요한 구성 요소(Config)</li>
<li>실행시킬 프로그램<br>

</li>
</ol>
<p>하지만 docker에서 Nginx 웹서버를 컨테이너로 실행 하기 위해서는 소프트웨어에 필요한 의존 요소를 설치하는 과정이 없이 Nginx라는 이미지 이름만 지정한다. 그렇다면 <strong>어떻게 서버를 구성하는 과정이 없이 Nginx를 실행시킬 수 있었을까?</strong>
<strong>→ Nginx 이미지를 통해 컨테이너를 실행했기 때문이다.</strong></p>
<h3 id="이미지image란">이미지(Image)란?</h3>
<p>→ <strong>파일 시스템에 특정 시점을 저장해 놓은 압축 파일</strong>을 말한다. (like 중간 저장)</p>
<p>이미지는 제작 단계에서부터 소프트웨어뿐만 아니라 소프트웨어가 실행되기 위해 필요한 모든 요소들을 미리 준비해서 압축한다.
Nginx 이미지의 경우는 Nginx 개발사가 OS 위에 Nginx와 의존성 요소들을 미리 준비하고 Nginx를 실행할 준비가 되어 있는 상태 자체를 이미지로 저장한 뒤에 공유한다.
이렇게 이 이미지 안에는 OS와 구성요소, 프로그램이 포함되어 있다. 따라서 우리가 docker run 에 Nginx 이미지를 입력하면 이미지를 다운 받아 격리된 공간에 컨테이너가 실행될 수 있었던 것이다. 
컨테이너를 실행할 때 이미지 안에 있던 Nginx 프로그램들이 같이 실행된 것이다. </p>
<p>즉, 이미지는 백업 기능과 비슷하다. 컴퓨터에 윈도우 OS와 프로그램을 설치하고 그 상태를 백업으로 저장해 놓으면 언제든지 해당 프로그램을 실행할 수 있는 시점으로 만들 수 있기 때문이다. 또한, 이러한 백업 파일을 다른 컴퓨터에도 공유할 수 있기 때문이다. 
비슷하게 가상 머신에서는 스냅샷이라는 기능을 제공한다. 하지만 컨테이너의 이미지는 백업이나 스냅샷보다 압축파일의 사이즈가 아주 작다. 따라서 인터넷을 통해 공유, 저장하기가 훨씬 쉽다. </p>
<p>이미지는 컨테이너를 사용하는데 있어 가장 중요한 기능 중 하나이며, 이미지는 Nginx 처럼 다른 사람이 만든 이미지를 다운받을 수도, 직접 제작할 수도 있다.</p>
<p><del>이미지를 다운받고 공유하는 공간인 레지스트리와 이미지가 구성되는 원리와 직접 만들어 보는 건 추후에 강의에서 진행한다하니 추후 벨로그로 돌아오겠다....</del>
<br></p>
<p>도커는 가상 환경의 서버를 운영하는 기술이며, 컨테이너 내에서 웹서버, 웹 애플리케이션 같은 소프트웨어를 운영하는 것이 도커의 사용 목적이다. <strong>컨테이너는 이미지를 통해 실행할 수 있으며, 이미지는 특정 소프트웨어를 실행하기 위해 OS, 의존 요소, 소프트웨어가 포함되어 있는 파일 시스템의 상태를 저장해 놓은 압축 파일</strong>이다.</p>
<p>이미지를 컨테이너로 실행시키면 호스트 OS 안에서 완전히 격리된 공간인 컨테이너가 만들어지며, 이 안에서 소프트웨어가 실행된다. 이런 이미지는 다른 사람이 만든 것을 다운받아 사용할 수도, 직접 만들어 사용할 수도 있다. 
<br>
<br></p>
<h2 id="이미지와-컨테이너">이미지와 컨테이너</h2>
<p><img src="https://velog.velcdn.com/images/garden-y/post/fabddcd7-4ffd-4163-9f99-84780b317088/image.png" alt=""></p>
<p>이미지와 컨테이너의 관계를 알아보자.</p>
<h3 id="프로그램과-프로세스의-차이">프로그램과 프로세스의 차이</h3>
<p>노트북에 크롬을 깔면 C드라이브 특정 경로에 설치된다. 이는 프로그램이 파일 시스템의 공간을 차지하도록 설치한 것이다. 프로그램인 상태에서는 CPU나 메모리 같은 컴퓨터의 리소스를 사용하지 않고 오직 스토리지의 디스크 공간만 차지한다.
설치된 파일 형태의 프로그램을 실행하면 프로세스가 된다. 크롬 창을 여러 창 띄울 수 있으며, 많이 띄우게 되면 컴퓨터가 느려진다. 이는 각각의 크롬 프로세스들이 CPU와 메모리를 사용하고 있기 때문이다.
따라서 프로그램은 디스크에 저장되어서 실행 가능한 상태인 파일이며, 프로세스는 프로그램을 실행해서 실행 중인 상태를 말한다. </p>
<p><strong>이미지와 컨테이너</strong>는 <strong>프로그램과 프로세스의 관계</strong>와 같다.</p>
<p>즉, 이미지는 프로그램이 실행되기 위한 환경이 모두 포함되어 있는 파일 시스템이며, 컨테이너는 이미지를 실행한 것이다. </p>
<p>컨테이너를 실행시키기 위해서는 해당하는 이미지를 가지고 있어야 하며, 이미지는 압축 파일의 형태로 호스트 머신의 특정 경로에 위치한다. 
하나의 이미지로 여러 개의 컨테이너를 실행할 수 있으며, 동일한 이미지에서 실행한 컨테이너는 내부에서 모두 동일한 프로세스로 실행된다. 
이미지인 상태에서는 디스크 공간만 차지하며, 이미지 컨테이너를 실행하는 순간부터 CPU와 메모리를 사용하게 된다. 
<br></p>
<h3 id="이미지와-일반적인-프로세스와의-차이">이미지와 일반적인 프로세스와의 차이</h3>
<p>→ 컨테이너는 가상화 기술이기 떄문에 이미지를 컨테이너로 실행할 때는 <strong>격리된 공간이 만들어 진다</strong>. (이 격리된 공간에서 이미지에서 사전에 지정해 높은 프로그램이 실행된다. )</p>
<p>따라서 이미지를 컨테이너로 실행시키는 것은 이미지에 저장되어 있는 모든 요소들을 격리된 공간으로 만든 다음에 격리된 공간 안에서 프로그램을 프로세스로 실행시키는 단계를 거치는 것이다.</p>
<h2 id="이미지의-메타데이터">이미지의 메타데이터</h2>
<p>메타 데이터는 <strong>데이터에 대한 데이터</strong>를 말한다. 이미지가 실제 압축된 데이터라면, 메타데이터는 이 이미지에 대한 정보를 기술하는 데이터이다. (식물의 이름부터, 물 주는 횟수, 온도 등.. 다양한 정보를 정리해둔 라벨이 이에 해당된다고 생각하면 좋다.)</p>
<p>하나의 이미지는 실제로 압축된 파일과 이 파일의 정보가 저장되어 있는 메타데이터로 구성되어 있다.</p>
<h3 id="메타-데이터에-어떤-정보들이-있을까">메타 데이터에 어떤 정보들이 있을까?</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/7f569752-4a8a-4fde-b97f-faf4712fb197/image.png" alt=""></p>
<p>위 사진은 Nginx의 메타데이터를 표시했다. 이 메타데이터는 이미지의 아이디, 이름, 파일 사이즈와 같은 정보들이 들어있다. </p>
<p>여기서 중요한 것은 <strong>Env와 Cmd 필드</strong>이다.</p>
<p>Env는 <strong>애플리케이션이 사용하는 환경 설정의 값</strong>을 말한다. 키와 값으로 이뤄졌으며 =의 기준으로 왼쪽이 키 오른쪽이 값에 해당한다. Nginx 이미지에는 위처럼 소프트웨어 버전과 Nginx 프로그램을 실행할 때 필요한 파일의 경로 같은 정보가 Env에 작성되어 있다. 실제 이 값들이 변경되면 Nginx도 다른 방식으로 동작할 수 있다. </p>
<p>즉, Env 는 소프트웨어가 실행할 때 사용할 정보가 들어있다고 생각하면 된다. </p>
<p>Cmd는 <strong>이미지를 컨테이너로 실행할 때 명령어를 지정할 수 있다</strong>. 예를 들면 윈도우에서 프로그램을 실행할 때 바탕화면에서 클릭을 하듯 리눅스에서는 일반적으로 명령어를 통해 프로그램을 실행한다. 이미지를 컨테이너로 실행할 때 Cmd에 있는 명령어를 통해서 어떤 프로그램을 실행할지를 메타 데이터에서 결정한다. </p>
<p>여기서 지정한 Nginx 이미지의 이미지 압축파일과 이미지 메타데이터를 사용해서 격리된 공간인 컨테이너가 만들어진다. 이 시점에서 컨테이너에 있는 파일 시스템과 메타 데이터는 새로 격리되어 있는 공간이다. 이 이미지가 컨테이너로 실행될 때 Cmd 필드에 있는 nginx -g deamon off라는 명령어가 실행된다. </p>
<p>이 Nginx 같은 프로세스가 실행되면서 Env 필드에 있는 환경설정 값을 사용할 수 있다. 그리고 이 메타데이터는 컨테이너를 실행할 때 새로운 값으로 덮어쓰기 할 수도 있다. </p>
<p>(ex. 실제 실행시 cmd 필드를 덮어쓰기해서 다른 프로세스를 실행할 수도 있다.)
<br>
<br></p>
<h3 id="이미지-컨테이너-메타데이터-관련-명령어">이미지, 컨테이너 메타데이터 관련 명령어</h3>
<p><strong>이미지의 세부 정보 조회</strong></p>
<pre><code class="language-docker">docker image inspect 이미지명</code></pre>
<p>해당 명령어를 작성하면 Nginx의 이미지의 ID를 확인할 수 있고, 이미지를 다운 받을 때 사용하는 태그, 생성 시간..부터 &quot;Config&quot;에선 Env와 Cmd 필드까지 확인할 수 있다. </p>
<p>(Env에선 Nginx 버전과 패스 환경변수를 확인할 수 있으며, Cmd에선 nginx -g deamon off라는 명령어가 있다. 이 Cmd는 띄어쓰기 인식이 안돼서 띄어쓰기를 기준으로 배열로 저장되어있다. 배열을 합치면 저 위의 명령어가 된다.) 
<br></p>
<p><strong>컨테이너의 세부 정보 조회</strong></p>
<pre><code class="language-docker">docker container inspect 컨테이너명</code></pre>
<br>

<p><strong>컨테이너 실행 시 메타데이터의 cmd 덮어쓰기</strong></p>
<pre><code class="language-docker">docker run 이미지명 (실행명령)</code></pre>
<br>

<p><strong>컨테이너 실행 시 메타 데이터의 env 덮어쓰기</strong></p>
<pre><code class="language-docker">docker run --env KEY=VALUE 이미지명 </code></pre>
<p>이렇게 명령어들을 통해 이미지를 컨테이너로 실행하면서 이미지의 메타데이터에 있는 Cmd와 Env 필드를 덮어쓰기 할 수 있다.
이미지로 컨테이너를 실행시키면 이미지에 있는 Cmd, Env들을 그대로 복사해서 컨테이너로 가져온다. 
<br>
<br></p>
<h4 id="변경-예시">변경 예시</h4>
<p>docker run --name customCmd nginx cat usr/share/nginx/html/index.html 를 실행하면 기본 이미지의 메타데이터를 사용해 컨테이너를 실행할 수 있다.</p>
<p>실제로 위 명령어를 실행할 경우 index.html이 cat(출력)된다.</p>
<p>이 customCmd 컨테이너에서 실행한 cat 명령어는 파일의 내용을 출력하고 종료하는 일회성 프로세스이기 때문에 Cmd에서 지정한 cat 명령어가 종료되는 순간 컨테이너도 함께 종료된다. 따라서 CustomCmd는 종료된 상태의 컨테이너이기 때문에 일반적인 docker ps로는 조회가 되지 않는다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/451c5b68-2bc2-4c3f-9902-386d5b671c01/image.png" alt=""></p>
<p>docker ps -a 를 통해 종료된 컨테이너까지 확인할 수 있다. </p>
<p>exited 된 이 컨테이너는 <strong>이미지의 데이터를 복사해서 만들어지기 때문에</strong> 실제로 컨테이너의 메타데이터를 바꾼다고 해서 <strong>이미지의 메타데이터가 바뀌지는 않는다.</strong> (즉, custoCmd의 메타 데이터를 바꾼다해서 기존의 nginx 이미지가 바뀌진 않는다는 소리이다. docker image inspect nginx 로 확인 가능)</p>
<p><strong>→ 즉, 우리가 변경할 수 있는 것은 이미지를 복사해오면서 실행하는 시점에서 컨테이너의 메타 데이터만 변경할 수 있다.</strong>
<br></p>
<p>이미지는 실제 압축파일과 메타데이터로 구성되어 있다. 이미지를 컨테이너로 실행하면 이미지의 파일과 메타데이터가 컨테이너로 만들어지면서 메타데이터의 Env에 정의되어 있는 환경 변수가 사용된다. 또한, Cmd에 정의되어 있는 명령어로 프로그램을 실행한다.</p>
<p>또한, Env와 Cmd는 컨테이너를 실행할 때 덮어쓰기가 가능하기 때문에 이 내용을 수정해서 같은 이미지에서 다른 결과를 확인할 수도 있다. </p>
<p>이렇게 실행 시 메타데이터를 변경하는 것이 일반적인 방법은 아니다. 보통 이미지를 디버깅할 때 많이 사용되는 방법이다. 하지만 직접 메타데이터를 수정하면서 메타 데이터의 개념을 익히는 것도 좋은 방법이다.</p>
<p><br><br></p>
<h2 id="컨테이너의-라이프사이클">컨테이너의 라이프사이클</h2>
<p><img src="https://velog.velcdn.com/images/garden-y/post/611b4fe4-880f-4fa1-b7eb-5fd57ca04e80/image.png" alt=""></p>
<p>컨테이너는 <strong>이미지에서부터 시작</strong>한다. 이미지는 애플리케이션을 실행할 수 있는 모든 환경이 준비되어 있는 압축 파일이다. </p>
<p>컨테이너가 맨 처음으로 가질 수 있는 상태는 <strong>Created, 생성 단계</strong>이다. 이 단계는 컨테이너를 실행하기 위한 격리된 공간이 만들어지는 상태이다. 그래서 네트워크나 스토리지, 환경 변수와 같은 모든 리소스가 격리된 공간인 컨테이너로 분리된 상태이다. 또한, 생성 단계에서는 내부에서 프로세스를 실제로 실행하지 않기 때문에 호스트 OS의 CPU나 메모리를 사용하지 않는다.</p>
<p>생성 단계에서 start 명령을 한다면 <strong>Running, 실행 단계</strong>로 가게된다. 실행 상태가 되었다는 것은 컨테이너 내에서 정상적으로 프로세스가 진행 중이라는 것을 의미한다. 그리고 프로세스가 실제로 CPU와 메모리를 사용한다. </p>
<p>실제 <strong>run 명령어는 create와 start가 합쳐진 명령어</strong>이다. 그래서 docker run 명령을 사용하면 컨테이너를 만드는 것과 동시에 바로 실행할 수 있다.</p>
<p>또한, 실행 상태에서 <strong>restart를 하면 재시작</strong> 할 수 있고, 일시정지를 하거나 종료를 할 수도 있다. 실행 중인 프로세스에 종료나 재시작 신호를 보내면 10초 뒤에 이 신호가 동작하게 된다. </p>
<p><strong>일시정지 (Paused) 상태</strong>에서는 컨테이너에서 실행 중인 모든 프로세스가 일시 중지된 상태이며, 현재의 상태를 모두 메모리에 저장해 둔다. 따라서 일시정지 상태에서는 CPU는 사용하지 않고 메모리만 사용하게 된다. 그리고 이 상태에서 <strong>unpause 명령어를 사용하면 프로세스를 일시정지했던 시점부터 재시작</strong> 할 수 있다. </p>
<p><strong>Stopped 상태</strong>는 컨테이너에서 실행 중인 프로세스를 완전히 중단시켰다는 것을 의미한다. 이 상태에서는 메모리와 CPU 사용이 모두 중단되고, 종료된 컨테이너를 다시 시작하면 컨테이너의 프로세스가 처음부터 다시 실행된다.</p>
<p>그리고 이 컨테이너는 모든 상태에서 rm 명령을 사용해서 삭제할 수 있다. (실행 중인 컨테이너를 삭제하려면 -f 옵션을 사용해야한다!)
<br></p>
<p>컨테이너의 라이프 사이클은 크게 생성, 실행, 정지, 종료, 삭제로 나눠지며, 대부분은 이 컨테이너에서 실행되는 프로세스의 상태와 일치한다. 이처럼 실제로 컨테이너와 프로세스는 많은 연관성을 가지고 있다.(프로세스가 실행되면 컨테이너가 실행되, 프로세스가 멈추거나 종료되면 컨테이너도 동일하다.) 즉, 프로세스를 잘 설계하고 다루는 것이 컨테이너를 잘 사용하는 것과 방향성이 일치하다.</p>
<p><br><br><br><br></p>
<h3 id="레퍼런스">레퍼런스</h3>
<ul>
<li>[2024 NEW] 개발자를 위한 쉬운 도커</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] HTTP Method 속성 (안전성, 멱등성, 캐시 가능성)]]></title>
            <link>https://velog.io/@garden-y/HTTP-HTTP-Method-%EC%86%8D%EC%84%B1-%EC%95%88%EC%A0%84%EC%84%B1-%EB%A9%B1%EB%93%B1%EC%84%B1-%EC%BA%90%EC%8B%9C-%EA%B0%80%EB%8A%A5%EC%84%B1</link>
            <guid>https://velog.io/@garden-y/HTTP-HTTP-Method-%EC%86%8D%EC%84%B1-%EC%95%88%EC%A0%84%EC%84%B1-%EB%A9%B1%EB%93%B1%EC%84%B1-%EC%BA%90%EC%8B%9C-%EA%B0%80%EB%8A%A5%EC%84%B1</guid>
            <pubDate>Wed, 17 Apr 2024 14:05:02 GMT</pubDate>
            <description><![CDATA[<p>HTTP Method 에는 각각의 여러 속성이 있다. 
어떤 메서드로 서버에 요청했는지에 따라 API 설계, 복구 메커니즘, 캐시 최적화 등 설계의 로직에 영향을 준다.
HTTP 메서드의 속성엔 대표적으로 <strong>안전성, 멱등성, 캐시 가능성</strong>이 있다.</p>
<h2 id="안전성이란">안전성이란?</h2>
<p>HTTP Method가 <strong>서버의 상태를 바꾸지 않으면</strong> 그 메서드가 안전하다고 할 수 있다. 즉, 읽는 작업만 수행하는 메서드가 안전하다. 따라서 자주 볼 수 있는 HTTP Method 중에서는 <strong>GET, HEAD, OPTIONS</strong> 가 안전성을 가진다.
<br><br></p>
<h2 id="멱등성이란">멱등성이란?</h2>
<p>멱등이란 연산을 여러 번 적용하더라도 결괏값이 달라지지 않는 일을 말한다.</p>
<p>이 정의를 HTTP의 멱등성에 대입해보자. <strong>요청을 한 번 호출하든, 여러 번 호출하든 그 결과가 같음</strong>을 말한다. 즉, 동일한 요청을 한 번 보내는 것과 여러번 연속으로 보내는 것이 같은 효과를 가지고, 서버의 상태도 동일하게 남을 때 해당 HTTP Method가 멱등성을 가진다고 할 수 있다.</p>
<p>GET은 여러 번 호출해도 같은 결과가 돌아오며, 리소스에 변화를 주지도 않는다. PUT은 여러 번 호출해도 매번 같은 리소스로 업데이트되기 때문에 결과가 달라지지 않는다. 따라서 GET, PUT 처럼 리소스를 조회하거나 대체하는 메서드는 멱등성을 가진다. DELETE 또한 여러 번 호출해도 삭제된 리소스에 대한 결과가 달라지진 않는다.</p>
<p>이처럼 <strong>GET, PUT, DELETE, HEAD, OPTIONS, TRACE</strong>와 같은 HTTP Method가 멱등성을 가진다. </p>
<h4 id="안전성과-멱등성의-차이">안전성과 멱등성의 차이?</h4>
<p>요청을 한 번 호출하든 여러 번 호출하든 리소스에 수정이 발생하지 않는 것이 안전성을 가지는 것이며,
리소스에 수정이 발생한다해도 메서드를 여러 번 실행한 결과가 한 번 실행한 결과와 같다면 멱등성을 가진다.</p>
<p>멱등성을 API 관점에서 본다면 멱등한 API는 두 번 이상 요청해도 결과는 처음 요청과 똑같이 돌아온다. 단순히 돌아온 값 뿐만 아니라 서버 상태(DB)에도 영향을 미치지 않는다. 이처럼 시스템에 의도하지 않은 문제를 일으키지 않고 요청을 재시도할 수 있기 때문에 멱등성은 결함없고 안전한 API를 만드는데 중요하다.</p>
<p>이처럼 멱등한 API인지 확인하려면 확인하는 방법도 있다. 자세한 내용은 다음 링크를 참고하면 좋을 듯하다.</p>
<p><a href="https://www.tosspayments.com/blog/articles/21448">https://www.tosspayments.com/blog/articles/21448</a></p>
<p>안전성이 보장된 메서드는 리소스를 변경하지 않기에 동시에 멱등성도 보장된다. 하지만 멱등성을 지닌 메서드가 항상 안전성을 보장하진 않는다. </p>
<h4 id="patch의-멱등성">PATCH의 멱등성?</h4>
<p>PUT은 해당 리소스를 완전히 교체하기 때문에 멱등이라 할 수 있다.
하지만 PATCH는 HTTP 스펙상 구현 방법에 제한이 없고, 요청에 꼭 덮어 쓸 데이터가 있을 필요가 없다. 따라서 PATCH는 기본적으로 멱등성을 가지지 않는 메서드이며, 멱등으로 설계할 수도, 멱등이 아니게 설계할 수도 있다.
<br><br></p>
<h2 id="캐시-가능성이란">캐시 가능성이란?</h2>
<p><strong>응답 결과 리소스를 캐싱해서 효율적으로 사용할 수 있는가</strong>에 대한 여부이다.</p>
<p>캐시가 꼭 운영체제, 서버에만 있는 것이 아닌 브라우저 자체도 하나의 소프트웨어라 캐시 공간이 있다. 캐시 공간은 클라이언트가 서버에 한 번 요청했던 데이터에 대해 매 요청마다 다시 전송할 필요 없도록 브라우저에서 임시적으로 보관하고 있는 장소이다.</p>
<p>따라서 캐싱이 가능한 HTTP Method는 빠르게 결과 값을 받을 수 있다.</p>
<p>스펙상은 <strong>GET, HEAD, POST, PATCH</strong> 메서드가 캐시 가능성을 가지고 있다.</p>
<p>하지만 POST, PATCH와 같은 메서드들은 캐싱하기엔 body 데이터가 꽤 크기 때문에 실제로는 GET과 HEAD 메서드 정도를 캐시로 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] Docker와 가상화 기술(하이퍼바이저 가상화 & 컨테이너 가상화)]]></title>
            <link>https://velog.io/@garden-y/Docker-%EA%B0%80%EC%83%81%ED%99%94-%EA%B8%B0%EC%88%A0%ED%95%98%EC%9D%B4%ED%8D%BC%EB%B0%94%EC%9D%B4%EC%A0%80-%EA%B0%80%EC%83%81%ED%99%94-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EA%B0%80%EC%83%81%ED%99%94%EA%B3%BC-Docker</link>
            <guid>https://velog.io/@garden-y/Docker-%EA%B0%80%EC%83%81%ED%99%94-%EA%B8%B0%EC%88%A0%ED%95%98%EC%9D%B4%ED%8D%BC%EB%B0%94%EC%9D%B4%EC%A0%80-%EA%B0%80%EC%83%81%ED%99%94-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EA%B0%80%EC%83%81%ED%99%94%EA%B3%BC-Docker</guid>
            <pubDate>Fri, 12 Apr 2024 07:07:02 GMT</pubDate>
            <description><![CDATA[<p>도커 스터디를 진행하고 있는 겸.. 강의를 통해 배운 내용을 하나씩 정리해볼 예정이다!</p>
<p>1주차엔 가상화 기술이 무엇이며, 왜 사용하는지 알아보고, 가상화 기술에 있는 Hypervisor과 Container를 비교한다. 또한, Container 가상화 도구 중 하나인 Docker에 대해 알아본다.</p>
<p>(시작하기 전 이전 포스팅을 본다면 조금 더 이해하기 쉬울지도..?)</p>
<h1 id="가상화-기술">가상화 기술</h1>
<h4 id="가상이란">가상이란?</h4>
<p>실제로 존재하지 않지만 마치 존재하는 것처럼 느껴지는 거짓 현상을 의미한다.</p>
<h4 id="그렇다면-가상화-기술이란-무엇인가">그렇다면 가상화 기술이란 무엇인가?</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/aaf2f816-f986-41f1-bb2b-599e514b18fb/image.png" alt=""></p>
<p>컴퓨터에서 사용되는 기술이다. 실제로 존재하는 컴퓨터가 아니지만 마치 존재하는 것처럼 만들어주는 기술이다. 가상화 기술을 활용하면 하나의 컴퓨터에서 여러 대의 컴퓨터를 실행시킬 수 있다. (한 대의 컴퓨터로 여러 대를 가진 것처럼 사용 가능하다. 물론 성능이 좋아야 가능하다.) 가상화 기술은 컴퓨터 안에서 컴퓨터를 또 실행하는 기술이다. <strong>즉, 물리적인 컴퓨팅 환경 내부에서 논리적인 컴퓨팅 환경을 만들 수 있는 기술이다.</strong> </p>
<h3 id="가상화기술을-사용하는-이유">가상화기술을 사용하는 이유</h3>
<p>하나의 성능 좋은 컴퓨터에서 리소스 부족하지 않게 여러 대를 띄운다면 리소스 사용량에는 문제가 없다. 또한, 하나의 OS에서 프로그램이 모두 실행되고 있기에 관리하기도 편하다. 하지만 하나의 프로그램에 문제가 생길 경우 다른 프로그램에도 영향을 줄 수 있다. (하나의 프로그램의 사용량이 급증해서 리소스를 모두 소모하는 경우와 같은 상황들이 발생할 수 있다.)
→ 그래서 하나의 OS에서 여러 대의 운영을 피하는 것이 좋다.</p>
<h3 id="가상화-기술을-사용해-격리된-환경에서-프로세스-실행">가상화 기술을 사용해 격리된 환경에서 프로세스 실행</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/0db7ccdb-5960-4f16-a270-ae60c3a1ad0a/image.png" alt=""></p>
<p>가상화 기술을 사용한다면 한 대의 컴퓨터에 여러 대의 격리된 논리적인 OS 환경을 만들 수 있다. 또한, 가상으로 만들어진 컴퓨터에 사용자가 리소스를 직접 분배할 수도 있다.</p>
<br>

<h4 id="리소스-분배란">리소스 분배란?</h4>
<p>→ 하나의 가상 OS가 사용할 수 있는 리소스의 최댓값을 설정하는 것이다.
→ OS가 1개에서 총 5개로 증가했기 때문에 프로그램을 4개 사용하는 것보다는 총 리소스량은 증가한다. 하지만 가상의 컴퓨터들은 논리적으로 격리되어 있기 때문에 더 안전하다.</p>
<p>따라서 한 대의 프로그램에서 에러가 발생해도 다른 프로그램에는 영향을 주지 않는다. 하나의 프로그램에 버그가 생겨 리소스 사용량이 급증해도 OS 한 대의 용량은 제한되어 있기 때문에 하나의 환경 문제로 마칠 수 있다.</p>
<p>이렇게 가상화 기술을 사용하면 각각의 소프트 웨어는 여러 대의 컴퓨터를 사용하는 것처럼 안정적으로 실행 할 수 있다. 물리적으로는 한 대의 컴퓨터를 사용해서 경제적이지만 사용자는 마치 여러 대의 컴퓨터를 사용하는 것처럼 가상 환경을 사용해서 안전하게 소프트웨어를 운영할 수 있다. </p>
<br>

<h4 id="그러면-하드웨어를-여러-대-사용하면-굳이-복잡하게-가상화-기술을-사용하는가">그러면 하드웨어를 여러 대 사용하면 굳이 복잡하게 가상화 기술을 사용하는가?</h4>
<p>→ 하드웨어의 성능은 빠르게 발전하고 있다. 동시에 하드웨어를 사용하는 소프트웨어의 크기는 상대적으로 감소하는 추세이다. 
→ 기업 입장에서는 낮은 사양의 컴퓨터를 여러 대 사용하는 것보다 높은 사양의 컴퓨터 한 대 사용하는 것이 가격, 설치 공간, 설치 인력, 서버 운영, 하드웨어 사이즈나 배선 같이 여러 부분에서 경제적이기 때문이다.  (성능이 높다고 하드웨어 크기가 커지는 것도 아니고, 기기가 여러 대면 전기 사용량도 늘어난다.. 등) 따라서 가상화 기술을 사용해서 하나의 하드웨어를 효율적으로 활용하는 것이 엔터프라이즈 환경에서는 필수적이다.</p>
<h2 id="하이퍼바이저hypervisor-가상화">하이퍼바이저(Hypervisor) 가상화</h2>
<p><img src="https://velog.velcdn.com/images/garden-y/post/d4bacac1-0e51-4ce4-8ccf-780591c7d83f/image.png" alt=""></p>
<p>하이퍼바이저 가상화는 전통적인 가상화 기술 방법이다. 
하이퍼바이저는 컴퓨터에 설치되는 프로그램이다. 단일 물리적 머신에서 여러 가상 머신을 실행하는 데 사용할 수 있는 소프트웨어이다. OS에 설치하고 프로그램 실행해서 가상화 환경을 관리할 수 있다. 하이퍼 바이저를 통해 가상 OS를 만들 수 있고, 가상 OS를 실행시키고 종료시킬 수 도 있다.  이처럼 모든 가상 머신에는 고유한 운영 체제와 애플리케이션이 있다.
가상 OS를 만들면 사용자가 정해놓은 CPU나 메모리만큼 컴퓨터의 격리된 공간을 만들 수 있다. 
가상환경은 일반적인 프로그램과 비슷하다. 가상환경을 만들 때 마다 프로그램을 설치하는 것처럼 디스크(Storage) 공간을 차지한다. 가상환경을 실행하면 프로그램을 실행시키는 것과 마찬가지로 사용자가 지정한 만큼의 CPU와 메모리를 사용한다.</p>
<p>여기서 호스트 OS와 게스트 OS라는 용어가 나온다.</p>
<h4 id="호스트-os란">호스트 OS란?</h4>
<p>→ 물리적인 서버에 설치되는 OS를 말한다. 호스트 OS에서는 하이퍼바이저를 설치해서 가상환경들을 만들 수 있다. 하이퍼바이저는 호스트 OS의 자원을 격리해서 새로운 OS를 실행한다. </p>
<h4 id="게스트-os란">게스트 OS란?</h4>
<p>→ 호스트 OS에 의해서 격리되어 만들어지고, 실행되는 OS를 말한다.</p>
<p>호스트 OS는 물리적인 하드웨어와 직접 연결되어 있고, 게스트 OS는 호스트 OS의 리소스를 나눈 논리적인 공간이다. 이렇게 논리적으로 격리되어 있는 게스트 OS를 일반적으로 가상 머신이라고 부른다.</p>
<p>그리고 이런 가상머신에서 웹서버나 WAS, DB와 같은 서버 프로그램을 프로세스로 실행해서 운영한다 . (프로세스란? : 실행 중인 프로그램)</p>
<p>즉, 엔터프라이즈 운영 환경에서는 서버에 호스트 OS를 설치하고, 가상화를 사용하기 위해서 하이퍼바이저를 설치한 다음에 가상 머신을 만들어서 게스트 OS를 실행한다. 이렇게 만들어진 게스트 OS에서는 실제 실행을 원하는 프로세스를 운영한다.</p>
<h3 id="하이퍼바이저-원리">하이퍼바이저 원리</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/65f622fc-7f34-40fc-9dac-cebcc3a6b0d8/image.png" alt=""></p>
<p>프로세스는 정상적으로 실행되기 위해서 CPU나 메모리(하드웨어) 같은 리소스를 사용해야 한다. 프로세스가 하드웨어를 사용하기 위해서는 OS를 통해서만 가능하다. OS는 하드웨어를 사용하기 위해 커널이라는 중요한 도구가 설치되어 있다. 
일반적인 프로그램인 웹 브라우저나 문서 편집 프로그램을 실행할 때도 실행되어 있는 프로세스들은 OS의 커널에게 하드웨어 리소스를 요청한다.</p>
<h4 id="커널이란">커널이란?</h4>
<p>→ 커널은 운영체제 중 항상 메모리에 올라가 있는 운영체제의 핵심 부분이다. 하드웨어와 소프트웨어 사이에서 인터페이스를 제공하며, 컴퓨터 자원들을 관리하는 역할을 한다. 커널은 인터페이스로 소프트웨어 수행에 필요한 여러가지 서비스를 제공하고, 여러 하드웨어(CPU, 메모리 등)의 리소스를 관리한다.</p>
<h4 id="커널의-존재-이유">커널의 존재 이유?</h4>
<p>→ 커널이라는 중간다리를 통해 요청이 가는 이유는 하드웨어의 리소스를 사용하는 것은 아주 복잡하고 조심히 다뤄야하기 떄문이다. 그래서 커널은 하드웨어에 사용 요청을 대신 전달해주는 시스템 콜 이라는 표준을 정해놨다. 즉, 중요한 업무를 처리하는 소통 창구를 따로 만들어 놓은 것이다. 그래서 프로세스들은 커널에 시스템 콜에 보내서 하드웨어의 자원을 사용할 수 있다.</p>
<h3 id="다양한-os의-종류에서-하이퍼바이저">다양한 OS의 종류에서 하이퍼바이저</h3>
<p>대표적으로 윈도우, 리눅스, 맥OS 가 있다. 각각의 OS는 다른 종류의 커널을 사용한다. 따라서 시스템 콜도 모두 다르다. (각각의 OS들은 서로 소통이 안되는 각각 다른 나라의 외국인들이라고 생각하자)</p>
<p>이때, 윈도우 호스트 OS에서 게스트 OS로 맥이나 리눅스 OS를 실행해야 한다고 생각해보자. 게스트 OS의 커널은 실제로 물리적인 하드웨어가 없기 때문에 리소스를 사용하려면 호스트 OS의 커널로 리소스 사용을 요청해야 한다. 하지만 호스트 OS와 게스트 OS의 종류가 다르면 호스트 OS는 게스트 OS에서 전달받은 시스템 콜을 처리할 수 없게 된다. 여기서 하이퍼바이저가 다른 커널 간의 언어를 통역해주는 통역가 역할을 수행한다. </p>
<p>그래서 하이퍼바이저를 사용하면 격리된 공간을 만들면서 호스트 OS와 다른 종류의 게스트 OS도 사용 가능하다.</p>
<h3 id="다양한-os의-특징과-하이퍼바이저">다양한 OS의 특징과 하이퍼바이저</h3>
<p>하이퍼바이저를 사용하는 많은 케이스가 윈도우 호스트 OS에서 게스트 OS로 리눅스를 사용하는 경우이다. 윈도우 OS는 사용성은 뛰어나지만 한 대의 성능이 무거우며, 라이선스 가격도 비싸다. 따라서 메인 OS는 윈도우로 유지하면서 실제로 프로그램을 실행시키는 서버 운영은 가벼운 리눅스 서버를 사용하는 경우가 많다. (VirtualBox라는 하이퍼바이저를 사용해 윈도우 OS에 리눅스 OS를 설치했을 것이다.)</p>
<p>하이퍼바이저는 특정 제품이 아닌 기술의 종류이므로, 하이퍼바이저 역할을 수행하는 VirtualBox, VMware, RHEV 등 다양한 소프트웨어들이 존재한다. </p>
<p>(어느정도 CPU, 메모리를 할당할지 선택 가능, OS를 설치하기 위한 설치 파일인 이미지 선택 가능, OS를 설치한 후 가상 머신을 실행하면 실제 컴퓨터에 OS를 설치한 것과 동일한 화면, 이렇게 여러 대 설치, 실행 가능하며 이렇게 만들어진 것은 게스트 OS라 할 수 있다. 이렇게 설치한 가상 머신에서 리소스 사용량을 할당하고 실제 프로그램을 실행해서 운영을 할 수 있다.)</p>
<p><br><br><br></p>
<h2 id="컨테이너container-가상화">컨테이너(Container) 가상화</h2>
<p>컨테이너 가상화 기술은 현대 애플리케이션 운영 환경에서 하이퍼바이저 방식보다 더 선호 되는 가상화 기술이다. 
이는 가볍고, 빠르다 라는 핵심 장점이 있다. (웹 서버 하나 띄우는데 각각 60초와 3초가 걸린다.)</p>
<h3 id="하이퍼바이저와-컨테이너-가상화의-기술적인-차이">하이퍼바이저와 컨테이너 가상화의 기술적인 차이</h3>
<p><img src="https://velog.velcdn.com/images/garden-y/post/e498ad48-cc4e-4892-ab57-457752ade94f/image.png" alt=""></p>
<p>컨테이너 가상화는 리눅스 커널이 제공하는 LXC라는 자체 격리 기술에서 출발했다. 하이퍼바이저라는 소프트웨어는 격리된 공간을 만들어 준다. 반면 컨테이너 가상화는 커널의 자체 기능만을 사용해서 격리된 공간(컨테이너)을 만들 수 있다.</p>
<h4 id="lxc-기술">LXC 기술</h4>
<p>커널의 네임스페이스와 Cgroups 라는 기능을 활용한다. 
네임스페이스는 프로세스의 하드 드라이브, 네트워크, 사용자, 호스트 네임처럼 리소스를 나누는 기준의 역할을 한다. Cgroups는 프로세스가 사용하는 메모리, CPU, 하드디스크, 네트워크 bandwidth 처럼 리소스의 사용량을 배분하는 기술이다. 
이렇게 LXC 기술을 사용해 만들어진 각각의 격리된 공간을 컨테이너라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/21e6eb8f-369c-4592-a058-8c0b56247862/image.png" alt="">
컨테이너 가상화는 하이퍼바이저 없이 커널의 자체 기술을 활용한 가상화임을 기억하면 된다. 컨테이너 가상화는 커널의 격리 기능을 활용하기 때문에 모든 컨테이너는 Host OS의 커널을 공유해서 사용한다. 
<br><br></p>
<p>하이퍼바이저는 호스트 OS와 게스트 OS의 커널이 각각 독립적으로 존재하며, 하이퍼바이저라는 소프트웨어가 중간에서 커널 간의 통신을 지원하기에 각각의 시스템 콜들이 하이퍼바이저의 통역을 거쳐가야 한다. 따라서 이렇게 요청이 거쳐가는 단계가 늘어나며 이것을 오버헤드가 크다라고 할 수 있다. </p>
<p>하지만 컨테이너는 호스트 OS의 커널을 사용하기 때문에 중간 단계가 따로 없어져 상대적으로 오버헤드가 적다. 즉, 하드웨어 리소스 사용 요청이 더 효율적으로 이뤄진다는 것을 의미한다. </p>
<p>또한, 하이퍼바이저처럼 커널이 독립적으로 있는 것은 커널을 실행하는데 있어서 더 오랜 시간이 걸린다는 것이다. 각각의 컨테이너들은 자체적 커널이 없고 호스트 OS 커널을 공유하기 때문에 커널을 실행하는 시간도 없어진다. 즉, 부팅 속도가 빨라진다.</p>
<p>이렇게 적은 오버헤드와 빠른 부팅이 컨테이너가 빠를 수 있는 이유이다.</p>
<p>하지만 커널을 독립적으로 가지고 있는 것이 보안면으로는 더 뛰어나다고 볼 수도 있다. 또한, 컨테이너는 호스트 OS의 커널을 공유하기 때문에 호스트 OS와 다른 종류의 OS는 실행할 수 없다는 것이 상대적인 단점이라고 할 수 있다.</p>
<p>커널이 자체적으로 제공하는 가상화 기술은 사용자가 직접 컨트롤하기는 어렵다. 따라서 도커는 이 커널의 컨테이너 가상화 기술을 편리하게 사용하기 위해 만들어진 소프트 웨어이다.  사용자는 도커를 통해 컨테이너를 만들고, 운영할 수 있다. 하이퍼바이저 방식에서는 격리된 공간을 만드는 소프트웨어가 하이퍼바이저라는 소프트웨어라면, 컨테이너 가상화에서는 실제 격리를 수행하는 주체는 도커가 아닌 커널 자체이다. 도커는 커널의 가상화 기술을 활용할 수 있도록 도와주는 보조 도구이다. 
<br><br><br></p>
<h1 id="docker">Docker</h1>
<p><img src="https://velog.velcdn.com/images/garden-y/post/b33667c0-9519-459f-bdc4-072a27499e17/image.png" alt=""></p>
<p>컨테이너 가상화 기술을 사용하기 위한 도구이다. 도커를 사용하면 커널의 컨테이너 가상화 기술을 사용자가 손쉽게 활용할 수 있다. </p>
<p>도커와 같은 컨테이너 가상화 도구를 컨테이너 플랫폼이라고 부른다. 컨테이너 플랫폼은 자체적으로 가지고 있는 컨테이너 엔진과 컨테이너 런타임으로 구성되어 있다. </p>
<h4 id="컨테이너-엔진이란">컨테이너 엔진이란?</h4>
<p>→ 말 그대로 사용자의 요청을 받아서 컨테이너를 관리해주는 역할을 한다. </p>
<h4 id="컨테이너-런타임이란">컨테이너 런타임이란?</h4>
<p>→ 직접 커널과 통신하면서 실제로 격리된 공간을 만드는 역할을 한다.</p>
<p>도커는 runc라는 컨테이너 런타임을 사용한다. 이 컨테이너 런타임은 OCI라는 곳에서 규정한 컨테이너 런타임 인터페이스, CRI 표준을 구현했기 때문에 무조건 runc를 사용해야하는 것은 아니다. 하지만 runc는 도커가 지원하는 기본 컨테이너 런타임이다. 이 컨테이너 플랫폼에는 Podman이나 Containerd 같은 다른 소프트웨어들도 있다. 컨테이너 가상화를 사용할 때 어떤 컨테이너 플랫폼을 사용할지 또는 어떤 컨테이너 런타임을 사용할지는 자유롭게 선택할 수 있다. 
여기서 도커는 가장 점유율이 높은 컨테이너 플랫폼이다.</p>
<h2 id="도커의-아키텍처">도커의 아키텍처</h2>
<p><img src="https://velog.velcdn.com/images/garden-y/post/bbe22485-95cb-4e8f-b33a-142b49696fce/image.png" alt=""></p>
<p>도커는 클라이언트-서버 모델로 실행된다. (서버-클라와 동일한 관계이다.) 도커에도 사용자의 명령을 전달해주는 클라이언트와 실제로 컨테이너를 관리해주는 도커데몬 이라는 서버가 존재한다.</p>
<p>클라이언트는 사용자의 명령을 도커 데몬에게 전달하고, 도커 데몬은 컨테이너를 관리하는 기능을 제공해주고 도커 D라고도 부른다. 
(보통 데몬이라고 이름 붙은 소프트웨어는 서버에서 지속적으로 실행이 되는 소프트웨어를 의미한다. )</p>
<p>도커 데몬도 호스트 OS에서 지속적으로 실행되면서 클라이언트의 요청에 따라서 컨테이너를 관리한다. 그리고 도커 데몬은 클라이언트가 이런 기능들을 사용할 수 있도록 API를 제공한다. </p>
<h4 id="api란">API란?</h4>
<p>→ 상호 간의 약속을 의미한다. 
→ API는 혼인신고서, 전입신고서 와 같이 상호간의 주고받는 데이터의 약속된 양식이다. </p>
<p>그래서 도커 데몬을 컨테이너 마을에서 컨테이너를 관리해주는 주민센터 직원이라고 생각하면 된다. 도커 데몬은 컨테이너를 관리하기 위해 API 명세서를 제공해준다. 컨테이너를 생성하려면 컨테이너 생성 API 요청을 보내야 하고, 삭제하려면 삭제 API.. 와같이 양식에 맞게 보내면 실행 된다. </p>
<p><a href="https://docs.docker.com/engine/api/v1.45/">https://docs.docker.com/engine/api/v1.45/</a> 다음 링크는 Docker 엔진이 제공하는 API에 대한 공식 문서이다. Docker 엔진한테 어떤 요청을 보낼 때는 이 API 양식에 맞춰 보내야 한다. 일반 API와 비슷하게 각각의 메서드로 컨테이너스에 json으로 요청을 보낼 수 있다. 요청 보냈을 때 받는 응답은 json의 포맷으로 돌아온다.  이외에도 쿼리 파라미터에 대한 정보도 볼 수 있다. 
이런 형식에 맞춰 요청을 보내는 것도, 응답을 제대로 읽는 것도 어렵다. 또한, 작업마다 API를 파악하고 전달하려면 시간이 오래 걸린다. 따라서 사용자가 직접 API를 사용하기는 어렵다. </p>
<p>그래서 Docker에서는 Command Line 도구인 Docker CLI(Command Line Interface) 가 클라이언트 툴로 제공된다. 이 CLI는 클라이언트가 명령어를 입력하면 이 명령어를 서버의 API 형식에 맞게 만들어서 대신 전달해주는 역할을 한다. </p>
<p>따라서 사용자는 Docker CLI를 통해 도커 데몬의 API와 쉽게 통신할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/60eb7575-4230-424f-88d5-587d62e78608/image.png" alt=""></p>
<p>위 사진과 같이 중간다리 역할을 해준다고 생각하면 된다. 
(원리 예시: Docker가 제공하는 클라이언트 툴을 사용하면 명령어 한 줄로 API와 통신할 수 있으며, 그의 예시로는 CLI에서 Docker PS를 입력하면 컨테이너 목록을 볼 수 있다. 실제로는 CLI가 대신 get메서드로 container list API를 형식에 맞춰 도커데몬에게 보내준다. 도커 데몬에선 이러한 요청을 분석해서 컨테이너 리스트를 불러온 뒤 json 형태로 응답을 보내준다. CLI는 json 형태의 요청을 보기 편리한 테이블의 형태로 만들어서 우리에게 보여준다.)</p>
<h4 id="도커의-아키텍처-정리">도커의 아키텍처 정리</h4>
<p><img src="https://velog.velcdn.com/images/garden-y/post/4e360ed6-ed86-47a4-bb7f-9943c468cc27/image.png" alt=""></p>
<p>→ 도커는 클라이언트 - 서버 모델로 실행된다. 클라이언트는 CLI, 서버는 도커 데몬으로 구성된다. 사용자는 CLI를 통해 간단한 명령어를 사용해서 컨테이너를 관리할 수 있으며, 명령어를 실행하면 CLI는 API에 맞게 요청을 만들어서 Docker 데몬으로 전달하고, Docker 데몬은 컨테이너 런타임을 통해서 컨테이너를 조작하고 그 결과를 다시 CLI로 보내준다.
<br><br><br><br></p>
<h3 id="레퍼런스">레퍼런스</h3>
<ul>
<li>[2024 NEW] 개발자를 위한 쉬운 도커</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] Server와 Server 운영 방법]]></title>
            <link>https://velog.io/@garden-y/Docker-Server%EC%99%80-Server-%EC%9A%B4%EC%98%81-%EB%B0%A9%EB%B2%95Baremetal-Hypervisor-Container</link>
            <guid>https://velog.io/@garden-y/Docker-Server%EC%99%80-Server-%EC%9A%B4%EC%98%81-%EB%B0%A9%EB%B2%95Baremetal-Hypervisor-Container</guid>
            <pubDate>Fri, 12 Apr 2024 06:21:03 GMT</pubDate>
            <description><![CDATA[<p>제목 앞에 도커를 둔 이유는 .. 가상화 기술을 학습하기 전 서버에 개념을 정리해두는 것이 좋기 때문이다. 서버와 더불어 서버 운영 방법에는 어떤 것이 있는지도 간단하게 정리해보려 한다.</p>
<h1 id="서버란">서버란?</h1>
<p>서버는 IT 산업에서 쉽게 접할 수 있는 용어이다. 서버 프로그래머, 서버 운영, 서버 설치 등 다양한 곳에서 사용 된다.
한마디로 정의되면 하드웨어와 그 하드웨어에서 실행중인 소프트웨어까지 모두 포함하는 단어이다.
하드웨어 기계만 두고도, 소프트웨어만 있을 때도 다 서버라 부르는 경우가 있기에 문맥에 따라 이해하면 된다.</p>
<p>서버의 전원이 꺼졌습니다 → 하드웨어 서버
서버 설치 파일을 다운받았습니다 → 소프트웨어 서버</p>
<p>위처럼 서버는 상황과 환경에 따라 의미가 다르다.
일반적으로는 하드웨어에서 실행 중인 소프트웨어 라고 생각하면 된다.
<br><br></p>
<p>Server의 어원을 살펴보자.Serve 라는 단어가 무언가를 제공해 준다는 것을 의미한다.
Service, Serving, Serve.. → 따라서 Server ‘제공하는 주체’이다.
→ 서버는 항상 어떤 요청에 대한 결과를 제공해 준다.
(서버에 무언가를 요청하는 주체를 클라이언트라고 한다.)</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/d41b7f9d-4651-4df6-a0c8-1804434809ed/image.png" alt=""></p>
<p>클라이언트가 서버에게 요청을 보내면 물리적인 하드웨어를 통해 소프트웨어로 전달되고 소프트웨어가 요청에 해당하는 실제 기능을 수행한 다음에 클라이언트에게 원하는 결과를 응답해준다.
it 산업에서 애플리케이션을 운영하는 근본적인 이유도 사용자에게 어떤 서비스를 제공하기 위해서이다.</p>
<p>어떤 서비스를 제공 하는지는 실행 중인 소프트웨어에 따라서 달라지겠지만 클라이언트의 요청에 대한 결과를 제공해주는 근본적인 역할은 모든 서버가 동일하다.</p>
<h3 id="실제-기업에서-운영하는-서버의-종류">실제 기업에서 운영하는 서버의 종류</h3>
<p>서버는 서버에서 어떤 소프트웨어가 실행 중 인지에 따라 다양한 서버로 분류할 수 있다. 다양한 서버가 있지만 우리는 기업에서 운영하는 서버의 종류에 대해서 알아보려 한다. </p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/464fc415-066c-44f4-a119-e250ac54c53b/image.png" alt=""></p>
<ul>
<li><p>파일 서버 
파일 공유 소프트웨어가 설치되어 있는 서버이다.
→ 파일 공유하는 기능을 클라에게 제공한다.</p>
</li>
<li><p>DB 서버
데이터를 관리하는 DBMS가 설치된 서버이다.
→ 데이터 저장하는 기능을 클라에게 제공한다.
→ 오픈소스인 MySQL이나 PostgreSQL 사용이나, 오라클 DB를 구매해 설치해 사용 가능하다.</p>
</li>
</ul>
<p>위와 같은 서버들은 이미 개발되어 있는 소프트웨어를 다운 받아서 실행시키는 형태이다.(우리가 웹 브라우저를 사용하기 위해 크롬을 다운받아 쓰는 것과 비슷하다.)또한, 다운받은 소프트웨어는 오픈 소스 소프트웨어를 사용할 수도, 유로 라이선스를 구매해 사용할 수도 있다. </p>
<ul>
<li><p>웹 서버 (WEB)
클라이언트가 웹 브라우저를 통해서 HTTP 요청을 보내면 정적인 웹 페이지를 제공해주는 서버이다.
주로 HTML, JS, CSS 등 프론트엔드 개발자가 개발한 파일들이 사용된다.
웹 서버의 종류에는 Nginx, Apache HTTP Server, LiteSpeed .. 가 있다.</p>
</li>
<li><p>웹애플리케이션 서버(WAS)
Java와 같은 프로그래밍 언어로 개발된 백엔드 애플리케이션을 실행하는 서버이다. 
→ 소스 코드를 통해 개발된 소프트웨어를 실행시키는 경우 웹서버나 웹플리케이션서버가 있다. </p>
</li>
</ul>
<p>이 외에도 프록시 서버, 메일 서버 등 다양한 서버가 있지만 기업에서 운영하는 대표적인 서버로는 4가지로 볼 수 있다.</p>
<h1 id="서버-운영-방법">서버 운영 방법</h1>
<p>엔터프라이즈 환경에서는 아주 많은 양의 서버를 운영해야한다. WEB, WAS, DB.. 등
이렇게 많은 양의 서버를 운영하는 방법으로 크게 3가지를 뽑을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/761f0a69-380b-4ed9-97c5-bf787a2f5bb6/image.png" alt=""></p>
<ol>
<li>베어메탈 (Baremetal)
베어메탈 이라는 용어는 하드웨어 상에 어떤 소프트웨어도 설치되어 있지 않은 상태를 말한다. 물리 서버를 그대로 제공해주는 것이다.
일반적으로 컴퓨터를 구입하고 프로그램을 실행시키는 방식과 비슷하다. 서버를 하나 구입한 뒤 서버 위에 OS를 설치하고 여러 개의 소프트웨어를 실행시키는 방식이다.</li>
</ol>
<p>→ 따라서 기업에서 운영하기엔 비효율적인 경우가 많다.</p>
<ol start="2">
<li>하이퍼바이저 (Hypervisor)</li>
<li>컨테이너 (Container)</li>
</ol>
<p>여기서 2,3 번은 하나의 큰 서버를 열 개의 논리적인 작은 서버로 쪼개서 사용하는 가상화 기술을 사용한 방식이다. 이 중에서도 도커는 컨테이너 방식에 해당한다.</p>
<p>2,3은 가상화 기술을 사용한 방식인데 이 두개는 가상화 기술과 함께 다음 포스팅에서 자세히 다뤄볼 예정이다!</p>
<h3 id="레퍼런스">레퍼런스</h3>
<ul>
<li>[2024 NEW] 개발자를 위한 쉬운 도커</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SpringBoot] 의존성 주입(DI)과 제어의 역전(IoC)]]></title>
            <link>https://velog.io/@garden-y/SpringBoot-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85DI%EA%B3%BC-%EC%A0%9C%EC%96%B4%EC%9D%98-%EC%97%AD%EC%A0%84IoC</link>
            <guid>https://velog.io/@garden-y/SpringBoot-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85DI%EA%B3%BC-%EC%A0%9C%EC%96%B4%EC%9D%98-%EC%97%AD%EC%A0%84IoC</guid>
            <pubDate>Wed, 03 Apr 2024 11:52:33 GMT</pubDate>
            <description><![CDATA[<p>SpringBoot를 얼렁뚱땅 배운 탓에 요즘 학교 수업을 들으면서 처음부터 하나하나 정리하는 중이다..^^
<br><br></p>
<h3 id="스프링-프레임워크">스프링 프레임워크</h3>
<p>스프링 프레임워크는 자바 플렛폼을 위한 오픈 소스 애플리케이션 프레임워크로 동적인 웹 사이트 개발을 위해 여러가지 서비스를 제공해준다.
스프링 프레임 워크는 IoC/DI, PSA, AOP 이렇게 세가지 핵심 프로그래밍 모델을 지원하는데, 오늘 IoC/DI에 대해 알아보려 한다.
<br></p>
<h3 id="의존성">의존성</h3>
<p>우선 의존성 주입을 알기 위해선 의존성이 무엇인지부터 아는 것이 중요하다.
다음 코드를 보며 의존성을 알아보자!</p>
<pre><code>public class TodoService {
    private final FileTodoPersistence persistence;

    public TodoService() {
        this.persistence = new FileTodoPersistence();
    }

    public void create() {
        ...
        persistence.create(~);
    }
}</code></pre><p>TodoService는 FileTodoPersistence에 의존한다. 이 말은 즉, TodoService 객체는 FileTodoPersistence 없이는 제 기능을 못하며, TodoService가 FileTodoPersistence 객체를 생성하고 관리한다.</p>
<p>이렇게 의존성이 있는 코드의 문제점은 FileTodoPersistence를 다른 클래스로 변경시(ex. DatabaseTodoPersistence나 S3TodoPersistence) TodoService의 코드를 수정해야 한다.
이러한 문제는 FileTodoPersistence에 의존하는 클래스가 많을수록 수정해야 하는 코드가 늘어난다는 점이다.
<br><br></p>
<h2 id="의존성-주입">의존성 주입</h2>
<p>이러한 의존성의 단점으로 의존성 주입이 필요하다.
의존성 주입(Dependency Injection, DI)이란 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴이다. 인터페이스를 사이에 둬서 클래스 레벨에서 의존 관계가 고정되지 않게 해준다.
또한, 객체 생성 시 의존하는 객체를 외부에서 주입한다. 외부에서 의존하는 객체의 종류를 바꿀 수 있기 때문에 의존성의 단점을 해결할 수 있다.</p>
<p>재사용이 쉬우며, 테스트나 유지 관리가 쉬워진다. 또한, 유연성을 향상시킨다.
<br>
단점도 있다. 책임을 분리하기 위해 Class가 늘어나 복잡해질 수 있다. 주입된 객체의 코드 추적이 어려우며, 프레임워크에 대한 의존도를 높인다. </p>
<p>그렇다면 의존성 주입은 어떻게 할 수 있는가?</p>
<p>Spring에서 의존성을 주입하는 덴 다음과 같이 여러 방법이 있다.</p>
<ol>
<li>생성자 주입</li>
<li>수정자 주입 (메서드 주입)</li>
<li>필드 주입</li>
</ol>
<h3 id="생성자-주입">생성자 주입</h3>
<p>생성자를 통해 의존 관계를 주입받는 방법이다.</p>
<p>다음은 생성자 주입의 예시 코드이다.</p>
<pre><code>@Component
public class TodoService {
    private ITodoPersistence persistence; // Interface

    @Autowired
    public TodoService (ITodoPersistence persistence) {
        this.persistence = persistence;
    }

    public void create() {
        ...
        persistence.create(~);
    }
}</code></pre><p>@Autowired는 스프링 컨테이너에 의존성을 주입해달라고 지시하는 어노테이션이다.
생성자에 @Autowired를 하면 스프링 컨테이너에 @Component로 등록된 빈에서 생성자에 필요한 빈들을 주입해주기 때문에 @Autowired를 붙였다. </p>
<p>이러한 주입 방법은 생성자 호출 시점에 1번만 호출되는 것을 보장해주며, 불변과 필수 의존 관계에 사용한다. NPE(NullPointException)을 방지 가능하며, 주입 받을 필드를 final로 선언이 가능하다는 장점이 있다.</p>
<p>Spring 기준 4.3부터는 생성자가 하나일 경우, 생성자의 파라미터 타입이 빈으로 등록 가능할 경우 @Autowired 어노테이션을 생략할 수 있다고 한다.</p>
<h3 id="수정자-주입메서드-주입">수정자 주입(메서드 주입)</h3>
<p>메서드로 의존 관계를 주입받는 방법이다. 선택과 변경 가능성이 있는 의존 관계에서 사용된다.
자바 빈 property 규약의 수정자 메서드 방식을 사용하는 방법이다. set필드명 메서드를 생성하여 의존관계를 주입한다.</p>
<p>다음은 수정자 주입의 예시 코드이다.</p>
<pre><code>@Component
public class TodoService {
    private ITodoPersistence persistence; // Interface

    @Autowired
    public void setITodoPersistence (ITodoPersistence persistence) {
        this.persistence = persistence;
    }
}</code></pre><h3 id="필드-주입">필드 주입</h3>
<p>필드에서 바로 주입하는 방법이다.</p>
<p>다음은 필드 주입의 예시 코드이다.</p>
<pre><code>@Component
public class TodoService {
    @Autowired
    private final ITodoPersistence persistence; // Interface

}</code></pre><p>코드가 간결하다는 장점이 있다. 하지만 외부에서 변경 불가능해서 테스트하기 어려움이 있으며, DI 프레임워크(Spring)가 없으면 아무 것도 하지 못한다. </p>
<h3 id="의존성-주입-결론">의존성 주입 결론?!</h3>
<p>의존성 주입에는 다양한 방법이 있지만 생성자 주입이 권장되고 있다.
(Spring 프레임워크에서도 생성자 주입 사용을 권장하고 있다.)</p>
<p>생성자 주입이 권장되는 이유는 1. 필드 주입의 단점 2. 수정자 주입의 단점 3. 객체의 불변성 확보 4. 테스트 코드 작성의 편리함 5. 순환 참조 방지 6. 개발자의 의존성 주입 실수 방지(final) 이 있다.</p>
<p>각각의 이유는 찾아보길 바란다 :)</p>
<pre><code>@Component
@RequiredArgsConstructor
public class TodoService {

    private final ITodoPersistence persistence; // Interface

}</code></pre><p>다음 코드는 내가 DI 하기 위해 사용하는 유형의 예시이다.(생성자 주입 방식이다.)
@RequiredArgsConstructor를 통해 final 변수, 필드를 매개변수로 하는 생성자를 만들 수 있기에 주입 받을 객체를 final로 선언해줬다. 또한, Spring 기준 4.3부터는 생성자가 하나일 경우, 생성자의 파라미터 타입이 빈으로 등록 가능할 경우 @Autowired 어노테이션을 생략할 수 있기에 위와 같이 @Autowired를 생략하고 작성했다.</p>
<h2 id="제어의-역전">제어의 역전</h2>
<p>IoC(Inversion of Control)란 제어의 역전이다. 
내가 사용할 객체를 내가 결정하지 않고 외부에서 결정하는 것을 말한다. 즉, 메서드나 객체의 호출 작업을 개발자가 아닌 스프링에게 제어권을 넘기는 것을 말한다.
제어의 흐름을 바꾸는 것이며, 객체의 의존성을 역전시킴으로써 객체 간의 결합도를 줄이며, 유연성을 높인다.</p>
<p>DI는 객체를 직접 생성하는 것이 아닌 외부(IOC컨테이너)에서 생성한 후 주입시켜주는 방식이므로, DI를 통해 IoC가 일어나기 때문에 둘이 같이 묶여서 개념이 정의된다. (Spring에서 위와 같이 DI 시킨다는 한해서 정의한다면!)</p>
<p>하지만 DI와 IoC를 찾다보니 DI를 사용한다고 IoC 컨테이너가 무조건 필요한 것은 아니라고 한다. IoC 개념을 프레임워크와 라이브러리 차이에서도 볼 수 있듯이 IoC는 제어권의 흐름이 변하는 것을 의미하기에 DI보다 @Autowired이 하는 역할이라고 이해하는게 맞는 것 같다.</p>
<p>DI == IoC이 아닌 DI ⊂ IoC라고 생각했는데 둘다 아닌.. (심지어 첨부한 아래 글엔 교집합이 있는 두 개의 집합이라 표현한다.) Spring의 IoC 컨테이너(DI 컨테이너)가 IoC를 발생한다고 생각하는 것이 가장 정확한 표현인 것 같다.</p>
<p><a href="https://jwchung.github.io/DI%EB%8A%94-IoC%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EC%95%84%EB%8F%84-%EB%90%9C%EB%8B%A4">https://jwchung.github.io/DI%EB%8A%94-IoC%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EC%95%84%EB%8F%84-%EB%90%9C%EB%8B%A4</a></p>
<p>해당 개념에 대해 이해하는데 읽어보면 좋을 글일 것 같아 첨부한다!</p>
<p><br><br><br></p>
<p>글을 쓰면서도 느끼지만 부족한 부분이 정말 많았고, 여전히 많음을 느낀다. 
글을 쓰면서 정확하게 정리하기 위해 더 자세히 찾아보게 되기에.. 블로그를 통해 개념을 정리하는 것은 정말 좋은 습관인거 같다. 이 글을 시작으로 개발 블로그를 본격 진행해봐야겠다는 후기^_^</p>
<p>반박 언제나 받습니다!!!!!
<br><br></p>
<h3 id="레퍼런스">레퍼런스</h3>
<ul>
<li>React.js, 스프링 부트, AWS로 배우는 웹 개발 101</li>
<li><a href="https://jwchung.github.io/DI%EB%8A%94-IoC%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EC%95%84%EB%8F%84-%EB%90%9C%EB%8B%A4">https://jwchung.github.io/DI%EB%8A%94-IoC%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EC%95%84%EB%8F%84-%EB%90%9C%EB%8B%A4</a></li>
<li><a href="https://programforlife.tistory.com/111">https://programforlife.tistory.com/111</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SOPT] DO SOPT 33기 서버(Server)파트 YB 서류 · 면접 최종 합격 후기]]></title>
            <link>https://velog.io/@garden-y/SOPT-DO-SOPT-33%EA%B8%B0-%EC%84%9C%EB%B2%84Server%ED%8C%8C%ED%8A%B8-YB-%EC%84%9C%EB%A5%98-%EB%A9%B4%EC%A0%91-%EC%B5%9C%EC%A2%85-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@garden-y/SOPT-DO-SOPT-33%EA%B8%B0-%EC%84%9C%EB%B2%84Server%ED%8C%8C%ED%8A%B8-YB-%EC%84%9C%EB%A5%98-%EB%A9%B4%EC%A0%91-%EC%B5%9C%EC%A2%85-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 29 Feb 2024 19:07:04 GMT</pubDate>
            <description><![CDATA[<p>이미 33기 솝트가 끝나고 34기 솝트가 시작하려하지만.. 더 늦기 전에 기록을 해보려 한다. 더불어 주변에서 솝트에 어떻게 들어가냐고 많이들 물어보길래 겸사겸사 써본다.<del>(하지만 지극히 주관적일지도?)</del></p>
<p>SOPT는 대학생 연합 IT 벤처창업 동아리로 기획/디자인/안드/iOS/웹/서버 파트별로 활동한다. 다양한 활동이 있으니 궁금하면 인스타랑 홈페이지를 찾아보길 바란다.</p>
<h2 id="서류-문항">서류 문항</h2>
<p>우선 솝트는 늘 공통 문항과 파트별 문항이 나눠져 있다. 공통 문항은 보통 4문항인 것 같고, 파트별 문항은 기수마다, 파트마다 다르겠지만 4<del>6개정도 되는 것 같다. 문항별 500</del>800자까지 제한이 있으며, SOPT 자체 페이지에서 서류를 받기 때문에 제한된 글자를 넘으면 더 못쓰도록 되어있으니 글자수 맞춰서 잘 쓰시길..</p>
<p>솝트는 매 기수별로** 3가지 핵심 가치**가 있다. 보통 서류 문항에서는 그것과 연관된 질문들이 많이 나온다. 대놓고 핵심 가치에 관한 질문도 있지만, 질문에 핵심 가치가 없다하더라도 연관 지어서 답변하면 좋은 것 같다.</p>
<p>33기 DO SOPT의 핵심 가치는 <strong>&quot;실천&quot;, &quot;몰입&quot;, &quot;공유&quot;</strong> 였고, 대체적으로 이에 관련해서 나를 보여줄 수 있는 대답을 쓴 것 같다.</p>
<p>글은 개인마다 스타일이 너무 다르고 정답이 없으므로 질문마다 어떤 내용을 작성했는지 정말 간단하게만 쓰겠다.</p>
<h3 id="공통-문항">공통 문항</h3>
<blockquote>
</blockquote>
<ol>
<li>살면서 가장 깊게 몰입했던 경험에 대해 소개해주시고, 해당 경험에서 가장 어려웠던 문제와 그 해결 과정을 새롭게 깨달은 점을 중심으로 설명해 주세요. (창업 경험과 무관해도 괜찮습니다.) (700자)</li>
</ol>
<p>전공 시작한 지 얼마 안돼서 협업한 경험을 썼다. 부족한 상태에서 많이 배우려 했다는 점에서 몰입이라는 키워드를 활용해서 작성했다.</p>
<blockquote>
</blockquote>
<ol start="2">
<li>지원자님이 동료로부터 받았던 긍정적인 피드백과 부정적인 피드백을 하나씩 소개해주시고, 다음 단계로의 성장을 위해 두 가지의 피드백을 어떻게 활용하고 계신지 작성해 주세요. (600자)</li>
</ol>
<p>모든 문항 중에 가장 많이 고민한 질문인 것 같다. 단점을 적으라는 질문은 늘 어렵다. 나는 멋사 여대톤에서 대상 탄 경험을 바탕으로 작성했고, 나름 극복할 수 있는 단점으로 적은 것 같다.?!</p>
<blockquote>
</blockquote>
<ol start="3">
<li>실천의 관점에서 성공과 실패의 차이점을 작성하고, 둘 중 어떤 것에서 더 많이 배울 수 있다고 생각하시는지 경험을 바탕으로 설명해 주세요.  (600자)</li>
</ol>
<p>이 질문도 굉장히 모호하지만 그냥 내가 생각하는 실천에 대해서 작성했다.</p>
<blockquote>
</blockquote>
<ol start="4">
<li>본인이 속해 있는/있었던 가장 애정이 가는 단체를 간략하게 소개해주시고, 그곳에서 지원자님이 어떤 역할이었으며 해당 단체의 성장을 위해 어떤 노력을 했는지 경험을 바탕으로 설명해 주세요. (600자)</li>
</ol>
<p>피로그래밍 회장하면서 이 동아리를 위해 내가 노력한 부분들과 실천한 부분들에 대해서 작성했다. <del>피로그래밍을 애정해서 회장까지한 나에게 가장 찰떡같은 질문이 아니였나 라는 생각을 한다..</del></p>
<h3 id="서버-파트-문항">서버 파트 문항</h3>
<blockquote>
</blockquote>
<ol>
<li>DO SOPT 서버 YB 모집에 지원해 주셔서 감사합니다. 서버 파트에 지원하게 된 동기와 DO SOPT 서버파트 활동을 통해 어떠한 목표를 이루고 싶은지 구체적으로 작성해 주세요. (700자)</li>
</ol>
<p>지금까지 Django만 배웠는데 Spring을 배워 진정한 백엔드 개발자가 되고 싶다고 작성했다. 추가로 이 문항에서 동아리를 향한 애정을 좀 보여줬던 것 같다.</p>
<blockquote>
</blockquote>
<ol start="2">
<li>협업하면서 완수하지 못한 경험 혹은 아쉬웠던 경험을 작성해 주세요. 또한 해당 경험으로 인해 성장한 점과 SOPT에서 어떠한 자세로 협업에 임할 것인지 구체적으로 작성해 주세요. (800자)</li>
</ol>
<p>개발 협업이 아닌 대학 교양 팀플 얘기를 작성했다.</p>
<blockquote>
</blockquote>
<ol start="3">
<li>자신이 사용해 본 프레임워크나 언어의 활용 정도에 대해 적어주세요. (3점 - 참고 자료 없이 프로젝트가 가능함 어려운 부분에 대해서는 약간의 참고를 통해 구현이 가능함, 2점 - 시스템 동작 방식을 알고 있고 기본적인 기능에 대해 구현이 가능함, 1점 - 약간의 코드 작성만 가능함, 0점 - 경험 없음) [작성 예시 : Java - 3, Python - 2, Spring - 1, TypeScript - 3, Nest.js 2] 또, 자신이 자신 있는 프레임워크나 언어가 존재한다면 이를 학습한 과정을 구체적으로 작성해 주세요. 만약 프레임워크나 언어에 대한 경험이 없거나 부족하다고 판단되면, 서버파트에서 학습을 위해 어떤 노력을 할 것인지 구체적으로 작성해 주세요. (800자)</li>
</ol>
<p>나름? 솔직하게 작성했던 것 같다. 어차피 실력은 크게 중요하지 않다고 생각하기에.. 오히려 잘한다 하고 면접가서 모르면 그게 더 큰일이다..</p>
<blockquote>
</blockquote>
<ol start="4">
<li>DO SOPT에서 지원자님이 만들어 보고 싶은 프로덕트가 무엇인지 만들고 싶은 이유와 함께 설명해 주세요. 또한 해당 프로덕트를 만드는 과정에서 팀원으로서 무엇을 기여할 수 있을지 구체적으로 작성해 주세요. (프로덕트의 주제에 제한은 없습니다. 자유롭게 작성해 주세요.) (800자)</li>
</ol>
<p>기획적인 측면에서 이 질문을 물어봤나 고민했다. 물론 아이디어를 작성해서 낸 사람도 많겠지만 난 어떤 팀을 만들어가고 싶은지에 대해서 작성했다.</p>
<blockquote>
</blockquote>
<ol start="5">
<li>서류 문항 답변을 증명할 수 있는 프로젝트 결과물이나 블로그, Github, Notion 등 기타 첨부할 자료가 있다면 링크로 첨부해 주세요. 최대 2개까지 부탁드립니다. (200자)</li>
</ol>
<p>Notion과 Github 링크를 냈다. 꼭 필수가 아니여서 내지 않아도 되지만 최대한 증명할 수 있는 소재로 지원서를 작성해서 내길 바란다.</p>
<br>

<p>개인적으로 솝트는 서류 합격 하는 것이 어렵다고 생각한다.. (대체적으로 많은 경쟁률들이 다 서류에서 떨어지기 때문에?) 그래서 <strong>서류에서 나를 잘 표현하는 것</strong>이 가장 중요하다!</p>
<h3 id="나름-나만의-잘-쓰는-꿀팁-방법이-있다면">나름 나만의 잘 쓰는 꿀팁? 방법?이 있다면</h3>
<p>우선 <strong>모든 문항에 다 다른 경험을 쓰려고 노력했다.</strong> 물론 그만큼의 경험이 없어서 겹치는 내용을 써야한다면 어쩔 수 없다. 하지만 오직 8~10가지 문항으로 나를 평가한다면 최대한 다양한 모습의 나를 보여주는게 좋지 않을까 싶다.
다음은 <strong>최대한 주어지는 세가지 가치에 잘 맞춰서 썼다.</strong> 3가지 가치 말고는 주어진 힌트가 없기에,, 주어진 핵심 가치에 내가 알맞는 사람이라는 것을 보여주려 했다.
그리고 무엇보다도 <strong>이 동아리가 뭐하는 동아리인지 정말 잘 아는 것이 중요하다</strong>고 생각한다. 작은 행사부터 뭘 배우고, 뭘 하는지를 알아야 애정도를 잘 나타낼 수 있다고 생각이 든다. 어차피 동아리는 실력 좋은 사람을 뽑는게 아니라 동아리에 애정 가득한 사람들과 협업하는 것을 배우는 것에 더 가깝기에..?(물론 실력 좋은사람도 되게 많음) 이  <strong>동아리를 잘 알고, 그런 부분이 나에게 왜 필요한지, 왜 나를 뽑아야만 하는지</strong>를 어필하면 되는 것 같다.</p>
<p>이전에 동아리 운영진을 하다보니 동아리에서 어떤 사람을 뽑고 싶을까? 내가 저 사람이라면 어때야 매력이 있을까? 라는 부분을 가장 많이 생각하게 된다. 물론 이걸 안다고 다 뽑히는 것은 아니지만..</p>
<p>이 부분은 지극히 나의 주관적인 생각이긴하지만.. 내가 뽑힌 걸 보면 이런 사람을 싫어하진 않는 것 같다?!</p>
<p>며칠 뒤, 서류 결과가 나오고 합격해서 기뻤다?! 사실 지원서는 내 멋대로 작성했는데 이때부터는 구글링해서 면접 후기 블로그를 몇 개 찾아봤다.</p>
<br>
<br>


<h2 id="면접-후기">면접 후기</h2>
<p>사실 면접 질문은 예측하기엔 너무나 많은 리소스..가 소모된다 생각해서 그냥 내 의견을 침착하게 말하자 라고만 다짐하는 편이다. (<del>그냥 면접 준비 안했다는 소리를 잘 포장해봤다.</del>)</p>
<p>그래도 어느 느낌인지 궁금하다면 해당 파트의 면접 후기를 찾아보길 바란다. (많이 찾아 볼수록 준비 하는 것에는 좋지만 난 그렇지 못했다..)</p>
<p>질문들을 찾아보는 것도 좋지만 내가 작성한 지원서에 대해서 잘 이해하고 있어야 한다. 질문은 공통 질문도 있지만, 지원서 기반 질문도 많이 하기 때문에 뻔한 공통 질문보단 지원서에 있는 내 이야기를 잘 하는 것이 더 중요하다 생각한다?!</p>
<p>마지막으로 인스타랑 공식 홈페이지를 보면서 어떤 행사들이 있고, 뭘 하는 동아리인지 최대한 많이 알아가려했다. 이런 부분을 어필해서 이 동아리에 이만큼 열정이 있다는 것을 보여주면 좋을 것 이다!</p>
<h3 id="아이스-브레이킹">아이스 브레이킹</h3>
<p>면접 전에 긴장을 풀어주기 위한 아브가 준비되어있다. 각 파트별로 한명씩이였나? (기획인가 디자인은 두명인 것 같기도 하다.) 그렇게 7~8명이랑 OB랑 정말 다양한 주제로 말을 한다. 면접과는 정말 아무 관련 없으니, 긴장 풀면서 이야기 하는게 다음 면접을 위해 좋을 것이다.
이때까지 긴장이 안돼서 평소처럼 이런 저런 얘기를 했다..</p>
<h3 id="회장단-면접">회장단 면접</h3>
<p>회장, 부회장님이 있는 방과 운영팀장, 미디어팀장, 총무님이 있는 방이 나눠져 있었다. 파트별로 정해진 곳에 들어가면 되는데 난 운영팀장, 미디어팀장, 총무님한테 면접을 봤다. 3:3이나 3:4 면접이기 때문에 생각보다 긴장된다.</p>
<p>공통 질문으로는 핵심가치와 관련해서 자기소개 하는 것과 협업 관련 질문이 나왔다. </p>
<p>갑자기 긴장된 탓에 기억 나는게 별로 없지만 피로그래밍 회장한 내용에 대한 질문을 주셨는데 뭐였는지는 기억이 안난다..^^ 총 6-7개 질문을 물어본 것 같다.</p>
<p>공통 질문은 첫 번째만 아니면 다른 분 대답할 때 생각하면 된다. 내가 아무 말이나 하고 나서, 다른 분이 대답할 때 이대로 하면 떨어지겠다 라는 생각이 문득 들었다. 그래서 그때부터 나름 정신 차리고 대답했던 것 같다.</p>
<p>공통 질문은 <strong>핵심 가치 관련 질문이나, 협업 내용</strong>이 대체적으로 많이 나오는 것 같다. 다양한 후기 보면서 준비하시길..</p>
<h3 id="파트장-면접">파트장 면접</h3>
<p>자리를 이동해 1:1로 파트장과 면접을 본다.
간단한 자기소개를 물어보며, 회장단 면접 잘봤냐고 물어봐주셨는데 나는 &#39;엉엉엉 아니요.. 망한 거 같아요 그래서 파트장 면접 잘봐야 합니다.&#39; 를 시전했다.</p>
<p>다대다 면접은 정말 처음이여서 떨렸는데 1:1은 왜인지 모르게 마음이 편해졌다. 그냥 있는 그대로의 나를 보여주면 될 거 같은 느낌이었다?!</p>
<p><strong>솝트에서 하고 싶은 것, 솝트에서 얻어 가고 싶은 것, 협업 관련 질문들, 커리큘럼에 대해서 기대하는 부분</strong> 등 다양한 질문들이 나왔던 것 같다.
추가로 <strong>지원서 기반 질문</strong>도 꽤 있었다. Django MTV 패턴에 대해서도 물어봤고, Java 생성자, 객체지향 등 지금 생각해보면 <strong>내 수준에 맞는 기본적인 기술질문</strong>을 했던 것 같다. + 마지막으로 하고 싶은 말까지..</p>
<p>꽤 많은 질문을 한 것 같았는데, 몇분 일찍 끝났었다. 무슨 자신감인지는 모르겠지만 나 안뽑으면 손해다 라는 마인드로 합격을 기다리고 있었다^_^</p>
<p>붙고 나니 최대 경쟁률은 14:1이었고, 보통 최대 경쟁률은 <strong>&#39;서버&#39;</strong>라고 한다?! 
서버가 최대 경쟁률인걸 알고나니 내가 왜 붙었는지 더 의문이 들었지만.. 기분이 매우 좋았다.
<br>
솝트 지원서 작성하면서 <del>대기업도 이렇게는 안보겠다..</del> 라는 생각이 정말 많이 들었다..
하지만 기대했던 것 만큼 좋은 동아리고 그만큼의 가치가 있다고 생각한다. 다시 돌아가서 지원할거냐 물어본다면 당연히 <del>(물론 서류 작성하는게 정말 너무 힘들다)</del> 할 것 이다!!!!</p>
<p>멋있는 사람들도 너무 많고, 세미나부터 해커톤, 앱잼까지 대학생이 즐기면서 성장할 수 있는 최적의 동아리가 아닐까 싶다^_^</p>
<p><del>그래서 나 OB한다. 그렇다.. OB기념 작성 글이다.</del> 34기 NOW SOPT도 화이팅</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SQLD] 전공자 벼락치기 합격 후기 (feat. 5시간의 기적)]]></title>
            <link>https://velog.io/@garden-y/SQLD-%EC%A0%84%EA%B3%B5%EC%9E%90-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0-feat.-5%EC%8B%9C%EA%B0%84%EC%9D%98-%EA%B8%B0%EC%A0%81</link>
            <guid>https://velog.io/@garden-y/SQLD-%EC%A0%84%EA%B3%B5%EC%9E%90-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0-feat.-5%EC%8B%9C%EA%B0%84%EC%9D%98-%EA%B8%B0%EC%A0%81</guid>
            <pubDate>Mon, 11 Dec 2023 15:26:20 GMT</pubDate>
            <description><![CDATA[<p>따끈따끈한 51회 SQLD 합격 후기입니다. 물론 아직 예정이긴 하지만 ..^^
<br></p>
<p>일단 5시간에 혹해서 들어오셨을 그대들에게..
물론 저도 어쩌다 보니 붙었지만 안전하게 붙으시려면 아무리 전공자여도 하루 정도는 시간 투자하시길 바랍니다.
(저는 학교에서 DB강의를 수강했지만 그렇게 잘하진 않는 전공생?입니다)</p>
<p>시험을 봐보니 어느 부분이 중요한 거 같다 라는 생각은 조금 든거 같아 저의 공부 방법과 나름의 팁..?을 작성해볼게요.
<br></p>
<p>우선 짧은 시간이다보니 저는 요약해주는 강의에 의존했습니다. 
sqld 문제 유형조차 시험 며칠 전에 알았기 때문에 무엇보다 <strong>주입식</strong> 개념과 문제풀이를 해야겠다 생각했습니다.</p>
<p>그래서 제가 추천해드릴 강의들은 다 압축(요약)형 강의이니 참고하세요.
<br><br></p>
<h3 id="개념-강의">개념 강의</h3>
<blockquote>
<p>[SQLD 시험대비!!] 마무리 쪽집게 특강 - 44개의 핵심포인트!
<a href="https://www.youtube.com/watch?v=BQxAxMnHByU">https://www.youtube.com/watch?v=BQxAxMnHByU</a></p>
</blockquote>
<p>이건 sqld 합격한 지인에게 추천받은 강의입니다. (노랭이랑 같이 추천받은 sqld 정석 버전인거 같아요) 1시간 40분 강의이며, 나오는 유형의 대표적 개념을 알려줍니다. 
저는 처음에 노션에 해당 개념을 정리하려 했는데 하다 보니 시간 부족할 것 같아 멈췄습니다. 하지만 유튜브 더보기 통해서 카페 들어가면 개념 교안(ppt) 다운받을 수 있으니까 정말 노션 정리할 시간도 없다 하시는 분들은 다운받아서 개념 한 번 익히세요.</p>
<p>교안 봤을 때 이해나 암기가 되신다면 굳이 1시간 40분 강의를 다 들을 필요는 없는 거 같아요.</p>
<p>저는 이 영상 보면서 대체적으로 개념은 알고 있었기 때문에 가볍고 빠르게 한 번 봤어요. (2배속...)</p>
<p><br><br>
하지만 이 영상만 보고 문제를 보면 단 한 문제도 풀지 못할 수도 있어요.
sqld에는 개념문제가 잘 안나옵니다..</p>
<p>그래서 저는 문제 풀어주는 영상을 봤어요. 이것도 추천받았어요. 
유튜브 알고리즘한테...</p>
<p>맞아요 그냥 위 영상 보니까 이 영상이 떴습니다. 노랭이는 양이 너무 많아서 손을 대는 것 자체가 겁났습니다.<del>(약간 수학 문제집 1단원만 푸는 느낌 일까봐)</del> 그래서 차라리 전체 문제 유형을 빠른 시간 내에 보는 방법이 좋겠다 생각이 들어 봤습니다.
<br></p>
<h3 id="문제-강의">문제 강의</h3>
<blockquote>
<p>[SQLD 시험대비!!] 40개의 문제로 정리해보는 SQLD 쪽집게 특강(1번 ~ 20번)
<a href="https://www.youtube.com/watch?v=I7rdwDMZexo">https://www.youtube.com/watch?v=I7rdwDMZexo</a></p>
</blockquote>
<blockquote>
<p>[SQLD 시험대비!!] 40개의 문제로 정리해보는 SQLD 쪽집게 특강(21번 ~ 40번)
<a href="https://www.youtube.com/watch?v=349KjuRO8II&amp;list=LL&amp;index=14">https://www.youtube.com/watch?v=349KjuRO8II&amp;list=LL&amp;index=14</a></p>
</blockquote>
<br>
총 40문제를 유형 별로 나눠서 풀어줍니다. 저는 개념만 1시간 40분 듣는 것 보다 차라리 44분 + 53분으로 문제 유형 다 보는게 더 유익했어요.

<p>이 영상은 생략할 것 없이 꼭 다 보셨으면 좋겠어요.</p>
<p>물론 제가 교수나 강의자를 가리지 않는 성격이여서 아무나 들어도 다 똑같기에 그냥 눈에 보이는 거 들었는데 사람마다 선호하는 강의 방식이 다르기 때문에 혹시 저 분이 안맞으시면 sqld 쳐서 적당한 시간에 비슷한 주제인 강의를 들으시면 될 것 같아요.</p>
<br>
영상들 다 보고나니 시험까지 시간이 꽤 남았길래 남은 시간동안 노랭이 문제 하나씩 봤어요.
한 단원을 쭈욱 다 보는 게 아니라 듬성듬성.. 맛보기 처럼

<p>(이렇게 말하니까 되게 많은 시간이 남은 것 같지만 이미 시험 보는 당일 새벽이었다는 점^_^)</p>
<p>그래도 문제 풀어주는 영상 보고 나니까 비슷한 문제들이 많아서 오~ 풀 수 있겠다~ 하면서 본 것 같아요.</p>
<p><br><br></p>
<h3 id="sqld-후기와-tip">SQLD 후기와 Tip?</h3>
<p>사실 점수가 엄청 좋다가 아닌 턱걸이와 비슷해서 운 나쁘면 떨어졌을 거예요. 그래도 급하다면 빠르게 <strong>개념 -&gt; 문풀</strong>로 가셔야 합니다.</p>
<p>전공자 분들은 정말 정말 정말 급하면 문풀로 가세요.. 개념만 미친 듯이 공부하고도 한 문제도 못풀 수 있어요.</p>
<p>그리고 정말 유명한 노랭이는.. 시험과 적당히 비슷하다? 정도가 맞는 것 같아요. 후기 보면 노랭이 다 풀고도 합격 못한 후기도 많이 봐서 정말 뭐가 맞는지는 회바회 인거 같아요.</p>
<p>그래서 저는 종합적으로 
<strong>개념강의는 정말 스윽~ + 문제유형요약강의 + 노랭이 40문제 정도</strong> (될까요..?)
가 최종 공부량 입니다. (기출도 안보고 간 건 시험 끝나고 알았어요)</p>
<br>
그래서 개념 -> 문풀 강의 듣고 문제를 풀 때 시간이 남으시면 기출을 풀어보세요. (사실 노랭이 띄엄띄엄 보기랑 똑같을거 같은데 이건 선호 방식대로 하면 될 것 같아요.) 

<p>아 그리고 1과목,20 2과목 80점 만점인데 저는 1과목을 더 못봤어요. 
원인은 제가 암기를 별로 안좋아하기도 하고, 쿼리문 푸는 것이 더 쉽게 느껴져서 2과목 실습 부분을 연습 때도 시험장에서도 많이 푼 것 같아요.</p>
<br>
근데 단기간엔 1과목을 더 집중적으로 파는 것도 좋은 것 같아요.(내용도 적고 응용보단 개념 느낌이라 공부만 하면 맞을 유형입니다.) 그래서 1과목 만점을 목표로 하면 2과목 반타작만 해도 합격이기 때문에 이런 전략을 쓰시는 것도 방법입니다...
<br>
<br>


<p>이상 운 좋게 5시간 정도 공부하고 SQLD 합격한 전공생입니다😊😊😊
다들 합격하세요~!❤️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[피로그래밍] 18기 서류 · 면접 최종 합격 후기]]></title>
            <link>https://velog.io/@garden-y/%ED%94%BC%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-18%EA%B8%B0-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@garden-y/%ED%94%BC%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-18%EA%B8%B0-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 10 Dec 2023 07:43:07 GMT</pubDate>
            <description><![CDATA[<p>합격하고 1년이 되었지만 다시 돌아보는 피로그래밍 합격 후기입니다. (맨날 써야지하다가 시험기간에 쓰고 있는 이런 심리.. 게다가 18기 후기 전멸이라 작성합니다.)
생각한 것 이상으로 정말 많은 것을 배울 수 있었고, 이 경험으로 다양한 기회가 생겼으며, 멋진 사람들 좋은 사람들도 너무 너무 많기 때문에(18기야 사랑해) 피로그래밍은 개발 입문자라면 정말 후회 없는 선택이지 않을까 생각합니다. (<del>시험기간에 서류와 면접을 준비하는 것이 가장 큰 단점</del>이라 할 수 있지만 정말 고민없이 지원하세요)</p>
<p>다들 합격 하시길 바라며 서류와 면접의 기억을 꺼내어 써보도록 하겠습니다!</p>
<h2 id="피로그래밍이란">피로그래밍이란?</h2>
<blockquote>
<p>피로그래밍은 두달의 방학기간 동안 일주일에 3번 이루어지는 세션을 통해 웹 개발에 대한 전반적인 지식과 경험을 쌓는 대학생들을 위한 연합 동아리입니다. 협업툴인 Git과 GitHub를 다루는 방법, 웹 개발 기초 (HTML, CSS, JavaScript)를 배우고, Python과 Django 프레임워크를 기반으로 웹 프로그래밍 학습을 진행합니다. 피로그래밍에서는 성장과 협력의 가치를 이어나갑니다.</p>
</blockquote>
<blockquote>
<p>첫 한달 동안 매 세션마다 과제가 주어지며, 이후 아이디어 발표와 팀 빌딩을 통해 팀원들과 협업하여 웹 서비스를 제작하고 프로젝트 발표를 끝으로 활동을 마무리하게 됩니다. 각종 행사 및 커뮤니티가 활성화되어있어 개발자 외 창업가, 기획자로 활동하는 피로그래머들과 소통을 하실 수 있습니다. 다양한 교류를 통해 진로에 대한 고민과 현업에 대한 인사이트를 얻어가시기 바랍니다 :)</p>
</blockquote>
<p>출처는 <a href="https://pirogramming.com/about/">https://pirogramming.com/about/</a></p>
<p>사실 두달동안 화,목,토에 6시간씩 세션을 듣고, 세션마다 과제를 하는 것은 쉽지 않은 일이지만 다 하고나면 많은 성장을 하기 때문에 정말 방학동안 많이 배우고 싶다! 하시면 추천드립니다. 
세션과 과제 꼭 열심히 하세요!!!! 그래야 배우는 것이 정말 많습니다. (어차피 세션이랑 과제 안하시면 보증금도 깎여요)</p>
<h2 id="서류-문항">서류 문항</h2>
<blockquote>
</blockquote>
<ol>
<li>자기소개 및 지원동기와 목표 500~650</li>
<li>프로그래밍 경험 및 수준, 학습계획 300~450</li>
<li>만들고 싶은 웹 서비스 250~350</li>
<li>협업 또는 팀플 경험 300~450</li>
<li>어려운 일을 겪고 극복한 경험 혹은 성취한 경험 300 ~ 450</li>
<li>방학계획(자유형식)</li>
</ol>
<p>이 질문을 통해서 나의 무엇을 보여줄 것인가 생각해서 쓰는 것도 지원서를 잘 쓰게 만들어주는 방법인거 같아요 (이런 생각 안하고 작성하면 지원서 내용이 산으로 가기도 합니다..)</p>
<p>그리고 지원서에서 사기치지 마세요.. 면접 때 보면 거짓말인거 다 알아요...</p>
<p>지원서 쓰는 것은 저도 여전히 어렵기 때문에.. 각자 본인만의 스토리를 녹여서 하고자 하는 말을 잘 전달하시면 될 것 같습니다.</p>
<p>아 그리고 글자수는 꼭 맞춰서 쓰세요. 괜히 저 기준이 있는게 아닙니다.</p>
<p>많이 쓰면 좋은거 아냐? 하시는데 누군가는 적은 글자에 자신을 더 잘 표현하기 때문에 굳이 더 많은 것도 사치..라고 생각합니다. (기준보다 적게 쓰는 것도.. 추천은 안함)</p>
<h2 id="면접-질문">면접 질문</h2>
<p>면접은 면접관 3분에 저 혼자 봤고 면접 시간은 30분으로 되어있습니다.
면접 10분전쯤 가서 대기방에서 기다렸던거 같은데 음료와 과자를 나눠주셨어요.
면접관분들이 다들 웃어주시며 분위기를 편안하게 만들어 주셔서 정말 따뜻하게 면접을 봤습니다.</p>
<p><strong>&lt;공통 질문&gt;</strong></p>
<blockquote>
</blockquote>
<ol>
<li>간단한 자기소개</li>
<li>다양한 종류의 개발 중, 웹 개발을 하고 싶은 이유</li>
<li>비전공자/전공자 분들과 함께 프로젝트를 수행하게 될 경우 실력의 차이 등을 극복하기 위해 본인이 어떤 태도로 프로젝트에 임할 것인지</li>
<li>(동아리 활동 시) 본인만의 장점이 무엇인지</li>
<li>리더와 팔로워 중 해당되는 역할과 해당 역할로서 가장 필요한 자질은 무엇이라고 생각하는지</li>
<li>팀플에서 중요하다 생각하는 것, 싫은 사람의 유형</li>
<li>오류나 문제가 생기면 어떻게 하는지</li>
<li>협업 시 갈등상황에서 대처 방법</li>
<li>프로젝트 시 퀄리티를 낮출 것인지, 기간 내에 끝내지 않을 것인지</li>
<li>피로그래밍 보증금 제도, 보증금과 활동비가 각각 얼마인지</li>
<li>1-2월 중 피로그래밍 외에 하고 있는 다른 활동들이 있는지</li>
<li>마지막으로 하고 싶은 말</li>
</ol>
<p><strong>&lt;기술 질문&gt;</strong></p>
<blockquote>
</blockquote>
<ol>
<li>자료구조에 대해서 아는 것 아무거나</li>
<li>객체지향과 절차지향의 차이점</li>
</ol>
<p>이때 저는 전공 1년차인 아무것도 모르는 뽀짝이여서 지원서 기반으로 기술 질문을 주신거 같습니다. (지원서에 자료구조를 배웠다 작성함)</p>
<p>사실 너무 오래돼서 정확히 질문이 다 기억 나진 않지만 공통질문들은 예상 범위를 크게 넘는 질문들은 없었어요. 아마 이 글을 포함한 피로 면접 관련 블로그들 보시면서 질문들을 모아 준비하다 보면 크게 어려움은 없을 것 같습니다.</p>
<p>기술 질문은 지원서 기반으로 물어보거나 파이썬이나 html,css,js (피로그래밍에서 사용하는 기술들 위주)를 하셨다면 관련 문법 질문들을 하시는 것 같아요. </p>
<p>아무것도 모르는 비전공자라해도 적어도 여기서 뭘 배우고 어떻게 하는지를 알아가는 노력정도는 필요하다는 생각이 듭니다..(노력이 다 동아리에 대한 애정이니까요..) </p>
<p>-&gt; 인스타나 공식 홈페이지 자세히 보고 동아리 정보 알고 가세요 (이건 면접 보기 전에 꼭 다 하세요)</p>
<p>공통질문이나 기술질문을 제외하고는 보통 지원서에서 궁금한 부분을 물어보기도 하는데 이에 대한 대답도 어느정도 준비를 해두면 좋을 것 같습니다.</p>
<p>면접은 보통 15-20분 정도 보는 것 같은데 저는 면접이 되게 빨리 끝난 편이라 걱정을 했지만 붙었습니다🤗🤗</p>
<h2 id="합격-tip과-개인적인-생각">합격 Tip과 개인적인 생각?</h2>
<p>사실 피로그래밍은 비전공자를 위한 동아리에서 시작됐기 때문에 실력이 엄청 중요하진 않은거 같습니다. (실력을 본다면 전 불합격일지도? 하지만 아예 중요하지 않다고도 안했어요.. 들어가보면 잘하는 사람들이 참 많거든요)</p>
<p>동아리 특성상 짧은 기간내에 많은 것을 배우고 과제도 꽤 있는 편이기 때문에 이 기간 내에 동아리에만 몰입할 수 있는 사람이 더 중요해서 열정을 많이 보는 것 같아요.</p>
<p>물론 매번 운영진이 달라지기 때문에 그 운영진들만의 기준이 항상 같지만은 않습니다. 하지만 기본적으로 서류나 면접에서 동아리에 대한 애정과 본인의 성실함을 잘 드러내고 나 자신을 잘 표현할 수 있다면 합격할 수 있다는 생각이 듭니다!</p>
<p>다들 합격하시고 알차고 행복한 방학 보내시길 바랍니다^.^</p>
<p>쓰다보니 개인적인 생각이 많이 주입되기도 했지만<del>(19기 리크루팅 당시 운영진의 생각이랄까요..)</del> 감안해서 보고 참고하시면 좋을 거 같습니다.</p>
<p><del>20기 리크루팅 끝나는 날 올리니 21기 부터 참고하세요ㅎㅎ</del></p>
<h2 id="피로그래밍-후기">피로그래밍 후기</h2>
<p>웹 개발 입문으로 html, css, js에 django까지 배워서 프로젝트를 혼자 완성할 수 있을 모든 조건을 다 만들어 주는 동아리입니다. 이 외에도 git, aws배포, 디자인, db 등 다양한 세션이 있어서 정말 개발에 대해 스펙트럼 넓게 배우기 좋아요.
다들 열정 넘치는 멋진 사람들이 합격하기 때문에 배울 점도 많고 좋은 사람들을 많이 알아갈 수 있으며, 무엇보다 세션을 다 선배 기수 분들이 해주시는데 현업자 분들도 많이 오시기 때문에 정말 많은 도움이 됐습니다.</p>
<p>열심히 한 만큼 가져갈 수 있는 동아리입니다! 다들 피로하세요☺️</p>
<p><img src="https://velog.velcdn.com/images/garden-y/post/ef800c7a-467a-46b3-b9e4-cb2c42e904f7/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>