Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat support external traffic policy #1544

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ The currently supported options are:
| kompose.service.healthcheck.liveness.http_get_path | kubernetes liveness httpGet path |
| kompose.service.healthcheck.liveness.http_get_port | kubernetes liveness httpGet port |
| kompose.service.healthcheck.liveness.tcp_port | kubernetes liveness tcpSocket port |
| kompose.service.external-traffic-policy | 'cluster', 'local', '' | |

**Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail.

Expand Down Expand Up @@ -411,6 +412,25 @@ services:

- `kompose.service.healthcheck.readiness` defines Kubernetes [readiness](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes)

- `kompose.service.external-traffic-policy` defines Kubernetes Service [external traffic policy.](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip).

For example:

```yaml
version: "3.3"

services:
front-end:
image: gcr.io/google-samples/gb-frontend:v4
environment:
- GET_HOSTS_FROM=dns
ports:
- 80:80
labels:
kompose.service.expose: lb
kompose.service.external-traffic-policy: local
kompose.service.type: loadbalancer
```
## Restart

If you want to create normal pods without controller you can use `restart` construct of docker-compose to define that. Follow table below to see what happens on the `restart` value.
Expand Down
1 change: 1 addition & 0 deletions pkg/kobject/kobject.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ type ServiceConfig struct {
User string `compose:"user"`
VolumesFrom []string `compose:"volumes_from"`
ServiceType string `compose:"kompose.service.type"`
ServiceExternalTrafficPolicy string `compose:"kompose.service.external-traffic-policy"`
NodePortPort int32 `compose:"kompose.service.nodeport.port"`
StopGracePeriod string `compose:"stop_grace_period"`
Build string `compose:"build"`
Expand Down
7 changes: 7 additions & 0 deletions pkg/loader/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,13 @@ func parseKomposeLabels(labels map[string]string, serviceConfig *kobject.Service
}

serviceConfig.ServiceType = serviceType
case LabelServiceExternalTrafficPolicy:
serviceExternalTypeTrafficPolicy, err := handleServiceExternalTrafficPolicy(value)
if err != nil {
return errors.Wrap(err, "handleServiceExternalTrafficPolicy failed")
}

serviceConfig.ServiceExternalTrafficPolicy = serviceExternalTypeTrafficPolicy
case LabelServiceExpose:
serviceConfig.ExposeService = strings.Trim(strings.ToLower(value), " ,")
case LabelNodePortPort:
Expand Down
13 changes: 13 additions & 0 deletions pkg/loader/compose/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
const (
// LabelServiceType defines the type of service to be created
LabelServiceType = "kompose.service.type"
// LabelServiceExternalTrafficPolicy defines the external policy traffic of service to be created
LabelServiceExternalTrafficPolicy = "kompose.service.external-traffic-policy"
// LabelServiceGroup defines the group of services in a single pod
LabelServiceGroup = "kompose.service.group"
// LabelNodePortPort defines the port value for NodePort service
Expand Down Expand Up @@ -151,6 +153,17 @@ func handleServiceType(ServiceType string) (string, error) {
}
}

func handleServiceExternalTrafficPolicy(ServiceExternalTrafficPolicyType string) (string, error) {
switch strings.ToLower(ServiceExternalTrafficPolicyType) {
case "", "cluster":
return string(api.ServiceExternalTrafficPolicyTypeCluster), nil
case "local":
return string(api.ServiceExternalTrafficPolicyTypeLocal), nil
default:
return "", errors.New("Unknown value " + ServiceExternalTrafficPolicyType + " , supported values are 'local, cluster'")
}
}

func normalizeContainerNames(svcName string) string {
return strings.ToLower(svcName)
}
Expand Down
7 changes: 7 additions & 0 deletions pkg/transformer/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,7 @@ func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.Servi
if service.ServiceType == "LoadBalancer" {
svcs := k.CreateLBService(name, service)
for _, svc := range svcs {
svc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(service.ServiceExternalTrafficPolicy)
*objects = append(*objects, svc)
}
if len(svcs) > 1 {
Expand All @@ -1368,11 +1369,17 @@ func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.Servi
if service.ExposeService != "" {
*objects = append(*objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
}
if service.ServiceExternalTrafficPolicy != "" && svc.Spec.Type != api.ServiceTypeNodePort {
log.Warningf("External Traffic Policy is ignored for the service %v of type %v", name, service.ServiceType)
}
}
} else {
if service.ServiceType == "Headless" {
svc := k.CreateHeadlessService(name, service)
*objects = append(*objects, svc)
if service.ServiceExternalTrafficPolicy != "" {
log.Warningf("External Traffic Policy is ignored for the service %v of type Headless", name)
}
} else {
log.Warnf("Service %q won't be created because 'ports' is not specified", service.Name)
}
Expand Down
34 changes: 34 additions & 0 deletions pkg/transformer/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ func newKomposeObjectHostPortProtocolConfig() kobject.ServiceConfig {
}
}

func newServiceConfigWithExternalTrafficPolicy() kobject.ServiceConfig {
loadBalancerServiceType := string(api.ServiceTypeLoadBalancer)
return kobject.ServiceConfig{
Name: "app",
Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456}},
ServiceType: loadBalancerServiceType,
ServiceExternalTrafficPolicy: "local",
}
}

