2019년 10월 16일 수요일

Docker Install for CentOS


Get Docker Engine - Community for CentOS
Estimated reading time: 10 minutes
To get started with Docker Engine - Community on CentOS, make sure you meet the prerequisites, then install Docker.
Prerequisites
Docker EE customers
To install Docker Enterprise Edition (Docker EE), go to Get Docker EE for CentOS instead of this topic.
To learn more about Docker EE, see Docker Enterprise Edition.
OS requirements
To install Docker Engine - Community, you need a maintained version of CentOS 7. Archived versions aren’t supported or tested.
The centos-extras repository must be enabled. This repository is enabled by default, but if you have disabled it, you need to re-enable it.
The overlay2 storage driver is recommended.
Uninstall old versions
Older versions of Docker were called docker or docker-engine. If these are installed, uninstall them, along with associated dependencies.
$ sudo yum remove docker \
                  docker-client
\
                  docker-client-latest
\
                  docker-common
\
                  docker-latest
\
                  docker-latest-logrotate
\
                  docker-logrotate
\
                  docker-engine
It’s OK if yum reports that none of these packages are installed.
The contents of /var/lib/docker/, including images, containers, volumes, and networks, are preserved. The Docker Engine - Community package is now called docker-ce.
Install Docker Engine - Community
You can install Docker Engine - Community in different ways, depending on your needs:
  • Most users set up Docker’s repositories and install from them, for ease of installation and upgrade tasks. This is the recommended approach.
  • Some users download the RPM package and install it manually and manage upgrades completely manually. This is useful in situations such as installing Docker on air-gapped systems with no access to the internet.
  • In testing and development environments, some users choose to use automated convenience scripts to install Docker.
Install using the repository
Before you install Docker Engine - Community for the first time on a new host machine, you need to set up the Docker repository. Afterward, you can install and update Docker from the repository.
SET UP THE REPOSITORY
  1. Install required packages. yum-utils provides the yum-config-manager utility, and device-mapper-persistent-data and lvm2are required by the devicemapper storage driver.
    $ sudo yum install -y yum-utils \
      device-mapper-persistent-data
    \
      lvm2
  2. Use the following command to set up the stable repository.
    $ sudo yum-config-manager \
       
    --add-repo \
       
    https://download.docker.com/linux/centos/docker-ce.repo

Optional: Enable the nightly or test repositories.
These repositories are included in the docker.repo file above but are disabled by default. You can enable them alongside the stable repository. The following command enables the nightly repository.
$ sudo yum-config-manager --enable docker-ce-nightly
To enable the test channel, run the following command:
$ sudo yum-config-manager --enable docker-ce-test
You can disable the nightly or test repository by running the yum-config-manager command with the --disable flag. To re-enable it, use the --enable flag. The following command disables the nightly repository.
$ sudo yum-config-manager --disable docker-ce-nightly

INSTALL DOCKER ENGINE - COMMUNITY
  1. Install the latest version of Docker Engine - Community and containerd, or go to the next step to install a specific version:
    $ sudo yum install docker-ce docker-ce-cli containerd.io

    If prompted to accept the GPG key, verify that the fingerprint matches 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35, and if so, accept it.
    Got multiple Docker repositories?
    If you have multiple Docker repositories enabled, installing or updating without specifying a version in the yum installor yum update command always installs the highest possible version, which may not be appropriate for your stability needs.
    Docker is installed but not started. The
     docker group is created, but no users are added to the group.
  2. To install a specific version of Docker Engine - Community, list the available versions in the repo, then select and install:
    a. List and sort the versions available in your repo. This example sorts results by version number, highest to lowest, and is truncated:
    $ yum list docker-ce --showduplicates | sort -r

    docker-ce.x86_64  3:18.09.1-3.el7                     docker-ce-stable
    docker-ce.x86_64  3:18.09.0-3.el7                     docker-ce-stable
    docker-ce.x86_64  18.06.1.ce-3.el7                    docker-ce-stable
    docker-ce.x86_64  18.06.0.ce-3.el7                    docker-ce-stable

    The list returned depends on which repositories are enabled, and is specific to your version of CentOS (indicated by the .el7suffix in this example).
    b. Install a specific version by its fully qualified package name, which is the package name (
    docker-ce) plus the version string (2nd column) starting at the first colon (:), up to the first hyphen, separated by a hyphen (-). For example, docker-ce-18.09.1.
    $ sudo yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io

    Docker is installed but not started. The docker group is created, but no users are added to the group.
  3. Start Docker.
    $ sudo systemctl start docker
  4. Verify that Docker Engine - Community is installed correctly by running the hello-world image.
    $ sudo docker run hello-world

    This command downloads a test image and runs it in a container. When the container runs, it prints an informational message and exits.
Docker Engine - Community is installed and running. You need to use sudo to run Docker commands. Continue to Linux postinstall to allow non-privileged users to run Docker commands and for other optional configuration steps.

UPGRADE DOCKER ENGINE - COMMUNITY
To upgrade Docker Engine - Community, follow the installation instructions, choosing the new version you want to install.

Install from a package
If you cannot use Docker’s repository to install Docker, you can download the .rpm file for your release and install it manually. You need to download a new file each time you want to upgrade Docker Engine - Community.
  1. Go to https://download.docker.com/linux/centos/7/x86_64/stable/Packages/ and download the .rpm file for the Docker version you want to install.
    Note: To install a nightly or test (pre-release) package, change the word stable in the above URL to nightly or test.Learn about nightly and test channels.
  2. Install Docker Engine - Community, changing the path below to the path where you downloaded the Docker package.
    $ sudo yum install /path/to/package.rpm

    Docker is installed but not started. The docker group is created, but no users are added to the group.
  3. Start Docker.
    $ sudo systemctl start docker
  4. Verify that Docker Engine - Community is installed correctly by running the hello-world image.
    $ sudo docker run hello-world

    This command downloads a test image and runs it in a container. When the container runs, it prints an informational message and exits.
Docker Engine - Community is installed and running. You need to use sudo to run Docker commands. Continue to Post-installation steps for Linux to allow non-privileged users to run Docker commands and for other optional configuration steps.
UPGRADE DOCKER ENGINE - COMMUNITY
To upgrade Docker Engine - Community, download the newer package file and repeat the installation procedure, using yum -y upgrade instead of yum -y install, and pointing to the new file.

