<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Today's Minding</title>
    <link>https://minding-deep-learning.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 9 May 2026 05:52:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Minding</managingEditor>
    <item>
      <title>[QA] Positive / Negative / Edge Case 개념 정리</title>
      <link>https://minding-deep-learning.tistory.com/294</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;QA 엔지니어 면접을 진행하는 도중 Positive Case, Negative Case, Edge Case에 대한 질문을 받았는데, 개념이 명확히 잡혀있지 않아 답변을 제대로 하지 못했다. TC를 작성하는 등 전체적인 테스트 설계에 핵심이 되는 개념인 만큼, 제대로 정리할 필요가 있다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Positive Case&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;정상적인 입력/상황에서 기능이 기대한 대로 동작하는 지에 대해 검증하는 테스트 케이스&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 시스템이 정상적인 사용 흐름에서 정확히 동작하는지 확인&lt;/li&gt;
&lt;li&gt;입력값: 유효하며 일반적인 값 사용&lt;/li&gt;
&lt;li&gt;ex) 로그인 화면에서 올바른 ID, PW 입력 후 로그인 성공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Positive Case는 쉽게 말해서 &quot;정상적으로 잘 사용했을 때, 잘 동작하는가?&quot;를 보는 테스트 케이스다. 대부분의 기능 요구사항에 대한 케이스가 여기에 속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Negative Case&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;잘못된 입력 또는 예외 상황에서 시스템이 오류를 잘 처리하는지에 대해 검증하는 테스트 케이스&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 잘못된 사용, 입력, 시스템 상태 등에서 에러 처리가 적절한지 확인&lt;/li&gt;
&lt;li&gt;입력값: 잘못된 값, 비정상적인 값 사용&lt;/li&gt;
&lt;li&gt;ex) 회원가입 화면에서 ID를 입력하지 않았을 때 &quot;ID를 입력하세요&quot; 등의 에러 메시지 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Negative Case는 &quot;비정상적으로 사용했을 때, 시스템이 망가지지 않는지(에러 메시지가 잘 표시되는지)를 보는 테스트 케이스다. 보통 사용자의 실수나 예외 흐름 등을 고려해서 설계한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Edge Case&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;입력 값이나 조건이 한계점에 가까울 때, 시스템이 올바르게 반응하는지 확인하는 테스트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 최소값/최대값, 경계값, 시스템 한계 상황에서의 동작 확인&lt;/li&gt;
&lt;li&gt;입력값: 제한된 범위의 가장자리 값, 입력 크기 한계 등&lt;/li&gt;
&lt;li&gt;ex) Zoom 옵션 필드(0~18 제한)에서 0, 18, -1, 19등 값을 입력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Edge Case는 &quot;경계에 닿은 값을 입력했을 때도 잘 동작하는가?&quot;를 보는 테스트다. 정말 예외적인 상황이기 때문에 비교적 우선순위는 떨어지지만, 소홀히 할 경우 예상치 못한 버그가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 그 동안 Negative Case와 Edge Case에 대한 개념을 헷갈리고 있었다. '예외사항'이라는 큰 틀에 두 가지 개념을 혼동하고 있었다. 물론 Negative Case 또한 '예외적인' 상황의 케이스이긴 하지만, 목적과 범위가 다르다. Negative Case는 일부러 말도 안되는 상황을 가정하는 테스트이고, Edge Case는 정상 범위 내 가장자리(끝)를 찔러보는 테스트라고 보면될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Negative와 Edge를 한 번에 사용할 수도 있다. 예를 들어 API 응답에 대한 테스트를 진행한다고 했을 때, Timeout 임계점이 5초라고 가정해보자. 일부러 응답을 임계값에 가깝게 지연시켜 아래와 같은 시나리오를 작성할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;시나리오&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;기대 결과&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;API 응답 시간이 4.9초일 때 vs 5.1초일 때 응답 차이 확인&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4.9초: 정상 처리&lt;br /&gt;5.1초: Timeout 처리 후 5초 지연 발생&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;경계 기준 전후의 반응 차이를 보기 위함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직도 갈 길이 멀다. 공부할 것들이 산더미다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>edge case</category>
      <category>negative case</category>
      <category>positive case</category>
      <category>QA</category>
      <category>software test</category>
      <category>SQA</category>
      <category>TC</category>
      <category>Test</category>
      <category>test case</category>
      <category>테스트</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/294</guid>
      <comments>https://minding-deep-learning.tistory.com/294#entry294comment</comments>
      <pubDate>Mon, 24 Mar 2025 12:56:06 +0900</pubDate>
    </item>
    <item>
      <title>SQLAlchemy QueuePool 에러 해결하기 (sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30.00)</title>
      <link>https://minding-deep-learning.tistory.com/293</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;외주 프로젝트 중 백엔드 서버로부터 SQLAlchemy TimeoutError가 지속적으로 발견되는 것을 확인했다. 에러 코드를 읽어보면, QueuePool의 limit이 5(overflow 허용치는 10)인데, 한계값에 도달했으며, timeout 30초에 도달하여 연결이 끊겼다는 설명이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 원인은 크게 2가지일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 비동기 처리 코드 처리 중 DB 트랜잭션이 열린 채로 방지되어 커넥션이 해제되지 않는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SQLAlchemy의 커넥션 풀 크기가 너무 작게 설정되어 있는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 2가지 원인에 대한 해결 방법을 찾아보니, 아래와 같은 결과가 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 비동기 처리 코드 처리 중 DB 트랜잭션이 열린 채로 방지되어 커넥션이 해제되지 않는 경우&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1742452954807&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 이전 코드
async def get_db():
    db = database.AsyncSessionLocal()

# DB 세션 의존성 수정
async def get_db():
    db = database.AsyncSessionLocal()
    try:
        yield db
    finally:
        await db.close()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 전의 코드는 db를 호출하기만 한 뒤, 아무 동작도 하지 않아 DB 트랜잭션이 열린 채로 유지되었다. 결국 API 호출이 쌓이다 보면 위와 같은 에러가 발생할 수밖에 없다.&lt;br /&gt;&lt;br /&gt;코드에 try-finally를 추가해 DB 세션을 전달(try)한 뒤, 동작이 종료되면 db.close()가 되도록 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. SQLAlchemy의 커넥션 풀 크기가 너무 작게 설정되어 있는 경우&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1742451877415&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

DATABASE_URL = &quot;{DB_URL}&quot;

# 비동기 엔진 생성
engine = create_async_engine(
    DATABASE_URL,
    echo=True,
    pool_size=20,
    max_overflow=30,
    pool_timeout=60,
    pool_recycle=1800,
)

# 비동기 세션 설정
AsyncSessionLocal = sessionmaker(
    bind=engine,
    class_=AsyncSession,
    expire_on_commit=False,
)

Base = declarative_base()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 DB 엔진을 연결하는 비동기 세션을 만드는 코드다. 위 비동기 엔진(engine)을 선언할 때, pool_size, max_overflow, pool_timeout, pool_recycle 등의 파라미터를 활용해 세부 항목을 설정할 수 있다. 각 파라미터의 의미는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pool_size: 기본적으로 유지할 커넥션 개수(기본값: 5)&lt;/li&gt;
&lt;li&gt;max_overflow: 추가로 허용할 커넥션 개수(기본값: 10)&lt;/li&gt;
&lt;li&gt;pool_timeout: 커넥션을 기다릴 최대 시간(초 / 기본값: 30초)&lt;/li&gt;
&lt;li&gt;pool_recycle: 커넥션을 재사용 가능하도록 설정할 시간(초)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Minding's Programming/에러 코드</category>
      <category>async</category>
      <category>db</category>
      <category>db 트랜잭션</category>
      <category>queuepool</category>
      <category>SQLAlchemy</category>
      <category>sqlalchemy error</category>
      <category>timeouterror</category>
      <category>에러코드</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/293</guid>
      <comments>https://minding-deep-learning.tistory.com/293#entry293comment</comments>
      <pubDate>Thu, 20 Mar 2025 15:49:35 +0900</pubDate>
    </item>
    <item>
      <title>[Testing] SW 테스트 기본 개념들 정리</title>
      <link>https://minding-deep-learning.tistory.com/292</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pn3hk/btsMBjkWQLO/NXKCpCyC8R0fckr9zkF5m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pn3hk/btsMBjkWQLO/NXKCpCyC8R0fckr9zkF5m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pn3hk/btsMBjkWQLO/NXKCpCyC8R0fckr9zkF5m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpn3hk%2FbtsMBjkWQLO%2FNXKCpCyC8R0fckr9zkF5m0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;696&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 &lt;a href=&quot;https://sten.or.kr/bbs/board.php?bo_table=infodata&amp;amp;wr_id=2633&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;STEN에서 공유한 '소프트웨어 테스트 실무 가이드 Part 1' 도서&lt;/a&gt;를 읽고 SW 테스트의 개념들을 정리한 글로, 책에서 나온 내용과 개인적으로 웹 검색 등을 통해 학습한 내용을 함께 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 테스트 설계 기법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;명세 기반 테스트 설계 (Specification-based Testing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능/요구사항 명세서 또는 기획서 등을 기준으로 테스트 케이스를 설계하는 방법. 시스템의 기능과 동작을 테스트하는데 중점을 두며, &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%B8%94%EB%9E%99%EB%B0%95%EC%8A%A4_%EA%B2%80%EC%82%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;블랙박스 테스트 기법&lt;/a&gt;이 주로 사용된다. 이 설계 기법의 테스트 케이스는 외부 동작과 기대 결과에 기반해 작성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;명세 기반 테스트 예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 요구사항 명세서(기획서)가 존재할 때, 명세 기반 테스트 기법으로 여러가지 방법을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1741080270720&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 요구사항 예시 (ex. 대학교 학점 계산 시스템)
- 시험 점수: 0~70 사이 정수
- 과제 점수: 0~20 사이 정수
- 출석 점수: 0~10 사이 정수
- A학점은 80점 이상, C학점은 49점 이하, 그 외에는 B학점&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;동등 분할 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 항목의 입/출력이 여러 개의 독립된 영역으로 구분되는 경우에 적용된다. 동일 영역 내에서는 어떠한 값을 선택해도 결과가 항상 같다는 원리를 이용하며, 모든 영역에서 최소 하나 이상의 값을 선택해 테스트한다. TC를 작성한다면, 아래와 같은 예시가 나올 수 있다. (A학점은 80점 이상, C학점은 50점 이하일 때)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 175px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 50px;&quot; rowspan=&quot;2&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 25px; width: 61.4493%;&quot; colspan=&quot;4&quot;&gt;입력값 또는 조건&lt;/td&gt;
&lt;td style=&quot;width: 68.5507%; text-align: center; height: 50px;&quot; rowspan=&quot;2&quot;&gt;기대 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 16.256%; text-align: center; height: 25px;&quot;&gt;시험 점수&lt;/td&gt;
&lt;td style=&quot;width: 18.7923%; text-align: center; height: 25px;&quot;&gt;과제 점수&lt;/td&gt;
&lt;td style=&quot;width: 14.9517%; text-align: center; height: 25px;&quot;&gt;출석 점수&lt;/td&gt;
&lt;td style=&quot;width: 11.4493%; text-align: center; height: 25px;&quot;&gt;총점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 25px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 16.256%; text-align: center; height: 25px;&quot;&gt;-10&lt;/td&gt;
&lt;td style=&quot;width: 18.7923%; text-align: center; height: 25px;&quot;&gt;-20&lt;/td&gt;
&lt;td style=&quot;width: 14.9517%; text-align: center; height: 25px;&quot;&gt;-30&lt;/td&gt;
&lt;td style=&quot;width: 11.4493%; text-align: center; height: 25px;&quot;&gt;-60&lt;/td&gt;
&lt;td style=&quot;width: 68.5507%; text-align: center; height: 25px;&quot;&gt;경고 메시지 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 25px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 16.256%; text-align: center; height: 25px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 18.7923%; text-align: center; height: 25px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 14.9517%; text-align: center; height: 25px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 11.4493%; text-align: center; height: 25px;&quot;&gt;45&lt;/td&gt;
&lt;td style=&quot;width: 68.5507%; text-align: center; height: 25px;&quot;&gt;C학점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 25px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 16.256%; text-align: center; height: 25px;&quot;&gt;25&lt;/td&gt;
&lt;td style=&quot;width: 18.7923%; text-align: center; height: 25px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 14.9517%; text-align: center; height: 25px;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 11.4493%; text-align: center; height: 25px;&quot;&gt;55&lt;/td&gt;
&lt;td style=&quot;width: 68.5507%; text-align: center; height: 25px;&quot;&gt;B학점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 25px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 16.256%; text-align: center; height: 25px;&quot;&gt;65&lt;/td&gt;
&lt;td style=&quot;width: 18.7923%; text-align: center; height: 25px;&quot;&gt;15&lt;/td&gt;
&lt;td style=&quot;width: 14.9517%; text-align: center; height: 25px;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 11.4493%; text-align: center; height: 25px;&quot;&gt;90&lt;/td&gt;
&lt;td style=&quot;width: 68.5507%; text-align: center; height: 25px;&quot;&gt;A학점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 25px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 16.256%; text-align: center; height: 25px;&quot;&gt;80&lt;/td&gt;
&lt;td style=&quot;width: 18.7923%; text-align: center; height: 25px;&quot;&gt;30&lt;/td&gt;
&lt;td style=&quot;width: 14.9517%; text-align: center; height: 25px;&quot;&gt;15&lt;/td&gt;
&lt;td style=&quot;width: 11.4493%; text-align: center; height: 25px;&quot;&gt;125&lt;/td&gt;
&lt;td style=&quot;width: 68.5507%; text-align: center; height: 25px;&quot;&gt;경고 메시지 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 유효 범위를 기준으로 더 적게 입력했을 때/올바르게 입력했을 때/더 많이 입력했을 때의 케이스를 동등하게 분할하여 테스트하도록 했으며, 각 출력 결과(A/B/C학점) 또한 모두 동등 분할해 테스트 케이스를 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;경계 값 분석 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 영역의 상한 또는 하한 경계값을 테스트 데이터로 선택하는 테스트 기법으로, 유효 범위에서 가장 작은 수(하한 경계값)와 가장 큰 수(상한 경계값)를 입력해 테스트 하는 기법이다. 경계값을 사용하는 이유는 유효 범위의 '시작'과 '끝'을 가리키는 수이기 때문에, 상대적으로 결함 발생 가능성이 높기 때문이다. 이 방법을 사용해 TC를 작성한다면 아래와 같은 예시가 나오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 입력 값을 기준으로 경계 값 분석 테스트 케이스를 작성한 예시다. 2Bytes 정수만을 입력받는다고 가정하면 입력 가능한 범위는 -32,768 ~ 32,767이고, 그 중 유효한 입력 범위는 위 요구사항 명세서에서 제시한 대로다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot; rowspan=&quot;2&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;width: 66.6668%; text-align: center;&quot; colspan=&quot;4&quot;&gt;입력 값(2Bytes 정수 입력 기준)&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot; rowspan=&quot;2&quot;&gt;기대 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;시험 점수&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;과제 점수&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;출석 점수&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;총점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;-32,768&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;-32,768&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;-32,768&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;-98,304&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;경고 메시지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;-3&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;경고 메시지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;C학점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;70&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;A학점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;71&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;21&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;103&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;경고 메시지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;32,767&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;32,767&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;32,767&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;98,301&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;경고 메시지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 입력값의 범위만을 고려한 위의 경우에는 'B학점'에 대한 출력 결과에 대한 TC가 없다. 따라서, 출력 결과(총점 및 기대 결과)에 대한 경계값 분석 테스트도 필요하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot; rowspan=&quot;2&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;width: 57.4917%; text-align: center;&quot; colspan=&quot;4&quot;&gt;입력 값&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot; rowspan=&quot;2&quot;&gt;기대 결과&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot; rowspan=&quot;2&quot;&gt;경계값 구분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;시험 점수&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;과제 점수&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;출석 점수&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;총점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;60&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;15&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;80&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;A학점&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;A학점 하한 경계값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;70&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;A학점&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;A학점 상한 경계값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;35&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;50&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;B학점&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;B학점 하한 경계값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;59&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;12&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;79&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;B학점&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;B학점 상한 경계값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;C학점&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;C학점 하한 경계값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;43&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;49&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;C학점&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;C학점 상한 경계값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;-30,000&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;-2,000&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;-768&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;-32,768&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;경고 메시지&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;경고 하한 경계값(-)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;-9&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;-2&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;-1&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;경고 메시지&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;경고 상한 경계값(-)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;9&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;25&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;70&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;101&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;경고 메시지&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;경고 하한 경계값(+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.33217%; text-align: center;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 16.2626%; text-align: center;&quot;&gt;767&lt;/td&gt;
&lt;td style=&quot;width: 16.495%; text-align: center;&quot;&gt;2,000&lt;/td&gt;
&lt;td style=&quot;width: 14.8671%; text-align: center;&quot;&gt;30,000&lt;/td&gt;
&lt;td style=&quot;width: 9.86705%; text-align: center;&quot;&gt;32,767&lt;/td&gt;
&lt;td style=&quot;width: 16.4951%; text-align: center;&quot;&gt;경고 메시지&lt;/td&gt;
&lt;td style=&quot;width: 20.681%; text-align: center;&quot;&gt;경고 상한 경계값(+)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 출력 결과를 고려한 테스트 케이스 작성으로 B학점에 대한 기대 결과값도 테스트할 수 있게 되었다. 경계값 분석은 일반적으로 동등 분할 기법과 함께 사용하며, 연속적인 값이 입/출력으로 주어질 때 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;조합 테스트(페어와이즈 기법)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조합 테스트는 동등 분할 조건의 모든 조합에 대해서 테스트를 진행하는 것을 의미한다. 하지만 현실적으로 모든 조합을 테스트하는 '전수 테스트'는 시간과 비용 등의 이슈로 불가능한 경우가 많다. 책에서는 이 문제를 해결할 대안으로 '페어와이즈 기법'을 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페어와이즈 기법은 각 변수의 가능한 값들이 최소한 한 번 이상 서로 조합되는 방식으로 TC를 도출하는 기법이다. 모든 조합을 테스트하지 않고도 효과적으로 커버리지를 확보할 수 있다. 예를 들어, OTT 서비스에서 아래와 같은 테스트 조건이 있다고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1741083068288&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# OTT 서비스 예시 - 각 변수 조건
- 네트워크 환경: Wi-Fi, 5G, LTE
- 기기 유형: 스마트TV, 모바일, 태블릿
- 해상도: 720p, 1080p, 4K
- 자막: 한국어, 영어, 없음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 주어진 조건에 따라 모든 조합을 테스트 하려면, 3(네트워크)*3(기기 유형)*3(해상도)*3(자막) = 81개의 TC가 필요하다. 하지만 페어와이즈 기법을 통해 모든 변수 조합이 최소 한 번 이상 포함되는 TC를 9~10개 정도로 생성해 테스트할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rPcZw/btsMB2XfUFR/MhmXqk4XELbIQCx9D5bUNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rPcZw/btsMB2XfUFR/MhmXqk4XELbIQCx9D5bUNk/img.png&quot; data-alt=&quot;페어와이즈 기법을 통해 생성한 TC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rPcZw/btsMB2XfUFR/MhmXqk4XELbIQCx9D5bUNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrPcZw%2FbtsMB2XfUFR%2FMhmXqk4XELbIQCx9D5bUNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;340&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;페어와이즈 기법을 통해 생성한 TC&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 페어와이즈 기법을 사용하면 각 변수 값이 최소한 한 번 이상 TC에 포함시킬 수 있다. 여러 조합을 바꿔가며 테스트하는 것이기 때문에, Selenium 또는 Playwright와 같은 자동화 프레임워크와 결합해서 더 효율적으로 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페어와이즈 조합은 자동 생성 도구를 통해 손 쉽게 생성할 수 있는데, 대표적으로 아래와 같은 종류들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AllPairs (무료 / CLI 기반)&lt;/li&gt;
&lt;li&gt;PICT (MS에서 제공)&lt;/li&gt;
&lt;li&gt;Hexawise (GUI 지원)&lt;/li&gt;
&lt;li&gt;ChatGPT와 같은 AI 도구 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;전이 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전이 테스트는 SW 시스템이 특정 상태에서 다른 상태로 전이(transition)할 때의 동작을 검증하는 테스트 기법이다. 상태(State)와 상태 간 전이에 초점을 맞추며, 주로 상태 머신(State Machine) 모델을 기반으로 테스트를 설계한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 웹/앱 서비스의 복잡성이 늘어남으로써, 전이 테스트의 중요성이 높아지고 있다. 단순한 입/출력 구조가 아닌 여러 상태(ex. 로그인, 결제 진행 등)를 가지는 경우가 많기 때문에, 각 상태와 상태 변화에 따라 작동하는지 검증할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 동작을 검증하는 테스트를 진행한다고 했을 때, 전이 테스트는 아래와 같은 예시로 TC를 작성할 수 있다. 먼저, 상태가 전이되는 프로세스를 담은 다이어 그램을 그려본다면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1741084194739&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   [로그아웃] &amp;rarr; (로그인 시도) &amp;rarr; [인증 중] &amp;rarr; (성공) &amp;rarr; [로그인 완료]
                                      ↳ (실패) &amp;rarr; [로그아웃]&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;현재 상태&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;입력 (이벤트)&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;기대 결과 (전이 후 상태)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;로그아웃(미로그인)&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;로그인 시도&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;인증 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;인증 중&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;올바른 정보 입력&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;로그인 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;인증 중&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;잘못된 정보 입력&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;로그아웃&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;로그인 완료&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;로그아웃 버튼 클릭&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;로그아웃&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 복잡한 과정을 다뤄보자. 아래와 같이 ATM에서 현금을 인출하는 과정은 더 많은 상태를 거친다.&lt;/p&gt;
&lt;pre id=&quot;code_1741084368743&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  [대기 상태] &amp;rarr; (카드 삽입) &amp;rarr; [PIN 입력] &amp;rarr; (올바른 PIN 입력) &amp;rarr; [메뉴 선택]  
                                      ↳ (잘못된 PIN 입력) &amp;rarr; [대기 상태]
  [메뉴 선택] &amp;rarr; (현금 인출 선택) &amp;rarr; [출금 처리] &amp;rarr; (성공) &amp;rarr; [대기 상태]
                                      ↳ (잔액 부족) &amp;rarr; [메뉴 선택]&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;현재 상태&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;입력(이벤트)&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;기대 결과 (전이 후 상태)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;대기 상태&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;카드 삽입&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;PIN 입력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;PIN 입력&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;올바른 PIN 입력&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;메뉴 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;PIN 입력&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;잘못된 PIN 입력&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;대기 상태 (카드 압류)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;메뉴 선택&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;현금 인출 선택&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;출금 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;출금 처리&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;잔액 부족&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;메뉴 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;출금 처리&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;성공&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;대기 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전이 테스트 또한 조합 테스트와 마찬가지로 시나리오에 따라 다양한 조합이 있지만, 시간과 비용 문제로 모든 조합을 시행하긴 어렵다. 따라서 페어와이즈 등의 기법을 활용해 주요 상태 조합을 먼저 테스트한 뒤, 예외적인 상태 전이에 대한 검증(ex. 로그아웃 상태에서 결제 시도 등)을 진행할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;사용사례 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용사례 테스트는 사용자의 관점에서 시스템을 테스트하는 기법으로, SW가 실제 사용 시나리오에서 기대하는 동작을 수행하는지를 중점적으로 테스트한다. 주로 E2E(End-to-End) 테스트에 활용하는 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 실제로 서비스를 이용하는 시나리오를 기반으로 TC를 작성하고, 해당 사용자가 목표를 달성하는 것을 중심으로 테스트를 수행한다. E2E 테스트에 활용되는 기법인 만큼 기능 간 연계를 검증하고, 전체적인 흐름과 예외 사항을 포함하는 테스트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용사례 테스트는 아래와 같은 요소를 포함해 TC를 작성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;액터(Actor): 시스템을 사용하는 주체(사용자, 유저, 외부 시스템, 관리자 등)&lt;/li&gt;
&lt;li&gt;사용사례(Use Case): 특정 기능을 수행하는 과정(ex. 로그인, 상품 구매, 동영상 시청 등)&lt;/li&gt;
&lt;li&gt;이벤트 흐름(Flow): 사용자가 시스템과 상호작용하는 단계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주 흐름(main flow): 정상적인 시나리오&lt;/li&gt;
&lt;li&gt;대체 히름(alternate flow): 예외 처리 및 분기 발생 시나리오(ex. 비밀번호 오류)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사전 조건(Precondition): 테스트를 수행하기 전 시스템이 충족해야하는 조건(ex. 회원가입이 완료된 상태)&lt;/li&gt;
&lt;li&gt;후 조건(Postcondition): 테스트 완료 후 시스템이 만족해야 하는 조건 (ex. 로그인 성공 시 홈 화면으로 이동)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 쇼핑몰에서 상품을 구매하는 시스템을 예시로 TC를 작성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1741087707814&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 상품 구매 시스템
[사용자] &amp;rarr; (상품 선택) &amp;rarr; (장바구니 추가) &amp;rarr; (결제 진행) &amp;rarr; (결제 완료)
                          ↳ (결제 오류) &amp;rarr; (재시도)&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 88px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 12.7907%; text-align: center; height: 20px;&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;width: 10.9303%; text-align: center; height: 20px;&quot;&gt;액터&lt;/td&gt;
&lt;td style=&quot;width: 44.5348%; text-align: center; height: 20px;&quot;&gt;이벤트 흐름&lt;/td&gt;
&lt;td style=&quot;width: 31.7442%; text-align: center; height: 20px;&quot;&gt;기대 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 12.7907%; text-align: center; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 10.9303%; text-align: center; height: 17px;&quot;&gt;User1&lt;/td&gt;
&lt;td style=&quot;width: 44.5348%; text-align: center; height: 17px;&quot;&gt;상품&amp;nbsp;선택&amp;nbsp;&amp;rarr;&amp;nbsp;장바구니&amp;nbsp;추가&amp;nbsp;&amp;rarr;&amp;nbsp;결제&lt;/td&gt;
&lt;td style=&quot;width: 31.7442%; text-align: center; height: 17px;&quot;&gt;결제&amp;nbsp;완료&amp;nbsp;&amp;amp;&amp;nbsp;주문&amp;nbsp;확인&amp;nbsp;페이지&amp;nbsp;이동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 12.7907%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 10.9303%; text-align: center; height: 17px;&quot;&gt;User2&lt;/td&gt;
&lt;td style=&quot;width: 44.5348%; text-align: center; height: 17px;&quot;&gt;상품&amp;nbsp;선택&amp;nbsp;&amp;rarr;&amp;nbsp;장바구니&amp;nbsp;추가&amp;nbsp;&amp;rarr;&amp;nbsp;카드&amp;nbsp;정보&amp;nbsp;오류&amp;nbsp;입력&lt;/td&gt;
&lt;td style=&quot;width: 31.7442%; text-align: center; height: 17px;&quot;&gt;결제&amp;nbsp;실패&amp;nbsp;&amp;amp;&amp;nbsp;오류&amp;nbsp;메시지&amp;nbsp;표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 12.7907%; text-align: center; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 10.9303%; text-align: center; height: 17px;&quot;&gt;User3&lt;/td&gt;
&lt;td style=&quot;width: 44.5348%; text-align: center; height: 17px;&quot;&gt;상품&amp;nbsp;선택&amp;nbsp;&amp;rarr;&amp;nbsp;품절된&amp;nbsp;상품&amp;nbsp;선택&lt;/td&gt;
&lt;td style=&quot;width: 31.7442%; text-align: center; height: 17px;&quot;&gt;구매&amp;nbsp;불가&amp;nbsp;&amp;amp;&amp;nbsp;품절&amp;nbsp;안내&amp;nbsp;메시지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 12.7907%; text-align: center; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 10.9303%; text-align: center; height: 17px;&quot;&gt;User4&lt;/td&gt;
&lt;td style=&quot;width: 44.5348%; text-align: center; height: 17px;&quot;&gt;상품&amp;nbsp;선택&amp;nbsp;&amp;rarr;&amp;nbsp;결제&amp;nbsp;진행&amp;nbsp;중&amp;nbsp;인터넷&amp;nbsp;끊김&lt;/td&gt;
&lt;td style=&quot;width: 31.7442%; text-align: center; height: 17px;&quot;&gt;결제&amp;nbsp;실패&amp;nbsp;&amp;amp;&amp;nbsp;네트워크&amp;nbsp;오류&amp;nbsp;메시지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 구조 기반 테스트 설계 기법 (Structure-based Testing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SW 내부의 코드 구조, 설계, 아키텍처 등을 기준으로 TC를 설계하는 방법. 시스템의 내부 구조를 테스트하는 데 중점을 두며, &lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%99%94%EC%9D%B4%ED%8A%B8%EB%B0%95%EC%8A%A4_%EA%B2%80%EC%82%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;화이트박스 테스트 기법&lt;/a&gt;이 주로 사용된다. TC는 코드의 실행 경로, 조건, 루프 등을 고려해 작성된다. &lt;a href=&quot;https://err0rcode7.github.io/backend/2021/05/11/%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;커버리지&lt;/a&gt; 라는 기준을 주로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;구조 기반 테스트 예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;문장 커버리지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문장 커버리지는 코드 상의 모든 문장이 실행되는지 확인하기 위해 TC를 작성한다. 예를 들어, 아래와 같은 간단한 Python 함수가 있다고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1741088688915&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def add_num(a, b):
    sum = a + b
    if sum &amp;gt; 100:
        sum = 100
    return sum&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수는 변수 a와 b를 인자로 받으며, if 조건문이 존재한다. 따라서, if문을 실행하는 입력값과 실행하지 않는 입력값을 인자로 주어 모든 문장이 실행되도록 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot; rowspan=&quot;2&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot; colspan=&quot;2&quot;&gt;입력 값&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot; rowspan=&quot;2&quot;&gt;기대 결과(sum)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;a&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;50&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;30&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;80 (if문 실행하지 않음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;60&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;50&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;100 (if문 실행)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;조건 커버리지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건 커버리지는 특정 조건에 따라 결과값이 달라지는 함수(또는 최소 단위)에 대해 각 개별 조건의 참과 거짓이 최소한 한 번씩 실행되는지 확인하는 테스트 기법이다. 아래 함수를 기준으로 TC를 작성해본다고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1741089148207&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def check_discount(member, purchase_amount):
    if member == &quot;VIP&quot; or purchase_amount &amp;gt; 100:  # 조건 A: member == &quot;VIP&quot;, 조건 B: purchase_amount &amp;gt; 100
        return &quot;Discount Applied&quot;
    else:
        return &quot;No Discount&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수의 if 조건문에는 2가지의 조건이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조건 A: member == &quot;VIP&quot;&lt;/li&gt;
&lt;li&gt;조건 B: purchase_amount &amp;gt; 100&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 개별 조건에 대해 참과 거짓이 최소 한 번씩 실행될 수 있도록 TC를 작성해보자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;member&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;purchase_amount&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;조건 A&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;조건 B&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;기대 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&quot;VIP&quot;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;50&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;True&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;Discount Applied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&quot;Normal&quot;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;150&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;True&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;Discount Applied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&quot;Normal&quot;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;50&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;No Discount&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3가지 케이스를 통해 각 조건이 최소 한 번씩 True또는 False값을 가지고 실행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;조건/분기 커버리지(Condition/Decision Coverage, C/DC)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 위 조건 커버리지를 살펴보면, 조건 A와 B가 모두 True인 상황에 대해서는 테스트를 하지 않고 있다. 입력 값에 대한 True, False 조건만을 살피기 때문에, 전체 분기(Branch)를 모두 커버하지는 못한다. 이런 상황에서는 조건/분기 커버리지를 사용해 논리적으로 강력한 검증을 통해 보완할 수 있다. 이번엔 and가 있는 if 조건문에 대해서 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1741089750267&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def is_eligible(age, is_member):
    if age &amp;gt;= 18 and is_member:  # 조건 A: age &amp;gt;= 18, 조건 B: is_member
        return &quot;Eligible&quot;
    else:
        return &quot;Not Eligible&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 마찬가지로 조건이 2가지다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조건 A: age &amp;gt; 18&lt;/li&gt;
&lt;li&gt;조건 B: is_member&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건/분기 커버리지에서는 각 조건에 대해서 True/False가 최소 한 번씩 실행될 뿐만 아니라, 모든 분기(if, else)에 대해서도 최소 한 번씩 실행되어야 하는 TC 설계 조건을 가진다.&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.85275%;&quot;&gt;TC&lt;/td&gt;
&lt;td style=&quot;width: 12.8295%;&quot;&gt;age&lt;/td&gt;
&lt;td style=&quot;width: 14.9226%;&quot;&gt;is_member&lt;/td&gt;
&lt;td style=&quot;width: 13.876%;&quot;&gt;조건 A&lt;/td&gt;
&lt;td style=&quot;width: 13.3334%;&quot;&gt;조건 B&lt;/td&gt;
&lt;td style=&quot;width: 22.5194%;&quot;&gt;분기 실행 결과&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;기대 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.85275%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 12.8295%;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 14.9226%;&quot;&gt;True&lt;/td&gt;
&lt;td style=&quot;width: 13.876%;&quot;&gt;True&lt;/td&gt;
&lt;td style=&quot;width: 13.3334%;&quot;&gt;True&lt;/td&gt;
&lt;td style=&quot;width: 22.5194%;&quot;&gt;if 실행&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;Eligible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.85275%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 12.8295%;&quot;&gt;17&lt;/td&gt;
&lt;td style=&quot;width: 14.9226%;&quot;&gt;True&lt;/td&gt;
&lt;td style=&quot;width: 13.876%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 13.3334%;&quot;&gt;True&lt;/td&gt;
&lt;td style=&quot;width: 22.5194%;&quot;&gt;else 실행&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;Not&amp;nbsp;Eligible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.85275%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 12.8295%;&quot;&gt;21&lt;/td&gt;
&lt;td style=&quot;width: 14.9226%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 13.876%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 13.3334%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 22.5194%;&quot;&gt;else 실행&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;Not Eligible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 5.85275%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 12.8295%;&quot;&gt;16&lt;/td&gt;
&lt;td style=&quot;width: 14.9226%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 13.876%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 13.3334%;&quot;&gt;False&lt;/td&gt;
&lt;td style=&quot;width: 22.5194%;&quot;&gt;else 실행&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;Not Eligible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건/분기 커버리지 기법에서는 위와 같이 (True, True)를 가진 TC도 포함된다는 것을 알 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>ISTQB</category>
      <category>QA</category>
      <category>sten</category>
      <category>SW테스트</category>
      <category>구조기반테스트</category>
      <category>명세기반테스트</category>
      <category>블랙박스테스트</category>
      <category>소프트웨어테스트</category>
      <category>조건분기커버리지</category>
      <category>코드커버리지</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/292</guid>
      <comments>https://minding-deep-learning.tistory.com/292#entry292comment</comments>
      <pubDate>Tue, 4 Mar 2025 18:24:42 +0900</pubDate>
    </item>
    <item>
      <title>[OmniParserV2] LLM과 함께 사용할 수 있는 화면 인식 GUI 자동화 도구 (설치 및 실행 방법)</title>
      <link>https://minding-deep-learning.tistory.com/291</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;OmniParserV2&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OmniParserV2는 Microsoft에서 개발한 컴퓨터 비전(모델은 YOLO) 기반 GUI 자동화 도구로, 사용자의 UI 스크린샷을 구조화된 데이터로 변환해 LLM이 구조를 이해하고 상호작용할 수 있도록 돕는다. 이 도구를 통해 LLM과의 상호작용을 통해 GUI 테스트 등을 자동화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 실제 클릭과 같은 경우는 사용자의 판단이 필요하거나, 유해 콘텐츠 필터링 기능이 제공되지 않는 한계점이 존재하지만, OmniParserV2와 같은 도구를 통해 SW의 UI 테스트를 자동화하거나, 반복되는 업무를 자동화할 수 있을 것으로 기대된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OmniParserV2의 특징&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상호작용 요소 탐지 및 분석: UI 스크린샷에서 클릭 가능한 버튼, 아이콘 등을 감지할 수 있고, 해당 요소의 의미를 파악해 LLM이 이를 파악할 수 있도록 한다.&lt;/li&gt;
&lt;li&gt;성능 향상: 이전 모델에 비해 분석 속도를 최대 60% 단축했고, ScreenSpot Pro 벤치마크에서 39.6%의 정확도를 기록했다.&lt;/li&gt;
&lt;li&gt;다양한 OS에서 사용 가능: Windows, macOS, Linux, iOS, Android의 다양한 OS와 App에서 사용 가능하다.&lt;/li&gt;
&lt;li&gt;오픈소스: 일부 모델(icon_detect)을 제외한 모든 소스는 오픈 소스로 제공되어 자유롭게 사용 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;OmniParserV2 사용해보기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OmniParserV2 설치 및 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OmniParserV2는 LLM과 CV 모델을 로컬에서 돌리는 만큼 GPU에 대한 기본 세팅이 필요하다. NVIDIA 드라이버와 CUDA 설치가 필요한데, 이 글에서는 다루지 않으니 미리 세팅해놓도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/OmniParser&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MS의 OmniParserV2 공식 Github&lt;/a&gt;에 접속해 git clone 명령어를 통해 소스코드를 다운로드받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1740449613458&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git clone https://github.com/microsoft/OmniParser.git&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 해당 페이지에서 안내하는대로 새로운 가상환경을 만들고, 종속성 라이브러리를 설치한다. (공식 안내에서는 conda를 사용한다.)&lt;/p&gt;
&lt;pre id=&quot;code_1740449728228&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd OmniParser
conda create -n &quot;omni&quot; python==3.12
conda activate omni
pip install -r requirements.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 OmniParserV2 모델에서 사용할 weights를 아래 명령어를 통해 다운로드받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1740449845189&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   # download the model checkpoints to local directory OmniParser/weights/
   for f in icon_detect/{train_args.yaml,model.pt,model.yaml} icon_caption/{config.json,generation_config.json,model.safetensors}; do huggingface-cli download microsoft/OmniParser-v2.0 &quot;$f&quot; --local-dir weights; done
   mv weights/icon_caption weights/icon_caption_florence&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 했다면 OmniParserV2를 실행할 기본적인 세팅은 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Demo 사용해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradio_demo.py를 실행시키면 OmniParserV2의 화면 인식을 데모 버전으로 실행시켜볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740450307061&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python gradio_demo.py&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 http://0.0.0.0:7861/ 링크를 통해 데모 페이지를 열어볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFGbdE/btsMwyaC7Ec/sRa6c67frWjKeqcpDUMbK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFGbdE/btsMwyaC7Ec/sRa6c67frWjKeqcpDUMbK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFGbdE/btsMwyaC7Ec/sRa6c67frWjKeqcpDUMbK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFGbdE%2FbtsMwyaC7Ec%2FsRa6c67frWjKeqcpDUMbK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;959&quot; height=&quot;1090&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;1090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측 상단의 Upload Image를 통해서 스크린샷 이미지를 업로드할 수 있고, 해당 스크린샷을 기반으로 OmniParserV2가 작동해 화면의 각 요소들을 탐지하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세부 설정으로는 4가지 정도를 설정할 수 있는데 각 설정에 대한 설명은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Box Threshold: 객체 탐지에 사용하는 예측 Bounding Box의 유효성을 검증하는 기준(임계)값을 의미한다.&lt;/li&gt;
&lt;li&gt;IOU Threshold: 예측된 바운딩 박스와 실제 객체 위치 간의 겹치는 비율로, 해당 값이 높을수록 겹치는 비율이 높아야만 예측이 정확하다고 판단한다.&lt;/li&gt;
&lt;li&gt;Use PaddleOCR: 이미지나 문서에서 텍스트를 인식하고 추출하는 데 사용하는 PaddleOCR의 사용여부를 선택할 수 있다.&lt;/li&gt;
&lt;li&gt;Icon Detect Image Size: Input으로 주어지는 스크린샷의 사이즈로, 값이 높을수록 세세한 탐지가 가능하지만 속도가 느려질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버의 메인 화면을 스크린샷한 뒤 업로드해보았다. 결과는 다음과 같이 나왔다. 완벽하진 않지만 거의 모든 요소에 대한 인식을 성공한 모습이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lB5EL/btsMvbAMhzJ/YKnDRS3LjEPokezkBGM7K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lB5EL/btsMvbAMhzJ/YKnDRS3LjEPokezkBGM7K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lB5EL/btsMvbAMhzJ/YKnDRS3LjEPokezkBGM7K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlB5EL%2FbtsMvbAMhzJ%2FYKnDRS3LjEPokezkBGM7K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;959&quot; height=&quot;1090&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;1090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (2).webp&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;1048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nWrq1/btsMuxEhy9f/eqdzKxI6AgaNSjY5xJ7H91/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nWrq1/btsMuxEhy9f/eqdzKxI6AgaNSjY5xJ7H91/img.webp&quot; data-alt=&quot;주요 버튼과 클릭 가능한 요소들에 대한 인식을 마친 모습. 모든 요소가 인식되진 않았다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nWrq1/btsMuxEhy9f/eqdzKxI6AgaNSjY5xJ7H91/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnWrq1%2FbtsMuxEhy9f%2FeqdzKxI6AgaNSjY5xJ7H91%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1854&quot; height=&quot;1048&quot; data-filename=&quot;image (2).webp&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;1048&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;주요 버튼과 클릭 가능한 요소들에 대한 인식을 마친 모습. 모든 요소가 인식되진 않았다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 화면에서 인식된 데이터는 아래와 같이 출력되기도 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740451661240&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;icon 0: {'type': 'text', 'bbox': [0.14994606375694275, 0.007633587811142206, 0.19255663454532623, 0.033396948128938675], 'interactivity': False, 'content': 'NAVER', 'source': 'box_ocr_content_ocr'}
icon 1: {'type': 'text', 'bbox': [0.7885652780532837, 0.011450381949543953, 0.8198489546775818, 0.03148854896426201], 'interactivity': False, 'content': 'Gradio', 'source': 'box_ocr_content_ocr'}
icon 2: {'type': 'text', 'bbox': [0.1877022683620453, 0.08683206140995026, 0.2324703335762024, 0.10687022656202316], 'interactivity': False, 'content': 'D YouTube', 'source': 'box_ocr_content_ocr'}
icon 3: {'type': 'text', 'bbox': [0.5323624610900879, 0.6374045610427856, 0.5706580281257629, 0.6583969593048096], 'interactivity': False, 'content': 'OhmyNews', 'source': 'box_ocr_content_ocr'}
icon 4: {'type': 'text', 'bbox': [0.18662351369857788, 0.6908397078514099, 0.20927724242210388, 0.7118320465087891], 'interactivity': False, 'content': 'MBN', 'source': 'box_ocr_content_ocr'}
icon 5: {'type': 'text', 'bbox': [0.17583602666854858, 0.7404580116271973, 0.2206040918827057, 0.7614504098892212], 'interactivity': False, 'content': 'IHUFFPOSTI', 'source': 'box_ocr_content_ocr'}
icon 6: {'type': 'text', 'bbox': [0.2572815418243408, 0.7461832165718079, 0.2793959081172943, 0.7614504098892212], 'interactivity': False, 'content': 'iT dongA', 'source': 'box_ocr_content_ocr'}
icon 7: {'type': 'text', 'bbox': [0.3122977316379547, 0.7461832165718079, 0.3656958043575287, 0.7643129825592041], 'interactivity': False, 'content': 'ECONOMYChOsun', 'source': 'box_ocr_content_ocr'}
icon 8: {'type': 'text', 'bbox': [0.3883495032787323, 0.7442747950553894, 0.4320388436317444, 0.7643129825592041], 'interactivity': False, 'content': 'ECONOTIMES', 'source': 'box_ocr_content_ocr'}
...
icon 123: {'type': 'icon', 'bbox': [0.44583913683891296, 0.6316617131233215, 0.5054893493652344, 0.6619281768798828], 'interactivity': True, 'content': 'MyMyMy', 'source': 'box_yolo_content_yolo'}
icon 124: {'type': 'icon', 'bbox': [0.9278861284255981, 0.9368280172348022, 0.9532544612884521, 0.9853677749633789], 'interactivity': True, 'content': 'Currency', 'source': 'box_yolo_content_yolo'}
icon 125: {'type': 'icon', 'bbox': [0.34240198135375977, 0.8066388368606567, 0.4105178117752075, 0.8410437703132629], 'interactivity': True, 'content': 'New', 'source': 'box_yolo_content_yolo'}
icon 126: {'type': 'icon', 'bbox': [0.6124023795127869, 0.9523950219154358, 0.6713324189186096, 1.0], 'interactivity': True, 'content': 'A text input or translation function.', 'source': 'box_yolo_content_yolo'}
icon 127: {'type': 'icon', 'bbox': [0.39023855328559875, 0.6901223063468933, 0.4010043740272522, 0.7121850252151489], 'interactivity': True, 'content': 'Navigator', 'source': 'box_yolo_content_yolo'}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'interactiviry'에는 해당 요소가 상호작용이 가능한지의 여부에 대해 제공하고, 'content'라는 칼럼에는 해당 요소의 의미를 해석한 모습이 보인다. 한글이 대부분이기 때문에 완벽하진 않지만, 생각보다는 괜찮은 성과를 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OmniTool 설치 및 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Gradio demo를 통해 OmniParserV2가 제대로 동작하는 것을 확인했다면, 이제는 OmniTool을 이용해서 LLM과 상호작용 기능까지 사용해 볼 차례다. OmniTool을 사용하기 위해서는 몇 가지 설치 및 세팅이 추가로 필요하다. &lt;a href=&quot;https://github.com/microsoft/OmniParser/tree/master/omnitool&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 Github페이지&lt;/a&gt;를 참고해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. omniparserserver&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 OmniParserV2의 설치과정을 잘 따랐다면, 이 부분에서는 서버를 실행시켜주기만 하면된다. omniparser를 이용하기 위한 FastAPI 서버로, Web UI도 제공해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1740452117292&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd OmniParser/omnitool/omniparserserver
python -m omniparserserver&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. omnibox&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;omnibox는 Docker 컨테이너에서 실행되는 Windows 11 VM(Virtual Machine)이다. 이를 위해 사전에 &lt;a href=&quot;https://docs.docker.com/desktop/setup/install/linux/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker Desktop 설치&lt;/a&gt;가 필요하다. 드라이브에 남은 공간이 30GB 이상 필요하니, 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, &lt;a href=&quot;https://info.microsoft.com/ww-landing-windows-11-enterprise.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Microsoft Evaluation Center&lt;/a&gt;에 접속해 자신의 OS에 맞는 Windows 11 Enterprise Evaluation (90-day trial, English, United States) ISO 파일을 다운로드 받아준다. 해당 파일은 custom.iso라는 이름으로 바꾼 뒤, OmniParser/omnitool/omnibox/vm/win11iso의 경로로 이동시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음, 아래 명령어를 통해 도커 이미지를 다운로드 받은 뒤 컨테이너를 빌드 및 ISO 파일을 통해 스토리지 폴더를 설치하는 스크립트를 실행시킨다. 여기엔 꽤 많은 시간이 소요된다. 공식 홈페이지에서는 일반적으로 1시간이 소요된다고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740452858993&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd OmniParser/omnitool/omnibox/scripts

# 스크립트 실행
./manage_vm.sh create&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서는 Progress Bar가 제공되지 않아 정상적으로 실행되고 있는지 알 수가 없는데, 아래 명령어를 통해 스토리지 폴더의 용량을 확인해 설치가 되고 있는지 정도는 확인할 수 있다. 10초마다 업데이트 되는데, 이 때 용량 변화가 있다면 정상적으로 실행되고 있다는 뜻이다.&lt;/p&gt;
&lt;pre id=&quot;code_1740453092897&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;watch -n 10 'du -sh ~/project/OmniParser/omnitool/omnibox/vm/win11storage'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 나는 개인적으로 에러를 발견했는데, 위 명령어를 통해 스토리지 용량을 지속적으로 확인해도 변함이 없었다. 그 이유는 docker container가 제대로 실행되지 않는 문제였다. 컨테이너는 omnibox 디렉토리 아래 compose.yml 파일을 실행시키는 것이었기 때문에 직접 실행해보았다. 해결 내용은 아래 접은 글을 참고하길 바란다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740461116632&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd OmniParser/omnitool/omnibox

# compose.yml 파일 직접 실행해보기
docker compose up&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어를 실행시켜 본 결과 노출된 메시지는 아래와 같았다.&lt;/p&gt;
&lt;pre id=&quot;code_1740461166505&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ERROR: Your configured RAM_SIZE of 8 GB is too high for the 8 GB of memory available, please set a lower value.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메시지는 docker desktop의 메모리 Resource 사용량 제한이 8GB인데, 해당 compose.yml 파일에 설정된 최소치 또한 8GB이기 때문에 더 낮은 값으로 재설정하라는 뜻이다. 나는 compose.yml 파일을 수정하기 보다는, docker desktop에서 메모리 허용량을 늘리는 방향으로 해결했다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9wQBL/btsMwrC3aut/TBLeP6OCCQzurj2HS788I0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9wQBL/btsMwrC3aut/TBLeP6OCCQzurj2HS788I0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9wQBL/btsMwrC3aut/TBLeP6OCCQzurj2HS788I0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9wQBL%2FbtsMwrC3aut%2FTBLeP6OCCQzurj2HS788I0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1270&quot; height=&quot;720&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;Settings &amp;gt; Resources &amp;gt; Memory limit을 10GB로 재설정한 뒤, 다시 manage_vm.sh create 명령어를 실행시켰다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EK05k/btsMwcTAhIy/c7rKecqUwtPOPpSLsDCjS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EK05k/btsMwcTAhIy/c7rKecqUwtPOPpSLsDCjS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EK05k/btsMwcTAhIy/c7rKecqUwtPOPpSLsDCjS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEK05k%2FbtsMwcTAhIy%2Fc7rKecqUwtPOPpSLsDCjS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;866&quot; height=&quot;766&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;용량이 5.1G로 변하며 설치가 진행중인 모습을 확인할 수 있었다. 혹시나 권한 문제로 설치가 중단될 수도 있으니, 해당 문제가 있는 사람은 sudo를 붙여서 실행해보는 것도 방법일 것이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면 'VM&amp;nbsp;+&amp;nbsp;server&amp;nbsp;is&amp;nbsp;up&amp;nbsp;and&amp;nbsp;running!!'이라는 메시지가 터미널 창에 노출될 것이다. 최초 설치 이후에는 아래 명령어를 통해 컨테이너를 빌드하고 멈출 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740461548873&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./manage_vm.sh start
./manage_vm.sh stop

# 삭제가 필요할 시
./manage_vm.sh delete&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. gradio&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradio는 omnibox에서 명령을 제공하고 추론과 실행을 감시하는 UI의 역할이다. 기본적으로 위에서 설정한 omni 가상환경을 필요로한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740453307393&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# gradio 폴더로 이동
cd OmniParser/omnitool/gradio

# omni 가상환경 실행중인지 확인
conda activate omni

# gradio 서버 실행
python app.py --windows_host_url localhost:8006 --omniparser_server_url localhost:8000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OmniTool을 이용해 LLM 모델과 함께 사용해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmYcFF/btsMvPRYdkN/GURJQFoeUxSlvuZr5XKtV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmYcFF/btsMvPRYdkN/GURJQFoeUxSlvuZr5XKtV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmYcFF/btsMvPRYdkN/GURJQFoeUxSlvuZr5XKtV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmYcFF%2FbtsMvPRYdkN%2FGURJQFoeUxSlvuZr5XKtV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;959&quot; height=&quot;960&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 세팅이 모두 마무리 된 다음, gradio의 WebUI(http://0.0.0.0:7888/)에 접속하면 위와 같은 화면이 노출된다. Settings에서 사용할 LLM Model을 선택할 수 있다. 기본적으로는 API Key(유료버전)를 입력해야 사용할 수 있다. 만약 과금이 부담된다면, &lt;a href=&quot;https://www.youtube.com/watch?v=UECfiRv0XjU&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 영상&lt;/a&gt;을 한번 참고하길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 OpenAI의 API KEY를 이용했다. 브라우저를 열고 닫는 간단한 작업을 수행해봤다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/453268994&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/2D442/hyYjvHk4en/kbN7d0O8ICOBeH0B3KFgN1/img.jpg?width=908&amp;amp;height=918&amp;amp;face=0_0_908_918,https://scrap.kakaocdn.net/dn/bi7tLF/hyYjpNUliL/J1mUtioptKN7DeCFnBlnw0/img.jpg?width=908&amp;amp;height=918&amp;amp;face=0_0_908_918&quot; data-video-width=&quot;860&quot; data-video-height=&quot;869&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;869&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;'Today's Minding'에서 업로드한 동영상&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/453268994?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;869&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에게 명령을 내렸을 때, 구조화된 데이터를 읽고 그에 따른 수행 과정 자체는 잘 판단했으나, 실제 VM에서의 행동은 제대로 이루어지지 않는 모습이었다. 생각보다는 실망이었다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령에 따른 LLM에서 답변은 아래와 같다. 위치와 행동은 제대로 파악했으나, 이상하게도 작동은 하지 않았다.&lt;/p&gt;
&lt;pre id=&quot;code_1740468380541&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Analysis: The task is to quit the browser, and the screen currently shows the Google Chrome interface with an ad privacy overlay. To close the browser, I need to click the 'X' button at the top-right corner of the window.
Next Action: left_click
Box ID: 49
box_centroid_coordinate: [1029, 29]

Next I will perform the following action: {'action': 'mouse_move', 'coordinate': [1029, 29]}

Moved mouse to (1029, 29)

Next I will perform the following action: {'action': 'left_click'}

Performed left_click&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 비싼 모델을 사용해야했을까..? 기본 모델이다보니 사용량이 한계인 점도 있는 것 같다. 다음에 사용할 때는 이런 점들을 보완할 수 있는 방법을 찾아봐야 할 것 같다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>ChatGPT</category>
      <category>ComputerVision</category>
      <category>gpt4o</category>
      <category>gui 자동화</category>
      <category>llm</category>
      <category>omniparser</category>
      <category>omniparserv2</category>
      <category>OpenAI</category>
      <category>YOLO</category>
      <category>테스트 자동화</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/291</guid>
      <comments>https://minding-deep-learning.tistory.com/291#entry291comment</comments>
      <pubDate>Tue, 25 Feb 2025 16:29:33 +0900</pubDate>
    </item>
    <item>
      <title>[Python 3.13] Python에서 GIL을 비활성화 할 수 있다?</title>
      <link>https://minding-deep-learning.tistory.com/290</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Python 3.13과 GIL&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Medium을 구독하면서 읽게된 글을 통해 Python 3.13 버전이 출시되었다는 것을 알게 되었는데, 그 중에서도 놀라운 글을 보게되었다. 바로 &lt;a href=&quot;https://medium.com/@r_bilan/python-3-13-without-the-gil-a-game-changer-for-concurrency-5e035500f0da&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Python 3.13에서는 GIL(Global Interpreter Lock)을 비활성화할 수 있다는 것&lt;/a&gt;이다. GIL은 Python의 큰 특징 중 하나이기도 했고, 대표적인 한계점이기도 했기 때문에 이 소식은 꽤나 놀라웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GIL은 프로세스 별로 여러 스레드가 동시에 코드를 실행하지 못하도록 하여 스레드 실행을 동기화하는 매커니즘이다. GIL을 사용하는 인터프리터인 Python은 멀티코어 프로세서가 실행되어도 항상 하나의 스레드가 실행된다. 그래서 Python에서는 멀티 프로세싱 대신 멀티 스레딩 방식을 주로 사용한다. 위 글에서 소개한 GIL의 장단점은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;4251&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GIL의 장점&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li id=&quot;baf4&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;구현의 단순성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: GIL은 Python 객체에 대한 동시 액세스를 방지하여 CPython의 메모리 관리를 단순화하며, 이를 통해 경쟁 조건 및 기타 스레딩 문제를 방지하는 데 도움이 될 수 있습니다.&lt;/li&gt;
&lt;li id=&quot;564e&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;단일 스레드 프로그램의 사용 편의성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 단일 스레드 애플리케이션의 경우 GIL은 스레드 안전성을 관리하는 데 따른 오버헤드를 제거하여 간단하고 효율적인 코드 실행이 가능합니다.&lt;/li&gt;
&lt;li id=&quot;a11a&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;C 확장 기능과의 호환성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: GIL을 사용하면 복잡한 스레딩 모델을 구현하지 않고도 C 확장 기능을 작동할 수 있으므로 C 라이브러리와 인터페이스하는 Python 확장 기능의 개발이 간소화됩니다.&lt;/li&gt;
&lt;li id=&quot;3689&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;I/O 바운드 작업의 성능&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: I/O 바운드 애플리케이션에서 GIL은 성능을 크게 방해하지 않습니다. I/O 작업 중에 스레드를 전환하여 다른 스레드를 실행할 수 있기 때문입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;f5fa&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GIL의 단점&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li id=&quot;87b5&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;제한된 멀티스레딩 성능&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: GIL은 한 번에 하나의 스레드만 Python 바이트코드를 실행하도록 허용하므로 CPU에 집중된 멀티스레딩 애플리케이션의 성능을 심각하게 제한할 수 있으며, 이는 멀티코어 프로세서의 활용도 저하로 이어질 수 있습니다.&lt;/li&gt;
&lt;li id=&quot;34cc&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;스레드 관리의 복잡성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: GIL은 메모리 관리를 간소화하지만 동시적 애플리케이션의 설계를 복잡하게 만들어 개발자가 스레드 문제를 신중하게 관리하거나 대신 멀티프로세싱을 사용해야 할 수도 있습니다.&lt;/li&gt;
&lt;li id=&quot;200a&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;병렬 처리의 방해 요소&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: GIL을 활성화하면 Python 애플리케이션에서 진정한 병렬 처리를 구현하는 것이 어려워서 개발자가 멀티코어 아키텍처를 효과적으로 활용하기 어렵습니다.&lt;/li&gt;
&lt;li id=&quot;781c&quot; style=&quot;list-style-type: decimal; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;컨텍스트 전환의 비효율성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: GIL로 인한 빈번한 컨텍스트 전환은 오버헤드를 유발할 수 있으며, 특히 스레드가 많은 애플리케이션에서 성능 저하로 이어질 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Python 3.13에서의 GIL&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;python 3.13 버전에서의 GIL에 대한 기능이 아래와 같이 추가되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;934d&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;자유 스레드 모드에 대한 실험적 지원&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: Python 3.13은 GIL을 비활성화할 수 있는 실험적 모드를 도입합니다. 이는 멀티스레딩 기능을 개선하고 CPU 바운드 작업에서 더 나은 성능을 가능하게 하는 것을 목표로 합니다.&lt;/li&gt;
&lt;li id=&quot;ac7f&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;특수 인터프리터 향상&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 특수 인터프리터는 GIL 없이 스레드 안전을 보장하기 위해 수정되었습니다. 여기에는 동시 특수화를 방지하기 위해 뮤텍스를 사용하고 일관된 캐시된 값을 보장하는 것이 포함됩니다.&lt;/li&gt;
&lt;li id=&quot;a499&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;새로운 Py_mod_gil 슬롯&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 확장 기능은 이제 모듈을 로드할 때 GIL 동작을 관리하기 위해 새로운 PEP 489 스타일&lt;span&gt;&amp;nbsp;&lt;/span&gt;Py_mod_gil슬롯을 정의할 수 있습니다. 이 슬롯이 제대로 설정되지 않으면 인터프리터는 GIL을 활성화하고 모든 스레드를 일시 중지하여 사용자에게 경고를 제공합니다.&lt;/li&gt;
&lt;li id=&quot;b6eb&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;PYTHONGIL 환경 변수&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 사용자는 환경 변수를 사용하여 런타임에 GIL 동작을 제어할 수 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;PYTHONGIL 환경변수를 0으로 설정하면&lt;span&gt; &lt;/span&gt;GIL이 비활성화 상태로 유지되고, 1로 설정하면&lt;span&gt; &lt;/span&gt;활성화됩니다.&lt;/li&gt;
&lt;li id=&quot;ea45&quot; style=&quot;list-style-type: disc; color: #242424;&quot; data-selectable-paragraph=&quot;&quot;&gt;&lt;b&gt;비세대 가비지 컬렉션(Non-Generational Garbage Collection)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: GIL 변경 사항은 세대별 순환 가비지 컬렉션에서 비세대 모델로의 전환을 지원하여 가비지 컬렉션 주기 동안 스레드 일시 중지를 줄이고 멀티 스레딩 효율성을 개선하는 것을 목표로 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python3.13의 GIL 비활성화 기능은 아직 테스트 버전에서만 작동할 수 있기 때문에, python3.13이 아닌 python3.13t 버전을 사용해야 한다. python3.13t를 설치하는 방법은 아래 접은 글에 기술되어 있다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1740471894814&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# python3.13t 설치

# 소스 코드 다운로드
git clone https://github.com/python/cpython.git
cd cpython
git checkout 3.13

# 빌드 설정 (GIL 비활성화 옵션 포함)
./configure --enable-optimizations --disable-gil
sudo make -j $(nproc)

# 설치
sudo make altinstall&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1740469734753&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import datetime
import sys
from multiprocessing import Process
from threading import Thread
from typing import Any, Callable


def log_time(func: Callable[..., Any]) -&amp;gt; Callable[..., Any]:
    &quot;&quot;&quot;
    A decorator that logs the execution time of a function.

    :param func: The function to be decorated.
    :return: The wrapped function with execution time logging.
    &quot;&quot;&quot;

    def wrapper(*args: Any, **kwargs: Any) -&amp;gt; Any:
        start_time = datetime.datetime.now()
        result = func(*args, **kwargs)
        execution_time = datetime.datetime.now() - start_time
        print(
            f&quot;Function '{func.__name__}' executed in {execution_time.total_seconds()} seconds.&quot;
        )
        return result

    return wrapper


def do_something(n: int = 1) -&amp;gt; int:
    &quot;&quot;&quot;
    Computes the n-th Fibonacci number.

    :param n: The position in the Fibonacci sequence to compute (default is 1).
              The first Fibonacci number is at position 1.
    :return: The n-th Fibonacci number.
    &quot;&quot;&quot;
    a, b = 0, 1
    for _ in range(n - 1):
        a, b = b, a + b
    return a


@log_time
def run_multi_thread_task(func: Callable[[Any], Any], input_data: list[Any]) -&amp;gt; None:
    &quot;&quot;&quot;
    Executes a function in multiple threads concurrently.

    :param func: The function to execute, taking one argument.
    :param input_data: A list of input data that will be passed to the function.
    :return: None
    &quot;&quot;&quot;
    threads = []
    for data in input_data:
        thread = Thread(target=func, args=(data,))
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()


@log_time
def run_multi_processing_task(func: Callable[[Any], Any], input_data: list[Any]) -&amp;gt; None:
    &quot;&quot;&quot;
    Executes a function in multiple processes concurrently.

    :param func: The function to execute, taking one argument.
    :param input_data: A list of input data that will be passed to the function.
    :return: None
    &quot;&quot;&quot;
    processes = []

    for data in input_data:
        process = Process(target=func, args=(data,))
        processes.append(process)
        process.start()

    for process in processes:
        process.join()


@log_time
def run_single_thread_task(func: Callable[[Any], Any], input_data: list[Any]) -&amp;gt; None:
    &quot;&quot;&quot;
    Executes a function in one thread.

    :param func: The function to execute, taking one argument.
    :param input_data: A list of input data that will be passed to the function.
    :return: None
    &quot;&quot;&quot;
    for data in input_data:
        func(data)


def main(func: Callable[[Any], Any], input_data: list[Any]) -&amp;gt; None:
    run_single_thread_task(func=func, input_data=input_data)
    run_multi_processing_task(func=func, input_data=input_data)
    run_multi_thread_task(func=func, input_data=input_data)


if __name__ == &quot;__main__&quot;:
    print(f&quot;Current python v: {sys.version}&quot;)

    status = False
    if sys.version_info == (3, 13):
        status = sys._is_gil_enabled()

    print(f&quot;Global Interpreter Lock status: {&quot;disabled&quot; if not status else &quot;enabled&quot;}&quot;)

    test_data = [400000] * 5

    main(
        func=do_something,
        input_data=test_data,
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 글에서 제공한 테스트 코드를 통해 Python 3.13과 3.12버전 간 GIL에 대해 얼마나 달라졌는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740469969690&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# python3.12 버전
Current python v: 3.12.0 | packaged by Anaconda, Inc. | (main, Oct  2 2023, 17:29:18) [GCC 11.2.0]
Global Interpreter Lock status: enabled
Function 'run_single_thread_task' executed in 5.410144 seconds.
Function 'run_multi_processing_task' executed in 1.119341 seconds.
Function 'run_multi_thread_task' executed in 5.859033 seconds.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740470065873&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# python 3.13 (GIL 활성화 상태)
Current python v: 3.13.0 | packaged by Anaconda, Inc. | (main, Oct  7 2024, 21:29:38) [GCC 11.2.0]
Global Interpreter Lock status: enabled
Function 'run_single_thread_task' executed in 5.458232 seconds.
Function 'run_multi_processing_task' executed in 1.125929 seconds.
Function 'run_multi_thread_task' executed in 5.736521 seconds.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740471671748&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Python3.13t (GIL 비활성화 상태)
Current python v: 3.13.2+ experimental free-threading build (heads/3.13:90fc6117da5, Feb 25 2025, 17:17:35) [GCC 13.3.0]
Global Interpreter Lock status: disabled
Function 'run_single_thread_task' executed in 6.825225 seconds.
Function 'run_multi_processing_task' executed in 1.396384 seconds.
Function 'run_multi_thread_task' executed in 1.376363 seconds.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 파일 실행 결과를 보면, GIL 비활성화 효과를 제대로 파악할 수 있다. GIL을 비활성화할 경우, 싱글 스레드 작업과 멀티 프로세싱 작업은 비슷하지만 멀티 스레드 작업에서 상당한 개선 효과를 보여준다. 3.12버전과 3.13 GIL 활성화 상태에서는 5.7~8초 정도의 시간을 보였지만, 3.13 GIL 비활성화 상태에서는 1.3초만에 작업이 완료되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfks7l/btsMwyh4yv3/qHTmCUIZAWPb1dFZvtfrrk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfks7l/btsMwyh4yv3/qHTmCUIZAWPb1dFZvtfrrk/img.jpg&quot; data-alt=&quot;medium 글에서 제공해준 시각화 자료 3.13 no GIL의 성능 개선 효과를 알 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfks7l/btsMwyh4yv3/qHTmCUIZAWPb1dFZvtfrrk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfks7l%2FbtsMwyh4yv3%2FqHTmCUIZAWPb1dFZvtfrrk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;525&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;medium 글에서 제공해준 시각화 자료 3.13 no GIL의 성능 개선 효과를 알 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GIL 비활성화 버전은 아직 정식 배포된 것은 아니지만, 조금 더 안정될 경우 AI나 ML 부문에서 아주 좋은 옵션으로 사용될 것 같다. GIL 비활성화 기능이 정식으로 포함된 3.13버전이 배포된다면, 더 많이 써봐야할 것 같다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>GiL</category>
      <category>global interpreter lock</category>
      <category>Python</category>
      <category>python 3.13t</category>
      <category>python gil</category>
      <category>python gil 비활성화</category>
      <category>python3.13</category>
      <category>파이썬</category>
      <category>파이썬 GIL</category>
      <category>파이썬 gil 비활성화</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/290</guid>
      <comments>https://minding-deep-learning.tistory.com/290#entry290comment</comments>
      <pubDate>Mon, 24 Feb 2025 11:50:02 +0900</pubDate>
    </item>
    <item>
      <title>[QA/Testing] 모바일 앱 테스트 자동화 오픈소스 Appium 사용해보기</title>
      <link>https://minding-deep-learning.tistory.com/289</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Appium?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appium은 다양한 플랫폼에서 모바일 app의 UI를 자동화할 수 있는 테스트 프레임워크다. Windows, Mac, Linux에서 모두 실행 가능하며, 하나의 테스트 스크립트로 Android와 iOS 앱을 모두 테스트할 수 있다. 테스트 스크립트 또한 Java, Python, Ruby, JS 등 자신이 익숙한 언어를 선택할 수 있다는 것도 큰 장점이다. 네이티브와 하이브리드 앱을 모두 테스트할 수 있고, 무엇보다도 오픈소스이기 때문에 무료로 사용할 수 있다. 따라서 현업에서도 앱 테스트시 CI/CD 파이프라인에 통합하여 가장 많이 사용하는 도구 중 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Appium의 아키텍처&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgrzO9/btsMpJLcfqi/Bl1NfJ1fRACxs3B5iklfA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgrzO9/btsMpJLcfqi/Bl1NfJ1fRACxs3B5iklfA0/img.png&quot; data-alt=&quot;출처: https://www.lambdatest.com/blog/appium-architecture/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgrzO9/btsMpJLcfqi/Bl1NfJ1fRACxs3B5iklfA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgrzO9%2FbtsMpJLcfqi%2FBl1NfJ1fRACxs3B5iklfA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;820&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://www.lambdatest.com/blog/appium-architecture/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appium의 아키텍처는 위 그림과 같이 구성되어 있다. 각 구성요소를 살펴보자면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Appium Client: 사용자가 작성한 테스트 스크립트를 서버로 전달하는 역할
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python, Java 등 다양한 언어의 스크립트 가능&lt;/li&gt;
&lt;li&gt;JSON Wire Protocol 또는 W3C WebDriver 표준을 사용해 서버와 통신&lt;/li&gt;
&lt;li&gt;테스트 스크립트에는 디바이스 정보와 테스트 명령이 함께 포함됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Appium Server: 클라이언트로부터 받은 스크립트를 해석하고, 디바이스에서 실행되도록 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js로 구현된 HTTP 서버&lt;/li&gt;
&lt;li&gt;클라이언트로 받은 요청을 각 플랫폼(Android/iOS)에 맞는 명령으로 변환&lt;/li&gt;
&lt;li&gt;Android는 UiAutomator2, iOS는 XCUITest와 같은 드라이버를 통해 디바이스와 상호작용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;End Device(디바이스): 테스트가 실행되는 실제 환경
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 디바이스 연결 or 에뮬레이터(시뮬레이터)에서 테스트를 수행&lt;/li&gt;
&lt;li&gt;Android는 UiAutomator2, iOS는 XCUITest와 같은 드라이버로 명령을 처리함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Appium 설치 및 세팅&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 설치 과정은 Ubuntu 24.04에서 진행했으며, &lt;a href=&quot;https://jay-ji.tistory.com/122&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Fitware Jay님의 블로그 글&lt;/a&gt;을 참고했다. 이 글에서는 Android 테스트에 대해서 다룰 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Appium 서버 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appium을 설치하기 전에, node.js, npm(서버 설치에 필요), JDK(appium은 java기반이므로), ADB(Android 디바이스 통신에 필요) 소프트웨어 설치가 필요하다. 다음 명령어로 설치 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1740039496721&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update

# Node.js 및 npm 설치
sudo apt install -y nodejs npm

# Java JDK 설치
sudo apt install -y openjdk-11-jdk

# ADB 설치
sudo apt install -y adb&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 npm을 통해 Appium 서버를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740039536721&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo npm install -g appium

# 설치 확인
appium --version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Appium Inspector 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 요소를 식별, 테스크 스크립트 생성 및 디버깅 등의 기능을 제공하는 Appium Inspector를 설치한다. 설치 파일은 &lt;a href=&quot;https://github.com/appium/appium-inspector/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Appium Inspector 공식 Github&lt;/a&gt;에서 다운로드 받을 수 있으며, Ubuntu의 경우 .AppImage 파일을 다운로드해 실행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740039858913&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실행 권한 부여
chmod a+x ~/Downloads/Appium-Inspector-&amp;lt;version&amp;gt;-linux-x86_64.AppImage

# 실행
~/Downloads/Appium-Inspector-&amp;lt;version&amp;gt;-linux-x86_64.AppImage&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ubuntu 환경에서는 데스크톱 바로가기가 따로 생성되지 않기 때문에, 바로가기가 필요하다면 아래 과정을 통해 생성할 수 있다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로가기 생성하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. .desktop 파일 생성&lt;/p&gt;
&lt;pre id=&quot;code_1740039990196&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vim /opt/share/applications/appium-inspector.desktop&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. .desktop 파일에 아래 내용 추가 (--no-sandbox 옵션은 Ubuntu 24.04 보안이슈로 인해 사용)&lt;/p&gt;
&lt;pre id=&quot;code_1740040020773&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Desktop Entry]
Version=1.0
Name=Appium Inspector
Exec=/path/to/Appium-Inspector-&amp;lt;version&amp;gt;-linux-x86_64.AppImage --no-sandbox # 파일경로
Icon=/path/to/icon.png # 아이콘 이미지 경로
Type=Application
Categories=Development;
Terminal=false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 파일 저장 및 실행 권한 부여&lt;/p&gt;
&lt;pre id=&quot;code_1740040117311&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;chmod +x /opt/share/applications/appium-inspector.desktop&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 애플리케이션 메뉴 확인&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Appium Python Client 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python 언어로 된 스크립트를 읽을 수 있는 Appium Python Client 설치가 필요하다. 만약 다른 언어를 사용한다면, 해당 언어에 맞는 클라이언트 설치가 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1740040370321&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install Appium-Python-Client&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. Appium 드라이버 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android에 사용하는 UiAutomator2, iOS에 사용하는 XCUITest 드라이버를 설치한다. 필요할 경우 Flutter 앱과 Windows 앱 테스트에 사용하는 드라이버도 설치할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740040896537&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# UiAtuomator2 설치
appium driver install uiautomator2

# XCUITest 설치
appium driver install xcuitest

# flutter 앱 테스트
appium driver install --source=npm appium-flutter-driver

# Windows 앱 테스트
appium driver install --source=npm appium-windows-driver&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. Android Studio 설치 &amp;amp; 에뮬레이터 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android 기기 연결 또는 가상 에뮬레이터 실행을 위해서 Android Studio를 설치한다. &lt;a href=&quot;https://developer.android.com/studio/install?hl=ko#linux&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 홈페이지&lt;/a&gt;에서 설치를 진행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Android SDK 경로를 설정해준다. 자신이 사용하는 쉘에 따라서 아래와 같이 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740043366163&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi ~/.zshrc # 또는 ~/.bash_profile

# 아래 내용 추가
export ANDROID_HOME=~/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 이후 Android Studio에서 가상 기기를 선택해 에뮬레이터를 생성할 수 있다. &lt;a href=&quot;https://developer.android.com/studio/run/emulator?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;를 통해 에뮬레이터를 만들고 관리하는 법이 소개되고 있으나, 구버전을 기준으로 설명하고 있는 듯하다. 현재(2025.2) 기준 Linux 최신 버전은 아래와 사진을 참고해 생성할 수 있다. (Android Studio 우측 메뉴 중 Device Manager 선택)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-20 17-57-58.png&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvK4yp/btsMq2iM3We/MvgLhKkEnGY8JR92qtD78K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvK4yp/btsMq2iM3We/MvgLhKkEnGY8JR92qtD78K/img.png&quot; data-alt=&quot;상단 + 버튼을 통해 새로운 에뮬레이터 생성이 가능하다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvK4yp/btsMq2iM3We/MvgLhKkEnGY8JR92qtD78K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvK4yp%2FbtsMq2iM3We%2FMvgLhKkEnGY8JR92qtD78K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;394&quot; height=&quot;270&quot; data-filename=&quot;스크린샷 2025-02-20 17-57-58.png&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;상단 + 버튼을 통해 새로운 에뮬레이터 생성이 가능하다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 실행&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Android 테스트 - 샘플 테스트 앱(apk)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android 앱 테스트를 위한 세팅을 마쳤다. 하지만 Appium으로 Android 앱을 테스트하기 위해선 .apk파일이 필요한데, 과거와 달리 요즘에는 .apk파일을 구하기가 꽤나 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사하게도, 최근 관심을 가지고 있는 &lt;a href=&quot;https://goddessbest-qa.tistory.com/263&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;QA를 재미있게 블로그에서 Sample Test App을 공유&lt;/a&gt;해주셨다. 해당 apk파일을 가지고 테스트를 진행해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Appium Inspector 실행해 UI Element 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 작성하기 전에, 앱의 UI Element가 어떤 값을 가졌는지 알아야한다. Appium Inspector를 실행하면 해당 요소들의 값을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Appium Server 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appium Inspector가 앱에 접근하기 위해서는 서버가 실행되어 있어야 한다. 터미널에 아래 명령어를 통해 서버를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740042650839&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;appium&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Appium Inspector 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-20 18-11-30.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RpEgA/btsMplD68jD/2xFVyI9wt1R8lIXj8c79i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RpEgA/btsMplD68jD/2xFVyI9wt1R8lIXj8c79i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RpEgA/btsMplD68jD/2xFVyI9wt1R8lIXj8c79i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRpEgA%2FbtsMplD68jD%2F2xFVyI9wt1R8lIXj8c79i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1128&quot; height=&quot;776&quot; data-filename=&quot;스크린샷 2025-02-20 18-11-30.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Appium Inspector를 실행시키면 위와 같은 화면이 노출된다. Host, Port 등은 default 세팅이니 이 과정에서는 그대로 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 JSON Representation 부분에 아래와 같이 Capability를 정의해주어야 한다. (실행 시에는 주석 제거)&lt;/p&gt;
&lt;pre id=&quot;code_1740042812696&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;platformName&quot;: &quot;Android&quot;, 
  &quot;appium:automationName&quot;: &quot;uiautomator2&quot;, // 안드로이드 전용 드라이버
  &quot;appium:deviceName&quot;: &quot;emulator-5554&quot;, // 안드로이드 에뮬레이터는 이 이름으로 고정
  &quot;appium:app&quot;: &quot;.../app.apk&quot; // apk 경로
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740042833320&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// iOS 기기
{
  &quot;platformName&quot;: &quot;ios&quot;,
  &quot;appium:automationName&quot;: &quot;XCUITest&quot;,  // ios driver
  &quot;appium:platformVersion&quot;: &quot;17.2&quot;,
  &quot;appium:deviceName&quot;: &quot;iPhone12&quot;,
  &quot;appium:app&quot;: &quot;../app.ipa&quot;, // 앱 경로
  &quot;appium:wdaBaseUrl&quot;: &quot;http://xxx.xxx.xxx.xxx:8100&quot;, // wda 로컬 네트워크
  &quot;appium:xcodeSigningId&quot;: &quot;iPhone Developer&quot;,
  &quot;appium:xcodeOrgId&quot;: &quot;&quot;,   // xcode org id
  &quot;appium:udid&quot;: &quot;&quot;  // device UDID
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1740042877933&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// iOS 시뮬레이터
{
  &quot;platformName&quot;: &quot;ios&quot;,
  &quot;appium:automationName&quot;: &quot;XCUITest&quot;,
  &quot;appium:platformVersion&quot;: &quot;17.2&quot;,
  &quot;appium:udid&quot;: &quot;&quot;,
  &quot;appium:deviceName&quot;: &quot;simulator&quot;,
  &quot;appium:bundleId&quot;: &quot;&quot; # 앱 번들 ID
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &quot;Start Session&quot; 버튼을 클릭하면 에뮬레이터에 앱이 설치되고, Appium Inspector에 앱 화면이 캡처된 형태로 보이게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-20 18-16-54.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kKdLP/btsMoPlnWXn/8JJkBz97laLH5K1M73tha0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kKdLP/btsMoPlnWXn/8JJkBz97laLH5K1M73tha0/img.png&quot; data-alt=&quot;선택한 UI Element의 세부 속성 정보도 알 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kKdLP/btsMoPlnWXn/8JJkBz97laLH5K1M73tha0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkKdLP%2FbtsMoPlnWXn%2F8JJkBz97laLH5K1M73tha0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1128&quot; height=&quot;776&quot; data-filename=&quot;스크린샷 2025-02-20 18-16-54.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;선택한 UI Element의 세부 속성 정보도 알 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 스크립트 작성하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 알아낸 UI Element를 기반으로 테스트 스크립트를 작성할 수 있다. 테스트 스크립트는 &lt;a href=&quot;https://github.com/JAY-Chan9yu/appium-python-example&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Fitware-Jay님의 Github 코드&lt;/a&gt;를 참고해 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 테스트 실행에 필요한 패키지를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740043685374&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install pipenv
pipenv install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 실행하는 경우 아래 환경변수를 설정한다. (DeviceFarm에서 사용하는 경우 별도의 Capability 설정이 필요없음)&lt;/p&gt;
&lt;pre id=&quot;code_1740043737244&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export use_device_farm=false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 아래 명령어를 통해 테스트를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740043762930&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 전체 테스트 실행
pipenv run python -m pytest tests/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하면 아래 영상과 같이 테스트가 진행되는 것을 알 수 있다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/453155587&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bo7gd6/hyYjMnL8AR/vVboimzrvt6AUkjmKxlzSK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/j44Qa/hyYjt2RwbS/W3WQQP4RcpXZD50JWrvXbk/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;'Today's Minding'에서 업로드한 동영상&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/453155587?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고) 영상에서 디바이스 화면을 볼 수 있는 기능은 VSCode의 확장 기능 중 하나인 Android iOS Emulator이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;NEXT STEP&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Appium을 간단하게 사용해보는 과정을 다뤄봤다. 다음 글에서는 iOS와 함께 다중 기기를 다루거나, 심화된 테스트 과정을 진행하는 법에 대해서 공부한 뒤 소개해보려고 한다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>android studio</category>
      <category>android 테스트</category>
      <category>appium</category>
      <category>appium inspector</category>
      <category>ios 테스트</category>
      <category>uiautomator2</category>
      <category>xcuitest</category>
      <category>앱 ui 테스트</category>
      <category>앱테스트</category>
      <category>테스트 자동화</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/289</guid>
      <comments>https://minding-deep-learning.tistory.com/289#entry289comment</comments>
      <pubDate>Thu, 20 Feb 2025 18:36:10 +0900</pubDate>
    </item>
    <item>
      <title>[프로젝트] Catch Me My Capital - 합리적인 투자 의사결정을 위한 금융 데이터 파이프라인 및 백테스팅 도구 (테스트 도입 편)</title>
      <link>https://minding-deep-learning.tistory.com/288</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://minding-deep-learning.tistory.com/283&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 프로젝트 소개 글&lt;/a&gt;에서 NEXT STEP 중 하나였던 테스트 도구 추가를 진행 중이다. 테스트 도입 계획부터 어떤 문제를 겪었고 어떻게 해결했는지를 이 글에서 다뤄보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 도입 계획&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 도입하기에 앞서, 다음과 같은 항목들을 우선해서 선정했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;어떤 것을 테스트할 것인지?&lt;/li&gt;
&lt;li&gt;어떤 도구(라이브러리)를 사용할 것인지?&lt;/li&gt;
&lt;li&gt;테스트 코드는 어디에, 어떤 방식으로 구성할 것인지?&lt;/li&gt;
&lt;li&gt;테스트는 어떻게 실행할 것인지?&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 어떤 것을 테스트해야 할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Catch Me My Capital(이하 CMMC) 프로젝트는 기본적으로 금융 데이터를 수집한 뒤 적절히 변환해 데이터를 저장하고, 이를 대시보드로 시각화하는 &quot;데이터 파이프라인&quot;이 중심이다. 따라서, 데이터 파이프라인이 정상적으로 잘 작동하는지 판단할 수 있는 요소들이 테스트 대상이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 연결 및 응답 (Ingestion)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연결 상태 확인&lt;/li&gt;
&lt;li&gt;정상 연결시 데이터 응답값 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DAG 내 사용되는 함수 (Bronze &amp;amp; Silver Layer)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 수집 및 처리함수&lt;/li&gt;
&lt;li&gt;데이터 저장함수&lt;/li&gt;
&lt;li&gt;Glue Crawler 실행함수&lt;/li&gt;
&lt;li&gt;Glue job 실행함수&lt;/li&gt;
&lt;li&gt;Custom Operator에서 실행되는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Glue ETL job에 사용되는 spark script (Silver Layer)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 변환 함수&lt;/li&gt;
&lt;li&gt;데이터 저장 함수(to Redshift)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dbt (SQL 쿼리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 데이터 수집의 원천인 API가 정상적으로 잘 작동하는지 확인할 필요가 있다고 판단했다. API가 수정되었거나, 정상적인 응답 상태가 아니라면 뒤에서 실행될 코드의 의미가 사라지기 때문이다. API에 요청을 보내고 해당 응답의 상태 코드와 데이터를 검증하는 테스트가 필요하다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로 Airflow 내 DAG에서 실행되는 함수 로직이 의도한대로 동작하는지에 대한 테스가 필요하다고 판단했다. 현재는 데이터 파이프라인이 정상적으로 작동되는 것을 확인했기 때문에 해당 로직에는 문제가 없겠지만, 테스트의 목적은 '코드의 변경 가능성'까지 염두에 두고 진행하는 것이기 때문이다. 추후 해당 함수들의 코드가 변경되더라도, 의도한대로 작동되고 있는지에 검증이 필요하다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째로 Glue ETL job에 사용되는 스크립트에 대한 검증이 필요하다고 판단했다. 위와 마찬가지로, 스크립트 코드가 변경됐을 때에도 우리가 의도한대로 데이터를 변환하고 저장하고 있는지에 대한 검증이 필요하다. 특히 Bronze Layer와 달리 Redshift에 테이블 형태로 저장되기 때문에, 쿼리에 대한 검증도 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 dbt에 사용되는 SQL 쿼리에 대한 검증도 필요하다고 판단했다. Gold Layer로 저장되는 데이터들은 Redshift에서 전달받은 뒤 dbt를 통해 변환되기 때문에, 쿼리의 유효성과 함께 코드가 변경되더라도 일관적인 처리를 하고 있는지에 대한 테스트가 필요하다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 항목 중 Airflow DAG 실행 자체에 대한 테스트는 빠졌다. 물론 테스트가 가능하긴 하다. 그러나 Airflow는 Web UI를 통해 실행 결과를 쉽게 알 수 있고, 대부분의 코드가 AWS와 연결을 요구하기 때문에 테스트 환경에서는 매우 많은 mock(모킹)처리가 필요하다는 점 등의 리소스 문제가 있어 항목에서는 빠지게 되었다. 대신 DAG에 사용되는 모든 함수들을 검증하는 테스트가 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 어떤 도구(라이브러리)를 사용할 것인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 대상을 지정했다면, 이제는 어떤 도구를 사용해 어떻게 테스트할지 정해야 할 차례다. 위에서 정한 4가지 테스트 대상에 대해 아래와 같은 도구를 사용하기로 했다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;테스트 항목&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;테스트 도구&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;API 연결 및 응답&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Postman, Github Actions&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;postman script를 github actions를 통해 실행 및 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;DAG 내 사용되는 함수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;pytest, moto&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;moto로 AWS 서비스 모킹 및 pytest로 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Glue ETL job에 사용되는 spark script&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;pytest, pyspark, moto&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;spark 코드는 pyspark로, moto로 AWS 서비스 모킹 및 pytest로 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;dbt&amp;nbsp;(SQL&amp;nbsp;쿼리)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;dbt test&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;dbt test 기능으로 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Postman&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 API 연결 및 응답 테스트에는 Postman을 사용했다. 데이터 소스를 찾는 과정에서 Postman에 Collection 형태로 저장해 둔 덕에 쉽고 빠르게 테스트 환경을 만들 수 있었고, &lt;a href=&quot;https://minding-deep-learning.tistory.com/286&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Postman API를 통해 Github Actions에서 자동화&lt;/a&gt;도 가능했기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;pytest&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG 내 사용되는 함수들과 Glue ETL job의 spark script는 기본적으로 pytest 라이브러리를 사용해 테스트를 진행했다. pytest는 간결하면서도 pytest-cov등의 풍부한 플러그인을 가지고 있어 실제 현업에서도 많이 쓰이는 라이브러리이며, 단위/API/E2E 등 다양한 테스트 유형을 가지고 있어 선택하게 되었다. 특히, 이미 만들어진 기능에 테스트를 더하는 것이기 때문에 pytest의 fixture 기능을 통해 테스트 환경을 용이하게 설정할 수 있다는 점을 높게 평가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;moto&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAG 또는 Glue에서 실행되는 함수들은 대부분 AWS 서비스와의 연결을 필요로한다. 하지만 테스트는 '함수' 자체가 잘 작동하는 것에 중점을 두고 있고, 인터넷과 보안 키 등이 없는 상황에서도 실행되어야 하기 때문에 해당 함수들을 그대로 사용할 수 없다. 또한 테스트 과정에서 실제 데이터 파이프라인에 불필요한 데이터가 저장될 수도 있다. moto 라이브러리를 이용하면, AWS 서비스를 모킹(mock: 모조품/모의객체)해 사용할 수 있어 실제 AWS 서비스와의 연결 없이 테스트 환경을 구축할 수 있다. (boto3를 mock 처리하면 --&amp;gt; moto)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;dbt test&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dbt 라이브러리는 dbt test라는 명령어를 통해 해당 쿼리가 정상적으로 작성되었는지에 대한 검증을 할 수 있다. 해당 명령어와 github actions를 통해 테스트를 자동화하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 테스트 코드는 어디에, 어떤 방식으로 구성할 것인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드가 실제 데이터 파이프라인이 작동되는 코드와 함께 있을 경우 헷갈릴 뿐더러 충돌 위험성이 있다고 생각했다. 따라서, 테스트 코드 디렉토리를 따로 분리해 아래와 같은 방식으로 구성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1739853502617&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;├── pytest.ini # 테스트 설정 파일
└── tests
    ├── conftest.py # pytest 공통 설정 및 기능 - fixture 등 (하위 디렉토리에서 모두 사용가능)
    └── unit
        ├── extractors # 수집 및 저장 함수
        │   ├── test_coin_extractor.py
        │   ├── test_exchange_rate_extractor.py
        │   ├── ...
        ├── oprators # custom operator ㅎ마수
        │   ├──  test_yf_operator.py
        │   ├── ...        
        ├── glue_scripts # glue script 함수
        │   ├── test_coin_script.py
        │   ├── ...  
        ├── dbt
        │   ├── test_dbt.py        
        │   └── ...
        └── utils # 공통 사용 함수(s3 파일 저장 함수)
            └── test_s3_utils.py&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pytest.ini: Pytest의 설정 파일로, 테스트 실행 시 필요한 여러 설정을 미리 정의한다. 테스트 파일 패턴을 지정하고, 기본 실행 옵션 및 환경 변수와 플러그인 등을 설정할 수 있다.&lt;/li&gt;
&lt;li&gt;tests/conftest.py: Pytest에서 공통적으로 사용되는 설정과 기능을 정의한다. Pytest가 자동으로 감지하므로 별도의 import없이도 여기에 정의된 기능(함수)을 하위 디렉토리의 테스트 코드에서 사용할 수 있다. 주로 fixture를 관리하고, 테스트 실행 전후의 작업을 세팅할 수 있다.(Hook 함수)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 테스트는 어떻게 실행할 것인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postman, pytest, dbt등의 테스트 코드는 Github Actions 기능을 통해 CI 파이프라인을 설정한다. 해당 테스트들이 작동하는 workflow 파일을 생성해둔다면, Github에 새로운 코드가 push되거나 PR이 생성될 때 자동으로 테스트를 실행하고 결과를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 테스트 도입 환경 구축에 대해 작성했다. 다음 포스팅에서는 해당 테스트 코드들이 어떻게 작성되었고, 어떤 테스트 케이스(TC)를 포함하고 있는지에 대한 글을 작성해보려고 한다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>dbt</category>
      <category>github actions</category>
      <category>github test</category>
      <category>github workflow</category>
      <category>MOTO</category>
      <category>Postman</category>
      <category>pytest</category>
      <category>데이터 파이프라인</category>
      <category>데이터 파이프라인 테스트</category>
      <category>테스트</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/288</guid>
      <comments>https://minding-deep-learning.tistory.com/288#entry288comment</comments>
      <pubDate>Tue, 18 Feb 2025 14:13:29 +0900</pubDate>
    </item>
    <item>
      <title>[QA/Testing] Charles를 이용해 테스트해보기 (Throttle test, Breakpoint test)</title>
      <link>https://minding-deep-learning.tistory.com/287</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Charles Setting&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Charles Proxy tool을 이용하기 위해서는 설치 및 일부 세팅이 필요하다. 세팅 과정은 &lt;a href=&quot;https://techblog.gccompany.co.kr/charles-proxy-%EC%86%8C%EA%B0%9C-4c4a3bbc8994&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기어때 기술블로그&lt;/a&gt;를 참고했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Charles Proxy Tool 다운로드 및 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.charlesproxy.com/download/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 설치 문서&lt;/a&gt;에서 Charles를 다운로드 받을 수 있다. 자신의 OS에 맞는 프로그램을 선택해 다운로드 및 설치하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이선스를 구매하지 않을 경우 30일 동안 무료로 사용 가능하다. (+ 1세션 당 30분까지 사용 가능)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNEYKQ/btsMkILJthc/kk8vIHStEFRikC1rQiCA71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNEYKQ/btsMkILJthc/kk8vIHStEFRikC1rQiCA71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNEYKQ/btsMkILJthc/kk8vIHStEFRikC1rQiCA71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNEYKQ%2FbtsMkILJthc%2Fkk8vIHStEFRikC1rQiCA71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;481&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Chales PC 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되었다면, PC 프로그램 상 몇 가지 설정이 필요하다. 먼저 CA 인증서를 설치해주어야 한다. 내가 사용하는 OS인 Windows 환경을 예시로 소개하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인증서 설치&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Charles의 Help 메뉴에서 SSL Proxying &amp;gt; Install Charles Root Certificate 클릭&lt;/li&gt;
&lt;li&gt;시스템에서&amp;nbsp;Charles&amp;nbsp;Procy&amp;nbsp;CA&amp;nbsp;신뢰할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;인증서&amp;nbsp;설치&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C3tGo/btsMjGnXw5X/kT5uMgUkFPbuD1X7KbE5Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C3tGo/btsMjGnXw5X/kT5uMgUkFPbuD1X7KbE5Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C3tGo/btsMjGnXw5X/kT5uMgUkFPbuD1X7KbE5Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC3tGo%2FbtsMjGnXw5X%2FkT5uMgUkFPbuD1X7KbE5Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;397&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9oPVI/btsMjMuSTl2/qJ66WZmiTQbZM4v16FHVM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9oPVI/btsMjMuSTl2/qJ66WZmiTQbZM4v16FHVM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9oPVI/btsMjMuSTl2/qJ66WZmiTQbZM4v16FHVM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9oPVI%2FbtsMjMuSTl2%2FqJ66WZmiTQbZM4v16FHVM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;470&quot; height=&quot;587&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;PC 네트워킹 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워킹을 기록하고 테스트하기 위한 Charles 설정이 필요하다. 다음 순서를 따라 설정하면 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Charles의&amp;nbsp;Proxy&amp;nbsp;메뉴에서&amp;nbsp;Recording&amp;nbsp;Settings&amp;nbsp;진입&lt;/li&gt;
&lt;li&gt;include에 * Add, Exclude 에는 공란 후 OK 버튼 클릭 (특정 URL만 기록하길 원한다면 해당 URL을 입력하면 된다.)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;Proxy 메뉴에서 Windows Proxy MacOS Proxy 체크 표시 확인 (만약 PC 네트워킹을 추적하길 원하지 않는다면 해제)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;Proxy 메뉴에서 Proxy Settings 클릭 후 포트 설정(기본: 8888)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;Help&amp;nbsp;메뉴에서&amp;nbsp;Local&amp;nbsp;IP&amp;nbsp;Address의&amp;nbsp;노트북/PC의&amp;nbsp;IP&amp;nbsp;주소&amp;nbsp;확인&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R3scG/btsMk33cNSq/0IdMMhwC5gLeBVEanIefMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R3scG/btsMk33cNSq/0IdMMhwC5gLeBVEanIefMK/img.png&quot; data-alt=&quot;1~2번&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R3scG/btsMk33cNSq/0IdMMhwC5gLeBVEanIefMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR3scG%2FbtsMk33cNSq%2F0IdMMhwC5gLeBVEanIefMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;363&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1~2번&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nkBRy/btsMkuUzXO6/q0SIZDGxmxXkzjyp0cKkw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nkBRy/btsMkuUzXO6/q0SIZDGxmxXkzjyp0cKkw1/img.png&quot; data-alt=&quot;3번&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nkBRy/btsMkuUzXO6/q0SIZDGxmxXkzjyp0cKkw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnkBRy%2FbtsMkuUzXO6%2Fq0SIZDGxmxXkzjyp0cKkw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;304&quot; height=&quot;512&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;3번&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mbqeC/btsMkXvaiR2/gWvrKyGX8k6BqDAsLFx6AK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mbqeC/btsMkXvaiR2/gWvrKyGX8k6BqDAsLFx6AK/img.png&quot; data-alt=&quot;4번&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mbqeC/btsMkXvaiR2/gWvrKyGX8k6BqDAsLFx6AK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmbqeC%2FbtsMkXvaiR2%2FgWvrKyGX8k6BqDAsLFx6AK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;467&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;4번&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;167&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqKpgZ/btsMjP5Yckw/I6G1SXrucTGBf21u3yfZd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqKpgZ/btsMjP5Yckw/I6G1SXrucTGBf21u3yfZd1/img.png&quot; data-alt=&quot;5번&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqKpgZ/btsMjP5Yckw/I6G1SXrucTGBf21u3yfZd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqKpgZ%2FbtsMjP5Yckw%2FI6G1SXrucTGBf21u3yfZd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;167&quot; height=&quot;266&quot; data-origin-width=&quot;167&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5번&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 모바일(iOS/Android) 연결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC의 Charles에서 모바일의 네트워킹을 기록하고 테스트하기 위해서는 우선 같은 네트워크에 연결해주어야 한다.(LAN 또는 Wi-fi) Android 기기를 예시로 소개하겠다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;노트북/PC와 동일한 Wi-Fi를 디바이스에 연결 후 위에서 Wi-Fi의 프록시 설정을 수동으로 설정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;위에서 설정한 포트 번호와 Local IP Address를 수동으로 저장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;사파리&amp;nbsp;혹은&amp;nbsp;크롬&amp;nbsp;웹브라우저에서&amp;nbsp;&lt;a href=&quot;https://chls.pro/ssl&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chls.pro/ssl&lt;/a&gt;&amp;nbsp;입력&amp;nbsp;후&amp;nbsp;검색하여&amp;nbsp;프로파일&amp;nbsp;다운로드&amp;nbsp;허용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;다운로드 받은 인증서 설치(가져오기)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;Charles에 디바이스의 통신 내역이 기록되면 성공&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFUxu9/btsMjHtDgfl/gkWrKHQ9WpHwKQ9Qfv1Px1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFUxu9/btsMjHtDgfl/gkWrKHQ9WpHwKQ9Qfv1Px1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFUxu9/btsMjHtDgfl/gkWrKHQ9WpHwKQ9Qfv1Px1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFUxu9%2FbtsMjHtDgfl%2FgkWrKHQ9WpHwKQ9Qfv1Px1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;486&quot; height=&quot;612&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdch0A/btsMjpNuPJC/3kokU3k2XnJRDfBosZchy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdch0A/btsMjpNuPJC/3kokU3k2XnJRDfBosZchy0/img.png&quot; data-alt=&quot;위와 같이 Charles에 디바이스 네트워킹이 기록되면 성공이다. (aladin 홈페이지를 진입한 모습)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdch0A/btsMjpNuPJC/3kokU3k2XnJRDfBosZchy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcdch0A%2FbtsMjpNuPJC%2F3kokU3k2XnJRDfBosZchy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;879&quot; height=&quot;200&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위와 같이 Charles에 디바이스 네트워킹이 기록되면 성공이다. (aladin 홈페이지를 진입한 모습)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 테스트 진행해보기 (Breakpoint test, Throttle test)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 2가지 테스트를 진행해보았다. HTTP(S) 요청을 가로채 파라미터 등을 원하는 값으로 수정할 수 있는 Breakpoint test와 네트워크 지연을 일으키는 Throttle test를 진행해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 테스트를 진행하기 위해서는 우선 테스트할 대상 URL을 SSL Proxying을 Enable로 설정해주어야 한다. 해당 URL에 우클릭 한뒤 'Enable SSL Proxying'을 설정해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y7se9/btsMi2rsDSb/07avtkEqb6sf1caKaaoFxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y7se9/btsMi2rsDSb/07avtkEqb6sf1caKaaoFxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y7se9/btsMi2rsDSb/07avtkEqb6sf1caKaaoFxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy7se9%2FbtsMi2rsDSb%2F07avtkEqb6sf1caKaaoFxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;290&quot; height=&quot;582&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Breakpoint test&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Breakpoint test를 위해서는 테스트할 대상에 대해 breakpoint를 설정해주어야 한다. 위에서 설정한 것과 마찬가지로, 해당 대상에 우클릭한 뒤 'Breakpoints'로 설정해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLKK6K/btsMlpEUKyM/RXQXcPyfvCI9vXpereRLkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLKK6K/btsMlpEUKyM/RXQXcPyfvCI9vXpereRLkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLKK6K/btsMlpEUKyM/RXQXcPyfvCI9vXpereRLkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLKK6K%2FbtsMlpEUKyM%2FRXQXcPyfvCI9vXpereRLkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;304&quot; height=&quot;682&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Charles 상단의 Enable Breakpoints 기능을 클릭해 활성화한 다음 해당 대상에 다시 한번 요청을 보내면, 요청(Request)을 수정할 수 있는 화면이 Charles에 노출된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jmolb/btsMljSgUwN/mdoywJI021FL4d07AzSyI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jmolb/btsMljSgUwN/mdoywJI021FL4d07AzSyI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jmolb/btsMljSgUwN/mdoywJI021FL4d07AzSyI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJmolb%2FbtsMljSgUwN%2FmdoywJI021FL4d07AzSyI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;270&quot; height=&quot;74&quot; data-origin-width=&quot;270&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eTTxaN/btsMjTHhoHa/jNJMwrSwXBKk8zrH9avdPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eTTxaN/btsMjTHhoHa/jNJMwrSwXBKk8zrH9avdPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eTTxaN/btsMjTHhoHa/jNJMwrSwXBKk8zrH9avdPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeTTxaN%2FbtsMjTHhoHa%2FjNJMwrSwXBKk8zrH9avdPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;869&quot; height=&quot;179&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 화면에서 요청을 원하는 값으로 수정할 수 있다. 내 경우에는 aladin 홈페이지에서 '한강'을 검색한 뒤, searchWord를 '무라카미 하루키'로 바꾸어 보는 테스트를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Throttle&amp;nbsp;test&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Throttle test는 네트워크 지연을 일으켜 결과를 살펴볼 수 있는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Proxy &amp;gt; Throttle Settings에서 속도, 대역폭 등 세부 설정을 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8wA0J/btsMkKpgwkV/Ig0aF2gaaO7viGWLaZ3KUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8wA0J/btsMkKpgwkV/Ig0aF2gaaO7viGWLaZ3KUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8wA0J/btsMkKpgwkV/Ig0aF2gaaO7viGWLaZ3KUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8wA0J%2FbtsMkKpgwkV%2FIg0aF2gaaO7viGWLaZ3KUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;524&quot; height=&quot;608&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 테스트는 Charles 상단의 Start Throttling을 클릭해 테스트를 활성화시킬 수 있다. 이후 해당 대상에 재요청을 보내면 네트워크 지연이 일어나는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIEPcW/btsMj5gnbDS/9HnKtA28i5Kz54Eja3GWA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIEPcW/btsMj5gnbDS/9HnKtA28i5Kz54Eja3GWA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIEPcW/btsMj5gnbDS/9HnKtA28i5Kz54Eja3GWA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIEPcW%2FbtsMj5gnbDS%2F9HnKtA28i5Kz54Eja3GWA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;216&quot; height=&quot;94&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 테스트 과정은 아래 영상을 통해 확인할 수 있다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/453039904&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dk34hv/hyYfVTdzjp/LlJx6EH4mCBOf5JPcRZMgk/img.jpg?width=1480&amp;amp;height=1020&amp;amp;face=0_0_1480_1020,https://scrap.kakaocdn.net/dn/c7NGQ3/hyYf0fUW2v/qlKTujyovxMbkD4ri8exak/img.jpg?width=1480&amp;amp;height=1020&amp;amp;face=0_0_1480_1020&quot; data-video-width=&quot;860&quot; data-video-height=&quot;593&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;593&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;'Today's Minding'에서 업로드한 동영상&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/453039904?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;593&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>breakpoint test</category>
      <category>Charles</category>
      <category>charles proxy tool</category>
      <category>charles setting</category>
      <category>charles 다운로드</category>
      <category>charles 설치</category>
      <category>charles 세팅</category>
      <category>QA</category>
      <category>throttle test</category>
      <category>모바일 테스트 자동화</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/287</guid>
      <comments>https://minding-deep-learning.tistory.com/287#entry287comment</comments>
      <pubDate>Sat, 15 Feb 2025 20:19:18 +0900</pubDate>
    </item>
    <item>
      <title>[Postman/Github Actions] Github Actions를 통해 API 테스트 자동화하기</title>
      <link>https://minding-deep-learning.tistory.com/286</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;API를 테스트하는 도구로 많은 사람들이 Postman을 사용한다. Postman에서 미리 스크립트를 작성해두면, API에 대한 테스트를 자동화시킬 수 있는데, Github Actions를 이용하면 Github에 배포할 때마다 테스트를 시도하게 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Postman에서 API 테스트를 자동화하는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 테스트 예시로 코인 데이터를 수집할 수 있는 Binance API를 사용해보았다. (코인 기본 데이터만 수집할 시 API 키는 필요없다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/binance/binance-spot-api-docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/binance/binance-spot-api-docs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739436164435&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - binance/binance-spot-api-docs: Official Documentation for the Binance Spot APIs and Streams&quot; data-og-description=&quot;Official Documentation for the Binance Spot APIs and Streams - GitHub - binance/binance-spot-api-docs: Official Documentation for the Binance Spot APIs and Streams&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/binance/binance-spot-api-docs&quot; data-og-url=&quot;https://github.com/binance/binance-spot-api-docs&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/g2Kr7/hyYakNuoVL/Av2nokk6VtoRPZlDtO2hGk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cSmPFs/hyYfDdyRRC/KtSSza6tLHqXwkS8Mwfab1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/binance/binance-spot-api-docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/binance/binance-spot-api-docs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/g2Kr7/hyYakNuoVL/Av2nokk6VtoRPZlDtO2hGk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cSmPFs/hyYfDdyRRC/KtSSza6tLHqXwkS8Mwfab1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - binance/binance-spot-api-docs: Official Documentation for the Binance Spot APIs and Streams&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Official Documentation for the Binance Spot APIs and Streams - GitHub - binance/binance-spot-api-docs: Official Documentation for the Binance Spot APIs and Streams&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Enviroments 설정하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는 기본적으로 특정 URL에 요청을 보내는 방식으로 작동하는데, 파라미터가 바뀔 경우마다 요청을 보내는 URL도 계속해서 바뀐다. 이 파라미터들을 환경 변수로 미리 설정해둔다면, 번거로운 작업을 피할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mHVG6/btsMgNU2UJU/MF291baWaewPuZFxBLq1dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mHVG6/btsMgNU2UJU/MF291baWaewPuZFxBLq1dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mHVG6/btsMgNU2UJU/MF291baWaewPuZFxBLq1dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmHVG6%2FbtsMgNU2UJU%2FMF291baWaewPuZFxBLq1dK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;295&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 만들어 둔 Collection 페이지로 진입한 뒤, 우측 상단 dropdown 리스트를 펼친 뒤, + 버튼을 클릭하면 새로운 Enviroments를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cK6gLs/btsMgvtyY3I/SH3TErh9HDJWkJF4UF6Ez0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cK6gLs/btsMgvtyY3I/SH3TErh9HDJWkJF4UF6Ez0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cK6gLs/btsMgvtyY3I/SH3TErh9HDJWkJF4UF6Ez0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcK6gLs%2FbtsMgvtyY3I%2FSH3TErh9HDJWkJF4UF6Ez0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;333&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Binance API 테스트에 사용할 변수를 미리 만들어 두었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;coin_symbols: 테스트할 코인의 심볼 리스트(고정)&lt;/li&gt;
&lt;li&gt;interval: 데이터 수집 주기 (1d(1일), 1m, 1y 등 / 고정)&lt;/li&gt;
&lt;li&gt;coin_symbol, currentIndex, start_timestamp는 테스트 스크립트에서 생성되는 변수로, 뒤에서 설명하겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Collection의 해당 API문서로 돌아와, Params 항목에 환경변수로 지정할 파라미터의 Value를 아래와 같이 바꿔준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCFRYW/btsMgKEbuXg/dJk8iDYM2JGZHanQNL61w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCFRYW/btsMgKEbuXg/dJk8iDYM2JGZHanQNL61w0/img.png&quot; data-alt=&quot;{{ value }} 형식으로 작성하면 된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCFRYW/btsMgKEbuXg/dJk8iDYM2JGZHanQNL61w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCFRYW%2FbtsMgKEbuXg%2FdJk8iDYM2JGZHanQNL61w0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;936&quot; height=&quot;302&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;{{ value }} 형식으로 작성하면 된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Test Script 작성하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Scripts 탭으로 진입해서 테스트 스크립트를 작성한다. 스크립트는 크게 두 가지로 나뉜다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEV1F1/btsMi39D6Nq/MgK5k9Z5SK2qvwYFGJobk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEV1F1/btsMi39D6Nq/MgK5k9Z5SK2qvwYFGJobk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEV1F1/btsMi39D6Nq/MgK5k9Z5SK2qvwYFGJobk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEV1F1%2FbtsMi39D6Nq%2FMgK5k9Z5SK2qvwYFGJobk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;310&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pre-request (요청 전 세팅해야 하는 것)&lt;/li&gt;
&lt;li&gt;Post-response (응답받은 후 실질적인 테스트 코드)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; Pre-request&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pre-request 스크립트에서는 주로 API 요청 전 설정해야 할 환경변수들에 대한 코드다. 코드는 javascripts로 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739438415872&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let symbols = JSON.parse(pm.environment.get(&quot;coin_symbols&quot;)); // JSON 문자열 -&amp;gt; 배열로 변환
let index = pm.environment.get(&quot;currentIndex&quot;) || 0; // 초기값 0으로 설정
let now = new Date();
let yesterday = new Date(now.getTime() - (24 * 60 * 60 * 1000)); // 하루 전 시간 계산

// 어제 날짜를 밀리초 단위 UNIX Timestamp로 변환
let yesterdayTimestamp = yesterday.getTime();

if (index &amp;lt; symbols.length) {
    pm.environment.set(&quot;coin_symbol&quot;, symbols[index]);
    pm.environment.set(&quot;currentIndex&quot;, index + 1);
    pm.environment.set(&quot;start_timestamp&quot;, yesterdayTimestamp);
} else {
    pm.environment.set(&quot;currentIndex&quot;, 0); // 초기화
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 경우에는 조회할 코인의 심볼이 여러 개이기 때문에, indexing(currentIndex)을 통해서 순서대로 coin을 조회하도록 설정했다.(coin_symbol) 또한 1일 전의 날짜를 계산하고, 이를 UNIX Timestamp 형식으로 변환해 start_timestamp 변수에 값을 입력해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Post-response&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Post-response는 응답받은 값을 기준으로 실질적인 테스트가 이루어진다.&lt;/p&gt;
&lt;pre id=&quot;code_1739438912881&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 응답예시

[
    [
        1739404800000,
        &quot;0.79980000&quot;,
        &quot;0.80390000&quot;,
        &quot;0.76930000&quot;,
        &quot;0.78110000&quot;,
        &quot;50719753.50000000&quot;,
        1739491199999,
        &quot;39749505.64638000&quot;,
        206845,
        &quot;24925356.00000000&quot;,
        &quot;19531214.66539000&quot;,
        &quot;0&quot;
    ]
]&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1739438843878&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pm.test(&quot;Status code is 200&quot;, function () {
    pm.response.to.have.status(200);
});

pm.test(&quot;Response contains valid data&quot;, function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData).to.be.an('array');
    pm.expect(jsonData[0]).to.have.lengthOf(12); // Kline 데이터 배열 길이 확인
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 2개의 테스트 코드를 작성했다. 첫 번째는 응답코드가 정상(200)인지를 확인하고, 두 번째는 해당 데이터가 array 형태인지와, 데이터 배열의 길이가 12인지를 확인하는 코드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 자체에 대해 더 깊게 들어가 테스트해볼 수도 있다. 아래 코드와 같이 데이터의 key를 확인하거나, 타입 확인 또는 값의 크기를 확인해볼 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739438861834&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pm.test(&quot;Status code is 200&quot;, function () { // 응답 코드 확인 테스트
    pm.response.to.have.status(200);
});

pm.test(&quot;Response has correct structure and types&quot;, function () { // 데이터 구조 및 타입 확인 테스트
    let data = pm.response.json();
    pm.expect(data).to.be.an('array'); // 응답이 배열인지 확인
    pm.expect(data[0]).to.have.keys(&quot;Open&quot;, &quot;Close&quot;, &quot;High&quot;, &quot;Low&quot;, &quot;Volume&quot;, &quot;Date&quot;); // 키 존재 여부 확인
    
    // 데이터 타입 확인
    pm.expect(data[0].Open).to.be.a('number');
    pm.expect(data[0].Close).to.be.a('number');
    pm.expect(data[0].High).to.be.a('number');
    pm.expect(data[0].Low).to.be.a('number');
    pm.expect(data[0].Volume).to.be.a('string'); // 빈 문자열도 string으로 간주됨
    pm.expect(data[0].Date).to.be.a('string');
});

pm.test(&quot;Business logic validation&quot;, function () { // 데이터 값 확인테스트 (High 기준)
    let data = pm.response.json();
    
    // High는 항상 Open, Close, Low보다 크거나 같아야 함
    let high = data[0].High;
    let open = data[0].Open;
    let close = data[0].Close;
    let low = data[0].Low;
    
    pm.expect(high).to.be.at.least(open);
    pm.expect(high).to.be.at.least(close);
    pm.expect(high).to.be.at.least(low);
    
    // Low는 항상 Open, Close보다 작거나 같아야 함
    pm.expect(low).to.be.at.most(open);
    pm.expect(low).to.be.at.most(close);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Test 실행해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 실행해보기 위해서는 해당 Collection의 추가 옵션(...)버튼을 누른 다음 'Run collection'을 클릭한 뒤,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;198&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsu81G/btsMgLJZokr/knfL8apiV2nCK2KZU4XP5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsu81G/btsMgLJZokr/knfL8apiV2nCK2KZU4XP5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsu81G/btsMgLJZokr/knfL8apiV2nCK2KZU4XP5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsu81G%2FbtsMgLJZokr%2FknfL8apiV2nCK2KZU4XP5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;198&quot; height=&quot;447&quot; data-origin-width=&quot;198&quot; data-origin-height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하단의 Run 버튼을 누르면 테스트가 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boocZt/btsMh5UNazP/a49ntKG3ztZQ0RjOo0MZt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boocZt/btsMh5UNazP/a49ntKG3ztZQ0RjOo0MZt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boocZt/btsMh5UNazP/a49ntKG3ztZQ0RjOo0MZt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboocZt%2FbtsMh5UNazP%2Fa49ntKG3ztZQ0RjOo0MZt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;650&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Github Actions으로 Postman CLI 이용한 테스트 자동화하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 각 Collection 별로 테스트 스크립트 작성을 마치고 실행까지 확인했다면, 이제 Github 배포 시에 자동으로 작동할 수 있도록 설정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postman에서는 API를 통해 해당 계정의 Collection에 저장된 테스트 코드들을 실행시킬 수 있다. Github Actions에서 Postman CLI를 사용한다면, collection을 실행시키고 그 결과를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Postman API Key 발급 &amp;amp; Github Secrets 등록&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postman API를 사용하기 위해서는 API Key를 발급받아야 한다. &lt;a href=&quot;https://web.postman.co/settings/me/api-keys&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 통해 Postman에 접속한 뒤 API KEY를 발급받자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 해당 테스트를 실행시킬 Github Repository에 진입한 후 해당 API KEY를 Secrets에 등록해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions &amp;gt; New repository secrets 경로를 통해 등록해줄 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ott0Y/btsMhPq5qTa/71vHPD7CkHKNR9m0cVhlfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ott0Y/btsMhPq5qTa/71vHPD7CkHKNR9m0cVhlfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ott0Y/btsMhPq5qTa/71vHPD7CkHKNR9m0cVhlfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fott0Y%2FbtsMhPq5qTa%2F71vHPD7CkHKNR9m0cVhlfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1444&quot; height=&quot;860&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XloAR/btsMiZzxWtB/5e3VzO9vzzt8fKXec8dW70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XloAR/btsMiZzxWtB/5e3VzO9vzzt8fKXec8dW70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XloAR/btsMiZzxWtB/5e3VzO9vzzt8fKXec8dW70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXloAR%2FbtsMiZzxWtB%2F5e3VzO9vzzt8fKXec8dW70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;791&quot; height=&quot;138&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Github Actions로 Postman CLI 실행하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 repository의 Actions 탭으로 이동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mtcYR/btsMiaPpDA8/HbX7oZ6yIZ2DSLzKmb0Rk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mtcYR/btsMiaPpDA8/HbX7oZ6yIZ2DSLzKmb0Rk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mtcYR/btsMiaPpDA8/HbX7oZ6yIZ2DSLzKmb0Rk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmtcYR%2FbtsMiaPpDA8%2FHbX7oZ6yIZ2DSLzKmb0Rk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;807&quot; height=&quot;58&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;New workflow 클릭 (처음 만드는거라면 UI가 다르게 나올 수 있다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/plwPG/btsMh7d6xeo/JtkmWfeQpmOUFANKhCrZrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/plwPG/btsMh7d6xeo/JtkmWfeQpmOUFANKhCrZrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/plwPG/btsMh7d6xeo/JtkmWfeQpmOUFANKhCrZrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FplwPG%2FbtsMh7d6xeo%2FJtkmWfeQpmOUFANKhCrZrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;244&quot; height=&quot;436&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github에서 제공하는 여러 확장기능을 사용할 수 있긴하지만, 나는 'set up a workflow yourself'를 통해 직접 코드를 작성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xjdtf/btsMi1qJyBP/As2y1fuYgcVTEGrbXs3oqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xjdtf/btsMi1qJyBP/As2y1fuYgcVTEGrbXs3oqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xjdtf/btsMi1qJyBP/As2y1fuYgcVTEGrbXs3oqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXjdtf%2FbtsMi1qJyBP%2FAs2y1fuYgcVTEGrbXs3oqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;907&quot; height=&quot;683&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래와 같이 .github/workflows 폴더 내 yml 파일을 작성할 수 있는 창이 노출된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB5yTm/btsMgMhLBn4/pvi1xwMebQTj5DOliKQN61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB5yTm/btsMgMhLBn4/pvi1xwMebQTj5DOliKQN61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB5yTm/btsMgMhLBn4/pvi1xwMebQTj5DOliKQN61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB5yTm%2FbtsMgMhLBn4%2Fpvi1xwMebQTj5DOliKQN61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;897&quot; height=&quot;792&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1739457685558&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Run Multiple Postman Collections and Save Reports

on:
  push:
    branches:
      - main

jobs:
  postman-cli-tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        config:
          - { collection_id: &quot;40443175-0e230280-1eea-45ad-b7e4-2c6316a91b62&quot;, environment_id: &quot;40443175-1c302d5e-5930-4579-880f-ce404a2c4b29&quot; }
          - { collection_id: &quot;40443175-85560488-42ec-4d0e-b866-fda8ca231b15&quot;, environment_id: &quot;40443175-55f8a2ab-0dc1-48a9-8279-59b370c19d78&quot; }

    steps:
      # 코드 체크아웃
      - name: Checkout code
        uses: actions/checkout@v2

      # Postman CLI 설치
      - name: Install Postman CLI
        run: |
          curl -o- &quot;https://dl-cli.pstmn.io/install/linux64.sh&quot; | sh
      # Postman CLI 로그인
      - name: Login to Postman CLI
        run: |
          postman login --with-api-key ${{ secrets.POSTMAN_API_KEY }}
      # Postman CLI 실행 및 리포트 생성
      - name: Run Collection and Generate Report
        run: |
          postman collection run ${{ matrix.config.collection_id }} \
            --environment ${{ matrix.config.environment_id }}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 부분을 나눠서 설명하자면, 아래 코드는 workflow와 어떤 상황에 이 workflow를 실행할지를 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739457835452&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 해당 workflow의 이름
name: Run Multiple Postman Collections and Save Reports

# 언제 실행할건지를 설정 - main 브랜치에 push할 때
on:
  push:
    branches:
      - main&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 postman CLI에서 test할 항목들을 설정해놓는 파트로, matrix 전략을 통해 해당 작업들을 동시에 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;a href=&quot;https://community.postman.com/t/pm-info-request-id-returns-partial-id/30905/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;collection_id와 environment_id는 Postman 웹이나 애플리케이션에서 확인&lt;/a&gt;할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739457898659&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jobs:
  postman-cli-tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        config:
          - { collection_id: &quot;40443175-0e230280-1eea-45ad-b7e4-2c6316a91b62&quot;, environment_id: &quot;40443175-1c302d5e-5930-4579-880f-ce404a2c4b29&quot; }
          - { collection_id: &quot;40443175-85560488-42ec-4d0e-b866-fda8ca231b15&quot;, environment_id: &quot;40443175-55f8a2ab-0dc1-48a9-8279-59b370c19d78&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 코드를 checkout하고, Postman CLI를 curl 명령을 통해 설치하는 과정이다.&lt;/p&gt;
&lt;pre id=&quot;code_1739458071539&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    steps:
      # 코드 체크아웃
      - name: Checkout code
        uses: actions/checkout@v2

      # Postman CLI 설치
      - name: Install Postman CLI
        run: |
          curl -o- &quot;https://dl-cli.pstmn.io/install/linux64.sh&quot; | sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 Postman CLI에 위에서 발급받고 secrets에 저장한 API KEY를 이용해 로그인한 후, CLI를 실행하는 부분이다.&lt;/p&gt;
&lt;pre id=&quot;code_1739458123523&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      # Postman CLI 로그인
      - name: Login to Postman CLI
        run: |
          postman login --with-api-key ${{ secrets.POSTMAN_API_KEY }}
      # Postman CLI 실행 및 리포트 생성
      - name: Run Collection and Generate Report
        run: |
          postman collection run ${{ matrix.config.collection_id }} \
            --environment ${{ matrix.config.environment_id }}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정한 뒤 Commit Changes를 클릭한다면, main 브랜치에 push되는 것이므로 자동으로 workflow가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Actions 탭에 진입해 결과를 살펴보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vhBy9/btsMjoFUPeu/yK4U3CkMob9Otb74RProhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vhBy9/btsMjoFUPeu/yK4U3CkMob9Otb74RProhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vhBy9/btsMjoFUPeu/yK4U3CkMob9Otb74RProhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvhBy9%2FbtsMjoFUPeu%2FyK4U3CkMob9Otb74RProhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;889&quot; height=&quot;453&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 테스트가 잘 실행된 것을 확인해볼 수 있다!&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>api테스트 자동화</category>
      <category>github actions</category>
      <category>github secrets</category>
      <category>Postman</category>
      <category>postman api</category>
      <category>postman api test automation</category>
      <category>postman api테스트</category>
      <category>postman cli</category>
      <category>postman cli github</category>
      <category>테스트 자동화</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/286</guid>
      <comments>https://minding-deep-learning.tistory.com/286#entry286comment</comments>
      <pubDate>Thu, 13 Feb 2025 18:48:56 +0900</pubDate>
    </item>
    <item>
      <title>[AWS RDS/MySQL] AWS RDS에서 MySQL 이벤트 스케줄러 설정하기</title>
      <link>https://minding-deep-learning.tistory.com/285</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MySQL 이벤트 스케줄러?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL의 이벤트 스케줄러는 특정 시간 간격 또는 지정된 시점에 SQL 쿼리를 자동으로 실행할 수 있도록 도와주는 기능으로, 이를 통해 특정 시간에 예약해야 하는 작업들을 반복적으로 실행할 수 있다. Cron과 유사한 역할이라고 보면 되며, DB 내에서 직접 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL의 장점은 MySQL 서버 내에서 쿼리가 자동으로 실행되기 때문에 외부의 서버 또는 애플리케이션을 통해 스크립트를 보내지 않아도 된다는 것이다. 클라이언트와도 독립적으로 실행되기 때문에, 간단한 작업의 경우 이벤트 스케줄러를 이용하는 장점이 극대화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;구성 요소&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 이벤트 스케줄러는 크게 '이벤트'와 '스케줄러' 2가지로 나뉘어져 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이벤트: 실행할 SQL 명령어 또는 명령어 블록이다.&lt;/li&gt;
&lt;li&gt;스케줄러: MySQL 서버에서 이벤트를 관리하고 실행하는 시스템 프로세스&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 스케줄러가 '이벤트'를 단위로 실행한다는 개념이다. 스케줄러는 기본적으로 비활성화 되어 있으며, 활성화를 위해서는 아래 명령어를 실행시켜야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739158812324&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET GLOBAL event_scheduler = ON;

-- 스케줄러 상태 확인
SHOW VARIABLES LIKE 'event_scheduler';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래와 같이 이벤트를 생성해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739158971523&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 반복 실행 이벤트 예시
CREATE EVENT daily_event
ON SCHEDULE EVERY 1 DAY
STARTS '2025-02-10 00:00:00'
DO
BEGIN
    -- 실행할 SQL 문
    DELETE FROM old_data WHERE created_at &amp;lt; NOW() - INTERVAL 30 DAY;
END;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AWS의 RDS에서는 MySQL의 시스템 설정을 직접 변경할 수는 없어 이벤트 스케줄러 설정이 불가능하다. 따라서 '파라미터 그룹(Parameter Group)을 통해 이벤트 스케줄러를 활성화시켜야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AWS RDS에서 이벤트 스케줄러 설정하기&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AIUQK/btsMcIFNGOj/4jSgK7EZcu3nSnqjdQeOeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AIUQK/btsMcIFNGOj/4jSgK7EZcu3nSnqjdQeOeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AIUQK/btsMcIFNGOj/4jSgK7EZcu3nSnqjdQeOeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAIUQK%2FbtsMcIFNGOj%2F4jSgK7EZcu3nSnqjdQeOeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;351&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 먼저 RDS 페이지에 접속해 '파라미터 그룹' 탭 - '파라미터 그룹 생성' 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlUE1G/btsMbXRa8Mi/sCeJCLI88xRpShd70OQK20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlUE1G/btsMbXRa8Mi/sCeJCLI88xRpShd70OQK20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlUE1G/btsMbXRa8Mi/sCeJCLI88xRpShd70OQK20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlUE1G%2FbtsMbXRa8Mi%2FsCeJCLI88xRpShd70OQK20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;937&quot; height=&quot;664&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 파라미터 그룹을 생성해준다. (DB 엔진 버전 확인)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1873&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9TS27/btsMd24toxD/QIldAkVoVr8nwe61SecSSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9TS27/btsMd24toxD/QIldAkVoVr8nwe61SecSSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9TS27/btsMd24toxD/QIldAkVoVr8nwe61SecSSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9TS27%2FbtsMd24toxD%2FQIldAkVoVr8nwe61SecSSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1873&quot; height=&quot;418&quot; data-origin-width=&quot;1873&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 생성한 파라미터 그룹의 상세 정보에 'event_scheduler'가 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xisPO/btsMdmvyL2V/kURjZSQOCkUOOvslnHOkfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xisPO/btsMdmvyL2V/kURjZSQOCkUOOvslnHOkfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xisPO/btsMdmvyL2V/kURjZSQOCkUOOvslnHOkfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxisPO%2FbtsMdmvyL2V%2FkURjZSQOCkUOOvslnHOkfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1602&quot; height=&quot;232&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. '편집'을 눌러 event_scheduler의 값을 'ON'으로 설정해주고 변경 사항을 저장해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ba07c/btsMedrajIM/dOKuPGhb30pGORoacl2mKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ba07c/btsMedrajIM/dOKuPGhb30pGORoacl2mKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ba07c/btsMedrajIM/dOKuPGhb30pGORoacl2mKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBa07c%2FbtsMedrajIM%2FdOKuPGhb30pGORoacl2mKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;274&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 해당 파라미터 그룹을 적용시킬 DB를 선택하고 '수정' 버튼 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;249&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ks4y2/btsMcB02Tt4/7FjTlgEvdIlV9v5kDd6pZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ks4y2/btsMcB02Tt4/7FjTlgEvdIlV9v5kDd6pZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ks4y2/btsMcB02Tt4/7FjTlgEvdIlV9v5kDd6pZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKs4y2%2FbtsMcB02Tt4%2F7FjTlgEvdIlV9v5kDd6pZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;249&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;249&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 추가 구성 탭의 데이터베이스 옵션에서 DB 파라미터 그룹을 위에서 생성한 파라미터 그룹으로 수정해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzp7TB/btsMeagUkM8/U4X40YK61IpscMiQWz2Kt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzp7TB/btsMeagUkM8/U4X40YK61IpscMiQWz2Kt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzp7TB/btsMeagUkM8/U4X40YK61IpscMiQWz2Kt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzp7TB%2FbtsMeagUkM8%2FU4X40YK61IpscMiQWz2Kt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;924&quot; height=&quot;453&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 수정 예약을 '즉시 적용'으로 바꾼 뒤 'DB 인스턴스 수정' 클릭 (재부팅을 해주어야만 해당 파라미터 그룹이 적용된다.)&lt;/p&gt;
&lt;pre id=&quot;code_1739171751263&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE EVENT daily_create_view
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP + INTERVAL 1 DAY
DO
BEGIN

	-- 블라스트 view 생성 쿼리
	CREATE OR REPLACE VIEW blast_by_status AS
	SELECT 
	    p.status,
...

END&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 이후 이벤트를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739172023261&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 등록한 이벤트 확인
SHOW EVENTS;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. SHOW EVENTS라는 쿼리로 생성된 이벤트를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 단계를 따르면 AWS RDS에서도 이벤트 스케줄러를 설정할 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>event scheduler</category>
      <category>MySQL</category>
      <category>mysql event scheduler</category>
      <category>mysql 이벤트 스케줄러</category>
      <category>RDS</category>
      <category>rds 이벤트 스케줄러</category>
      <category>SQL</category>
      <category>sql 쿼리</category>
      <category>sql 쿼리 cron</category>
      <category>sql 쿼리 반복실행</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/285</guid>
      <comments>https://minding-deep-learning.tistory.com/285#entry285comment</comments>
      <pubDate>Mon, 10 Feb 2025 12:44:16 +0900</pubDate>
    </item>
    <item>
      <title>[QA/Testing] Charles Proxy Tool - 테스트 효율을 높일 수 있는 툴</title>
      <link>https://minding-deep-learning.tistory.com/284</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 우아한형제들의 기술블로그 글을 참고하여 정리한 글입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/14550/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/14550/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738915616180&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;너 혹시 T(Tester)야? : 테스트 효율을 높이는 Charles 툴 활용기 | 우아한형제들 기술블로그&quot; data-og-description=&quot;테스트 데이터를 효율적으로 쌓기 위해 Charles라는 프록시 툴을 도입하였습니다. 프록시 : 서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 가리킴 (출처 : 위키백과) Charles 툴&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/14550/&quot; data-og-url=&quot;https://techblog.woowahan.com/14550/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bAFRhy/hyYcjNqvBc/kHp7xuwaI4J8tBNkNk6cY0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bzbVfX/hyYb7lT3az/pal3vOKgOEdwZXBQ3uFx40/img.jpg?width=2618&amp;amp;height=230&amp;amp;face=0_0_2618_230&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/14550/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/14550/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bAFRhy/hyYcjNqvBc/kHp7xuwaI4J8tBNkNk6cY0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/bzbVfX/hyYb7lT3az/pal3vOKgOEdwZXBQ3uFx40/img.jpg?width=2618&amp;amp;height=230&amp;amp;face=0_0_2618_230');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;너 혹시 T(Tester)야? : 테스트 효율을 높이는 Charles 툴 활용기 | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;테스트 데이터를 효율적으로 쌓기 위해 Charles라는 프록시 툴을 도입하였습니다. 프록시 : 서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 가리킴 (출처 : 위키백과) Charles 툴&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Charles? (&lt;a href=&quot;https://www.charlesproxy.com/overview/about-charles/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Charles는 웹 디버깅 프록시 툴로, HTTP 및 HTTPS 트래픽을 모니터링 및 분석할 수 있으며, 요청 및 응답 데이터를 조작할 수 있는 기능을 가지고 있다. 많은 QA 엔지니어와 개발자들이 애플리케이션 간의 네트워크 통신을 디버깅하고 테스트하는데 사용되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Charles는 많은 기능들을 가지고 있는데, 그 중 테스트에 사용되는 몇 가지 기능을 살펴봤다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;트래픽 모니터링 기능으로 클라이언트와 서버 간 모든 HTTP/HTTPS 트래픽을 실시간 기록 및 분석 가능&lt;/li&gt;
&lt;li&gt;SSL/TLS 트래픽을 복호화하여 요청 및 응답 데이터를 평문으로 확인할 수 있음&lt;/li&gt;
&lt;li&gt;HTTP/HTTPS 요청 및 응답을 수정해 테스트 시나리오 실행 가능&lt;/li&gt;
&lt;li&gt;대역폭 제한(Bandwidth Throttling)을 통해 느린 인터넷 환경 등을 시뮬레이션 할 수 있음&lt;/li&gt;
&lt;li&gt;Breakpoint(중단점)를 설정하여 데이터를 수정하거나 분석할 수 있음&lt;/li&gt;
&lt;li&gt;iOS, Android 모바일 디바이스 지원&lt;/li&gt;
&lt;li&gt;서버 데이터를 로컬 파일로 대체하여 테스트 환경을 구축하거나 변경 사항을 시뮬레이션 할 수 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외 JSON 구조를 트리 형식으로 시각화할 수 있는 등 다양한 기능을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Charles의 주요 사례&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;블랙박스 테스트 중 특정 버튼을 눌렀을 때, 어떤 API가 호출되고 어떤 값이 응답되는지 알 수 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Charles를 통해 API 요청 및 응답을 시각적으로 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상당한 양의 테스트 데이터를 생성해야 할 경우, 수동 조작 시 굉장한 시간이 소요됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Charles를 통해 요청/응답 데이터를 수정하여 짧은 시간 내에 테스트할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 Postman과 같은 API 테스트 툴은 API의 요청 및 응답 테스트에 특화된 것에 비해, Charles는 시스템과 인터넷 간 모든 HTTP/SSL/HTTPS 트래픽을 기록, 분석 및 조작할 수 있다는 장점이 있다. (웹 디버깅 프록시 기능에 특화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시는 &lt;a href=&quot;https://techblog.woowahan.com/14550/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;우아한형제들의 기술블로그&lt;/a&gt;에서 가져왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 배달의민족 예시 - 1: 서버 응답 데이터 변조&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. 서버 응답 데이터 변조&lt;/h4&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;배달의민족의 가게는 사용자가 &amp;lsquo;찜&amp;rsquo;을 할 수 있고 찜한 가게들만 별도로 확인할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;찜의 카운트는 9,999까지는 서버의 데이터를 그대로 표시하지만 10,000 카운트가 넘어가게 되면 &quot;&lt;b&gt;1만+&lt;/b&gt;&quot;라고 클라이언트에서 가공 처리를 해주어야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;1만+&lt;/b&gt;&quot;라고 클라이언트에서 표시해 주는지 확인을 위해선?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1대의 디바이스 혹은 1개의 배민회원으론 가게에 찜 등록을 한 번만 할 수 있기 때문에 찜이 1만 개인 가게 테스트 데이터를 만들기 위해선&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1만 대 디바이스 혹은 1만 개의 배민 회원 계정&lt;/b&gt;이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수동으로 설정하긴 불가능한 테스트 데이터라 SQL을 활용하여 db를 조작하여 데이터를 만들 수밖에 없습니다. db 접근 권한이 없거나 SQL 관련 지식이 없으면 서버 개발자에게 데이터 설정을 요청해야 하는데 이로 인한 커뮤니케이션 리소스가 소모됩니다. Charles의 프록시 기능을 활용하여 서버 응답 값을 변조하여 로컬 환경에서 재현시키는 방법으로 간단하게 테스트를 할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBB2ug/btsMbFuWu77/XUYhW2HbbRLhgEl1YWajcK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBB2ug/btsMbFuWu77/XUYhW2HbbRLhgEl1YWajcK/img.jpg&quot; data-alt=&quot;Charles로 서버 응답 데이터 가로채고 대체하는 과정 (출처: 우아한 기술블로그)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBB2ug/btsMbFuWu77/XUYhW2HbbRLhgEl1YWajcK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBB2ug%2FbtsMbFuWu77%2FXUYhW2HbbRLhgEl1YWajcK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;112&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Charles로 서버 응답 데이터 가로채고 대체하는 과정 (출처: 우아한 기술블로그)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;배달의민족 가게 상세 API에서 서버 응답받은 찜 카운트 값을 변조하는 방법을 적어보겠습니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #6a737d; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Charles로 API의 서버 응답 값 변조 방법&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;배달의민족 가게 상세 API 명세서에서 찜의 데이터를 노출시켜주는 파라미터와 응답 구조 확인&lt;br /&gt;* 파라미터와 응답 구조는 보안 문제 발생 감안하여 업로드하진 않겠습니다.&lt;/li&gt;
&lt;li&gt;JSON Editor tool을 사용해 실제 서버 응답을 가로채고 대체할 API 응답 값을 JSON 구조로 만들기&lt;br /&gt;* JSON Editor tool은 Visual Studio Code 추천합니다. 파싱 에러 발생 시 툴에 표시해 주기도 하고 무엇보다 무료라는 강점이 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0366d6;&quot; href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code 다운로드&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Charles 실행 : Sequence 목록에서 배달의민족 가게 상세 API &amp;gt; 마우스 오른쪽 클릭 &amp;gt; Map Local 선택&lt;/li&gt;
&lt;li&gt;Edit Mapping 창의 Local path에서 대체할 JSON 파일 업로드&lt;/li&gt;
&lt;li&gt;Charles와 연결된 모바일 기기로 배달의민족 가게 상세 화면 진입 시 대체한 응답 파일구조로 클라이언트에서 화면에 그려주는 것을 확인 가능&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qvbzv/btsMcqYffkX/zzoXQ9hTZkNKeH070kmlU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qvbzv/btsMcqYffkX/zzoXQ9hTZkNKeH070kmlU1/img.jpg&quot; data-alt=&quot;가게의 찜 카운트 응답 값 전/후 변화 사진 (출처: 우아한 기술블로그)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qvbzv/btsMcqYffkX/zzoXQ9hTZkNKeH070kmlU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqvbzv%2FbtsMcqYffkX%2FzzoXQ9hTZkNKeH070kmlU1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;910&quot; height=&quot;1014&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;1014&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가게의 찜 카운트 응답 값 전/후 변화 사진 (출처: 우아한 기술블로그)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 배달의민족 예시 - 2: 서버 API 에러 응답 상황에서 클라이언트 동작 확인&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. 서버 API 에러 응답 상황에서 클라이언트 동작 확인&lt;/h4&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자들이 앱을 사용하며 API가 호출될 때 항상 정상적인 200 코드 응답만 받으며 잘 동작되는 것은 아닙니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #24292e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트의 요청에서 문제가 있는 경우 응답받는 400번대 코드&lt;/li&gt;
&lt;li&gt;서버 API에 문제가 있는 경우 응답받는 500번대 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 에러 응답을 받는 상황이 있고 에러 응답을 받으면 클라이언트는 서버의 응답 데이터로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;화면을 정상적으로 그려줄 수 없습니다.&lt;/b&gt;&lt;br /&gt;사용자를 고려한 서비스라면 이런 예외적인 상황에서 사용자에게 에러 상태인 걸 가시화해서 보여주고 재시도, 화면 이탈 등의 액션 이벤트를 시도할 수 있게 해주는 유저 플로우가 필요합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NYREy/btsMb1YRCKB/QIktWxyfMZtMPNgB8oiwFk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NYREy/btsMb1YRCKB/QIktWxyfMZtMPNgB8oiwFk/img.jpg&quot; data-alt=&quot;배달의민족 배달 현황 화면의 API가 에러 응답을 받는 상황 (출처: 우아한기술블로그)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NYREy/btsMb1YRCKB/QIktWxyfMZtMPNgB8oiwFk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNYREy%2FbtsMb1YRCKB%2FQIktWxyfMZtMPNgB8oiwFk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;750&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;배달의민족 배달 현황 화면의 API가 에러 응답을 받는 상황 (출처: 우아한기술블로그)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이에 대한 테스트의 결과를 도출하기 위해선 아래와 같은 번거로운 절차가 필요합니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #6a737d; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6f8fa;&quot;&gt;서버 개발자에게 테스트 서버에 장애를 만들어달라고 요청&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;에러 응답 상황 만들기&lt;/li&gt;
&lt;li&gt;테스트 수행&lt;/li&gt;
&lt;li&gt;테스트 완료 시 원복 요청&lt;/li&gt;
&lt;li&gt;코드 수정 후 원복&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이것만으로도 커뮤니케이션 리소스가 발생되는데 더욱 큰 문제는 테스트 서버를 사용하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;모든 유관부서 인원에게 영향을 미칠 수 있단 점&lt;/b&gt;입니다.&lt;br /&gt;(10분 동안 100명이 테스트 서버를 사용 못 한다고 했을 때 낭비되는 시간은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;1,000&lt;/b&gt;분이 될 것입니다.)&lt;/p&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 에러 응답을 받는 상태에 대한 테스트도 charles의 프록시 기능을 활용하여 QA 인원 개인의 로컬 환경에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;테스트 서버에 전혀 영향을 주지 않고 테스트 가능합니다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;배달의민족 가게 목록 API에서 500 응답을 받는 상황을 로컬에서 만들어서 테스트하는 방법을 공유해 보겠습니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #6a737d; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Charles로 API 200 응답 -&amp;gt; 500 응답으로 변경하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Charles 실행 : Sequence 목록에서 사용하고자 하는 API를 선택 &amp;gt; 마우스 오른쪽 클릭 &amp;gt; Copy URL 선택&lt;/li&gt;
&lt;li&gt;Tools &amp;gt; Rewrite 메뉴 진입&lt;/li&gt;
&lt;li&gt;Enable Rewrite 체크&lt;/li&gt;
&lt;li&gt;[Add] 버튼 선택하여 Rewrite 항목 추가&lt;/li&gt;
&lt;li&gt;Name 입력 후, Location의 [Add] 버튼 선택하여 &amp;lsquo;Edit Location&amp;rsquo; 창 불러온 후 아래 값 입력
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Protocol : http / https 중 선택&lt;/li&gt;
&lt;li&gt;Host : 가게 목록 API 입력 (복사 -&amp;gt; 붙여넣기 가능)&lt;/li&gt;
&lt;li&gt;Path : API Path 입력 (&amp;lsquo;Host&amp;rsquo;에 URL 붙여넣기 후, 키보드의 [Tab] 키 선택 시 자동으로 값 채워짐)&lt;/li&gt;
&lt;li&gt;Query : 특정한 정보를 요청하여 Rewrite 대상을 한정할 경우 입력&lt;br /&gt;(&amp;lsquo;Host&amp;rsquo;에 URL 붙여넣기 후, 키보드의 [Tab] 키 선택 시 자동으로 값 채워짐)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Type의 [Add] 버튼 선택하여 &amp;lsquo;Rewrite Rule&amp;rsquo; 창 불러온 후 아래 값 입력 후 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Type : Response Status&lt;/li&gt;
&lt;li&gt;Match &amp;ndash; Value : 200&lt;/li&gt;
&lt;li&gt;Replace &amp;ndash; Value : 500&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모바일 기기로 Rewrite Rule을 설정한 API인 가게 목록 화면을 진입하여 API 요청 &amp;amp; 응답이 이루어지면 [절차 6]에서 대체한 500 Status 에러 응답을 받기 때문에 클라이언트 화면에서 에러 화면을 제공해 주는지, (다시 시도) 액션 이벤트가 잘 이루어지는지 등의 테스트를 서버 장애 상황이 아닌 경우에도 가능&lt;br /&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnDNsf/btsMaeFyxNZ/xhUJ09HUcqF9wVFYMFi9a1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnDNsf/btsMaeFyxNZ/xhUJ09HUcqF9wVFYMFi9a1/img.jpg&quot; data-alt=&quot;가게 목록 API가 200 응답일 때와 500 응답일 때 앱 화면 비교 (출처: 우아한기술블로그)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnDNsf/btsMaeFyxNZ/xhUJ09HUcqF9wVFYMFi9a1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnDNsf%2FbtsMaeFyxNZ%2FxhUJ09HUcqF9wVFYMFi9a1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;1000&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가게 목록 API가 200 응답일 때와 500 응답일 때 앱 화면 비교 (출처: 우아한기술블로그)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/soUQC/btsMcd5U90P/lqnl5qMGSJvevusSsxQjPk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/soUQC/btsMcd5U90P/lqnl5qMGSJvevusSsxQjPk/img.jpg&quot; data-alt=&quot;가게 목록 API가 200 응답일 때와 500 응답일 때 Charles 화면 비교 (출처: 우아한기술블로그)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/soUQC/btsMcd5U90P/lqnl5qMGSJvevusSsxQjPk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsoUQC%2FbtsMcd5U90P%2Flqnl5qMGSJvevusSsxQjPk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;81&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가게 목록 API가 200 응답일 때와 500 응답일 때 Charles 화면 비교 (출처: 우아한기술블로그)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 배달의민족 예시 - 3: Timeout&amp;nbsp;상황&amp;nbsp;테스트&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Timeout은 &amp;lsquo;프로그램이 특정한 시간 내에 성공적으로 수행되지 않아서 진행이 자동적으로 중단되는 것&amp;rsquo;을 말합니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #6a737d; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;만약 앱에 Timeout이 구현되지 않았다면?&lt;/b&gt;&lt;br /&gt;엘리베이터나 만원 지하철 등, 네트워크가 느려질 수 있는 상황에서 API 호출하였을 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;끝없이 로딩이 발생&lt;/b&gt;될 수 있습니다. Timeout이 구현되었다면 일정 시간 이후 API 호출 시도가 자동적으로 중단됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Timeout 테스트는 일반적인 도심의 사무실 환경에선 테스트하기가 쉽지 않지만&lt;br /&gt;Charles는 앱의 Timeout 상황을 재현하여 테스트할 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #24292e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;배달의민족의 가게목록에서 가게를 선택하는 시점에 timeout이 발생하는 테스트 상황을 만드는 내용을 영상과 함께 공유하겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c21ZAR/btsMbbnD3Ko/RqDUMZd8jiFF41nQLIoKK1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c21ZAR/btsMbbnD3Ko/RqDUMZd8jiFF41nQLIoKK1/img.gif&quot; data-alt=&quot;latency (ms)를 10000ms로 설정하고 배달의민족 앱에서 Timeout 상황 재현 (출처: 우아한 기술블로그)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c21ZAR/btsMbbnD3Ko/RqDUMZd8jiFF41nQLIoKK1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c21ZAR/btsMbbnD3Ko/RqDUMZd8jiFF41nQLIoKK1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;1280&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;latency (ms)를 10000ms로 설정하고 배달의민족 앱에서 Timeout 상황 재현 (출처: 우아한 기술블로그)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;color: #6a737d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Charles로 Timeout 상황 재현 방법&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #6a737d; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Charles 실행 : Proxy &amp;gt; Throttle settings 선택&lt;/li&gt;
&lt;li&gt;Enable Throttling 체크&lt;/li&gt;
&lt;li&gt;특정 Host에만 throttle 설정을 하고 싶으면 [Only for selected hosts] 체크하고 Host 정보를 입력 (체크를 하지 않으면 모든 요청에 throttle 설정이 적용)&lt;/li&gt;
&lt;li&gt;상세 설정값들을 테스트에 적절한 값으로 입력 후 적용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;throttle preset : 3G, 4G 등 일반적인 인터넷 연결 유형을 설정&lt;/li&gt;
&lt;li&gt;Bandwidth : 전송할 수 있는 최대 데이터양을 정의합니다.&lt;/li&gt;
&lt;li&gt;Utilisation : 한 번에 사용자가 접근할 수 있는 전체 대역폭의 백분율&lt;/li&gt;
&lt;li&gt;Round-trip latency : 클라이언트와 원격 서버 간의 요청 지연 시간 (ms)&lt;/li&gt;
&lt;li&gt;MTU : 현재 프리셋의 최대 전송 단위를 정의&lt;/li&gt;
&lt;li&gt;stability : 연결이 완전히 실패할 가능성을 측정, 신뢰할 수 없는 네트워크 조건을 시뮬레이션 하는 데 사용&lt;/li&gt;
&lt;li&gt;Unstability quality range : 연결이 &amp;lsquo;불안정한&amp;rsquo;상태 일 가능성을 측정하여 품질을 저하 시킴. 주기적으로 연결 품질이 떨어지는 모바일 네트워크와 같은 네트워크를 시뮬레이션 할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;앱에서 API 호출이 발생되면 throttle 설정이 적용되어 네트워크 지연 상태일 때의 클라이언트 동작을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Charles 이용 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Charles는 &lt;a href=&quot;https://www.charlesproxy.com/download/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 설치 페이지&lt;/a&gt;에서 사용하는 OS(Windows, MacOS, Linux64)에 맞게 다운로드 받아 설치하면 된다. 라이선스를 구매하지 않을 경우에는 30일 동안 무료 평가판을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.gccompany.co.kr/charles-proxy-%EC%86%8C%EA%B0%9C-4c4a3bbc8994&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기어때 기술블로그&lt;/a&gt;에서 설치 및 세팅 방법을 자세히 설명하고 있으니 참고하면 좋을 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 Charles Proxy Tool을 설치 및 세팅한 뒤 특정 App에서 작동시켜보는 것까지 해본 뒤 포스팅할 예정이다. 현재 개발 중인 웹 서비스가 있는데, 그것 또한 Charles를 이용해서 테스트한다면 좋은 방법이 될 것 같다.(기회가 된다면 개발 중인 웹서비스에 적용시켜본 것도 포스팅하겠다.)&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>Charles</category>
      <category>charles proxy tool</category>
      <category>Proxy</category>
      <category>QA</category>
      <category>tester</category>
      <category>testing</category>
      <category>우아한기술블로그</category>
      <category>테스트</category>
      <category>품질검증</category>
      <category>품질관리</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/284</guid>
      <comments>https://minding-deep-learning.tistory.com/284#entry284comment</comments>
      <pubDate>Fri, 7 Feb 2025 18:10:10 +0900</pubDate>
    </item>
    <item>
      <title>[프로젝트] Catch Me My Capital - 합리적인 투자 의사결정을 위한 금융 데이터 파이프라인 및 백테스팅 도구 (프로젝트 소개 편)</title>
      <link>https://minding-deep-learning.tistory.com/283</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Github: &lt;a href=&quot;https://github.com/DE-ta-e-il/catch-me-my-capital&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/DE-ta-e-il/catch-me-my-capital&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738808980423&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - DE-ta-e-il/catch-me-my-capital: A data engineering project focused on collecting and managing financial data with a vis&quot; data-og-description=&quot;A data engineering project focused on collecting and managing financial data with a vision for backtesting and analysis. - DE-ta-e-il/catch-me-my-capital&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/DE-ta-e-il/catch-me-my-capital&quot; data-og-url=&quot;https://github.com/DE-ta-e-il/catch-me-my-capital&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FwiPJ/hyYcerqDT5/PMQZTVwiqYtlZ6seCwJnoK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/CRh6a/hyYcerqDWK/VkX7sgX4vgBjJ4ZvOeSzKK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/DE-ta-e-il/catch-me-my-capital&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/DE-ta-e-il/catch-me-my-capital&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FwiPJ/hyYcerqDT5/PMQZTVwiqYtlZ6seCwJnoK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/CRh6a/hyYcerqDWK/VkX7sgX4vgBjJ4ZvOeSzKK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - DE-ta-e-il/catch-me-my-capital: A data engineering project focused on collecting and managing financial data with a vis&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A data engineering project focused on collecting and managing financial data with a vision for backtesting and analysis. - DE-ta-e-il/catch-me-my-capital&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트 개요&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로젝트 배경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;우리는 정말 원숭이보다 투자를 더 잘하고 있을까요?&quot;라는 질문에 대부분의 사람은 이 질문에 자신있게 대답하지 못한다. 그 이유는 투자 세계는 종종 객관적인 데이터보다 감정과 편향된 사고에 의해 좌우되며, 두려움, 탐욕, 확증 편향 등 심리적 요인들이 투자 판단에 큰 영향을 미친다. 그리고 이는 종종 비합리적인 결정을 초래하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 극복하기 위해서는 데이터 기반의 분석과 전략적 판단이 필수적이다. 본 프로젝트는 사용자들이 이러한 심리적 함정을 벗어나 객관적인 데이터를 바탕으로 투자 결정을 내릴 수 있도록 돕는 도구를 제안한다. 이를 통해 감정에 흔들리지 않는 합리적이고 체계적인 투자 문화를 정착시키고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목표&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;금융 데이터의 통합 및 시각화를 통한 포트폴리오 관리 최적화&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터의 체계적인 처리 및 분석을 위한 ETL 파이프라인 구축&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장 가능한 AWS기반 솔루션 설계&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Serverless AWS 서비스와 버킷, DW 클러스터 활용&lt;/li&gt;
&lt;li&gt;자동화된 워크플로우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주식, 채권, ETF, 외환 등 다양한 투자 상품을 필터링하여 인사이트 생성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tableau 기반 인터랙티브 대시보드&lt;/li&gt;
&lt;li&gt;맞춤형 필터 및 뷰 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 처리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 수집, 변환, 정제 및 분석 제공&lt;/li&gt;
&lt;li&gt;데이터 품질 관리: 누락 데이터 처리 및 중복 제거&lt;/li&gt;
&lt;li&gt;최적화된 데이터셋 제공을 통한 빠른 인사이트 도출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소프트 스킬 배양&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제한된 일정 안에서 우선순위를 정하고, 목표를 체계적으로 달성&lt;/li&gt;
&lt;li&gt;Scrum과 Sprint를 효과적으로 관리하여 지속적인 개선 실현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기술 스택&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 수집 및 처리:&lt;/b&gt; Python(3.11), AWS Glue, dbt
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python 패키지 의존성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백테스팅 구현 관련: backtrader, quantstats&lt;/li&gt;
&lt;li&gt;데이터 수집 관련: cloudscraper, requests,bs4, yfinance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 저장:&lt;/b&gt; AWS S3, AWS Redshift, AWS RDS&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 서빙 및 시각화:&lt;/b&gt; Tableau(BI), Lambda(API)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오케스트레이션:&lt;/b&gt; AWS MWAA(Airflow 2.10.1)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;운영 자동화:&lt;/b&gt; Terraform(인프라 설정), GitHub Actions(CI/CD), AWS CloudWatch(모니터링)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안:&lt;/b&gt; AWS Secrets Manager, AWS VPC&lt;/li&gt;
&lt;li&gt;&lt;b&gt;협업 관리&lt;/b&gt;: Jira, Postman, GitHub, Slack, Discord&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로컬 개발 환경 구성&lt;/b&gt;: Docker + Airflow(2.10.1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트 설계 및 아키텍처&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;데이터 아키텍쳐 - 2번 (1).png&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1717&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XpXeA/btsL8j0s0HO/KFLBYQdBvGAu6DfYsfxfX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XpXeA/btsL8j0s0HO/KFLBYQdBvGAu6DfYsfxfX1/img.png&quot; data-alt=&quot;Catch Me My Capital 프로젝트의 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XpXeA/btsL8j0s0HO/KFLBYQdBvGAu6DfYsfxfX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXpXeA%2FbtsL8j0s0HO%2FKFLBYQdBvGAu6DfYsfxfX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2672&quot; height=&quot;1717&quot; data-filename=&quot;데이터 아키텍쳐 - 2번 (1).png&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1717&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Catch Me My Capital 프로젝트의 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설계 의도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 두 개의 계정으로 아키텍처를 구현함.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 프로젝트는 프로그래머스 데이터 엔지니어링 데브코스 4기의 최종 프로젝트로 진행했다는 점을 먼저 알린다. 주최 측에서 제공하는 AWS 계정이 있었지만, 해당 계정으로 이용 가능한 서비스 자원이 한정되어 있었다. 따라서 별도의 팀 계정을 따로 생성하여 2개의 계정을 연결해 사용하는 방식을 채택했다. 2개의 계정은 각각 아래와 같은 역할을 가지고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 계정 (팀 계정 - MWAA 전용 AWS IAM)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MWAA (Managed Workflows for Apache Airflow): AWS에서 제공하는 Airflow 완전 관리형 서비스로, 설치/유지보수/업그레이드 및 확장을 자동으로 처리한다. 관리가 용이한 점과 단기간에 개발을 할 수 있다는 장점이 있어 채택.&lt;/li&gt;
&lt;li&gt;실제 기업 사례에서도 Airflow 초기 도입에 MWAA를 많이 사용한다는 블로그 글 등 참고 (ex. &lt;a href=&quot;https://aws.amazon.com/ko/blogs/tech/29cm-sagemaker-mwaa-recsys-mlops-journey/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;29CM 개인화 추천시스템 사례&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;데이터 수집과 파이프라인 관리에 사용되는 DAG 실행하는 환경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;aws cross account(1).png&quot; data-origin-width=&quot;2458&quot; data-origin-height=&quot;2231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bThfZM/btsL9Jjkfv0/xflQ1UHuiqK9v3s8D3zKK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bThfZM/btsL9Jjkfv0/xflQ1UHuiqK9v3s8D3zKK0/img.png&quot; data-alt=&quot;참고: A 계정의 MWAA 구성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bThfZM/btsL9Jjkfv0/xflQ1UHuiqK9v3s8D3zKK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbThfZM%2FbtsL9Jjkfv0%2FxflQ1UHuiqK9v3s8D3zKK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;402&quot; data-filename=&quot;aws cross account(1).png&quot; data-origin-width=&quot;2458&quot; data-origin-height=&quot;2231&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참고: A 계정의 MWAA 구성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;B 계정 (데브코스 지원 계정 - Glue, DB 전용 IAM / 아래 서비스는 모두 지원되는 항목)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;S3: 데이터 레이크로 사용할 목적으로 구성함. 여러 형태의 데이터를 원본 형태로 수용할 수 있으며, 용량 제한 및 연결 편의성 측면에서 뛰어나다는 점이 있어 채택.&lt;/li&gt;
&lt;li&gt;Glue: 서버리스 기반의 데이터 처리 서비스로, 간단한 작동 방식으로 Spark를 통해 데이터를 처리할 수 있으며 데이터 처리 과정을 간소화할 수 있다는 장점이 있어 채택.&lt;/li&gt;
&lt;li&gt;Redshfit: 데이터 웨어하우스로 사용. 데이터 수요에 따라 자원이 유연하게 확장되며, 컬럼형 방식 및 병렬 처리가 가능해 빅 데이터셋에 효율적인 장점이 있기 때문에 채택.&lt;/li&gt;
&lt;li&gt;Lambda: API 서버를 구성하는 데 사용. 트래픽에 따라 자동 확장 가능한 서버리스 관리형 서비스이며, 사용하지 않을 시 비용이 따로 발생하지 않음. API 사용량과 API로 제공하는 데이터 크기를 고려했을 때 Lambda로 구성하는 게 효율적이라 판단되어 채택.&lt;/li&gt;
&lt;li&gt;RDS: API 서빙용 데이터베이스로 사용. ElasticCache를 in-memory 캐시로 사용하는 관리형 서비스다. Redshift에 저장되어 있는 데이터를 RDS에도 저장하여 API 서빙 시 빠른 반응을 할 수 있도록 하기 위해 채택.&lt;/li&gt;
&lt;li&gt;Secrets Manager: API KEY 등 비밀 정보를 Secrets Manager를 통해 안전하게 보관 및 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;계정 간 연동 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀 계정(MWAA service role)과 데브코스 계정(Glue role)에 각각 IAM Role 구성&lt;/li&gt;
&lt;li&gt;데브코스 계정에 Trusted entity 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Glue / Redshift / S3 등과 상호작용을 위한 정책 권한 설정&lt;/li&gt;
&lt;li&gt;Secrets Manager 권한 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 데이터 파이프라인 아키텍처를 메달리온 아키텍처로 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메달리온 아키텍처는 데이터 파이프라인을 Bronze, Silver, Gold 세 계층으로 구성하는 아키텍처로, Bronze에 원본 데이터를 저장한 뒤 계층이 올라갈 때마다 데이터를 점진적으로 개선하는 방식을 가지고 있다. 메달리온 아키텍처를 채택한 이유는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3개의 계층으로 데이터를 점진적으로 개선할 수 있어 고품질의 데이터를 제공할 수 있다.&lt;/li&gt;
&lt;li&gt;데이터 리니지 추적이 쉬워(모든 데이터가 B -&amp;gt; S -&amp;gt; G의 흐름을 가지기 때문) 체계적인 데이터 관리가 가능하다.&lt;/li&gt;
&lt;li&gt;데이터가 변환되는 과정을 각 레이어 별로 정의할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 프로젝트에서는 각 레이어 별로 다음과 같은 변환과정을 거쳤다.&lt;/p&gt;
&lt;pre id=&quot;code_1738811690124&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Bronze(S3 / 원본 데이터) -&amp;gt; Silver(Redshift / 정제 및 변환된 데이터) -&amp;gt; Gold(Redshift&amp;amp;RDS / 시각화 및 데이터 서빙에 적합한 형태의 데이터)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 파이프라인이 작동하는 흐름은 아래의 순서대로 진행되도록 설계했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;데이터 아키텍쳐 - 2번 (1).png&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1717&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XpXeA/btsL8j0s0HO/KFLBYQdBvGAu6DfYsfxfX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XpXeA/btsL8j0s0HO/KFLBYQdBvGAu6DfYsfxfX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XpXeA/btsL8j0s0HO/KFLBYQdBvGAu6DfYsfxfX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXpXeA%2FbtsL8j0s0HO%2FKFLBYQdBvGAu6DfYsfxfX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;423&quot; data-filename=&quot;데이터 아키텍쳐 - 2번 (1).png&quot; data-origin-width=&quot;2672&quot; data-origin-height=&quot;1717&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MWAA DAG에서 API 호출 및 웹 크롤링 작업을 통해 Raw Data를 수집&lt;/li&gt;
&lt;li&gt;수집된 Raw Data를 B 계정(데브코스 계정)의 S3에 저장 (Bronze Layer)&lt;/li&gt;
&lt;li&gt;Silver Layer를 위한 DAG 실행, 데이터 스키마 변경 여부를 감지하기 위해 Glue 크롤러 트리거&lt;/li&gt;
&lt;li&gt;Glue 크롤러가 Data Catalog를 업데이트&lt;/li&gt;
&lt;li&gt;Silver Layer DAG에서 Glue ETL job을 실행 (Spark)&lt;/li&gt;
&lt;li&gt;Glue ETL job이 완료된 데이터를 Redshift에 저장해 분석 준비 (Silver Layer)&lt;/li&gt;
&lt;li&gt;Gold Layer를 위한 DAG 실행, 이 작업에서는 DBT(Data Build Tool)를 사용하며, 결과 데이터를 Redshift에 저장&lt;/li&gt;
&lt;li&gt;Gold Layer 데이터는 분석 및 시각화 관련 데이터를 프로덕션에서 쉽게 사용할 수 있도록 RDS에도 저장&lt;/li&gt;
&lt;li&gt;Gold Layer 데이터를 AWS Lambda에 구성한 FastAPI 서버를 통해 데이터 서빙&lt;/li&gt;
&lt;li&gt;Tableau와 연동되어 데이터 시각화를 제공&lt;/li&gt;
&lt;li&gt;Athena를 통해 테스트 쿼리 실행&lt;/li&gt;
&lt;li&gt;DAG, Glue job script의 변경 사항이 발견될 때마다(main 브랜치에 변동 사항이 있을 경우) Github Actions을 통해 S3에 자동 배포함.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. Terraform을 활용해 프로비저닝&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정을 2개 사용하는 이유 중 하나였던 MWAA는 AWS의 다른 서비스에 비해 요금이 높은 서비스다. 팀 사정상 MWAA를 프로젝트 기간동안 띄워둘 수는 없었기 때문에, DAG을 실행하는 동안에만 MWAA를 실행할 수 있도록 하는 프로비저닝 도구가 필요했다. 팀원과 논의 후에 Terraform을 프로비저닝 도구로 사용하기로 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Terraform 세부 설정]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terraform으로 프로비저닝하는 서비스들은 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;서브넷&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MWAA는 &lt;b&gt;3개의 서브넷&lt;/b&gt;으로 구성되어 있으며, 각 서브넷은 3개의 가용 영역에 분산.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Webserver 서브넷&lt;/b&gt;: Airflow 웹서버가 위치.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Worker 서브넷&lt;/b&gt;: Airflow 작업자(Worker)가 실행.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Scheduler 서브넷&lt;/b&gt;: Airflow 스케줄러가 실행.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;**인터넷 게이트웨이(IGW)**는 &lt;b&gt;Webserver 서브넷&lt;/b&gt;에 연결되어, 웹서버가 외부와 통신할 수 있도록 설정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NAT 게이트웨이&lt;/b&gt;는 Worker와 Scheduler 서브넷에 배치되어, 외부 인터넷 액세스가 필요할 경우를 지원.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클러스터&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Airflow 작업은 &lt;b&gt;AWS Fargate&lt;/b&gt; 클러스터에서 실행.&lt;/li&gt;
&lt;li&gt;서버리스 기반으로 확장성과 관리 효율성 극대화 및 개발 시간 단축.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메타데이터 DB&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Airflow 메타데이터 데이터베이스는 &lt;b&gt;Aurora 인스턴스&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;이 데이터베이스는 &lt;b&gt;Webserver 서브넷&lt;/b&gt;에 위치.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CloudWatch&lt;/b&gt;:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;SQS&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Airflow의 브로커로 사용되어 태스크 메시지를 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MWAA 로그 및 성능 모니터링 도구로 사용.&lt;/li&gt;
&lt;li&gt;Webserver, Worker, Scheduler의 실행 상태를 추적 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Secrets Manager&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Airflow 변수 및 연결 정보를 안전하게 저장.&lt;/li&gt;
&lt;li&gt;민감한 데이터(예: API 키, 데이터베이스 자격 증명 등)의 보호를 위해 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;S3&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Airflow의 루트 폴더로 사용.&lt;/li&gt;
&lt;li&gt;DAG 파일, 플러그인, 환경 파일이 저장됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IAM 역할 및 정책&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MWAA가 필요한 권한을 가질 수 있도록 IAM 역할 및 정책 구성.&lt;/li&gt;
&lt;li&gt;S3, SQS, CloudWatch, Secrets Manager 등과 통합을 지원.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Terraform 프로젝트 구조]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (5).png&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nkWCD/btsL90SUVmX/RtaomdJq9ZcxCNffOP0wW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nkWCD/btsL90SUVmX/RtaomdJq9ZcxCNffOP0wW0/img.png&quot; data-alt=&quot;Terraform 프로젝트 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nkWCD/btsL90SUVmX/RtaomdJq9ZcxCNffOP0wW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnkWCD%2FbtsL90SUVmX%2FRtaomdJq9ZcxCNffOP0wW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;260&quot; height=&quot;471&quot; data-filename=&quot;image (5).png&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Terraform 프로젝트 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Terraform 실행 절차]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Secrets 초기화&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Secretsmanager에서 시크릿을 바로 삭제하지 않기 때문에 Terraform 실행 전에 &lt;b&gt;Startprocedure.sh&lt;/b&gt;를 실행하여, 기존에 저장된 환경 변수를 정리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Terraform Apply&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;terraform plan/apply 명령어를 실행하여 MWAA 환경을 프로비저닝.&lt;/li&gt;
&lt;li&gt;실행 결과로 위의 리소스가 배포됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 파이프라인 프로세스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 메달리온 아키텍처를 기본으로 데이터 파이프라인을 구성했으며, 데이터 소스 수집 계층인 Ingestion과 프로덕션 계층인 Serving Layer를 양 끝에 붙인 형태로 최종 구성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;아키텍쳐 브레인스토밍 - Page 6 (4) (1).png&quot; data-origin-width=&quot;6563&quot; data-origin-height=&quot;2670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzHppZ/btsL7LDvgBg/wRN1hJQhhS8ZYeuR3RDV5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzHppZ/btsL7LDvgBg/wRN1hJQhhS8ZYeuR3RDV5k/img.png&quot; data-alt=&quot;데이터 파이프라인 프로세스 (데이터 수집 중심의 그림)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzHppZ/btsL7LDvgBg/wRN1hJQhhS8ZYeuR3RDV5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzHppZ%2FbtsL7LDvgBg%2FwRN1hJQhhS8ZYeuR3RDV5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;6563&quot; height=&quot;2670&quot; data-filename=&quot;아키텍쳐 브레인스토밍 - Page 6 (4) (1).png&quot; data-origin-width=&quot;6563&quot; data-origin-height=&quot;2670&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터 파이프라인 프로세스 (데이터 수집 중심의 그림)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. Ingestion(데이터 소스)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 주제가 '합리적인 투자 의사결정을 위한 금융 데이터 파이프라인 및 백테스팅 도구'인 만큼, 여러 금융 데이터 소스가 필요했다. 금융 데이터를 구할 수 있는 소스에 대한 조사를 마친 뒤 아래 기준에 따라 선정했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일일 API 제한량이 프로젝트에 사용할 수 있을만큼 충분한지&lt;/li&gt;
&lt;li&gt;데이터에 대한 신뢰성이 보장되는지&lt;/li&gt;
&lt;li&gt;요금이 부과되지 않는 소스인지&lt;/li&gt;
&lt;li&gt;특정 주기 별 배치 데이터를 수집할 수 있는 데이터인지 (Daily, Monthly, Quarterly, Yearly)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 RESTful API, 웹 크롤링, Python 라이브러리 등의 방법을 통해 아래와 같은 데이터들을 수집할 수 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 호출: Binance API(코인), NewYorkTimes API(뉴스), 한국은행 API(경제지표) 등&lt;/li&gt;
&lt;li&gt;웹 크롤링: Investing.com(주식, 종합지수), KRX(한국 ETF), Business Insider(채권, MSCI 지표) 등&lt;/li&gt;
&lt;li&gt;Python 라이브러리: yfinance(환율, 주식 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Bronze Layer&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 것처럼, 본 프로젝트는 메달리온 아키텍처로 구성했다. 제일 첫 번째 순서인 Bronze Layer에는 원본 데이터를 저장하는 계층(Glue ETL job 사용을 고려해 데이터 구조를 일부 변경하는 데이터도 있긴했다.)으로, 데이터 레이크로 사용할 수 있는 S3에 데이터를 저장했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 Airflow DAG을 통해 각 데이터 성격에 따라 특정 주기 별로 API 호출, 웹 크롤링 등의 방법을 통해 수집했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (6).png&quot; data-origin-width=&quot;1623&quot; data-origin-height=&quot;883&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxm3Nr/btsL7QkdLLX/p9uyOh3zis5uMg53Jkm1MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxm3Nr/btsL7QkdLLX/p9uyOh3zis5uMg53Jkm1MK/img.png&quot; data-alt=&quot;Airflow DAG 실행 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxm3Nr/btsL7QkdLLX/p9uyOh3zis5uMg53Jkm1MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxm3Nr%2FbtsL7QkdLLX%2Fp9uyOh3zis5uMg53Jkm1MK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1623&quot; height=&quot;883&quot; data-filename=&quot;image (6).png&quot; data-origin-width=&quot;1623&quot; data-origin-height=&quot;883&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Airflow DAG 실행 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 수집된 데이터는 각 데이터 폴더에 묶여 S3에 날짜 기준의 파티셔닝 키를 사용해 저장했다. AWS Glue Crawler의 &lt;a href=&quot;https://docs.aws.amazon.com/glue/latest/dg/tables-described.html#tables-partition&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;자동으로 파티셔닝 키를 인식하는 기능&lt;/a&gt;을 이용해 추후 날짜 인덱스로 활용해 효율성을 높이기 위함이었다. (&lt;a href=&quot;https://aws.amazon.com/ru/blogs/big-data/work-with-partitioned-data-in-aws-glue/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS Glue에서의 파티션 활용 관련 링크&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이미지와 같이 파티셔닝 키를 설정하면, 크롤러가 데이터를 읽은 뒤 Data Catalog에 저장할 때 ymd라는 파티션 인덱스를 자동으로 생성해준다. ('date'라는 파티셔닝 키를 사용하지 않은 이유는, 데이터 원본에 date라는 칼럼이 있는 경우가 많기 때문에 칼럼 명 중복 방지를 위해 'ymd'라는 키를 사용했다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (7).png&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CDJqk/btsL7zQAJ4A/vkS9dEPwVyKJOL4QhXf190/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CDJqk/btsL7zQAJ4A/vkS9dEPwVyKJOL4QhXf190/img.png&quot; data-alt=&quot;ymd=YYYY-mm-dd 형식으로 저장했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CDJqk/btsL7zQAJ4A/vkS9dEPwVyKJOL4QhXf190/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCDJqk%2FbtsL7zQAJ4A%2FvkS9dEPwVyKJOL4QhXf190%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;753&quot; height=&quot;478&quot; data-filename=&quot;image (7).png&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ymd=YYYY-mm-dd 형식으로 저장했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. Silver Layer&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Silver Layer에는 원본 데이터를 분석하기 적합한 형태로 정제하고 변환한 데이터를 저장하는 계층이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Silver Layer DAG을 Airflow를 통해 실행하면, 우선 AWS Glue Crawler를 통해 Bronze Layer의 데이터를 Data Catalog의 테이블 형태로 저장한다. 이후 Glue ETL job에서 GlueContext의 from_catalog 또는 from_options 메서드를 이용해 S3에 저장된 데이터를 수집해 Glue의 Spark Engine을 통해 적절히 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정제 및 변환된 데이터를 구조화하여 Redshift에 저장한다. 저장 시 테이블의 사용 목적에 따라 fact, dimension 테이블로 나누어 저장한다. 각 테이블의 정의는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fact 테이블: 실제 사실이 기록된 테이블(ex. 주가의 일봉 종가 데이터 등)&lt;/li&gt;
&lt;li&gt;dimension 테이블: fact 테이블에서 참고하는 메타 데이터 성격의 테이블&lt;/li&gt;
&lt;li&gt;fact_, dim_ 과 같은 prefix를 붙여 테이블 이름 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. Gold Layer&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gold Layer에는 시각화와 데이터 서빙에 적합한 형태로 저장되는 계층이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시각화 또는 데이터 서빙에 필요한 데이터 형태가 정해지면, Redshift에 테이블 형태로 저장된 Silver Layer 데이터를 추출한다. 이 과정에서는 dbt(Data Build Tool)를 이용해 SQL Query로 데이터를 변환하는 방식을 사용했다. 변환된 데이터는 다시 Redshift의 Gold 스키마에 저장되며, 테이블 이름 앞에 'mart_'라는 prefix를 붙여 저장했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. Serving Layer&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 이용자들이 데이터를 확인할 수 있는 Serving Layer에서는 크게 2가지 기능을 제공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Tableau를 사용한 시각화(대시보드 필터링 및 데이터 조작 가능)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;환율 대시보드 (1) (1).png&quot; data-origin-width=&quot;1697&quot; data-origin-height=&quot;755&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chr9dC/btsL94H2gAD/6hVgA9ZbgIteaCxrwtf4U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chr9dC/btsL94H2gAD/6hVgA9ZbgIteaCxrwtf4U0/img.png&quot; data-alt=&quot;Tableau 대시보드 예시(환율 관련 대시보드)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chr9dC/btsL94H2gAD/6hVgA9ZbgIteaCxrwtf4U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchr9dC%2FbtsL94H2gAD%2F6hVgA9ZbgIteaCxrwtf4U0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1697&quot; height=&quot;755&quot; data-filename=&quot;환율 대시보드 (1) (1).png&quot; data-origin-width=&quot;1697&quot; data-origin-height=&quot;755&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Tableau 대시보드 예시(환율 관련 대시보드)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. AWS Lambda를 사용해 FastAPI 서버로 데이터 호출&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI 서버를 통해 제공되는 데이터는 RDS에 따로 저장하여 대기 시간을 최소화했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (8).png&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCxLnc/btsL9deESHw/MJE2q6uVDQPenkKGq9k37k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCxLnc/btsL9deESHw/MJE2q6uVDQPenkKGq9k37k/img.png&quot; data-alt=&quot;FastAPI 서버 Swagger UI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCxLnc/btsL9deESHw/MJE2q6uVDQPenkKGq9k37k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCxLnc%2FbtsL9deESHw%2FMJE2q6uVDQPenkKGq9k37k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1522&quot; height=&quot;747&quot; data-filename=&quot;image (8).png&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;747&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FastAPI 서버 Swagger UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (9).png&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0uSuH/btsMah76q6b/YrSLH71n8PyI4fVa72bFgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0uSuH/btsMah76q6b/YrSLH71n8PyI4fVa72bFgK/img.png&quot; data-alt=&quot;FastAPI 서버 Swagger UI에서 테스트한 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0uSuH/btsMah76q6b/YrSLH71n8PyI4fVa72bFgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0uSuH%2FbtsMah76q6b%2FYrSLH71n8PyI4fVa72bFgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1412&quot; height=&quot;746&quot; data-filename=&quot;image (9).png&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FastAPI 서버 Swagger UI에서 테스트한 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도전 과제(어려웠던 점)&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;과거 데이터 수집 문제&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: 10년치 데이터를 수집을 목표로 잡았으나, API 일일 호출량 제한으로 인해 과거 데이터를 수집하는 데 어려움을 겪었다.&lt;/li&gt;
&lt;li&gt;해결: Airflow DAG의 Backfill을 이용한다면 일일 호출량 제한에 걸릴 확률이 매우 높았기 때문에, 1회 호출 시 과거 데이터 기간을 길게 잡아 한꺼번에 수집한 후, 파티셔닝 키를 기준으로 다시 나누어 S3에 저장하는 방식을 채택했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;부적합한 데이터 읽기 방식 선택으로 AWS Glue 실행 시간 지연&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: AWS Glue Crawler가 데이터를 읽을 때 의도치 않게 과도하게 많은 파티션으로 분할하는 경우가 발생했고, 따라서 각 파티션을 처리하는데 많은 시간이 소요되는 문제가 발생했다.&lt;/li&gt;
&lt;li&gt;해결: &lt;a href=&quot;https://aws.amazon.com/blogs/big-data/improve-query-performance-using-aws-glue-partition-indexes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS Glue 공식 문서&lt;/a&gt;를 참고해 파티션 처리 방식을 재검토했고, 데이터 읽기에 최적화된 메서드로 변경하여 성능을 개선했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;데이터 일관성 문제&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: 다양한 소스에서 수집하는 데이터 사이의 데이터 형식(날짜 등)이 불일치해 통합 등의 문제가 발생했다.&lt;/li&gt;
&lt;li&gt;해결: 데이터 표준화 규칙을 설정하고, 이에 맞추어 데이터 형식을 검사하는 로직을 DAG에 추가했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 소스 문제&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: 주식, 코인 등의 데이터는 종류가 매우 많아 선정하기 어려웠고, 채권/ETF 등의 데이터는 수집하기 어렵거나 제한된 날짜의 데이터만 가지고 있는 경우가 있어 수집 문제가 발생했다.&lt;/li&gt;
&lt;li&gt;해결: 'API 호출 + 웹 크롤링' 등 다양한 방법을 결합해 사용한 뒤 데이터를 표준화해 저장하는 방식을 채택했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;팀원 간 개발 환경 통일 문제&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: 팀원 간 작업 환경(Mac 2명, Linux 1명, Windows(WSL) 1명) 및 코드 린팅&amp;amp;포맷팅 도구가 서로 달라 코드 및 &lt;a href=&quot;https://brunch.co.kr/@hongjyoun/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;개행 표현(LF/CRLF)&lt;/a&gt;이 다른 문제가 발생&lt;/li&gt;
&lt;li&gt;해결: 코드 린터 및 포맷터 도구로 Ruff를 사용하고, 개행 표현의 경우 Windows 환경에서 .gitattributes 파일을 통해 Git에 push할 경우 개행 표현 방식을 LF로 고정하도록 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ruff: black, flake8, isort의 기능을 통합한 도구로, Rust로 구현되어 속도가 빠르다. 최근 Airflow, Pandas, FastAPI 등 대중적인 라이브러리에서 사용하며 Github Star의 성장세가 가파르게 상승하는 주목받는 도구.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인프라/클라우드 구성 퀄리티 향상에 대한 과금 문제&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: 데브코스에서 지원 받는 항목이 아닌 경우 팀원들이 사비를 들여 서비스 과금을 부담해야 하는 상황이기 때문에, 인프라 및 클라우드 구성 퀄리티 향상과 과금 부담 간의 딜레마가 발생&lt;/li&gt;
&lt;li&gt;해결: 퀄리티 향상과 더불어 과금을 최소화 하기 위해 프로젝트를 단계적으로 Develop하는 방식을 선택
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1단계: MWAA를 사용해 개발 기간을 단축, 이외에는 데브코스에서 지원되는 서비스 항목을 최대한 활용(Glue, Redshift, S3 등) - 테라폼으로 프로비저닝하여 DAG을 실행시키는 시간 동안에만 MWAA 실행&lt;/li&gt;
&lt;li&gt;2단계: Glue 대신 EMR을 사용해 Spark로 데이터 변환하는 과정을 처리&lt;/li&gt;
&lt;li&gt;3단계: EKS를 사용해 Airflow, Spark Engine 등을 구성하고, 컨테이너를 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DAG 작성 방식 통일 문제&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제: Airflow DAG 코드 작성 시 팀원 간 PythonOperator 사용과 &lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/taskflow.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Taskflow API&lt;/a&gt; 사용 방식 간 통일이 되지 않는 문제&lt;/li&gt;
&lt;li&gt;해결: Taskflow API 방식을 사용할 경우 XCom 데이터가 json 직렬화 가능한 값으로만 제한되며, 의존성 충돌 문제, 디버깅 및 유지보수, 성능 문제 등이 발생할 수 있어 PythonOperator를 사용하는 방식으로 통일
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Taskflow가 무조건 나쁘다는 뜻은 아님! Pythonic한 코드를 작성할 수 있으며, 간단한 ETL 프로세스에서는 오히려 강점이 있음. 대신 PythonOperator가 커스텀 및 디버깅에 유리한 점이 있다는 것!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결과 및 성과&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;성과 요약 (프로젝트 및 개인 관점)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자들에게 유용한 인사이트 제공
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 금융 데이터 기반의 분석 및 투자 도구를 제공&lt;/li&gt;
&lt;li&gt;과거부터 현재까지의 데이터를 1일 단위로 제공해 잠재적 리스크 및 안정적인 포트폴리오 구성 가능&lt;/li&gt;
&lt;li&gt;여러가지 포트폴리오에 대한 백테스팅 도구를 제안해 안정적이면서 이익을 극대화한 포트폴리오 구성에 도움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기술적 역량 향상
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 가능한 파이프라인을 구축해볼 수 있는 경험이 됨&lt;/li&gt;
&lt;li&gt;단기간에 새로운 기술들을 익혀 프로젝트에 적용&lt;/li&gt;
&lt;li&gt;매니지드 서비스들의 가격 대비 성능에 대한 감각&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제한점 및 향후 계획&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 소스 추가, automation 고도화&lt;/li&gt;
&lt;li&gt;스트리밍 데이터 처리&lt;/li&gt;
&lt;li&gt;머신러닝 모델 적용을 통한 포트폴리오 분석 고도화&lt;/li&gt;
&lt;li&gt;인프라 고도화 (MWAA + Glue &amp;rarr; EMR on EKS 등)&lt;/li&gt;
&lt;li&gt;데이터 품질 테스트 추가&lt;/li&gt;
&lt;li&gt;웹/앱서비스로의 확장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;NEXT STEP&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에는 아래 내용들을 적용해 프로젝트의 퀄리티를 향상시킨 결과를 소개하고자 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 처리 - 다양한 작업이 한 번에 실행할 수 있는 코드에 비동기를 적용하여 작업 시간을 줄임&lt;/li&gt;
&lt;li&gt;테스트 도구 추가(Unittest) - 데이터 수집 및 품질에 대한 테스트 도구를 추가해 신뢰성 향상&lt;/li&gt;
&lt;li&gt;웹 UI 추가 - 웹서비스로의 확장을 위한 UI를 추가&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>Airflow</category>
      <category>AWS</category>
      <category>AWS Glue</category>
      <category>MWAA</category>
      <category>spark</category>
      <category>데이터엔지니어</category>
      <category>데이터엔지니어링</category>
      <category>데이터엔지니어링 프로젝트</category>
      <category>메달리온 아키텍처</category>
      <category>프로그래머스 데이터엔지니어링 데브코스</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/283</guid>
      <comments>https://minding-deep-learning.tistory.com/283#entry283comment</comments>
      <pubDate>Thu, 6 Feb 2025 16:16:40 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스 코딩테스트/MySQL] 특정 물고기를 잡은 총 수 구하기</title>
      <link>https://minding-deep-learning.tistory.com/282</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제&amp;nbsp;설명&lt;/b&gt; &lt;br /&gt;낚시앱에서&amp;nbsp;사용하는&amp;nbsp;FISH_INFO&amp;nbsp;테이블은&amp;nbsp;잡은&amp;nbsp;물고기들의&amp;nbsp;정보를&amp;nbsp;담고&amp;nbsp;있습니다.&amp;nbsp;FISH_INFO&amp;nbsp;테이블의&amp;nbsp;구조는&amp;nbsp;다음과&amp;nbsp;같으며&amp;nbsp;ID,&amp;nbsp;FISH_TYPE,&amp;nbsp;LENGTH,&amp;nbsp;TIME은&amp;nbsp;각각&amp;nbsp;잡은&amp;nbsp;물고기의&amp;nbsp;ID,&amp;nbsp;물고기의&amp;nbsp;종류(숫자),&amp;nbsp;잡은&amp;nbsp;물고기의&amp;nbsp;길이(cm),&amp;nbsp;물고기를&amp;nbsp;잡은&amp;nbsp;날짜를&amp;nbsp;나타냅니다. &lt;br /&gt;&lt;br /&gt;Column&amp;nbsp;name Type Nullable &lt;br /&gt;ID INTEGER FALSE &lt;br /&gt;FISH_TYPE INTEGER FALSE &lt;br /&gt;LENGTH FLOAT TRUE &lt;br /&gt;TIME DATE FALSE &lt;br /&gt;단,&amp;nbsp;잡은&amp;nbsp;물고기의&amp;nbsp;길이가&amp;nbsp;10cm&amp;nbsp;이하일&amp;nbsp;경우에는&amp;nbsp;LENGTH&amp;nbsp;가&amp;nbsp;NULL&amp;nbsp;이며,&amp;nbsp;LENGTH&amp;nbsp;에&amp;nbsp;NULL&amp;nbsp;만&amp;nbsp;있는&amp;nbsp;경우는&amp;nbsp;없습니다. &lt;br /&gt;&lt;br /&gt;FISH_NAME_INFO&amp;nbsp;테이블은&amp;nbsp;물고기의&amp;nbsp;이름에&amp;nbsp;대한&amp;nbsp;정보를&amp;nbsp;담고&amp;nbsp;있습니다.&amp;nbsp;FISH_NAME_INFO&amp;nbsp;테이블의&amp;nbsp;구조는&amp;nbsp;다음과&amp;nbsp;같으며,&amp;nbsp;FISH_TYPE,&amp;nbsp;FISH_NAME&amp;nbsp;은&amp;nbsp;각각&amp;nbsp;물고기의&amp;nbsp;종류(숫자),&amp;nbsp;물고기의&amp;nbsp;이름(문자)&amp;nbsp;입니다. &lt;br /&gt;&lt;br /&gt;Column&amp;nbsp;name Type Nullable &lt;br /&gt;FISH_TYPE INTEGER FALSE &lt;br /&gt;FISH_NAME VARCHAR FALSE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제&lt;/b&gt; &lt;br /&gt;FISH_INFO&amp;nbsp;테이블에서&amp;nbsp;잡은&amp;nbsp;BASS와&amp;nbsp;SNAPPER의&amp;nbsp;수를&amp;nbsp;출력하는&amp;nbsp;SQL&amp;nbsp;문을&amp;nbsp;작성해주세요. &lt;br /&gt;&lt;br /&gt;컬럼명은&amp;nbsp;'FISH_COUNT`로&amp;nbsp;해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt; &lt;br /&gt;예를&amp;nbsp;들어&amp;nbsp;FISH_INFO&amp;nbsp;테이블이&amp;nbsp;다음과&amp;nbsp;같고 &lt;br /&gt;&lt;br /&gt;ID &lt;br /&gt;|&amp;nbsp;FISH_TYPE&amp;nbsp;|&amp;nbsp;LENGTH&amp;nbsp;|&amp;nbsp;TIME&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;--&amp;nbsp;|&amp;nbsp;--&amp;nbsp;|&amp;nbsp;--&amp;nbsp;|&amp;nbsp;--&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;0&amp;nbsp;|&amp;nbsp;0&amp;nbsp;|&amp;nbsp;30&amp;nbsp;|&amp;nbsp;2021/12/04&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;1&amp;nbsp;|&amp;nbsp;0&amp;nbsp;|&amp;nbsp;50&amp;nbsp;|&amp;nbsp;2020/03/07&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;2&amp;nbsp;|&amp;nbsp;0&amp;nbsp;|&amp;nbsp;40&amp;nbsp;|&amp;nbsp;2020/03/07&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;3&amp;nbsp;|&amp;nbsp;1&amp;nbsp;|&amp;nbsp;20&amp;nbsp;|&amp;nbsp;2022/03/09&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;4&amp;nbsp;|&amp;nbsp;1&amp;nbsp;|&amp;nbsp;NULL&amp;nbsp;|&amp;nbsp;2022/04/08&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;5&amp;nbsp;|&amp;nbsp;2&amp;nbsp;|&amp;nbsp;13&amp;nbsp;|&amp;nbsp;2021/04/28&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;6&amp;nbsp;|&amp;nbsp;0&amp;nbsp;|&amp;nbsp;60&amp;nbsp;|&amp;nbsp;2021/07/27&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;7&amp;nbsp;|&amp;nbsp;0&amp;nbsp;|&amp;nbsp;55&amp;nbsp;|&amp;nbsp;2021/01/18&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;8&amp;nbsp;|&amp;nbsp;2&amp;nbsp;|&amp;nbsp;73&amp;nbsp;|&amp;nbsp;2020/01/28&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;9&amp;nbsp;|&amp;nbsp;2&amp;nbsp;|&amp;nbsp;73&amp;nbsp;|&amp;nbsp;2021/04/08&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;10&amp;nbsp;|&amp;nbsp;2&amp;nbsp;|&amp;nbsp;22&amp;nbsp;|&amp;nbsp;2020/06/28&amp;nbsp;| &lt;br /&gt;|&amp;nbsp;11&amp;nbsp;|&amp;nbsp;2&amp;nbsp;|&amp;nbsp;17&amp;nbsp;|&amp;nbsp;2022/12/23&amp;nbsp;| &lt;br /&gt;&lt;br /&gt;FISH_NAME_INFO&amp;nbsp;테이블이&amp;nbsp;다음과&amp;nbsp;같다면 &lt;br /&gt;&lt;br /&gt;FISH_TYPE FISH_NAME &lt;br /&gt;0 BASS &lt;br /&gt;1 SNAPPER &lt;br /&gt;2 ANCHOVY &lt;br /&gt;'BASS'&amp;nbsp;는&amp;nbsp;물고기&amp;nbsp;종류&amp;nbsp;0에&amp;nbsp;해당하고,&amp;nbsp;'SNAPPER'&amp;nbsp;는&amp;nbsp;물고기&amp;nbsp;종류&amp;nbsp;1에&amp;nbsp;해당하므로&amp;nbsp;잡은&amp;nbsp;'BASS'&amp;nbsp;와&amp;nbsp;'SNAPPER'&amp;nbsp;수는&amp;nbsp;7마리입니다. &lt;br /&gt;&lt;br /&gt;FISH_COUNT &lt;br /&gt;7&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 풀이&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1737435711094&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT SUM(FISH_COUNT) AS FISH_COUNT
FROM (
    SELECT COUNT(fni.FISH_NAME) AS FISH_COUNT
    FROM FISH_INFO fi
    JOIN FISH_NAME_INFO fni ON fi.FISH_TYPE = fni.FISH_TYPE
    WHERE fni.FISH_NAME = 'BASS' OR fni.FISH_NAME = 'SNAPPER'
    GROUP BY fni.FISH_NAME
) AS subquery;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FISH_NAME이라는 칼럼은 FISH_NAME_INFO라는 테이블에 따로 관리되고 있었으므로 JOIN을 통해 'BASS'와 'SNAPPER'라는 이름을 가진 물고기를 WHERE문으로 가려냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COUNT 함수와 GROUP BY를 통해 각 물고기의 숫자를 알아냈으나, 문제에서 원하는 것은 두 물고기의 합계였으므로 위 쿼리를 서브쿼리로 돌린 다음, SUM 함수를 통해 하나의 칼럼으로 만들었다.&lt;/p&gt;</description>
      <category>Minding's Programming/프로그래머스 코딩테스트</category>
      <category>MySQL</category>
      <category>SQL연습</category>
      <category>sql코딩테스트</category>
      <category>코딩테스트</category>
      <category>특정 물고기를 잡은 총 수 구하기</category>
      <category>프로그래머스</category>
      <category>프로그래머스 코딩테스트</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/282</guid>
      <comments>https://minding-deep-learning.tistory.com/282#entry282comment</comments>
      <pubDate>Tue, 21 Jan 2025 14:13:57 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka/Python] Kafka 설치 방법(Conduktor)</title>
      <link>https://minding-deep-learning.tistory.com/281</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;실시간 데이터 처리 플랫폼의 대표라고 할 수 있는 Kafka에 대해 공부하기 위해서, Kafka를 어떻게 설치하는지 알아보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 글에서는 Kafka를 Docker Container로 실행할 예정이기 때문에, 기본적으로 Docker가 설치되어 있어야 한다. Docker 설치는 아래 글을 참고하길 바란다. (Windows의 경우 WSL2 설치가 필요할 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://minding-deep-learning.tistory.com/262&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&amp;nbsp;Docker의 개념 및 기본 실행 명령어&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733380385665&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Docker] Docker의 개념 및 기본 실행 명령어&quot; data-og-description=&quot;Docker&amp;nbsp;Docker는 애플리케이션을 컨테이너라는 독립된 환경에서 실행할 수 있게 해주는 Linux 컨테이너 기반 플랫폼이다. 애플리케이션과 관련된 라이브러리와 종속성을 하나의 패키지로 묶어 어디&quot; data-og-host=&quot;minding-deep-learning.tistory.com&quot; data-og-source-url=&quot;https://minding-deep-learning.tistory.com/262&quot; data-og-url=&quot;https://minding-deep-learning.tistory.com/262&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QszRb/hyXKnBRWm9/ej5YFASu6X0L3emMZkAal0/img.png?width=800&amp;amp;height=554&amp;amp;face=0_0_800_554,https://scrap.kakaocdn.net/dn/bhsWbG/hyXKkZr67r/MnJ3h0kVkOOVppf9ziKkN0/img.png?width=800&amp;amp;height=554&amp;amp;face=0_0_800_554,https://scrap.kakaocdn.net/dn/bd5Nqt/hyXKwFAtqH/AUSHzn1ZgG1HAu4KErjTk1/img.png?width=1280&amp;amp;height=887&amp;amp;face=0_0_1280_887&quot;&gt;&lt;a href=&quot;https://minding-deep-learning.tistory.com/262&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://minding-deep-learning.tistory.com/262&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QszRb/hyXKnBRWm9/ej5YFASu6X0L3emMZkAal0/img.png?width=800&amp;amp;height=554&amp;amp;face=0_0_800_554,https://scrap.kakaocdn.net/dn/bhsWbG/hyXKkZr67r/MnJ3h0kVkOOVppf9ziKkN0/img.png?width=800&amp;amp;height=554&amp;amp;face=0_0_800_554,https://scrap.kakaocdn.net/dn/bd5Nqt/hyXKwFAtqH/AUSHzn1ZgG1HAu4KErjTk1/img.png?width=1280&amp;amp;height=887&amp;amp;face=0_0_1280_887');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Docker] Docker의 개념 및 기본 실행 명령어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Docker&amp;nbsp;Docker는 애플리케이션을 컨테이너라는 독립된 환경에서 실행할 수 있게 해주는 Linux 컨테이너 기반 플랫폼이다. 애플리케이션과 관련된 라이브러리와 종속성을 하나의 패키지로 묶어 어디&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;minding-deep-learning.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Kafka 설치하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;kafka는 Docker Compose를 통해 실행할 예정이며, 아래의 Github repo를 이용할 예정이다. (kafka는 기본적으로 오픈 소스로 공식 홈페이지가 따로 있지만, 아래 repo가 docker-compose 형태로 잘 구성해 놓았기 때문에 이를 이용한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/conduktor/kafka-stack-docker-compose&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733380440448&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - conduktor/kafka-stack-docker-compose: docker compose files to create a fully working kafka stack&quot; data-og-description=&quot;docker compose files to create a fully working kafka stack - conduktor/kafka-stack-docker-compose&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot; data-og-url=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bzJ82Z/hyXKoU4ZPD/F10tBKQKHmknibTAiXFq30/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/YwSnA/hyXKoneNfW/QicRfBgS6furJXEG9QiWj1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bzJ82Z/hyXKoU4ZPD/F10tBKQKHmknibTAiXFq30/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/YwSnA/hyXKoneNfW/QicRfBgS6furJXEG9QiWj1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - conduktor/kafka-stack-docker-compose: docker compose files to create a fully working kafka stack&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;docker compose files to create a fully working kafka stack - conduktor/kafka-stack-docker-compose&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;git clone을 통해 위 repo의 파일들을 다운로드 받아준 뒤, 해당 폴더로 이동한다.&lt;/p&gt;
&lt;pre id=&quot;code_1733380599493&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git clone https://github.com/conduktor/kafka-stack-docker-compose.git

cd kafka-stack-docker-compose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;폴더를 살펴보면 여러가지 yml파일이 보이는데, 그 중 컴퓨터 사양에 따라 하나를 골라서 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1733380705326&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;❯ ls -tl
total 60
-rw-r--r-- 1 minding minding 11345 Dec  5 15:36 LICENSE
-rw-r--r-- 1 minding minding  8089 Dec  5 15:36 README.md
-rw-r--r-- 1 minding minding  1043 Dec  5 15:36 conduktor.yml
-rw-r--r-- 1 minding minding  4833 Dec  5 15:36 full-stack.yml
-rw-r--r-- 1 minding minding  2236 Dec  5 15:36 jmx-exporter.yml
-rwxr-xr-x 1 minding minding  2185 Dec  5 15:36 test.sh
-rw-r--r-- 1 minding minding  3934 Dec  5 15:36 zk-multiple-kafka-multiple-schema-registry.yml
-rw-r--r-- 1 minding minding  3396 Dec  5 15:36 zk-multiple-kafka-multiple.yml
-rw-r--r-- 1 minding minding  1954 Dec  5 15:36 zk-multiple-kafka-single.yml
-rw-r--r-- 1 minding minding  2673 Dec  5 15:36 zk-single-kafka-multiple.yml
-rw-r--r-- 1 minding minding  1326 Dec  5 15:36 zk-single-kafka-single.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사양에 따른 yml 파일 실행은 아래와 같다. (각 사양의 기준은 따로 없으니, 실행해보고 가장 잘 실행되는 것을 선택하자.)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;full-stack.yml: 최고 사양&lt;/li&gt;
&lt;li&gt;zk-single-kafka-single.yml: zookeeper와 kafka 모두 single로 실행 (저사양)&lt;/li&gt;
&lt;li&gt;zk-single-kafka-multiple.yml: zookeeper는 single, kafka는 multiple로 실행 (중간 사양)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;full-stack.yml 파일을 살펴보자. zookeeper와 kafka 이외에도 kafka connect, kafka schema registry, kafka rest proxy 등 여러 라이브러리들이 함께 있는 것을 알 수 있다. 이 repo를 만든 conduktor 회사의 console도 포함되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1733381218345&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# full stack

version: '2.1'

services:
  zoo1:
    image: confluentinc/cp-zookeeper:7.3.2
    hostname: zoo1
    container_name: zoo1
    ports:
      - &quot;2181:2181&quot;
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_SERVER_ID: 1
      ZOOKEEPER_SERVERS: zoo1:2888:3888

  kafka1:
    image: confluentinc/cp-kafka:7.3.2
    hostname: kafka1
    container_name: kafka1
    ports:
      - &quot;9092:9092&quot;
      - &quot;29092:29092&quot;
      - &quot;9999:9999&quot;
    environment:
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:19092,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092,DOCKER://host.docker.internal:29092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_ZOOKEEPER_CONNECT: &quot;zoo1:2181&quot;
      KAFKA_BROKER_ID: 1
      KAFKA_LOG4J_LOGGERS: &quot;kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO&quot;
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_JMX_PORT: 9001
      KAFKA_JMX_HOSTNAME: ${DOCKER_HOST_IP:-127.0.0.1}
      KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer
      KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: &quot;true&quot;
    depends_on:
      - zoo1

  kafka-schema-registry:
    image: confluentinc/cp-schema-registry:7.3.2
    hostname: kafka-schema-registry
    container_name: kafka-schema-registry
    ports:
      - &quot;8081:8081&quot;
    environment:
      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
      SCHEMA_REGISTRY_HOST_NAME: kafka-schema-registry
      SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081
    depends_on:
      - zoo1
      - kafka1

  
  kafka-rest-proxy:
    image: confluentinc/cp-kafka-rest:7.3.2
    hostname: kafka-rest-proxy
    container_name: kafka-rest-proxy
    ports:
      - &quot;8082:8082&quot;
    environment:
      # KAFKA_REST_ZOOKEEPER_CONNECT: zoo1:2181
      KAFKA_REST_LISTENERS: http://0.0.0.0:8082/
      KAFKA_REST_SCHEMA_REGISTRY_URL: http://kafka-schema-registry:8081/
      KAFKA_REST_HOST_NAME: kafka-rest-proxy
      KAFKA_REST_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
    depends_on:
      - zoo1
      - kafka1
      - kafka-schema-registry

  
  kafka-connect:
    image: confluentinc/cp-kafka-connect:7.3.2
    hostname: kafka-connect
    container_name: kafka-connect
    ports:
      - &quot;8083:8083&quot;
    environment:
      CONNECT_BOOTSTRAP_SERVERS: &quot;kafka1:19092&quot;
      CONNECT_REST_PORT: 8083
      CONNECT_GROUP_ID: compose-connect-group
      CONNECT_CONFIG_STORAGE_TOPIC: docker-connect-configs
      CONNECT_OFFSET_STORAGE_TOPIC: docker-connect-offsets
      CONNECT_STATUS_STORAGE_TOPIC: docker-connect-status
      CONNECT_KEY_CONVERTER: io.confluent.connect.avro.AvroConverter
      CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL: 'http://kafka-schema-registry:8081'
      CONNECT_VALUE_CONVERTER: io.confluent.connect.avro.AvroConverter
      CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: 'http://kafka-schema-registry:8081'
      CONNECT_INTERNAL_KEY_CONVERTER: &quot;org.apache.kafka.connect.json.JsonConverter&quot;
      CONNECT_INTERNAL_VALUE_CONVERTER: &quot;org.apache.kafka.connect.json.JsonConverter&quot;
      CONNECT_REST_ADVERTISED_HOST_NAME: &quot;kafka-connect&quot;
      CONNECT_LOG4J_ROOT_LOGLEVEL: &quot;INFO&quot;
      CONNECT_LOG4J_LOGGERS: &quot;org.apache.kafka.connect.runtime.rest=WARN,org.reflections=ERROR&quot;
      CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: &quot;1&quot;
      CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: &quot;1&quot;
      CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: &quot;1&quot;
      CONNECT_PLUGIN_PATH: '/usr/share/java,/etc/kafka-connect/jars,/usr/share/confluent-hub-components'
    volumes:
      - ./connectors:/etc/kafka-connect/jars/
    depends_on:
      - zoo1
      - kafka1
      - kafka-schema-registry
      - kafka-rest-proxy
    command:
      - bash
      - -c
      - |
        confluent-hub install --no-prompt debezium/debezium-connector-mysql:latest
        confluent-hub install --no-prompt confluentinc/kafka-connect-datagen:0.4.0
        /etc/confluent/docker/run

  
  ksqldb-server:
    image: confluentinc/cp-ksqldb-server:7.3.2
    hostname: ksqldb-server
    container_name: ksqldb-server
    ports:
      - &quot;8088:8088&quot;
    environment:
      KSQL_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
      KSQL_LISTENERS: http://0.0.0.0:8088/
      KSQL_KSQL_SERVICE_ID: ksqldb-server_
    depends_on:
      - zoo1
      - kafka1

  postgresql:
    hostname: postgresql
    container_name: postgresql
    extends:
      service: postgresql
      file: conduktor.yml 
            
  conduktor-console:
    hostname: conduktor-console
    container_name: conduktor-console
    extends:
      service: conduktor-console
      file: conduktor.yml

volumes:
  pg_data: {}
  conduktor_data: {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 docker compose 명령어를 통해 해당 파일을 Docker를 통해 실행시켜준다. 실행에 시간이 다소 걸린다.&lt;/p&gt;
&lt;pre id=&quot;code_1733381453356&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose -f full-stack.yml up&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm6k9P/btsK8l5GOZv/zmhMpAZPGvmF80mNqKCzTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm6k9P/btsK8l5GOZv/zmhMpAZPGvmF80mNqKCzTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm6k9P/btsK8l5GOZv/zmhMpAZPGvmF80mNqKCzTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm6k9P%2FbtsK8l5GOZv%2FzmhMpAZPGvmF80mNqKCzTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1273&quot; height=&quot;417&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Docker Desktop 프로그램에서 컨테이너가 실행된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 full-stack.yml을 통해 컨테이너를 실행했다면, http://localhost:8080/를 통해 conduktor console 화면도 확인할 수 있다. 로그인 후 아래 화면이 노출되는 것을 확인할 수 있다. (기본 ID: admin@admin.io / PW: admin), 초기 가입 화면을 통해 자신의 마음대로 입력할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dppSvw/btsK9h2vuzE/lbkMOVI47M29C1FVVDyGDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dppSvw/btsK9h2vuzE/lbkMOVI47M29C1FVVDyGDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dppSvw/btsK9h2vuzE/lbkMOVI47M29C1FVVDyGDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdppSvw%2FbtsK9h2vuzE%2FlbkMOVI47M29C1FVVDyGDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;961&quot; height=&quot;805&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/321fu/btsK8Il3DPV/ivUGArRc1asPA4RkZ0onv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/321fu/btsK8Il3DPV/ivUGArRc1asPA4RkZ0onv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/321fu/btsK8Il3DPV/ivUGArRc1asPA4RkZ0onv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F321fu%2FbtsK8Il3DPV%2FivUGArRc1asPA4RkZ0onv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;262&quot; height=&quot;418&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;왼쪽 메뉴바를 통해 topic, schema registry, consumer group 등을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Kafka에서 Python을 사용하기 위한 라이브러리 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Kafka를 Python으로 프로그래밍하기 위해서는 별도 라이브러리를 설치해 주어야한다. Python 라이브러리는 크게 2가지가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Confluent Kafka Python: Confluent(Kafka 개발자들이 모여만든 회사)에서 개발한 공식 kafka python 라이브러리&lt;/li&gt;
&lt;li&gt;Kafka-Python: 오픈소스 kafka python 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 글에서는 두 번째 Kafka-Python을 사용해 볼 예정이다. 아래 pip 명령어를 통해 라이브러리를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1733383163438&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install kafka-python&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 간단한 Producer를 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1733383324709&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# producer.py

from time import sleep
from json import dumps
from kafka import KafkaProducer

producer = KafkaProducer(
   bootstrap_servers=['localhost:9092'],
   value_serializer=lambda x: dumps(x).encode('utf-8')
)

for j in range(999):
   print(&quot;Iteration&quot;, j)
   data = {'counter': j}
   producer.send('topic_test', value=data) # key와 headers는 지정되어 있지 않은 상태
   sleep(0.5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 로컬 kafka 인스턴스를 연결하는 kafka producer 객체를 생성한다. 전송하려는 데이터는 json문자열로 변환 뒤 utf-8로 인코딩해 직렬화한다. 여기서 bootstrap_servers 파라미터는 리스트 형태로 연결할 1개 이상의 broker들을 지정해주는 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 있는 반복문에는 0.5초마다 &quot;topic_test&quot;라는 토픽과 반복 횟수 카운터를 데이터로 포함하는 이벤트를 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위 코드의 파일을 한번 실행해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1733383879026&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;❯ python producer.py
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5
Iteration 6
Iteration 7
Iteration 8
Iteration 9
Iteration 10
Iteration 11
.
.
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 파일이 실행된 것을 확인했다면, 이제 conduktor console로 돌아가 'Topic' 탭을 살펴보자. 'topic_test'라는 항목을 발견할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 consumer도 생성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1733387214535&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from kafka import KafkaConsumer
from json import loads
from time import sleep

consumer = KafkaConsumer(
   'topic_test',
   bootstrap_servers=['localhost:9092'],
   auto_offset_reset='earliest',
   enable_auto_commit=True,
   group_id='my-group-id',
   value_deserializer=lambda x: loads(x.decode('utf-8'))
)
for event in consumer:
   event_data = event.value
   # Do whatever you want
   print(event_data)
   sleep(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2초마다 'topic_test'라는 이름을 가진 topic에서 빠른 순서(earliest)대로 데이터를 가져오는 코드다. 실행하면, 위 producer에서 전송한대로 데이터를 받아오는 모습을 확인할 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>conduktor</category>
      <category>docker</category>
      <category>Docker Compose</category>
      <category>Kafka</category>
      <category>kafka container</category>
      <category>kafka docker</category>
      <category>kafka docker compose</category>
      <category>kafka python</category>
      <category>kafka 설치</category>
      <category>카프카</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/281</guid>
      <comments>https://minding-deep-learning.tistory.com/281#entry281comment</comments>
      <pubDate>Thu, 5 Dec 2024 16:15:51 +0900</pubDate>
    </item>
    <item>
      <title>[Python/Unittest] Unittest</title>
      <link>https://minding-deep-learning.tistory.com/280</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Unittest?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Python의 테스트 프레임워크인 unittest는 코드 상의 특정 기능을 테스트하기 위해 작성된다. 일반적으로 특정 입력을 주고 예상된 출력이 나오는지의 형태로 테스트한다. 최근 CI/CD의 자동화와 안정성이 중요해지면서 전체 코드의 테스트 커버리지의 중요성도 함께 높아졌는데, 이 때 Python코드에서는 unittest가 일반적으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;unittest는 테스트 케이스를 class 단위로 작성하고, 그 안에 포함된 여러가지 메서드를 통해 기능을 테스트한다. 아래 코드는 unittest의 예시다.&lt;/p&gt;
&lt;pre id=&quot;code_1733121481807&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == '__main__':
    unittest.main()
    
&amp;gt;&amp;gt;&amp;gt;
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;assertEqual(), assertTrue, assertFalse등의 메서드를 통해 위 함수의 결과값이 예상한 결과와 같은지 확인하고, 그에 따른 결과를 성공/실패로 판단해 결과를 출력해준다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>Python</category>
      <category>python unittest</category>
      <category>python 코드 테스트</category>
      <category>unittest</category>
      <category>unittest module</category>
      <category>유닛테스트</category>
      <category>파이썬</category>
      <category>파이썬 유닛테스트</category>
      <category>파이썬 코드테스트</category>
      <category>파이썬 테스트</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/280</guid>
      <comments>https://minding-deep-learning.tistory.com/280#entry280comment</comments>
      <pubDate>Mon, 2 Dec 2024 15:44:08 +0900</pubDate>
    </item>
    <item>
      <title>[Spark/Hive] Spark에서 Hive 메타 스토어 사용하기</title>
      <link>https://minding-deep-learning.tistory.com/279</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spark의 DB와 테이블&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;SparkSQL을 통해 임시 테이블을 만들어 SQL 조작이 가능하지만, Spark는 기본적으로 인메모리 기반이기 때문에, 세션이 종료되면 카탈로그라고 불리는 테이블과 뷰가 사라진다. 이 문제로 인해 Spark에서는 계속해서 사용해야 하는 테이블을 그때 그때 불러와줘야 하는 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Hive 메타스토어&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이를 해결하기 위해 Disk에 저장이 가능한 Hive와 호환이 되는 Persistent라는 카탈로그를 제공한다. 각 테이블들은 DB라고 부르는 폴더와 같은 구조로 관리된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;353&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhioNw/btsK40fqMqU/S4USFqTY1fpiFnKZMSyzpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhioNw/btsK40fqMqU/S4USFqTY1fpiFnKZMSyzpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhioNw/btsK40fqMqU/S4USFqTY1fpiFnKZMSyzpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhioNw%2FbtsK40fqMqU%2FS4USFqTY1fpiFnKZMSyzpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1116&quot; height=&quot;353&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 스토리지 기반의 테이블은 기본적으로 HDFS오 Parquet 포맷을 사용하며, Hive와 호환되는 메타스토어를 사용한다. 여기엔 두 종류의 테이블이 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Managed Table: Spark에서 실제 데이터와 메타 데이터 모두 관리&lt;/li&gt;
&lt;li&gt;Unmanaged (External) Table: Spark에서 메타 데이터만 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스토리지 기반 카탈로그 사용 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Hive와 호환되는 메타스토어를 사용하기 위해서는, SparkSession을 생성할 때 enableHiveSupport()라는 함수를 호출할 수 있다. 기본으로 'default'라는 이름의 데이터베이스(폴더)가 생성된다.&lt;/p&gt;
&lt;pre id=&quot;code_1733118148053&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql import SparkSession
spark = SparkSession \
   .builder \
   .appName(&quot;Python Spark Hive&quot;) \
   .enableHiveSupport() \
   .getOrCreate()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Managed Table 사용방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2가지 방법을 통해 테이블을 생성하고, 이 테이블이 저장될 위치를 가리키면 해당 위치에 저장된다. 기본 데이터 포맷은 parquet이다. 태블로나 PowerBi 등 외부에서도 Spark 테이블을 통해 처리하면 JDBC/ODBC등으로 Spark에 연결해 처리할 수 있다는 장점도 있다. 일반적으로 External Table보다 Managed Table로 처리하는 것이 성능이 우수하다.&lt;/p&gt;
&lt;pre id=&quot;code_1733118394837&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 테이블 생성(.saveAsTable() 또는 SQL의 CREATE 사용)
dataframe.saveAsTable(&quot;TableName&quot;)

# spark.sql.warehouse.dir(&quot;저장할위치&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;External Table 사용방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이미 HDFS에 존재하는 데이터에 스키마를 정의해서 사용하는 방법이다. LOCATION이라는 프로퍼티를 사용하고, 이 경우 실제 데이터는 제외한 메타데이터만 Spark 카탈로그에 기록된다.&lt;/p&gt;
&lt;pre id=&quot;code_1733118609414&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE table_name (
    column1 type1,
    column2 type2,
    column3 type3,
    &amp;hellip;
 )
 USING PARQUET
 LOCATION 'hdfs_path';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Spark</category>
      <category>spark</category>
      <category>spark db</category>
      <category>spark external table</category>
      <category>spark hive</category>
      <category>spark managed table</category>
      <category>spark 테이블</category>
      <category>sparksql</category>
      <category>SQL</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/279</guid>
      <comments>https://minding-deep-learning.tistory.com/279#entry279comment</comments>
      <pubDate>Mon, 2 Dec 2024 15:30:26 +0900</pubDate>
    </item>
    <item>
      <title>[Spark/pySpark] SparkSQL UDF(User Define Function)</title>
      <link>https://minding-deep-learning.tistory.com/278</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;UDF?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;UDF(User Define Function)는 SQL(Spark에서는 DataFrame까지)에서 적용할 수 있는 사용자 정의 함수다. 일반적으로 SQL에서 Scalar함수(UPPER, LOWER 등), Aggregation함수(SUM, MIN, AVG 등)를 제공하고 있지만, 상황에 따라서 특정 계산식이 반복해서 필요할 때가 있다. 그럴 때 유용하게 사용할 수 있는 것이 UDF이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;pySpark에서 UDF를 사용해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;UDF는 크게 두 가지의 종류가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Transformation 함수&lt;/li&gt;
&lt;li&gt;UDAF(User Define Aggregation Function): Aggregation 환경에서 사용하는 함수(Pyspark에서는 미지원)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;UDAF의 경우는 스칼라 또는 자바로 구현해야한다. 그렇다면, 다른 함수는 파이썬 환경에서 어떻게 구현할 수 있을까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python lambda 함수&lt;/li&gt;
&lt;li&gt;Python 일반 함수&lt;/li&gt;
&lt;li&gt;Python Pandas 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 일반적으로 사용되는 함수를 지정한 뒤, 해당 함수를 UDF로 등록해주면 된다. 3가지 방법 중에서는 Pandas함수가 가장 추천되는 방식이다. 레코드 하나씩 함수를 적용하는 것이 아닌, 데이터 전체를 Series 형태로 받아 처리할 수 있기 때문이다. UDF는 아래 메서드를 이용해 등록 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pyspark.sql.functions.udf - 데이터프레임에서만 사용 가능&lt;/li&gt;
&lt;li&gt;spark.udf.register - SQL 모두에서 사용 가능&lt;/li&gt;
&lt;li&gt;pyspark.sql.functions.pandas_udf - Pandas 함수 등록 가능(데이터프레임에서만 사용 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아래는 UDF를 파이썬 함수로 정의한 뒤, 등록과 사용하는 예시이다.&lt;/p&gt;
&lt;pre id=&quot;code_1733101054003&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# UDF 등록 및 사용 예시
import pyspark.sql.functions as F
from pyspark.sql.types import *
 
upperUDF = F.udf(lambda z:z.upper())
# 사용 시 withColumn() 메서드 사용
df.withColumn(&quot;Curated Name&quot;, upperUDF(&quot;Name&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1733101142647&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 파이썬 일반 함수 등록해 SQL로 사용하기
def upper(s):
   return s.upper()
   
# 먼저 테스트
upperUDF = spark.udf.register(&quot;upper&quot;, upper)
spark.sql(&quot;SELECT upper('aBcD')&quot;).show()

 # DataFrame 기반 SQL에 적용
df.createOrReplaceTempView(&quot;test&quot;)
spark.sql(&quot;&quot;&quot;SELECT name, upper(name) &quot;Curated Name&quot; FROM test&quot;&quot;&quot;).show()&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1733101639217&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Pandas UDF Scalar 함수 - 가장 추천되는 방식!
from pyspark.sql.functions import pandas_udf
import pandas as pd

# 데코레이터를 통해 udf를 annotation, Series 형태로 데이터를 처리(벌크 처리)
@pandas_udf(StringType())
def upper_udf2(s: pd.Series) -&amp;gt; pd.Series:
	return s.str.upper()
    
# upper_udf라는 이름으로 함수 등록
upperUDF = spark.udf.register(&quot;upper_udf&quot;, upper_udf2)
df.select(&quot;Name&quot;, upperUDF(&quot;Name&quot;)).show()
spark.sql(&quot;&quot;&quot;SELECT name, upper_udf(name) `Curated Name` FROM test&quot;&quot;&quot;).show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 3가지 방법 중 pandad udf가 좀 더 추천되는 이유는 데이터 처리를 Series 단위로 한꺼번에 처리할 수 있다는 장점과 함께, Apache Arrow를 통한 데이터 전송이 가능하기 때문이다. Apache Arrow를 통해 데이터 전송할 경우, python 객체를 java 객체로 바꿔 처리할 수 있어 좀 더 효율적이고 빠른 처리가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;덤으로, 이 경우는 pyspark에서는 할 수 없었던 Aggregation 함수도 등록할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1733102251139&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; # Pandas UDF로 Spark DataFrame/SQL에 Aggregation 함수 사용
 from pyspark.sql.functions import pandas_udf
 import pandas as pd
 
 @pandas_udf(FloatType())
 def average(v: pd.Series) -&amp;gt; float:
   return v.mean()
   
 averageUDF = spark.udf.register('average', average)
 spark.sql('SELECT average(b) FROM test').show()
 df.agg(averageUDF(&quot;b&quot;).alias(&quot;count&quot;)).show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Spark</category>
      <category>pyspark</category>
      <category>python spark</category>
      <category>spark</category>
      <category>spark udaf</category>
      <category>spark udf</category>
      <category>sparksql</category>
      <category>SQL</category>
      <category>udaf</category>
      <category>UDF</category>
      <category>스파크</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/278</guid>
      <comments>https://minding-deep-learning.tistory.com/278#entry278comment</comments>
      <pubDate>Mon, 2 Dec 2024 10:47:47 +0900</pubDate>
    </item>
    <item>
      <title>[Spark] Spark의 개념, 구조, 프로그램 실행 옵션</title>
      <link>https://minding-deep-learning.tistory.com/277</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spark?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spark는 2013년에 출시된 Scala 기반의 빅데이터 처리 기술로, YARN 등을 분산환경으로 사용한다. 최근엔 MapReduce와 Hive 대신 Spark가 많이 선택받고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spark의 구성 (3.0 기준)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spark 3.0 기준 아래와 같은 라이브러리로 구성되어 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spark Core: 각 App을 실행시키는 엔진 역할&lt;/li&gt;
&lt;li&gt;Spark SQL: SQL 등으로 DB, Dataframe 등을 조작할 수 있는 기능&lt;/li&gt;
&lt;li&gt;Spark ML: 머신러닝 기능을 사용할 수 있음&lt;/li&gt;
&lt;li&gt;Spark Streaming&lt;/li&gt;
&lt;li&gt;Spark GraphX&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spark의 특징 (vs MapReduce)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spark는 메모리를 우선 사용하며, 메모리가 부족해지면 디스크를 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MapReduce의 경우 디스크를 사용해 Spark에 비해 속도가 느림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spark는 하둡(YARN) 이외에도 다른 분산 컴퓨팅 환경을 지원한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k8s, Mesos 등 환경 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spark는 Pandas DataFrame과 개념적으로 동일한 데이터 구조도 지원함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MapReduce의 경우 key-value 포맷만 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;배치 데이터 처리, 스트림 데이터 처리, SQL, 머신러닝 등 다양한 컴퓨팅 방식 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spark 프로그래밍 API&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spark는 2가지 방식의 API를 지원한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RDD (Resilient Distrubuted Dataset)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;low-level의 API로, 세밀한 제어가 가능하지만 코딩 복잡도가 올라감&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DataFrame &amp;amp; Dataset
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;high-levle의 API로, 최근에 점점 많이 사용되는 추세&lt;/li&gt;
&lt;li&gt;Pandas의 Dataframe과 흡사한 형태로 데이터 조작 가능&lt;/li&gt;
&lt;li&gt;하지만, 꼭 필요한 경우가 아니라면 사용할 필요 없음 (ex. 머신러닝, feature engineering 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spark 데이터 시스템 사용 예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;대표적으로 Spark가 사용되는 예시는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대용량 비구조화된 데이터 처리 (ETL, ELT)&lt;/li&gt;
&lt;li&gt;ML 모델에 사용되는 대용량 피쳐 처리 (배치/스트림)&lt;/li&gt;
&lt;li&gt;Spark ML을 이용한 대용량 훈련 데이터 모델 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spark 프로그램의 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsSrR3/btsKZvn8F8X/92hCxsGqV0bXDy22jqNznK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsSrR3/btsKZvn8F8X/92hCxsGqV0bXDy22jqNznK/img.png&quot; data-alt=&quot;출처: AI Mind&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsSrR3/btsKZvn8F8X/92hCxsGqV0bXDy22jqNznK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsSrR3%2FbtsKZvn8F8X%2F92hCxsGqV0bXDy22jqNznK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;515&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: AI Mind&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spark는 크게 Driver와 Executer로 구분 지을 수 있다. Driver는 실행 코드의 마스터 역할, Executer는 실제 task를 실행하는 역할을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;좀 더 자세히 들어가서 각각의 역할에 대해 알아보자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Driver&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Driver는 실행 모드에 따라 실행 되는 곳이 달라진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Client: 노트북, spark shell 등처럼 개발/학습용으로 사용할 때&lt;/li&gt;
&lt;li&gt;Cluster: 개발 후 Spark Cluster 안에서 실행할 때 (실제 서비스용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 보통 SparkContext를 만들어 Spark 클러스터와 통신을 수행한다. 통신 상대는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cluster Manager (=YARN의 경우 Resource Manager)&lt;/li&gt;
&lt;li&gt;Executer&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Executer&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제 task를 실행해주는 역할이며, YARN에서는 Container가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spark 데이터 시스템 아키텍처&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqg09a/btsKZA39ddW/EPAqRYebSCaQPovmpvt1lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqg09a/btsKZA39ddW/EPAqRYebSCaQPovmpvt1lk/img.png&quot; data-alt=&quot;출처: 프로그래머스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqg09a/btsKZA39ddW/EPAqRYebSCaQPovmpvt1lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqg09a%2FbtsKZA39ddW%2FEPAqRYebSCaQPovmpvt1lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1162&quot; height=&quot;466&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 프로그래머스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spark는 기본적으로 Resource Manager라고 불리는 YARN, K8s 등 위에서 작동한다. 내부 데이터는 HDFS, AWS S3등에서 받아오는 형태이다. 외부에 있는 데이터(ex. RDS 등)는 주기적인 ETL을 통해서 최신화해 내부 데이터로 가져오거나, 필요할 때마다 바로 처리하는 방법을 사용한다. 이 처리 과정을 통해 외부 DB로 적재시키기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spark의 병렬 데이터 처리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Hadoop Map의 데이터 블록처럼, Spark에서는 파티션(partition)이라고 불리는 데이터 처리 단위가 존재한다. 하둡과 마찬가지로 128MB의 기본크기를 가지고 있다. 이렇게 파티션 단위로 나눠진 데이터를 메모리에 로드하여 Executor를 배정하고, 각각 따로 동시에 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kKFDD/btsK059sj4t/Ezv6KCkGufj0WK8j9PIkM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kKFDD/btsK059sj4t/Ezv6KCkGufj0WK8j9PIkM0/img.png&quot; data-alt=&quot;출처: Cloud Fundls&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kKFDD/btsK059sj4t/Ezv6KCkGufj0WK8j9PIkM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkKFDD%2FbtsK059sj4t%2FEzv6KCkGufj0WK8j9PIkM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;411&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Cloud Fundls&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위처럼 데이터(최상단)를 파티션으로 나눈 뒤, 이를 각 Executor에 배정해 데이터를 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;셔플링 (Shuffling)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;파티션 간 데이터 이동이 필요한 경우에는 셔플링이라는 현상이 발생한다. 대표적으로 아래 경우와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시적 파티션을 새롭게 하는 경우(ex. 파티션 수를 줄이기)&lt;/li&gt;
&lt;li&gt;시스템에 의해 이뤄지는 셔플링(ex. Group By, Sort 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;셔플링이 발생할 때는 Network를 타고 데이터가 이동한다.(서버가 여러 대인 경우) 오퍼레이션에 따라 파티션 수가 결정되며(기본값은 200개가 최대), random, hashing partition, range partition등의 오퍼레이션 옵션이 있다. 셔플링이 일어날 때는 Data Skew가 발생할 수 있다는 점을 유의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Spark</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/277</guid>
      <comments>https://minding-deep-learning.tistory.com/277#entry277comment</comments>
      <pubDate>Thu, 28 Nov 2024 17:55:56 +0900</pubDate>
    </item>
    <item>
      <title>[Hadoop] MapReduce 프로그래밍이란?</title>
      <link>https://minding-deep-learning.tistory.com/276</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MapReduce 프로그래밍의 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MapReduce 프로그래밍은 기본적으로 빅 데이터 처리를 위해 만들어졌기 때문에, 일반 데이터 처리와는 다른 특징이 있다. 큰 특징은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 셋은 Key, Value의 집합이며 변경 불가(immutable) - 포맷은 하나로 고정&lt;/li&gt;
&lt;li&gt;데이터 조작은 map과 reduce 2개의 오퍼레이션으로만 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 2개의 오퍼레이션은 항상 하나의 쌍으로 연속 실행&lt;/li&gt;
&lt;li&gt;이 두 오퍼레이션 코드를 개발자가 채워야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MapReduce 시스템이 Map의 결과를 Reduce단으로 모아줌
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 단계를 셔플링이라고 부르며, Network단을 통한 데이터 이동이 발생&lt;/li&gt;
&lt;li&gt;Map의 결과 중 key가 같은 것을 모아주고 Reduce로 보냄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Map과 Reduce&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Map: (k, v) --&amp;gt; [(k', v')*]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력은 시스템에 의해 주어지며, 입력으로 지정된 HDFS 파일에서 넘어옴&lt;/li&gt;
&lt;li&gt;key, value 페어를 새로운 key, value 페어 리스트로 변환한다.&lt;/li&gt;
&lt;li&gt;출력: 입력과 동일한 key, value 페어를 출력하거나, 출력이 없어도 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Reduce: (k', [v1', v2', v3', v4' ...]) --&amp;gt; (k&quot;, v&quot;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력은 시스템에 의해 주어지는데, Map의 출력 중 같은 key를 갖는 key, value 페어를 시스템이 묶어 입력&lt;/li&gt;
&lt;li&gt;key, value 리스트를 새로운 key, value 페어로 변환&lt;/li&gt;
&lt;li&gt;SQL의 GROUP BY와 흡사한 모습&lt;/li&gt;
&lt;li&gt;출력이 HDFS에 저장됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예시: WordCount&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blB7pg/btsKY4jJzNe/Ovp9obmqCp6eg9cqL357RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blB7pg/btsKY4jJzNe/Ovp9obmqCp6eg9cqL357RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blB7pg/btsKY4jJzNe/Ovp9obmqCp6eg9cqL357RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblB7pg%2FbtsKY4jJzNe%2FOvp9obmqCp6eg9cqL357RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;548&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MapReduce로 단어를 세는 프로그램을 작동시켜본다고 가정하면, 위와 같은 흐름으로 나타난다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;3개의 문장이 input으로 주어진다.&lt;/li&gt;
&lt;li&gt;각 문장 별로 단어를 나누어, mapping을 통해 단어 수를 센다&lt;/li&gt;
&lt;li&gt;같은 key를 가진 단어를 그룹으로 묶는다.&lt;/li&gt;
&lt;li&gt;같은 key를 가진 단어의 그룹을 Reducing하여, Input의 모든 문장에서 해당 단어가 나온 횟수를 표시한다.&lt;/li&gt;
&lt;li&gt;결과값을 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Spark</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/276</guid>
      <comments>https://minding-deep-learning.tistory.com/276#entry276comment</comments>
      <pubDate>Thu, 28 Nov 2024 16:03:51 +0900</pubDate>
    </item>
    <item>
      <title>[Hadoop] 하둡의 분산처리 시스템, YARN 개념 정리</title>
      <link>https://minding-deep-learning.tistory.com/275</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;YARN은 Hadoop 2.0에서부터 지원되는 하둡의 Resourc Management Layer로, 세부 리소스 관리가 가능한 범용 컴퓨팅 프레임워크이다. HDFS 위에서 실행되며, YARN을 통해 Spark, MapReduce, Tez 등의 다양한 애플리케이션이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;YARN의 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deniu1/btsKYxzBfr7/qrFyb1o3moAvyeJ9MuXUk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deniu1/btsKYxzBfr7/qrFyb1o3moAvyeJ9MuXUk0/img.jpg&quot; data-alt=&quot;YARN의 구조 (출처: GeeksforGeeks)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deniu1/btsKYxzBfr7/qrFyb1o3moAvyeJ9MuXUk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdeniu1%2FbtsKYxzBfr7%2FqrFyb1o3moAvyeJ9MuXUk0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;957&quot; height=&quot;699&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;699&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;YARN의 구조 (출처: GeeksforGeeks)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;YARN의 구조는 위 그림과 같다. 마스터 노드라고 할 수 있는 'Resource Manager'와 그 아래 슬레이브 노드인 'Node Manager'가 있다. 노드 매니저들은 리소스 매니저의 요구에 따라 자신이 가지고 있는 자원을 일부 넘겨주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;노드 매니저가 넘겨준다는 자원은 '컨테이너'라고 불린다. 컨테이너는 어떤 모듈을 실행시키는 구성 요소로, Java의 JVM과 비슷한 존재라고 생각할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;YARN의 동작&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMes9x/btsKY3rzt2J/I4gCMkijDuVtj8XCqegH4k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMes9x/btsKY3rzt2J/I4gCMkijDuVtj8XCqegH4k/img.jpg&quot; data-alt=&quot;YARN의 동작 (출처: GeeksforGeeks)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMes9x/btsKY3rzt2J/I4gCMkijDuVtj8XCqegH4k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMes9x%2FbtsKY3rzt2J%2FI4gCMkijDuVtj8XCqegH4k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;741&quot; height=&quot;461&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;YARN의 동작 (출처: GeeksforGeeks)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;YARN은 위와 같은 방식으로 동작한다. 하나씩 살펴보자면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Client에서 실행 코드(+환경 정보)를 Resource Manager(RM)에게 제출&lt;/li&gt;
&lt;li&gt;RM이 Application Manager(AM)를 실행시키기 위해 컨테이너를 할당함&lt;/li&gt;
&lt;li&gt;AM이 RM에 등록&lt;/li&gt;
&lt;li&gt;AM이 RM으로 코드 실행에 필요한 리소스 요구&lt;/li&gt;
&lt;li&gt;AM이 Node Manager로부터 컨테이너를 받아와 실행 시작&lt;/li&gt;
&lt;li&gt;클라이언트가 요구한 실행 코드가 컨테이너에서 실행됨&lt;/li&gt;
&lt;li&gt;RM을 통해 클라이언트가 App 상태 모니터링&lt;/li&gt;
&lt;li&gt;App 실행이 완료되면 RM에 대한 등록을 취소(제거)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Hadoop 3.0과 YARN 2.0&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;최신 버전의 하둡인 3.0버전에서는 YARN도 2.0버전을 사용한다. YARN 1.0버전과 비교해서 달라진 점을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;YARN 프로그램들의 논리적인 그룹(flow)으로 나눠서 자원 관리가 가능함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이를 통해 서버에서 Hbase를 기본 스토리지로 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파일 시스템(하둡 3.0)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네임노드의 경우 다수의 스탠바이 네임노드를 지원함&lt;/li&gt;
&lt;li&gt;HDFS, S3, Azure Storage, Azure DataLake Storage 등을 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Minding's Programming/Spark</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/275</guid>
      <comments>https://minding-deep-learning.tistory.com/275#entry275comment</comments>
      <pubDate>Thu, 28 Nov 2024 15:14:35 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow] Airflow API를 통해 모니터링하기</title>
      <link>https://minding-deep-learning.tistory.com/273</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Airflow API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow는 현재 실행되고 있는 airflow의 상태 등을 알 수 있고, 외부에서 조작이 가능하도록 하는 API를 제공하고 있다. 이 API를 사용하기 위해서는 몇 가지 설정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Airflow API 활성화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;airflow.cfg의 api 섹션에서 auth_backend의 값을 변경해야 한다. 일반적으로 docker-compose.yaml파일을 사용할 경우 이미 설정이 되어있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1731989660194&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker-compose.yaml 파일

    &amp;amp;airflow-common-env
    
    ...
    
    AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 아래 명령어를 통해 확인해볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1731989880233&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker exec -it {scheduler container 이름} airflow config get-value api auth_backends

&amp;gt;&amp;gt;&amp;gt;
airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Airflow API 사용자 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow API 사용자는 Webserver UI 또는 CLI를 통해 추가하거나 삭제할 수 있다. (Admin 권한이 있어야 함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Security] - [List Users] - [+]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vQ6PD/btsKO5Bdz6v/sGbgKsQquaKruenBS93XE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vQ6PD/btsKO5Bdz6v/sGbgKsQquaKruenBS93XE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vQ6PD/btsKO5Bdz6v/sGbgKsQquaKruenBS93XE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvQ6PD%2FbtsKO5Bdz6v%2FsGbgKsQquaKruenBS93XE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;909&quot; height=&quot;768&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 정보를 입력하고, Role에 'User'를 지정한다. (Admin 계정을 사용하는 것보다 이렇게 사용하는 것이 안전하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;API 호출해보기 (Airflow 정상 동작 확인)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731990357991&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X GET --user &quot;monitor:minding&quot; http://localhost:8080/health&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1731990403575&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{&quot;dag_processor&quot;: {&quot;latest_dag_processor_heartbeat&quot;: null, &quot;status&quot;: null}, &quot;metadatabase&quot;: {&quot;status&quot;: &quot;healthy&quot;}, &quot;scheduler&quot;: {&quot;latest_scheduler_heartbeat&quot;: &quot;2024-11-19T04:25:08.947698+00:00&quot;, &quot;status&quot;: &quot;healthy&quot;}, &quot;triggerer&quot;: {&quot;latest_triggerer_heartbeat&quot;: &quot;2024-11-19T04:25:07.776136+00:00&quot;, &quot;status&quot;: &quot;healthy&quot;}}%&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 airflow 각 구성에 대한 상태값을 반환해준다. 시간단위는 UTC기준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외 여러가지 기능을 API를 통해 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1731990639009&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 특정 DAG을 API로 Trigger하기
curl -X POST --user &quot;airflow:airflow&quot; -H 'Content-Type: application/json' -d '{&quot;execution_date&quot;:&quot;2023-05-24T00:00:00Z&quot;}' &quot;http://localhost:8080/api/v1/dags/HelloWorld/dagRuns&quot;

# 모든 DAG 리스트 출력하기
curl -X GET --user &quot;airflow:airflow&quot; http://localhost:8080/api/v1/dags

# 모든 Variable 리스트 출력하기
curl -X GET --user &quot;airflow:airflow&quot; http://localhost:8080/api/v1/variables&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Minding's Programming/Airflow</category>
      <category>Airflow</category>
      <category>airflow api</category>
      <category>airflow api dags</category>
      <category>airflow api 호출</category>
      <category>airflow healthy api</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/273</guid>
      <comments>https://minding-deep-learning.tistory.com/273#entry273comment</comments>
      <pubDate>Tue, 19 Nov 2024 14:00:48 +0900</pubDate>
    </item>
    <item>
      <title>[Airflow/Slack] Airflow DAG 실패 시 Slack으로 알림 보내기</title>
      <link>https://minding-deep-learning.tistory.com/272</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;Slack은 Webhook이라는 기능을 통해 Post 명령으로 특정 채널에 메시지를 보낼 수 있는 기능을 지원한다. 이를 이용해 Airflow DAG에서 에러가 발생할 때, Slack에 메시지를 보내는 기능을 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 알림을 전달할 채널 생성(또는 선택)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;799&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biLLlx/btsKNuIVFjC/BLWLYlJUyXzKZ1Kl5gYhp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biLLlx/btsKNuIVFjC/BLWLYlJUyXzKZ1Kl5gYhp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biLLlx/btsKNuIVFjC/BLWLYlJUyXzKZ1Kl5gYhp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiLLlx%2FbtsKNuIVFjC%2FBLWLYlJUyXzKZ1Kl5gYhp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;954&quot; height=&quot;799&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;799&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;나는 test할 워크스페이스와 채널을 미리 만들어두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Slack app 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://api.slack.com/messaging/webhooks&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.slack.com/messaging/webhooks&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731977527933&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Sending messages using incoming webhooks&quot; data-og-description=&quot;Create an incoming webhook with a unique URL to which you send a JSON payload with message text and options.&quot; data-og-host=&quot;api.slack.com&quot; data-og-source-url=&quot;https://api.slack.com/messaging/webhooks&quot; data-og-url=&quot;https://slack.com/messaging/webhooks&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cI6mDl/hyXzXdt98X/Oyak5WRMbnngjTxMfEuKr0/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200,https://scrap.kakaocdn.net/dn/ZqP8n/hyXzKrFkwu/m4vsNA9ChSpKK1IYuSfmtK/img.png?width=460&amp;amp;height=460&amp;amp;face=0_0_460_460,https://scrap.kakaocdn.net/dn/1Kl34/hyXzOni6Qy/dNVrkbAGnLnDsJKiyrdqY1/img.png?width=464&amp;amp;height=395&amp;amp;face=0_0_464_395&quot;&gt;&lt;a href=&quot;https://api.slack.com/messaging/webhooks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.slack.com/messaging/webhooks&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cI6mDl/hyXzXdt98X/Oyak5WRMbnngjTxMfEuKr0/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200,https://scrap.kakaocdn.net/dn/ZqP8n/hyXzKrFkwu/m4vsNA9ChSpKK1IYuSfmtK/img.png?width=460&amp;amp;height=460&amp;amp;face=0_0_460_460,https://scrap.kakaocdn.net/dn/1Kl34/hyXzOni6Qy/dNVrkbAGnLnDsJKiyrdqY1/img.png?width=464&amp;amp;height=395&amp;amp;face=0_0_464_395');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Sending messages using incoming webhooks&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Create an incoming webhook with a unique URL to which you send a JSON payload with message text and options.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.slack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 링크에 접속한 뒤, 'Create Your Slack app' 버튼을 눌러 아래와 같이 app을 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKPSRl/btsKM6V2si7/kxfPvYrKHE6KQdxIDZeoqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKPSRl/btsKM6V2si7/kxfPvYrKHE6KQdxIDZeoqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKPSRl/btsKM6V2si7/kxfPvYrKHE6KQdxIDZeoqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKPSRl%2FbtsKM6V2si7%2FkxfPvYrKHE6KQdxIDZeoqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;933&quot; height=&quot;731&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. Create an App 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4Hl4p/btsKMGwG1rf/MZCNw8hC2rL9NaXYeUD2CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4Hl4p/btsKMGwG1rf/MZCNw8hC2rL9NaXYeUD2CK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4Hl4p/btsKMGwG1rf/MZCNw8hC2rL9NaXYeUD2CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4Hl4p%2FbtsKMGwG1rf%2FMZCNw8hC2rL9NaXYeUD2CK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;920&quot; height=&quot;364&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. From scratch 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;353&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lisIc/btsKN43Yh7c/xWLTLhO7aSk3MbsOAcqNL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lisIc/btsKN43Yh7c/xWLTLhO7aSk3MbsOAcqNL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lisIc/btsKN43Yh7c/xWLTLhO7aSk3MbsOAcqNL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlisIc%2FbtsKN43Yh7c%2FxWLTLhO7aSk3MbsOAcqNL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;353&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 앱 이름과 워크스페이스 선택 후 Create App 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIEIYZ/btsKOeSTUhD/KffQzfn5EsPoYTDdS1ZhG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIEIYZ/btsKOeSTUhD/KffQzfn5EsPoYTDdS1ZhG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIEIYZ/btsKOeSTUhD/KffQzfn5EsPoYTDdS1ZhG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIEIYZ%2FbtsKOeSTUhD%2FKffQzfn5EsPoYTDdS1ZhG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;525&quot; height=&quot;504&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. 좌측 메뉴 바에서 Features의 'Incoming Webhooks' 선택 및 기능 ON으로 설정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;172&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckeras/btsKMgkArSY/31gQosDswl9OeLrkD8Oq1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckeras/btsKMgkArSY/31gQosDswl9OeLrkD8Oq1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckeras/btsKMgkArSY/31gQosDswl9OeLrkD8Oq1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fckeras%2FbtsKMgkArSY%2F31gQosDswl9OeLrkD8Oq1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;172&quot; height=&quot;207&quot; data-origin-width=&quot;172&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFizhG/btsKOb2XwVu/xjg1nSRafG3QzNGK4Cf5w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFizhG/btsKOb2XwVu/xjg1nSRafG3QzNGK4Cf5w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFizhG/btsKOb2XwVu/xjg1nSRafG3QzNGK4Cf5w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFizhG%2FbtsKOb2XwVu%2Fxjg1nSRafG3QzNGK4Cf5w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;574&quot; height=&quot;521&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. 하단 Webhook URL 섹션에서 'Add New Webhook to Workspace' 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btRIBa/btsKM27atVx/74gtups2ds1Ci7rpclxby0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btRIBa/btsKM27atVx/74gtups2ds1Ci7rpclxby0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btRIBa/btsKM27atVx/74gtups2ds1Ci7rpclxby0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtRIBa%2FbtsKM27atVx%2F74gtups2ds1Ci7rpclxby0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;166&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;6. 채널 선택 및 엑세스 허용&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu4zpb/btsKNT2KSE5/7gLM4cC5T8Pi4LSjlaHVW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu4zpb/btsKNT2KSE5/7gLM4cC5T8Pi4LSjlaHVW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu4zpb/btsKNT2KSE5/7gLM4cC5T8Pi4LSjlaHVW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu4zpb%2FbtsKNT2KSE5%2F7gLM4cC5T8Pi4LSjlaHVW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;477&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;7. 생성된 Webhook URL 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rdGYp/btsKNsqX9pH/p9g570r5kk3gYyckmJQQr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rdGYp/btsKNsqX9pH/p9g570r5kk3gYyckmJQQr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rdGYp/btsKNsqX9pH/p9g570r5kk3gYyckmJQQr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrdGYp%2FbtsKNsqX9pH%2Fp9g570r5kk3gYyckmJQQr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;133&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Webhook URL로 메시지 보내기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에서 생성한 URL을 이용해 아래와 같이 터미널에서 메시지를 보내보자.&lt;/p&gt;
&lt;pre id=&quot;code_1731978868965&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X POST -H 'Content-type: application/json' --data '{&quot;text&quot;:&quot;Hello, World!&quot;}' {URL}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpBilo/btsKNyK9WaO/wL9IHlA3TZMNHrVNKy5NbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpBilo/btsKNyK9WaO/wL9IHlA3TZMNHrVNKy5NbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpBilo/btsKNyK9WaO/wL9IHlA3TZMNHrVNKy5NbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpBilo%2FbtsKNyK9WaO%2FwL9IHlA3TZMNHrVNKy5NbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;584&quot; height=&quot;119&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Data-Alert 앱이 채널에 메시지를 전달한 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. Airflow 에러를 Slack 메시지로 보내기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에서 얻은 링크를 Variables로 저장해준 뒤, 이를 이용해 slack에 에러가 발생할 경우 메시지를 보내는 모듈을 개발한다. 그리고 DAG 인스턴스를 만들 때 해당 모듈을 에러 콜백으로 지정해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Variables에 'slack_url'이라는 이름으로 URL(뒷부분만)을 입력해준 뒤, 아래와 같은 모듈로 메시지를 보내게 된다. 이 모듈은 slack.py라는 이름으로 plugins 폴더에 배치했다.&lt;/p&gt;
&lt;pre id=&quot;code_1731980248312&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from airflow.models import Variable

import logging
import requests

def on_failure_callback(context):
    &quot;&quot;&quot;
    https://airflow.apache.org/_modules/airflow/operators/slack_operator.html
    Define the callback to post on Slack if a failure is detected in the Workflow
    :return: operator.execute
    &quot;&quot;&quot;
    text = str(context['task_instance'])
    text += &quot;```&quot; + str(context.get('exception')) +&quot;```&quot;
    send_message_to_a_slack_channel(text, &quot;:scream:&quot;)


# def send_message_to_a_slack_channel(message, emoji, channel, access_token):
def send_message_to_a_slack_channel(message, emoji):
    # url = &quot;https://slack.com/api/chat.postMessage&quot;
    # &quot;https://hooks.slack.com/services/&quot;까지는 공통적이기 때문에, 뒷부분만 저장함
    url = &quot;https://hooks.slack.com/services/&quot;+Variable.get(&quot;slack_url&quot;)
    headers = {
        'content-type': 'application/json',
    }
    data = { &quot;username&quot;: &quot;Data GOD&quot;, &quot;text&quot;: message, &quot;icon_emoji&quot;: emoji }
    r = requests.post(url, json=data, headers=headers)
    return r&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 이를 DAG에 적용해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1731980388100&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from plugins import slack

...

dag = DAG(
    dag_id = 'name_gender_v4',
    start_date = datetime(2023,4,6), # 날짜가 미래인 경우 실행이 안됨
    schedule = '0 2 * * *',  # 적당히 조절
    max_active_runs = 1,
    catchup = False,
    default_args = {
        'retries': 1,
        'retry_delay': timedelta(minutes=3),
        # failure_collback: 모든 task에 대해 실패 시 사용할 함수 - Slack 메시지 전송
        'on_failure_callback': slack.on_failure_callback,
    }
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 원래 잘 실행되던 DAG을 살짝 수정해 일부러 에러가 나도록 해보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lItJD/btsKMMjd8ev/ZcKLTEB8VF6uLJzQ89z5ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lItJD/btsKMMjd8ev/ZcKLTEB8VF6uLJzQ89z5ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lItJD/btsKMMjd8ev/ZcKLTEB8VF6uLJzQ89z5ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlItJD%2FbtsKMMjd8ev%2FZcKLTEB8VF6uLJzQ89z5ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;755&quot; height=&quot;104&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 에러메시지가 Slack으로 전송되는 것을 확인할 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/Airflow</category>
      <category>Airflow</category>
      <category>airflow slack</category>
      <category>airflow 슬랙</category>
      <category>airflow 슬랙 연동</category>
      <category>airflow 에러 slack</category>
      <category>airflow 에러메시지</category>
      <category>slack</category>
      <category>slcka 연동</category>
      <category>슬랙 연동</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/272</guid>
      <comments>https://minding-deep-learning.tistory.com/272#entry272comment</comments>
      <pubDate>Tue, 19 Nov 2024 10:48:00 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Docker Volume</title>
      <link>https://minding-deep-learning.tistory.com/271</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker Volume?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Container가 실행되었다가 중단된다면, 그 안에 있는 데이터들은 일반적으로 유실된다. 하지만 Container에서 DB같은 프로그램이 동작하는거라면, 그 데이터가 유실되면 안될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 데이터를 보장하는 기능이 Docker Volume이라고 할 수 있다. Docker Volume은 Docker Container의 가상 파일 시스템과 호스트 시스템(OS)의 파일 시스템을 맵핑해 기록을 남기는 방식으로 데이터를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WLJHk/btsKKyDGiNk/ofPSjyGgFoCFgnAOcf1LG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WLJHk/btsKKyDGiNk/ofPSjyGgFoCFgnAOcf1LG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WLJHk/btsKKyDGiNk/ofPSjyGgFoCFgnAOcf1LG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWLJHk%2FbtsKKyDGiNk%2FofPSjyGgFoCFgnAOcf1LG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;502&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 방식으로 Container의 특정 폴더 경로를 OS 시스템의 포더 경로와 마운트해 해당 폴더를 공유하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker Volume 타입&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Host&amp;nbsp;Volumes:&amp;nbsp;docker&amp;nbsp;run&amp;nbsp;-v를&amp;nbsp;실행할&amp;nbsp;때&amp;nbsp;페어로&amp;nbsp;지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker run -v /home/minding/logs:/var/lib/airflow/logs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Anonymous&amp;nbsp;Volumes:&amp;nbsp;docker&amp;nbsp;run&amp;nbsp;-v를&amp;nbsp;실행할&amp;nbsp;때&amp;nbsp;컨테이너&amp;nbsp;패스만&amp;nbsp;지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker&amp;nbsp;run&amp;nbsp;-v&amp;nbsp;/var/lib/mysql/data&lt;/li&gt;
&lt;li&gt;Dockerfile에서 사용되는 방식으로, 호스트 시스템에 저장되지는 않지만 재시작해도 유지됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Named&amp;nbsp;Volumes:&amp;nbsp;docker&amp;nbsp;run&amp;nbsp;-v를&amp;nbsp;실행할&amp;nbsp;때&amp;nbsp;이름과&amp;nbsp;컨테이너&amp;nbsp;패스를&amp;nbsp;지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker&amp;nbsp;run&amp;nbsp;-v&amp;nbsp;name:/var/lib/mysql/data&lt;/li&gt;
&lt;li&gt;이 방식이 하나의 Volume을 다수의 컨테이너에서 공유하는 것도&amp;nbsp;가능하게&amp;nbsp;해줌&lt;/li&gt;
&lt;li&gt;docker compose에서도 사용되는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미지 생성시 Docker Volume 사용법&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Dockerfile
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VOLUME 명령을 통해 anonymous volume만 지정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;docker compose
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Host volume이나 Named Volume을 사용하는 것이 일반적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker-compose.yaml 파일 예시(Airflow)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker를 통해 airflow를 설치했을 경우 docker-compose.yaml 파일에는 아래와 같이 volume이 지정되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1731632974489&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ...
  volumes:
    - ${AIRFLOW_PROJ_DIR:-.}/airflow/dags:/opt/airflow/dags
    - ${AIRFLOW_PROJ_DIR:-.}/logs:/opt/airflow/logs
    - ${AIRFLOW_PROJ_DIR:-.}/config:/opt/airflow/config
    - ${AIRFLOW_PROJ_DIR:-.}/airflow/plugins:/opt/airflow/plugins
    - ${AIRFLOW_PROJ_DIR:-.}/airflow/files:/opt/airflow/files
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우 minding 폴더 아래에 airflow 폴더가 위치해 있기 때문에, 'minding/airflow/dags'가 airflow의 '/opt/airflow/dags'와 마운트되어 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker Volume 명령어&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;docker volume ls: docker volume 리스트 출력&lt;/li&gt;
&lt;li&gt;docker volume rm: 특정 volume 삭제&lt;/li&gt;
&lt;li&gt;docker volume prune: 사용되지 않는 모든 volume 삭제&lt;/li&gt;
&lt;li&gt;docker volume inspect: 특정 volume의 상세정보 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>docker</category>
      <category>docker volume</category>
      <category>docker 데이터 저장</category>
      <category>docker 폴더</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/271</guid>
      <comments>https://minding-deep-learning.tistory.com/271#entry271comment</comments>
      <pubDate>Fri, 15 Nov 2024 10:40:34 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD] CI, CD 개념 및 Github Actions</title>
      <link>https://minding-deep-learning.tistory.com/270</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SW 빌드란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;개발한 소프트웨어를 최종적으로 출시하기 위한 형태로 만드는 것이다. 참여 개발자들이 많을수록 이 과정은 더더욱 중요해지며, 개발이 끝나기 전부터 빌드를 해서 테스트를 진행하면 SW의 안정성이 증대된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CI (Continuous Integration)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CI는 Software Engineering Practice의 하나로, 아래와 같은 기본 원칙을 가지고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 Repo는 하나만 유지한다. (Master or Main)&lt;/li&gt;
&lt;li&gt;코드변경을 최대한 자주 반영&lt;/li&gt;
&lt;li&gt;테스트를 최대한 추가&lt;/li&gt;
&lt;li&gt;빌드를 계속적으로 수행 (자동화)&lt;/li&gt;
&lt;li&gt;성공한 빌드의 프로덕션을 릴리스 (자동화)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CD (Continuous Delivery): 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;만약 빌드가 실패할 경우&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;새 코드의 commit으로 테스트가 실패하는 경우, 많은 회사들이 다시 빌드가 성공할 때까지 코드 변경을 금지시킨다. 규모가 큰 회사일수록 빌드 담당 엔지니어가 있고, 빌드 실패시 가벼운 형태로 페널티를 부여하기도 한다. 따라서 코드를 커밋하기 전, 자신의 환경에서 테스트가 잘 되는지 확인할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Github Actions&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Github에서 제공하는 CI/CD 도구로, 특정 브랜치(ex. main)의 코드가 push/merge 등으로 수정됐을 때, 테스트를 수행하고 빌드하는 등의 과정을 workflow라는 이름으로 구현할 수 있도록 한다. Public Repo의 경우 무료로 이용 가능하며, Private Repo의 경우 이용 시간 또는 용량에 따라 유료로 이용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Workflow 컴포넌트 구성&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Events&lt;/li&gt;
&lt;li&gt;Jobs&lt;/li&gt;
&lt;li&gt;Actions&lt;/li&gt;
&lt;li&gt;Runner
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Github hosted runners&lt;/li&gt;
&lt;li&gt;self hosted runners&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Workflow&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Workflow는 트리거 이벤트(코드 커밋, PR 생성, 다른 Workflow 성공 등)가 발생하면 시작되는 일련의 동작들을 지칭한다. Workflow를 위한 명령어들은 YAML 파일로 저장되며, Workflow는 각각의 Job으로 나눠진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Github Acitons 사용 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 Actions를 사용하고자 하는 repo에 접속해 상단 메뉴 중 Actions 버튼을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0x4yQ/btsKHXYMIKE/9znAxuDWKl2L9NJurkKD81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0x4yQ/btsKHXYMIKE/9znAxuDWKl2L9NJurkKD81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0x4yQ/btsKHXYMIKE/9znAxuDWKl2L9NJurkKD81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0x4yQ%2FbtsKHXYMIKE%2F9znAxuDWKl2L9NJurkKD81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;103&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;895&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bExxC1/btsKIEc6pyf/sl1vtMwhHVAk1Gnb2VVSSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bExxC1/btsKIEc6pyf/sl1vtMwhHVAk1Gnb2VVSSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bExxC1/btsKIEc6pyf/sl1vtMwhHVAk1Gnb2VVSSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbExxC1%2FbtsKIEc6pyf%2Fsl1vtMwhHVAk1Gnb2VVSSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;922&quot; height=&quot;895&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;895&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 페이지에 접속하게 되면, Workflow를 지정할 YAML 파일을 직접 만들거나(상단 set up workflow yourself) Github에서 제공하는 템플릿을 선택해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;나는 현재 진행중인 데브코스에서 제공받은 코드(hangman 웹 서비스)를 기준으로 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://github.com/JeongMinHyeok/hangman_web&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/JeongMinHyeok/hangman_web&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731549641850&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - JeongMinHyeok/hangman_web&quot; data-og-description=&quot;Contribute to JeongMinHyeok/hangman_web development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/JeongMinHyeok/hangman_web&quot; data-og-url=&quot;https://github.com/JeongMinHyeok/hangman_web&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4Y1SB/hyXwqAwLW5/uUm8k0RQTA3xI5kYh1PgJk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/r8o8n/hyXzLJLeLJ/HJcGDDUUL7WyKpkQ6zOWbk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/JeongMinHyeok/hangman_web&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/JeongMinHyeok/hangman_web&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4Y1SB/hyXwqAwLW5/uUm8k0RQTA3xI5kYh1PgJk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/r8o8n/hyXzLJLeLJ/HJcGDDUUL7WyKpkQ6zOWbk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - JeongMinHyeok/hangman_web&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to JeongMinHyeok/hangman_web development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Github Actions를 통해 main 브런치에 push 또는 PR이 있을 때, test.py(테스트 코드)를 실행해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 Actions 페이지에서 Python application이라는 CI tamplate을 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D9wGk/btsKGZb521U/RnZ8aYDILLcIDPgKCBRfRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D9wGk/btsKGZb521U/RnZ8aYDILLcIDPgKCBRfRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D9wGk/btsKGZb521U/RnZ8aYDILLcIDPgKCBRfRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD9wGk%2FbtsKGZb521U%2FRnZ8aYDILLcIDPgKCBRfRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;302&quot; height=&quot;203&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;test.py는 unittest 기능을 통해 이용할 것이고, flake8을 통해 코딩 스타일 등까지 체크해볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731551076981&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python application

on:
  push:
    branches: [ &quot;main&quot; ]
  pull_request:
    branches: [ &quot;main&quot; ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python 3.10
      uses: actions/setup-python@v3
      with:
        python-version: &quot;3.10&quot;
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8 pytest
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
    - name: Lint with flake8
      run: |
        # stop the build if there are Python syntax errors or undefined names
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
    - name: Test with unittest
      run: |
        # test로 시작하는 모든 py파일에 대해 unittest 실행
        python -m unittest discover -p 'test*.py'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;YAML 파일은 위와같이 구성했다. main 브런치만을 대상으로 하며, python 3.10 환경에서 작동한다. pip 명령어를 통해 종속성 라이브러리를 설치한 뒤, flake8과 python -m unittest 명령어를 통해 테스트를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XQl5J/btsKIAoguw0/r1kSEJZxL1ZYPzF0k0zB4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XQl5J/btsKIAoguw0/r1kSEJZxL1ZYPzF0k0zB4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XQl5J/btsKIAoguw0/r1kSEJZxL1ZYPzF0k0zB4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXQl5J%2FbtsKIAoguw0%2Fr1kSEJZxL1ZYPzF0k0zB4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;905&quot; height=&quot;678&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 우측 상단 Commit Changes 버튼을 클릭해 커밋하고, 변경한 점을 새로운 PR로 생성했다. PR에서 머지한 뒤 해당 브랜치는 삭제해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfJsIR/btsKGVnd43O/RkJZfOxrCNzAPt6wXGy7w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfJsIR/btsKGVnd43O/RkJZfOxrCNzAPt6wXGy7w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfJsIR/btsKGVnd43O/RkJZfOxrCNzAPt6wXGy7w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfJsIR%2FbtsKGVnd43O%2FRkJZfOxrCNzAPt6wXGy7w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;586&quot; height=&quot;107&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;commit과 merge가 끝나게 되면, 커밋 메시지 옆에 주황색 불이 켜지는데, 이는 Github Actions가 실행되고 있다는 것을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceGs9I/btsKIw7sYzU/mxhOdaAR0CMCpDceKItPy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceGs9I/btsKIw7sYzU/mxhOdaAR0CMCpDceKItPy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceGs9I/btsKIw7sYzU/mxhOdaAR0CMCpDceKItPy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceGs9I%2FbtsKIw7sYzU%2FmxhOdaAR0CMCpDceKItPy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;113&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 버튼을 눌러보면 Github Actions의 상세 정보를 볼 수 있는데, 간단한 프로그램이다보니 금방 테스트가 완료된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Details를 눌러 살펴보면 각 jobs의 단계별로 실행된 것을 확인 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKQlRm/btsKHJsZ5B2/CkGqzOcWvZVZFSKsZF43Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKQlRm/btsKHJsZ5B2/CkGqzOcWvZVZFSKsZF43Ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKQlRm/btsKHJsZ5B2/CkGqzOcWvZVZFSKsZF43Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKQlRm%2FbtsKHJsZ5B2%2FCkGqzOcWvZVZFSKsZF43Ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;920&quot; height=&quot;602&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>CD</category>
      <category>CI</category>
      <category>github</category>
      <category>github actions</category>
      <category>github actions python</category>
      <category>github actions unittest</category>
      <category>pytest</category>
      <category>unittest</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/270</guid>
      <comments>https://minding-deep-learning.tistory.com/270#entry270comment</comments>
      <pubDate>Thu, 14 Nov 2024 11:44:17 +0900</pubDate>
    </item>
    <item>
      <title>[BI/시각화] Superset</title>
      <link>https://minding-deep-learning.tistory.com/269</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Superset&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Looker, Tableau 등 다양한 시각화 도구들이 있지만, 기능이 많은 만큼 배우기 다소 어렵거나 비용이 든다는 단점이 있다. Superset은 Airbnb에서 시작된 시각화 관련 오픈소스로, 다양한 형태의 시각화와 손쉬운 인터페이스를 지원한다. SQLAlchemy와 연동되어 다양한 DB를 지원하고, 실시간 데이터 시각화 및 API와 플러그인 아키텍처로 확장성도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Superset은 Flask와 React JS로 구성되어 있으며, sqlite를 메타데이터 DB로 사용한다. Redis를 캐싱 레이어로 사용하고, SqlAlchemy가 백엔드 DB 접근에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Preset으로 Superset 사용해보기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Superset은 크게 2가지 방법으로 사용해볼 수 있다. 첫 번째 방법은 preset이라는 서비스를 통해 웹 페이지에서 실행하는 것이고, 두 번째 방법은 Docker 상에서 실행하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹에서 실습하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://preset.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://preset.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730351887671&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Modern BI Powered by Open Source Apache Superset&amp;trade;&quot; data-og-description=&quot;Powerful, easy to use data exploration and visualization platform, powered by open-source Apache Superset&amp;trade;. Modern business intelligence for your entire organization.&quot; data-og-host=&quot;preset.io&quot; data-og-source-url=&quot;https://preset.io/&quot; data-og-url=&quot;https://preset.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cw88Ay/hyXpAiQesX/SNlZP2fqfDyjGWEqCQN02K/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/c3HVIe/hyXpzYyoKo/9NqZKsbUmoufO0JHDZMXxK/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900&quot;&gt;&lt;a href=&quot;https://preset.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://preset.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cw88Ay/hyXpAiQesX/SNlZP2fqfDyjGWEqCQN02K/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/c3HVIe/hyXpzYyoKo/9NqZKsbUmoufO0JHDZMXxK/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Modern BI Powered by Open Source Apache Superset&amp;trade;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Powerful, easy to use data exploration and visualization platform, powered by open-source Apache Superset&amp;trade;. Modern business intelligence for your entire organization.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;preset.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 링크로 preset 홈페이지에 접속해 회원가입을 진행한다. (Sign up for free)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3nagk/btsKrho9IPM/BEOTeCGe2wNkoJFcenJ5sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3nagk/btsKrho9IPM/BEOTeCGe2wNkoJFcenJ5sK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3nagk/btsKrho9IPM/BEOTeCGe2wNkoJFcenJ5sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3nagk%2FbtsKrho9IPM%2FBEOTeCGe2wNkoJFcenJ5sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;340&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 14일 동안에는 Professional 플랜을 무료로 이용할 수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8PRuq/btsKr769ACS/cFxl2brOkLOqJseggRvEl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8PRuq/btsKr769ACS/cFxl2brOkLOqJseggRvEl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8PRuq/btsKr769ACS/cFxl2brOkLOqJseggRvEl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8PRuq%2FbtsKr769ACS%2FcFxl2brOkLOqJseggRvEl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;926&quot; height=&quot;806&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인을 진행하면 위와 같은 대시보드가 노출되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker에서 Superset 설치해 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 설치는 이 링크를 참고하길 바란다. --&amp;gt; &lt;a href=&quot;https://minding-deep-learning.tistory.com/262&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker의 개념 및 기본 실행 명령어&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730354280688&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Docker] Docker의 개념 및 기본 실행 명령어&quot; data-og-description=&quot;Docker&amp;nbsp;Docker는 애플리케이션을 컨테이너라는 독립된 환경에서 실행할 수 있게 해주는 Linux 컨테이너 기반 플랫폼이다. 애플리케이션과 관련된 라이브러리와 종속성을 하나의 패키지로 묶어 어디&quot; data-og-host=&quot;minding-deep-learning.tistory.com&quot; data-og-source-url=&quot;https://minding-deep-learning.tistory.com/262&quot; data-og-url=&quot;https://minding-deep-learning.tistory.com/262&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cuJfp9/hyXppPcKBB/FVEMEIJEGOgXftOdkh2DN1/img.png?width=1280&amp;amp;height=887&amp;amp;face=0_0_1280_887&quot;&gt;&lt;a href=&quot;https://minding-deep-learning.tistory.com/262&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://minding-deep-learning.tistory.com/262&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cuJfp9/hyXppPcKBB/FVEMEIJEGOgXftOdkh2DN1/img.png?width=1280&amp;amp;height=887&amp;amp;face=0_0_1280_887');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Docker] Docker의 개념 및 기본 실행 명령어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Docker&amp;nbsp;Docker는 애플리케이션을 컨테이너라는 독립된 환경에서 실행할 수 있게 해주는 Linux 컨테이너 기반 플랫폼이다. 애플리케이션과 관련된 라이브러리와 종속성을 하나의 패키지로 묶어 어디&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;minding-deep-learning.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 설치를 마쳤다면 이제 Superset을 설치할 차례이다. (&lt;a href=&quot;https://superset.apache.org/docs/installation/installing-superset-using-docker-compose/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://superset.apache.org/docs/installation/installing-superset-using-docker-compose/&lt;/a&gt; &amp;lt;-- Superset docker 설치 공식문서)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널 창에 아래 명령어를 입력해 superset을 clone 받는다. (나의 경우는 Windows 환경에서 WSL2를 설치해 Ubuntu에 설치했다.) git clone을 통해 파일을 다운로드 받으면 최신 버전을 다운로드 받는 것이므로, 안정성이 걱정된다면 git checkout 명령어를 이용해 특정 버전을 받는 것도 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1730355785668&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git clone https://github.com/apache/superset.git&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clone을 마쳤다면 해당 폴더로 이동해 2개의 명령을 차례로 실행한다. (이미지를 다운받고 docker 컨테이너 실행)&lt;/p&gt;
&lt;pre id=&quot;code_1730355944776&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd superset

docker compose -f docker-compose-non-dev.yml pull
docker compose -f docker-compose-non-dev.yml up&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 생성하고 컨테이너를 실행하는데 다소 시간이 걸리니 인내의 시간을 가져보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8qlUr/btsKq9d2IMc/GJKpyJwkEKAk1ohcBdWtWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8qlUr/btsKq9d2IMc/GJKpyJwkEKAk1ohcBdWtWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8qlUr/btsKq9d2IMc/GJKpyJwkEKAk1ohcBdWtWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8qlUr%2FbtsKq9d2IMc%2FGJKpyJwkEKAk1ohcBdWtWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;959&quot; height=&quot;346&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Desktop 또는 터미널에서 컨테이너 실행이 확인되면, 기본 admin 주소인 http://localhost:8088/ 에 접속해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 화면이 노출될텐데, id와 비밀번호로 모두 'admin'을 입력하면 관리자 계정으로 접속 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;771&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uPglW/btsKp1ab2ll/7RLj7Ks0K0EBHdNI84tbk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uPglW/btsKp1ab2ll/7RLj7Ks0K0EBHdNI84tbk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uPglW/btsKp1ab2ll/7RLj7Ks0K0EBHdNI84tbk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuPglW%2FbtsKp1ab2ll%2F7RLj7Ks0K0EBHdNI84tbk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;951&quot; height=&quot;771&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;771&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 화면이 노출되고, 이제 데이터베이스를 연결해 사용해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Redshift를 Superset에 연결해보기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MrVeV/btsKrLX3zLY/MtJVURiS7GprKf48eEPWxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MrVeV/btsKrLX3zLY/MtJVURiS7GprKf48eEPWxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MrVeV/btsKrLX3zLY/MtJVURiS7GprKf48eEPWxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMrVeV%2FbtsKrLX3zLY%2FMtJVURiS7GprKf48eEPWxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;256&quot; height=&quot;346&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 상단 'Settings' 메뉴에 'Database Connections' 항목을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mX1D1/btsKrzDvTgl/RWBNzY6GbuqXIijaHEGWKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mX1D1/btsKrzDvTgl/RWBNzY6GbuqXIijaHEGWKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mX1D1/btsKrzDvTgl/RWBNzY6GbuqXIijaHEGWKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmX1D1%2FbtsKrzDvTgl%2FRWBNzY6GbuqXIijaHEGWKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;285&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다시 우측 상단에 있는 '+ Database' 버튼을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;827&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djzo97/btsKse6vzXp/Gqk8tJDf6SkqXNTuoSXN60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djzo97/btsKse6vzXp/Gqk8tJDf6SkqXNTuoSXN60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djzo97/btsKse6vzXp/Gqk8tJDf6SkqXNTuoSXN60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdjzo97%2FbtsKse6vzXp%2FGqk8tJDf6SkqXNTuoSXN60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;491&quot; height=&quot;827&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;827&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Superset 내에서 사용할 DB 유형을 선택할 수 있다. 나는 PostgreSQL을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wpHe2/btsKrv2hAPH/bW6PllGzi45NKJs3bT3pBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wpHe2/btsKrv2hAPH/bW6PllGzi45NKJs3bT3pBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wpHe2/btsKrv2hAPH/bW6PllGzi45NKJs3bT3pBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwpHe2%2FbtsKrv2hAPH%2FbW6PllGzi45NKJs3bT3pBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;493&quot; height=&quot;828&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 연결할 Redshift의 정보를 입력한다. Host에는 해당 redshift의 엔드포인트, Port는 5439다. 계정은 관리자 대신 사용자를 따로 만들어 사용하기를 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OVcXL/btsKrcBTtuF/N4kKLUaRkrkthga4D2yUSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OVcXL/btsKrcBTtuF/N4kKLUaRkrkthga4D2yUSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OVcXL/btsKrcBTtuF/N4kKLUaRkrkthga4D2yUSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOVcXL%2FbtsKrcBTtuF%2FN4kKLUaRkrkthga4D2yUSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;777&quot; height=&quot;117&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 정상적으로 연결된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dataset 추가 및 차트 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 연결한 redshift에서 테이블을 Dataset으로 추가해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kVLk5/btsKsaws0Jj/gw1sVOmYK01Byy5oDMKtO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kVLk5/btsKsaws0Jj/gw1sVOmYK01Byy5oDMKtO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kVLk5/btsKsaws0Jj/gw1sVOmYK01Byy5oDMKtO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkVLk5%2FbtsKsaws0Jj%2Fgw1sVOmYK01Byy5oDMKtO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;73&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단 메뉴에서 'Datasets'를 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blOhP7/btsKqAwDF9r/6ffoV9cehieP0l5kJcU9uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blOhP7/btsKqAwDF9r/6ffoV9cehieP0l5kJcU9uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blOhP7/btsKqAwDF9r/6ffoV9cehieP0l5kJcU9uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblOhP7%2FbtsKqAwDF9r%2F6ffoV9cehieP0l5kJcU9uK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;162&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 상단 '+ Dataset' 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZpWvT/btsKqsSZzZt/S8vJWRSFNw82q60NMdIVKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZpWvT/btsKqsSZzZt/S8vJWRSFNw82q60NMdIVKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZpWvT/btsKqsSZzZt/S8vJWRSFNw82q60NMdIVKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZpWvT%2FbtsKqsSZzZt%2FS8vJWRSFNw82q60NMdIVKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;943&quot; height=&quot;399&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측에서 원하는 DB, 스키마, 테이블을 선택한다. 그리고 우측 하단 'CREATE DATASET AND CREATE CHART'를 클릭힌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr65YR/btsKqMqatKT/qz8TpLQZdcWltaK9YkbuSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr65YR/btsKqMqatKT/qz8TpLQZdcWltaK9YkbuSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr65YR/btsKqMqatKT/qz8TpLQZdcWltaK9YkbuSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr65YR%2FbtsKqMqatKT%2Fqz8TpLQZdcWltaK9YkbuSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;930&quot; height=&quot;844&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 화면이 나오게 된다. 여기서 원하는 타입의 차트를 선택한 뒤, 'Create new chart'를 누른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1b6TV/btsKsyDDYmM/A1sHfHEhvoh50YP6nvrQmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1b6TV/btsKsyDDYmM/A1sHfHEhvoh50YP6nvrQmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1b6TV/btsKsyDDYmM/A1sHfHEhvoh50YP6nvrQmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1b6TV%2FbtsKsyDDYmM%2FA1sHfHEhvoh50YP6nvrQmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;947&quot; height=&quot;864&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형식으로 원하는 차트를 생성할 수 있다. 컬럼을 지정하거나 SQL문으로 커스텀할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgdOwL/btsKr9dfbQb/Fn0pw4tTHaRqdXotzpXVK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgdOwL/btsKr9dfbQb/Fn0pw4tTHaRqdXotzpXVK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgdOwL/btsKr9dfbQb/Fn0pw4tTHaRqdXotzpXVK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgdOwL%2FbtsKr9dfbQb%2FFn0pw4tTHaRqdXotzpXVK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1915&quot; height=&quot;918&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 chart가 오른쪽에 보이는 것을 확인할 수 있다. 'Save'를 눌러 이 데이터셋과 차트를 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddTHGg/btsKrcaPx4p/GWwOvkbyEFlAMCk4Ix7s0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddTHGg/btsKrcaPx4p/GWwOvkbyEFlAMCk4Ix7s0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddTHGg/btsKrcaPx4p/GWwOvkbyEFlAMCk4Ix7s0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddTHGg%2FbtsKrcaPx4p%2FGWwOvkbyEFlAMCk4Ix7s0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;895&quot; height=&quot;432&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장 후 상단 Charts 항목에서 방금 생성한 차트를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;차트를 Dashboards에 추가하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Dashboard는 아래 2가지 방법으로 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 차트 저장 시 대시보드 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vcLVH/btsKqKlDGSk/s0M4FltWnNmZZbGNDDNHL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vcLVH/btsKqKlDGSk/s0M4FltWnNmZZbGNDDNHL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vcLVH/btsKqKlDGSk/s0M4FltWnNmZZbGNDDNHL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvcLVH%2FbtsKqKlDGSk%2Fs0M4FltWnNmZZbGNDDNHL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;433&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Dashboards 탭에서 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqzYky/btsKrLqkj7e/byT6LleQprWzDcVZGYkyj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqzYky/btsKrLqkj7e/byT6LleQprWzDcVZGYkyj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqzYky/btsKrLqkj7e/byT6LleQprWzDcVZGYkyj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqzYky%2FbtsKrLqkj7e%2FbyT6LleQprWzDcVZGYkyj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;922&quot; height=&quot;368&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 대시보드를 생성했다면, 아무 대시보드에도 소속되지 않은 차트를 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs6JVV/btsKrIUIzAL/zKiiMkirU9xj4GLmFATrt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs6JVV/btsKrIUIzAL/zKiiMkirU9xj4GLmFATrt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs6JVV/btsKrIUIzAL/zKiiMkirU9xj4GLmFATrt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs6JVV%2FbtsKrIUIzAL%2FzKiiMkirU9xj4GLmFATrt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;220&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 만들었던 차트에 진입해서 오른쪽 상단 'Save'버튼을 눌러보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vtAsQ/btsKre7zyrB/6bNRTQ1XNNfBFtwtxsDc71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vtAsQ/btsKre7zyrB/6bNRTQ1XNNfBFtwtxsDc71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vtAsQ/btsKre7zyrB/6bNRTQ1XNNfBFtwtxsDc71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvtAsQ%2FbtsKre7zyrB%2F6bNRTQ1XNNfBFtwtxsDc71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;359&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장 팝업에서 새로운 대시보드를 선택해 지정할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대시보드 편집&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqrXwP/btsKqCgXvHy/wicugRiaRQRJGjX7htUekK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqrXwP/btsKqCgXvHy/wicugRiaRQRJGjX7htUekK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqrXwP/btsKqCgXvHy/wicugRiaRQRJGjX7htUekK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqrXwP%2FbtsKqCgXvHy%2FwicugRiaRQRJGjX7htUekK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;924&quot; height=&quot;99&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 대시보드에 진입해 'Edit dashboard' 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPoxKz/btsKrySnHuu/YCcjKdnubZ3MYPpBm0LYIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPoxKz/btsKrySnHuu/YCcjKdnubZ3MYPpBm0LYIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPoxKz/btsKrySnHuu/YCcjKdnubZ3MYPpBm0LYIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPoxKz%2FbtsKrySnHuu%2FYCcjKdnubZ3MYPpBm0LYIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;926&quot; height=&quot;664&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 차트의 크기나 위치 등을 조정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>BI</category>
      <category>Preset</category>
      <category>preset db 연결</category>
      <category>superset</category>
      <category>superset database 연결</category>
      <category>superset docker</category>
      <category>superset redshift</category>
      <category>데이터 시각화</category>
      <category>데이터 시각화 도구</category>
      <category>시각화</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/269</guid>
      <comments>https://minding-deep-learning.tistory.com/269#entry269comment</comments>
      <pubDate>Thu, 31 Oct 2024 13:45:55 +0900</pubDate>
    </item>
    <item>
      <title>[Snowflake] Snowflake 알아보기 (설치 방법)</title>
      <link>https://minding-deep-learning.tistory.com/268</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Snowflake?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Snowflake는 2014년에 출시한 데이터 저장부터 시각화, 머신러닝까지 가능한 클라우드 플랫폼이다. 글로벌 클라우드라고 불리는 AWS, GCP, Azure에서 모두 동작할 수 있어 많은 회사들이 Snowflake를 선택하고 있기도하다. 데이터 판매가 가능하기도 하며, ETL과 관련된 다양한 데이터 통합 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Snowflake의 특징&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가변 비용 모델: 스토리지와 컴퓨팅 인프라가 별도로 설정됨&lt;/li&gt;
&lt;li&gt;SQL 기반 데이터 처리 가능&lt;/li&gt;
&lt;li&gt;CSV, JSON, Parquet 등 다양한 데이터 포맷 지원&lt;/li&gt;
&lt;li&gt;배치 데이터 중심이지만 실시간 데이터 처리도 지원&lt;/li&gt;
&lt;li&gt;Time Travel 기능으로 과거 데이터까지 분석 가능&lt;/li&gt;
&lt;li&gt;웹 콘솔 및 Python API로 관리/제어 가능 (ODBC, JDBC 연결 지원)&lt;/li&gt;
&lt;li&gt;클라우드 스토리지를 외부 테이블로 사용 가능&lt;/li&gt;
&lt;li&gt;Data Marketplace를 통한 데이터 판매 가능&lt;/li&gt;
&lt;li&gt;Data Sharing을 통해 사내 또는 파트너 회사에게 데이터 공유 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Snowflake의 계정 구성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Snowflake의 계정은 Organization, Account, Database 순서로 구성되어있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Oraganization은 고객(회사)이며, 말 그대로 최상위 레벨의 컨테이너를 뜻한다.&lt;/li&gt;
&lt;li&gt;Account는 자체 사용자(1명)를 뜻하며, 접근권한을 독립적으로 가진다.&lt;/li&gt;
&lt;li&gt;Database는 1개의 Account에 속한 데이터를 다루는 논리적인 컨테이너다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Snowflake 30일 무료 평가판 사용해보기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.snowflake.com/en/emea/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.snowflake.com/en/emea/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730339382285&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;The Snowflake AI Data Cloud - Mobilize Data, Apps, and AI&quot; data-og-description=&quot;Snowflake enables organizations to learn, build, and connect with their data-driven peers. Collaborate, build data apps &amp;amp; power diverse workloads in the AI Data Cloud.&quot; data-og-host=&quot;www.snowflake.com&quot; data-og-source-url=&quot;https://www.snowflake.com/en/emea/&quot; data-og-url=&quot;https://www.snowflake.com/content/snowflake-site/global/en/emea&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PPDwx/hyXsTufXCT/gZe0cAFCXaw65AM1QzLV7k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bHm4fI/hyXsVyP4sw/IUvs6JAKoDwW0PCD6tQsF1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eCu8HH/hyXsQK4dax/vUFCO3me6PNAPu3l5mA110/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.snowflake.com/en/emea/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.snowflake.com/en/emea/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PPDwx/hyXsTufXCT/gZe0cAFCXaw65AM1QzLV7k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bHm4fI/hyXsVyP4sw/IUvs6JAKoDwW0PCD6tQsF1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eCu8HH/hyXsQK4dax/vUFCO3me6PNAPu3l5mA110/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The Snowflake AI Data Cloud - Mobilize Data, Apps, and AI&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Snowflake enables organizations to learn, build, and connect with their data-driven peers. Collaborate, build data apps &amp;amp; power diverse workloads in the AI Data Cloud.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.snowflake.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 링크를 통해 snowflake 홈페이지로 진입해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddTFcF/btsKpvu4xgm/AxDQn2SfDp9uOtJ11bGhZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddTFcF/btsKpvu4xgm/AxDQn2SfDp9uOtJ11bGhZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddTFcF/btsKpvu4xgm/AxDQn2SfDp9uOtJ11bGhZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddTFcF%2FbtsKpvu4xgm%2FAxDQn2SfDp9uOtJ11bGhZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;510&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'START FOR FREE' 클릭!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xwR1e/btsKqYpfe2T/ijMAxKkQS46HNagEDR92l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xwR1e/btsKqYpfe2T/ijMAxKkQS46HNagEDR92l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xwR1e/btsKqYpfe2T/ijMAxKkQS46HNagEDR92l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxwR1e%2FbtsKqYpfe2T%2FijMAxKkQS46HNagEDR92l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;487&quot; height=&quot;746&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 정보를 차례대로 입력해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bypVF6/btsKph4SbmE/ZCXjXEubneMwz4fbieGEPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bypVF6/btsKph4SbmE/ZCXjXEubneMwz4fbieGEPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bypVF6/btsKph4SbmE/ZCXjXEubneMwz4fbieGEPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbypVF6%2FbtsKph4SbmE%2FZCXjXEubneMwz4fbieGEPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;632&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 다음 에디션 선택과 클라우드 공급자를 선택할 수 있는데, 나는 Standard 에디션과 AWS를 선택했다. (지역 = 서울)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccolkY/btsKoZi6uIi/tg4Y4niF4KWTZjMtE6ecoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccolkY/btsKoZi6uIi/tg4Y4niF4KWTZjMtE6ecoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccolkY/btsKoZi6uIi/tg4Y4niF4KWTZjMtE6ecoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccolkY%2FbtsKoZi6uIi%2Ftg4Y4niF4KWTZjMtE6ecoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;452&quot; height=&quot;536&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이어지는 몇 가지 설문에 대답하면, 위와 같이 등록이 완료되었다는 메시지가 노출된다. 이제 이메일로 가서 확인하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMeqtv/btsKqBBgu9R/g8yHd09TmydWSOwzaXTwBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMeqtv/btsKqBBgu9R/g8yHd09TmydWSOwzaXTwBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMeqtv/btsKqBBgu9R/g8yHd09TmydWSOwzaXTwBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMeqtv%2FbtsKqBBgu9R%2Fg8yHd09TmydWSOwzaXTwBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;641&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 활성화 버튼이 있는 메일을 받은 것을 확인할 수 있다. 'CLICK TO ACTIVATE' 버튼을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KCRlX/btsKqtQMqf9/TkmaXOqgGZLlq8qcfhYK80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KCRlX/btsKqtQMqf9/TkmaXOqgGZLlq8qcfhYK80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KCRlX/btsKqtQMqf9/TkmaXOqgGZLlq8qcfhYK80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKCRlX%2FbtsKqtQMqf9%2FTkmaXOqgGZLlq8qcfhYK80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;731&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기에 사용할 계정의 이름과 암호를 입력해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYKhKo/btsKqn4bT9W/e5s9kesxyJZx4GI4KGhcM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYKhKo/btsKqn4bT9W/e5s9kesxyJZx4GI4KGhcM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYKhKo/btsKqn4bT9W/e5s9kesxyJZx4GI4KGhcM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYKhKo%2FbtsKqn4bT9W%2Fe5s9kesxyJZx4GI4KGhcM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;933&quot; height=&quot;822&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 위와 같이 Snowflake를 사용할 수 있게 되었다. 무료 평가판은 30일 또는 400달러가 한도이니 이에 유의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Snowflake는 따로 프로그램이 있는게 아닌 특정 URL로 로그인해야 서비스를 이용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XKLnv/btsKpmrGHd5/1cXkMqBRebwnOaFNeXzt81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XKLnv/btsKpmrGHd5/1cXkMqBRebwnOaFNeXzt81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XKLnv/btsKpmrGHd5/1cXkMqBRebwnOaFNeXzt81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXKLnv%2FbtsKpmrGHd5%2F1cXkMqBRebwnOaFNeXzt81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;487&quot; height=&quot;383&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이메일에서 수신한 URL을 기억해두어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Snowflake 둘러보기 &amp;amp; 기본설정&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Worksheet&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/npckD/btsKpUnTyGf/duKwHOUj8ZuiuDgfzRlkj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/npckD/btsKpUnTyGf/duKwHOUj8ZuiuDgfzRlkj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/npckD/btsKpUnTyGf/duKwHOUj8ZuiuDgfzRlkj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnpckD%2FbtsKpUnTyGf%2FduKwHOUj8ZuiuDgfzRlkj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;670&quot; height=&quot;530&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Worksheet에서 다양한 예시가 담긴 노트북 파일을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crQd1e/btsKq1fdaXX/XYK2p9kkpT2vGoOnJcVDQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crQd1e/btsKq1fdaXX/XYK2p9kkpT2vGoOnJcVDQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crQd1e/btsKq1fdaXX/XYK2p9kkpT2vGoOnJcVDQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrQd1e%2FbtsKq1fdaXX%2FXYK2p9kkpT2vGoOnJcVDQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;887&quot; height=&quot;833&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;833&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Worksheet는 웹 SQL 에디터 기능으로, 위와 같이 일종의 노트북 같은 방식으로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Database&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Snowflake는 기본적으로 2개의 데이터베이스를 세팅해놓는다. 좌측 메뉴 바에서 Data - Databases를 클릭해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;811&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAymvs/btsKpiip6aH/4rKY8Ek5UGGanEKCnvGEjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAymvs/btsKpiip6aH/4rKY8Ek5UGGanEKCnvGEjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAymvs/btsKpiip6aH/4rKY8Ek5UGGanEKCnvGEjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAymvs%2FbtsKpiip6aH%2F4rKY8Ek5UGGanEKCnvGEjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;934&quot; height=&quot;811&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;811&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'SNOWFLAKE'와 'SNOWFLAKE_SAMPLE_DATA'라는 2개의 데이터베이스 밑에 수많은 스키마가 있고, 그 아래에 존재하는게 테이블이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Warehouse&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqZ9v0/btsKrgDbfl6/32KIlJWPKkSNAhfg76ebBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqZ9v0/btsKrgDbfl6/32KIlJWPKkSNAhfg76ebBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqZ9v0/btsKrgDbfl6/32KIlJWPKkSNAhfg76ebBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqZ9v0%2FbtsKrgDbfl6%2F32KIlJWPKkSNAhfg76ebBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;921&quot; height=&quot;788&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;스토리지에 있는 데이터를 프로세싱하는 컴퓨팅 리소스인 웨어하우스는 Admin - Warehouses에서 확인할 수 있다. 무료 평가판에서는 COMPUTE_WH라는 웨어하우스가 가장 작은 사이즈로 제공된다. 만약 더 큰 사이즈의 웨어하우스가 필요하다면 우측 상단 '+ Warehouse' 버튼을 통해 새롭게 추가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>Data warehouse</category>
      <category>snowflake</category>
      <category>데이터 엔지니어</category>
      <category>데이터베이스</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/268</guid>
      <comments>https://minding-deep-learning.tistory.com/268#entry268comment</comments>
      <pubDate>Thu, 31 Oct 2024 11:26:08 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] AWS Redshift Spectrum으로 S3 외부 테이블 조작해보기</title>
      <link>https://minding-deep-learning.tistory.com/267</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Redshift&amp;nbsp;Spectrum&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Redshift Spectrum은 Redshift의 확장 기능으로, S3 스토리지에 있는 파일들을 마치 테이블처럼 취급해 SQL로 조작할 수 있도록 만드는 기능이다. S3의 있는 파일을 Redshift로 옮겨와야할 때, 크기가 너무 크거나 너무 정제되지 않은 채로 업데이트할 경우 비용이나 용량 문제가 발생할 수 있다. 이럴 때, Redshift Spectrum을 이용해 파일에서 필요한 정보들만 redshift에 옮겨올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Fact&amp;nbsp;테이블과&amp;nbsp;Dimension&amp;nbsp;테이블&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spectrum을 이용하기에 앞서, 분석 대상 데이터를 크게 2가지 테이블로 나눌 수 있다는 점을 알아보자. 아래와 같이 분석의 메인이 되는 Fact 테이블과 상세 정보를 제공하는 Dimension 테이블로 나눌 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fact 테이블: 분석의 초점이 되는 양적 정보를 포함하는 중앙 테이블
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 매출 수익, 판매량 등 사실 또는 측정 항목을 포함&lt;/li&gt;
&lt;li&gt;비즈니스 결정에 사용&lt;/li&gt;
&lt;li&gt;일반적으로 외래 키(FK)를 통해 여러 Dimension 테이블과 연결됨&lt;/li&gt;
&lt;li&gt;보통 Fact 테이블의 크기가 훨씬 더 큼.&lt;/li&gt;
&lt;li&gt;ex) Order 테이블: 사용자들의 상품 주문에 대한 정보가 들어간 테이블(상품 id, 주문 id, 사용자 id)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dimension 테이블: Fact 테이블에 대한 상세 정보를 제공하는 테이블
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고객, 제품 등과 같은 테이블로, Fact 테이블에 대한 상세 정보를 제공함.&lt;/li&gt;
&lt;li&gt;사용자가 다양한 방식으로 데이터를 조작내고 분석 가능하게 해줌&lt;/li&gt;
&lt;li&gt;일반적으로 PK를 가지며, Fact 테이블의 FK에서 참조함.&lt;/li&gt;
&lt;li&gt;보통 Dimension 테이블의 크기가 훨씬 더 작음&lt;/li&gt;
&lt;li&gt;ex) User 테이블 / Product 테이블&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일반적으로 규모가 작은 Dimension 테이블은 쉽게 Redshift에 적재하여 사용할 수 있지만, 크기가 큰 Fact 테이블은 다르다. Redshift Spectrum을 이용한다면 파일 형태로 S3에 남아있는 Fact 테이블을 굳이 Redshift에 적재하지 않고도 두 테이블을 조인해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Redshift Spectrum을 사용하기 위한 외부 테이블 용 스키마 설정 (+권한 추가)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;외부 테이블을 전달받을 스키마를 만들어주자. 하지만 이 전에 외부 테이블에 대한 권한 설정이 필요하다. AWS IAM으로 이동해 기존에 생성한 redshift.read.s3(역할 생성 방법(&lt;a href=&quot;https://minding-deep-learning.tistory.com/266&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;역할 생성 방법&lt;/a&gt;) 역할로 이동하자. (또는 현재 적용할 redshift 클러스터에 적용된 역할로 이동)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/47PEu/btsKnrmsdAo/wwegONaaqaGwpzAzLSKBm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/47PEu/btsKnrmsdAo/wwegONaaqaGwpzAzLSKBm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/47PEu/btsKnrmsdAo/wwegONaaqaGwpzAzLSKBm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F47PEu%2FbtsKnrmsdAo%2FwwegONaaqaGwpzAzLSKBm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;415&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;권한 정책 섹션에서 '권한 추가' - '정책 연결'을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/odEQA/btsKnQGfs1b/bThilc3LjiOUBG4FIlktkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/odEQA/btsKnQGfs1b/bThilc3LjiOUBG4FIlktkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/odEQA/btsKnQGfs1b/bThilc3LjiOUBG4FIlktkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FodEQA%2FbtsKnQGfs1b%2FbThilc3LjiOUBG4FIlktkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;834&quot; height=&quot;452&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWSGlueConsoleFullAccess를 선택하고 '권한 추가' 클릭하면 완료 된다. 여기서 AWS Glue에 대한 권한이 왜 필요하냐면, AWS Glue가 AWS Serverless ETL 서비스로서 아래와 같은 기능들을 제공하기 때문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 카탈로그: 데이터 소스 및 메타데이터 대상 검색 기능 제공 (S3 또는 AWS 서비스 상의 데이터 소스가 대상)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redshift Spectrum의 경우에는 외부 테이블들이 그 대상이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ETL 작업 생성: AWS Glue Studio&lt;/li&gt;
&lt;li&gt;작업 모니터링 및 로그&lt;/li&gt;
&lt;li&gt;서버리스 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, Redshift Spectrum으로 외부 테이블 생성 시 S3에 저장된 데이터를 쿼리하고, 외부 테이블의 메타데이터도 AWS Glue의 데이터 카탈로그에 저장하기 위해 AWS Glue에 대한 권한이 필요한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;권한 부여를 완료했다면, 아래 명령어를 통해 외부 테이블 전용 스키마를 생성한다. (필자는 Colab 환경을 사용했다.)&lt;/p&gt;
&lt;pre id=&quot;code_1730256164516&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;%%sql

-- AWSGlueConsoleFullAccess
CREATE EXTERNAL SCHEMA external_schema
from data catalog
database {원하는 DB 이름}
iam_role '{Redshift 계정에 연결된 역할의 ARN}'
create external database if not exists;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 S3에 있는 파일 하나를 선택해 아래와 같이 외부 테이블을 생성해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1730256590226&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 예시
%%sql

CREATE EXTERNAL TABLE external_schema.user_session_channel(
   userid integer ,
   sessionid varchar(32),
   channel varchar(32)
)
row format delimited
fields terminated by ','
stored as textfile
location 's3://minhyeok-test-bucket/usc/';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 위와 같이 생성한 외부 테이블들과 Redshift 내부 테이블 모두를 SQL로 조작이 가능하다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>aws redshift spectrum</category>
      <category>aws spectrum</category>
      <category>aws 외부 테이블</category>
      <category>Redshift</category>
      <category>redshift spectrum</category>
      <category>redshift 외부 테이블</category>
      <category>spectrum 외부 테이블</category>
      <category>외부 스키마</category>
      <category>외부 테이블</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/267</guid>
      <comments>https://minding-deep-learning.tistory.com/267#entry267comment</comments>
      <pubDate>Wed, 30 Oct 2024 13:28:12 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] AWS Redshift Serverless 설치 (무료 평가판 / Free Trial)</title>
      <link>https://minding-deep-learning.tistory.com/266</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;AWS의 대표적인 데이터 웨어하우스 서비스인 Redshift는 Serverless를 처음 이용할 경우 3달 또는 300달러까지 무료로 이용할 수 있다. Redshift를 공부도 해볼 겸, Redshift Serverless를 설치하고 초기 설정까지 해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AWS 로그인 및 Redshift 진입&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dlw1t/btsKo01gJlY/6OGySNKXoMLX7UtG5hB4b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dlw1t/btsKo01gJlY/6OGySNKXoMLX7UtG5hB4b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dlw1t/btsKo01gJlY/6OGySNKXoMLX7UtG5hB4b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDlw1t%2FbtsKo01gJlY%2F6OGySNKXoMLX7UtG5hB4b1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;880&quot; height=&quot;622&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS 홈페이지(aws.amazon.com)에 접속해 로그인한 뒤, Redshift 섹션으로 진입하면 위와 같은 화면이 나올 것이다. 아직까지 해당 계정으로 Redshift를 사용해본 적이 없기 때문에, '무료 평가판 사용해보기'라는 버튼이 노출된다. 해당 버튼을 눌러 Redshift Serverless 설치 과정을 시작해보자. (주의: 이 화면 아래로 내려가 '클러스터 생성' 버튼을 누르면 Serverless가 아닌 고정 비용이 드는 Redshift 클러스터를 생성하는 과정이다. 이 클러스터는 무료 평가판이 아니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Redshift Severless 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDA2i1/btsKorylumu/MUBBHx7tV4C0zTrxbewyR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDA2i1/btsKorylumu/MUBBHx7tV4C0zTrxbewyR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDA2i1/btsKorylumu/MUBBHx7tV4C0zTrxbewyR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDA2i1%2FbtsKorylumu%2FMUBBHx7tV4C0zTrxbewyR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;741&quot; height=&quot;741&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러면 위 이미지처럼 Serverless 클러스터를 생성하기 위해 설정하는 페이지로 진입하게 된다. 지금은 Redshift를 체험해 볼 목적이기 때문에 기본 설정을 사용한다. 이외 아직 따로 추가 설정을 해 줄 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 '구성 저장'을 눌러 클러스터를 생성해본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qQVve/btsKnUOudWq/iMd9gjqItaXzji3VIszbn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qQVve/btsKnUOudWq/iMd9gjqItaXzji3VIszbn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qQVve/btsKnUOudWq/iMd9gjqItaXzji3VIszbn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqQVve%2FbtsKnUOudWq%2FiMd9gjqItaXzji3VIszbn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;841&quot; height=&quot;554&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다소 시간이 소요되니 인내심을 가지고 기다려보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/skvD4/btsKodUDH1Y/tGMc0fAqkxmmlQKGtjKNj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/skvD4/btsKodUDH1Y/tGMc0fAqkxmmlQKGtjKNj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/skvD4/btsKodUDH1Y/tGMc0fAqkxmmlQKGtjKNj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FskvD4%2FbtsKodUDH1Y%2FtGMc0fAqkxmmlQKGtjKNj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;780&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;작업이 완료되면 위와 같이 대시보드 페이지로 이동하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pXLR5/btsKmJHeS8b/oWaqEJDk3qVS4ZtrKNq5wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pXLR5/btsKmJHeS8b/oWaqEJDk3qVS4ZtrKNq5wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pXLR5/btsKmJHeS8b/oWaqEJDk3qVS4ZtrKNq5wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpXLR5%2FbtsKmJHeS8b%2FoWaqEJDk3qVS4ZtrKNq5wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;256&quot; height=&quot;207&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 섹션의 남은 크레딧과 평가판 만료일을 잘 확인해서 쓸데없이 돈 나갈일이 없도록 하자. 학습이 끝났다면 꼭 클러스터를 종료하기!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Colab으로 Redshift Severless 연결해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;엔드포인트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 위 대시보드에서 '네임스페이스 / 작업그룹' 섹션을 찾아 작업 그룹 아래에 있는 'default-workgroup'을 클릭해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ1WHZ/btsKoNuym9R/ST36S9SfJTtaZrwNiKijaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ1WHZ/btsKoNuym9R/ST36S9SfJTtaZrwNiKijaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ1WHZ/btsKoNuym9R/ST36S9SfJTtaZrwNiKijaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ1WHZ%2FbtsKoNuym9R%2FST36S9SfJTtaZrwNiKijaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;158&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7n3zw/btsKmTwuVQa/C9MK9nY9gwmVXVTb2HvIR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7n3zw/btsKmTwuVQa/C9MK9nY9gwmVXVTb2HvIR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7n3zw/btsKmTwuVQa/C9MK9nY9gwmVXVTb2HvIR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7n3zw%2FbtsKmTwuVQa%2FC9MK9nY9gwmVXVTb2HvIR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;236&quot; height=&quot;156&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러면 우측에 '엔드포인트'라고 하는 항목이 보일텐데, 해당 주소를 복사해 놓는다. 이 주소가 해당 Redshift Severless 클러스터의 엔드포인트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;엑세스할 수 있는 계정 세팅&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Redshift에 엑세스할 수 있는 계정을 세팅하는 방법에는 크게 두 가지가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;admin(관리자) 계정을 생성하고 관리자 계정으로 엑세스&lt;/li&gt;
&lt;li&gt;admin(관리자) 계정을 생성하고 IAM 역할 부여를 통해 다른 계정으로 엑세스&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 글에서는 admin 계정을 이용해 엑세스해보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ1WHZ/btsKoNuym9R/ST36S9SfJTtaZrwNiKijaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ1WHZ/btsKoNuym9R/ST36S9SfJTtaZrwNiKijaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ1WHZ/btsKoNuym9R/ST36S9SfJTtaZrwNiKijaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ1WHZ%2FbtsKoNuym9R%2FST36S9SfJTtaZrwNiKijaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;158&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제는 이 섹션에서 제일 앞에있는 'default-namespace'를 클릭해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBSing/btsKnioTGGc/LdjkZSSL8pQKx6C4GeyZfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBSing/btsKnioTGGc/LdjkZSSL8pQKx6C4GeyZfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBSing/btsKnioTGGc/LdjkZSSL8pQKx6C4GeyZfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBSing%2FbtsKnioTGGc%2FLdjkZSSL8pQKx6C4GeyZfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;741&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼 위 페이지로 진입하게 될텐데, 오른쪽 위 '작업' 리스트를 열어 '관리자 보안 인증 정보 편집'으로 진입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDxSvD/btsKnRLe6lE/IrjsktgnqK4IORcxRg1kY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDxSvD/btsKnRLe6lE/IrjsktgnqK4IORcxRg1kY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDxSvD/btsKnRLe6lE/IrjsktgnqK4IORcxRg1kY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDxSvD%2FbtsKnRLe6lE%2FIrjsktgnqK4IORcxRg1kY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;429&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그렇게되면 하단에 관리자 이름과 비밀번호를 설정할 수 있는 항목이 있는데, 암호 표시를 선택해 현재 암호를 알아내거나, 수동으로 관리자 암호 추가를 선택해 암호를 설정하는 방식 중 하나를 고른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Redshift 클러스터에 Public Access 허용 설정하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Redshift 클러스터를 생성하면 기본적으로 외부에서의 액세스를 허용하지 않고 있다. Colab을 통해 외부에서 클러스터로 접속하기 위해서는 이와 관련된 설정이 우선되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 한번 '네임스페이스 / 작업그룹' 섹션의 작업 그룹 아래에 있는 'default-workgroup'을 클릭해 진입한 뒤, '네트워크 및 보안' 섹션으로 향해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRBaz6/btsKoMJfb06/hS6sKXYDlVk40ReYutvkak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRBaz6/btsKoMJfb06/hS6sKXYDlVk40ReYutvkak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRBaz6/btsKoMJfb06/hS6sKXYDlVk40ReYutvkak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRBaz6%2FbtsKoMJfb06%2FhS6sKXYDlVk40ReYutvkak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;444&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러면 우측에 '퍼블릭 엑세스 가능' 항목이 '꺼짐'으로 되어있는 것을 확인할 수 있다. 우측 상단 '편집'을 눌러 해당 기능을 켜주도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgeFe/btsKmJUQyD0/e5BtuftQ8fZhCkNkbKVRQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgeFe/btsKmJUQyD0/e5BtuftQ8fZhCkNkbKVRQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgeFe/btsKmJUQyD0/e5BtuftQ8fZhCkNkbKVRQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpgeFe%2FbtsKmJUQyD0%2Fe5BtuftQ8fZhCkNkbKVRQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;333&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'변경 사항 저장'을 눌러 퍼블릭 액세스 가능 기능을 켜주자. 해당 기능이 적용되는데는 다소 시간이 걸린다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;309&quot; data-origin-height=&quot;41&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuGujM/btsKojHjXQC/RaWoJKAQL5fvxcDbrT3OZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuGujM/btsKojHjXQC/RaWoJKAQL5fvxcDbrT3OZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuGujM/btsKojHjXQC/RaWoJKAQL5fvxcDbrT3OZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuGujM%2FbtsKojHjXQC%2FRaWoJKAQL5fvxcDbrT3OZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;309&quot; height=&quot;41&quot; data-origin-width=&quot;309&quot; data-origin-height=&quot;41&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같은 완료 메시지 창이 뜨면 다음 단계로 넘어가보자. 아직 끝난게 아니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lM4B9/btsKoelOYrG/WptL7kx9XRzlkePzzNRK90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lM4B9/btsKoelOYrG/WptL7kx9XRzlkePzzNRK90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lM4B9/btsKoelOYrG/WptL7kx9XRzlkePzzNRK90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlM4B9%2FbtsKoelOYrG%2FWptL7kx9XRzlkePzzNRK90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;795&quot; height=&quot;377&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번에는 VPC 보안 그룹의 설정을 해주어야 한다. 퍼블릭 액세스 기능을 켜놓았더라도 VPC의 보안 규칙이 설정되어 있지 않는다면 접속이 불가능하다. VPC 보안 그룹 아래에 있는 링크를 따라 가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qOFt2/btsKo6UKkIo/RKiPOh3kwOXkCCBdpTzhj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qOFt2/btsKo6UKkIo/RKiPOh3kwOXkCCBdpTzhj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qOFt2/btsKo6UKkIo/RKiPOh3kwOXkCCBdpTzhj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqOFt2%2FbtsKo6UKkIo%2FRKiPOh3kwOXkCCBdpTzhj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;532&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;보안 그룹 리스트가 있는 페이지로 이동하게 된다. 방금 Redshift에 설정된 VPC 보안그룹을 선택한 뒤, 아래 페이지에서 '인바운드 규칙'탭을 선택해준다. 그리고 '인바운드 규칙 편집' 버튼을 눌러 액세스 규칙을 생성해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bixyC4/btsKna5Vojn/5K5qtNCWFpLUwOYOZmwaS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bixyC4/btsKna5Vojn/5K5qtNCWFpLUwOYOZmwaS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bixyC4/btsKna5Vojn/5K5qtNCWFpLUwOYOZmwaS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbixyC4%2FbtsKna5Vojn%2F5K5qtNCWFpLUwOYOZmwaS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;855&quot; height=&quot;656&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재는 위와 같이 기본 규칙 1가지밖에 없는 상태이다. 현재는 같은 VPC 아래에 있는 서버나 서비스 내에서만 엑세스 가능하도록 되어 있다. '규칙 추가'를 통해 외부에서도 접속할 수 있도록 설정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRbtUG/btsKnQ6IXXq/wvqcDVtgRlc9MfKKxiGxAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRbtUG/btsKnQ6IXXq/wvqcDVtgRlc9MfKKxiGxAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRbtUG/btsKnQ6IXXq/wvqcDVtgRlc9MfKKxiGxAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRbtUG%2FbtsKnQ6IXXq%2FwvqcDVtgRlc9MfKKxiGxAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;829&quot; height=&quot;565&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;포트는 Redshift에서 사용하는 5439 포트를, 소스는 0.0.0.0/0으로 어느 IP에서도 접속할 수 있도록 설정해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;위 정보들을 이용해 Colab으로 Redshift Serverless 연결해보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 Redshift에 호환되는 SQLAlchemy 라이브러리를 다시 설치해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1730183180595&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;!pip install SQLAlchemy==1.4.47&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 다음 sql 모듈을 연결해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1730183195578&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;%load_ext sql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 다음 아래 명령어를 통해 자신이 만든 Redshift 클러스터와 연결해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1730183258968&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;%sql postgresql://{관리자 계정}:{관리자 비밀번호}@{엔드포인트}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;132&quot; data-origin-height=&quot;51&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lsaPj/btsKnpIgySF/EoJef0ipVPDFrDfSXECtp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lsaPj/btsKnpIgySF/EoJef0ipVPDFrDfSXECtp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lsaPj/btsKnpIgySF/EoJef0ipVPDFrDfSXECtp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlsaPj%2FbtsKnpIgySF%2FEoJef0ipVPDFrDfSXECtp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;132&quot; height=&quot;51&quot; data-origin-width=&quot;132&quot; data-origin-height=&quot;51&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 체크 표시가 나타나면서 아무런 에러 메시지가 나타나지 않았다면, 성공적으로 연결된 것이다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>AWS Redshift</category>
      <category>aws redshift serverless</category>
      <category>redshift serverless free trial</category>
      <category>redshift serverless 무료 평가판</category>
      <category>redshift 무료 평가판</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/266</guid>
      <comments>https://minding-deep-learning.tistory.com/266#entry266comment</comments>
      <pubDate>Tue, 29 Oct 2024 16:29:10 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] ApiGateway</title>
      <link>https://minding-deep-learning.tistory.com/265</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ApiGateway&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS의 ApiGateway는 서버 없이 게이트웨이 기능을 이용할 수 있도록 해주는 서비스다. API를 생성, 배포, 유지 및 관리할 수 있는 완전 관리형 서비스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기능&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RESTful 및 WebSocket API 지원&lt;/li&gt;
&lt;li&gt;트래픽 관리&lt;/li&gt;
&lt;li&gt;보안&lt;/li&gt;
&lt;li&gt;모니터링 및 로깅&lt;/li&gt;
&lt;li&gt;개발자 포털 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;API 유형&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;815&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HFipn/btsKltYoWUp/lbI7xZ0PnLwWLYdUgNmS0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HFipn/btsKltYoWUp/lbI7xZ0PnLwWLYdUgNmS0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HFipn/btsKltYoWUp/lbI7xZ0PnLwWLYdUgNmS0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHFipn%2FbtsKltYoWUp%2FlbI7xZ0PnLwWLYdUgNmS0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;815&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;815&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;HTTP, WebSocket, REST API, 프라이빗 REST API까지 여러 유형의 API를 생성할 수 있다. 이 글에서는 REST API를 생성해보는 과정을 작성해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;751&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzw3Cm/btsKnboLSlE/aCVoenfAoukjw4gwiFrks1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzw3Cm/btsKnboLSlE/aCVoenfAoukjw4gwiFrks1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzw3Cm/btsKnboLSlE/aCVoenfAoukjw4gwiFrks1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzw3Cm%2FbtsKnboLSlE%2FaCVoenfAoukjw4gwiFrks1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;828&quot; height=&quot;751&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;751&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 정보를 입력한 뒤 API 생성을 클릭해 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;597&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ql9iv/btsKlFddoBG/tkKaKz1sLq1kGikowyE4P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ql9iv/btsKlFddoBG/tkKaKz1sLq1kGikowyE4P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ql9iv/btsKlFddoBG/tkKaKz1sLq1kGikowyE4P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQl9iv%2FbtsKlFddoBG%2FtkKaKz1sLq1kGikowyE4P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;597&quot; height=&quot;731&quot; data-origin-width=&quot;597&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 다음 리소스를 생성해주어야 한다. '리소스 생성' 버튼을 눌러 리소스를 생성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMfnBc/btsKmvnFd6a/Z6iAbBYn8qQuWSgHF4OOP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMfnBc/btsKmvnFd6a/Z6iAbBYn8qQuWSgHF4OOP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMfnBc/btsKmvnFd6a/Z6iAbBYn8qQuWSgHF4OOP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMfnBc%2FbtsKmvnFd6a%2FZ6iAbBYn8qQuWSgHF4OOP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;440&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;간단히 test라는 이름의 리소스를 생성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HGHPX/btsKnl5KLWl/sVgynxJyKFnned7XANmLo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HGHPX/btsKnl5KLWl/sVgynxJyKFnned7XANmLo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HGHPX/btsKnl5KLWl/sVgynxJyKFnned7XANmLo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHGHPX%2FbtsKnl5KLWl%2FsVgynxJyKFnned7XANmLo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1232&quot; height=&quot;243&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음 순서는 메서드 생성이다. 위에서 만든 /test 리소스를 선택한 뒤 POST 방식의 메서드를 하나 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbAxgv/btsKlzR8BBV/lQQCwJMvKSvAgkDswL2Mn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbAxgv/btsKlzR8BBV/lQQCwJMvKSvAgkDswL2Mn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbAxgv/btsKlzR8BBV/lQQCwJMvKSvAgkDswL2Mn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbAxgv%2FbtsKlzR8BBV%2FlQQCwJMvKSvAgkDswL2Mn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;754&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 이미지처럼 메서드 유형을 선택하고 Lambda 등 다양한 방법과 결합할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>API</category>
      <category>apigateway</category>
      <category>AWS</category>
      <category>aws api</category>
      <category>aws apigateway</category>
      <category>Lambda</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/265</guid>
      <comments>https://minding-deep-learning.tistory.com/265#entry265comment</comments>
      <pubDate>Mon, 28 Oct 2024 14:58:01 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Lambda</title>
      <link>https://minding-deep-learning.tistory.com/264</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Lambda&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Lambda는 AWS의 대표적인 서버리스 서비스이다. 별도의 물리적인 서버없이 등록한 함수가 실행될 수 있도록 설정할 수 있다. Lambda는 마치 Python의 lambda 함수처럼 어떤 함수 하나를 등록해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;AWS Lambda 함수 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czPlxD/btsKlGCXyTV/nPnKVLN1GKM7RnT0C5AFJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czPlxD/btsKlGCXyTV/nPnKVLN1GKM7RnT0C5AFJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czPlxD/btsKlGCXyTV/nPnKVLN1GKM7RnT0C5AFJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczPlxD%2FbtsKlGCXyTV%2FnPnKVLN1GKM7RnT0C5AFJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;742&quot; height=&quot;461&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'함수 생성' 버튼을 통해 Python 함수 하나를 등록하고 실행해보자. 아래 함수는 AWS의 s3에서 트리거(온도)를 전달 받아 온도의 정도(뜨거운지 아닌지)를 판단하는 함수이다.&lt;/p&gt;
&lt;pre id=&quot;code_1730090200649&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import json
import boto3
from datetime import datetime

client = boto3.client('s3')


def lambda_handler(event, context):
    what_time = datetime.now().strftime(&quot;%Y-%m-%d %H:%M:%S&quot;)
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    try:
        response = client.get_object(Bucket=bucket, Key=key)
        
        text = response['Body'].read().decode()
        data = json.loads(text)
        
        if data['temperature'] &amp;gt; 40:
            print(f&quot;Temperature detected : {data['temperature']}C at {what_time}&quot;)
            print(&quot;Be careful! It's getting really hot!!&quot;)
        else:
            print(&quot;So far so good&quot;)
    except Exception as e:
        print(e)
        raise e&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhqeRZ/btsKliW6cW5/IddNjAtOnpadR5vqfLUje0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhqeRZ/btsKliW6cW5/IddNjAtOnpadR5vqfLUje0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhqeRZ/btsKliW6cW5/IddNjAtOnpadR5vqfLUje0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhqeRZ%2FbtsKliW6cW5%2FIddNjAtOnpadR5vqfLUje0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;537&quot; height=&quot;753&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이름과 실행시킬 프로그래밍 언어(런타임)를 선택한 후 함수를 생성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsW8SE/btsKllTOR7i/FijlS9JkVN3gqcRVbkHzmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsW8SE/btsKllTOR7i/FijlS9JkVN3gqcRVbkHzmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsW8SE/btsKllTOR7i/FijlS9JkVN3gqcRVbkHzmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsW8SE%2FbtsKllTOR7i%2FFijlS9JkVN3gqcRVbkHzmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;815&quot; height=&quot;608&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;생성한 함수 상세페이지에 진입하면, 하단에 소스 코드를 수정할 수 있는 페이지가 나타난다. 여기에 위에 있는 파이썬 함수를 붙여넣어 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJaUnj/btsKl44GqmN/s7oGrY6PSMWFA4H5sWm0bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJaUnj/btsKl44GqmN/s7oGrY6PSMWFA4H5sWm0bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJaUnj/btsKl44GqmN/s7oGrY6PSMWFA4H5sWm0bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJaUnj%2FbtsKl44GqmN%2Fs7oGrY6PSMWFA4H5sWm0bK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;183&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 좌측에 있는 'Deploy' 버튼을 눌러야 코드가 함수에 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;S3 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 미리 만들어 둔 S3 버킷에 아래 내용이 담긴 json 파일을 업로드하자.&lt;/p&gt;
&lt;pre id=&quot;code_1730090625504&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;temperature&quot;: 45
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnU48o/btsKlPNHh4U/DJv4GumNd9B3hnZN15RNAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnU48o/btsKlPNHh4U/DJv4GumNd9B3hnZN15RNAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnU48o/btsKlPNHh4U/DJv4GumNd9B3hnZN15RNAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnU48o%2FbtsKlPNHh4U%2FDJv4GumNd9B3hnZN15RNAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;187&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 다음 버킷의 '속성' 탭에 진입,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MdWNF/btsKnn93Ydt/3iQhUmoEuDkSwxSDoUPG8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MdWNF/btsKnn93Ydt/3iQhUmoEuDkSwxSDoUPG8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MdWNF/btsKnn93Ydt/3iQhUmoEuDkSwxSDoUPG8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMdWNF%2FbtsKnn93Ydt%2F3iQhUmoEuDkSwxSDoUPG8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;266&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이벤트 알림 항목에 '이벤트 알림 생성'을 통해 S3가 위에서 설정한 함수에 json파일에 있는 데이터를 보낼 수 있도록 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvtHQL/btsKlll2noh/vTlyYCinoXKDkMNKefyUvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvtHQL/btsKlll2noh/vTlyYCinoXKDkMNKefyUvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvtHQL/btsKlll2noh/vTlyYCinoXKDkMNKefyUvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvtHQL%2FbtsKlll2noh%2FvTlyYCinoXKDkMNKefyUvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;512&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFZqpB/btsKmRX99oL/fk8CZsYadAHIRHunijQbg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFZqpB/btsKmRX99oL/fk8CZsYadAHIRHunijQbg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFZqpB/btsKmRX99oL/fk8CZsYadAHIRHunijQbg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFZqpB%2FbtsKmRX99oL%2Ffk8CZsYadAHIRHunijQbg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;806&quot; height=&quot;573&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;573&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이벤트 유형은 '전송'으로 설정하고 전달할 lambda 함수를 지정해준 뒤 이벤트를 생성해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct3dc3/btsKl17YU9b/EsImww9Ku8WxFQjR2vBkf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct3dc3/btsKl17YU9b/EsImww9Ku8WxFQjR2vBkf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct3dc3/btsKl17YU9b/EsImww9Ku8WxFQjR2vBkf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct3dc3%2FbtsKl17YU9b%2FEsImww9Ku8WxFQjR2vBkf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;825&quot; height=&quot;552&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 다시 lambda 함수 페이지로 되돌아와 새로고침을 눌러주면, 위 이미지와 같이 트리거에 'S3'가 생긴 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 모니터링 탭으로 가서 CloudWatch Logs 중 가장 최신 로그를 선택해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1603&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PvWRj/btsKk1H4nbX/SOiVn4sEkwpTCf73E8vWr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PvWRj/btsKk1H4nbX/SOiVn4sEkwpTCf73E8vWr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PvWRj/btsKk1H4nbX/SOiVn4sEkwpTCf73E8vWr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPvWRj%2FbtsKk1H4nbX%2FSOiVn4sEkwpTCf73E8vWr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1603&quot; height=&quot;386&quot; data-origin-width=&quot;1603&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1485&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfZyxa/btsKlVUFiGG/thlDRCsuVm7EvhPvK9XQ7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfZyxa/btsKlVUFiGG/thlDRCsuVm7EvhPvK9XQ7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfZyxa/btsKlVUFiGG/thlDRCsuVm7EvhPvK9XQ7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfZyxa%2FbtsKlVUFiGG%2FthlDRCsuVm7EvhPvK9XQ7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1485&quot; height=&quot;200&quot; data-origin-width=&quot;1485&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;의도한대로 45를 넣었더니 매우 뜨겁다고 말하는 로그가 적혀있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 위 로그가 뜨지 않고&amp;nbsp;&lt;b&gt; &lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;[ERROR] ClientError: An error occurred (AccessDenied) when calling the GetObject operation&lt;/span&gt; &lt;/b&gt;라는 로그가 떠 있다면, 아래 해결 방법을 참고해보자.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h2 style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;권한 부족&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Lambda 함수에 연결된 IAM 역할(&lt;/span&gt;&lt;span&gt;lambda-test-role-h4y52dod&lt;/span&gt;&lt;span&gt;)에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;s3:GetObject&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;권한이 없습니다. 이로 인해 S3 객체에 접근할 수 없습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;버킷 정책 문제&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: S3 버킷 정책이 해당 역할에 대해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;s3:GetObject&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;권한을 명시적으로 허용하지 않을 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;IAM 역할에 권한 추가&lt;/b&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;IAM 콘솔에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;lambda-test-role-h4y52dod&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;역할을 찾습니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;역할에 다음과 같은 정책을 추가하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;s3:GetObject&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;권한을 부여합니다:&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1730093666651&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;s3:GetObject&quot;,
      &quot;Resource&quot;: &quot;arn:aws:s3:::elasticbeanstalk-us-east-1-565393054532/ex.json&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>AWS Lambda</category>
      <category>Lambda</category>
      <category>lambda s3</category>
      <category>Lambda 트리거</category>
      <category>s3 lambda 연결</category>
      <category>s3:getobject</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/264</guid>
      <comments>https://minding-deep-learning.tistory.com/264#entry264comment</comments>
      <pubDate>Mon, 28 Oct 2024 14:35:22 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] ECS / ECR</title>
      <link>https://minding-deep-learning.tistory.com/263</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ECR&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ECR은 Docker의 Dockerhub와 같이 AWS에서 제공하는 이미지 저장공간(레포지토리)이다. ECR을 통해 컨테이너 이미지를 공개 또는 비공개적으로 공유할 수 있다. 해당 메뉴는 ECS 내에 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ECS&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ECR에 저장되어 있는 이미지를 기반으로 가상화된 서비스를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ECR 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;767&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjQ7a2/btsKl68ZLkY/rf11K3tmWLYPdh6OOzoig1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjQ7a2/btsKl68ZLkY/rf11K3tmWLYPdh6OOzoig1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjQ7a2/btsKl68ZLkY/rf11K3tmWLYPdh6OOzoig1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjQ7a2%2FbtsKl68ZLkY%2Frf11K3tmWLYPdh6OOzoig1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;826&quot; height=&quot;767&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;767&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기본 레포지토리 생성 방법은 간단하다. 레포지토리 이름만 적은 뒤 생성을 눌러주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7NDPH/btsKlNPuQXr/vpkoKePaOdWmOnOnvtsgS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7NDPH/btsKlNPuQXr/vpkoKePaOdWmOnOnvtsgS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7NDPH/btsKlNPuQXr/vpkoKePaOdWmOnOnvtsgS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7NDPH%2FbtsKlNPuQXr%2FvpkoKePaOdWmOnOnvtsgS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;538&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아직은 이미지가 없는 빈 레포지토리인 것을 볼 수 있다. 오른쪽 상단 '푸시 명령 보기'를 누르면 이미지를 업로드할 수 있는 방법을 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgOCDC/btsKmon1dMI/KVdlgri36Mw4YczxQtey1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgOCDC/btsKmon1dMI/KVdlgri36Mw4YczxQtey1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgOCDC/btsKmon1dMI/KVdlgri36Mw4YczxQtey1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgOCDC%2FbtsKmon1dMI%2FKVdlgri36Mw4YczxQtey1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;799&quot; height=&quot;786&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 순서대로 준비한 Dockerfile을 업로드해줄 수 있다. 하지만 그 전에 ECR에 접근할 수 있는 권한을 먼저 설정해줄 필요가 있다. IAM에서 ecr-fullaccess 권한을 추가해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M6LIP/btsKmyD6jby/K5nt1NaA8P4KP20yLi5Dj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M6LIP/btsKmyD6jby/K5nt1NaA8P4KP20yLi5Dj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M6LIP/btsKmyD6jby/K5nt1NaA8P4KP20yLi5Dj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM6LIP%2FbtsKmyD6jby%2FK5nt1NaA8P4KP20yLi5Dj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;530&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 위 명령어를 따라 실행해보면 위와 같이 이미지가 업로드된 것을 볼 수 있다. 이제 이 이미지를 원하는 서버에서 pull한 뒤 컨테이너를 실행시킬 수 있다. EC2 또는 ElasticeBeanstalk 등을 이용할 수 있지만, 별도의 서버없이 이 이미지를 실행시키고 이용할 수 있는 서비스가 있다. 바로 ECS다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ECS 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z1AMX/btsKlFwZval/L7mYfUAMLJFOTK8zHL3580/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z1AMX/btsKlFwZval/L7mYfUAMLJFOTK8zHL3580/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z1AMX/btsKlFwZval/L7mYfUAMLJFOTK8zHL3580/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ1AMX%2FbtsKlFwZval%2FL7mYfUAMLJFOTK8zHL3580%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;401&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ECS는 클러스터 단위로 실행된다. 먼저 클러스터를 생성해주자. 기본적으로 이름을 제외하고는 따로 입력할 것이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHRLoT/btsKlFwZzUh/nVqgWrdAZFDwjm80z0Q371/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHRLoT/btsKlFwZzUh/nVqgWrdAZFDwjm80z0Q371/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHRLoT/btsKlFwZzUh/nVqgWrdAZFDwjm80z0Q371/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHRLoT%2FbtsKlFwZzUh%2FnVqgWrdAZFDwjm80z0Q371%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;436&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;클러스터가 생성되었다면, 이제 이 클러스터에 '태스크'를 정의하고 컨테이너를 구성해주어야 한다. 좌측 메뉴 바에 '태스크 정의' 항목에 진입해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;617&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pgldk/btsKlxsDMAV/bXD2kyNDnlqD1kurql6z5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pgldk/btsKlxsDMAV/bXD2kyNDnlqD1kurql6z5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pgldk/btsKlxsDMAV/bXD2kyNDnlqD1kurql6z5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPgldk%2FbtsKlxsDMAV%2FbXD2kyNDnlqD1kurql6z5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;900&quot; height=&quot;617&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;617&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'새 태스크 정의 생성'을 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfGbzT/btsKmyjO6na/aaVJ9y0oE9YLBK3Bk9Go21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfGbzT/btsKmyjO6na/aaVJ9y0oE9YLBK3Bk9Go21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfGbzT/btsKmyjO6na/aaVJ9y0oE9YLBK3Bk9Go21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfGbzT%2FbtsKmyjO6na%2FaaVJ9y0oE9YLBK3Bk9Go21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;433&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;원하는 OS 환경 등을 지정한 뒤, 필수 컨테이너에 자신이 실행할 이미지 URI를 복사해 붙여넣고 생성한다. (ECR에서 확인 가능하다.)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ljN7M/btsKlwtMfcC/iWq0kfj2WMlp03MxSFtKKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ljN7M/btsKlwtMfcC/iWq0kfj2WMlp03MxSFtKKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ljN7M/btsKlwtMfcC/iWq0kfj2WMlp03MxSFtKKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FljN7M%2FbtsKlwtMfcC%2FiWq0kfj2WMlp03MxSFtKKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;418&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 다시 클러스터로 돌아와 위에서 등록한 태스크를 클러스터에서 실행한다. 태스크 탭의 '새 태스크 실행'을 눌러 해당 태스크를 실행시켜준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;태스크가 실행 완료되었다면, 이제는 서비스를 생성할 차례다. 서비스 탭으로 이동해 서비스를 생성해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MbKQk/btsKluCwb9i/ZCInyl4RckMH3IWAZgjg81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MbKQk/btsKluCwb9i/ZCInyl4RckMH3IWAZgjg81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MbKQk/btsKluCwb9i/ZCInyl4RckMH3IWAZgjg81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMbKQk%2FbtsKluCwb9i%2FZCInyl4RckMH3IWAZgjg81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;574&quot; height=&quot;607&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;607&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;태스크에서 지정한 패밀리와 똑같이 지정해 준 다음 서비스를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmLv6n/btsKlwAuzHd/zGG7kagJSEA6mIOfIjANQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmLv6n/btsKlwAuzHd/zGG7kagJSEA6mIOfIjANQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmLv6n/btsKlwAuzHd/zGG7kagJSEA6mIOfIjANQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmLv6n%2FbtsKlwAuzHd%2FzGG7kagJSEA6mIOfIjANQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;578&quot; height=&quot;369&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;서비스까지 완성되었다면 해당 이미지가 컨테이너로 정상 실행되었는지 확인해봐야 한다. 확인은 별도의 ELB를 만들어 이를 통해 확인할 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>aws docker</category>
      <category>aws dockerfile 업로드</category>
      <category>AWS ECR</category>
      <category>AWS ECS</category>
      <category>aws 이미지 업로드</category>
      <category>aws 컨테이너</category>
      <category>docker</category>
      <category>dockerfile</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/263</guid>
      <comments>https://minding-deep-learning.tistory.com/263#entry263comment</comments>
      <pubDate>Mon, 28 Oct 2024 12:27:12 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Docker의 개념 및 기본 실행 명령어</title>
      <link>https://minding-deep-learning.tistory.com/262</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Docker&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Docker는 애플리케이션을 컨테이너라는 독립된 환경에서 실행할 수 있게 해주는 Linux 컨테이너 기반 플랫폼이다. 애플리케이션과 관련된 라이브러리와 종속성을 하나의 패키지로 묶어 어디서든 일관된 환경에서 실행되도록 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이전에는 하나의 PC(또는 서버)에 가상화(Virtual Machine)를 통해 여러 개의 애플리케이션을 실행시키는 방법이 많이 사용됐다. 하지만 최근에는 Docker를 통해 각 컨테이너 별로 애플리케이션을 실행시키는 방식이 많이 도입되고 있다. 하나의 하드웨어에서 여러 개의 소프트웨어를 실행시킨다는 기본적인 개념은 같지만, Docker를 쓰기 시작한 데에는 많은 이유가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: start; width: 14.0698%;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 40.9302%;&quot;&gt;&lt;b&gt;가상 머신(VM)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 44.8837%;&quot;&gt;&lt;b&gt;Docker 컨테이너&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: start; width: 14.0698%;&quot;&gt;운영체제&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 40.9302%;&quot;&gt;각 VM마다 별도의 게스트 OS 필요&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 44.8837%;&quot;&gt;호스트 OS의 커널 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: start; width: 14.0698%;&quot;&gt;시작 속도&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 40.9302%;&quot;&gt;느림&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 44.8837%;&quot;&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: start; width: 14.0698%;&quot;&gt;자원 사용&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 40.9302%;&quot;&gt;게스트 OS 포함으로 인해 무겁고 비효율적&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 44.8837%;&quot;&gt;경량화되어 효율적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: start; width: 14.0698%;&quot;&gt;격리 수준&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 40.9302%;&quot;&gt;높은 수준의 격리 제공&lt;/td&gt;
&lt;td style=&quot;text-align: start; width: 44.8837%;&quot;&gt;프로세스 수준의 격리 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;887&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Gudt/btsKkpaRvSb/hmucZZTgt5TV88p7heYEI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Gudt/btsKkpaRvSb/hmucZZTgt5TV88p7heYEI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Gudt/btsKkpaRvSb/hmucZZTgt5TV88p7heYEI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Gudt%2FbtsKkpaRvSb%2FhmucZZTgt5TV88p7heYEI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;887&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;887&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 그림과 표에서도 볼 수 있듯 가상화의 경우 각 VM마다 OS가 포함되어 있기 때문에 매우 무거워진다는 단점이 있다. 따라서 Size도 커지고, 부팅 속도도 느려진다. 하지만 컨테이너의 경우 호스트 OS 커널을 각 컨테이너에 공유하는 방식이기 때문에, 비교적 Size가 가볍고 부팅 속도도 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런 장점이 있기 때문에 Docker는 클라우드 환경에서 대규모 서버 관리, 애플리케이션 배포에 널리 사용되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Docker의 구성 요소&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker Engine: 컨테이너를 생성하고 관리하는 핵심 요소, Docker Daemon과 클라이언트로 구성됨.&lt;/li&gt;
&lt;li&gt;Docker Image: 컨테이너를 실행하기 위한 읽기 전용 템플릿, 이는 Dockerfile이라는 파일에 정의된 명령어들로부터 생성됨. 컨테이너의 목적에 맞는 바이너리와 의존성이 설치되어 있고, 여러 개의 계층으로 된 바이너리 파일로 존재함.&lt;/li&gt;
&lt;li&gt;Docker Container: Docker Image를 실행한 상태로, 격리된 시스템 자원 및 네트워크를 사용하는 독립된 공간&lt;/li&gt;
&lt;li&gt;Registry: Docker 이미지를 저장하고 공유할 수 있는 저장소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Docker는 Image로 컨테이너를 생성하고 실행시킨다는게 제일 큰 특징이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Docker 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Docker 설치는 아래 공식 홈페이지의 순서에 따라 진행하면 된다. (Ubuntu 기준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;https://docs.docker.com/engine/install/ubuntu/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730075233557&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Ubuntu&quot; data-og-description=&quot;Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install Docker Engine on Ubuntu.&quot; data-og-host=&quot;docs.docker.com&quot; data-og-source-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; data-og-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/byBhgl/hyXlIIAp1a/LTKgHWKTJzzg8Hxr6xN6MK/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260&quot;&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/byBhgl/hyXlIIAp1a/LTKgHWKTJzzg8Hxr6xN6MK/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Ubuntu&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install Docker Engine on Ubuntu.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 Windows 환경이라면, WSL2를 설치해 Ubuntu 환경을 먼저 세팅한 다음에 Docker 설치를 진행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://minding-deep-learning.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Windows 환경에서 Linux 사용을 위한 WSL2 설치&lt;/a&gt; (이 링크 참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Mac의 경우 터미널에 아래 명령어를 통해 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730075355842&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install --cask docker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Docker의 실행 흐름&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uHfD2/btsKlF4NLg2/apqJDFNBx8s0MfIVuPEqgk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uHfD2/btsKlF4NLg2/apqJDFNBx8s0MfIVuPEqgk/img.gif&quot; data-alt=&quot;출처: Vikas Rajput Linkedin&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uHfD2/btsKlF4NLg2/apqJDFNBx8s0MfIVuPEqgk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/uHfD2/btsKlF4NLg2/apqJDFNBx8s0MfIVuPEqgk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;519&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Vikas Rajput Linkedin&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Docker의 전체적인 실행 흐름은 위 이미지와 같다. Dockerhub 등에서 이미 만들어진 Image를 가져올 수도 있고, 특정 이미지를 dockerfile에서 build 명령어를 통해 자신이 생성할 수도 있다. 이미지는 pull 또는 push 명령을 통해 docker registry에서 가져오거나 저장시킬 수 있고, run 명령어를 통해 컨테이너를 실행시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Docker와 Port forwarding&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Docker 컨테이너는 기본적으로 외부 네트워크와 격리된 상태다. 따라서, 외부에서 컨테이너 내부 서비스에 접근할 수 있도록 하기 위해서는 '포트 포워딩'이라는 과정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;포트 포워딩은 네트워크에서 특정 포트로 들어오는 트래픽을 다른 포트로 전달하는 것을 의미한다. Docker에서는 호스트의 특정 포트를 컨테이너의 포트에 매핑해 외부에서 컨테이너 내부의 애플리케이션에 접근할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;791&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oMwwx/btsKl8MsrS6/Ej9MZSJ3GFdC98UTcC5RG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oMwwx/btsKl8MsrS6/Ej9MZSJ3GFdC98UTcC5RG1/img.png&quot; data-alt=&quot;출처: https://iximiuz.com/en/posts/docker-publish-container-ports/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oMwwx/btsKl8MsrS6/Ej9MZSJ3GFdC98UTcC5RG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoMwwx%2FbtsKl8MsrS6%2FEj9MZSJ3GFdC98UTcC5RG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;791&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;791&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://iximiuz.com/en/posts/docker-publish-container-ports/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 이미지처럼 호스트와 서버가 동일할 경우 컨테이너(서버)에 80번 포트가 열려있다면, 호스트에서도 80번 포트로 접속하면 된다. 그러나 Docker 컨테이너의 경우 완전히 격리된 상황이므로 포트 포워딩으로 호스트의 8080포트를 컨테이너의 80번 포트로 연결한다면, 호스트의 8080포트에서도 접근할 수 있게된다. (꼭 8080포트를 쓰지는 않아도 된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Docker의 주요 명령어&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;container 생성 및 실행 관련명령어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;container 생성 및 실행: run&lt;/li&gt;
&lt;li&gt;container 중지 : stop&lt;/li&gt;
&lt;li&gt;container 실행 : start&lt;/li&gt;
&lt;li&gt;conatiner 재실행 : restart&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;container 관리 관련 명령어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;container 확인 : ps&lt;/li&gt;
&lt;li&gt;container 삭제 : rm&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;container 실행 관리 관련명령어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;container log 확인 : logs&lt;/li&gt;
&lt;li&gt;container에 명령어 수행: exec&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;image 관리 관련명령어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;image 확인: images&lt;/li&gt;
&lt;li&gt;image 삭제: rmi&lt;/li&gt;
&lt;li&gt;image 다운로드하기: pull&lt;/li&gt;
&lt;li&gt;image 업로드하기: push&lt;/li&gt;
&lt;li&gt;image 태그지정하기: tag&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Dockerfile&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Dockerfile은 아래의 명령어들로 구성된다. 이 Dockerfile을 통해 Image를 생성한다.&lt;/p&gt;
&lt;table style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start; border-collapse: collapse; width: 100%; height: 391px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;FROM&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;베이스 이미지를 지정합니다. Dockerfile은 반드시&lt;span&gt;&amp;nbsp;&lt;/span&gt;FROM&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어로 시작해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;RUN&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;새로운 이미지 레이어에서 명령을 실행합니다. 주로 패키지 설치나 설정에 사용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 40px;&quot;&gt;CMD&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 40px;&quot;&gt;컨테이너 실행 시 기본으로 실행할 명령을 지정합니다. 여러 개의 CMD가 있을 경우, 마지막 CMD만 적용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;LABEL&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;이미지에 메타데이터를 추가합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;EXPOSE&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;컨테이너가 수신할 포트를 명시합니다. 이는 문서화 목적이며 실제 포트 개방은 아닙니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;ENV&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;환경 변수를 설정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;ADD&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;파일 또는 디렉토리를 이미지에 추가합니다. URL에서 파일을 다운로드할 수도 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;COPY&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;로컬 파일 또는 디렉토리를 이미지에 복사합니다. ADD보다 단순한 기능을 제공합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;ENTRYPOINT&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;컨테이너의 기본 실행 파일을 지정합니다. CMD와 함께 사용하여 기본 인수를 설정할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;VOLUME&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;컨테이너 내의 디렉토리를 호스트와 공유할 수 있는 볼륨으로 지정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;USER&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;명령어 실행 시 사용할 사용자와 그룹을 지정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;WORKDIR&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;명령어 실행 전 작업 디렉토리를 설정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;ARG&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;빌드 시 사용할 변수 값을 정의합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;ONBUILD&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;부모 이미지로부터 파생된 이미지가 빌드될 때 실행할 명령을 지정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;STOPSIGNAL&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;컨테이너 종료 시 사용할 시스템 호출 신호를 설정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 34px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 34px;&quot;&gt;HEALTHCHECK&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 34px;&quot;&gt;컨테이너의 상태를 확인하기 위한 테스트를 정의합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;SHELL&lt;/td&gt;
&lt;td style=&quot;text-align: start; height: 20px;&quot;&gt;기본 셸을 설정합니다. 일반적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;/bin/sh -c가 기본값입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Docker-compose&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 컨테이너를 여러 개 사용한다면 이를 하나씩 실행시키고 관리하기에는 매우 불편한 점이 있다.(Airflow 하나만 사용하더라도 컨테이너 4개가 필요하다.) 그 때 사용하는게 Docker-compose이며, yaml 파일에 실행할 컨테이너의 정보를 작성하고 한 번에 실행시킬 수 있다. 예를 들어 웹 서버, DB, 캐시 등 다양한 서비스가 함께 작동해야 하는 애플리케이션에 매우 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;yaml 파일 내에는 애플리케이션의 서비스, 네트워크 볼륨 등이 정의되어 있다. 이 파일은 애플리케이션 스택의 청사진 역할이라고 할 수 있으며, 각 컨테이너가 어떻게 빌드되고 구성되는지를 명시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;프로젝트의 루트 디렉토리에 yaml파일은 생성한 뒤에, 아래 명령어로 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730079801430&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose up&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;각 서비스의 로그는 아래 명령어로 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730079830128&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose logs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모든 서비스를 중지하고 제거할 때는 아래 명령어를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730079855281&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose down&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>docker</category>
      <category>Docker Container</category>
      <category>Docker 컨테이너</category>
      <category>docker-compose</category>
      <category>vm</category>
      <category>vm docker</category>
      <category>도커</category>
      <category>컨테이너</category>
      <category>포트 포워딩</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/262</guid>
      <comments>https://minding-deep-learning.tistory.com/262#entry262comment</comments>
      <pubDate>Mon, 28 Oct 2024 11:00:20 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] AWS CLI (Command Line Interface)</title>
      <link>https://minding-deep-learning.tistory.com/261</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AWS CLI&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS CLI (Command Line Interface)는 AWS 서비스를 관리할 수 있는 통합 도구로, 명령어를 통해 서비스를 제어하고 스크립트를 통해 자동화할 수 있는 shell이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AWS CLI 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;pip 명령어를 통해 간단히 설치할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1729841735677&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install awscli&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;설치 확인은 아래 명령어로 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1729842079527&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws --version

&amp;gt;&amp;gt;&amp;gt;
aws-cli/1.35.14 Python/3.11.7 Windows/10 botocore/1.35.48&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;계정 연결&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS CLI 설치 뒤 내 계정을 등록해주어야 CLI를 통해 AWS 서비스에 접근할 수 있다. 이를 위해서는 액세스 키를 발급받아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kt27b/btsKi1H68IG/jAenndkfhGOvcFyV9M2c60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kt27b/btsKi1H68IG/jAenndkfhGOvcFyV9M2c60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kt27b/btsKi1H68IG/jAenndkfhGOvcFyV9M2c60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKt27b%2FbtsKi1H68IG%2FjAenndkfhGOvcFyV9M2c60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;302&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;임의로 사용자를 하나 생성해보았다. 해당 사용자 계정 상세 정보로 진입해 '보안 자격 증명'이라는 탭을 눌러보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KnipW/btsKk9RAhNS/iPevMJKVvHLyFJxDibJHC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KnipW/btsKk9RAhNS/iPevMJKVvHLyFJxDibJHC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KnipW/btsKk9RAhNS/iPevMJKVvHLyFJxDibJHC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKnipW%2FbtsKk9RAhNS%2FiPevMJKVvHLyFJxDibJHC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;283&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;283&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 이미지와 같이 액세스 키를 발급 받을 수 있는 공간이 나온다. '엑세스 키 만들기'를 눌러본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUiMm7/btsKk3jN7Td/a2QRLxSjvEw0Z6kW0Bg120/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUiMm7/btsKk3jN7Td/a2QRLxSjvEw0Z6kW0Bg120/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUiMm7/btsKk3jN7Td/a2QRLxSjvEw0Z6kW0Bg120/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUiMm7%2FbtsKk3jN7Td%2Fa2QRLxSjvEw0Z6kW0Bg120%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;704&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우리는 CLI를 사용할 목적으로 키를 발급받는 것이므로 사례에 CLI를 선택해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7lNqR/btsKjsZAzBR/kTcfe3lKDk7Ws90SpN3mH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7lNqR/btsKjsZAzBR/kTcfe3lKDk7Ws90SpN3mH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7lNqR/btsKjsZAzBR/kTcfe3lKDk7Ws90SpN3mH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7lNqR%2FbtsKjsZAzBR%2FkTcfe3lKDk7Ws90SpN3mH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;716&quot; height=&quot;615&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러면 위와 같이 키가 발급된다. 위의 액세스 키와 비밀 엑세스 키 2개를 아래와 같이 입력하면 된다. (region name과 format은 원하는대로 입력하면 된다.)&lt;/p&gt;
&lt;pre id=&quot;code_1729842608040&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws configure

&amp;gt;&amp;gt;&amp;gt;
AWS Access Key ID [None]: {엑세스 키}
AWS Secret Access Key [None]: {비밀 엑세스 키}
Default region name [None]: ap-northeast-2
Default output format [None]: json&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1729842655167&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws configure list

&amp;gt;&amp;gt;&amp;gt;
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                &amp;lt;not set&amp;gt;             None    None
access_key     ****************SKNM shared-credentials-file
secret_key     ****************wIOR shared-credentials-file
    region           ap-northeast-2      config-file    ~/.aws/config&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;연결에 성공하면 위와 같이 'aws configure list' 명령어에 등록한 계정의 정보가 노출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729842856423&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws s3 ls

&amp;gt;&amp;gt;&amp;gt;
2024-10-25 14:58:34 elasticbeanstalk-us-east-1-565393054532&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 이 계정에 허용된 권한에 해당하는 서비스에 접근할 수 있다. 위는 s3의 리스트를 나타내는 명령어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아래에서 CLI에서 S3를 다루는 명령어를 간단하게 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1729842965056&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# s3 버킷을 새로 만드는 명령어
aws s3 mb s3://{버킷 이름}

# 로컬에 있는 파일 버킷에 업로드
aws s3 sync {파일 명} s3://{버킷 이름}

# 버킷의 파일 모두 지우기
aws s3 rm s3://{버킷 이름} --recursive # --recursive 옵션: 하위 폴더까지 모두 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이처럼 AWS CLI에서는 명령어를 통해 AWS의 서비스를 제어할 수 있다. 이를 응용하면 서비스를 자동화하는 데 많은 도움이 될 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>aws cli</category>
      <category>aws shell</category>
      <category>cli</category>
      <category>S3</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/261</guid>
      <comments>https://minding-deep-learning.tistory.com/261#entry261comment</comments>
      <pubDate>Fri, 25 Oct 2024 17:01:12 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] CI / CD (CodeCommit, CodeBuild, CodeDeploy, CodePipeline)</title>
      <link>https://minding-deep-learning.tistory.com/260</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CI&amp;nbsp;/&amp;nbsp;CD&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CI와 CD는 프로그램을 배포하는 과정에서 많이 쓰이는 용어다. 그 개념에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CI (continuous Integration)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모든 개발자가 개발한 코드를 공유 리포지토리에 하루에도 여러 번 코드를 커밋하고 병합하는 것으로, 빌드 및 테스트 자동화 과정을 의미한다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CD (continuous Delivery)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;개발팀이 짧은 주기로 소프트웨어를 개발하고 언제든지 운영환경으로 안정적 배포하는 것으로, 배포 자동화 과정을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, CI와 CD는 하나의 통합된 과정이며, '개발 - 테스트 - 배포 - 운영 - 개발'의 흐름이 반복되는 과정이라고 생각할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU4raU/btsKisFaba1/vybmlMJxEKi0LySB6uMoZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU4raU/btsKisFaba1/vybmlMJxEKi0LySB6uMoZ1/img.png&quot; data-alt=&quot;출처: AWS 공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU4raU/btsKisFaba1/vybmlMJxEKi0LySB6uMoZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU4raU%2FbtsKisFaba1%2FvybmlMJxEKi0LySB6uMoZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;400&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: AWS 공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위는 AWS EC2에 웹 애플리케이션을 배포하는 파이프라인이다. Github 리포지토리에서 변경 사항을 커밋해 AWS 리소스에 엑세스하고, S3에 업로드한 뒤 CodeDeploy를 통해 EC2에 배포하는 방식이다. 이는 AWS를 이용한 CI/CD 방식 중 하나의 예시일 뿐이다. 아래에서 AWS에서 제공하는 CI/CD 관련 서비스는 어떤 것이 있는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CodeCommit&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CodeCommit은 클라우드에서 자산(문서, 소스 코드 등)을 비공개로 저장하여 관리하는 데 사용할 수 있도록 AWS에서 호스팅되는 버전 관리 서비스이다. Github(git)을 사용하는 방법과 거의 유사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CodeBuild&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CodeBuild는 클라우드 상 완전관리형 빌드 서비스로, 소스 코드를 컴파일하고 단위 테스트 실행하며 배포 준비가 완료된 아티팩트를 생성한다. (빌드 - 테스트 - 배포 도구에게 연결까지 실행가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CodeBuild에서는 자체 빌드 서버를 프로비저닝, 관리/확장할 필요가 없으며, Apache Maven, Gradle 등과 같이 널리 사용되는 프로그래밍 언어 및 빌드 도구에 맞게 사전 패키지된 빌드 환경을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 빌드 환경을 사용자 지정해 사용자 고유의 빌드 도구를 사용하는 것도 가능하며, 최대 빌드 요청 수에 맞게 자동으로 확장하는 기능도 가지고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s38pf/btsKiL5iMFD/8LZpUJPxxZoezvU59Uffy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s38pf/btsKiL5iMFD/8LZpUJPxxZoezvU59Uffy0/img.png&quot; data-alt=&quot;출처: AWS 공식 홈페이지 - 빌드와 테스트 과정에서 codebuild를 이용하는 모습이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s38pf/btsKiL5iMFD/8LZpUJPxxZoezvU59Uffy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs38pf%2FbtsKiL5iMFD%2F8LZpUJPxxZoezvU59Uffy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;288&quot; height=&quot;369&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: AWS 공식 홈페이지 - 빌드와 테스트 과정에서 codebuild를 이용하는 모습이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CQbeX/btsKir0BOOH/hKGKz8ZL2JhCtCGhhOVV01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CQbeX/btsKir0BOOH/hKGKz8ZL2JhCtCGhhOVV01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CQbeX/btsKir0BOOH/hKGKz8ZL2JhCtCGhhOVV01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCQbeX%2FbtsKir0BOOH%2FhKGKz8ZL2JhCtCGhhOVV01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;415&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CodeBuild의 작동방식은 위 다이어 그램과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CodeBuild 입력으로 빌드 프로젝트 제공(소스 코드, 사용할 빌드 환경, 실행할 빌드 명령 등)&lt;/li&gt;
&lt;li&gt;위에서 전달한 프로젝트를 사용해 빌드 환경 생성&lt;/li&gt;
&lt;li&gt;소스 코드 다운로드 및 빌드 사양(buildspec) 사용&lt;/li&gt;
&lt;li&gt;빌드 출력이 있으면 해당 출력값을 S3에 업로드 (또는 Amazon SNS를 통해 알림 전송)&lt;/li&gt;
&lt;li&gt;빌드가 실행되는 동안 CloudWatch Logs에 CodeBuild 정보 전송&lt;/li&gt;
&lt;li&gt;빌드가 실행되는 동안 CodeBuild 콘솔 또는 log 확인 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CodeDeploy&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CodeDeploy는 EC2 인스턴스, 온프레미스 인스턴스 등으로 애플리케이션 배포를 자동화하는 배포 서비스이며, 코드부터 패키지, 스크립트, 멀티미디어 파일 등 다양한 애플리케이션 콘텐츠를 거의 무제한으로 배포할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CodeDeploy는 서버에서 실행되며 S3 버킷, Github 레포지토리에 저장되는 애플리케이션 콘텐츠도 배포 가능하다. 또한 CodeDeploy를 사용하기 위해 기존 코드를 수정하지 않아도 된다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CodePipeline&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CodePipeline은 빠르고 안정적인 애플리케이션 및 인프라 업데이트를 위한 릴리스 파이프라인을 자동화하는 데 도움이 되는 서비스다. 위에서 소개한 CodeCommit, CodeBuild, CodeDeploy가 통합된 하나의 파이프라인 서비스라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SW 릴리스 프로세스를 모델링하고, 서버를 설정하거나 프로비저닝할 필요성 감소&lt;/li&gt;
&lt;li&gt;AWS Console 또는 CLI를 사용해 SW 릴리스 프로세스 단계 정의 가능&lt;/li&gt;
&lt;li&gt;피드백 반복하고 각 코드 변경 테스트하여 버그를 포착하는 새로운 기능을 신속하게 릴리스할 수 있음&lt;/li&gt;
&lt;li&gt;릴리스 프로세스 모든 단계에서 자체 플러그 또는 사전 구축된 플러그인을 사용해 필요에 따라 조정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>aws cd</category>
      <category>aws ci</category>
      <category>aws cicd</category>
      <category>CI/CD</category>
      <category>codebuild</category>
      <category>CodeCommit</category>
      <category>codedeploy</category>
      <category>CodePipeline</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/260</guid>
      <comments>https://minding-deep-learning.tistory.com/260#entry260comment</comments>
      <pubDate>Fri, 25 Oct 2024 11:40:15 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] S3</title>
      <link>https://minding-deep-learning.tistory.com/259</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;S3&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Amazon S3(Simple Storage Service)는 객체 스토리지 서비스다.(파일이나 객체 등을 저장) 데이터 레이크, 웹 사이트, 모바일 앱 등 다양한 분야에서 원하는 양의 데이터를 저장하고 보호할 수 있는 기능을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;S3는 특정 비즈니스, 조직 및 규정 준수 요구 사항에 맞게 데이터에 대한 엑세스를 최적화, 구조화 및 구성할 수 있는 관리 기능을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;S3 기능&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스토리지 클래스&lt;/li&gt;
&lt;li&gt;스토리지 관리&lt;/li&gt;
&lt;li&gt;액세스 관리&lt;/li&gt;
&lt;li&gt;데이터 처리&lt;/li&gt;
&lt;li&gt;스토리지 로깅 및 모니터링&lt;/li&gt;
&lt;li&gt;분석 및 인사이트&lt;/li&gt;
&lt;li&gt;강력한 일관성&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정적 웹 사이트 호스팅&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;S3를 이용해 별도의 서버없이 정적 웹 사이트를 호스팅할 수도 있다. 클라이언트 측 스크립트를 포함할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;S3의 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;S3는 크게 버킷, 객체, 키로 구성되어 있다. 버킷 &amp;gt; 객체 &amp;gt; 키 순서로 구성되어 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버킷: S3에 저장된 객체에 대한 컨테이너, 모든 객체는 어떤 버킷에 포함된다. (일종의 폴더)&lt;/li&gt;
&lt;li&gt;객체: S3에 저장되는 기본 객체, 객체는 객체 데이터와 메타데이터로 구성됨&lt;/li&gt;
&lt;li&gt;키: 버킷 내 객체의 고유 식별자, &quot;버킷 + 키 + 버전&quot;과 객체 자체 사이의 기본 데이터가 맵으로 연결되어 고유하게 식별할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;S3 설정&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;버킷 만들기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLJ1A4/btsKibDG9YK/KtjmF2fkgMc1etvbvK78dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLJ1A4/btsKibDG9YK/KtjmF2fkgMc1etvbvK78dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLJ1A4/btsKibDG9YK/KtjmF2fkgMc1etvbvK78dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLJ1A4%2FbtsKibDG9YK%2FKtjmF2fkgMc1etvbvK78dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;833&quot; height=&quot;788&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;S3를 이용하기 위해서 우선 '버킷'을 생성해주어야 한다. '버킷 만들기' 항목에 진입해 버킷을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YpsSz/btsKh0IVLxp/ay4ioH7lqFNB3UAAdih341/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YpsSz/btsKh0IVLxp/ay4ioH7lqFNB3UAAdih341/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YpsSz/btsKh0IVLxp/ay4ioH7lqFNB3UAAdih341/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYpsSz%2FbtsKh0IVLxp%2Fay4ioH7lqFNB3UAAdih341%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;846&quot; height=&quot;618&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 생성된 버킷에 진입해 '업로드' 버튼을 눌러 원하는 파일을 업로드시킬 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>aws s3</category>
      <category>aws 스토리지</category>
      <category>aws 정적 웹 페이지 호스팅</category>
      <category>aws 호스팅</category>
      <category>S3</category>
      <category>정적 웹 페이지 호스팅</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/259</guid>
      <comments>https://minding-deep-learning.tistory.com/259#entry259comment</comments>
      <pubDate>Fri, 25 Oct 2024 10:36:55 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] IAM</title>
      <link>https://minding-deep-learning.tistory.com/258</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;IAM&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS IAM(Identity and Access Management)은 AWS 리소스에 대한 액세스를 안전하게 제어할 수 있는 웹 서비스이다. IAM을 사용해 리소스를 사용하도록 인증(로그인) 및 권한 부여된 대상을 제어할 수 있다. 즉, 각 계정의 역할을 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS 계정 생성 시 해당 계정의 모든 AWS 서비스와 리소스에 완전한 액세스 권한이 있는 단일 로그인 ID로 시작한다.(=루트 계정) 이 때 생성 시 사용한 이메일 주소와 암호로 로그인해 액세스 한다. 하지만 일상적인 작업 시 이 루트 계정을 사용하는 것은 추천하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;IAM의 특징&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS 계정에 대한 공유&lt;/li&gt;
&lt;li&gt;세분화된 권한&lt;/li&gt;
&lt;li&gt;Amazon EC2에서 실행되는 애플리케이션을 위한 보안 AWS 리소스 액세스&lt;/li&gt;
&lt;li&gt;멀티 팩터 인증(MFA)&lt;/li&gt;
&lt;li&gt;ID 페더레이션&lt;/li&gt;
&lt;li&gt;보장을 위한 자격 증명 정보&lt;/li&gt;
&lt;li&gt;PCI DSS 준수&lt;/li&gt;
&lt;li&gt;많은 AWS 서비스와의 통합&lt;/li&gt;
&lt;li&gt;최종 일관성&lt;/li&gt;
&lt;li&gt;무료 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;IAM 정책&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;IAM은 각 개인 또는 그룹에 부여된 정책에 따라 권한의 유무를 판단한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nUH0Q/btsKjONt0I5/zP6ikWAIH30Bglmifu03OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nUH0Q/btsKjONt0I5/zP6ikWAIH30Bglmifu03OK/img.png&quot; data-alt=&quot;출처: AWS 공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nUH0Q/btsKjONt0I5/zP6ikWAIH30Bglmifu03OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnUH0Q%2FbtsKjONt0I5%2FzP6ikWAIH30Bglmifu03OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;309&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: AWS 공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4Nll2/btsKhzZfkf4/k1UwYKwR6YKP1GbOW63Mi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4Nll2/btsKhzZfkf4/k1UwYKwR6YKP1GbOW63Mi1/img.png&quot; data-alt=&quot;출처: AWS 공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4Nll2/btsKhzZfkf4/k1UwYKwR6YKP1GbOW63Mi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4Nll2%2FbtsKhzZfkf4%2Fk1UwYKwR6YKP1GbOW63Mi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;709&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: AWS 공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어 어떤 사용자가 S3에 접근하고자 한다면, 아래 단계로 권한 유무를 판단한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;해당 사용자에게 S3 이용 정책이 부여되었는지 확인&lt;/li&gt;
&lt;li&gt;해당 사용자가 속한 그룹에 정책이 부여되었는지 확인&lt;/li&gt;
&lt;li&gt;해당 사용자에게 위임된 역할에 정책이 부여되었는지 확인&lt;/li&gt;
&lt;li&gt;위 3가지에 모두 해당되지 않으면 권한이 없는 것으로 판단&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;IAM 설정&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;사용자 계정 추가&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UxixJ/btsKhLZwPEP/uBUMJ9dYDNvGuj98QlMsy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UxixJ/btsKhLZwPEP/uBUMJ9dYDNvGuj98QlMsy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UxixJ/btsKhLZwPEP/uBUMJ9dYDNvGuj98QlMsy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUxixJ%2FbtsKhLZwPEP%2FuBUMJ9dYDNvGuj98QlMsy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;905&quot; height=&quot;441&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;루트 계정으로 접속한 뒤 IAM의 사용자 항목으로 진입하면 사용자를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG2ux7/btsKiVs7dZ5/v3xIrxN7LtCh13SNYyWXKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG2ux7/btsKiVs7dZ5/v3xIrxN7LtCh13SNYyWXKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG2ux7/btsKiVs7dZ5/v3xIrxN7LtCh13SNYyWXKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG2ux7%2FbtsKiVs7dZ5%2Fv3xIrxN7LtCh13SNYyWXKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;427&quot; height=&quot;396&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;691&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt2PBV/btsKjtiuKvp/bMtMiSUu2XXVS2M9Hgm5R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt2PBV/btsKjtiuKvp/bMtMiSUu2XXVS2M9Hgm5R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt2PBV/btsKjtiuKvp/bMtMiSUu2XXVS2M9Hgm5R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt2PBV%2FbtsKjtiuKvp%2FbMtMiSUu2XXVS2M9Hgm5R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;397&quot; height=&quot;499&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;691&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;781&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LyJo9/btsKiYi1NsH/sJcbPZZRVptvav4rKOD1IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LyJo9/btsKiYi1NsH/sJcbPZZRVptvav4rKOD1IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LyJo9/btsKiYi1NsH/sJcbPZZRVptvav4rKOD1IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLyJo9%2FbtsKiYi1NsH%2FsJcbPZZRVptvav4rKOD1IK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;649&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;781&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 세부 정보, 권한 설정(또는 그룹 설정)을 통해 사용자를 생성할 수 있다. 사용자 생성 뒤 해당 사용자에 대한 ID와 암호가 노출되는데, 이를 이메일을 통해 해당 사용자에게 전송하거나 저장해야 한다. 이 화면 이후에는 또 다시 볼 기회가 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정책 커스텀 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여러가지 기본 정책을 하나로 묶어 일종의 커스텀화를 할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvbZUe/btsKiU11JrS/wBkYq0oWhntF5ertgQqMQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvbZUe/btsKiU11JrS/wBkYq0oWhntF5ertgQqMQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvbZUe/btsKiU11JrS/wBkYq0oWhntF5ertgQqMQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvbZUe%2FbtsKiU11JrS%2FwBkYq0oWhntF5ertgQqMQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;778&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;IAM - 정책 항목으로 진입해 '정책 생성'을 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blTOKw/btsKh27PXw4/ploiG4chsu7iNEjqdLC7r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blTOKw/btsKh27PXw4/ploiG4chsu7iNEjqdLC7r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blTOKw/btsKh27PXw4/ploiG4chsu7iNEjqdLC7r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblTOKw%2FbtsKh27PXw4%2FploiG4chsu7iNEjqdLC7r1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;634&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 CloudFront 내에서 어떤 정책에 대해서 허용할 것인지 선택해 이 것 자체를 정책으로 생성할 수 있다. 또는 특정 서비스에 대한 모든 권한을 추가하는 정책을 새로 만들 수도 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;역할은 마치 '팀장'처럼 권한을 해당 '역할'자체에 두는 것이다. 해당 역할을 가지게 된 사용자는 그 역할에 대한 권한을 사용할 수 있다. 또한 이 역할은 다른 사용자에게 주거나 역할을 해제할 수도 있다. 역할을 사용하여 일반적으로 AWS 리소스에 액세스할 수 없는 사용자, 애플리케이션 또는 서비스에 액세스 권한을 위임할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>AWS IAM</category>
      <category>aws 권한</category>
      <category>aws 사용자</category>
      <category>aws 역할</category>
      <category>aws 정책</category>
      <category>IAM</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/258</guid>
      <comments>https://minding-deep-learning.tistory.com/258#entry258comment</comments>
      <pubDate>Fri, 25 Oct 2024 10:12:22 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] VPC</title>
      <link>https://minding-deep-learning.tistory.com/257</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;VPC&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;VPC(Virtual Private Cloud)는 사용자가 정의한 가상 네트워크로, AWS에서는 Amazon VPC라는 이름의 서비스로 제공되고 있다. 클라우드 망에서 가상의 네트워크망을 구축하는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;이를 통해 데이터 엔지니어는 안전한 네트워크 망에서 중요한 데이터가 오갈 수 있도록 설정할 수 있고, 내부 네트워크 디자인 경로를 디자인할 수 있어 데이터를 처리하는 과정을 모두 내부화할 수 있다. DB에 접근을 제어할 수 있을 뿐 아니라 공유할 때에도 VPC 연결 설정을 이용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;VPC의 기능&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브넷: VPC의 IP 주소 범위이다. 서브넷은 단일 가용 영역에 상주해야 하고, 서브넷 추가 후에 VPC에 AWS 리소스를 배포할 수 있다.&lt;/li&gt;
&lt;li&gt;IP 주소 지정: VPC와 서브넷에 IPv4 주소와 IPv6 주소를 할당할 수 있다.&lt;/li&gt;
&lt;li&gt;라우팅: 라우팅 테이블을 사용해 서브넷 또는 게이트웨이의 네트워크 트래픽이 전달되는 위치를 결정한다.&lt;/li&gt;
&lt;li&gt;게이트웨이: 게이트웨이는 VPC를 다른 네트워크에 연결하는 역할이다. (ex. 인터넷 게이트웨이를 사용해 VPC를 인터넷에 연결)&lt;/li&gt;
&lt;li&gt;엔드포인트: VPC 엔드포인트를 사용해 인터넷 게이트웨이 또는 NAT 장치를 사용하지 않고 AWS 서비스에 비공개로 연결함&lt;/li&gt;
&lt;li&gt;피어링 연결: VPC 피어링 연결을 사용해 두 VPC의 리소스 간 트래픽을 라우팅한다.&lt;/li&gt;
&lt;li&gt;트래픽 미러링: 네트워크 인터페이스에서 네트워크 트래픽을 복사하고 심층 패킷 검사를 위해 보안 및 모니터링 어플라이언스로 전송한다.&lt;/li&gt;
&lt;li&gt;Transit Gateway: 중앙 허브 역할을 하는 전송 게이트웨이를 사용하여 VPC, VPN 연결 및 AWS Direct Connect 연결 간에 트래픽을 라우팅한다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;VPC 흐름로그: 흐름 로그는 VPC의 네트워크 인터페이스로 들어오고 나가는 IP 트래픽에 대한 정보를 캡처한다.&lt;/li&gt;
&lt;li&gt;VPN 연결: AWS Virtual Private Network(AWS VPN)을 사용하여 온프레미스 네트워크에 VPC를 연결한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CIDR&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CIDR은 클래스 없는 도메인 간 라우팅 기법이다. 네트워크를 설계하고 IP 주소 범위를 정의하는데 중요한 역할을 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enFr8x/btsKi59uqs8/LlpWvqk3HbN3RcRX2Caga0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enFr8x/btsKi59uqs8/LlpWvqk3HbN3RcRX2Caga0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enFr8x/btsKi59uqs8/LlpWvqk3HbN3RcRX2Caga0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenFr8x%2FbtsKi59uqs8%2FLlpWvqk3HbN3RcRX2Caga0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;464&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;IP는 기본적으로 2진수 및 8비트로 나타낸다. 예를들어 174는 10101110으로 나타내는 것처럼 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일반적으로 IP주소는 network 영역과 host 영역으로 나뉘는데, 이는 아래와 같은 기준으로 나뉜다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RVKeG/btsKhakRSo8/2ltmAS8RDPTQxeLTwllFA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RVKeG/btsKhakRSo8/2ltmAS8RDPTQxeLTwllFA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RVKeG/btsKhakRSo8/2ltmAS8RDPTQxeLTwllFA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRVKeG%2FbtsKhakRSo8%2F2ltmAS8RDPTQxeLTwllFA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1099&quot; height=&quot;616&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;IP주소 '/' 뒤에 어떤 숫자가 오느냐에 따라 host 영역의 범위가 결정된다. 예를 들어 24가 오면 앞의 24자리가 network 영역이라는 뜻이기 때문에 마지막 8자리가 host 영역이 된다. host 영역은 사용자가 직접 할당할 수 있는 IP 대역이다. 많은 대역을 사용하고 싶다면 network 영역을 줄여 더 많은 IP 대역을 할당할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;VPC 생성&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;생성 단계&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. IP 주소 범위 선택&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;2. 가용 영역(AZ) 서브넷 설정&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;3. 인터넷으로 향하는 경로(route) 만들기&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;4. VPC 로(부터)의 트래픽 설정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpzh5E/btsKi5n85OU/H6lJhexOvhWf0wFyUx7VWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpzh5E/btsKi5n85OU/H6lJhexOvhWf0wFyUx7VWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpzh5E/btsKi5n85OU/H6lJhexOvhWf0wFyUx7VWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcpzh5E%2FbtsKi5n85OU%2FH6lJhexOvhWf0wFyUx7VWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;421&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;421&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VPC에서 172.31.0.0/16으로 IP 범위를 설정하고, 2개의 서브넷을 만든 모습이다. 각 서브넷의 host영역을 결정하는 '/' 뒤의 숫자는 VPC의 IP 범위를 설정하는 숫자보다 작을 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;VPC 생성 시 전체적인 구성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1225&quot; data-origin-height=&quot;723&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BcW7B/btsKiqGTn9R/D8OBw8uKE8k2H4a1kUA6xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BcW7B/btsKiqGTn9R/D8OBw8uKE8k2H4a1kUA6xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BcW7B/btsKiqGTn9R/D8OBw8uKE8k2H4a1kUA6xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBcW7B%2FbtsKiqGTn9R%2FD8OBw8uKE8k2H4a1kUA6xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1225&quot; height=&quot;723&quot; data-origin-width=&quot;1225&quot; data-origin-height=&quot;723&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 다이어그램은 AWS VPC 내에서 퍼블릭 서브넷과 프라이빗 서브넷을 구분해 네트워크 리소스를 배치한 전형적인 아키텍처를 보여주고 있다. 인터넷 게이트웨이, NAT 게이트웨이, 로드 밸런서, Bastion 호스트를 통해 외부와의 통신을 관리한다. 중요한 데이터와 시스템은 프라이빗 서브넷에 배치해 보안을 강화했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;VPC CIDR&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VPC의 CIDR 블록은 10.0.0.0/16으로 설정되어 있습니다. 이는 이 VPC에서 사용할 수 있는 전체 IP 주소 범위를 나타냅니다. 이 범위 내에서 퍼블릭 서브넷과 프라이빗 서브넷이 나뉘어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;서브넷(Subnet)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;퍼블릭 서브넷 (Public Subnet)&lt;/b&gt;: 10.0.1.0/24, 10.0.2.0/24 범위로 설정된 서브넷입니다. 퍼블릭 서브넷은 외부 인터넷과 직접 통신이 가능하며, 보통 인터넷 게이트웨이를 통해 외부와 연결됩니다. 여기에는 &lt;b&gt;NAT 게이트웨이&lt;/b&gt;와 &lt;b&gt;Bastion Host&lt;/b&gt;가 배치되어 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프라이빗 서브넷 (Private Subnet)&lt;/b&gt;: 10.0.3.0/24, 10.0.4.0/24 등으로 설정된 서브넷입니다. 프라이빗 서브넷은 외부 인터넷과 직접 통신할 수 없으며, 외부와의 통신은 NAT 게이트웨이를 통해 이루어집니다. 이 서브넷에는 &lt;b&gt;EC2 인스턴스&lt;/b&gt;들이 배치되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;b&gt;NAT 게이트웨이&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NAT 게이트웨이는 퍼블릭 서브넷에 위치하고 있으며, 프라이빗 서브넷에 있는 리소스들이 외부 인터넷으로 나가는 트래픽을 처리할 수 있게 해줍니다. 이때 외부에서 프라이빗 서브넷의 리소스로는 직접 접근할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. &lt;b&gt;인터넷 게이트웨이 (Internet Gateway)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷 게이트웨이는 VPC가 인터넷과 통신할 수 있도록 해주는 구성 요소입니다. 퍼블릭 서브넷에 배치된 리소스들이 외부 인터넷과 통신할 수 있게 연결해 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. &lt;b&gt;로드 밸런서 (ELB-ALB)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ELB(Elastic Load Balancer)&lt;/b&gt; 또는 **ALB(Application Load Balancer)**는 여러 EC2 인스턴스에 트래픽을 분산시키는 역할을 합니다. 이 로드 밸런서는 퍼블릭 서브넷에 위치하며, 인터넷에서 들어오는 트래픽을 받아서 적절한 EC2 인스턴스로 라우팅합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. &lt;b&gt;EC2 인스턴스&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프라이빗 서브넷&lt;/b&gt;에 배치된 EC2 인스턴스들은 외부 인터넷과 직접 통신하지 않으며, 외부에서 들어오는 요청은 로드 밸런서를 통해 전달받습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Bastion Host&lt;/b&gt;: 퍼블릭 서브넷에 위치한 이 인스턴스는 외부에서 SSH 또는 RDP를 통해 접속할 수 있으며, 이를 통해 프라이빗 서브넷에 있는 인스턴스에 간접적으로 접근할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. &lt;b&gt;S3 버킷 및 CloudFront&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;S3 버킷은 AWS의 객체 스토리지 서비스로, 외부 개발자나 사용자가 저장된 데이터를 접근할 수 있습니다. &lt;b&gt;CloudFront&lt;/b&gt;는 AWS의 콘텐츠 전송 네트워크(CDN)로, S3 버킷의 콘텐츠를 전 세계 사용자에게 빠르고 효율적으로 전달하는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. &lt;b&gt;가용 영역 (Availability Zone)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다이어그램에서 보여지는 두 개의 가용 영역(AZ 2a, AZ 2c)은 AWS 리전 내에서 서로 다른 물리적 데이터 센터입니다. 각각의 가용 영역 내에 퍼블릭 및 프라이빗 서브넷이 존재하며, 가용성을 높이기 위해 리소스가 여러 가용 영역에 걸쳐 분산되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;AWS에서 VPC 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BbZhC/btsKh27iEga/gGDBgzNKaH1OwtF4y630R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BbZhC/btsKh27iEga/gGDBgzNKaH1OwtF4y630R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BbZhC/btsKh27iEga/gGDBgzNKaH1OwtF4y630R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBbZhC%2FbtsKh27iEga%2FgGDBgzNKaH1OwtF4y630R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;840&quot; height=&quot;766&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VPC 서비스에 진입해 이름과 CIDR을 지정해주면 기본적인 VPC를 생성할 수 있다. 만약 생성할 리소스에서 'VPC 등'을 선택한다면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FICag/btsKheHoRbs/mBb5qgIp00fPSMImD6eMJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FICag/btsKheHoRbs/mBb5qgIp00fPSMImD6eMJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FICag/btsKheHoRbs/mBb5qgIp00fPSMImD6eMJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFICag%2FbtsKheHoRbs%2FmBb5qgIp00fPSMImD6eMJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1331&quot; height=&quot;428&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;661&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PJHIw/btsKixyZFjb/PbGRLWkVHgmbDsHRcHQSd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PJHIw/btsKixyZFjb/PbGRLWkVHgmbDsHRcHQSd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PJHIw/btsKixyZFjb/PbGRLWkVHgmbDsHRcHQSd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPJHIw%2FbtsKixyZFjb%2FPbGRLWkVHgmbDsHRcHQSd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;661&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;661&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이와 같이 미리보기 창이 나타나며 가용 영역부터 서브넷과 라우팅 테이블 등을 한 번에 설정할 수 있다. 이 글에서는 'VPC만'을 선택해 진행해보려고 한다. (실제로 프로젝트를 진행할 때는 위 방법을 사용할 예정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1605&quot; data-origin-height=&quot;661&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnAk5X/btsKhvvkYZL/mvQkuKeROKkB7ZtOQtgufk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnAk5X/btsKhvvkYZL/mvQkuKeROKkB7ZtOQtgufk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnAk5X/btsKhvvkYZL/mvQkuKeROKkB7ZtOQtgufk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnAk5X%2FbtsKhvvkYZL%2FmvQkuKeROKkB7ZtOQtgufk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1605&quot; height=&quot;661&quot; data-origin-width=&quot;1605&quot; data-origin-height=&quot;661&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VPC를 설정했고, 이제 서브넷을 만들어주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R55vx/btsKiwtibIn/RxzwwkPUtjCM96zz1Acbr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R55vx/btsKiwtibIn/RxzwwkPUtjCM96zz1Acbr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R55vx/btsKiwtibIn/RxzwwkPUtjCM96zz1Acbr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR55vx%2FbtsKiwtibIn%2FRxzwwkPUtjCM96zz1Acbr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;847&quot; height=&quot;510&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이런 식으로 vpc를 선택해 서브넷을 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>aws cidr</category>
      <category>AWS VPC</category>
      <category>AZ</category>
      <category>CIDR</category>
      <category>Gateway</category>
      <category>subnet</category>
      <category>VPC</category>
      <category>가용 영역</category>
      <category>서브넷</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/257</guid>
      <comments>https://minding-deep-learning.tistory.com/257#entry257comment</comments>
      <pubDate>Thu, 24 Oct 2024 17:31:18 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] ELB (Elastic Load Balancing)</title>
      <link>https://minding-deep-learning.tistory.com/256</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ELB&amp;nbsp;(Elastic&amp;nbsp;Load&amp;nbsp;Balancing)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ELB (Elastic Load Balancing)는 애플리케이션을 지원하는 리소스 풀 전체에 네트워크 트래픽을 균등하게 배포하는 방법이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;로드 밸런서 (Load Balancer)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;로드 밸랜서는 서버에 가해지는 부하(로드)를 분산(밸런싱)해주는 장치 또는 기술을 통칭한다. 크게 L4 로드밸런서와 L7 로드밸런서로 나뉘는데, 다음과 같은 차이가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4wxbY/btsKinJZVHI/GPHumDNVS1ijXkkCGqjcp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4wxbY/btsKinJZVHI/GPHumDNVS1ijXkkCGqjcp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4wxbY/btsKinJZVHI/GPHumDNVS1ijXkkCGqjcp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4wxbY%2FbtsKinJZVHI%2FGPHumDNVS1ijXkkCGqjcp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1101&quot; height=&quot;606&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ELB 대상그룹&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;로드 밸런서를 생성 후 어디에 연결되는 ELB인지 대상그룹을 등록해 주어야 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ELB 설정 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ELB는 다른 서비스와 달리 EC2에 포함된 서비스로, EC2 대시보드의 좌측 메뉴 바에서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;186&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzqsLu/btsKiv17L10/TaWbVbowV46Vbh9MowkBDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzqsLu/btsKiv17L10/TaWbVbowV46Vbh9MowkBDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzqsLu/btsKiv17L10/TaWbVbowV46Vbh9MowkBDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzqsLu%2FbtsKiv17L10%2FTaWbVbowV46Vbh9MowkBDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;186&quot; height=&quot;132&quot; data-origin-width=&quot;186&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'로드 밸런서' 항목에 진인해 로드 밸런서 생성을 누르면 다음과 같이 유형을 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rFyoj/btsKhzYBGfn/b2NYQ2SGRg0wntEddTCrd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rFyoj/btsKhzYBGfn/b2NYQ2SGRg0wntEddTCrd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rFyoj/btsKhzYBGfn/b2NYQ2SGRg0wntEddTCrd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrFyoj%2FbtsKhzYBGfn%2Fb2NYQ2SGRg0wntEddTCrd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;742&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;지금 사용하는 App의 유형이나 설정에 따라 로드 밸런서를 선택할 수 있다. 여기서는 Application Load Balancer의 예시를 보려고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TvXv0/btsKi5O8r9S/auO4OZjfSwf02CqFY9njDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TvXv0/btsKi5O8r9S/auO4OZjfSwf02CqFY9njDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TvXv0/btsKi5O8r9S/auO4OZjfSwf02CqFY9njDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTvXv0%2FbtsKi5O8r9S%2FauO4OZjfSwf02CqFY9njDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;496&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egkIjx/btsKiYQbg1D/mflEkMsyttZQ5JeIfKlnfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egkIjx/btsKiYQbg1D/mflEkMsyttZQ5JeIfKlnfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egkIjx/btsKiYQbg1D/mflEkMsyttZQ5JeIfKlnfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegkIjx%2FbtsKiYQbg1D%2FmflEkMsyttZQ5JeIfKlnfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;767&quot; height=&quot;316&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VPC와 리스너에 따른 프로토콜 설정 등을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;95&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSckDM/btsKiyxHljL/XAacy0brbyLyOIiUA2YEv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSckDM/btsKiyxHljL/XAacy0brbyLyOIiUA2YEv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSckDM/btsKiyxHljL/XAacy0brbyLyOIiUA2YEv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSckDM%2FbtsKiyxHljL%2FXAacy0brbyLyOIiUA2YEv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;95&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;95&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ELB가 성공적으로 생성된 것을 확인할 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>AWS ELB</category>
      <category>Elastic Load Balancer</category>
      <category>elb</category>
      <category>l4 load balancer</category>
      <category>l7 load balancer</category>
      <category>Load Balancer</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/256</guid>
      <comments>https://minding-deep-learning.tistory.com/256#entry256comment</comments>
      <pubDate>Thu, 24 Oct 2024 16:43:08 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] CloudFront</title>
      <link>https://minding-deep-learning.tistory.com/255</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CloudFront&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CloudFront는 AWS의 콘텐츠 전송 네트워크(CDN) 서비스이며, 굳이 메인 서버까지 가지 않고 각 지역에 퍼져있는 가장 가까운 서버에서 콘텐츠를 다운로드 받을 수 있다. 서버가 분산되어 성능 및 보안 측면에서 유리하다. 주로 SW나 게임의 패치에 CDN이 많이 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 자주 반복되는 이미지, 콘텐츠 등을 캐싱(저장)하여 서버가 이를 반복적으로 처리하지 않고 빠르게 사용자에게 전달 가능하다는 장점도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대기시간 감소&lt;/li&gt;
&lt;li&gt;보안 향상&lt;/li&gt;
&lt;li&gt;비용 절감&lt;/li&gt;
&lt;li&gt;사용자 정의 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CloudFront 배포 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNWFsW/btsKiMh4oyq/iKgZ0OSTjAMjbWhsfWNQjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNWFsW/btsKiMh4oyq/iKgZ0OSTjAMjbWhsfWNQjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNWFsW/btsKiMh4oyq/iKgZ0OSTjAMjbWhsfWNQjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNWFsW%2FbtsKiMh4oyq%2FiKgZ0OSTjAMjbWhsfWNQjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;842&quot; height=&quot;772&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;미리 등록된 도메인(CDN 서비스가 가능한)을 등록해 CloudFront 배포를 생성할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRKKzx/btsKhcCMqEz/qu9lBH6VdORvZdfE8w0iw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRKKzx/btsKhcCMqEz/qu9lBH6VdORvZdfE8w0iw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRKKzx/btsKhcCMqEz/qu9lBH6VdORvZdfE8w0iw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRKKzx%2FbtsKhcCMqEz%2Fqu9lBH6VdORvZdfE8w0iw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;624&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;HTTP, HTTPS등 프로토콜부터 HTTP 요청 방식에 제약을 둘 수도 있다. 이외에 특정 IP를 차단하는 등의 설정을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;*주의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CDN 서비스는 '글로벌'서비스이기 때문에, 특정 리전에서 인증서를 받았다면 해당 배포 설정 창에서 선택이 되지 않는다. 인증서를 받을 때 글로벌 기준으로 받아야 CDN 서비스를 이용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>AWS CDN</category>
      <category>AWS CloudFront</category>
      <category>CDN</category>
      <category>CDN 개념</category>
      <category>HTTP</category>
      <category>https</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/255</guid>
      <comments>https://minding-deep-learning.tistory.com/255#entry255comment</comments>
      <pubDate>Thu, 24 Oct 2024 16:00:20 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Certification Manager</title>
      <link>https://minding-deep-learning.tistory.com/254</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Certification&amp;nbsp;Manager&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS Certification Manager(ACM)는 AWS 서비스 및 연결된 내부 리소스에 대한 공인 및 사설 SSL/TLS 인증서를 프로비저닝하고 관리/배포할 수 있는 서비스다. ACM을 이용하면 인증서를 구매, 업로드 및 갱신할 때 드는 시간 및 프로세스 소모를 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ACM의 특징&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ACM 통합 서비스를 위한 무료 퍼블릭 인증서&lt;/li&gt;
&lt;li&gt;관리형 인증서 갱신&lt;/li&gt;
&lt;li&gt;손쉽게 인증서 받을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SSL 인증서&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;https에 사용되는 SSL 인증서는 공개 키(publice key)와 개인 키(private key)라는 쌍을 가지고 있으며, 이 키들이 함께 작용해 암호화된 연결을 수립한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 인증서에는 주체(subject)를 포함하고 이는 인증서/웹사이트 소유자의 ID를 뜻한다. 인증서를 얻기 위해서는 인증서 서명 요청(CSR)을 생성해야 하고, 이 과정에서 개인 키와 공개 키가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ACM 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxkVL/btsKhwHy08M/AvTz46CGOBcPRkCuoEfa51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxkVL/btsKhwHy08M/AvTz46CGOBcPRkCuoEfa51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxkVL/btsKhwHy08M/AvTz46CGOBcPRkCuoEfa51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxkVL%2FbtsKhwHy08M%2FAvTz46CGOBcPRkCuoEfa51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;855&quot; height=&quot;515&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS의 ACM 항목에 접속해 인증서를 요청(구매)하거나 외부에서 얻은 인증서를 AWS로 가져올 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시) 퍼블릭 인증서 요청&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WIxKO/btsKhf0kg1x/NfUOrKfE3GKpymOnKYZRv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WIxKO/btsKhf0kg1x/NfUOrKfE3GKpymOnKYZRv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WIxKO/btsKhf0kg1x/NfUOrKfE3GKpymOnKYZRv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWIxKO%2FbtsKhf0kg1x%2FNfUOrKfE3GKpymOnKYZRv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;837&quot; height=&quot;775&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;퍼블릭 인증서는 무료로 받을 수 있다. 하지만 그 전에 Route53(&lt;a href=&quot;https://minding-deep-learning.tistory.com/253&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;) 등을 통해서 도메인을 미리 등록해놓아야 한다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>ACM</category>
      <category>AWS</category>
      <category>AWS Certification Manager</category>
      <category>aws 퍼블릭 인증서</category>
      <category>도메인</category>
      <category>퍼블릭 인증서</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/254</guid>
      <comments>https://minding-deep-learning.tistory.com/254#entry254comment</comments>
      <pubDate>Thu, 24 Oct 2024 15:44:51 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Route53</title>
      <link>https://minding-deep-learning.tistory.com/253</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Route53&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Route53은 AWS의 DNS(도메인 이름 시스템) 웹 서비스로, 가용성과 확장성이 뛰어나다. 도메인 등록, DNS 라우팅, 상태 확인을 조합해 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Route53은 publice host zone과 private host zone에서 사용할 수 있는 도메인을 나눠서 제공하고 있다. Route53의 기능은 DNS(네임서버) + 모니터링 + L4 + GSLB의 조합이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VvaaC/btsKiGvlwCI/Fnb5sCkUOzEzo7kimgaLS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VvaaC/btsKiGvlwCI/Fnb5sCkUOzEzo7kimgaLS1/img.png&quot; data-alt=&quot;출처: AWS 공식문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VvaaC/btsKiGvlwCI/Fnb5sCkUOzEzo7kimgaLS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVvaaC%2FbtsKiGvlwCI%2FFnb5sCkUOzEzo7kimgaLS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;445&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: AWS 공식문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DNS를 찾아가는 흐름은 위 그림과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자&lt;/li&gt;
&lt;li&gt;특정 도메인 요청&lt;/li&gt;
&lt;li&gt;DNS root name server(글로벌)에서 해당 도메인 탐색&lt;/li&gt;
&lt;li&gt;Name server for .com TLD (지역/리전)에서 해당 도메인 탐색&lt;/li&gt;
&lt;li&gt;Route 53 name server(로컬)에서 해당 도메인 검색&lt;/li&gt;
&lt;li&gt;해당 도메인에 등록된 IP 주소 반환&lt;/li&gt;
&lt;li&gt;해당 IP 주소를 사용자에게 전달&lt;/li&gt;
&lt;li&gt;해당 IP 주소의 웹 서버에 요청을 보냄&lt;/li&gt;
&lt;li&gt;해당 도메인에 해당하는 웹 페이지를 응답&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;도메인 등록&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rR7ar/btsKiDyEs2O/kZ40yfc7k0kPOKTNz5UwWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rR7ar/btsKiDyEs2O/kZ40yfc7k0kPOKTNz5UwWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rR7ar/btsKiDyEs2O/kZ40yfc7k0kPOKTNz5UwWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrR7ar%2FbtsKiDyEs2O%2FkZ40yfc7k0kPOKTNz5UwWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;877&quot; height=&quot;731&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;도메인 등록은 AWS Route53 대시보드 페이지에서 '도메인 등록' 버튼 또는 도메인 이름을 입력한 뒤 '확인'버튼을 누르면 된다. 도메인은 가비아, 카페24 등에서 특정 도메인을 구입하거나, AWS에서도 구입할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eh3ok2/btsKioIKptM/27s8TkGoloXcBoUZ1dQtBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eh3ok2/btsKioIKptM/27s8TkGoloXcBoUZ1dQtBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eh3ok2/btsKioIKptM/27s8TkGoloXcBoUZ1dQtBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feh3ok2%2FbtsKioIKptM%2F27s8TkGoloXcBoUZ1dQtBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;790&quot; height=&quot;754&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일단 minding.co.kr은 안되나보다. 유사한 다른 도메인을 추천해주기도 한다. 요금은 위 이미지에 나와있는 것과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 가비아나 카페 24에서 도메인을 구매했다면,&amp;nbsp;DNS 관리에서 호스팅 영역 생성을 통해 해당 도메인과 연결이 필요하다. 해당 과정은 AWS 공식 문서 (&lt;a href=&quot;https://aws.amazon.com/ko/getting-started/hands-on/get-a-domain/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;링크&lt;/a&gt;)에 자세히 나와있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/253</guid>
      <comments>https://minding-deep-learning.tistory.com/253#entry253comment</comments>
      <pubDate>Thu, 24 Oct 2024 15:27:53 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] RDS, DocumentDB, DynamoDB</title>
      <link>https://minding-deep-learning.tistory.com/252</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RDS&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS에서 지원하는 RDB로, 기본적인 DB 형태를 가지고 있다. DB의 인스턴스는 클라우드에서 실행하는 격리된 환경이며, 여러 사용자가 만든 DB가 포함될 수 있다. 액세스할 때는 독립 실행형 DB에 엑세스할 때 사용하는 도구 및 애플리케이션을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS 명령줄 도구, Amazon RDS API 작업 또는 AWS Management Console을 사용해 간단히 DB 인스턴스 제작 및 수정 가능&lt;/li&gt;
&lt;li&gt;직접 시스템 로그인 불가능&lt;/li&gt;
&lt;li&gt;서버리스 시스템이 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;611&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rBLh8/btsKgrT5bLj/62sqLVyGtB8co61dQiCC5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rBLh8/btsKgrT5bLj/62sqLVyGtB8co61dQiCC5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rBLh8/btsKgrT5bLj/62sqLVyGtB8co61dQiCC5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrBLh8%2FbtsKgrT5bLj%2F62sqLVyGtB8co61dQiCC5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1003&quot; height=&quot;611&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;611&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;RDS는 main이 되는 Primary와 데이터 손실 방지용 StandBy 2개로 구성이 되어 있으며, 그 내부에는 인스턴스(서버)와 실질적으로 데이터를 저장하는 EBS로 이루어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;RDS 이용해보기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;849&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpmnKL/btsKgtK62bV/BV1v1jwesGuHCviQMKwAV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpmnKL/btsKgtK62bV/BV1v1jwesGuHCviQMKwAV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpmnKL/btsKgtK62bV/BV1v1jwesGuHCviQMKwAV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpmnKL%2FbtsKgtK62bV%2FBV1v1jwesGuHCviQMKwAV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;928&quot; height=&quot;849&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;849&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS에 로그인 후 RDS를 검색해 대시보드로 진입한다. 이후 메뉴바의 '데이터베이스'를 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdkNTe/btsKgM4Jx9u/wQMV9N3iE4TWKrRHY3kst0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdkNTe/btsKgM4Jx9u/wQMV9N3iE4TWKrRHY3kst0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdkNTe/btsKgM4Jx9u/wQMV9N3iE4TWKrRHY3kst0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdkNTe%2FbtsKgM4Jx9u%2FwQMV9N3iE4TWKrRHY3kst0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;236&quot; height=&quot;140&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;데이터베이스 생성 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BVSgP/btsKh8SL4py/USlgKjGXmSlL2bmTf6ZmoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BVSgP/btsKh8SL4py/USlgKjGXmSlL2bmTf6ZmoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BVSgP/btsKh8SL4py/USlgKjGXmSlL2bmTf6ZmoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBVSgP%2FbtsKh8SL4py%2FUSlgKjGXmSlL2bmTf6ZmoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;768&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 DB 엔진 옵션부터 버전, 템플릿, 스토리지 등을 선택할 수 있다. 옵션 중 MSSQL과 ORACLE의 경우 별도의 라이센스 비용이 발생할 수 있다. 프리 티어로 이용할 경우 MySQL을 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;거의 모든 설정을 기본값으로 생성했으나, '퍼블릭 액세스'에 대해서만 '예'로 지정했다. 외부에서도 연결이 잘 되는지 확인하기 위해서다. 이 항목을 '아니오'로 선택하면 퍼블릭 IP 주소를 생성하지 않아 VPC 내부의 EC2에서만 연결 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;* VPC 설정사항&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MySQL 또는 Aurora 선택 후 DB 연결을 하기 위해서는 VPC에서 인바운드 규칙을 추가로 설정해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DB에 연결한 보안 그룹으로 이동해 MySQL 및 3306 포트를 확인한다. (DB 상세화면에서 VPC 보안 그룹 링크를 눌러 접속해도 된다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1584&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PoDlo/btsKhqmh2hY/8lEAViRZKNzMutKXDkUEQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PoDlo/btsKhqmh2hY/8lEAViRZKNzMutKXDkUEQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PoDlo/btsKhqmh2hY/8lEAViRZKNzMutKXDkUEQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPoDlo%2FbtsKhqmh2hY%2F8lEAViRZKNzMutKXDkUEQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1584&quot; height=&quot;261&quot; data-origin-width=&quot;1584&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위처럼 MYSQL/Aurora, TCP, 3306에 해당하는 규칙이 없다면 우측 상단 '인바운드 규칙 편집'을 눌러 규칙을 추가해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PdmIS/btsKhBuluUa/HWh4Y2JCkmOHssftNKLiR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PdmIS/btsKhBuluUa/HWh4Y2JCkmOHssftNKLiR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PdmIS/btsKhBuluUa/HWh4Y2JCkmOHssftNKLiR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPdmIS%2FbtsKhBuluUa%2FHWh4Y2JCkmOHssftNKLiR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1798&quot; height=&quot;384&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;유형을 선택하면 프로토콜과 포트 범위가 자동으로 지정되며, 소스에는 외부에서 쉽게 연결할 수 있도록 0.0.0.0/0을 우선 선택한다. (실제 서비스에서는 IP를 한정시킨다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Td2wQ/btsKhZn5F3l/sUkGtgwB9YFmAvF4rTUI3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Td2wQ/btsKhZn5F3l/sUkGtgwB9YFmAvF4rTUI3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Td2wQ/btsKhZn5F3l/sUkGtgwB9YFmAvF4rTUI3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTd2wQ%2FbtsKhZn5F3l%2FsUkGtgwB9YFmAvF4rTUI3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1581&quot; height=&quot;729&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;생성이 완료되었다면 좌측하단에 엔드포인트 주소가 노출될 것이다. 이 주소를 통해 DB에 접속할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Workbench로 RDS 연결하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MySQL DB를 연결하는 대표적인 툴인 Workbench로 RDS를 연결해보려고 한다. 설치 링크는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/downloads/workbench/&quot;&gt;https://dev.mysql.com/downloads/workbench/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729733399138&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MySQL :: Download MySQL Workbench&quot; data-og-description=&quot;&quot; data-og-host=&quot;dev.mysql.com&quot; data-og-source-url=&quot;https://dev.mysql.com/downloads/workbench/&quot; data-og-url=&quot;https://dev.mysql.com/downloads/workbench/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHkgEq/hyXlQ624Rd/fkZFNM8iOjvLFFOYklqmI0/img.png?width=700&amp;amp;height=260&amp;amp;face=0_0_700_260&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/downloads/workbench/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.mysql.com/downloads/workbench/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHkgEq/hyXlQ624Rd/fkZFNM8iOjvLFFOYklqmI0/img.png?width=700&amp;amp;height=260&amp;amp;face=0_0_700_260');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MySQL :: Download MySQL Workbench&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev.mysql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wMYs2/btsKhLKm9kD/D2sZlquYbdWgCQTYXYKtjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wMYs2/btsKhLKm9kD/D2sZlquYbdWgCQTYXYKtjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wMYs2/btsKhLKm9kD/D2sZlquYbdWgCQTYXYKtjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwMYs2%2FbtsKhLKm9kD%2FD2sZlquYbdWgCQTYXYKtjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1159&quot; height=&quot;778&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Recommended Download에 속지 말고 Other Downloads 아래에 있는 설치 파일을 다운로드 받아야 한다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;893&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9xarf/btsKhdN9Ked/uOlsZXU0qKJb9ObSMdKWyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9xarf/btsKhdN9Ked/uOlsZXU0qKJb9ObSMdKWyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9xarf/btsKhdN9Ked/uOlsZXU0qKJb9ObSMdKWyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9xarf%2FbtsKhdN9Ked%2FuOlsZXU0qKJb9ObSMdKWyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1186&quot; height=&quot;893&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;893&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MySQL Connections 오른쪽에 있는 '+' 버튼을 눌러 연결을 시도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rb0le/btsKgfe9LG4/eZIbk1mEYD1NJqPPyK2um0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rb0le/btsKgfe9LG4/eZIbk1mEYD1NJqPPyK2um0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rb0le/btsKgfe9LG4/eZIbk1mEYD1NJqPPyK2um0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frb0le%2FbtsKgfe9LG4%2FeZIbk1mEYD1NJqPPyK2um0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;493&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 다음 파라미터를 채워주면 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hostname: RDS 상세 화면에서 확인할 수 있는 엔드포인트 주소&lt;/li&gt;
&lt;li&gt;Username, Password: 위에서 DB 생성시 설정한 ID/PW, PW은 'Store in Vault' 눌러 입력&lt;/li&gt;
&lt;li&gt;Connection name: 원하는대로 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2OX9v/btsKhhp9dbz/FvvnEP0OWlyfklCGVXfpAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2OX9v/btsKhhp9dbz/FvvnEP0OWlyfklCGVXfpAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2OX9v/btsKhhp9dbz/FvvnEP0OWlyfklCGVXfpAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2OX9v%2FbtsKhhp9dbz%2FFvvnEP0OWlyfklCGVXfpAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;273&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그런데, 필자는 위와 같이 원인을 알 수 없는 에러가 자꾸만 노출돼 그냥 DBeaver를 사용했다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DBeaver에 대해서 알아보려면, &lt;a href=&quot;https://minding-deep-learning.tistory.com/205&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Postgres 컨테이너 추가해 DB 접속하기&lt;/a&gt;를, DBeaver에 AWS RDS를 연결하는 방법은 &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://velog.io/@shawnhansh/AWS-RDS-DBeaver에-연결하기&quot;&gt;https://velog.io/@shawnhansh/AWS-RDS-DBeaver에-연결하기&lt;/a&gt;를 읽어보길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스냅샷&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DB를 백업하는 용도라고 생각하면 된다. 해당 시점의 상태를 기억해 그 시점으로 되돌릴 수 있다. 기본적으로 자동 생성되지만 수동으로 스냅샷을 생성할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brMdRd/btsKhmRJCUr/lwliFNbfWZC4NbNX3Qm5Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brMdRd/btsKhmRJCUr/lwliFNbfWZC4NbNX3Qm5Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brMdRd/btsKhmRJCUr/lwliFNbfWZC4NbNX3Qm5Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrMdRd%2FbtsKhmRJCUr%2FlwliFNbfWZC4NbNX3Qm5Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;595&quot; height=&quot;473&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DocumentDB&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DocumentDB는 MongoDB API를 사용한 문서 전용 데이터베이스이다. 저장 형식은 JSON으로 이뤄져 있으며, AWS에는 스토리지 및 컴퓨팅이 분리되어 각각 독립적으로 조정된다는 점이 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;NoSQL DB이므로 RDB에 비해 읽기 요청 처리가 굉장히 빠르며 인덱싱이 유연하다는 것도 장점이다. 지연 시간이 짧은 읽기 전용 복제복을 몇 분 내 최대 15개까지 추가한다. AWS DocumentDB는 99.99%의 가용성을 위해 설계되었고 6개의 데이터 복사본을 3개의 AWS 가용 영역(AZ)에 복제한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK0NCU/btsKisxszo2/7VU5lmKjm3hwyAev5HLWYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK0NCU/btsKisxszo2/7VU5lmKjm3hwyAev5HLWYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK0NCU/btsKisxszo2/7VU5lmKjm3hwyAev5HLWYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK0NCU%2FbtsKisxszo2%2F7VU5lmKjm3hwyAev5HLWYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;911&quot; height=&quot;682&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS에서 DocumentDB를 만드는 법은 간단하다. 클러스터를 생성한 뒤, 그 안에 데이터베이스를 생성하는 방식이다. 하지만 여기서 RDB와 다른 점이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OCIeL/btsKiD6mTU9/1uB6DWAaRHEav8hiAW4Lvk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OCIeL/btsKiD6mTU9/1uB6DWAaRHEav8hiAW4Lvk/img.jpg&quot; data-alt=&quot;출처: Blockchain Simplified&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OCIeL/btsKiD6mTU9/1uB6DWAaRHEav8hiAW4Lvk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOCIeL%2FbtsKiD6mTU9%2F1uB6DWAaRHEav8hiAW4Lvk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1112&quot; height=&quot;695&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;695&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Blockchain Simplified&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MongoDB 기반으로 만들어진 '문서형 DB'이기 때문에, RDB에서 Table, row, column이라고 불렸던 것들이 Collection, document, field라고 불린다. 물론 생긴 모습도 다르다. 하나의 document는 하나의 json이라고 볼 수 있고, json 내 key에 해당하는 것들이 RDB의 column에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;AWS에서 생성한 DocumentDB는 MongoDB에 연결해서도 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DynamoDB&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DynamoDB는 완전관리형 Key-Value 기반 NoSQL DB 서비스로, Auto-Scailing을 통해 용량을 따로 설정하지 않아도 되는 특징이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;유휴 시 암호화를 제공해 중요 데이터 보호 및 운영 부담/복잡성을 제거하고, 원하는 양의 데이터를 저장/검색/처리할 수 있는 테이블을 만들 수 있다. 테이블 생성 시 스키마를 따로 생성하지 않아도 된다는 장점도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러나 특정 리전에서만 사용 가능하며 다소 비용이 비싸다는 단점이 있다. 무료로 제공되는 스토리지가 없기도 하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJCxF4/btsKiwT0GMd/vSDSxeDLkiczWZCjOpLyS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJCxF4/btsKiwT0GMd/vSDSxeDLkiczWZCjOpLyS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJCxF4/btsKiwT0GMd/vSDSxeDLkiczWZCjOpLyS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJCxF4%2FbtsKiwT0GMd%2FvSDSxeDLkiczWZCjOpLyS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;847&quot; height=&quot;721&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;테이블 생성은 매우 간단하다. 파티션 키와 정렬 키 등을 입력해 설정한다.&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>AWS Database</category>
      <category>aws db</category>
      <category>RDS</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/252</guid>
      <comments>https://minding-deep-learning.tistory.com/252#entry252comment</comments>
      <pubDate>Thu, 24 Oct 2024 14:44:19 +0900</pubDate>
    </item>
    <item>
      <title>[Playwright/Python] 비동기 처리가 가능한 웹 스크래핑 라이브러리, Playwright</title>
      <link>https://minding-deep-learning.tistory.com/251</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;프로그래머스 데이터 엔지니어링 데브코스 프로젝트 중 동적 웹 페이지에서 브라우저 조작 및 스크래핑으로 데이터를 수집해야하는 작업이 있었다. 처음에는 기존에 사용하던 Selenium을 사용했지만, 수집할 데이터가 다수였던 만큼 시간이 너무 많이 소요된다는 단점이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;시간 비용 소모 문제를 해결하기 위해 Selenium을 사용하는 함수를 비동기 프로그래밍 처리하려고 했지만, Selenium은 기본적으로 비동기 처리를 지원하지 않기 때문에 다소 복잡한 과정을 거쳐야 한다.(driver를 가져오는 함수는 따로 작성하고 sync to async를 통해 처리해줘야 한다던지... 등등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Selenium의 비동기 처리 문제를 해결하던 중, 우연히 검색을 통해 비동기 프로그래밍을 지원하는 브라우저 제어 라이브러리가 있다는 것을 알게되었다. 바로 Microsoft에서 개발한 Playwright다. Selenium과 거의 동일한 기능(더 좋은 기능도 많다.)을 가지고 있고, 여러 가지 브라우저(Chrome, firefox 등)를 지원한다. 실제로 웹 페이지 개발 뒤 QA 과정에서도 많이 사용되는 툴이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;285&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQuzj/btsKiapnczr/MuAkehPTGhdIaRkfaIgA11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQuzj/btsKiapnczr/MuAkehPTGhdIaRkfaIgA11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQuzj/btsKiapnczr/MuAkehPTGhdIaRkfaIgA11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQuzj%2FbtsKiapnczr%2FMuAkehPTGhdIaRkfaIgA11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;285&quot; height=&quot;177&quot; data-origin-width=&quot;285&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Playwright의 대표적인 기능&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중 브라우저 지원: Chromium, Firefox, WebKit 등 다양한 브라우저에서 테스트 가능&lt;/li&gt;
&lt;li&gt;자동 대기: 요소가 준비될 때까지 자동으로 기다리는 기능을 제공&lt;/li&gt;
&lt;li&gt;네트워크 인터셉션: 요청을 수정하거나 모의 응답 제공 가능&lt;/li&gt;
&lt;li&gt;모바일 장치 에뮬레이션: 모바일 환경 시뮬레이션 가능&lt;/li&gt;
&lt;li&gt;강력한 셀렉터 엔진: CSS, XPath 외에도 텍스트 내용, 레이블 등으로 요소 선택 가능&lt;/li&gt;
&lt;li&gt;헤드리스 및 헤드풀 모드 지원: 다양한 환경에서 실행 가능(Linux 등)&lt;/li&gt;
&lt;li&gt;Codegen 기능: 사용자의 브라우저 조작을 기록하여 자동으로 테스트 코드 생성&lt;/li&gt;
&lt;li&gt;Tracing 및 Debugging: Tracing 기능을 제공하여 디버깅 시 유용한 정보를 제공&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Trace Viewer를 통해 기록된 트레이스를 시각적으로 분석 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Playwright 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Python에서는 pip 명령어를 통해 Playwright를 설치할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1729684922324&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install playwright&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;설치 이후, 브라우저 바이너리를 추가로 설치해 주어야 라이브러리를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1729684970122&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;playwright install
# or
python -m playwright install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Playwright 기본예제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아래는 Playwright를 통해 웹 페이지의 title을 가져오는 예제이다. (동기 함수)&lt;/p&gt;
&lt;pre id=&quot;code_1729685043654&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False) # 크롬 브라우저 설치
    page = browser.new_page() # 새 페이지 생성
    page.goto('https://www.google.com') # 해당 링크로 접속
    print(page.title()) # title 출력
    browser.close()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Playwright로 비동기 처리하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;browser에서 .new_context() 메서드를 통해 독립적인 세션을 만들어준다. (크롬의 탭 같은 기능인 듯하다.) asyncio와 await 명령어를 통해 함수를 비동기 처리해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1729685620018&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 예제

import asyncio
from playwright.async_api import async_playwright

async def run(playwright):
    browser = await playwright.chromium.launch(headless=True)
    context = await browser.new_context() # 새로운 컨텍스트 생성(독립적인 세션 운영을 위함)
    page = await context.new_page()
    await page.goto('https://example.com')
    title = await page.title()
    print(title)
    await browser.close()

async def main():
    async with async_playwright() as playwright:
        await run(playwright)

asyncio.run(main())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 함수를 병렬적으로 한번에 처리하려면 asyncio.gather() 메서드를 이용하면 된다. 프로젝트에서는 아래와 같이 구현했다. (접은 글 또는 Github 링크 참고 / &lt;a href=&quot;https://github.com/yjbenkang/denpil&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/yjbenkang/denpil&lt;/a&gt; (updater.py 파일))&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1729685887415&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async def get_purchase_data(url):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        
        try:
            await page.goto(url, timeout=60000)  # 타임아웃을 60초로 증가
        except Exception as e:
            print(f&quot;Error navigating to {url}: {e}&quot;)
            await browser.close()
            return {}

        SCROLL_PAUSE_TIME = 1
        scroll = 0
        purchase_data = {}

        while True:
            try:
                element = await page.query_selector('.Ere_prod_graphwrap_a')
                if element:
                    # print(&quot;Element found&quot;)  # 디버깅 출력
                    content = await element.inner_html()
                    soup = BeautifulSoup(content, 'html.parser')
                    data = soup.find_all('div', class_='per')
                    age = ['10대 여성', '10대 남성', '20대 여성', '20대 남성', '30대 여성', '30대 남성', '40대 여성', '40대 남성', '50대 여성', '50대 남성', '60대 이상 여성', '60대 이상 남성']

                    for i in range(0, len(age)):
                        purchase_data[age[i]] = float(data[i].text[:-1])
                    break
                else:
                    # print(&quot;Element not found&quot;)  # 디버깅 출력
                    pass
            except Exception as e:
                print(f&quot;Error: {e}&quot;)

            scroll += 1500
            if scroll &amp;gt;= 13500:
                # print(&quot;Reached maximum scroll limit&quot;)  # 디버깅 출력
                break
            await page.evaluate(f&quot;window.scrollTo(0, {scroll});&quot;)
            await asyncio.sleep(SCROLL_PAUSE_TIME)

        await browser.close()
        # print(&quot;Purchase data:&quot;, purchase_data)  # 디버깅 출력
        return purchase_data
        
        async def get_book_purchase_data_by_batch(batch_size=3):
    # start_time = time.time()
    links = await get_link()
    
    if not links:
        print(&quot;No links found&quot;)  # 디버깅 출력
        return

    tasks = [get_purchase_data(link) for link in links]

    # 작업을 배치로 나누기
    for i in range(0, len(tasks), batch_size):
        batch = tasks[i:i + batch_size]
        print(f'{i//batch_size + 1} 번째 배치 처리 중')
        
        # 배치를 비동기적으로 처리
        try:
            results = await asyncio.gather(*batch)
        except Exception as e:
            print(f&quot;Error during batch processing: {e}&quot;)
            continue

        for link, purchase_data in zip(links[i:i + batch_size], results):
            if purchase_data:
                # print(f&quot;Link: {link}, Purchase Data: {purchase_data}&quot;)
                await update_book_purchase_data(link, purchase_data)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Playwright 메서드 소개&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;페이지 조작&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;goto(url): 지정한 URL로 이동&lt;/p&gt;
&lt;pre id=&quot;code_1729686071270&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;page.goto(&quot;https://example.com&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;click(selector): 선택한 요소 클릭&lt;/p&gt;
&lt;pre id=&quot;code_1729686104868&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;page.click(&quot;button#submit&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;fill(selector, value): 입력 필드에 값을 입력&lt;/p&gt;
&lt;pre id=&quot;code_1729686136742&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;page.fill(&quot;input#username&quot;, &quot;my_username&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;screenshot(path=&quot;screenshot.png&quot;): 현재 페이지의 스크린샷을 저장&lt;/p&gt;
&lt;pre id=&quot;code_1729686166748&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;page.screenshot(path=&quot;screenshot.png&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;요소 선택 및 정보 가져오기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;locator(selector): 특정 셀렉터에 해당하는 요소 찾기&lt;/p&gt;
&lt;pre id=&quot;code_1729686205099&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;locator = page.locator(&quot;div.content&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;inner_text(): 요소의 내부 텍스트를 반환&lt;/p&gt;
&lt;pre id=&quot;code_1729686221197&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;text = locator.inner_text()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;get_attribute(name): 요소의 특정 속성 값을 반환&lt;/p&gt;
&lt;pre id=&quot;code_1729686285160&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;attr_value = locator.get_attribute(&quot;href&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;상태 확인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;is_visible(): 요소가 보이는지 여부를 반환&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729686333060&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;visible = locator.is_visible()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;is_enabled(): 요소가 활성화되어 있는지 여부를 반환&lt;/p&gt;
&lt;pre id=&quot;code_1729686350884&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enabled = locator.is_enabled()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;고급 조작&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;hover():&amp;nbsp;요소&amp;nbsp;위에&amp;nbsp;마우스를&amp;nbsp;올리기&lt;/p&gt;
&lt;pre id=&quot;code_1729686383783&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;locator.hover()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;drag_to(target_locator):&amp;nbsp;요소를&amp;nbsp;다른&amp;nbsp;위치로&amp;nbsp;드래그하여&amp;nbsp;놓기&lt;/p&gt;
&lt;pre id=&quot;code_1729686399223&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;source.drag_to(target)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;evaluate(expression): 페이지 내에서 JavaScript 코드를 실행하고 결과를 반환&lt;/p&gt;
&lt;pre id=&quot;code_1729686427829&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;result = page.evaluate(&quot;document.title&quot;)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Minding's Programming/Crawling</category>
      <category>asyncio</category>
      <category>playwright</category>
      <category>playwright 비동기</category>
      <category>Selenium</category>
      <category>동적 웹크롤링</category>
      <category>동적 웹페이지</category>
      <category>비동기 프로그래밍</category>
      <category>웹 스크래핑</category>
      <category>웹 크롤링</category>
      <category>프로그래머스 데브코스</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/251</guid>
      <comments>https://minding-deep-learning.tistory.com/251#entry251comment</comments>
      <pubDate>Wed, 23 Oct 2024 21:28:29 +0900</pubDate>
    </item>
    <item>
      <title>[AWS/Elastic Beanstalk] Elastic Beanstalk</title>
      <link>https://minding-deep-learning.tistory.com/250</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;Elastic Beanstalk는 애플리케이션을 신속하게 구성하고 관리할 수 있는 AWS의 서비스다. 관리 복잡성을 줄일 수 있다는 장점이 있으며, 애플리케이션을 업로드하기만 하면 용량 프로비저닝, 로드 밸런싱, 조정, 애플리케이션 상태 모니터링 세부 정보를 자동으로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Go, Java, .NET, PHP, Python 등의 애플리케이션을 지원한다. 애플리케이션을 배포할 때, Elastice Beanstalk가 선택된 지원 가능 플랫폼 버전을 구축하고, Amazon EC2 등의 AWS 리소스를 하나 이상 프로비저닝해 애플리케이션을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; Elastic Beanstalk 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Elastic Beanstalk &amp;gt; 애플리케이션 생성 으로 진입한다. 1단계 화면에서는 환경 티어, 정보, 플랫폼(언어) 등을 설정할 수 있고, 실습 환경의 경우 대부분을 기본값으로 진행하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJ0rL/btsKf80WFpB/B5huZKtnudoRbkvZSUZAc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJ0rL/btsKf80WFpB/B5huZKtnudoRbkvZSUZAc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJ0rL/btsKf80WFpB/B5huZKtnudoRbkvZSUZAc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJ0rL%2FbtsKf80WFpB%2FB5huZKtnudoRbkvZSUZAc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;540&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;* 주의할 점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2024년 10월 1일부터 Elastic Beanstalk의 새로운 환경을 설정할 때 Launch configurations 대신 Launch templates로 대체한다고 한다. Launch templates를 사용하기 위해서는 아래 옵션 중 한 가지를 선택해야 한다. (Launch templates의 장점은 아래 접은 글 참고)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; Launch templates의 장점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;런치&amp;nbsp;템플릿과&amp;nbsp;런치&amp;nbsp;구성의&amp;nbsp;차이점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;런치 구성: EC2 인스턴스를 시작하기 위한 설정 정보를 제공하며, 한 번 생성되면 수정할 수 없습니다. 버전 관리 기능이 없어 다양한 버전을 유지하기 어렵습니다.&lt;/li&gt;
&lt;li&gt;런치 템플릿: 런치 구성과 유사하게 인스턴스 설정 정보를 제공하지만, 여러 버전을 관리할 수 있습니다. 이는 다양한 설정을 테스트하거나 업데이트할 때 유용합니다. 또한, 최신 EC2 기능을 사용할 수 있도록 지원합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;런치&amp;nbsp;템플릿의&amp;nbsp;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버전 관리: 여러 버전을 생성하여 다양한 설정을 테스트하고 유지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;최신 기능 지원: 최신 EC2 인스턴스 유형 및 기능을 사용할 수 있습니다. 예를 들어, 시스템 관리자 매개변수, 최신 EBS 볼륨, T2 무제한 인스턴스 등을 지원합니다.&lt;/li&gt;
&lt;li&gt;유연성:&amp;nbsp;다양한&amp;nbsp;인스턴스&amp;nbsp;유형과&amp;nbsp;구매&amp;nbsp;옵션을&amp;nbsp;설정할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RootVolumeType을 gp3로 설정&lt;/li&gt;
&lt;li&gt;BlockDeviceMappings에 gp3 포함&lt;/li&gt;
&lt;li&gt;DisableIMDSv1을 true로 설정&lt;/li&gt;
&lt;li&gt;EnableSpot을&amp;nbsp;true로&amp;nbsp;설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;+ IAM 오류 발생시&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1729665811214&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;The instance profile aws-elasticbeanstalk-ec2-role associated with the environment does not exist.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Elastic Beanstalk 생성 시 인스턴스 프로파일이 존재하지 않는다는 오류가 나타날 수 있다. AWS 보안 정책 변경으로 인해 Role을 자동 생성이 아닌 수동으로 생성해야 하기 때문이다. 아래 접은 글에 해결 방법을 적어놓았다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;IAM 콘솔에서 역할 생성&lt;/b&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;AWS Management Console에서 IAM 서비스로 이동합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Roles&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;페이지를 열고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;Create role&lt;/b&gt;&lt;/span&gt;&lt;span&gt;을 선택합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Trusted entity type&lt;/b&gt;&lt;/span&gt;&lt;span&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;AWS service&lt;/b&gt;&lt;/span&gt;&lt;span&gt;를 선택하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;Use case&lt;/b&gt;&lt;/span&gt;&lt;span&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;EC2&lt;/b&gt;&lt;/span&gt;&lt;span&gt;를 선택합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;정책 연결&lt;/b&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Elastic Beanstalk에 필요한 관리형 정책을 연결합니다. 일반적으로 다음과 같은 정책이 필요합니다:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;AWSElasticBeanstalkWebTier&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;AWSElasticBeanstalkWorkerTier&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;AWSElasticBeanstalkMulticontainerDocker&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;AWSElasticBeanstalkWebTier&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;AWSElasticBeanstalkWorkerTier&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;AWSElasticBeanstalkMulticontainerDocker&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;역할 이름 지정 및 생성&lt;/b&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;역할 이름을 &quot;aws-elasticbeanstalk-ec2-role&quot;로 지정하고 역할을 생성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;환경에 인스턴스 프로파일 할당&lt;/b&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Elastic Beanstalk 환경 설정으로 돌아가 새로 생성한 인스턴스 프로파일을 선택하여 환경 생성을 계속 진행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Elastic Beanstalk 인스턴스 연결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Elastic Beanstalk로 생성된 인스턴스와 터미널을 통해 콘솔을 연결할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1729666250592&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh -i {your_key}.pem ec2-user@{ElasticIP}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBqZih/btsKhzbDFwD/k8XN8MvWY4yX0vabxe6boK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBqZih/btsKhzbDFwD/k8XN8MvWY4yX0vabxe6boK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBqZih/btsKhzbDFwD/k8XN8MvWY4yX0vabxe6boK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBqZih%2FbtsKhzbDFwD%2Fk8XN8MvWY4yX0vabxe6boK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;454&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ElasticBeanstalk가 나타나는 화면과 함께 인스턴스가 연결됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실행되는 어플리케이션 파일은 '/var/app/current'에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1729666536677&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ec2-user@ip-172-31-15-150 ~]$ cd /var/app
[ec2-user@ip-172-31-15-150 app]$ ls
current  venv
[ec2-user@ip-172-31-15-150 app]$ cd current
[ec2-user@ip-172-31-15-150 current]$ ls
application.py  cron.yaml  Procfile  __pycache__&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실행에 대한 로그는 '/var/log'에 저장된다. 웹 애플리케이션에 대한 로그는 'web.stdout.log'에, ElasticBeanstalk에 대한 로그는 'eb-engine.log'에 저장된다.&lt;/p&gt;
&lt;pre id=&quot;code_1729666694985&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ec2-user@ip-172-31-15-150 current]$ cd ../..
[ec2-user@ip-172-31-15-150 var]$ cd log
[ec2-user@ip-172-31-15-150 log]$ ls
amazon       cfn-init-cmd.log  cloud-init.log         eb-cfn-init.log  healthd   nginx    sa        web.stdout.log
audit        cfn-init.log      cloud-init-output.log  eb-engine.log    httpd     private  secure    wtmp
btmp         cfn-wire.log      cron                   eb-hooks.log     lastlog   README   sssd      xray
cfn-hup.log  chrony            eb-cfn-init-call.log   eb-publish.log   messages  rotated  tallylog
[ec2-user@ip-172-31-15-150 log]$ pwd
/var/log&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>aws ec2</category>
      <category>aws elasticbeanstalk</category>
      <category>AWS IAM</category>
      <category>elasticbeanstalk</category>
      <category>아마존 웹 서비스</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/250</guid>
      <comments>https://minding-deep-learning.tistory.com/250#entry250comment</comments>
      <pubDate>Wed, 23 Oct 2024 16:22:48 +0900</pubDate>
    </item>
    <item>
      <title>[AWS/EC2] EC2로 서버 생성 및 연결하는 방법</title>
      <link>https://minding-deep-learning.tistory.com/249</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;키 페어&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;키 페어는 AWS 인스턴스(서버)에 로그인할 때 사용하는 정보로, AWS에 저장되는 Public Key와 사용자가 개인 보관하는 Private Key의 쌍으로 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;경로: EC2 &amp;gt; 키 페어 &amp;gt; 키 페어 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HHlTB/btsKf6uQJsr/sILwDwTgvvgl02oyAKAyw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HHlTB/btsKf6uQJsr/sILwDwTgvvgl02oyAKAyw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HHlTB/btsKf6uQJsr/sILwDwTgvvgl02oyAKAyw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHHlTB%2FbtsKf6uQJsr%2FsILwDwTgvvgl02oyAKAyw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;736&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;프라이빗 키 파일 형식은 두 가지인데, 각자 사용하는 OS 환경에 따라 다르게 선택한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.pem: Mac, Linux (필자 선택)&lt;/li&gt;
&lt;li&gt;.ppk: Windows&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 '키 페어 생성'을 클릭하면, 선택한 프라이빗 키 형식의 파일을 다운로드 받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJMln/btsKgCNCVzn/rk9mi1k1JRyAYgNQnzPpi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJMln/btsKgCNCVzn/rk9mi1k1JRyAYgNQnzPpi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJMln/btsKgCNCVzn/rk9mi1k1JRyAYgNQnzPpi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJMln%2FbtsKgCNCVzn%2Frk9mi1k1JRyAYgNQnzPpi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;617&quot; height=&quot;47&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;47&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 키는 개인이 관리하는 key이므로, 절대 잃어버리지 않도록 유의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인스턴스 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;본격적으로 가상 서버(인스턴스)를 생성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;경로: EC2 &amp;gt; 인스턴스 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;338&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HGGyt/btsKfyMinXl/H0Or9uBemQgXBlH9opEFA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HGGyt/btsKfyMinXl/H0Or9uBemQgXBlH9opEFA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HGGyt/btsKfyMinXl/H0Or9uBemQgXBlH9opEFA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHGGyt%2FbtsKfyMinXl%2FH0Or9uBemQgXBlH9opEFA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;338&quot; height=&quot;272&quot; data-origin-width=&quot;338&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;1584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/px8R4/btsKg8efhsH/7qJPt7eYyKpBWNbn3Un0l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/px8R4/btsKg8efhsH/7qJPt7eYyKpBWNbn3Un0l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/px8R4/btsKg8efhsH/7qJPt7eYyKpBWNbn3Un0l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpx8R4%2FbtsKg8efhsH%2F7qJPt7eYyKpBWNbn3Un0l0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;854&quot; height=&quot;1584&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;1584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;서버 이름 및 OS, AMI등을 선택할 수 있다. AMI는 운영체제 및 애플리케이션 등이 포함된 일종의 템플릿으로, Amazon에서는 다양한 AMI를 제공하고 있다. ('더 많은 AMI 찾아보기'에서 확인 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 인스턴스 유형을 선택할 수 있다. 다양한 코어의 cpu와 메모리를 가진 서버를 선택할 수 있다. 데모 버전의 서비스의 경우 t2.micro를 선택해 프리 티어를 사용해도 되지만, 실제 서비스를 할 경우 더 큰 규모의 서버가 필요할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 어떤 키 페어를 사용할지 선택할 수 있다. 이전에 만들어놓은 키 페어를 사용하거나 새로운 키 페어를 생성할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mOZN4/btsKf80DUnu/XfzlD6VWkipeibRslkkaR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mOZN4/btsKf80DUnu/XfzlD6VWkipeibRslkkaR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mOZN4/btsKf80DUnu/XfzlD6VWkipeibRslkkaR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmOZN4%2FbtsKf80DUnu%2FXfzlD6VWkipeibRslkkaR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;829&quot; height=&quot;1162&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음으로 네트워크 설정을 통해 보안 그룹을 생성하거나 지정할 수 있다.해당 서버에 접속할 수 있는 프로토콜, 포트 등을 지정하는 방화벽의 기능을 한다. 이후에도 재설정 가능하니 기본값으로 설정해도 무관하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;스토리지의 용량을 설정할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;793&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/opcJ2/btsKhabXDy6/xKbfLkzkwyaXrSORluiRSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/opcJ2/btsKhabXDy6/xKbfLkzkwyaXrSORluiRSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/opcJ2/btsKhabXDy6/xKbfLkzkwyaXrSORluiRSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FopcJ2%2FbtsKhabXDy6%2FxKbfLkzkwyaXrSORluiRSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;793&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;793&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 '인스턴스 시작'을 눌러 서버를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IPDcl/btsKfIBgAtP/1oENQ1vWc5WPLwtBzVIV3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IPDcl/btsKfIBgAtP/1oENQ1vWc5WPLwtBzVIV3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IPDcl/btsKfIBgAtP/1oENQ1vWc5WPLwtBzVIV3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIPDcl%2FbtsKfIBgAtP%2F1oENQ1vWc5WPLwtBzVIV3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1590&quot; height=&quot;102&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;성공적으로 서버가 생성됐고, 실행되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;터미널을 통해 인스턴스 콘솔에 접속하는 법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;본인은 Windows 사용자이지만 WSL 설치를 통해 Linux로 진행하므로, 아래 방법은 WSL2 설치를 한 뒤 ubuntu 콘솔로 진행한다는 점을 참고하길 바란다. (&lt;a href=&quot;https://minding-deep-learning.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Windows 환경에서 Linux 사용을 위한 WSL2 설치&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선 미리 다운로드 받아놓은 키 페어 파일은 .pem 파일을 특정 폴더에 넣어 놓는 것이 좋다. 필자의 경우 .ssh라는 폴더를 생성해 그 안에 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그런 뒤 wsl 콘솔을 통해 해당 폴더에 진입한 뒤, 권한 설정을 먼저 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1729661744965&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(base)  minding  ~/aws/.ssh
❯ sudo chmod 400 minding.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;chmod 400 명령어를 통해 해당 파일을 '나만 읽을 수 있게' 설정한다. 보안에 민감한 개인 key이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 AWS 인스턴스 화면에 돌아가 연결한 인스턴스를 클릭한 뒤, 상단의 '연결' 버튼을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKtLWb/btsKfNQcnAg/KmZXwwY3cqLudeEdN1VgJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKtLWb/btsKfNQcnAg/KmZXwwY3cqLudeEdN1VgJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKtLWb/btsKfNQcnAg/KmZXwwY3cqLudeEdN1VgJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKtLWb%2FbtsKfNQcnAg%2FKmZXwwY3cqLudeEdN1VgJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;832&quot; height=&quot;662&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러면 위와 같은 화면이 노출되는데, 'SSH 클라이언트' 항목으로 진입해 아래 명령어를 복사해 콘솔 창에 붙여넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/00kWl/btsKguP1k4l/lnU3UIqtMVFkl7HPcjVfak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/00kWl/btsKguP1k4l/lnU3UIqtMVFkl7HPcjVfak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/00kWl/btsKguP1k4l/lnU3UIqtMVFkl7HPcjVfak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F00kWl%2FbtsKguP1k4l%2FlnU3UIqtMVFkl7HPcjVfak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1369&quot; height=&quot;335&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 서버(인스턴스)에 접속된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;탄력적 IP 설정 (Elastic IP)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;고정된 공용 IPv4 주소로, AWS 계정에 할당되어 사용자가 직접 관리할 수 있는 IP 주소다. &lt;/span&gt;좌측 메뉴 바에서 '탄력적 IP' 메뉴에서 탄력적 IP를 할당받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p5gkk/btsKfUIqNJW/oKQYDZTGLyau7ekVrzvkHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p5gkk/btsKfUIqNJW/oKQYDZTGLyau7ekVrzvkHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p5gkk/btsKfUIqNJW/oKQYDZTGLyau7ekVrzvkHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp5gkk%2FbtsKfUIqNJW%2FoKQYDZTGLyau7ekVrzvkHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;227&quot; height=&quot;613&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr70D3/btsKg0Otwun/U79tGVbBUrSZh5iZkyNFL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr70D3/btsKg0Otwun/U79tGVbBUrSZh5iZkyNFL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr70D3/btsKg0Otwun/U79tGVbBUrSZh5iZkyNFL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr70D3%2FbtsKg0Otwun%2FU79tGVbBUrSZh5iZkyNFL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;161&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 IP를 선택한 뒤 상단의 작업 버튼을 클릭하면 해당 IP 주소와 서버를 연결할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eF7iCc/btsKhfqWmrS/KdfYkqx6REwjuiDqRpOrbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eF7iCc/btsKhfqWmrS/KdfYkqx6REwjuiDqRpOrbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eF7iCc/btsKhfqWmrS/KdfYkqx6REwjuiDqRpOrbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeF7iCc%2FbtsKhfqWmrS%2FKdfYkqx6REwjuiDqRpOrbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;328&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;619&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2Akv0/btsKgaj0fZA/jkifZyAp188yX0yyJGJCQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2Akv0/btsKgaj0fZA/jkifZyAp188yX0yyJGJCQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2Akv0/btsKgaj0fZA/jkifZyAp188yX0yyJGJCQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2Akv0%2FbtsKgaj0fZA%2FjkifZyAp188yX0yyJGJCQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;619&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;619&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B2CSl/btsKg4QKfNH/AEMAuye1UdLK5UTcjxKCi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B2CSl/btsKg4QKfNH/AEMAuye1UdLK5UTcjxKCi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B2CSl/btsKg4QKfNH/AEMAuye1UdLK5UTcjxKCi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB2CSl%2FbtsKg4QKfNH%2FAEMAuye1UdLK5UTcjxKCi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;230&quot; height=&quot;64&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 인스턴스에 탄력적 IP 주소가 생성된 것을 알 수 있다. 특정 방화벽을 열거나 특정 IP를 사용해야만 하는 경우 이 방법을 이용할 수 있다. 해당 IP는 연결된 서버와 해제도 가능하며, 다른 서버와도 연결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Minding's Programming/AWS</category>
      <category>AWS</category>
      <category>AWS SSH</category>
      <category>aws 인스턴스 생성</category>
      <category>aws 인스턴스 연결</category>
      <category>EC2</category>
      <category>Key pair</category>
      <category>PEM</category>
      <category>PPK</category>
      <category>ssh</category>
      <category>인스턴스</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/249</guid>
      <comments>https://minding-deep-learning.tistory.com/249#entry249comment</comments>
      <pubDate>Wed, 23 Oct 2024 14:06:35 +0900</pubDate>
    </item>
    <item>
      <title>[Django] Testing(Testing Serializer, Testing View)</title>
      <link>https://minding-deep-learning.tistory.com/247</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Testing&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Testing은 각 기능의 테스트를 일일히 하는 것이 아닌, 주요 기능들을 컴퓨터 스스로 할 수 있도록 자동화하는 것을 뜻한다. 개발 시간을 줄여주고, 문제를 발견 및 예방하는데 도움이 된다. 테스트 코드로 인해 코드도 간결해지고, 협업에 도움이 된다는 장점도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;polls_api 폴더로 이동해보자. 생성한 앱에는 자동으로 test.py 파일이 존재하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;166&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LLsoE/btsJ34KvuFl/rP8V0jGtAKKLzSodfcr8Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LLsoE/btsJ34KvuFl/rP8V0jGtAKKLzSodfcr8Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LLsoE/btsJ34KvuFl/rP8V0jGtAKKLzSodfcr8Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLLsoE%2FbtsJ34KvuFl%2FrP8V0jGtAKKLzSodfcr8Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;166&quot; height=&quot;234&quot; data-origin-width=&quot;166&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 test.py 파일에 testing 코드를 넣고 파일을 실행해 테스트를 자동화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Testing Serializer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저 시리얼라이저에 대한 테스트 코드를 작성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728724660035&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.test import TestCase
from polls_api.serializers import QuestionSerializer
import unittest
# Create your tests here.
class QuestionSerializerTest(TestCase):
    # 유효한 데이터에 대한 테스트
    def test_with_valid_data(self):
        serializer = QuestionSerializer(data={&quot;question_text&quot;: &quot;What is your name?&quot;})
        # assertTrue() 메서드: 주어진 조건이 True인지 확인
        self.assertTrue(serializer.is_valid())
        question = serializer.save()
        # assertIsNotNone() 메서드: 주어진 객체가 None이 아닌지 확인
        self.assertIsNotNone(question.id)
        
    # 유효하지 않은 데이터에 대한 테스트
    def test_with_invalid_data(self):
        serializer = QuestionSerializer(data={&quot;question_text&quot;: &quot;&quot;})
        self.assertFalse(serializer.is_valid())
        self.assertEqual(serializer.errors[&quot;question_text&quot;][0], &quot;This field may not be blank.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Question 시리얼라이저에 대한 테스트 함수 2개를 작성해주었다. 유효한 데이터와 유효하지 않은 데이터를 넣었을 때의 각각 제대로 처리되는지에 대한 테스트 함수로, assertTrue, assertIsNotNone 등과 같은 메서드를 통해 시리얼라이저의 메서드가 어떻게 응답되는지 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;터미널에서 테스트 코드를 실행해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728724784956&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;❯ python manage.py test     
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK
Destroying test database for alias 'default'...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;테스트 케이스 2개를 찾았고, 2개 모두 정상적으로 처리되었다는 메시지를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 VoteSerializer를 test하는 코드를 만들어 전체적인 과정을 테스트해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728726530274&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.test import TestCase
from polls_api.serializers import QuestionSerializer, VoteSerializer
from django.contrib.auth.models import User
from polls.models import Question, Choice, Vote

class VoteSerializerTest(TestCase):
    # 각 테스트 메서드가 실행되기 전에 실행되는 메서드로, 테스트에 필요한 데이터를 설정
    def setUp(self):
        self.user = User.objects.create(username='testuser')
        self.question = Question.objects.create(
            question_text='테스트 질문입니다.',
            owner=self.user,
        )
        self.choice = Choice.objects.create(
            question=self.question,
            choice_text='테스트 선택지'
        )
    
    # 정상적인 상황 테스트
    def test_vote_serializer(self):  
        self.assertEqual(User.objects.all().count(), 1)
        data = {
            'question': self.question.id,
            'choice': self.choice.id,
        }
        serializer = VoteSerializer(data=data, context={'request': type('Request', (), {'user': self.user})()})
        self.assertTrue(serializer.is_valid())
        vote = serializer.save()
    
        self.assertEqual(vote.question, self.question)
        self.assertEqual(vote.choice, self.choice)
        self.assertEqual(vote.voter, self.user)
    
    # 중복 투표 테스트
    def test_vote_serializer_with_duplicate_vote(self):
        Vote.objects.create(question=self.question, choice=self.choice, voter=self.user)
    
        data = {
            'question': self.question.id,
            'choice': self.choice.id,
        }
        serializer = VoteSerializer(data=data, context={'request': type('Request', (), {'user': self.user})()})
        self.assertFalse(serializer.is_valid())
        self.assertIn('non_field_errors', serializer.errors)
    
    # 질문과 선택지가 일치하지 않는 경우 테스트
    def test_vote_serializer_with_unmatched_question_and_choice(self):
        question2 = Question.objects.create(
            question_text='다른 테스트 질문입니다.',
            owner=self.user,
        )
    
        choice2 = Choice.objects.create(
            question=question2,
            choice_text='다른 테스트 선택지'
        )
        data = {
            'question': self.question.id,
            'choice': choice2.id,
        }
        serializer = VoteSerializer(data=data, context={'request': type('Request', (), {'user': self.user})()})
        self.assertFalse(serializer.is_valid())
        self.assertIn('non_field_errors', serializer.errors)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;정상적인 상황/중복투표/질문-선택지 일치하지 않는 경우 총 3가지로 나누어 테스트 코드를 작성했다. 각 테스트 코드가 실행되기 전에 실행되는 setUp() 메서드는 테스트에 필요한 데이터를 미리 설정해주는 메서드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Testing View&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번엔 View에 대해서 테스트 코드를 작성해보자. View는 리스트를 불러오거나, 레코드를 생성하는 등의 테스트 케이스가 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1728728476022&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from rest_framework.test import APITestCase
from django.urls import reverse
from rest_framework import status
from django.utils import timezone
import unittest

# view를 테스트할 때는 APITestCase를 사용
class QuestionListTest(APITestCase):
    # 테스트 메서드가 실행되기 전에 실행되는 메서드로, 테스트에 필요한 데이터를 설정
    def setUp(self):
        self.question_data = {'question_text': '테스트 질문입니다.'}
        self.url = reverse('question-list')
        
    # 질문 생성 테스트
    def test_create_question(self):
        user = User.objects.create_user(username='testuser', password='testpass')
        self.client.force_authenticate(user=user)
        response = self.client.post(self.url, self.question_data)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Question.objects.count(), 1)
        self.assertEqual(Question.objects.first().question_text, self.question_data['question_text'])
        self.assertLess((timezone.now() - Question.objects.first().pub_date).total_seconds(), 1)
    
    # 인증되지 않은 사용자(로그인하지 않은 경우) 질문 생성 테스트
    def test_create_question_without_authentication(self):
        response = self.client.post(self.url, self.question_data)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        
    # 질문 목록 조회 테스트
    def test_list_questions(self):
        question = Question.objects.create(question_text='Question1')
        choice = Choice.objects.create(question=question, choice_text='Question1')
        Question.objects.create(question_text='Question2')
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 2)
        self.assertEqual(response.data[0]['choices'][0]['choice_text'], choice.choice_text)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Coverage&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;coverage는 작성한 코드의 어떤 부분이 얼마나 잘 테스트 되고 있는지를 판단하는 파이썬 라이브러리이다.&lt;br /&gt;&lt;br /&gt;coverage 라이브러리를 설치한 뒤, Django 프로젝트의 테스트 코드를 실행하면서 실행된 코드의 커버리지를 측정하는 명령어를 아래와 같이 입력할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1728728952056&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 설치
pip install coverage

# 커버리지 측정
coverage run manage.py test

# 코드 커버리지 결과 요약
coverage report

&amp;gt;&amp;gt;&amp;gt;
Name                                                                       Stmts   Miss  Cover
----------------------------------------------------------------------------------------------
manage.py                                                                     11      2    82%
mysite/__init__.py                                                             0      0   100%
mysite/settings.py                                                            21      0   100%
mysite/urls.py                                                                 3      0   100%
polls/__init__.py                                                              0      0   100%
polls/admin.py                                                                13      0   100%
polls/apps.py                                                                  4      0   100%
polls/migrations/0001_initial.py                                               6      0   100%
polls/migrations/0002_question_owner_alter_question_pub_date_and_more.py       6      0   100%
polls/migrations/0003_alter_choice_question_vote_and_more.py                   6      0   100%
polls/migrations/__init__.py                                                   0      0   100%
polls/models.py                                                               29      5    83%
polls/tests.py                                                                 1      0   100%
polls/urls.py                                                                  5      0   100%
polls/views.py                                                                34     15    56%
polls_api/__init__.py                                                          0      0   100%
polls_api/migrations/__init__.py                                               0      0   100%
polls_api/permissions.py                                                       9      4    56%
polls_api/serializers.py                                                      48      7    85%
polls_api/tests.py                                                            69      0   100%
polls_api/urls.py                                                              3      0   100%
polls_api/views.py                                                            46     10    78%
----------------------------------------------------------------------------------------------
TOTAL                                                                        314     43    86%&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Minding's Programming/Django</category>
      <category>apitestcase</category>
      <category>django</category>
      <category>django python</category>
      <category>django testing</category>
      <category>testing serializer</category>
      <category>testing view</category>
      <category>장고 test.py</category>
      <category>장고 테스트</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/247</guid>
      <comments>https://minding-deep-learning.tistory.com/247#entry247comment</comments>
      <pubDate>Sat, 12 Oct 2024 16:37:52 +0900</pubDate>
    </item>
    <item>
      <title>[Django] 투표 기능 구현하기</title>
      <link>https://minding-deep-learning.tistory.com/246</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1인 1투표 기능 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Vote 모델 만들기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이전에 투표 기능을 구현할 때(&lt;a href=&quot;https://minding-deep-learning.tistory.com/237&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Django] 폼(Forms)&lt;/a&gt;)는 User의 정보를 따로 받지않아 1명의 User가 여러 번 투표를 할 수 있었다. 이번에는 RESTful API framework를 이용해 계정 정보를 저장하는 모델을 따로 만들어 1개의 계정이 여러 번 투표를 할 수 없도록 하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저, 투표한 질문, 답변, 계정 정보를 담을 테이블을 생성하기 위해 모델을 만들어준다.&lt;/p&gt;
&lt;pre id=&quot;code_1728629831673&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls/models.py

from django.contrib.auth.models import User

class Vote(models.Model):
    # 투표한 질문
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    # 투표한 답변
    choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
    # 투표자 정보
    voter = models.ForeignKey(User, related_name='votes', on_delete=models.CASCADE)
    
    class Meta:
        # question과 voter 조합이 유니크한 값이어야 함(1개만 존재해야 함)
        constraints = [
            models.UniqueConstraint(fields=['voter', 'question'], name='unique_voter_question')
        ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Vote 모델의 필드로는 question, choice, voter가 있으며 모델의 메타데이터(class Meta)에서 'voter'와 'question' 조합이 유일할 것을 제약 조건(constraints)으로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모델을 새로 만들었으므로 마이그레이션도 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1728630036626&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;❯ python manage.py makemigrations
❯ python manage.py migrate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 Vote모델에 레코드를 하나 생성해보자. (Shell 사용)&lt;/p&gt;
&lt;pre id=&quot;code_1728630380397&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 질문/답변 선택
&amp;gt;&amp;gt;&amp;gt; from polls.models import *
&amp;gt;&amp;gt;&amp;gt; question = Question.objects.first()
&amp;gt;&amp;gt;&amp;gt; choice = question.choices.first()
&amp;gt;&amp;gt;&amp;gt; print(question, choice)
[2024-10-07 04:20:27+00:00] 가장 추천하는 가을 캠핑장은 어디인가요? [가장 추천하는 가을 
캠핑장은 어디인가요?] 포천

# 투표자 선택
&amp;gt;&amp;gt;&amp;gt; from django.contrib.auth.models import User
&amp;gt;&amp;gt;&amp;gt; user = User.objects.get(username='user1')

# Vote 모델에 레코드 생성
&amp;gt;&amp;gt;&amp;gt; Vote.objects.create(voter=user, question=question, choice=choice)
&amp;lt;Vote: Vote object (1)&amp;gt;
&amp;gt;&amp;gt;&amp;gt; Vote.objects.first()
&amp;lt;Vote: Vote object (1)&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Vote 모델에 레코드가 정상적으로 생성되는 것을 확인했다. 하지만 아직 Vote 모델과 Choice 모델의 'votes' 필드는 연결되지 않은 상태이기 때문에, Vote 모델에 새로운 레코드가 생성되어도 Choice의 votes 숫자는 전혀 변하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;시리얼라이저에서 votes 필드를 가져올 때, Vote 모델을 참조해 해당 답변의 수가 몇 개인지 카운트하는 기능이 필요하다. serializers.py로 이동해 다음과 같이 코드를 수정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728630811375&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/serializers.py

class ChoiceSerializer(serializers.ModelSerializer):
    votes_count = serializers.SerializerMethodField()
    
    def get_votes_count(self, obj):
        return obj.vote_set.count()
    
    class Meta:
        model = Choice
        fields = ['choice_text', 'votes_count']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;votes_count를 SerializerMethodField() 메서드를 사용해 구한다. 이 메서드는 특정 필드의 값을 구할 때 사용되는데, get_{필드 이름}() 메서드를 오버라이딩해 커스텀할 수 있다. 이를 이용해 get_votes_count로 해당 답변을 카운트한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2uEwG/btsJ181b4XQ/5TmI2kkkGE8Ghk9dOayeQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2uEwG/btsJ181b4XQ/5TmI2kkkGE8Ghk9dOayeQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2uEwG/btsJ181b4XQ/5TmI2kkkGE8Ghk9dOayeQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2uEwG%2FbtsJ181b4XQ%2F5TmI2kkkGE8Ghk9dOayeQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;739&quot; height=&quot;561&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;561&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Django 서버에서 해당 질문을 살펴봤을 때 Vote모델에 등록했던 '포천' 답변의 votes_count가 1 증가한 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Vote Serializer 만들고 직접 Vote 생성해보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번에는 Vote Serializer를 만들고 그에 대한 view와 url을 설정해 RESTful API를 통해 Vote를 직접 생성해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728632418839&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from polls.models import Question, Choice, Vote

class VoteSerializer(serializers.ModelSerializer):
    voter = serializers.ReadOnlyField(source='voter.username')
    
    class Meta:
        model = Vote
        fields = ['id', 'question', 'choice', 'voter']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저 Vote Serializer를 생성해준다. voter 필드에 한해서만 고정적일 수 있게 ReadOnlyField 설정을 지정해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728632505980&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from polls.models import Question, Vote
from .serializers import QuestionSerializer, UserSerializer, RegisterSerializer, VoteSerializer
from .permissions import IsOwnerOrReadOnly, IsVoteOwner

class VoteList(generics.ListCreateAPIView):
    serializer_class = VoteSerializer
    # IsAuthenticated 클래스는 인증된 사용자만 투표 가능하고 볼 수 있음
    permission_classes = [permissions.IsAuthenticated]
    
    # 투표 목록을 가져올 때 투표자가 인증된 현재 사용자인 경우만 가져옴
    def get_queryset(self):
        return Vote.objects.filter(voter=self.request.user)
    
    def perform_create(self, serializer):
        # save 메서드는 주어진 필드를 모두 사용하므로 readonly와 관계없이 지정 가능
        serializer.save(voter=self.request.user)
    
class VoteDetail(generics.RetrieveDestroyAPIView):
    queryset = Vote.objects.all()
    serializer_class = VoteSerializer
    permission_classes = [permissions.IsAuthenticated, IsVoteOwner]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 views.py에서 VoteList와 VoteDetail을 생성해주었다. 기본적으로 QuestionList, QuestionDetail과 비슷하다. 하지만 투표의 경우 현재 로그인된 유저에 해당되는 결과만 가져와야 하기 때문에, 몇 가지 조건이 추가됭었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VoteList의 경우 queryset을 get_queryset() 메서드를 통해 가져오게 된다. 모델 필터링을 통해 voter가 현재 요청한 유저와 동일할 경우에만 그 리스트를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VoteDetail의 경우 해당하는 투표를 모두 가져오되, permission이 2가지가 걸려있다. 우선 로그인한 계정이어야 하고, 투표의 주인이어야 한다.(IsVoteOwner) IsVoteOwner는 permissions.py를 통해 따로 구현해 주었다. SAFE_METHOD를 허용했던 IsOwnerOrReadOnly와는 다르게, 완벽히 사용자가 같은 경우에만 허용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728632817562&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/permissions.py

class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        # SAFE_METHODS는 GET, OPTIONS, HEAD 메서드(읽기 전용)
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.owner == request.user
        
# 조건 추가
class IsVoteOwner(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        # 투표자와 현재 사용자가 같은 경우에만 True 반환
        return obj.voter == request.user&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이어서 urls.py에 해당 URL을 추가해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1728632922516&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.urls import path, include
from .views import *

urlpatterns = [
	...
    path('vote/', VoteList.as_view(), name='vote-list'),
    path('vote/&amp;lt;int:pk&amp;gt;/', VoteDetail.as_view(), name='vote-detail'),
    ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 서버를 통해 '/vote' URL로 접속해보자. (User1로 로그인한 상태)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mbwDz/btsJ2uiMqUV/aSRzf60ViOp2jSuMgrLiM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mbwDz/btsJ2uiMqUV/aSRzf60ViOp2jSuMgrLiM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mbwDz/btsJ2uiMqUV/aSRzf60ViOp2jSuMgrLiM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmbwDz%2FbtsJ2uiMqUV%2FaSRzf60ViOp2jSuMgrLiM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;715&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2wOL4/btsJ1JOw3kJ/UsWkGCZWEwfeuHKcERZ8U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2wOL4/btsJ1JOw3kJ/UsWkGCZWEwfeuHKcERZ8U0/img.png&quot; data-alt=&quot;참고: 로그아웃 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2wOL4/btsJ1JOw3kJ/UsWkGCZWEwfeuHKcERZ8U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2wOL4%2FbtsJ1JOw3kJ%2FUsWkGCZWEwfeuHKcERZ8U0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;736&quot; height=&quot;275&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참고: 로그아웃 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 이미지에서 보듯이 로그인 상태에서 Vote list 페이지에 접속했을 때 자신이 답변한 질문에 대해서만 리스트가 노출되고 있는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상세페이지에 진입해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTqdc9/btsJ3FXRArq/skAQ3k7cZeWAioApnPHoDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTqdc9/btsJ3FXRArq/skAQ3k7cZeWAioApnPHoDK/img.png&quot; data-alt=&quot;내 답변이 있는 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTqdc9/btsJ3FXRArq/skAQ3k7cZeWAioApnPHoDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTqdc9%2FbtsJ3FXRArq%2FskAQ3k7cZeWAioApnPHoDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;339&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내 답변이 있는 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lyDnj/btsJ13MKePi/3WduyGxJoPhgwxIMuQ2lS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lyDnj/btsJ13MKePi/3WduyGxJoPhgwxIMuQ2lS1/img.png&quot; data-alt=&quot;내 답변이 없는 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lyDnj/btsJ13MKePi/3WduyGxJoPhgwxIMuQ2lS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlyDnj%2FbtsJ13MKePi%2F3WduyGxJoPhgwxIMuQ2lS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;290&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내 답변이 없는 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상세페이지에서도 내 답변이 있는 경우에는 보여주고, 없는 경우에는 보여주지 않는다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Validation 적용해 예외사항 방어하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에서 구현한 투표 기능은 몇 가지 문제점이 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;첫째로, 만약 이미 답변 완료한 질문에 또 한번 답변을 하게 된다면 아래와 같은 에러가 발생한다. &lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQqlS/btsJ34Kt2qk/AfSPRIBvRWhkmVok36Pu9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQqlS/btsJ34Kt2qk/AfSPRIBvRWhkmVok36Pu9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQqlS/btsJ34Kt2qk/AfSPRIBvRWhkmVok36Pu9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQqlS%2FbtsJ34Kt2qk%2FAfSPRIBvRWhkmVok36Pu9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;119&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에서 설정한 규칙에 의해 1개의 계정은 1개의 질문에 대해 하나의 답변을 가질 수 있는게 맞긴 하지만, 에러 코드를 살펴보면 해당 규칙을 어겨서가 아닌, 서버 에러로 응답하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QQnKU/btsJ24xT9Im/JLuHYqurdxipsmoDz6vlK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QQnKU/btsJ24xT9Im/JLuHYqurdxipsmoDz6vlK0/img.png&quot; data-alt=&quot;500 서버 에러 코드를 응답하는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QQnKU/btsJ24xT9Im/JLuHYqurdxipsmoDz6vlK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQQnKU%2FbtsJ24xT9Im%2FJLuHYqurdxipsmoDz6vlK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;176&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;500 서버 에러 코드를 응답하는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이미 답변한 질문에 대해서 또 한번 답변을 하는 것은 서버 에러가 아닌 사용자의 책임이 있는 것이므로, 500대 에러코드가 아닌 400대 에러코드가 반환되어야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;둘째로, 각 질문에 속한 답변이 아닌 다른 질문의 답변도 선택해 저장할 수 있다는 것이다.&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O4J2e/btsJ290kEPs/qTl4yI3X5cMtWRXdyvtqek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O4J2e/btsJ290kEPs/qTl4yI3X5cMtWRXdyvtqek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O4J2e/btsJ290kEPs/qTl4yI3X5cMtWRXdyvtqek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO4J2e%2FbtsJ290kEPs%2FqTl4yI3X5cMtWRXdyvtqek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;749&quot; height=&quot;309&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 이미지에서 선택된 질문은 '야구 vs 축구'라는 질문이지만, 해당 질문의 답변 레코드인 '야구'와 '축구' 이외에도 다른 질문의 답변들이 선택 가능하다. 저장도 정상적으로 되는 모습을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 문제들은 Validation 이라는 개념을 활용하면 방어가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저 첫 번째 문제를 해결하기 위해 시리얼라이저에 validator를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728719528898&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/serializers.py

from rest_framework.validators import UniqueTogetherValidator

class VoteSerializer(serializers.ModelSerializer):
    voter = serializers.ReadOnlyField(source='voter.username')
    
    class Meta:
        model = Vote
        fields = ['id', 'question', 'choice', 'voter']
        # is_valid() 메서드가 호출될 때 유효성 검사방법을 정의
        validators = [
            UniqueTogetherValidator(
                queryset=Vote.objects.all(),
                fields=['voter', 'question']
            )
        ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Validator란, 투표 레코드를 create할 때 유효성 검사를 하는 부분인 is_valid() 메서드를 호출할 때 그 검사 방법을 정의하는 것이라고 볼 수 있다. UniqueTogetherValidator()를 사용해 Vote모델의 모든 object를 대상으로 'voter'와 'question'의 조합이 중복되지 않는지 검사하고, 중복될 경우 레코드 생성을 허가하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bftMkj/btsJ2RS7ftf/n1BVgT4cSbeMwPP96l9je0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bftMkj/btsJ2RS7ftf/n1BVgT4cSbeMwPP96l9je0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bftMkj/btsJ2RS7ftf/n1BVgT4cSbeMwPP96l9je0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbftMkj%2FbtsJ2RS7ftf%2Fn1BVgT4cSbeMwPP96l9je0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;552&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 첫 번째 문제와 같은 방법으로 POST 요청을 보냈을 때 더 이상 이전과 같은 에러가 발생하지 않고, 400 에러 코드가 응답되는 것을 볼 수 있다. 하지만 조금 이상하게 느껴지는게 있다. 응답 메시지를 살펴보면 voter 필드가 비어 있다고 출력되는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 이유는 현재 views.py의 VoteList 클래스에서는 CreateModelMixin 클래스의 perform_create() 메서드를 상속 받아 사용하는데, 이 메서드의 수행 순서가 is_valid() 메서드의 뒷 순서이기 때문에 ReadOnlyField로 설정된 Voter 필드가 전달되지 않는 것이다.(is_valid() 메서드에 사용하는 validated_data에 읽기 전용 필드는 포함되지 않음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;(아래 코드의 create, perform_create 메서드 참고)&lt;/p&gt;
&lt;pre id=&quot;code_1728719962158&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CreateModelMixin:
    &quot;&quot;&quot;
    Create a model instance.
    &quot;&quot;&quot;
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 'voter' 필드가 전달되지 않아 에러가 발생하는 방식이 아닌 의도한 바대로의 에러 응답을 내기 위해서는 create() 메서드를 상속받아 사용해야 하고, voter 필드를 읽기 전용 필드로 설정하지 않아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728720227543&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/views.py

class VoteList(generics.ListCreateAPIView):
    serializer_class = VoteSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def get_queryset(self):
        return Vote.objects.filter(voter=self.request.user)
    
    
    def create(self, request, *args, **kwargs):
        new_data = request.data.copy()
        new_data['voter'] = request.user.id
        serializer = self.get_serializer(data=new_data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
    
    def perform_create(self, serializer):
        # save 메서드는 주어진 필드를 모두 사용하므로 readonly와 관계없이 지정 가능
        serializer.save(voter=self.request.user)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재 request의 data를 복사한 new_data에 'voter'필드를 현재 요청을 보낸 사용자의 id로 덮어쓴다. 클라이언트가 제공한 값이 아닌 요청자의 id를 사용해 안전성을 확보하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728721992024&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class VoteSerializer(serializers.ModelSerializer):
    # voter = serializers.ReadOnlyField(source='voter.username')
    
    class Meta:
        model = Vote
        fields = ['id', 'question', 'choice', 'voter']
        # is_valid() 메서드가 호출될 때 유효성 검사방법을 정의
        validators = [
            UniqueTogetherValidator(
                queryset=Vote.objects.all(),
                fields=['voter', 'question']
            )
        ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 VoteSerializer의 voter 필드를 읽기 전용 필드로 설정한 부분을 주석처리해 validated_data에 voter 필드가 포함되도록 한다. 이제 유효성 검사가 정상적으로 작동할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EtdzI/btsJ3OA0pu4/pTChBjEEJtkXaHEe5xVVj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EtdzI/btsJ3OA0pu4/pTChBjEEJtkXaHEe5xVVj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EtdzI/btsJ3OA0pu4/pTChBjEEJtkXaHEe5xVVj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEtdzI%2FbtsJ3OA0pu4%2FpTChBjEEJtkXaHEe5xVVj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;612&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;서버에서 위와 같은 요청을 다시 보냈을 때, 해당 필드는 voter와 question이 unique해야 한다는 메시지가 노출된다. 의도한 바대로의 응답 메시지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러나 위 이미지에서 또 하나의 문제점이 발견된다. Voter 필드를 읽기 전용으로 설정하지 않았기 때문에, 계정이 따른 계정을 선택해 POST 요청을 진행할 수 있게 된 것이다. voter 필드를 읽기 전용으로 설정하지 않으면서, 수정하지 못하도록 하게 하는 방법은 없을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. HiddenField 이용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728722530178&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class VoteSerializer(serializers.ModelSerializer):
    # voter 필드를 자동으로 현재 사용자로 설정
    voter = serializers.HiddenField(default=serializers.CurrentUserDefault())
    
    class Meta:
        model = Vote
        fields = ['id', 'question', 'choice', 'voter']
        # is_valid() 메서드가 호출될 때 유효성 검사방법을 정의
        validators = [
            UniqueTogetherValidator(
                queryset=Vote.objects.all(),
                fields=['voter', 'question']
            )
        ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;첫 번째 방법은 VoteSerializer 클래스에서 voter 필드를 HiddenField로 설정해준 뒤, default값을 현재 로그인된 유저로 설정하는 것이다. 이렇게 되면 voter 필드를 수정할 수 없으면서 전달되는 voter 값은 현재 로그인된 유저가 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. perform_update() 메서드 이용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728722639785&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class VoteList(generics.ListCreateAPIView):
    serializer_class = VoteSerializer
    # IsAuthenticated 클래스는 인증된 사용자만 투표 가능하고 볼 수 있음
    permission_classes = [permissions.IsAuthenticated]
    
    # 투표 목록을 가져올 때 투표자가 인증된 현재 사용자인 경우만 가져옴
    def get_queryset(self):
        return Vote.objects.filter(voter=self.request.user)
    
    def create(self, request, *args, **kwargs):
        new_data = request.data.copy()
        new_data['voter'] = request.user.id
        serializer = self.get_serializer(data=new_data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
    
    def perform_create(self, serializer):
        # save 메서드는 주어진 필드를 모두 사용하므로 readonly와 관계없이 지정 가능
        serializer.save(voter=self.request.user)
    
class VoteDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Vote.objects.all()
    serializer_class = VoteSerializer
    permission_classes = [permissions.IsAuthenticated, IsVoteOwner]
    
    def perform_update(self, serializer):
        # save 메서드는 주어진 필드를 모두 사용하므로 readonly와 관계없이 지정 가능
        serializer.save(voter=self.request.user)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;VoteList에서는 투표 레코드를 생성할 경우 perform_create() 메서드에 의해 현재 요청한 User 정보가 강제로 지정되므로 Voter 필드를 수정하는 의미가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 VoteDetail에서는 해당 메서드가 없었기 때문에, 실제로도 수정이 가능했다. 이를 perform_update 메서드를 상속받아 save할 때 voter를 요청한 계정 정보로 강제 지정해 문제를 해결하는 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;각 질문에 속한 답변이 아닌 다른 질문의 답변도 선택해 저장 가능한 문제 - 해결 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;드디어 두 번째 문제를 해결할 때가 왔다. 이전에 RestfulAPI에서 계정을 생성할 때 비밀번호 확인을 했던 절차가 기억나는가?(&lt;a href=&quot;https://minding-deep-learning.tistory.com/243&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;User생성 관련 링크&lt;/a&gt;) 그 때 사용했던 validate() 메서드를 다시 상속받아 여기서 이용해 보도록 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728723077756&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/serializers.py

class VoteSerializer(serializers.ModelSerializer):
    # voter 필드를 자동으로 현재 사용자로 설정
    voter = serializers.HiddenField(default=serializers.CurrentUserDefault())
    
    def validate(self, attrs):
        if attrs['choice'].question.id != attrs['question'].id:
            raise serializers.ValidationError(&quot;선택한 질문과 투표한 질문에 대한 답변이 일치하지 않습니다.&quot;)
        return attrs
    
    class Meta:
        model = Vote
        fields = ['id', 'question', 'choice', 'voter']
        # is_valid() 메서드가 호출될 때 유효성 검사방법을 정의
        validators = [
            UniqueTogetherValidator(
                queryset=Vote.objects.all(),
                fields=['voter', 'question']
            )
        ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;POST 요청한 답변의 question.id와 질문의 id가 일치하지 않으면 에러를 발생시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGHV8d/btsJ4aDGuIF/jFDrwNxn1jWe7G0DhFHbL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGHV8d/btsJ4aDGuIF/jFDrwNxn1jWe7G0DhFHbL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGHV8d/btsJ4aDGuIF/jFDrwNxn1jWe7G0DhFHbL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGHV8d%2FbtsJ4aDGuIF%2FjFDrwNxn1jWe7G0DhFHbL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;763&quot; height=&quot;565&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 서로 질문id가 다른 질문과 답변을 POST했을 때, 위와 같은 에러 메시지와 함께 400 에러코드가 노출되는 것을 확인할 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/Django</category>
      <category>django</category>
      <category>django python</category>
      <category>django validation</category>
      <category>django vote</category>
      <category>vote user</category>
      <category>vote 계정</category>
      <category>vote 생성</category>
      <category>파이썬 장고</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/246</guid>
      <comments>https://minding-deep-learning.tistory.com/246#entry246comment</comments>
      <pubDate>Fri, 11 Oct 2024 17:09:37 +0900</pubDate>
    </item>
    <item>
      <title>[Django] RelatedField</title>
      <link>https://minding-deep-learning.tistory.com/245</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RelatedField&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. PrimaryKeyRelatedField&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8vkuO/btsJ2IHtKYw/p6ctuU4xRWbtXv0UTZw780/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8vkuO/btsJ2IHtKYw/p6ctuU4xRWbtXv0UTZw780/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8vkuO/btsJ2IHtKYw/p6ctuU4xRWbtXv0UTZw780/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8vkuO%2FbtsJ2IHtKYw%2Fp6ctuU4xRWbtXv0UTZw780%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;239&quot; height=&quot;442&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재 User List를 살펴보면, 각 유저의 question 필드에 질문 레코드의 id값이 표시되는 것을 확인할 수 있다. 이것은 serializers.py에서 UserSerializer를 설정할 때 PrimaryKeyRelatedField를 사용했기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1728623629367&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/serializers.py

class UserSerializer(serializers.ModelSerializer):
    # questions 필드를 명시하는 이유: User 모델과 Question 모델 간의 관계를 보여주기 위함
    questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;PK-FK 관계로 이어진 값을 나타내기 때문에, 해당 필드에 id값이 표시되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.&lt;span&gt; StringRelatedField&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;id값이 아닌 __str__() 함수에 의해 표시되는 문자열로 표시하게 할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728623759066&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# User 모델과 Question 모델 간의 관계를 보여주는 Serializer
class UserSerializer(serializers.ModelSerializer):
    questions = serializers.StringRelatedField(many=True, read_only=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ACPrb/btsJ1J1q12p/TK13CLw5mpx4ypAA4MKWx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ACPrb/btsJ1J1q12p/TK13CLw5mpx4ypAA4MKWx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ACPrb/btsJ1J1q12p/TK13CLw5mpx4ypAA4MKWx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FACPrb%2FbtsJ1J1q12p%2FTK13CLw5mpx4ypAA4MKWx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;588&quot; height=&quot;436&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;StringRelatedField를 사용하면 Model의 __str__() 함수로 표현되는 문자열로 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.&lt;span&gt;&lt;span&gt; SlugRelatedField&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;&lt;span&gt;자식 모델이 가지고 있는 필드 중 원하는 필드를 하나 골라서 출력하는 방법도 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728623902823&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserSerializer(serializers.ModelSerializer):
    questions = serializers.SlugRelatedField(many=True, slug_field='pub_date', read_only=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VUv1D/btsJ1IVRHNH/a3KMKAuhw6Wcm2QkszBt90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VUv1D/btsJ1IVRHNH/a3KMKAuhw6Wcm2QkszBt90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VUv1D/btsJ1IVRHNH/a3KMKAuhw6Wcm2QkszBt90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVUv1D%2FbtsJ1IVRHNH%2Fa3KMKAuhw6Wcm2QkszBt90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;283&quot; height=&quot;123&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Question 모델의 'pub_date' 필드를 출력하도록 했다. SlugRelatedField를 사용하면 이처럼 원하는 필드를 출력하게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.&lt;span&gt;&lt;span&gt;&lt;span&gt; HyperlinkedRelatedField&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;해당 모델의 상세 페이지로 이동할 수 있는 하이퍼링크를 출력하게 할 수도 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728624028269&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserSerializer(serializers.ModelSerializer):
    questions = serializers.HyperlinkedRelatedField(many=True, view_name='question-detail', read_only=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBsZPW/btsJ1cC56D8/0tg6s3z82Kgl6KDhN4h9p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBsZPW/btsJ1cC56D8/0tg6s3z82Kgl6KDhN4h9p1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBsZPW/btsJ1cC56D8/0tg6s3z82Kgl6KDhN4h9p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBsZPW%2FbtsJ1cC56D8%2F0tg6s3z82Kgl6KDhN4h9p1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;449&quot; height=&quot;132&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;HyperlinkedRelatedField를 사용하면 위처럼 하이퍼링크를 넣을 수 있다. view_name은 urls.py에서 지정한 name을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728624169118&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 참고 urls.py

from django.urls import path, include
from .views import *

urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    path('question/&amp;lt;int:pk&amp;gt;/', QuestionDetail.as_view(), name='question-detail'),
    ...
    ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Related_name 이용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모델에서 FK로 상속받는 필드에 related_name을 지정하면 상위 모델에서 그 이름으로 역참조를 할 수 있다는 것을 기억하는가? (&lt;a href=&quot;https://minding-deep-learning.tistory.com/243&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;User-Question 모델 사이 연결&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Choice 모델의 question 필드에도 related_name을 지정해 QuestionSerializer에서 사용할 수 있도록 하려고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728624679865&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysite/polls/models.py

class Choice(models.Model):
    # 어떤 질문에 대한 답인지 알아볼 수 있도록 Question 클래스에서 FK를 받아옴
    question = models.ForeignKey(Question, related_name='choices', on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    # 답변의 빈도를 세기 위한 정수 필드
    votes = models.IntegerField(default=0)

    # 문자열을 표시할 때 question_text를 반환
    def __str__(self):
        return f'[{self.question.question_text}] {self.choice_text}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;choices 라는 이름으로 related_name을 지정해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 시리얼라이저에 choices가 표시되도록 설정해보자. 우선 ChoiceSerializer부터 생성한 뒤, 그 클래스를 사용해 choices를 QuestionSerializer에서 불러올 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1728624762142&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ChoiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Choice
        fields = ['choice_text', 'votes']

# Question 모델을 보여주는 Serializer
class QuestionSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    choices = ChoiceSerializer(many=True, read_only=True)

    class Meta:
        model = Question
        fields = ['id', 'question_text', 'pub_date', 'owner', 'choices']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 코드를 수정한 뒤 Question_list를 다시 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgFdeW/btsJ3hWPXpj/EQiU0Mv5Vykw3s7yio60V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgFdeW/btsJ3hWPXpj/EQiU0Mv5Vykw3s7yio60V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgFdeW/btsJ3hWPXpj/EQiU0Mv5Vykw3s7yio60V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgFdeW%2FbtsJ3hWPXpj%2FEQiU0Mv5Vykw3s7yio60V0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;762&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 해당 질문 레코드에 관계된 답변(choices)이 모두 출력되는 것을 볼 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/Django</category>
      <category>django</category>
      <category>django python</category>
      <category>django relatedfield</category>
      <category>Python</category>
      <category>related_name</category>
      <category>장고</category>
      <category>장고 파이썬</category>
      <category>파이썬</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/245</guid>
      <comments>https://minding-deep-learning.tistory.com/245#entry245comment</comments>
      <pubDate>Fri, 11 Oct 2024 14:34:31 +0900</pubDate>
    </item>
    <item>
      <title>[POSTMAN] POSTMAN으로 API 호출해보기</title>
      <link>https://minding-deep-learning.tistory.com/244</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;포스트맨 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.postman.com/downloads/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.postman.com/downloads/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728620411058&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Download Postman | Get Started for Free&quot; data-og-description=&quot;Try Postman for free! Join 30 million developers who rely on Postman, the collaboration platform for API development. Create better APIs&amp;mdash;faster.&quot; data-og-host=&quot;www.postman.com&quot; data-og-source-url=&quot;https://www.postman.com/downloads/&quot; data-og-url=&quot;https://www.postman.com/downloads/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cPIMRn/hyXhQkmtqG/pWiiWNAXQm6w1kWaHLaD3k/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/CQRpu/hyXhSJe4Ty/vlMlhoTHRbTXqdhPtGqsXk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.postman.com/downloads/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.postman.com/downloads/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cPIMRn/hyXhQkmtqG/pWiiWNAXQm6w1kWaHLaD3k/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/CQRpu/hyXhSJe4Ty/vlMlhoTHRbTXqdhPtGqsXk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Download Postman | Get Started for Free&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Try Postman for free! Join 30 million developers who rely on Postman, the collaboration platform for API development. Create better APIs&amp;mdash;faster.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.postman.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;POSTMAN으로 API 호출해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;POSTMAN은 RESTful API 테스트를 위한 플랫폼으로, 다양한 HTTP 요청을 보내고 응답 결과를 쉽게 확인할 수 있도록 도와준다. 또한, API 요청과 응답 결과를 저장하고 공유할 수 있는 기능도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 글에서는 이전에 Django로 구현한 RESTful API(&lt;a href=&quot;https://minding-deep-learning.tistory.com/233&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;해당 링크 이후 글 부터&lt;/a&gt;)를 POSTMAN으로 호출해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;POSTMAN에 접속해 HTTP Request 창을 열어본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;807&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqyo6p/btsJ1NWGk6Y/ygKNkJyPZxvIDzOWG8njo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqyo6p/btsJ1NWGk6Y/ygKNkJyPZxvIDzOWG8njo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqyo6p/btsJ1NWGk6Y/ygKNkJyPZxvIDzOWG8njo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqyo6p%2FbtsJ1NWGk6Y%2FygKNkJyPZxvIDzOWG8njo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1294&quot; height=&quot;807&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;807&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같은 화면이 노출된다. 여기서 상세페이지에 대한 PUT 요청을 진행해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eIfVDO/btsJ0SqUmoT/QxTYZbcPG4uiuq9YzrYKS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eIfVDO/btsJ0SqUmoT/QxTYZbcPG4uiuq9YzrYKS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eIfVDO/btsJ0SqUmoT/QxTYZbcPG4uiuq9YzrYKS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeIfVDO%2FbtsJ0SqUmoT%2FQxTYZbcPG4uiuq9YzrYKS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;382&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;django 서버에서는 위와 같은 화면으로, 특징으로는 로그인하지 않거나 해당 질문 레코드의 owner가 아니라면 PUT, DELETE 같은 요청을 할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 POSTMAN에 위 URL과 함께 PUT 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xgrX3/btsJ24JPwOX/zTnboBIEiiqPgzjzD37VuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xgrX3/btsJ24JPwOX/zTnboBIEiiqPgzjzD37VuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xgrX3/btsJ24JPwOX/zTnboBIEiiqPgzjzD37VuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxgrX3%2FbtsJ24JPwOX%2FzTnboBIEiiqPgzjzD37VuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;849&quot; height=&quot;243&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 이미지와 같이 HTTP Method는 PUT으로 바꿔준 뒤 링크를 붙여넣는다. 또한 Headers 항목에 진입해 Key와 Value에 각각 'Content-Type', 'application/json'을 선택해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVgRpy/btsJ1PmJugG/32VGXiX1lf3mc2xbPv9h5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVgRpy/btsJ1PmJugG/32VGXiX1lf3mc2xbPv9h5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVgRpy/btsJ1PmJugG/32VGXiX1lf3mc2xbPv9h5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVgRpy%2FbtsJ1PmJugG%2F32VGXiX1lf3mc2xbPv9h5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;145&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Body에는 위와 같은 폼에 담아 전송했다.&lt;/p&gt;
&lt;pre id=&quot;code_1728621165820&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;detail&quot;: &quot;Authentication credentials were not provided.&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;당연하게도 돌아온 메시지는 403 에러코드와 함께 권한이 없다는 뜻의 메시지였다. 로그인 정보를 전달해주지 않았기 때문이다. 그 로그인 정보는 어떻게 전달해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u3Vz7/btsJ0Vacgdt/fOKRDYLEQkAcGQq6JkOkS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u3Vz7/btsJ0Vacgdt/fOKRDYLEQkAcGQq6JkOkS1/img.png&quot; data-alt=&quot;로그인 하지 않았을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u3Vz7/btsJ0Vacgdt/fOKRDYLEQkAcGQq6JkOkS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu3Vz7%2FbtsJ0Vacgdt%2FfOKRDYLEQkAcGQq6JkOkS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;403&quot; height=&quot;70&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로그인 하지 않았을 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/en9GIf/btsJ1gdVRm1/3VoOVZjDgkdIagYmbePXKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/en9GIf/btsJ1gdVRm1/3VoOVZjDgkdIagYmbePXKK/img.png&quot; data-alt=&quot;로그인 했을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/en9GIf/btsJ1gdVRm1/3VoOVZjDgkdIagYmbePXKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fen9GIf%2FbtsJ1gdVRm1%2F3VoOVZjDgkdIagYmbePXKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;414&quot; height=&quot;98&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로그인 했을 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 이미지는 Django 서버에서 로그인 했을 때와 하지 않았을 때의 쿠키를 비교한 것이다. 2개의 차이는 바로 'sessionid'라는 값으로, 해당 쿠키가 로그인 정보를 나타낸다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;sessionid에 해당하는 Value값을 복사해 로그인 정보를 함께 넣어 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eDNGXg/btsJ1gSyVoq/6nhhVVJtOHK4oiWqRjf2a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eDNGXg/btsJ1gSyVoq/6nhhVVJtOHK4oiWqRjf2a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eDNGXg/btsJ1gSyVoq/6nhhVVJtOHK4oiWqRjf2a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeDNGXg%2FbtsJ1gSyVoq%2F6nhhVVJtOHK4oiWqRjf2a0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;850&quot; height=&quot;228&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Header에 key는 'Cookie', Value에는 'sessionid={복사한 sessionid의 value값}'을 넣어 다시 한번 요청을 보내본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728621693684&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;detail&quot;: &quot;CSRF Failed: CSRF cookie not set.&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번에는 CSRF 토큰이 없다는 메시지가 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Django 서버에서 쿠키를 살펴봤을 때, csrf token이라는 쿠키가 있었다는 것을 기억하는가? sessionid의 value값을 가져올 때처럼 csrf token의 value를 다시 가져와 header에 추가해주자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FTAxz/btsJ2Wyu0WF/1KqeSKm8Oju7SFgp4RgPtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FTAxz/btsJ2Wyu0WF/1KqeSKm8Oju7SFgp4RgPtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FTAxz/btsJ2Wyu0WF/1KqeSKm8Oju7SFgp4RgPtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFTAxz%2FbtsJ2Wyu0WF%2F1KqeSKm8Oju7SFgp4RgPtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;839&quot; height=&quot;214&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212121; text-align: left;&quot;&gt;X-CSRFToken, &lt;span style=&quot;background-color: #ffffff; color: #212121; text-align: left;&quot;&gt;Cookie 두 가지를 추가해주었다. 이후 Send를 눌러 요청을 보내면,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728621926051&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;id&quot;: 11,
    &quot;owner&quot;: &quot;user1&quot;,
    &quot;question_text&quot;: &quot;POSTMAN에서 보냄&quot;,
    &quot;pub_date&quot;: &quot;2024-10-11T02:20:09.054461Z&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212121; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212121; text-align: left;&quot;&gt;위와 같은 메시지가 노출되며 200 응답 코드가 돌아오는걸 확인할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kAjnM/btsJ295offF/POEj3YwjHncKnaWQ0nubXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kAjnM/btsJ295offF/POEj3YwjHncKnaWQ0nubXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kAjnM/btsJ295offF/POEj3YwjHncKnaWQ0nubXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkAjnM%2FbtsJ295offF%2FPOEj3YwjHncKnaWQ0nubXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;544&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Django 서버에서도 정상적으로 반영된 것을 확인할 수 있다.&lt;/p&gt;</description>
      <category>Minding's Programming/Knowledge</category>
      <category>django</category>
      <category>HTTP</category>
      <category>Postman</category>
      <category>restful api</category>
      <category>포스트맨</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/244</guid>
      <comments>https://minding-deep-learning.tistory.com/244#entry244comment</comments>
      <pubDate>Fri, 11 Oct 2024 13:55:22 +0900</pubDate>
    </item>
    <item>
      <title>[Django] User 항목 모델에 추가 / User 관리 / User생성 / User 권한</title>
      <link>https://minding-deep-learning.tistory.com/243</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모델에 User 항목 추가하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;User는 기본적으로 contrib.auth를 통해 관리된다. 해당 라이브러리 안의 Users라는 모델을 불러와 User가 어떤 형식으로 구성되어 있는지 shell을 통해 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728546015233&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from django.contrib.auth.models import User
&amp;gt;&amp;gt;&amp;gt; User
&amp;lt;class 'django.contrib.auth.models.User'&amp;gt;
&amp;gt;&amp;gt;&amp;gt; User._meta.get_fields()
(&amp;lt;ManyToOneRel: admin.logentry&amp;gt;, &amp;lt;django.db.models.fields.AutoField: id&amp;gt;, &amp;lt;django.db.models.fields.CharField: password&amp;gt;, &amp;lt;django.db.models.fields.DateTimeField: last_login&amp;gt;, &amp;lt;django.db.models.fields.BooleanField: is_superuser&amp;gt;, &amp;lt;django.db.models.fields.CharField: username&amp;gt;, &amp;lt;django.db.models.fields.CharField: first_name&amp;gt;, &amp;lt;django.db.models.fields.CharField: last_name&amp;gt;, &amp;lt;django.db.models.fields.EmailField: email&amp;gt;, &amp;lt;django.db.models.fields.BooleanField: is_staff&amp;gt;, &amp;lt;django.db.models.fields.BooleanField: is_active&amp;gt;, &amp;lt;django.db.models.fields.DateTimeField: date_joined&amp;gt;, &amp;lt;django.db.models.fields.related.ManyToManyField: groups&amp;gt;, &amp;lt;django.db.models.fields.related.ManyToManyField: user_permissions&amp;gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;User가 가지고 있는 필드는 매우 많다. id와 password부터 슈퍼유저 여부, 이메일 등등 다양한 필드를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재 User의 리스트를 먼저 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728546118567&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; User.objects.all()
&amp;lt;QuerySet [&amp;lt;User: admin&amp;gt;]&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재는 이전에 만들어 두었던 관리자 계정인 admin만 남아있는 상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 유저(admin)를 Question 모델에서도 사용할 수 있게 하려면, 해당 모델에 user의 정보를 알려주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728546317063&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls/models.py

class Question(models.Model):
    question_text = models.CharField(max_length=200, verbose_name='질문')
    pub_date = models.DateTimeField(auto_now_add=True, verbose_name='생성일')
    # 해당 질문 레코드 작성자, User에서 FK로 가져온 값임 1(user):N(Question)의 관계
    owner = models.ForeignKey('auth.User', related_name='questions', on_delete=models.CASCADE, null=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;owner 정보를 모델에 추가해주었다. key의 이름은 auth.User이며, on_delete=models.CASCADE 옵션을 넣어주어 해당 계정이 삭제될 경우 관련된 모든 질문 레코드도 삭제하라는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 모델이 변경되었으니 마이그레이션을 진행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728546539630&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;❯ python manage.py makemigrations
Migrations for 'polls':
  polls/migrations/0002_question_owner_alter_question_pub_date_and_more.py
    - Add field owner to question
    - Alter field pub_date on question
    - Alter field question_text on question
    
❯ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0002_question_owner_alter_question_pub_date_and_more... OK&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 shell을 통해 user와 question 모델이 어떻게 연결되었는지 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728546801353&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 라이브러리 임포트
&amp;gt;&amp;gt;&amp;gt; from django.contrib.auth.models import User
&amp;gt;&amp;gt;&amp;gt; from polls.models import *

# User 중 첫번째 선택
&amp;gt;&amp;gt;&amp;gt; user = User.objects.first()

# Question 모델에서 related_name으로 사용한 것을 메서드로 사용
&amp;gt;&amp;gt;&amp;gt; user.questions.all()
&amp;lt;QuerySet []&amp;gt;

# 유저에 연결된 질문을 찾는 쿼리 출력
&amp;gt;&amp;gt;&amp;gt; print(user.questions.all().query)
SELECT &quot;polls_question&quot;.&quot;id&quot;, &quot;polls_question&quot;.&quot;question_text&quot;, &quot;polls_question&quot;.&quot;pub_date&quot;, &quot;polls_question&quot;.&quot;owner_id&quot; FROM &quot;polls_question&quot; WHERE &quot;polls_question&quot;.&quot;owner_id&quot; = 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;User 중 하나를 불러와 그에 연결된 질문을 찾을 때는 위 models.py에서 작성했던 realated_name을 사용하면 알 수 있다. 위와 같이 realted_name 파라미터를 지정해준다면, shell에서 해당 이름으로 FK를 연결해 찾을 수 있다. (지정해주지 않을 경우 모델 이름 + _set으로 찾음 (ex)Question.choice_set.all() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 레코드를 찾는 쿼리를 살펴보면, 유저의 id를 이용해 조건에 맞는 레코드를 찾는다는 것을 알 수 있다. 이는 마치 Question에 연결된 choice를 불러올 때와 비슷한 모습을 보인다.&lt;/p&gt;
&lt;pre id=&quot;code_1728547266098&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# user 정보로 question 목록 불러올 때
&amp;gt;&amp;gt;&amp;gt; print(user.questions.all().query)
SELECT &quot;polls_question&quot;.&quot;id&quot;, &quot;polls_question&quot;.&quot;question_text&quot;, &quot;polls_question&quot;.&quot;pub_date&quot;, &quot;polls_question&quot;.&quot;owner_id&quot; FROM &quot;polls_question&quot; WHERE &quot;polls_question&quot;.&quot;owner_id&quot; = 1

# question 정보로 choice 목록 불러올 때
&amp;gt;&amp;gt;&amp;gt; print(q.choice_set.all().query)
SELECT &quot;polls_choice&quot;.&quot;id&quot;, &quot;polls_choice&quot;.&quot;question_id&quot;, &quot;polls_choice&quot;.&quot;choice_text&quot;, &quot;polls_choice&quot;.&quot;votes&quot; FROM &quot;polls_choice&quot; WHERE &quot;polls_choice&quot;.&quot;question_id&quot; = 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;User 관리 기능 구현&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;User 모델을 Serializer로 만들고 view로 구현하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;serializer.py에 User 모델을 위한 시리얼라이저를 새롭게 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728547757172&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserSerializer(serializers.ModelSerializer):
    questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())

    class Meta:
        model = User
        fields = ['id', 'username', 'questions']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기서 PrimaryKeyRelatedField는 해당 모델의 PK를 통해서 연결된 항목이 있을 경우 사용한다. 1:N의 관계일 경우 many라는 파라미터를 True로 전달한다. 위 예시에서는 1명의 User에 대해서 N개의 Question을 가질 수 있는 관계이므로 위와 같이 코드를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 명시되어야 하는 이유는 해당 정보가 User 모델 내 있는 정보가 아닌, Question 모델에 있기 때문이다. 즉, 시리얼라이저 입장에서 다른 모델의 테이블을 살펴봐야 하므로 위와 같이 명시해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 view를 구현해보자. 이전에 사용했던 generics의 ListCreateAPIView와 RetrieveUpdateDestroyAPIView를 이용해 GET,POST,PUT,DELETE가 가능한 view를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728548236450&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/views.py

from rest_framework import status, mixins, generics
from polls.models import Question
from .serializers import QuestionSerializer, UserSerializer
from django.contrib.auth.models import User

class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

class UserDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;view를 추가했다면 당연히 URL도 추가해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728548330881&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.urls import path
from . import views

app_name = 'polls_api'

urlpatterns = [
    path('question/', views.QuestionList.as_view(), name='question_list'),
    path('question/&amp;lt;int:pk&amp;gt;/', views.QuestionDetail.as_view(), name='question_detail'),
    # user 관련 URL 추가
    path('users/', views.UserList.as_view(), name='user_list'),
    path('users/&amp;lt;int:pk&amp;gt;/', views.UserDetail.as_view(), name='user_detail'),
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 해당 URL로 접속해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA5A73/btsJ0aSqOdB/VCnRZkhYpMvC8s4SjuMrp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA5A73/btsJ0aSqOdB/VCnRZkhYpMvC8s4SjuMrp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA5A73/btsJ0aSqOdB/VCnRZkhYpMvC8s4SjuMrp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA5A73%2FbtsJ0aSqOdB%2FVCnRZkhYpMvC8s4SjuMrp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;704&quot; height=&quot;728&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;User List라는 이름으로 현재 등록된 User 목록이 노출되는 것을 볼 수 있다. 현재 User는 Admin 1개이자, 가지고 있는 question이 없는 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;일반 User 생성하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 Admin 계정은 Django 설정 시 만들었던 슈퍼계정(관리자)이기 때문에, 각각의 글을 쓸 수 있는 일반 User를 생성해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Form을 사용해 일반 User 생성하기 (django 제공 기능 활용)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일반적인 회원가입 페이지처럼 User에게 정보를 폼(form)으로 제공받아보자. polls 앱의 views.py를 열어 다음과 같은 클래스를 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1728549250755&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls/views.py

from django.views import generic # rest framework에서 사용한 generics와 비슷한 역할
from django.urls import reverse_lazy
from django.contrib.auth.forms import UserCreationForm # django 기본 제공 회원가입 폼

...
class SignUpView(generic.CreateView): 
    form_class = UserCreationForm
    # reverse_lazy: urls.py에서 정의한 이름을 기반으로 URL을 동적으로 생성
    # polls_api의 user_list로 해당 정보를 보내줌
    success_url = reverse_lazy('user-list')
    template_name = 'polls/signup.html'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Django에서 기본으로 제공하는 회원가입 폼과 generic의 CreateView를 상속받는다. 입력된 정보는 polls_api의 user_list로 전달되어 POST된다. 템플릿으로는 signup.html을 사용하는데, 아래와 같이 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728549325138&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- mysite/polls/templates/polls/signup.html --&amp;gt;

&amp;lt;h2&amp;gt;회원가입&amp;lt;/h2&amp;gt;
&amp;lt;form method=&quot;post&quot;&amp;gt;
    &amp;lt;!-- csrf_token: 보안을 위해 필요 --&amp;gt;
    {% csrf_token %}
    {{ form.as_p }}
    &amp;lt;button type=&quot;submit&quot;&amp;gt;회원가입&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 화면을 보여줄 url도 등록해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728549443372&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.urls import path
from . import views

app_name = 'polls'

urlpatterns = [
    path('', views.index, name='index'),
    path('&amp;lt;int:question_id&amp;gt;/', views.detail, name='detail'),
    path('&amp;lt;int:question_id&amp;gt;/vote/', views.vote, name='vote'), 
    path('&amp;lt;int:question_id&amp;gt;/results/', views.results, name='results'),
    # 회원가입 페이지 URL 추가
    path('signup/', views.SignUpView.as_view(), name='signup'),
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 해당 URL('polls/signup')에 접속하면 아래와 같은 화면이 노출된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pW061/btsJ04quQqk/1eRv3Ukk1zhvSJuX5UPbvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pW061/btsJ04quQqk/1eRv3Ukk1zhvSJuX5UPbvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pW061/btsJ04quQqk/1eRv3Ukk1zhvSJuX5UPbvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpW061%2FbtsJ04quQqk%2F1eRv3Ukk1zhvSJuX5UPbvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;821&quot; height=&quot;341&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ID, PW, PW 확인란이 기본으로 제공되며 기본적인 규칙 또한 설정되어 있는 모습을 볼 수 있다. 해당 정보를 입력해 회원가입을 완료하고 나면 User가 추가되었는지 알 수 있도록 User list 화면으로 이동하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dckN1n/btsJ0Cnxi4l/wYE1LBL1Og7Zc9qs5vTKg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dckN1n/btsJ0Cnxi4l/wYE1LBL1Og7Zc9qs5vTKg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dckN1n/btsJ0Cnxi4l/wYE1LBL1Og7Zc9qs5vTKg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdckN1n%2FbtsJ0Cnxi4l%2FwYE1LBL1Og7Zc9qs5vTKg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;680&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Serializer를 사용하여 User 생성하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;시리얼라이저를 이용하면 REST API를 통해서도 User를 생성할 수 있다. 일단 User를 생성하는 역할을 하는 시리얼라이저를 생성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728608886560&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/serializer.py

# User 모델을 등록하는 Serializer
class RegisterSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username', 'password']
        # write_only: 비밀번호 필드를 읽기 전용으로 설정
        extra_kwargs = {'password': {'write_only': True}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 시리얼라이저를 만들어준다. 특히 password 필드의 경우 읽지 못하도록 가리는 옵션인 'write_only'를 True로 전달해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그 다음 views.py로 이동해 이 시리얼라이저를 이용할 수 있도록 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728609001396&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/views.py

from .serializers import QuestionSerializer, UserSerializer, RegisterSerializer

class RegisterView(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = RegisterSerializer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CreateAPIView를 사용해 POST 메서드를 가진 View를 생성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;마지막으로 urls.py에서 해당 view가 작동될 URL을 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728609091834&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from django.urls import path, include
from .views import *

urlpatterns = [
    ...
    path('register/', RegisterView.as_view(), name='register'),
    ...
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이름을 register로 갖는 URL을 하나 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 서버를 열어 해당 URL로 진입해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ugfy9/btsJ1GpzrvD/C3eP524E5Zkh04QkU974Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ugfy9/btsJ1GpzrvD/C3eP524E5Zkh04QkU974Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ugfy9/btsJ1GpzrvD/C3eP524E5Zkh04QkU974Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fugfy9%2FbtsJ1GpzrvD%2FC3eP524E5Zkh04QkU974Fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;564&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;등록화면이 정상적으로 노출되는 것을 알 수 있다. User list가 노출되는 부분에 &quot;Method \&quot;GET\&quot; not allowed&quot;라는 메시지가 출력되는데, 이는 위에서 view를 CreateAPIView로 구성했기 때문이다. GET까지 사용하려면 ListCreateAPIView를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpKue4/btsJ1e02dFE/A3Q33FfB6zMaC2i6JvkSYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpKue4/btsJ1e02dFE/A3Q33FfB6zMaC2i6JvkSYk/img.png&quot; data-alt=&quot;ListCreateAPIView를 사용한 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpKue4/btsJ1e02dFE/A3Q33FfB6zMaC2i6JvkSYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpKue4%2FbtsJ1e02dFE%2FA3Q33FfB6zMaC2i6JvkSYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;699&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;699&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ListCreateAPIView를 사용한 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 Username과 Password를 입력해 등록이 잘 되는지 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SeFor/btsJ1DTTXaj/JOImLS0O8cyDPM6iqLPbnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SeFor/btsJ1DTTXaj/JOImLS0O8cyDPM6iqLPbnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SeFor/btsJ1DTTXaj/JOImLS0O8cyDPM6iqLPbnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSeFor%2FbtsJ1DTTXaj%2FJOImLS0O8cyDPM6iqLPbnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;267&quot; height=&quot;171&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;정상적으로 잘 구현된다. 만약, username이 같은 User를 한번 더 생성하려고 하면 어떻게될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/91LRp/btsJ1LEjqMU/EZTJRvqf9MIRAp7i8rTnmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/91LRp/btsJ1LEjqMU/EZTJRvqf9MIRAp7i8rTnmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/91LRp/btsJ1LEjqMU/EZTJRvqf9MIRAp7i8rTnmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F91LRp%2FbtsJ1LEjqMU%2FEZTJRvqf9MIRAp7i8rTnmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;756&quot; height=&quot;376&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400 에러코드가 반환되며 이미 해당 username을 사용하는 유저가 있다고 한다. 이는 User 모델 자체에서 username이 중복되는 User가 생성되지 못하도록 설정해 놓았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한 시리얼라이저를 이용해 유저를 생성할 경우, Password에 아무런 규칙이 없어 아주 간단한 비밀번호도 설정할 수 있다.(ex. 12345678) 위에서 실습한 Form의 경우 기본적으로 설정된 규칙이 있었지만, 이 방법을 이용할 경우 보안 문제가 발생할 우려가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;비밀번호 규칙 설정을 위해서는 시리얼라이저의 설정을 약간 수정해주어야 한다. 그리고 비밀번호를 2차 확인하는 함수까지 넣어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728609915337&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/serializers.py

# 비밀번호를 검사하는 메서드 임포트
from django.contrib.auth.password_validation import validate_password

...
# User 모델을 등록하는 Serializer
class RegisterSerializer(serializers.ModelSerializer):
    # password 필드를 validate_password 함수를 사용하여 유효성 검사
    password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
    password2 = serializers.CharField(write_only=True, required=True)

    # password와 password2가 일치하는지 검사
    def validate(self, attrs):
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({&quot;password&quot;: &quot;Password fields didn't match.&quot;})
        return attrs
    
    class Meta:
        model = User
        fields = ['username', 'password', 'password2']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;validate_password라는 함수를 불러와 password 필드에 기본 규칙을 적용해 유효성 검사를 하도록 만들었다. 또한, password2 필드를 통해 비밀번호 확인하는 함수까지 생성했다. validate에 전달되는 attrs는 시리얼라이저에 전달된 데이터를 포함하는 딕셔너리로, password와 password2 데이터가 포함되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 서버를 열어 실행해보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5vIY4/btsJ080VQeu/NRyVJumlb3gff6Y67yeHK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5vIY4/btsJ080VQeu/NRyVJumlb3gff6Y67yeHK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5vIY4/btsJ080VQeu/NRyVJumlb3gff6Y67yeHK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5vIY4%2FbtsJ080VQeu%2FNRyVJumlb3gff6Y67yeHK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;742&quot; height=&quot;357&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfDSqZ/btsJ11mBocT/JgBggRXGthK1NBopUlN8BK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfDSqZ/btsJ11mBocT/JgBggRXGthK1NBopUlN8BK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfDSqZ/btsJ11mBocT/JgBggRXGthK1NBopUlN8BK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfDSqZ%2FbtsJ11mBocT%2FJgBggRXGthK1NBopUlN8BK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;728&quot; height=&quot;315&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같이 비밀번호 규칙과 확인란이 잘 동작하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러나 모든 규칙을 잘 지킨 상태로 POST를 요청하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqyywH/btsJ2KxLwgz/dxlC5f7bNUo62IEXmRyed0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqyywH/btsJ2KxLwgz/dxlC5f7bNUo62IEXmRyed0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqyywH/btsJ2KxLwgz/dxlC5f7bNUo62IEXmRyed0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqyywH%2FbtsJ2KxLwgz%2FdxlC5f7bNUo62IEXmRyed0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;933&quot; height=&quot;185&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같은 에러가 발생한다. 이는 User 모델에는 username과 password 필드만 존재할 뿐, 위의 경우 password2 필드까지 포함되어 전송하고 있기 때문이다. 이 경우에는 create 메서드를 직접 구현해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728610689288&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# User 모델을 등록하는 Serializer
class RegisterSerializer(serializers.ModelSerializer):
    # password 필드를 validate_password 함수를 사용하여 유효성 검사
    password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
    password2 = serializers.CharField(write_only=True, required=True)

    # password와 password2가 일치하는지 검사
    def validate(self, attrs):
        # attrs는 serializer에 전달된 데이터를 포함하는 딕셔너리
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({&quot;password&quot;: &quot;비밀번호 필드가 일치하지 않습니다.&quot;})
        return attrs
    
    # create 메서드 직접 구현
    def create(self, validated_data):
        user = User.objects.create(
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user
    
    class Meta:
        model = User
        fields = ['username', 'password', 'password2']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;create 메서드를 구현한 뒤 User 생성을 시도하면 정상적으로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;User 권한 관리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Question 레코드에도 owner라는 필드로 작성자 이름을 남기고 있는 상태이다. 이제 게시글에도 작성자 이름이 노출되도록 하고, 해당 작성자만 게시글을 수정/삭제할 수 있는 권한을 줄 수 있도록 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Question 생성 시 현재 로그인한 계정이 작성자로 표시되도록 설정하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;settings.py에서 로그인/로그아웃 이후 리다이렉트 할 URL을 먼저 설정해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1728612061807&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysite/settings.py

from django.urls import reverse_lazy

# 로그인/로그아웃 후 리다이렉트할 URL
LOGIN_REDIRECT_URL = reverse_lazy('question_list')
LOGOUT_REDIRECT_URL = reverse_lazy('question_list')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 코드를 settings.py 상단에 입력한다. (위치는 큰 상관 없다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 '/question' URL에서 우측 상단 'Log in' 버튼을 눌러 로그인해보자. 이전에 등록한 계정을 활용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwCl3o/btsJ2ImqLpI/KkmTgsIRm60iBgLR8Qc6d0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwCl3o/btsJ2ImqLpI/KkmTgsIRm60iBgLR8Qc6d0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwCl3o/btsJ2ImqLpI/KkmTgsIRm60iBgLR8Qc6d0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwCl3o%2FbtsJ2ImqLpI%2FKkmTgsIRm60iBgLR8Qc6d0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;403&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;user1로 로그인이 정상적으로 처리됐고, question list 페이지로 리다이렉트된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 현재 로그인한 계정이 새로운 Question 레코드를 만들 때, 해당 레코드의 owner가 되도록 설정해보자. 현재 polls_api의 serializers.py에는 QuestionSerializer의 fields가 '__all__'로 설정되어 있기 때문에, 모든 필드가 노출되는 상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYvmuB/btsJ1fyTTma/aC8HTOApZjdMFbaFczNZak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYvmuB/btsJ1fyTTma/aC8HTOApZjdMFbaFczNZak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYvmuB/btsJ1fyTTma/aC8HTOApZjdMFbaFczNZak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYvmuB%2FbtsJ1fyTTma%2FaC8HTOApZjdMFbaFczNZak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;775&quot; height=&quot;302&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는 질문을 등록할 때도 마찬가지지만, 현재 로그인한 계정이 아닌 Owner를 고를 수 있게 설정되어 있는 상태이다. 현재 로그인한 계정이 질문의 owner가 되도록 고정하려면 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에서도 그랬던 것처럼, 우선 owner 필드가 변경되지 않도록 하려면 readonly 옵션을 설정해주어야 한다. 또한 그 리소스는 현재 계정의 username이 되어야 할 것이다. serializers.py를 다음과 같이 수정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728612863054&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Question 모델을 보여주는 Serializer
class QuestionSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Question
        fields = '__all__'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rzuhu/btsJ10Bgu3a/nNNAFr5kMlH7ja8GPNn1dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rzuhu/btsJ10Bgu3a/nNNAFr5kMlH7ja8GPNn1dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rzuhu/btsJ10Bgu3a/nNNAFr5kMlH7ja8GPNn1dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frzuhu%2FbtsJ10Bgu3a%2FnNNAFr5kMlH7ja8GPNn1dK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;281&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 owner를 고를 수 있는 칸이 사라지고, 질문 칸만 남아있는 것을 알 수 있다. 하지만 아직 owner가 자동으로 지정되는 건 아니다. 임의로 설정할 수 없게 만들었을 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;views.py로 이동해 아래 함수를 QuestionList 클래스 내에 추가해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1728613125795&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class QuestionList(generics.ListCreateAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;perform_create 함수는 create 메서드가 실행될 때, 시리얼라이저의 owner 정보를 현재 요청한 user의 정보를 받아들여 저장하게 된다. 즉, 질문 생성 시 작성자를 자동으로 설정하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OidmQ/btsJ0s6SWGE/fZ2sphZpYpz643IjI0KJkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OidmQ/btsJ0s6SWGE/fZ2sphZpYpz643IjI0KJkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OidmQ/btsJ0s6SWGE/fZ2sphZpYpz643IjI0KJkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOidmQ%2FbtsJ0s6SWGE%2FfZ2sphZpYpz643IjI0KJkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;755&quot; height=&quot;527&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 POST 요청을 통해 질문을 생성하게 되면, 현재 로그인된 계정인 user1이 자동으로 owner로 지정되는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;로그인한 계정에 한해서 질문 생성할 수 있도록 설정하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 로그아웃된 상태에서 질문을 만들게되면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSoh4A/btsJ2vAMBXo/eLrMOqVz1xt1OlmpSjW6yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSoh4A/btsJ2vAMBXo/eLrMOqVz1xt1OlmpSjW6yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSoh4A/btsJ2vAMBXo/eLrMOqVz1xt1OlmpSjW6yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSoh4A%2FbtsJ2vAMBXo%2FeLrMOqVz1xt1OlmpSjW6yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;864&quot; height=&quot;112&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같은 에러가 발생하게 된다. Question.owner 필드는 User 인스턴스가 필요하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 사용자에게 이런 에러 화면을 보여줄 수는 없을 것이다. 이런 상황에 대비한 처리가 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1728613504111&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/views.py

from rest_framework import status, mixins, generics, permissions


class QuestionList(generics.ListCreateAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
    # 인증된 사용자만 질문 생성 가능
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    # 질문 생성 시 작성자를 자동으로 설정
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;permissions 메서드를 임포트해, permisson_classes에 IsAuthenticatedOrReadOnly를 지정해준다. 이렇게 되면 인증을 받은(로그인 된) 계정에 한해서만 질문을 생성할 수 있고, 그렇지 않다면 읽는 동작만 가능하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 서버에 다시 접속하게 되면 아래와 같이 입력 창이 사라진 것을 볼 수 있다. (로그아웃 상태)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP1rUE/btsJ0M47J3O/CccrEscTtOv70Bie0BMSN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP1rUE/btsJ0M47J3O/CccrEscTtOv70Bie0BMSN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP1rUE/btsJ0M47J3O/CccrEscTtOv70Bie0BMSN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP1rUE%2FbtsJ0M47J3O%2FCccrEscTtOv70Bie0BMSN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;330&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 로그인을 하게 되면 다시 생성 폼이 생기는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCdlZQ/btsJ11NLjOC/Pkhkh1CkFkft415e5OPp51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCdlZQ/btsJ11NLjOC/Pkhkh1CkFkft415e5OPp51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCdlZQ/btsJ11NLjOC/Pkhkh1CkFkft415e5OPp51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCdlZQ%2FbtsJ11NLjOC%2FPkhkh1CkFkft415e5OPp51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;865&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;865&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자신이 생성한 Question에 대해서만 수정/삭제 권한 부여하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재 세부페이지에 진입했을 때, permission에 의해 로그인한 계정에 한해서만 생성/수정/삭제를 진행할 수 있지만, 다른 계정이 만든 질문에도 해당 권한이 부여되고 있는 상태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;의도하려는 바와 같이 자신이 생성한 질문에 대해서만 수정/삭제 권한을 부여하기 위해서는 permission을 커스텀하게 만들 필요가 있다. polls_api 폴더 내 permissions.py를 새로 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728614370470&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mysite/polls_api/permissions.py

from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        # SAFE_METHODS는 GET, OPTIONS, HEAD 메서드(읽기 전용)
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.owner == request.user&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;세부 페이지에 접속했을 때, 해당 질문의 owner인지 아닌지에 대해 판단하는 클래스이다. 우선 SAFE_METHOD인 GET, OPTIONS, HEAD 등 읽는 동작 요청에 대해서는 True로 전달해주며, 이 외의 요청(PUT, DELETE 등)은 질문의 owner와 요청하는 owner가 동일할 때만 True로 전달해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이를 views.py의 QuestionDetail에 적용해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1728614951140&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from .permissions import IsOwnerOrReadOnly

class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 서버를 확인하면, User1로 로그인한 상태에서 자신이 만든 질문에는 PUT, DELETE 기능이 노출되지만,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwcIVG/btsJ0Pnf57h/Y6wxRaSLtYlkrkGeQBP1gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwcIVG/btsJ0Pnf57h/Y6wxRaSLtYlkrkGeQBP1gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwcIVG/btsJ0Pnf57h/Y6wxRaSLtYlkrkGeQBP1gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwcIVG%2FbtsJ0Pnf57h%2FY6wxRaSLtYlkrkGeQBP1gK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;643&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;User3으로 접속했을 때는 아무 폼도 보이지 않는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzHnh/btsJ0tLxsY4/7PIz48VJcW3DQO89i9RNnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzHnh/btsJ0tLxsY4/7PIz48VJcW3DQO89i9RNnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzHnh/btsJ0tLxsY4/7PIz48VJcW3DQO89i9RNnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzHnh%2FbtsJ0tLxsY4%2F7PIz48VJcW3DQO89i9RNnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;448&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Minding's Programming/Django</category>
      <category>django</category>
      <category>django python</category>
      <category>장고 restapi</category>
      <category>장고 user 관리</category>
      <category>장고 user 권한</category>
      <category>장고 user 생성</category>
      <category>장고 로그인</category>
      <category>파이썬 장고</category>
      <author>Minding</author>
      <guid isPermaLink="true">https://minding-deep-learning.tistory.com/243</guid>
      <comments>https://minding-deep-learning.tistory.com/243#entry243comment</comments>
      <pubDate>Thu, 10 Oct 2024 18:12:17 +0900</pubDate>
    </item>
  </channel>
</rss>