kubernetes clinet-go 開發(一)
kubernetes目前提供兩種方式來建立所需要的pod,service,replicationcontroller等,一種是通過kubectl create -f ,一種通過http 的restful 介面,由於工作項目的原因,需要根據實際的業務需求來定製化的開發k8s的api,我所用到的庫是官方給出的,程式碼程式庫地址:https://github.com/kubernetes/client-go,以下是我在開發時的一些思路整理,由於水平有限,講得不好,還請廣大博友諒解。 初始化串連 代碼解讀 建立namespace 建立pod 建立replicationController 建立service 初始化串連 方式一:
直接上代碼:
package mainimport ( "fmt" "time" "k8s.io/client-go/1.4/kubernetes" "k8s.io/client-go/1.4/pkg/api" "k8s.io/client-go/1.4/rest")func main() { // creates the in-cluster config config, err := rest.InClusterConfig() if err != nil { panic(err.Error()) } // creates the clientset clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) }}
代碼解讀
以上代碼就是擷取一個kubernetes叢集的client
config, err := rest.InClusterConfig()
本行代碼是初始化一個預設的k8s的rest.Config,該config源碼如下:
type Config struct {// Host must be a host string, a host:port pair, or a URL to the base of the apiserver.// If a URL is given then the (optional) Path of that URL represents a prefix that must// be appended to all request URIs used to access the apiserver. This allows a frontend// proxy to easily relocate all of the apiserver endpoints.Host string// APIPath is a sub-path that points to an API root.APIPath string// Prefix is the sub path of the server. If not specified, the client will set// a default value. Use "/" to indicate the server root should be usedPrefix string// ContentConfig contains settings that affect how objects are transformed when// sent to the server.ContentConfig// Server requires Basic authenticationUsername stringPassword string// Server requires Bearer authentication. This client will not attempt to use// refresh tokens for an OAuth2 flow.// TODO: demonstrate an OAuth2 compatible client.BearerToken string// Impersonate is the username that this RESTClient will impersonateImpersonate string// Server requires plugin-specified authentication.AuthProvider *clientcmdapi.AuthProviderConfig// Callback to persist config for AuthProvider.AuthConfigPersister AuthProviderConfigPersister// TLSClientConfig contains settings to enable transport layer securityTLSClientConfig// Server should be accessed without verifying the TLS// certificate. For testing only.Insecure bool// UserAgent is an optional field that specifies the caller of this request.UserAgent string// Transport may be used for custom HTTP behavior. This attribute may not// be specified with the TLS client certificate options. Use WrapTransport// for most client level operations.Transport http.RoundTripper// WrapTransport will be invoked for custom HTTP behavior after the underlying// transport is initialized (either the transport created from TLSClientConfig,// Transport, or http.DefaultTransport). The config may layer other RoundTrippers// on top of the returned RoundTripper.WrapTransport func(rt http.RoundTripper) http.RoundTripper// QPS indicates the maximum QPS to the master from this client.// If it's zero, the created RESTClient will use DefaultQPS: 5QPS float32// Maximum burst for throttle.// If it's zero, the created RESTClient will use DefaultBurst: 10.Burst int// Rate limiter for limiting connections to the master from this client. If present overwrites QPS/BurstRateLimiter flowcontrol.RateLimiter// Version forces a specific version to be used (if registered)// Do we need this?// Version string
}
該config包含了串連apiserver需要的一些資訊,我包括api的串連ip port(即hostname),用於認證用的一些資訊,如:username,password等,以及api調用傳回值的類型,序列化等。
回到InClusterConfig()這個方法中:
func InClusterConfig() (*Config, error) {host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")if len(host) == 0 || len(port) == 0 { return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")}token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountTokenKey)if err != nil { return nil, err}tlsClientConfig := TLSClientConfig{}rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountRootCAKeyif _, err := crypto.CertPoolFromFile(rootCAFile); err != nil { glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)} else { tlsClientConfig.CAFile = rootCAFile}return &Config{ // TODO: switch to using cluster DNS. Host: "https://" + net.JoinHostPort(host, port), BearerToken: string(token), TLSClientConfig: tlsClientConfig,}, nil}
首先就是擷取KUBERNETES_SERVICE_HOST 和 KUBERNETES_SERVICE_PORT這連個環境變數,所以必須要設定這兩個環境變數(就是apiserver的ip和port),然後是擷取/var/run/secrets/kubernetes.io/serviceaccount/下的認證檔案,進行config的初始化,從而為下一步的串連做準備。如果在相應的環境中沒有相應的ca檔案,則該方法會報錯,初始化k8s的client不成功 方式二:
var ( kubeconfig = flag.String("kubeconfig", "./config", "absolute path to the kubeconfig file"))func main() { flag.Parse() // uses the current context in kubeconfig config, err := clientcmd.BuildConfigFromFlags("183.131.19.231:8080", *kubeconfig) if err != nil { panic(err.Error()) } // creates the clientset clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) }}
其中config設定檔資訊如下:
apiVersion: v1clusters:- cluster: api-version: v1 server: http://183.131.19.231:8080 name: k8s-clustercontexts:- context: cluster: k8s-server user: myself name: default-contextcurrent-context: default-contextkind: Configpreferences: colors: trueusers:- name: myself user: password: admin username: admin11232
這種方式是通過設定檔的方式去連結kube-apiserver,從而擷取clientset,設定檔的設定以及擷取可以參考官方文檔:http://kubernetes.io/docs/user-guide/kubeconfig-file/ 建立namespace
首先講建立namesapce的原因是因為後續的pod,replicationController,service等的建立,在通常的業務中都會和namesapce相關聯,namespace其實就相當於租戶的概念,或者就相當於group,起到資源隔離的作用。
首先來看看client.Core()這個方法中包含了那些介面以及介面的實現:
func (c *CoreClient) ComponentStatuses() ComponentStatusInterface { return newComponentStatuses(c)}func (c *CoreClient) ConfigMaps(namespace string) ConfigMapInterface { return newConfigMaps(c, namespace)}func (c *CoreClient) Endpoints(namespace string) EndpointsInterface { return newEndpoints(c, namespace)}func (c *CoreClient) Events(namespace string) EventInterface { return newEvents(c, namespace)}func (c *CoreClient) LimitRanges(namespace string) LimitRangeInterface { return newLimitRanges(c, namespace)}func (c *CoreClient) Namespaces() NamespaceInterface { return newNamespaces(c)}func (c *CoreClient) Nodes() NodeInterface { return newNodes(c)}func (c *CoreClient) PersistentVolumes() PersistentVolumeInterface { return newPersistentVolumes(c)}func (c *CoreClient) PersistentVolumeClaims(namespace string) PersistentVolumeClaimInterface { return newPersistentVolumeClaims(c, namespace)}func (c *CoreClient) Pods(namespace string) PodInterface { return newPods(c, namespace)}func (c *CoreClient) PodTemplates(namespace string) PodTemplateInterface { return newPodTemplates(c, namespace)}func (c *CoreClient) ReplicationControllers(namespace string) ReplicationControllerInterface { return newReplicationControllers(c, namespace)}func (c *CoreClient) ResourceQuotas(namespace string) ResourceQuotaInterface { return newResourceQuotas(c, namespace)}func (c *CoreClient) Secrets(namespace string) SecretInterface { return newSecrets(c, namespace)}func (c *CoreClient) Services(namespace string) ServiceInterface { return newServices(c, namespace)}func (c *CoreClient) ServiceAccounts(namespace string) ServiceAccountInterface { return newServiceAccounts(c, namespace)}
其中我們這裡主要用到的是
func (c *CoreClient) Namespaces() NamespaceInterface { return newNamespaces(c)}
在看NamespaceInterface 這個介面中的方法:
type NamespaceInterface interface { Create(*v1.Namespace) (*v1.Namespace, error) Update(*v1.Namespace) (*v1.Namespace, error) UpdateStatus(*v1.Namespace) (*v1.Namespace, error) Delete(name string, options *api.DeleteOptions) error DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error Get(name string) (*v1.Namespace, error) List(opts api.ListOptions) (*v1.NamespaceList, error) Watch(opts api.ListOptions) (watch.Interface, error) Patch(name string, pt api.PatchType, data []byte, subresources ...string) (result *v1.Namespace, err error) NamespaceExpansion}
這裡我們主要講解namespace的建立:
建立namespace需要傳入v1.Namespace這個struct的指標,我們在看看這個strutc的結構:
type Namespace struct { unversioned.TypeMeta `json:",inline"` // Standard object's metadata. // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec defines the behavior of the Namespace. // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status Spec NamespaceSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` // Status describes the current status of a Namespace. // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status Status NamespaceStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`}
其中的三個struct是於yaml中的類型,中繼資料,還有space是一一對應的。
//create a namespace nc := new(v1.Namespace) ncTypeMeta := unversioned.TypeMeta{Kind: "NameSpace", APIVersion: "v1"} nc.TypeMeta = ncTypeMeta nc.ObjectMeta = v1.ObjectMeta{ Name: "k8s-test", } nc.Spec = v1.NamespaceSpec{}
這個其實就相當於yaml檔案中做如下定義:
apiVersion: v1kind: NameSpacemetadata: name: "k8s-test"spec:
如果有lable,space之類的設定,查閱相關代碼加上即可。
之後建立namespace:
resultnc, err := dao.Clientset.Core().Namespaces().Create(nc)
穿件成功完成之後會返回建立的namespace,如果失敗,會返回相應的錯誤資訊。
之後在k8s叢集中通過kubectl命令查看建立的namespace,建立成功。同理還有刪除修改等操作,這裡就不在一一示範了。 建立pod
建立pod以及rc,service的方式和namaspace的方式基本上是一致的,所以我就直接貼代碼:
pod:=new(v1.Pod) pod.TypeMeta=unversioned.TypeMeta{Kind: "Pod", APIVersion: "v1"} pod.ObjectMeta=v1.ObjectMeta{Name: app.Name, Namespace: app.UserName, Labels: map[string]string{"name": app.Name}} pod.Spec=v1.PodSpec{ RestartPolicy: v1.RestartPolicyAlways, Containers: []v1.Container{ v1.Container{ Name: app.Name, Image: app.Image, Ports: []v1.ContainerPort{ v1.ContainerPort{ ContainerPort: 9080, Protocol: v1.ProtocolTCP, }, }, Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse(app.Cpu), v1.ResourceMemory: resource.MustParse(app.Memory), }, }, }, }, } result, err := dao.Clientset.Core().Pods(NameSpace).Create(pod)
建立replicationController
//create a replicationController rc := new(v1.ReplicationController) rcTypeMeta := unversioned.TypeMeta{Kind: "ReplicationController", APIVersion: "v1"} rc.TypeMeta = rcTypeMeta rcObjectMeta := v1.ObjectMeta{Name: app.Name, Namespace: app.UserName, Labels: map[string]string{"name": app.Name}} rc.ObjectMeta = rcObjectMeta rcSpec := v1.ReplicationControllerSpec{ Replicas: &app.InstanceCount, Selector: map[string]string{ "name": app.Name, }, Template: &v1.PodTemplateSpec{ v1.ObjectMeta{ Name: app.Name, Namespace: app.UserName, Labels: map[string]string{ "name": app.Name, }, }, v1.PodSpec{ RestartPolicy: v1.RestartPolicyAlways, Containers: []v1.Container{ v1.Container{ Name: app.Name, Image: app.Image, Ports: []v1.ContainerPort{ v1.ContainerPort{ ContainerPort: 9080, Protocol: v1.ProtocolTCP, }, }, Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceCPU: resource.MustParse(app.Cpu), v1.ResourceMemory: resource.MustParse(app.Memory), }, }, }, }, }, }, } rc.Spec = rcSpec result, err := dao.Clientset.Core().ReplicationControllers(NameSpace).Create(rc)
建立service
//create service service := new(v1.Service) svTypemeta := unversioned.TypeMeta{Kind: "Service", APIVersion: "v1"} service.TypeMeta = svTypemeta svObjectMeta := v1.ObjectMeta{Name: app.Name, Namespace: app.UserName, Labels: map[string]string{"name": app.Name}} service.ObjectMeta = svObjectMeta svServiceSpec := v1.ServiceSpec{ Ports: []v1.ServicePort{ v1.ServicePort{ Name: app.Name, Port: 9080, TargetPort: intstr.FromInt(9080), Protocol: "TCP", // NodePort: 32107, }, }, Selector: map[string]string{"name": app.Name}, Type: v1.ServiceTypeNodePort, // LoadBalancerIP: "172.17.11.2", // Status: v1.ServiceStatus{ // LoadBalancer: v1.LoadBalancerStatus{ // Ingress: []v1.LoadBalancerIngress{ // v1.LoadBalancerIngress{IP: "172.17.11.2"}, // }, // }, // }, } service.Spec = svServiceSpec _, err := dao.Clientset.Core().Services(NameSpace).Create(service)