Compare commits

..

3 Commits

Author SHA1 Message Date
Minghe
293481f081 release 0.7.3 (#311) 2019-10-12 21:41:49 +08:00
Minghe
c12d967ced fix typo (#310) 2019-10-12 21:09:06 +08:00
Minghe
b2a62cbd94 * create a deployment instead of create a pod directly (#308)
* remove host port expose to fix the port ocuppied issue
2019-10-12 20:41:02 +08:00
7 changed files with 257 additions and 59 deletions

View File

@@ -101,7 +101,7 @@ jobs:
echo "workflow is running on branch ${GITHUB_REF}" echo "workflow is running on branch ${GITHUB_REF}"
if [[ ${GITHUB_REF} == "refs/heads/production" ]];then if [[ ${GITHUB_REF} == "refs/heads/master" ]];then
version=${version}-alpha.${commit} version=${version}-alpha.${commit}
echo "alpha release $version" echo "alpha release $version"
elif [[ "${GITHUB_REF}" == *--auto-release ]];then elif [[ "${GITHUB_REF}" == *--auto-release ]];then

View File

@@ -0,0 +1,67 @@
package kubernetes
import (
"github.com/metrue/fx/constants"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func generateDeploymentSpec(
name string,
image string,
replicas int32,
selector map[string]string,
) *appsv1.Deployment {
container := apiv1.Container{
Name: "fx-placeholder-container-name",
Image: image,
Ports: []apiv1.ContainerPort{
apiv1.ContainerPort{
Name: "fx-container",
ContainerPort: constants.FxContainerExposePort,
},
},
}
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: selector,
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: selector,
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{container},
},
},
},
}
}
// GetDeployment get a deployment
func (k *K8S) GetDeployment(namespace string, name string) (*appsv1.Deployment, error) {
return k.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
}
// CreateDeployment create a deployment
func (k *K8S) CreateDeployment(namespace string, name string, image string, replicas int32, selector map[string]string) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, image, replicas, selector)
return k.AppsV1().Deployments(namespace).Create(deployment)
}
// UpdateDeployment update a deployment
func (k *K8S) UpdateDeployment(namespace string, name string, image string, replicas int32, selector map[string]string) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, image, replicas, selector)
return k.AppsV1().Deployments(namespace).Update(deployment)
}
// DeleteDeployment delete a deployment
func (k *K8S) DeleteDeployment(namespace string, name string) error {
return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
}

View File

@@ -0,0 +1,49 @@
package kubernetes
import (
"os"
"testing"
)
func TestDeployment(t *testing.T) {
namespace := "default"
name := "fx-hello-world"
image := "metrue/kube-hello"
selector := map[string]string{
"app": "fx-app",
}
kubeconfig := os.Getenv("KUBECONFIG")
if kubeconfig == "" {
t.Skip("skip test since no KUBECONFIG given in environment variable")
}
k8s, err := Create()
if err != nil {
t.Fatal(err)
}
if _, err := k8s.GetDeployment(namespace, name); err == nil {
t.Fatalf("should get not found error")
}
replicas := int32(2)
deployment, err := k8s.CreateDeployment(namespace, name, image, replicas, selector)
if err != nil {
t.Fatal(err)
}
if deployment == nil {
t.Fatalf("deploymetn should not be %v", nil)
}
if deployment.Name != name {
t.Fatalf("should get %s but got %s", name, deployment.Name)
}
if *deployment.Spec.Replicas != replicas {
t.Fatalf("should get %v but got %v", replicas, deployment.Spec.Replicas)
}
if err := k8s.DeleteDeployment(namespace, name); err != nil {
t.Fatal(err)
}
}

View File

