目的
在编译Petalinux的时候,需要至少4核CPU。GCE的虚拟机用完了,AWS计算资源又非常贵,不过还好有免费的$100可以用。因此我决定采取非常丑的办法(还不是因为菜),在准备编译时关闭AWS Instance,从t2.micro(AWS免费)升级到t2.xlarge,然后启动机器将Public IP发送到Telegram Bot;在编译完成时关闭并降级最后再重新启动Instance。
此外,希望能让Telegram Bot定时发送账单信息给我,毕竟$100眨眼就没了。
环境
AWS Instance 1 (Server t2.micro)
AWS Instance 2 (Client t2.xlarge / t2.micro)
Telegram Bot
通信
一开始没有考虑ssh连接到实例需要公网IP的问题,在实例升级完成重启之后就结束了。考虑了IP的问题之后,决定将IP发送到Telegram Bot上。最后才去的做法是,通过RPC在服务端注册对象,通过对象的类型名暴露这个服务,客户端通过远程调用使用这些方法。公网IP在完成升级重启后发送到Telegram Bot。当然可以不用RPC,把所有请求都丢给Telegram Bot,好看一些。
RPC
使用Go的
net/rpc
库Telegram Bot
- 向Telegram BotFather发送
/newbot
- 输入以
bot
结尾的Bot名字 - 获得BotFather返回的Bot Token
- 向Telegram BotFather发送
Server / Client 代码实现
全部代码见AWS-save-money
整个代码构成是这样的,需要四个文件夹:server、client、conf、proto。server存放服务端代码,client存放客户端代码,conf存放server的公钥、私钥,proto存放protobuf文件。
conf:存放生成的server端公钥、私钥
Secret key
openssl ecparam -genkey -name secp384r1 -out server.key
Public key
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
proto
首先需要编写一些
.proto
文件,在其中定义需要传递的消息类型message
和服务service
syntax = "proto3"; // version 3
package proto; // specify package
// define service
service ServerClient {
rpc StartInstance(Request) returns (Response) {}
rpc DescribeInstance(Request) returns (Response) {}
rpc StopInstance(Request) returns (Response) {}
rpc ModifyUpInstance(Request) returns (Response) {}
rpc ModifyDownInstance(Request) returns (Response) {}
}
// define message to transfer: Request, Response
message Request {
bytes line = 1; // different number means its unique
bool ack = 2;
}
message Response {
}之后根据proto文件生成相应的go文件,采用下面的指令:
protoc --go_out=plugins=grpc:. service.proto
在使用该代码前请手动定义这些参数
import "github.com/aws/aws-sdk-go/aws" |
Server
设置服务端监听端口
生成server端公钥、密钥
在服务端注册一个
Listener对象,
type Listener ec2.EC2`, 创建这个对象之后注册其对应的服务,这些服务必须是可以暴露的type Service EC2.ec2
func main() {
// listen on port 1234
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("Listen TCP error: ", err)
}
// init server
c, err := cre.NewServerTLSFromFile("../conf/server.pem", "../conf/server.key")
if err != nil {
log.Fatalf("GRPC credentials.NewServerTLSFromFile err : %v", err)
}
server := grpc.NewServer(grpc.Creds(c))
pb.RegisterServerClientServer(server, NewEC2Sess())
reflection.Register(server)
if err := server.Serve(listener); err != nil {
log.Fatalf("Failed to Serve %v", err)
}
}实现需要的功能
根据需求实现需要暴露的功能
Client
需要的功能有启动、关闭、升级和降级实例,用
switch
语句选择即可func main() {
c, err := credentials.NewClientTLSFromFile("../conf/server.pem", "Suika")
if err != nil {
log.Fatalf("credentials.NewClientTLSFromFile err: err")
}
conn, err := grpc.Dial(InstanceIP + ":1234", grpc.WithTransportCredentials(c))
if err != nil {
log.Fatalf("grpc.Dail error %v", err)
}
defer conn.Close()
client := pb.NewServerClientClient(conn)
// flags
var in string
flag.StringVar(&in, "s", in, "name")
flag.Parse()
var line []byte = []byte(in)
method := string(line)
fmt.Printf("Current Method is %s\n", method)
var reply bool
switch method {
case "b":
fmt.Println("Restart and Modify to Build")
_, err = client.ModifyUpInstance(context.Background(), &pb.Request{Line:line, Ack:reply})
if err != nil {
log.Fatal(err)
}
case "f":
fmt.Println("Finish Building")
_, err = client.ModifyDownInstance(context.Background(), &pb.Request{Line:line, Ack:reply})
if err != nil {
log.Fatal(err)
}
case "start":
fmt.Println("Start Instance")
_, err = client.StartInstance(context.Background(), &pb.Request{Line:line, Ack:reply})
if err != nil {
log.Fatal(err)
}
case "stop":
fmt.Println("Stop Instace")
_, err = client.StopInstance(context.Background(), &pb.Request{Line:line, Ack:reply})
if err != nil {
log.Fatal(err)
}
case "des":
fmt.Println("Describe Instace")
_, err = client.DescribeInstance(context.Background(), &pb.Request{Line:line, Ack:reply})
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("No Such Method!!!")
}
}