Compare commits
3 Commits
0.7.2-alph
...
0.7.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
293481f081 | ||
|
|
c12d967ced | ||
|
|
b2a62cbd94 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -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
|
||||||
|
|||||||
67
deploy/kubernetes/deployment.go
Normal file
67
deploy/kubernetes/deployment.go
Normal 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{})
|
||||||
|
}
|
||||||
49
deploy/kubernetes/dpeloyment_test.go
Normal file
49
deploy/kubernetes/dpeloyment_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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{})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
2
fx.go
@@ -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{
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user