Jcloud

2013. 10. 23. 21:10Cloud/멀티클라우드

Jcloud

 

1) 소개

jcloud는 여러 클라우드 서비스간에 상호 호환할 수 있는 서비스를 위한 공통된 API개발을 목표로 시작되었으며, Public Cloud의 관리 기능들을 추상화 해놓은 라이브러리이다. Jcloud는 클라우드의 주요 기능 등, 예를 들어 S3, EBS등을 JAVA API로 이루어져 있으며. 또한, AWS, Azure, Ecualytus, Terramark등 주요 클라우드 서비스들을 호출할 수 있고, API가 추상화 되어 있어서 비슷한 기능은 Service Provider만 바꾸면 되는 구조이다. 즉 Azure의 Blobstorage나 Amazon의 S3는 그 구조와 용도가 유사한데, Jcloud에서는 이것을 하나의 인터페이스로 묶어서 클라우드 서비스 사업자의 의존성 없이 프로그래밍을 가능하게 해준다.

2) 특징

Jcloud는 개발자나 ISV가 클라우드를 서비스를 이용하기 위해서 사용해야 하는 프로그래밍 인터페이스를 추상화하여 공통된 API를 제공하고 있으며 다음과 같은 특징을 가진다.

    - REST-like APIS or Webserivces를 거치지 않고 실행 ( 맵핑 역할을 담당 )

    - Amazon EC2,S3, GoGrid, Ninefold, Vloud, OpneStack, Azure 등 30여개 서비스 지원

    - Runtime Portability

Jcloud는 다음과 같은 기능들을 제공한다.

- BlobStore API : Microsoft Azure의 Blob 서비스 및 아마존 S3등의 스토리지 서비스 관리 기능을 제공한다. 또한 동기 및 비동기식 API들 뿐만 아니라, 데이터에 대한 엑세스 기능을 제공한다.

- Compute API : Amazon의 Public Cloud인 EC2나 Private Cloud인 VMware의 Vcloud 를 매핑시키는 기능을 제공한다. 또한, Jcloud Compute API는 Cloud Service의 여러 인스턴스에 있어서 다수의 연결이 아닌 하나의 연결로 다수 개의 인스턴스를 엑세스 할 수 있다. 그리고 jcloud는 기본 클라우드 API에 상관 없이 인스턴스를 실행할 수 있다. 또한, 서비스가 지원하는 임의의 암호나 키에 대해서 서비스를 시작시킬 때 인스턴스에 SSH key를 함께 이동 할 수 있다.

 

3) 구조도

Jcloud는 드라이버 기반으로 각 클라우드 프로바이더에 접근할 수 있는 프레임 워크를 제공하며 각 서비스 별로 나누어져 있는 드라이버를 통해 이용할 수 있는 공통된 API집합을 제공한다.

< Jcloud Arcjitecture>

4) 사용방법

가) Jcloud 설치

Jcloud를 사용하기 위해서는 Apache Maven이 깔려 있어야 한다. Apache Maven 설치는 다음과 같다.

<1> Maven 설치

Maven 다운로드 및 설치

Maven의 공식 사이트인 http://maven.apache.org에서 최신 버전의 maven을 다운

다운받은 파일을 설치, 설치 방법은 다음과 같다.

1. 설치하려는 위치에 압축을 푼다.

2. 설치된 경로를 환경변수에 등록한다.

 

[출처] Maven 설치 및 이클립스 플러그인 설치|작성자 깡통

[출처] Maven 설치 및 이클립스 플러그인 설치|작성자 깡통

 

 

 

 

설치와 환경변수 등록이 제대로 되었는지 확인하기 위해, Command창에서 mvn -version을 입력한다.

아래와 같이 자바와 Maven의 버전정보가 나오면 설치가 정상적으로 된 것이다.

[출처] Maven 설치 및 이클립스 플러그인 설치|작성자 깡통

 

로컬 저장소의 디렉토리를 변경한다.[출처] Maven 설치 및 이클립스 플러그인 설치|작성자 깡통

 

설치된 Maven의 경로로 이동하여 conf폴더 아래 settings.xml 파일을 열고, 아래 localRepository 경로를 새롭게 지정합니다.

(예: D:\eclipse\apache-maven-3.0.2\conf\settings.xml)

[출처] Maven 설치 및 이클립스 플러그인 설치|작성자 깡통

 

 

 

 

