<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Eraser's ProjectLog</title>
    <link>https://projectlog-eraser.tistory.com/</link>
    <description>#즐겁게 #꾸준히 #시나브로</description>
    <language>ko</language>
    <pubDate>Tue, 2 Jun 2026 22:12:35 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>eraser</managingEditor>
    <image>
      <title>Eraser's ProjectLog</title>
      <url>https://tistory1.daumcdn.net/tistory/3820137/attach/b4bfb72f4ced4a069881ea27f05c4e76</url>
      <link>https://projectlog-eraser.tistory.com</link>
    </image>
    <item>
      <title>[3] Nginx를 이용한 이미지 서버 ⎻ Nginx 기본 설정 파일 작성</title>
      <link>https://projectlog-eraser.tistory.com/entry/3-Nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%84%9C%EB%B2%84-%E2%8E%BB-Nginx-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC-%EC%9E%91%EC%84%B1</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;업로드, 다운로드, 삭제 기능 등을 구현하기 전에 이미지 서버의 웹 서버인 Nginx의 설정을 해 준다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# Nginx 설정 파일 디렉토리 확인&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;/etc/nginx 디렉토리에서 Nginx 설정을 위해 사용할 디렉토리 및 폴더는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;/etc/nginx# ls -al
total 88
drwxr-xr-x 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;4096 Apr 21 17:22 .
drwxr-xr-x 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;4096 Apr 26 19:06 ..
drwxr-xr-x 1 nginx nginx 4096 Apr 26 19:06 conf.d # include될 config 파일들
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;1077 Apr 21 17:22 fastcgi.conf 
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;1077 Apr 21 17:22 fastcgi.conf.default
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;1007 Apr 21 17:22 fastcgi_params # 업로드, 다중 업로드, 삭제 fastcgi
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;1007 Apr 21 17:22 fastcgi_params.default
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;2837 Apr 21 17:22 koi-utf
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;2223 Apr 21 17:22 koi-win
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;5231 Apr 21 17:22 mime.types # 다운로드 시 필요한 미디어 타입
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;5231 Apr 21 17:22 mime.types.default
-rw-rw-r-- 1&amp;nbsp;&amp;nbsp;1000&amp;nbsp;&amp;nbsp;1000&amp;nbsp;&amp;nbsp;547 Apr 26 19:55 nginx.conf
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;2656 Apr 21 17:22 nginx.conf.default
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp; 636 Apr 21 17:22 scgi_params
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp; 636 Apr 21 17:22 scgi_params.default
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp; 664 Apr 21 17:22 uwsgi_params
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp; 664 Apr 21 17:22 uwsgi_params.default
-rw-r--r-- 1 root&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;3610 Apr 21 17:22 win-utf&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## fastcgi.conf&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #555555; font-family: 'Noto Sans Light';&quot;&gt; 설정 파일 중 fastcgi.conf 파일을 볼 수 있는데, &lt;b&gt;Nginx에서 fastcgi 모듈을 사용할 때 필요한 FastCGI 관련 설정&lt;/b&gt;이 모여 있다. fastcgi_param은 Nginx ngx_http_fastcgi_module이 FastCGI 규약에 따라 FastCGI 프로세스(실제 구현 상에서는 fcgiwrap 프로그램)에 전달해야 할 변수를 나타내는 지시자이다. 이후 fcgiwrap 프로세스에서 이 변수 값들에 접근할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;fastcgi_param&amp;nbsp;&amp;nbsp;QUERY_STRING&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $query_string;
fastcgi_param&amp;nbsp;&amp;nbsp;REQUEST_METHOD&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $request_method;
fastcgi_param&amp;nbsp;&amp;nbsp;CONTENT_TYPE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $content_type;
fastcgi_param&amp;nbsp;&amp;nbsp;CONTENT_LENGTH&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $content_length;

fastcgi_param&amp;nbsp;&amp;nbsp;SCRIPT_NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$fastcgi_script_name;
fastcgi_param&amp;nbsp;&amp;nbsp;REQUEST_URI&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$request_uri;
fastcgi_param&amp;nbsp;&amp;nbsp;DOCUMENT_URI&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $document_uri;
fastcgi_param&amp;nbsp;&amp;nbsp;DOCUMENT_ROOT&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$document_root;
fastcgi_param&amp;nbsp;&amp;nbsp;SERVER_PROTOCOL&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$server_protocol;
fastcgi_param&amp;nbsp;&amp;nbsp;REQUEST_SCHEME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $scheme;
fastcgi_param&amp;nbsp;&amp;nbsp;HTTPS&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$https if_not_empty;

fastcgi_param&amp;nbsp;&amp;nbsp;GATEWAY_INTERFACE&amp;nbsp;&amp;nbsp;CGI/1.1;
fastcgi_param&amp;nbsp;&amp;nbsp;SERVER_SOFTWARE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;nginx/$nginx_version;

fastcgi_param&amp;nbsp;&amp;nbsp;REMOTE_ADDR&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$remote_addr;
fastcgi_param&amp;nbsp;&amp;nbsp;REMOTE_PORT&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$remote_port;
fastcgi_param&amp;nbsp;&amp;nbsp;SERVER_ADDR&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$server_addr;
fastcgi_param&amp;nbsp;&amp;nbsp;SERVER_PORT&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$server_port;
fastcgi_param&amp;nbsp;&amp;nbsp;SERVER_NAME&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param&amp;nbsp;&amp;nbsp;REDIRECT_STATUS&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;200;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;## mime.types&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;확장자별 미디어 타입을 규정하는 mime.types 파일도 볼 수 있다. 업로드 및 다운로드 시 확장자를 보고 미디어 타입을 추론할 때 사용할 것이다. 특히, 다운로드 시 응답 헤더의 Content-Type이 확장자에 맞게 설정된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;types {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(...)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image/png&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;png;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image/svg+xml&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;svg svgz;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image/tiff&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; tif tiff;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image/vnd.wap.wbmp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; wbmp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image/webp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; webp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image/x-icon&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ico;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image/x-jng&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jng;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image/x-ms-bmp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; bmp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(...)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;# Nginx.conf&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Nginx 웹 서버 구동 시 기본적으로 필요한 설정 항목들을 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;worker_processes: Nginx master process가 관리할 worker process의 수를 설정한다. auto로 설정하면 자동으로 할당된다. 보통 Nginx 웹 서버 성능 튜닝에 관한 글을 보면, CPU core 수의 80~90% 정도로 설정하는 것이 좋다고 한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;error_log: nginx 웹 서버 error log 레벨을 설정한다. 개발 시 편의를 위해 debug 레벨로 설정해 뒀는데, 로그 출력량이 info 레벨 수준과 비교가 안 되게 많아진다(;;). 성능 튜닝 시 로그 레벨도 중요한 포인트가 될 듯하다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;worker_connections: 이벤트 처리 시 최대 동시 접속 수라고 하는데, 기본값으로 뒀다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;http: http 어플리케이션 특정 도메인이나 IP 주소로의 요청을 처리할 블록으로, 여기서 다른 설정 값들을 include한다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;mime.types: 업로드, 다운로드 시 파일 미디어 타입 처리를 위한 값들이 미리 규정되어 있는데, 이를 포함한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;http.conf: http 어플리케이션 설정 파일을 포함하도록 설정한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;user&amp;nbsp;&amp;nbsp;nginx; # nginx user 설정
worker_processes auto; # nginx worker process 수 설정

error_log /var/log/nginx/error.log debug; # nginx 에러 로그 설정

events {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;worker_connections&amp;nbsp;&amp;nbsp;1024; # nginx worker process 1개 당 최대 동시 접속 수
}

