<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>JIWOO.log</title>
        <link>https://velog.io/</link>
        <description>아키텍트를 목표로 공부하는 2년차 시스템 관리자</description>
        <lastBuildDate>Tue, 27 Aug 2024 07:48:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>JIWOO.log</title>
            <url>https://velog.velcdn.com/images/jiwoo_048/profile/15de1073-f9ad-4b02-bc7b-ddb107ff33fd/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. JIWOO.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jiwoo_048" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[OSSCA] Master 1주차 - Designate 기여하기]]></title>
            <link>https://velog.io/@jiwoo_048/OSSCA-Master-1%EC%A3%BC%EC%B0%A8-Designate-%EA%B8%B0%EC%97%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jiwoo_048/OSSCA-Master-1%EC%A3%BC%EC%B0%A8-Designate-%EA%B8%B0%EC%97%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 27 Aug 2024 07:48:13 GMT</pubDate>
            <description><![CDATA[<p><strong>기여 내용</strong></p>
<blockquote>
<p><code>GET /v2/zones/{zone_id}/nameservers</code> API를 이용하여 Name Server 목록을 불러오는 CLI 명령어 만들기.</p>
</blockquote>
<h1 id="1-네임서버-목록-불러오기">1) 네임서버 목록 불러오기</h1>
<h2 id="designate-client로부터-name-server-리스트-불러오기">designate client로부터 name server 리스트 불러오기</h2>
<p>designate client는 다음과 같이 사용할 수 있다.</p>
<pre><code class="language-python">client = self.app.client_manager.dns</code></pre>
<p>출력해보면 <code>&lt;designateclient.v2.client.Client object at 0x1038f35b0&gt;</code>라고 나오는데, designate client를 불러온다는 것을 확인할 수 있다.</p>
<pre><code class="language-python">print(client.nameservers)</code></pre>
<p>designate client 코드를 보면, nameservers.py 파일에 NameServerController라는 클래스가 존재한다. 이것이 nameserver를 불러오는 클래스라는 것을 직관적으로 알 수 있다. 위처럼 <code>client.nameservers</code>를 출력해보면 <code>&lt;designateclient.v2.nameservers.NameServerController object at 0x110180cd0&gt;</code>임을 알 수 있다.</p>
<p>designateclient의 NameServerController의 코드는 다음과 같다.</p>
<pre><code class="language-python">class NameServerController(V2Controller):
    def list(self, zone):
        zone = v2_utils.resolve_by_name(self.client.zones.list, zone)

        url = f&#39;/zones/{zone}/nameservers&#39;

        return self._get(url, response_key=&#39;nameservers&#39;)</code></pre>
<p>즉, list라는 메소드를 통해 네임서버 목록을 불러오는 것으로 보인다. 인자 값으로 zone의 id를 함께 보내야 하므로 다음과 같이 코드를 작성할 수 있다.</p>
<pre><code class="language-python">zones = client.zones.list()

for s in zones:
    for t in client.nameservers.list(s[&#39;id&#39;]):
        print(t)</code></pre>
<p>zone의 리스트를 불러와서 그 id를 이용해 네임서버 목록을 불러온다. 결과는 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/779ed08b-ca8e-4ed2-a26d-8c892716d59a/image.png" alt=""></p>
<h1 id="2-결과-출력하기">2) 결과 출력하기</h1>
<h2 id="표에-포함될-컬럼-정하기">표에 포함될 컬럼 정하기</h2>
<pre><code class="language-python">columns = [&#39;hostname&#39;, &#39;priority&#39;, &#39;zone_id&#39;, &#39;zone_name&#39;]
column_headers = [&#39;Name Server&#39;, &#39;Priority&#39;, &#39;Zone Id&#39;, &#39;Zone Name&#39;]</code></pre>
<p>name server의 이름과 priority 뿐만 아니라 zone id랑 name까지 같이 출력하기로 했다.</p>
<h2 id="표-모양으로-결과-출력하기">표 모양으로 결과 출력하기</h2>
<p>우선 기존에 상속받는 클래스로 <code>command.Command</code>를 설정했는데, 이것을 ListServer와 동일하게 <code>command.Lister</code>로 변경하자 표 모양이 생성되었다. (그런데, 안에 내용이 채워지지는 않았음)</p>
<p>ListServer에서는 어떤 식으로 표에 내용을 채워넣는 지 알아내기 위해 코드를 분석했고, 다음과 같이 테이블 형태로 결과를 리턴함을 확인하였다.</p>
<pre><code class="language-python">table = (
    column_headers,
    (
        utils.get_item_properties(
            s,
            columns,
            mixed_case_fields=(
                &#39;task_state&#39;,
                &#39;power_state&#39;,
                &#39;availability_zone&#39;,
                host&#39;,
            ,
            formatters={
                &#39;power_state&#39;: PowerStateColumn,
                &#39;addresses&#39;: AddressesColumn,
                &#39;metadata&#39;: format_columns.DictColumn,
                &#39;security_groups_name&#39;: format_columns.ListColumn,
                &#39;hypervisor_hostname&#39;: HostColumn,
            },
        )
        for s in data
    ),
)
return table</code></pre>
<p>data는 리스트 형태이고, 이 리스트에서 <code>설정한 columns</code>와 일치하는 것의 값을 가져오는 방식인 것 같다. 그래서 동일하게 data 라는 리스트 내에 딕셔너리 형태로 네임서버를 하나씩 저장하기로 했다. 다음이 네임서버 정보를 data 라는 리스트에 저장하는 코드이다.</p>
<pre><code class="language-python">data = []
for s in zones:
    m = {}
    for t in client.nameservers.list(s[&#39;id&#39;]):
        m[&#39;zone_id&#39;] = s[&#39;id&#39;]
        m[&#39;zone_name&#39;] = s[&#39;name&#39;]
        m[&#39;priority&#39;] = t[&#39;priority&#39;]
        m[&#39;hostname&#39;] = t[&#39;hostname&#39;]

    data.append(m)</code></pre>
<p>각각의 zone에 대해 id를 이용하여 네임서버 목록을 불러오고, 이를 <code>m</code> 딕셔너리에 하나씩 저장한다. 이러한 딕셔너리를 <code>data</code> 리스트에 추가하면, 결과적으로 다음과 같은 형태가 된다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/6c600657-5b73-44ca-b29f-e44463014ea9/image.png" alt=""></p>
<p>현재는 zone도 1개고, nameserver도 1개여서 하나만 출력되는 것을 확인할 수 있다.</p>
<p>이제 이를 위에 table 형태로 추출할 수 있게 해줘야 한다. ListServer에서는 data에 저장된 값이 <code>openstack.compute.v2.server.Server</code> 객체 형태이기 때문에 <code>utils.get_item_properties()</code> 함수를 이용했었다. 그런데 지금은 리스트 내에 dictionary 형태이므로 인식을 하지 못하는 문제가 발생했다. (표에 값이 채워지지 않음) 그래서 <code>utils</code> 모듈에 다른 어떤 함수가 있는지 확인해본 결과, <code>get_dict_properties()</code> 함수를 발견했다. 다음은 해당 함수의 설명이다.</p>
<pre><code class="language-python">Return a tuple containing the item properties.

:param item: a single dict resource
:param fields: tuple of strings with the desired field names
:param mixed_case_fields: tuple of field names to preserve case
:param formatters: dictionary mapping field names to callables
    to format the values</code></pre>
<p>단일 딕셔너리 리소스로부터 tuple을 리턴해주는 함수이다. 딱 내가 원하던 함수! 그래서 바로 적용해보았다.</p>
<pre><code class="language-python">table = (
    column_headers,
    (
        utils.get_dict_properties(
            s,
            columns,
        )
        for s in data
    ),
)
return table</code></pre>
<p>이렇게 코드를 작성하니 비로소 테이블에 값이 채워졌다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/e20dd23d-a6d0-41d3-af59-84c4963032fe/image.png" alt=""></p>
<p>결과이다! zone 추가했을 때 네임서버도 추가로 출력되는지 확인해보자.</p>
<h1 id="전체-코드">전체 코드</h1>
<pre><code class="language-python">from osc_lib.command import command
from openstackclient.i18n import _
from osc_lib import utils


class ListNameServer(command.Lister):
    _description = _(&quot;List name servers&quot;)

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)

        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.dns
        zones = client.zones.list()

        columns = [&#39;hostname&#39;, &#39;priority&#39;, &#39;zone_id&#39;, &#39;zone_name&#39;]
        column_headers = [&#39;Name Server&#39;, &#39;Priority&#39;, &#39;Zone Id&#39;, &#39;Zone Name&#39;]

        data = []
        for s in zones:
            m = {}
            for t in client.nameservers.list(s[&#39;id&#39;]):
                m[&#39;zone_id&#39;] = s[&#39;id&#39;]
                m[&#39;zone_name&#39;] = s[&#39;name&#39;]
                m[&#39;priority&#39;] = t[&#39;priority&#39;]
                m[&#39;hostname&#39;] = t[&#39;hostname&#39;]

            data.append(m)

        table = (
            column_headers,
            (
                utils.get_dict_properties(
                    s,
                    columns,
                )
                for s in data
            ),
        )
        return table</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OSSCA] python-openstackcli 프로젝트]]></title>
            <link>https://velog.io/@jiwoo_048/OSSCA-python-openstackcli-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@jiwoo_048/OSSCA-python-openstackcli-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Mon, 26 Aug 2024 06:11:28 GMT</pubDate>
            <description><![CDATA[<h1 id="python-openstackcli-프로젝트란">python-openstackcli 프로젝트란?</h1>
<p>오픈스택의 CLI 도구로, 명령어를 입력했을 때 인자값을 파싱하여 적절한 결과값을 리턴해주는 프로젝트이다. <code>openstack server list</code> 라는 명령어를 입력하면 가상머신의 리스트를 보여주는데, 이러한 명령어를 사용할 수 있게 해주는 것이다.</p>
<p>원래 컴포넌트마다 CLI가 따로 존재했는데, 이를 하나로 통합하여 간편하게 기능을 이용할 수 있도록 하기 위한 목적에서 개발되었다.</p>
<p><strong>setup.cfg</strong></p>
<p>프로젝트의 메타데이터와 구성 옵션을 정의하는 아주 중요한 파일이다. 파일에서 정의하고 있는 내용은 다음과 같다.</p>
<ul>
<li>메타데이터: 프로젝트 이름, 버전, 저자, 라이센스 등의 기본 정보.</li>
<li>옵션: 설치 옵션, 종속성, 테스트 요구 사항 등의 설정.</li>
<li>엔트리 포인트: CLI 명령어와 그에 매핑되는 함수 지정.</li>
<li>패키징 정보: 포함될 패키지와 데이터 파일 등의 정보.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OSSCA] 오픈스택이란?]]></title>
            <link>https://velog.io/@jiwoo_048/OSSCA-%EC%B1%8C%EB%A6%B0%EC%A7%80-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jiwoo_048/OSSCA-%EC%B1%8C%EB%A6%B0%EC%A7%80-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Fri, 23 Aug 2024 06:27:01 GMT</pubDate>
            <description><![CDATA[<p>1주차 과제 정리</p>
<h1 id="📌-오픈스택이란">📌 오픈스택이란?</h1>
<blockquote>
<p>가상머신, 베어메탈, 컨테이너 생성을 도와주는 클라우드 인프라 구축 도구</p>
</blockquote>
<p>주로 프라이빗 클라우드를 구축하는 데에 사용되는 오픈소스 소프트웨어이다. 처음 회사를 시작할 때는 퍼블릭 클라우드를 이용하는 것이 훨씬 좋지만, 퍼블릭 클라우드가 비용이 저렴한 편이 아니기 때문에 규모가 커질 수록 마진이 맞지 않는 경우가 많다. 그래서 대기업의 경우 오픈스택 등을 이용하여 직접 프라이빗 클라우드를 구축하는 편.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/6339fd09-a94c-4df8-92d6-77b01143f7d0/image.png" alt=""></p>
<p><strong>클라우드 인프라란?</strong></p>
<p>물리적으로 제공되던 IT 자원들을 가상화하여 API로 제어할 수 있도록 제공해주는 인프라 환경을 의미한다.</p>
<p>클라우드 인프라는 사용자가 소유하는 것이 아니라, <code>테넌트</code>라는 개념 안에 속해있다고 한다. 테넌트를 나타내는 방식은 클라우드 제공사마다 다르다.</p>
<p><strong>핵심 컴포넌트</strong></p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/fecf4671-a2ee-4f3d-a929-420289354e20/image.png" alt=""></p>
<ul>
<li>nova: 가상머신 생성</li>
<li>neutron: 가상 네트워크 생성</li>
<li>glance: OS 이미지 생성</li>
<li>cinder: 블록 스토리지 생성</li>
<li>swift: 객체 스토리지 생성</li>
<li>keystone: 인증, 인가 서비스</li>
</ul>
<blockquote>
<p><em>블록 스토리지 vs 객체 스토리지</em></p>
</blockquote>
<h2 id="오픈스택-동작방식">오픈스택 동작방식</h2>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/24e9e7e7-14c3-4632-aac4-be844debd3b9/image.png" alt=""></p>
<p>컴포넌트는 API 서비스와 Agent 서비스로 구분된다. 클라우드 인프라에 대한 요청은 API로 이루어지는데, 이 API를 처리하는 것이 API 서비스이다. 그리고 동일 컴포넌트 내에서 실제 인프라를 생성하는 작업을 진행해 주는 것이 Agent 서비스이다. API 서비스와 Agent 서비스 간에는 <code>RPC Call</code>로 통신한다. Agent는 대기하고 있다가 본인에게 RPC Call이 온 경우, 적절하게 이를 처리한다. nova 컴포넌트 뿐만 아니라 다른 컴포넌트 역시 동일한 방식으로 처리하게 된다.</p>
<p>인스턴스 생성을 하는 요청을 했을 때 nova api 서버가 해당 API 요청을 처리하게 된다. 실제 인스턴스를 생성하는 동작은 동일한 nova 컴포넌트의 Agent 서비스가 처리하고, 인스턴스를 생성하기 위해 필요한 Image, 스토리지 등은 다른 컴포넌트의 api 서버를 호출함으로써 처리하게 된다.</p>
<p>오픈스택은 자원의 상태만 관리해줄 뿐이지, 실제로 가상머신을 생성하는 작업은 KVM 같은 별도의 드라이버나 플러그인을 이용한다. nova agent는 KVM을 관리해주는 libvirt를 이용해 가상머신이 생성될 수 있도록 처리한다. (그래서 실제 가상머신 생성 작업은 KVM이 수행하는 것이다)</p>
<p><strong>오픈스택은 API를 비동기 방식으로 처리한다.</strong></p>
<p>인스턴스 생성 API를 실행하더라도 즉시 인스턴스가 생성되지 않는다. 오픈스택이 libvirt에 요청을 보낸 뒤 계속 상태 확인을 하고, 인스턴스 생성이 확인된 후에야 다음 요청을 처리하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/b8d59ca8-4b5a-4f09-b034-b2fce08e7d83/image.png" alt=""></p>
<ul>
<li>메세지 큐: API와 Agent 간 RPC Call 통신을 할 때, RabbitMQ에 의해 메세지 큐에 요청 내용이 차례대로 들어간다. 그러면 Agent가 이 큐에서 하나씩 작업을 꺼내서 처리하게 된다.</li>
<li>스케쥴러: 인스턴스의 flavor에 따라 어느 하이퍼바이저에 생성할지 결정하는 역할을 수행한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] EventBridge 도입으로 프로젝트 효율성 개선 사례]]></title>
            <link>https://velog.io/@jiwoo_048/Event-Driven-Architecture</link>
            <guid>https://velog.io/@jiwoo_048/Event-Driven-Architecture</guid>
            <pubDate>Sat, 13 Apr 2024 07:08:11 GMT</pubDate>
            <description><![CDATA[<p>프로젝트에 이벤트 기반 아키텍처를 도입하게 된 이유에 대해서 정리해보고자 합니다.</p>
<h1 id="프로젝트-개요">프로젝트 개요</h1>
<p>먼저 어떤 프로젝트인지에 대해서 짧게 언급하고 넘어가겠습니다. 대학교 4학년 때 수강했었던 <code>클라우드 컴퓨팅</code>이라는 교과목에서 진행했던 프로젝트입니다. AWS에서 제공하는 SDK(Software Development Kit)를 이용하여 AWS의 리소스를 다루는 코드를 작성하는 것입니다. 최종으로 구현한 기능은 다음과 같습니다.</p>
<blockquote>
<ul>
<li>EC2 인스턴스 2개로 HTCondor Cluster를 구축할 것</li>
<li>인스턴스 리스트 출력, 인스턴스 시작, 인스턴스 중지, AMI로부터 인스턴스 생성, AMI 이미지 정보 출력, 가용영역과 리전 출력, 인스턴스 재부팅, 인스턴스 종료 구현</li>
<li>Systems Manager를 이용하여 HTCondor Cluster에 <code>condor_status</code> 명령어 결과 반환</li>
<li>SNS(Simple Notification Service)를 활용한 이메일 알림 서비스</li>
</ul>
</blockquote>
<p>프로젝트에 대해 더 궁금하다면, Github를 참고해주세요!</p>
<ul>
<li>프로젝트 Github: <a href="https://github.com/Ohjiwoo-lab/cloud-project">https://github.com/Ohjiwoo-lab/cloud-project</a></li>
</ul>
<h1 id="기존-아키텍처">기존 아키텍처</h1>
<p>저는 <code>SNS를 활용한 이메일 알림 서비스</code>에 이벤트 기반 아키텍처를 도입하였습니다. 해당 서비스는 사용자가 알림을 설정하면 이메일을 전송하는 기능을 가지고 있습니다. 알림은 <code>인스턴스 시작</code>, <code>인스턴스 중지</code>, <code>인스턴스 생성</code>, <code>인스턴스 종료</code>에 대해 설정할 수 있고, 만약 <code>인스턴스 시작</code>에 대한 알림을 설정했다면 인스턴스를 시작하는 작업을 할 때마다 이메일로 알림을 받을 수 있습니다.</p>
<p>기능을 구현하기 위해 AWS의 SNS(Simple Notification Service)를 사용했습니다. 사용자가 알림을 생성하면 그에 해당하는 SNS Topic을 생성합니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/61eef653-53c2-4718-9a52-e45880da5530/image.png" alt=""></p>
<p>또한 알림을 생성할 때 이메일을 입력받는데, 20초 안에 해당 이메일을 통해 확인을 하지 않으면 알림을 생성할 수 없도록 구현하였습니다.</p>
<blockquote>
<p>이는 SNS에서 Subscribe로 이메일을 등록했을 때 확인해야만 정상적으로 구독이 이루어지기 때문입니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/732a7e2c-efbd-4f9c-8acf-8fac7c17bd5a/image.png" alt=""></p>
<p>메일을 통해 확인을 하면 SNS Topic과 구독이 정상적으로 생성됩니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/52ea7852-a287-43ad-aa68-0cff341e491d/image.png" alt=""> <img src="https://velog.velcdn.com/images/jiwoo_048/post/33e5dfe8-4ae9-4651-9910-c91e1692ae0c/image.png" alt=""></p>
<p>이제 <code>인스턴스 시작</code>에 대한 알림이 생성된 것입니다. 알림의 존재 여부를 판단하기 위해 알림의 후보가 될 수 있는 4가지 API 작업이 이루어질 때마다 아래의 코드를 실행하였습니다.</p>
<pre><code># 알람 확인 후 알람 전송
def send(self, action):
    try:
        topics = self.sns.list_topics()
        for topic in topics[&#39;Topics&#39;]:
            if topic[&#39;TopicArn&#39;].split(&#39;:&#39;)[-1] == action:
                self.sns.publish(
                    TopicArn=topic[&#39;TopicArn&#39;],
                    Message=f&quot;당신의 AWS 계정으로 {action} 작업이 이루어졌습니다. 본인의 활동이 맞는지 확인해보세요.&quot;
                    )
                break

    except ClientError as err:
        print(&quot;Cannot send the email&quot;)
        print(err.response[&quot;Error&quot;][&quot;Code&quot;], end=&quot; &quot;)
        print(err.response[&quot;Error&quot;][&quot;Message&quot;])</code></pre><p>어떤 알림이 설정되었는지 알지 못하기 때문에 알림의 후보인 <code>인스턴스 시작</code>, <code>인스턴스 중지</code>, <code>인스턴스 생성</code>, <code>인스턴스 종료</code> 작업을 프로그램으로 수행할 때마다 SNS Topic이 있는지를 확인하고, 있다면 직접 메시지를 전송하는 방식인 것입니다.</p>
<p>아키텍처로 그려보면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/d3de0a70-dda4-4b0e-a695-b183379de256/image.png" alt=""></p>
<p>해당 아키텍처의 문제점은 2가지가 있습니다.</p>
<ol>
<li><p>만약 프로그램이 확장되어 알림의 개수가 많아진다면 너무 비효율적이다.</p>
<p> 알림의 개수가 10개만 되어도 만약 존재할지도 모르는 알림을 발견하기 위해 10번의 for문을 돌아야 하는 셈입니다. 이는 너무 시작 복잡도 측면에서 비효율적입니다.</p>
</li>
<li><p>프로그램이 아닌 AWS 콘솔을 통해 작업을 수행하면 알림을 받지 못한다.</p>
<p> SNS Topic에 메시지를 보내는 <code>send 함수</code>는 프로그램 내부에서만 호출됩니다. 그래서 프로그램을 통해서가 아닌 AWS 콘솔에서 작업을 수행하면 알림이 설정되어 있어도 이메일을 받지 못하게 됩니다.</p>
</li>
</ol>
<p>만약 해당 프로젝트를 대학교 전공 수업 수준에서 마무리한다면 이런 식으로 아키텍처를 구성해도 문제가 되지 않을 것입니다. 하지만 실제로 운영되는 서비스라고 한다면, 보다 효율적으로 서비스를 개선할 필요가 있을 것입니다. 그래서 이벤트 기반 아키텍처를 생각하게 되었습니다.</p>
<h1 id="개선된-아키텍처">개선된 아키텍처</h1>
<p>결과적으로 개선된 아키텍처는 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/44a935e1-1aa0-4b8e-8f6f-89c64032563b/image.png" alt=""></p>
<ol>
<li>CloudTrail 추적이 모든 API 호출을 기록합니다.</li>
<li>사용자가 알림을 생성하면 그에 맞는 EventBridge 규칙과 SNS Topic을 생성합니다.</li>
<li>알림으로 설정한 API 작업이 일어난다면 EventBridge에 의해 자동으로 SNS에 메시지가 게시됩니다.</li>
<li>SNS에 의해 사용자에게 이메일 알림이 전송됩니다.</li>
</ol>
<p>이렇게 아키텍처를 구성하면 콘솔에서 작업을 해도 알림을 받을 수 있으며, EventBridge에 의해 알아서 API 호출을 인식할 수 있습니다.</p>
<h2 id="코드-살펴보기">코드 살펴보기</h2>
<p><a href="https://docs.aws.amazon.com/ko_kr/eventbridge/latest/userguide/eb-log-api-call.html">AWS 자습서</a>를 참고했습니다.</p>
<h3 id="cloudtrail-추적-생성">CloudTrail 추적 생성</h3>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/1bbde22d-80ab-4424-b3ad-f5236b2c2a92/image.png" alt=""></p>
<p>콘솔을 통해 CloudTrail 추적을 생성합니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/37a773f1-a7fc-4a3c-be26-1118001016c0/image.png" alt=""></p>
<p>그러면 S3 버킷이 자동으로 생성되고, 버킷으로 API 호출 이력이 저장됩니다.</p>
<h3 id="eventbridge-규칙-생성">EventBridge 규칙 생성</h3>
<pre><code>event_pattern = &#39;{\n&#39; \
+ &#39;  &quot;source&quot;: [&quot;aws.ec2&quot;],\n&#39; \
+ &#39;  &quot;detail-type&quot;: [&quot;AWS API Call via CloudTrail&quot;],\n&#39; \
+ &#39;  &quot;detail&quot;: {\n&#39; \
+ &#39;    &quot;eventSource&quot;: [&quot;ec2.amazonaws.com&quot;],\n&#39; \
+ &#39;    &quot;eventName&quot;: [&quot;&#39; + name + &#39;&quot;]\n&#39; \
+ &#39;  }\n&#39; \
+ &#39;}&#39;

# EventBridge 규칙 생성
self.event.put_rule(
    Name = name,
    EventPattern=event_pattern,
    State=&#39;ENABLED&#39;
)</code></pre><p>EventBridge 규칙 생성을 자동화하기 위해 API 이름을 <code>name</code>에 저장하여 사용했습니다.</p>
<h3 id="eventbridge-규칙에-대상-연결">EventBridge 규칙에 대상 연결</h3>
<pre><code># EventBridge가 SNS에 메시지를 게시할 권한 부여 (리소스 기반 정책)
attribute = self.client.get_topic_attributes(  # 기존 권한 가져오기
    TopicArn=topic
)

json_policy = json.loads(attribute[&#39;Attributes&#39;][&#39;Policy&#39;])   # 문자열을 json으로 변환

# 추가할 권한 정의
policy = {
    &quot;Sid&quot;: &quot;AWSEvents_&quot; + name + &quot;_id&quot;,
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Principal&quot;: {
         &quot;Service&quot;: &quot;events.amazonaws.com&quot;
    },
    &quot;Action&quot;: &quot;sns:Publish&quot;,
    &quot;Resource&quot;: topic
}
json_policy[&#39;Statement&#39;].append(policy)   # 권한 추가

# 새로운 권한 생성 (json을 문자열로 바꾸기 위해 json.dumps 사용)
new_policy = &#39;{&quot;Version&quot;:&quot;&#39; + json_policy[&#39;Version&#39;] + &#39;&quot;,&#39; + &#39;&quot;Id&quot;:&quot;&#39; + json_policy[&#39;Id&#39;] + &#39;&quot;,&#39; + &#39;&quot;Statement&quot;:&#39; + json.dumps(json_policy[&#39;Statement&#39;]) + &#39;}&#39;

# 속성 업데이트
response = self.client.set_topic_attributes(
    TopicArn = topic,
    AttributeName = &#39;Policy&#39;,
    AttributeValue = new_policy
)

# 대상(sns) 연결
self.event.put_targets(
    Rule=name,
    Targets=[
    {
        &quot;Id&quot;: name + &quot;_SNS&quot;,
        &quot;Arn&quot;: topic,
        &quot;Input&quot;: json.dumps(&quot;귀하의 계정으로 &quot; + name + &quot; 작업이 이루어졌습니다. 본인이 수행한 활동이 아니라면 계정의 보안을 체크해보세요.&quot;, ensure_ascii = False)
    }]
)</code></pre><p>이 부분이 가장 어려웠습니다. EventBridge 규칙에 SNS Topic을 대상으로 연결하기 위해서는 SNS에 리소스 기반 정책을 부여해야 했기 때문입니다. 그래서 생성한 SNS Topic의 속성을 가져와서 Policy 부분을 수정하는 작업을 수행했습니다.</p>
<ol>
<li>Topic의 속성에서 Policy 부분 가져와서 Json으로 변환</li>
<li>EventBridge가 SNS에 메시지를 Publish 할 수 있도록 권한을 Json으로 작성</li>
<li>새로운 권한을 기존 권한 뒤에 붙이기</li>
<li>새로운 정책을 문자열로 변환하여 속성 업데이트</li>
<li>권한이 부여된 SNS Topic을 EventBridge 대상으로 설정</li>
</ol>
<h1 id="결론">결론!</h1>
<p>이렇게 불필요한 API 사용을 줄이고 코드 효율을 증가할 수 있었습니다. 아키텍처에 사용된 EventBridge, S3, SNS 모두 서버리스 서비스이기 때문에 비용 측면에서도 아주 효율적입니다. 다만 CloudTrail 추적으로 인해 S3에 과도한 API 이력이 저장되면서 비용이 발생할 우려가 있어서, 알림이 존재할 때만 추적 로깅을 활성화하도록 코드를 구현하였습니다. 즉 알림을 생성할 때 기존에 설정된 알림이 없을 때에 로깅을 활성화했고, 알림을 삭제할 때 삭제하려는 알림이 마지막 남은 것이라면 로깅을 비활성화했습니다.</p>
<p>이보다 더 좋은 아키텍처가 있을지를 고민해보고 계속 프로젝트를 업데이트해나갈 예정입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] 로드밸런서 health check failed 해결]]></title>
            <link>https://velog.io/@jiwoo_048/AWS-%EB%A1%9C%EB%93%9C%EB%B0%B8%EB%9F%B0%EC%84%9C-health-check-failed-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@jiwoo_048/AWS-%EB%A1%9C%EB%93%9C%EB%B0%B8%EB%9F%B0%EC%84%9C-health-check-failed-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Sun, 11 Feb 2024 14:50:24 GMT</pubDate>
            <description><![CDATA[<p>AWS 로드밸런서의 상태체크 원리에 대해 알아보겠습니다.</p>
<p>로드밸런서는 여러 대의 서버에 네트워크 트래픽을 분산해주는 장치입니다. 이때 트래픽이 전달된 서버가 제대로 동작하지 않으면, 해당 트래픽은 처리되지 못한 채로 사라져버릴 것입니다. 사용자의 요청을 적절히 처리하기 위해서는 현재 정상적으로 운영 중인 서버에만 트래픽을 전달해야 합니다.</p>
<p>그래서 로드밸런서는 <code>상태 확인</code> 기능을 제공합니다. 서버의 상태를 확인하여 동작 중인 서버에만 트래픽을 전달합니다. 그러면 실제로 어떻게 상태 확인을 진행하는지 알아봅시다.</p>
<h1 id="실습을-위한-아키텍처-구축">실습을 위한 아키텍처 구축</h1>
<h2 id="서버-및-로드밸런서-구성">서버 및 로드밸런서 구성</h2>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/d3d34bbd-d182-425a-8a16-3062d5604ecd/image.png" alt=""></p>
<p>로드밸런서가 트래픽을 전달할 서버를 생성합니다.</p>
<blockquote>
<p>실습을 간단하게 진행하기 위해 2대의 서버로 진행하였고, 모든 서버는 퍼블릭 IP를 가지고 있습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/bdc00cff-4ea1-4adf-859d-e2442d3097b2/image.png" alt=""></p>
<p>앞서 생성한 2대의 EC2 인스턴스로 대상 그룹 <code>test</code>를 생성합니다. 여기서 대상 그룹이란 로드밸런서가 트래픽을 전달하는 대상을 그룹으로 묶어놓은 것을 의미합니다. 이때 대상은 인스턴스, Lambda 함수, IP 주소 등이 포함될 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/b8cd33a8-63b9-4cd8-a6f8-792b44c91850/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/947da129-3caa-49a6-b3ef-569cb84fe16d/image.png" alt=""></p>
<p>EC2 인스턴스를 생성한 VPC에 로드밸런서를 생성한 뒤, 앞서 만들었던 대상 그룹을 지정합니다. 그러면 이제 해당 로드밸런서는 들어온 트래픽을 <code>test</code> 대상 그룹에 전달할 것입니다.</p>
<h2 id="⭐-보안-그룹-설정">⭐ <strong>보안 그룹 설정</strong></h2>
<h3 id="로드밸런서의-보안-그룹">로드밸런서의 보안 그룹</h3>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/c539f61b-497e-409b-9d9a-4d4b1d29ddbb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/9b6f70b4-7646-4fdb-acba-bde7ce7831dd/image.png" alt=""></p>
<p>로드 밸런서의 경우 <code>HTTP 80 포트</code>를 허용해주어야 합니다. 사용자는 로드밸런서로 HTTP 요청을 보낼 것이기 때문에 80 포트로 오는 모든 요청을 허용해줍니다. 만약 로드밸런서로 요청을 보내도 로딩만 걸린다면, 이는 로드밸런서의 보안 그룹에서 요청을 차단하고 있는 것이기 때문에 보안 그룹을 변경해줄 필요가 있습니다.</p>
<blockquote>
<p>보안 그룹의 경우 상태를 저장하기 때문에 인바운드 규칙으로 허용한 트래픽은 아웃바운드 규칙에 상관없이 허용된다는 특징을 가지고 있습니다. 그래서 인바운드 규칙만 설정해주었습니다.</p>
</blockquote>
<h3 id="서버의-보안-그룹">서버의 보안 그룹</h3>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/42cad5fa-fbc4-47d6-843b-691bb019bd8b/image.png" alt=""></p>
<p>서버는 로드밸런서로부터 오는 HTTP 상태 확인 요청을 허용해야 하기 때문에 인바운드 규칙으로 로드밸런서의 보안 그룹 (<code>sg-0ff1b43cfb6b5da7d</code>)을 설정해줍니다. 이렇게 되면 서버는 로드밸런서에서 오는 모든 HTTP 요청만을 허용하게 됩니다. 해당 실습에서는 서버에 접속하여 설정을 진행해야 하기 때문에 SSH를 위한 22번 포트도 허용해주었습니다.</p>
<blockquote>
<p>단순히 모든 HTTP 요청을 허용하게 되면 아무나 서버에 접속할 수 있기 때문에 보안상 추천하지 않는 방법입니다. 서버는 로드밸런서를 통해 전달되는 요청만을 허용하는 것이 좋습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/f2b7afad-7e83-42a7-8197-83a46761d2fe/image.png" alt=""></p>
<p>만약 위와 같이 <code>Request timed out</code> 이라고 표시되면, 아예 서버에 트래픽이 전달되지 못했다는 것이므로 서버의 보안 그룹을 확인해보는 것이 좋습니다.</p>
<blockquote>
<p>time out 의 경우 트래픽이 전달되지 못한 경우에 발생하고, fail 은 트래픽이 전달되기는 하였지만 처리하지는 못했을 경우에 발생합니다. 오류 메시지를 잘 살펴보면 문제의 원인을 파악하는 데에 도움이 될 수 있습니다.</p>
</blockquote>
<h1 id="상태-검사-실패-원인">상태 검사 실패 원인</h1>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/b8bf2c1f-d12c-4f7c-848d-59c0e5e31f51/image.png" alt=""></p>
<p>생성한 로드밸런서의 DNS 이름으로 접속하면 트래픽이 대상 그룹에 전달되어야 하는데, 실제로 해보면 다음과 같이 연결할 수 없다고 표시됩니다. 왜 그런 것일까요?</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/70e67554-caa1-45e2-bb12-88cd82b298f8/image.png" alt=""></p>
<p>대상 그룹을 확인해보면, 상태 확인 결과가 모두 <code>Unhealthy</code>입니다. 로드밸런서에서 서버가 정상적으로 동작하지 않는다고 판단한 것입니다. EC2 인스턴스는 정상적으로 실행되고 있는데 왜 이렇게 판단했을까요?</p>
<p>해답은 바로 상태 확인의 원리에 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/7952f87f-55fa-4ec5-8b7a-784abebeb383/image.png" alt=""></p>
<p>생성한 대상 그룹을 다시 확인해보면 상태 확인 프로토콜로 HTTP, 경로는 /, 간격은 10초로 설정했습니다. 즉 로드밸런서는 대상 그룹 내의 서버의 / 경로로 10초에 한 번씩 HTTP 요청을 보낸 뒤, 응답 코드 200이 반환되면 해당 서버가 정상적으로 동작하고 있다고 판단하는 것입니다. 서버가 실행 중이더라도 요청에 대한 응답을 설정해주지 않으면 로드밸런서에서는 해당 서버가 동작 중이라고 인식할 수 없습니다.</p>
<p>현재 서버에는 / 경로에 대해 어떻게 응답을 보내야 하는지를 전혀 지정해주지 않았기 때문에 상태 확인이 실패했다고 나올 수밖에 없는 것입니다.</p>
<h1 id="오류-해결">오류 해결</h1>
<p>그러면 제대로 상태 확인이 가능하도록 변경해보겠습니다.</p>
<h2 id="웹-서버-설치">웹 서버 설치</h2>
<p>EC2 인스턴스에 아파치 웹서버인 <code>httpd</code>를 설치하기 위해 EC2 인스턴스에 접속한 뒤 아래의 명령어를 입력해줍니다.</p>
<pre><code>sudo yum install -y httpd
sudo systemctl start httpd
sudo systemctl enable httpd
sudo systemctl status httpd</code></pre><p><img src="https://velog.velcdn.com/images/jiwoo_048/post/7a9b17ef-162c-4b34-9775-13b876a76c47/image.png" alt=""></p>
<p>웹 서버가 정상적으로 실행되었습니다. </p>
<h2 id="indexhtml이-있는-경우">index.html이 있는 경우</h2>
<p>웹 서버는 /(루트)로 요청이 들어오면 자동으로 <code>/var/www/html</code>에서 index.html 파일을 검색합니다. 해당 파일이 있을 경우 200 코드와 함께 파일을 리턴해줍니다. 다시 대상 그룹을 확인해봅시다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/0a9616a6-a059-452a-a9c4-b8cced958038/image.png" alt=""></p>
<p>웹 서버는 실행되었지만, index.html이 없기 때문에 여전히 상태 확인에 실패하였습니다. index.html 파일을 생성해봅시다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/a69be922-ddf9-4836-9556-fbe3a6fdea1b/image.png" alt=""></p>
<p>2대의 서버 중 1대에 다음과 같이 index.html 파일을 생성해보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/651b8dc6-805c-492b-bbb5-91b887be9ebc/image.png" alt=""></p>
<p>다시 확인해보면 elb_test1 인스턴스가 <code>Healthy</code> 상태로 변경되었습니다. 로드밸런서에서 해당 서버로부터 정상적인 응답을 받았다는 의미일 것입니다. 나머지 서버에도 동일한 작업을 수행해주겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/df720c65-5579-40d5-94bf-c79f0c45f6d1/image.png" alt=""></p>
<p>2대의 서버 모두 정상 상태가 되었습니다! 이제 로드밸런서로 요청을 보내면 앞서 작성했던 index.html이 화면에 표시될 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/ed5b1fef-261d-442a-be0b-1beaf5261217/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/e3f9ba06-1b7c-4321-a170-ef38f1959aad/image.png" alt=""></p>
<p>로드밸런서에 의해 요청이 2대의 서버로 분산되기 때문에 instance 1과 instance 2가 번갈아가며 표시되는 것을 확인할 수 있습니다.</p>
<h2 id="indexhtml이-없는-경우">index.html이 없는 경우</h2>
<p>만약 index.html이 아니라 다른 파일이 있다면 어떻게 될까요?</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/0c494992-ba29-4448-a028-5982d5a3e376/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/3dfe2029-db51-4e74-94a0-7737495c4131/image.png" alt=""></p>
<p>index.html 파일을 삭제하고 a.html 이라는 새로운 파일을 생성해줍니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/5afddb4e-8ab0-4f66-bd1e-5664a1fd13c7/image.png" alt=""></p>
<p>다시 상태 확인에 실패하였습니다. 이를 해결하기 위해서는 로드밸런서가 루트 위치에 있는 a.html로 상태 확인 요청을 보내도록 설정해야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/693d1cd8-0c60-48af-b789-cd565d07d097/image.png" alt=""></p>
<p>대상 그룹에서 상태 확인 경로를 <code>/a.html</code> 로 변경해줍니다. 그러면 웹 서버는 <code>/var/www/html</code> 위치에서 a.html 파일을 찾아서 반환해줄 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/4908c418-674b-4fa5-bbdb-81b01a2a8597/image.png" alt=""></p>
<p>다시 정상적으로 돌아왔습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/58a49c0f-1502-4e26-a62b-c2cb86a66ca0/image.png" alt=""></p>
<p>하지만 웹사이트를 확인해보면 a.html 내용이 표시되지 않습니다. 이는 로드밸런서의 요청이 /(루트)로 전달되기 때문에 웹 서버에서 제공해주는 index.html 파일이 표시되는 것입니다. a.html을 확인하고 싶으면 url 뒤에 /a.html을 추가해주어야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/43d71872-2fcd-48dd-8ed2-b25200f68781/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/64711f5c-6c4b-4127-b93d-fa7507945a25/image.png" alt=""></p>
<p>이제 2대의 서버에서 번갈아가며 a.html이 화면에 보여집니다.</p>
<blockquote>
<p>여기서 알 수 있는 것은 로드밸런서 요청과 상태 확인이 별개라는 것입니다. 상태 검사를 a.html의 응답으로 확인한 것일 뿐이고, 요청을 보낼 때는 url을 따로 설정해주어야 합니다.</p>
</blockquote>
<h1 id="결론">결론</h1>
<ol>
<li>상태 확인은 특정 url로 요청을 보냈을 때 정상적인 200 응답이 돌아오는 것을 체크하는 것이다.</li>
<li>상태 확인과 실제 로드밸런서의 요청을 별개의 내용이다.</li>
</ol>
<p>처음 로드밸런서를 실습할 때 해당 내용이 많이 헷갈렸습니다. 분명히 EC2 인스턴스가 실행 중인데 상태 검사는 왜 계속 실패하는지, 상태 검사 url과 로드밸런서의 리스너 url은 왜 따로 있는지 등을 이해하는 데에 많은 시간을 소요하였습니다. 계속 상태 검사 url을 변경해보고, 다양한 방식으로 구성을 변경하다 보니 비로소 작동 원리를 이해할 수 있었습니다.</p>
<p>저처럼 로드밸런서를 구축할 때 헤매는 분이 계시다면 이 글이 도움이 되었으면 좋겠습니다 😄</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] CAA-C03 자격증 취득 후기]]></title>
            <link>https://velog.io/@jiwoo_048/aws-solutions-architect-associate</link>
            <guid>https://velog.io/@jiwoo_048/aws-solutions-architect-associate</guid>
            <pubDate>Wed, 31 Jan 2024 02:48:42 GMT</pubDate>
            <description><![CDATA[<p>AWS Solutions Architect Associate 자격증을 취득하기 위해 어떤 준비를 했는지에 대한 글을 작성해보고자 합니다!</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/d0ca05b9-445b-4002-89cb-c722fa51aff0/image.png" alt=""></p>
<p>자격증을 취득하게 된 배경은 클라우드 엔지니어로 직무를 결정하고 나서 클라우드에 대해 아무것도 모르는 상태니까 자격증을 따면서 공부해보자라는 생각이 들어서 였습니다. 그래도 클라우드 자격증이 하나라도 있어야 클라우드 엔지니어로서 한 걸음 나아가는 것이라 생각했기 때문에 자격증 취득만을 위해서가 아닌 클라우드에 대해 공부하기 위한 목적으로 자격증 준비를 시작했습니다.</p>
<p>AWS에서 취득할 수 있는 자격증의 종류는 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/dd6f9069-f90a-42ed-8a2d-3ff6f6a33b8e/image.png" alt=""> 출처: <a href="https://aws.amazon.com/ko/certification/?nc2=sb_ce_co">https://aws.amazon.com/ko/certification/?nc2=sb_ce_co</a></p>
<p>등급 순서는 <code>Foundational</code>, <code>Associate</code>, <code>Professional</code> 입니다. Foundational이 가장 하위 등급입니다. <code>Specialty</code>라고 별도의 전문가용 자격증이 존재합니다.</p>
<p>자격증에 등급이 나눠져 있지만, 꼭 하위 등급부터 응시할 필요는 없습니다. 만약 자신이 있다면 Foundational을 건너뛰고 Associate나 Professional을 도전해도 됩니다. 사람들이 가장 많이 응시하는 것은 Associate입니다. 바로 Professional을 응시하기에는 가격이나 난이도 측면에서 부담이 크기 때문에 보통 Associate 부터 따는 것 같습니다. </p>
<p><em>Professional은 가격이 $300 정도입니다😨</em> </p>
<p>또한 자격증을 취득하면 다음 시험에 대한 할인 쿠폰을 제공해줍니다. 만약 Associate를 합격했다면, 다음에 Professional을 응시할 때 50% 할인된 가격을 제공합니다. 그래서 하위 등급을 먼저 취득하는 것이 금액 측면에서도 괜찮은 선택일 수 있습니다.</p>
<p>저는 클라우드에 대한 경험과 지식이 별로 없었기 때문에 Foundational과 Associate 중에 고민했지만, 저의 최종 목표는 Professional을 취득하는 것이었기 때문에 Fouondational부터 시작하기에는 가격이 부담되었습니다. 그래서 Associate에 도전했습니다. Associate는 $150로, 환율을 고려하여 17만원 정도입니다.</p>
<p>저는 공부 목적으로 자격증을 취득하는 것이었기 때문에, 단순히 덤프를 외우면서 자격증을 준비하는 것은 의미가 없다고 생각했습니다. 그래서 <code>Udemy 강의</code>와 <code>덤프 공부</code>를 병행하여 공부하였습니다.</p>
<h1 id="📚-공부-방법">📚 공부 방법</h1>
<h2 id="1-udemy-강의">1. Udemy 강의</h2>
<p><a href="https://www.udemy.com/course/best-aws-certified-solutions-architect-associate/">강의 링크</a></p>
<p>개인적으로 Udemy 강의를 듣는 것이 도움이 많이 되었습니다. AWS의 리소스와 서비스, 아키텍처에 대해서 상세하게 알려주는데, 기초부터 설명해주기 때문에 AWS를 처음 접하는 사람도 쉽게 이해할 수 있을 것 같았습니다. 저는 강의를 듣고 Notion을 통해 강의에서 다룬 내용을 다시 리마인드하면서 정리하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/abee58f0-6d32-4c00-ad41-4dea533fb6fc/image.png" alt=""></p>
<blockquote>
<p>저는 강의의 내용을 전부 다 정리하려고 했는데, 나중에 다시 봤을 때 중요한 내용을 알아보기 쉽도록 핵심만 간략하게 정리하는 것을 추천드립니다..</p>
</blockquote>
<p>총 33개 섹션에 391개의 강의로 이루어져있기 때문에 모든 내용을 정리하기가 쉽지 않았습니다. 공부 기간은 총 한달 반 정도였는데, 하루에 들을 분량을 정해놓고 매일매일 꾸준히 듣지않으면 절대 완강하지 못할 것 같습니다. 그래서 강의로 공부를 하고자 한다면, 최소 2달은 시간을 투자하는 것이 좋을 것 같습니다.</p>
<p><em>마지막에 벼락치기로 완강을 하기는 하였으나 죽는줄 알았습니다..</em></p>
<p>시험도 AWS의 상세한 기능을 묻기보다 어떠한 상황에 어떤 서비스를 사용하는 것이 좋은지를 선택하는 유형의 문제가 나오기 때문에, 각각의 서비스들을 너무 꼼꼼히 볼 필요는 없을 것 같습니다.</p>
<p>Udemy에서는 주기적으로 할인을 합니다. 그래서 원래 8만원이 넘는 가격인데 할인 기간에 사면 12,000~14,000원이면 살 수 있으므로 꼭 할인 기간을 이용하기를 바랍니다.</p>
<h2 id="2-무료-덤프">2. 무료 덤프</h2>
<p><a href="https://www.examtopics.com/exams/amazon/aws-certified-solutions-architect-associate-saa-c03/">examtopics</a></p>
<p>Udemy 강의를 다 듣고 나니 시험까지 남은 기간이 3일 뿐이었습니다. 그래서 남은 3일 동안에는 examtopic을 통해 덤프 문제를 풀었습니다. 600개가 넘는 문제가 있기 때문에 전부 다 풀지는 못했고, 200문제 정도 풀고 시험을 봤습니다. Udemy 강의를 제대로 들었다면 200문제 정도만 풀어도 충분할 것 같습니다. 덤프에 정답이 나오기는 하지만 엥? 스러운 정답도 많이 있었습니다. 그래서 정답을 너무 믿고 외우지 말고, 커뮤니티를 참고하는 것이 좋습니다. 커뮤니티에서 가장 많이 정답으로 고른 답이 표시되고, 사람들이 왜 정답으로 생각하는 지를 상세하게 설명해놓기 때문에 많은 도움이 될 것입니다.</p>
<p>유료 덤프 문제도 존재합니다. 무료 덤프와 달리 정확한 답과 해설이 제공되기 때문에, 불안하다면 유료 덤프를 구입하여 풀어보는 것도 좋을 것 같습니다.</p>
<blockquote>
<p>제 생각에는 Associate 수준에서는 무료 덤프로도 충분할 것 같습니다.</p>
</blockquote>
<h1 id="😊-결론">😊 결론</h1>
<p>한달 반 정도의 짧다면 짧고 길다면 긴 공부 기간을 거치며 느낀점은 좀 미리 시작했다면 좋았을 것 같다는 것이었습니다. 사실 한달 반이라고는 하였지만 졸업작품과 같이 병행하면서 진행했기 때문에 실질적인 공부 기간은 한달 정도였을 것입니다. 덤프만 풀면서 공부했다면 충분한 시간일 것 같지만, Udemy 강의를 하나하나 꼼꼼히 보려고했기 때문에 짧은 시간이었습니다.</p>
<p>만약 저처럼 공부 목적으로 자격증을 취득하려는 사람이 있다면, 최소한 2달 전부터 공부를 시작하여 AWS Well-Architected 같은 백서 공부도 병행하면 좋을 것 같습니다. <a href="https://aws.amazon.com/ko/architecture/?cards-all.sort-by=item.additionalFields.sortDate&amp;cards-all.sort-order=desc&amp;awsf.content-type=*all&amp;awsf.methodology=*all&amp;awsf.tech-category=*all&amp;awsf.industries=*all&amp;awsf.business-category=*all">AWS 아키텍처 센터</a>를 통해 AWS에서 제공하는 아키텍처 사례를 담고있는 백서를 확인할 수 있습니다.</p>
<p>백서를 읽어보면 클라우드 아키텍처에 대해 훨씬 잘 이해할 수 있게 되고, 효과적인 아키텍처에 대해 고민할 수 있을 것 같다는 생각이 들었습니다. 이제 자격증을 취득하였으니 백서를 보면서 아키텍처를 다시 공부해나갈 예정입니다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/6ea69c3c-6fe7-4db7-a1e9-d42a02c02722/image.png" alt=""></p>
<p>시험 결과는 767점. 간신히 합격했습니다! 다음에는 Solutions Architect Professional도 도전해볼 것입니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[교육이수] 리눅스 국비교육 최종 발표회 및 회고]]></title>
            <link>https://velog.io/@jiwoo_048/linux-education</link>
            <guid>https://velog.io/@jiwoo_048/linux-education</guid>
            <pubDate>Sat, 07 Oct 2023 13:51:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>2023.04~2023.08 기간동안 달려왔던 K-Digital Training 리눅스 시스템 및 커널 전문가 과정 교육을 드디어 수료했다. 이는 최종 발표회에 대한 기록이다.</p>
</blockquote>
<p>최종 발표회는 서초구에 위치한 프로그래머스 본사에서 진행되었다. 각자 수업 내용 중에 흥미롭게 들었던 부분을 선택하여 본인만의 스타일로 재구성하여 발표하는 방식이다. 나는 <code>프로세스 스택</code>을 주제로 선택하여 발표하였다. 다음은 발표했던 내용을 정리한 것이다.</p>
<h1 id="프로세스-스택">프로세스 스택</h1>
<h2 id="프로세스-스택의-구조">프로세스 스택의 구조</h2>
<p>프로세스에 대한 정보는 <code>task_struct</code> 구조체에 저장된다. 이는 프로세스의 속성 정보를 표현하는 중요한 자료구조로, 프로세스가 생성되면 커널에 의해 프로세스에게 할당된다. 즉 프로세스마다 1개의 task_struct 구조체를 가지게 되는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/5f5123aa-cb7a-4ce7-ab5a-f57343d0aef5/image.png" alt=""> 출처: <a href="https://www.researchgate.net/figure/Linux-task-struct-structure-with-PCX-fields_fig21_308737624">https://www.researchgate.net/figure/Linux-task-struct-structure-with-PCX-fields_fig21_308737624</a></p>
<p>task_struct 구조체의 구조는 다음과 같다. 프로세스 상태(state), 프로세스 아이디(pid), 부모 프로세스(parent) 등을 저장한다. 이때 주의깊게 보아야할 속성은 <code>thread_info</code>이다. thread_info 구조체는 프로세스 스택의 최하단에 위치해있다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/bf9efce9-d787-4172-836b-63bfcb0aa3ed/image.png" alt="">
출처: <a href="https://www.doc-developpement-durable.org/file/Projets-informatiques/cours-&amp;-manuels-informatiques/Linux/Linux%20Kernel%20Development,%203rd%20Edition.pdf">Linux Kernel Development (3rd Edition)</a></p>
<p>프로세스 스택은 높은 주소 공간에서 낮은 주소 공간으로 자란다. thread_info 구조체는 스택의 최하단에 위치하므로, 가장 낮은 주소 공간에 배치될 것이다. thread_info의 속성으로 <code>task</code>가 있는데, 이는 task_struct의 시작 주소를 가리킨다.</p>
<p>프로세스 내에서 함수가 실행되면 해당 함수로 jump 하기 전에 레지스터 값, 복귀할 주소 등을 저장해두어야 한다. 이러한 정보가 저장되는 공간이 바로 프로세스 스택이다. 즉, 함수가 실행이 되면 현재 정보를 스택에 push 한다. 스택에는 함수가 종료된 후 복귀할 주소가 들어있으므로, 함수 실행이 끝나면 스택에서 값을 pop 한 뒤, program counter가 복귀할 주소를 가리키게 함으로써 원래 상태로 되돌아간다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/031356bb-2222-45c1-969b-f67fa1618aeb/image.png" alt="">
이는 TRACE32라는 디버깅 툴로 확인한 스택 공간이다. <img src="https://velog.velcdn.com/images/jiwoo_048/post/b7362249-3bbb-4035-a28e-f4ed332f89e4/image.png" alt=""> 
이때 실행한 함수의 목록은 다음과 같은데, <code>invoke_syscall()</code>, <code>do_el0_syc()</code> 함수를 스택 공간에서 확인할 수 있다. </p>
<p>스택을 더 자세히 살펴보면 다음과 같다.
<img src="https://velog.velcdn.com/images/jiwoo_048/post/ea5a5e43-e83c-47e3-8923-c513a4b55e8d/image.png" alt=""> 가장 낮은 주소부터 스택이 시작된다. 스택에 최하단에 <code>thread_info</code> 구조체가 저장되어있는 것을 확인할 수 있다. 즉, thread_info의 주소는 스택의 시작 주소와 같다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/cfdcd6d1-bd3d-4398-b78d-25c2e1a2b74f/image.png" alt=""> 프로세스에서 호출된 함수의 흐름을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/c27bea46-6a23-40ea-8fca-d0ff1edeb9d4/image.png" alt=""> 이 부분은 레지스터 값들이 저장된 것이다. 함수가 종료되고 나면 해당 값들을 pop하여 함수를 실행하기 전 상태로 돌아간다.</p>
<h2 id="프로세스-스택-할당-과정">프로세스 스택 할당 과정</h2>
<p>프로세스가 생성되면 리눅스 커널은 <code>copy_process()</code>라는 함수를 호출한다.
<img src="https://velog.velcdn.com/images/jiwoo_048/post/8d79b70c-aaec-48f3-b5d1-58188b6e7b25/image.png" alt=""> <img src="https://velog.velcdn.com/images/jiwoo_048/post/47b90148-5442-42be-9a22-94a0ddb1fbeb/image.png" alt=""></p>
<p>copy_process 함수 내부에서 <code>dup_task_struct</code>라는 함수를 호출한다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/10d98f7f-1643-422f-8509-c740c12ca8ef/image.png" alt=""></p>
<p><code>dup_task_struct</code> 함수에서 <code>alloc_thread_stack_node()</code>함수를 통해 스택을 위한 공간을 할당해준다. 그 뒤, task_struct의 stack 속성에 할당받은 공간을 연결해줌으로써 프로세스에 스택이 할당된다.</p>
<h1 id="스택-카나리">스택 카나리</h1>
<p>스택 카나리는 프로세스 스택의 오버플로우를 검출할 수 있도록 저장되는 값이다.
<img src="https://velog.velcdn.com/images/jiwoo_048/post/5f5123aa-cb7a-4ce7-ab5a-f57343d0aef5/image.png" alt="">
출처: <a href="https://www.researchgate.net/figure/Linux-task-struct-structure-with-PCX-fields_fig21_308737624">https://www.researchgate.net/figure/Linux-task-struct-structure-with-PCX-fields_fig21_308737624</a></p>
<p>위에서 본 그림을 다시 확인해보면, thread_info 구조체 아래에 stack_canary 가 저장되어 있다. 해당 stack_canary 필드는 검사에 이용되는 원본 카나리 값이다.</p>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/58989284-40fd-43f8-a8a1-c311042b1b2a/image.png" alt=""></p>
<p>스택에 저장되는 스택 프레임은 다음과 같은 형태로 되어 있다. 스택은 그림을 기준으로 위에서 아래 방향으로 자라나기 때문에 만약 오버플로우가 발생한다면 카나리 값이 변조될 수밖에 없을 것이다. 함수 실행 전에 카나리 값을 프레임에 함께 넣어 push 하고, 함수 실행이 끝난 뒤 해당 프레임을 pop 하여 카나리 값을 검사했을 때 변조되었다면 오버플로우가 발생했음을 짐작할 수 있다. 이는 중요한 값인 복귀할 주소가 오염되지 않도록 방지하는 역할과 동시에 공격자에 의한 버퍼 오버플로우 공격을 감지하여 막는 역할도 수행할 수 있다.</p>
<h2 id="카나리-값이-저장되는-과정">카나리 값이 저장되는 과정</h2>
<p><img src="https://velog.velcdn.com/images/jiwoo_048/post/3b0025aa-cffc-411d-806f-7483415ddd08/image.png" alt=""></p>
<p>이는 어셈블리 명령어의 일부분이다. 하나씩 살펴보자.</p>
<pre><code class="language-asembly">mrs        x2,#0x3,#0x0,c4,c1,#0x0        ;    x2, SP_EL0</code></pre>
<p>이 명령어는 x2의 값을 SP_EL0 레지스터에 저장하는 명령어이다. x2에는 보통 현재 실행 중인 프로세스의 task_struct의 시작 주소를 저장하고 있다.</p>
<pre><code class="language-asembly">ldr        x3,[x2,#0x540]        ;    x3,[x2,#1344]</code></pre>
<p>이는 x2로부터 0x540만큼 떨어진 곳의 값을 x3에 저장하는 명령어이다. 앞서 x2에는 task_struct의 시작주소를 저장되어 있다고 하였다. 
<img src="https://velog.velcdn.com/images/jiwoo_048/post/c1eb44ba-fa4c-40c2-8553-b5846e23fe1b/image.png" alt=""> task_struct의 시작 주소로부터 0x540만큼 떨어진 곳에는 stack_canary 값이 저장되어 있다. (0x540을 10진수로 변환한 것이 1344이다) 그러면 스택 카나리 값을 x3에 저장한 것이다.</p>
<pre><code class="language-asembly">str        x3,[sp,#0x1A8]        ;    x3,[sp,#424]</code></pre>
<p>이는 sp(스택 포인터)가 가리키는 곳에서 0x1A8만큼 더한 곳에 x3 값을 저장하는 명령어이다. 즉 스택 공간에 스택 카나리 값을 저장했음을 알 수 있다.</p>
<p>이러한 과정을 거쳐 스택에 카나리 값이 저장된다. 이제 모든 함수 실행이 끝나고 나면 저장되었던 값과 task_struct에 저장되어 있는 값을 비교하여 만약 다르다면 <code>_stack_chk_fail()</code>함수가 컴파일러에 의해 삽입되면서 더이상의 코드 실행이 이루어지지 않고 panic 모드로 전환된다. 공격이 일어났을 수도 있으니 이를 방지하기 위해 아예 프로세스를 종료시켜버리는 것이다.</p>
<h1 id="😊-회고">😊 회고</h1>
<p>발표회를 위해 주어진 시간은 일주일 정도밖에 되지 않아서, 자료 조사, ppt 제작 및 발표 영상 촬영까지 하는 것이 빠듯했다. 단순히 영상을 듣는 것에서 그치지 않고 다른 사람에게 설명하기 위해서는 나부터가 해당 개념을 완벽히 숙지하고 있어야 했기 때문이다. 이게 맞는 개념인지를 끊임없이 고민하고 의심하면서 정확한 자료를 찾기 위해 노력했고, 결과적으로 발표를 잘 마칠 수 있었다. 멘토님께 어려운 개념인데 이해하기 쉽게 잘 정리했다는 칭찬도 받았다 :) 교육을 들으면서 기억에 남는 내용이 별로 없는데, 프로세스 스택만큼은 확실히 잊어버리지 않을 것 같다. 교육을 들으며 중간중간 포기하고 싶었던 적도 많았는데, 그래도 포기하지 않고 하루하루 해나가다보니 여기까지 올 수 있었던 것 같다. </p>
<p>교육은 수료하였지만, 학업과 병행하다보니 놓친 부분도 많이 있어서 아쉽기는 하다. 그래도 생소한 리눅스 커널에 대해 동작 원리를 조금이나마 이해할 수 있었던 교육이었던 것 같다.</p>
]]></description>
        </item>
    </channel>
</rss>