Cloudcone是2017年成立于美国的一家主机供应商。继vultr变得“不靠谱”和低价vm资源经常“售尽”的情况下,我转而使用cloudcone提供的vps服务用于日常学习和开发。
除了支持按小时计费、提供更换IP服务及支持支付宝/paypal等灵活的支付方式外,我最看重的是,它的价格能做到真正的弹性,即在vm关机状态不计费CPU/内存等费用,比online费用大概减半。
类似于其他云供应商平台,cloudcone还提供了开放的API来管理云主机。
这里为了方便使用和合理控制资源,自己便用go写了一个小程序
代码实现
主要逻辑如下,通过cloudcone 提供的API。每天晚上23左右关机,次日早晨7点左右启动 如果调用失败的话,会再次重试一次。(如果依然不行的话,就当运气不好了:<)
以下是草稿版本代码
- secret.go
 
package main
var (
    // 配置相关信息,这里我填写的是假的啦
	serverID  = 123456
	appSecret = "Q2aQr9323QE233r2"
	hashCode  = "zXqV22222222222222222222222222222222225bL"
)
serverID可以在compute菜单控制面板下查看,或查看浏览器url里的参数。
appSecret和hashCode访问https://app.cloudcone.com/user/api创建即可。
- main.go
 
/*
* cloudcone api https://api.cloudcone.com/
 */
package main
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"sync"
	"time"
	"github.com/robfig/cron"
)
// CloudconeResponse represents the API Response
type CloudconeResponse struct {
	Status  int
	Message string
	Data    interface{} `json:"_data"`
}
const (
	baseAPIURL = "https://api.cloudcone.com/api/v1"
)
var (
	client = &http.Client{}
)
func main() {
	f, err := os.OpenFile("app.log", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0644)
	if err != nil {
		log.Fatalf("Error while opening file: %v\n", err)
	}
	defer f.Close()
	log.SetOutput(f)
	wg := sync.WaitGroup{}
	_, cancel := context.WithCancel(context.Background())
	c := cron.New()
	// boot at 07:00
	c.AddFunc("0 0 7 * * *", func() {
		log.Println("booting at time", time.Now())
		if err := boot(); err != nil {
			log.Println(err)
			time.Sleep(15 * time.Second)
			// try again
			boot()
		}
	})
	// shutdown at 23:45
	c.AddFunc("0 45 23 * * *", func() {
		log.Println("Shutdown at time", time.Now())
		if err := shutdown(); err != nil {
			log.Println(err)
			time.Sleep(15 * time.Second)
			// try again
			shutdown()
		}
	})
	c.Start()
	wg.Add(1)
	go func() {
		defer wg.Done()
		os.Stdin.Read(make([]byte, 1)) // wait for Enter keystroke
		c.Stop()
		cancel() // cancel the associated context
	}()
	log.Println("Running...")
	wg.Wait()
}
func boot() error {
	// compute/:id/boot
	endpoint := fmt.Sprintf("%s/compute/%d/%s", baseAPIURL, serverID, "boot")
	req, err := http.NewRequest("GET", endpoint, nil)
	if err != nil {
		return err
	}
	req.Header.Set("App-Secret", appSecret)
	req.Header.Set("Hash", hashCode)
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	fmt.Println(string(body))
	var dat CloudconeResponse
	if err := json.Unmarshal(body, &dat); err != nil {
		return err
	}
	if dat.Status != 1 {
		return errors.New(dat.Message)
	}
	log.Println(dat.Message)
	return nil
}
func shutdown() error {
	// compute/:id/shutdown
	endpoint := fmt.Sprintf("%s/compute/%d/%s", baseAPIURL, serverID, "shutdown")
	req, err := http.NewRequest("GET", endpoint, nil)
	if err != nil {
		return err
	}
	req.Header.Set("App-Secret", appSecret)
	req.Header.Set("Hash", hashCode)
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var dat CloudconeResponse
	if err := json.Unmarshal(body, &dat); err != nil {
		return err
	}
	if dat.Status != 1 {
		return errors.New(dat.Message)
	}
	log.Println(dat.Message)
	return nil
}