Install using the convenience script
Docker provides convenience scripts at get.docker.com and test.docker.com for installing edge and testing versions of Docker Engine - Community into development environments quickly and non-interactively. The source code for the scripts is in the docker-installrepository. Using these scripts is not recommended for production environments, and you should understand the potential risks before you use them:
  • The scripts require root or sudo privileges to run. Therefore, you should carefully examine and audit the scripts before running them.
  • The scripts attempt to detect your Linux distribution and version and configure your package management system for you. In addition, the scripts do not allow you to customize any installation parameters. This may lead to an unsupported configuration, either from Docker’s point of view or from your own organization’s guidelines and standards.
  • The scripts install all dependencies and recommendations of the package manager without asking for confirmation. This may install a large number of packages, depending on the current configuration of your host machine.
  • The script does not provide options to specify which version of Docker to install, and installs the latest version that is released in the “edge” channel.
  • Do not use the convenience script if Docker has already been installed on the host machine using another mechanism.
This example uses the script at get.docker.com to install the latest release of Docker Engine - Community on Linux. To install the latest testing version, use test.docker.com instead. In each of the commands below, replace each occurrence of get with test.
Warning:
Always examine scripts downloaded from the internet before running them locally.
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh
<output truncated>
If you would like to use Docker as a non-root user, you should now consider adding your user to the “docker” group with something like:
  sudo usermod -aG docker your-user
Remember to log out and back in for this to take effect!
Warning:
Adding a user to the “docker” group grants them the ability to run containers which can be used to obtain root privileges on the Docker host. Refer to Docker Daemon Attack Surface for more information.
Docker Engine - Community is installed. It starts automatically on DEB-based distributions. On RPM-based distributions, you need to start it manually using the appropriate systemctl or service command. As the message indicates, non-root users can’t run Docker commands by default.
UPGRADE DOCKER AFTER USING THE CONVENIENCE SCRIPT
If you installed Docker using the convenience script, you should upgrade Docker using your package manager directly. There is no advantage to re-running the convenience script, and it can cause issues if it attempts to re-add repositories which have already been added to the host machine.

Uninstall Docker Engine - Community
  1. Uninstall the Docker package:
    $ sudo yum remove docker-ce
  2. Images, containers, volumes, or customized configuration files on your host are not automatically removed. To delete all images, containers, and volumes:
    $ sudo rm -rf /var/lib/docker
You must delete any edited configuration files manually.


출처: <https://docs.docker.com/install/linux/docker-ce/centos/>

2016년 12월 7일 수요일

[Tomcat] Commons DBCP 이해하기

출처 : http://d2.naver.com/helloworld/5102792


데이터베이스와 애플리케이션을 효율적으로 연결하는 커넥션 풀(connection pool) 라이브러리는 웹 애플리케이션에서 필수 요소입니다. 웹 애플리케이션 서버로 상용 제품을 사용하다면 보통 제조사에서 제공하는 커넥션 풀 구현체를 사용합니다. 그 외에 오픈소스 라이브러리로 Apache의 Commons DBCP와 Tomcat-JDBC, BoneCP, HicariCP 등이 있습니다.

커넥션 풀 라이브러리를 잘 사용하면 데이터베이스와 애플리케이션의 일부분에서 발생하는 문제가 전체로 전파되지 않게 할 수 있고, 일시적인 문제가 긴 시간 이어지지 않게 할 수 있습니다. 반대로 값을 적절하지 못하게 설정해서 커넥션 풀이 애플리케이션에서 병목 지점이 되는 경우도 있습니다. 웹 애플리케이션의 요청은 대부분 DBMS(database management system)로 연결되기 때문에 커넥션 풀 라이브러리의 설정은 전체 애플리케이션의 성능과 안정성에 영향을 미치는 핵심입니다.

이렇게 중요한 커넥션 풀 라이브러리를 적절하게 사용하려면 라이브러리의 내부 구조와 원리, 속성값의 의미를 이해해야 합니다. 이 글에서는 대표적인 오픈소스 커넥션 풀 라이브러리인 Commons DBCP를 살펴보겠습니다.

버전 선택과 속성 설정 방법

JDK 버전과 Commons DBCP 버전

JDK(Java development kit)의 버전에 따라서 Connection이나 Statement 같은 JDBC(Java database connectivity)의 인터페이스가 조금씩 다르므로 사용하는 JDK의 버전에 맞게 Commons DBCP 버전을 선택해야 안정된 동작을 기대할 수 있다. 예를 들어 JDK 6을 사용하면서 Commons DBCP 1.3.x를 사용한다면 JDBC 4에 추가된 SQLTimeoutException 등을 드라이버에서 전달할 때 Commons DBCP가 정교하게 처리하지 못해 오류 파악하기 어려울 수 있다.
Commons DBCP 주요 버전과 그에 대응하는 JDK 버전과 JDBC 버전은 다음과 같다.
표 1 Commons DBCP 버전에 대응하는 JDK 버전과 JDBC 버전
Commons DBCP 버전JDK 버전JDBC 버전
Commons DBCP 2JDK 7JDBC 4.1
Commons DBCP 1.4JDK 6JDBC 4
Commons DBCP 1.3JDK 1.4~1.5JDBC 3
참고로 Connection.getMetaData() 메서드를 호출한 다음 커넥션을 닫지 않았을 때 메모리 누수가 발생하는 문제 등 주요 버그가 Commons DBCP 1.4.1에서 패치됐지만 몇 년째 Commons DBCP 1.4.1의 정식 배포는 안 나오고 있다. JDK 6을 쓴다면 Commons DBCP 1.4.1-SNAPSHOT 버전에 추가된 커밋을 분석해 보는 것이 좋다. JDK 7부터 사용할 수 있는 Commons DBCP 2에는 Commons DBCP 1.4.1의 패치가 반영됐고 Commons DBCP 1.4.x보다는 비교적 활발히 개발이 이어지고 있다. 이 글에서는 Commons DBCP 1.x를 기준으로 소스 코드를 분석했지만 Commons DBCP 2.x의 기본 구조도 동일하다.
DBCP 1.4.1의 메모리 누수 버그
DBCP 1.4.1에서 수정한 메모리 누수 버그에 관한 내용은 https://issues.apache.org/jira/browse/DBCP-330를 참고한다.

Commons DBCP 속성 설정

