AWS-Save-Money

目的

在编译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,好看一些。

  1. RPC

    使用Go的net/rpc

  2. Telegram Bot

    参考Telegram Bot使用

    • 向Telegram BotFather发送/newbot
    • 输入以bot结尾的Bot名字
    • 获得BotFather返回的Bot Token

Server / Client 代码实现

全部代码见AWS-save-money

整个代码构成是这样的,需要四个文件夹:server、client、conf、proto。server存放服务端代码,client存放客户端代码,conf存放server的公钥、私钥,proto存放protobuf文件。

  • conf:存放生成的server端公钥、私钥

    1. Secret key

      openssl ecparam -genkey -name secp384r1 -out server.key
    2. 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"

var (
InstanceID *string = aws.String("")
BotToken string = ""
UpInstanceType string = ""
DownInstanceType string = ""
ChatID int = 123456
Region *string = aws.String("")
AWSID string = ""
AWSSecret string = ""
)
  • Server

    1. 设置服务端监听端口

    2. 生成server端公钥、密钥

    3. 在服务端注册一个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)
      }
      }
    4. 实现需要的功能

      根据需求实现需要暴露的功能

  • 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!!!")
    }
    }
Author: suikammd
Link: https://suikammd.github.io/2019/05/29/AWS-Save-Money/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.