소스 검색

Merge pull request #1196 from gtardif/kube_expose_LoadBalancer

Support exposing ports and cross-service communication
Guillaume Tardif 4 년 전
부모
커밋
6215445b8a
5개의 변경된 파일143개의 추가작업 그리고 46개의 파일을 삭제
  1. 3 0
      .github/labeler.yml
  2. 35 42
      kube/charts/kubernetes/kube.go
  3. 87 0
      kube/charts/kubernetes/kube_test.go
  4. 13 0
      kube/e2e/kube-simple-demo/demo_sentences.yaml
  5. 5 4
      local/compose/labels.go

+ 3 - 0
.github/labeler.yml

@@ -7,6 +7,9 @@ ecs:
 local:
   - local/**/*
 
+kube:
+  - kube/**/*
+
 cli:
   - cli/**/*
 

+ 35 - 42
kube/charts/kubernetes/kube.go

@@ -25,6 +25,7 @@ import (
 	"time"
 
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/compose-cli/api/compose"
 	apps "k8s.io/api/apps/v1"
 	core "k8s.io/api/core/v1"
 	resource "k8s.io/apimachinery/pkg/api/resource"
@@ -33,6 +34,10 @@ import (
 	"k8s.io/apimachinery/pkg/util/intstr"
 )
 
+const (
+	clusterIPHeadless = "None"
+)
+
 //MapToKubernetesObjects maps compose project to Kubernetes objects
 func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object, error) {
 	objects := map[string]runtime.Object{}
@@ -46,13 +51,13 @@ func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object,
 		}
 
 		if service.Deploy != nil && service.Deploy.Mode == "global" {
-			daemonset, err := mapToDaemonset(project, service, project.Name)
+			daemonset, err := mapToDaemonset(project, service)
 			if err != nil {
 				return nil, err
 			}
 			objects[fmt.Sprintf("%s-daemonset.yaml", service.Name)] = daemonset
 		} else {
-			deployment, err := mapToDeployment(project, service, project.Name)
+			deployment, err := mapToDeployment(project, service)
 			if err != nil {
 				return nil, err
 			}
@@ -61,7 +66,7 @@ func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object,
 		for _, vol := range service.Volumes {
 			if vol.Type == "volume" {
 				vol.Source = strings.ReplaceAll(vol.Source, "_", "-")
-				objects[fmt.Sprintf("%s-persistentvolumeclaim.yaml", vol.Source)] = mapToPVC(service, vol)
+				objects[fmt.Sprintf("%s-persistentvolumeclaim.yaml", vol.Source)] = mapToPVC(project, service, vol)
 			}
 		}
 	}