Commons DBCP의 속성은 BasicDataSource 클래스의 세터(setter) 메서드로 설정할 수 있다. Spring 프레임워크를 사용한다면 다음과 같이 bean 설정으로 속성을 등록한다.
예제 1 Commons DBCP 1.x의 BasicDataSource 클래스 설정 사례
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"  
    destroy-method="close"
    p:driverClassName="${db.driverClassName }"
    p:url="${db.url}"
    p:username="${db.username}"
    p:password="${db.password}"
    p:maxActive="${db.maxActive}"
    p:maxIdle="${db.maxIdle}"
    p:maxWait="${db.maxWait}"
/>
Commons DBCP 2에서는 패키지 이름이 org.apache.commons.dbcp에서 org.apache.commons.dbcp2로 변경되고 maxWait 속성의 이름이 maxWaitMillis로 바뀌었다. Commons DBCP 2가 Commons DBCP 1.x와 하위 호환성을 보장하지 않아 Commons DBCP 1.x에서 Commons DBCP 2로 버전을 올릴 때 BasicDataSource 설정 코드를 그대로 사용할 수 없다.
예제 2 Commons DBCP 2.x의 BasicDataSource 클래스 설정 사례
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"  
    destroy-method="close"
    p:driverClassName="${db.driverClassName }"
    p:url="${db.url}"
    p:username="${db.username}"
    p:password="${db.password}"
    p:maxTotal="${db.maxTotal}"
    p:maxIdle="${db.maxIdle}"
    p:maxWaitMillis="${db.maxWaitMills}""
