云原生应用管理:原理与实践
上QQ阅读APP看书,第一时间看更新

4.2.2 performRelease

当拼接完毕后,下一步就是安装环节。首先要检查Chart是否含有一些Pre-hooks,特别是crd-install这种Hooks。因为针对这种类型的Hooks,Helm会在创建其他资源之前,第一步优先创建该资源,否则后面依赖该资源的对象都会安装失败。


// performRelease runs a release.
func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {

  // crd-install hooks
  if !req.DisableHooks && !req.DisableCrdHook {
    if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.CRDInstall, req.Timeout); err != nil {
      fmt.Printf("Finished installing CRD: %s", err)
      return res, err
    }
  } else {
    s.Log("CRD install hooks disabled for %s", req.Name)
  }
  
  ...
  ...
  ...
}
func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook string, timeout int64) error {
  kubeCli := s.env.KubeClient
  code, ok := events[hook]
  if !ok {
    return fmt.Errorf("unknown hook %s", hook)
  }

  ...
  
  // 首先根据不同的权重,将各种Hooks排序,依次将Hooks做成一个可执行数组
  executingHooks = sortByHookWeight(executingHooks)
  
  // 遍历所有的Hooks
  for _, h := range executingHooks {
  
    // 筛选before-create hook,看看是否需要提前执行Hooks的删除
    if err := s.deleteHookByPolicy(h, hooks.BeforeHookCreation, name, namespace, hook, kubeCli); err != nil {
      return err
    }

    b := bytes.NewBufferString(h.Manifest)
    // 将剩余的资源,特别是CRD资源装载到集群中
    if err := kubeCli.Create(namespace, b, timeout, false); err != nil {
      s.Log("warning: Release %s %s %s failed: %s", name, hook, h.Path, err)
      return err
    }
    
    // 这里要保证pre-install需要安装的资源必须等待安装成功后才能继续下一步          
if hook != hooks.CRDInstall {
      // 在这里一直等待资源创建成功
      if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil {
        s.Log("warning: Release %s %s %s could not complete: %s", name, hook, h.Path, err)
        // 如果Hooks失败,检查Hooks的注释,以确定是否应该删除该Hooks
        // 在Hooks失败的情况下,清除Hooks中相应的资源
        if err := s.deleteHookByPolicy(h, hooks.HookFailed, name, namespace, hook, kubeCli); err != nil {
          return err
        }
        return err
      }
    } else {
      if err := kubeCli.WaitUntilCRDEstablished(b, time.Duration(timeout)*time.Second); err != nil {
        s.Log("warning: Release %s %s %s could not complete: %s", name, hook, h.Path, err)
        return err
      }
    }
  }

  s.Log("hooks complete for %s %s", hook, name)
}

·将Hooks按照权重的顺序依次排序,优先级从高到低,一般需要先安装pre-install。

·deleteHookByPolicy函数将需要在安装前删除的资源优先删除。

·将剩余需要创建的资源,尤其是CRD资源提交给集群创建。

·提交后一直等待资源创建完毕,默认超时时间为1min。

·全部Hooks资源创建完毕后,代表该Chart Hooks准备完毕。

Hooks准备完成就可以安装资源了。


s.recordRelease(r, false)
if err := s.ReleaseModule.Create(r, req, s.env); err != nil {
  msg := fmt.Sprintf("Release %q failed: %s", r.Name, err)
  s.Log("warning: %s", msg)
  r.Info.Status.Code = release.Status_FAILED
  r.Info.Description = msg
  s.recordRelease(r, true)
  return res, fmt.Errorf("release %s failed: %s", r.Name, err)
}

可以看到,接下来先将Release信息记录到Kubernetes集群中,目前Helm默认的方式是记录到kube-system下的configmap中。

基本信息记录完毕后,就可以将剩余Chart渲染后的资源文件提交给Kubernetes ApiServer。下面我们依次看一下代码。


// 如果是重用名称,直接更新对应的configmap
// 如果是新的名称,直接创建对应的configmap
func (s *ReleaseServer) recordRelease(r *release.Release, reuse bool) {
  if reuse {
    if err := s.env.Releases.Update(r); err != nil {
      s.Log("warning: Failed to update release %s: %s", r.Name, err)
    }
  } else if err := s.env.Releases.Create(r); err != nil {
    s.Log("warning: Failed to record release %s: %s", r.Name, err)
  }
}
// 将Chart内容编译成字节流,然后通过client-go无结构体提交给ApiServer
func (m *LocalReleaseModule) Create(r *release.Release, req *services.InstallReleaseRequest, env *environment.Environment) error {
  b := bytes.NewBufferString(r.Manifest)
  return env.KubeClient.Create(r.Namespace, b, req.Timeout, req.Wait)
}
[root@iZhp38179i8shwfjy8jbigZ ~]# kubectl get cm -n kube-system --show-labels
NAME                                 DATA   AGE    LABELS
wordpress-default.v1                 1      40s    NAME=wordpress-default,OWNER
=TILLER,STATUS=DEPLOYED,VERSION=1

由上可见,集群中安装了Release的信息都被存放在kube-system命名空间下的configmap中。configmap的名称就是Release的名字,后面的点对应着它的版本号。比如这里的v1就是第一次安装的版本,后面的labels也能表明一些身份,比如OWNER=TILLER,代表它是Tiller创建的。


[root@iZhp38179i8shwfjy8jbigZ ~]# kubectl get cm wordpress-default.v1 -n kube-system -o yaml
apiVersion: v1
data:
  release: H4sIAAAAAAAC/+z9XWwkyZYfhuPeuXf33lxAkHoF4a8L/O3Y4mjZnGFmk
kind: ConfigMap
metadata:
  creationTimestamp: "2019-11-27T11:57:21Z"
  labels:
    MODIFIED_AT: "1574855841"
    NAME: wordpress-default
    OWNER: TILLER
    STATUS: DEPLOYED
    VERSION: "1"
  name: wordpress-default.v1
  namespace: kube-system
  resourceVersion: "1637228"
  selfLink: /api/v1/namespaces/kube-system/configmaps/wordpress-default.v1
  uid: 118b0a0e-110d-11ea-83b0-00163e00d57b

Release后面的信息就是base64编码后的Chart信息,有兴趣的读者可以反编译看一下内容。

这些操作全部完成后,需要再次执行post-install hooks,也就是安装之后需要执行的Hooks,执行流程与前面相似。

这样全部的创建流程就完成了,后面就可以将这个Release的状态改为Status_DEPLOYED。