<2> 이클립스에서의 사용을 위한 Maven 플러그인 설치

이클립스 상단메뉴의 Help > Install New Softwares... 를 선택하여 아래 설치 다이얼로그를 열어준다.

기존 설치주소는 http://m2eclipse.sonatype.org/sites/m2e

Add 버튼을 눌러 아래와 같이 m2eclipse와 변경된 주소를 입력하고 설치 과정을 진행 한다.

설치가 완료되면 이클립스를 재시작 하라는 메시지가 나오고, 깔끔하게 재 시작 해주면 설치 과정이 완료 된다.

[출처] Maven 설치 및 이클립스 플러그인 설치|작성자 깡통

 

 

<2> Jcloud 를 Apache Maven Project 내에서 사용하기 위한 방법

Maven Project의 Build.xml 파일에 아래와 같은 Dependency를 넣어주고 Maven install을 시키면 된다.

[출처] Maven 설치 및 이클립스 플러그인 설치|작성자 깡통

[출처] Maven 설치 및 이클립스 플러그인 설치|작성자 깡통

 

 

 

 

<3> Jcloud 개발용 Eclipse 설정을 하기 위한 법은 다음과 같다.

eclipse 설정에 M2_REPO 클래스패스 값 설정하고

- Eclipse Preferences → Java → Build Path → Classpath Variables

- New

- Name: M2_REPO

- Path: /path/to/.m2/repository

 

프로젝트 import 하면 된다..

  • File, import, General, Existing projects into workspace
    • Choose the directory your git clone created
    • Choose all modules

테스트를 위해 TestNG 플러그인 설치까지 해주면 된다.

  • Help, Install New Software
    • Add
      • Name: testng
      • Location: http://beust.com/eclipse

 

나) Jcloud API사용

Jcloud API는 Compute API와 Blobsotre API를 제공한다. 본 paper에서는 Compute API에 대해 사용하는 방법만을 기술 한다.

Jcloud Compute API를 사용하여 Cloud Service의 하나 혹은 다수의 인스턴스OS를 관리하기 위해서는 다음과 같은 과정을 거친다.

1) 해당 Cloud Service와의 Driver 연결

2) Cloud Service의 인스턴스 OS와의 연결

① 사용자가 이용할 수 있는 인스턴스에 관한 정보 리스트를 확인

② Jcloud에서 Cloud Service를 사용하기 위해 SSH 클라이언트 인증을 실시

③ 인스턴스 OS 버전을 템플릿에 매칭 시킴

④ 인스턴스 OS 부트스트랩을 위한 부팅 스크립트 작성 및 환경 설정

 

<Jcloud Compute API>

Jcloud API는 클라우드 프로바이더와 통신할 수 있는 프레임워크와 드라이버를 제공한다. 이러한 드라이버를 통하여 Jcloud를 사용할 수 있다.

JCloud Comput API는 Cloud의 인스턴스를 관리하는 API이다. 별도의 연결 없이 클라우드에서 인스턴스 리소스를 관리 할 수있다. 또한 운영 시스템의 CPU 개수, API의 매개변수와 일치하는 구성을 검색 할 수 있는 템플릿 기능이 있다. 마지막으로 인스턴스의 부트 스트랩 프로세스를 실행하는 스크립트를 포함하고 있다.

템플릿이란 각각 다른 클라우드 환경을 구축할 때 사용자가 요구하는 인스턴스의 환경 및 요구사항을 캡슐화 하는 방법이다. 템플릿은 다음과 같은 요소로 구성된다.

Image

인스턴스를 부팅하는 운영체제

Hardware

CPU, 메모리, 디스크 등

Location

인스턴스를 실행하는 데이터센터

Option

부팅할 때 실행 하는 포트나 선택적 매개 변수를 포함한 스크립트를 정의

 

JCloud Compute API에 대한 자세한 사항은 http://deltacloud.org/api.html에서 확인 가능하다.

Cloud Service프로바이더에 연결하기 위해서는 다음과 같이 작성될 수 있다.

ComputeServiceContext context =

new ComputeServiceContextFactory().createContext("terremark", user, password);

 

ComputeService computeService = context.getComputeService();

 

클라우드 서비스가 제공하는 이미지 리스트를 받기 위해서 다음과 같은 형태로 작성될 수 있다.

for (ComputeMetadata node : client.listNodes()) {

node.getId(); // how does jclouds address this in a global scope

node.getProviderId(); // how does the provider api address this in a specific scope

node.getName(); // if the node is named, what is it?

node.getLocation(); // where in the world is the node

}

 

