클라우드 컴퓨팅 교과목의 개인 프로젝트 과제를 수행하며 개인적으로 정리한 내용임을 미리 알립니다.
AWS에서 EC2 인스턴스를 생성하고 삭제하는 등 클라우드 환경에서 컴퓨팅 자원을 다루는 방법을 간단히 실습했었다. 그런데 만약 당신이 서버 관리자이고 EC2 인스턴스를 한 번에 100개를 생성해달라는 요청이 들어온다면 어떻게 할 것인가? 이를 AWS 콘솔을 통해 일일이 버튼을 클릭하고 있자하니, 관리자에게 그것 말고도 더 중요한 일이 많을 것이다. AWS에서는 AWS 서비스를 프로그래밍 방식으로 쉽게 사용할 수 있도록 AWS SDK(Services Software Development Kit)를 제공한다. 이를 활용하면 반복문으로 단 몇 초만에 인스턴스 대량 생성이 가능해 질 것이다. 뿐만 아니라, 프로그래머는 입맛에 맞게 aws 서비스를 자신이 잘 아는 프로그래밍 언어로 자신만의 애플리케이션을 만들어 볼 수도 있을 것이다.
이에 이전 과정에서 해보았던 HTCondor 클러스터를 AWS에 올리고, 동적으로 클러스터를 관리하는 간단한 프로그램을 만들어 보고자 한다. AWS는 Java, Python 등 몇 가지의 프로그래밍 언어의 SDK를 제공하며, 그 중 JavaScript SDK를 사용할 것이다. 아래는 프로그램 제작 시 활용한 AWS SDK for JavaScript(v3) 공식문서이며, 이 곳에 웬만한 서비스에 대한 api 활용법이 설명되어 있으니 참고하도록 한다.
AWS 공식문서 - AWS SDK for JavaScript v3
우선 웹 기반의 프로그램을 만들어야 하므로, JavaScript 런타임 환경인 Node.js로 서버를 로컬 환경에서 실행한다. Express.js 프레임워크를 사용하여 REST API를 구축할 것이며, 전체적인 애플리케이션 구조는 MVC 패턴을 따른다. (참고로 view에는 ejs를 활용한다.)
├───node_modules
├───public
│ ├───css
│ └───js
├───src
│ ├───config # aws 관련 설정 파일
│ ├───controller # 컨트롤러
│ ├───routes # 라우터
│ ├───service # 서비스
│ │ └───ec2
│ └───utils # 기타 함수
└───views
└───ec2 # 뷰
API 구축
Postman에 api를 정리하고, 테스트에 활용했다.
환경 변수
AWS SDK를 사용하려면 AWS 리전정보, 액세스 키 정보가 필수적이다. 이를 위해.env
를 채워넣도록 한다.
# .env.example
AWS_REGION= # AWS 리전
AWS_ACCESS_KEY_ID= # AWS 액세스 키
AWS_SECRET_ACCESS_KEY= # AWS 비밀 액세스 키
# 부가 정보
PORT= # 서버 실행 포트
HTCONDOR_TAG_KEY=Name # HTCondor 클러스터 태그 키
HTCONDOR_TAG_PREFIX=HTCondor_Data # HTCondor 클러스터 태그명 접두사
HTCondor_SG_ID=sg-xxxx # HTCondor 클러스터가 속한 보안그룹
전체적인 아키텍쳐는 그림을 참고한다. 참고로 데이터 노드는 N개(최대 10개) 생성 가능하다.
condor_status
명령의 결과가 제대로 나오는지 확인한다. 데이터 노드가 클러스터에 잡힌 것을 확인할 수 있다.인스턴스를 생성하고, 조회하는 등의 작업을 수행하려면 중간자 역할을 수행하는 클라이언트가 필요하다. 따라서 관련 클라이언트 객체를 생성하는 코드를 작성한다. 이후 동일한 객체가 인스턴스와 관련한 일련의 작업을 수행할 것이다.
aws-config.js
// 예시
// CONFIG: EC2 클라이언트 객체
export const ec2ClientConfig = {
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
};
aws-client.js
// 예시
export const ec2Client = new EC2Client(ec2ClientConfig);
실제 작업을 수행하는 서비스 단의 코드를 작성하도록 한다. 아래는 현재 생성된 인스턴스 목록을 조회하는 함수이다.
/**
* SERVICE: 인스턴스 목록을 조회하는 함수
* @returns {Array} instances - 배열 형태의 인스턴스 정보
*/
import { DescribeInstancesCommand } from "@aws-sdk/client-ec2";
import { ec2Client } from "../aws-client.js";
const listInstances = async () => {
const command = new DescribeInstancesCommand({});
try {
const { Reservations } = await ec2Client.send(command);
// 인스턴스 정보를 담을 배열
const instances = [];
Reservations.forEach((reservation) => {
reservation.Instances.forEach((instance) => {
// 필요한 정보만 추출하여 객체 생성
const instanceInfo = {
Name:
instance.Tags?.find((tag) => tag.Key === "Name")?.Value ||
"No Name",
InstanceId: instance.InstanceId || "N/A",
...
};
// 인스턴스 정보를 배열에 추가
instances.push(instanceInfo);
});
});
// 추출한 인스턴스 정보 반환
return instances;
} catch (caught) {
}
};
export default listInstances;
컨트롤러를 작성한 후, 라우터에 등록한다. 응답값을 바탕으로 인스턴스 목록을 HTML로 보여주는 instances.ejs
도 작성한다.(생략)
// GET /instances
export const listInstances = async (req, res) => {
try {
const existingInstances = await ec2Service.listInstances();
return res.render("ec2/instances", { instances: existingInstances });
} catch (error) {
return res.status(500).json({
status: "error",
message: error.message || "Failed to list instances",
});
}
};
import { Router } from "express";
import { listInstances } from "../controller/ec2.controller.js";
const router = Router();
// GET instances
router.get("/instances", listInstances);
export default router;
이와 같이 다른 서비스들에 대해서도 서비스, 컨트롤러, 뷰를 구성한다. 서비스 구성 함수는 다음과 같다.
└───service
└───ec2
├───list-instances.service.js # list instance
├───list-availability-zones.service.js # availability zones
├───list-availability-regions.service.js # available regions
├───control-instances.service.js # start/stop/reboot instance
├───create-instances.service.js # create instance
├───list-images.service.js # list images
├───get-condor-status.service.js # get result of condor_status command
├───get-condor-queue-status.service.js # get result of condor_q command
├───get-htcondor-metrics.service.js # get CloudWatch result of cluster
├───create-asg.service.js # create autoscaling group
└───submit-condor-job.service.js # submit condor job to cluster
인스턴스를 생성/시작/중지/재시작할 수 있게 화면을 구성하였다.
클러스터의 컨트롤 노드의 공인 IP를 입력하여 클러스터 상태를 조회할 수 있게 했다. 웹 워커를 사용하여 condor_status
, condor_q
, condor_submit
명령어를 수행하고 결과를 반환하게 구현하였고, 대시보드를 통해 결과를 확인할 수 있게 하였다.
start
시 condor_status
확인stop
시 condor_status
확인 (생략)create
시 condor_status
확인AWS SDK for JavaScript v3 - Auto Scaling
ASG(Auto Scaling Group) 생성
시작 템플릿(보안그룹: HTCondor 클러스터가 존재하는 보안그룹/리소스 태그: 키는 Name, 값은 HTCondor_Data로 설정)은 이미 생성이 되어있다는 전제 하에 ASG를 생성할 수 있게 구현하였다.
AWS SDK for JavaScript v3 - 오토스케일링그룹 생성
Automatic Scaling Policy API 생성
오토스케일링 그룹의 동적 크기 조정 정책을 생성할 수 있게 구현하였다. (이는 이후 CloudWatch의 경보 기능과 연동할 때 사용된다.)
AWS SDK for JavaScript v3 - 동적 크기 조정 정책 수정
동적 크기 조정 정책
수요 변화에 맞춰 오토 스케일링의 규모를 조정하며, 반응형 동적 크기 조정 정책을 사용하면 특정 CloudWatch 지표를 추적하고 CloudWatch 경보 임계값에 도달할 때 조치를 취할 수 있다.
AWS SDK for JavaScript v3 - CloudWatch
HTCondor 작업 제출/큐 상태 확인
컨트롤 노드의 IP를 입력한 후, 파일(예시에선, CPU에 부하를 줄 만한 작업이 작성된 쉘 스크립트)을 업로드한 후, 제출 버튼을 클릭하면 작업 큐에 새로운 작업이 들어간 것을 확인할 수 있게 구현하였다.
#!/bin/bash
# CPU 부하를 위해 4개의 무한 루프 생성
for i in {1..4}; do
while :; do :; done &
done
# 300초 동안 유지 후 종료
sleep 300
killall -9 bash
CPU 메트릭 확인
데이터 노드들의 CPU 사용량을 조회하여 그래프를 통해 구현하였다. (Chart.js 활용)
AWS SDK for JavaScript v3 - 메트릭 조회
AWS 콘솔에서 확인
메일 수신 확인
생성해 두었던 SNS에 설정한 이메일로 알람이 수신된 것을 확인할 수 있다.
새로운 노드 생성 확인
CPU 사용률이 임계점을 넘김으로써 기존에 1개였던 데이터 노드가 2개로 늘어난 것을 확인(동적 크기 조정 정책을 1로 설정했음)할 수 있다. (사진 생략)
Your requested instance type (<instance type>) is not supported in your requested Availability Zone (<instance Availability Zone>)...
오토스케일링 그룹 생성 시, ‘유효하지 않은 가용 영역’이라는 오류가 나며 생성을 실패한 경우가 발생했다. 이는 인스턴스 유형에 따라 가용 영역이 제한되는 경우가 있었기 때문이고, 인스턴스에 맞는 가용영역을 선택하여 해결하였다.
Amazon EC2 Auto Scaling 문제 해결: EC2 인스턴스 시작 실패 - Amazon EC2 Auto Scaling