func equalStringSlice(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
Expand Down Expand Up @@ -985,3 +995,27 @@ func TestCreateHostPortAndProtocol(t *testing.T) {
}
}
}

func TestServiceExternalTrafficPolicy(t *testing.T) {
groupName := "pod_group"
komposeObject := kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfigWithExternalTrafficPolicy()},
}
k := Kubernetes{}
objs, err := k.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName})
if err != nil {
t.Error(errors.Wrap(err, "k.Transform failed"))
}
for _, obj := range objs {
if service, ok := obj.(*api.Service); ok {
serviceExternalTrafficPolicy := string(service.Spec.ExternalTrafficPolicy)
if serviceExternalTrafficPolicy != strings.ToLower(string(api.ServiceExternalTrafficPolicyTypeLocal)) {
t.Errorf("Expected Local as external lifecycle policy, got %v", serviceExternalTrafficPolicy)
}
serviceType := service.Spec.Type
if serviceType != api.ServiceTypeLoadBalancer {
t.Errorf("Expected LoadBalancer as service type, got %v", serviceType)
}
}
}
}
7 changes: 7 additions & 0 deletions pkg/transformer/openshift/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
if service.ServiceType == "LoadBalancer" {
svcs := o.CreateLBService(name, service)
for _, svc := range svcs {
svc.Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyType(service.ServiceExternalTrafficPolicy)
objects = append(objects, svc)
}
if len(svcs) > 1 {
Expand All @@ -398,10 +399,16 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
if service.ExposeService != "" {
objects = append(objects, o.initRoute(name, service, svc.Spec.Ports[0].Port))
}
if service.ServiceExternalTrafficPolicy != "" && svc.Spec.Type != corev1.ServiceTypeNodePort {
log.Warningf("External Traffic Policy is ignored for the service %v of type %v", name, service.ServiceType)
}
}
} else if service.ServiceType == "Headless" {
svc := o.CreateHeadlessService(name, service)
objects = append(objects, svc)
if service.ServiceExternalTrafficPolicy != "" {
log.Warningf("External Traffic Policy is ignored for the service %v of type Headless", name)
}
}

err := o.UpdateKubernetesObjects(name, service, opt, &objects)
Expand Down
35 changes: 35 additions & 0 deletions pkg/transformer/openshift/openshift_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"

"github.com/kubernetes/kompose/pkg/kobject"
Expand Down Expand Up @@ -56,6 +57,16 @@ func newServiceConfig() kobject.ServiceConfig {
}
}

func newServiceConfigWithExternalTrafficPolicy() kobject.ServiceConfig {
loadBalancerServiceType := string(corev1.ServiceTypeLoadBalancer)
return kobject.ServiceConfig{
Name: "app",
Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456}},
ServiceType: loadBalancerServiceType,
ServiceExternalTrafficPolicy: "local",
}
}

func TestOpenShiftUpdateKubernetesObjects(t *testing.T) {
t.Log("Test case: Testing o.UpdateKubernetesObjects()")
var object []runtime.Object
Expand Down Expand Up @@ -425,3 +436,27 @@ func TestRecreateStrategyWithVolumesPresent(t *testing.T) {
}
}
}

