github.com/wangchanggan/helm@v0.0.0-20211020154240-11b1b7d5406d/pkg/tiller/release_install.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tiller 18 19 import ( 20 "fmt" 21 "strings" 22 23 ctx "golang.org/x/net/context" 24 25 "k8s.io/helm/pkg/chartutil" 26 "k8s.io/helm/pkg/hooks" 27 "k8s.io/helm/pkg/proto/hapi/release" 28 "k8s.io/helm/pkg/proto/hapi/services" 29 relutil "k8s.io/helm/pkg/releaseutil" 30 "k8s.io/helm/pkg/timeconv" 31 ) 32 33 // InstallRelease installs a release and stores the release record. 34 func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { 35 s.Log("preparing install for %s", req.Name) 36 // 针对客户端传递过来的信息做准备,主要检查是否重名,然后对传递过来的各个参数和values.yaml进行渲染,然后拼接出Release对象。 37 rel, err := s.prepareRelease(req) 38 if err != nil { 39 s.Log("failed install prepare step: %s", err) 40 res := &services.InstallReleaseResponse{Release: rel} 41 42 // On dry run, append the manifest contents to a failed release. This is 43 // a stop-gap until we can revisit an error backchannel post-2.0. 44 // 如果是测试场景,就仅返回渲染失败的错误信息 45 if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { 46 err = fmt.Errorf("%s\n%s", err, rel.Manifest) 47 } 48 return res, err 49 } 50 51 s.Log("performing install for %s", req.Name) 52 // 真正进行Release安装的函数 53 res, err := s.performRelease(rel, req) 54 if err != nil { 55 s.Log("failed install perform step: %s", err) 56 } 57 return res, err 58 } 59 60 // prepareRelease builds a release for an install operation. 61 func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { 62 if req.Chart == nil { 63 return nil, errMissingChart 64 } 65 66 // 检查用户执行的Release名称是否唯一,如果是自动生成的,会自动保证该名称的唯一性 67 // 如果名称是用户指定的,这里会检查集群是否含有重名的Release 68 name, err := s.uniqName(req.Name, req.ReuseName) 69 if err != nil { 70 return nil, err 71 } 72 73 // 检查客户端和服务端之间的兼容性,判断客户端、服务端以及ApiServer是否兼容 74 caps, err := capabilities(s.clientset.Discovery()) 75 if err != nil { 76 return nil, err 77 } 78 79 // 每一个Release默认都有一个版本号,这里就是第一个版本号 80 revision := 1 81 ts := timeconv.Now() 82 options := chartutil.ReleaseOptions{ 83 Name: name, 84 Time: ts, 85 Namespace: req.Namespace, 86 Revision: revision, 87 IsInstall: true, 88 } 89 // 将传入的value进行渲染,组成新的values.yaml 90 valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) 91 if err != nil { 92 return nil, err 93 } 94 95 // 分离出安装资源、Hooks资源,以及将当前集群的ApiServer信息填入结构体,为下一步构造安装结构做铺垫 96 hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, req.SubNotes, caps.APIVersions) 97 if err != nil { 98 // Return a release with partial data so that client can show debugging 99 // information. 100 // 该结构体就是最终会存储的结构体,将需要安装的信息、Hooks信息和状态等内容进行初始化 101 rel := &release.Release{ 102 Name: name, 103 Namespace: req.Namespace, 104 Chart: req.Chart, 105 Config: req.Values, 106 Info: &release.Info{ 107 FirstDeployed: ts, 108 LastDeployed: ts, 109 Status: &release.Status{Code: release.Status_UNKNOWN}, 110 Description: fmt.Sprintf("Install failed: %s", err), 111 }, 112 Version: 0, 113 } 114 if manifestDoc != nil { 115 rel.Manifest = manifestDoc.String() 116 } 117 return rel, err 118 } 119 120 // Store a release. 121 rel := &release.Release{ 122 Name: name, 123 Namespace: req.Namespace, 124 Chart: req.Chart, 125 Config: req.Values, 126 Info: &release.Info{ 127 FirstDeployed: ts, 128 LastDeployed: ts, 129 Status: &release.Status{Code: release.Status_PENDING_INSTALL}, 130 Description: "Initial install underway", // Will be overwritten. 131 }, 132 Manifest: manifestDoc.String(), 133 Hooks: hooks, 134 Version: int32(revision), 135 } 136 if len(notesTxt) > 0 { 137 rel.Info.Status.Notes = notesTxt 138 } 139 140 return rel, nil 141 } 142 143 func hasCRDHook(hs []*release.Hook) bool { 144 for _, h := range hs { 145 for _, e := range h.Events { 146 if e == events[hooks.CRDInstall] { 147 return true 148 } 149 } 150 } 151 return false 152 } 153 154 // performRelease runs a release. 155 // 安装环节 156 // 首先要检查Chart是否含有一些Pre-hooks, 特别是crd-install这种Hooks 157 // 因为针对这种类型的Hooks, Helm 会在创建其他资源之前,第一步优先创建该资源,否则后面依赖该资源的对象都会安装失败。 158 func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { 159 res := &services.InstallReleaseResponse{Release: r} 160 manifestDoc := []byte(r.Manifest) 161 162 if req.DryRun { 163 s.Log("dry run for %s", r.Name) 164 165 if !req.DisableCrdHook && hasCRDHook(r.Hooks) { 166 s.Log("validation skipped because CRD hook is present") 167 res.Release.Info.Description = "Validation skipped because CRDs are not installed" 168 return res, nil 169 } 170 171 // Here's the problem with dry runs and CRDs: We can't install a CRD 172 // during a dry run, which means it cannot be validated. 173 if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil { 174 return res, err 175 } 176 177 res.Release.Info.Description = "Dry run complete" 178 return res, nil 179 } 180 181 // crd-install hooks 182 if !req.DisableHooks && !req.DisableCrdHook { 183 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.CRDInstall, req.Timeout); err != nil { 184 fmt.Printf("Finished installing CRD: %s", err) 185 return res, err 186 } 187 } else { 188 s.Log("CRD install hooks disabled for %s", req.Name) 189 } 190 191 // Because the CRDs are installed, they are used for validation during this step. 192 if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil { 193 return res, fmt.Errorf("validation failed: %s", err) 194 } 195 196 // pre-install hooks 197 if !req.DisableHooks { 198 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { 199 return res, err 200 } 201 } else { 202 s.Log("install hooks disabled for %s", req.Name) 203 } 204 205 switch h, err := s.env.Releases.History(req.Name); { 206 // if this is a replace operation, append to the release history 207 case req.ReuseName && err == nil && len(h) >= 1: 208 s.Log("name reuse for %s requested, replacing release", req.Name) 209 // get latest release revision 210 relutil.Reverse(h, relutil.SortByRevision) 211 212 // old release 213 old := h[0] 214 215 // update old release status 216 old.Info.Status.Code = release.Status_SUPERSEDED 217 s.recordRelease(old, true) 218 219 // update new release with next revision number 220 // so as to append to the old release's history 221 r.Version = old.Version + 1 222 updateReq := &services.UpdateReleaseRequest{ 223 Wait: req.Wait, 224 Recreate: false, 225 Timeout: req.Timeout, 226 } 227 s.recordRelease(r, false) 228 if err := s.ReleaseModule.Update(old, r, updateReq, s.env); err != nil { 229 msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err) 230 s.Log("warning: %s", msg) 231 old.Info.Status.Code = release.Status_SUPERSEDED 232 r.Info.Status.Code = release.Status_FAILED 233 r.Info.Description = msg 234 s.recordRelease(old, true) 235 s.recordRelease(r, true) 236 return res, err 237 } 238 239 default: 240 // nothing to replace, create as normal 241 // regular manifests 242 s.recordRelease(r, false) 243 if err := s.ReleaseModule.Create(r, req, s.env); err != nil { 244 msg := fmt.Sprintf("Release %q failed: %s", r.Name, err) 245 s.Log("warning: %s", msg) 246 r.Info.Status.Code = release.Status_FAILED 247 r.Info.Description = msg 248 s.recordRelease(r, true) 249 return res, fmt.Errorf("release %s failed: %s", r.Name, err) 250 } 251 } 252 253 // post-install hooks 254 // 再次执行post-install hooks,也就是安装之后需要执行的Hooks 255 if !req.DisableHooks { 256 if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil { 257 msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err) 258 s.Log("warning: %s", msg) 259 r.Info.Status.Code = release.Status_FAILED 260 r.Info.Description = msg 261 s.recordRelease(r, true) 262 return res, err 263 } 264 } 265 266 // 全部的创建流程就完成,将这个Release的状态改为Status_DEPLOYED。 267 r.Info.Status.Code = release.Status_DEPLOYED 268 if req.Description == "" { 269 r.Info.Description = "Install complete" 270 } else { 271 r.Info.Description = req.Description 272 } 273 // This is a tricky case. The release has been created, but the result 274 // cannot be recorded. The truest thing to tell the user is that the 275 // release was created. However, the user will not be able to do anything 276 // further with this release. 277 // 278 // One possible strategy would be to do a timed retry to see if we can get 279 // this stored in the future. 280 s.recordRelease(r, true) 281 282 return res, nil 283 }