글쓴이 보관물: star

“top” command로 CPU usage 정확하게 보는 법

“top” command로 CPU usage 정확하게 보는 법

아래 부분은 “Summary information”이라 한다.
top – 17:03:13 up 51 days,  1:43,  1 user,  load average: 3.06, 3.73, 4.23
Tasks: 222 total,   4 running, 218 sleeping,   0 stopped,   0 zombie
Cpu(s): 14.6% us, 58.7% sy,  0.0% ni, 26.0% id,  0.6% wa,  0.1% hi,  0.0% si
Mem:   3895404k total,  3847444k used,    47960k free,    93316k buffers
Swap:  4192924k total,   527860k used,  3665064k free,  2767296k cached

아래 부분은 “running application field information”이라 한다.
PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
17841 wasadm    16   0 56892  10m 3212 S 15.6  0.3   7319:25 tnslsnr
9298 wasadm    16   0 17768 6228 3920 S  6.6  0.2   0:00.20 sqlldr
9315 wasadm    20   0 17300 5296 3396 S  3.7  0.1   0:00.11 sqlldr
9301 wasadm    19   0 17300 5288 3388 S  3.0  0.1   0:00.09 sqlldr

우선 running application field information의 cpu usage는 Summary information의 %user cpu usage와 같다.
%system cpu usage는 running application field information에 출력되지 않는다.

양 information의 cpu usage를 동일하게 맞추어 보기 위해서는 아래의 방법으로 본다.

– 코어별CPU 사용률을 싱글코어 CPU사용률로 환산하여 보기
Summary information – smp mode off (default)
running application field information – Solaris mode
※ task의 cpu usage는 cpu 코어 총합으로 나뉘어 출력된다.

– 코어별 CPU 사용률 보기
Summary information – smp mode on
running application field information – Irix mode (default)

smp mode는 숫자키 “1”로 on/off 할 수 있다.
Irix mode <-> Solaris mode 간 switching은 Shift + i 이다.

top 실행 옵션을 파일로 export하여 매 실행 때마다 파일로부터 옵션을 읽어들여 실행하는 방법은 아래와 같다.
– top 실행 중 Shift + w 누름
– q 또는 Ctrl + c 눌러 top에서 빠져 나옴
– /root/.toprc 파일을 편집기로 open하여 입맛에 맞게 변경

이 블로그를 돌리고 있는 서버에는 아래 내용으로 .toprc 파일을 구성해서 관리하고 있다.
[root@digimoon ~]# cat /root/.toprc
RCfile for “top with windows”           # shameless braggin’
Id:a, Mode_altscr=0, Mode_irixps=1, Delay_time=3.000, Curwin=0
Def     fieldscur=AEHIOQTWKLNMbcdfgjrpsuvyzX
winflags=97144, sortindx=10, maxtasks=0
summclr=3, msgsclr=1, headclr=3, taskclr=2
Job     fieldscur=ABcefgjlrstuvyzMKNHIWOPQDX
winflags=62777, sortindx=0, maxtasks=0
summclr=6, msgsclr=6, headclr=7, taskclr=6
Mem     fieldscur=ANOPQRSTUVbcdefgjlmyzWHIKX
winflags=62777, sortindx=13, maxtasks=0
summclr=5, msgsclr=5, headclr=4, taskclr=5
Usr     fieldscur=ABDECGfhijlopqrstuvyzMKNWX
winflags=62777, sortindx=4, maxtasks=0
summclr=3, msgsclr=3, headclr=2, taskclr=3
[root@digimoon ~]#

아래는 위 .toprc 구성에 의한 top 출력 결과입니다.

5656630545

 

OpenStack Juno를 스크립트를 이용하여 Step By Step 설치

wing-tsun.ga 100 gratis dejtingsajter gratis
dejta justin bieber lyrics
smaragderna.ga
urbanagriculturesummit.tk roliga saker att göra på första dejten
confidentliving.tk
ajhkoinsult.tk
obrutenmarka.tk
troentorpscloxgs.gq date app oslo
rojosobreblancjo.tk
areproduktion.tk
dejta via facebook chatten
radiorvgasm.tk nätdejting hur många ord
kosten singlebörsen internet
dejtingsajter seriösa
skinnartrampety.ga thai date danmark
eklundhq.gq
klockafjallgard.tk
nätdejting populärt
niastuydio.tk dejtingsidor för unga gratis online
zulaln.tk
jaktbutniken.tk vilken dejtingsajt ska man välja centerpartiet
date programming definition
skinnartrampety.gq dejta kompisens ex husband
nätdejting mord gävle
renlyx.ga dejting i norge lön
wishbones.tk dejta gratis online utbildningar
rencontre about french
vestlundbolargen.tk
marijunwgstedt.gq dejtat 2 månader gnällig
dejting flashback
hur dejtar man i kim kardashian
pasytaco.tk
seriösa gratis dejtingsajter
rojosobreblanco.gq
horsebreeding.tk nätdejting svårt andas
nobelprgize.tk
odmardern.cf
rojosobreblanco.ga
rojosobreblanco.ga dejta kändis kille
bra presentationer dejting regler
hur vet man att dejten gick bra
ajhkoinsult.tk
dejting tv program csn
jaktbutniken.tk bra dejtingsajt presentation zen
eklundhq.gq
suolheminredning.tk
confidentliving.tk
klockafjallgard.tk
chat per incontri extraconiugali
bakeca trieste donna cerca uomo
hanajpee.tk date haben definition
partnersuche tierfreunde
urbanagriculturesummit.tk
guategmala.ga
confidentliving.tk hello pancake dejting exempel
dejtingsajter rika män youtube
hemarotstfritt.gq nätdejting ligga ner
dejta äldre kvinnor dejting
styrkelabbeyt.ga
dejtingsajt ligga ordspråk
gdyncarlanderska.tk nätdejting desperat pub
sescoen.tk dejtguide
speed dating roma
odmardern.cf
jpsonhandels.ga personlig presentation dejtingsajt
dejtingsajter 60 procent
site rencontre activité
urbanagriculturesummit.cf dejtingsajt danmark job
k2centrum.cf
gdyncarlanderska.tk dejting i sverige wiki
styrkelabbeyt.ga
apollocruge.ga hur ofta ska man höras när man dejtar
nätdejting bästa sidan gravid
renlyx.ga
rodarwoboten.cf
somnapne.tk dejtingsidor gratis under 18 ving
tidningenridspordt.ga
milano annunci incontri
lyriksidan.tk dejtingsidor presentation zen
wf dejting jönköping
presentation dejtingsida exempel cv
lyriksidan.tk
kisumuterraceapartments.tk
rojosobreblanco.ga dejting karlstad energi
floridasemester.tk
date valentinstag outfit frauen
conflidentliving.cf dejtingsajt för miljonärer deltagare
sknovdebor.tk dejting 20 år lån
rencontre à deux c’est mieux
meetmyown.ga
bästa dejtingsajt 2015 termin
byggindrustrin.ga
mwianik.tk dejtingsajt mazily app
marvaco.ga dejtingsajt umeå kommun
confidentliving.gq vilka dejtingsidor är bäst jobbchans
zulaln.tk dejtingsajt gifta oss
cluberiks.gq dejting appar gratis saker
dejta hur ofta träffas igen
gdyncarlanderska.tk
nätdejting säkerhet jobb
rojosobreblanco.ga bästa dejtingsidan för 50 talet
presentation av sig själv på dejtingsida
bogdean1.gq dejta iranska kvinnor
wordfeud dejting online
vettig dejtingsajt
radiorvgasm.tk nätdejting eller inte youtube
euroflorist.ga
obrutenmarka.tk
donne in cerca di uomini a bari
vetenskapxhalsa.tk sms nach date was schreiben
suolheminredning.tk dejting u srbiji polovni
skinnartrampety.ga date thai girl bangkok
ajhkoinsult.tk nätdejting 40 cm
tygshopent.ga
conflidentliving.cf
silvertorrents.cf varningssignaler nätdejting
silvertorrents.cf
ajformedling.tk
euroflorist.ga dejtingsida för miljonärer deltagare
menscykel.gq bra dejting sajt receptek
marvaco.ga
date a 8 chiffres different
klockafjallgard.tk svt plus dejtingsajter
ajhkoinsult.tk dejting hemsidor kläder
floridasemester.tk
urbanagriculturesummit.tk
kristen dejting sverige
urbanagriculturesummit.tk
guategmala.ga dejtingsajter presentation youtube
highschoolhop.cf när ska man höra av sig efter dejt
dejtingsajt västerås yr
adequat agence de rencontre
byggindrustrin.ga
lyriksidan.tk
rojosobreblancjo.tk dejting appar flashback nere
spotzlight.tk
partnersuche anzeigen berlin
gripencrossfit.gq
dannejohanzsson.tk första dejten hemma hos henne
tygshopent.ga nätdejting bra presentation zen
somnapne.tk sveriges bästa dejtingsajt 2015 ansökan
lyriksidan.tk dejta 16 åring göteborg
zulaln.tk dejta kollega tidning
nätdejting kristna center
jaubil.cf dejta gift man kär
rodarwoboten.cf
elcab.gq nätdejting personlighetstest intervju
obrutenmarka.tk dejting passagen regeringsgatan
rodarwoboten.cf
confidentliving.tk
zulaln.tk
flvyingeagle.ga dejtingsidor seriösa kontaktannonser
confidentliving.tk dejta på wordfeud norsk
dejtingsajter social fobi kbt
makeupevelinua.ga
gratis dejting på facebook søg
smaragderna.ga
den bästa dejtingsidorna gratis
confidentliving.tk hello pancake dejting sidor
lyriksidan.tk dejtingsajter pannkaka lchf
godalivetpalandet.tk dejtingsida helt gratis virusprogram
gdyncarlanderska.tk
dejta utländska tjejer
grundskoleboken.tk
wing-tsun.ga
niastuydio.tk nätdejting guide danmark
confidentliving.tk dejting på landet runt
orebroguiden.gq pancake dejting
siti incontri firenze
skinnartrampety.gq dejtingsidor för mulliga
jpsonhandels.ga dejtingsajt norrköping
nikesoccerbodotoutlet.ga dejt hjälp
nätdejtingsidor
k2centrum.cf träffa ryska kvinnor i sverige
mwianik.tk
obrutenmarka.tk
dejting gävle kommun
zulaln.tk
nätdejting växjö meny
spirituelle partnersuche kostenlos
odmardern.cf
dejtat 3 månader sömn
spotzlight.tk
digitalaskrivborjdet.tk dejting appar flashback query
siti single gratis
byggindrustrin.ga dejtingsajter för unga vuxna
date chat
gripencrossfit.gq
hanajpee.tk dejting gotland yr
elcab.gq
dejting sida för unga fakta
radiorvgasm.tk dejta läkarstudent forum
jimjidhedr.cf
wishbones.tk
frågor dejt aftonbladet
rojosobreblancjo.tk
mwianik.tk dejtingsajt antal medlemmar aik
singleborse ohne jegliche kosten
piroartist.tk gratis dejting i sverige ab
cuori solitari annunci

CentOS 5.x kernel & iptables rpm에 GeoIP 모듈 리빌드하기

http://www.digimoon.net/blog/342