@@ -60,17 +60,27 @@ func (k *K8S) Deploy(
// By using a label selector between Pod and Service, we can link Service and Pod directly, it means a Endpoint will // By using a label selector between Pod and Service, we can link Service and Pod directly, it means a Endpoint will
// be created automatically, then incoming traffic to Service will be forward to Pod. // be created automatically, then incoming traffic to Service will be forward to Pod.
// Then we have no need to create Endpoint manually anymore. // Then we have no need to create Endpoint manually anymore.
labels := map[string]string{ selector := map[string]string{
"fx-app": "fx-app-" + uuid.New().String(), "app": "fx-app-" + uuid.New().String(),
} }
if _, err := k.CreatePod(
const replicas = int32(3)
if _, err := k.GetDeployment(namespace, name); err != nil {
// TODO enable passing replica from fx CLI
if _, err := k.CreateDeployment(
namespace, namespace,
name, name,
image, image,
labels, replicas,
selector,
); err != nil { ); err != nil {
return err return err
} }
} else {
if _, err := k.UpdateDeployment(namespace, name, image, replicas, selector); err != nil {
return err
}
}
// TODO fx should be able to know what's the target Kubernetes service platform // TODO fx should be able to know what's the target Kubernetes service platform
// it's going to deploy to // it's going to deploy to
@@ -79,15 +89,28 @@ func (k *K8S) Deploy(
if !isOnPublicCloud { if !isOnPublicCloud {
typ = "NodePort" typ = "NodePort"
} }
if _, err := k.GetService(namespace, name); err != nil {
if _, err := k.CreateService( if _, err := k.CreateService(
namespace, namespace,
name, name,
typ, typ,
ports, ports,
labels, selector,
); err != nil { ); err != nil {
return err return err
} }
} else {
if _, err := k.UpdateService(
namespace,
name,
typ,
ports,
selector,
); err != nil {
return err
}
}
return nil return nil
} }
@@ -102,7 +125,7 @@ func (k *K8S) Destroy(ctx context.Context, name string) error {
if err := k.DeleteService(namespace, name); err != nil { if err := k.DeleteService(namespace, name); err != nil {
return err return err
} }
if err := k.DeletePod(namespace, name); err != nil { if err := k.DeleteDeployment(namespace, name); err != nil {
return err return err
} }
return nil return nil

View File

@@ -1,56 +1,67 @@
package kubernetes package kubernetes
import ( import (
"strconv"
"github.com/metrue/fx/constants" "github.com/metrue/fx/constants"
v1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
intstr "k8s.io/apimachinery/pkg/util/intstr" intstr "k8s.io/apimachinery/pkg/util/intstr"
) )
func generateServiceSpec(
namespace string,
name string,
typ string,
ports []int32,
selector map[string]string,
) *apiv1.Service {
servicePorts := []apiv1.ServicePort{
apiv1.ServicePort{
Name: "http",
Protocol: apiv1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
},
apiv1.ServicePort{
Name: "https",
Protocol: apiv1.ProtocolTCP,
Port: 443,
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
},
}
// Append custom Port
for index, port := range ports {
servicePorts = append(servicePorts, apiv1.ServicePort{
Name: "custom-port-" + strconv.Itoa(index),
Protocol: apiv1.ProtocolTCP,
Port: port,
TargetPort: intstr.FromInt(int(3000)),
})
}
return &apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
ClusterName: namespace,
},
Spec: apiv1.ServiceSpec{
Ports: servicePorts,
Type: apiv1.ServiceType(typ),
Selector: selector,
},
}
}
// CreateService create a service // CreateService create a service
func (k *K8S) CreateService( func (k *K8S) CreateService(
namespace string, namespace string,
name string, name string,
typ string, typ string,
ports []int32, ports []int32,
podsLabels map[string]string, selector map[string]string,
) (*v1.Service, error) { ) (*apiv1.Service, error) {
servicePorts := []v1.ServicePort{ service := generateServiceSpec(namespace, name, typ, ports, selector)
v1.ServicePort{
Name: "http",
Protocol: v1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
},
v1.ServicePort{
Name: "https",
Protocol: v1.ProtocolTCP,
Port: 443,
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
},
}
// Append custom Port
for _, port := range ports {
servicePorts = append(servicePorts, v1.ServicePort{
Name: "custom",
Protocol: v1.ProtocolTCP,
Port: port,
TargetPort: intstr.FromInt(int(3000)),
})
}
service := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
ClusterName: namespace,
},
Spec: v1.ServiceSpec{
Ports: servicePorts,
Type: v1.ServiceType(typ),
Selector: podsLabels,
},
}
createdService, err := k.CoreV1().Services(namespace).Create(service) createdService, err := k.CoreV1().Services(namespace).Create(service)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -59,9 +70,32 @@ func (k *K8S) CreateService(
return createdService, nil return createdService, nil
} }
// UpdateService update a service
// TODO this method is not perfect yet, should refactor later
func (k *K8S) UpdateService(
namespace string,
name string,
typ string,
ports []int32,
selector map[string]string,
) (*apiv1.Service, error) {
svc, err := k.GetService(namespace, name)
if err != nil {
return nil, err
}
svc.Spec.Selector = selector
svc.Spec.Type = apiv1.ServiceType(typ)
return k.CoreV1().Services(namespace).Update(svc)
}
// DeleteService a service // DeleteService a service
func (k *K8S) DeleteService(namespace string, name string) error { func (k *K8S) DeleteService(namespace string, name string) error {
// TODO figure out the elegant way to delete a service // TODO figure out the elegant way to delete a service
options := &metav1.DeleteOptions{} options := &metav1.DeleteOptions{}
return k.CoreV1().Services(namespace).Delete(name, options) return k.CoreV1().Services(namespace).Delete(name, options)
} }
// GetService get a service
func (k *K8S) GetService(namespace string, name string) (*apiv1.Service, error) {
return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
}

