Apache + Docker + Ghost
글쓰기 귀차ㄴ아
나도 안다, 이건 핑계다. 글을 쓰려고만 했다면 몇번이고 썼을 것이다. 지금 세상에 무료 마이크로블로깅 서비스가 얼마나 많은가, 글을 쓰려고만 했다면 페북이나 트위터나 텀블러나 어디라도 쓸 수 있다. 게다가 개인적으로도 이미 위키나 티켓관리 시스템을 운용하고 있으니, 글을 쓸곳이 없어서 안썼다는 변명은 내가 생각해도 너무 빈약하다.
그래서 새로운 변명을 하나 더 꾸며내 보았다. 글을 쓰기가 너무 복잡하다는 어떨까? 확실히 Jekyll 은 가볍고, 자유롭지만.. 대개 이런 불길한 단어 뒤에는 가벼운 만큼 당장 써먹을 기능이 없고, 자유로운 만큼 네가 알아야 할 것이 많다는 진실은 알려주지 않는다. 내말을 믿어라, 제품 설명서에 붙은 각종 수사 뒤에는 불편한 진실이 숨어있게 마련이다.
다른걸 찾아보자
그러는 와중에, Ghost 를 발견했다. node.js 로 작성된 주제에 이름이 Ghost.js가 아니란 점이 인상적이다.
특징은 다음과 같다.
- 예쁘다
- 예쁘다
- 예쁘다
그렇다. 예쁘다. node.js 로 작성되어서, 어디든 그냥 실행하면 자체 웹서버로 돌아간다. 그래서 ghost 만 직접 실행하여 곧바로 서비스 하는것도 문제 없다. 그러나 이미 가지고 있는 웹 서버가 있기에, 여기에 연결해보려 한다. 일일이 설치하기가 귀찮아서, docker 로 띄워서 apache 에 연동시켰다. 가능하면 관리자 권한을 최대한 피하고, 개인계정에서 작업하는 상황을 염두하여 다음과 같이 구성해 보았다.
- docker 로 ghost 구동
- apache2 웹 서버에서 mod_userdir 모듈을 구성하여,
http://hostname/~user
의 형태의 요청을~user/public_dir
파일시스템으로 연결함 $HOME/public_dir
에.htaccess
를 구성하여, 들어오는 모든 요청을 ghost 인스턴스로 Proxy
간단한 개요는 위와 같다. 이것을 구성하기 위해서는 다음이 필요하다. 관리자에게 요청하여 준비할 수 있다.
- docker 사용 권한
apache2 + (mod_userdir, .htaccess, mod_rewrite)
글을 쓰다보니 점점 튜토리얼이 되어갈 것 같은 아주 불안한 느낌이 든다. 사용자의 환경이 모두 다르고, 이 글을 쓴 이후에도 버전과 환경은 달라질 것이라, 가능하면 일반적인 내용으로 접근하려 한다. (하지만 그렇게 될리가 없을 것 같다..)
시작하기 전에
대단히 혼란스러울 수 있는 명칭 몇개를 정리하고자 한다. 이는 올바른 정의(definition)가 아닌, 글에서 쓰이는 용어를 어떤 느낌으로 사용했는가 정도로만 보아주시길 바란다.1
또한, mod_rewrite
나 웹서버의 설정은 대개 HTTP 프로토콜을 조작한다. 따라서, 웹서버를 이해하기 위해서 HTTP 프로토콜의 RFC 문서를 통해 동작원리를 파악하는것이 좋다.2
-
- 블로그
- 뭔가 적긴 적어야겠는데, 매우 귀찮은 존재를 말한다. “일기”나 “보고서”로 대체하여 생각할 수 있다. 경우에 따라서 블로그 행위를 즐기는 이상한 사람들도 간혹 존재한다.
-
- 사용자 요청
- HTTP 클라이언트(대표적으로는 웹브라우져) 에서 웹서버에 대해 URL 을 이용하여 자원을 요청하는 행위에 대해 사용자 요청이라 하겠다. 쉽게말해, 브라우져에 URL을 입력하고 엔터를 탁- 치면, 브라우져는 사용자의 이 행위를 HTTP 프로토콜로 생성하여 웹서버측에 던진다. 이것이 웹 서버측 입장에서는 사용자 요청이라고 볼 수 있다.3
-
웹클라이언트 HTTP 프로토콜을 이용하여 웹 서버와 통신하는 소프트웨어이다. agent 라고 부르기도 한다. 일반적으로는 브라우져를 생각하면 된다. 우리가 생각할때 브라우져가 아닌것들도 웹클라이언트가 될 수 있다.
-
- 웹서버
- 클라이언트로부터 전송된 HTTP 프로토콜을 처리할 수 있는 소프트웨어를 지칭한다. 가장 일반적인 동작은 웹 클라이언트가 HTTP 프로토콜을 사용하여 웹 서버로 자원을 요청하게 되고, 웹 서버는 이 요청에 합당한 자원을 찾아내어 웹 클라이언트로 반환한다. 이 글에서는 apache httpd 2.4 를 기준으로 한다.
-
- 웹 어플리케이션
- 짧게 웹앱 이라고도 한다.하는 일은 일반적인 어플리케이션과 비슷하지만, 동작과 이용이 모두 웹상에서 일어난다. 웹 서버를 통해 웹어플리케이션이 제공되고, 웹클라이언트(브라우져 등)를 이용하여 이용할 수 있다. 웹앱은 보통 HTTP 프로토콜을 이용하여 서비스를 제공하므로, 사용자 입장에서는 HTTP 클라이언트(보통은 웹브라우져)만 있으면 어떤 종류의 웹어플리케이션도 모두 이용할 수 있다. 우리가 접하는 모든 동적인 웹사이트는 웹어플리케이션이라고 보아도 무방하다. 이 글에서는 설치형 마이크로블로그 웹 어플리케이션인 ghost 를 말한다.
-
- proxy
- 클라이언트로부터 넘겨받은 요청을 다른 서버에게 “클라이언트로서” 요청할 수 있다. 이렇게 요청한 결과를 받아, 최초 요청한 클라이언트에 “서버로서” 응답한다. 경우에 따라 때로는 서버가 되고 때로는 클라이언트가 될 수 있다. 클라이언트로부터 받은 요청을 가공하여 대신 다른 서버에 요청할 수 있다. 주로 보안의 이유로, 클라이언트가 서버측에 직접 연결하는것을 막기 위해 중간에 proxy 역할을 하는 서버를 두는 경우가 있다. 이 글에서는, 사용자는 아파치 웹서버에 요청을 던지면, 아파치가 대신 ghost 웹 어플리케이션에 요청을 전달한다. 이때 웹서버는 웹어플리케이션에 대해 proxy 서버처럼 동작하게 된다. 사용자가 직접 웹 어플리케이션에 접근하지 못하는 경우, 해당 웹 어플리케이션에 직접 접근할 수 있는 웹서버를 대신 이용하는 것이다. 비유를 하자면, 학생운동이 활발했던 대학 내에, 경찰들이 쁘락치(proxy) 를 간첩처럼 심어놨다는 옛날얘기와 비슷하다. 쁘락치는 경찰이 직접 다가가지 못하는 정보에 접근할 수 있고, 획득한 정보를 클라이언트인 경찰에게 다시 전달할 수 있다.
-
- URI / URL 4 5
- 짧게 말하자면, URI 는 URL 과 URN 으로 나눌 수 있다. URL 과 URN 은 URI 의 원소가 되는것이다. URI(Uniform Resource Identifier) 는 물리/추상 자원을 표현하는 짧은 문자열이다. URI 가 가르키는 자원은 실제 존재하는 자원의 위치(Location)일 수도 있고, 자원을 식별하는 이름(Name)일 수 도 있다. 즉, URN 이 자원을 식별하는 이름이라면, URL 은 그 자원을 찾기 위한 방법과 위치를 알려준다. 그러나 이 글에서는 URL 을 전체를 사용하지 않고, 프로토콜과 도메인에 독립적인 자원을 축약하여 표현하기 위해 URI 표현을 사용하였다.
URL | http://class.gnu.ac.kr/~jehos |
URI | /~jehos |
Syntax Components |
URL scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
URL: foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
| _____________________|__
/ \ / \
URN: urn:example:animal:ferret:nose
-
- URI 와 파일시스템의 경로의 혼용
- 사용자는 URL 을 이용하여 웹서버에게 자원을 요청하는데, 자원이 웹서버가 접근 가능한 파일시스템상에 존재한다면 직접 파일시스템의 파일을 전달해준다. 따라서, 웹 서버는 URL 과 파일시스템을 서로 연결하는 설정이 필요하다. 이 글에서는 전체 URL 대신 URI 를 사용하여, scheme 와 authority 이후의 path 부터 표현하였다. 문제는, URI 과 파일시스템 경로는 그 형태가 매우 유사하다는 것이다. 둘은 서로 비슷하지만 전혀 다른 차원의 자원을 가르킨다. 이 글에서도 URI 과 파일시스템이 수차례 혼용되어 나온다. 이것이 URI 인지, 파일시스템상의 경로인지 자세히 확인해야 할 것이다. 가능하면 이것이 URI 인지, 파일시스템인지 함께 병기하도록 하겠다. (글을 다 쓴 다음에서야 이 미묘한 차이를 발견한 터라, 다음번 글쓰기에서는 이러한 미묘한 부분이 없도록 하겠습니다.)
URL | /~jehos |
파일시스템,경로,디렉토리 | ~jehos |
ghost 준비
ghost 를 기동시키기 위해서는 node.js 를 기동시킬 수 있는 환경만 준비되면 된다. 하지만 라이브러리 설치와… 생각보다 고려할 점이 많았다.
- node.js + npm 으로 기동 환경 구축
- systemd 를 활용하여 자동 기동하도록 설정
뭔가 예쁘지 않다. 관리자 권한도 필요하고, 손댈것도 많다. 깔끔하게 도커로 말아놓은 ghost 인스턴스에 빨대만 꽂으면 되는것이 아닌가. 그래서 docker 에서 ghost 를 받아왔다.
jehos@class:~⟫ docker pull ghost
... < 중략 > ...
Status: Downloaded newer image for ghost:latest
ghost 의 Dockerfile 에 따르면,
컨테이너의 /var/lib/ghost
경로에 ghost 의 내용이 저장되게 되어있다.
또한, 2368
포트가 노출되어 있는것도 알 수 있다.
ENV GHOST_CONTENT /var/lib/ghost
RUN mkdir -p "$GHOST_CONTENT" && chown -R user:user "$GHOST_CONTENT"
VOLUME $GHOST_CONTENT
EXPOSE 2368
이를, 다음과 같이 실행시킬 수 있다.
다음은 컨테이너상에 노출된 2368
포트를 호스트의 127.0.0.1:2368
로 연결시키고,
컨테이너상에 노출된 /var/lib/ghost
디렉토리를 호스트의 /home/jehos/ghost
디렉토리로 연결시키는 구문이다.
docker run --name ghost-jehos-p2368 -d -p 127.0.0.1:2368:2368 -v /home/jehos/workspace/ghost:/var/lib/ghost ghost:latest
그러나, 이 명령은 호스트의 포트 하나를 소모시킨다. 포트 하나 소모시키는 정도는 큰 문제는 아니나, 만약 다수의 사용자가 사용한다면 포트가 중복되는 문제가 있을 수 있다. 이를 회피하기 위해서는 포트대신 소켓을 로컬에 마운트 시켜서 사용하는 방법이 가능할 것 같다.
도커 컨테이너의 접근
docker 가 정상적으로 기동되면, 여기서부터가 복잡한 부분이다. 이 컨테이너는 호스트에 납작 붙어있다.
즉, 127.0.0.1:2368
에 바인딩 되어 있다.
따라서, 외부에서 접근해서 확인하려면 ssh 의 포트포워드 를 사용하여 로컬에서 확인하여야 한다.
다음의 ssh 명령은, hostname
호스트에 user
사용자로 접속하되,
해당 호스트의 2368
포트를 로컬의 127.0.0.1:2368
포트로 포트포워딩 하는 명령이다.
이렇게 접속한 후, 브라우저에서 localhost:2368
로, ssh 원격 호스트상의 2368
포트로 접근할 수 있다.
ssh user@hostname -L 2368:127.0.0.1:2368
어쨌든 이 조치는 단순히 도커 컨테이너가 접속이 되는구만!의 확인 차원으로만 사용해야만 한다.
왜 포트로 직접 사용하지 않나?
이 부분에서 분명 이 질문을 떠올리시는 분이 계실 것 같아,
굳이 포트를 직접 노출하지 않는 이유를 설명하고자 한다.
docker 인스턴스를 기동할 때, -p 0.0.0.0:2368:2368
하면 호스트상의 전체 아이피에 bind 되면서,
외부로 2368
포트로 서비스 할 수 있다.
어려운 일은 아니지만, 몇가지 부분에서 문제가 될 수 있다.
사용자 요청 —> ghost (node.js)
- 프로그램이 직접 외부로 노출됨
추후 보안 문제가 발생할 수 있음 - 부하 분산이나 프록시 같은 제어를 할 수 없음
웹 어플리케이션에서 이런 복잡한 처리로직을 가지고 있을 필요가 없다. 오류의 기회만 많아질 것이다. 그런 복잡한 기능은 웹서버에 맡겨두자 - 더러워 ← (중요)
세상에, 홈페이지를 접속하는 포트까지 입력해야 하다니.. 더러워..
사실 다른 이유는 죄다 핑계이고, 포트까지 입력해야 한다는 사실을 참을수가 없었다. 외우는데 아주 큰 문제가 된다. 사람머리는 그렇게 좋지 않다. 더구나 해당 시스템에서 다른 사용자들도 같이 사용한다고 하면, 포트번호는 엉망진창이 될 것이다.
사용자 요청 —> 웹서버 —> ghost
따라서, 앞단에 웹서버를 두고 사용자로부터 요청이 오면 proxy 를 이용하여 웹서버가 내부의 웹앱에 대신 접근하고 그 결과를 다시 사용자에게 돌려주는 방식으로 처리할 것이다.
웹서버 설정
nginx 가 대세이지만, httpd(apache) 도 나쁘지 않은 선택이다.
mpm_event
모듈을 쓰면 이벤트 방식으로 동작한다.
또한 mod_userdir
이나 mod_rewrite
, mod_security
등 강력한 모듈도 포기할 수 없는 이유이다.
그리고 가장 큰 이유는 아직 apache 도 제대로 조작하지 못하는 주제에 nginx 까지 익힐수가 없었다.. ㅠㅠ
mod_userdir
아까부터 mod_userdir
모듈을 계속 언급하게 되는데, 이유가 있다.
mod_userdir
모듈은 사용자 홈 계정의 특정 디렉토리를 /~user
같은 형태의 URL 로 연결해준다.
즉, 사용자 홈 디렉토리에 특정 디렉토리만 만들고, 자원(정적자원, 혹은 CGI 같은 동적자원)을 올려두면 바로 서비스가 가능하다.
이 모듈이 사용되는 상황은, 보통 대학의 실습에서 많이 사용된다. 웹서버는 요청받은 URL 을 해석하여, 자원을 찾는다. 이 자원은 다른 서버에 있을 수도 있지만, 보통은 파일시스템 내에 위치하게 된다. 따라서, 웹서버 관리자는 이 URL 요청과 파일시스템 경로를 일치시키는 작업을 하게된다.
만약 학생이 100명쯤 된다면? 웹서버 관리자는 각 학생마다의 경로와, 각 학생마다의 디렉토리를 생성하여 관리해야 할 것이다.
그러나, mod_userdir
은 간단한 규칙을 사용한다.
사용자 아이디를 이용하여 URL 패턴을 만들고,
만약 이 URL로 요청이 들어온다면 사용자의 홈 디렉토리 내의 특정 디렉토리로 연결해주는 것이다.
각 사용자의 홈 디렉토리에 특정 디렉토리(기본값은 public_html
이다)를 생성하고,
이 디렉토리에 파일만 올려두면, 웹서버 관리자가 따로 설정하지 않도라도 바로 서비스를 할 수 있게 되는것이다.
학습자료를 검색하다보면 학생들의 이름으로 심심찮게 /~user
같은 형태의 URL 을 쉽게 볼 수 있다.
관리하는 입장에서는 따로따로 Alias 를 걸어주거나 할 필요없는 굉장히 편리한 모듈이다.
그런데, 혼자 웹서버를 사용하는 경우에는 이 모듈을 쓸 일이 없다. 최소한 실습실 서버 관리를 해본 사람이 아니고서야 처음 들어보는 사람도 있을 것이다. 이유는, 최대한 관리자 권한 없이 개인 사용자 입장에서 서버를 구성하도록 하기 위해서이다. 만약 이 모듈을 사용하지 않고 나머지 과정을 진행하고자 한다면, 처음부터 끝까지 웹서버를 동작시키기 위한 관리자 권한이 필요하다. 따라서, 관리자가 최소한의 서버 설정은 해줬다는 가정 하에 이 작업이 가능함을 보일것이다. 이는, 온라인 상의 웹 호스팅 서비스를 받을때도 아주 유사한 상황이 되므로, 유용하게 써먹을 수 있다고 생각한다.
개인적인 이유로, 가능하면 모든 과정을 관리자 계정 없이 수행하는것을 매우 중요하게 생각한다. 관리자 권한으로 수행할 경우 발생할 수 있는 조작상의 문제나, 보안상의 문제도 큰일이지만, 가장 큰 사유는 관리자 권한으로 작업을 수행할 경우 좋은 공부거리를 놓치게 되는 경우가 많이 있기 때문이다. (아주 중요하게 생각하는 부분이라, 추후 글로써 정리하도록 하겠다.)
따라서, mod_userdir
모듈을 사용하여 개인의 홈 계정을 설정하고, 이를 ghost 컨테이너로 연결하려고 한다.
.htaccess
mod_userdir
모듈을 설명하면서, 개인 사용자에게 호스팅 공간을 직접 관리할 수 있도록 했다는 이야기를 했다.
이게 왜 중요하냐면, 보통 웹서버에서 자원을 준비하는 방식이 관리자에 의해 설정되는 방식이기 때문이다.
특정 요청이 오면 특정 디렉토리의 자원을 반환하도록 설정 파일에 기입하고
웹서버를 재기동하여 변경된 설정을 반영하는데, 이 과정을 일반 사용자가 직접 하게 할 수는 없는 것이다.
전체 서버에 영향이 가는 작업이니, 절대로 사용자에게 맡겨선 안될 일이다.
mod_userdir
모듈은 /~user
같은 형태의 URL 로 자원요청이 들어오면,
해당 사용자가 권한을 가진 파일시스템으로 연결해준다.
사용자는 이 설정 만으로도 정적 파일들을 자신의 디렉토리에 올려, /~user
같은 주소체제를 통해 자원을 제공할 수 있다.
그러나 사용자는 언제나 불만이 많다. 끊임없이 관리자를 괴롭힐 것이다.
그래서 나온 절충안은 이렇다. 관리자가 특정 디렉토리에 대한 권한을 위임하는 방식으로, 특정 디렉토리의 설정을 동적으로 바꿀 수 있다. 아주 중요하다. 무려 동적으로 실시간으로 변경이 가능하다. (그러나 이 구조는 성능상 아주 좋지 않은 결과를 가져온다.)
디렉토리에 도대체 어떻게 권한을 위임하는가, 그것이 바로 .htaccess
파일이다.
이 파일은 과거부터 내려오던 관례적인 이름이기 때문에,
실제 서버의 중앙 설정파일에서 어떻게 설정되어있는가에 따라 다를 수 있다.
중앙 설정 파일의 AccessFileName
을 참조하라.
# AccessFileName: The name of the file to look for in each directory
# for additional configuration directives. See also the AllowOverride
# directive.
#
AccessFileName .htaccess
방식은 이렇다.
- 중앙 서버 설정파일에서, .htaccess 옵션이 활성화 되어 있을 때
- 요청받은 자원을 포함한 디렉토리 내에서 .htaccess 파일이 존재하는지 확인하고
- .htaccess 파일이 있다면, 해당 파일에 기입된 설정을 처리한다.
이런 식으로, 해당 디렉토리에 접근 할 수 있는 사용자라면 .htaccess 에 원하는 설정을 입력할 수 있다. 또한, 서버를 재기동 하지 않고도 설정을 적용시킬 수 있는 장점이 있다. 그러나 이 구조는 무시무시한 댓가를 가져오게 만드는데, 바로 보안 문제와 성능 문제이다.
일단 성능 문제가 가장 심각한데, 접속량이 얼마 되지 않는 사이트라면 큰 문제는 없을 것이다. 그러나 디렉토리를 바꿀 때마다 .htaccess 를 확인하는 동작은 분명 비용이 소모된다. 따라서 규모가 큰 서버에서는 이 기능을 꺼놓는게 일반적이다. 또한 사용자가 설정을 잘못 기입 할 경우 보안 문제가 일어날 수 있다. 다운로드 되어선 안될 파일을 열게 된다던지, 프록시 설정을 잘못하여 서버의 모든 커넥션을 재귀적으로 싹 잡아먹는다던지 하는 문제이다.
보안 문제를 막기 위해서, 관리자는 사용자에게 어디까지 권한을 위임할 것인지를 선택할 수 있다.
이것이 바로 AllowOverride
설정이다.
이름이 풍기는 느낌대로, 주 서버 설정에 기입된 설정을, .htaccess
에서 Override 할 수 있도록 허가해 주는 것이다.
즉, 사용자는 AllowOverride
를 통해 Override 가 허가된 설정에 한해서, 설정을 덮어쓸 수 있다
mod_rewrite
mod_rewrite
는 아주 중요하고, 강력하고, 위험한 도구이다.
보통 들어가서는 안될 단어들이 이렇게 범벅 되어있으니, 얼마나 까다롭고 위험한 물건인지 잘 알 수 있다.
말하자면 흑마법 같은 존재이다.
쉽게 말하자면, HTTP 헤더를 실시간으로 수정해주는 모듈이다. HTTP 프로토콜에 들어가는 모든 헤더는 다 건드릴 수 있다. 물론 대부분 가장 많이 조작하게 되는것은 URL 이기 때문에 자칫 URL을 조작하는 하찮은 모듈로 생각할 수도 있지만, 그것은 신성모독이라고 할 수 있다. 다시 말하지만, 이건 흑마법이다.
그래서
위의 나온 세가지를 조합하여 구성할 것이다.
mod_userdir
로 구성된 개인 홈 디렉토리의 계정에,mod_rewrite
를 이용한 proxy 설정을,.htaccess
파일에 기록하여 올려둔다.
이렇게 되면 아마 다음과 같이 동작할 것이다.
- 사용자가 웹브라우저를 통해 웹서버로 요청을 보낸다.
- 웹서버는 이 사용자가 요청한 자원이, 특정 사용자의 홈 디렉토리에 존재함을 알고 넘긴다
- 특정 디렉토리에
.htaccess
가 있는지 검사한다 - 만약 존재한다면,
.htaccess
를 해석한다 .htaccess
에는, 이 주소로 들어온 모든 요청에 대해서 특정 포트로 proxy 하라는 구문이 적혀있다- 특정 포트에는 node.js 로 작성된 ghost 웹 어플리케이션이 구동되어 있다.
- 사용자의 요청은 웹서버에 의해 ghost 웹 어플리케이션에 proxy 된다.
이런 식으로, 사용자는 자신이 웹서버와 연결된 줄 알겠지만, 사실은 웹서버가 뒷단의 웹 어플리케이션과 proxy 로 연결되어 있게 된다.
시작
mod_userdir
모듈을 활성시켰다면, 사용자의 홈 디렉토리 기본값은 $HOME/public_html
이다.
내 계정이 jehos
이니, /~jehos
가 붙은 요청은 자동으로 ~jehos/public_html
디렉토리로 연결되는 것이다.
계정명 jehos
홈 디렉토리(파일시스템 경로) ~jehos/public_html
URL /~jehos
이런 환경에서, 사용자가 /~jehos/blog
URL 로 접근할 경우, 이를 ghost 로 연결하게 하고 싶다.
따라서, 이 URL 이 연결된 파일시스템상의 ~jehos/public_html/blog
디렉토리를 생성하였다.
jehos@class:~⟫ mkdir -p $HOME/public_html/blog
jehos@class:~⟫ tree -d $HOME/public_html
/home/jehos/public_html
└── blog
이 상태라면, 사용자가 /~jehos/blog
로 접근할 경우, 웹 서버는 파일시스템상의 ~jehos/public_html/blog
디렉토리에서
자원을 찾으려고 시도 할 것이다.
이때, 웹서버는 해당 디렉토리에 .htaccess
가 있는지 찾아볼 것이므로, 바로 이 부분에 .htaccess
를 추가시키는 것이다.
# Copy & Paste!
cat << EOF >> $HOME/public_html/blog/.htaccess
DirectoryIndex disabled
RewriteEngine on
RewriteRule .? http://localhost:2368%{REQUEST_URI} [P]
EOF
Rewrite
로 시작하는 지시자가 mod_rewrite
의 명령들이다.
이 자리에서 자세히 설명하지 못함을 용서하시기 바랍니다.
다행히 만국공통어 정규표현식이라, 대충 어떤 역할을 할 것이다 정도는 감이 오리라 생각한다.
간단하게 해석하자면 다음과 같다.
- 디렉토리 인덱스를 생성하지 말것
빈 디렉토리에 접근 할 경우, 파일의 목록을 자동으로 생성하는(AutoIndex) 서버설정이 켜져있을 수 있기 때문이다. RewriteEngine
을 켜시오RewriteRule
에 따라,/~jehos/blog/
에 요청받은 모든 URL 은,http://localhost:2368%{REQUEST_URI}
로 Proxy 하여 요청하라
여기까지가 서버 설정의 끝이다. 그러나, 아직 접속되지 않을 것이다.
ghost 웹 어플리케이션의 설정
사실 나는 웹 개발자가 아니다. 개발자와는 한참 거리가 먼 일반 사용자에 가깝다고 생각한다. 그렇기 때문에, 웹 어플리케이션을 개발해보지 않으면 알 수 없을 문제로 인해 고생한적이 상당히 많다. 운영을 하려면 아주 간단한 형태의 웹 어플리케이션이라도 개발해 봐야, 문제의 원인을 찾을 수 있다고 생각한다.
서버 설정은 끝났는데, 어째서 웹어플리케이션은 제대로 동작하지 않는가?
웹 어플리케이션과 서버의 합을 맞추지 않았기 때문이다.
예를 들자면 이런 상황이다. 사용자가 서버에게 /~jehos/blog/func/
라는 URL 로 요청을 보냈다고 가정하자.
이때, 요청은 mod_rewrite
로 인해 뒷단의 ghost 웹 어플리케이션으로 넘어간다.
문제는 여기부터다. ghost 가 받은 요청은, 다시 수십~수백개의 요청으로 불어난다.
어째서인가? 해당 자원 안에도 수많은 다른 자원들이 있기 때문이다.
간단한 .html
파일 하나만 생각해봐도 그렇다.
.html
파일 안에는 수많은 정적 자원들이 포함되어 있다.
각종 이미지 파일과 css 파일만 생각해봐도 그렇다.
즉, 내가 index.html
을 요청했다면, 그 파일 내에 링크된 수많은 이미지와
css 같은 각종 정적 자원 및, 동적자원들이 브라우져에 의해 다시 요청된다.
모든 자원을 브라우져가 확보할때까지 재귀적으로 반복된다.
문제가 무엇인지 알겠는가?
- 사용자가 브라우져를 통해 특정 페이지를 요청
- 웹서버가 수신
- ghost 웹 어플리케이션으로 proxy
- ghost 웹 어플리케이션이 응답한 자료를 브라우져가 수신
- 응답한 자료 내에 포함된 다른 자원들을 다시 브라우져가 요청
- 웹서버가 수신
- 해당 자원이 없거나, 오류를 일으킴
바로 5, 6, 7 이 문제가 된다. ghost 웹 어플리케이션은 자신의 주소가 설정된 기본 주소가 엉뚱한 곳으로 설정될 경우 일어나는 문제이다. 그래서 위에서 합을 맞춘다는 표현을 쓴 것이다.
- 웹 서버의 기본 URL 은
/~jehos/blog/
이다.
즉, 사용자 요청은 이 주소를 기준으로 요청된다. - ghost 웹앱의 기본 URL 은
/
이다.
즉, ghost 는 자신의 자원을/
을 기준으로 찾으려고 시도한다.
바로 이 부분이 차이이다.
- (1, 2번) 사용자는
/~jehos/blog/
URL으로 웹서버에 요청을 전달했다. - (3번) 웹서버는 proxy 를 이용하여 ghost 까지 어떻게든 사용자의 요청을 전달했고,
- (4번) ghost 는 그 요청에 대한 응답을 다시 웹서버에 돌려줬고, 웹서버는 사용자에게 돌려줬다. 이때 돌려준 응답은, 자원의 주소가 포함되어 있는 html 코드이다.
- (5번) 그런데, ghost 는 자신의 자원을
/~jehos/blog/
를 기준으로 삼지 않고, 자신의 기준인/
를 기준 URL 로 삼았다. 브라우져는 ghost 가 돌려준 응답속의 자원을 찾으려고/
을 기준 URL 로 하는 자원의 주소를 다시 서버측에 요청한다. - (6번) 서버측에서는
/
을 기준 URL 로 삼아 자원을 찾아보지만, 해당 요청에 대한 자원이 없다. 따라서 그딴거 없엉! 하며 404 코드를 반환한다 - (7번) 브라우져는 몇번 시도하다가, 결국 이미지와 css 가 헐벗어진 앙상한 html 코드만 보여주며 깨진다.
다시 한번 말하지만, 합을 맞춰야 한다. 즉, 웹서버가 생각하는 기준URL 과, 웹어플리케이션이 생각하는 기준URL 을 일치시키면 된다. 그러면 사용자가 요청한 자원의 기준 URL 과, 웹앱이 반환하는 코드 내에 동적으로 삽입될 기준URL 이 일치하게 되므로, 페이지는 정상적으로 뜨게 된다.
ghost 설정
docker 에서 인스턴스를 기동할 때, 컨테이너 내의 /var/lib/ghost
디렉토리를 호스트 컴퓨터의 특정 디렉토리로 연결하였다.
docker 는 컨테이너 방식의 가상화 기술로서, 일반적인 가상화 기술과 확연하게 다른 차이를 보이는데, 바로 ephemeral 하다는 특징을 가진다.
이게 뭔 얘기냐면, 껐다 켜면 사라진다 어쩌다가 이런 특징을 가지게 되었는가는 다른 기회에 다시 말하기로 하고,
일단 그런 괴랄한 특징을 가졌기 때문에 자료 날리기 싫으면 컨테이너 내의 데이터가 쌓이는 디렉토리를 호스트쪽의 디렉토리에
연결시켜서 데이터를 컨테이너 바깥으로 빼내야 한다.
바로 이것이, docker 를 실행할때 붙는 -v
옵션의 의미이다.
만약 이 옵션없이 도커 컨테이너를 기동했다면, 도커를 끄는 순간 모든 데이터는 날아간다.
아까 예제에서는 -v /home/jehos/workspace/ghost:/var/lib/ghost
와 같이 연결하였다.따라서, ghost 도커 인스턴스가 기동되며 기록되는 /var/lib/ghost/
의 내용들은 /home/jehos/workspace/ghost/
디렉토리에 기록된다. (아쉬운점은, 이 옵션 경로는 오직 절대 경로만 인식한다.)
요즘 나오는 웹앱들은 이런 속성을 인식하고 있기 때문에, 사용자 설정(preferrence) 도 이곳에 함께 저장하여 제공한다. 따라서, 이곳에 저장된 설정을 수정하고, docker 컨테이너를 재기동하면 변경된 설정으로 웹앱이 다시 실행되게 된다.
ghost 의 경우, config.js
라는 이름으로 설정이 제공된다.
위의 예제에 따르면, /home/jehos/workspace/ghost/config.js
에 존재할 것이다.
이 파일을 열어보면, 다음과 같은 부분이 존재한다.
30 // ### Development **(default)**
31 development: {
32 // The url to use when providing links to the site, E.g. in RSS and email.
33 // Change this to your Ghost blog's published URL.
34 url: 'http://localhost:2368',
ghost 도커 컨테이너는 기본값으로 개발환경으로 동작하도록 작성되었더라. 왜 그랬는지는 도커 말아놓은 분이 아시겠지만, 개발 환경에서도 큰 무리없이 동작하기에 일단은 그냥 썼다. 정확한 이해 없이 기본값을 건드리는 것은 썩 훌륭한 방법이 아니기 때문이다.
바로 이 부분이 문제다.
url: ‘http://localhost:2368’,
이 경로가 ghost 웹앱의 기본 경로가 되고, ghost 가 응답하는 모든 자원 앞에는 자동으로 이 경로가 붙는다. 따라서, ghost 의 첫번째 응답을 받은 사용자 브라우져는, 전혀 엉뚱한 http://localhost:2368/ 을 기준 URL 로 삼아 자원을 요청해버린 것이다. 바로 이 부분이 아주 큰 문제다!!!
이 부분을, 현재 웹서버의 기본 URL 값으로 수정하였다.
url: ‘http://class.gnu.ac.kr/~jehos/blog’,
변경된 설정파일을 저장하고, ghost 도커 인스턴스를 재기동 한다. 반드시! ghost 도커 인스턴스를 재기동 하여야 한다. ghost 도커 인스턴스가 다시 기동되면서, 변경된 설정파일을 적용시킬 것이기 때문이다.
이제 다시 접속해보자.
http://class.gnu.ac.kr/~jehos/blog
- 사용자는
http://class.gnu.ac.kr/~jehos/blog
로 요청을 보낸다. - 서버는 요청을 받고,
/~jehos/blog
주소를 통해~jehos/public_html/blog
경로를 탐색한다 - 해당 경로 내에,
.htaccess
가 있음을 발견하고, 설정을 적용한다. - 설정에 의해, 사용자의 요청은 ghost 웹앱으로 proxy 된다
- ghost 웹앱이 필요한 자원들의 기준 URL 을
http://class.gnu.ac.kr/~jehos/blog
로 삼은 응답을 다시 사용자에게 반환한다. - 사용자 브라우져는 전달받은 응답을 해석하여,
http://class.gnu.ac.kr/~jehos/blog
를 기준 URL 로 하는 요청을 다시 서버에 보낸다 - 모든 자원을 획득할때까지 1번으로 가시오.
- work like a charm :-D
마치며
처음 운을 뗄 때부터 뭔가 불안했다. 분명 간단하게 요점만 쓴다고 하다가 삼천포로 빠지겠지. 한도 끝도 없이 장황해지겠지. 결코 튜토리얼은 되지 않으리라 다짐했지만, 결국 나는 한치의 예상을 벗어나지 못했다.
그래서 어떻게든 적긴 했는데, 선무당의 문제는 항상 이렇게 귀결된다.
적으려니 어설프고, 안 적으려니 까먹겠고,
이 문서를 보고 참고할 생각일랑은 절대 하지 마시옵고, 다만 이 문서에 언급된 키워드를 직접 하나씩 검색해 가시며 검증하시는 시간이 되시기를, 언급된 개념이나 개요를 골격삼아 직접 알아낸 지식들로 핏줄기와 살덩이를 입히시기를, 그렇게 자신의 것으로 체화하는 글이 되기를 바랍니다.
(이렇게 책임으로부터 회피!+1)
같이보시오
-
엄밀하게 정제된 용어로서 살펴보려면, rfc2068: Hypertext Transfer Protocol – HTTP/1.1: 1.3 Terminology를 살펴보는것을 추천한다. ↩
-
HTTP 프로토콜의 기본적인 동작을 흝어보려면 1.4 Overall Operation을 참고하라 ↩
-
RFC 2616 Hypertext Transfer Protocol – HTTP/1.1: 5. Request ↩