使用Golang SDK操作GCE的Instance Group Manager

除了一部分,GCP的各种服务的Golang SDK都是根据API定义的JSON Schema自动生成的。
由于API定义和生成的SDK具有一些特点,实际使用时可能会遇到一些困难,因此我想在这里举几个例子进行解释。

可以在这里查看实际的JSON Schema和Golang的代码。

GCE概念的解释

「Instance Group Manager」是什么意思?

一个可以管理通过GCE创建的实例的工具,这是对它直接的解释。
具体而言,它可以做到以下几点:
– 监控实例的状态
– 分配负载
– 自动扩展
– 使用实例模板

有一些实例组,具体内容如下所示。

“Instance Template” 是什么意思?

要进行的是对Instance的配置。
可以进行配置的内容有:
– 操作系统和机型
– 硬盘
– 启动时的脚本
– 网络

创建实例模板。

具体举例

我打算使用Golang SDK来创建上述资源。
具体步骤是在GCP上创建一个实例模板,然后使用该模板创建托管实例组。

样本


import (
    "errors"
    "fmt"
    "strings"
    "time"

    "golang.org/x/net/context"
    "golang.org/x/oauth2/google"
    compute "google.golang.org/api/compute/v1"
)

func readError(op *compute.Operation) error {
    if op.Error == nil {
        return nil
    }

    errs := []string{}
    for _, err := range op.Error.Errors {
        errs = append(errs, err.Message)
    }
    return errors.New(strings.Join(errs, ","))
}

func main() {
    err := func() error {
        s, err := ioutil.ReadFile("/path/to/credential.json") // GoogleのIAMで作成するCredentialのJSON
        if err != nil {
          return err
        }

        c, err := google.JWTConfigFromJSON(s, compute.ComputeScope)
        if err != nil {
            return err
        }
        ctx := context.Background()
        client := c.Client(ctx)
        service, err := compute.New(client) // 上記のCredentialにより作成したこのServiceを全リクエストで用いる
        if err != nil {
            return err
        }

        startupScript := `
#!/bin/bash
sudo apt-get update && sudo apt-get install nginx
`

        temp := &compute.InstanceTemplate{
            Name: "sample-template",
            Properties: &compute.InstanceProperties{
                Tags: &compute.Tags{
                    Items: []string{
                        "https-server", // GoogleのFirewallはタグによってルールをインスタンスに設定する形で用いる。
                        "http-server",
                    },
                },
                NetworkInterfaces: []*compute.NetworkInterface{
                    {
                        AccessConfigs: []*compute.AccessConfig{
                            {
                                Name: "external-IP", 
                                Type: "ONE_TO_ONE_NAT",
                            },
                        },
                        Network: "global/networks/default",
                    },
                },
                Disks: []*compute.AttachedDisk{
                    {
                        Boot: true,
                        InitializeParams: &compute.AttachedDiskInitializeParams{
                            SourceImage: "projects/debian-cloud/global/images/debian-8-jessie-v20170308",
                        },
                    },
                },
                MachineType: "n1-standard-1",
                Metadata: &compute.Metadata{
                    Items: []*compute.MetadataItems{
                        {
                            Key:   "startup-script", // 起動時に走らせるscriptはこの名前でメタデータに登録する。
                            Value: &startupScript,
                        },
                    },
                },
            },
        }

        op, err := compute.NewInstanceTemplatesService(service).
            Insert(project, temp).
            Do()
        if err != nil {
            return err
        }
        if err := readError(op); err != nil {
            return err
        }

        // templateが登録されるまでラグがあるので待つ必要がある
        for {
            time.Sleep(1 * time.Second)

            b := false
            ts, err := compute.NewInstanceTemplatesService(service).
                List(project).
                Do()
            if err != nil {
                return err
            }
            for _, t := range ts.Items {
                if "sample-template" == t.Name {
                    b = true
                }
            }
            if !b {
                continue
            }
            break
        }

        grp := &compute.InstanceGroupManager{
            InstanceTemplate: "global/instanceTemplates/sample-template",
            TargetSize:       1,
            Name:             "sample-group-manager",
            BaseInstanceName: "sample-group",
        }
        op, err = compute.NewInstanceGroupManagersService(service).
            Insert(project, zone, grp).
            Do()
        if err != nil {
            return err
        }
        if err := readError(op); err != nil {
            return err
        }
        return nil
    }()
    if err != nil {
        fmt.Println(err)
        return
    }
}


sample-template被应用于sample-group-manager,根据sample-template的设定,会创建一个实例并正确地启动一台。

说明

服务

Golang SDK中有一个名为Service的概念(其他SDK可能也有这个概念)。
通过提供带有Credential信息的JSON来创建该Service,该JSON会由每个Service Account生成,
因此每个请求都将使用这个特殊Service进行创建。

请求主体

由于Golang SDK中没有关于各个请求属性的信息,所以需要查看每个API的参考资料。

实例模板
实例组管理器

在我使用Golang SDK尝试操作GCP服务时,我发现只有在API参考文档中才能找到哪些属性是必需的,以及它们受到了怎样的验证。也许由于自动生成的特性,这很困难,而且相当难以处理。

完全是另一回事,同样是自动生成的Cloud Vision API的Golang SDK的Godoc,它的示例代码由Python、Java、Objective-C等组成,却没有Golang的示例代码,这破坏了Godoc的完整性。

元数据

在Instance Template中,可以加入Metadata。这样做有很多方便之处,但在此我们略过(详细内容请参考本文)。重点是,可以通过名为startup-script的脚本来嵌入脚本,在服务器启动或重新启动时运行该脚本。详情如下:
运行启动脚本

操作

关于插入和删除请求,它们的返回值是通过操作(Operation)来确定的。
若要创建具有多个依赖关系的资源,则需要充分理解各个供应商服务的特性,并进行应用。由于模板的创建可能需要一些微妙的时间来反映变化,因此在重现某些难以捉摸的错误时,我确实有这样的经历。

总结

因为学习GCP的Golang SDK需要相当大的成本,所以对于小规模项目来说,最好还是用gcloud来构建。我觉得这可能是因为在我使用Golang SDK的时候,似乎很少见到其他人使用它的原因,所以我暂时自己去解释了一下。

我什么也不了解,所以希望有人能教我正直的实践方法。

bannerAds