Skip to content

Commit

Permalink
feat: support external traffic policy
Browse files Browse the repository at this point in the history
Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>
  • Loading branch information
TessaIO committed Feb 13, 2023
1 parent f2aeb70 commit 6366876
Show file tree
Hide file tree
Showing 18 changed files with 650 additions and 6 deletions.
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: local | kubernetes service external traffic policy |

**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).
The possible values are: `cluster` (default), `local`, and `''`.
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

0 comments on commit 6366876

Please sign in to comment.