目录
1. 当前的Provider 支持程度
1.1 官方支持的Provider:
- AWS,支持最丰富,且版本总是跟着最新的Crossplane 而更新,可惜没有针对虚拟机的Managed Resource,EC2等等。
- 阿里云,非常没有诚意,这么多云资源,只象征性的,有一个Redis服务,非常敷衍。
- Azure,支持仅次于AWS,但也很有限,只有数据库和网络相关的一些资源。
- GCP,和Azure 差不多,只有桶啊,网络资源等,其余啥也没有。
- Oenstack,官方就没有支持,连个空的项目都没建。
1.2 个人针对Openstack 的支持:
看介绍,以为这个provider 可以handle openstack 的复杂场景。
代码地址: rochaporto/provider-openstack. 仔细阅读后,发现这个项目非常简陋,看代码几乎什么也没做,NOTHING,垃圾,什么也不是。
2. 理解crossplane-runtime
有些基础概念,要事先解释,参考我的上一篇文章,介绍Crossplane的概念。
它的本质并没有脱离informer-controller 机制,这个引擎是工作在k8s.io/controller-runtime 基础之上的。
它的调谐哲学:
crossplane runtime 把external (外部provider)接口定义出来,我们在写新的provider 的时候,不需要,也完全不应该关注k8s 资源recognize的逻辑,external provider 只有修改外部资源的功能,万万不可修改k8s 的crd。
-
external 接口
type ExternalClient interface { // Observe the external resource the supplied Managed resource represents, // if any. Observe implementations must not modify the external resource, // but may update the supplied Managed resource to reflect the state of the // external resource. Observe(ctx context.Context, mg resource.Managed) (ExternalObservation, error) // Create an external resource per the specifications of the supplied // Managed resource. Called when Observe reports that the associated // external resource does not exist. Create(ctx context.Context, mg resource.Managed) (ExternalCreation, error) // Update the external resource represented by the supplied Managed // resource, if necessary. Called unless Observe reports that the // associated external resource is up to date. Update(ctx context.Context, mg resource.Managed) (ExternalUpdate, error) // Delete the external resource upon deletion of its associated Managed // resource. Called when the managed resource has been deleted. Delete(ctx context.Context, mg resource.Managed) error }
外部 provider 需要实现上述接口,这些接口必须针对的是外部资源池,那么有同学要问了,如果我像修改managedresource,该怎么办呢?ExternalClient 接口的实现,一般我们要杜绝直接修改managedresource 的spec。这是因为k8s 的设计哲学是,不可变基础设施,资源是人定义的,定义后,就不能改,如果你非要改,怎么改?有两种方式:
- 在你的ExternalClient对象中,增加kubeclient 的构成,默认官方的模板,是不建议的。因为你只是去执行,而不应该修改资源的描述文件,这个权限给的就非常大了。
- 在ExternalCreation的返回值中,描述下你的ExternalNameAssigned 为true,这里可以修改你的annotation,增加名称的修改。意思是,你的同一个资源,在k8s 里叫张三,在外部provider那里,就叫李四,可以通过一个annotation,传递给crossplane runtime,来增加名称的转换,也只能改名称。
必须先通过一个Connect 方法获取external client,这个connect 返回外部provider 的客户端,比如awsclient、openstackclient等。
3. 创建一个openstack provider
这小节,主要完成,创建一个openstack provider,并且实现创建和删除openstack虚拟机的过程。
- fork 下这个provider-template。
- 修改掉这里的Mytype,改成virtualmachine,或者你喜欢的名字。
- 修改3个接口
-
connect
这里返回的是由openstack client组成的external,我们需要拿到openstack 的auth配置文件。
我们这里取到的是,openstack 的yaml配置文件,我们取出来.cloud.openstack
的内容,大概长这个样子:auth: auth_url: http://111.11.11.11:5000 username: "XXXXXXX" password: "XXXXXXX" project_id: XXXXXXXXXXXXXXXXX project_name: "XXXXXXXXXXXXX" user_domain_name: "Default" region_name: "RegionOne" interface: "public" identity_api_version: 3
-
构造一个struct对象,对这个yaml结构体进行解析
newOpenstackClient = func(credentials []byte) (interface{}, error) { openstackConfig := new(OpenstackConfig) err := yaml.Unmarshal(credentials, &openstackConfig) if err != nil { return nil, err } options := gophercloud.AuthOptions{ Username: openstackConfig.Auth.Username, Password: openstackConfig.Auth.Password, DomainName: openstackConfig.Auth.UserDomainName, TenantName: openstackConfig.Auth.ProjectName, TenantID: openstackConfig.Auth.ProjectID, IdentityEndpoint: openstackConfig.Auth.AuthUrl, } client, authError := openstack.AuthenticatedClient(options) if authError != nil { return nil, err } return client, nil }
-
Observe 接口
这个接口,是为了根据当前ManagedResource 查询这个资源在provider里的详情的,它在不同的操作中,起到的作用是不一样的。- 在第一次创建资源的时候,observe 是为了检测下,资源在provider 中是否存在(不存在要create的)。
- 在修改了managedresource 的时候,是为了检测,资源的diff,是否需要更新。
- 如果要删除,需要查看下资源是否存在,如果存在就要删资源。
给一段我写的示例代码。注意,名称需要精准匹配
fmt.Sprintf("^%s$", cr.Name)
。// 3. server client client, serverError := openstack.NewComputeV2((c.service).(*gophercloud.ProviderClient), gophercloud.EndpointOpts{ Region: "RegionOne", }) if serverError!=nil { return managed.ExternalObservation{}, errors.New("can't get nova client") } // 4. servers ops exactalName := fmt.Sprintf("^%s$", cr.Name) opts := servers.ListOpts{Name: exactalName} // Retrieve a pager (i.e. a paginated collection) pager := servers.List(client, opts)
-
- create 接口
create 相对来说比较简单。直接给示例代码:需要说明的是,这里的annotation里需要带上id。因为将来删的时候,必须要要通过ID去删除。// 3. server client client, _ := openstack.NewComputeV2((c.service).(*gophercloud.ProviderClient), gophercloud.EndpointOpts{ Region: "RegionOne", }) // 4. servers ops result, CreateError := servers.Create(client, servers.CreateOpts{ Name: cr.Name, ImageName: "CentOS-8-GenericCloud-8.3.2011", FlavorName: "hyperos-flavor", Networks: []servers.Network{ {UUID: "246af0d1-6bcb-447d-b82a-eae1225e1aaa"}, }, ServiceClient: client, }).Extract() if CreateError != nil { log.Println("Create vm error:", CreateError.Error()) } anno := mg.GetAnnotations() anno[meta.AnnotationKeyExternalName] = result.ID mg.SetAnnotations(anno)
- delete 接口
最简单,拿到annotation,直接删除就完事儿了。// 3. server client client, _ := openstack.NewComputeV2((c.service).(*gophercloud.ProviderClient), gophercloud.EndpointOpts{ Region: "RegionOne", }) // 4. servers ops return servers.Delete(client, cr.GetAnnotations()[meta.AnnotationKeyExternalName]).ExtractErr() }
总结:
通过这个方式,我们可以利用crossplane 的runtime 实现直接操作openstack的接口,分离k8s资源和provider的资源。