인프라/Web Server & WAS 2020. 8. 22. 15:43

오늘 포스팅해볼 내용은 Web server 중 하나인 Nginx의 설치 및 사용방법에 대해 다루어본다. 우선 Nginx는 무엇인가 알아보자.

 

예제 설정은 아래 깃헙사이트에 있다.

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

Wiki(https://ko.wikipedia.org/wiki/Nginx)
Nginx(엔진 x라 읽는다)는 웹 서버 소프트웨어로, 가벼움과 높은 성능을 목표로 한다. 웹 서버, 리버스 프록시 및 메일 프록시 기능을 가진다.
2017년 10월 기준으로 실질적으로 작동하는 웹 사이트(active site)들에서 쓰이는 웹 서버 소프트웨어 순위는 아파치(44.89%), 엔진엑스(20.65%), 구글 웹 서버(7.86%), 마이크로소프트 IIS(7.32%)순이다.[1] 이 조사에서 생성은 되어있으나 정상적으로 작동하지 않는 웹 사이트들은 배제되었으며[2] 특히 MS의 인터넷 정보 서비스(IIS)를 설치한 웹 사이트들의 상당수가 비활성 사이트였다. 그런 사이트들도 포함하면 MS IIS가 1위이다. 2017년 6월 현재 Nginx는 한국 전체 등록 도메인 중 24.73%가 사용하고 있다.[3]
Nginx는 요청에 응답하기 위해 비동기 이벤트 기반 구조를 가진다. 이것은 아파치 HTTP 서버의 스레드/프로세스 기반 구조를 가지는 것과는 대조적이다. 이러한 구조는 서버에 많은 부하가 생길 경우의 성능을 예측하기 쉽게 해준다.

 

또한 nginx는 하나의 마스터 프로세스와 여러 워커 프로세스가 있고, 마스터 프로세스는 주로 설정 파일을 읽고 적용하며 워커 프로세스들을 관리하는 역할을 하게 된다. 워커 프로세스는 실제 요청에 대한 처리를 하게 된다. nginx는 event driven 모델을 메커니즘으로 사용하여 실제 워커 프로세스간 요청을 효율적으로 분산한다.

 

실습은 Mac os 기준으로 실습을 진행해 볼것이다. 우선 nginx를 설치해보자.

 

Nginx install
> brew install nginx

 

brew로 설치를 아래와 같은 디렉터리들이 생성된다. 우선 아래 디렉토리를 실습을 진행하면서 전부 알아볼 것이다.

 

Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
  brew services start nginx
Or, if you don't want/need a background service you can just run:
  nginx
==> Summary
🍺  /usr/local/Cellar/nginx/1.19.2: 25 files, 2.1MB
==> Caveats
==> nginx
Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
  brew services start nginx
Or, if you don't want/need a background service you can just run:
  nginx

 

Nginx 구동 명령어(nginx -s <signal>
  • nginx : 서버시작
  • nginx -s stop : 서버종료(워커들이 요청을 처리중이더라도 그냥 종료한다.)
  • nginx -s quit : 워커 프로세스가 현재 요청 처리를 완료할 때까지 대기하고 모두 처리완료된 후에 서버 종료.
  • nginx -s reload : nginx config를 새로 로드한다. 마스터 프로세스가 설정을 다시 로드하라는 요청을 받으면 설정 유효성 검사후 새로운 워커 프로세스를 시작하고, 이전 워커 프로세스에게 종료 메시지를 보내게 되고 이전 워커 프로세스는 요청을 완료하게 되면 종료된다.

위 명령어로 nginx를 시작 해보자 !

 

> nginx
> lsof -i:8080
COMMAND   PID         USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
nginx   88891 yun-yeoseong    6u  IPv4 0x7370b7ed168f296f      0t0  TCP *:http-alt (LISTEN)
nginx   88892 yun-yeoseong    6u  IPv4 0x7370b7ed168f296f      0t0  TCP *:http-alt (LISTEN)
#실행중인 모든 nginx 프로세스 목록을 가져온다.
> ps -ax | grep nginx
88891 ??         0:00.00 nginx: master process nginx
88892 ??         0:00.01 nginx: worker process
89201 ttys000    0:00.03 vi nginx.conf
89695 ttys001    0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn nginx

 

디폴트 포트인 8080으로 nginx 프로세스가 잘 떠있다. 이제 웹브라우저에서 localhost:8080으로 접속해보자.

 

> curl localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

 

 

브라우저에 welcome to nginx가 보인다면 설치 및 실행이 잘된 것이다 ! 어 그렇다면, 여기서 조금 의아한 것이 있을 것이다. 과연 저 html은 어디서 응답을 준것일까?

 

Docroot

답은 도큐먼트 루트에 있다. 설치를 하면 아래와 같은 로그가 출력되어있을 것인데, 해당 디렉토리 내에 html 파일이 존재한다.

 

Docroot is: /usr/local/var/www

 

기본적으로 웹서버는 다른 서버로 프록시 하지 않는 이상 uri로 명시한 path로 도큐먼트 루트 디렉토리를 찾아서 응답을 주게 된다. 사실 localhost:8080은 localhost:8080/index.html과 같다고 보면된다. 그렇다면 index.html의 위치를 바꾸면 어떻게 될까?

 

> cd /usr/local/var/www
> mkdir backup
> mv index.html ./backup

 

이제 아래 요청을 보내보자.

 

> curl localhost:8080/index.html
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.19.2</center>
</body>
</html>

 

우리는 index.html을 다른 디렉토리로 옮겼기 때문에 404 not found가 뜨게 된다. 그렇다면 옮긴 디렉토리 path를 명시해서 요청을 보내보자.

 

> curl http://localhost:8080/backup/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

 

응답이 잘 도착하였다. 보통 도큐멘트 루트는 어떻게 사용이 될까? 보통은 정적인 리소스 파일(css, html)을 위치시키게 된다. 그렇다면 정적인 리로스 파일을 위치시키는 이유는 무엇일까? 만약 WAS에 해당 정적인 리소스 파일을 위치시키게 되면, 사실상 서버 동작과 관련이 적은 정적 리소스를 가져오기 위한 요청도 모두 WAS로 들어가기 때문에 앱에 부하가 많이 가게 될수 있다. 그렇기 때문에 보통 정적인 리소스는 nginx(웹서버)에서 처리하고 WAS는 백엔드 데이터만 제공하게 하여 WAS의 부담을 줄여줄 수 있다.

 

이제는 본격적으로 Nginx의 설정을 커스터마이징해보자.

 

Configuration file's structure

nginx의 설정 파일은 simple directives(단순 지시문)과 block directives(블록 지시문)으로 나뉜다. 단순 지시문을 공백으로 구분 된 이름과 매개변수로 구성되며 세미콜론(;)으로 끝난다. 블록 지시문은 단순 지시문과 구조가 동일하지만 세미콜론 대신 중괄호({})로 명령 블록을 지정한다. 또한 블록지시문을 블록지시문의 중첩구조로도 이루어 질 수 있다. 이러한 지시문으로 nginx에 플러그인 된 여러 모듈을 제어하게 된다.

 

Nginx Configuration

nginx.conf 파일에는 nginx의 설정 내용이 들어간다. 해당 파일의 전체적인 구조(모듈)는 아래와 같이 이루어져있다.

 

user  nginx;
worker_processes  1;

error_log  logs/error.log;

events {
    worker_connections  1024;
}
http { 
    include       mime.types;
    #응답의 기본 default mime type을 지정
    default_type  application/octet-stream;
    
    charset utf-8;
    
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';
    
    access_log  /var/log/nginx/access.log  main;
    
	#지정된 에러 코드에 대해 응답나갈 document root의 html 파일을 지정
    #docroot의 html말고 다른 URL로 리다이렉션 가능하다.
    error_page 500 502 503 504 /50x.html;
    #error_page 500 502 503 504 http://example.com/error.html
    
    sendfile        on;
    tcp_nopush     on;
    
    keepalive_timeout  65;
    #keepalive로 유지되는 커넥션으로 최대 처리할 요청수를 지정
    #keepalive_requests 100;    
    
    #nginx의 버전을 숨길 것인가에 대한 옵션이다. 보안상 활성화하는 것을 권장한다.
    server_tokens            on;
    #응답 컨텐츠를 압축하는 옵션, 해당 옵션말고 gzip관련 다양한 옵션 존재(압축 사이즈 등등)
    gzip  on;
    
    #context : http, server, location
    #클라이언트 요청 본문을 읽기 위한 버퍼 크기를 설정 64bit platform default 16k
    client_body_buffer_size 16k;
    #클라이언트 요청 본문을 읽기 위한 타임아웃 시간 설정
    client_body_timeout 60s;
    #클라이언트 요청 헤더를 읽기위한 버퍼 크기 설정
    client_header_buffer_size 1k;
    client_header_timeout 60s;
    #클라이언트가 보낸 요청 본문의 최대 사이즈
    client_max_body_size 1m;
    
    server {
        listen       80;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

 

  • Core 모듈 설정 : 위 예제의 worker_processes와 같은 지시자 설정 파일 최상단에 위치하면서 nginx의 기본적인 동작 방식을 정의한다.
  • http 모듈 블록 : 밑에서 설명할 server, location의 루트 블록이라고 할 수 있고, 여기서 설정된 값을 하위 블록들은 상속한다. http 블록은 여러개를 사용할 수 있지만 관리상의 이슈로 한번만 정의하는 것을 권장한다. http, server, location 블록은 계층구조를 가지고 있고 많은 지시어가 각각의 블록에서 동시에 사용될 수 있는데,  http의 내용은 server의 기본값이 되고, server의 지시어는 location의 기본값이 된다. 그리고 하위의 블록에서 선언된 지시어는 상위의 선언을 무시하고 적용된다.
  • server 블록 : server 블록은 하나의 웹사이트를 선언하는데 사용된다. 가상 호스팅(vhost)의 개념이다.
  • location 블록 : location 블록은 server 블록 안에 정의하며 특정 URL을 처리하는 방법을 정의한다. 예를 들어 uri path마다 다르게 요청을 처리하고 싶을 때 해당 블록 내에 정의한다.
  • events 블록 : nginx는 event driven을 메커니즘으로 동작하는데, 이 event driven 동작 방식에 대한 설정을 다룬다.

 

nginx.conf

"user"

user의 값이 root로 되어 있다면 일반 계정으로 변경하는 것이 좋다. nginx는 마스터 프로세스와 워커 프로세스로 동작하고, 워커 프로세스가 실질적인 웹서버의 역할을 수행하는데 user 지시어는 워커프로세스의 권한을 지정한다. 만약 user의 값이 root로 되어 있다면 워커 프로세스를 root의 권한으로 동작하게 되고, 워커 프로세스를 악의적으로 사용자가 제어하게 된다면 해당 머신을 루트 사용자의 권한으로 원격제어하게 되는 셈이기 때문에 보안상 위험하다.

 

user 설정의 값으로는 대표성있는 이름(nginx)로 사용하고, 이 계정은 일반 유저의 권한으로 쉘에 접속할 수 없어야 안전하다.

 

> useradd --shell /sbin/nologin www-data

 

"worker_process"

worker_process는 워커 프로세스를 몇개 생성할 것인지를 지정하는 지시어이다. 이 값이 1이라면 모든 요청을 하나의 프로세스로 실행하겠다는 뜻인데, 여러개의 CPU 코어가 있는 시스템이라면 CPU 코어수만큼 지정하길 권장한다.

 

"events.worker_connections"

이 값은 몇개의 접속을 동시에 처리할 것인가를 지정하는 값이다. 이 값과 worker_process의 값을 조합해 동시에 최대로 처리할 수 있는 커넥션의 양을 산출할 수 있다.(worker_process*worker_connections)

 

"http.incloud"

가상 호스트 설정이나, 반복되는 설정들을 파일로 저장해놓고, incloude를 통해 불러올 수 있다.

 

"http.log_format"

access 로그에 남길 로그 포맷을 지정한다. 보통 어떠한 장애가 났을 때, 가장 먼저보는 것이 로그 파일이기 때문에 디버깅하기 위해 유용한 값들을 로그에 남겨두는 것이 중요하다. 특히나, 여러 프록시 서버를 지나오는 서버 구성인 경우에는 x-forwarded-ip 등을 지정하면 지나온 프록시들의 아이피들을 할 수 있다.

 

"http.access_log"

access로그를 어느 디렉토리에 남길지 설정한다.

 

"http.keepalive_timeout"

소켓을 끊지 않고 얼마나 유지할지에 대한 설정이다. 자세한 내용은 keepalive 개념을 확인하자.

 

"http.server_tokens"

nginx의 버전을 숨길 것인가에 대한 옵션이다. 보안상 활성화하는 것을 권장한다.

 

기타 설정들은 위 예제 파일에 주석으로 달아놓았다.

 

다음은 실제 프록시 설정이 들어가는 server 블록 설정을 다루어 보자.

 

server {
    listen 80;
    server_name levi.local.com;
    access_log  logs/access.log;
    error_log   logs/error.log;
    error_page  500 502 503 504 /50x.html;
    charset     utf-8;
    
    location / {
    	proxy_pass  http://app;
    }
}

upstream app {
	server localhost:8080;
}

 

위 설정은 http 블록 하위로 들어가게 된다. 크게 어려운 설정은 없고, "levi.local.com:80/"으로 요청이 들어오면 upstream(요청받는 서버)으로 요청을 리버스 프록시 한다라는 뜻이다. 실제로 앱하나를 띄워보고 프록시 되는지 확인해보자. 

 

> curl levi.local.com/api
new api ! - 7

 

위처럼 응답이 잘오는 것을 볼 수 있다. 그런데 사실 server 블록이 하나일때는 server_name에 적혀있는 도메인으로 오지않아도 응답을 준다. server_name이 진짜 도메인네임을 구분하기 위한 server_name으로 사용되기 위해서는 listen 포트가 같은 server 블록이 두개 이상 존재할때 이다. 아래 예제를 보자.

 

    server {
        listen 80;
        server_name levi.local.com;
        #access_log  logs/access.log;
        #error_log   logs/error.log;
        error_page  500 502 503 504 /50x.html;
        charset     utf-8;

        location / {
            proxy_pass  http://app;
        }
    }

    upstream app {
        server localhost:8080;
    }

    server {
        listen 80;
        server_name local.yoon.com;
        #access_log  logs/access.log;
        #error_log   logs/error.log;
        error_page  500 502 503 504 /50x.html;
        charset     utf-8;

        location / {
            proxy_pass  http://app2;
        }
    }

    upstream app2 {
        server localhost:7070;
    }

 

위와 같이 설정하고, 각 도메인을 분리해서 요청을 보내보자. server_name으로 분리되어 요청이 프록시 될것이다.

 

Nginx cache

마지막으로 location 블록에 대한 설정중 nginx cache에 설정에 대해 주로 다루어보자.

 

 

  • /path/to/cache ==> 캐시 내용이 local disk 에 저장될 위치
  • levels=1:2 ==> directory depth 와 사용할 name 길이.
    • ex ) /data/nginx/cache/c/29/b7f54b2df7773722d382f4809d65029c
  • keys_zone ==> 캐시 키로 사용될 이름과 크기. 1MB 는 약 8천개의 이름 저장. 10MB면 8만개.
  • max_size ==> 캐시 파일 크기의 maximum. size 가 over 되면 가장 오래전에 사용한 데이터 부터 삭제한다.
  • inactive ==> access 되지 않았을 경우 얼마 뒤에 삭제 할 것인가.
  • use_temp_path ==> 설정된 path 외에 임시 저장 폴더를 따로 사용할 것인가? 따로 설정하지 않는 것이 좋다.
  • proxy_cache <namev> ==> 캐시로 사용할 메모리 zone 이름.
  • proxy_cache_methods ==> request method를 정의한다. default : GET, HEAD
  • proxy_cache_key ==> 캐시 할 때 사용할 이름.
  • proxy_cache_bypass ==> 예를 들어 "http://www.example.com/?nocache=true" 이러한 요청이 왔을 때 캐싱되지 않은 response 를 보낸다. 이 설정이 없다면 nocache 아규먼트는 동작하지 않는다. http_pragma==> 헤더 Pragma:no-cache
  • proxy_cache_lock ==> 활성화 시키면 한 번에 단 하나의 요청만 proxy server로 전달되어 proxy_cache_key 에 따라 캐싱된 데이터로 사용합니다. 다른 request 들은 캐싱된 데이터를 사용하거나 proxy_cache_lock_timeout의 설정에 따라 proxy server로 전달 될 수 있습니다.
  • proxy_cache_valid ==> 기본적으로 캐싱할 response code 와 시간을 정의한다.

 

 

예제 설정으로는 아래와 같다.

 

proxy_cache_path /usr/local/etc/nginx/cache levels=1:2 keys_zone=myapp:10m max_size=10g inactive=60s use_temp_path=off;

server {
    listen 80;
    server_name levi.local.com;
    access_log  logs/access.log;
    error_log   logs/error.log;
    error_page  500 502 503 504 /50x.html;
    charset     utf-8;
    
    location / {
        proxy_cache myapp;
        proxy_cache_methods GET;
        proxy_cache_key "$uri$is_args$args";
        proxy_cache_bypass $cookie_nocache $arg_nocache $http_pragma;
        proxy_ignore_headers Expires Cache-Control Set-Cookie;
        #proxy_cache_lock on;
        #200ok인 응답을 1분동안 캐싱
        proxy_cache_valid 200 1m; 
        
        proxy_pass  http://app;
    }
}

upstream app {
	server localhost:8080;
}

 

실제로 캐싱이 잘되는지 요청을 보내보고 실제 캐싱이 저장되는 디렉토리로 들어가보자.

 

> cd /usr/local/etc/nginx/cache
> ls
8
> cd 8
> ls
68
> cd 68
> ls
5d198634e5fa00f3cf3a478fcdf57688
> vi 5d198634e5fa00f3cf3a478fcdf57688
^E^@^@^@^@^@^@^@û½@_^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ÿÿÿÿÿÿÿÿ¿½@_^@^@^@^@#Y|^V^@^@d^Aè^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
KEY: /api?arg=args
HTTP/1.1 200 ^M
Content-Type: text/html;charset=UTF-8^M
Content-Length: 13^M
Date: Sat, 22 Aug 2020 06:39:59 GMT^M
Connection: close^M
^M
new api ! - 5

 

응답이 잘 캐싱된것을 볼수 있다. 그리고 대략 1분후에는 해당 캐싱 파일 지워져있다.

 

여기까지 간단하게 Nginx 설치 및 사용방법에 대해 다루어보았다. 맘 같아선 캐싱에 대해 더 자세히 다루고 싶었다. 대규모 웹사이트 같은 경우는 정말 장비를 늘리는 것으로는 트래픽을 받는데 한계가 있기 때문에 사실상 캐싱 싸움이 될것이기 때문이다. 이번 포스팅에서는 Nginx에 대해 맛보기 정도만 하였지만, 다음 시간에는 조금더 딥한 내용까지 다루어 볼 계획이다.

 

참조

 

nginx cache

1. cache dir 설정 proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=cache:2m 2. cache 사용 설정 server { listen  80; server_name cached.test.co.kr; access_log /var/log/nginx/cache-access.log c..

semode.tistory.com

 

posted by 여성게
:
인프라/운영체제 2020. 8. 22. 00:23

 

 

UNIX/LINUX : 용량 확인 명령어 (df/du)

unix/linux Unix/Linux 디스크 용량 확인 (df/du) 디스크 용량을 확인하는 명령어들이다. df : 디스크의 남은 용량을 확인 df -k : 킬로바이트 단위로 현재 남은 용량을 확인 df -m : 메가바이트 단위로 남은 �

ra2kstar.tistory.com

 

디스크의 남은 용량 확인
  • df -k : 킬로바이트 단위로 현재 남은 용량을 확인
  • df -m : 메가바이트 단위로 남은 용량을 왁인 
  • df -h : 보기 좋게 보여줌
  • df . : 현재 디렉토리가 포함된 파티션의 남은 용량을 확인
> df -h
Filesystem      Size   Used  Avail Capacity iused      ifree %iused  Mounted on
/dev/disk1s1   466Gi   10Gi  379Gi     3%  487648 4881965192    0%   /
devfs          190Ki  190Ki    0Bi   100%     659          0  100%   /dev
/dev/disk1s2   466Gi   73Gi  379Gi    17% 1142296 4881310544    0%   /System/Volumes/Data
/dev/disk1s5   466Gi  2.0Gi  379Gi     1%       2 4882452838    0%   /private/var/vm
map auto_home    0Bi    0Bi    0Bi   100%       0          0  100%   /System/Volumes/Data/home

 

현재 디렉토리의 용량 확인
  • du -a : 현재 디렉토리의 사용량을 파일단위 출력
  • du -s : 총 사용량을 확인
  • du -h : 보기 좋게 바꿔줌 
  • du -sh * : 한단계 서브디렉토리 기준으로 보여준다. 
> du -sh *
4.0K	Dockerfile
4.0K	HELP.md
 12K	README.md
 34M	build
4.0K	build.gradle
 60K	gradle
8.0K	gradlew
4.0K	gradlew.bat
284K	kube-logging
 48K	kube-resource
4.0K	kube-sample.iml
 24K	nginx
4.0K	settings.gradle
4.0K	src

 

posted by 여성게
:

 

 

[번역] Elasticsearch 퍼포먼스 튜닝 방법 - ebay

Elasticsearch에 대해 검색하다가 ebay에 퍼포먼스 튜닝방법에 대해 좋은 글이 있어서 간단하게 정리해봤다. 새롭게 알게된 사실이 많아서 좋았다. 정리 잘된 기술 블로그를 보는것은 책을 읽는거보�

wedul.site

 

posted by 여성게
:
Web/Gradle 2020. 8. 17. 18:06

 

그래들 프로젝트를 사용중인데, 간혹 idea에서 "Could not initialize class org.codehaus.groovy.runtime.InvokerHelper"라는 에러 메시지가 뜨는 경우가 있다. 여러가지 요인이 있을 수 있지만, 필자가 저 에러를 보았던 순간은 jdk 1.8에서 jdk14 버전으로 올리면서 났던 에러 메시지 인데, 이유는 gradle version이 jdk14를 지원하지 못하는 낮은 버전이었기 때문이다.

 

 

그래서, 프로젝트 디렉토리중 "gradle/wrapper/gradle-wrapper.properties"에서 gradle 버전을 올려주어서 해결하였다.(5.x -> 6,3)

posted by 여성게
:
Database/MongoDB 2020. 8. 16. 22:26

 

이번에 다루어볼 내용은 몽고디비에서 데이터를 백업하고 복구하는 방법이다.

 

백업하기(덤프, dump)

몽고디비가 설치되어 있다면, mongodump라는 명령어로 몽고디비 데이터를 백업할 수 있다.

 

> mongodump --host 127.0.0.1 --port 27017

 

위 명령으로 데이터를 백업한다면, 현재 디렉토리에 /dump 디렉토리가 생기고 이 디렉토리 밑에 데이터가 복구되어 있다.(DB 별로 폴더가 생겨있고, 그 폴더안에 BSON으로 데이터가 백업되어 있다.)

 

> mongodump --out ~/mongo_backup --host 127.0.0.1 --port 27017

 

--out 옵션으로 데이터 백업의 디렉토리 위치를 정해줄 수 있다.

 

> mongodump --out <dump data path> --host 127.0.0.1 --port 27017 -u <username> -p <password> 

 

username/password로 인증이 필요하다면, 위 명령어로 백업이 가능하다.

 

> mongodump --out <dump data path> --host 127.0.0.1 --port 27017 -u <username> -p <password> --db <덤프할 db명>

 

만약 몽고디비에서 특정 데이터베이스만 백업하고 싶다면, 위처럼 --db 옵션을 이용하면 된다.

 

> mongodump --out <dump data path> --host <dbhost> --port 27017 -u <username> -p <password> --db <dbname> --collection <collectionName>

 

특정 컬렉션 단위까지 세분화하여 백업하려면 --collection 옵션을 이용한다. 만약 mongodump&mongorestore에서 "error connecting to db server: server returned error on SASL authentication step:Authentication failed" 에러가 났다면, 아래와 같이 옵션하나를 넣어준다.

 

--authenticationDatabase admin

 

복구하기(restore)

다음은 위에서 덤프한 데이터를 복구하는 방법이다. 복구는 mongorestore라는 명령어를 이용한다.

 

> mongorestore --host 127.0.0.1 --port 27017 \
-u <username> -p <password> --drop <drop db name> \
--db <복구할 db name> <복구할 덤프데이터가 있는 디렉토리>

 

--drop 옵션은 복구전에 드랍시킬 데이터베이스 명을 입력하면 된다.(복구 전 원래 데이터베이스를 드랍시키고 백업 데이터로 새로 복구하는 것이다.) 위 명령을 간단하게 작성해보면 아래와 같다.

 

> mongorestore --host 127.0.0.1 --port 27017 --db local /mongo_backup/local

 

특정 데이터베이스를 복구하고 싶다면, 덤프 데이터가 있는 디렉토리에서 특정 데이터베이스의 디렉토리를 명시해야한다. 만약 모든 데이터를 전부다 백업하고 싶다면 아래 명령어를 입력한다.

 

> mongorestore --host 127.0.0.1 --port 27017 <dump data가 있는 디렉토리>

 

컬렉션 단위로 리스토어하기 위해서는 --collection 옵션을 사용하며, collectionName.bson까지 백업데이터 경로를 명시해주어야한다.

 

> mongorestore --host <dbhost> --port 27017 --db <dbname> --collection <collectionName> <data-dump-path/dbname/collection.bson> --drop
ex)
> mongorestore --port 27017 --db test2 --collection rest2 /mydata/restoredata/test/restaurants.bson --drop

 

여기까지 간단하게 몽고디비 데이터 백업 및 복구에 대해 다루어보았다.

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 8. 15. 22:31

 

오늘은 쿠버네티스의 볼륨에 대해 다루어 볼 것이다. 간단하게 몇가지 볼륨 플러그인에 대해 예제를 다루어보고, 퍼시스턴트 볼륨&볼륨 클레임에 대해 다루어본다.

 

 

yoonyeoseong/kubernetes-sample

Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

emptyDir

해당 플러그인은 pod가 실행되는 호스트의 디스크를 임시로 컨테이너에 볼륨으로 할당해서 사용하는 방법이다. pod가 사라지면 emptyDir에 마운트해서 사용하는 데이터도 모두 사라진다. 하지만, pod이 종료되지 않고 단순히 container만 재시작된 것이라면 데이터는 유지된다. 주로 대용량 데이터 계산의 중간 연산 저장 용도로 사용한다.

 

apiVersion: v1
kind: Pod
metadata:
  name: springboot-web
spec:
  containers:
  - name: springboot-web
    image: 1223yys/springboot-web:0.1.6
    ports:
    - containerPort: 8080
    volumeMounts:
      - mountPath: /emptyDir
        name: emptyDir_vol
  volumes:
    - name: emptyDir_vol
      emptyDir: {}

 

hostPath

pod가 실행된 호스트의 파일이나 디렉터리를 pod에 마운트한다. emptyDir가 임시 디렉터리를 마운트하는 것이라면, hostPath는 호스트에 있는 실제 파일이나 디렉터리를 마운트하는 것이며 pod를 재시작하더라도 데이터가 보존된다. 보통은 /var/lib/docker 같은 도커 시스템용 디렉토리를 컨테이너에 마운트해 시스템 모니터링 등을 진행할 때 사용하기도 한다.

 

apiVersion: v1
kind: Pod
metadata:
  name: springboot-web
spec:
  containers:
  - name: springboot-web
    image: 1223yys/springboot-web:0.1.6
    ports:
    - containerPort: 8080
    volumeMounts:
      - mountPath: /test-volume
        name: hostPath-vol
  volumes:
    - name: hostPath-vol
      hostPath:
        path: /tmp
        type: Directory

 

실제로 볼륨이 잘 마운트 되었는지 확인해보자.

 

> kubectl exec <pod-name> -it sh
> cd /test-volume
> touch test.txt
> exit
> ls /tmp

 

호스트의 /tmp 디렉토리에 test.txt가 생성되었다면, 볼륨이 잘 마운트 된 것이다. hostPath 볼륨에는 아래와 같이 여러가지 타입이 존재한다.

 

  • null: hostPath 볼륨을 마운트하기 전에 아무것도 확인하지 않는다.
  • DirectoryOrCreate: 설정한 경로에 디렉터리가 없으면 퍼미션이 755인 빈 디렉터리를 만든다.
  • Directory: 설정한 경로에 디렉터리가 존재해야한다. 호스트에 해당 디렉터리가 없으면 파드는 ContainerCreating 상태로 남고 생성이 안된다.
  • FileOrCreate: 설정한 경로에 파일이 없으면 퍼미션이 644인 빈 파일을 만든다.
  • File: Directory와 동일
  • Socket: 설정한 경로에 유닉스 소켓 파일이 있어야한다.

 

퍼시스턴트 볼륨&볼륨 클레임(Persistent Volume, Persistent Volume Claim)

쿠버네티스에서 볼륨을 사용하는 구조는 PV와 PVC로 분리되어있다. PV는 볼륨 자체를 뜻하고 클러스터 안에서 자원으로 다룬다. 파드하고는 별개로 관리되고 별도의 생명주기가 있다.

 

 

PVC는 사용자가 PV에 하는 요청이다. 사용하고 싶은 용량은 얼마인지, 읽기/쓰기는 어떤 모드를 사용하고 싶은지 등을 정하여 요청한다. 즉, 쿠버네티스는 이처럼 파드에 볼륨을 직접 할당하는 형태가 아니라, 중간에 PVC를 두어 파드와 파드가 사용할 스토리지를 분리하는 전략인 것이다. 이렇게 분리됨으로써 이점은 다양한 스토리지를 PV로 사용할 수 있는데, 파드는 어떠한 스토리지의 볼륨인지 신경쓸 필요없이 PVC으로 요청만 하면 되기 때문에 의존성이 줄어들게 되고, manifest도 분리됨으로써 설정파일 자체의 복잡함이 사라진다.

 

PV&PVC 생명주기
  • Provisioning: PV를 만드는 단계를 뜻한다.
    • static provisioning: 미리 PV를 만들어 두고 사용자의 요청이 있으면 미리 만들어둔 PV를 할당한다.(보통 스토리지 용량의 제한이 있을때 사용한다.)
    • dynamic provisioning: 사용자가 PVC를 거쳐 PV를 요청했을 때, PV를 생성해 제공한다.
  • Binding: 바인딩은 프로비저닝으로 만든 PV를 PVC와 연결하는 단계이다. PVC에서 원하는 스토리지의 용량과 접근 방법을 명시해서 요쳥하면 맞는 PV가 할당된다. 이때 PVC에서 원하는 PV가 없다면 요청은 실패하고, PVC에서 원하는 PV가 생길 때까지 대기하다가 PVC에 바인딩된다.(PVC 하나에 여러 PV가 매핑될 수 없다.)
  • Using: PVC는 파드에 설정되고 파드는 PVC를 볼륨으로 인식해서 사용한다. 할당된 PVC는 파드를 유지하는 동안 계속 사용하며 시스템에서 임의로 삭제할 수 없다. 이 기능을 "Storage Object In Use Protection"이라 한다.
  • Reclaiming: 사용이 끝난 PVC는 삭제되고 PVC를 사용하던 PV를 초기화하는 과정을 뜻한다. 초기화 정책으로는 아래와 같다.
    • Retain: PV를 그대로 보존한다. PVC가 삭제되면 사용 중이던 PV는 해제(released)상태라서 아직 다른 PVC가 재사용할 수 없다.(데이터는 아직 그대로 보존되어있다.) 만약 해당 PV를 재사용하려면 아래와 같은 순서로 직접 초기화해줘야한다.
      1. PV삭제. 만약 PV가 외부 스토리지와 연결되어있다면 PV는 삭제되더라도 외부 스토리지의 볼륨은 그대로 남아있다.
      2. 외부 스토리지에 남은 데이터를 직접 정리한다.
      3. 남은 스토리지의 볼륨을 삭제하거나 재사용하려면 해당 볼륨을 이용하는 PV를 다시 만든다.
    • Delete: PV를 삭제하고 연결된 외부 스토리지 쪽의 볼륨도 삭제한다. 동적 프로비저닝은 기본적으로 해당 정책을 따른다.
    • Recycle: PV의 데이터들을 삭제하고 다시 새로운 PVC에서 PV를 사용할 수 있도록한다.

 

이제 실제로 실습을 통해 알아보자. 다음은 퍼시스턴트 볼륨 템플릿이다.

 

apiVersion: v1
kind: PersistentVolume
metadata:
  name: persistent-volume
  namespace: levi-volume
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  persistentVolumeReclaimPolicy: Delete
  hostPath:
    path: /tmp

 

간단하게 설정파일에 작성된 설정을 설명하면 아래와 같다.

 

  • accessModes
    • ReadWriteOne: 노드 하나에만 볼륨을 읽기/쓰기하도록 마운트한다.
    • ReadOnlyMany: 여러 개 노드의 읽기 전용으로 마운트한다.
    • ReadWriteMany: 여러 개 노드에서 읽기/쓰기를 허용하도록 마운트한다.
  • storageClassName: 스토리지 클래스를 설정하는 필드이고, PVC가 특정 스토리지 클래스를 명시하여 요청하면 해당 스토리지 클래스로 선언된 PV와 연결된다. 만약 스토리지 클래스를 설정하지 않았다면, 특정 스토리지 클래스를 명시하지 않은 PVC가 요청하면 매핑된다.
  • persistentVolumeReclaimPolicy: PV가 해제되었을 때의 초기화 옵션을 설정한다.(Retain/Recycle/Delete)
  • .spec.hostPath: 해당 PV의 볼륨 플러그인을 명시한다.

 

볼륨이 잘 생성되었는지 확인해보자.

 

> kubectl apply -f ./kube-resource/persistent-volume-sample.yaml
> kubectl get pvc -n levi-volume
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
persistent-volume   1Gi        RWO            Delete           Available           manual       

 

STATUS는 아래와 같이 4가지의 상태값을 갖는다.

 

  • Available: PVC에서 사용할 수 있는 상태
  • Bound: 특정 PVC에 연결된 상태
  • Released: PVC는 삭제되었고, PV는 아직 초기화되지 않은 상태
  • Failed: 자동 초기화를 실패한 상태

 

다음은 퍼시스턴트 볼륨 클레임 설정이다.

 

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: persistent-volume-claim
  namespace: levi-volume-claim
spec:
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  resources:
    requests:
      storage: 500Mi

 

다른 내용은 볼륨의 설정과 크게 차이가 없고 한가지만 설명하자면, ".spec.resources.requests.storage"는 자원을 얼마나 사용할 것인지 명시하는 것이며, PV의 용량보다 높다면, 할당되지 않고 Pending 상태가 된다.

 

볼륨 클레임이 잘 생성되었는지 확인해보자.

 

> kubectl apply -f ./kube-resource/persistent-volume-claim-sample.yaml
> kubectl get pvc -n levi-volume-claim
NAME                      STATUS   VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistent-volume-claim   Bound    persistent-volume   1Gi        RWO            manual         11s
> kubectl get pv -n levi-volume
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                       STORAGECLASS   REASON   AGE
persistent-volume   1Gi        RWO            Delete           Bound    levi-volume-claim/persistent-volume-claim   manual                  4h44m

 

PVC와 PV가 binding 된 이후에는 각각 STATUS가 Bound 상태로 변경되었다. 보통 PV와 PVC를 연동할때는 storageClassName을 보고 연결되는데, 또 다른 방법으로는 label로 연결할 수도 있다.

 

#볼륨
apiVersion: v1
kind: PersistentVolume
metadata:
  name: persistent-volume
  namespace: levi-volume
  labels:
    location: local
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  persistentVolumeReclaimPolicy: Delete
  hostPath:
    path: /tmp

#볼륨 클레임
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: persistent-volume-claim
  namespace: levi-volume-claim
spec:
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  resources:
    requests:
      storage: 500Mi
  selector:
    matchLabels:
      location: local

 

마지막으로 pod에 볼륨을 마운트해보자.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: springboot-web
  template:
    metadata:
      labels:
        app: springboot-web
    spec:
      containers:
        - name: springboot-web
          image: 1223yys/springboot-web:0.2.5
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          volumeMounts:
            - mountPath: /test-volume
              name: persistent-volume
          livenessProbe:
            httpGet:
              port: 9090
              path: /api
            initialDelaySeconds: 60
          readinessProbe:
            httpGet:
              port: 9090
              path: /api
            initialDelaySeconds: 60
      volumes:
        - name: persistent-volume
          persistentVolumeClaim:
            claimName: persistent-volume-claim

 

만약 예제대로 따라왔다면, pod은 뜨지 못하고 pending된 상태로 머물러 있을 것이다. 왜냐하면 클러스터는 클레임을 사용하는 pod와 동일한 네임스페이스에 있어야하기 때문이다. 위 deployment는 네임스페이스가 default이므로, 볼륨 클레임을 default 네임스페이스에 하나 생성해주어야 한다.

 

여기까지 정말 간단하게 쿠버네티스 볼륨과 볼륨 클레임에 대해 다루어보았다. 사실 다루어볼 볼륨 플러그인이 아주 많기 때문에 다음 포스팅에서 더 자세히 다루어볼 것이다.

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 8. 2. 22:52

 

이번 포스팅에서 다루어볼 내용은 간단하게 쿠버네티스 ingress-nginx를 설치하고, 외부 트래픽을 내부 팟에게 전달해주는 예제이다. 바로 예제로 들어간다.

 

> git clone https://github.com/kubernetes/ingress-nginx.git
> cd ./ingress-nginx/deploy/static/provider/baremetal
> kubectl apply -f .
> kubectl get deploy -n ingress-nginx
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
ingress-nginx-controller   1/1     1            1           60s

 

여기까지 따라왔다면 설치는 완료되었고, ingress-nginx를 위한 서비스 등이 떴을 것이다.

 

kubectl get svc -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.97.27.106   <none>        80:30431/TCP,443:31327/TCP   4m35s

 

30431로 접속해보자.

 

> curl levi.local.com:30431
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.19.1</center>
</body>
</html>

 

호스트 설정을 통해 levi.local.com을 localhost로 포워딩하도록 설정하였다. 실습에 localhost는 사용하기 힘들기 때문에 etc/hosts 설정을 통해 로컬을 특정 도메인처럼 할당해보자.

 

> sudo vi /etc/hosts
127.0.0.1 levi.local.com

 

이제 ingress-nginx가 포워딩할 웹어플리케이션 팟을 띄워보자.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: springboot-web
  template:
    metadata:
      labels:
        app: springboot-web
    spec:
      containers:
        - name: springboot-web
          image: 1223yys/springboot-web:0.2.5
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              port: 8080
              path: /api
            initialDelaySeconds: 60
          readinessProbe:
            httpGet:
              port: 8080
              path: /api
            initialDelaySeconds: 60

 

디플로이먼트 컨트롤러로 springboot-web 애플리케이션 팟을 관리하도록 매니페스트를 작성하였다. 해당 매니패스트를 적용해보자.

 

> kubectl apply -f deployment.yaml

 

다음으로는 springboot-web으로 접근할 수 있게 해주는 외부 통로인 서비스를 작성해보자.

 

apiVersion: v1
kind: Service
metadata:
  name: springboot-web-service
spec:
  selector:
    app: springboot-web
  ports:
    - name: http
      port: 80
      targetPort: 8080

 

해당 서비스도 배포해보자.

 

> kubectl apply -f service.yaml

 

마지막으로 ingress nginx 매니페스트 파일을 작성해보자.

 

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress-sample
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: levi.local.com
    http:
      paths:
      - path: /
        backend:
          serviceName: springboot-web-service
          servicePort: 80

 

위 매니페스트 파일에서 host를 유의하자. 포스팅 초반에 localhost를 로컬 도메인으로 할당해야한다 했는데, 그 이유가 위 host 때문이다. 위에 보이는 host는 실제로 http 요청이 들어올때 요청 헤어의 "Host : levi.local.com" 을 참조하기 때문이다.

 

이제 요청을 보내보자.

 

> curl http://levi.local.com:30431/api
new api !


 ingress-nginx를 통해 웹앱 팟에 잘 접근되는 것을 확인할 수 있다.

posted by 여성게
:
Tools/Git&GitHub 2020. 8. 2. 20:17

 

오늘 다루어볼 내용은 git의 cherry-pick(체리픽)이다. 보통 체리픽을 사용하는 이유는 다른 브랜치의 커밋의 일부만 가져올때 많이 사용한다. 간단하게 예제를 다루어보자.

 

> gb
* [1] feature/add-function
  [2] feature/update-function

 

위처럼 두개의 브랜치가 존재하고 각 브랜치의 커밋로그는 아래와 같다.

 

> feature/add-function
* 13e321a - (HEAD -> feature/add-function) add c.txt (9 minutes ago) <levi.yoon>
* f894b25 - add b.txt (9 minutes ago) <levi.yoon>
* 35402a1 - add a.txt (9 minutes ago) <levi.yoon>

> feature/update-function
* cb6a172 - (HEAD -> feature/update-function) add d.txt (7 minutes ago) <levi.yoon>
* d516ac8 - update a.txt (7 minutes ago) <levi.yoon>
* 13e321a - (feature/add-function) add c.txt (9 minutes ago) <levi.yoon>
* f894b25 - add b.txt (10 minutes ago) <levi.yoon>
* 35402a1 - add a.txt (10 minutes ago) <levi.yoon>

 

여기서 feature/update-function 브랜치에 있는 "d516ac8 - update a.txt" 이 커밋내용만 가져와 feature/add-function 브랜치에 넣고 싶을 때 cherry-pick을 이용한다.

 

> git checkout feature/add-function
> git cherry-pick d516ac8
> git log
* 09edc77 - (HEAD -> feature/add-function) update a.txt (46 seconds ago) <levi.yoon>
* 13e321a - add c.txt (12 minutes ago) <levi.yoon>
* f894b25 - add b.txt (12 minutes ago) <levi.yoon>
* 35402a1 - add a.txt (12 minutes ago) <levi.yoon>

 

지금 한 예제는 같은 레포지토리의 다른 브랜치에서 커밋을 가져왔지만, 다른 원격 레포지토리의 특정 브랜치에 있는 커밋내용도 내 레포지토리 브랜치에 넣을 수 있다.

 

posted by 여성게
: