From b9f1cc97a9aaca0107aa98ce0916314c7d817bd8 Mon Sep 17 00:00:00 2001 From: JohnJan Date: Tue, 11 Jul 2023 18:44:05 +0800 Subject: [PATCH] Feat: add container-ports trait for hostPort and hostIP (#6187) * Feat: add container-ports trait for hostPort and hostIP Signed-off-by: wuzhongjian * Feat: add container-ports trait for hostPort and hostIP Signed-off-by: wuzhongjian * Feat: add container-ports trait for hostPort and hostIP Signed-off-by: wuzhongjian * Feat: add container-ports trait for hostPort and hostIP Signed-off-by: wuzhongjian * Feat: add container-ports trait for hostPort and hostIP Signed-off-by: wuzhongjian --------- Signed-off-by: wuzhongjian --- .../defwithtemplate/container-ports.yaml | 139 ++++++++++++++++++ .../def-doc/trait/container-ports.eg.md | 36 +++++ .../internal/trait/container-ports.cue | 132 +++++++++++++++++ 3 files changed, 307 insertions(+) create mode 100644 charts/vela-core/templates/defwithtemplate/container-ports.yaml create mode 100644 references/docgen/def-doc/trait/container-ports.eg.md create mode 100644 vela-templates/definitions/internal/trait/container-ports.cue diff --git a/charts/vela-core/templates/defwithtemplate/container-ports.yaml b/charts/vela-core/templates/defwithtemplate/container-ports.yaml new file mode 100644 index 00000000000..f3b0d0d923b --- /dev/null +++ b/charts/vela-core/templates/defwithtemplate/container-ports.yaml @@ -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}] + diff --git a/references/docgen/def-doc/trait/container-ports.eg.md b/references/docgen/def-doc/trait/container-ports.eg.md new file mode 100644 index 00000000000..da00d019ed8 --- /dev/null +++ b/references/docgen/def-doc/trait/container-ports.eg.md @@ -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 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 +``` diff --git a/vela-templates/definitions/internal/trait/container-ports.cue b/vela-templates/definitions/internal/trait/container-ports.cue new file mode 100644 index 00000000000..843ca52a63a --- /dev/null +++ b/vela-templates/definitions/internal/trait/container-ports.cue @@ -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}] +}