사용자가 이용할 수 있는 인스턴스에 관한 정보 리스트를 받기 위해서는 다음과 같은 형태로 작성될 수 있다.

NodeMetadata metadata = client.getNodeMetadata(node);
																	

 

metadata.getId();

metadata.getProviderId();

metadata.getLocation();

metadata.getName();

metadata.getGroup();// if part of a nodeset, this identifies which one.

metadata.getHardware();

metadata.getImageId(); // if the node was created by an image, what is its id?

metadata.getOperatingSystem();

metadata.getState();

metadata.getPrivateAddresses();

metadata.getPublicAddresses();

metadata.getCredentials();// only available after createNodesInGroup, identifies login user/credential

 

Jcloud에서 모든 인스턴스에 대한 가능한 Location (데이터 센터)를 알 수 있으며 지정할 수 있다..

Set<? extends Location> listAssignableLocations();

 

Jcloud에서는 해당 인스턴스에 대한 Hardware 상태를 확인 할 수 있습니다. 다음과 같은 명령어를 통하여 가상 CPU갯수, 메모리 및 디스크 등의 설정을 알 수 있다. 각 인스턴스의 CPU사용량 뿐만 아니라 전체 시스템에 있어서 해당 인스턴스가 CLOUD내에서 얼마만큼 쓰이는지 알 수 있다.

Set<? extends Hardware> listHardwareProfiles();

 

Jcloud에서는 Image 명령을 통하여 인스턴스의 운영체제, 메타데이터를 정의할 수 있다. 그리고 cloud마다 특정 지역에 바인딩을 하거나, 구성요소는 다르게 정의 할 수 있다. 사용자는 명시적으로 이미지를 선택하는 것 외에도 TempletBuilder와 같은 API를 통하여 운영체제와 같은 이미지 요구 사항을 매칭시켜야 한다

Set<? extends Image> listImages();

.

 

 

그룹과 인스턴스를 만들기 위해서는 JCloud API는 사용자가 지정한 그룹에 따라 그룹으로 인스턴스를 생성 한다. 또한 이 그룹을 사용하면 Cloud의 구현 세부 사항과 관계업    이 하나 또는 여러 인스턴스에서 작동 시킬 수 있다. CreateNodeInGroup API는 포트22에서 실행중인 상태로 시작할 수 있는 인스턴스를 모두 반환한다. 이러한 그룹내에서 인스턴스가 리소스가 필요한 경우에는 이 그룹 영역 내에서 이 리소스는 재사용되거나 생성 된다.

NodeSet nodes = client.createNodesInGroup(group, 2, template);

 

반환 되는 세트는 인스턴스에 자격증명을 포함하는 SSH를 사용 할 수 있다. 이자격 증명의 Credential 부분이 비밀번호 및 Private key이다. 이 때 사용자는 잘못된 사용자로 로그인을 시도하지 않도록 자격증명 부분에 indentify에 개체를 확인해주어야 한다.

if (node.getCredentials().credential.startsWith("-----BEGIN RSA PRIVATE KEY-----"))

// it is a private key, not a password.

 

Jcloud내에서 개별적으로 인스턴스에 대한 명령은 특정 인스턴스의 ID에 대해 실행 된다. 인스턴스에 대한 메타 데이터를 가져오려하는 경우에는 GetNodeMetadata()명령을 통하여 알 수 있다.

NodeMetadata metadata = client.getNodeMetadata(savedId);

 

Jcloud에서 Cloud Service를 사용하기 위해서는 SSH키가 필요한데 이를 구성하는 방법은 다음과 같다.

Properties overrides = new Properties();

Set<Module> wiring = ImmutableSet.of(new JschSshClientModule(), new Log4JLoggingModule(), new EnterpriseConfigurationModule());

ComputeServiceContext context = new ComputeServiceContextFactory().createContext("terremark", user, password,

wiring, overrides);

이것에 대한 더 필요한 정보는 JcloudsAPI 위키 페이지에서 확인 가능하다.

 

 

 

Jcloud에서 Cloud Service의 인스턴스를 생성하기 위해서는 node.getCredentials () 에서 로그인 설정을 인증해야 한다.

client = context.utils().sshForNode().apply(node);

 

인스턴스에 대한 자격 인증이 되어 있지 않는 경우 직접 인증하는 방법은 다음과 같다.