/>
Commons DBCP 1.x에서 Commons DBCP 2.x로 넘어가면서 이름이 바뀐 주요 속성은 다음과 같다. 이 글에서는 Commons DBCP 1.x를 기준으로 설명하기 때문에 Commons DBCP 2.x을 사용하려면 바뀐 속성 이름으로 이해해야 한다.
표 2 Commons DBCP 2.x에서 이름이 바뀐 Commons DBCP 1.x의 속성
Commons DBCP 1.xCommons DBCP 2.x
maxActivemaxTotal
maxWaitmaxWaitMills
removeAbandoned다음과 같이 세분화됐다.
  • removeAbandonedOnBorrow
  • removeAbandonedOnMaintenance

    커넥션의 개수

    커넥션 풀의 저장 구조

    커넥션 개수를 제대로 설정하려면 Commons DBCP 내부에서 커넥션 풀이 어떤 구조로 저장되는지 이해해야 한다. Commons DBCP는 그림 1처럼 commons-pool에서 제공하는 리소스 풀의 기능을 이용한다.
    그림 1 commons-pool의 GeneriObjectPool
    그림 1 commons-pool의 GeneriObjectPool
    커넥션 생성은 Commons DBCP에서 이루어진다. Commons DBCP는 PoolableConnection 타입의 커넥션을 생성하고 생성한 커넥션에 ConnectionEventListener를 등록한다. ConnectionEventListener에는 애플리케이션이 사용한 커넥션을 풀로 반환하기 위해 JDBC 드라이버가 호출할 수 있는 콜백 메서드가 있다. 이렇게 생성된 커넥션은 commons-pool의 addObject() 메서드로 커넥션 풀에 추가된다. 이때 commons-pool은 내부적으로 현재 시간을 담고 있는 타임스탬프와 추가된 커넥션의 레퍼런스를 한 쌍으로 하는 ObjectTimestampPair라는 자료구조를 생성한다. 그리고 이들을 LIFO(last in first out) 형태의 CursorableLinkedList로 관리한다.
    그림 1의 구조를 기억한다면 Commons DBCP에서 설정할 수 있는 속성 값에 따라서 내부에 저장된 커넥션의 개수가 어떻게 달라지는지 이해하는 데 도움이 될 것이다.

    커넥션 개수 관련 속성

    커넥션의 개수는 BasicDataSource 클래스의 다음 속성으로 지정할 수 있다.
    표 3 BasicDataSource 클래스의 커넥션 개수 지정 속성
    속성 이름설명
    initialSizeBasicDataSource 클래스 생성 후 최초로 getConnection() 메서드를 호출할 때 커넥션 풀에 채워 넣을 커넥션 개수
    maxActive동시에 사용할 수 있는 최대 커넥션 개수(기본값: 8)
    maxIdle커넥션 풀에 반납할 때 최대로 유지될 수 있는 커넥션 개수(기본값: 8)
    minIdle최소한으로 유지할 커넥션 개수(기본값: 0)
    만약 8개의 커넥션을 최대로 활용할 수 있을 때 4개는 사용 중이고 4개는 대기 중인 상태라면 커넥션 풀의 상태는 그림 2와 같을 것이다.
    그림 2 일부 커넥션이 활성화된 상태
    그림 2 일부 커넥션이 활성화된 상태
    커넥션 개수와 관련된 속성은 다음과 같은 조건을 만족시켜야 한다.
    • maxActive >= initialSize
      maxActive = 10이고 initialSize = 20이라고 가정하면 최초에 커넥션을 생성할 때 initialSize 값이 최대 커넥션 개수인 maxActive 값보다 커서 논리적으로 오류가 있는 설정이다.
    • maxIdle >= minIdle
      maxIdle < minIdle로 설정할 수는 있지만 최솟값이 최댓값보다 커서 논리적으로 오류가 있는 설정이다.
    • maxActive = maxIdle
      maxActive 값과 maxIdle 값이 같은 것이 바람직하다. maxActive = 10이고 maxIdle = 5라고 가정해 보자. 항상 커넥션을 동시에 5개는 사용하고 있는 상황에서 1개의 커넥션이 추가로 요청된다면 maxActive = 10이므로 1개의 추가 커넥션을 데이터베이스에 연결한 후 풀은 비즈니스 로직으로 커넥션을 전달한다. 이후 비즈니스 로직이 커넥션을 사용 후 풀에 반납할 경우, maxIdle=5에 영향을 받아 커넥션을 실제로 닫아버리므로, 일부 커넥션을 매번 생성했다 닫는 비용이 발생할 수 있다.
    initialSize와 maxActive, maxIdle, minIdle 항목을 동일한 값으로 통일해도 무방하다. 커넥션 개수와 관련된 가장 중요한 성능 요소는 일반적으로 커넥션의 최대 개수다. 4개 항목의 설정 값 차이는 성능을 좌우하는 중요 변수는 아니다.
    maxActive 값은 DBMS의 설정과 애플리케이션 서버의 개수, Apache, Tomcat에서 동시에 처리할 수 있는 사용자 수 등을 고려해서 설정해야 한다. DBMS가 수용할 수 있는 커넥션 개수를 확인한 후에 애플리케이션 서버 인스턴스 1개가 사용하기에 적절한 개수를 설정한다. 사용자가 몰려서 커넥션을 많이 사용할 때는 maxActive 값이 충분히 크지 않다면 병목 지점이 될 수 있다. 반대로 사용자가 적어서 사용 중인 커넥션이 많지 않은 시스템에서는 maxActive 값을 지나치게 작게 설정하지 않는 한 성능에 큰 영향이 없다.
    Commons DBCP에서는 DBMS에 로그인을 시도하고 있는 커넥션도 사용 중인 것으로 간주한다. 만약 DBMS에 로그인을 시도하고 있는 상태에서 무한으로 대기하고 있다면, 애플리케이션에서 모든 커넥션이 사용 중인 상태가 돼 새로운 요청을 처리하지 못할 수도 있다. 이런 경우 장애 확산을 최소화하려면 Microsoft SQL Server의 JDBC 드라이버에서 설정하는 loginTimeOut 속성같은 JDBC 드라이버별 타임아웃 속성을 설정하는 것이 좋다. JDBC의 타임아웃에 관한 자세한 내용은 "JDBC Internal - 타임아웃의 이해" 글을 참고한다.

    커넥션을 얻기 전 대기 시간

    BasicDataSource 클래스의 maxWait 속성은 커넥션 풀 안의 커넥션이 고갈됐을 때 커넥션 반납을 대기하는 시간(밀리초)이며 기본값은 무한정이다. maxWait 속성을 적절하게 설정하지 않아도 일반적인 상황에서는 큰 문제가 되지 않는다. 하지만 사용자가 갑자기 급증하거나 DBMS에 장애가 발생했을 때 장애를 더욱 크게 확산시킬 수 있어 주의해야 한다.
    적절한 maxWait 값을 설정하려면 TPS(transaction per seconds)와 Tomcat에서 처리 가능한 스레드 개수 등을 이해해야 한다. 예를 들어 자세히 살펴보겠다.

    TPS(transaction per seconds)

    maxActive = 5maxIdle = 5, minIdle = 5로 설정한 상황을 가정한다. 사용자의 요청 A는 그림 3과 같이 요청 하나에 쿼리 10개를 실행한다고 가정하자. 각 쿼리의 평균 실행 시간은 50밀리초라고 하면 전체 10개 쿼리의 실행 시간은 500밀리초다. 결국 요청에 대한 최종 응답 시간은 500밀리초라고 생각할 수 있다. 물론 요청에 응답하기 위해 다른 컴포넌트도 시간을 소비하지만 무시할 수 있는 정도의 값이라고 생각해 제외했다.
    그림 3 사용자 요청의 처리 과정
    그림 3 사용자 요청의 처리 과정
    앞의 가정에서 시스템 전체의 TPS를 대략적으로 산출하면 그림 4와 같다. 요청 하나의 응답 시간이 500밀리초이므로 커넥션 풀에 이용 가능한 유휴 상태의 커넥션이 5개일 때는 동시에 5개의 요청을 500밀리초 동안 처리한다. 따라서 1초 동안에는 10개의 요청을 처리할 수 있고 성능 지수는 10TPS라고 볼 수 있다.
    그림 4 커넥션풀의 커넥션 개수가 5인 경우
    그림 4 커넥션풀의 커넥션 개수가 5인 경우

    TPS와 커넥션 개수와의 관계

    커넥션의 개수가 TPS와 밀접한 관계가 있는 것은 다음 그림처럼 처리할 요청 수가 증가해도 커넥션 풀의 커넥션 개수가 5개이면 10TPS 이상의 성능을 낼 수 없기 때문이다. 1번부터 5번까지의 요청이 실행되는 동안은 커넥션 풀에 여분의 커넥션이 없기 때문에 6번부터 10번까지의 요청은 대기(wait) 상태가 돼 여분의 커넥션이 생길 때까지 maxWait 값만큼 기다린다.
    그림 5 대기로 인한 성능 저하 발생
    그림 5 대기로 인한 성능 저하 발생
    이렇게 커넥션이 부족한 상태의 스택 덤프를 보면 다음과 같이 BasicDataSource.getConnection() 메서드에서 스레드가 대기하고 있는 것을 확인할 수 있다.
    "TP-Processor104" daemon prio=10 tid=0x00007f76e8093800 nid=0x2d80 in Object.wait() [0x00007f76f905a000]
        java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000f886fee0> (a org.apache.commons.pool.impl.GenericObjectPool$Latch)
        at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1112)
        - locked <0x00000000f886fee0> (a org.apache.commons.pool.impl.GenericObjectPool$Latch)
        at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
        at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
        at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
        at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:80)
    
    이를 해결하는 가장 쉬운 방법은 그림 6처럼 단순히 maxActive 값을 높여서 커넥션 풀의 개수를 늘리는 것이다.
    그림 6 커넥션 개수 증가로 인한 TPS증가
    그림 6 커넥션 개수 증가로 인한 TPS증가
    커넥션의 개수를 5에서 10으로 늘리면 전체적인 성능도 10TPS에서 20TPS로 증가한다. 하지만 일반적으로 DBMS의 리소스는 다른 서비스와 공유해 사용하는 경우가 많기 때문에 무조건 커넥션 개수를 크게 설정할 수 없는 상황이 많다. 따라서 예상 접속자 수와 서비스의 실제 부하를 측정해 최적값을 설정하는 것이 중요하다. 그림 5의 대기 시간(wait) 값 조절이 무한히 커넥션 개수를 늘리지 않고 최적의 시스템 환경을 구축하는 데 중요한 역할을 한다. maxWait 값을 어떻게 설정했는지가 일시적인 과부하 상태에서 드러나는 시스템의 전체적인 견고함을 결정짓는다.
    그렇다면 적당한 maxWait 값은 얼마일까? 이 부분을 이해하려면 Commons DBCP 외에 Tomcat의 동작 방식도 고려해야 한다. Tomcat은 스레드 기반으로 동작해 사용자의 요청을 처리한다. Commons DBCP가 커넥션 풀을 가지고 있는 것처럼 Tomcat도 내부에 스레드 풀(wait set)을 가지고 있어 그림 7처럼 사용자의 요청이 들어올 때마다 스레드 풀에서 하나씩 스레드를 꺼내 요청을 처리한다.
    그림 7 Tomcat 스레드의 대기
    그림 7 Tomcat 스레드의 대기
    중점적으로 살펴볼 부분은 1~5번의 요청이 처리되기 전에 또 다른 요청이 들어올 때 시작된다. 즉 동시에 6개의 요청이 들어왔을 때 그림 7처럼 6번 요청은 여분의 커넥션이 없으므로 maxWait 값만큼 기다린다. 여기서 중요한 사실은 기다리는 주체가 Tomcat의 스레드라는 점이다. Tomcat에서 사용자의 연결을 처리하는 최대의 스레드 개수는 server.xml 파일에서 maxThread 속성으로 지정한다.

    적절한 maxWait 값은?

    그림 7에서 maxWait 속성에 설정한 시간이 1,000밀리초면 처리량을 넘어서는 요청의 스레드는 10초 동안 대기 상태에 있게 된다. 그리고 사용자의 요청이 계속 증가하면 결국 Tomcat 스레드 풀의 모든 스레드가 소진돼 Tomcat은 다음과 같은 오류를 출력하며 응답을 멈출 것이다.
    심각: All threads (512) are currently busy, waiting. Increase maxThreads (512) or check the servlet status
    
    더욱 억울한 것은 결국 10초 동안의 대기 상태가 해제되고 커넥션을 획득해 사용자의 요청을 열심히 처리하고 응답을 보내도 그 응답을 받을 사용자는 이미 떠나고 난 뒤라는 점이다. 클릭 후 2~3초 내에 반응이 없으면 페이지를 새로 고치거나 다른 페이지로 이동하는 것이 보통인 인터넷 사용자의 행동을 생각하면 쉽게 이해되리라. 결국은 기다리는 사람도 없는 요청에 응답하기 위해 자원을 낭비한 셈이 된다. 사용자가 인내할 수 있는 시간을 넘어서는 maxWait 값은 아무런 의미가 없다.
    그럼 반대로 너무 작게 설정하면 어떤 문제가 발생할까? 상상대로다. 과부하 시 커넥션 풀에 여분의 커넥션이 없을 때마다 오류가 반환될 것이고 사용자는 너무 자주 오류 메시지를 볼 것이다.
    이렇듯 maxWait 값도 사용자의 대기 가능한 시간 같은, 애플리케이션의 특성과 다른 주변의 설정, 자원의 상황 등을 고려해 판단해야 한다. 만약 갑작스럽게 사용자가 증가해 maxWait 값 안에 커넥션을 얻지 못하는 빈도가 늘어난다면 maxWait 값을 더 줄여서 시스템에서 사용하는 스레드가 한도에 도달하지 않도록 방어할 수 있다. 전체 시스템 장해는 피하고 '간헐적 오류'가 발생하는 정도로 장애의 영향을 줄이는 것이다. 이런 상황이 자주 있다면 Commons DBCP의 maxActive 값과 Tomcat의 maxThread 값을 동시에 늘이는 것을 고려한다. 그러나 시스템 자원의 한도를 많이 넘는 요청이 있다면 설정을 어떻게 변해도 장애를 피할 수 없다. 애플리케이션 서버의 자원이 설정 변경을 수용할 만큼 충분하지 않다면 시스템을 확충해야 할 것이다.

    커넥션의 검사와 정리

    유효성 검사 쿼리(validation query)와 Evictor 스레드 관련 설정으로도 애플리케이션의 안정성을 높일 수 있다.

    유효성 검사 쿼리 설정

    JDBC 커넥션의 유효성은 validationQuery 옵션에 설정된 쿼리를 실행해 확인할 수 있다. Commons DBCP 1.x에서는 다음과 같은 세 가지 테스트 옵션으로 유효성을 검사한다. 유효성을 검사할 때는 validationQuery 옵션에 하나 이상의 결과를 반환하는 쿼리를 설정해야 한다. Commons DBCP 2.x에서는 validationQuery 옵션이 없을 때 Connection.isValid() 메서드를 호출해 유효성을 검사한다.
    • testOnBorrow: 커넥션 풀에서 커넥션을 얻어올 때 테스트 실행(기본값: true)
    • testOnReturn: 커넥션 풀로 커넥션을 반환할 때 테스트 실행(기본값: false)
    • testWhileIdle: Evictor 스레드가 실행될 때 (timeBetweenEvictionRunMillis > 0) 커넥션 풀 안에 있는 유휴 상태의 커넥션을 대상으로 테스트 실행(기본값: false)
    validationQuery 옵션에는 DBMS에 따라 다음과 같이 쿼리를 설정하기를 권장한다. 실제 테이블에 있는 데이터를 조회하는 쿼리를 validationQuery 옵션에 설정했다가 운영 서버에서 많은 데이터를 조회해 장애로 이어진 사례도 있다.
    • Oracle: select 1 from dual
    • Microsoft SQL Server: select 1
    • MySQL: select 1
    • CUBRID: select 1 from db_root
    검증에 지나치게 자원을 소모하지 않게 testOnBorrow 옵션과 testOnReturn 옵션은 false로 설정하고, 오랫동안 대기 상태였던 커넥션이 끊어지는 현상을 막게 testWhileIdle 옵션은 true로 설정하는 것을 추천한다. 참고로 CUBRID는 자체적으로 커넥션을 관리하고 자동으로 다시 연결하도록 구현됐다. DBCP 수준에서 한 번 더 유효성 검사 쿼리를 보내는 것은 추가 비용을 소모할 뿐이므로 CUBRID를 사용할 때는 testWhileIdle 옵션도 false로 설정하기를 권장한다.
    Oracle JDBC 드라이버 9.x에서는 강제로 세션을 종료했을 때 발생하는 ORA-00028 오류가 난 후 부적절한 상태의 커넥션이 커넥션 풀로 반납돼 데이터베이스에 로그인되지 않은 때 발생하는 오류인 ORA-01012 오류가 계속 발생한 사례가 있다. 근본적인 원인은 Oracle JDBC 드라이버가 해당 오류 상황에서 JDBC 명세에 정의된 ConnectionEventListener.connectionErrorOccurred() 메서드를 제대로 호출하지 않았기 때문이었다. Oracle JDBC 드라이버를 10.x로 업그레이드해서 테스트했을 때는 같은 오류가 재현되지 않았다. 오류가 발생하는 버전을 사용하는 애플리케이션에서 Commons DBCP의 testWhileIdle 옵션을 true로 설정한 서버에서도 오류가 발생하지 않았다. Commons DBCP에서 vadliationQuery 옵션을 실행하면서 오류가 발생하면 해당 커넥션을 커넥션 풀에서 제외했기 때문이다. 이렇듯 testWhileIdle 옵션과 유효성 검사 쿼리 설정으로 예상치 못한 오류 상황도 대비할 수 있다.

    Evictor 스레드와 관련된 속성

    Evictor 스레드는 Commons DBCP 내부에서 커넥션 자원을 정리하는 구성 요소이며 별도의 스레드로 실행된다. 이와 관련된 속성은 다음과 같다.
    • timeBetweenEvictionRunsMillis: Evictor 스레드가 동작하는 간격. 기본값은 -1이며 Evictor 스레드의 실행이 비활성화돼 있다.
    • numTestsPerEvictionRun: Evictor 스레드 동작 시 한 번에 검사할 커넥션의 개수
    • minEvictableIdleTimeMillis: Evictor 스레드 동작 시 커넥션의 유휴 시간을 확인해 설정 값 이상일 경우 커넥션을 제거한다(기본값: 30분)
    Evictor 스레드의 역할은 3가지인데 각각의 역할을 수행할 때 위의 속성이 어떻게 참조되는지 살펴보자.
    첫째, 커넥션 풀 내의 유휴 상태의 커넥션 중에서 오랫동안 사용되지 않은 커넥션을 추출해 제거한다. Evictor 스레드 실행 시 설정된 numTestsPerEvictionRun 속성값만큼 CursorableLinkedList의 ObjectTimestampPair를 확인한다. ObjectTimestampPair의 타임스탬프 값과 현재 시간의 타임스탬프 값의 차이가 minEvictableIdleTimeMillis 속성값을 초과하면 해당 커넥션을 제거한다. 커넥션 숫자를 적극적으로 줄여야 하는 상황이 아니라면 minEvictableIdleTimeMillis 속성값을 -1로 설정해서 해당 기능을 사용하지 않기를 권장한다.
    둘째, 커넥션에 대해서 추가로 유효성 검사를 수행해 문제가 있을 경우 해당 커넥션을 제거한다. testWhileIdle 옵션이 true로 설정됐을 때만 이 동작을 수행한다. 첫 번째 작업 시 minEvictableIdleTimeMillis 속성값을 초과하지 않은 커넥션에 대해서 추가로 유효성 검사를 수행하는 것이다.
    셋째, 앞의 두 작업 이후 남아 있는 커넥션의 개수가 minIdle 속성값보다 작으면 minIdle 속성값만큼 커넥션을 생성해 유지한다.
    예를 들어, testWhileIdle=true && timeBetweenEvictionRunMillis > 0이면 위의 3가지 역할을 다 수행하고, testWhileIdle=false && timeBetweenEvictionRunMillis > 0이면 두 번째 동작은 수행하지 않는다.
    Evictor 스레드는 동작 시에 커넥션 풀에 잠금(lock)을 걸고 동작하기 때문에 너무 자주 실행하면 서비스 실행에 부담을 줄 수 있다. 또한 numTestsPerEvictionRun 값을 크게 설정하면 Evictor 스레드가 검사해야 하는 커넥션 개수가 많아져 잠금 상태에 있는 시간이 길어지므로 역시 서비스 실행에 부담을 줄 수 있다. 게다가 커넥션 유효성 검사를 위한 테스트 옵션(testOnBorrow, testOnReturn, testWhileIdle)을 어떻게 설정하느냐에 따라 애플리케이션의 안정성과 DBMS의 부하가 달라질 수 있다. 그러므로 Evictor 스레드와 테스트 옵션을 사용할 때는 데이터베이스 관리자와 상의해서 사용하는 DBMS에 최적화될 수 있는 옵션으로 설정해야 한다.
    IDC(internet data center) 정책에 따라서는 서버 간의 소켓 연결 후 정해진 시간 이상 아무런 패킷도 주고받지 않으면 연결을 종료한다. 이런 경우 timeBetweenEvictionRunsMillis 속성 등으로 의도하지 않게 연결이 끊어지는 것을 방어할 수 있다. 예를 들어 30분 동안 통신이 없을 때 연결이 끊어지는 정책으로 네트워크를 운영한다면, BasicDataSource가 풀링(pooling)하는 커넥션의 수가 30개라고 가정할 때 30분 안에 모든 커넥션에 유효성 검사 쿼리를 한 번씩은 실행하는 것이 바람직하다. Evictor 스레드가 5분에 한 번씩 실행되도록 설정했을 때 30분 동안 Evictor 스레드 실행 횟수는 6번이므로 매번 5개의 커넥션을 검사해야 전체 커넥션을 테스트할 수 있다. 30분 안에 5분마다 Evctor 스레드가 실행되면 6번 실행되지만 오차를 감안해 5번으로 가정하면 이때 설정해야 할 numTestsPerEvictionRun 값은 다음과 같이 구할 수 있다.
    6 * numTestsPerEvictionRun > 30개  
    
    따라서 numTestsPerEvictionRun 속성값은 최소 6 이상이어야 한다. 일반적인 공식으로 정리하면 다음과 같다.
    ('IDC 정책에서 허용하는 최대 유휴 커넥션 유지 시간' / timeBetweenEvictionRunsMillis 속성값) * numTestsPerEvictionRun 속성값) > 전체 커넥션 개수
    

    statement pooling 관련 옵션

    statement pooling은 JDBC 3.0에 정의된 명세다. JDBC 드라이버가 3.0 명세를 지원하지 않으면 사용할 수 없는 기능이다. 하지만 JDBC 2.0 명세만 지원하는 JDBC 드라이버를 사용할 때도 커넥션 풀로 Commons DBCP를 사용하고 있다면 poolPreparedStatements 옵션을 true로 설정해서 Commons DBCP를 커넥션 풀뿐만 아니라 statement pool로도 사용할 수 있다. 이때는 반드시 maxOpenPreparedStatements 옵션을 같이 사용해 커넥션당 풀링할 PreparedStatement의 적절한 개수를 설정해야 한다. 그렇지 않으면 런타임에서 메모리 부족(out of memory) 오류 등이 발생할 수 있다.
    maxOpenPreparedStatements 값은 문제가 발생지 않도록 50 정도로 작게 설정한 후 데이터베이스 관리자의 도움을 얻어 PreparedStatement의 캐시 적중률(hit ratio)을 관찰한 후 조정하기를 권장한다. 여기서 설정한 PreparedStatement 개수는 개별 커넥션마다 할당된다. 즉 커넥션 풀에 10개의 커넥션이 있을 때 maxOpenPreparedStatements = 50이라면 총 10 x 50 = 500개의 PreparedStatement가 캐시에 저장된다. 절대 BasicDataSource 클래스에 설정되는 개수가 아니다.

    기본값을 그대로 쓰기를 권장하는 옵션

    다음 값은 특별한 이유가 없다면 기본값을 쓰는 것을 권장한다.

    removeAbandoned 옵션

    removeAbandoned 옵션은 false가 기본값이다. removeAbandoned 옵션은 오랫동안 열려만 있고 Connection.close() 메서드가 호출되지 않는 커넥션을 임의로 닫는 기능을 설정하는 옵션이다. removeAbandoned 옵션을 true로 설정하고 removeAbandonedTimeout 옵션에 허용할 최대 시간을 지정하면 Commons DBCP에서 자동으로 Connection.close() 메서드를 호출한다.
    애플리케이션 개발자가 직접 JDBC API를 다루던 때는 Connection.close() 메서드 호출을 누락해서 전체 시스템의 자원을 고갈시키는 경우가 많았다. 근래에는 대부분의 애플리케이션이 Spring이나 MyBatis 등의 프레임워크를 사용하기 때문에 그럴 위험이 없다. 하지만 커넥션 자원이 제대로 반납되지 않는다는 의심이 있다면 Commons DBCP 수준에서 방어하기보다는 문제 지점을 찾아서 근본적으로 수정해야 애플리케이션을 더 안정적으로 만들 수 있다.
    removeAbandoned 옵션을 true로 설정하면 실행 시간이 긴 쿼리의 커넥션을 의도하지 않게 닫는 부작용이 있다. removeAbandoned 옵션은 기본값이 false로 사용하고, 오래 걸리는 쿼리는 JDBC Statement의 쿼리 타임아웃(query timeout) 등 다른 속성으로 제어하는 편이 바람직하다.

    defaultAutoCommit 속성

    defaultAutoCommit 속성은 true가 기본값이다. 이 속성을 false로 설정하면 커넥션을 커넥션 풀에서 꺼낼 때 바로 setAutocommit(false) 메서드를 호출해서 트랜잭션을 시작하겠다는 의미다. defaultAutoCommit 속성을 false로 설정하면 애플리케이션에서 트랜잭션 처리가 되어 있지 않은 경우에는 INSERT 쿼리나 UPDATE 쿼리가 제대로 반영되지 않는다. 따라서 기본값인 true를 그대로 사용하는 것이 무난하다.

    마치며

    애플리케이션에서 주로 실행되는 쿼리의 성격, 사용자가 대기 가능한 시간, DBMS와 애플리케이션 서버의 자원, 발생 가능한 예외 상황, Tomcat 설정 등 커넥션 풀 라이브러리를 제대로 설정하려면 많은 요소를 고려해야 한다. 그래서 특별한 문제가 없으면 대충 설정하는 경우가 오히려 더 많다. 그러나 커넥션 풀 라이브러리는 전체 애플리케이션의 성능과 안정성에 큰 영향을 미치는 구성 요소이므로 사용자가 많은 서비스라면 충분히 시간과 노력을 투자해서 고민해야 하는 영역이다.

    2016년 11월 21일 월요일

    [Mesos] Apache Mesos 소개

    Apache Mesos 소개

    Twitter의 Rob Benson이 "앞으로 트위터의 인프라는 Mesos로 간다."고 해 개인적으로 관심을 좀 가지고 있다가 AirBnB의 오픈 소스 스케쥴링 시스템인 Chronos가 Mesos기반에서 돌아가는 것을 보고 급 관심에서 오는 궁금함을 못참고 조사를 해 봤습니다.

    개요

    먼저 어원을 알아보면, UC 버클리 RAD Lab의 "Nexus: A Common Substrate for Cluster Computing" 논문에서 출발해서 AMP Lab의 "Mesos: A Platform for Fine-Grained Resource Sharing in the Data Center"라는 논문에 이르러 Mesos라는 용어가 탄생하게 되었다. 참고로 두 논문의 이름은 다르지만 저자 들은 한명 외에는 모두 같은 것으로 보아 랩의 이름이 과거에는 RAD Lab이었다가 현재는 AMP Lab으로 바뀐거 같다.

    데이터 센터 내의 자원을 공유/격리를 관리하는 기술로 개발된 Mesos는 응용 프로그램과 풀링된 서버 간의 추출 레이어로 작동되며, 분산 환경에서 작업 실행을 최적화 할 수 있으며, 1만대 노드에도 대응이 가능하고, ZooKeeper를 이용한 내결함성, 멀티 자원 스케줄링, Web 기반 관리 UI, Java 및 Python, C++ 용 API의 제공과 같은 특징을 가지고 있다. 이것을 이용하여 Hadoop, MPI, Hypertable, Spark 같은 응용 프로그램을 동적 클러스터 환경에서 리소스 공유와 분리를 통해 자원 최적화가 가능하게 된다.
    즉, Mesos는 클러스터에서 사용 가능한 계산 자원을 추적하고 사용자의 요구에 따라 그것을 할당하는 일을 한다고 말할 수 있다.

    좀 이해를 쉽게 도울 수 있는, 기존의 자원 할당 매커니즘과 Mesos의 매커니즘을 잘 보여주는 그림이 있다.


    Mesos의 매커니즘은 클러스터링 환경에서 동적으로 자원을 할당하고 격리해 주는 매커니즘을 가지고 있어 장점으로 활용되고 있다. 이는 응용 프로그램에 맞는 최적의 자원을 할당(공유된 기존 자원을 적절하게 할당)해서 돌아가기 때문에 응용 프로그램 간의 자원 간섭을 Mesos가 막아줘(격리) 독립적으로 응용프로램이 해당 자원을 잘 활용해서 실행을 완료하게 끔 해준다.

    아키텍처

    1. 기본 구조


    Mesos는 각 클러스터 의 노드에서 실행되는 Slave 데몬과 이 Slave 데몬을 관리하는 Master 데몬, 그리고 Slave에서 Task로 실행되는 Mesos 애플리케이션(Frameworks라고도 함)들로 구성된다.

    Master 데몬은 애플리케이션 간의 디테일한 리소스(CPU , 메모리 등)의 공유를 가능하게 한다. 각각의 자원 요청은 다음과 같은 목록으로 구성된다. <slave ID, resource1: amount1, resource2, amount2, …>. Master는 주어진 정책에 따라 각 프레임워크에 대해 얼마나 많은 자원을 할당할지 여부를 결정한다. 여기에서 "정책"은 Fair Sharing, Strict Sharing 등이 제공된다. 다양한 정책 세트에 지원하기 위해 Master 데몬은 플러그인 메커니즘과 같은 새로운 할당 모듈을 쉽게 추가할 수 있는 모듈형 아키텍처를 채용하고 있다.

    Mesos에서 실행되는 프레임워크는 다음 두가지 콤포넌트로 구성되어 있다. 하나는 Scheduler인데, Master에 필요한 리소스 요청을 등록한다. 다른 하나는 Executor로, Slave노드상에서 런칭해 프레임워크의 Task들을 수행한다.(좀 더 자세한 Scheduler/Executor의 내용은 App/Framework 개발 가이드를 참조하라.) Master가 각 프레임워크에 대해 얼마나 많은 자원을 할당 할지를 결정할 때, 각 프레임워크의 Scheduler는 제안된 리소스안에 어느 것을 사용할지를 결정한다. 프레임워크는 제공된 자원을 수령한 경우 프레임워크는 Mesos에 대해 어떤 작업을 수행할 것인지의 실행 정보를 전달한다. Mesos는 프레임워크에서 받은 실행 정보를 바탕으로 Slave 노드에서 Task들을 구동시킨다.

    2. 자원 할당 예



    위 그림은 Mesos의 자원 할당과 관련된 흐름을 나타내고 있다.
    1. Slave1은 자신이 4CPU와 4GB의 메모리를 사용할 수 있는지 Master에게 알린다. Master는 할당 정책을 실행하고 Framework1 대해 모든 사용 가능한 자원을 할당하기로 결정한다.
    2. Master는 Slave1이 4CPU와 4GB의 메모리를 사용할 수 있는지 Framework1에 통지한다.
    3. Framework1의 Scheduler는 Master에게 Slave에서 실행하려는 두 개의 Task 정보 알린다. 첫번째 Task에는 2CPU & 1GB 메모리를, 두번째 Task에는 1CPU & 2GB 메모리를 필요로 하는 정보를 통보한다.
    4. 마지막으로, Master는 Slave1에 Task를 보내고, Framework의 Executor에 적절한 자원을 할당하고, 두 개의 작업을 차례 차례로 런칭한다.(그림의 점선 라인으로 묘사되어 있다) 그 상태에서 나머지 1CPU & 1GB 메모리가 할당되지 않았기 때문에 자원 할당 모듈은 Framework2에 할당 된다.
    자원 배분 프로세스는 작업이 실행 완료되고 자원이 해제되면 다시 배분 작업을 수행한다.

    Mesos가 제공하는 thin 인터페이스는 각 Framework가 개별적으로 확장될 수 있게 되어 있다. 하지만, 여기에 하나의 의문점이 남는다. "각 Framework에는 제약이 존재하지만, Mesos는 개별 Framework의 제약 조건을 몰라도 이러한 Framework의 제약성을 어떻게 만족시킬 수 있을까?" 예를 들어, Framework가 요구하는 데이터가 어디에 저장되어 있는지 Mesos는 모르는데, 어떻게 Framework는 데이터 지역성(locality)을 달성할 수 있을까? 이러한 의문에 대해서 Mesos는 Framework쪽에서 리소스 요청을 거부할 수 있도록 하는것으로 대응하고 있다. Framework는 Mesos에서 배분 받은 자원을 자신의 제약 조건을 충족하지 못하는 경우 거부한다. 특별히, 지연 스케줄링라는 심플한 정책을 가지고 있다. 이것은 Framework측이 입력 데이터가 저장되는 노드를 획득할까지의 제한된 시간동안 대기를 허용하는 것을 통해, 이를 이용하여 Framework측의 데이터 지역성(locality)을 극복할 수 있다.

    활용 사례

    Mesos의 사용자에는 Twitter, Facebook, AirBnb, Conviva, UC Berkeley, UC San Francisco 등이 있는데, 가장 활발하게 사용하고 있는 Twitter에서는 일찍부터 Mesos을 도입해 운영 환경에서 사용하고 있다고 한다.

    그리고 AirBnb에서는 Chronos라는 잡스케쥴 관리 시스템에서 잡 자원 할당에 이 Mesos를 사용하고 있다.

    설치 방법

    1. 설치 매뉴얼
    아래는 CentOS 6.4에서 설치한 순서를 나타낸다.
    $ sudo yum install autoconf make gcc gcc-c++ patch
      python-devel git libtool java-1.7.0-openjdk-devel zlib-devel libcurl-devel
      openssl-devel libsasl2-dev tar ntp cyrus-sasl-devel
    $ wget http://apache.tt.co.kr/maven/maven-3/3.0.5/binaries/
    apache-maven-3.0.5-bin.tar.gz
    $ tar xvfz apache-maven-3.0.5-bin.tar.gz
    
    $ wget http://download.savannah.gnu.org/releases/libunwind/libunwind-1.1.tar.gz
    $ tar xvfz libunwind-1.1.tar.gz
    $ cd libunwind-1.1
    $ ./configure
    $ make
    $ sudo make install
    
    $ wget http://apache.tt.co.kr/zookeeper/zookeeper-3.4.5/zookeeper-3.4.5.tar.gz
    $ tar xvfz zookeeper-3.4.5.tar.gz
    $ cd zookeeper-3.4.5
    $ cp zoo_sample.cfg zoo.cfg
    $ vi zoo.cfg
    $ dataDir 수정해줌.
    $ bin/zkServer.sh start
    
    $ vi ~/.bash_profile
    M2_HOME=/home/vagrant/server/apache-maven-3.0.5;export M2_HOME
    PATH 추가
    
    JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.45.x86_64;export JAVA_HOME
    PATH 추가
    source ~/.bash_profile
    
    git clone https://github.com/apache/mesos.git
    cd mesos
    
    ./bootstrap
    ./configure --prefix=/home/vagrant/server/mesos --with-webui 
     --with-included-zookeeper --disable-perftools
    make
    sudo make install
    
    ./mesos-master  --zk=zk://localhost:2181/mesos &
    ./mesos-slave --master=zk://localhost:2181/mesos &
    

    2. 대시보드 화면
    Mesos 관련 대시보드 화면은 아래와 같다. 이 화면을 통해 자원의 이용환경을 볼 수 있다.