View File

@@ -2,6 +2,7 @@ package kubernetes
import ( import (
"os" "os"
"reflect"
"testing" "testing"
) )
@@ -49,6 +50,10 @@ func TestK8S(t *testing.T) {
} }
serviceName := podName + "-svc" serviceName := podName + "-svc"
if _, err := k8s.GetService(namespace, serviceName); err == nil {
t.Fatalf("should get no service name %s", serviceName)
}
svc, err := k8s.CreateService(namespace, serviceName, "NodePort", ports, labels) svc, err := k8s.CreateService(namespace, serviceName, "NodePort", ports, labels)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -56,6 +61,26 @@ func TestK8S(t *testing.T) {
if svc.Name != serviceName { if svc.Name != serviceName {
t.Fatalf("should get %s but got %s", serviceName, svc.Name) t.Fatalf("should get %s but got %s", serviceName, svc.Name)
} }
svc, err = k8s.GetService(namespace, serviceName)
if err != nil {
t.Fatal(err)
}
if svc.Name != serviceName {
t.Fatalf("should get %s but got %v", serviceName, svc.Name)
}
selector := map[string]string{"hello": "world"}
svc, err = k8s.UpdateService(namespace, serviceName, "NodePort", ports, selector)
if err != nil {
t.Fatal(err)
}
if svc.Name != serviceName {
t.Fatalf("should get %s but got %v", serviceName, svc.Name)
}
if !reflect.DeepEqual(svc.Spec.Selector, selector) {
t.Fatalf("should get %v but got %v", selector, svc.Spec.Selector)
}
// TODO check service status // TODO check service status
if err := k8s.DeleteService(namespace, serviceName); err != nil { if err := k8s.DeleteService(namespace, serviceName); err != nil {
t.Fatal(err) t.Fatal(err)

2
fx.go
View File

@@ -27,7 +27,7 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Name = "fx" app.Name = "fx"
app.Usage = "makes function as a service" app.Usage = "makes function as a service"
app.Version = "0.7.2" app.Version = "0.7.3"
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{ {