반복적으로 들어오는 이벤트의 환경을 세팅하는게 번거롭기에 자동으로 인스턴스와 이벤트를 관리해주는 기능 개발
어드민에서 서버스팩선택해서 서버생성 버튼 클릭시 인스턴스 생성 및 user-data 파일실행하여 웹서버 실행
초기 세팅
- env
이렇게 import 해주면 프로젝트 루트 경로의 .env파일을 읽어와서 process.env를 통하여 사용할 수 있게된다. 추가적으로 모듈을 다른 모듈에서 사용하려고하면 사용하려는 모듈을 import해줘야하는데, isGlobal옵션으로 다른 모듈에서는 Import없이 사용할 수 있게된다.app.module.ts import { ConfigModule } from '@nestjs/config' @Module({ import: [ConfigModule.forRoot({ isGlobal: true})] })
- nestJS에서 env를 사용할 수 있게 nestjs/config 라이브러리를 설치하여 app.module에 import해준다.
- DB - mongo
이렇게 mongoose를 세팅해주면, 우리가 생성하는 모듈별로 collection을 만들어준다.app.module.ts @Module({ import: [ ConfigModule.forRoot({ isGlobal: true}), MongooseModule.forRootAsync({ imports:[ConfigModule] // 위에있는 config모듈을 가져와서 useFactory: async (config:ConfigService)=>({ uri: conf.get<string>('MONGODB_URL'), user: conf.get<string>('MONGODB_USER'), pass: conf.get<string>('MONGODB_pass'), }), inject: [ConfigService], }) ] })
- nest에 mongoDB를 사용할 려고 하니 nestjs 공홈에서 mongoose를 활용하라 추천해준다. 추천해주는대로 mongoose 와 nestjs/mongoose를 설치해준다. db정보는 .env에 숨겨두었으니 env에 있는 정보로 db연결을 해줘야한다.
- AWS
app.module.ts @Module({ import: [ ConfigModule.forRoot({ isGlobal: true}), MongooseModule.forRootAsync({ imports:[ConfigModule] // 위에있는 config모듈을 가져와서 useFactory: async (config:ConfigService)=>({ uri: conf.get<string>('MONGODB_URL'), user: conf.get<string>('MONGODB_USER'), pass: conf.get<string>('MONGODB_pass'), }), inject: [ConfigService], }), AwsSdkModule.forRoorAsync({ defaultServiceOptions: { useFactory: (cs: ConfigService) => { return { region: 'ap-northeast-2', credentials: { accessKeyId: cs.get<string>('AWS_ACCESS_KEY'), secretAccessKey: cs.get<string>('AWS_SEC_ACCESS_KEY'), }, }; }, inject: [ConfigService], }, }) ] })
이렇게 기본적인 준비가 끝났다. 이제 인스턴스 생성부터 시작하자.- 자동으로 인스턴스를 띄워서
- 내부의 서버를 띄우고
- 상황에 따라 이벤트를 띄우는 것이다.
instance_init.sh Content-Type: multipart/mixed; boundary="//" MIME-Version: 1.0 --// Content-Type: text/cloud-config; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="cloud-config.txt" #cloud-config cloud_final_modules: - [scripts-user, always] --// Content-Type: text/x-shellscript; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="userdata.txt" -------------------------- 여기까지가 인스턴스 start될 때마다 실행되게하는 스크립트 #!/bin/bash /home/ec2-user/.nvm/versions/node/v20.9.0/bin/npm install -g pm2 # 인스턴스 실행시 환경변수가 제대로 적용되지않았을 수 있어서 직접 경로를 입력하여 사용한다. export PATH=$PATH:/home/ec2-user/.nvm/versions/node/v20.9.0/bin sudo env PATH=$PATH:/usr/bin /home/ec2-user/.nvm/versions/node/v20.9.0/bin/pm2 startup systemd -u ec2-user --hp /home/ec2-user instanceId=$(cat /var/lib/cloud/data/instance-id) # ec2 instance는 본인의 인스턴스 아이디를 가지고 있다. 이를 가져와서 변수에 담는다. curl -X POST -H "Content-Type:application/json" api-url-넣으시고 --data '{"instanceId":"'"${instanceId}"'"}' # 필자는 인스턴스가 실행되면, pm2로 서버를 띄우고, 실행된 인스턴스id를 api로 받았다.
이렇게 실행 스크립트를 작성했으니 인스턴스를 만들어보자. instance모듈을 만든다.
src/instance/instance.module.ts
@Module({
imports: [
MongooseModule.forFeature([]),
AwsSdkModule.forFeatures([EC2])
],
controllers: [],
providers: []
})
아까 mongoose가 자동으로 만들어주는 collection을 사용하기위해서 MongooseModule을 사용하는데, 이때 스키마를 먼저 선언해줘야한다.
src/instance/schema/instance.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoosePaginate from 'mongoose-paginate-v2';
import * as mongoose from 'mongoose';
@Schema({
timestamps: { createdAt: 'created', updatedAt: 'updated' },
})
export class Instance {
@Prop({ required: false, type: mongoose.Schema.Types.String })
ip: string;
@Prop({ required: true, type: mongoose.Schema.Types.String })
spec: string;
@Prop({ required: false, type: mongoose.Schema.Types.String })
status: string;
@Prop({ required: false, type: mongoose.Schema.Types.String })
instanceId: string;
}
export type InstanceDocument = mongoose.HydratedDocument<Instance>;
const schema = SchemaFactory.createForClass(Instance);
schema.plugin(mongoosePaginate);
export const InstanceSchema = schema;
이렇게 데이터 구조를 짜고
src/instance/instance.module.ts
import { Instance, InstanceSchema } from './schema/instance.schema';
@Module({
imports: [
MongooseModule.forFeature([
{ name: Instance.name, schema: InstanceSchema } //추가해준다.
]),
AwsSdkModule.forFeatures([EC2])
],
controllers: [],
providers: []
})
이제서야 준비가 진짜 끝났다.
src/instance/instance.service.ts
@Injectable()
export class InstanceService{
constructor(
@InjectModel(Instance.name)
private InstanceModel: PaginateModel,
@InjectAwsService(EC2) private readonly ec2: EC2,
){}
async create(createInstanceDto: CreateInstanceDto){
const startScript = await readFileSync('src/instance_init.sh', {encoding: 'base64'});
// 먼저 준비해둔 실행 스크립트 파일을 base64형태로 가져온다.
const createEC2Props: EC2.Types.RunInstancesRequest = {
ImageId: 미리-세팅해둔-ec2이미지,
InstanceType: spec,
KeyName: 키페어-이름,
SecurityGroupIds: [보안-그룹-아이디],
MinCount: 1,
MaxCount: 1,
SubnetId: 서브넷-아이디,
TagSpecifications: [
{
ResourceType: 'instance',
Tags: [{ Key: 'Name', Value: 인스턴스-이름 }],
},
],
BlockDeviceMappings: [
{
DeviceName: '/dev/xvda',
Ebs: {
VolumeSize: 20, // 사용할 스토리지 사이즈
},
},
],
UserData: startScript, // 앞에서 가져온 실행스크립트
};
try{
const createdEc2 = await this.ec2.runInstances(createEC2Props).promise();
// 그냥 sdk의 메소드를 사용하면 aws객체가 나온다고 하니 promise를 받게 해주고
const instanceId = createdEc2.Instances[0].InstanceId;
// 생성한 인스턴스 id를 가져와서
await new Promise((resolve) => setTimeout(resolve, 2000));
// 2초정도 기다려주고
const getEC2 = await this.ec2
.describeInstances({
InstanceIds: [instanceId],
})
.promise();
//인스턴스의 정보를 가져온다.
const createDefault = {
spec,
eventName: '',
ip: getEC2.Reservations[0].Instances[0].PublicIpAddress || '10.010.1',
//해당 인스턴스의 public ip를 가져오고
status: 'booting',
instanceId,
};
const instance = new this.InstanceModel(createDefault);
await instance.save(); //mongoDB에 저장해준다.
return true;
}catch (err) {
console.log(err);
return false;
}
}
}
이렇게 ip를 가져다두면, 해당 인스턴스에 떠있는 노드 서버로 api를 쏠 수 있게된다
인스턴스를 시작했으면 끄기도 해야한다.
async remove(id: string) {
try {
const objectId = new mongoose.Types.ObjectId(id);
const data = await this.InstanceModel.findById(objectId);
await this.ec2
.stopInstances(
{
InstanceIds: [data.instanceId],
},
(err, data) => {
if (err) {
console.log(err);
throw new Error('err');
} else {
console.log(data);
}
},
)
.promise();
await this.InstanceModel.updateOne(
{
_id: objectId,
},
{
status: 'stop',
eventName: '',
ip: '',
},
).exec();
//[mongoose도 그냥 메소드를 사용하면 유사 프로미스를 반환하니 제대로된 프로미스를 받기위해 exec를 쓴다](<https://tesseractjh.tistory.com/166>)
return true;
} catch (err) {
console.log(err);
return false;
}
}
'js' 카테고리의 다른 글
트위치 멀티 뷰어 개발기 (1) | 2023.12.21 |
---|---|
작업하던거 뒤로가기, 되돌리기 기능 (0) | 2023.07.30 |
개발에도 온고지신이 필요하다. (0) | 2023.01.13 |
HTML string trim() 하기 (0) | 2022.12.31 |
ajax 요청 보내기 (0) | 2022.11.21 |