client = context.utils().sshForNode().apply(NodeMetadataBuilder.fromNodeMetadata(node).credentials(new Credentials("adrian",sshKeyInString)).build());

 

SSH 클라이언트 설정이 끝나면 파일을 넣을 수 있다.

client.put("/path/to/file", Payloads.newFilePayload(contents));

이에 대한 자세한 소스는 다음과 같다.

SshClient ssh = context.utils().sshForNode().apply(node);

try {

ssh.connect();

ssh.put("/path/to/file",

Payloads.newPayload(fileOrInputStreamOrBytesOrString));

} finally {

if (ssh != null)

ssh.disconnect();

}

 

인스턴스 OS에 대한 부트스트랩 방법을 통합할 수 있는 방법은 다음과 같다.

if (OperatingSystemPredicates.supportsApt().apply(os))

return RunScriptData.APT_RUN_SCRIPT;

else if (OperatingSystemPredicates.supportsYum().apply(os))

return RunScriptData.YUM_RUN_SCRIPT

else if (OperatingSystemPredicates.supportsZypper().apply(os))

return RunScriptData.ZYPPER_RUN_SCRIPT;

else

throw new IllegalArgumentException("don't know how to handle" + os.toString());

 

 

포트22에 공개 IP에 대한 엑세스 권한은 직접 다르게 지정하지 않는 한 명시적으로 구성되어 있다. Hosting.com 및 Rackspace 같은 Cloud는 모든 서비스에서 기본적으로 사용할 수 있지만 Terremark나 EC2 같은 Cloud는 적어도 1포트를 추가하여야 한다. 방법은 다음과 같다.

import static org.jclouds.compute.options.TemplateOptions.Builder.inboundPorts;

...

Template template = client.templateBuilder().options(inboundPorts(22, 8080)).build();

// start 2 nodes

nodes = client.createNodesInGroup(group, 2, template);

 

운영체제 버전을 매칭 시키는 방법은 다음과 같다.

template = client.templateBuilder().hardwareId(InstanceType.M1_SMALL)

.osVersionMatches("10.04").imageDescriptionMatches("ubuntu-images").osFamily(OsFamily.UBUNTU).build();

 

RSA SSH 공개키를 인증하는 방법에 있어서 Jcloud-Compute API는 인스턴스 또는 인스턴스 집합에 공개 키를 허가하는것에 대해 지원한다. 이렇게 하면 인스턴스 집합 전체에 걸쳐 단일 자격 증명을 사용할 수 있다. 이 인스턴스의 개인키를 변경하지는 않지만 개인키를 변경하는 방법에 있어서는 installprivatekey 옵션에 있다.

이 기능을 사용하기 위해서는 RSA 공개키 생성하여 로드해야 한다. 이값에 옵션 authorizePublickey를 설정한다. 제대로 이작업이 완료한 경우에는 키는 SSH-RSA로 시작된다.

import static org.jclouds.compute.options.TemplateOptions.Builder.installPrivateKey;

...

// start 10 nodes