http {&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 미디어 타입 설정
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include mime.types;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 하위 설정
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include conf.d/http.conf;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;## http.conf&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;실제 요청 처리에 필요한 설정을 작성한다. 이후 개발을 통해 API 엔드 포인트 별로 설정되어야 할 항목들을 빈 칸으로 남겨 둔다. 다만, 실제 개발 시 빈 칸으로 남겨 두고 파일을 실행하면 오류가 날 수 있으므로, listen 블록 외에 채워지지 않은 설정 항목은 주석 처리하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;listen: 이미지 서버는 ipv4, ipv6 8888 포트로 오는 요청을 대기한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;location: 요청을 처리할 서버 블록&lt;br /&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;~ \.py$: .py로 끝나는 엔드포인트에서는 이미지 서버 동적 요청 처리를 위한 python script를 실행한다. python script 실행을 위한 fastcgi.conf 항목을 포함해 준다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;/images/upload: 이미지 단건 업로드록를 처리할 서버 블록이다. upload module을 이용해 파일을 디스크에 저장한 뒤, 이미지가 저장될 디렉토리의 계층화, 이미지 id 발급 등을 위한 스크립트를 호출할 것이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;/images/upload_many: 이미지 다중 업로드를 처리할 서버 블록이다. upload module을 이용해 파일을 디스크에 저장한 뒤, 각각의 이미지가 저장될 디렉토리의 계층화, 이미지 id 발급 등을 위한 스크립트를 호출할 것이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;/images/download/: 이미지 다운로드를 처리할 서버 블록이다. /images/{image_id} 엔드 포인트로 image_id를 path variable로 하여 요청을 보내면, path variable을 디렉토리 계층별로 분리하고, 파일시스템의 해당 디렉토리에서 이미지 파일을 찾아 반환할 것이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;/images/delete/: 이미지 삭제를 처리할 서버 블록이다. /images/delete/{image_id} 엔드 포인트로 image_id를 path variable로 하여 요청을 보내면, path variable을 디렉토리 계층별로 분리하고, 파일 시스템의 해당 디렉토리에서 이미지 파일을 찾아 삭제하는 스크립트를 호출할 것이다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;server {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen 8888;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen [::]:8888;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# CGI python scripts
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location ~ \.py$ {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include conf.d/fastcgi.conf;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 이미지 단건 업로드 엔드포인트
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location /images/upload {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 단건 업로드 처리를 위한 설정 항목
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 이미지 다중 업로드
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location /images/upload_many {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # 다중 업로드 처리를 위한 설정 항목 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 이미지 다운로드
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location /images/download/ {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 	# 다운로드 처리를 위한 설정 항목
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 이미지 삭제
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location /images/delete {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 삭제 처리를 위한 설정 항목
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Image Server</category>
      <category>CGi</category>
      <category>fastcgi</category>
      <category>fcgiwrap</category>
      <category>NGINX</category>
      <category>nginx-upload-module</category>
      <category>nginx.conf</category>
      <category>정적파일</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/72</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/3-Nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%84%9C%EB%B2%84-%E2%8E%BB-Nginx-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC-%EC%9E%91%EC%84%B1#entry72comment</comments>
      <pubDate>Mon, 6 Jun 2022 18:45:38 +0900</pubDate>
    </item>
    <item>
      <title>[2] Nginx를 이용한 이미지 서버 ⎻ Nginx(w/ upload module), fcgiwrap 설치</title>
      <link>https://projectlog-eraser.tistory.com/entry/2-Nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%84%9C%EB%B2%84-%E2%8E%BB-Nginx-fcgiwrap-%EC%84%A4%EC%B9%98</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;개발을 위해 이미지 서버 구축에 필요한 스택을 설치한다. 개발 환경은 Ubuntu 20.04.이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# Nginx 설치&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Nginx 소스 코드로부터&amp;nbsp;nginx-upload-module을 함께 컴파일해서 설치한다. Nginx는 C로 개발되어 있으므로, gcc 등 C 컴파일 관련 라이브러리가 설치되어 있어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Nginx 의존 라이브러리 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Nginx를 실행하기 위해서는 기본적으로 pcre, openssl, zlib 등의 라이브러리가 필요하다. 패키지 매니저를 통해 설치해도 되지만(추후 Dockerfile 작성 시에는 이 방법을 사용하였다), 애초에 컴파일해서 설치하는 김에 의존 라이브러리도 컴파일하는 방식으로 설치해보기로 하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Nginx 설치 시 빌드 경로 설정을 쉽게 하기 위하여, /usr/src/temp 경로 밑에서 진행했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. &lt;a href=&quot;https://www.pcre.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pcre&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;perl 정규식 관련 라이브러리라고 한다. Nginx에서 정규식이 필요한 부분(url 관련 설정?)에서 사용하는 듯하다. 소스코드를 다운받고 실행 파일을 만들어 설치하면 되는데,&amp;nbsp;&lt;a href=&quot;https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/?_ga=2.128322953.426025564.1654501211-238844794.1654501211#compiling-and-installing-from-source&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nginx 공식 문서&lt;/a&gt;에 나와 있는 것과는 pcre 소스코드 위치가 달라진 듯하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654501509466&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd /usr/src/temp
$ wget https://sourceforge.net/projects/pcre/files/pcre/pcre-8.44.tar.gz
$ tar -zxf pcre-8.44.tar.gz
$ cd pcre-8.44
$ ./configure
$ make
$ sudo make install&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. &lt;a href=&quot;https://z-lib.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;zlib&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;C로 작성된 데이터 압축 관련 라이브러리라고 한다. Nginx에 gzip 등 컨텐츠 압축 설정을 할 수 있다고 하는데, 이 때 사용하는 듯하다. Nginx 공식 문서를 따라 진행하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654501736940&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd /usr/src/temp
$ wget http://zlib.net/zlib-1.2.11.tar.gz
$ tar -zxf zlib-1.2.11.tar.gz
$ cd zlib-1.2.11/
$ ./configure
$ make
$ sudo make install&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. &lt;a href=&quot;https://www.openssl.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;openssl&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;TLS, SSL 등 네트워크 데이터 통신에 쓰이는 프로토콜의 오픈소스 구현 라이브러리라고 한다. Nginx에서 HTTPS 프로토콜을 사용하기 위해 필요하다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654503220898&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd /usr/src/temp
$ wget http://www.openssl.org/source/openssl-1.1.1g.tar.gz
$ tar -zxf openssl-1.1.1g.tar.gz
$ cd openssl-1.1.1.g
$ ./Configure linux-x86_64 --prefix=/usr # /usr 디렉토리 밑에 openssl 헤더 파일 위치
$ make
$ sudo make install&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## upload module 소스 코드 다운&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Nginx-upload-module 소스 코드를 받은 후, 압축 해제한다. 아래 명령어를 따를 경우, 압축 파일은 2.3.0.tar.gz라는 이름으로 다운로드되고, 해당 파일을 tar 명령어를 통해 압축 해제하면 nginx-upload-module-2.3.0 이름의 폴더가 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1654502337794&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd /usr/src/temp
$ wget ttps://github.com/vkholodkov/nginx-upload-module/archive/2.3.0.tar.gz
$ tar -xzvf ${pwd}/2.3.0.tar.gz # 압축 파일은 버전명만으로 다운로드됨&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Nginx 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Nginx 1.20.2. 버전의 소스 코드를 다운로드한 후, 의존 라이브러리 및 upload module의 경로와 함께 빌드 옵션을 설정한다. 의존 라이브러리 및 upload module 경로가 핵심이지만, 다른 빌드 옵션을 설정할 수도 있다(&lt;a href=&quot;https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/?_ga=2.128322953.426025564.1654501211-238844794.1654501211#configuring-nginx-paths&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nginx path 관련 설정&lt;/a&gt;, gcc 컴파일러 옵션 등을 모두 설정할 수 있다).&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;아래 설정을 따르면, Nginx를 pcre, zlib, ssl, upload module과 함께 설치하게 되며, Nginx 에러 로그 출력 시 debug 옵션을 설정할 수 있게 된다. 또한, Nginx 프로그램 소유자 및 소유 그룹이 모두 nginx로 설정된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654503055251&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd /usr/src/temp
$ wget https://nginx.org/download/nginx-1.20.2.tar.gz
$ tar -zxf nginx-1.20.2.tar.gz
$ cd nginx-1.20.2
$ .configure \
--with-pcre=../pcre-8.44 \
--with-zlib=../zlib-1.2.11 \
--with-openssl=../openssl-1.1.1g \
--add-module=../nginx-upload-module-2.3.0 \ # upload module 추가
--with-debug
--user=nginx
--group=nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;빌드 옵션 설정까지 완료되면, 설치하면 된다. 설치 과정 중간에 addon으로 upload 모듈이 들어가는 것이 보이면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654503717839&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ make
$ make install

&amp;gt;&amp;gt; (...)
&amp;gt;&amp;gt; objs/src/http/modules/ngx_http_upstream_random_module.o \
&amp;gt;&amp;gt; objs/src/http/modules/ngx_http_upstream_keepalive_module.o \
&amp;gt;&amp;gt; objs/src/http/modules/ngx_http_upstream_zone_module.o \
&amp;gt;&amp;gt; .objs/addon/nginx-upload-module-2.3.0/ngx_http_upload_module.o \
&amp;gt;&amp;gt; objs/ngx_modules.o \
&amp;gt;&amp;gt; -ldl -lpthread -lcrypt ../tmp/pcre-8.44/.libs/libpcre.a -lssl -lcrypto -ldl -lpthread ../tmp/zlib-1.2.11/libz.a \
&amp;gt;&amp;gt; (...)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;설치 후 nginx 명령어를 통해 Nginx를 실행한다. 오류 없이 실행되고, 브라우저에서 http://localhost로 접속했을 때 다음과 같은 화면이 나오면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1654506479181&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eWj5TL/btrD2InmVj3/e5CtpNTdU7khMAr18Jk471/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eWj5TL/btrD2InmVj3/e5CtpNTdU7khMAr18Jk471/img.png&quot; data-alt=&quot;ㅎㅇㅎㅇ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eWj5TL/btrD2InmVj3/e5CtpNTdU7khMAr18Jk471/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeWj5TL%2FbtrD2InmVj3%2Fe5CtpNTdU7khMAr18Jk471%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;204&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ㅎㅇㅎㅇ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## nginx 설정 파일 위치&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;설치 후 /etc/nginx 디렉토리에 가면 nginx 설정 관련 파일들이 모여 있는 것을 확인할 수 있다. 앞으로 이미지 서버를 개발하며 필요한 nginx 관련 설정 파일들은 이 아래에 작성하게 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;nginx.conf: nginx 기본 설정 파일. 로그, nginx 프로세스 관련 설정 등을 담게 될 것&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;conf.d/: include할 nginx config 파일 모음. 소스 코드(&lt;a href=&quot;https://projectlog-eraser.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://projectlog-eraser.tistory.com/65&lt;/a&gt;) 상 config/ 안에 들어갈 설정 파일들이 conf.d/ 아래 위치하게 된다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1654503935397&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/etc/nginx# ls -al
total 88
drwxr-xr-x 1 root  root  4096 Apr 21 17:22 .
drwxr-xr-x 1 root  root  4096 Apr 26 19:06 ..
drwxr-xr-x 1 nginx nginx 4096 Apr 26 19:06 conf.d
-rw-r--r-- 1 root  root  1077 Apr 21 17:22 fastcgi.conf
-rw-r--r-- 1 root  root  1077 Apr 21 17:22 fastcgi.conf.default
-rw-r--r-- 1 root  root  1007 Apr 21 17:22 fastcgi_params
-rw-r--r-- 1 root  root  1007 Apr 21 17:22 fastcgi_params.default
-rw-r--r-- 1 root  root  2837 Apr 21 17:22 koi-utf
-rw-r--r-- 1 root  root  2223 Apr 21 17:22 koi-win
-rw-r--r-- 1 root  root  5231 Apr 21 17:22 mime.types
-rw-r--r-- 1 root  root  5231 Apr 21 17:22 mime.types.default
-rw-rw-r-- 1  1000  1000  547 Apr 26 19:55 nginx.conf
-rw-r--r-- 1 root  root  2656 Apr 21 17:22 nginx.conf.default
-rw-r--r-- 1 root  root   636 Apr 21 17:22 scgi_params
-rw-r--r-- 1 root  root   636 Apr 21 17:22 scgi_params.default
-rw-r--r-- 1 root  root   664 Apr 21 17:22 uwsgi_params
-rw-r--r-- 1 root  root   664 Apr 21 17:22 uwsgi_params.default
-rw-r--r-- 1 root  root  3610 Apr 21 17:22 win-utf&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# fcgiwrap 설치&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;fcgiwrap은 패키지 매니저를 통해 설치하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1654504828295&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ apt-get update
$ apt-get install fcgiwrap&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;설치하면 fcgiwrap 프로세스를 spawn할 수 있는 spawn-fcgi 프로그램이 같이 설치되는데, 이미지 서버에서 이 프로세스를 이용할 수 있도록 fcgiwrap 프로세스를 아래 명령어를 이용해 spawn한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1654504995618&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spawn-fcgi \
-s /var/run/fcgiwrap.socket \ # fcgiwrap unix domain socket
-a &quot;0.0.0.0&quot; \
-u 102 -g 102 -U 102 -G 102 \ # fcgiwrap user, group 설정
-M 766 /usr/sbin/fcgiwrap&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;fcgiwrap의 기본 user, group은 www-data인데, 나중에 nginx와 user, group이 달라 에러가 발생하는 것을 막기 위해(&lt;a href=&quot;https://sirzzang.github.io/dev/Dev-fcgiwrap-python/#%EA%B6%8C%ED%95%9C-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EC%A0%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sirzzang.github.io/dev/Dev-fcgiwrap-python/#%EA%B6%8C%ED%95%9C-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EC%A0%9C&lt;/a&gt;) nginx user id, nginx group id를 이용해 fcgiwrap의 user, group을 변경해 준다. nginx user, nginx group의 id가 무엇인지 /etc/passwd, /etc/group에서 확인한 후 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;기타 옵션은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://linux.die.net/man/1/spawn-fcgi&quot;&gt;spawn-fcgi documentation&lt;/a&gt;에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend/Image Server</category>
      <category>CGi</category>
      <category>fastcgi</category>
      <category>fcgiwrap</category>
      <category>NGINX</category>
      <category>nginx-upload-module</category>
      <category>정적파일</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/66</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/2-Nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%84%9C%EB%B2%84-%E2%8E%BB-Nginx-fcgiwrap-%EC%84%A4%EC%B9%98#entry66comment</comments>
      <pubDate>Mon, 6 Jun 2022 13:46:17 +0900</pubDate>
    </item>
    <item>
      <title>[1] Nginx를 이용한 이미지 서버 ⎻ 개요</title>
      <link>https://projectlog-eraser.tistory.com/entry/1-Nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%84%9C%EB%B2%84-%E2%8E%BB-%EA%B0%9C%EC%9A%94</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;회사에서 서비스에 필요한 이미지 파일의 업로드, 다운로드, 삭제 등의 기능을 담당하는 이미지 서버를 개발하는 프로젝트를 진행했다. 이미지 서빙을 WAS에서 겸할 수도 있지만, 정적 파일 서빙에 대한 것은 어플리케이션 기능과는 분리되는 것이 맞다고 보여 별도의 웹 서버를 두고 해당 웹 서버에서 이미지 업로드, 다운로드, 삭제 요청을 처리하기로 하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;웹 서버에서 파일 업로드, 삭제 등과 같은 동적 기능을 구현하기 위해&amp;nbsp;&lt;a href=&quot;https://sirzzang.github.io/cs/CS-webserver,was/#%EB%8F%99%EC%A0%81-%EC%9A%94%EC%B2%AD-%EC%B2%98%EB%A6%AC-cgi-was&quot;&gt;CGI 방식&lt;/a&gt;을 이용했다. &lt;i&gt;&lt;s&gt;(아마존 S3 따라잡기라는 원대한 꿈을 가지고 시작했으나(...) 수많은 시행착오를 겪고)&lt;/s&gt;&lt;/i&gt; 웹 서버를 이용해 정적 파일 업로드, 다운로드 등의 기능을 어떻게 구현할 수 있는지 고민했던 내용을 기록하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1654650889207&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - sirzzang/nginx-image-server: 정적 이미지 파일 업로드, 다운로드, 삭제 서비스&quot; data-og-description=&quot;정적 이미지 파일 업로드, 다운로드, 삭제 서비스. Contribute to sirzzang/nginx-image-server development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/sirzzang/nginx-image-server&quot; data-og-url=&quot;https://github.com/sirzzang/nginx-image-server&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/beIzbt/hyOG3sFbtM/584IFQh1otRG9KcIzvjc21/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/sirzzang/nginx-image-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/sirzzang/nginx-image-server&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/beIzbt/hyOG3sFbtM/584IFQh1otRG9KcIzvjc21/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - sirzzang/nginx-image-server: 정적 이미지 파일 업로드, 다운로드, 삭제 서비스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;정적 이미지 파일 업로드, 다운로드, 삭제 서비스. Contribute to sirzzang/nginx-image-server development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 개발 요구 사항&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;프로젝트에서 개발해야 하는 기능은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 업로드&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이미지 업로드 시,&amp;nbsp;이미지는 파일 시스템에 저장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파일 시스템 저장 시, 파일 검색 성능 보장을 위한 디렉토리 계층화 정책 설계 필요&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예컨대, 파일명을 2글자 정도씩 쪼개서 디렉토리를 짜는 설계 가능(예: 파일명이 ar213fdvxcwe.png인 경우, /ar/21/3fdvxcwe.png와 같이 저장할 수 있음)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;말단 디렉토리 하나당 들어가는 이미지 파일의 개수가 분산될 수 있도록 디렉토리 규칙 및 depth를 고려하여야 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;업로드 시, 이미지 고유 식별이 가능한 id(예: uuid, hash)를 발급해 응답으로 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다중 업로드 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 다운로드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이미지 id로 다운로드 요청 시, 파일 시스템에서 해당 이미지 파일 반환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. 삭제&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이미지 id로 삭제 요청 시, 파일 시스템에서 해당 이미지 파일 삭제&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;위의 개발 요구 사항 중, 이미지 id 발급, 이미지 파일 삭제 등이 웹 서버가 수행해야 하는 동적인 기능이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 기술 스택&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;프로젝트 진행을 위해 사용한 기술 스택은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Nginx 1.20.2: 웹 서버&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://github.com/gnosek/fcgiwrap&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;fcgiwrap&lt;/a&gt;: FastCGI 프로세스 wrapper&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;nginx-upload-module 2.3.0: Nginx에서 파일 업로드를 처리할 수 있도록 하는 모듈&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;Python 3.9: FastCGI 스크립트 작성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/42ft9/btrD5Tass5j/TY3TCPFVWeGUWjSkb9C4qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/42ft9/btrD5Tass5j/TY3TCPFVWeGUWjSkb9C4qK/img.png&quot; data-alt=&quot;각 스택을 이용해 구축될 이미지 서버 구조 개요&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/42ft9/btrD5Tass5j/TY3TCPFVWeGUWjSkb9C4qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F42ft9%2FbtrD5Tass5j%2FTY3TCPFVWeGUWjSkb9C4qK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;178&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;각 스택을 이용해 구축될 이미지 서버 구조 개요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Nginx&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;사용할 수 있는 웹 서버로 Nginx, Apache Httpd, WebtoB 등이 있다. 프로젝트 구현에 사용할 수 있는 기술 스택을 선정하면서, 예전에 웹 서버 및 CGI에 대해 공부했던 내용 중, &lt;b&gt;웹 서버에서 CGI로 동적인 서비스를 구현할 때 매번 새로운 프로세스를 fork하여 나타날 수 있는 성능 하락 문제&lt;/b&gt;를 복기했다. 그리고 각 스택이 해당 문제를 어떻게 해결하고 있는지 살펴 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;세 스택 중 앞의 두 스택은 CGI 방식을 개선해, 하나의 프로세스에서 동적인 요청을 처리하도록 하는 FastCGI를, 관련 모듈을 제공하는 방식으로 지원한다(Nginx의 경우, &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ngx_http_fastcgi_module&lt;/a&gt;을, Apache Httpd의 경우 &lt;a href=&quot;https://httpd.apache.org/mod_fcgid/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mod_fcgid&lt;/a&gt; 모듈이 제공된다).&amp;nbsp;WebtoB는 앞의 두 스택과는 달리, 웹 서버가 동적 기능을 하는 서비스를 구현할 수 있도록 &lt;a title=&quot;Tmax WebtoB WBAPI&quot; href=&quot;https://technet.tmaxsoft.com/upload/download/online/webtob/pver-20160622-000001/administrator/ch09.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WBAPI&lt;/a&gt;를 제공한다. 이 API는 WebtoB 서버 기동 시, WBAPI 서비스를 메모리에 로딩함으로써 서비스 호출시마다 매번 새로운 프로세스를 fork하지 않도록 설계되었다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;특히 WebtoB의 경우 CGI 방식의 문제점을 개선했고, wbSaveFile, wbPutFile 등 개발 요구 사항에 알맞은 기능이 WBAPI 함수로 이미 제공되고 있었다. 다만, &lt;b&gt;WBAPI가 C언어로 제공되고 있어&lt;/b&gt; C를 알지 못하는 상태에서 해당 스택을 선택하기 쉽지 않았다. 결국 Nginx와 Apache Httpd 둘 중 하나를 선택하기로 하였고, 그 중 더 최신 기술이고, 성능이 더 좋다고 알려진 Nginx를 이용해 웹 서버를 구축하기로 하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Fcgiwrap&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Nginx는 FastCGI 방식을 지원하기 위한 ngx_http_fastcgi_module을 기본적으로 제공한다. 한편, 이 모듈을 통해 &lt;b&gt;이미지 서버가 수행해야 할 동적인 기능을 처리하기 위한 FastCGI 프로세스&lt;/b&gt;가 필요하다. fcgiwrap이라는 프로그램이 이 역할을 담당한다. fcgiwrap은 fgciwrap.socket&lt;i&gt;(혹은 fcgiwrap.sock)&lt;/i&gt;이라는 유닉스 도메인 소켓을 통해 Nginx와 프로세스 간 통신을 하며 Nginx가 수행하지 못하는 이미지 id 발급, 이미지 파일 삭제 등의 기능을 한다. fcgiwrap이 동적인 요청을 처리하기 위해 필요한 스크립트는 Python으로 작성할 예정이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp; &lt;a href=&quot;https://help.ubuntu.com/community/FcgiWrap&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Ubuntu Community의 위키&lt;/a&gt;에 따르면, fcgiwrap은 FastCGI 규약에 따라 CGI 어플리케이션을 수행하는 간단한 프로세스 wrapper라고 나와 있으며, Nginx에 CGI support를 제공하기 위한 것이라고 한다. 비슷한 목적의 프로그램으로는 php로 스크립트를 작성할 때 사용하는 php-fpm이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## nginx-upload-module&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Nginx에서 파일 업로드를 처리할 수 있도록 도와 주는 3rd party module이다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;엄밀히 웹 서버는 정적인 기능만을 할 수 있기 때문에, 파일을 받아 메모리에 올려서 디스크에 저장하는 것 역시 웹 서버가 할 수 없는 동적인 기능이다. 이 기능을 구현할 수도 있지만&lt;i&gt;(예컨대, fcgiwrap 프로세스에서 클라이언트가 전송한 폼 데이터를 전부든, 일부든 메모리에 올려서 디스크에 저장할 수 있을 것이다)&lt;/i&gt;, 이미 Nginx, Apache Httpd 등의 웹 서버에서는 파일 업로드 기능을 구현해 놓은 모듈이 존재하기 때문에, 이를 사용하면&lt;b&gt; FastCGI 프로세스 단까지 넘어오지 않고서도 웹 서버 단에서 업로드를 처리할 수 있다&lt;/b&gt;. &lt;s&gt;또한, 직접 구현하는 것보다 성능이 낫지 않을까...&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;다만, Nginx에서 기본으로 제공하는 모듈이 아니기 때문에, Nginx를 소스 코드로부터 컴파일하고, 이 과정에 해당 모듈을 사용할 수 있게 해 주는 설정이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 소스 코드 구조&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;위와 같이 개요를 잡고 개발을 진행했으며, 이 때 대략적인 소스 코드 구조는 다음과 같이 구성했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1654499761018&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;└── (project root)
    ├── config/  : nginx 웹 서버 설정 관련 파일 모음
    ├── scripts/ : 이미지 서버 동적 요청 처리를 위한 스크립트 파일 모음
    ├── .env                
    ├── Dockerfile
    ├── docker-compose.yml
    ├── (...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Image Server</category>
      <category>CGi</category>
      <category>fastcgi</category>
      <category>fcgiwrap</category>
      <category>NGINX</category>
      <category>nginx-upload-module</category>
      <category>정적파일</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/65</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/1-Nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%84%9C%EB%B2%84-%E2%8E%BB-%EA%B0%9C%EC%9A%94#entry65comment</comments>
      <pubDate>Mon, 6 Jun 2022 13:43:54 +0900</pubDate>
    </item>
    <item>
      <title>[ELK] AI 모델링 시각화</title>
      <link>https://projectlog-eraser.tistory.com/entry/ELK-AI-%EB%AA%A8%EB%8D%B8%EB%A7%81-%EC%8B%9C%EA%B0%81%ED%99%94</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;AI 모델링에 사용한 데이터를 데이터 분석을 위한 Elastic stack(&lt;a href=&quot;https://sirzzang.github.io/dev/Dev-elk-stack-01/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sirzzang.github.io/dev/Dev-elk-stack-01/&lt;/a&gt;)을 이용해 관리하고 시각화할 수 있다. AI 모델링 서버(&lt;a href=&quot;https://projectlog-eraser.tistory.com/64&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://projectlog-eraser.tistory.com/64&lt;/a&gt;)에서 로깅을 통해 input, modeling(모델링 중간 과정에 발생하는 데이터) data를 남기고, ELK stack을 통해 이를 수집 및 가공해 적재하고, 시각화하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 구조&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;전체 구조도는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;백엔드_ELK_티스토리.png&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;931&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjAUBJ/btrv11GYeyL/eRGB74ZTus4WLs55nrsWw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjAUBJ/btrv11GYeyL/eRGB74ZTus4WLs55nrsWw0/img.png&quot; data-alt=&quot;AI modeling visualization을 위해 AI app server에서 발생한 로그 데이터를 활용한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjAUBJ/btrv11GYeyL/eRGB74ZTus4WLs55nrsWw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjAUBJ%2Fbtrv11GYeyL%2FeRGB74ZTus4WLs55nrsWw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;581&quot; data-filename=&quot;백엔드_ELK_티스토리.png&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;931&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AI modeling visualization을 위해 AI app server에서 발생한 로그 데이터를 활용한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;AI app server의 Model, Datastore에서 input, output data 및 모델링 과정을 나타내는 데이터(예컨대, 클러스터링 점수 등)를 로그로 남긴다. Elastic stack은 로그 데이터를 이용해 AI modeling 데이터를 관리 및 시각화한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Elastic stack을 이루는 각각의 스택이 담당하는 역할은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Filebeats: 실시간으로 발생하는 로그 데이터를 읽고, 로그 데이터에서 필요한 데이터를 추출해 Logstash로 전송&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Logstash: Filebeats에서 전송한 데이터를 가공하여 Elasticsearch로 전송&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Elasticsearch: 데이터 적재 및 검색&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Kibana: Elasticsearch에 적재된 데이터를 시각화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;이 때, 클라이언트는 Kibana를 통해 시각화된 모델링 결과를 대시보드로 확인할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 구현&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Model1에서 발생하는 로그 데이터의 형태는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2021-12-24 16:54:39,804 - root - Model1 - inference - INFO:['ST20000515', '20210909115502', 90.33, 70.22, 30.33, 'Good', 'Moderate', 'Bad']&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2021-12-24 16:54:39,804 - root - Model2 - _get_cluster_score - INFO:['ST20000509', '20210928092810', 23.73, 59.13, 0]&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Model2에서 발생하는 로그 데이터의 형태는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2021-12-24 16:54:39,804 - root - Model2 - _solution - INFO : {'type': 'optimizer', 'order': 1, 'id': 1, 'from': {'lat': '36.88563292330548', 'lon': '128.73457595862516'}, 'to': {'lat': '36.817064467622814', 'lon': '128.96344333457913'}, 'duration': '0:32:00'}&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2021-12-24 16:54:39,806 - root - Model2 - _solution - INFO : Optimizer Total Time(minute): 521&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2021-12-24 16:54:39,806 - root - Model3 - _solution - INFO : Ortools Total Distance(meter): 267104&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Filebeats에서 로그 데이터를 읽어 필요한 데이터를 추출하고, Lostash에서 자료형을 바꾸는 방식으로 가공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Filebeats.yml&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;기본적으로 입력(input), 처리(processor), 출력(output)과 관련된 설정 항목을 작성하면 된다. filebeats를 설치하면 자동으로 깔리는 예시 configuration 파일(&lt;/span&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-reference-yml.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-reference-yml.html&lt;/a&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;)을 참고하거나, Filebeats 공식 문서를 참고해 설정하면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;아래 설정은 예시일 뿐, 개발 요구사항에 맞추어 작성해야 한다. 예컨대, exclude_lines, include_lines와 같은 것은 로그 패턴을 보고 수집할 것과 수집하지 않을 것을 결정해야 한다. 또한, processor dissect 모듈 등을 활용하여 로그 데이터에서 필요한 정보를 추출하는데, 추출되는 데이터가 문자열 형태임에 유의한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647273984637&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# --------------------------------- filebeats 입력 ---------------------------------
filebeat.inputs:
- type: filestream # 실시간 로그 filestream
  enabled: true
  prospector.scanner.check_interval: 30s # scan 간격
  paths:
    - /path/to/log # glob-based path 설정도 가능
  
  # 수집하지 않을 라인
  exclude_lines: ['DEBUG']
  
  # 수집할 로그 라인
  include_lines: ['inference', '_solution', '_get_cluster_score']

# --------------------------------- filebeats 설정 파일 -------------------------------
filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml

  # 설정 변경시 reload 여부
  reload.enabled: true

# --------------------------------- filebeats output -------------------------------
# Kibana 설정
setup.kibana:
  host: &quot;localhost:5601&quot; # Kibana host

# 아래 주석 풀고 작성 시 Filebeats에서 바로 Elasticsearch로 전송
# output.elasticsearch:
#   # Array of hosts to connect to.
#   hosts: [&quot;localhost:9200&quot;]
  # Protocol - either `http` (default) or `https`.
  #protocol: &quot;https&quot;

  # Authentication credentials - either API key or username/password.
  #api_key: &quot;id:api_key&quot;
  #username: &quot;elastic&quot;
  #password: &quot;changeme&quot;

# logstash output
output.logstash:
  # The Logstash hosts
  hosts: [&quot;localhost:5044&quot;]

# --------------------------------- filebeats processor -------------------------------
processors:
  - drop_fields: # 제외할 필드. 로그 외에 자동으로 생성되는 필드들도 있으므로 확인 후 제외
      fields: [&quot;log&quot;, &quot;host&quot;, &quot;architecture&quot;, &quot;agent&quot;, &quot;input&quot;, &quot;ecs&quot;]
  - dissect: # 데이터 부분만 추출
      field: &quot;message&quot;
      tokenizer: '%{log_timestamp-&amp;gt;} %{+log_timestamp} - %{} - %{model} - %{method} - %{} :     %{data}'
      target_prefix: &quot;&quot;
  - dissect: # Model1 데이터 추출
      when:
        and:
          - equals:
              model: &quot;Model2&quot;
          - contains:
              message: &quot;{&quot;
      field: &quot;data&quot;
      tokenizer: &quot;%{}: '%{type}', %{}: %{order}, %{}: %{id}, %{}: %{}: '%{from.lat}', %{}: '%{from.lon}'}, %{}: %{}: '%{to.lat}', %{}: '%{to.lon}'}, %{}: '%{duration}'&quot;
      target_prefix: &quot;&quot;
      ignore_failure: &quot;true&quot;
  - dissect:
      when:
        and:
          - equals:
              model: &quot;Model2&quot;
          - contains:
              message: &quot;Distance&quot;
      field: &quot;data&quot;
      tokenizer: &quot;%{}: %{distance}&quot;
      target_prefix: &quot;&quot;
  - dissect:
      when:
        and:
          - equals:
              model: &quot;Model3&quot;
          - contains:
              message: &quot;Time&quot;
      field: &quot;data&quot;
      tokenizer: &quot;%{}: %{time}&quot;
      target_prefix: &quot;&quot;
  - dissect:
      when:
        and:
          - equals:
              model: &quot;Model1&quot;
          - equals:
              method: &quot;inference&quot;
      field: &quot;data&quot;
      tokenizer: &quot;['%{id}', '%{date}', %{score1}, %{score2}, %{score3}, '%{grade1}', '%{grade2}', '%{grade3}']&quot;
      target_prefix: &quot;&quot;
  - dissect:
      when:
        and:
          - equals:
              model: &quot;freshnessModel&quot;
          - equals:
              method: &quot;_get_cluster_score&quot;
      field: &quot;data&quot;
      tokenizer: &quot;['%{id}', '%{date}', %{x}, %{y}, %{cluster}]&quot;
      target_prefix: &quot;&quot;
  - drop_fields:
      fields: [&quot;data&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## logstash.conf&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;입력(input), 처리(filter), 출력(output)과 관련된 설정 항목을 작성하면 된다. 입력의 경우, beats 스택으로부터 tcp 소켓을 통해 5044 포트의 logstash로 전송된다. 앞서 filebeats.yml의 logstash output에서 설정한 부분이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647276949034&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;input {
	# filebeats input
    beats {
        port =&amp;gt; 5044
    }
}

filter {

    # Model1 데이터 가공
    if [model] == &quot;Model1&quot; {        
        # score 데이터 자료형 변환
        if [method] == &quot;inference&quot; {
            mutate {
                convert =&amp;gt; {
                    &quot;score1&quot; =&amp;gt; &quot;float&quot;
                    &quot;score2&quot; =&amp;gt; &quot;float&quot;
                    &quot;score3&quot; =&amp;gt; &quot;float&quot;
                }
            }
        }

        # x, y 좌표 데이터 자료형 변환
        if [method] == &quot;_get_cluster_score&quot; {
            mutate {
                convert =&amp;gt; {
                    &quot;x&quot; =&amp;gt; &quot;float&quot;
                    &quot;y&quot; =&amp;gt; &quot;float&quot;
                }
            }
        }
    }

    # Model2 데이터 가공
    if [model] == &quot;Model2&quot; {
        mutate {
        
        	# 자료형 변환
            convert =&amp;gt; {
                &quot;[from][lon]&quot; =&amp;gt; &quot;float&quot;
                &quot;[from][lat]&quot; =&amp;gt; &quot;float&quot;
                &quot;[to][lon]&quot; =&amp;gt; &quot;float&quot;
                &quot;[to][lat]&quot; =&amp;gt; &quot;float&quot;
                &quot;order&quot; =&amp;gt; &quot;integer&quot;
                &quot;id&quot; =&amp;gt; &quot;integer&quot;
                &quot;time&quot; =&amp;gt; &quot;float&quot;
                &quot;distance&quot; =&amp;gt; &quot;float&quot;
            }
        }
    }

    # 필요 없는 필드 제거
    mutate {
        remove_field =&amp;gt; [&quot;tags&quot;, &quot;@version&quot;]
    }
}

output {

    # Model1 elasticsearch 인덱스
    if [model] == &quot;Model1&quot; {
    	elasticsearch {
            hosts =&amp;gt; [&quot;http://localhost:9200&quot;]
            index =&amp;gt; &quot;model1&quot;
            ecs_compatibility =&amp;gt; disabled
        }
    }

    # Model2 elasticsearch 인덱스
    if [model] == &quot;Model2&quot; {
        elasticsearch {
            hosts =&amp;gt; [&quot;http://localhost:9200&quot;]
            index =&amp;gt; &quot;model2&quot;
            ecs_compatibility =&amp;gt; disabled
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## kibana.yml&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Kibana 설정은 앞에 비해서는 간단한 편이다. Kibana 웹 인터페이스를 통해서도 설정 파일의 항목을 쉽게 변경할 수 있다. Kibana 서버 포트와 쿼리를 전송할 Elasticsearch 호스트 설정에 주의한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647277279334&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Kibana 서버 포트
server.port: 5601

# Kibana 서버 호스트
server.host: &quot;kibana.server.host.address&quot;

# Enables you to specify a path to mount Kibana at if you are running behind a proxy.
# Use the `server.rewriteBasePath` setting to tell Kibana if it should remove the basePath
# from requests it receives, and to prevent a deprecation warning at startup.
# This setting cannot end in a slash.
#server.basePath: &quot;&quot;

# Specifies whether Kibana should rewrite requests that are prefixed with
# `server.basePath` or require that they are rewritten by your reverse proxy.
# This setting was effectively always `false` before Kibana 6.3 and will
# default to `true` starting in Kibana 7.0.
#server.rewriteBasePath: false

# Elasticsearch host
elasticsearch.hosts: [&quot;http://localhost:9200&quot;]

# kibana pid 저장
pid.file: /path/to/kibana/kibana-7.16.0-linux-x86_64/kibana.pid

# kibana 로그
logging.dest: /path/to/kibana/kibana-7.16.0-linux-x86_64/logs/kibana.log&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;참고: server.basepath, server.rewritebasepath 설정&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;여기서는 설정하지 않았지만, K8S 환경에 ELK stack을 적용했을 때(&lt;a href=&quot;https://projectlog-eraser.tistory.com/63&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://projectlog-eraser.tistory.com/63&lt;/a&gt;), ingress를 통해 회사 앱 서버 url을 지정했는데, 이 경우 server.basepath를 설정해 주어야 한다. 또한, 설정된 basepath가 제대로 적용될 수 있도록 server.rewritebasepath를 설정해 주어야 한다. &lt;a title=&quot;stack overflow 글 참고&quot; href=&quot;https://stackoverflow.com/questions/52628605/how-do-i-setup-ingress-for-kibana-in-kubernetes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 결론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;기존에 로그 관리 시스템에 적용해 본 것 외에도 데이터 분석에 Elastic stack을 적용할 수 있음을 배웠다. 특히 그것을 AI 모델링 서버와 연결해 진행할 수 있어 더 의미가 있는 경험이었다. 그 동안 Python 기반 matplotlib, seaborn 라이브러리 등을 이용해 시각화하는 것만 경험해 왔는데, ELK stack을 활용하면 일련의 파이프라인을 통해 데이터 수집부터 시각화할 수 있음을 깨달았다. 심지어 아래와 같이 시각화된 결과를 웹 대시보드 형태로도 제공할 수 있기에, 필요한 경우 ELK stack을 활용하는 것도 좋아 보인다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;823&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1PnuF/btrvY4qUQYF/JokdjLZiywKeZhpBpxj6A0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1PnuF/btrvY4qUQYF/JokdjLZiywKeZhpBpxj6A0/img.png&quot; data-alt=&quot;시각화에 활용된 데이터는 전부 다 Elasticsearch에 대한 쿼리 결과이다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1PnuF/btrvY4qUQYF/JokdjLZiywKeZhpBpxj6A0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1PnuF%2FbtrvY4qUQYF%2FJokdjLZiywKeZhpBpxj6A0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;257&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;823&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;시각화에 활용된 데이터는 전부 다 Elasticsearch에 대한 쿼리 결과이다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/AI App Server</category>
      <category>elasticsearch</category>
      <category>Elk</category>
      <category>Filebeats</category>
      <category>kibana</category>
      <category>logstash</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/62</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/ELK-AI-%EB%AA%A8%EB%8D%B8%EB%A7%81-%EC%8B%9C%EA%B0%81%ED%99%94#entry62comment</comments>
      <pubDate>Tue, 15 Mar 2022 02:20:08 +0900</pubDate>
    </item>
    <item>
      <title>[App Server] Flask로 AI 백엔드 서버 구축해 보기</title>
      <link>https://projectlog-eraser.tistory.com/entry/Tekton</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;지난 포스팅(&lt;a href=&quot;https://projectlog-eraser.tistory.com/m/57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://projectlog-eraser.tistory.com/m/57&lt;/a&gt;)에서 받은 피드백을 통해, 앱 서버의 구조를 변경해 개발을 진행하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 구조&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;백엔드_티스토리.png&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3v5jq/btrvVAkar7e/rABSwOv9Qz5UkKRKY9FPmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3v5jq/btrvVAkar7e/rABSwOv9Qz5UkKRKY9FPmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3v5jq/btrvVAkar7e/rABSwOv9Qz5UkKRKY9FPmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3v5jq%2FbtrvVAkar7e%2FrABSwOv9Qz5UkKRKY9FPmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;961&quot; height=&quot;401&quot; data-filename=&quot;백엔드_티스토리.png&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;백엔드에서 따로 모델을 돌리는 프로세스를 생성하지 않고, socket 통신도 하지 않는다. 따라서 불필요하게 모델을 돌리기 위한 프로세스를 새로 생성하지 않아도 되고, Flask app 프로세스와 모델 프로세스 간에 통신으로 인해 (혹시라도 발생할 수 있는) 서버 부하를 줄일 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;또한, 모델별로 설계를 진행했던 것과 달리, 새로운 설계에서는 컴포넌트별로 레이어를 나누었다. Controller, Service, Model이 그것이다. Controller는 클라이언트의 요청을 받아, Service로 전달한다. Service는 모델링에 필요한 데이터를 Model에 input으로 전달하고, Model에서 모델링 결과를 반환하도록 inference 메소드를 호출한다. 모델마다 필요한 데이터가 다르기 때문에, 데이터베이스와 연결해서 데이터를 가져와야 하는 경우 datastore를, 미리 저장된 캐시 데이터나 외부 API를 이용해야 하는 경우, 해당 메소드를 구현하도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;이러한 설계를 통해 백엔드의 각 컴포넌트가 수행해야 하는 역할을 계층 별로 명확히 분리할 수 있다. AI 모델 서비스는 총 3가지로, 각각의 엔드포인트 별로 Controller &amp;rarr; Service &amp;rarr; Model ( &amp;rarr; datastore / external API etc. ) &amp;rarr; Service &amp;rarr; Controller의 흐름을 거쳐 클라이언트의 요청을 처리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 구현&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;구현에 사용한 (주요) 라이브러리는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;flask: Flask app은 Controller 역할 담당&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;dependency-injector: 백엔드 주요 컴포넌트(Service, Model, Datastore 등) object를 생성하고 주입. 실행에 필요한 환경 설정 정보도 함께 로드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;pyodbc: Datastore의 Tibero DB 접근 담당&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;pandas&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Controller&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Controller 역할을 하는 Flask app이다. 실제 서비스가 되고 있는 코드이므로, 전체 구조를 보여줄 수 있는 예시만 첨부한다. 예시의 API 엔드 포인트 URI는 관례에 어긋난 것으로, 예시로서만 남긴다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647268607464&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from flask import Flask, request, jsonify
from model2 import APIError, NotExecutableError
from container import Container
import logging

# Flask 앱
app = Flask(__name__)

# service, datastore 등 object 주입
container = Container()
container.init_resources()

model1_service = container.model1_service()
model2_service = container.model2_service()
model3_service = container.model3_service()

model1_datastore = container.model1_datastore()
model1_model = container.model1_model()


# model1 엔드포인트
@app.route(&quot;/model1/fill&quot;, methods=['POST'])
def fill_model1_score():

    resp = {}
    
    # 클라이언트의 요청 query parameter 오지 않은 경우
    date = request.args.get('date')
    if not date:
        resp['status'] = 400
        resp['msg'] = '`date` field should be in request query'
        return jsonify(resp), 400
  	
    # model1 서비스 호출
    try:
        model1_service.fill_model1_data(date)
    except:
        # TODO: model1 에러 핸들링
        pass
    else:
        resp['status'] = 200
        resp['msg'] = f'model1 score fill task on date {date} OK'
    
    return jsonify(resp)

# model2 엔드포인트
@app.route(&quot;/model2&quot;, methods=['POST'])
def model2():

    resp = {}

    # 클라이언트의 요청 body 데이터가 json 형태가 아니거나 비어 있는 경우
    if not (request.is_json and request.json):
        logging.debug('request body data not json format or empty')
        resp['status'] = 400
        resp['msg'] = 'request body data should be json format'
        return jsonify(resp), 400

    # 클라이언트의 요청 body 데이터가 API 명세에 정의된 payload와 맞지 않는 경우
    try:
        input = request.json    
        model2_input_dao = model2DAO()
        model2_input_dao.depot = input['depot']
        model2_input_dao.start_time = input['startTime']
        model2_input_dao.maxtime = input['maxTime']
    except KeyError as ex:
        logging.debug(f'field {ex} missing in request body data')
        resp['status'] = 400
        resp['msg'] = f'request body data should have {ex} field' 
        return jsonify(resp), 400
    
    # model2 서비스 호출
    try:
        result = model2_service.get_model2_data(model2_input_dao)
    except APIError as ex:
        '''
            - 클라이언트의 요청에는 문제가 없으나,
            - 외부 서비스 API 호출 단계에서 APIError가 발생한 경우
        '''
        resp['status'] = 200
        resp['msg'] = str(ex)
    except NotExecutableError as ex:
        '''
            - 클라이언트의 요청에는 문제가 없으나,
            - 클라이언트의 요청 데이터로 모델에서 최적해를 찾아낼 수 없는 경우
        '''
        resp['status'] = 200
        resp['msg'] = str(ex)
    else:
        resp['status'] = 200
        resp['msg'] = 'OK'
        resp['data'] = result

    return jsonify(resp)

# reloader 옵션 해제
if __name__ == &quot;__main__&quot;:
    app.run(debug=True, host='0.0.0.0', port=4000, use_reloader=False)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;서비스 운영 중 로그를 확인하다 Flask reload와 관련된 문제를 알게 되었다. model1 서비스에서 똑같은 메소드가 2번씩 호출되고 있었던 것이다. Container를 통해 의존성을 주입했기 때문에 Model, Service 등 object가 2번 호출될 일이 없는데도 말이다. 서버에서 실행되고 있는 프로세스를 확인해 보니, app.py를 한 번 실행했음에도 해당 명령어로 실행한 프로세스가 2개임을 발견하게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Flask 공식 문서(https://flask.palletsprojects.com/en/1.1.x/api/?highlight=use_reloader) 및 Stackoverflow 글(&lt;a href=&quot;https://stackoverflow.com/questions/25504149/why-does-running-the-flask-dev-server-run-itself-twice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/25504149/why-does-running-the-flask-dev-server-run-itself-twice&lt;/a&gt;)을 통해 문제를 해결할 수 있었다. 내가 위에서 작성했던 것과 같이, 파이썬 스크립트에서 app.run()으로 Flask app을 실행할 시, flask 라이브러리에서 개발 서버를 띄우도록 설계되어 있는데, 이 경우 &lt;b&gt;개발 편의 목적으로 코드 변경 감지를 위해 child process를 하나 더 띄운다&lt;/b&gt;고 한다. 이 때문에 flask run 커맨드를 통해 Flask app을 실행하거나, 위와 같이 코드 내에서 실행하고자 할 경우, use_reloader=False 옵션을 주어야 한다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Service&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Service 인터페이스는 다음과 같다. 각 모델 별로 inference 메소드를 호출할 메소드를 구현하도록 설계한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647270335424&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from abc import *


class Model2ServiceInterface(metaclass=ABCMeta):

    @abstractmethod
    def get_model2_data(self, input: object) -&amp;gt; object:
        raise NotImplementedError&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Model2에 해당하는 Service 구현체는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647270438013&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from abc import *
import logging

from common.Model2ServiceInterface import Model2ServiceInterface
from common.model import Model


class Model2Service(Model2ServiceInterface):
    def __init__(self, model: Model):
        self._model = model

    def get_model2_data(self, input):
        result = self._model.inference(input)
        return result&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Model, Datastore&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Model 인터페이스는 다음과 같다. 모든 Model 구현체는 이 인터페이스를 상속하는데, Service가 호출할 `inference` 메소드만 구현하면 된다. 구현체는 AI 모델링에 대한 부분으로, 서비스와 밀접한 연관이 있어 생략한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647270737574&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import abc


class Model(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def inference(self, input_data: object) -&amp;gt; object:
        raise NotImplementedError&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Datastore 인터페이스는 다음과 같다. Model1에서만 Datastore를 이용하는데, 다음의 인터페이스를 상속해 구현한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647270930668&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from abc import *


class Model1DatastoreInterface(metaclass=ABCMeta):

    def __init__(self):
        pass

    @abstractmethod
    def update_status(self: object, sensor_id: str) -&amp;gt; None:
        raise NotImplementedError

    @abstractmethod
    def findall_by_date(self: object, date: str) -&amp;gt; object:
        raise NotImplementedError
 
    @abstractmethod
    def store_data(self: object, data: object) -&amp;gt; None:
        raise NotImplementedError&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Container&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;그 외에 의존성 주입을 위한 Container는 다음과 같이 구현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647271348085&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import logging.config
import json
import os
import sys
from core.model1Service import model1Service
from core.model2Service import model2Service
from core.model3Service import model3Service
from core.model1Model import model1Model
from core.model2Model import model2Model
from core.model3Model import model3Model
from core.model1DataStore import model1DataStore


class Container(containers.DeclarativeContainer):

    # 모델링에 필요한 설정 정보
    config = providers.Configuration()
    config.from_yaml(f'{os.environ[&quot;APP_ROOT&quot;]}/resource/config.yaml', required=True)

    # 로깅 설정 정보
    with open(os.path.join(sys.model2[0], &quot;logging.json&quot;), 'rt') as f:
        logging = providers.Resource(logging.config.dictConfig, json.load(f))

    # 백엔드 object 생성 및 주입
    model1_datastore = providers.Factory(model1DataStore)
    model1_model = providers.Factory(model1Model, config=config.model1())
    model2_model = providers.Factory(
        model2Model, url=config.model2.url(), key=config.model2.key())
    model3_model = providers.Factory(model3Model, config=config.model3())
    model1_service = providers.Factory(
        model1Service, datastore=model1_datastore, model=model1_model)
    model2_service = providers.Factory(model2Service, model=model2_model)
    model3_service = providers.Factory(model3Service, model=model3_model)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;dependency-injector 라이브러리는 특정 프레임워크에 종속되지 않고 사용할 수 있는 Python의 의존성 주입 라이브러리이다. 사용법은 공식 문서(&lt;a href=&quot;https://python-dependency-injector.ets-labs.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://python-dependency-injector.ets-labs.org&lt;/a&gt;)에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647271489725&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Dependency Injector &amp;mdash; Dependency injection framework for Python &amp;mdash; Dependency Injector 4.38.0 documentation&quot; data-og-description=&quot;Dependency Injector &amp;mdash; Dependency injection framework for Python Dependency Injector is a dependency injection framework for Python. It helps implementing the dependency injection principle. Key features of the Dependency Injector: Providers. Provides Fac&quot; data-og-host=&quot;python-dependency-injector.ets-labs.org&quot; data-og-source-url=&quot;https://python-dependency-injector.ets-labs.org&quot; data-og-url=&quot;https://python-dependency-injector.ets-labs.org&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://python-dependency-injector.ets-labs.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://python-dependency-injector.ets-labs.org&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Dependency Injector &amp;mdash; Dependency injection framework for Python &amp;mdash; Dependency Injector 4.38.0 documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Dependency Injector &amp;mdash; Dependency injection framework for Python Dependency Injector is a dependency injection framework for Python. It helps implementing the dependency injection principle. Key features of the Dependency Injector: Providers. Provides Fac&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;python-dependency-injector.ets-labs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 배운 점&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;지난 피드백 이후 여러 곳에서 배우며 처음으로 AI 서비스에 활용할 수 있는 백엔드를 구축해 보았다. 백엔드 구축에 있어 계층을 어떻게 분리하여 설계할 수 있는지, 의존성 관리, 인터페이스 작성이 왜 필요한지 배울 수 있었다. 클래스 및 인터페이스의 작성에 익숙하지 않았는데, AI 서비스 백엔드 설계에도 dao, controller, service 등과 같은 백엔드 컴포넌트 개념을 적용할 수 있음을 느낄 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;아직 구현되지 않아, 더 연구해 보고 싶은 부분은 Model에서의 에러 처리 부분이다. 위의 코드 중 #TODO로 남겨진 부분인데, 사실 해당 부분은 특별한 문제가 있지 않으면 에러가 나지 않는(나면 안 되는) 부분이기 때문에 에러 처리를 어떻게 해야 할지 모르겠어 연구가 필요할 듯하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/AI App Server</category>
      <category>dependency-injector</category>
      <category>flask</category>
      <category>pyodbc</category>
      <category>Python</category>
      <category>server</category>
      <category>앱서버</category>
      <category>파이썬</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/64</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/Tekton#entry64comment</comments>
      <pubDate>Thu, 23 Dec 2021 17:43:13 +0900</pubDate>
    </item>
    <item>
      <title>[ELK] K8S 환경에 ELK stack 배포하기</title>
      <link>https://projectlog-eraser.tistory.com/entry/ELK-K8S-%ED%99%98%EA%B2%BD%EC%97%90-ELK-stack-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;회사에서 Springboot Application의 로그를 모니터링하기 위해 ELK stack을 활용해 모니터링 시스템을 개발했다. 그런데&amp;nbsp;&lt;b&gt;QA 및 테스트 환경에서의 로그 모니터링이 필요해짐에 따라&lt;/b&gt; 해당 시스템을 &lt;b&gt;사내 데이터센터의 클라우드 환경&lt;/b&gt;(&lt;i&gt;현재 사내 QA 및 테스트는 사내 데이터 센터 클라우드 환경에서 namespace 및 리소스를 할당 받아 이루어지고 있다&lt;/i&gt;)에 배포할 필요성이 생겼다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;사내 데이터센터 클라우드 환경이 K8S와 거의 동일하기 때문에, docker-compose를 이용해 컨테이너 환경에서 개발했던 것을 쿠버네티스 환경에 올리기만 하면 될 것이라고 생각했는데, 어떻게 구성할 지부터 시작해, 실제 쿠버네티스 오브젝트 매니페스트를 작성하고 배포하는 것이 쉽지 않았기 때문에, 해당 과정을 정리해 두고자 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1654586031323&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - sirzzang/elk-on-k8s-tutorial: 쿠버네티스 환경에 ELK stack 배포하기&quot; data-og-description=&quot;쿠버네티스 환경에 ELK stack 배포하기. Contribute to sirzzang/elk-on-k8s-tutorial development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/sirzzang/elk-on-k8s-tutorial&quot; data-og-url=&quot;https://github.com/sirzzang/elk-on-k8s-tutorial&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cLF4zo/hyOG1gQHcV/UTYnGH7VBIXlpzRKWhexU0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/sirzzang/elk-on-k8s-tutorial&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/sirzzang/elk-on-k8s-tutorial&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cLF4zo/hyOG1gQHcV/UTYnGH7VBIXlpzRKWhexU0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - sirzzang/elk-on-k8s-tutorial: 쿠버네티스 환경에 ELK stack 배포하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;쿠버네티스 환경에 ELK stack 배포하기. Contribute to sirzzang/elk-on-k8s-tutorial development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;사내 데이터센터 클라우드는 &lt;b&gt;쿠버네티스 API 서버 1.17.8 버전을 사용하는 쿠버네티스 환경&lt;/b&gt;이다. 웹에서 YAML 에디터가 제공되기 때문에 해당 에디터를 이용해 오브젝트 매니페스트를 작성하거나, 로컬에서 YAML 파일을 작성한 뒤 kubectl 명령어를 통해 오브젝트를 생성하고 업데이트할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;다만, 현재 QA 및 테스트를 위해 할당받은 namespace에서는 노드, persistent volume 등 특정 오브젝트에 대한 접근 권한이 없다. 때문에 오브젝트 생성 후 어떤 파드를 어떤 노드에서 실행할 것인지 할당하는 등의 작업은 불가능하며, 영구 볼륨을 생성하는 것이 아니라, 영구 볼륨 클레임을 생성하여 스토리지를 사용할 수 있다. 스토리지 클래스의 경우, 할당받은 namespace에 기본적으로 제공되는 것들(hdd, ssd 등)이 있어 이것을 사용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 구조&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;배포 시 구조는 다음과 같이 구성했다. Elasticsearch와 Kibana의 경우에는 elastic에서 제공하는 기본 이미지를 사용했고, Logstash는 elastic에서 제공하는 기본 이미지에 로그 파싱을 위해 필요한 플러그인을 설치한 이미지를 생성해 사용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;541&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctqGlc/btroITRv0uQ/l6slTPOiL1PHeGt6lgPttk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctqGlc/btroITRv0uQ/l6slTPOiL1PHeGt6lgPttk/img.png&quot; data-alt=&quot;쿠버네티스를 공부한 지 얼마 되지 않아, 아래와 같이 그림을 그리는 것은 옳지 않을 수 있다.&amp;amp;amp;nbsp; 머릿속으로 생각했을 때 이러 이러한 구조로 그리면 맞겠지 하고 그린 그림일 뿐...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctqGlc/btroITRv0uQ/l6slTPOiL1PHeGt6lgPttk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctqGlc%2FbtroITRv0uQ%2Fl6slTPOiL1PHeGt6lgPttk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;511&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;541&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;쿠버네티스를 공부한 지 얼마 되지 않아, 아래와 같이 그림을 그리는 것은 옳지 않을 수 있다.&amp;amp;nbsp; 머릿속으로 생각했을 때 이러 이러한 구조로 그리면 맞겠지 하고 그린 그림일 뿐...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Elasticsearch&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Elasticsearch의 경우, 로그 데이터를 저장하고 있어야 하므로 &lt;b&gt;파드가 내려가도 데이터가 사라지지 않도록&lt;/b&gt; Statefulset 컨트롤러 오브젝트를 생성해 파드를 실행하도록 했다. 그리고 PVC 오브젝트를 생성해 스토리지에 데이터를 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;다만, Elasticsearch 클러스터는 구성하지 못했다. 즉, &lt;b&gt;replicas를 1로 설정해&lt;/b&gt; Elasticsearch 노드를 1개만 띄웠다. 처음 모니터링 시스템 개발 시 Elasticsearch 클러스터 구성 방법을 파악한 뒤 개발한 것이 아니었기 때문에, 배포 시 이 점을 반영할 수 없었다. 이렇게 한 개의 노드로만 구성했을 때 장애에 대처할 수 없고(&lt;i&gt;예컨대, Elasticsearch 노드에 문제가 발생하면 그 때 발생하는 로그는 적재되지 않게 된다&lt;/i&gt;), Elasticsearch의 장점을 활용할 수도 없는 것이기 때문에, 추후 클러스터를 구성하도록 전체 아키텍쳐를 변경하는 것이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;배포된 환경에서 Logstash 노드와 Kibana 노드가 Elasticsearch와 통신할 수 있어야 하므로 NodePort 타입의 서비스 오브젝트를 생성하고, 9200 포트를 열어 주었다. 그림 상에서는 9300 포트도 나와 있는데, 사실 9300 포트의 경우 Elasticsearch를 여러 개의 노드로 구성할 때 노드 간 사용하게 될 포트로, 지금은 1개의 노드로 구성해 필요가 없지만, 추후 여러 노드로 구성할 때를 대비해 지금 단계에서도 열어 두었다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Logstash, Kibana&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Logstash와 Kibana의 경우 Elasticsearch와 달리 데이터를 저장하거나 상태를 기억해야 할 필요가 없기 때문에, Deployment 컨트롤러 오브젝트로 파드를 실행하도록 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;둘 모두 장애가 났을 때 굳이 여러 개의 파드가 떠 있어야 할 필요가 없다고 판단했기 때문에, replicas를 1로 설정했다. Logstash 파드의 경우 장애가 발생했을 때 나면 로그 유실 우려가 있지만, 이런 문제가 발생한다면 &lt;b&gt;TCP로 로그를 바로 쏴 주는 현재 아키텍쳐에서 비롯되는 문제일 것이라 보기 때문에&lt;/b&gt;, Logstash 파드를 여러 개 띄우는 것이 아니라 Filebeats 파드를 띄우는 식으로 개선하고자 한다. Kibana 파드의 경우 장애가 발생할 때 대시보드를 조회하지 못할 수는 있다. 다만, Kibana Saved Objects가 모두 Elasticsearch 파드에 bound된 스토리지에 저장되기 때문에 데이터 유실 우려는 없어 가용성을 높일 필요는 없을 것이라 판단했다. &lt;i&gt;(물론, Elasticsearch 파드에 문제가 생긴다면 Kibana Saved Objects도 모두 유실된다..)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Logstash 파드는 Elasticsearch 파드와 통신하면 되므로 NodePort 타입의 서비스 오브젝트를, Kibana 파드는 사내에서 접속해 로그 대시보드를 확인할 수 있어야 하므로 LoadBalancer 타입의 서비스 오브젝트를 생성해 주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내용 추가&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 해당 내용에 대해 세미나를 진행했는데, Kibana 대시보드 접속 설정은 굳이 LoadBalancer 서비스 오브젝트를 붙이지 않고, ingress 오브젝트 단에서 진행해도 될 듯하다는 피드백이 있었다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2. 추후 서비스 오브젝트에 대해 다시 공부하면서 보니, Logstash 디플로이먼트 오브젝트와 Kibana 디플로이먼트 오브젝트에 연결할 서비스 오브젝트는 굳이 NodePort가 아니었어도 될 듯하다. 클러스터 내에서만 통신해도 되기 때문에, ClusterIP로 해도 된다.&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 배포&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;namespace, 스토리지 등 사내 클라우드 환경에서 기본적으로 제공되는 것은 각주로 대체했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Elasticsearch&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;NodePort 타입의 Service 오브젝트를 작성한 뒤 적용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: v1 
kind: Service
metadata:
&amp;nbsp;&amp;nbsp;name: elasticsearch
&amp;nbsp;&amp;nbsp;namespace: # namespace
spec:
&amp;nbsp;&amp;nbsp;ports:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: http-rest
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port: 9200
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetPort: 9200
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: tcp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port: 9300
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetPort: 9300
&amp;nbsp;&amp;nbsp;selector:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: elasticsearch&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;아래와 같이 Statefulset 오브젝트를 작성한 뒤 적용한다. Statefulset의 VolumeClaimTemplates를 PersistentVolumeClaim 매니페스트로 따로 작성해도 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: StatefulSet
metadata:
&amp;nbsp;&amp;nbsp;name: elasticsearch
&amp;nbsp;&amp;nbsp;namespace: # namespace
spec:
&amp;nbsp;&amp;nbsp;replicas: 1
&amp;nbsp;&amp;nbsp;selector:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;matchLabels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: elasticsearch
&amp;nbsp;&amp;nbsp;template:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;labels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: elasticsearch
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;spec:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containers:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: elasticsearch
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image: 'docker.elastic.co/elasticsearch/elasticsearch:7.15.2'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;imagePullPolicy: IfNotPresent
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resources:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;limits:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cpu: &quot;1&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;memory: 8Gi
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requests:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cpu: 500m
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;memory: 4Gi&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: ES_JAVA_OPTS
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value: '-Xmx2g -Xms2g'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: discovery.type 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value: single-node
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports: 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: http-rest
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containerPort: 9200
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: tcp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containerPort: 9300
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumeMounts:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: es-data-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mountPath: /usr/share/elasticsearch/data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;restartPolicy: Always
&amp;nbsp;&amp;nbsp;volumeClaimTemplates:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- kind: PersistentVolumeClaim
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;apiVersion: v1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name: es-data-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;spec:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;accessModes:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- ReadWriteOnce
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resources:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requests:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;storage: 10Gi
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;storageClassName: # storage class
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumeMode: Filesystem&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위의 두 YAML 파일을&amp;nbsp;elasticsearch.yaml과 같이 하나의 파일로 작성한 뒤 적용해도 된다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;elasticsearch.yaml&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: v1 
kind: Service
metadata:
&amp;nbsp;&amp;nbsp;name: elasticsearch
&amp;nbsp;&amp;nbsp;namespace: # namespace
spec:
&amp;nbsp;&amp;nbsp;ports:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: http-rest
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port: 9200
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetPort: 9200
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: tcp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port: 9300
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetPort: 9300
&amp;nbsp;&amp;nbsp;selector:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: elasticsearch
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
&amp;nbsp;&amp;nbsp;name: elasticsearch
&amp;nbsp;&amp;nbsp;namespace: # namespace
spec:
&amp;nbsp;&amp;nbsp;replicas: 1
&amp;nbsp;&amp;nbsp;selector:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;matchLabels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: elasticsearch
&amp;nbsp;&amp;nbsp;template:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;labels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: elasticsearch
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;spec:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containers:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: elasticsearch
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image: 'docker.elastic.co/elasticsearch/elasticsearch:7.15.2'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;imagePullPolicy: IfNotPresent
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resources:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;limits:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cpu: &quot;1&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;memory: 8Gi
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requests:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cpu: 500m
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;memory: 4Gi&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: ES_JAVA_OPTS
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value: '-Xmx2g -Xms2g'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: discovery.type 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value: single-node
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports: 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: http-rest
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containerPort: 9200
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: tcp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containerPort: 9300
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumeMounts:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: es-data-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mountPath: /usr/share/elasticsearch/data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;restartPolicy: Always
&amp;nbsp;&amp;nbsp;volumeClaimTemplates:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- kind: PersistentVolumeClaim
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;apiVersion: v1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name: es-data-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;spec:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;accessModes:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- ReadWriteOnce
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resources:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requests:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;storage: 10Gi
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;storageClassName: # storage class
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumeMode: Filesystem&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;적용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;kubectl apply -f elasticsearch.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Logstash&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Logstash 설정 및 파이프라인 작성을 위한 conf 파일을 ConfigMap 오브젝트로 작성한다. filter 부분은 대체.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
&amp;nbsp;&amp;nbsp;name: logstash-config
&amp;nbsp;&amp;nbsp;namespace: elk-stack
data:
&amp;nbsp;&amp;nbsp;# configuration for logstash pipeline
&amp;nbsp;&amp;nbsp;logstash.conf: |
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;input {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tcp {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port =&amp;gt; 5000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;filter {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;source =&amp;gt; &quot;message&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mutate {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remove_field =&amp;gt; [&quot;@version&quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;output {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stdout {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;codec =&amp;gt; rubydebug
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;# logstash configuration
&amp;nbsp;&amp;nbsp;logstash.yml: |
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;http.host: &quot;0.0.0.0&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path.config: /usr/share/logstash/pipeline&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;NodePort 타입의 서비스 오브젝트를 작성한 뒤 적용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
&amp;nbsp;&amp;nbsp;name: logstash
&amp;nbsp;&amp;nbsp;namespace: # namespace
spec:
&amp;nbsp;&amp;nbsp;type: NodePort
&amp;nbsp;&amp;nbsp;ports:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: tcp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port: 5000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetPort: 5000
&amp;nbsp;&amp;nbsp;selector:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: logstash&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Logstash 파드의 Deployment 컨트롤러 오브젝트를 작성한 뒤 적용한다. 만약, ConfigMap을 수정했다면, Deployment를 다시 실행해 주어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
&amp;nbsp;&amp;nbsp;name: logstash
&amp;nbsp;&amp;nbsp;namespace: # namespace
spec:
&amp;nbsp;&amp;nbsp;replicas: 1
&amp;nbsp;&amp;nbsp;selector:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;matchLabels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: logstash
&amp;nbsp;&amp;nbsp;template:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;labels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: logstash
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;spec:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumes:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: logstash-config-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;configMap:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name: logstash-config
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- key: logstash.yml
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path: logstash.yml
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: logstash-pipeline-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;configMap:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name: logstash-config
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- key: logstash.conf
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path: logstash.conf
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containers:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: logstash
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image: # logstash image from private repository
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;imagePullPolicy: Always
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resources:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;limits:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cpu: 500m
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;memory: 4Gi
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requests:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cpu: 300m
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;memory: 2Gi&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: LS_JAVA_OPTS
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value: '-Xmx1g -Xms1g'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: tcp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containerPort: 5000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumeMounts:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: logstash-config-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mountPath: /usr/share/logstash/config
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: logstash-pipeline-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mountPath: /usr/share/logstash/pipeline&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## Kibana&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Kibana 설정을 위한 파일을 ConfigMap 오브젝트로 작성한다. elasticsearch.hosts 설정의 경우, 쿠버네티스의 DNS 시스템에 따라 elasticsearch라는 파드 이름을 통해 elasticsearch 서비스 오브젝트에 접속할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
&amp;nbsp;&amp;nbsp;name: kibana-config
&amp;nbsp;&amp;nbsp;namespace: # namespace
data:
&amp;nbsp;&amp;nbsp;kibana.yml: |
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server.host: 0.0.0.0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elasticsearch.hosts: [&quot;http://elasticsearch:9200&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;LoadBalancer 타입의 Service 오브젝트를 작성해 적용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
&amp;nbsp;&amp;nbsp;name: kibana
&amp;nbsp;&amp;nbsp;namespace: # namespace
spec:
&amp;nbsp;&amp;nbsp;ports:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: http
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port: 5601
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetPort: 5601
&amp;nbsp;&amp;nbsp;selector:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: kibana
&amp;nbsp;&amp;nbsp;type: LoadBalancer&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Kibana 파드의 Deployment 컨트롤러 오브젝트를 작성한 뒤 적용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
&amp;nbsp;&amp;nbsp;name: kibana
&amp;nbsp;&amp;nbsp;namespace: # namespace
spec:
&amp;nbsp;&amp;nbsp;replicas: 1
&amp;nbsp;&amp;nbsp;selector:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;matchLabels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: kibana
&amp;nbsp;&amp;nbsp;template:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;labels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app: kibana
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;spec:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumes:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: kibana-config-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- key: kibana.yml
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path: kibana.yml
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containers:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: waplpay-kibana
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image: 'docker.elastic.co/kibana/kibana:7.15.2'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;imagePullPolicy: IfNotPresent
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: kibana
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containerPort: 5601
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocol: TCP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resources:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;limits:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cpu: '1'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;memory: 2Gi
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requests:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cpu: 500m
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;memory: 1Gi
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;volumeMounts:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: kibana-config-volume
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mountPath: /usr/share/kibana/config
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;restartPolicy: Always&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위와 같이 각 오브젝트의 매니페스트를 작성한 뒤 배포하면, 위에서 언급한 구조와 같은 형태의 ELK stack이 클라우드 환경에 배포된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아래 사진의 예시와 같이 kubectl get pods, kubectl get statefulsets, kubectl get deployments, kubectl get pvc 등과 같은 명령어를 통해 각 오브젝트의 상태를 확인해 보면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPtDvb/btroEnzaK3d/sRbAW4Qf7caKfJcTEuST7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPtDvb/btroEnzaK3d/sRbAW4Qf7caKfJcTEuST7K/img.png&quot; data-alt=&quot;글 작성 시점에서는 elasticsearch 파드가 생성된 지 14일이 지났다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPtDvb/btroEnzaK3d/sRbAW4Qf7caKfJcTEuST7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPtDvb%2FbtroEnzaK3d%2FsRbAW4Qf7caKfJcTEuST7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;122&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;글 작성 시점에서는 elasticsearch 파드가 생성된 지 14일이 지났다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# Troubleshooting&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## OOMKilled 에러&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Elasticsearch 파드를 실행하는 과정에서 아래와 같은 &lt;b&gt;OOMKilled 에러&lt;/b&gt;를 마주했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJEMo9/btroKE0jepP/Vk8FHPt8jtCQgYBz5mawG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJEMo9/btroKE0jepP/Vk8FHPt8jtCQgYBz5mawG0/img.png&quot; data-alt=&quot;OOMKilled 후 CrashLoopBackOff 상태를 반복한다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJEMo9/btroKE0jepP/Vk8FHPt8jtCQgYBz5mawG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJEMo9%2FbtroKE0jepP%2FVk8FHPt8jtCQgYBz5mawG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;112&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OOMKilled 후 CrashLoopBackOff 상태를 반복한다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;메모리 관련 에러인 듯하여 찾아 보니, Elasticsearch 컨테이너에 `Xmx2g Xms2g` 옵션을 주어 JVM 힙 메모리 사이즈를 설정했는데, 파드에 할당한 자원 할당량이 2Gi 였기 때문에 발생하는 문제인 듯하였다. 파드에 할당하는 자원 할당량 중 메모리 할당량을 높여 주니 문제가 발생하지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;관련해 더 찾다 보니, LINE Engineering에서 프로메테우스를 사용해 메트릭을 모니터링하다 OOMKilled 에러를 겪었다는 글을 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;누가 Kubernetes 클러스터에 있는 나의 사랑스러운 Prometheus 컨테이너를 죽였나! - LINE ENGINEERING&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;안녕하세요. 이번 글에서는 CrashLoopBackoff 상태에 있는 Prometheus 컨테이너 이슈의 원인을 조사하고 해결하는 과정에서 겪은 흥미로운 경험을 공유하려고 합니다. 사실 이런 현상이 발생하는 원인&quot; data-og-host=&quot;engineering.linecorp.com&quot; data-og-source-url=&quot;https://engineering.linecorp.com/ko/blog/prometheus-container-kubernetes-cluster/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bfzQcu/hyMO4UffN4/rSXux22O8exrCBjMKtBRc0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot; data-og-url=&quot;https://engineering.linecorp.com/ko/blog/prometheus-container-kubernetes-cluster/&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://engineering.linecorp.com/ko/blog/prometheus-container-kubernetes-cluster/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://engineering.linecorp.com/ko/blog/prometheus-container-kubernetes-cluster/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bfzQcu/hyMO4UffN4/rSXux22O8exrCBjMKtBRc0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;누가 Kubernetes 클러스터에 있는 나의 사랑스러운 Prometheus 컨테이너를 죽였나! - LINE ENGINEERING&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 이번 글에서는 CrashLoopBackoff 상태에 있는 Prometheus 컨테이너 이슈의 원인을 조사하고 해결하는 과정에서 겪은 흥미로운 경험을 공유하려고 합니다. 사실 이런 현상이 발생하는 원인&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;engineering.linecorp.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## PVC bound 실수&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;사실 이건 해결한 문제는 아니지만, 아주 바보 같은 실수를 해서 기록한다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;ELK stack을 작성해 배포한 뒤, kubectl get pvc 명령어를 통해 PVC 상태를 확인했는데, PVC가 bound되어 있지 않음을 발견했다. Grafana 대시보드를 통해 네임스페이스 내 PVC 오브젝트들의 상태를 확인해도 bound되어 있는 Elasticsearch PVC를 찾을 수 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;알고 보니 &lt;b&gt;Elasticsearch Statefulset 오브젝트 매니페스트를 작성하면서 volumeMounts 부분을 작성하지 않았던 것&lt;/b&gt;이었다. volumeMounts 부분을 작성해서 Statefulset 컨트롤러 매니페스트를 다시 작성해서 배포했는데, 그 결과 Elasticsearch 파드가 재기동되면서 이전까지 쌓여 있었던 약 5만 개 가량의 로그 데이터가 사라지고, 인덱스가 새로 매핑되면서 Kibana 파드도 재기동해야 하는 문제가 발생했다. Kibana 서버에 장애가 발생한 것은 아니었지만, 이전 Elasticsearch 파드에 저장되어 있던 Kibana Saved Objects가 사라졌음은 말할 것도 없다...&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;처음 배포할 때부터 완벽하게 매니페스트를 작성했더라면 좋았겠지만, 다음에도 이런 상황이 발생한다면 무턱대고 Statefulset을 다시 작성해 배포하는 것이 아니라, &lt;b&gt;해당 파드에 있는 데이터를 클라우드 환경 내 스토리지를 사용해 저장한 뒤 다시 배포하는 방법을 찾아야&lt;/b&gt; 할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;로컬에서 구축한 모니터링 시스템을 클라우드 상의 운영 환경에 올린 것에 의의를 둔 초기 배포였지만, 앞으로 다음과 같은 부분을 더 연구해서 발전시켜 나가야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;구조&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Elasticsearch 클러스터를 어떻게 구성해야 하며, 구성 시 클라우드 환경 상의 아키텍쳐는 어떻게 달라져야 하는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;로그 모니터링 시스템에 filebeats 스택이 추가되었을 때, 클라우드 환경 상의 아키텍쳐는 어떻게 달라져야 하는가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자원 할당&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;로그 이벤트 1건 당 용량은 얼마나 되며, 로그 발생량에 기초했을 때 Elasticsearch 파드에 bound되는 PVC의 용량은 어떻게 산정해야 하는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Elasticsearch, Logstash, Kibana 각 파드의 CPU, 메모리 사용량을 Grafana를 통해 관찰했을 때 어느 정도의 추이를 보이며, 이를 기반으로 했을 때, 각 파드의 자원 할당량을 어떻게 최적화할 수 있는가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra</category>
      <category>elasticsearch</category>
      <category>Elk</category>
      <category>IDC</category>
      <category>k8s</category>
      <category>kibana</category>
      <category>kubernetes</category>
      <category>logstash</category>
      <category>모니터링</category>
      <category>쿠버네티스</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/63</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/ELK-K8S-%ED%99%98%EA%B2%BD%EC%97%90-ELK-stack-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0#entry63comment</comments>
      <pubDate>Sun, 5 Dec 2021 12:01:36 +0900</pubDate>
    </item>
    <item>
      <title>[Tibero] Tibero에서 UPSERT 쿼리 구현하기</title>
      <link>https://projectlog-eraser.tistory.com/entry/Tibero-Tibero%EC%97%90%EC%84%9C-UPSERT-%EC%BF%BC%EB%A6%AC-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;지난 글에서 개발하던 앱 서버를 운영하던 중, 이슈가 발생해 해당 이슈를 해결하기 위해 UPSERT 쿼리를 구현할 필요성이 생겼다. &lt;b&gt;UPSERT&lt;/b&gt;란, &lt;b&gt;키 값이 존재하는 row에 대해서는 UPDATE를 수행하고, 그렇지 않은 row에 대해서는 INSERT를 수행하는 쿼리&lt;/b&gt;이다. MySQL, PostgreSQL, Oracle 등 많이 사용하는 데이터베이스에서는 UPSERT를 수행하기 위한 쿼리가 제공된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;간략하게 &lt;s&gt;&lt;i&gt;(전적으로 내 기준)&lt;/i&gt;&lt;/s&gt; 기억하기 쉽도록 각 DBMS에서 제공되는 UPSERT 쿼리를 정리하면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MySQL: ON DUPLICATE KEY UPDATE&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;PostgreSQL: ON CONFLICT DO UPDATE SET&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ORACLE: MERGE WHEN MATHCED THEN WHEN NOT MATCHED THEN&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;해당 글에서는 앱 서버 운영 중 마주친 이슈를 해결하기 위해&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이슈를 해결하기 위해 앱 서버의 데이터베이스인 Tibero에서 해당 쿼리를 구현한 방법을 기록한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# MySQL UPSERT 테스트&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Tibero에서 UPSERT를 구현하기 앞서, MySQL에서 UPSERT를 어떻게 구현할 수 있는지 테스트해 보았다. 센서 id가 존재하면 센서 상태(status)를 OFF로 update하고, 센서 id가 없으면 센서 상태가 ON이 되도록 센서 id와 상태를 insert하는 작업을 수행하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;테스트를 위해 아래와 같이 테스트 데이터베이스, 유저를 생성해 주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rVaIk/btrh5Z5saox/if48x2pQNTT6xVkdkra2qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rVaIk/btrh5Z5saox/if48x2pQNTT6xVkdkra2qK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rVaIk/btrh5Z5saox/if48x2pQNTT6xVkdkra2qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrVaIk%2Fbtrh5Z5saox%2Fif48x2pQNTT6xVkdkra2qK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;356&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;그리고 sensor_status라는 테스트 테이블을 생성했다. 이 때, 센서 id가 존재하는 값인지 확인해야 하므로, 센서 id에 해당하는 NAME 열을 unique index로 지정해 주었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634560261623&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE UPSERT.status (
name VARCHAR(10) NOT NULL,
status VARCHAR(3) NOT NULL,
PRIMARY KEY (name),
UNIQUE INDEX name_UNIQUE (name ASC) VISIBLE);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;UPSERT 쿼리는 다음과 같이 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1634560308610&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO status (NAME, status)
VALUES ('ST2000', 'ON')
ON DUPLICATE KEY UPDATE status='OFF';&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;해당 쿼리를 여러 번 수행하며 UPSERT 문이 잘 작동하는지 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/svk4n/btrh7xOrbLB/0F2LURyUQZADt7CHk4z930/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/svk4n/btrh7xOrbLB/0F2LURyUQZADt7CHk4z930/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/svk4n/btrh7xOrbLB/0F2LURyUQZADt7CHk4z930/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsvk4n%2Fbtrh7xOrbLB%2F0F2LURyUQZADt7CHk4z930%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;254&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;중복되는 키 값이 존재하는 경우, UPSERT 쿼리 수행 시 '2 rows affected'라는 메시지가 뜨는 것을 볼 수 있다. &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Documentation&lt;/a&gt;에 의하면 INSERT가 수행된 경우 '1 row affected', UPDATE가 정상적으로 수행된 경우 '2 rows affected', UPDATE가 수행되나 기존 값과 같은 경우 '0 row affected'라는 결과가 나타난다고 한다. 더 자세한 경우를 알아보고 싶으면 &lt;a href=&quot;https://gywn.net/2018/03/mad-usage-with-mysql-affected-rows/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;을 참고해도 좋을 듯.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;MySQL에서 이와 같이 UPSERT 쿼리를 구현할 수 있다. 찾아 보니 UPSERT를 수행해야 하는 상황에 &lt;a href=&quot;https://redcow77.tistory.com/262&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;REPLACE를 사용하는 방법&lt;/a&gt;도 있다고 하니, 참고.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# Tibero 데이터베이스에서 UPSERT 쿼리 구현&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Tibero에서 UPSERT 작업을 수행하려면 Oracle 데이터베이스와 비슷하게 MERGE 문을 사용하면 된다. 아래 사진에서와 같이 공식 Tibero SQL 참조서는 여러 삽입, 갱신, 삭제 작업을 수행하기 위해 MERGE 문을 사용하라고 안내한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GiHZw/btricDlSwEo/CMU3Aop7bmEWP4agEsyLlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GiHZw/btricDlSwEo/CMU3Aop7bmEWP4agEsyLlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GiHZw/btricDlSwEo/CMU3Aop7bmEWP4agEsyLlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGiHZw%2FbtricDlSwEo%2FCMU3Aop7bmEWP4agEsyLlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;517&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;공식 참조서를 따라 쉽게 이슈 해결을 위한 UPSERT 쿼리를 구현할 수 있었다. 다만, &lt;b&gt;단일 테이블에서 row가 존재하는지를 판단해야 했기 때문에&lt;/b&gt; USING 절에 DUAL 테이블을 사용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1634561156778&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MERGE INTO IOT.TEST_SENSOR_STATUS T
USING DUAL
ON (T.SENSOR_ID = 'ST20000509')
WHEN MATCHED THEN
	UPDATE
		SET T.STATUS = 'OFF'
WHEN NOT MATCHED THEN
	INSERT (T.SENSOR_ID, T.STATUS)
	VALUES ('ST20000509', 'ON');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;UPSERT 쿼리가 잘 동작하는지 확인해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 220px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 14px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 14px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;INSERT&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 14px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;UPDATE&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 206px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 206px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cklK8N/btrh9RyExk7/DK8SJD2A8g7aaKHD1v5Ui1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cklK8N/btrh9RyExk7/DK8SJD2A8g7aaKHD1v5Ui1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cklK8N/btrh9RyExk7/DK8SJD2A8g7aaKHD1v5Ui1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcklK8N%2Fbtrh9RyExk7%2FDK8SJD2A8g7aaKHD1v5Ui1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;300&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 206px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EgyHn/btrh4M6qMIw/5QTRCRGYtWuZYkfQrjfgrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EgyHn/btrh4M6qMIw/5QTRCRGYtWuZYkfQrjfgrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EgyHn/btrh4M6qMIw/5QTRCRGYtWuZYkfQrjfgrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEgyHn%2Fbtrh4M6qMIw%2F5QTRCRGYtWuZYkfQrjfgrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;308&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위와 같은 과정을 통해 Tibero 데이터베이스에서 쉽게 UPSERT 쿼리를 구현할 수 있다. 더 알아보고 싶은 부분은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;USING 절에 테이블을 사용할 때와 뷰를 사용할 때, 어떤 차이가 있는가? 성능적인 측면에서?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;UPSERT 문과 연결된 API 설계를 어떻게 해야 할 것인가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/AI App Server</category>
      <category>tibero</category>
      <category>upsert</category>
      <category>데이터베이스</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/60</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/Tibero-Tibero%EC%97%90%EC%84%9C-UPSERT-%EC%BF%BC%EB%A6%AC-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0#entry60comment</comments>
      <pubDate>Mon, 18 Oct 2021 21:49:09 +0900</pubDate>
    </item>
    <item>
      <title>[App Server] Flask,  Socket으로 앱 서버 구축해 보기</title>
      <link>https://projectlog-eraser.tistory.com/entry/App-Server-Flask-Socket%EC%9C%BC%EB%A1%9C-%EC%95%B1-%EC%84%9C%EB%B2%84%EB%A5%BC-%EA%B5%AC%EC%B6%95%ED%95%98%EB%A9%B0-%EA%B2%AA%EC%9D%80-%EC%8B%9C%ED%96%89%EC%B0%A9%EC%98%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;(지난 포스팅에 이어) 클라이언트의 요청이 들어 왔을 때, 요청을 처리해 모델링을 한 뒤, 해당 결과를 클라이언트에게 전송하는 앱 서버를 개발하였다. &lt;b&gt;Flask와 Socket을 이용해 개발했는데, 구조를 기획하고 실제 코드를 작성하는 과정에서 배운 점이 많았기 때문에&lt;/b&gt; 간략하게 전체적인 과정을 기록하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;예전에 데이터 분석, AI를 배울 때부터 꼭 해보고 싶었던 일이기에, 회사 업무를 통해 경험할 수 있다는 것 자체가 큰 성장이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# 구조&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;개발에 앞서 다음 그림과 같은 구조를 기획했다. &lt;b&gt;Flask app server는 클라이언트의 요청을 라우팅하는 router 모듈로 동작하고, backend server는 Flask app server에서 받은 요청에 따라 그에 맞는 모델을 호출해 작업을 수행한 뒤 결과를 돌려준다.&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;구조도.png&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzxD7Q/btrb1ryODzr/kPwPZ3nQvFcvtjt6qRkAAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzxD7Q/btrb1ryODzr/kPwPZ3nQvFcvtjt6qRkAAk/img.png&quot; data-alt=&quot;Flask app과 model, DB 부분이 모두 우리 backend지만, flask는 routing 역할만 담당하도록 구상했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzxD7Q/btrb1ryODzr/kPwPZ3nQvFcvtjt6qRkAAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzxD7Q%2Fbtrb1ryODzr%2FkPwPZ3nQvFcvtjt6qRkAAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;345&quot; data-filename=&quot;구조도.png&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Flask app과 model, DB 부분이 모두 우리 backend지만, flask는 routing 역할만 담당하도록 구상했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;서빙될 모델은 2개이다. 클라이언트는 model1과 model2에 대해 GET 요청을 보낸다. &lt;b&gt;Flask app serve&lt;/b&gt;r는 model1에 대한 URL &amp;lt;server&amp;gt;/model1로 요청이 들어 오면 backend server에 model1에 대한 요청이 들어 왔음을 &lt;b&gt;알린다.&lt;/b&gt; model2에 대한 경우도 마찬가지이다. 이후 model1과 model2에서 사용자가 알고자 하는 결과에 대한 inference가 끝나면, 각 모델은 inference가 끝났다는 &lt;b&gt;알림&lt;/b&gt;(과 데이터)을 Flask app에 보내고, Flask app은 클라이언트에게 해당 결과를 전송한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;각 모델이 동작하는 방식은 내부적으로 다르지만, Flask app으로부터 호출을 받는다는 것이 핵심이다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;model1의 경우, DB와 외부 API에서 모델링에 필요한 데이터를 로드해 전처리한 뒤, inference 로직에 전처리된 데이터를 주입한다. inference 결과를 DB의 result table에 INSERT하면, 클라이언트 앱 서버를 구축하는 쪽에서 해당 result table에 접근해 모델 inference 결과를 가져 간다. 따라서 INSERT가 완료되면 model inference가 끝났다는 response를 돌려 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;model2의 경우, 클라이언트가 GET 요청을 보낼 때 request body에 모델링에 필요한 데이터가 함께 전송된다. 따라서 model2는 Flask app server로부터 전송된 데이터를 전처리하고, inference 과정을 거친다. model1과 달리 inference 결과를 Flask app server에 돌려 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Nginx는 Flask app server를 배포하기 위해 필요한 것으로, 구조도를 작성할 때는 집어 넣었지만, 지금 단계에서는 구현하지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;# 구현&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;구현에 사용한 라이브러리는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;flask&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;socket: flask app과 backend server간 통신을 위한 용도&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;pyodbc: Tibero DB에 접근하기 위한 용도&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;requests&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;pandas&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;python-dotenv: 환경변수(Tibero DSN 설정 등)를 import해서 사용하기 위한 용도&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;flake8: python 린팅&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;전체적인 디렉토리 구조는 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628784314640&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;backend
ㄴ model1
   ㄴ loader.py
   ㄴ preprocessor.py
   ㄴ model.py
ㄴ model2
   ㄴ preprocessor.py
   ㄴ model.py
   ㄴ api.py
app.py
server.py
config.py&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;app.py에서 Flask app을 구현하고, server.py에서 backend server를 구현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모델에 대한 구현은 내가 담당한 부분이 아니기 때문에, 상세히 기록하지는 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;각 모델이 model.py에서 정의되는 Model1 혹은 Model2 클래스에서 predict 메소드를 호출하면 inference 결과가 나온다. inference 과정에서 load, preprocess, api 등 필요한 로직을 사용한다. 그리고 inference 결과로 나올 것이라 예상되는 데이터 형태에 맞추어 dummy data를 반환하도록 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;사실 원래 Model1의 경우 Tibero DB의 result table에 결과를 INSERT해야 하지만, 일단은 response로 dummy data를 반환하는 것으로 가정하고 개발했다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;## app.py&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628784936891&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from flask import Flask, request, jsonify
import json
import socket

from config import HOST, PORT

# Flask app
app = Flask(__name__)

# model1 routing
@app.route('/model1'):
def model1():
	# date for model inference
    req = request.data
    req_data = json.loads(req.decode())
    date = req_data['date']
    
    # send message via socket to server
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect((HOST, PORT))
    client_msg = f'model1, {date}'.encode()
    client_socket.sendall(client_msg)
    
    # receive message(model result) via socket from server
    server_msg = client_socket.recv(65535)
    client_socket.close()
    response = server_msg.decode('utf-8')
    return jsonify(response)

# model2 routing
@app.route('/model2')
def model2():
    # data for model inference
    req = request.data
    req_data = json.loads(req.decode())
    
    # send message via socket to server with data in request body
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_msg = f'model2, {data}'.encode()
    client_socket.sendall(client_msg)
    
    # receive message(model result) via socket from server
    server_msg = client_socket.recv(65535)
    client_socket.close()
    
    response = server_msg.decode('utf-8')
    return jsonify(response)
    

if __name__ == '__main__':
    app.run(debug=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Flask app이 라우팅 후 각 모델을 호출하기 위해 &lt;b&gt;server에 알림을 보내는 부분&lt;/b&gt;이 중요했다. 요청이 들어 오면 client 소켓을 열어 메시지와 데이터를 server 소켓에 전송한다. server 소켓이 model inference 결과를 메시지로 전송하고, 이 메시지를 받은 후 client 소켓을 닫는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;해당 코드를 구현할 때 주의해야 할 점은 소켓 생성이 각 url별 함수 안에서 이루어져야 한다는 것이다. 함수 밖에서 client 소켓을 생성하게 되면, Flask app server가 열려 있는 동안 계속해서 client 소켓이 열리게 된다. client 소켓은 client의 요청이 들어올 때만 열면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇지 않으면(&lt;i&gt;예컨대, 위의 코드에서 6라인 혹은 8라인 사이에서 `client_socket = socket.socket()`과 같은 코드를 작성하는 경우이다&lt;/i&gt;), 아래와 같이 Flask app server를 종료한 후에야 server에 메시지가 전송되는 불상사를 겪게 된다. Flask app server가 동작하는 동안 계속해서 client 소켓을 생성하기 때문에 send를 타지 못하는 것이 아닐까 하는 추측을 해 보았지만, 좀 더 연구해 볼 필요가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷, 2021-08-13 01-34-46.png&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2RyJ8/btrb226tEJO/kBTGgbWQ3PNc2BZjXHPRy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2RyJ8/btrb226tEJO/kBTGgbWQ3PNc2BZjXHPRy1/img.png&quot; data-alt=&quot;분명 client socket이 연결되고, 오른쪽 Flask app server의 client socket에서 메시지를 보냈지만, 왼쪽 server 코드에서 아무 것도 받지 못한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2RyJ8/btrb226tEJO/kBTGgbWQ3PNc2BZjXHPRy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2RyJ8%2Fbtrb226tEJO%2FkBTGgbWQ3PNc2BZjXHPRy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1197&quot; height=&quot;207&quot; data-filename=&quot;스크린샷, 2021-08-13 01-34-46.png&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;분명 client socket이 연결되고, 오른쪽 Flask app server의 client socket에서 메시지를 보냈지만, 왼쪽 server 코드에서 아무 것도 받지 못한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;## server.py&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628786617946&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import socket
import json
from threading import Thread

from model1.loader import DataLoader
from model1.preprocessor import DataPreprocessor
from model1.model import Model1
from model2.model import Model2
from config import HOST, PORT, DSN


class ClientThread(Thread):
    def __init__(self, host, port):
        Thread.__init__(self)
        self.host = host
        self.port = port
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # prevent WinError 10048 error 
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.bind((self.host, self.port))
        self.server_socket.listen(10)
    
    def run(self):
        while True:
            client_socket, addr = self.server_socket.accept()
            while True:
                client_msg = client_socket.recv(1024)
                if not client_msg:
                    break
                data = client_msg.decode().split()[-1]
                if b'model1' in client_msg:
                    data_loader = DataLoader(dsn=DSN)
                    rows = data_loader.fetch(data=data)
                    data_preprocessor = DataPreprocessor(rows=rows)
                    data = data_preprocessor.preprocess()
                    model = Model1(data)
                else:
                    model = Model2(data)
                
                result = model.predict()
                result_json = json.dumps(result)
                client_socket.sendall(result_json.encode())
            client_socket.close()
        self.server_socket.close()
        
 
 if __name__ == '__main__':
     thread_client = ClientThread(HOST, PORT)
     thread_client.start()
     thread_client.join()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;여러 클라이언트의 요청을 실행하기 위해 쓰레드를 활용하였다. &lt;b&gt;threading.Thread 클래스를 상속받아 쓰레드가 실행할 run() 메소드를 재정의&lt;/b&gt;하면 된다. 각각의 쓰레드에 client 소켓을 보고 있는 server 소켓이 있다. 쓰레드가 시작되기 전까지 server socket은 listen, bind 상태로 client 소켓을 바라 보고(?) 있다. 쓰레드가 시작되어 server 소켓이 client 소켓의 연결을 accept하면, client 소켓과 server 소켓이 연결되고, 이후 통신이 진행된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;그 이후 로직은 간단하다. server 소켓은 client 소켓으로부터의 메시지를 해석하여 메시지에 'Model1'이 있으면 Model1을, 그렇지 않다면 Model2를 호출한다. &lt;i&gt;&lt;s&gt;사실 이렇게 메시지를 해석해 분기 처리하는 것이 하드 코딩이 아닌가 싶긴 했는데, 당장 떠오르는 방법이 없었다.&lt;/s&gt;&lt;/i&gt; 이후 각 모델로부터 predict 결과가 나오면 client 소켓에 다시 메시지와 predict 결과를 보내고, 소켓을 닫는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;start()는 run 메소드를 호출해 쓰레드를 시작하는 역할을, join()은 새로 생성한 자식 스레드의 종료를 기다리는 것으로, 한 프로세스에서 여러 클라이언트의 요청을 처리할 수 있도록 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;이를 구현하는 과정에서 주의해야 할 점은 두 가지가 있었다. 첫째로, 소켓 메시지는 byte string이다. 따라서 server 소켓에서 client 메시지를 해석해 분기 처리를 할 때, b'' 형식의 문자열을 사용해야 한다. 둘째로, &lt;b&gt;소켓 재사용&lt;/b&gt;을 위해서는 socket 플래그로 socket.SO_REUSEADDR를 사용해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;특히 두 번째 경우, 플래그를 추가하지 않으면 아래 그림에서와 같이 에러가 발생한다. 위와 같이 플래그를 추가해주지 않으면, 일일이 해당 프로세스를 죽인 후 서버를 구동해야(kill [pid]) 한다.&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷, 2021-08-13 01-44-12.png&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlMce9/btrb0phiy6n/yYest1oFMRKCFKTddkCZ8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlMce9/btrb0phiy6n/yYest1oFMRKCFKTddkCZ8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlMce9/btrb0phiy6n/yYest1oFMRKCFKTddkCZ8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlMce9%2Fbtrb0phiy6n%2FyYest1oFMRKCFKTddkCZ8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;177&quot; data-filename=&quot;스크린샷, 2021-08-13 01-44-12.png&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;## 테스트&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;테스트를 위해서 다음과 같이 클라이언트 터미널에서 curl 커맨드를 사용해 Flask app으로 요청을 보내면 된다. 아래와 같이 클라이언트 터미널에 dummy data가 response로 반환된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷, 2021-08-13 02-13-54.png&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y6uPB/btrbQ9zVpxb/kTT26NMZCyrdQtOzI0maK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y6uPB/btrbQ9zVpxb/kTT26NMZCyrdQtOzI0maK0/img.png&quot; data-alt=&quot;왼쪽부터 차례로 client, Flask app server, backend server&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y6uPB/btrbQ9zVpxb/kTT26NMZCyrdQtOzI0maK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy6uPB%2FbtrbQ9zVpxb%2FkTT26NMZCyrdQtOzI0maK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1372&quot; height=&quot;283&quot; data-filename=&quot;스크린샷, 2021-08-13 02-13-54.png&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;283&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;왼쪽부터 차례로 client, Flask app server, backend server&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;# 피드백&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;가장 우선적인 피드백은 구조 설계에 관한 것이었다. 애초에 &lt;b&gt;Flask app server와 backend server가 나뉘어야 할 필요가 있는지, 그에 따라 둘 간에 네트워킹의 필요성이 있는지&lt;/b&gt;에 관한 부분이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터가 많고 모델이 무거운 경우라면, 위와 같은 구조로 Flask는 라우팅 역할을 담당하고, backend server에서 모델을 돌리는 게 필요할 수 있다. 그러나 이 서비스의 경우는 그렇게 무겁지 않기 때문에 Flask app server에서 처리하는 것이 더 나아 보인다. &lt;s&gt;&lt;i&gt;(과하게 어렵게 짰다)&lt;/i&gt;&lt;/s&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 소켓 네트워킹 역시 필요하지 않다. 게다가 Flask가 애초에 socket listen, bind 처리를 다 해준다. &amp;rArr; &lt;i&gt;Flask의 동작 원리에 대해 제대로 알지 못한 채 구조를 설계했음을 알 수 있었다. 위의 피드백에서처럼 대용량 데이터 및 큰 모델을 돌려야 하는 경우여서 네트워킹이 필요하다면 모를까, 이 경우에는 애초에 Flask에서 처리하는 작업을 중복으로 하는 코드라고도 볼 수 있다.&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;&lt;b&gt;모듈화&lt;/b&gt;에 대한 피드백도 있었다. 지금 코드에서는 model 부분에 load, 전처리 등의 코드가 모두 있는데, 이를 각 기능별로 나누어, 예컨대 데이터베이스에서 데이터를 로드하는 부분, 전처리하는 부분 등으로 나누어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모듈화를 위해서 먼저 인터페이스부터 작성해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각 모듈 간 이동해야 하는 데이터를 Data Access Object로 정의하는 것이 좋다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;그 외에 코드 구현 레벨에서의 피드백은 다음과 같은 것들이 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;에러 핸들링이 필요하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아마 지금과 같이 backend server를 구현하면, 쓰레드가 생성되지 않을 것처럼 보인다. &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;처음 도전해 보는 서버 개발이었는데, 여러 모로 부족한 점이 많았다. 그럼에도 불구하고 처음부터 끝까지 구조를 설계해 보고, 요청과 응답이 돌아 오는 서버를 미약하게나마 구현해 본 것에 의의를 둔다. 또한, 피드백을 통해 개발에 대한 시야가 넓어질 수 있었다는 것에 매우 감사함을 느낀다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;앞으로 더 연구해 보고 싶은 부분은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인터페이스 작성 방법, 파이썬에서의 인터페이스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;현재 구현된 backend/server.py 코드에서 실제 쓰레드 생성되는지 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Flask 쓰레드 구현 방식 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Flask app에서 client 소켓의 생성 위치에 따른 동작의 차이&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Backend/AI App Server</category>
      <category>flask</category>
      <category>pyodbc</category>
      <category>Python</category>
      <category>server</category>
      <category>앱서버</category>
      <category>파이썬</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/57</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/App-Server-Flask-Socket%EC%9C%BC%EB%A1%9C-%EC%95%B1-%EC%84%9C%EB%B2%84%EB%A5%BC-%EA%B5%AC%EC%B6%95%ED%95%98%EB%A9%B0-%EA%B2%AA%EC%9D%80-%EC%8B%9C%ED%96%89%EC%B0%A9%EC%98%A4#entry57comment</comments>
      <pubDate>Fri, 13 Aug 2021 02:34:55 +0900</pubDate>
    </item>
    <item>
      <title>[App Server] ODBC를 이용해 Tibero와 Python 연동하기</title>
      <link>https://projectlog-eraser.tistory.com/entry/ODBC-Tibero%EC%99%80-Python-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;회사에서 애플리케이션 API 서버를 개발하며, &lt;b&gt;Tibero에 적재된 데이터를 불러오는 모듈&lt;/b&gt;을 개발할 일이 생겼다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Tibero 데이터 I/O 모듈의 위치는 다음 그림에서와 같고, 데이터베이스로부터 데이터를 추출해 Preparation 모듈로 넘기는 역할을 담당한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGK0c/btraRxgeRU0/q6ipyCLE21C9GxwwUhSU7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGK0c/btraRxgeRU0/q6ipyCLE21C9GxwwUhSU7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGK0c/btraRxgeRU0/q6ipyCLE21C9GxwwUhSU7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGK0c%2FbtraRxgeRU0%2Fq6ipyCLE21C9GxwwUhSU7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;856&quot; height=&quot;460&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 API 서버 및 애플리케이션 내 AI 모델을 Python으로 개발할 예정이기 때문에, Tibero 데이터 I/O 모듈도 Python으로 개발하고자 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Tibero의 tbCLI는 DBMS와의 연결을 지원하기 위해 ODBC, JDBC를 지원한다. 사실 Python에는 각 데이터베이스와 연동을 지원하는 라이브러리가 있는데(예컨대, mysql과 연동하기 위해서는 pymysql 등을 사용하면 된다.), 아직 Tibero와의 연동을 지원하는 라이브러리는 없다. 다만, &lt;b&gt;Tibero는 ODBC 방식을 사용할 수 있도록 Tibero ODBC 드라이버를 제공하고 있고, 공식 문서에 Python과 연동하기 위해 pyodbc 라이브러리를 사용하라고 설명&lt;/b&gt;하고 있다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 글은 Tibero 데이터 I/O 모듈을 개발하는 과정에서 어떻게 ODBC 드라이버를 설정하고, pyodbc를 통해 Python과 연동할 수 있는지를 다룬다. &lt;s&gt;&lt;i&gt;사실 며칠 동안 너무 많은 삽질을 한 게 아까워서 기억하고 싶어서..&lt;/i&gt;&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Windows 10과 Ubuntu 20.04 환경에서 모두 개발을 진행해 보았다. OS는 다르지만, 모두 다음과 같은 동일한 과정을 거친다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Tibero 클라이언트 설치&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ODBC 설치&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ODBC DSN 설정&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;처음에는 운영체제가 다른 것에 익숙하지 않아 어려움을 많이 겪었는데, 이후 정리하면서 보니 기본적으로 GUI에서 진행하는지 터미널에서 진행하는지, 파일 경로가 어디에 위치하는지 정도에서 차이가 나고, 각 단계별 진행 과정은 별반 다르지 않음을 깨달을 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# Windows 10&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## tbCLI 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;티베로6 클라이언트를 다운로드한 뒤, 다음과 같이 환경변수를 설정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxEqO6/btraLtzlxYs/baU64nFWBruGEcW0sKJnpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxEqO6/btraLtzlxYs/baU64nFWBruGEcW0sKJnpK/img.png&quot; data-alt=&quot;TB_HOME 환경변수를 설정한다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxEqO6/btraLtzlxYs/baU64nFWBruGEcW0sKJnpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxEqO6%2FbtraLtzlxYs%2FbaU64nFWBruGEcW0sKJnpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;432&quot; height=&quot;340&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TB_HOME 환경변수를 설정한다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## tbodbc 드라이버 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;이후 tbodbc 드라이버를 다운로드해 설치한다. &lt;b&gt;Windows 10 64비트 운영체제이더라도, 32비트 클라이언트 프로그램을 다운로드 받아 설치&lt;/b&gt;해야 한다. &lt;s&gt;&lt;i&gt;엄청난 삽질의 시작이었다.&lt;/i&gt;&lt;/s&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;tibero6을 64비트 버전으로 다운로드했더니, (당연히) 안에 포함되어 있는 ODBC 드라이버는 64비트 버전이었다. 그런데 이 드라이버를 설치하면, ODBC 데이터 원본(64비트)에서 Tibero 드라이버를 아예 발견할 수가 없다. 64비트 드라이버 자체가 레지스트리에 아예 등록이 되지 않았는데, 이유를 알고 싶어서 구글링해 봐도 32비트 프로그램을 받아야 된다는 글밖에 찾을 수 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mGlgV/btraNIW7iCE/Vs1BLRLFlzfT4RTKUW250k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mGlgV/btraNIW7iCE/Vs1BLRLFlzfT4RTKUW250k/img.png&quot; data-alt=&quot;64비트 드라이버를 설치한 후에도 드라이버를 찾을 수 없다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mGlgV/btraNIW7iCE/Vs1BLRLFlzfT4RTKUW250k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmGlgV%2FbtraNIW7iCE%2FVs1BLRLFlzfT4RTKUW250k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;196&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;64비트 드라이버를 설치한 후에도 드라이버를 찾을 수 없다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;32비트 드라이버를 설치한 후, ODBC 데이터 원본 관리자(32비트)를 열어 확인하면, 다음과 같이 Tibero ODBC 드라이버가 잘 등록된 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh1dPm/btraPu5oxSc/9tcYt2ydVBXZplq1TTfye0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh1dPm/btraPu5oxSc/9tcYt2ydVBXZplq1TTfye0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh1dPm/btraPu5oxSc/9tcYt2ydVBXZplq1TTfye0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh1dPm%2FbtraPu5oxSc%2F9tcYt2ydVBXZplq1TTfye0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;418&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; font-family: 'Noto Sans Light';&quot;&gt;참고: ODBC 데이터 원본 관리자 위치&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;윈도우 검색 창에서 ODBC 데이터 원본 관리자를 검색하면 되지만, 사실 32비트와 64비트 원본 관리자는 서로 다른 경로에 저장되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ODBC 데이터 원본 관리자(32비트): &lt;span style=&quot;color: #000000;&quot;&gt;C:\Windows\SysWOW64\odbcad32.exe&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ODBC 데이터 원본 관리자(64비트): &lt;span style=&quot;color: #000000;&quot;&gt;C:\Windows\System32\odbcad32.exe&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; 32비트와 64비트에서 돌아가는 프로그램은 서로 다른 경로에서 관리되는 듯하다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어찌 되었든, 이런 이유로 Windows 10 환경에서 개발을 할 때는, 이후 Python 32비트 인터프리터로 개발해야 한다. Python 64비트 인터프리터를 사용하면, 이후 단계에서 다 성공을 했더라도 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;'[ODBC] 지정된 DSN은 드라이버와 응용 프로그램 간 아키텍처 불일치를 포함합니다.'&lt;/b&gt;&lt;/span&gt;와 같은 에러를 마주하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## DSN 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ODBC 데이터 원본 관리자에서 Tibero ODBC 드라이버를 찾을 수 있으면, DSN을 설정하면 된다. DSN은 &lt;b&gt;접근하고자 하는 데이터베이스 내 데이터 원본, 그리고 그 원본에 접근하기 위해 필요한 ODBC 연결&lt;/b&gt;에 대한 정보를 담고 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;시스템 DSN은 시스템 전체에서 사용되는 DSN으로, 권한이 있는 모든 사용자가 로그인할 수 있다. 반면, 사용자 DSN은 특정 사용자에 대해서만 만든 DSN이다. 지금은 GUI로 쉽게 구분되지만, 이후 Ubuntu에서 설정할 때는 설정 파일의 경로가 달라지므로 주의해야 한다. 일단 지금 단계에서는 사용자 별로 DSN을 다르게 설정해야 할 이유는 없으므로&lt;s&gt;&lt;i&gt;(추후 도커 이미지를 만드는 과정에서는 달라질 수 있으려나)&lt;/i&gt;&lt;/s&gt;, 시스템 DSN을 설정한다. DSN 설정 시&lt;b&gt; 데이터베이스 접근을 위한&lt;/b&gt; 서버 IP, Port 등을 입력한다. 해당 정보는 이후 우분투에서 DSN을 설정할 때에도 그대로 사용된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;설정 후, Test 버튼을 클릭해 연결이 성공하는지 확인해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3CTqv/btraWTikh6F/s8kMrMQHbnCI3bGsBOTNS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3CTqv/btraWTikh6F/s8kMrMQHbnCI3bGsBOTNS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3CTqv/btraWTikh6F/s8kMrMQHbnCI3bGsBOTNS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3CTqv%2FbtraWTikh6F%2Fs8kMrMQHbnCI3bGsBOTNS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;479&quot; height=&quot;398&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# Linux(Ubuntu 20.04)&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기본적으로 ODBC는 마이크로소프트에서 개발한 것이다. 이를 NIX 계열 운영체제에서 사용하기 위해서는 다른 라이브러리를 사용해야 하는데, 주로 Unix ODBC나 iODBC가 사용된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Tibero 공식 문서에서는 iODBC를 기준으로 설정하는 방법을 설명하고 있지만, 나는 Unix ODBC를 이용해 연결을 진행했기 때문에 해당 방법을 기록하도록 한다. 다행히(?) Linux에서는 tbCLI와 ODBC 드라이버가 동일하게 64비트여도 된다. 이 부분에서는 훨씬 간편했지만, 명령어, 커맨드 창이 익숙하지 않아 솔찬히 삽질했다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## tbCLI 다운로드&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Linux 64비트 tbCLI 프로그램을 설치한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## odbc 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;apt 패키지 매니저를 통해 unixodbc, odbcinst를 설치한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1652759717434&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apt install unixodbc
apt install odbcinst&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;설치가 잘 되었는지 확인해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1652759758492&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;eraser@eraser-desktop:/etc$ which isql
/usr/bin/isql

eraser@eraser-desktop:/etc$ odbcinst -j
unixODBC 2.3.9
DRIVERS............: /etc/odbcinst.ini
SYSTEM DATA SOURCES: /etc/odbc.ini
FILE DATA SOURCES..: /etc/ODBCDataSources
USER DATA SOURCES..: /home/eraser/.odbc.ini
SQLULEN Size.......: 8
SQLLEN Size........: 8
SQLSETPOSIROW Size.: 8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## ODBC 드라이버 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;tibero6을 설치한 경로에 보면, &lt;b&gt;tibero6/client/lib에 libtbodbc.so 파일&lt;/b&gt;이 있는 것을 확인할 수 있다. 이 파일이 Linux 운영체제에서의 Tibero ODBC 드라이버이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해당 드라이버를 사용하기 위해, ODBC 드라이버 위치를 지정하는 odbcinst.ini 파일을 작성한다. 경로는 /etc/odbcinst.ini이다. &lt;i&gt;&quot;ODBC 방식으로 Tibero에 연결하고자 할 때는 이 드라이버를 사용해라&quot;&lt;/i&gt; 정도로 이해하자. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpQtmS/btraQrm3VeC/Uo3PKe4PRj0afkKjzode7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpQtmS/btraQrm3VeC/Uo3PKe4PRj0afkKjzode7K/img.png&quot; data-alt=&quot;odbcinst.ini 파일의 Driver, Setup에 $TB_HOME 환경변수를 사용하면, 인식하지 못한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpQtmS/btraQrm3VeC/Uo3PKe4PRj0afkKjzode7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpQtmS%2FbtraQrm3VeC%2FUo3PKe4PRj0afkKjzode7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;130&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;odbcinst.ini 파일의 Driver, Setup에 $TB_HOME 환경변수를 사용하면, 인식하지 못한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;odbcinst configuration 정보는 터미널에서 odbcinst -j 명령어를 통해 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8905z/btraNIb9wEF/A9XgkNFeobhXJJL4Oz8FEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8905z/btraNIb9wEF/A9XgkNFeobhXJJL4Oz8FEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8905z/btraNIb9wEF/A9XgkNFeobhXJJL4Oz8FEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8905z%2FbtraNIb9wEF%2FA9XgkNFeobhXJJL4Oz8FEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;160&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;## DSN 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;상술했듯, Linux 운영체제에서는 시스템 DSN과 사용자 DSN 설정 파일의 경로가 달라진다. 시스템 DSN은 &lt;b&gt;root 디렉토리&lt;/b&gt; 하에, 사용자 DSN은 &lt;b&gt;~ 디렉토리&lt;/b&gt; 하에 존재하며, 파일 이름도 전자의 경우 odbc.ini, 후자의 경우 .odbc.ini가 된다. 즉, &lt;b&gt;시스템 DSN 설정은 /etc/odbc.ini에서, 사용자 DSN 설정은 ~/.odbc.ini에서&lt;/b&gt; 한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아래와 같이 시스템 DSN을 설정하자. [ODBC]에는 로그 파일의 추적 여부 및 그 단계, 추적 파일의 경로를 지정하고, 그 아래에 DSN 연결 시 사용할 정보를 작성하면 된다. 이 때 설정한 DSN 이름을 이후 접속 단계에서 계속 사용하게 된다. 나는 [tibero]로 설정했다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rvb07/btraWSqyiM7/G8mkn9yPTy2tJCt6H0bny0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rvb07/btraWSqyiM7/G8mkn9yPTy2tJCt6H0bny0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rvb07/btraWSqyiM7/G8mkn9yPTy2tJCt6H0bny0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRvb07%2FbtraWSqyiM7%2FG8mkn9yPTy2tJCt6H0bny0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;274&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;2021.09.25. 내용 추가&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;간혹 dsn 연결 오류가 생기면, 설정 후&lt;b&gt; `odbcinst -i -s -f [odbc.ini 파일 경로]`&lt;/b&gt; 명령을 통해 Datasource를 등록해 주었는지 확인해 보자.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;odbc.ini 파일을 설정할 때, 위의 사진처럼 Driver 경로를 전부 다 명시해 주어도 되지만, 아래와 같이 odbcinst.ini에 설정해 놓은 Driver 이름을 그대로 사용해도 된다.&lt;/span&gt;
&lt;pre id=&quot;code_1632548516897&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[tibero]
Description=Tibero6 data source
Driver=Tibero
SERVER=
...&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;설정 후 접속이 되는지 확인해 보자. 터미널에서 isql 명령어를 사용하면 된다. 만약 위에서 DSN 정보에 사용자와 패스워드 정보를 설정하지 않았다면, 인자로 사용자와 패스워드까지 인자로 넘겨야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rsi7N/btraTa6ijZg/M2hT0Z74yatKizi5mvkA11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rsi7N/btraTa6ijZg/M2hT0Z74yatKizi5mvkA11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rsi7N/btraTa6ijZg/M2hT0Z74yatKizi5mvkA11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frsi7N%2FbtraTa6ijZg%2FM2hT0Z74yatKizi5mvkA11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;158&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;# Python에서 데이터 불러 오기&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;위와 같이 DSN 설정을 완료한 뒤, 접속에 성공하는 것까지 확인했다면, Python에서 연동할 차례다. &lt;b&gt;pyodbc 라이브러리를 사용해&lt;/b&gt; 아래 코드를 작성하면 된다. &quot;x&quot;라는 데이터베이스 테이블에 접근해 각 컬럼의 정보를 출력하는 간단한 코드이다. 연결 시 연결 문자열에는 DSN 정보를 넣어 주면 된다. 위에서와 마찬가지로 DSN 정보에 사용자와 패스워드 정보가 존재하지 않는다면, 연결 문자열에 UID, PWD를 추가해주어야 한다. 또한, Windows 10 환경에서는 Python3 32bit를 설치해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import pyodbc as odbc

conn = odbc.connect(&quot;DSN=tibero&quot;) 
cursor = conn.cursor() 

for row in cursor.columns(table=&quot;x&quot;): 
    print(row)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위와 같은 과정을 거쳐 Tibero 데이터 I/O 모듈 개발을 위한 기본 작업을 마칠 수 있었다. 적지 않은 고생을 했는데, 돌이켜 보니 별 것 아닌데 내가 너무 몰라서 그랬던 것 같기도. 감사하게도 잘 정리된 글들 덕에 많이 배울 수 있었다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://jinisbonusbook.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jinisbonusbook.tistory.com/65&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;파이썬(python) 티베로 DB 접속 방법 - ODBC&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;이번 포스팅은 파이썬에서 데이터베이스에 연동하는 방법에 대한 내용입니다. 데이터베이스는 오라클, MS-SQL, mysql , PostgreSQL 등등 여러가지가 있습니다. 이중 국산제품인 데이터베이스인 티베로&quot; data-og-host=&quot;jinisbonusbook.tistory.com&quot; data-og-source-url=&quot;https://jinisbonusbook.tistory.com/65&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkBNys/hyK3CfnpyK/PxDrQJONcClNtLvFVKxGZk/img.jpg?width=800&amp;amp;height=393&amp;amp;face=0_0_800_393,https://scrap.kakaocdn.net/dn/cb4nBS/hyK3MI3Q4w/Kls0EEMvClmkf20gbMEhE1/img.jpg?width=800&amp;amp;height=393&amp;amp;face=0_0_800_393,https://scrap.kakaocdn.net/dn/bRtxrt/hyK4Oeb3bT/6jCfknrnqcrIomxfhDjy7K/img.jpg?width=931&amp;amp;height=458&amp;amp;face=0_0_931_458&quot; data-og-url=&quot;https://jinisbonusbook.tistory.com/65&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://jinisbonusbook.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jinisbonusbook.tistory.com/65&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkBNys/hyK3CfnpyK/PxDrQJONcClNtLvFVKxGZk/img.jpg?width=800&amp;amp;height=393&amp;amp;face=0_0_800_393,https://scrap.kakaocdn.net/dn/cb4nBS/hyK3MI3Q4w/Kls0EEMvClmkf20gbMEhE1/img.jpg?width=800&amp;amp;height=393&amp;amp;face=0_0_800_393,https://scrap.kakaocdn.net/dn/bRtxrt/hyK4Oeb3bT/6jCfknrnqcrIomxfhDjy7K/img.jpg?width=931&amp;amp;height=458&amp;amp;face=0_0_931_458');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;파이썬(python) 티베로 DB 접속 방법 - ODBC&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅은 파이썬에서 데이터베이스에 연동하는 방법에 대한 내용입니다. 데이터베이스는 오라클, MS-SQL, mysql , PostgreSQL 등등 여러가지가 있습니다. 이중 국산제품인 데이터베이스인 티베로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jinisbonusbook.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://blog.boxcorea.com/wp/archives/2881&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.boxcorea.com/wp/archives/2881&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Ubuntu+Tibero+Python3+ODBC 연결하기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Ubuntu 18.04에서 python3과 tibero 데이타베이스 연동하기 Tibero는 python 드라이버를 지원하지 않으므로, ODBC를 통해서 연결해야한다. 우분투리눅스에 ODBC를 아래 명령어로 설치한다. [crayon-6104971033ecb5640&quot; data-og-host=&quot;blog.boxcorea.com&quot; data-og-source-url=&quot;https://blog.boxcorea.com/wp/archives/2881&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baWaME/hyK3OUpubA/R1BOzbkQQ8IukJCEojFc7k/img.jpg?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot; data-og-url=&quot;https://blog.boxcorea.com/wp/archives/2881&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://blog.boxcorea.com/wp/archives/2881&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.boxcorea.com/wp/archives/2881&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baWaME/hyK3OUpubA/R1BOzbkQQ8IukJCEojFc7k/img.jpg?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Ubuntu+Tibero+Python3+ODBC 연결하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Ubuntu 18.04에서 python3과 tibero 데이타베이스 연동하기 Tibero는 python 드라이버를 지원하지 않으므로, ODBC를 통해서 연결해야한다. 우분투리눅스에 ODBC를 아래 명령어로 설치한다. [crayon-6104971033ecb5640&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.boxcorea.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://blog.neonkid.xyz/179&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.neonkid.xyz/179&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;리눅스에서 Unix ODBC를 사용해보자&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;안녕하세요. 2018년의 연말이 다가왔습니다. 오늘은 2018년의 마지막 포스트로 우분투에서 Unix ODBC를 설정하는 방법에 대해 알아보도록 하겠습니다. What is ODBC? Unix ODBC를 설치하기 전에, ODBC가 무엇&quot; data-og-host=&quot;blog.neonkid.xyz&quot; data-og-source-url=&quot;https://blog.neonkid.xyz/179&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/m8h7T/hyK3HgHELA/N5bkqhkaUpH7Dfw4cj2m1K/img.jpg?width=386&amp;amp;height=299&amp;amp;face=0_0_386_299,https://scrap.kakaocdn.net/dn/dnwuAG/hyK4RhGAOY/76kGon5IKe8v5xHYixW22K/img.jpg?width=386&amp;amp;height=299&amp;amp;face=0_0_386_299,https://scrap.kakaocdn.net/dn/KAvr4/hyK4Oyt8lE/CGJMQG77Gkm4i11HegLr9K/img.jpg?width=1280&amp;amp;height=520&amp;amp;face=0_0_1280_520&quot; data-og-url=&quot;https://blog.neonkid.xyz/179&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://blog.neonkid.xyz/179&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.neonkid.xyz/179&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/m8h7T/hyK3HgHELA/N5bkqhkaUpH7Dfw4cj2m1K/img.jpg?width=386&amp;amp;height=299&amp;amp;face=0_0_386_299,https://scrap.kakaocdn.net/dn/dnwuAG/hyK4RhGAOY/76kGon5IKe8v5xHYixW22K/img.jpg?width=386&amp;amp;height=299&amp;amp;face=0_0_386_299,https://scrap.kakaocdn.net/dn/KAvr4/hyK4Oyt8lE/CGJMQG77Gkm4i11HegLr9K/img.jpg?width=1280&amp;amp;height=520&amp;amp;face=0_0_1280_520');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;리눅스에서 Unix ODBC를 사용해보자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 2018년의 연말이 다가왔습니다. 오늘은 2018년의 마지막 포스트로 우분투에서 Unix ODBC를 설정하는 방법에 대해 알아보도록 하겠습니다. What is ODBC? Unix ODBC를 설치하기 전에, ODBC가 무엇&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.neonkid.xyz&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞으로 모듈을 개발하는 과정에서 더 연구해 보고 싶은 부분은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자가 매 번 앱에 요청을 날릴 때마다 DSN을 설정해 주어야 하는가?&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DSN 설정 시 모듈에서 configuration 정보를 어디다 보관하고, 어떻게 설정해야 하는가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DSN 설정을 위한 shell script를 어떻게 작성해야 하는가&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Django에서 pyodbc를 연결해 데이터를 추출할 수 있는가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/AI App Server</category>
      <category>ODBC</category>
      <category>pyodbc</category>
      <category>tibero</category>
      <category>데이터베이스</category>
      <category>티베로</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/56</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/ODBC-Tibero%EC%99%80-Python-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0#entry56comment</comments>
      <pubDate>Fri, 30 Jul 2021 17:48:36 +0900</pubDate>
    </item>
    <item>
      <title>어디다 기록할 지 몰라 여기로 온 Pandas 사용법</title>
      <link>https://projectlog-eraser.tistory.com/entry/%EC%96%B4%EB%94%94%EB%8B%A4-%EA%B8%B0%EB%A1%9D%ED%95%A0-%EC%A7%80-%EB%AA%B0%EB%9D%BC-%EC%97%AC%EA%B8%B0%EB%A1%9C-%EC%98%A8-Pandas-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp; 프로젝트 도중 데이터 분석이 필요할 때면, Pandas 쓸 일이 많다. 여기 저기서 자주 사용했는데, 정리하기에는 너무 사소해서 정리하지 않다 보니 자꾸 또 다시 찾아보게 된다. 사소하지만 내 기준에서 유용하고, 까먹고 싶지 않은 Pandas 사용법을 몰아 넣어 정리해 본다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;중복 답변 개수 체크&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;프로젝트 기획 단계에서 설문 중복 응답을 분류할 때 사용했던 코드이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1619958854353&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['앱이름_수정'] = df['앱이름'].str.replace(r&quot;\([^()]*\)&quot;, &quot;&quot;) # 괄호 안 설명 제거
new_df = df['앱이름_수정'].str.split(',', expand=True) # 중복 답안 각각의 컬럼으로 expand
new_df.stack().reset_index().value_counts([0]) # stack 후 개수 세기&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; '[서비스명 후보](서비스명 후보에 대한 설명)' 형태로, 중복 응답이 가능했던 문항에 대한 응답을 분류할 때 유용했다. 다른 건 다 괜찮고, 정규표현식을 사용해 서비스명에 대한 설명을 제거하는 과정이 좀 귀찮았기에 기록한다. &lt;s&gt;&lt;i&gt;사실 정규표현식은 쓸 때마다 찾아봐야 해서 넘나리 어렵..&lt;/i&gt;&lt;/s&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;\(, \): `(`로 시작해서 `)`로 끝나는 문자열&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;[]*: `[]` 안의 패턴과 일치하는 문자열 모두&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;^(): `()` 안에 등장하는 문자열을 제외한 모두. 여기서는 ()안에 아무 것도 없으므로 모든 문자&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg01uZ/btq3Vg0klLd/s8bvAM5MJTEYZfrncQpWY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg01uZ/btq3Vg0klLd/s8bvAM5MJTEYZfrncQpWY1/img.png&quot; data-alt=&quot;처음 설문 응답 데이터의 정제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg01uZ/btq3Vg0klLd/s8bvAM5MJTEYZfrncQpWY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg01uZ%2Fbtq3Vg0klLd%2Fs8bvAM5MJTEYZfrncQpWY1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;처음 설문 응답 데이터의 정제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>기타</category>
      <author>eraser</author>
      <guid isPermaLink="true">https://projectlog-eraser.tistory.com/48</guid>
      <comments>https://projectlog-eraser.tistory.com/entry/%EC%96%B4%EB%94%94%EB%8B%A4-%EA%B8%B0%EB%A1%9D%ED%95%A0-%EC%A7%80-%EB%AA%B0%EB%9D%BC-%EC%97%AC%EA%B8%B0%EB%A1%9C-%EC%98%A8-Pandas-%EC%82%AC%EC%9A%A9%EB%B2%95#entry48comment</comments>
      <pubDate>Sun, 2 May 2021 21:35:16 +0900</pubDate>
    </item>
  </channel>
</rss>