<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>halim.log</title>
        <link>https://velog.io/</link>
        <description>나는 하림</description>
        <lastBuildDate>Sun, 09 Jan 2022 10:39:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. halim.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/halim_limha" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Linux 네트워크 관련 명령어 정리]]></title>
            <link>https://velog.io/@halim_limha/Linux-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B4%80%EB%A0%A8-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@halim_limha/Linux-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B4%80%EB%A0%A8-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 09 Jan 2022 10:39:58 GMT</pubDate>
            <description><![CDATA[<h2 id="ifconfig">ifconfig</h2>
<p>시스템의 네트워크에 대한 전반적인 정보를 출력하고 설정을 수정할 수 있다. (참고로 Windows는 ifconfig 명령어 대신 ipconfig 명령어를 사용한다.)
명령어 입력하고 출력을 해보면 아래와 같은 정보가 출력된다. </p>
<pre><code class="language-bash">$ ifconfig</code></pre>
<p><img src="https://images.velog.io/images/halim_limha/post/235a51cc-cb1a-46d4-8a7f-d591840d8ef1/image.png" alt=""><img src="https://images.velog.io/images/halim_limha/post/e312dedf-d4c2-4830-9a7f-42f1031d1a49/image.png" alt="">
eth0은 첫번째 이더넷 카드를 말하고 lo는 Loopback 주소이다.eth0는 공유기에서 할당받은 사설 네트워크 대역의 IP(192.168.0.0/16)을 받았고 lo는 Loopback 주소인 127.0.0.1을 받았다. </p>
<blockquote>
<p>참고 : NIC이라고 불리는 Network Interface Card는 이더넷 카드보다 좀 더 포괄적인 개념이다. Wifi 동글도 일종의 NIC이라고 할 수 있고 이더넷 카드도 마찬가지이다. </p>
</blockquote>
<p>ifconfig은 네트워크 환경정보를 출력하는 것 외에도 직접 설정을 조작하거나 네트워크 드라이버를 On/Off 할 수 있다고 했다. </p>
<p>그러나 ifconfig에서 진행한 설정 변경은 메모리 위에서만 저장되어 있기 때문에 서버가 재시작하면 원상복구된다. 이 설정을 계속 유지시키고싶다면 직접 네트워크를 설정하는 파일을 수정해야 한다. </p>
<p>네트워크 관련 파일들의 목록은 아래와 같다. </p>
<ol>
<li><strong>/etc/sysconfig/network</strong> : 네트워크 활성화 여부, Host명, 게이트웨이를 지정한다. </li>
<li><strong>/etc/resolv.conf</strong> : DNS 서버의 주소가 저장되어 있다. </li>
<li><strong>/etc/sysconfig/network-scripts</strong> : 네트워크 관련 명령어들이 저장되어 있는 디렉토리</li>
<li><strong>init 스크립트</strong>(/etc/init.d/network) or <strong>Systemd 관리 서비스</strong></li>
</ol>
<p>그래도 하나 기억해야 할 것은 ifconfig up/down 명령어이다. 직접적으로 NIC을 끄고 켜는 명령어이다. 네트워크 설정을 바꾼 다음에 NIC도 재시작을 해야 설정이 적용이 되는 경우가 있어 이 명령어가 필요하다.</p>
<pre><code class="language-bash"># 첫번째 이더넷 카드 사용 중지
$ ifconfig eth0 down
# 첫번째 이더넷 카드 사용 시작
$ ifconfig eth0 up</code></pre>
<p>따라서 원격 접속 시에는 함부로 ifconfig down을 하면 원격 접속이 바로 끊길 수 있으니 주의해야 한다. </p>
<p>추가로 <strong>ifup</strong>과 <strong>ifdown</strong>(빌트인 명령어가 아닌 스크립트이다. /etc/sysconfig/network-scripts 디렉토리에서 찾을 수 있다.)도 비슷한 역할을 한다. 
그렇다면 차이점이 궁금할 수도 있는데, 구글링하면 쉽게 찾을 수 있다. 
레드햇 지식 베이스 : <a href="https://access.redhat.com/solutions/27166">https://access.redhat.com/solutions/27166</a></p>
<p>링크를 들어가면 이미 요약이 되어 있지만 한번 더 요약 및 번역을 해보면 아래와 같이 요약을 할 수 있을 것이다. </p>
<ul>
<li>ifconfig과 ifup/down은 둘 다 NIC을 활성화/비활성화 시킬 수 있다. 다만 ifup/down은 스크립트이고 이 스크립트는 다시 안에서 &#39;<strong>ip</strong>&#39;라는 명령어를 사용한다. </li>
<li>둘 다 ioctl() 시스템콜을 사용하여 NIC을 제어한다. 그러나 ifconfig은 커널에 IFF_UP과 IFF_RUNNING Flag를, ifup/down은 단지 IFF_UP Flag를 던져준다고 한다. <blockquote>
<p>참고 : IFF_UP Flag와 차이는 있지만 IFF_RUNNING Flag는 크게 신경쓰지 않아도 될 듯하다. <a href="https://stackoverflow.com/questions/11679514/what-is-the-difference-between-iff-up-and-iff-running">https://stackoverflow.com/questions/11679514/what-is-the-difference-between-iff-up-and-iff-running</a></p>
</blockquote>
</li>
<li>ifconfig은 IP나 고정 라우팅 설정까지 하지 않지만 ifup/down은 한다. </li>
</ul>
<h2 id="iwconfig">iwconfig</h2>
<p>ifconfig이 이더넷을 포함한 모든 네트워크 환경 정보를 출력해주었다면 iwconfig은 무선랜 네트워크 환경을 출력해준다. </p>
<p>일단 실험하고 있는 WSL CentOS 7 리눅스 환경에는 iwconfig 명령 자체를 찾을 수 없고 다운로드 받아야 한다. </p>
<p>그러나 다운로드 받아도 데스크탑에 무선랜 장치가 없기 때문에 아무것도 뜨지 않을 것이다. 나중에 WiFi 동글 찾아서 꼽고 실험을 해봐야 할 것 같다. </p>
<p>책을 보니 ifconfig과는 다르게 sit0라는 특별한 인터페이스가 있다는 것을 볼 수 있는데 이는 무선 환경에서는 IPv4와 IPv6의 호환을 위해 사용하는 특수 목적의 가상장치를 이용하기 때문이라고 한다. </p>
<h2 id="ip">ip</h2>
<p>위에서도 나왔지만 ipup/down 스크립트에서 사용되는 명령어이다. 라우팅과 디바이스 터널링에 대한 정보를 출력하고 조작할 수 있다. </p>
<h2 id="route">route</h2>
<p>커널의 라우팅 테이블을 출력하거나 수정할 수 있다. </p>
<pre><code class="language-bash">$ route</code></pre>
<p><img src="https://images.velog.io/images/halim_limha/post/ba353fec-688b-493c-8399-d54cc682d23e/image.png" alt="">
위의 사진을 보면 직접 route 명령어를 쳐서 출력한 결과이다. 리스트의 가장 첫번째가 중요한데, 의미를 해석해보자면 Destination이 0.0.0.0인 패킷은 모두 192.168.25.1 GW(내가 쓰는 공유기)를 거친다는 것이다. 
Genmask는 패킷의 목적지 IP에 AND 연산을 통해 목적지 IP를 지정하는 역할을 한다. </p>
<h2 id="tcpdump">tcpdump</h2>
<p>네트워크 패킷을 실시간으로 출력, 옵션 없이 실행하면 모든 네트워크 패킷을 볼 수 있고, 옵션을 통해 port를 지정해줄 수도 있다. </p>
<h2 id="netstat">netstat</h2>
<p>ifconfig가 네트워크 인터페이스 환경에 대한 정보였다면 netstat은 네트워크 통계 정보라고 볼 수 있다. 주로 사용되고 있는 포트가 어떤 것이 있는지 확인하기 위해서 이 명령어를 사용한다. </p>
<pre><code class="language-bash">$ netstat -lptu</code></pre>
<p>netstat 명령어를 많이 사용해봤을 수도 있지만 netstat 명령어와 여러가지 옵션이 붙어 헷갈릴 때가 많다. 
매뉴얼 페이지를 보고 간단하게 정리를 해보면 아래와 같다.</p>
<ol>
<li>-l : Listening, 즉 사용되고 있는지 여부를 따진다.</li>
<li>-p : p는 program의 약자, Process의 PID와 이름을 보여준다.</li>
<li>-t : TCP</li>
<li>-u : UDP, 그러니까 옵션에 tu가 들어가 있는 것은 TCP와 UDP 포트 모두 보겠다는 것이다. </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[RSelenium 과제 도와주기]]></title>
            <link>https://velog.io/@halim_limha/RSelenium-%EA%B3%BC%EC%A0%9C-%EB%8F%84%EC%99%80%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@halim_limha/RSelenium-%EA%B3%BC%EC%A0%9C-%EB%8F%84%EC%99%80%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Sun, 12 Dec 2021 06:09:48 GMT</pubDate>
            <description><![CDATA[<h1 id="작성-계기">작성 계기</h1>
<p>어젯밤, 친구에게로부터 과제 도움 요청을 받았다. 평소에 친구들 과제 도와주는 것을 좋아하던 터라 흔쾌히 도움을 주겠다고 했고, 들어본 적은 있지만 사용해본 적은 없는 <strong>R 언어</strong>와 <strong>Selenium</strong>이라는 프레임워크를 이용한 과제였기에 흥미로워 도움을 주는 것이 재미있을 것 같았다. </p>
<p>그리고 이 포스팅을 하면서 <strong>문제 해결을 했던 과정</strong>들을 남겨보려고 한다. 내가 R 언어로 쓰인 코드를 이번 과제를 도와주면서 처음 봤음에도 불구하고 문제 해결에 도움을 줄 수 있었던 이유는 아무래도 문과생인 친구보다는 컴퓨터 공학을 공부한 내가 문제가 되는 코드 한 줄을 찾는 과정과 노하우를 더 잘 알기 때문이다. (나도 초보 수준이긴 하지만,,)</p>
<p>학창시절, 친구들의 코딩 과제를 도와주면서 느낀 것이 있는데 코딩을 처음 해보는 친구들 중 처음부터 잘하는 친구들도 있긴 했지만 대부분은 자신이 작성한 코드에 문제가 생기면 어디서부터 문제가 시작되는 지를 잘 파악하지 못했다는 것이다. 그러다 보니 문제 해결을 위한 검색도 더 넓은 범위에서 시작하게 되어 해결에 오랜 시간이 걸리는 것 같다. 내가 이번 포스팅에서 강조하고 싶은 것은 <strong>문제의 시작점을 찾기 위해 문제가 발생된 영역을 점점 좁혀 나가는 방법</strong>이다. 비록 초보적인 수준이긴 하지만 코딩을 접한지 얼마되지 않은 사람들에게는 충분히 도움이 될 수 있을 것 같다. </p>
<hr>
<h1 id="문제-설명">문제 설명</h1>
<h2 id="필요-배경-지식">필요 배경 지식</h2>
<p>문제를 설명하기 전에 R 언어와 Selenium이 무엇인지에 대해서 먼저 설명이 필요할 것 같다. 내가 과제 도움 요청을 받기 전에도 R과 Selenium에 대해서 들어본 적은 있기 때문에 대충은 알고 있었다. 아래의 설명은 내가 아는 선까지만 얘기를 하고 자세한 설명과 정의는 WIKI백과를 이용하거나 검색을 하여 찾기를 바란다. </p>
<blockquote>
<p><strong>R 언어</strong> : 주로 통계 및 데이터 분석에 자주 사용되는 언어, 스크립트 언어라 굳이 컴파일을 안해도되고 한줄한줄씩 실행해서 결과를 볼 수 있다. </p>
</blockquote>
<blockquote>
<p><strong>Selenium</strong> : 웹페이지를 방문하여 웹페이지의 조작을 자동화 해주는 소프트웨어 프레임워크. 웹 페이지에서 데이터를 자동으로 수집하는(이것을 웹 크롤링이라고도 한다.) 프로그램을 만드는 데 사용될 수도 있고, 어떤 사람은 Selenium을 사용해 잔여백신을 자동으로 확인해주는 프로그램을 만든 것도 본 적이 있다. </p>
</blockquote>
<h2 id="문제-상황">문제 상황</h2>
<p>친구가 하려던 과제는 R 언어와 Selenium을 활용해 Youtube 웹페이지를 방문하여 동영상의 제목, URL, 조회수, 댓글수 등의 데이터를 모아서 하나의 csv 파일에 쓰는 것이었다. </p>
<h3 id="code-분석">Code 분석</h3>
<p>문제를 찾아달라는 코드는 아래와 같았다. </p>
<pre><code class="language-r">for(i in 1:length(trending_yt$URL)){
  remDr$navigate(trending_yt$URL[i])
  Sys.sleep(10)

  page &lt;- remDr$getPageSource()[[1]]
  html &lt;- read_html(page)

  channel &lt;- html %&gt;% html_node(xpath=&#39;//*[@id=&quot;text&quot;]/a&#39;) %&gt;% html_text()
  subscriber &lt;- html %&gt;% html_node(xpath=&#39;//*[@id=&quot;owner-sub-count&quot;]&#39;) %&gt;% html_text()
  view &lt;- html %&gt;% html_node(xpath=&#39;//*[@id=&quot;count&quot;]/ytd-video-view-count-renderer/span[1]&#39;) %&gt;% html_text()
  date &lt;- html %&gt;% html_node(xpath=&#39;//*[@id=&quot;info-strings&quot;]/yt-formatted-string&#39;) %&gt;% html_text()
  comment &lt;- remDr$findElement(using = &quot;xpath&quot;, &#39;//*[@id=&quot;count&quot;]/yt-formatted-string&#39;)$getElementText()[1][[1]]

  trending_yt$Channel[i] &lt;- ifelse(str_detect(channel, &quot;[[:alnum:]]&quot;), channel, NA)
  trending_yt$Subscriber[i] &lt;- ifelse(str_detect(subscriber, &quot;[[:alnum:]]&quot;), subscriber, NA)
  trending_yt$View[i] &lt;- ifelse(str_detect(view, &quot;[[:alnum:]]&quot;), view, NA)
  trending_yt$Date[i] &lt;- ifelse(str_detect(date, &quot;[[:alnum:]]&quot;), date, NA)
  trending_yt$Comment[i] &lt;- ifelse(str_detect(comment, &quot;[[:alnum:]]&quot;), comment, NA)
}


trending_yt

write.csv(trending_yt, file=&quot;trending_yt.csv&quot;, row.names = F)
</code></pre>
<p>문제를 해결하기 전에 대략적인 코드 해석이 필요할 것으로 보인다. </p>
<ul>
<li><h4 id="1-remdr">1. remDr</h4>
<p>익숙하지 않은 코드에 당황하기도 했지만, 차근차근보면 <strong>remDr이라는 객체가 웹 페이지를 방문하고 데이터를 받아오는 역할</strong>을 하는 것을 알 수 있다. (검색해서 찾아보니 remDr은 remote Driver란 뜻이고 RSelenium 라이브러리에 포함이 되어있는 친구였다.)</p>
</li>
<li><h4 id="2-syssleep10">2. Sys.sleep(10)</h4>
<p>처음보는 언어라도 sleep는 어느 언어에서든 의도적으로 지연을 주기 위해서 사용되므로 <strong>10초간 지연을 의도적으로 준 것이라고 해석</strong>할 수 있다. 아마도 Youtube에 웹 페이지 요청을 과도하게 빠르게 보내는 것을 방지하고 웹 크롤러가 블락 당하는 것을 방지하기 위해서 사용된 것으로 보인다. 추가로 <strong>for문 안에 이 sleep 함수가 존재하기 때문에 만약에 코드가 정상적으로 실행이 된다면 실행하는데 오랜 시간이 걸릴 것</strong>이다. 에러를 찾는데 힌트가 된 부분이기도 하다. </p>
</li>
<li><h4 id="3-page-html">3. page, html</h4>
<p>remDr이란 객체의 getPageSource라는 함수를 통해서 page의 소스를 얻어온 부분이다. 그리고 얻어온 page 소스를 html로 읽어 html이라는 객체에 저장한다. </p>
</li>
<li><h4 id="4-channel-subscriber-view-date-comment">4. channel, subscriber, view, date, comment</h4>
<p>각 객체에는 위에서 얻은 html이란 객체에서 channel 정보, 구독자 정보, 조회수, 날짜, 댓글수를 찾아서 저장한 것이다. 아래에서 얘기하겠지만 문제가 생긴 부분은 일단 댓글수인 comment이다.  </p>
</li>
<li><h4 id="5-trending_yt">5. trending_yt</h4>
<p>4번까지 이해가 갔다면 trending_yt는 일종의 Dataframe이라고 할 수 있다. 그러니까 trending_yt$Channel[i]...로 시작하는 코드는 trending_yt의 Channel이라는 컬럼의 i번째 행에 channel이라는 객체(text)를 넣겠다는 의미이다.  </p>
</li>
<li><h4 id="6-writecsv">6. write.csv</h4>
<p>마지막으로 trending_yt라는 Dataframe을 csv 파일에 쓴다. </p>
</li>
</ul>
<h3 id="문제-상황-설명">문제 상황 설명</h3>
<p>문제 상황에 대한 이야기는 2가지이다. 말해준 순서대로 얘기를 해보면</p>
<p><strong>①</strong> 댓글수를 받아오는 부분(comment &lt;- remDr$findElement...)이 어쩔 때는 실행이 되고 어쩔 때는 실행이 안된다.<br><strong>②</strong> trending_yt라는 Dataframe을 csv 파일로 쓸 때, for문에서 trending_yt에 추가한 컬럼들은 제외가 되었다. </p>
<p>친구의 말로는 상황①이 언제는 되고 언제는 안된다고 하여 ②부터 진행을 하고 있다고 하여 나도 그 문제부터 해결하고자 시도해보았다. </p>
<h3 id="문제-해결-과정">문제 해결 과정</h3>
<p>메신저로만 문제 상황을 잘 설명하는 것은 쉽지 않다. 게다가 에러 로그를 요구했더니 ①번 상황은 에러 메세지가 뜨지만 ②번 상황에서는 아무런 메세지가 뜨지 않는다고 한다.. 그래서 차라리 전체 실행 과정을 모두 보여달라고 했다. <strong>Google Meet</strong>를 활용하여 <strong>화면공유</strong>를 통해 <strong>실행과정</strong>을 볼 수 있었다. 친구가 말한대로 ①댓글수를 받아오는 부분에서 에러 메세지가 뜨기는 했으나 다음 코드를 계속 실행할 수 있었다. </p>
<p>여기서 이상한 점은 <strong>for문 안에는 Sys.sleep(10)이라는 함수가 사용되고 있는데 for문 실행이 굉장히 빨리 끝났다는 것</strong>이다. Dataframe의 행의 개수가 159개라는데, 그러면 실행하는데는 적어도 159x10초 이상, 대략 25분 이상이 걸려야 하는 것인데 그렇지 못했고 <strong>for문이 정상적으로 실행이 되지 않았다는 것을 예상</strong>할 수 있었다. </p>
<p>그래서 <strong>for문 안에서 에러 메세지를 출력했던 댓글수를 받아오는 부분을 지워보고 실행</strong>해보라고 했다.</p>
<p>예상했던 대로 댓글수를 받아오는 부분을 지우고 실행해보니 실행 시간 중 사담을 나누고 있을 정도로 오래 걸렸다.(한 30분? 걸렸던 것 같다.) 그리고 생성된 csv 파일에도 Comment 컬럼을 제외한 나머지 컬럼들이 정상적으로 write되어 저장이 되었다. 즉 write.csv 함수는 문제가 없다는 것을 알 수 있다. for문 같은 경우, for문 안에서 댓글수를 받아오는 부분에서 에러가 생기다보니 for문 전체가 실행이 안되었던 것이다.</p>
<p>그러면 문제를 좁힐 수가 있다. 위의 코드에서 아래의 댓글수를 받는 부분만 문제가 있는 것이라고 할 수 있다. </p>
<pre><code>comment &lt;- remDr$findElement(using = &quot;xpath&quot;, &#39;//*[@id=&quot;count&quot;]/yt-formatted-string&#39;)$getElementText()[1][[1]]</code></pre><p>확인해본 에러 메세지는 아래와 같다. </p>
<blockquote>
<p>Selenium message:no such element: Unable to locate element: {&quot;method&quot;:&quot;xpath&quot;,&quot;selector&quot;:&quot;//*[@id=&quot;count&quot;]/yt-formatted-string&quot;}
  (Session info: chrome=96.0.4664.93)
For documentation on this error, please visit: <a href="https://www.seleniumhq.org/exceptions/no_such_element.html">https://www.seleniumhq.org/exceptions/no_such_element.html</a>
Build info: version: &#39;4.0.0-alpha-2&#39;, revision: &#39;f148142cf8&#39;, time: &#39;2019-07-01T21:30:10&#39;
System info: host: &#39;LAPTOP-NBG0LFLD&#39;, ip: &#39;192.168.219.103&#39;, os.name: &#39;Windows 10&#39;, os.arch: &#39;amd64&#39;, os.version: &#39;10.0&#39;, java.version: &#39;1.8.0_311&#39;
Driver info: driver.version: unknown
Error:     Summary: NoSuchElement
     Detail: An element could not be located on the page using the given search parameters.
     class: org.openqa.selenium.NoSuchElementException
    Further Details: run errorDetails method</p>
</blockquote>
<p>친절하게도 에러에 대한 문서화가 되어 있어 있다. 
<a href="https://www.seleniumhq.org/exceptions/no_such_element.html">https://www.seleniumhq.org/exceptions/no_such_element.html</a></p>
<p>페이지를 방문해보면 아래와 같이 문제의 원인에 대해서 설명해준다. &quot;No Such Element&quot;라는 에러를 마주했으니 그 부분을 보면 된다. 
<img src="https://images.velog.io/images/halim_limha/post/7ed9848b-9933-4655-baf1-18d06bf23c24/image.png" alt=""></p>
<p>결국 element가 없어서 생긴 문제이며 해결 방안으로는 <strong>locator를 한번 더 확인</strong>해보라는 것이었다. </p>
<p>코드에 사용된 locator의 내용은 &#39;<strong>//*[@id=&quot;count&quot;]/yt-formatted-string</strong>&#39;이다. 아래와 같이 개발자 도구(웹페이지에서 F12 키를 누르면 된다.)를 사용해서 locator를 확인할 수 있다. (xpath를 사용해서 Copy XPath 버튼을 클릭하면 클립보드에 복사가 된다.)
<img src="https://images.velog.io/images/halim_limha/post/ef519186-6109-4e4e-aa17-9f10eadf299b/image.png" alt=""></p>
<p>확인해본 결과 locator가 <strong>//*[@id=&quot;count&quot;]/yt-formatted-string</strong>로 코드에 사용된 것과 같았다. <strong>그러면 locator는 문제가 없다는 것</strong>을 알 수 있다. </p>
<p><strong>힌트는 어떨 때는 잘 동작하다가 어떨 때는 또 에러 메세지를 출력한다는 것</strong>이다. 어떻게 보면 코드 문제라기 보다는 <strong>웹페이지의 소스가 유동적으로 변하다보니 생길 수 있는 문제</strong>라고 생각했다. 그러면 Locator의 내용이 유동적으로 바뀔 수도 있는 것이라고 예상할 수 있다. </p>
<p>그래서 직접 유튜브 웹페이지를 방문하여 Locator를 확인해보았다. 웹 페이지를 방문하자마자 <strong>F12 키를 입력하여 개발자 도구를 열고, Ctrl+F 키를 눌러 찾기를 한 다음에 XPath locator를 입력하였는데 XPath를 찾지 못하는 경우가 있었다.</strong> </p>
<p>그래서 개발자 도구를 이용해서 댓글수의 Locator를 다시 찾아보았다. 아래의 이미지 좌측 상단에 커서와 사각형 모양이 합쳐진 버튼을 사용하고 페이지에 커서를 옮겨 놓으면 소스에 관련된 Element가 어디있는지 찾아준다.
<img src="https://images.velog.io/images/halim_limha/post/6b3d1788-1a1f-4413-8f40-b6d5017bdd10/image.png" alt=""></p>
<p>찾아서 다시 Locator를 확인해보니 <strong>//*[@id=&quot;count&quot;]/yt-formatted-string</strong>로 똑같았다. 그래서 실수했나 하고 다른 유튜브 동영상 웹 페이지를 방문했는데, 여기서 문제 원인을 확실히 알게 되었다. <strong>결론은 유튜브 동영상 웹페이지를 처음 방문하면 댓글에 대한 Element가 다 같이 로드되지 않기 때문</strong>이었다. 이것을 유튜브 페이지를 방문해보면서 스크롤 다운을 하니 아주 잠깐 동안 아래와 같은 Wait Icon이 뜨는 것을 보고 그 다음에 댓글이 나오는 것을 확인하고 알게 되었다. 
<img src="https://images.velog.io/images/halim_limha/post/60d87839-98b7-49ac-9849-a5ea60436c74/image.png" alt=""></p>
<p>그래서 내가 예상한 대로 코드 자체의 문제라기 보다는 웹 페이지 소스가 유동적으로 변하다보니 생긴 문제였고, <strong>해결방안으로는 Selenium으로 스크롤 다운 동작을 한번 한 다음에 댓글수를 수집하는 코드를 실행하면 될 것</strong>이라고 해주었다. </p>
<p>친구가 내가 제시한 해결방안이 아니라 다른 방법(연관 동영상을 뜨지 않게 하는 방법, 연관 동영상이 뜨지 않으면 댓글수가 웹 페이지를 방문하자마자 나온다.)을 쓰기는 했지만 과제를 잘 처리할 수 있었고 문제의 원인을 확실하게 파악할 수 있었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Shell script 제어문 분석]]></title>
            <link>https://velog.io/@halim_limha/Shell-script-%EC%A0%9C%EC%96%B4%EB%AC%B8-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@halim_limha/Shell-script-%EC%A0%9C%EC%96%B4%EB%AC%B8-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Fri, 03 Dec 2021 07:14:00 GMT</pubDate>
            <description><![CDATA[<h2 id="작성계기">작성계기</h2>
<p>Shell Script를 공부하다가 반복문이나 조건문 같은 제어문의 사용이 다른 언어에 비해 복잡하다는 것을 느꼈다. 그래서 공부를 해도 조금씩 헷갈리기에 한번 정리하여 글로 남겨보려고 이번 포스팅을 작성하였다. </p>
<p>주로 다른 언어 특성과 비교하면서 Shell Script에서 유념해야 할 부분에 대해서 정리를 해보았다. </p>
<p>아래에 C/C++과 Bash Shell script 비교 예시 코드를 가져왔고 두 코드 모두 변수 x에 대해서 x가 1보다 작다면 &quot;ok&quot;를 출력하는 코드이다. </p>
<hr>
<pre><code class="language-cpp">// C/C++ 에서의 조건문
int x = 0;
if( x &lt; 1 )
    cout&lt;&lt;&quot;ok&quot;&lt;&lt;endl;</code></pre>
<hr>
<pre><code class="language-bash"># Bash Shell에서의 조건문
x=0
if [ $x -lt 1 ]
then
    echo &quot;ok&quot;
fi</code></pre>
<hr>
<h3 id="분석">분석</h3>
<p>위의 코드를 보고 눈여겨 보아야 하는 점들을 아래에 기록을 해놓았다. </p>
<h4 id="①-if-다음에-오는-괄호">① if 다음에 오는 괄호</h4>
<p>다른 언어에서와 달리 Shell Script에서는 <strong>if문의 첫번째 괄호에 반드시 띄어쓰기가 들어가야 한다.</strong> 띄어쓰기가 없으면 문법적으로 오류가 생기기 때문에 주의해야 한다. &quot;if&quot;라는 제어명령을 쓴 후에 띄어쓰기를 한번 하고 시작괄호를 넣고 그 다음 한번 더 띄어쓰기를 해주어야 한다. </p>
<pre><code class="language-bash">if [ $x -lt 1 ] # 정상
if[ $x -lt 1 ] # 에러 
if [$x -lt 1 ] # 에러</code></pre>
<h4 id="②-부등호-표시-대신--lt">② 부등호 표시 대신 -lt</h4>
<p>영어에 익숙하신 분들은 직감적으로 <strong>&quot;lt&quot;</strong>가 <strong>&quot;less than&quot;</strong>을 의미한다는 것을 눈치챘을 것이다. 그래서 사람에 따라서 부등호 표시보다 이렇게 영어 소문자로 비교를 하는 것이 더 편할 수도 있다. 그리고 &quot;작다&quot;는 표현이 &quot;-lt&quot;이니까 &quot;크다&quot;는 표현은 &quot;-gt&quot; 라는 것을 유추해낼 수 있다. 아래는 모든 비교표현에 대해 정리한 표이다. </p>
<table>
<thead>
<tr>
<th align="center">비교 표현</th>
<th align="center">비교 표현 기호</th>
<th align="center">비교 표현</th>
</tr>
</thead>
<tbody><tr>
<td align="center">x가 y보다 작다</td>
<td align="center">x &lt; y</td>
<td align="center">x -lt y</td>
</tr>
<tr>
<td align="center">x가 y보다 크다</td>
<td align="center">x &gt; y</td>
<td align="center">x -gt y</td>
</tr>
<tr>
<td align="center">x가 y보다 작거나 같다</td>
<td align="center">x &lt;= y</td>
<td align="center">x -le y</td>
</tr>
<tr>
<td align="center">x가 y보다 크거나 같다</td>
<td align="center">x &gt;= y</td>
<td align="center">x -ge y</td>
</tr>
<tr>
<td align="center">x가 y와 같다</td>
<td align="center">x == y</td>
<td align="center">x -eq y</td>
</tr>
<tr>
<td align="center">x가 y와 같지 않다</td>
<td align="center">x != y</td>
<td align="center">x -ne y</td>
</tr>
</tbody></table>
<p>개인적으로 정수를 비교할 때는 영어로된 약자를 쓰기보다는 부등호를 사용하는 것이 편하다고 느낀다. 그러나 파일을 테스트할 때는 영어로된 약자를 사용하는 것이 직관적이다. 예를 들면 아래와 같다. </p>
<pre><code class="language-bash">if [ -f /root/dir/file1 ] # /root/dir에 file1이라는 파일이 존재하는지 아닌지 확인
if [ -d /root/dir/dir1 ] # /root/dir에 존재하는 dir1이 디렉터리인지 아닌지 확인
# 그 외..
</code></pre>
<h4 id="③-영어-약자-대신-부등호를-쓰고-싶다면">③ 영어 약자 대신 부등호를 쓰고 싶다면?</h4>
<h5 id="첫-번째-방법">첫 번째 방법</h5>
<p>다행히도 제어문에서 비교를 할 때 무조건 영어로된 비교 표현을 써야 하는 것은 아니다. Bash의 2점대 버전 이후로 더블 브라켓( &quot;[[ ]]&quot; )을 사용하면 C/C++에서 제어문을 사용할 때처럼 비교 표현 기호를 사용할 수 있다.</p>
<h5 id="두-번째-방법">두 번째 방법</h5>
<p>추가로 &quot;let&quot;이라는 쉘 빌트인 명령을 사용하는 방법도 있다. let 명령어는 산술 표현식을 평가하는 명령이며 소괄호 2개( &quot;(( ))&quot; )를 활용해서 사용할 수 있다. 아래는 man page에서 let 명령어에 대한 설명을 가져온 부분이다. </p>
<blockquote>
<p>let 명령어 man page 
let arg [arg ...] 
Each  arg  is  an arithmetic expression to be evaluated (see ARITHMETIC EVALUATION above). If the last arg evaluates to 0, let returns 1; 0 is returned otherwise.</p>
</blockquote>
<p>그래서 아래의 조건문은 모두 같은 의미를 가진다. </p>
<pre><code class="language-bash">if [ $x -lt 1 ] 
if [[ $x &lt; 1 ]] # Bash version 2.x 이후부터 지원
if (( $x &lt; 1 )) # let 명령어 활용</code></pre>
<h4 id="④-표현식이-반환하는-값">④ 표현식이 반환하는 값</h4>
<p>C/C++에서는 아래의 표현식이 비교 결과에 따라서 Bool값의 true(1) 혹은 false(0)을 반환한다. </p>
<pre><code class="language-cpp">if( x &lt; 1 )</code></pre>
<p>그러나 <strong>Shell Script에서는 반대이다. Shell Script는 정수형 값을 이용하는데 0이면 true이고 그 이외의 숫자들은 false로 인식</strong>한다. 그리고 <strong>exit 값을 이용</strong>한다. </p>
<p>exit 값은 exit라는 명령어와 함께 사용된 아규먼트 값을 말한다. 누군가 작성해놓은 Shell Script를 보면 예외 처리를 하면서 exit 1, exit 3 이런식으로 사용된 것을 볼 수 있을텐데 여기서 1과 3이 exit 값이다. (예외 처리에서는 exit 명령어의 아규먼트로 0이 아닌 숫자를 이용한다. 그러니까 이 값을 통해 실행이 잘 되었는지 아닌지 확인할 수 있는 것이다.)</p>
<blockquote>
<p><strong>참고!</strong> exit 값은 Shell의 예약 변수로도 저장이 되어 있다. ?라는 변수에 exit 값이 저장되어 있으니 &quot;echo $?&quot;라고 명령어를 입력해 엔터를 누르면 확인할 수 있다. 이전 명령어가 정상적으로 실행되었다면 0을 출력하고 그렇지 못하다면 그 이외의 숫자를 출력할 것이다.</p>
</blockquote>
<p>지금까지 예시로 들었던 if문에서도 exit 값을 반환하는 부분이 있다. &quot;[ ]&quot;와 &quot;[[ ]]&quot;는 <strong>&quot;test&quot;</strong>라는 쉘 빌트인 명령을 내부적으로 사용하여 exit 값을 반환하는 것이고 &quot;(( ))&quot;는 위에서 설명했듯이 <strong>&quot;let&quot;</strong>이라는 쉘 빌트인 명령을 사용한 것이다.  </p>
<p>exit 값을 이용한다는 사실을 기억해야 하는 이유를 코드로 설명을 해보겠다. 이번에는 if문이 아니라 while문을 사용했는데 조건식을 사용하는 방법은 똑같다. </p>
<pre><code class="language-bash">x=1
while [ $x ]
do
    echo $?
       sleep 5
done</code></pre>
<p>x 변수에 0이 아닌 1을 대입했기 때문에 while문이 동작하지 않을 것이라고 생각할 수도 있지만 while문은 계속 반복하여 실행이 될 것이고 지속해서 0이 출력될 것이다. </p>
<p>그 이유는 일단 기본적으로 <strong>Shell Script에서 변수는 정수형이 아니라 문자열로 인식되어 저장</strong>된다. 그러니까 x에는 &quot;1&quot;이라는 문자열이 들어가 있는 상태인 것이다. 그리고 while문 옆에서 test라는 쉘 빌트인 명령을 사용하는데 아규먼트는 변수 x, 즉 문자열 &quot;1&quot;인 것이다. 그런데 <strong>test 명령어는 단순한 문자열 아규먼트에 대해서는 exit 값으로 0을 반환</strong>한다. 그러니까 while문이 반복해서 실행되는 것이다. </p>
<p>마찬가지로 아래의 코드도 반복문이 계속해서 실행된다. </p>
<pre><code class="language-bash">while [ 1 ]
do
    echo $?
       sleep 5
done</code></pre>
<p>다만 이는 대괄호 안의 값이 true로 인식되어서가 아니라 test 명령에 의한 exit값이 계속해서 0이기 때문이다. 그래서 대괄호 안의 값이 0이어도 계속 실행되기 때문에 Shell Script를 작성할 때 주의해야 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Shell Script 분석 연습 - Hadoop start-all.sh]]></title>
            <link>https://velog.io/@halim_limha/Shell-Script-%EB%B6%84%EC%84%9D-%EC%97%B0%EC%8A%B5-Hadoop-start-all.sh</link>
            <guid>https://velog.io/@halim_limha/Shell-Script-%EB%B6%84%EC%84%9D-%EC%97%B0%EC%8A%B5-Hadoop-start-all.sh</guid>
            <pubDate>Sat, 13 Nov 2021 16:12:40 GMT</pubDate>
            <description><![CDATA[<h2 id="작성계기">작성계기</h2>
<p>요즘 Shell Script에 대해서 공부하고 있다. 연습이 필요해 Hadoop의 start-all.sh라는 스크립트를 분석해보면서 공부했던 내용도 리마인드하고 몰랐던 내용도 새로 알아보기 위해 시도해보았다. </p>
<p>start-all.sh라는 스크립트는 Hadoop Binary 파일 내에 포함되어 있으며, 클러스터 내에 존재하는 HDFS(Hadoop File System)의 Namenode와 Datanode는 물론 클러스터 매니저인 Yarn의 Resource Manager와 Node Manager를 같이 실행시켜 Hadoop 클러스터 운영을 시작하는 스크립트이다. </p>
<p>start-all.sh 파일은 $HADOOP_HOME_DIR/sbin에서 찾을 수 있다. </p>
<hr>
<ul>
<li>start-all.sh<pre><code class="language-bash">#!/usr/bin/env bash
</code></pre>
</li>
</ul>
<h1 id="licensed-to-the-apache-software-foundation-asf-under-one-or-more">Licensed to the Apache Software Foundation (ASF) under one or more</h1>
<h1 id="contributor-license-agreements--see-the-notice-file-distributed-with">contributor license agreements.  See the NOTICE file distributed with</h1>
<h1 id="this-work-for-additional-information-regarding-copyright-ownership">this work for additional information regarding copyright ownership.</h1>
<h1 id="the-asf-licenses-this-file-to-you-under-the-apache-license-version-20">The ASF licenses this file to You under the Apache License, Version 2.0</h1>
<h1 id="the-license-you-may-not-use-this-file-except-in-compliance-with">(the &quot;License&quot;); you may not use this file except in compliance with</h1>
<h1 id="the-license--you-may-obtain-a-copy-of-the-license-at">the License.  You may obtain a copy of the License at</h1>
<p>#</p>
<h1 id="httpwwwapacheorglicenseslicense-20"><a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a></h1>
<p>#</p>
<h1 id="unless-required-by-applicable-law-or-agreed-to-in-writing-software">Unless required by applicable law or agreed to in writing, software</h1>
<h1 id="distributed-under-the-license-is-distributed-on-an-as-is-basis">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</h1>
<h1 id="without-warranties-or-conditions-of-any-kind-either-express-or-implied">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</h1>
<h1 id="see-the-license-for-the-specific-language-governing-permissions-and">See the License for the specific language governing permissions and</h1>
<h1 id="limitations-under-the-license">limitations under the License.</h1>
<h2 id="description--catch-the-ctrl-c">@description  catch the ctrl-c</h2>
<h2 id="audience-----private">@audience     private</h2>
<h2 id="stability----evolving">@stability    evolving</h2>
<h2 id="replaceable--no">@replaceable  no</h2>
<p>function hadoop_abort_startall()
{
  exit 1
}</p>
<h1 id="lets-locate-libexec">let&#39;s locate libexec...</h1>
<p>if [[ -n &quot;${HADOOP_HOME}&quot; ]]; then
  HADOOP_DEFAULT_LIBEXEC_DIR=&quot;${HADOOP_HOME}/libexec&quot;
else
  this=&quot;${BASH_SOURCE-$0}&quot;
  bin=$(cd -P -- &quot;$(dirname -- &quot;${this}&quot;)&quot; &gt;/dev/null &amp;&amp; pwd -P)
  HADOOP_DEFAULT_LIBEXEC_DIR=&quot;${bin}/../libexec&quot;
fi</p>
<p>HADOOP_LIBEXEC_DIR=&quot;${HADOOP_LIBEXEC_DIR:-$HADOOP_DEFAULT_LIBEXEC_DIR}&quot;</p>
<h1 id="shellcheck-disablesc2034">shellcheck disable=SC2034</h1>
<p>HADOOP_NEW_CONFIG=true
if [[ -f &quot;${HADOOP_LIBEXEC_DIR}/hadoop-config.sh&quot; ]]; then
  . &quot;${HADOOP_LIBEXEC_DIR}/hadoop-config.sh&quot;
else
  echo &quot;ERROR: Cannot execute ${HADOOP_LIBEXEC_DIR}/hadoop-config.sh.&quot; 2&gt;&amp;1
  exit 1
fi</p>
<p>if ! hadoop_privilege_check; then
  trap hadoop_abort_startall INT
  hadoop_error &quot;WARNING: Attempting to start all Apache Hadoop daemons as ${USER} in 10 seconds.&quot;
  hadoop_error &quot;WARNING: This is not a recommended production deployment configuration.&quot;
  hadoop_error &quot;WARNING: Use CTRL-C to abort.&quot;
  sleep 10
  trap - INT
fi</p>
<h1 id="start-hdfs-daemons-if-hdfs-is-present">start hdfs daemons if hdfs is present</h1>
<p>if [[ -f &quot;${HADOOP_HDFS_HOME}/sbin/start-dfs.sh&quot; ]]; then
  &quot;${HADOOP_HDFS_HOME}/sbin/start-dfs.sh&quot; --config &quot;${HADOOP_CONF_DIR}&quot;
fi</p>
<h1 id="start-yarn-daemons-if-yarn-is-present">start yarn daemons if yarn is present</h1>
<p>if [[ -f &quot;${HADOOP_YARN_HOME}/sbin/start-yarn.sh&quot; ]]; then
  &quot;${HADOOP_YARN_HOME}/sbin/start-yarn.sh&quot; --config &quot;${HADOOP_CONF_DIR}&quot;
fi</p>
<h2 id="">```</h2>
<h3 id="1-shebang">1. Shebang</h3>
<p>먼저 <strong>Shebang</strong>이란 스크립트 파일이 어떤 스크립트인지 알려주는 역할을 한다. (예를 들면 Python인지, Bash인지..) </p>
<blockquote>
<p><strong>추가!</strong> 정확히 말하면 스크립트를 실행할 인터프리터의 절대경로를 지정하는 것이다. </p>
</blockquote>
<blockquote>
<p><strong>참고!</strong> &#39;#!&#39;인지 &#39;!#&#39;인지 헷갈려하는 사람이 있을 수도 있는데, 이름이 Shebang인 이유 자체가 &#39;<strong>#</strong>(<strong>Hash</strong> 혹은 Number sign)&#39;과 &#39;<strong>!</strong>(<strong>Bang</strong> 혹은 exclamation mark)&#39;인 것을 기억하면 된다. </p>
</blockquote>
<p>스크립트 첫 라인부터 보이는 &quot;#!/usr/bin/env bash&quot;가 shebang이다. 그런데 책에서는 bash 스크립트를 작성할 때, <strong>&quot;#!/bin/bash&quot;</strong>로 shebang을 작성했는데, 여기서는 <strong>&quot;#!/usr/bin/env bash&quot;</strong>로 달랐다. 분명 bash 스크립트인 것은 같은 것이지만 무언가 차이가 있을 것이라고 생각하고 조사를 했더니 보통은 start-all.sh 파일에 붙은 shebang처럼 <strong>&quot;#!/usr/bin/env bash&quot;</strong> 형태를 많이 사용한다고 한다. </p>
<p>그 이유는 <strong>시스템마다 환경 구성이 다르기 때문이다.</strong> 나는 지금 CentOS 7 버전을 사용하고 있는데, Linux 배포판마다 인터프리터의 위치가 다를 수도 있을 것이다. 그러면 bash 인터프리터가 /bin 디렉토리에 존재하지 않을 수도 있고, 그렇게 되면 Shebang을 &quot;#!/bin/bash&quot;로 입력했을 때 인터프리터를 제대로 찾지 못할 수도 있는 것이다. 그래서 보통 &quot;#!/usr/bin/env bash&quot; 형태로 shebang을 입력하여 다양한 환경에서 스크립트가 실행될 수 있도록 한다.</p>
<blockquote>
<p><strong>참고!</strong> /usr/bin/env는 환경변수를 출력해주는 실행 가능한 프로그램이다. (env 명령어를 입력하면 /usr/bin/env가 실행되는 것이다.)</p>
</blockquote>
<p><img src="https://images.velog.io/images/halim_limha/post/0072b8d1-ecac-4cb9-82b1-347beb577941/image.png" alt=""></p>
<h3 id="2-함수">2. 함수</h3>
<p>start-all.sh 파일 내에 함수는 하나 정의가 되어 있다. hadoop_abort_startall()이 그것인데, 함수 내용은 exit 1으로 내용은 딱히 없다. <strong>다만 exit 0이 아니라 1이기 때문에 무언가 문제가 생겼을 때 사용되는 함수라는 것을 예상</strong>할 수 있다. </p>
<h3 id="3-locate-libexec">3. Locate libexec</h3>
<p>첫 번째 if문을 보면 표현식 내에 <strong>-n 옵션</strong>과 <strong>$HADOOP_HOME이라는 변수</strong>가 있다. -n 옵션은 <strong>변수의 길이가 0이 아닐 때 True를 반환</strong>한다.(즉 $HADOOP_HOME 변수가 설정이 되어 있어야 조건문을 실행한다는 말이다.)</p>
<p>만약 $HADOOP_HOME 변수가 설정이 되어 있으면 HADOOP_DEFAULT_LIBEXEC_DIR라는 변수를 &quot;$HADOOP_HOME/libexec&quot;라고 설정한다. </p>
<p>그렇지 않으면 this라는 변수를 &quot;${BASH_SOURCE-$0}&quot;로 설정한다. 이 때 ${BASH_SOURCE-$0}은 변수 확장 변경자를 이용한 것으로 변수를 치환할 때, BASH_SOURCE가 설정되어 있지 않으면 $0로 치환되고 그렇지 않으면 BASH_SOURCE가 된다는 것이다. </p>
<p>그리고 bin 변수에는 $(cd -P -- &quot;$(dirname -- &quot;${this}&quot;)&quot; &gt;/dev/null &amp;&amp; pwd -P)를 할당한다. cd와 pwd에 붙은 -P 옵션은 심볼릭 링크가 아닌 피지컬 링크를 사용한다는 의미이고, 리다이렉션을 통해 cd 명령의 출력값을 /dev/null로 보냈으니 출력하지 않겠다는 의미이다. 그리고 &#39;&amp;&amp;&#39;은 앞의 cd 명령어가 에러 없이 실행되었을 경우 뒤의 pwd 명령을 실행한다는 것이다. </p>
<p>마지막으로 HADOOP_DEFAULT_LIBEXEC_DIR 변수를 방금 전 설정한 bin 변수를 이용하여 할당하고 if문이 끝난다. </p>
<p>HADOOP_LIBEXEC_DIR 변수값은 <strong>변수 확장 변경자</strong>를 사용하여 HADOOP_LIBEXEC_DIR가 NULL이 아니라면 그대로 HADOOP_LIBEXEC_DIR 값을 가지고 그렇지 않으면 위의 if 조건문에서 설정된 HADOOP_DEFAULT_LIBEXEC_DIR값으로 치환된다. </p>
<h3 id="4-start-hadoop--yarn-daemons">4. start Hadoop &amp; Yarn daemons</h3>
<p>Hadoop과 Yarn Daemon들을 실행시키는 부분은 2개의 if문으로 구성되어 있다. 먼저 Hadoop Daemon을 실행시키는 if문을 보면 표현식 내에 <strong>-f 옵션</strong>이 들어가 있는데, 이는 <strong>file이 존재하고 보통의 파일이면 True를 반환</strong>한다. </p>
<p>if문 내에 기재된 파일은 ${HADOOP_HDFS_HOME}/sbin/start-dfs.sh이며 start-all.sh 파일과 같은 디렉토리에 존재하는 start-dfs.sh 파일이다. 그리고 함수의 내용은 그 파일을 실행하는 것이다.(--config 옵션으로 설정파일의 디렉토리도 설정된다.)</p>
<p>즉 start-all.sh는 그 자체로 Hadoop Daemon을 실행하도록 되어 있는것이 아니라 스크립트 파일이 또 다른 스크립트 파일(start-dfs.sh)을 실행시키는 것이다. </p>
<p>Yarn도 마찬가지이다. start-yarn.sh 파일이 있으면 그것을 실행한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LeetCode] Best Time to Buy and Sell Stock II]]></title>
            <link>https://velog.io/@halim_limha/LeetCode-Best-Time-to-Buy-and-Sell-Stock-II</link>
            <guid>https://velog.io/@halim_limha/LeetCode-Best-Time-to-Buy-and-Sell-Stock-II</guid>
            <pubDate>Wed, 10 Nov 2021 07:07:19 GMT</pubDate>
            <description><![CDATA[<h2 id="작성계기">작성계기</h2>
<p>11월 10일, LeetCode 오늘의 문제를 풀다가 느낀점을 기록하기 위해서 포스팅을 작성하였다. 결론을 먼저 말하면 <strong>한가지 접근 방법을 고집하지 말자는 것</strong>을 느꼈다. 코딩을 하다보면 몇번씩 이런 생각이 드는데 오늘은 기록을 하는 것이 좋겠다고 생각했다. </p>
<p>오늘의 문제는 &quot;<strong>Best Time to Buy and Sell Stock II</strong>&quot;이라는 문제인데, <strong>일자별 주식 가격이 1차원 배열</strong>로 주어지고, 그것을 바탕으로 <strong>매매를 했을 때 얻을 수 있는 최대 수익</strong>을 구하는 문제였다. 추가적으로 <strong>주식은 한번에 한 주만 가지고 있을 수 있다는 조건</strong>이 있다. 무슨 얘기냐하면 어느날 주식을 하나 사서 갖고 있다면 이 주식을 팔지 않으면 주식을 살 수 없다는 말이다. </p>
<ul>
<li>정확한 문제 설명은 LeetCode 페이지를 참고
<a href="https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/">https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/</a></li>
</ul>
<hr>
<h2 id="접근방법-①">접근방법 ①</h2>
<blockquote>
<p><strong>참고!</strong> 접근방법 ①은 Time Limit Exceeded가 떠서 통과가 안된다. </p>
</blockquote>
<p>일단 코딩 문제를 풀 때, 나는 문제 설명을 읽고 그 다음 예시로 주어진 Case를 확인한 후에, 제한 사항을 확인한다. </p>
<p>문제를 보자마자, 가장 단순하게 모든 경우를 다 따져보는 것을 생각해보았다. 그러니까 첫날부터 주식을 사는 경우와 사지 않는 경우로 시작하는 것이다. 말로 설명하는 것보다 일종의 순서도로 나타내는 것이 편할 것 같아 그려보았다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/d6365844-65f8-4570-bb6a-4d2a5f1ca6b0/image.png" alt=""></p>
<p>그림은 3일까지밖에 그리지 않았지만 일수가 늘어날 수록 경우의 수가 많아지는 것을 알 수 있을 것이다. 그래서 이 접근 방법을 생각한 후에 제한 사항을 보았다. 배열의 크기 제한이 30,000이기 때문에 <strong>모든 경우의 수는 2^30000-1가지인 것을 예상</strong>할 수 있고, 이 경우의 수를 모두 따져보는 것은 <strong>시간이 매우 오래 걸릴 것이고 당연히 정답이 아닐 것</strong>이다. </p>
<p>그래서 <strong>모든 경우를 따져보기보다는 따지지 않아도 되는 경우는 아예 브랜치를 잘라버렸다.</strong>(순서도가 Tree 모양을 띄고 있어서 이렇게 표현해보았다. 쉽게 말하면 더 따져볼 필요가 없는 경우는 후에 계산을 생략했다는 말이다.)</p>
<p>내가 생각한 더 이상 계산을 할 필요가 없어지는 때를 정리해보면 아래와 같았다. </p>
<ul>
<li><ol>
<li>주식을 구매한 날의 가격보다 주식의 가격이 낮을 때는 판매를 할 이유가 없다. (daytobuy라는 변수를 활용했다.) </li>
</ol>
</li>
<li><ol start="2">
<li>N일째까지의 최대 이익을 연산을 하면서 기록해놓는다. 그리고 N일째 이익을 계산할 때, 미리 기록해놓은 최대 이익보다 작으면 연산을 멈춘다.(이 때 주식을 갖고 있는지 아닌지 구분이 필요하여 함수의 파라미터로 check라는 Bool값을 사용하였다.) </li>
</ol>
</li>
</ul>
<pre><code class="language-cpp">class Solution {
private:
    vector&lt;int&gt; maximum; 
    int daytobuy;
public:
    void maxProfit(vector&lt;int&gt;&amp; prices, int day, int profit, bool check /*hold stock(true) or not(false)*/ )
    {
        // 마지막 날까지 계산했을 때
        if(day == prices.size())
        {
            if(profit &gt; maximum[day-1])
                maximum[day-1] = profit;
            return;
        }
        // 더 이상 연산이 필요 없을 때    
        if(profit &lt; 0 &amp;&amp; !check)
            return;
        if(profit &lt; maximum[day] &amp;&amp; !check)
            return;

        if(profit &gt; maximum[day])
        {
            maximum[day] = profit;
        }

        if(check) // hold stock
        {
            maxProfit(prices, day+1, profit, check); // do nothing
            if(prices[daytobuy] &lt; prices[day]){
                maxProfit(prices, day+1, profit+prices[day], !check); // sell
            }
        }
        else // not hold stock
        {
            maxProfit(prices, day+1, profit, check); // do nothing
            int prev = daytobuy;
            daytobuy=day;
            maxProfit(prices, day+1, profit-prices[day], !check); // buy
            daytobuy= prev;
        }
    }

    int maxProfit(vector&lt;int&gt;&amp; prices) {
        int answer = 0;
        maximum = vector&lt;int&gt;(prices.size(), -10001);

        maxProfit(prices, 0, 0, 0);

        answer = maximum[prices.size() - 1];

        return answer;

    }
};</code></pre>
<p>그래서 작성한 코드는 위와 같다. 주어진 테스트 케이스는 모두 통과를 하였고, 자체적으로 만든 테스트 케이스도 통과하여 제출을 했는데, 배열의 길이가 1,000개 정도가 넘어가자 &#39;Time Limit Exceeded&quot;가 떴다. </p>
<p>그래서 코드를 짠 후에도 1시간 정도, 브랜치를 더 자를 수 있는 아이디어가 있을까 고민해보았다. </p>
<hr>
<h2 id="접근방법-②">접근방법 ②</h2>
<p>결국 아이디어가 잘 생각이 나질 않았고, 떠올라도 연산 시간에 그렇게 크게 영향을 미칠 것 같지 않았다. 그래서 다른 사람들의 해결방법을 보기 위해서 Discuss 탭에 들어가서 다른 사람들의 풀이를 보았다. </p>
<p>풀이를 보자마자 <strong>&#39;아 접근방법 ①로 풀면 안되는구나&#39;</strong>라는 생각이 들었다. 왜냐하면 그래프를 보자마자 새로운 아이디어가 떠올랐기 때문이다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/930ac159-1755-4e67-a1c3-66c432187828/image.png" alt=""></p>
<h2 id="위는-문제-풀이에-포함된-그래프이다">위는 문제 풀이에 포함된 그래프이다. </h2>
<h3 id="그냥-나의-느낀점">그냥 나의 느낀점..</h3>
<p>안타까운 것은 내가 문제를 보자마자 주어진 Input 값을 단순히 [1,7,2,3,6,7,6,7]과 같이 수열로만 볼 것이 아니라 머리속으로 그래프를 그려보았다면 한가지 방법을 고집하지 않았을 것이란 것이다. </p>
<p>코딩을 해보면서 많이 느끼지만 <strong>데이터를 다각적으로 볼 필요가 있고 그럴 수 있는 능력은 참 중요한 것 같다.</strong></p>
<p>예전 같았으면 이런 상황에서 단순한 생각도 하지 못한 것에 대해 자괴감을 느꼈을 것이다. 하지만 코딩을 해보다 보니 생각의 전환을 하는 것은 생각보다 쉽지 않고 그런 능력은 하루 아침에 얻어지지 않는다. 다만 분명한 것은 아직 초보이긴하지만 컴퓨터를 처음 공부했을 때보다는 지금 코딩을 더 잘한다. 그러니까 지금처럼 조금씩이라도 코딩을 해보려고 지속적으로 노력하고 훈련한다면 분명 나아질 것이라고 믿는다. </p>
<hr>
<p>그래프를 다시 보면 뭔가 느껴지는 것이 있을 것이다. 설명을 보지 않아도 <strong>A+B+C의 값이 D의 값과 같다</strong>는 것을 눈치챘다. </p>
<p>어렵게 바꿔 말해보면 <strong>주식 가격 배열(수열)의 어떤 구간 내의 수열이 단조증가하면 그 구간에서의 최대 수익은 수열의 모든 원소에 대해서 원소(주식 가격)와 그 다음 원소의 차이값의 합과 같다.</strong> </p>
<p>이를 코드로 옮기면 아래와 같다. Leetcode의 &#39;archit91&#39;란 사람의 코드를 인용했다. </p>
<pre><code class="language-cpp">class Solution {
public:
    int maxProfit(vector&lt;int&gt;&amp; P) {
        int profit = 0;
        for(int i = 1; i &lt; size(P); i++)
            if(P[i] &gt; P[i-1])              // yesterday was valley, today is peak
                profit += P[i] - P[i-1];   // buy yesterday, sell today
        return profit;
    }
};</code></pre>
<p>훨씬 짧은 코드로 Time Limit Exceeded 없이 모든 테스트 케이스가 통과되는 것을 확인할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS VPC & EC2 튜토리얼]]></title>
            <link>https://velog.io/@halim_limha/AWS-VPC-EC2-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC</link>
            <guid>https://velog.io/@halim_limha/AWS-VPC-EC2-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC</guid>
            <pubDate>Wed, 03 Nov 2021 07:04:05 GMT</pubDate>
            <description><![CDATA[<h2 id="작성계기">작성계기</h2>
<p>리눅스 스터디를 진행하면서 실습용으로 리눅스 환경을 구성할 필요가 생겼다. 사실 나는 Windows를 이용해서 Windows의 <strong>WSL2</strong>를 사용해도 충분히 실습하는데는 문제가 없지만 이번 기회에 퍼블릭 클라우드 사용 연습도 해볼 겸 <strong>AWS</strong>를 이용하여 <strong>VPC</strong>를 생성하고 <strong>EC2</strong>로 CentOS 7 가상머신을 생성해보았다. </p>
<p>이전에 퍼블릭 클라우드를 아예 안다뤄본 것은 아니지만, 그 때는 VPC가 정확히 무엇인지도 모르고 그냥 튜토리얼만 따라했던 것 같다. 그래서 이번 포스팅에서는 단순히 튜토리얼만 제공하기보다는 VPC의 개념을 파악할 수 있도록 작성해볼 생각이다.</p>
<h2 id="준비사항">준비사항</h2>
<p>*<em>1. AWS 계정
2. VPC 학습 *</em></p>
<p>이 중에서 <strong>두 번째</strong>의 경우 유튜브에 올라온 강의(<a href="https://www.youtube.com/watch?v=R1UWYQYTPKo">가상 데이터 센터 만들기 - VPC 기본 및 연결 옵션</a>)를 추천한다. </p>
<p>위에 링크를 올린 강의가 친절하고 쉽게 설명되어 있기는 하지만 그래도 스스로 개념도 정리해보는 차원에서 전체구조를 한번 그려보면 아래와 같다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/12caa558-1b8e-4b53-a096-5a29c573da19/image.png" alt=""></p>
<p>먼저 용어 정리를 한번 해보면,</p>
<ul>
<li><p><strong>1. VPC</strong> : VPC는 <strong>Virtual Private Cloud</strong>를 의미한다. 즉 <strong>가상의 Private Cloud</strong>인데, 이렇게 적어놓으면 이해하기 힘드니까 의미를 설명해보겠다. 예전의 AWS의 인스턴스는 VPC 없이 하나의 네트워크 내부에서 제공이 되었다. 즉 이 말은 <strong>①다른 고객들의 인스턴스들과 내가 사용하는 인스턴스가 분리가 되지 않았다</strong>는 뜻이다. 이 문제를 해결하기 위해 <strong>②논리적으로 네트워크를 분리해서 내가 사용하는 인스턴스가 다른 고객들이 사용하는 인스턴스와 분리를 한 것이 VPC</strong>인 것이다. </p>
</li>
<li><p><strong>2. Public &amp; Private Subnet</strong> : 위의 그림에서 VPC 안에 Public Subnet이 있고, Private Subnet이 있는 것을 확인할 수 있는데, VPC 내부에서도 Subnetting을 통해 네트워크를 나눌 수 있다. 이 때 <strong>Public Subnet은 외부 인터넷과 연결이 가능한 Subnet이고 Private Subnet은 불가능</strong>하다. Private Subnet이 아예 외부 인터넷과 단절된 것은 아니다. <strong>NAT(Network Address Translation)</strong> 기술과 같은 VPC 내에 존재하는 <strong>Public Subnet</strong>을 이용해서 Private Subnet의 데이터를 외부 인터넷으로 전송할 수 있다. </p>
</li>
<li><p><strong>3. Internet Gateway</strong> : Public Subnet은 외부 인터넷과 통신이 가능하다고 했는데, 이 때 Internet Gateway가 Public Subnet에서부터 생성된 데이터를 외부 인터넷으로 보내주고, 또 외부 인터넷에서 송신된 데이터를 Public Subnet으로 보내주는 역할을 한다. </p>
</li>
<li><p><strong>4. EC2 Instance</strong> : EC2는 Elastic Compute Cloud를 의미한다. 클라우드라는 것을 처음 접해본 사람들은 Instance라는 용어가 헷갈릴 수 있는데, 객체지향프로그래밍의 객체라는 의미로 사용될 수도 있으나 <strong>클라우드에서는 CPU, Disk, Memory 등의 HW를 갖춘 하나의 서버</strong>라고 보는 것이 편할 것이다. 그러니까 인스턴스를 5개를 쓴다면 5개의 서버를 사용한다는 것이다. </p>
</li>
</ul>
<h2 id="aws로-vpc--ec2-생성해보기">AWS로 VPC &amp; EC2 생성해보기</h2>
<h3 id="aws-console-접속">AWS Console 접속</h3>
<p><img src="https://images.velog.io/images/halim_limha/post/623266bc-1e1f-42fc-898d-66869a41724d/image.png" alt="">
먼저 AWS로 들어가서 로그인을 해서 AWS 관리 콘솔로 들어가면 위와 같은 화면이 보일 것이다. 나는 이 포스팅을 작성하기 전에 EC2와 VPC를 먼저 작성해보았기 때문에 최근 방문한 서비스로 EC2와 VPC가 뜨지만 처음 AWS를 사용하는 사람은 아닐 것이다. 
<img src="https://images.velog.io/images/halim_limha/post/b8814c01-2342-4b7b-a5d1-cd9efbd74980/image.png" alt="">
그럴 때는 우측상단에 있는 서비스 버튼을 클릭해서 EC2와 VPC 서비스를 찾을 수 있고 또한 검색을 통해서도 찾을 수 있다.</p>
<h3 id="vpc-생성">VPC 생성</h3>
<p>EC2를 생성하기 전에 먼저 네트워크를 구성하려고 하니 AWS 관리 콘솔에서 VPC 관리 콘솔을 찾아간다. 
<img src="https://images.velog.io/images/halim_limha/post/2dd1ea94-4440-4d41-b138-7327264c92b0/image.png" alt="">
VPC 관리 콘솔로 들어가면 위와 같은 화면을 볼 수 있다. 이 때 VPC 마법사 시작으로 자동으로 VPC를 구성할 수도 있지만 직접 VPC를 구성해보기 위해서 <strong>좌측에 보이는 가상 프라이빗 클라우드에서 VPC 버튼을 클릭</strong>한다.</p>
<p><img src="https://images.velog.io/images/halim_limha/post/d640a82a-f15a-4d01-8087-3f7469d95a3e/image.png" alt="">
VPC 버튼을 클릭하고 나면 페이지에 VPC 목록이 보일 것이다. 새로운 VPC를 구성할 것이기 때문에 <strong>우측 상단의 VPC 생성 버튼을 클릭</strong>한다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/a8594074-1f01-4e65-8bbd-0eb7d7ac8e03/image.png" alt=""></p>
<p>VPC 생성 버튼을 누르고 나면, 새로운 VPC의 이름과 CIDR를 지정해줄 수 있다. 이름은 본인 선택에 따라서 정하면 되는데 나는 회색 글자로 표시된 예시 그대로 &#39;my-vpc-01&#39;을 그대로 사용했다. </p>
<p>여기서 헷갈리는 것은 CIDR일텐데 일단 CIDR의 정의부터 알고 시작을 하는 것이 좋을 것이다. </p>
<blockquote>
<p><strong>CIDR</strong> : <strong>Classless</strong> Inter-domain Routing의 약자로 말 그대로 Class없이 사용하는 네트워크 주소라고 할 수 있다. 예전에는 IP 주소에서 <strong>Class</strong>라는 개념을 사용했지만 IP 주소를 더 원활하게 할당하기 위해서 CIDR을 사용하게 되었다. CIDR을 사용하면 기존의 Class처럼 네트워크 주소와 호스트 주소의 길이를 Class에 따라 고정하지 않아도 되기 때문에 유연하게 IP 주소를 활용할 수 있다. 예를 들어 Class 방식에서는 네트워크 주소를 Class에 따라 8 bit, 16 bit, 24 bit로 사용했다면, CIDR에서는 172.16.0.0/12처럼 자유롭게 Subnet Mask의 길이(&#39;/&#39; 문자 옆의 숫자, 12)를 지정해주어 네트워크 주소와 호스트 주소를 구분한다. </p>
</blockquote>
<p>이전에 <a href="https://velog.io/@halim_limha/IP-%EC%A3%BC%EC%86%8C%EC%B2%B4%EA%B3%84-%EC%A0%95%EB%A6%ACfeat.-%EC%A7%80%ED%95%98%EC%B2%A0-Wifi">IP의 Class에 대해서 포스팅</a>을 했었는데, 참고하길 바란다. (지금은 IP 주소에서 Class가 아닌 CIDR을 사용해서 언젠가 포스팅을 해야겠다고 생각하고는 있었는데, 이제야 올린다..)</p>
<p>CIDR를 사용할 때 한가지 더 알아야 할 것이 있다. RFC 1918 규약인데, Public IP와 Private IP를 헷갈리지 않기 위해서이다. </p>
<blockquote>
<p>Private IP는 내부망에서 사용하고, Public IP는 인터넷에서 사용하는 것이다. 그러니까 하나의 Public IP는 전세계에서 유일한 값을 가진다.</p>
</blockquote>
<blockquote>
<p>CIDR를 사용할 때, RFC 1918 규약을 지켜야 하는데, 이는 위에서 언급한 Public IP와 Private IP를 헷갈리지 않기 위해서이다. RFC 1918에서는 내부망에서 사용하는 Private IP의 주소 범위를 정해놓았다. 주소 범위는 아래를 참고</p>
</blockquote>
<table>
<thead>
<tr>
<th align="center">주소 범위</th>
<th align="center">Subnet 길이</th>
<th align="center">표시</th>
<th align="center">이진법</th>
</tr>
</thead>
<tbody><tr>
<td align="center">10.0.0.0 ~ 10.255.255.255</td>
<td align="center">8</td>
<td align="center">10.0.0.0/8</td>
<td align="center">00001010.x.x.x</td>
</tr>
<tr>
<td align="center">172.16.0.0 ~ 172.31.255.255</td>
<td align="center">12</td>
<td align="center">172.16.0.0/12</td>
<td align="center">10101100.0000x.x.x</td>
</tr>
<tr>
<td align="center">192.168.0.0 ~ 192.168.255.255</td>
<td align="center">16</td>
<td align="center">192.168.0.0/16</td>
<td align="center">11000000.10101000.x.x</td>
</tr>
</tbody></table>
<p>CIDR에 대해서 감이 잡힌다면 이제 VPC의 IPv4 CIDR 블록을 잡아보겠다. AWS에서는 CIDR 블록의 넷마스크의 <strong>길이를 16~28로 제한</strong>하였다. 그러니까 이 범위 안의 넷마스크를 써야하는 것이다. 일단 나는 AWS EC2 인스턴스를 그렇게 많이 만들지 않을 것이다. 그러니까 VPC 내부의 Host 주소가 많을 필요가 없다. 그래서 그냥 <strong>AWS가 추천해준대로 10.0.0.0/24로 CIDR 블록을 설정</strong>해주었고 이렇게 하면 RFC 1918 규약도 지켰으며, Host 주소를 8 bit을 사용해서 VPC 내에서 Private IP는 2^8=256개를 사용할 수 있다.(사실 특수 용도 IP 때문에 256개보다는 적은 개수를 쓸 수 있다.)</p>
<p>CIDR 블록을 설정을 완료했으면 VPC 생성 버튼을 눌러 VPC 생성을 완료한다. </p>
<h3 id="subnet-생성">Subnet 생성</h3>
<p>VPC를 생성했으니 이제 Subnet을 생성해야 한다. 이번 포스팅에서는 Public Subnet만을 생성해볼 것이다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/61afb56a-35a5-4830-81bc-bc9382abafd1/image.png" alt=""></p>
<p>VPC를 생성했을 때처럼 VPC 대시보드(관리 콘솔에서 좌측에 보이는)의 가상 프라이빗 클라우드 항목 아래의 두번째에 서브넷 버튼이 있다. 그것을 클릭해서 <strong>서브넷 관리 페이지로 이동</strong>한다. </p>
<p>서브넷 관리 페이지도 VPC 관리 페이지와 UI가 비슷하다. 우측 상단의 서브넷 생성 버튼을 눌러 서브넷을 생성한다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/c4f20df5-fa4e-47aa-a067-da5acef66e52/image.png" alt="">
서브넷 생성 버튼을 눌러 이동하면 위와 같이 어떤 VPC 내에 서브넷을 만들 것인지 결정할 수 있다. 위에서 생성한 &#39;my-vpc-01&#39;을 선택한다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/3e158d95-5c46-486f-8ec7-a14c619fa8f9/image.png" alt="">
VPC를 선택하면 위와 같이 서브넷 설정창이 뜬다. 서브넷 이름은 추천대로 &#39;my-subnet-01&#39;로 정해주었고 가용영역을 중간에 설명을 안했는데, 일단은 장애가 발생했을 때도 서비스가 지속 운영되기 위해 필요한 장치라고만 기억하고 넘어가고 &#39;ap-northeast-2a&#39;로 설정한다. </p>
<blockquote>
<p><strong>참고!</strong> 아시아 태평양, ap-northeast-2a가 뜨지 않는다면, 관리 콘솔 우측 상단의 지역이 서울인지 아니면 다른 지역인지 확인할 것. 만약 서울이 아니라 다른 지역이면 ap-northeast-2a가 뜨지 않고 다른 지역(Region)의 가용영역이 뜰 것이다. </p>
</blockquote>
<p>그 다음 서브넷의 IPv4 CIDR 블록은 VPC에서 10.0.0.0/24로 CIDR 블록을 지정해주었으니 넷마스크가 24보다 작거나 같아야 한다. (VPC 내의 IP 주소 중 앞의 24 bit가 이미 고정이기 때문이다.)
나는 &#39;10.0.0.0/26&#39;으로 설정해주었다. </p>
<p>마지막으로 생성 버튼을 누르면 서브넷이 생성이 된다. 그리고 여기서는 Public Subnet으로 만들 것이기 때문에 Public IP가 필요하다.</p>
<p><img src="https://images.velog.io/images/halim_limha/post/e126ce3a-c9e5-4efe-bc1e-7e654b3cf508/image.png" alt=""></p>
<p>생성된 서브넷을 클릭하고, 우측 상단의 작업 버튼을 눌러 &#39;자동 할당 IP 설정 수정&#39; 버튼을 누른다. 
<img src="https://images.velog.io/images/halim_limha/post/8ca18ada-40d8-400e-88c1-59943b54756a/image.png" alt="">
그 다음 퍼블릭 <strong>IPv4 주소 자동 할당 활성화를 체크</strong>한 뒤에 저장한다. </p>
<hr>
<ul>
<li><h4 id="추가-subnet이-헷갈리는-사람을-위한-그림">(추가!) Subnet이 헷갈리는 사람을 위한 그림</h4>
</li>
</ul>
<p>아무래도 Subnet 형성을 할 때, 10.0.0.0/24니 10.0.0.0/26이니 계속 헷갈리게 숫자가 나와서 이해하기 어려운 사람들이 있을 것이라고 생각해서 그림을 준비해보았다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/b706555f-a6e6-4d2b-98e4-402a0f53e86d/image.png" alt=""></p>
<p>32 bit 길이의 IPv4의 주소를 표로 나타내면 위와 같을 것이다. (특수목적의 주소를 제외하지는 않았다.)</p>
<p>이 포스팅에서 <strong>VPC를 생성할 때 CIDR 블록을 10.0.0.0/24로 설정</strong>하였다. 이 말은 즉 앞의 24 bit는 고정이어서 이진수로 나타낼 때, 0000 1010 0000 0000 0000 0000은 항상 똑같다는 말이다. 이를 표현한 그림은 아래와 같다. 
<img src="https://images.velog.io/images/halim_limha/post/2a8ee59c-87b8-4371-a21c-f4110185cc93/image.png" alt="">
그림을 보면 앞의 24 bit가 0000 1010 0000 0000 0000 0000 값을 가진 곳은 모두 <span style="color:blue"><strong>파란색</strong></span>으로 칠해놓았다. 즉 이 부분이 <strong>VPC 내에서 사용하는 IP 주소</strong>라는 것이다. </p>
<p>그리고 <strong>Subnet을 생성할 때 CIDR 블록은 10.0.0.0/26으로 설정</strong>하였다. 똑같이 이 말은 앞의 26 bit가 고정이라는 것이다. 그러니까 0000 1010 0000 0000 0000 0000 + 00까지 항상 똑같다는 말이다. 
<img src="https://images.velog.io/images/halim_limha/post/46b1d32d-8e9e-4076-998f-3e1163167c54/image.png" alt="">
그림으로 표현하면 위와 같이, <span style="color:pink"><strong>분홍색</strong></span>으로 칠한 부분이 생성한 <strong>Subnet에서 사용하는 IP 주소 영역</strong>이다. IP 주소의 26 bit가 모두 0000 1010 0000 0000 0000 0000 00으로 같은 것을 알 수 있다. </p>
<hr>
<h3 id="internet-gateway-생성">Internet Gateway 생성</h3>
<p>이제 Public Subnet이 인터넷으로 연결되기 위한 Internet Gateway가 필요하다. 지금까지 해보았으면 알겠지만 Internet Gateway도 대시보드의 가상 프라이빗 클라우드 항목 아래에서 찾을 수 있다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/277d0e4e-2fe4-47bc-867b-7ebc91169674/image.png" alt=""></p>
<p>인터넷 게이트웨이 관리 페이지로 들어가서 <strong>우측 상단의 인터넷 게이트웨이 생성 버튼을 클릭</strong>하여 이름을 정해주면 인터넷 게이트웨이는 쉽게 생성된다. </p>
<p>생성이 되었으면 VPC와 연결해주어야 한다. 
<img src="https://images.velog.io/images/halim_limha/post/cd01627f-abb3-4ffa-ba1b-ad56c8643b31/image.png" alt="">
인터넷 게이트웨이를 생성한 직후 위 그림의 초록색 팝업 메세지에서도 VPC에 연결을 하라고 안내가 나오며, 작업 버튼을 클릭해서도 VPC 연결 항목을 골라서 VPC에 연결할 수 있다. </p>
<h3 id="라우팅-테이블-작성">라우팅 테이블 작성</h3>
<p>마지막으로 라우팅 테이블을 작성해주어야 한다.  <strong>라우팅 테이블을 수정하여 VPC 내의 데이터 패킷이 인터넷 게이트웨이로 향하는 경로를 만들어주어야</strong> 하는 것이다. </p>
<p><img src="https://images.velog.io/images/halim_limha/post/6f190252-7d8b-45bf-be9b-e2755366aac9/image.png" alt=""></p>
<p>다시 서브넷 관리 콘솔로 들어와서 아까 생성한 서브넷을 클릭한 후 라우팅 테이블 항목을 클릭하면 라우팅 테이블 상태를 볼 수 있다. 지금은 10.0.0.0/24는 local로 향한다는 내용밖에 없다. </p>
<p>라우팅 테이블을 생성하려면 가상 프라이빗 클라우드 항목 아래의 라우팅 테이블을 클릭하여 라우팅 테이블 관리 페이지로 들어간다. 
<img src="https://images.velog.io/images/halim_limha/post/c7507bc9-c864-4ab2-9fc3-3b332fa7757e/image.png" alt="">
그 다음 라우팅 테이블 생성 버튼을 클릭하고 VPC 연결은 아까 만든 VPC에 연결하여 라우팅 테이블을 생성하면 된다. 
<img src="https://images.velog.io/images/halim_limha/post/1fe2610a-5e2a-4eaf-b93f-03be4ed39201/image.png" alt="">
라우팅 테이블을 생성한 뒤에 생성한 라우팅 테이블을 클릭하고 아래에서 라우팅 항목을 클릭하면 라우팅 테이블 정보가 표시된다. 이 때 오른쪽의 <strong>라우팅 편집 버튼을 눌러 라우팅 테이블을 수정</strong>한다. 
<img src="https://images.velog.io/images/halim_limha/post/650dcfb8-b09c-45a2-8f9a-2267e155f664/image.png" alt="">
좌측 하단의 라우팅 추가 버튼을 클릭하여 새 라우팅 정보를 입력한다. 대상 항목에는 0.0.0.0/0을 입력한다. (0.0.0.0/0은 외부 인터넷으로 향해야 하는 데이터 패킷을 의미) 그리고 그것들이 인터넷 게이트웨이를 거쳐야 외부 인터넷으로 향할 수 있기 때문에 두번째 대상 항목을 클릭하고 인터넷 게이트웨이 항목을 눌러 생성한 인터넷 게이트웨이를 선택해주고 변경사항을 저장해주면 된다. </p>
<p>마지막으로 &#39;서브넷 연결 항목&#39;을 클릭해 생성한 서브넷과 연결해주면 Public Subnet이 인터넷 게이트웨이를 통해 인터넷과 연결될 수 있는 VPC 환경이 만들어진다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[IP 주소체계 정리(feat. 지하철 Wifi)]]></title>
            <link>https://velog.io/@halim_limha/IP-%EC%A3%BC%EC%86%8C%EC%B2%B4%EA%B3%84-%EC%A0%95%EB%A6%ACfeat.-%EC%A7%80%ED%95%98%EC%B2%A0-Wifi</link>
            <guid>https://velog.io/@halim_limha/IP-%EC%A3%BC%EC%86%8C%EC%B2%B4%EA%B3%84-%EC%A0%95%EB%A6%ACfeat.-%EC%A7%80%ED%95%98%EC%B2%A0-Wifi</guid>
            <pubDate>Wed, 02 Jun 2021 15:09:17 GMT</pubDate>
            <description><![CDATA[<h2 id="지하철-wifi는-ip-주소를-몇개나-할당할-수-있나">지하철 Wifi는 IP 주소를 몇개나 할당할 수 있나?</h2>
<p>요즈음, 네트워크와 관련해서 궁금한 것이나 헷갈리는 것이 생기면 바로바로 왜 그런 것인지 생각하고 찾아보려는 노력을 하고 있다. </p>
<p>어제인가 생각 난 것인데, 지하철의 Wifi는 IP 주소를 어떻게 할당하는지가 궁금해졌다. 보통 Wifi는 192로 시작하는 Class C의 IP 주소를 사용하기 때문에 Host 파트인 8 bit로 IP 주소를 할당해야 한다.(브로드캐스팅용과 네트워크 주소 그 자체를 빼면 2^8 - 2 = 254개의 주소를 할당 할 수 있다.) </p>
<p>그런데 지하철에는 사람이 엄청 많은데 어떻게 Class C로 IP 주소를 사람들 모두에게 할당할 수 있는걸까? 그래서 지하철은 Class B를 써서 Host 파트를 더 늘리나 싶었지만, 오늘 아침 지하철에서 한번 IP 주소를 확인해보니 192로 시작하는 Class C의 주소였다. (<del>다시 생각해보니까 지하철 한칸에 아무리 많아도 200명도 안될 것 같긴하다.</del>)</p>
<blockquote>
<p>진짜로 찾아보니 160명이 지하철 혼잡도 100%라고 한다. 그런데 출근길의 지하철 객차의 혼잡도가 200%가 넘을 때도 있다고 하는데, 이 말은 320명이 넘게 한 객차에 있다는 말이된다. 그렇다면 Class C로는 IP 주소를 다 할당하지 못할텐데 기회가 되면 실험을 해봐야겠다. </p>
</blockquote>
<blockquote>
<p>참고 : 지하철에는 맨끝 객차에 중계기(Repeater)가 있고 객차마다 Wifi 공유기가 설치되어 있다고 한다. (중계기는 약해진 신호를 받아 다시 강하게 전달해주는 역할을 함.)</p>
</blockquote>
<p>사실 이런 고민들을 학창 시절, 컴퓨터 네트워크와 컴퓨터 통신 수업을 들을 때 했어야 했는데 그러지 않았던 것이 아쉽다. 그랬으면 공부할 때 이해도 더 잘되고 기억에도 오래 남을 수 있었을텐데..</p>
<p>네트워크 수업 때 IP 주소체계를 처음 들었을 때, 이해가 잘 되지 않았다. 지금 생각해보면 위와 같이 실생활에 적용해보면 쉽게 알 수 있었을 것이라는 생각이 든다. </p>
<h1 id="ip-주소-체계">IP 주소 체계</h1>
<p>IP는 크게 IPv6와 IPv4 두가지로 나뉜다. IPv6는 IPv4의 고갈 문제 때문에 주소의 크기를 확장시킨 것이라고 짧게 설명을 마치겠다. </p>
<p>IPv4는 <strong>32 bit</strong>로 이루어져있으며 <strong>클래스</strong>로 구분을 한다. 클래스는 A,B,C,D,E 총 5개로 나뉘어진다. IPv4의 32 bit 중 가장 앞쪽에 1이 몇개인지에 따라 클래스를 구분할 수 있다. A Class는 맨 앞에 1이 0개, B는 1개, C는 2개 이런 방식이다. 아래에 IP 주소를 이진수로 표현해놓은 것에 볼드체를 해놓았다.  </p>
<h2 id="class-a">Class A</h2>
<p>이진수 : <strong>0000</strong>000<strong>1</strong>.x.x.x </p>
<blockquote>
<p>1을 넣은 이유는 Class A는 Network 파트가 8 bit까지인데, 모두가 0이면 특수한 용도의 주소로 사용되기 때문이다.</p>
</blockquote>
<ul>
<li>범위 : 1.0.0.0~126.255.255.255</li>
<li>Network Part : 맨 앞 8 bit</li>
<li>Host Part : 나머지 24 bit</li>
<li>할당 가능 IP 개수 : 2^24 - 2개</li>
</ul>
<h2 id="class-b">Class B</h2>
<ul>
<li>이진수 : <strong>1000</strong>0000.x.x.x</li>
<li>범위 : 128.0.0.0~191.255.255.255</li>
<li>Network Part : 맨 앞 16 bit</li>
<li>Host Part : 나머지 16 bit</li>
<li>할당 가능 IP 개수 : 2^16 - 2개<h2 id="class-c">Class C</h2>
</li>
<li>이진수 : <strong>1100</strong>0000.x.x.x</li>
<li>범위 : 192.0.0.0~223.255.255.255</li>
<li>Network Part : 맨 앞 24 bit</li>
<li>Host Part : 나머지 8 bit</li>
<li>할당 가능 IP 개수 : 2^8 - 2개(254개)<blockquote>
<p>A, B, C Class까지가 일반적으로 사용되는 IP 주소 Class이다.</p>
</blockquote>
</li>
</ul>
<h2 id="class-d">Class D</h2>
<ul>
<li>이진수 <strong>1110</strong>0000.x.x.x</li>
<li>범위 : 224.0.0.0~239.255.255.255</li>
</ul>
<p>멀티캐스팅 용도로 사용되는 Class이다.</p>
<h2 id="class-e">Class E</h2>
<ul>
<li>이진수 <strong>1111</strong>0000.x.x.x</li>
<li>범위 : 240.0.0.0~255.255.255.255</li>
</ul>
<p>연구개발 목적으로 사용되는 Class이다. </p>
<h2 id="class-범위-계산">Class 범위 계산</h2>
<p>처음에는 범위도 외워야 하는 줄 알았다. 그러나 조금만 더 생각해보면 쉽게 계산할 수 있다는 것을 알 수 있다. 예를 들면 Class C의 경우 범위가 192.0.0.0~223.255.255.255인데, Class C의 경우 <strong>맨 앞의 비트가 110이어야 하고 111이 되면 안된다.</strong> 그렇기 때문에 Class C Network 파트의 최댓값은 이진수로 <strong>11011111</strong>로 <strong>223</strong>인것을 알 수 있다.</p>
<p>그러니까 Class C의 주소는 192.0.0.0~223.255.255.255의 범위를 가지는 것이고 다른 Class들도 마찬가지이다. </p>
<h2 id="특수-목적-ipv4-주소">특수 목적 IPv4 주소</h2>
<p>위의 A, B, C Class에서 할당 가능 IP 개수를 보면 Host 파트가 24, 16, 8 bit임에도 2^24개, 2^16개, 2^8개의 IP 주소를 할당할 수 없다. 이는 특수한 목적의 IPv4 주소가 따로 지정되어 있기 때문인데, Host 파트의 경우 전부 0이면 네트워크 주소 그 자체를 가리키고, 전부 1이면 그 네트워크 내에 모든 주소로 브로드캐스트를 하기 위한 용도로 사용된다. </p>
<p>특수 목적 IPv4 주소는 아래의 표와 같이 정리할 수 있다.</p>
<table>
<thead>
<tr>
<th align="center">특수 주소</th>
<th align="center">Network Part</th>
<th align="center">Host Part</th>
</tr>
</thead>
<tbody><tr>
<td align="center">Network 주소</td>
<td align="center">특정</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">직접 브로드캐스팅 하기 위한 주소</td>
<td align="center">특정</td>
<td align="center">모두 1</td>
</tr>
<tr>
<td align="center">제한된 브로드캐스팅을 위한 주소</td>
<td align="center">모두 1</td>
<td align="center">모두 1</td>
</tr>
<tr>
<td align="center">이 네트워크에 있는 이 호스트</td>
<td align="center">모두 0</td>
<td align="center">모두 0</td>
</tr>
<tr>
<td align="center">이 네트워크에 있는 특정 호스트</td>
<td align="center">모두 0</td>
<td align="center">특정</td>
</tr>
<tr>
<td align="center">Loop Back</td>
<td align="center">127</td>
<td align="center">상관 없음</td>
</tr>
</tbody></table>
<h2 id="cidr">CIDR</h2>
<p>이 포스팅에서 IPv4의 주소 체계를 이야기했고, Class가 어떻게 나누어지는지 설명하였다. 그러나 현재는 IP 주소를 Class로 나누지 않고 <strong>CIDR</strong>(<strong>Classless</strong> Inter-Domain Routing)이라는 기법을 사용한다. 이에 대해서는 AWS로 VPC라는 가상의 네트워크를 생성하면서 설명했으니 참고하길 바란다.
<a href="https://velog.io/@halim_limha/AWS-VPC-EC2-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC">https://velog.io/@halim_limha/AWS-VPC-EC2-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DHCP IP 할당 과정]]></title>
            <link>https://velog.io/@halim_limha/DHCP-IP-%ED%95%A0%EB%8B%B9-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@halim_limha/DHCP-IP-%ED%95%A0%EB%8B%B9-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Mon, 31 May 2021 14:38:43 GMT</pubDate>
            <description><![CDATA[<p>IP 할당이 어떻게 일어나는지 알고 있느냐는 질문에 DHCP가 생각이 났지만, 제대로 설명을 하지 못했다. 분명히 DHCP란 것이 IP 주소가 부족하기 때문에 동적으로 IP 주소를 할당해주고 그 IP 주소를 DHCP 서버로부터 가져오는 것이라고 대충은 알고 있었는데, 그 세부적인 동작 과정을 설명하지 못할 것 같아서 잘 모른다는 답변을 했다.</p>
<p>머리속에만 떠오르고 다른 사람들에게 설명하지 못하면 그것은 모르는 것이나 마찬가지라고 생각한다. 그래서 이번 글을 써보면서 DHCP가 무엇인지 다시 정리해보기로 했다.</p>
<h1 id="dhcp란">DHCP란?</h1>
<p>일단 DHCP는 Dynamic Host Configuration Protocol로 <strong>프로토콜의 한 종류</strong>이다. 프로토콜의 하나의 종류로, <strong>호스트들의 IP를 자동으로 할당해주는 역할</strong>을 한다. </p>
<p>호스트들의 IP를 자동으로 할당해주는 역할을 왜 해야하는지 생각을 해보았다. </p>
<p>첫번째로 생각나는 것은 이 세상에는 수 많은 컴퓨터가 존재하는데, 그 컴퓨터마다 일일이 IP 주소를 직접 설정해주는 것은 굉장히 번거로운 일이라고 생각을 했다. </p>
<p>두번째로는 컴퓨터를 사용할 때, 가정에서 사용하는 PC 같은 경우는 켜져 있는 시간도 있지만 사용하지 않는 시간대도 분명히 있을 것이다. 이 때, 켜져있지도 않은 PC를 위해 IP 주소를 할당해놓는 것은 낭비일 것이라는 생각을 했다. 그래서 차라리 컴퓨터가 켜질 때, IP 주소를 할당받는게 좋지 않을까 하는 생각이다. </p>
<p>세번째는 첫번째 이유와 비슷한데, 만약에 사람이 직접 IP 주소를 할당받도록 한다면, 실수로 동시에 여러 Host에서 IP를 같이 써서 충돌이 일어날 수도 있겠다는 생각을 했다. </p>
<p>스스로 생각해본 DHCP를 사용하는 이유는 위와 같다. 실제로 인터넷에서도 확인해본 결과 비슷한 이유를 설명하고 있었다. </p>
<h1 id="dhcp-동작-과정">DHCP 동작 과정</h1>
<p>조금만 더 생각해보면 DHCP 동작 과정도 기억해낼 수 있었을 것 같아 아쉬움이 남는다. </p>
<p>중요한 것은 IP 주소도 할당받지 못한 Host는 DHCP 서버가 어디에 있는지 알 수가 없다. 그렇기 때문에 Host가 속한 Subnet에 브로드캐스팅을 해야 한다. </p>
<blockquote>
<p>참고 : IP를 할당받지 않았으니 IP를 이용하는 것이 아니고 물리적 주소인 MAC 주소를 사용하여 브로드캐스팅을 한다. (브로드캐스팅용 MAC 주소 : FF:FF:FF:FF:FF:FF) </p>
</blockquote>
<p>연결 대상이 어디에 있는지 알 수가 없기 때문에 브로드 캐스팅을 하는 것을 생각해보면, TCP나 UDP 중 UDP가 적합한 것을 쉽게 유추할 수 있다. TCP는 연결을 한 뒤에 통신을 하지만 UDP는 연결 과정이 없기 때문에 더 빠르다. </p>
<p>다시 정리를 해보면 DHCP를 통해 IP를 할당받는 과정은 총 4단계로 설명할 수 있다.</p>
<h2 id="1-dhcp-discover">1. DHCP Discover</h2>
<p>Host가 DHCP 서버가 어디에 있는지 찾고 IP 할당을 요청하는 과정이다. 위에서 말했던대로 IP 할당 요청을 브로드캐스팅해서 DHCP 서버로 보낸다. </p>
<h2 id="2-dhcp-offer">2. DHCP Offer</h2>
<p>DHCP 서버가 발견되고 IP 할당 요청이 잘 도착하면 DHCP 서버에서는 Host에게 요청한 IP와 Host의 MAC 주소를 같이 보낸다.(DHCP Offer도 브로드캐스팅을 하기 때문에 MAC 주소도 같이 보내는 것으로 예상된다.)</p>
<h2 id="3-dhcp-request">3. DHCP Request</h2>
<p>DHCP Offer를 통해서 IP를 할당받으면 그 IP를 사용하겠다고 DHCP 서버에 확정 메세지를 보내야 한다. 그것이 DHCP Request이다. </p>
<h2 id="4-dhcp-ack">4. DHCP Ack</h2>
<p>DHCP 서버에서 DHCP Request를 받으면 다시 ACK를 보내주어야 마무리가 된다. 이 때 ACK는 브로드캐스팅을 할 수도 있고 유니캐스트를 할 수도 있는데, 왜 굳이 브로드캐스팅까지 가능하게 했는지는 한번 고민을 해봐야 할 것 같다. </p>
<p>Stackoverflow에 비슷한 질문을 한 사람이 있어서 답변을 살펴보았고 아래의 RFC를 볼 수 있었다.</p>
<p><strong>RFC2131</strong> : <a href="https://www.ietf.org/rfc/rfc2131.txt">https://www.ietf.org/rfc/rfc2131.txt</a></p>
<blockquote>
<p>   Normally, DHCP servers and BOOTP relay agents attempt to deliver
   DHCPOFFER, DHCPACK and DHCPNAK messages directly to the client using
   uicast delivery.  The IP destination address (in the IP header) is
   set to the DHCP &#39;yiaddr&#39; address and the link-layer destination
   address is set to the DHCP &#39;chaddr&#39; address.  Unfortunately, some
   client implementations are unable to receive such unicast IP
   datagrams until the implementation has been configured with a valid
   IP address (leading to a deadlock in which the client&#39;s IP address
   cannot be delivered until the client has been configured with an IP
   address).
 A client that cannot receive unicast IP datagrams until its protocol
   software has been configured with an IP address SHOULD set the
   BROADCAST bit in the &#39;flags&#39; field to 1 in any DHCPDISCOVER or
   DHCPREQUEST messages that client sends.  The BROADCAST bit will
   provide a hint to the DHCP server and BOOTP relay agent to broadcast
   any messages to the client on the client&#39;s subnet.  A client that can
   receive unicast IP datagrams before its protocol software has been
   configured SHOULD clear the BROADCAST bit to 0.  The BOOTP
   clarifications document discusses the ramifications of the use of the
   BROADCAST bit</p>
</blockquote>
<p>해석을 해보자면 몇몇의 Client는 IP 주소를 제대로 받기 전에 Unicast된 IP datagram을 받을 수 있도록 구현되지 않았기 때문이라고 할 수 있다.  </p>
<p>그래서 이런 Client는 DHCP Discover와 Request 메세지를 보낼 때, DHCP Server가 미리 Client의 상태를 알고 브로드캐스팅할 수 있도록 flag bit를 1로 만들고 보낸다고 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ElasticSearch Cluster에서 스냅샷 복구하기(NFS 활용)]]></title>
            <link>https://velog.io/@halim_limha/ElasticSearch-Cluster%EC%97%90%EC%84%9C-%EC%8A%A4%EB%83%85%EC%83%B7-%EB%B3%B5%EA%B5%AC%ED%95%98%EA%B8%B0NFS-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@halim_limha/ElasticSearch-Cluster%EC%97%90%EC%84%9C-%EC%8A%A4%EB%83%85%EC%83%B7-%EB%B3%B5%EA%B5%AC%ED%95%98%EA%B8%B0NFS-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Sat, 22 May 2021 06:44:52 GMT</pubDate>
            <description><![CDATA[<p>ElasticSearch의 스냅샷을 복구해보는 실험을 진행하기 위해 다른 사람들이 올려놓은 튜토리얼을 참고하였다. 그런데 다들 Local 모드에서 실험을 하여서 2개의 서버로 Cluster가 구성되어 있는 나의 사례와는 방법이 다를 것이라고 생각했고 실제로 그랬다. 아래는 Cluster 환경에서 스냅샷을 복구하는 테스트를 진행한 과정이다. 그래서 ElasticSearch 관련 내용이라기보다는 NFS 구성하는 방법에 대한 내용이 될 것이다.</p>
<p>결론부터 말하면 Local 모드와 다른 점은 스냅샷 리포지토리를 ES의 Node들이 공유가 가능해야 한다는 것이다. 사실 처음 생각하기에도, 각 Node에 스냅샷 파일들을 모두 저장하고 각각의 Node가 자신의 Local Storage에서 스냅샷을 불러오는 것은 비효율적이라는 느낌이 들었다. </p>
<blockquote>
<p>참고 : 혹시 몰라서 각 Node마다 똑같은 경로에 스냅샷 파일을 저장하고 테스트를 해봤지만 이렇게 하면 각 Node에서 스냅샷에 접근이 안된다고 에러가 뜬다..</p>
</blockquote>
<p>해야할 일은 아래와 같다. </p>
<p><strong>1. elasticsearch.yml 파일에 스냅샷 리포지토리 경로 설정</strong>
<strong>2. Shared File System 생성</strong>
<strong>3. Snapshot API를 활용하여 복구</strong></p>
<h2 id="스냅샷-리포지토리-경로-설정">스냅샷 리포지토리 경로 설정</h2>
<p>elasticsearch.yml 파일에서 스냅샷 리포지토리의 경로를 설정해주어야 한다. 마지막에 사용할 스냅샷 API에도 리포지토리의 경로에 대한 내용이 들어가긴 하지만 elasticsearch.yml 파일에 스냅샷 리포지토리의 경로가 없으면 에러가 뜬다. </p>
<p>파라미터의 이름은 <strong>path.repo</strong>이다. 설정한적이 없다면 아마 이 파라미터를 발견할 수 없을 것인데, 새로 만들어주고 경로를 입력하면된다. </p>
<pre><code>path.repo: [&lt;Snapshot Repository 경로&gt;, &lt;Snapshot Repository 경로2&gt;, ..]</code></pre><p>나는 NFS(Network File System)에 스냅샷을 저장해서 테스트를 했기 때문에 공유된 Directory의 경로를 입력해주었다. </p>
<h2 id="nfs-구성">NFS 구성</h2>
<h3 id="nfs-server-측">NFS Server 측</h3>
<p>Elasticsearch의 2개의 Node에 NFS을 구성해주었다. NFS Server는 Masternode가 있는 쪽에 설치해주었다. </p>
<p>먼저 apt를 update해주고</p>
<pre><code>$ sudo apt update</code></pre><p>nfs-kernel-server를 설치한다. </p>
<pre><code>$ sudo apt install nfs-kernel-server</code></pre><p>설치가 완료되었으면 nfs-server의 상태를 확인할 수 있다.</p>
<pre><code>$ sudo systemctl status nfs-server</code></pre><p>정상적으로 nfs-server가 구동되는 것을 확인한 후에 공유 용도로 쓸 Directory를 만들어 주었다. 권한과 소유권도 문제가 없도록 바꾸어주었다.  </p>
<pre><code>$ sudo chown nobody:nogroup &lt;공유할 Directory&gt;
$ sudo chmod -R 777 &lt;공유할 Directory&gt;</code></pre><p>그 다음 공유할 Directory에 Client가 접근할 수 있도록 &#39;<strong>/etc/exports</strong>&#39; 파일을 수정해주어야 한다.</p>
<pre><code>&lt;공유할 디렉토리&gt; &lt;client-IP&gt;(rw,sync,no_subtree_check)
</code></pre><p>여기서 <strong>rw</strong>는 read와 write를 허용한다는 의미이고, <strong>sync</strong>는 Disk에 파일이 쓰이기 전에 파일 접근을 막는 것을 의미한다.(허용하려면 async 옵션을 사용) 마지막으로 <strong>no_subtree_check</strong>는 subtree_check를 하지 않는다는 것을 뜻하는데 subtree_check는 NFS 서버가 Client로부터 접근되었을 때, 공유된 Directory에 접근되었는지를 확인하는 것이다. </p>
<blockquote>
<p>참고 : subtree_check는 대부분의 사용사례에서 불필요하며 오히려 문제를 일으킬 가능성이 높다고 한다. <a href="https://www.linuxquestions.org/questions/linux-newbie-8/please-explain-me-subtree_check-814080/">https://www.linuxquestions.org/questions/linux-newbie-8/please-explain-me-subtree_check-814080/</a></p>
</blockquote>
<p>/etc/exports 파일 수정이 완료되었으면 아래의 명령어를 입력해 Directory를 공유한다. </p>
<pre><code>$ sudo exportfs -a</code></pre><h3 id="nfs-client">NFS Client</h3>
<p>NFS Server가 구축이 되었으면 다른 서버에는 NFS Client가 있어야 한다.</p>
<p>nfs-kernel-server가 아닌 nfs-common으로 설치한다.</p>
<pre><code>$ sudo apt update
$ sudo apt install nfs-common</code></pre><p>설치가 완료되었으면 NFS Server를 원하는 Directory에 Mount한다.</p>
<pre><code>$ sudo mount &lt;nfs-server 주소&gt;:&lt;공유 Directory 경로&gt; &lt;Mount할 Directory 경로&gt;</code></pre><p>Mount까지 성공했으면 공유된 Directory에 파일을 하나 생성해보고 테스트를 할 수 있다. </p>
<h2 id="snapshot-api">Snapshot API</h2>
<p>공유 파일 시스템이 만들어졌으니 이제 스냅샷을 복구할 수 있다. </p>
<pre><code>PUT /_snapshot/my_fs_backup
{
  &quot;type&quot;: &quot;fs&quot;,
  &quot;settings&quot;: {
    &quot;location&quot;: &quot;&lt;공유 Directory Path&gt;&quot;,       
    &quot;compress&quot;: true
  }
}</code></pre><p>공유 파일 시스템을 쓰기 때문에 &quot;type&quot;이 &quot;fs&quot;(file system)이다. </p>
<blockquote>
<p>참고 : &quot;type&quot;을 url로 하면 url을 통해서 스냅샷을 받아올 수도 있다. 일단 테스트는 NFS를 사용했지만 만약에 실제로 Elasticsearch Cluster를 운영한다면 NFS에 스냅샷을 저장하는 것은 굉장히 번거로울 것이다.</p>
</blockquote>
<p>성공하면 Response로 acknowledge가 true로 반환된다. </p>
<p>참고(ES Official Guide) : <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshots-register-repository.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshots-register-repository.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ElasticSearch Cluster 암호 걸기]]></title>
            <link>https://velog.io/@halim_limha/ElasticSearch-Cluster-%EC%95%94%ED%98%B8-%EA%B1%B8%EA%B8%B0</link>
            <guid>https://velog.io/@halim_limha/ElasticSearch-Cluster-%EC%95%94%ED%98%B8-%EA%B1%B8%EA%B8%B0</guid>
            <pubDate>Wed, 19 May 2021 08:05:18 GMT</pubDate>
            <description><![CDATA[<p>X-Pack은 ElasticSearch의 Plugin이며 보안 기능이 추가되어 있다. 이전 버전에서는 따로 설치를 해야했지만 현재 버전(7.12)에서는 ElasticSearch를 설치하면 X-Pack도 포함이 되어 있다. </p>
<p>그래서 따로 플러그인을 설치할 필요없이 몇가지 작업을 해주면 ElasticSearch Cluster에 암호를 설정할 수 있고, User나 Role을 설정해서 User마다 Index, Cluster에 대한 접근 제한을 할 수 있다. </p>
<h2 id="tls-구성">TLS 구성</h2>
<p>먼저 Node마다 인증서가 필요하다. 다른 프로그램으로 인증서를 생성해도 되지만 ElasticSearch에서도 인증서를 만들어주는 기능이 있다. ElasticSearch의 bin 디렉토리가 있는 곳(/usr/share/elasticsearch)로 가서 아래의 명령어로 인증서를 생성한다.</p>
<pre><code>bin/elasticsearch-certutil cert -out /etc/elasticsearch/elastic-certificates.p12 -pass &quot;&quot;</code></pre><p>맨끝에 -pass &quot;&quot; 이 부분은 인증서의 비밀번호 설정이며 &quot;&quot;로 집어넣었기 때문에 비밀번호가 없는 것이다. -pass &quot;&quot;를 넣지않으면 인증서 비밀번호를 입력하라는 프롬프트가 뜬다. </p>
<blockquote>
<p>참고 : Java version이 9 이전 버전이라면 인증서 생성을 할 수 없을 것이다. (인증서를 생성하는 알고리즘이 구현이 안되어 있는 것으로 추정) JDK를 업데이트하면 해결될 것이다.</p>
</blockquote>
<p>Datanode에도 MasterNode에서 만든 인증서를 복사해서 같은 위치에 넣어주면 된다. SSH을 통한 통신으로 원격 서버에 인증서를 보내줄 수 있다. </p>
<pre><code>$ scp &lt;파일명(/etc/elasticsearch/elastic-certificates.p12)&gt; &lt;user@hostip&gt;:&lt;도착할 위치(/tmp)&gt;</code></pre><p>권한 문제 때문에 인증서가 도착할 원격 서버의 File Path는 /tmp로 하고 원격 서버에 SSH로 접속한 뒤에 Root User로 들어가 다시 /etc/elasticsearch directory로 옮겨주었다.</p>
<h2 id="elasticsearchyml-파일">elasticsearch.yml 파일</h2>
<p>인증서를 생성했으니 이제 elasticsearch.yml 파일도 TLS 통신을 하도록 설정을 해주어야 한다. </p>
<p>아래의 parameter들을 elasticsearch.yml 파일에 넣어주면 된다. xpack의 security 기능들을 사용하도록 설정한 것이다.</p>
<pre><code>xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12</code></pre><p><img src="https://images.velog.io/images/halim_limha/post/3ce92a57-f4ca-4d0a-984d-2fa3cc3787ff/image.png" alt="">
yml 파일을 수정했으면 ElasticSearch를 재시작한다.</p>
<pre><code>$ sudo systemctl restart elasticsearch.service</code></pre><p>그러나 위의 Parameter들을 추가하고, ElasticSearch를 재실행하면 아래와 같은 Error 메세지가 뜰 것이다. </p>
<blockquote>
<p>Job for elasticsearch.service failed because the control process exited with error code.
See &quot;systemctl status elasticsearch.service&quot; and &quot;journalctl -xe&quot; for details.</p>
</blockquote>
<p>이유를 확인하려고 Log를 확인하였더니 원인은 Permission이었다. </p>
<blockquote>
<p>참고 : Log는 /var/log/elasticsearch/&lt;클러스터이름&gt;.log를 확인하면 될 것이다.
<img src="https://images.velog.io/images/halim_limha/post/d0cf62a0-7cce-4d7f-a8cc-8b4385b80e69/image.png" alt="">
위의 사진을 보면 인증서 파일만 Group에 권한이 없는 것을 확인할 수 있다. 다른 파일들과 권한을 같게 만들어주었다.</p>
</blockquote>
<pre><code>$ chmod g+rw elastic-certificates.p12</code></pre><p>그리고 Elasticsearch를 재시작하면 정상적으로 작동할 것이다.</p>
<h2 id="cluster-비밀번호-설정">Cluster 비밀번호 설정</h2>
<p>Master Node와 Data Node가 다시 실행되었으면, Cluster의 비밀번호를 설정해야 한다. 비밀번호 설정은 Master Node에서 한다.</p>
<p>아래의 명령어를 입력하면 랜덤한 비밀번호를 자동으로 설정해준다.</p>
<pre><code>$ bin/elasticsearch-setup-passwords auto</code></pre><p>parameter를 interactive로 하면 직접 비밀번호를 입력하여 설정할 수도 있다. Test에서는 interactive를 선택해서 비밀번호를 작성하였다.</p>
<pre><code>$ bin/elasticsearch-setup-passwords interactive</code></pre><p>이 명령어를 실행하면 elasticsearch만 아니라 Kibana나 logstash의 비밀번호도 설정하라는 프롬프트가 뜨는데 모두 동일한 비밀번호를 설정해주었다.</p>
<blockquote>
<p>참고 : Cluster에 Datanode가 없거나 Masternode가 없는데 위의 명령어를 사용하면 에러가 뜰 것이다. 즉 Node간 연결이 이뤄지지 않았을 가능성이 있으니 확인해볼 것</p>
</blockquote>
<p><img src="https://images.velog.io/images/halim_limha/post/25c08781-2135-4fc6-bb08-4b9826aac402/image.png" alt="">
비밀번호를 설정 후 elasticsearch에 접속을 시도하면 위와 같이 Username과 Password를 입력하라는 팝업창이 뜬다. (계정명은 elastic이다.)</p>
<h2 id="basic-auth">Basic Auth</h2>
<p>HTTP 인증 방식에는 여러가지가 있지만 일단 테스트에서는 가장 기본적인 Basic Auth를 사용해보았다. 보낼 때만 암호화를 하기 때문에 스니핑에 취약하다고 한다. </p>
<h3 id="user-추가">User 추가</h3>
<p>ElasticSearch의 xpack을 사용하면 User와 Role을 부여해 User마다 Role을 부여해 접근 권한을 제한할 수 있다. </p>
<p>Elasticsearch의 REST API를 사용해서도 User와 Role을 추가할 수도 있지만, 아직 익숙하지 않아 Kibana를 이용해 GUI로 Test를 진행해보았다. </p>
<p>Kibana를 설치하고 elastic 계정으로 접속을 하면 왼쪽 하단에 톱니바퀴 모양의 아이콘이 보일텐데 그 아이콘을 클릭하면 Elasticsearch를 관리하는 옵션들이 나온다.
<img src="https://images.velog.io/images/halim_limha/post/5882fc13-00aa-4415-a3d1-49093cc8b327/image.png" alt="">
그리고 관리 항목 중 Security 항목의 Users를 클릭하면 User들의 목록을 볼 수 있고 User를 새로 만들어 줄 수도 있다. 테스트를 위해 test라는 user를 생성해보았다. </p>
<p>user를 생성했지만 아직 접근 권한을 제한할 Role을 만들지 않았기 때문에 user에 Role은 부여하지 않았다.</p>
<h3 id="role-부여">Role 부여</h3>
<p>Role도 User와 같이 Security 항목에 위치해있다. </p>
<p>Roles를 클릭하고 Create Role을 클릭한 뒤에 test-role이라는 Role을 만들어주었다.
<img src="https://images.velog.io/images/halim_limha/post/288497b2-6b3f-4785-8d43-e99e7d91b131/image.png" alt="">
아래의 Add index privilege 버튼을 눌러 test-role을 nutch라는 index을 읽을 수만 있게 권한을 부여해주었다.</p>
<p>그 뒤에 아까 만들어준 test라는 user에 test-role을 부여해주었다.</p>
<h2 id="user로-인증-후-검색">User로 인증 후 검색</h2>
<p>보안 기능을 활성화 했기 때문에 이제 ElasticSearch REST API를 이용하려면 Authorization header가 추가로 필요하다.
<img src="https://images.velog.io/images/halim_limha/post/8a1b4b2a-749b-41b8-9fce-720bcc843a73/image.png" alt="">
Postman의 Authorization 항목을 보면 Header를 추가할 수 있다. 일단 index가 어떤 것이 있는지 확인하기 위해 elastic 계정으로 인증하여 Request를 보냈다. </p>
<p>보안 기능을 활성화해서 생긴 Index를 제외하면 직접 생성한 index는 nutch와 test 하나이다. </p>
<p>이 중에서 test index는 test user에게 읽을 수 있는 권한을 주지 않았기 때문에 결과를 받을 수 없을 것이다.
<img src="https://images.velog.io/images/halim_limha/post/1485c682-00fe-4c2a-9c18-1ea897fbfe1a/image.png" alt=""></p>
<p>반면에 nutch index는 test라는 user에게 읽기 권한을 주었기 때문에 검색이 잘 된다.
<img src="https://images.velog.io/images/halim_limha/post/bd3b764f-004e-4cf5-8df1-135b93bffcc0/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HDFS Cluster 구성]]></title>
            <link>https://velog.io/@halim_limha/HDFS-Cluster-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@halim_limha/HDFS-Cluster-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Thu, 13 May 2021 14:44:25 GMT</pubDate>
            <description><![CDATA[<p>Apache Nutch라는 OpenSource 분산 크롤러를 사용하기 위해서는 Hadoop Cluster가 필요하다. 그래서 Linux(ubuntu) 서버 2대를 활용해 HDFS 구성 테스트를 진행해보았다.</p>
<h2 id="hadoop-install">Hadoop Install</h2>
<p>wget을 이용하여 Hadoop 3.2.2 version을 설치한다. </p>
<blockquote>
<p>$ sudo wget -P /usr/local/ <a href="https://downloads.apache.org/hadoop/common/hadoop-3.2.2/hadoop-3.2.2.tar.gz">https://downloads.apache.org/hadoop/common/hadoop-3.2.2/hadoop-3.2.2.tar.gz</a></p>
</blockquote>
<p>Hadoop을 설치한 Directory로 이동하여 압축을 풀어준다.</p>
<blockquote>
<p>$ sudo tar xzf hadoop-3.2.2.tar.gz</p>
</blockquote>
<h2 id="환경변수-설정">환경변수 설정</h2>
<p>Ubuntu에서 Test를 진행했기 때문에 ~/.bashrc에 환경변수를 추가해주었다. </p>
<blockquote>
<p>$ echo &#39;export HADOOP_HOME=/usr/local/hadoop-3.2.2&#39; &gt;&gt; ~/.bashrc
$ echo &#39;export PATH=&amp;#36PATH:&amp;#36HADOOP_HOME/bin:&amp;#36HADOOP_HOME/sbin&#39; &gt;&gt; ~/.bashrc</p>
</blockquote>
<p>source 명령어로 환경변수를 바로 적용해줄 수 있다.</p>
<blockquote>
<p>$ source ~/.bashrc</p>
</blockquote>
<h2 id="ssh-설치">SSH 설치</h2>
<p>Hadoop Node는 SSH를 이용하여 서로 통신을 하기 때문에 SSH Server를 설치해주어야 한다.</p>
<blockquote>
<p>$ sudo apt install openssh-server</p>
</blockquote>
<p>그리고 PDSH 기본통신을 RSH에서 SSH로 바꾸어주어야 한다. (이 부분은 아직 어떤 의미가 있는 것인지 정확히 파악이 되지 않았다. </p>
<blockquote>
<p>echo &#39;export PDSH_RCMD_TYPE=ssh&#39; &gt;&gt; ~/.bashrc</p>
</blockquote>
<p>PDSH 참고 : <a href="https://linux.die.net/man/1/pdsh">https://linux.die.net/man/1/pdsh</a></p>
<p>위의 명령어를 실행한 뒤에 변경사항을 적용하고 싶으면 hadoop 환경변수를 등록 후 source 명령어를 이용했던 것처럼 똑같이 해주면 된다.</p>
<p>SSH를 설치했으면 Hadoop이 암호화된 통신을 하기 위해 RSA Key를 만들고 분배해주어야 한다.</p>
<pre><code># 마스터노드에서 실행할 것
$ ssh-keygen -t rsa
$ cp id_rsa.pub authorized_keys

# 모든 네임노드에서 실행할 것
# {namenode00} - 모든 네임노드에 적용해야함을 의미함
$ scp authorized_keys {namenode00}:~/.ssh/authorized_keys

# 모든 데이터노드에서 실행할 것
# {datenode00} - 모든 데이터노드에 적용해야함을 의미함

ssh {datanode00}
$ mkdir -p /home/hadoop/.ssh
$ scp authorized_keys {datanode00}:~/.ssh/authorized_keys</code></pre><h2 id="hadoop-환경-설정">Hadoop 환경 설정</h2>
<p>Hadoop 환경 설정을 해주기 위해서는 4개의 xml 파일이 필요하다. 아래에 직접 테스트할 때 이용했던 xml 파일 내용을 첨부하였다. </p>
<h3 id="host-ip-alias">HOST IP Alias</h3>
<p>Node의 Host IP 주소가 들어가야 하는 부분들은 직접 IP 주소를 입력하기보다는 /etc/hosts에서 IP에 alias를 만들어주어서 그 alias를 입력하였다. </p>
<p>예를 들면 xxx.xxx.xxx.xxx라는 IP 주소를 datanode01이라고 alias를 만들어주어 사용한 것을 확인할 수 있을 것이다.</p>
<h3 id="hdfs용-스토리지">HDFS용 스토리지</h3>
<p>그리고 HDFS은 운영체제가 설치되어 있는 SSD가 아닌 HDD를 이용할 것이기 때문에 HDD를 리눅스 파일시스템으로 포맷을 하고 Mount를 해주었다.</p>
<pre><code># 스토리지 리스트 확인
sudo fdisk -l

# linux file system으로 포맷
sudo mkfs.ext4 /dev/sdb

# mount 할 디렉토리 생성
sudo mkdir /data

# mount
sudo mount /dev/sdb /data

# mount 된 폴더에 권한 부여
sudo chown -R hadoop:hadoop /data</code></pre><p>아래의 xml 파일들을 잘 보면 mounting된 /data directory를 사용하는 것을 확인할 수 있을 것이다.</p>
<h3 id="core-sitexml">core-site.xml</h3>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;?xml-stylesheet type=&quot;text/xsl&quot; href=&quot;configuration.xsl&quot;?&gt;
&lt;!--
  Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License. See accompanying LICENSE file.
--&gt;

&lt;!-- Put site-specific property overrides in this file. --&gt;

&lt;configuration&gt;
    &lt;property&gt;
        &lt;name&gt;fs.defaultFS&lt;/name&gt;
        &lt;value&gt;hdfs://namenode01:9000&lt;/value&gt;
    &lt;/property&gt;
    &lt;property&gt;
        &lt;name&gt;hadoop.tmp.dir&lt;/name&gt;
        &lt;value&gt;/home/data/hadoop/tmp&lt;/value&gt;
    &lt;/property&gt;
&lt;/configuration&gt;</code></pre><h3 id="hdfs-sitexml">hdfs-site.xml</h3>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;?xml-stylesheet type=&quot;text/xsl&quot; href=&quot;configuration.xsl&quot;?&gt;
&lt;!--
  Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License. See accompanying LICENSE file.
--&gt;

&lt;!-- Put site-specific property overrides in this file. --&gt;

&lt;configuration&gt;
    &lt;property&gt;
        &lt;name&gt;dfs.replication&lt;/name&gt;
        &lt;value&gt;1&lt;/value&gt;
    &lt;/property&gt;
    &lt;property&gt;
        &lt;name&gt;dfs.namenode.name.dir&lt;/name&gt;
        &lt;value&gt;/home/data/hadoop/dfs/name&lt;/value&gt;
    &lt;/property&gt;
    &lt;property&gt;
        &lt;name&gt;dfs.namenode.edits.dir&lt;/name&gt;
        &lt;value&gt;/home/data/hadoop/dfs/edit&lt;/value&gt;
    &lt;/property&gt;    
    &lt;property&gt;
        &lt;name&gt;dfs.datanode.data.dir&lt;/name&gt;
        &lt;value&gt;/home/data/hadoop/dfs/data&lt;/value&gt;
    &lt;/property&gt;
    &lt;property&gt;
        &lt;name&gt;dfs.namenode.secondary.http-address&lt;/name&gt;
        &lt;value&gt;namenode02:9868&lt;/value&gt;
    &lt;/property&gt;
    &lt;property&gt;
        &lt;name&gt;dfs.namenode.checkpoint.dir&lt;/name&gt;
        &lt;value&gt;/home/data/hadoop/dfs/checkpoint&lt;/value&gt;
    &lt;/property&gt;


&lt;/configuration&gt;</code></pre><h3 id="mapred-sitexml">mapred-site.xml</h3>
<pre><code>&lt;?xml version=&quot;1.0&quot;?&gt;

&lt;?xml-stylesheet type=&quot;text/xsl&quot; href=&quot;configuration.xsl&quot;?&gt;

&lt;!--

 Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);

 you may not use this file except in compliance with the License.

 You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software

 distributed under the License is distributed on an &quot;AS IS&quot; BASIS,

 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 See the License for the specific language governing permissions and

 limitations under the License. See accompanying LICENSE file.

--&gt;

&lt;!-- Put site-specific property overrides in this file. --&gt;

&lt;configuration&gt;
        &lt;property&gt;
                &lt;name&gt;mapreduce.framework.name&lt;/name&gt;
                &lt;value&gt;yarn&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;yarn.app.mapreduce.am.resource.mb&lt;/name&gt;
                &lt;value&gt;1228&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;yarn.app.mapreduce.am.env&lt;/name&gt;
                &lt;value&gt;HADOOP_MAPRED_HOME=/usr/local/hadoop-3.2.2&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;mapreduce.map.env&lt;/name&gt;
                &lt;value&gt;HADOOP_MAPRED_HOME=/usr/local/hadoop-3.2.2&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;mapreduce.reduce.env&lt;/name&gt;
                &lt;value&gt;HADOOP_MAPRED_HOME=/usr/local/hadoop-3.2.2&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;mapreduce.application.classpath&lt;/name&gt;
                &lt;value&gt;$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*,$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*,$HADOOP_MAPRED_HOME/share/hadoop/common/*,$HADOOP_MAPRED_HOME/share/hadoop/common/lib/*,$HADOOP_MAPRED_HOME/share/hadoop/yarn/*,$HADOOP_MAPRED_HOME/share/hadoop/yarn/lib/*,$HADOOP_MAPRED_HOME/share/hadoop/hdfs/*,$HADOOP_MAPRED_HOME/share/hadoop/hdfs/lib/*&lt;/value&gt;
        &lt;/property&gt;
&lt;/configuration&gt;</code></pre><h3 id="yarn-sitexml">yarn-site.xml</h3>
<p>yarn은 Hadoop Cluster의 Resource를 관리해주는 역할을 한다. 크게 ResourceManager와 NodeManager로 구성이 되어 있으며 Yarn에 대해서는 추후에 추가로 글을 더 작성해볼 예정이다. 
일단 yarn-site.xml 파일에서 해주어야 할 것은 아래의 resourcemanager.hostname property에 namenode의 Host 주소와 nodemanager.hostname property에 datanode의 Host 주소를 입력하면 된다.(Datanode가 여러개라면 각 Datanode의 yarn-site.xml 파일의 nodemanager.hostname은 각자의 Host 주소를 입력하면 된다. 예를 들면 datanode01에는 datanode01의 host 주소를, datanode02에는 datanode02의 host 주소를 입력한다.) </p>
<pre><code>&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;!--
  Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License. See accompanying LICENSE file.
--&gt;
&lt;configuration&gt;

&lt;!-- Site specific YARN configuration properties --&gt;
        &lt;property&gt;
                &lt;name&gt;yarn.resourcemanager.hostname&lt;/name&gt;
                &lt;value&gt;namenode01&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;yarn.nodemanager.hostname&lt;/name&gt;
                &lt;value&gt;datanode01&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;yarn.nodemanager.aux-services&lt;/name&gt;
                &lt;value&gt;mapreduce_shuffle&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;yarn.scheduler.capacity.maximum-am-resource-percent&lt;/name&gt;
                &lt;value&gt;1&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                &lt;name&gt;yarn.nodemanager.resource.memory-mb&lt;/name&gt;
                &lt;value&gt;4096&lt;/value&gt;
        &lt;/property&gt;
        &lt;property&gt;
                  &lt;name&gt;yarn.nodemanager.env-whitelist&lt;/name&gt;
                  &lt;value&gt;JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PERPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME&lt;/value&gt;
        &lt;/property&gt;
&lt;/configuration&gt;</code></pre><p>마지막으로 master와 workers file에 각각에 맞는 Node의 Host 주소를 입력해주면 된다. 
(namenode로 설정한 host의 IP는 master 파일에, datanode로 설정한 host의 IP는 workers 파일에 넣어주면 된다.)</p>
<h3 id="hadoop-실행">Hadoop 실행</h3>
<p>hadoop이 위치한 Directory로 이동한다. (직접 해본 Test에서는 /usr/local/hadoop-3.2.2에 위치)</p>
<p>그 뒤에 sbin directory에 있는 start-all.sh 스크립트를 실행하면 된다. sbin에 있는 shell script 파일을 확인해보면 stop-all.sh도 존재하는 것을 알 수 있을텐데 당연히 hadoop을 중지하는 script이다.</p>
<pre><code>$ sbin/start-all.sh</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[ElasticSearch Clustering 방법]]></title>
            <link>https://velog.io/@halim_limha/ElasticSearch-Clustering-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@halim_limha/ElasticSearch-Clustering-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sun, 02 May 2021 13:48:25 GMT</pubDate>
            <description><![CDATA[<p>이번 글은 ElasticSearch를 설치하는 방법과 Server 2대를 이용해 Cluster를 구성해보는 방법에 대한 글입니다. </p>
<h2 id="elasticsearch-설치">ElasticSearch 설치</h2>
<h3 id="jdk-설치">JDK 설치</h3>
<p>아래의 명령어로 JDK의 Version을 확인할 수 있다. JDK가 없으면 Not Found 메세지가 뜸.</p>
<pre><code>$ javac -version</code></pre><p>만약에 설치가 안되어 있으면 아래의 apt install으로 JDK 설치할 수 있음. ES를 쓰는데 OpenJDK가 아닌 다른 것을 써도 될 것임.</p>
<pre><code>$ sudo apt install openjdk-11-jdk</code></pre><h3 id="gpg-공개키">GPG 공개키</h3>
<p>ElasticSearch는 GPG Key를 이용하여 무결성을 검증하기 때문에 GPG Key를 가져와야 한다. 아래의 명령어를 입력해 GPG Key를 받아올 수 있다.</p>
<pre><code>$ wget -qO - https://artifacts.elastic.co./GPG-KEY-elasticsearch | sudo apt-key add -</code></pre><p><img src="https://images.velog.io/images/halim_limha/post/22ddc020-9bb4-4882-8cc2-a8d3ca37d2ce/image.png" alt="">
OK가 뜨면 문제가 없음.</p>
<h3 id="elasticsearch-repository-등록">ElasticSearch Repository 등록</h3>
<p>apt(Advanced Packaging Tool)에 Repository를 등록하면 apt를 이용하여 Elasticsearch를 설치할 수 있다.</p>
<p>직접 /etc/apt/sources.list.d Directory에서 만들어 주어도 되지만 아래의 명령어처럼 Shell Script를 실행하여 등록할 수도 있다.</p>
<pre><code>$ sudo sh -c &#39;echo &quot;deb https://artifacts.elastic.co/packages/7.x/apt stable main&quot; &gt; /etc/apt/sources.list.d/elastic-7.x.list&#39;</code></pre><p><img src="https://images.velog.io/images/halim_limha/post/97d630d3-803f-419a-b769-411af7086e8b/image.png" alt="">
elastic-7.x.list가 생긴 것을 확인할 수 있다. </p>
<h3 id="elasticsearch-설치-1">ElasticSearch 설치</h3>
<p>Repository를 등록했으니 이제 apt로 elasticsearch를 설치할 수 있다. 
아래의 명령어를 실행해 Elasticsearch를 설치한다.</p>
<pre><code>$ sudo apt update
$ sudo apt install elasticsearch</code></pre><h3 id="elasticsearch-실행">ElasticSearch 실행</h3>
<p>ElasticSearch가 설치가 완료되었으면 실행할 수 있다. 참고로 이 Guide를 따라서 ES를 설치하면 /etc/elasticsearch Directory로 설치가 되었을 것이다. </p>
<p>아래의 명령어로 ES를 실행한다.  </p>
<pre><code>$ sudo systemctl start elasticsearch.service</code></pre><p>참고로 부팅시 바로 실행하도록 설정할 수도 있다.</p>
<pre><code>$ sudo systemctl enable elasticsearch.service</code></pre><p>아래의 명령어로 ES instance의 상태를 확인할 수 있다. </p>
<pre><code>$ sudo systemctl status elasticsearch.service</code></pre><p><img src="https://images.velog.io/images/halim_limha/post/53c6c8f5-8f76-4f72-9d3f-9c2974929553/image.png" alt=""></p>
<p>Service 내리는것은 아래의 명령어</p>
<pre><code>$ sudo systemctl stop elasticsearch.service</code></pre><p><img src="https://images.velog.io/images/halim_limha/post/0d5cb860-9171-4cbe-8638-26bd6d10c60b/image.png" alt=""></p>
<h3 id="clustering-준비">Clustering 준비</h3>
<p>ElasticSearch는 이미 Local에 설치가 되었다고 가정, ElasticSearch Cluster를 구성하려면 ElasticSearch의 환경설정 파일인 <strong>elasticsearch.yml</strong> 파일에 수정이 필요하다.</p>
<p>Local에 설치했다면 elasticsearch.yml 파일은 아래의 Directory에서 찾을 수 있다.
/etc/elasticsearch</p>
<p>Cluster 설정을 따로 하지 않고 처음 yml 파일을 열면 path, data, path.logs 외에는 모두 주석으로 처리가 되어 있다.</p>
<p>주석을 지우고 바꿔야 할 항목은 아래와 같다.</p>
<p>●    <strong>cluster.name (String)</strong> : 구성하려는 Cluster의 이름이다. Node들을 같은 Cluster에 넣기 위해서는 같은 Cluster name을 써야지 인식할 수 있다.</p>
<p>●    <strong>node.name (String)</strong> : Node의 이름, 당연한 이야기이지만 같은 Cluster 내에 추가할 Node의 이름은 각기 다르게 설정해야 한다.</p>
<p>●    <strong>node.master (bool)</strong> :  true 혹은 false 값으로 이 ES instance를 master로 쓸지 아닌지 결정</p>
<p>●    <strong>node.data (bool)</strong> : true 혹은 false 값으로 이 ES instance를 data node로 쓸지 아닌지 결정</p>
<p>●    <strong>network.host (String)</strong> : Default로 ES는 Local에서만 접근이 가능하도록 설정이 되어 있다. 외부에서 접속 가능하도록 만들기 위해서는 network.host 값을 0.0.0.0으로 바꾸어주면 된다.</p>
<p>●    <strong>discovery.seed_hosts(String List)</strong> : ES node가 시작했을 때, Cluster에 구성되기 위해 찾아가는 Host의 주소의 List, IP주소를 직접 입력해도 되고, DNS 주소가 있다면 DNS를 입력해도 된다.</p>
<p>●    <strong>cluster.initial_master_nodes (String List)</strong> : Master Node가 될 수 있는 후보들의 List이다. </p>
<h3 id="clustering-test">Clustering Test</h3>
<h4 id="clustering-과정">Clustering 과정</h4>
<p>2개의 각기 다른 Server를 이용하여 Test를 진행하였다. 위의 Clustering 준비 부분에서 언급한 Parameter들에게 각각 값을 설정해주었다.</p>
<p>Server 1 yml 파일</p>
<table>
<thead>
<tr>
<th align="left">paramete</th>
<th align="center">value</th>
</tr>
</thead>
<tbody><tr>
<td align="left">cluster.name</td>
<td align="center">test-cluster</td>
</tr>
<tr>
<td align="left">node.name</td>
<td align="center">node-1</td>
</tr>
<tr>
<td align="left">network.host</td>
<td align="center">0.0.0.0</td>
</tr>
<tr>
<td align="left">node.master</td>
<td align="center">true</td>
</tr>
<tr>
<td align="left">node.data</td>
<td align="center">false</td>
</tr>
<tr>
<td align="left">discovery.seed_hosts</td>
<td align="center">[“host1”, “host2”, “Server 2의 주소(IP or DNS)&quot;]</td>
</tr>
<tr>
<td align="left">cluster.initial_master_nodes</td>
<td align="center">[“node-1”]</td>
</tr>
</tbody></table>
<p>Server 2 yml 파일</p>
<table>
<thead>
<tr>
<th align="left">paramete</th>
<th align="center">value</th>
</tr>
</thead>
<tbody><tr>
<td align="left">cluster.name</td>
<td align="center">test-cluster</td>
</tr>
<tr>
<td align="left">node.name</td>
<td align="center">node-2</td>
</tr>
<tr>
<td align="left">network.host</td>
<td align="center">0.0.0.0</td>
</tr>
<tr>
<td align="left">node.master</td>
<td align="center">false</td>
</tr>
<tr>
<td align="left">node.data</td>
<td align="center">true</td>
</tr>
<tr>
<td align="left">discovery.seed_hosts</td>
<td align="center">[“host1”, “host2”, “Server 1의 주소(IP or DNS)&quot;]</td>
</tr>
<tr>
<td align="left">cluster.initial_master_nodes</td>
<td align="center">[“node-1”]</td>
</tr>
</tbody></table>
<p>2개의 yml 파일에서 확인할 수 있듯이 cluster의 이름은 ‘test-cluster’로 통일하였고 server 1의 node 이름은 node-1, server 2의 node 이름은 node-2이다.</p>
<p>network.host는 ES Instance가 외부로도 노출될 수 있도록 0.0.0.0으로 설정하였다.</p>
<p>node-1을 Master로 만들 것이기 때문에 node-1의 node.master를 true, node.data를 false로 설정해주었고 node-2는 그 반대로 설정하였다.</p>
<p>Test에서는 server 1의 IP주소는 192.168.156.145이므로 server 2의 discovery.seed_hosts에 이 주소를 추가시켜주었고 server 1의 discovery.seed_hosts는 반대로 server 2의 IP주소인 192.168.156.146을 추가시켜주었다. (host2는 원래 적혀있는 것이라 그냥 두었는데 이 Test에서는 의미가 없다. DNS Resolution 안됨.)</p>
<p>마지막으로 Cluster를 형성했을 때 node-1이 master가 되도록 양쪽 yml 파일 모두 cluster.initial_master_nodes를 [“node-1”]으로 설정해주었다. </p>
<blockquote>
<p>참고: 모든 Node의 cluster.initial_master_nodes의 값은 같아야 한다.</p>
</blockquote>
<p>yml 파일을 모두 설정을 완료하고 server 1의 ES instance와 server 2의 ES instance를 실행하였음.</p>
<p>Cluster가 구성되었는지 확인하는 방법은 Host에 request를 보내면 된다. Test에서는 Curl을 이용하였다.</p>
<p>먼저 server 2에 있는 node-2에 아래와 같은 Request를 보내보았다.</p>
<pre><code>$ curl http://192.168.156.145:9200/_cluster/health?pretty</code></pre><p>Node가 죽어 있다면 아래와 같은 메시지가 뜬다.</p>
<pre><code>curl: (7) Failed to connect to 192.168.156.145 port 9200: Connection refused</code></pre><p>Cluster 구성이 되었다면 아래와 같이 node의 개수가 2개인 것을 확인할 수 있을 것이다.
<img src="https://images.velog.io/images/halim_limha/post/18fc99bf-b849-4dd9-9fa2-4aeb1cfbdbcb/image.png" alt=""></p>
<p>실제로 Master Node가 어떤 것인지 확인하기 위해서는 아래의 명령어를 활용했다.</p>
<pre><code>$ curl http://192.168.156.146:9200/_cat/nodes?v</code></pre><p><img src="https://images.velog.io/images/halim_limha/post/1f2d3e4f-e956-4663-803d-a12ddbde191b/image.png" alt="">
192.168.156.145의 IP주소를 가진 node-1의 Host가 Master인 것을 확인할 수 있다.</p>
<h4 id="test-issue">Test Issue</h4>
<p>처음 Test를 진행했을 때는 모르고 cluster.initial_master_nodes의 값을 [“node-1”, “node-2”]로 설정해서 node-1, node-2 모두 Master Node가 될 수 있도록 하였다. </p>
<p>그렇게 하니 처음에는 Cluster가 잘 형성되었는데, 어느 순간 연결이 잠시 끊기면 원래의 Cluster로 다시 복구되지 않았다. </p>
<p>그래서 모니터링을 시도해서 변화를 지켜보았고, watch 명령어를 활용해 1초마다 Curl Request를 보내서 응답을 확인하였다.</p>
<pre><code>$ watch -n 1 curl http://192.168.156.145:9200/_cat/nodes?v</code></pre><p>Cluster가 잘 구성되어 있는 상태로 Master Node가 있는 Host에 Curl Request를 보내면 Cluster의 Node가 2개로 Clustering 된 것이 확인된다.</p>
<blockquote>
<p>참고: Data Node에 Cluster API를 이용한 Request를 보내도 Cluster가 잘 구성되었다면 Master Node로 Routing되기 때문에 Data Node에 Request를 보내도 된다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/halim_limha/post/96ccba1e-2108-4f64-93f5-2ad982518035/image.png" alt="">  </p>
<p>시간이 얼마 지나고 Cluster의 연결이 끊겨 Node가 분리되었고 각각의 Node가 각자의 Cluster에서 Master Node가 되었다. 정확히 얘기하면 떨어져 나간 node-2가 새로운 Cluster를 형성한 것이다.</p>
<p><img src="https://images.velog.io/images/halim_limha/post/cbb52703-3e52-4d8c-b4c8-2ce9a9688eab/image.png" alt=""></p>
<p>이유를 찾고자 ES의 Log 파일을 뒤져보았다. elasticsearch.yml 파일에 log 파일의 Path가 명시되어 있으니 명시된 Directory로 가면된다. (/var/log/elasticsearch/)</p>
<p>필요한건 Cluster에 관련된 Log이기 때문에 처음에 cluster 이름으로 정했던 test-cluster.log 파일을 뒤져보았다.</p>
<p>아래의 Exception이 난 것을 확인했고..</p>
<pre><code>Caused by: org.elasticsearch.cluster.coordination.CoordinationStateRejectedException: join validation on cluster state with a different cluster uuid LcitTe7KQFCEF8Ww_MuU_g than local cluster uuid sTdyHyX8SFSN5Mr5XH1E0A</code></pre><p>이 Exception의 원인을 분석해보았는데 결과는 아래와 같다.</p>
<p>처음 Test에서는 node-1, node-2가 모두 Master Node가 될 수 있게 설정을 해놓았는데, Cluster 구성이 처음에는 잘 되었다가 연결이 어느 순간 끊기면 node-1, node-2가 각각의 Cluster를 새로 생성한다. 그런데 ES에서는 하나의 Cluster에 속해 있던 Datanode가 다른 Cluster로 편입되지 못하게 막아놓았고(Data Loss가 발생할 수 있기 때문에) 그렇기 때문에 각각의 서로 다른 Cluster를 형성해버린 node-1과 node-2가 다시 하나로 Clustering되지 못한 것이다. 이런 현상을 <strong>‘Split Brain’</strong>이라고 부르기도 하는 것 같다.</p>
<p>그래서 Test를 node-1이 Master node, node-2가 Data node가 되도록 다시 했다. 제대로 다시 Test하기 위해서는 한가지 작업이 더 필요했는데, 각 Node의 data가 저장된 Directory를 지워서 등록된 Cluster에 대한 정보를 없애는 것이다. </p>
<p>Node의 Data가 저장될 파일 Path는 elasticsearch.yml 파일에서 확인할 수 있다. (/var/lib/elasticsearch)</p>
<p>nodes라는 Directory가 있는 것을 확인할 수 있는데 지운다.</p>
<pre><code>$ sudo rm -rf nodes</code></pre><p>읽어주셔서 감사합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hadoop 메모]]></title>
            <link>https://velog.io/@halim_limha/Hadoop-%EB%A9%94%EB%AA%A8</link>
            <guid>https://velog.io/@halim_limha/Hadoop-%EB%A9%94%EB%AA%A8</guid>
            <pubDate>Sun, 18 Apr 2021 13:55:28 GMT</pubDate>
            <description><![CDATA[<h2 id="hadoop-ecosystem">Hadoop Ecosystem</h2>
<p><img src="https://images.velog.io/images/halim_limha/post/4fcb634f-8317-4f14-8410-88701fa9f7f7/image.png" alt="">
공부를 해보면서 Hadoop이라는 단어를 쓸 때 어떤 것을 지칭하는 지 확실히 해야 한다는 것을 느꼈음. 여러 문서를 읽어본 결과, 각각 문서에서 Hadoop이란 단어의 정확한 의미가 각자 다름. </p>
<p>예를들면..</p>
<p>Hadoop이란 단어를 </p>
<ol>
<li>Hadoop Ecosystem 전체를 지칭하는 경우</li>
<li>HDFS만을 지칭하는 경우</li>
<li>HDFS와 MapReduce 같은 분산처리 Framework만 포함하는 경우</li>
<li>그 외</li>
</ol>
<p>아무튼 Hadoop이란 용어를 쓸 때 헷갈리지 않게 하는 것이 좋을 것으로 보임. 아직 정리가 많이 되지 않아 많은 현재는 많은 내용을 올리지 않았지만 앞으로 Hadoop과 관련된 내용을 올릴 때는 헷갈리지 않게 Hadoop이라고 하면 1번의 의미로 사용하고 Hadoop Component를 지칭할 땐 그냥 그 Component의 이름을 사용할 것임. 예를 들면 HDFS을 지칭할 땐 그냥 HDFS라고 할 예정임.</p>
<h2 id="mapreduce">Map/Reduce</h2>
<p>위의 그림에서도 확인했겠지만 Map/Reduce는 Hadoop Ecosystem의 하나의 요소일 뿐 HDFS에 적용되어 있는 기술이 아님. </p>
<p>HDFS에 데이터가 어떻게 저장되는지 정확히 밝혀내지 못해 Map/Reduce가 어디서 사용되는지 헷갈렸는데, 지금까지 여러 문서를 본 결과를 적어보자면 </p>
<p><strong>HDFS</strong>은 말 그대로 Data를 저장하는 <strong>Distributed File System</strong>이며, <strong>Map/Reduce</strong>는 이러한 분산 환경에서의 처리를 수행하는 <strong>Framework</strong> 혹은 <strong>Programming Paradigm</strong>라고 할 수 있음. </p>
<p>그래서 잘보니까 ElasticSearch와 Hadoop의 연동을 돕는 ES-for-Hadoop이라는 Library도 ElasticSearch와 HDFS을 직접 연결하는 것이 아니라 HDFS 위에 있는 분산처리 Framework와 연동을 하는 것이었음. </p>
<p>ES-for-Hadoop Library는 Map/Reduce, Apache Spark, Pig, Hive, Storm과 같은 분산처리 Framework와의 연동을 지원함. </p>
<p>사실 이중에서 Map/Reduce는 실시간 데이터 분석 플랫폼을 만드는데는 적합하지 않다고 하는데, 이유는 일단 Map/Reduce를 코드로 구현하여 사용하는 것이 까다롭다고 함. 게다가 Map/Reduce는 Disk I/O 기반이라 느릴 수 밖에 없음.</p>
<p>그리고 ES-For-Hadoop의 사용사례를 검색해봐도 Map/Reduce를 사용한 사례는 거의 보지 못했고 Apache Spark가 가장 많이 보였음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Lucene File Format 메모]]></title>
            <link>https://velog.io/@halim_limha/Lucene-File-Format-%EB%A9%94%EB%AA%A8</link>
            <guid>https://velog.io/@halim_limha/Lucene-File-Format-%EB%A9%94%EB%AA%A8</guid>
            <pubDate>Sat, 17 Apr 2021 01:21:59 GMT</pubDate>
            <description><![CDATA[<h2 id="file-format-요약-다이어그램">File Format 요약 다이어그램</h2>
<p><img src="https://images.velog.io/images/halim_limha/post/b8be3f49-4cb2-4474-af9a-0748e97a8b5f/image.png" alt=""></p>
<p>Lucene index는 여러개의 Segment로 이루어져있다고 하는데 실제로 File을 들여다보면 위와 같이 정리할 수 있음. (_X.<ext>에서 X는 Segment의 이름이다.)</p>
<p> 왼쪽부터 시작하면 Lucene index는 실제로 여러 개의 Segment를 가질 수 있음. 그리고 Segment 외에도 존재하는 Segment들을 Referencing하기 위한 Segments_<N> file, Segments.gen file 그리고 여러개의 IndexWriter(Lucene 내에서 Index를 생성해주는 Class)가 Segment를 쓰는 것을 방지하기 위한 write.lock file이 있음.</p>
<p> Segment로 넘어가면, Segment는 실제로 하나의 file이 아니고 여러 개의 file로 이루어져있음. 그런데 Lucene에서는 Segment를 이루는 file들을 저장할 때 2가지 옵션을 제공하는데 하나는 Multifile 방식, 다른 하나는 Compound 방식임. </p>
<p> 참고로 두가지 방식 모두가 갖는 File들도 존재함
 <img src="https://images.velog.io/images/halim_limha/post/5a0202bd-2533-4339-b6af-f01aaa74322d/image.png" alt="">
_X.si file의 확장자 si는 Segment information을 나타내며, Segment의 Metadata()를 저장하고 있음. <em>X</em>D.del file은 Segment에서 삭제된 Document들에 대한 정보를 저장함. </p>
<h2 id="multifile-방식">Multifile 방식</h2>
<p> 다시 넘어가면, Multifile 방식은 이름 그대로 Segment를 저장할 때 여러개의 file을 쓰는 방식임. Index에는 Term vector, inverted index, stored field 등에 대한 정보가 들어가야 하는데, 이를 각 항목별로 file을 만들어 Segment를 저장하는 것임.</p>
<p> 아래는 Multifile 방식을 사용할 때, 생성되는 File들의 목록임. 원리를 참고한 책은 Lucene 3.x 버전인데 2021년 최신 버전은 8.x 버전이라서 기본적인 원리는 책을 참고했고 Release note를 확인해서 바뀐 것이나 추가된 것도 명시하였음. </p>
<table>
<thead>
<tr>
<th align="center">파일명</th>
<th align="center">설명</th>
<th align="center">관련항목</th>
</tr>
</thead>
<tbody><tr>
<td align="center">_X.fdt</td>
<td align="center">Stored Field 의 값들을 저장</td>
<td align="center">Stored Fields</td>
</tr>
<tr>
<td align="center">_X.fdx</td>
<td align="center">fdt에 있는 Stored Field를 가리키는 Pointer를 저장(Document Number를 통해 fdt 내의 정확한 위치를 알아낼 수 있음)</td>
<td align="center">Stored Fields</td>
</tr>
<tr>
<td align="center">_X.fnm</td>
<td align="center">Segment 내의 모든 Document에서 사용되는 모든 Field에 대한 정보를 가짐. Field Name뿐 아니라 Field Option(ex. term vector 사용여부)에 대한 정보도 포함.</td>
<td align="center">inverted index</td>
</tr>
<tr>
<td align="center">_X.frq -&gt; _X.doc</td>
<td align="center">Segment의 각 Term과 그것의 Frequency를 포함하고 있는 Document의 List 정보를 저장</td>
<td align="center">inverted index</td>
</tr>
<tr>
<td align="center">_X.nrm -&gt; _X.nvd, _X.nvm</td>
<td align="center">Normalization factor 정보 저장</td>
<td align="center">Norms</td>
</tr>
<tr>
<td align="center">_X.prx -&gt; _X.pos</td>
<td align="center">Term의 Index 내에서의 위치정보를 저장</td>
<td align="center">inverted index</td>
</tr>
<tr>
<td align="center">_X.tii -&gt; _X.tip</td>
<td align="center">Term Dictionary의 Index, 메모리(heap)에 저장되기 때문에 Segment가 File 형태로 Disk에 저장되어도 Memory 소비가 있는 이유 중 하나임 → 최신 버전은 Memory에 저장하는 대신 Disk와 File System Cache를 사용하여 Memory 사용량을 줄였음.</td>
<td align="center">Term Dictionary</td>
</tr>
<tr>
<td align="center">_X.tis -&gt; _X.tim</td>
<td align="center">Segment 내의 모든 Term(field name과 그 값 형을 포함한 하나의 행 형태로 나타냄)이 저장됨. 게다가 각 Term 마다 그 Term이 등장하는 Document의 개수도 저장함.</td>
<td align="center">Term Dictionary</td>
</tr>
<tr>
<td align="center">_X.tvd</td>
<td align="center">Term Vector Data 저장</td>
<td align="center">Term Vector</td>
</tr>
</tbody></table>
<blockquote>
<p>  <strong>참고</strong>
  <strong>Stored field</strong>는 Document를 indexing할 때 segment내에 저장된 field와 그에 따른 value. ES나 Solr 모두 Lucene을 사용하기 때문에 ES나 Solr을 이용해서 Indexing하고 Indexing된 Document를 다시 받아올 때 원본 Document를 받아오는 것이 아니라 Segment 내의 Stored field를 받아오는 것임. 물론 url 정보를 stored field로 하여 url을 받아온 뒤에 원본에 접근할 수도 있음.</p>
</blockquote>
<p>  <img src="https://images.velog.io/images/halim_limha/post/4d3539b7-e36d-47ca-a7ee-bd5d17607e5a/image.png" alt="">
   위의 그림은 Lucene in action이라는 책에서 가져온 이미지, .frq, .prx file은 현재 버전에서는 각각 .doc, .pos file로 변경되었음.  </p>
<h2 id="compound-방식">Compound 방식</h2>
<p> Multifile 방식은 Segment를 생성할 때, File을 너무 많이 생성한다는 단점이 있음. File의 수가 많아지면 그만큼 더 많은 Open File을 관리해야하는데 현대 OS에서는 Open File의 개수가 제한되어 있음. 그렇기 때문에 Multifile 방식을 계속 사용하다보면 갑자기 “Too many open files” IOException이 뜰 수가 있음. (특히 Segment Merging시에 하나의 Segment만 쓰는 것이 아니라 여러 개의 Segment를 사용하여 File Descriptor를 더 많이 소비함)</p>
<p> 그래서 Multifile 방식은 잘쓰지 않고 Default로 설정된 방식 역시 Compound 방식임. Compound 방식은 위의 표에서 나타나 있는 _X.fnm, _X.fdx.. 등등의 파일을 모두 _X.cfs, _X.cfe 파일에 저장함. </p>
]]></description>
        </item>
    </channel>
</rss>