nodes = client.createNodesInGroup(group, 10, installPrivateKey(Files.toString(new File("/home/me/.ssh/id_rsa"));

 

이후 부팅 스크립트를 추가하는 방법에서는 Jcloud-compute-api는 사출과 하나의 파일 루트 후 부팅등의 실행을 지원한다. 정확한 구현 방법은 대상 Cloud가 제공하는 기능에 따라 다르다. 이 기능을 사용하기 위해서는 Jcloud 정보 보호 정책 또는 문자열 값으로 스크립트를 생성하거나 로드해야한다. 또한 이 값을 RunScript옵션으로 설정 해야 한다

import static org.jclouds.compute.options.TemplateOptions.Builder.runScript;

...

// start 10 nodes

nodes = client.createNodesInGroup(group, 10, runScript(Files.toString(new File("runscript.sh")));

 

인스턴스에서 Root사용자가 아닌 추가 사용자를 추가하려면 UserAdd.Builder를 이용한다.

UserAdd.Builder userBuilder = UserAdd.builder();

userBuilder.login("john.doe");

userBuilder.authorizeRSAPublicKey("john's public key");

Statement userBuilderStatement = userBuilder.build();

 

Jcloud API는 OS별로 다른 스크립트에 의존하지 않고 구축할 수 있다. 개발자들은 OS의 자세한 내용을 처리할 필요 없이 프로그래밍을 할 수있다. 실행되는 명령을 확인하려면 예를 들어 다음과 같은 코드를 통해 확인 할 수 있다.

Statement.render (osFamily: UNIX)

 

다음 예제는 위 Jcloud Compute API의 예제로서 Create 명령을 줄 경우 EC2 의 KeyPair과 Security Group 을 생성하고 인스턴스를 실행하며 Desory명령을 줄 경우 keypair과 Security Group을 삭제하고 인스턴스를 삭제하는 예제이다

package org.jclouds.examples.ec2.spot;

 

import java.io.File;

import java.io.IOException;

import java.util.Set;

import java.util.concurrent.TimeUnit;

 

import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;

import org.jclouds.compute.ComputeService;

import org.jclouds.compute.ComputeServiceContextFactory;

import org.jclouds.compute.RunNodesException;

import org.jclouds.compute.domain.NodeMetadata;

import org.jclouds.compute.domain.Template;

import org.jclouds.compute.predicates.NodePredicates;

import org.jclouds.net.IPSocket;

import org.jclouds.predicates.InetSocketAddressConnect;

import org.jclouds.predicates.RetryablePredicate;

 

import com.google.common.base.Charsets;

import com.google.common.collect.Iterables;

import com.google.common.io.Files;

 

/**

* This the Main class of an Application that demonstrates the use of the Amazon EC2 extensions by

* creating a small spot server.

*

* Usage is: java MainApp accesskeyid secretkey group command where command in create destroy

*

* @author Adrian Cole

*/

public class MainApp {

 

public static int PARAMETERS = 4;

public static String INVALID_SYNTAX = "Invalid number of parameters. Syntax is: accesskeyid secretkey group command\nwhere command in create destroy";

 

public static void main(String[] args) {

 

if (args.length < PARAMETERS)

throw new IllegalArgumentException(INVALID_SYNTAX);

 

// Args

String accesskeyid = args[0];

String secretkey = args[1];

String group = args[2];

String command = args[3];

 

// Init

ComputeService compute = new ComputeServiceContextFactory().createContext("aws-ec2", accesskeyid, secretkey)

.getComputeService();

 

// wait up to 60 seconds for ssh to be accessible

RetryablePredicate<IPSocket> socketTester = new RetryablePredicate<IPSocket>(new InetSocketAddressConnect(), 60,

1, 1, TimeUnit.SECONDS);

try {

if (command.equals("create")) {

 

Template template = compute.templateBuilder().build();

 

template.getOptions().as(AWSEC2TemplateOptions.class)

// set the price as 3 cents/hr

.spotPrice(0.03f)

// authorize my ssh key

.authorizePublicKey(

Files.toString(new File(System.getProperty("user.home") + "/.ssh/id_rsa.pub"),

Charsets.UTF_8));

 

System.out.printf(">> running one spot node type(%s) with ami(%s) in group(%s)%n", template.getHardware()

.getProviderId(), template.getImage().getId(), group);

// run only a single node

NodeMetadata node = Iterables.getOnlyElement(compute.createNodesInGroup(group, 1, template));

 

System.out.printf("<< running node(%s)%n", node.getId());

IPSocket socket = new IPSocket(Iterables.get(node.getPublicAddresses(), 0), node.getLoginPort());

if (socketTester.apply(socket)) {

System.out.printf("<< socket ready [%s] node(%s)%n", socket, node.getId());

System.out.printf("ssh to node with the following command:%n ssh %s@%s%n",

node.getCredentials().identity, socket.getAddress());

System.exit(0);

} else {

System.out.printf("<< socket not ready [%s] node(%s)%n", socket, node.getId());

}

} else if (command.equals("destroy")) {

System.out.printf(">> destroying nodes in group(%s)%n", group);

Set<? extends NodeMetadata> destroyed = compute.destroyNodesMatching(NodePredicates.inGroup(group));

System.out.printf("<< destroyed(%d)%n", destroyed.size());

System.exit(0);

} else {

System.err.println(INVALID_SYNTAX);

System.exit(1);

}

} catch (RunNodesException e) {

System.err.println(e.getMessage());

for (NodeMetadata node : e.getNodeErrors().keySet())

compute.destroyNode(node.getId());

System.exit(1);

} catch (IOException e) {

System.err.println(e.getMessage());

System.exit(1);

} finally {

compute.getContext().close();

}

 

}

 

}