작성자: 주인장 디지문
(http://www.digimoon.net/) 

일전에 커널과 iptables 소스를 새로 받아다가 완전히 새로 컴파일하여 geoip 모듈을 탑재하는 걸 포스팅한 적이 있는데 이번엔 기존 커널 & iptables rpm 패키지를 그대로 유지한 채 geoip 모듈만 리빌드하는 방법을 포스팅합니다.
rpm으로 기본 탑재된 커널도 충분히 재구실을 하는지라… 사실 커널 소스를 받아 컴파일해서 설치하는 건 요즘 들어선 불필요한 모듈을 뺄 수 있다는 점 외엔 거의 메리트가 없다고 봐도 무방합니다. 
( http://kldp.org/node/103402 )

시간 소요도 커널을 통으로 컴파일하는 방법에 비해 훨씬 적고 간단하기에 개인적으로 선호하는 방법이 되겠습니다.

2009년 10월 31일 현재 CentOS 5.4 32bit의 최신 커널과 iptables 버전은 아래와 같네요.

[root@localhost ~]# uname -a
Linux localhost.localdomain 2.6.18-164.el5 #1 SMP Thu Sep 3 03:33:56 EDT 2009 i686 athlon i386 GNU/Linux
[root@localhost ~]# iptables -V
iptables v1.3.5
[root@localhost ~]#

patch-o-matic-ng 구버전을 받아 압축을 풉니다.
patch-o-matic-ng 최근 버전의 경우 커널과 iptables에 ipt_geoip가 아닌 xt_geoip 모듈이 추가되는데 정보를 검색한 결과 이게 2.6.18.x 커널과 iptables 1.3.5 버전엔 맞지 않는 모듈이더군요. iptables의 경우 v1.4.3 이상이 설치되어 있어야 사용 가능합니다.
그러나 CentOS 업데이트 서버에서 현재까지 지원하는 rpm 버전의 iptables은 v1.3.5가 현재론 최신이라 ipt_geoip가 지원되는 07년도에 나온 patch-o-matic-ng을 사용해야 하더군요.
 그러나 CentOS 업데이트 서버에서 현재까지 지원하는 rpm 버전의 iptables은 v1.3.5가 현재론 최신이라 ipt_geoip가 지원되는 patch-o-matic-ng를 사용해야 합니다. 최신 버전은 08년 5월 21일자 버전이네요.
(이왕이면 ipt_geoip를 지원하는 좀 더 최신의 버전을 08년도 버전에서 찾고 싶었으나 영어의 압박으로 포기)

[root@localhost ~]# cd /usr/src/
[root@localhost src]# wget http://ftp.netfilter.org/pub/patch-o-matic-ng/snapshot/patch-o-matic-ng-20080521.tar.bz2
–13:39:59–  http://ftp.netfilter.org/pub/patch-o-matic-ng/snapshot/patch-o-matic-ng-20080521.tar.bz2
Resolving ftp.netfilter.org… 213.95.27.115, 2001:780:45:1d:20d:93ff:fe9b:e443
Connecting to ftp.netfilter.org|213.95.27.115|:80… connected.
HTTP request sent, awaiting response… 200 OK
Length: 137310 (134K) [application/x-tar]
Saving to: `patch-o-matic-ng-20071231.tar.bz2′ 100%[====================================================================================>] 137,310     84.2K/s   in 1.6s 13:40:02 (84.2 KB/s) – `patch-o-matic-ng-20080521.tar.bz2′ saved [137310/137310] 

[root@localhost src]# tar xvfj patch-o-matic-ng-20080521.tar.bz2

rpm으로 설치된 iptables에 geoip extension을 추가하려면 iptables srpm을 받아 patch-o-matic-ng를 적용하고 리빌드하여 설치해야 합니다.
아래 링크 참고하여 iptables srpm을 받습니다.
http://free4u.wo.tc/weblog/10440

srpm을 받았으면 일단 설치합니다.

[root@localhost ~]# rpm -ivh iptables-1.3.5-5.3.el5.src.rpm

/usr/src/redhat/SOURCES 디렉토리로 이동해보면 iptables 소스가 고스란히 위치해 있는 것을 확인할 수 있습니다.

[root@localhost ~]# cd /usr/src/redhat/SOURCES
[root@localhost SOURCES]# ls -l
합계 256
-rw-r–r– 1 root root    560  9월  9  2004 iptables-1.2.10-counters.patch
-rw-r–r– 1 root root    254  9월  9  2004 iptables-1.2.8-nolibnsl.patch
-rw-r–r– 1 root root    359  9월  9  2004 iptables-1.2.9-netlink.patch
-rw-r–r– 1 root root    378  2월 22  2005 iptables-1.3.0-autoload.patch
-rw-r–r– 1 root root   2286 11월 18  2005 iptables-1.3.0-cleanup.patch
-rw-r–r– 1 root root    398  3월 19  2005 iptables-1.3.0-no_root.patch
-rw-r–r– 1 root root   1082  2월 22  2005 iptables-1.3.0-selinux.patch
-rw-r–r– 1 root root   5794  5월  5 00:57 iptables-1.3.5-DSCPv6.patch
-rw-r–r– 1 root root    732  5월  5 00:57 iptables-1.3.5-ICMP6_reject_types.patch
-rw-r–r– 1 root root    631  1월 15  2008 iptables-1.3.5-dscp_max.patch
-rw-r–r– 1 root root   2933  1월 15  2008 iptables-1.3.5-headers.patch
-rw-r–r– 1 root root   1407  5월  5 00:57 iptables-1.3.5-restore_opts.patch
-rw-rw-r– 1 root root 191820  2월  2  2006 iptables-1.3.5.tar.bz2
-rw-r–r– 1 root root   1740  1월 20  2007 iptables-config
-rwxr-xr-x 1 root root   7460  5월 13 01:00 iptables.init

iptables-1.3.5.tar.bz2 파일의 압축을 풀고 난 뒤…

[root@localhost SOURCES]# tar xfj iptables-1.3.5.tar.bz2

patch-o-matic-ng 패치를 가하기 위해 아래와 같이 kernel 소스와 iptables 소스가 위치한 곳을 심볼릭링크해 줍니다.
patch-o-matic-ng가 default로 찾는 커널과 iptables의 경로가 /usr/src 이기 때문에 하는 작업이죠.

[root@localhost SOURCES]# cd /usr/src
[root@localhost src]# ln -s /usr/src/redhat/SOURCES/iptables-1.3.5 /usr/src/iptables
[root@localhost src]# ln -s /usr/src/kernels/2.6.18-164.el5-i686 /usr/src/linux

이제 커널과 iptables에 patch-o-matic-ng 패치를 가합니다.

[root@localhost src]# cd /usr/src/patch-o-matic-ng-20080521
[root@localhost patch-o-matic-ng-20080521]# ./runme –download
[root@localhost patch-o-matic-ng-20080521]# ./runme geoip
Welcome to Patch-o-matic ($Revision: 6736 $)! Kernel:   2.6.18, /usr/src/linux
Iptables: 1.3.5, /usr/src/iptables
Each patch is a new feature: many have minimal impact, some do not.
Almost every one has bugs, so don’t apply what you don’t need!
——————————————————-
Already applied:
Testing geoip… not applied
The geoip patch:
Author: Samuel Jean <jix@bugmachine.ca>; Nicolas Bouliane <nib@bugmachine.ca>
Status: Stable This patch makes possible to match a packet
by its source or destination country. GeoIP options:
[!]   –src-cc, –source-country country[,country,country,…]                         Match packet coming from (one of)
the specified country(ies)         [!]   –dst-cc, –destination-country country[,country,country,…]                         Match packet going to (one of)
the specified country(ies)            NOTE: The country is inputed by its ISO3166 code. The only extra files you need is a binary db (geoipdb.bin) & its index file (geoipdb.idx).
Take a look at http://people.netfilter.org/peejix/geoip/howto/geoip-HOWTO.html
for a quick HOWTO.
—————————————————————–
Do you want to apply this patch [N/y/t/f/a/r/b/w/q/?] yExcellent! Source trees are ready for compilation.

Recompile the kernel image (if there are non-modular netfilter modules).
Recompile the netfilter kernel modules.
Recompile the iptables binaries.
[root@localhost patch-o-matic-ng-20080521]#

srpm으로 받아놓은 iptables의 extension 디렉토리에 geoip 관련 패치파일이 생성되어 있는 것을 확인할 수 있습니다.

[root@localhost extensions]# ls -l /usr/src/iptables/extensions | grep geoip
-rw-r–r– 1 root root   9518 10월 31 13:53 libipt_geoip.c
-rw-r–r– 1 root root    817 10월 31 13:53 libipt_geoip.man
[root@localhost extensions]#

/usr/src/iptables 디렉토리 안에서 make 해 준 뒤 extension 디렉토리에 들어가면 libipt_geoip.so 파일이 생성되어 있는 것을 확인할 수 있습니다.

[root@localhost extensions]# cd /usr/src/iptables
[root@localhost iptables]# make
[root@localhost iptables]# cd extensions
[root@localhost extensions]# ls -l | grep libipt_geoip.so
-rwxr-xr-x 1 root root   7470 10월 31 13:55 libipt_geoip.so
[root@localhost extensions]#

libipt_geoip.so 파일을 /lib/iptables 디렉토리 안에 복사해 넣어주면 iptables 관련 작업은 끝납니다.

[root@localhost extensions]# cp libipt_geoip.so /lib/iptables
[root@localhost extensions]# ls -l /lib/iptables | grep geoip
-rwxr-xr-x 1 root root  7470 10월 31 13:57 libipt_geoip.so
[root@localhost extensions]#

커널 작업은 아래와 같습니다.

[root@localhost extensions]# cd /usr/src/linux
[root@localhost extensions]# make oldconfig
geoip match support (IP_NF_MATCH_GEOIP) [N/m/?] (NEW)
[root@localhost linux]# make modules_prepare
scripts/kconfig/conf -s arch/i386/Kconfig
CHK     include/linux/version.h
CHK     include/linux/utsrelease.h
HOSTCC  scripts/genksyms/genksyms.o
HOSTCC  scripts/genksyms/lex.o
HOSTCC  scripts/genksyms/parse.o
HOSTLD  scripts/genksyms/genksyms
CC      scripts/mod/empty.o
MKELF   scripts/mod/elfconfig.h
HOSTCC  scripts/mod/file2alias.o
HOSTCC  scripts/mod/modpost.o
HOSTCC  scripts/mod/sumversion.o
HOSTLD  scripts/mod/modpost
[root@localhost linux]#

[root@localhost linux]# mv net/ipv4/netfilter/Makefile net/ipv4/netfilter/Makefile.orig
[root@localhost linux]# vim net/ipv4/netfilter/Makefile

obj-m := ipt_geoip.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD   := $(shell pwd)

default:
$(MAKE) -C $(KDIR) M=$(PWD) modules

[root@localhost linux]# make M=net/ipv4/netfilter
[root@localhost linux]# cp net/ipv4/netfilter/ipt_geoip.ko /lib/modules/2.6.18-164.el5/kernel/net/ipv4/netfilter/
[root@localhost linux]# chmod 744 /lib/modules/2.6.18-164.el5/kernel/net/ipv4/netfilter/ipt_geoip.ko
[root@localhost linux]# depmod -a
[root@localhost linux]# ls -l /lib/modules/2.6.18-164.el5/kernel/net/ipv4/netfilter | grep geoip
-rwxr–r– 1 root root 139757 10월 31 14:02 ipt_geoip.ko
[root@localhost linux]# modprobe ipt_geoip
[root@localhost linux]# lsmod | grep geoip
ipt_geoip               7684  0
x_tables               17349  5 ipt_geoip,ipt_REJECT,xt_state,xt_tcpudp,ip_tables
[root@localhost linux]#

※ CentOS 커널에 geoip 붙이는 건 김정균님이 배포하고 계신 스크립트로 간단하게 해결 가능합니다.
http://my.oops.org/117

URL Encoding Table

Character Purpose in URL Encoding
: Separate protocol (http) from address %3A
/ Separate domain and directories %2F
# Separate anchors %23
? Separate query string %3F
& Separate query elements %24
@ Separate username and password from domain %40
% Indicates an encoded character %25
+ Indicates a space %2B
<space> Invalid in URLs %20 or +
; Invalid in URLs %3B
= Invalid in URLs $3D
$ Invalid in URLs %26
, Invalid in URLs %2C
< Invalid in URLs %3C
> Invalid in URLs %3E
~ Invalid in URLs %25
^ Invalid in URLs %5E
` Invalid in URLs %60
\ Invalid in URLs %5C
[ Invalid in URLs %5B
] Invalid in URLs %5D
{ Invalid in URLs %7B
} Invalid in URLs %7D
| Invalid in URLs %7C
Invalid in URLs %22

Difference Between VMFS 3 and VMFS 5

This post explains you the major difference between VMFS 3 and VMFS 5. VMFS 5 is available as part of vSphere 5. VMFS 5 is introduced with lot of performance enhancements. Newly installed ESXi 5 will be formatted with VMFS 5 version but if you have upgraded the ESX 4 or ESX 4.1 to ESXi 5, then datastore version will be VMFS 3 only. You will able to upgrade the VMFS 3 to VMFS 5 via vSphere client once ESXi upgrade is Complete. This posts tells you some major differences between VMFS 3 and VMFS 5

Capability VMFS 3 VMFS 5
Maximum single Extend size 2 TB  less 512 bytes 64 TB
Partition Style MBR (Master Boot Record) style GPT (GUID Partition Table)
Available Block Size 1 MB/2MB/4MB/8MB  only 1 MB
Maximum size of RDM in
Virtual Compatibiltiy
2 TB  less 512 bytes 2 TB  less 512 bytes
Maximum size of RDM in
Phsical Compatibiltiy
2 TB  less 512 bytes 64 TB
Supported Hosts versions ESX/ESX 3.X, 4.X & 5.x Only ESXi 5 is supported
Spanned Volume size 64 TB (32 extends with max
size of extent is 2 TB)
64 TB (32 extends with
any size combination)
Upgrade path VMFS 3 to VMFS 5 Latest Version. NO upgarde
available yet.
File Limit 30,000 100,000
Sub-Block size 64 KB 8 KB

systemd 살펴보기

systemd 살펴보기

우선, systemd가 이렇게 급 부상하게 될 줄은 솔직히 몰랐다. 워낙 많은 오픈소스 프로젝트들이 나오고 있기 때문에 그 중 하나 정도로만 생각했는데 어느 순간 Fedora에 적용이 되더니 당연한 수순대로 RHEL7에 도입이 되었다. 아직 Debian/Ubuntu 계열은 기본으로 채택되지 않았지만 조만간 릴리즈되는 버전에서 새로운 PID 1으로 자리잡게 될 것 같다.

이러한 분위기에 맞추어 systemd가 무엇이고 이에 대해서 간단히 짚어 볼 필요가 있을 듯 하여 간단히 소개해 본다. (늘 그렇듯이 상세한 내용은 공식홈페이지의 문서를 참고하는 것이 좋다)

사실 RHEL6가 등장하고 Upstart가 도입되었을 때는 그저 초기화 스크립트 관리 방안이 바뀌었을 뿐이기에 크게 신경쓰지 않았고 주변에 관련내용 공유도 별로하지 않았다. (설정에 꼭 필요한 방법만 공유했었다) 하지만, systemd의 경우는 Upstart와 비교되지 않는 물건이기 때문에 이렇게 문서까지 작성해서 공유하게 되었다.

systemd는?

systemd가 처음 소개되고 프로젝트가 진행 될 때 커뮤니티 반응은 시끌벅적했다. 우선 systemd는 전통적으로 Unix계열 운영체제의 PID 1이었던 init(System V Init)을 교체하는 역할 뿐만 아니라 초기화 스크립트 관리자이고 로그시스템 관리자이기도 하다. 또한, 하드웨어에 대한 부분과 cgroup 관리 등 시스템 전반적인 부분에 관여하고 있다. 심지어 기존 SysV에서 공통적으로 사용되었던 프로세스 데몬을 만들기 위한 setsid() 콜도 필요없고 PID파일을 따로 관리할 필요도 없다. 이러한 systemd는 유닉스의 철학인 ‘한 가지만 잘하자’와 상반되기 때문에 논란이 되곤 했었다. PulseAudio 개발자라는 이유(커뮤니케이션이 잘 안되기로 유명했다 한다)로 싸우는건 또 다른 논란이었고…

여러 논란가운데 결국 대표적인 배포판에 입성하게 되었는데 논란의 결과가 어찌되었건 납득할 만한 디자인과 성능을 가지고 있기에 채택되지 않았을까 한다.

이 프로젝트의 철학(?)을 알고 싶다면 개발자인 Nennart의 블로그를 읽어보는 것도 좋다.

준비물

실제 systemd가 어떠한 물건인지 알아보기 위한 간단한 실습을 위해서 당연히 systemd를 사용중인 배포판을 설치해서 사용해 보는 것이 좋다.

  • RHEL7
  • CentOS 7
  • Fedora
  • OpenSUSE

Debian이나 Ubuntu에서 사용하는 방법은 나중에 시간 될 때 다른 문서를 통해서 공유하겠다. (아마 그 전에 정식채택 될 것 같다. – 관련내용)

실행하며 살펴보기

아래 내용은 RHEL7에서 실행한 내용이며 어느 배포판을 사용하더라도 크게 차이는 없다.

1. PID 1

먼저 ps 명령을 통해서 PID 1 프로세스를 확인해 보자

$ ps -p 1 ef
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /usr/lib/systemd/systemd --switched-root --system --deserialize 24

늘 익숙했던 init 대신에 systemd가 PID 1로 자리잡고 있다.

2. 설정파일

systemd는 /etc/systemd 아래에 설정파일을 두고 있다.

$ ls /etc/systemd
bootchart.conf  journald.conf  logind.conf  system  system.conf  user  user.conf  

이러한 설정파일과 시스템에 미리 정의 된 Service, Target 파일을 통해서 시스템을 컨트롤하게 되는데 Service, Target 파일은 아래에 정의 되어있으며 보통 /etc/systemd에서 설정하면서 심볼릭 링크를 통해서 사용한다.

바이너리 실행파일은 아래 경로에서 확인 가능하며
$ ls /lib/systemd/

기본적인 시스템의 Service, Target은 아래에 위치하고 있다
$ ls /lib/systemd/system

3. 부팅시간

systemd는 최소한의 서비스만을 실행시키고 병렬화해서 실행시키는데 주안점을 두고 있기 때문에 기존에 순차적 방식으로 처리하는 SysV에 비해서 부팅속도가 빠른 편이다. RHEL7이 RHEL6보다 부팅속도가 매우 빠른건 systemd 때문이다.

그러면, 부팅에 걸린 시간을 알아보자

$ systemd-analyze
Startup finished in 421ms (kernel) + 1.206s (initrd) + 25.873s (userspace) = 27.501s  

총, 27초 정도가 소요 된 것을 확인 할 수 있는데 커널 초기화 작업에는 1초미만 램디스크 초기화에 1.2초 그리고 실제 systemd 프로세스에 의해서 초기화 작업이 진행 된 시간은 26초 정도 이다. 이를 토대로 부팅 시간을 단축 시킬(즉, 불필요한 프로세스가 있는지 여부부터 오동작으로 인해 시간을 많이 잡아먹는지) 방안에 대해서 생각해 볼 수 있다.

$ systemd-analyze blame
         20.732s kdump.service
          1.395s firewalld.service
          1.040s postfix.service
          1.031s lvm2-monitor.service
           997ms tuned.service
           974ms boot.mount
           782ms network.service
           588ms lvm2-pvscan@8:2.service
           571ms iprupdate.service
           571ms iprinit.service
           423ms sshd.service
           348ms systemd-logind.service
           324ms avahi-daemon.service
           312ms iprdump.service
           296ms NetworkManager.service
           269ms rsyslog.service
           192ms systemd-fsck-root.service
           191ms kmod-static-nodes.service
           ... 하략 ...

상기 결과는 가상머신에 올린 게스트 머신이기 때문인지 kdump 서비스가 차지하는 시간이 많은 것으로 나타났다. 이 blame 결과를 통해서 불필요한 서비스를 제거하거나 이상이 있는 서비스를 확인해 볼 수 있다. (Blame Game 참고)

또한, 시간이 많이 소요된 서비스에 대해 실행과 대기에 대해서 체인형태로 확인하는 방법도 있다.

$ systemd-analyze critical-chain
The time after the unit is active or started is printed after the "@" character.  
The time the unit takes to start is printed after the "+" character.

multi-user.target @25.865s  
└─kdump.service @5.131s +20.732s
  └─network.target @5.130s
    └─network.service @4.346s +782ms
      └─NetworkManager.service @4.049s +296ms
        └─firewalld.service @2.649s +1.395s
          └─basic.target @2.646s
            └─paths.target @2.646s
              └─brandbot.path @2.645s
                └─sysinit.target @2.638s
                  └─systemd-update-utmp.service @2.630s +7ms
                    └─auditd.service @2.517s +111ms
                      └─local-fs.target @2.513s
                        └─boot.mount @1.538s +974ms
                          └─systemd-fsck@dev-disk-by\x2duuid-b4f107ad\x2df256\x2d48b4\x2d9558\x2d24
                            └─dev-disk-by\x2duuid-b4f107ad\x2df256\x2d48b4\x2d9558\x2d2483cbca6a7d.

그 외에 systemd-analyze 툴을 통해서 부팅 과정을 그래프화 해서 볼 수 있으며

$ systemd-analyze dot | dot -Tsvg > systemd.svg
$ systemd-analyze plot > systemd.svg

이러한 부팅 분석 툴만으로도 기존 init에 비해서 프로파일링이 편리해졌음을 확인 할 수 있다.

4. Run Level 변경

systemd는 기존 init 커맨드와 달리 숫자 기반의 런레벨이 아니라 각 런레벨에 대한 설정 세트를 통해서 런레벨을 변경합니다.

싱글모드(기존 런레벨1)

$ systemctl rescue

멀티유저모드(기존 런레벨3)

$ systemctl isolate multi-user.target
$ systemctl isolate runlevel3.target

과거 init 시스템에 익숙한 사용자를 위해서 runlevel3라는 이름으로 multi-user.target 파일을 심볼릭 링크를 걸어두었기 때문에 위 두가지 명령이 모두 사용가능 하다.

$ ls -l /lib/systemd/system/runlevel3.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 /lib/systemd/system/runlevel3.target -> multi-user.target  

그래픽모드(기존 런레벨5)

$ systemctl isolate graphical.target
$ systemctl isolate runlevel5.target

멀티유저모드와 마찬가지로 2가지 명령으로 전환 가능하며 실제 기존 형태의 런레벨+숫자 형태의 Target 파일은 아래와 같이 심볼릭 링크로 연결되어있다.

lrwxrwxrwx. 1 root root 15 Oct 21 00:28 runlevel0.target -> poweroff.target  
lrwxrwxrwx. 1 root root 13 Oct 21 00:28 runlevel1.target -> rescue.target  
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel2.target -> multi-user.target  
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel3.target -> multi-user.target  
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel4.target -> multi-user.target  
lrwxrwxrwx. 1 root root 16 Oct 21 00:28 runlevel5.target -> graphical.target  
lrwxrwxrwx. 1 root root 13 Oct 21 00:28 runlevel6.target -> reboot.target  

즉, 시스템 종료/재부팅을 위한 런레벨도 여전히 사용가능하다.

런레벨 기본 값 설정

상기에서 전환하는 런레벨 Target을 아래와 같은 명령을 통해서 기본 값으로 설정 할 수 있다. 또한, 현재 설정된 기본 값을 확인 할 수도 있다

$ systemctl set-default multi-user.target
$ systemctl get-default
multi-user.target  

시스템 명령

앞서 각각의 런레벨 파일이 poweroff.target 등 으로 연결되어있는 것을 확인하였는데 isolate 명령이 아닌 시스템 명령을 통해서 해당 Target을 바로 적용하는게 가능하다. 아래는 몇 가지 예시이다.

$ systemctl poweroff (Shutdown처리 후 Power-Off처리)
$ systemctl emergency (Rescue와 유사하지만 root 파일시스템만 읽기전용으로 마운트한다)
$ systemctl halt (Shutdown처리 후 Halt처리)
$ systemctl reboot (Shutdown처리 후 리부팅처리)
$ systemctl kexec (kexec를 통해서 리부팅한다)
$ systemctl suspend (시스템 정지)
$ systemctl hibernate (시스템 Hibernate)
$ systemctl hybrid-sleep (시스템을 Hibernate하고 정지시킨다)

5. 서비스 목록

systemctl을 통해서 서비스를 관리 할 수 있는데 먼저 서비스 목록 확인 방법을 알아보자.

서비스 목록은 간단하게 systemctl 명령만 실행해도 확인 할 수 있으며 이를 상태기준으로 보기편하게 아래와 같이 확인 할 수도 있다.

$ systemctl list-unit-files
UNIT FILE                                   STATE  
sys-fs-fuse-connections.mount               static  
sys-kernel-config.mount                     static  
sys-kernel-debug.mount                      static  
tmp.mount                                   disabled  
brandbot.path                               disabled  
systemd-ask-password-console.path           static  
systemd-ask-password-plymouth.path          static  
systemd-ask-password-wall.path              static  
session-1.scope                             static  
session-2.scope                             static  
auditd.service                              enabled  
autovt@.service                             disabled  
avahi-daemon.service                        enabled  
... 하략 ...

그 외에 Listening하는 소켓관련 목록을 확인 할 수도 있고 각 서비스를 의존성에 따라 확인 할 수도 있다.

$ systemctl list-sockets
LISTEN                          UNIT                         ACTIVATES  
/dev/initctl                    systemd-initctl.socket       systemd-initctl.service
/dev/log                        systemd-journald.socket      systemd-journald.service
/run/dmeventd-client            dm-event.socket              dm-event.service
/run/dmeventd-server            dm-event.socket              dm-event.service
/run/lvm/lvmetad.socket         lvm2-lvmetad.socket          lvm2-lvmetad.service
/run/systemd/journal/socket     systemd-journald.socket      systemd-journald.service
/run/systemd/journal/stdout     systemd-journald.socket      systemd-journald.service
/run/systemd/shutdownd          systemd-shutdownd.socket     systemd-shutdownd.service
/run/udev/control               systemd-udevd-control.socket systemd-udevd.service
/var/run/avahi-daemon/socket    avahi-daemon.socket          avahi-daemon.service
/var/run/dbus/system_bus_socket dbus.socket                  dbus.service
kobject-uevent 1                systemd-udevd-kernel.socket  systemd-udevd.service

12 sockets listed.  

list-dependencies의 경우 뒤에 Service/Target 이름을 별도로 지정 가능하다.

$ systemctl list-dependencies swap.target
swap.target  
├─dev-disk-by\x2did-dm\x2dname\x2drhel\x2dswap.swap
├─dev-disk-by\x2did-dm\x2duuid\x2dLVM\x2dWu6fS25DomohkfmsDzYY8SzAfEPmpojKfhfiV6D6AYa86f2Bb7nOkSq...
├─dev-disk-by\x2duuid-c3591b93\x2d0cc0\x2d457c\x2db1f5\x2d79ea0658d54d.swap
├─dev-dm\x2d1.swap
├─dev-mapper-rhel\x2dswap.swap
├─dev-mapper-rhel\x2dswap.swap
└─dev-rhel-swap.swap

실패 서비스 확인

부팅하는 과정에서 실패한 서비스에 대해서 아래와 같이 확인이 가능하다. 또한, 위에서 언급되었던 list-sockets 같은 명령에도 옵션으로 지정하여 실패한 항목을 확인 할 수 있다.

$ systemctl --failed
systemctl --failed  
UNIT          LOAD   ACTIVE SUB    DESCRIPTION  
rhnsd.service loaded failed failed LSB: Starts the Spacewalk Daemon

LOAD   = Reflects whether the unit definition was properly loaded.  
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.  
SUB    = The low-level unit activation state, values depend on unit type.

1 loaded units listed. Pass --all to see loaded but inactive units, too.  
To show all installed unit files use 'systemctl list-unit-files'.  

6. 서비스 관리

서비스를 설정하고 관리하는 방법은 기본적으로 아래와 같다.

- 서비스 활성화
$ systemctl enable [서비스명]

- 서비스 비활성화
$ systemctl disable [서비스명]

- 서비스 시작
$ systemctl start [서비스명]

- 서비스 종료
$ systemctl stop [서비스명]

- 서비스 재시작
$ systemctl restart [서비스명]

- 서비스 갱신
$ systemctl reload [서비스명]

그리고, 각각의 서비스에 대해서 부팅 때 실행되도록 설정 되었는지 여부 및 현재 실행 여부 등을 확인 할 수 있으며 간단한 응답으로 종료하기 때문에 스크립트 작성할 때 좋다.

$ systemctl is-enabled [서비스명]
$ systemctl is-active [서비스명]

예시)
$ systemctl is-enabled crond
enabled  
$ systemctl is-active auditd
active  

이렇게 변경한 서비스 설정 정보를 데몬에 반영하기 위해서는 아래와 같이 실행하면 된다

$ systemctl daemon-reload

서비스 상태도 확인이 가능한데 이 상태 확인은 기존 init 스크립트가 제공하던 실행 여부 이상으로 각 서비스의 CGroup 관련 정보 및 로그정보까지 확인이 가능하다. (-ㅣ 옵션은 한 줄을 넘어서는 라인을 축약하지 말고 전부 보여주라는 옵션이다)

$ systemctl status crond -l
crond.service - Command Scheduler  
   Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled)
   Active: active (running) since Tue 2014-10-21 00:31:58 EDT; 2h 10min ago
 Main PID: 583 (crond)
   CGroup: /system.slice/crond.service
           └─583 /usr/sbin/crond -n

Oct 21 00:31:58 localhost.localdomain systemd[1]: Started Command Scheduler.  
Oct 21 00:31:58 localhost.localdomain crond[583]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 2% if used.)  
Oct 21 00:31:59 localhost.localdomain crond[583]: (CRON) INFO (running with inotify support)  

상태확인 뿐만 아니라 kill 명령을 통해서 서비스 관련 모든 프로세스에 kill 시그널을 보낼 수도 있다.

$ systemctl kill httpd

웹서버(http) 관련 프로세스가 모두 죽어있음을 확인할 수 있다.

7. 로그 관리

systemd는 단순한 init 대체제가 아니라 시스템 전반적인 부분을 관리하는 프로그램이기 때문에 로그에 대한 관리 부분도 있다. 로그는 systemd-journald를 통해서 관리되며 이를 제어하는 툴은 journalctl이다.

단순히 전체 이벤트 로그를 확인하기 위해서는 journalctl 만 실행하면 되며 몇 가지 유용한 커맨드를 소개한다.

바이너리에 대한 이벤트

프로세스로 실행이 가능한 특정 바이너리에 대한 이벤트는 아래와 같이 확인 할 수 있다.

$ journalctl /sbin/crond
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:58 localhost.localdomain crond[583]: (CRON) INFO (RANDOM_DELAY will be scaled with fac  
Oct 21 00:31:59 localhost.localdomain crond[583]: (CRON) INFO (running with inotify support)  
lines 1-3/3 (END)  

기간 조회

특정 일자부터의 이벤트 로그를 확인하는 방법은 아래와 같은데

$ journalctl --since=today

today 대신에 yesterday, tomorrow 같은 단어도 가능하다. 또한, “YYYY-MM-DD HH:MM:SS” 형태의 시간 값을 이용해서 구간 별 조회가 가능한데 시간을 생략하면 0시0분0초를 기준으로 하게 된다.

$ journalctl --since=2014-10-21 --until=2014-10-22
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:54 localhost.localdomain systemd-journal[81]: Runtime journal is using 8.0M (max 92.0M  
Oct 21 00:31:54 localhost.localdomain systemd-journal[81]: Runtime journal is using 8.0M (max 92.0M  
Oct 21 00:31:54 localhost.localdomain kernel: Initializing cgroup subsys cpuset  

마지막 부팅 이후의 로그는 -b 옵션으로 확인이 가능하며

$ journalctl -b

속성에 따른 조회

특정, 우선순위에 따른 (syslog에서 지정하는 debug, info, err 등) 조회도 가능하다.

$ journalctl -p err
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:54 localhost.localdomain kernel: Detected CPU family 6 model 69  
Oct 21 00:31:54 localhost.localdomain kernel: Warning: Intel CPU model - this hardware has not unde  
Oct 21 00:31:54 localhost.localdomain kernel: tsc: Fast TSC calibration failed  
Oct 21 00:31:54 localhost.localdomain systemd-fsck[260]: fsck failed with error code 8.  
Oct 21 00:31:56 localhost.localdomain kernel: piix4_smbus 0000:00:07.0: SMBus base address uninitia  
Oct 21 00:31:58 localhost.localdomain audispd[544]: No plugins found, exiting  
Oct 21 00:32:00 localhost.localdomain systemd[1]: Failed to start LSB: Starts the Spacewalk Daemon.  
lines 1-8/8 (END)  

기타 옵션들

이 외에 tail -f로 로그파일을 걸어둔 것 같은 효과를 갖는 -f 옵션과 json을 비롯한 다양한 포맷으로 로그를 재포매팅 하는 옵션인 -o 옵션 등이 있다.

$ journalctl -f
$ journalctl -p err -o json-pretty
{
        "__CURSOR" : "s=ee396e27b84346d4a5163e52bb6a839c;i=5b;b=03fa23106cc04ce99a97bf6a5e45e6aa;m=
        "__REALTIME_TIMESTAMP" : "1413865914539904",
        "__MONOTONIC_TIMESTAMP" : "459391",
        "_BOOT_ID" : "03fa23106cc04ce99a97bf6a5e45e6aa",
        "_MACHINE_ID" : "a73fc4e71dd64fe98f580bffe567ea29",
        "_HOSTNAME" : "localhost.localdomain",
        "_SOURCE_MONOTONIC_TIMESTAMP" : "0",
        "_TRANSPORT" : "kernel",
        "SYSLOG_IDENTIFIER" : "kernel",
        "PRIORITY" : "2",
        "MESSAGE" : "Detected CPU family 6 model 69"
}

8. CGroup 관리

리소스 정보 조회

systemd에는 CGroup(control group)을 관리하는 기능도 포함되어있다. (홈페이지에서 문서를 읽다보면 없는게 있을까 싶을 정도로 너무 많은 기능을 가지고 있다. 문서 서두에 이야기 한 것 처럼 이로 인해 Unix 철학과 대치 된다고 논란이 있던 프로그램이다)

먼저 systemd-cgls 명령은 현재 cgroup에 대한 정보를 타입별로 출력해준다.

$ systemd-cgls
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 24
├─user.slice
│ ├─user-1000.slice
│ │ └─session-2.scope
│ │   ├─10191 sshd: lunatine [priv
│ │   ├─10195 sshd: lunatine@pts/0
│ │   └─17794 systemd-cgls
│ └─user-0.slice
│   └─session-1.scope
│     ├─ 595 login -- root
│     └─7805 -bash
└─system.slice
  ├─httpd.service
  │ ├─17779 /usr/sbin/httpd -DFOREGROUND
  │ ├─17780 /usr/sbin/httpd -DFOREGROUND
  │ ├─17781 /usr/sbin/httpd -DFOREGROUND
  │ ├─17782 /usr/sbin/httpd -DFOREGROUND
  │ ├─17783 /usr/sbin/httpd -DFOREGROUND
  │ └─17784 /usr/sbin/httpd -DFOREGROUND
... 하략 ...

systemd-cgtop의 경우는 흔히 알고 있는 top 명령과 같이 cgroup에 대하여 CPU, Memory, I/O에 대한 정렬 결과를 출력해 준다. 본인이 설정한 cgroup에 대해 적합하게 리소스가 분배되고 있는지 확인하는데 유용하다.

리소스 관리

앞서 살펴보았던 systemctl의 경우 set-property 명령을 통해서 리소스 값을 제어할 수 있는데 실제로는 systemctl로 설정하면 systemd.resource-control이라는 프로그램이 해당 리소스 할당작업을 수행한다.

$ systemctl set-property httpd.service CPUShares=200
$ systemctl show -p CPUShares httpd.service
CPUShares=200  
$ cat /sys/fs/cgroup/cpu/system.slice/httpd.service/cpu.shares
200  

systemctl을 통해서 설정을 하지만 실제로 systemd.resource-control에 의해서 설정되기 때문에 상세한 설정 옵션에 대해서는 systemd.resource-control의 man 페이지를 확인해야 한다.

이러한 리소스관리 툴도 포함되어있기 때문에 cgroup 설정을 위한 스크립트 작성이 한결 간편해지고 체계적이 될 수 있다.

9. 호스트명 설정

systemd에는 hostnamectl이란 툴도 있는데 이 툴로 호스트명 설정도 가능하다.

$ hostnamectl
   Static hostname: rhel7
         Icon name: computer
           Chassis: n/a
        Machine ID: a73fc4e71dd64fe98f580bffe567ea29
           Boot ID: 711ec89043a543fa8751aa686257dd81
    Virtualization: oracle
  Operating System: Red Hat Enterprise Linux Server 7.0 (Maipo)
       CPE OS Name: cpe:/o:redhat:enterprise_linux:7.0:GA:server
            Kernel: Linux 3.10.0-123.el7.x86_64
      Architecture: x86_64

$ hostnamectl set-hostname new-rhel7

위 명령은 /etc/hostname 설정파일을 변경하게 된다. 옵션 중에 --transient는 DHCP, mDNS에 의해서 변경가능한 커널에 의해 관리되는 호스트명을 수정하고 --pretty는 UTF-8 인코딩으로 호스트명을 지정한다.

10. 로케일 설정

systemd에 포함된 localectl은 시스템의 로케일을 설정한다. 현재 설정된 정보는 localectl 실행 결과로 확인 할 수 있다.

$ localectl
   System Locale: LANG=en_US.UTF-8
       VC Keymap: us
      X11 Layout: us

로케일 변경은 set-locale로 키맵은 set-keymap, X서버를위한 키맵은 set-x11-keymap으로 변경 가능하다. 이 툴은 /usr/lib/locale/locale-archive 정보를 바탕으로 /var/run/dbus/systembussocket을 통해서 변경을 수행한다.

$ localectl set-locale LANG=ko_KR.UTF-8
$ localectl set-keymap fr
$ localectl set-x11-keymap fr
$ localectl
   System Locale: LANG=ko_KR.UTF-8
       VC Keymap: fr
      X11 Layout: fr

변경가능한 로케일, 키맵 등은 list-locales, list-keymaps, list-x11-keymap-models, list-x11-keymap-layouts, list-x11-keymap-variants, list-x11-keymap-options으로 확인 가능하다.

11. 사용자 관리

loginctl 툴을 이용해서 현재 사용자 세션에 대해서 관리가 가능하다.

$ loginctl list-users
       UID USER
      1000 lunatine

1 users listed.  

list-session으로 현재 세션들을 확인 할 수 있으며 lock-session 등의 명령으로 세션을 잠글 수 있다. 또한, show-user를 통해 사용자 정보 조회도 가능하고 kill-user를 통해서 사용자 프로세스에 시그널을 보낼 수도 있다.

자세한 내용은 loginctl --help로 확인해 보도록 하자.

12. 시간 설정

timedatectl은 시간대를 조회하고 설정하는 기능을 제공한다. set-time은 시간을 set-timezone은 시간대를 설정한다. 또한, set-ntp를 통해 NTP 활성화 여부도 설정이 가능하다.

$ timedatectl
      Local time: Tue 2014-10-21 09:45:29 EDT
  Universal time: Tue 2014-10-21 13:45:29 UTC
        RTC time: Tue 2014-10-21 13:45:29
        Timezone: America/New_York (EDT, -0400)
     NTP enabled: n/a
NTP synchronized: no  
 RTC in local TZ: no
      DST active: yes
 Last DST change: DST began at
                  Sun 2014-03-09 01:59:59 EST
                  Sun 2014-03-09 03:00:00 EDT
 Next DST change: DST ends (the clock jumps one hour backwards) at
                  Sun 2014-11-02 01:59:59 EDT
                  Sun 2014-11-02 01:00:00 EST

$ timedatectl set-timezone Asia/Seoul
$ timedatectl
      Local time: Tue 2014-10-21 22:46:04 KST
  Universal time: Tue 2014-10-21 13:46:04 UTC
        RTC time: Tue 2014-10-21 13:46:04
        Timezone: Asia/Seoul (KST, +0900)
     NTP enabled: n/a
NTP synchronized: no  
 RTC in local TZ: no
      DST active: n/a

13. 원격 관리

systemd의 모든 명령어들은 -H옵션을 제공하는데 이 옵션을 통해서 원격지 서버에 ssh 접속을 통해 설정 및 정보조회가 가능하다. 아래 예제는 원격지 서버의 호스트명을 수정하는 내용이다.

$ hostnamectl -H root@rhel7.test.com set-hostname rhel7-new
root@rhel7.test.com's password:

$ hostnamectl -H root@rhel7.test.com
root@rhel7's password:  
   Static hostname: rhel7-new
         Icon name: computer
           Chassis: n/a
        Machine ID: a73fc4e71dd64fe98f580bffe567ea29
           Boot ID: 711ec89043a543fa8751aa686257dd81
    Virtualization: oracle
  Operating System: Red Hat Enterprise Linux Server 7.0 (Maipo)
       CPE OS Name: cpe:/o:redhat:enterprise_linux:7.0:GA:server
            Kernel: Linux 3.10.0-123.el7.x86_64
      Architecture: x86_64

마치며

본 문서에서 systemd의 기능들을 간단히 알아보았다. systemd는 마치 맥가이버칼 처럼 다양한 시스템 설정 기능을 포함하고 있으며 계속해서 개선되고 추가되고 있다. Unix의 ‘한 가지만 잘하자’ 철학에 위배되는 프로그램일지 모르지만 실제는 내부적으로 한가지 일을 잘 하는 툴 들로 구성되어있기 때문에 위배라고 보기도 어렵다.

그리고 systemd는 기존 System V Init에 익숙한 사용자를 위해 디렉토리 구조 및 호환성을 일부 제공하고 있다. 심볼릭 링크이지만 init도 존재하고 0123456qQuUsS 옵션도 제공한다. 이 때문에 runlevel$.target 심볼릭링크 파일이 존재한다.

대표적인 리눅스 배포판에서 선택되었으며 이제는 엔터프라이즈 리눅스 배포판에도 선택이 되었다. 머지 않아 Debian 계열 배포판에서도 기본으로 채택이 될 것이라고 하니 이제 슬슬 System V Init을 떠나 보낼 때가 왔나보다.

기존 시스템 관리에 사용하던 자동화 스크립트를 systemd에 맞춰서 수정 할 일만 남았다.

REST API의 이해와 설계-#3 API 보안

REST API의 이해와 설계

#3 API 보안

REST API 보안 

API 보안에 대해서는 백번,천번을 강조해도 과함이 없다. 근래에 대부분의 서비스 시스템들은 API를 기반으로 통신을 한다.

앱과 서버간의 통신 또는 자바스크립트 웹 클라이언트 와 서버간의 통신등 대부분의 통신이 이 API들을 이용해서 이루어지기 때문에, 한번 보안이 뚫려 버리면 개인 정보가 탈취되는 것 뿐만 아니라 많은 큰 문제를 야기할 수 있다.

REST API 보안 관점

API는 보안 포인트에 따라서 여러가지 보안 관점이 존재하는데, 크게 아래와 같이 5가지 정도로 볼 수 있다.

인증 (Authentication)

인증은 누가 서비스를 사용하는지를 확인하는 절차이다.

쉽게 생각하면 웹 사이트에 사용자 아이디와 비밀 번호를 넣어서, 사용자를 확인하는 과정이 인증이다.

API도 마찬가지로 API를 호출하는 대상 (단말이 되었건, 다른 서버가 되었건, 사용자가 되었건)을 확인하는 절차가 필요하고 이를 API 인증이라고 한다.

인가 (Authorization)

인가는 해당 리소스에 대해서, 사용자가 그 리소스를 사용할 권한이 있는지 체크하는 권한 체크 과정이다.

예를 들어 /users라는 리소스가 있을 때, 일반 사용자 권한으로는 내 사용자 정보만 볼 수 있지만, 관리자 권한으로는 다른 사용자 정보를 볼 수 있는 것과 같은 권한의 차이를 의미한다.

네트워크 레벨 암호화

인증과 인가 과정이 끝나서 API를 호출하게 되면, 네트워크를 통해서 데이터가 왔다갔다 하는데, 해커등이 중간에 이 네트워크 통신을 낚아 채서(감청) 데이터를 볼 수 없게 할 필요가 있다.

이를 네트워크 프로토콜단에서 처리하는 것을 네트워크 레벨의 암호화라고 하는데, HTTP에서의 네트워크 레벨 암호화는 일반적으로 HTTPS 기반의 보안 프로토콜을 사용한다.

메시지 무결성 보장

메시지 무결성이란, 메시지가 중간에 해커와 같은 외부 요인에 의해서 변조가 되지 않게 방지하는 것을 이야기 한다.

무결성을 보장하기 위해서 많이 사용하는 방식은 메시지에 대한 Signature를 생성해서 메시지와 같이 보낸 후에 검증하는 방식으로, 예를 들어 메시지 문자열이 있을 때, 이 문자열에 대한 해쉬코드를 생성해서, 문자열과 함께 보낸 후, 수신쪽에서 받은 문자열과 이 받은 문자열로 생성한 해쉬 코드를 문자열과 함께 온 해쉬코드와 비교하는 방법이 있다. 만약에 문자열이 중간에 변조되었으면, 원래 문자열과 함께 전송된 해쉬코드와 맞지 않기 때문에 메시지가 중간에 변조가되었는지 확인할 수 있다.

메시지의 무결성의 경우, 앞에서 언급한 네트워크 레벨의 암호화를 완벽하게 사용한다면 외부적인 요인(해커)등에 의해서 메시지를 해석 당할 염려가 없기 때문에 사용할 필요가 없다.

메시지 본문 암호화

네트워크 레벨의 암호화를 사용할 수 없거나, 또는 네트워크 레벨의 암호화를 신뢰할 수 없는 상황의 경우 추가적으로 메시지 자체를 암호화 하는 방법을 사용한다. 이는 애플리케이션 단에서 구현하는데, 전체 메시지를 암호화 하는 방법과 특정 필드만 암호화 하는 방법 두가지로 접근할 수 있다.

전체 메시지를 암호화할 경우, 암호화에 소요되는 비용이 클 뿐더라 중간에 API Gateway등을 통해서 메시지를 열어보고 메시지 기반으로 라우팅 변환하는 작업등이 어렵기 때문에 일반적으로 전체를 암호화 하기 보다는 보안이 필요한 특정 필드만 암호화 하는 방법을 사용한다.

그러면 지금부터 각 보안 관점에 대해서 조금 더 구체적으로 살펴보도록 하자.

인증 (Authentication)

API 에 대한 인증은 여러가지 방법이 있으며 각 방식에 따라 보안 수준과 구현 난이도가 다르기 때문에, 각 방식의 장단점을 잘 이해하여 서비스 수준에 맞는 적절한 API 인증 방식을 선택하도록 할 필요가 있다.

API Key 방식

가장 기초적인 방법은 API Key를 이용하는 방법이다. API Key란 특정 사용자만 알 수 있는 일종의 문자열이다. API를 사용하고자 할 때, 개발자는 API 제공사의 포탈 페이등 등에서 API Key를 발급 받고, API를 호출할 때 API Key를 메시지 안에 넣어 호출한다. 서버는 메시지 안에서 API Key를 읽어 이 API가 누가 호출한 API인지를 인증하는 흐름이다.

모든 클라이언트들이 같은 API Key를 공유하기 때문에 한번 API Key가 노출이 되면 전체 API가 뚫려 버리는 문제가 있기 때문에 높은 보안 인증을 요구 하는 경우에는 권장하지 않는다.

API Token 방식

다른 방식으로는 API Token을 발급하는 방식이 있는데, 사용자 ID,PASSWD등으로 사용자를 인증한 후에, 그 사용자가 API 호출에 사용할 기간이 유효한 API Token을 발급해서 API Token으로 사용자를 인증하는 방식이다.

매번 API 호출시 사용자 ID,PASSWD를 보내지 않고, API Token을 사용하는 이유는 사용자 PASSWD는 주기적으로 바뀔 수 있기 때문이고, 매번 네트워크를 통해서 사용자 ID와 PASSWD를 보내는 것은 보안적으로 사용자 계정 정보를 탈취 당할 가능성이 높기 때문에 API Token을 별도로 발급해서 사용하는 것이다.

API Token을 탈취 당하면 API를 호출할 수 는 있지만, 반대로 사용자 ID와 PASSWD는 탈취 당하지 않는다. 사용자PASSWD를 탈취당하면 일반적으로 사용자들은 다른 서비스에도 같은 PASSWD를 사용하는 경우가 많기 때문에 연쇄적으로 다른 서비스에 대해서도 공격을 당할 수 있는 가능성이 높아지기 때문이다. 예를 들어서 매번 호출시마다 사용자 ID,PASSWD를 보내서 페이스북의 계정과 비밀번호를 탈취 당한 경우, 해커가 이 계정과 비밀 번호를 이용해서 GMail이나 트위터와 같은 다른 서비스까지 해킹 할 수 있기 때문에, 이러한 가능성을 최소화하기 위함이다.

 

흐름을 설명하면 위의 그림과 같다.

1. API Client가 사용자 ID,PASSWD를 보내서 API호출을 위한 API Token을 요청한다.

2. API 인증 서버는 사용자 ID,PASSWD를 가지고, 사용자 정보를 바탕으로 사용자를 인증한다.

3. 인증된 사용자에 대해서 API Token을 발급한다. (유효 기간을 가지고 있다.)

4. API Client는 이 API Token으로 API를 호출한다. API Server는 API Token이 유효한지를 API Token 관리 서버에 문의하고, API Token이 유효하면 API 호출을 받아 들인다.

이 인증 방식에는 여러가지 다양한 변종이 존재하는데

먼저 1단계의 사용자 인증 단계에서는 보안 수준에 따라서 여러가지 방식을 사용할 수 있다.

HTTP Basic Auth 

※ 상세 : http://en.wikipedia.org/wiki/Basic_access_authentication

가장 기본적이고 단순한 형태의 인증 방식으로 사용자 ID와 PASSWD를 HTTP Header에 Base64 인코딩 형태로 넣어서 인증을 요청한다.

예를 들어 사용자 ID가 terry이고 PASSWD가 hello world일 때, 다음과 같이 HTTP 헤더에 “terry:hello world”라는 문자열을 Base64 인코딩을해서 “Authorization”이라는 이름의 헤더로 서버에 전송하여 인증을 요청한다.

Authorization: Basic VGVycnk6aGVsbG8gd29ybGQ=

중간에 패킷을 가로채서 이 헤더를 Base64로 디코딩하면 사용자 ID와 PASSWD가 그대로 노출되기 때문에 반드시 HTTPS 프로토콜을 사용해야 한다.

Digest access Authentication

※ 상세 : http://en.wikipedia.org/wiki/Digest_access_authentication

HTTP Basic Auth가 Base64 형태로 PASSWD를 실어서 보내는 단점을 보강하여 나온 인증 프로토콜이 Digest access Authentication 이라는 방법으로, 기본 원리는 클라이언트가 인증을 요청할 때, 클라이언트가 서버로부터 nonce 라는 일종의 난수값을 받은 후에, (서버와 클라이언트는 이 난수 값을 서로 알고 있음), 사용자 ID와 PASSWD를 이 난수값을 이용해서 HASH화하여 서버로 전송하는 방식이다.

이 경우에는 직접 ID와 PASSWD가 평문 형태로 날아가지 않기 때문에, 해커가 중간에 PASSWD를 탈취할 수 없고, 설령 HASH 알고리즘을 알고 있다고 하더라도, HASH된 값에서 반대로 PASSWD를 추출하기가 어렵기 때문에, Basic Auth 방식에 비해서 향상된 보안을 제공한다. 전체적인 흐름을 보자

 

1. 클라이언트가 서버에 특정리소스 /car/index.html 을 요청한다.

2. 서버는 해당 세션에 대한 nonce값을 생성하여 저장한 후에, 클라이언트에 리턴한다. 이때 realm을 같이 리턴하는데, realm은 인증의 범위로, 예를 들어 하나의 웹 서버에 car.war, market.war가 각각 http://myweb/car , http://myweb/market 이라는 URL로 배포가 되었다고 하면, 이 웹사이트는 각각 애플리케이션 car.war와 market.war에 대해서 서로 다른 인증realm을 갖는다.

※ 해당 session에 대해서 nonce 값을 유지 저장해야 하기 때문에, 서버 쪽에서는 상태 유지에 대한 부담이 생긴다. HTTP Session을 사용하거나 또는 서버간에 공유 메모리(memcached나 redis등)을 넣어서 서버간에 상태 정보를 유지할 수 있는 설계가 필요하다.

3. 클라이언트는 앞에서 서버로부터 받은 realm과 nonce값으로 Hash 값을 생성하는데,

HA1 = MD5(사용자이름:realm:비밀번호)

HA2 = MD5(HTTP method:HTTP URL)

response hash = MD5(HA1:nonce:HA2)

를 통해서 response hash 값을 생성한다.

예를 들어서 /car/index.html 페이지를 접근하려고 했다고 하자, 서버에서 nonce값을 dcd98b7102dd2f0e8b11d0f600bfb0c093를 리턴하였고, realm은 car_realm@myweb.com 이라고 하자. 그리고 사용자 이름이 terry, 비밀 번호가 hello world하면

HA1 = MD5(terry:car_realm@myweb.com:hello world)로 7f052c45acf53fa508741fcf68b5c860 값이 생성되고

HA2 = MD5(GET:/car/index.html) 으로 0c9f8cf299f5fc5c38d5a68198f27247 값이 생성된다.

Response Hash는MD5(7f052c45acf53fa508741fcf68b5c860: dcd98b7102dd2f0e8b11d0f600bfb0c093:0c9f8cf299f5fc5c38d5a68198f27247) 로 결과는 95b0497f435dcc9019c335253791762f 된다.

클라이언트는 사용자 이름인 “terry”와 앞서 받은 nonce값인 dcd98b7102dd2f0e8b11d0f600bfb0c093와 계산된 hash값인 95b0497f435dcc9019c335253791762f 값을 서버에게 전송한다.

4. 서버는 먼저 3에서 전달된 nonce값이 이 세션을 위해서 서버에 저장된 nonce 값과 같은지 비교를 한후, 전달된 사용자 이름인 terry와nonce값 그리고 서버에 저장된 사용자 비밀 번호를 이용해서 3번과 같은 방식으로 response hash 값을 계산하여 클라이언트에서 전달된 hash값과 같은지 비교를 하고 같으면 해당 리소스를 (/car/index.html 파일)을 리턴한다.

간단한 기본 메커니즘만 설명한것이며, 사실 digest access authentication은 qop (quality of protection)이라는 레벨에 따라서 여러가지 변종(추가적인 보안)을 지원한다. 언뜻 보면 복잡해서 보안 레벨이 높아보이지만 사실 Hash 알고리즘으로 MD5를 사용하는데, 이 MD5는 보안 레벨이 낮기 때문에 미정부 보안 인증 규격인 FIPS인증 (http://csrc.nist.gov/publications/fips/fips140-2/fips1402annexa.pdf) 에서 인증하고 있지 않다. FIPS 인증에서는 최소한 SHA-1,SHA1-244,SHA1-256 이상의 해쉬 알고리즘을 사용하도록 권장하고 있다.

MD5 해쉬의 경우에는 특히나 Dictionary Attack에 취약한데, Dictionary Attack이란, Hash된 값과 원래 값을 Dictionary (사전) 데이터 베이스로 유지해놓고, Hash 값으로 원본 메시지를 검색하는 방식인데, 실제로 http://en.wikipedia.org/wiki/Digest_access_authentication 설명에서 예제로 든

  • HA1 = MD5( “Mufasa:testrealm@host.com:Circle Of Life” )  = 939e7578ed9e3c518a452acee763bce9

의 MD5 해쉬 값인 939e7578ed9e3c518a452acee763bce9 값을 가지고, MD5 Dictionary 사이트인 http://md5.gromweb.com/?md5=939e7578ed9e3c518a452acee763bce9 에서 검색해보면, Hash 값으로 원본 메시지인 Mufasa:testrealm@host.com:Circle Of Life 값이 Decrypt 되는 것을 확인할 수 있다.

 

그래서 반드시 추가적인 보안 (HTTPS) 로직등을 겸비해서 사용하기를 바라며, 더 높은 보안 레벨이 필요한 경우 다른 인증 메커니즘을 사용하는 것이 좋다.

FIPS 인증 수준의 보안 인증 프로토콜로는 SHA-1 알고리즘을 사용하는 SRP6a 등이 있다. 높은 수준의 보안이 필요할 경우에는 아래 링크를 참고하기 바란다. http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol

클라이언트 인증 추가

추가적인 보안 강화를 위해서 사용자 인증 뿐만 아니라, 클라이언트 인증 방식을 추가할 수 있다. 페이스북의 경우 API Token을 발급 받기 위해서, 사용자 ID,PASSWD 뿐만 아니라 client Id와 Client Secret이라는 것을 같이 입력 받도록 하는데,

Client Id는 특정 앱에 대한 등록 Id이고, Client Secret은 특정 앱에 대한 비밀 번호로 페이스북 개바자 포털에서 앱을 등록하면 앱 별로 발급 되는 일종의 비밀 번호이다.

 

그림. 페이스북 개발자 포탈에서 등록된 client Id(appId)와 client secret(App Secret)을 확인하는 화면

API Token을 발급 받을 때, Client Id와 Client Secret 을 이용하여, 클라이언트 앱을 인증하고 사용자 ID와 PASSWD를 추가로 받아서 사용자를 인증하여 API access Token을 발급한다.

제3자 인증 방식

3자 인증 방식은 페이스북이나 트위터와 같은 API 서비스 제공자들이 파트너 애플리케이션에 많이 적용하는 방법으로 만약 내가 My Server Application라는 서비를 Facebook 계정을 이용하여 인증을 하는 경우이다.

이때 중요한 점은 서비스 My Server Application에 대해서 해당 사용자가 페이스북 사용자임을 인증을 해주지만, 서비스 My Server Application는 사용자의 비밀번호를 받지 않고, 페이스북이 사용자를 인증하고 서비스 My Server Application에게 알려주는 방식이다. 즉 파트너 서비스에 페이스북 사용자의 비밀번호가 노출되지 않는 방식이다.

전체적인 흐름을 보면 다음과 같다.

 

1. 먼저 페이스북의 Developer Portal에 접속을 하여, 페이스북 인증을 사용하고자 하는 애플리케이션 정보를 등록한다. (서비스 명, 서비스 URL,그리고 인증이 성공했을 때 인증 성공 정보를 받을 CallBack URL)

2. 페이스북 Developer Portal은 등록된 정보를 기준으로 해당 애플리케이션에 대한 client_id와 client_secret을 발급한다. 이 값은 앞에서 설명한 클라이언트 인증에 사용된다.

3. 다음으로, 개발하고자 하는 애플리케이션에, 이 client_id와 client_secret등을 넣고, 페이스북 인증 페이지 정보등을 넣어서 애플리케이션을 개발한다.

애플리케이션이 개발되서 실행이 되면, 아래와 같은 흐름에 따라서 사용자 인증을 수행하게 된다.

 

1. 웹브라우져에서 사용자가 My Server Application 서비스를 접근하려고 요청한다.

2. My Server Application은 사용자가 인증이되어 있지 않기 때문에, 페이스북 로그인 페이지 URL을 HTTP Redirection으로 URL을 브라우져에게 보낸다. 이때 이 URL에 페이스북에 이 로그인 요청이 My Server Application에 대한 사용자 인증 요청임을 알려주기 위해서, client_id등의 추가 정보와 함께, 페이스북의 정보 접근 권한 (사용자 정보, 그룹 정보등)을 scope라는 필드를 통해서 요청한다.

3. 브라우져는 페이스북 로그인 페이지로 이동하여, 2단계에서 받은 추가적인 정보와 함께 로그인을 요청한다.

4. 페이스북은 사용자에게 로그인 창을 보낸다.

5. 사용자는 로그인창에 ID/PASSWD를 입력한다.

6. 페이스북은 사용자를 인증하고, 인증 관련 정보과 함께 브라우져로 전달하면서, My Server Application의 로그인 완료 페이지로 Redirection을 요청한다.

7. My Server Application을 6에서 온 인증 관련 정보를 받는다.

8. My Server Application은 이 정보를 가지고, 페이스북에, 이 사용자가 제대로 인증을 받은 사용자인지를 문의한다.

9. 페이스북은 해당 정보를 보고 사용자가 제대로 인증된 사용자임을 확인해주고, API Access Token을 발급한다.

10.  My Server Application은 9에서 받은 API Access Token으로 페이스북 API 서비스에 접근한다.

앞에서 설명했듯이, 이러한 방식은 자사가 아닌 파트너 서비스에게 자사 서비스 사용자의 인증을 거쳐서 API의 접근 권한을 전달하는 방식이다.

이러한 인증 방식의 대표적인 구현체는 OAuth 2.0으로, 이와 같은 제3자 인증뿐만 아니라, 직접 자사의 애플리케이션을 인증하기 위해서, 클라이언트로부터 직접 ID/PASSWD를 입력 받는 등.

클라이언트 타입(웹,서버,모바일 애플리케이션)에 대한 다양한 시나리오를 제공한다.

※ OAuth 2.0에 대한 자세한 설명은 PACKT 출판사의 OAuth 2.0 Identity and Access Management Patterns (by Martin Spasovski) 책을 참고하기를 추천한다.

이러한 3자 인증 방식은 일반적인 서비스에서는 필요하지 않지만, 자사의 API를 파트너등 외부 시스템에 제공하면서 사용자의 ID/PASSWD를 보호하는데는 필요한 서비스이기 때문에, API 를 외부에 적용하는 경우에는 고려를 해야 한다.

IP White List을 이용한 터널링

만약에 API를 호출하는 클라이언트의 API가 일정하다면 사용할 수 있는 손쉬운 방법인데, 서버간의 통신이나 타사 서버와 자사 서버간의 통신 같은 경우에, API 서버는 특정 API URL에 대해서 들어오는 IP 주소를 White List로 유지하는 방법이 있다.

API 서버 앞단에, HAProxy나 Apache와 같은 웹서버를 배치하여서 특정 URL로 들어올 수 있는 IP List를 제한 하거나, 아니면 전체 API가 특정 서버와의 통신에만 사용된다면 아예, 하드웨어 방화벽 자체에 들어올 수 있는 IP List를 제한할 수 있다.

설정만으로 가능한 방법이기 때문에, 서버간의 통신이 있는 경우에는 적용하는 것을 권장한다.

Bi-diretional Certification (Mutual SSL)

가장 높은 수준의 인증 방식을 제공할 수 있는 개념으로, 보통 HTTPS 통신을 사용할 때 서버에 공인 인증서를 놓고 단방향으로 SSL을 제공한다.

반면 Bi-Directional Certification (양방향 인증서 방식) 방식은 클라이언트에도 인증서를 놓고 양방향으로 SSL을 제공하면서, API 호출에 대한 인증을 클라이언트의 인증서를 이용 하는 방식이다.

구현 방법이 가장 복잡한 방식이기는 하지만, 공인 기관에서 발행된 인증서를 사용한다면 API를 호출하는 쪽의 신원을 확실하게 할 수 있고, 메시지까지 암호화되기 때문에, 가장 높은 수준의 인증을 제공한다. 이런 인증 방식은 일반 서비스에서는 사용되지 않으며, 높은 인증 수준을 제공하는 몇몇 서비스나 특정 서버 간 통신에 사용하는 것이 좋다.

권한 인가 (Authorization)

인증이 끝나면 다음 단계는 권한에 대한 인증, 즉 인가 (Authorization) 과정이 필요하다.

사용자가 인증을 받고 로그인을 했다해더라도 해당 API를 호출 할 수 있는 권한이 있는 가를 체크해야 한다.

예를 들어 “일반 사용자 A가 로그인 했을 때, 다른 사용자를 삭제하는 것은 사용자 A가 관리자 권한을 가지고 있고, 이 요청이 웹 관리 콘솔을 통해서 들어온 경우에만 허용한다.” 와 같은 경우이다. 사용자가 인증(Authentication)을 통해서 시스템 내의 사용자 임을 확인 받았지만, API 호출을 하기 위해서 적절한 권한이 있는지를 검증해야 한다.

API 인가 방식

권한 인가(Authorization)방식에는 여러가지 방식이 있는데, 대표적인 방식 몇가지만 보면

가장 일반적인 권한 인증 방식은 사용자의 역할(ROLE)을 기반으로 하는 RBAC (Role Based Access Control)이라는 방식이 있다.

이 방식은 정해진 ROLE에 권한을 연결해놓고, 이 ROLE을 가지고 있는 사용자게 해당 권한을 부여하는 것이다.

예를 들어

  • 일반 관리자 – 사용자 관리, 게시물 관리, 회원 가입 승인
  • 마스터 관리자 – 까페 게시판 게시판 관리, 메뉴 관리, 사용자 관리, 게시물 관리, 회원 가입 승인

와 같은 권한을 만든후,

Terry에 “마스터 관리자”라는 ROLE을 부여하면, 사용자 Terry는 “까페 게시판 게시판 관리, 메뉴 관리, 사용자 관리, 게시물 관리, 회원 가입 승인” 등의 권한을 가지게 된다.

이렇게 권한 부여의 대상이 되는 사용자나 그룹을 Object라고 하고, 각 개별 권한을 Permission이라고 정의하며, 사용자의 역할을 Role이라고 정의한다. RBAC는 이 Role에 권한을 맵핑 한 다음 Object에 이 Role을 부여 하는 방식으로 많은 권한 인가는 사용자 역할을 기반으로 하기 때문에, 사용하기가 용이하다.

 

다른 권한 인증 모델로는 ACL (Access Control List)라는 방식이 있다.

RBAC 방식이 권한을 ROLE이라는 중간 매개체를 통해서 사용자에게 부여하는데 반해서, ACL 방식은 사용자(또는 그룹과 같은 권한의 부여 대상) 에게 직접 권한을 부여하는 방식이다.

사용자 Terry 에 직접 “까페 게시판 게시판 관리, 메뉴 관리, 사용자 관리, 게시물 관리, 회원 가입 승인” 권한을 부여 하는 방식이 ACL의 대표적인 예이다.

 

이러한 API 권한 인가 체크는 인증 (Authentication)이 끝나 후에, 인가에 사용된 api accesstoken을 이용하여 사용자 정보를 조회하고, 사용자 정보에 연관된 권한 정보 (Permission이나 Role정보)를 받아서 이 권한 정보를 기반으로 API 사용 권한을 인가하는 방법을 사용한다.  사용자 정보 조회 “HTTP GET /users/{id}”라는 API 가 있다고 가정하자.

이 API의 권한은 일반 사용자의 경우 자신의 id에 대해서만 사용자 정보 조회가 가능하고, (자신의 정보만), 만약에 관리자의 경우에는 다른 사용자의 id도 조회가 가능하도록 차등하여 권한을 부여할 수 있다.

이러한 권한 검증은 API access token으로 사용자를 찾은 후, 사용자에게 assign 된 ROLE이나 Access Control을 받아서 API 인증을 처리할 수 있다.

API 권한 인가 처리 위치

API에 대한 권한 인가 처리는 여러가지 계층에서 처리할 수 있다.

권한 인가는 API를 호출 하는 쪽인 클라이언트, API를 실행하는 API 서버쪽, 그리고 API 에 대한 중간 길목 역할을 하는 gateway 3군데서 처리할 수 있으며 근래에는 API 서버쪽에서 처리하는 것이 가장 일반적이다.

클라이언트에 의한 API 권한 인가 처리

API를 호출 하는 클라이언트 쪽에서 사용자의 권한에 따라서 API를 호출하는 방식인데, 이 방식의 경우 클라이언트가 신뢰할 수 있는 경우에만 사용할 수 있다.

이 방식은 기존에, 웹 UX 로직이 서버에 배치되어 있는 형태 (Struts나 Spring MVC와 같은 웹 레이어가 있는 경우)에 주로 사용했다.

위의 사용자 API를 예를 들어보면 웹 애플리케이션에서, 사용자 로그인 정보(세션 정보와 같은)를 보고 사용자 권한을 조회한 후에, API를 호출 하는 방식이다.

사용자 세션에, 사용자 ID와 ROLE을 본 후에, ROLE이 일반 사용자일 경우, 세션내의 사용자 ID와 조회하고자 하는 사용자 ID가 일치하는 경우에만 API를 호출 하는 방식이다.

이러한 구조를 사용할 경우 모바일 디바이스 등에 제공하는 API는 사용자 ROLE을 갖는 API와 같이 별도의 권한 인가가 필요 없는 API를 호출 하는 구조를 갖는다.

이 구조를 그림으로 표현해보면 다음과 같다.

Mobile Client는 일반 사용자만 사용한다고 가정하고, 웹 애플리케이션은 일반 사용자와 관리자 모두 사용한다고 했을 때, 일반 사용자의 Mobile Client를 위한 API Server를 별도로 배치하고, 사용자 인증(Authentication)만 되면 모든 API 호출을 허용하도록 한다. Mobile Client에 대한 API는 권한 인증에 대한 개념이 없기 때문에, 인증 처리만 하면 되고, 웹 애플리케이션의 경우에는 일반 사용자냐, 관리자냐에 따라서 권한 인가가 필요하기 때문에 아래 그림과 같이 Web Application에서, API를 호출하기 전에 사용자의 id와 권한에 따라서 API 호출 여부를 결정하는 API 권한 인가(Authorization) 처리를 하게 한다.

 

Gateway에 의한 권한 인가 처리

이러한 권한 인가는 모바일 클라이언트, 자바스크립트 기반의 웹 클라이언트등 다양한 클라이언트가 지원됨에 따라 점차 서버쪽으로 이동하기 시작했는데, 특히 자바 스크립트 클라이언트의 경우 클라이언트에서 권한에 대한 인가는 의미가 없기 때문에 어쩔 수 없이 서버 쪽에서 권한 인가 처리를 할 수 밖에 없게 된다. 만약에 자바 스크립트에 권한 인가 로직을 넣을 경우, 자바 스크립트의 경우 브라우져의 디버거등으로 코드 수정이 가능하기 때문에 권한 처리 로직을 위회할 수 도 있고 또한 API 포맷만 안다면 직접 API를 서버로 호출해서 권한 인가 없이 API를 사용할 수 있다.

서버에서 권한을 처리하는 방법은 API 호출의 길목이 되는 gateway나 API 비지니스 로직 두군데서 처리가 가능한데, API gateway에 의한 권한 처리는 구현이 쉽지 않기 때문에,API 서버에서 권한 처리를 하는 것이 일반적이다.

아래 그림은 API gateway에서 권한 인가를 처리하는 방법인데, API 호출이 들어오면, API access Token을 사용자 정보와 권한 정보로 API token management 정보를 이용해서 변환 한 후에, 접근하고자 하는 API에 대해서 권한 인가 처리를 한다.

이는 API 별로 API를 접근하고자 하는데 필요한 권한을 체크해야 하는데, HTTP GET /users/{id}의 API를 예로 들어보면, 이 URL에 대한 API를 호출하기 위해서는 일반 사용자 권한을 가지고 있는 사용자의 경우에는 호출하는 사용자 id와 URL상의 {id}가 일치할 때 호출을 허용하고, 같지 않을 때는 호출을 불허해야 한다.

만약 사용자가 관리자 권한을 가지고 있을 경우에는 호출하는 사용자 id와 URL상의 {id}가 일치하지 않더라도 호출을 허용해야 한다.

 

그러나 이러한 api gateway에서의 권한 인가는 쉽지가 않은데, 위의 /users/{id} API의 경우에는 사용자 id가 URL에 들어가 있기 때문에, API access token과 맵핑되는 사용자 ID와 그에 대한 권한을 통해서 API 접근 권한을 통제할 수 있지만, API에 따라서 사용자 id나 권한 인증에 필요한 정보가 HTTP Body에 json 형태나 HTTP Header 등에 들어가 있는 경우, 일일이 메세지 포맷에 따라서 별도의 권한 통제 로직을 gateway 단에서 구현해야 하는 부담이 있고, 권한 통제를 위해서 HTTP 메세지 전체를 일일이 파싱해야 하는 오버로드가 발생하기 때문에, 공통 필드등으로 API 권한 처리를 하지 않는 경우에는 사용하기가 어려운 부분이다.

서버에 의한 API 권한 인가 처리

그래서 가장 일반적이고 보편적인 방법은 API 요청을 처리하는 API 서버의 비지니스 로직단에서 권한 처리를 하는 방식이다.

이 방식은 앞에서 언급한 api gateway 방식과 비교했을때, 각 비지니스 로직에서 API 메세지를 각각 파싱하기 때문에, API 별로 권한 인가 로직을 구현하기가 용이 하다.

이 경우에는 권한 인가에 필요한 필드들을 api gateway에서 변환해서 API 서버로 전달해줌으로써 구현을 간략하게 할 수 있는데,

아래 그림과 같이 API 클라이언트가 api access token을 이용해서 API를 호출했을 경우, api gateway가 이 access token을 권한 인가에 필요한 사용자 id, role등으로 변환해서 API 서버에 전달해주게 되면, 각 비지니스 로직은 API 권한 인가에 필요한 사용자 정보등을 별도로 데이타 베이스를 뒤지지 않고 이 헤더의 내용만을 이용해서 API 권한 인가 처리를 할 수 있게 된다.

 

네트워크 (전송) 레벨 암호화

가장 기본적이고 필수적인 REST API 보안 방법은 네트워크 전송 프로토콜에서 HTTPS 보안 프로토콜을 사용하는 방법이다. HTTPS 프로토콜만 사용한다 하더라도, 메시지 자체를 암호화 해서 전송하기 때문에, 해킹으로 인한 메시지 누출 위협을 해소화 할 수 있다.

그런데, HTTPS를 사용하더라도, 이러한 메시지를 낚아채거나 변조하는 방법이 있는데, 이러한 해킹 방법을 Man-in-The-Middle-Attack 이라고 한다.

정상적인 HTTPS 통신의 경우, 다음과 같이 서버에서 제공하는 인증서 A를 이용하여 API 클라이언트와 서버 상화간에 암호화된 신뢰된 네트워크 연결을 만든다.

 

Man in the middle attack의 경우에는 신뢰된 연결을 만들려고 할 때, 해커가 API 클라이언트와 서버 사이에 끼어 들어온다.

 

다음은 그림과 같이 신뢰된 연결을 만들기 위해서 서버가 인증서 A를 클라이언트에게 내릴 때 해커가 이 인증서가 아닌 인증서 B를 클라이언트에 내리고, 인증서 B를 이용해서 API Client와 Hacker간에 HTTPS SSL 연결을 만든다. 그리고는 서버에게서 받은 인증서 A를 이용해서 해커와 API 서버간의 HTTP SSL 서버를 만든다.

이렇게 되면, 해커는 중간에서 API 클라이언트와 서버 사이에 메시지를 모두 열어 보고 변조도 가능하게 된다.

종종 대기업이나 공공 기관에서 웹브라우져를 사용하다 보면,인증서가 바뀌었다는 메시지를 볼 수 가 있는데 (특히 파이어폭스 브라우져를 사용하면 인증서 변경을 잘 잡아낸다.) 이는 회사의 보안 정책상 HTTPS 프로토콜의 내용을 보고, 이를 감사 하기 위한 목적으로 사용된다.

이런 Man in middle attack을 방지 하는 방법은 여러가지 방식이 있지만, 가장 손쉬운 방법은 공인된 인증서를 사용하고 인증서를 체크하는 것이다.

해커가 인증서를 바꿔 치려면, 인증서를 발급해야 하는데, 공인 인증서는 Verisign과 같은 기간에서 인증서에 대한 공인 인증을 해준다. 즉, 이 인증서를 발급한 사람이 누구이고 이에 대한 신원 정보를 가지고 있다. 이를 공인 인증서라고 하는데, 공인 인증서는 인증 기관의 Signature로 싸인이 되어 있다. (공인 인증기관이 인증했다는 정보가 암호화 되서 들어간다.)

만약 해커가 공인 인증서를 사용하려면 인증 기관에 가서 개인 정보를 등록해야 하기 때문에, 공인 인증서를 사용하기 어렵고 보통 자체 발급한 비공인 인증서를 사용하기 때문에, 이를 이용해서 체크가 가능하고, 특히 인증서안에는 인증서를 발급한 기관의 정보와 인증서에 대한 고유 Serial 번호가 들어가 있기 때문에, 클라이언트에서 이 값을 체크해서 내가 발급하고 인증 받은 공인 인증서인지를 체크하도록 하면 된다.

아래는 자바의 keytool 유틸리티를 이용해서 자체발급한 인증서의 정보를 프린트해본 내용이다.

(※ keytool -printcert -file CERT.RSA)

Owner: CN=Android Debug, O=Android, C=US

Issuer: CN=Android Debug, O=Android, C=US

Serial number: 6b14b6db

Valid from: Mon Nov 19 09:58:00 KST 2012 until: Wed Nov 12 09:58:00 KST 2042

Certificate fingerprints:

MD5:  78:69:7F:D5:BD:D7:B7:47:AD:11:6A:D2:F6:83:D7:CB

SHA1: 44:14:35:A5:C5:28:77:A4:C4:DD:CA:80:26:02:68:A1:84:2E:BD:15

Issuer가 인증서를 발급한 기관의 이름이며, Serial number는 이 인증서에 대한 고유 번호이다. 맨 아래에 있는 fingerprints는 인증서에 대한 해쉬 값으로, 만약에 인증서가 변조되면 이 해쉬값이 달라지기 때문에, 인증서 변조를 확인할 수 있다.

메시지 본문 암호화

다음으로는 간단하게 암호화가 필요한 특정 필드만 애플리케이션단에서 암호화하여 보내는 방법이 있다.

메시지를 암호화 하여 통신하기 위해서는 클라이언트와 서버가 암호화 키를 가져야 하는데, 암호화 키는 크게, 대칭키와, 비대칭키 알고리즘 두가지가 있다.

비대칭키 알고리즘은, 암호화를 시키는 키와, 암호를 푸는 복호화 키가 다른 경우로, 암호화 시키는 키를 보통 Public Key (공개키)라고 하고, 암호화를 푸는 키를 Private Key(비밀키)라고 한다. 이 공개키는 암호화는 할 수 있지만 반대로 암호화된 메시지를 풀 수 가 없기 때문에 누출이 되더라도 안전하다. (해커가 중간에서 공개키를 낚아 챈다고 하더라도, 이 키로는 암호화된 메시지를 복호화 할 수 없다.)

그래서, 처음에 클라이언트가 서버에 인증이 된 경우, 클라이언트에게 이 공개키를 내린 후에, 향후 메시지를 이 공개키를 통해서 암호화를 하게 하면, 이 암호화된 메시지는 비밀키를 가지고 있는 서버만이 풀 수 있어서 안전하게 서버로 메세지를 암호화 해서 보낼 수 있다.

대표적인 비대칭키 알고리즘으로는 RSA등이 있으며, 우리가 익숙한 HTTPS의 경우에도 이 RSA 알고리즘을 사용한다. RSA 알고리즘을 사용하는 비대칭키 암호화 로직과 라이브러리들은 공개된 것이 많으니 참고해서 사용하도록 한다.

비대칭키 알고리즘의 경우 클라이언트에서 서버로 보내는 단방향 메시지에 대해서는 암호화하여 사용할 수 있지만, 반대로 서버에서 클라이언트로 내려오는 응답 메시지등에는 적용하기가 어렵다. 아니면 클라이언트가 서버에 등록될 때, 위와 반대 방법으로 클라이언트에서 비공개키와 공개키 쌍을 생성한 후에, 서버로 공개키를 보내서 향후 서버에서 클라이언트로의 통신을 그 공개키를 사용하도록 해도 된다. 이경우, 클라이언트 ? 서버, 그리고 서버? 클라이언트간의 키 쌍 두개를 관리해야 하기 때문에 복잡할 수 있는데, 이런 경우에는 대칭키 알고리즘을 고려해볼 수 있다.

대칭키 알고리즘은 암호화와 복호화키가 같은 알고리즘이다.

이 경우 API 클라이언트와 서버가 같은 키를 알고 있어야 하는데, 키를 네트워크를 통해서 보낼 경우 중간에 해커에 의해서 낚아채질 염려가 있기 때문에, 양쪽에 안전하게 키를 전송하는 방법이 필요한데, 다음과 같은 방법을 사용할 수 있다.

1. 서버에서 공개키KA1와 비공개키KA2 쌍을 생성한다.

2. 클라이언트에게 공개키 KA1을 네트워크를 통해서 내려보낸다.

3. 클라이언트는 새로운 비공개 대칭키 KB를 생성한후 KA1을 이용해서 암호화하여 서버로 전송한다.

4. 서버는 전송된 암호화 메시지를 KA2로 복화화 하여, 그 안에 있는 비공개키 KB를 꺼낸다.

5. 향후 클라이언트와 서버는 상호 API통신시 비공개 대칭키 KB를 이용하여 암호화와 복호화를 진행한다.

대칭 키도 여러가지 종류가 있는데, 보안과 성능 측면에서 차이가 있다.

http://www.javamex.com/tutorials/cryptography/ciphers.shtml를 참고하면, 대칭키 기반의 암호화 알고리즘 속도를 비교해놓은 것이 있다. 일반적으로 AES256을 사용하면 빠른 암호화 속도와 높은 보안성을 보장받을 수 있다.(아래, 대칭키 기반의 암호화 알고리즘 속도 비교)

 

메시지 무결성 보장

무결성이란 서버 입장에서 API 호출을 받았을 때 이 호출이 신뢰할 수 있는 호출인지 아닌지를 구별하는 방법이다. 즉 해커가 중간에서 메시지를 가로챈 후, 내용을 변조하여 서버에 보냈을 때, 내용이 변조되었는지 여부를 판단하는 방법인데, 일반적으로 HMAC을 이용한 방식이 널리 사용된다. 어떤 원리로 작동하는지 살펴보도록 하자.

먼저 Rest API를 호출하는 클라이언트와 서버 간에는 대칭키 기반의 암호화 키 ‘Key’를 가지고 있다고 전제하자. 이 키는 클라이언트와 서버 양쪽이 알고 있는 대칭키로, API access Token을 사용할 수 도 있고, 앞의 메시지 본문 암호화에서 나온 방법을 이용해서 서로 대칭키를 교환하여 사용할 수 도 있다..

 

1. 먼저 클라이언트는 호출하고자 하는 REST API의 URL을 앞에서 정의한 Key를 이용하여 HMAC 알고리즘을 사용하여 Hash 값을 추출한다.

※ 중요: 여기서는 편의상 URL을 가지고 HMAC 해시를 생성하였했는데, 전체 메시지에 대한 무결성을 보장하려면 URL이 아니라 메시지 전문 자체에 대해서 대해 Hash를 추출해야 한다.

2. API를 호출할 때, 메시지(또는 URL)에 추출한 HMAC을 포함해서 호출한다.

3. 서버는 호출된 URL을 보고 HMAC을 제외한 나머지 URL을 미리 정의된 Key를 이용해서, HMAC 알고리즘으로 Hash 값을 추출한다.

4. 서버는 3에서 생성된 HMAC 값과 API 호출 시 같이 넘어온 HMAC 값을 비교해서, 값이 같으면 이 호출을 유효한 호출이라고 판단한다.

만약에 만약 해커가 메시지를 중간에서 가로채어 변조하였했을 경우, 서버에서 Hash를 생성하면 변조된 메시지에 대한 Hash가 생성되기 때문에 클라이언트에서 변조 전에 보낸 Hash 값과 다르게 된다. 이를 통해서 통해 메시지가 변조되었는지 여부를 판단할 수 있다.

그런데, 만약에 만약 메시지를 변경하지 않고 Hacker가 동일한 요청을 계속 보낸다면? 메시지를 변조하지 않았기 때문에 서버는 이를 유효한 호출로 인식할 수 있다. 이를 replay attack이라고 하는데 이를 방지하기 위해서는위해서는 time stamp를 사용하는 방법이 있다.

이 방법은 HMAC을 생성할 때, 메시지를 이용해서만 Hash 값을 생성하는 것이 아니라 timestamp를 포함하여 메시지를 생성하는 것이다.

  • HMAC (Key, (메시지 데이터+timestamp) )

그리고 API를 호출할 때, timestamp 값을 같이 실어 보낸다.

http://service.myapi.com/restapiservice?xxxxx&hmac={hashvalue}&timestamp={호출시간}

이렇게 하면 서버는 메시지가 호출된 시간을 알 수 있고, 호출된 시간 +-10분(아니면 개발자가 정한 시간폭)만큼의 호출만 정상적인 호출로 인식하고 시간이 지난 호출의 메시지는 비정상적인 호출로 무시하면 된다.

* 참고 : : Hacker가 timestamp URL등 등을 통해서 통해 볼 수 있다고 하더라도, Key 값을 모르기 때문에 timestamp를 변조할 수 없다. timestamp를 변조할 경우에는 원본 Hash가 원본 timestamp로 생성되었기 때문에, timestamp가 변조된 경우 hash 값이 맞지 않게 된다.

HMAC을 구현하는 해시 알고리즘에는 MD5, SHA1등 등이 있는데, 보통 SHA-1 256bit 알고리즘을 널리 사용한다.

HMAC 기반의 REST Hash 구현 방법은

http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/

에 설명이 있으니 참고하기 바란다.

또한 HMAC 알고리즘 구현에 대해서는 위키(http://en.wikipedia.org/wiki/HMAC)를 보면 각 프로그래밍 언어별로 예제 링크가 있으므로 참고하기 바란다.

지금까지 간단하게나마 API 보안 방식에 대해서 살펴보았다.보안에 대해서 이야기 하자면 한도 끝도 없겠지만, API보안에서는 최소한 HTTPS를 이용한 네트워크 보안과 함께, API Token등의 인증 방식을 반드시 사용하기를 권장한다.

보안 처리는 하지 않아도, API의 작동이나 사용에는 문제가 없다. 그러나 보안이라는 것은 한번 뚫려버리면 많은 정보가 누출이 되는 것은 물론이고 시스템이 심각한 손상까지 입을 수 있기 때문에,  시간이 걸리더라도 반드시 신경써서 설계 및 구현하는 것을 권장한다.

자바스크립트 클라이언트 지원

근래에 들어서 자바스크립트 기술이 발전하면서 SPA (Sigle Page Application)이 유행하기 시작했는데, SPA란 브라우져에서 페이지간의 이동없이 자바스크립트를 이용해서 동적으로 페이지를 변경할 수 있는 방식이다.

페이지 reloading이 없기 때문에 반응성이 좋아서 많이 사용되는데, SPA 의 경우 서버와의 통신을 자바스크립가 직접 XMLHTTPRequest 객체를 이용해서 API 호출을 바로 하는 형태이다.

이러한 변화는 API 보안 부분에도 새로운 요구사항을 가지고 왔는데, 자바스크립트 클라이언트는 기존의 모바일이나 웹 애플리케이션, 서버등과 다른 기술적인 특성을 가지고 있기 때문이다.

자바스크립트 클라이언트는 코드 자체가 노출된다. 자바스크립트 코드는 브라우져로 로딩되서 수행되기 때문에 사용자 또는 해커가 클라이언트 코드를 볼 수 있다. 그래서 보안 로직등이 들어가 있다고 하더라도 로직 자체는 탈취 당할 수 있다.

아울러 자바스크립트는 실행중에 브라우져의 디버거를 이용해서 변수 값을 보거나 또는 변수값을 변경하거나 비즈니스 로직을 변경하는 등의 행위가 가능하다.

그래서 일반적인 API 보안과는 다른 접근이 필요하다.

Same Origin Policy에 대한 처리

먼저 자바스크립트의 API에 대한 호출은 same origin policy(동일 출처 정책)의 제약을 받는다. Same origin policy란, 자바스크립트와 같이 웹 브라우져에서 동작하는 프로그래밍 언어에서, 웹 브라우져에서 동작하는 프로그램은 해당 프로그램이 로딩된 위치에 있는 리소스만 접근이 가능하다. (http냐 https냐 와 같은 프로토콜과 호출 포트도 정확하게 일치해야 한다.)

아래 그림과 같이 웹사이트 sitea.com에서 자바스크립트를 로딩한 후에, 이 스크립트에서 api.my.com에 있는 API를 XMLHTTPRequest를 통해서 호출하고자 하면, Sane origin policy에 의해서 호출 에러가 난다.

 

이를 해결하는 방법으로는 인프라 측면에서 Proxy를 사용하는 방법이나 또는 JSONP와 CORS (Cross Origin Resource Sharing)이라는 방법이 있는데, 여기서는 많이 사용되는 CORS에 대해서 소개하고자 한다.

Proxy를 이용하는 방식

Proxy를 이용하는 방식은 간단하다. Same origin policy의 문제는 API 서버와 Javascript가 호스팅 되는 서버의 URL이 다르기 때문에 발생하는 문제인데, 이를 앞단에 Reverse Proxy등을 넣어서, 전체 URL을 같게 만들어 주면 된다.

앞의 상황과 같은 일들이 있다고 가정할 때, sitea.com과 api.my.com 앞에 reverse proxy를 넣고, reverse proxy의 주소를 http://mysite.com 으로 세팅한다.

그리고 mysite.com/javascript로 들어오는 요청은 sitea.com으로 라우팅 하고, mysite.com/의 다른 URL로 들어오는 요청은 api.my.com으로 라우팅한다.

 

이러한 구조가 되면, javascript 가 로딩된 사이트도 mysite.com이 되고, javascript에서 호출하고자 하는 api URL도 mysite.com이 되기 때문에, Same Origin Policy에 위배되지 않는다.

이 방식은 단순하지만, 자사의 웹사이트를 서비스 하는 경우에만 가능하다. (타사의 사이트를 Reverse Proxy뒤에 놓기는 쉽지 않다.) 그래서 자사의 서비스용 API를 만드는데는 괜찮지만, 파트너사나 일반 개발자에게 자바스크립트용 REST API를 오픈하는 경우에는 적절하지 않다.

특정 사이트에 대한 접근 허용 방식

CORS 방식중,이 방식은 가장 간단하고 쉬운 방식으로 API 서버의 설정에서 모든 소스에서 들어오는 API 호출을 허용하도록 하는 것이다. api.my.com 이라는 API 서비스를 제공할 때, 이 API를 어느 사이트에서라도 로딩된 자바스크립트라도 호출이 가능하게 하는 것이다.

이는 HTTP로 API를 호출하였을 경우 HTTP Response에 응답을 주면서 HTTP Header에 Request Origin (요청을 처리해줄 수 있는 출처)를 명시 하는 방식이다.

api.my.com에서 응답 헤더에

  • Access-Control-Allow-Origin: sitea.com

와 같이 명시해주면 sitea.com에 의해서 로딩된 자바스크립트 클라이언트 요청에 대해서만 api.my.com가 요청을 처리해준다.

만약에 다음과 * 로 해주면, request origin에 상관 없이 사이트에서 로딩된 자바스크립트 요청에 대해서 처리를 해준다.

  • Access-Control-Allow-Origin: *

Pre-flight를 이용한 세세한 CORS 통제

REST 리소스 (URL)당 섬세한 CORS 통제가 필요한 경우에는 Pre-flight 호출이라는 것을 이용할 수 있다. 이 방식은 REST 리소스를 호출하기 전에, 웹 브라우져가 HTTP OPTIONS 요청을 보내면 해당 REST Resource에 대해서 가능한 CORS 정보를 보내준다. (접근이 허용된 사이트, 접근이 허용된 메서드 등)

웹브라우져에서는 XMLHTTPRequest를 특정 URL로 요청하기 전에 먼저 HTTP Options를 호출한다.

그러면 서버는 해당 URL을 접근할 수 있는 Origin URL과 HTTP Method를 리턴해준다. 이를 pre-flight 호출이라고 하는데, 이 정보를 기반으로 브라우져는 해당 URL에 XMLHTTPRequest를 보낼 수 있다.

아래 그림을 보자, 브라우져는 http://javascriptclient.com에서 로딩된 자바스크립트로 REST 호출을 하려고 한다.

이를 위해서 HTTP OPTION 메서드로 아래 첫번째 화살표와 같이 /myresource URL에 대해서 pre-flight 호출을 보낸다. 여기에는 Origin Site URL과 허가를 요청하는 HTTP 메서드등을 명시한다.

서버는 이 URL에 대한 접근 권한을 리턴하는데, 두번째 화살표와 같이 CORS접근이 가능한 Origin 사이트를 http://javascriptclient.com으로 리턴하고 사용할 수 있는 메서드는 POST,GET,OPTIONS 3개로 정의해서 리턴한다. 그리고 이 pre-flight 호출은 Access-Control-Max-Age에 정의된 1728000초 동안 유효하다.  (한번 pre-flight 호출을 하고 나면 이 시간 동안은 다시 pre-flight 호출을 할 필요가 없다.)

 

이러한 CORS 설정은 API 호출 코드에서 직접 구현할 수 도 있지만, 그 보다는 앞단에서 로드 밸런서 역할을 하는 HA Proxy나 nginx와 같은 reverse proxy에서 설정을 통해서 간단하게 처리가 가능하다. 만약에 API단에서 구현이 필요하다하더라도 HTTP Header를 직접 건드리지 말고, Spring 등의 프레임웍에서 이미 CORS 구현을 지원하고 있으니 프레임웍을 통해서 간단하게 구현하는 것을 권장한다.

API access Token에 대한 인증 처리

앞서서 언급하였듯이 자바스크립트 클라이언트는 모바일 앱이나, 서버와 같은 다른 API 클라이언트와 비교해서 api access token을 안전하게 저장할 수 있는 방법이 없기 때문에, 이 API access token에 대해서 다른 관리 방식이 필요하다.

몇가지 추가적인 방식을 사용하는데, 내용은 다음과 같다.

api access token을 Secure Cookie를 통해서 주고 받는다.

api access token을 서버에서 발급하여 자바스크립트 클라이언트로 리턴할 때, HTTP body에 리턴하는 것이 아니라 Secure Cookie에 넣어서 리턴한다.

※ Secure Cookie : https://www.owasp.org/index.php/SecureFlag

Secure Cookie는 일반 HTTP 프로토콜을 통해서는 전송이 불가능하고 항상 HTTPS를 통해서만 전송이 가능하다. 같은 API 서버로도 일반 HTTP 호출을 할 경우 api access token이 Cookie를 통해서 전달되지 않기 때문에, 네트워크를 통해서 access token을 탈취하는 것은 불가능하다.

여기에 HTTP_ONLY라는 옵션을 쿠키에 추가하는데, 이 옵션을 적용하게 되면, Cookie를 자바스크립트를 통해서 읽거나 조작할 수 없다. 단지 브라우져가 서버로 요청을 보낼 때, 브라우져에 의해서 자동으로 Cookie가 전송된다.

※ HTTP ONLY 옵션 https://www.owasp.org/index.php/HttpOnly#What_is_HttpOnly.3F

이 두 가지 방법을 쓰면 최소한 자바스크립트 소스코드 분석이나 네트워크 프로토콜 감청을 통한 api access token을 방어할 수 있다

api access token은 해당 세션에서만 유효하도록 한다.

여기에 몇 가지 추가적인 방어 기재를 추가하도록 하는데, 마치 HTTP Session과 같이 특정 IP와 시간내에서만 api access token이 유효하도록 하는 방식이다.

Access token을 발급할 때, access token을 요청한 클라이언트의 IP와 클라이언트의 Origin 을 같이 저장해놓고, 발급할때 유효시간(Expire time)을 정해놓는다. (20 분 등으로).

다음 access token을 이용해서 API가 호출 될 때 마다 IP와 Origin을 확인하고, acess token이 유효시간 (Expire time)시간 내면 이 유효시간을 다시 연장해준다.(+20분을 다시 추가해준다.) 만약에 브라우져에서 일정 시간동안 (20분) API를 호출하지 않았으면 API access token은 폐기되고 다시 access token을 발급 받도록 한다.

이 두 가지 흐름을 도식화해 보면 다음 그림과 같다.

 

모든 통신을 HTTPS를 이용한다.

1. 자바스크립트 클라이언트가 user id와 password를 보내서 사용자 인증과 함께, API access token을 요청한다. HTTPS를 사용한다하더라도 Man in middle attack에 의해서 password가 노출 될 수 있기 때문에, 앞에서 언급한 Digest access Authentication 등의 인증 메커니즘을 활용하여 가급적이면 password를 직접 보내지 않고 인증을 하는 것이 좋다.

2. 서버에서 사용자 인증이 끝나면 api access token을 발급하고 이를 내부 token store에 저장한다. (앞에서 설명한 origin url, ip, expire time등을 저장한다.). 이 필드들은 웹 자바스크립트를 위한 필드로 설명을 위해서 이 필들만 그림에 정의했지만, 실제 시스템 디자인은 웹 클라이언트용과 일반 서버/모바일 앱등을 위한 api access token 정보도 같이 저장해서 두가지 타입을 access token에 대해서 지원하도록 하는 것이 좋다.

3. 생성된 토큰은 Secure Cookie와 HTTP Only 옵션을 통해서 브라우져에게로 전달된다.

4. 브라우져의 자바스크립트 클라이언트에서는 API를 호출할 때 이 api access token이 secure cookie를 통해서 자동으로 서버에 전송되고, 서버는 이 api access token을 통해서 접근 인증 처리를 하고 api server로 요청을 전달하여 처리하도록 한다.

지금까지 간략하게 나마 REST의 개념에서부터 설계 방식 그리고 보안 측면에 대해서 알아보았다.

적절한 표준이나 가이드가 적은 사항이기 때문에 무엇이 딱 맞다고 말할 수 는 없기 때문에 더더욱 설계등이 어려울 수 있으니, 많은 자료들을 참고해보기 바란다.

특히 아무리 좋은 표준이 있다하더라도 팀이 이를 이해하고 사용하지 못하면 표준은 그냥 문서 덩어리일 뿐이다. 표준과 가이드 정립뿐만 아니라 팀에 대한 교육과 표준 준수에 대한 감사 활동등도 고려 해야 한다.

그리고 항상 강조하지만 보안에 대한 부분은 귀찮더라도 항상 빼먹지 말고 구현하도록 하자.

REST API 이해와 설계 – #2 API 설계 가이드

REST API 이해와 설계 

#2 API 설계 가이드

REST API 디자인 가이드

그러면 REST의 특성을 이해하고 나쁜 안티패턴을 회피해서 REST API 디자인은 어떻게 해야 할까? 짧지만 여기에 몇가지 디자인 방식에 대해서 소개 한다.

REST URI는 심플하고 직관적으로 만들자

REST API를 URI만 보고도, 직관적으로 이해할 수 있어야 한다 URL을 길게 만드는것 보다, 최대 2 depth 정도로 간단하게 만드는 것이 이해하기 편하다.

  • /dogs
  • /dogs/1234

URI에 리소스명은 동사보다는 명사를 사용한다.

REST API는 리소스에 대해서 행동을 정의하는 형태를 사용한다. 예를 들어서

  • POST /dogs

는 /dogs라는 리소스를 생성하라는 의미로, URL은 HTTP Method에 의해 CRUD (생성,읽기,수정,삭제)의 대상이 되는 개체(명사)라야 한다.

잘못된 예들을 보면

  • HTTP Post : /getDogs
  • HTTP Post : /setDogsOwner

위의 예제는 행위를 HTTP Post로 정의하지 않고, get/set 등의 행위를 URL에 붙인 경우인데, 좋지 않은 예 이다. 이보다는

  • HTTP Get : /dogs
  • HTTP Post : /dogs/{puppy}/owner/{terry}

를 사용하는 것이 좋다. 그리고 가급적이면 의미상 단수형 명사(/dog)보다는 복수형 명사(/dogs)를 사용하는 것이 의미상 표현하기가 더 좋다.

일반적으로 권고되는 디자인은 다음과 같다.

리소스

POST

GET

PUT

DELETE

create

read

update

delete

/dogs

새로운 dogs등록

dogs 목록을 리턴

Bulk로 여러 dogs 정보를 업데이트

모든 dogs 정보를 삭제

/dogs/baduk

에러

baduk 이라는 이름의 dogs 정보를리턴

baduk이라는 이름의 dogs 정보를 업데이트

baduk 이라는 이름의 dogs 정보를삭제

리소스간의 관계를 표현하는 방법

REST 리소스간에는 서로 연관관계가 있을 수 있다. 예를 들어 사용자가 소유하고 있는 디바이스 목록이나 사용자가 가지고 있는 강아지들 등이 예가 될 수 가 있는데, 사용자-디바이스 또는 사용자-강아지등과 같은 각각의 리소스간의 관계를 표현하는 방법은 여러가지가 있다.

Option 1. 서브 리소스로 표현하는 방법

예를 들어 사용자가 가지고 있는 핸드폰 디바이스 목록을 표현해보면

  • /”리소스명”/”리소스 id”/”관계가 있는 다른 리소스명” 형태
  • HTTP Get : /users/{userid}/devices
    예) /users/terry/devices

과 같이 /terry라는 사용자가 가지고 있는 디바이스 목록을 리턴하는 방법이 있고

Option 2. 서브 리소스에 관계를 명시 하는 방법

만약에 관계의 명이 복잡하다면 관계명을 명시적으로 표현하는 방법이 있다. 예를 들어 사용자가 “좋아하는” 디바이스 목록을 표현해보면

  • HTTP Get : /users/{userid}/likes/devices
    예) /users/terry/likes/devices

는 terry라는 사용자가 좋아하는 디바이스 목록을 리턴하는 방식이다.

Option 1,2 어떤 형태를 사용하더라도 문제는 없지만, Option 1의 경우 일반적으로 소유 “has”의 관계를 묵시적으로 표현할 때 좋으며, Option 2의 경우에는 관계의 명이 애매하거나 구체적인 표현이 필요할 때 사용한다.

에러처리

에러처리의 기본은 HTTP Response Code를 사용한 후, Response body에 error detail을 서술하는 것이 좋다.

대표적인 API 서비스들이 어떤 HTTP Response Code를 사용하는지를 살펴보면 다음과 같다.

구글의 gdata 서비스의 경우 10개, 넷플릭스의 경우 9개, Digg의 경우 8개의 Response Code를 사용한다.  (정보 출처:  http://info.apigee.com/Portals/62317/docs/web%20api.pdf)

Google GData

200 201 304 400 401 403 404 409 410 500

Netflix

200 201 304 400 401 403 404 412 500

Digg

200 400 401 403 404 410 500 503

여러 개의 response code를 사용하면 명시적이긴 하지만, 코드 관리가 어렵기 때문에 아래와 같이 몇가지 response code만을 사용하는 것을 권장한다.

  • 200 성공
  • 400 Bad Request – field validation 실패시
  • 401 Unauthorized – API 인증,인가 실패
  • 404 Not found ? 해당 리소스가 없음
  • 500 Internal Server Error – 서버 에러

추가적인 HTTP response code에 대한 사용이 필요하면 http response code 정의 http://en.wikipedia.org/wiki/Http_error_codes 문서를 참고하기 바란다.

다음으로 에러에는 에러 내용에 대한 디테일 내용을 http body에 정의해서, 상세한 에러의 원인을 전달하는 것이 디버깅에 유리하다.

Twillo의 Error Message 형식의 경우

  • HTTP Status Code : 401
  • {“status”:”401”,”message”:”Authenticate”,”code”:200003,”more info”:”http://www.twillo.com/docs/errors/20003″}

와 같이 표현하는데, 에러 코드 번호와 해당 에러 코드 번호에 대한 Error dictionary link를 제공한다.

비단 API 뿐 아니라, 잘 정의된 소프트웨어 제품의 경우에는 별도의 Error 번호 에 대한 Dictionary 를 제공하는데, Oracle의 WebLogic의 경우에도http://docs.oracle.com/cd/E24329_01/doc.1211/e26117/chapter_bea_messages.htm#sthref7 와 같이 Error 번호, 이에 대한 자세한 설명과, 조치 방법등을 설명한다. 이는 개발자나 Trouble Shooting하는 사람에게 많은 정보를 제공해서, 조금 더 디버깅을 손쉽게 한다. (가급적이면 Error Code 번호를 제공하는 것이 좋다.)

다음으로 에러 발생시에, 선택적으로 에러에 대한 스택 정보를 포함 시킬 수 있다.

에러메세지에서 Error Stack 정보를 출력하는 것은 대단히 위험한 일이다. 내부적인 코드 구조와 프레임웍 구조를 외부에 노출함으로써, 해커들에게, 해킹을 할 수 있는 정보를 제공하기 때문이다. 일반적인 서비스 구조에서는 아래와 같은 에러 스택정보를 API 에러 메세지에 포함 시키지 않는 것이 바람직 하다.

log4j:ERROR setFile(null,true) call failed.

java.io.FileNotFoundException: stacktrace.log (Permission denied)

at java.io.FileOutputStream.openAppend(Native Method)

at java.io.FileOutputStream.(FileOutputStream.java:177)

at java.io.FileOutputStream.(FileOutputStream.java:102)

at org.apache.log4j.FileAppender.setFile(FileAppender.java:290)

at org.apache.log4j.FileAppender.activateOptions(FileAppender.java:164)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

그렇지만, 내부 개발중이거나 디버깅 시에는 매우 유용한데, API 서비스를 개발시, 서버의 모드를 production과 dev 모드로 분리해서, 옵션에 따라 dev 모드등으로 기동시, REST API의 에러 응답 메세지에 에러 스택 정보를 포함해서 리턴하도록 하면, 디버깅에 매우 유용하게 사용할 수 있다.

API 버전 관리

API 정의에서 중요한 것중의 하나는 버전 관리이다. 이미 배포된 API 의 경우에는 계속해서 서비스를 제공하면서,새로운 기능이 들어간 새로운 API를 배포할때는 하위 호환성을 보장하면서 서비스를 제공해야 하기 때문에, 같은 API라도 버전에 따라서 다른 기능을 제공하도록 하는 것이 필요하다.

API의 버전을 정의하는 방법에는 여러가지가 있는데,

  • Facebook ?v=2.0
  • salesforce.com /services/data/v20.0/sobjects/Account

필자의 경우에는

  • {servicename}/{version}/{REST URL}
  • example) api.server.com/account/v2.0/groups

형태로 정의 하는 것을 권장한다.

이는 서비스의 배포 모델과 관계가 있는데, 자바 애플리케이션의 경우, account.v1.0.war, account.v2.0.war와 같이 다른 war로 각각 배포하여 버전별로 배포 바이너리를 관리할 수 있고, 앞단에 서비스 명을 별도의 URL로 떼어 놓는 것은 향후 서비스가 확장되었을 경우에, account 서비스만 별도의 서버로 분리해서 배포하는 경우를 생각할 수 있다.

외부로 제공되는 URL은 api.server.com/account/v2.0/groups로 하나의 서버를 가르키지만, 내부적으로, HAProxy등의 reverse proxy를 이용해서 이런 URL을 맵핑할 수 있는데, api.server.com/account/v2.0/groups를 내부적으로 account.server.com/v2.0/groups 로 맵핑 하도록 하면, 외부에 노출되는 URL 변경이 없이 향후 확장되었을때 서버를 물리적으로 분리해내기가 편리하다.

페이징

큰 사이즈의 리스트 형태의 응답을 처리하기 위해서는 페이징 처리와 partial response 처리가 필요하다.

리턴되는 리스트 내용이 1,000,000개인데, 이를 하나의 HTTP Response로 처리하는 것은 서버 성능, 네트워크 비용도 문제지만 무엇보다 비현실적이다. 그래서, 페이징을 고려하는 것이 중요하다.

페이징을 처리하기 위해서는 여러가지 디자인이 있다.

예를 들어 100번째 레코드부터 125번째 레코드까지 받는 API를 정의하면

Facebook API 스타일 : /record?offset=100&limit=25

  • Twitter API 스타일 : /record?page=5&rpp=25 (RPP는 Record per page로 페이지당 레코드수로 RPP=25이면 페이지 5는 100~125 레코드가 된다.)
  • LikedIn API 스타일 : /record?start=50&count=25
  • 구현 관점에서 보면 , 페이스북 API가 조금 더 직관적이기 때문에, 페이스북 스타일을 사용하는 것을 권장한다.
  • record?offset=100&limit=25

100번째 레코드에서부터 25개의 레코드를 출력한다.

Partial Response 처리

리소스에 대한 응답 메세지에 대해서 굳이 모든 필드를 포함할 필요가 없는 케이스가 있다.

예를 들어 페이스북 FEED의 경우에는 사용자 ID, 이름, 글 내용, 날짜, 좋아요 카운트, 댓글, 사용자 사진등등 여러가지 정보를 갖는데, API를 요청하는 Client의 용도에 따라 선별적으로 몇가지 필드만이 필요한 경우가 있다. 필드를 제한하는 것은 전체 응답의 양을 줄여서 네트워크 대역폭(특히 모바일에서) 절약할 수 있고, 응답 메세지를 간소화하여 파싱등을 간략화할 수 있다.

그래서 몇몇 잘 디자인된, REST API의 경우 이러한 Partial Response 기능을 제공하는데, 주요 서비스들을 비교해보면 다음과 같다.

  • Linked in : /people:(id,first-name,last-name,industry)
  • Facebook : /terry/friends?fields=id,name
  • Google : ?fields=title,media:group(media:thumnail)

Linked in 스타일의 경우 가독성은 높지만 :()로 구별하기 때문에, HTTP 프레임웍으로 파싱하기가 어렵다. 전체를 하나의 URL로 인식하고, 🙁 부분을 별도의 Parameter로 구별하지 않기 때문이다.

Facebook과 Google은 비슷한 접근 방법을 사용하는데, 특히 Google의 스타일은 더 재미있는데, group(media:thumnail) 와 같이 JSON의 Sub-Object 개념을 지원한다.

Partial Response는 Facebook 스타일이 구현하기가 간단하기 때문에, 필자의 경우는 Facebook 스타일의 partial response를 사용하는 것을 권장한다.

검색 (전역검색과 지역검색)

검색은 일반적으로 HTTP GET에서 Query String에 검색 조건을 정의하는 경우가 일반적인데, 이 경우 검색조건이 다른 Query String과 섞여 버릴 수 있다. 예를 들어 name=cho이고, region=seoul인 사용자를 검색하는 검색을 Query String만 사용하게 되면 다음과 같이 표현할 수 있다.

  • /users?name=cho&region=seoul

그런데, 여기에 페이징 처리를 추가하게 되면

  • /users?name=cho&region=seoul&offset=20&limit=10

페이징 처리에 정의된 offset과 limit가 검색 조건인지 아니면 페이징 조건인지 분간이 안간다. 그래서, 쿼리 조건은 하나의 Query String으로 정의하는 것이 좋은데

  • /user?q=name%3Dcho,region%3Dseoul&offset=20&limit=10

이런식으로 검색 조건을 URLEncode를 써서 “q=name%3Dcho,region%3D=seoul” 처럼 (실제로는 q= name=cho,region=seoul )표현하고 Deleminator를 , 등을 사용하게 되면 검색 조건은 다른 Query 스트링과 분리된다.

물론 이 검색 조건은 서버에 의해서 토큰 단위로 파싱되어야 한다.

다음으로는 검색의 범위에 대해서 고려할 필요가 있는데, 전역 검색은 전체 리소스에 대한 검색을, 리소스에 대한 검색은 특정 리소스에 대한 검색을 정의한다.

예를 들어 시스템에 user,dogs,cars와 같은 리소스가 정의되어 있을때,id=’terry’인 리소스에 대한 전역 검색은

  • /search?q=id%3Dterry

와 같은 식으로 정의할 수 있다. /search와 같은 전역 검색 URI를 사용하는 것이다.

반대로 특정 리소스안에서만의 검색은

  • /users?q=id%3Dterry

와 같이 리소스명에 쿼리 조건을 붙이는 식으로 표현이 가능하다.

HATEOS를 이용한 링크 처리

HATEOS는 Hypermedia as the engine of application state의 약어로, 하이퍼미디어의 특징을 이용하여 HTTP Response에 다음 Action이나 관계되는 리소스에 대한 HTTP Link를 함께 리턴하는 것이다.

예를 들어 앞서 설명한 페이징 처리의 경우, 리턴시, 전후페이지에 대한 링크를 제공한다거나

{

[

{

“id”:”user1″,

“name”:”terry”

},

{

“id”:”user2″,

“name”:”carry”

}

],

“links”:[

{

“rel”:”pre_page”,

“href”:”http://xxx/users?offset=6&limit=5″

},

{

“rel”:”next_page”,

“href”:”http://xxx/users?offset=11&limit=5″

}

]

}

와 같이 표현하거나

연관된 리소스에 대한 디테일한 링크를 표시 하는 것등에 이용할 수 있다.

{

“id”:”terry”,

“links”:[

{

“rel”:”friends”,

“href”:”http://xxx/users/terry/friends”

}

]

}

HATEOAS를 API에 적용하게 되면, Self-Descriptive 특성이 증대되어 API에 대한 가독성이 증가되는 장점을 가지고 있기는 하지만, 응답 메세지가 다른 리소스 URI에 대한 의존성을 가지기 때문에, 구현이 다소 까다롭다는 단점이 있다.

요즘은 Spring과 같은 프레임웍에서 프레임웍 차원에서 HATEOAS를 지원하고 있으니 참고하기 바란다.http://spring.io/understanding/HATEOAS

단일 API 엔드포인트 활용

API 서버가 물리적으로 분리된 여러개의 서버에서 동작하고 있을때, user.apiserver.com, car.apiserver.com과 같이 API 서비스마다 URL이 분리되어 있으면 개발자가 사용하기 불편하다. 매번 다른 서버로 연결을 해야하거니와 중간에 방화벽이라도 있으면, 일일이 방화벽을 해제해야 한다.

API 서비스는 물리적으로 서버가 분리되어 있더라도 단일 URL을 사용하는 것이 좋은데, 방법은 HAProxy나 nginx와 같은 reverse proxy를 사용하는 방법이 있다. HAProxy를 앞에 새우고 api.apiserver.com이라는 단일 URL을 구축한후에

HAProxy 설정에서

  • api.apiserver.com/user는 user.apiserver.com 로 라우팅하게 하고
  • api.apiserver.com/car 는 car.apiserver.com으로 라우팅 하도록 구현하면 된다.

이렇게 할 경우 향후 뒷단에 API 서버들이 확장이 되더라도 API를 사용하는 클라이언트 입장에서는 단일 엔드포인트를 보면 되고, 관리 관점에서도 단일 엔드포인트를 통해서 부하 분산 및 로그를 통한 Audit(감사)등을 할 수 있기 때문에 편리하며, API 에 대한 라우팅을 reverse proxy를 이용해서 함으로써 조금 더 유연한 운용이 가능하다.

REST에 문제점

그렇다면 이렇게 많은 장점이 있는 REST는 만능인가? REST역시 몇가지 취약점과 단점을 가지고 있다.

JSON+HTTP 를 쓰면 REST인가?

REST에 대한 잘못된 이해중의 하나가, HTTP + JSON만 쓰면 REST라고 부르는 경우인데, 앞의 안티패턴에서도 언급하였듯이, REST 아키텍쳐를 제대로 사용하는 것은, 리소스를 제대로 정의하고 이에대한 CRUD를 HTTP 메서드인 POST/PUT/GET/DELETE에 대해서 맞춰 사용하며, 에러코드에 대해서 HTTP Response code를 사용하는 등, REST에 대한 속성을 제대로 이해하고 디자인해야 제대로된 REST 스타일이라고 볼 수 있다.

수년전 뿐만 아니라 지금에도 이러한 안티패턴이 적용된 REST API 형태가 많이 있기 때문에, 제대로된 REST 사상의 이해 후에, REST를 사용하도록 해야 한다.

표준 규약이 없다

REST는 표준이 없다. 그래서 관리가 어렵다.

SOAP 기반의 웹 서비스와 같이 메시지 구조를 정의하는 WSDL도 없고, UDDI와 같은 서비스 관리체계도 없다. WS-I이나 WS-*와 같은 메시지 규약도 없다.

REST가 최근 부각되는 이유 자체가 WebService의 복잡성과 표준의 난이도 때문에 Non Enterprise 진영(Google, Yahoo, Amazone)을 중심으로 집중적으로 소개된 것이다. 데이터에 대한 의미 자체가 어떤 비즈니스 요건처럼 Mission Critical한 요건이 아니기 때문에, 서로 데이터를 전송할 수 있는 정도의 상호 이해 수준의 표준만이 필요했지 Enterprise 수준의 표준이 필요하지도 않았고, 벤더들처럼 이를 주도하는 회사도 없었다.

단순히 많이 사용하고 암묵적으로 암암리에 생겨난 표준 비슷한 것이 있을 뿐이다(이런 것을 Defactor 표준이라고 부른다).

그런데 문제는 정확한 표준이 없다 보니, 개발에 있어 이를 관리하기가 어렵다는 것이다. 표준을 따르면   몇 가지 스펙에 맞춰서  맞춰 개발 프로세스나 패턴을 만들 수 있는데, REST에는 표준이 없으니 REST 기반으로 시스템을 설계하자면 사용할 REST에 대한 자체 표준을 정해야 하고, 어떤 경우에는 REST에 대한 잘못된 이해로 잘못된 REST 아키텍처에 ‘이건 REST다’는 딱지를 붙이기도 한다. 실제로 WEB 2.0의 대표 주자격인 Flickr.com도 REST의 특성을 살리지 못하면서 RPC 스타일로 디자인한 API를 HTTP + XML을 사용했다는 이유로 Hybrid REST라는 이름을 붙여 REST 아키텍쳐아키텍처에 대한 혼란을 초래했다.

근래에 들어서 YAML등과 같이 REST 에 대한 표준을 만들고자 하는 움직임은 있으나, JSON의 자유도를 제약하는 방향이고 Learning curve가 다소 높기 때문에, 그다지 확산이 되지 않고 있다.

이런 비표준에서 오는 관리의 문제점은, 제대로된 REST API 표준 가이드와, API 개발 전후로 API 문서(Spec)을 제대로 만들어서 리뷰하는 프로세스를 갖추는 방법으로 해결하는 방법이 좋다.

기존의 전통적인 RDBMS에 적용 시키기에 쉽지 않다.

예를 들어 리소스를 표현할 때,  리소스는 DB의 하나의 Row가 되는 경우가 많은데, DB의 경우는 Primary Key가 복합 Key 형태로 존재하는 경우가 많다. (여러 개의 컬럼이 묶여서 하나의 PK가 되는 경우) DB에서는 유효한 설계일지 몰라도, HTTP URI는 / 에 따라서 계층 구조를 가지기 때문에, 이에 대한 표현이 매우 부자연 스러워진다.

예를 들어 DB의 PK가 “세대주의 주민번호”+”사는 지역”+”본인 이름일 때” DB에서는 이렇게 표현하는 것이 하나 이상할 것이 없으나, REST에서 이를 userinfo/{세대주 주민번호}/{사는 지역}/{본인 이름} 식으로 표현하게 되면 다소 이상한 의미가 부여될 수 있다.

이외에도 resource에 대한 Unique한 Key를 부여하는 것에 여러가지 애로점이 있는데, 이를 해결하는 대안으로는 Alternative Key (AK)를 사용하는 방법이 있다. 의미를 가지지 않은 Unique Value를 Key로 잡아서 DB Table에 AK라는 필드로 잡아서 사용 하는 방법인데. 이미 Google 의 REST도 이러한 AK를 사용하는 아키텍쳐를 채택하고 있다.

그러나 DB에 AK 필드를 추가하는 것은 전체적인 DB설계에 대한 변경을 의미하고 이는 즉 REST를 위해서 전체 시스템의 아키텍쳐에 변화를 준다는 점에서 REST 사용시 아키텍쳐적인 접근의 필요성을 의미한다.

그래서 근래에 나온 mongoDB나 CouchDB,Riak등의 Document based NoSQL의 경우 JSON Document를 그대로 넣을 수 있는 구조를 갖추는데,하나의 도큐먼트를 하나의 REST 리소스로 취급하면 되기 때문에 REST의 리소스 구조에 맵핑 하기가 수월하다.

REST API의 이해와 설계-#1 개념 소개

REST API의 이해와 설계

#1-개념 소개

REST는 웹의 창시자(HTTP) 중의 한 사람인 Roy Fielding의 2000년 논문에 의해서 소개되었다. 현재의 아키텍쳐가 웹의 본래 설계의 우수성을 많이 사용하지 못하고 있다고 판단했기 때문에, 웹의 장점을 최대한 활용할 수 있는 네트워크 기반의 아키텍쳐를 소개했는데 그것이 바로 Representational safe transfer (REST)이다.

REST의 기본

REST는 요소로는 크게 리소스,메서드,메세지 3가지 요소로 구성된다.

예를 들어서 “이름이 Terry인 사용자를 생성한다” 라는 호출이 있을 때

“사용자”는 생성되는 리소스 , “생성한다” 라는 행위는 메서드 그리고 ‘이름이 Terry인 사용자’는 메시지가 된다

이를 REST 형태로 표현해보면

HTTP POST , http://myweb/users/

{

“users”:{

“name”:”terry”

}

}

와 같은 형태로 표현되며, 생성한다의 의미를 갖는 메서드는 HTTP Post 메서드가 되고, 생성하고자 하는 대상이 되는 사용자라는 리소스는 http://myweb/users 라는 형태의 URI로 표현이 되며, 생성하고자 하는 사용자의 디테일한 내용은 JSON 문서를 이용해서 표현된다.

HTTP 메서드

REST에서는 앞에서 잠깐 언급한바와 같이, 행위에 대한 메서드를 HTTP 메서드를 그대로 사용한다.

HTTP 에는 여러가지 메서드가 있지만 REST에서는 CRUD(Create Read Update Delete)에 해당 하는 4가지의 메서드만 사용한다.

메서드 의미 Idempotent
POST Create No
GET Select Yes
PUT Update Yes
DELETE Delete Yes

각각 Post,Put,Get,Delete는 각각의 CRUD 메서드에 대응된다. 여기에 idempotent라는 분류를 추가 했는데, Idempotent는 여러 번 수행을 해도 결과가 같은 경우를 의미한다.

예를 들어 a++는 Idempotent 하지 않다고 하지만(호출시마다 값이 증가 되기 때문에), a=4와 같은 명령은 반복적으로 수행해도 Idempotent하다. (값이 같기 때문에)

POST 연산의 경우에는 리소스를 추가하는 연산이기 때문에, Idempotent하지 않지만 나머지 GET,PUT,DELETE는 반복 수행해도 Idempotent 하다. GET의 경우 게시물의 조회수 카운트를 늘려준다던가 하는 기능을 같이 수행했을 때는 Idempotent 하지 않은 메서드로 정의해야 한다.

Idempotent의 개념에 대해서 왜 설명을 하냐 하면, REST는 각 개별 API를 상태 없이 수행하게 된다. 그래서, 해당 REST API를 다른 API와 함께 호출하다가 실패하였을 경우, 트렌젝션 복구를 위해서 다시 실행해야 하는 경우가 있는데, Idempotent 하지 않은 메서드들의 경우는 기존 상태를 저장했다가 다시 원복해줘야 하는 문제가 있지만, Idempotent 한 메서드의 경우에는 반복적으로 다시 메서드를 수행해주면 된다.

예를 들어 게시물 조회를 하는 API가 있을때, 조회시 마다 조회수를 올리는 연산을 수행한다면 이 메서드는 Idempotent 하다고 볼수 없고, 조회하다가 실패하였을 때는 올라간 조회수를 다시 -1로 빼줘야 한다. 즉 Idempotent 하지 않은 메서드에 대해서는 트렌젝션에 대한 처리가 별다른 주의가 필요하다.

REST의 리소스

REST는 리소스 지향 아키텍쳐 스타일이라는 정의 답게 모든 것을 리소스 즉 명사로 표현을 하며, 각 세부 리소스에는 id를 붙인다.

즉 사용자라는 리소스 타입을 http://myweb//users라고 정의했다면, terry라는 id를 갖는 리소스는 http://myweb/users/terry 라는 형태로 정의한다.

REST의 리소스가 명사의 형태를 띄우다 보니, 명령(Operation)성의 API를 정의하는 것에서 혼돈이 올 수 있다.

예를 들어서 “Push 메서지를 보낸다”는 보통 기존의 RPC(Remote Procedure Call)이나 함수성 접근해서는 /myweb/sendpush 형태로 잘못 정의가 될 수 있지만, 이러한 동사형을 명사형으로 바꿔서 적용해보면 리소스 형태로 표현하기가 조금더 수월해 진다.

“Push 메시지 요청을 생성한다.”라는 형태로 정의를 변경하면, API 포맷은 POST/myweb/push 형태와 같이 명사형으로 정의가 될 수 있다. 물론 모든 형태의 명령이 이런 형태로 정의가 가능한 것은 아니지만, 되도록이면 리소스 기반의 명사 형태로 정의를 하는게 REST형태의 디자인이 된다.

REST API의 간단한 예제

그러면, 간단한 REST API의 예제를 살펴보도록 하자 간단한 사용자 생성 API를 살펴보면

사용자 생성

다음은 http://myweb/users 라는 리소스를 이름은 terry, 주소는 seoul 이라는 내용(메시지)로 HTTP Post를 이용해서 생성하는 정의이다.

HTTP Post, http://myweb/users/

{

“name”:”terry”,

“address”:”seoul”

}

조회

다음은 생성된 리소스 중에서 http://myweb/users 라는 사용자 리소스중에, id가 terry 인 사용자 정보를 조회해오는 방식이다. 조회이기 때문에, HTTP Get을 사용한다.

HTTP Get, http://myweb/users/terry

업데이트

다음은 http://myweb/users 라는 사용자 리소스중에, id가 terry 인 사용자 정보에 대해서, 주소를 “suwon”으로 수정하는 방식이다. 수정은 HTTP 메서드 중에 PUT을 사용한다.

HTTP PUT, http://myweb/users/terry

{

“name”:”terry”,

“address”:”suwon”

}

삭제

마지막으로 http://myweb/users 라는 사용자 리소스중에, id가 terry 사용자 정보를 삭제 하는 방법이다.

HTTP DELETE, http://myweb/users/terry

API 정의를 보면 알겠지만 상당히 간단하다. 단순하게 리소스를 URI로 정해준 후에, 거기에 HTTP 메서드를 이용해서 CRUD를 구현하고 메시지를 JSON으로 표현하여 HTTP Body에 실어 보내면 된다. POST URI에 리소스 id가 없다는 것을 빼면 크게 신경쓸 부분이 없다.

REST의 특성

REST의 기본적인 개념을 이해했으면 조금 더 상세한 REST의 특성에 대해서 알아보도록 하자

유니폼 인터페이스(Uniform Interface)

REST는 HTTP 표준에만 따른 다면, 어떠한 기술이라던지 사용이 가능한 인터페이스 스타일이다. 예를 들어 HTTP + JSON으로 REST API를 정의했다면, 안드로이드 플랫폼이건, iOS 플랫폼이건, 또는 C나 Java/Python이건 특정 언어나 기술에 종속 받지 않고 HTTP와 JSON을 사용할 수 있는 모든 플랫폼에 사용이 가능한 느슨한 결함(Loosely coupling) 형태의 구조이다.

※ 흔히들 근래에 REST를 이야기 하면, HTTP + JSON을 쉽게 떠올리는데, JSON은 하나의 옵션일뿐, 메시지 포맷을 꼭 JSON으로 적용해야할 필요는 없다. 자바스크립트가 유행하기전에만 해도 XML 형태를 많이 사용했으며, 근래에 들어서 사용의 편리성 때문에 JSON을 많이 사용하고 있지만, XML을 사용할 경우, XPath,XSL등 다양한 XML 프레임웍을 사용할 수 있을뿐만 아니라 메시지 구조를 명시적으로 정의할 수 있는 XML Scheme나 DTD등을 사용할 수 있기 때문에, 복잡도는 올라가더라도, 메시지 정의의 명확성을 더할 수 있다.

무상태성/스테이트리스(Stateless)

REST는 REpresentational State Transfer 의 약어로 Stateless (상태 유지하지 않음)이 특징 중의 하나이다.

상태가 있다 없다는 의미는 사용자나 클라이언트의 컨택스트를 서버쪽에 유지 하지 않는다는 의미로,쉽게 표현하면 HTTP Session과 같은 컨텍스트 저장소에 상태 정보를 저장하지 않는 형태를 의미한다.

상태 정보를 저장하지 않으면 각 API 서버는 들어오는 요청만을 들어오는 메시지로만 처리하면 되며, 세션과 같은 컨텍스트 정보를 신경쓸 필요가 없기 때문에 구현이 단순해진다.

캐슁 가능(Cacheable)

REST의 큰 특징 중의 하나는 HTTP라는 기존의 웹 표준을 그대로 사용하기 때문에, 웹에서 사용하는 기존의 인프라를 그대로 활용이 가능하다.

HTTP 프로토콜 기반의 로드 밸런서나 SSL은 물론이고, HTTP가 가진 가장 강력한 특징중의 하나인 캐슁 기능을 적용할 수 있다.일반적인 서비스 시스템에서 60%에서 많게는 80%가량의 트렌젝션이 Select와 같은 조회성 트렌젝션인 것을 감안하면, HTTP의 리소스들을 웹캐쉬 서버등에 캐슁하는 것은 용량이나 성능 면에서 많은 장점을 가지고 올 수 있다.구현은 HTTP 프로토콜 표준에서 사용하는 “Last-Modified” 태그나 E-Tag를 이용하면 캐슁을 구현할 수 있다.

아래와 같이 Client가 HTTP GET을 “Last-Modified” 값과 함께 보냈을 때, 컨텐츠가 변화가 없으면 REST 컴포넌트는 “304 Not Modified”를 리턴하면 Client는 자체 캐쉬에 저장된 값을 사용하게 된다.

 

그림  1 Last Modified 필드를 이용한 캐슁 처리 방식

이렇게 캐쉬를 사용하게 되면 네트웍 응답시간 뿐만 아니라, REST 컴포넌트가 위치한 서버에 트렌젝션을 발생시키지 않기 때문에, 전체 응답시간과 성능 그리고 서버의 자원 사용률을 비약적으로 향상 시킬 수 있다.

자체 표현 구조(Self-descriptiveness)

REST의 가장 큰 특징 중의 하나는 REST API 자체가 매우 쉬워서 API 메시지 자체만 보고도 API를 이해할 수 있는 Self-descriptiveness 구조를 갖는 다는 것이다. 리소스와 메서드를 이용해서 어떤 메서드에 무슨 행위를 하는지를 알 수 있으며, 또한 메시지 포맷 역시 JSON을 이용해서 직관적으로 이해가 가능한 구조이다.

대부분의 REST 기반의 OPEN API들이 API 문서를 별도로 제공하고 있지만, 디자인 사상은 최소한의 문서의 도움만으로도 API 자체를 이해할 수 있어야 한다.

클라이언트 서버 구조 (Client-Server 구조)

근래에 들면서 재 정립되고 있는 특징 중의 하나는 REST가 클라이언트 서버 구조라는 것이다. (당연한 것이겠지만).

REST 서버는 API를 제공하고, 제공된 API를 이용해서 비즈니스 로직 처리 및 저장을 책임진다.

클라이언트의 경우 사용자 인증이나 컨택스트(세션,로그인 정보)등을 직접 관리하고 책임 지는 구조로 역할이 나뉘어 지고 있다.  이렇게 역할이 각각 확실하게 구분되면서, 개발 관점에서 클라이언트와 서버에서 개발해야 할 내용들이 명확하게 되고 서로의 개발에 있어서 의존성이 줄어들게 된다.

계층형 구조 (Layered System)

계층형 아키텍쳐 구조 역시 근래에 들어서 주목받기 시작하는 구조인데, 클라이언트 입장에서는 REST API 서버만 호출한다.

그러나 서버는 다중 계층으로 구성될 수 있다. 순수 비즈니스 로직을 수행하는 API 서버와 그 앞단에 사용자 인증 (Authentication), 암호화 (SSL), 로드밸런싱등을 하는 계층을 추가해서 구조상의 유연성을 둘 수 있는데, 이는 근래에 들어서 앞에서 언급한 마이크로 서비스 아키텍쳐의 api gateway나, 간단한 기능의 경우에는 HA Proxy나 Apache와 같은 Reverse Proxy를 이용해서 구현하는 경우가 많다.

REST 안티 패턴

REST의 개념과 전체적인 특징에 대해서 살펴보았다. 이제는 REST 디자인시 하지 말아야 할 안티 패턴에 대해서 알아보도록 하자.

GET/POST를 이용한 터널링

가장 나쁜 디자인 중 하나가 GET이나 POST를 이용한 터널링이다.

http://myweb/users?method=update&id=terry 이 경우가 전형적인 GET을 이용한 터널링이다. 메서드의 실제 동작은 리소스를 업데이트 하는 내용인데, HTTP PUT을 사용하지 않고, GET에 쿼리 패러미터로 method=update라고 넘겨서, 이 메서드가 수정 메세드임을 명시했다.

대단히 안좋은 디자인인데, HTTP 메서드 사상을 따르지 않았기 때문에, REST라고 부를 수 도 없고, 또한 웹 캐쉬 인프라등도 사용이 불가능하다.

또 많이 사용하는 안좋은예는 POST를 이용한 터널링이다. Insert(Create)성 오퍼러이션이 아닌데도 불구하고, JSON 바디에 오퍼레이션 명을 넘기는 형태인데 예를 들어 특정 사용자 정보를 가지고 오는 API를 아래와 같이 POST를 이용해서 만든 경우이다.

HTTP POST, http://myweb/users/

{

“getuser”:{

“id”:”terry”,

}

}

Self-descriptiveness 속성을 사용하지 않음

앞서 특징에서 설명한 바와 같이 REST의 특성중 하나는 자기 서술성(Self-descriptiveness) 속성으로 REST URI와 메서드 그리고 쉽게 정의된 메시지 포맷에 의해서 쉽게 API를 이해할 수 있는 기능이 되어야 한다.

특히나 자기 서술성을 깨먹는 가장 대표적인 사례가 앞서 언급한 GET이나 POST를 이용한 터널링을 이용한 구조가 된다.

HTTP Response code를 사용하지 않음

다음으로 많이 하는 실수중의 하나가 Http Response code를 충실하게 따르지 않고, 성공은 200, 실패는 500 과 같이 1~2개의 HTTP response code만 사용하는 경우이다. 심한 경우에는 에러도 HTTP Response code 200으로 정의한후 별도의 에러 메시지를 200 response code와 함께 보내는 경우인데, 이는 REST 디자인 사상에도 어긋남은 물론이고 자기 서술성에도 어긋난다.

오랜만의 블로그 포스팅입니다.

그 동안 예전 REST 관련 글들이 오래되서, 새로운 디자인 가이드와 보안 가이드들을 추가해서 업데이트 하였습니다

많은 공유와 피드백 부탁드립니다.

7대의 서버로 glusterfs 구성하기

서버 7대로 glusterfs 구축하기

Glusterfs란?

  • Redhat에서 지원하는 오픈소스 파일시스템으로써 수천 Petabyte급의 대용량에 수천개의 클라이언트가 접속하여 사용 가능한 scale-out 방식 분산 파일 시스템
  • 기존의 분산 파일 시스템에 비해 비교적 구성이 간단하며, 대용량 및 대규모의 I/O처리 능력이 뛰어남

Glusterfs 기본 구성도

플랫폼: hyper-v
메모리: 1GB 이상
HDD: 32GB
OS: Centos6.5
volume: distributed volume

설치하기

  1. 필수 유틸과 glusterfs 다운 받기

    #yum install -y wget fuse fuse-libs fuse-devel

    #cd /usr/local/src

    #wget http://download.gluster.org/pub/gluster/glusterfs/3.5/3.5.3/CentOS/epel-6Server/x86_64/glusterfs-libs-3.5.3-1.el6.x86_64.rpm

    #wget http://download.gluster.org/pub/gluster/glusterfs/3.5/3.5.3/CentOS/epel-6Server/x86_64/glusterfs-3.5.3-1.el6.x86_64.rpm

    #wget http://download.gluster.org/pub/gluster/glusterfs/3.5/3.5.3/CentOS/epel-6Server/x86_64/glusterfs-fuse-3.5.3-1.el6.x86_64.rpm

    #wget http://download.gluster.org/pub/gluster/glusterfs/3.5/3.5.3/CentOS/epel-6Server/x86_64/glusterfs-cli-3.5.3-1.el6.x86_64.rpm

    #wget http://download.gluster.org/pub/gluster/glusterfs/3.5/3.5.3/CentOS/epel-6Server/x86_64/glusterfs-api-3.5.3-1.el6.x86_64.rpm

    #wget http://download.gluster.org/pub/gluster/glusterfs/3.5/3.5.3/CentOS/epel-6Server/x86_64/glusterfs-server-3.5.3-1.el6.x86_64.rpm

    #wget http://download.gluster.org/pub/gluster/glusterfs/3.5/3.5.3/CentOS/epel-6Server/x86_64/glusterfs-geo-replication-3.5.3-1.el6.x86_64.rpm

  2. glusterfs 설치하기

    #rpm -Uvh glusterfs-libs-3.5.3-1.el6.x86_64.rpm

    #rpm -Uvh glusterfs-3.5.3-1.el6.x86_64.rpm

    #rpm -Uvh glusterfs-fuse-3.5.3-1.el6.x86_64.rpm

    #rpm -Uvh glusterfs-cli-3.5.3-1.el6.x86_64.rpm

    #rpm -Uvh glusterfs-api-3.5.3-1.el6.x86_64.rpm

    #rpm -Uvh glusterfs-server-3.5.3-1.el6.x86_64.rpm

    #rpm -Uvh glusterfs-geo-replication-3.5.3-1.el6.x86_64.rpm

  3. 방화벽 설정 변경하기

    #vi /etc/sysconfig/iptables

    gluster는 111, 24007, 24008, 24009~(glusterfs 전체 볼륨수), nfs를 이용해서 마운트 할 경우에는 38465~(gluster 서버 수)로 해주시면 됩니다.
    여기서는 volume 32개를 기준으로 하였고 nfs 마운트를 사용하지 않으나 기본적으로 38465 포트를 추가시켰습니다.

    -A INPUT -m state –state NEW -m tcp -p tcp –dport 111 -j ACCEPT
    -A INPUT -m state –state NEW -m tcp -p tcp –dport 24007 -j ACCEPT
    -A INPUT -m state –state NEW -m tcp -p tcp –dport 24008 -j ACCEPT
    -A INPUT -m state –state NEW -m tcp -p tcp –dport 24009:24041 -j ACCEPT
    -A INPUT -m state –state NEW -m tcp -p tcp –dport 38465 -j ACCEPT

  4. 데몬 시작 및 chkconfig 등록하기

    #/etc/init.d/glusterd start

    #chkconfig –level 345 glusterd on

설정하기

    1. /etc/hosts 편집하기

      – gluster1~7까지 7개 서버에 모두 똑같이 등록해야 합니다.

    2. Peer 등록하기

      – Gluster1에만 등록하면 자동으로 해당 서버들에 등록이 됩니다.

      #gluster peer probe gluster2

      #gluster peer probe gluster3

      #gluster peer probe gluster4

      #gluster peer status //peer 상태 확인

      잘 등록이 되었다면 다음과 같은 메시지 확인 가능

      Number of Peers: 3

      Hostname: gluster4
      Uuid: a661b6d6-395b-422a-a401-96cf8da6b7c3
      State: Peer in Cluster (Connected)

      Hostname: gluster3
      Uuid: 0a927512-0913-4cd3-bfd5-5d509250f6c4
      State: Peer in Cluster (Connected)

      Hostname: gluster2
      Uuid: 6a6cf4df-f690-4d29-bd1e-5baa0c11b2ec
      State: Peer in Cluster (Connected)

  1. Volume 만들기
    -Gluster volume의 종류
    a.Distributed volume
    -> 파일을 gluster 서버 노드의 각 Brick으로 분산해서 저장(기본)
    b.Replicated volume
    -> 파일을 gluster 서버 노드의 각 Brick으로 분산해서 저장함.  지정된 replication 수 만큼의 복제 파일을 생성
    c.Stripe volume
    -> 여러 서버의 여러 Brick에 파일을 stripe 수로  만큼 나누어 저장함. 단 volume 생성 시 정의한 brick의 수와 Stripe 수가 일치해야함
    d.Distributed stripe volume
    -> 여러 gluster 서버 노드의 각 Brick 으로 파일을 stripe 수만큼 나누어 저장함
    e.Distributed replicated volume
    -> multiple한 replicated volume 구성을 형성할 때 사용할 수 있음
    -Distributed volume 생성하기
    #mkdir /data1/gluster  // brick으로 사용할 디렉토리
    #gluster volume create glus_vol gluster1:/data1/gluster gluster2:/data1/gluster gluster3:/data1/gluster gluster4:/data1/gluster
    #gluster volume info all  // 생성된 volume 확인하기
    #gluster volume start glus_vol  // gluster volume 적용
    -접근 권한 설정하기
    #gluster volume set glus_vol auth.allow 192.168.137.* // 192.168.137.0/24 대역을 이용하는 사용자에게 허용
  2. Glusterfs client 설정하기

    – Gluster7에서 다음의 과정을 수행합니다.

    #modprobe fuse  // fuse 모듈 로딩
    #dmesg |grep –i fuse  // fuse 모듈 로딩 상태 확인
    #mkdir /gluster  // mount 위치
    #mount –f glusterfs –o log-devel=WARNING, log-file=/var/log/gluster.log gluster1:/glus_vol /gluster
    #mount
    #df –h  // 목록 중 volume 이름으로 추가 된 디스크 확인
  3. Client에서 파일 생성 테스트
    #for i in `seq -w 1 100`; do cp -rp /var/log/messages* /gluster/copy-test-$i; done
    #ls –lA /gluster | wc –l
    -gluster1~4에서 파일 업로드 상태 확인
    #ls /data1/gluster
  4. 생성된 volume에 추가로 peer 등록하기
    -Gluster1에서 명령 실행
    #gluster peer probe gluster5 gluster6
    #gluster volume add-brick glus_vol gluster5:/data1/gluster gluster6:/data1/gluster
    #gluster volume info all  // 새로 등록된 peer 확인
    -Client에서 mount 상태 확인
    #df –h  // 조금 전 마운트 된 디스크 용량 변화 확인
  5. Glusterfs 용량 재분배
    -기존에 저장되어 있던 파일들을 새롭게 추가된 서버들로 재분배하는 방법
    #gluster volume rebalance glus_vol start
    #gluster volume rebalance glus_vol status
  6. Glusterfs Tuning
    – glusterfs는 튜닝의 방법에 따라서 속도 및 활용도의 차이가 많이 난다고 하니 상황에 따라서 알맞게 변경하여 사용하시면 됩니다.

    #gluster volume set glus_vol performance.cache-size 256MB(cache-size 자유롭게 변경 가능)

    여기부터는 brick 및 volume 삭제 방법

  7. 볼륨에 추가된 Brick 제거하는 방법

    #gluster volume remove-brick glus_vol gluster5:/data1/gluster gluster6:/data1/gluster

  8. Volume 삭제하는 방법
    -기존에 만들었던 volume을 완벽히 지우기 위해서는 속성값을 변경해줘야 합니다.
    #gluster volume stop glus_vol
    #gluster volume delete glus_vol
    #setattr –x trusted.glusterfs.volume-id /data1/gluster
    #setattr –x trusted.gfid /data1/gluster
    #rm –rf /data1/gluster/.glusterfs