func TestServiceExternalTrafficPolicy(t *testing.T) {
groupName := "pod_group"
komposeObject := kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfigWithExternalTrafficPolicy()},
}
o := OpenShift{}
objs, err := o.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName})
if err != nil {
t.Error(errors.Wrap(err, "k.Transform failed"))
}
for _, obj := range objs {
if service, ok := obj.(*corev1.Service); ok {
serviceExternalTrafficPolicy := string(service.Spec.ExternalTrafficPolicy)
if serviceExternalTrafficPolicy != strings.ToLower(string(corev1.ServiceExternalTrafficPolicyTypeLocal)) {
t.Errorf("Expected Local as external lifecycle policy, got %v", serviceExternalTrafficPolicy)
}
serviceType := service.Spec.Type
if serviceType != corev1.ServiceTypeLoadBalancer {
t.Errorf("Expected LoadBalancer as service type, got %v", serviceType)
}
}
}
}
15 changes: 15 additions & 0 deletions script/test/cmd/tests_new.sh
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,18 @@ k8s_output="$KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/output-k8s.yam
os_output="$KOMPOSE_ROOT/script/test/fixtures/host-port-protocol/output-os.yaml"
convert::expect_success_and_warning "$k8s_cmd" "$k8s_output"
convert::expect_success "$os_cmd" "$os_output"

# Test external traffic policy feature with valid configuration, warning is coming from the network policy.
k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml convert --stdout --with-kompose-annotation=false"
k8s_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml"
os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v1.yaml convert --stdout --with-kompose-annotation=false"
os_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v1.yaml"
convert::expect_success_and_warning "$k8s_cmd" "$k8s_output"
convert::expect_success "$os_cmd" "$os_output"
# Test external traffic policy feature with warning, because we have nodeport with external traffic policy
k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml convert --stdout --with-kompose-annotation=false"
k8s_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-k8s-v2.yaml"
os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/docker-compose-v2.yaml convert --stdout --with-kompose-annotation=false"
os_output="$KOMPOSE_ROOT/script/test/fixtures/external-traffic-policy/output-os-v2.yaml"
convert::expect_success_and_warning "$k8s_cmd" "$k8s_output"
convert::expect_success_and_warning "$os_cmd" "$os_output"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: "3.3"

services:
front-end:
image: gcr.io/google-samples/gb-frontend:v4
environment:
- GET_HOSTS_FROM=dns
ports:
- 80:80
labels:
kompose.service.expose: lb
kompose.service.expose.ingress-class-name: nginx
kompose.service.external-traffic-policy: local
kompose.service.type: loadbalancer
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: "3.3"

services:
front-end:
image: gcr.io/google-samples/gb-frontend:v4
environment:
- GET_HOSTS_FROM=dns
ports:
- 80:80
labels:
kompose.service.expose: lb
kompose.service.expose.ingress-class-name: nginx
kompose.service.external-traffic-policy: local
kompose.service.type: headless
85 changes: 85 additions & 0 deletions script/test/fixtures/external-traffic-policy/output-k8s-v1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
apiVersion: v1
kind: Service
metadata:
annotations:
kompose.service.expose: lb
kompose.service.expose.ingress-class-name: nginx
kompose.service.external-traffic-policy: local
kompose.service.type: loadbalancer
creationTimestamp: null
labels:
io.kompose.service: front-end-tcp
name: front-end-tcp
spec:
externalTrafficPolicy: Local
ports:
- name: "80"
port: 80
targetPort: 80
selector:
io.kompose.service: front-end
type: LoadBalancer
status:
loadBalancer: {}

---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.service.expose: lb
kompose.service.expose.ingress-class-name: nginx
kompose.service.external-traffic-policy: local
kompose.service.type: loadbalancer
creationTimestamp: null
labels:
io.kompose.service: front-end
name: front-end
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: front-end
strategy: {}
template:
metadata:
annotations:
kompose.service.expose: lb
kompose.service.expose.ingress-class-name: nginx
kompose.service.external-traffic-policy: local
kompose.service.type: loadbalancer
creationTimestamp: null
labels:
io.kompose.network/external-traffic-policy-default: "true"
io.kompose.service: front-end
spec:
containers:
- env:
- name: GET_HOSTS_FROM
value: dns
image: gcr.io/google-samples/gb-frontend:v4
name: front-end
ports:
- containerPort: 80
hostPort: 80
protocol: TCP
resources: {}
restartPolicy: Always
status: {}

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
creationTimestamp: null
name: external-traffic-policy-default
spec:
ingress:
- from:
- podSelector:
matchLabels:
io.kompose.network/external-traffic-policy-default: "true"
podSelector:
matchLabels:
io.kompose.network/external-traffic-policy-default: "true"
Loading