阿里云FC3.0 Custom Container 镜像适配

云栖大会函数计算推出了 3.0 版本,在我一直推荐的 Custom Container 运行环境下,有一个很影响业务的变更。

新版本中,函数镜像不太接受我们固定 tag 的模式,比如原本我们的镜像是 registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main ,FC 会存储 tag 的 digest 值,就变成了 registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools@sha256:9c0b86128618c0e7d0b82f4bb3c05d9e725545ea54703e6331bb99879982d734

每一个新实例启动的时候,会获取 tag 最新的 digest 值,如果判断和缓存的不一致,会收到 {"RequestId":"1-65543e3f-9c680ff7b26b8391c0bbf694","Code":"PullImageFailed","Message":"Failed to pull image. failed to create container due to resolved image uri deleted"} 的错误信息。

这时如果你业务紧急的话,可以通过 UpdateFunction 来更新 resolvedImageUri 字段。

因为我们使用的是 Custom Container 运行环境,我们必须使用 ACR 镜像仓库,个人版是支持配置 webhook 来接受镜像变更的信息,我们可以配置后接收到回调,然后来更新相关函数。

当然,这个也需要我们原本的 tag 进行一些变更,像我们正式版原来是 release,我们用 GitHub Actions 进行构建,可以用 $GITHUB_SHA 来获取当前提交的 SHA 值,然后加入到我们的 tag 中,让 tag 不在成为一个固定值,例如 release-83f96b6a42287f1afdf95c2dad41616c601422bb ,按照一定的规则来判断它们是否为同一个 “tag”,再调用 UpdateFunction API 来进行镜像版本更新。

下面提供我们 golang 版本的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package main

import (
"log"
"strings"
)

func main() {
type Item struct {
// currentImage 当前镜像
currentImage string
// newImage 新镜像
newImage string
// result 是否要更新
result bool
}

items := []Item{
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-11",
true,
},
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main",
false,
},
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-2",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-33",
true,
},
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main2-2",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main2-33",
true,
},
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-hhhongfs-122",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-11",
false,
},
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-11",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-hhhongfs-122",
false,
},
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:ma3in-11",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-hhhongfs-122",
false,
},
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/t2ools:main",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-11",
false,
},
{
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main-11",
"registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/t2ools:main",
false,
},
}

for _, item := range items {
item := item

if hasUpdate(item.currentImage, item.newImage) != item.result {
log.Printf("currentImage: %s, newImage: %s, 未符合预期结果", item.currentImage, item.newImage)
}
}

log.Println("done")
}

// hasUpdate 判断是否要更新
func hasUpdate(currentImage, newImage string) bool {
// currentImage=registry-vpc.cn-shenzhen.aliyuncs.com/hongfs/tools:main
// currentName=tools
// currentTag=main
currentName, currentTag := parseImage(currentImage)
newName, newTag := parseImage(newImage)

// 镜像名称不同,直接返回
if currentName != newName {
return false
}

// tag 一样会导致异常
if currentTag == newTag {
return false
}

// 不符合最新规范
if !strings.Contains(newTag, "-") {
return false
}

// 原本的镜像已经是最新规范,
// 那要通过 `-` 的长度来判断是不是同一个 tag
// 还要去掉 sha 值然后判断是否满足前缀
if strings.Contains(currentTag, "-") {
if strings.Count(currentTag, "-") != strings.Count(newTag, "-") {
return false
}

return strings.HasPrefix(newTag, strings.SplitAfterN(currentTag, "-", 2)[0])
}

if strings.Count(currentTag, "-")+1 != strings.Count(newTag, "-") {
return false
}

return strings.HasPrefix(newTag, currentTag+"-")
}

// parseImage 解析镜像名称、tag
func parseImage(image string) (string, string) {
values := strings.SplitAfterN(image, ":", 2)

tag := ""

if len(values) == 2 {
tag = values[1]
}

values = strings.SplitAfter(values[0][:len(values[0])-1], "/")

return values[len(values)-1], tag
}
往上