@@ -70,7 +75,12 @@ func MapToKubernetesObjects(project *types.Project) (map[string]runtime.Object,
 
 func mapToService(project *types.Project, service types.ServiceConfig) *core.Service {
 	ports := []core.ServicePort{}
+	serviceType := core.ServiceTypeClusterIP
+	clusterIP := ""
 	for _, p := range service.Ports {
+		if p.Published != 0 {
+			serviceType = core.ServiceTypeLoadBalancer
+		}
 		ports = append(ports,
 			core.ServicePort{
 				Name:       fmt.Sprintf("%d-%s", p.Target, strings.ToLower(p.Protocol)),
@@ -79,8 +89,8 @@ func mapToService(project *types.Project, service types.ServiceConfig) *core.Ser
 				Protocol:   toProtocol(p.Protocol),
 			})
 	}
-	if len(ports) == 0 {
-		return nil
+	if len(ports) == 0 { // headless service
+		clusterIP = clusterIPHeadless
 	}
 	return &core.Service{
 		TypeMeta: meta.TypeMeta{
@@ -91,46 +101,25 @@ func mapToService(project *types.Project, service types.ServiceConfig) *core.Ser
 			Name: service.Name,
 		},
 		Spec: core.ServiceSpec{
-			Selector: map[string]string{"com.docker.compose.service": service.Name},
-			Ports:    ports,
-			Type:     mapServiceToServiceType(project, service),
+			ClusterIP: clusterIP,
+			Selector:  selectorLabels(project.Name, service.Name),
+			Ports:     ports,
+			Type:      serviceType,
 		},
 	}
 }
 
-func mapServiceToServiceType(project *types.Project, service types.ServiceConfig) core.ServiceType {
-	serviceType := core.ServiceTypeClusterIP
-	if len(service.Networks) == 0 {
-		// service is implicitly attached to "default" network
-		serviceType = core.ServiceTypeLoadBalancer
-	}
-	for name := range service.Networks {
-		if !project.Networks[name].Internal {
-			serviceType = core.ServiceTypeLoadBalancer
-		}
-	}
-	for _, port := range service.Ports {
-		if port.Published != 0 {
-			serviceType = core.ServiceTypeNodePort
-		}
-	}
-	return serviceType
-}
-
-func mapToDeployment(project *types.Project, service types.ServiceConfig, name string) (*apps.Deployment, error) {
-	labels := map[string]string{
-		"com.docker.compose.service": service.Name,
-		"com.docker.compose.project": name,
-	}
-	podTemplate, err := toPodTemplate(project, service, labels)
-	if err != nil {
-		return nil, err
-	}
+func mapToDeployment(project *types.Project, service types.ServiceConfig) (*apps.Deployment, error) {
+	labels := selectorLabels(project.Name, service.Name)
 	selector := new(meta.LabelSelector)
 	selector.MatchLabels = make(map[string]string)
 	for key, val := range labels {
 		selector.MatchLabels[key] = val
 	}
+	podTemplate, err := toPodTemplate(project, service, labels)
+	if err != nil {
+		return nil, err
+	}
 	return &apps.Deployment{
 		TypeMeta: meta.TypeMeta{
 			Kind:       "Deployment",
@@ -149,11 +138,15 @@ func mapToDeployment(project *types.Project, service types.ServiceConfig, name s
 	}, nil
 }
 
-func mapToDaemonset(project *types.Project, service types.ServiceConfig, name string) (*apps.DaemonSet, error) {
-	labels := map[string]string{
-		"com.docker.compose.service": service.Name,
-		"com.docker.compose.project": name,
+func selectorLabels(projectName string, serviceName string) map[string]string {
+	return map[string]string{
+		compose.ProjectTag: projectName,
+		compose.ServiceTag: serviceName,
 	}
+}
+
+func mapToDaemonset(project *types.Project, service types.ServiceConfig) (*apps.DaemonSet, error) {
+	labels := selectorLabels(project.Name, service.Name)
 	podTemplate, err := toPodTemplate(project, service, labels)
 	if err != nil {
 		return nil, err
@@ -196,7 +189,7 @@ func toDeploymentStrategy(deploy *types.DeployConfig) apps.DeploymentStrategy {
 	}
 }
 
-func mapToPVC(service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object {
+func mapToPVC(project *types.Project, service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object {
 	rwaccess := core.ReadWriteOnce
 	if vol.ReadOnly {
 		rwaccess = core.ReadOnlyMany
@@ -208,7 +201,7 @@ func mapToPVC(service types.ServiceConfig, vol types.ServiceVolumeConfig) runtim
 		},
 		ObjectMeta: meta.ObjectMeta{
 			Name:   vol.Source,
-			Labels: map[string]string{"com.docker.compose.service": service.Name},
+			Labels: selectorLabels(project.Name, service.Name),
 		},
 		Spec: core.PersistentVolumeClaimSpec{
 			VolumeName:  vol.Source,

+ 87 - 0
kube/charts/kubernetes/kube_test.go

@@ -0,0 +1,87 @@
+// +build kube
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package kubernetes
+
+import (
+	"testing"
+
+	"gotest.tools/v3/assert"
+
+	core "k8s.io/api/core/v1"
+	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+func TestServiceWithExposedPort(t *testing.T) {
+	model, err := loadYAML(`
+services:
+  nginx:
+    image: nginx
+    ports:
+      - "80:80"
+`)
+	assert.NilError(t, err)
+
+	service := mapToService(model, model.Services[0])
+	assert.DeepEqual(t, *service, core.Service{
+		TypeMeta: meta.TypeMeta{
+			Kind:       "Service",
+			APIVersion: "v1",
+		},
+		ObjectMeta: meta.ObjectMeta{
+			Name: "nginx",
+		},
+		Spec: core.ServiceSpec{
+			Selector: map[string]string{"com.docker.compose.service": "nginx", "com.docker.compose.project": ""},
+			Ports: []core.ServicePort{
+				{
+					Name:       "80-tcp",
+					Port:       int32(80),
+					TargetPort: intstr.FromInt(int(80)),
+					Protocol:   core.ProtocolTCP,
+				},
+			},
+			Type: core.ServiceTypeLoadBalancer,
+		}})
+}
+
+func TestServiceWithoutExposedPort(t *testing.T) {
+	model, err := loadYAML(`
+services:
+  nginx:
+    image: nginx
+`)
+	assert.NilError(t, err)
+
+	service := mapToService(model, model.Services[0])
+	assert.DeepEqual(t, *service, core.Service{
+		TypeMeta: meta.TypeMeta{
+			Kind:       "Service",
+			APIVersion: "v1",
+		},
+		ObjectMeta: meta.ObjectMeta{
+			Name: "nginx",
+		},
+		Spec: core.ServiceSpec{
+			Selector:  map[string]string{"com.docker.compose.service": "nginx", "com.docker.compose.project": ""},
+			ClusterIP: "None",
+			Ports:     []core.ServicePort{},
+			Type:      core.ServiceTypeClusterIP,
+		}})
+}

+ 13 - 0
kube/e2e/kube-simple-demo/demo_sentences.yaml

@@ -0,0 +1,13 @@
+services:
+  db:
+    build: aci-demo/db
+    image: gtardif/sentences-db
+
+  words:
+    build: aci-demo/words
+    image: gtardif/sentences-api
+  web:
+    build: aci-demo/web
+    image: gtardif/sentences-web
+    ports:
+      - "80:80"

+ 5 - 4
local/compose/labels.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"fmt"
 
+	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/docker/api/types/filters"
 )
 
@@ -26,14 +27,14 @@ const (
 	containerNumberLabel = "com.docker.compose.container-number"
 	oneoffLabel          = "com.docker.compose.oneoff"
 	slugLabel            = "com.docker.compose.slug"
-	projectLabel         = "com.docker.compose.project"
-	volumeLabel          = "com.docker.compose.volume"
+	projectLabel         = compose.ProjectTag
+	volumeLabel          = compose.VolumeTag
 	workingDirLabel      = "com.docker.compose.project.working_dir"
 	configFilesLabel     = "com.docker.compose.project.config_files"
-	serviceLabel         = "com.docker.compose.service"
+	serviceLabel         = compose.ServiceTag
 	versionLabel         = "com.docker.compose.version"
 	configHashLabel      = "com.docker.compose.config-hash"
-	networkLabel         = "com.docker.compose.network"
+	networkLabel         = compose.NetworkTag
 
 	//ComposeVersion Compose version
 	ComposeVersion = "1.0-alpha"