Skip to content

Commit

Permalink
Feat: add container-ports trait for hostPort and hostIP (#6187)
Browse files Browse the repository at this point in the history
* Feat: add container-ports trait for hostPort and hostIP

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>

* Feat: add container-ports trait for hostPort and hostIP

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>

* Feat: add container-ports trait for hostPort and hostIP

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>

* Feat: add container-ports trait for hostPort and hostIP

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>

* Feat: add container-ports trait for hostPort and hostIP

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>

---------

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
  • Loading branch information
nuclearwu committed Jul 11, 2023
1 parent 2117554 commit b9f1cc9
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 0 deletions.
139 changes: 139 additions & 0 deletions charts/vela-core/templates/defwithtemplate/container-ports.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/container-ports.cue
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: Expose on the host and bind the external port to host to enable web traffic for your component.
name: container-ports
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
appliesToWorkloads:
- deployments.apps
- statefulsets.apps
- daemonsets.apps
- jobs.batch
podDisruptive: true
schematic:
cue:
template: |
import (
"strconv"
"strings"
)
#PatchParams: {
// +usage=Specify the name of the target container, if not set, use the component name
containerName: *"" | string
// +usage=Specify ports you want customer traffic sent to
ports: *[] | [...{
// +usage=Number of port to expose on the pod's IP address
containerPort: int
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
protocol: *"TCP" | "UDP" | "SCTP"
// +usage=Number of port to expose on the host
hostPort?: int
// +usage=What host IP to bind the external port to.
hostIP?: string
}]
}
PatchContainer: {
_params: #PatchParams
name: _params.containerName
_baseContainers: context.output.spec.template.spec.containers
_matchContainers_: [ for _container_ in _baseContainers if _container_.name == name {_container_}]
_baseContainer: *_|_ | {...}
if len(_matchContainers_) == 0 {
err: "container \(name) not found"
}
if len(_matchContainers_) > 0 {
_baseContainer: _matchContainers_[0]
_basePorts: _baseContainer.ports
if _basePorts == _|_ {
// +patchStrategy=replace
ports: [ for port in _params.ports {
containerPort: port.containerPort
protocol: port.protocol
if port.hostPort != _|_ {
hostPort: port.hostPort
}
if port.hostIP != _|_ {
hostIP: port.hostIP
}
}]
}
if _basePorts != _|_ {
_basePortsMap: {for _basePort in _basePorts {(strings.ToLower(_basePort.protocol) + strconv.FormatInt(_basePort.containerPort, 10)): _basePort}}
_portsMap: {for port in _params.ports {(strings.ToLower(port.protocol) + strconv.FormatInt(port.containerPort, 10)): port}}
// +patchStrategy=replace
ports: [ for portVar in _basePorts {
containerPort: portVar.containerPort
protocol: portVar.protocol
_uniqueKey: strings.ToLower(portVar.protocol) + strconv.FormatInt(portVar.containerPort, 10)
if _portsMap[_uniqueKey] != _|_ {
if _portsMap[_uniqueKey].hostPort != _|_ {
hostPort: _portsMap[_uniqueKey].hostPort
}
if _portsMap[_uniqueKey].hostIP != _|_ {
hostIP: _portsMap[_uniqueKey].hostIP
}
}
if _portsMap[_uniqueKey] == _|_ {
if portVar.name != _|_ {
name: portVar.name
}
}
}] + [ for port in _params.ports if _basePortsMap[strings.ToLower(port.protocol)+strconv.FormatInt(port.containerPort, 10)] == _|_ {
if port.containerPort != _|_ {
containerPort: port.containerPort
}
if port.protocol != _|_ {
protocol: port.protocol
}
if port.hostPort != _|_ {
hostPort: port.hostPort
}
if port.hostIP != _|_ {
hostIP: port.hostIP
}
}]
}
}
}
patch: spec: template: spec: {
if parameter.containers == _|_ {
// +patchKey=name
containers: [{
PatchContainer & {_params: {
if parameter.containerName == "" {
containerName: context.name
}
if parameter.containerName != "" {
containerName: parameter.containerName
}
ports: parameter.ports
}}
}]
}
if parameter.containers != _|_ {
// +patchKey=name
containers: [ for c in parameter.containers {
if c.containerName == "" {
err: "container name must be set for containers"
}
if c.containerName != "" {
PatchContainer & {_params: c}
}
}]
}
}
parameter: *#PatchParams | close({
// +usage=Specify the container ports for multiple containers
containers: [...#PatchParams]
})
errs: [ for c in patch.spec.template.spec.containers if c.err != _|_ {c.err}]
36 changes: 36 additions & 0 deletions references/docgen/def-doc/trait/container-ports.eg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
It's used to define Pod networks directly. hostPort routes the container's port directly to the port on the scheduled node, so that you can access the Pod through the host's IP plus hostPort.
Don't specify a hostPort for a Pod unless it is absolutely necessary(run `DaemonSet` service). When you bind a Pod to a hostPort, it limits the number of places the Pod can be scheduled, because each <hostIP, hostPort, protocol> combination must be unique. If you don't specify the hostIP and protocol explicitly, Kubernetes will use 0.0.0.0 as the default hostIP and TCP as the default protocol.
If you explicitly need to expose a Pod's port on the node, consider using `expose` or `gateway` trait, or exposeType and ports parameter of `webservice` component before resorting to `container-ports` trait.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: busybox
spec:
components:
- name: busybox
type: webservice
properties:
cpu: "0.5"
exposeType: ClusterIP
image: busybox
memory: 1024Mi
ports:
- expose: false
port: 80
protocol: TCP
- expose: false
port: 801
protocol: TCP
traits:
- type: container-ports
properties:
# you can use container-ports to control multiple containers by filling `containers`
# NOTE: in containers, you must set the container name for each container
containers:
- containerName: busybox
ports:
- containerPort: 80
protocol: TCP
hostPort: 8080
```
132 changes: 132 additions & 0 deletions vela-templates/definitions/internal/trait/container-ports.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import (
"strconv"
"strings"
)

"container-ports": {
type: "trait"
annotations: {}
labels: {}
description: "Expose on the host and bind the external port to host to enable web traffic for your component."
attributes: {
podDisruptive: true
appliesToWorkloads: ["deployments.apps", "statefulsets.apps", "daemonsets.apps", "jobs.batch"]
}
}

template: {
#PatchParams: {
// +usage=Specify the name of the target container, if not set, use the component name
containerName: *"" | string
// +usage=Specify ports you want customer traffic sent to
ports: *[] | [...{
// +usage=Number of port to expose on the pod's IP address
containerPort: int
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
protocol: *"TCP" | "UDP" | "SCTP"
// +usage=Number of port to expose on the host
hostPort?: int
// +usage=What host IP to bind the external port to.
hostIP?: string
}]
}

PatchContainer: {
_params: #PatchParams
name: _params.containerName
_baseContainers: context.output.spec.template.spec.containers
_matchContainers_: [ for _container_ in _baseContainers if _container_.name == name {_container_}]
_baseContainer: *_|_ | {...}
if len(_matchContainers_) == 0 {
err: "container \(name) not found"
}
if len(_matchContainers_) > 0 {
_baseContainer: _matchContainers_[0]
_basePorts: _baseContainer.ports
if _basePorts == _|_ {
// +patchStrategy=replace
ports: [ for port in _params.ports {
containerPort: port.containerPort
protocol: port.protocol
if port.hostPort != _|_ {
hostPort: port.hostPort
}
if port.hostIP != _|_ {
hostIP: port.hostIP
}
}]
}
if _basePorts != _|_ {
_basePortsMap: {for _basePort in _basePorts {(strings.ToLower(_basePort.protocol) + strconv.FormatInt(_basePort.containerPort, 10)): _basePort}}
_portsMap: {for port in _params.ports {(strings.ToLower(port.protocol) + strconv.FormatInt(port.containerPort, 10)): port}}
// +patchStrategy=replace
ports: [ for portVar in _basePorts {
containerPort: portVar.containerPort
protocol: portVar.protocol
_uniqueKey: strings.ToLower(portVar.protocol) + strconv.FormatInt(portVar.containerPort, 10)
if _portsMap[_uniqueKey] != _|_ {
if _portsMap[_uniqueKey].hostPort != _|_ {
hostPort: _portsMap[_uniqueKey].hostPort
}
if _portsMap[_uniqueKey].hostIP != _|_ {
hostIP: _portsMap[_uniqueKey].hostIP
}
}
if _portsMap[_uniqueKey] == _|_ {
if portVar.name != _|_ {
name: portVar.name
}
}
}] + [ for port in _params.ports if _basePortsMap[strings.ToLower(port.protocol)+strconv.FormatInt(port.containerPort, 10)] == _|_ {
if port.containerPort != _|_ {
containerPort: port.containerPort
}
if port.protocol != _|_ {
protocol: port.protocol
}
if port.hostPort != _|_ {
hostPort: port.hostPort
}
if port.hostIP != _|_ {
hostIP: port.hostIP
}
}]
}
}
}

patch: spec: template: spec: {
if parameter.containers == _|_ {
// +patchKey=name
containers: [{
PatchContainer & {_params: {
if parameter.containerName == "" {
containerName: context.name
}
if parameter.containerName != "" {
containerName: parameter.containerName
}
ports: parameter.ports
}}
}]
}
if parameter.containers != _|_ {
// +patchKey=name
containers: [ for c in parameter.containers {
if c.containerName == "" {
err: "container name must be set for containers"
}
if c.containerName != "" {
PatchContainer & {_params: c}
}
}]
}
}

parameter: *#PatchParams | close({
// +usage=Specify the container ports for multiple containers
containers: [...#PatchParams]
})

errs: [ for c in patch.spec.template.spec.containers if c.err != _|_ {c.err}]
}

0 comments on commit b9f1cc9

Please sign in to comment.