mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-07 04:03:20 +00:00
Merge pull request #5977 from smarterclayton/allow_expose_to_handle_other_resources
Allow `kubectl expose` to be polymorphic to the source of the selector
This commit is contained in:
@@ -7,11 +7,11 @@ Take a replicated application and expose it as Kubernetes Service
|
|||||||
|
|
||||||
Take a replicated application and expose it as Kubernetes Service.
|
Take a replicated application and expose it as Kubernetes Service.
|
||||||
|
|
||||||
Looks up a ReplicationController by name, and uses the selector for that replication controller
|
Looks up a replication controller or service by name and uses the selector for that resource as the
|
||||||
as the selector for a new Service on the specified port.
|
selector for a new Service on the specified port.
|
||||||
|
|
||||||
```
|
```
|
||||||
kubectl expose NAME --port=port [--protocol=TCP|UDP] [--container-port=number-or-name] [--service-name=name] [--public-ip=ip] [--create-external-load-balancer=bool]
|
kubectl expose RESOURCE NAME --port=port [--protocol=TCP|UDP] [--container-port=number-or-name] [--service-name=name] [--public-ip=ip] [--create-external-load-balancer=bool]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
@@ -20,6 +20,9 @@ kubectl expose NAME --port=port [--protocol=TCP|UDP] [--container-port=number-or
|
|||||||
// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
|
// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
|
||||||
$ kubectl expose nginx --port=80 --container-port=8000
|
$ kubectl expose nginx --port=80 --container-port=8000
|
||||||
|
|
||||||
|
// Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https"
|
||||||
|
$ kubectl expose service nginx --port=443 --container-port=8443 --service-name=nginx-https
|
||||||
|
|
||||||
// Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.
|
// Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.
|
||||||
$ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream
|
$ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream
|
||||||
```
|
```
|
||||||
|
@@ -16,8 +16,8 @@ kubectl expose \- Take a replicated application and expose it as Kubernetes Serv
|
|||||||
Take a replicated application and expose it as Kubernetes Service.
|
Take a replicated application and expose it as Kubernetes Service.
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
Looks up a ReplicationController by name, and uses the selector for that replication controller
|
Looks up a replication controller or service by name and uses the selector for that resource as the
|
||||||
as the selector for a new Service on the specified port.
|
selector for a new Service on the specified port.
|
||||||
|
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
@@ -197,6 +197,9 @@ as the selector for a new Service on the specified port.
|
|||||||
// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
|
// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
|
||||||
$ kubectl expose nginx \-\-port=80 \-\-container\-port=8000
|
$ kubectl expose nginx \-\-port=80 \-\-container\-port=8000
|
||||||
|
|
||||||
|
// Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx\-https"
|
||||||
|
$ kubectl expose service nginx \-\-port=443 \-\-container\-port=8443 \-\-service\-name=nginx\-https
|
||||||
|
|
||||||
// Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video\-stream'.
|
// Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video\-stream'.
|
||||||
$ kubectl expose streamer \-\-port=4100 \-\-protocol=udp \-\-service\-name=video\-stream
|
$ kubectl expose streamer \-\-port=4100 \-\-protocol=udp \-\-service\-name=video\-stream
|
||||||
|
|
||||||
|
@@ -131,11 +131,13 @@ for version in "${kube_api_versions[@]}"; do
|
|||||||
labels_field="labels"
|
labels_field="labels"
|
||||||
service_selector_field="selector"
|
service_selector_field="selector"
|
||||||
rc_replicas_field="desiredState.replicas"
|
rc_replicas_field="desiredState.replicas"
|
||||||
|
port_field="port"
|
||||||
if [ "$version" = "v1beta3" ]; then
|
if [ "$version" = "v1beta3" ]; then
|
||||||
id_field="metadata.name"
|
id_field="metadata.name"
|
||||||
labels_field="metadata.labels"
|
labels_field="metadata.labels"
|
||||||
service_selector_field="spec.selector"
|
service_selector_field="spec.selector"
|
||||||
rc_replicas_field="spec.replicas"
|
rc_replicas_field="spec.replicas"
|
||||||
|
port_field="spec.port"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Passing no arguments to create is an error
|
# Passing no arguments to create is an error
|
||||||
@@ -475,6 +477,26 @@ __EOF__
|
|||||||
# Post-condition: 3 replicas
|
# Post-condition: 3 replicas
|
||||||
kube::test::get_object_assert 'rc frontend-controller' "{{.$rc_replicas_field}}" '3'
|
kube::test::get_object_assert 'rc frontend-controller' "{{.$rc_replicas_field}}" '3'
|
||||||
|
|
||||||
|
### Expose replication controller as service
|
||||||
|
# Pre-condition: 3 replicas
|
||||||
|
kube::test::get_object_assert 'rc frontend-controller' "{{.$rc_replicas_field}}" '3'
|
||||||
|
# Command
|
||||||
|
kubectl expose rc frontend-controller --port=80 "${kube_flags[@]}"
|
||||||
|
# Post-condition: service exists
|
||||||
|
kube::test::get_object_assert 'service frontend-controller' "{{.$port_field}}" '80'
|
||||||
|
# Command
|
||||||
|
kubectl expose service frontend-controller --port=443 --service-name=frontend-controller-2 "${kube_flags[@]}"
|
||||||
|
# Post-condition: service exists
|
||||||
|
kube::test::get_object_assert 'service frontend-controller-2' "{{.$port_field}}" '443'
|
||||||
|
# Command
|
||||||
|
kubectl create -f examples/limitrange/valid-pod.json "${kube_flags[@]}"
|
||||||
|
kubectl expose pod valid-pod --port=444 --service-name=frontend-controller-3 "${kube_flags[@]}"
|
||||||
|
# Post-condition: service exists
|
||||||
|
kube::test::get_object_assert 'service frontend-controller-3' "{{.$port_field}}" '444'
|
||||||
|
# Cleanup services
|
||||||
|
kubectl delete pod valid-pod "${kube_flags[@]}"
|
||||||
|
kubectl delete service frontend-controller{,-2,-3} "${kube_flags[@]}"
|
||||||
|
|
||||||
### Delete replication controller with id
|
### Delete replication controller with id
|
||||||
# Pre-condition: frontend replication controller is running
|
# Pre-condition: frontend replication controller is running
|
||||||
kube::test::get_object_assert rc "{{range.items}}{{.$id_field}}:{{end}}" 'frontend-controller:'
|
kube::test::get_object_assert rc "{{range.items}}{{.$id_field}}:{{end}}" 'frontend-controller:'
|
||||||
|
@@ -45,6 +45,8 @@ const (
|
|||||||
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types
|
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types
|
||||||
// of resources and different API sets.
|
// of resources and different API sets.
|
||||||
// TODO: make the functions interfaces
|
// TODO: make the functions interfaces
|
||||||
|
// TODO: pass the various interfaces on the factory directly into the command constructors (so the
|
||||||
|
// commands are decoupled from the factory).
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
clients *clientCache
|
clients *clientCache
|
||||||
flags *pflag.FlagSet
|
flags *pflag.FlagSet
|
||||||
@@ -66,6 +68,9 @@ type Factory struct {
|
|||||||
Resizer func(mapping *meta.RESTMapping) (kubectl.Resizer, error)
|
Resizer func(mapping *meta.RESTMapping) (kubectl.Resizer, error)
|
||||||
// Returns a Reaper for gracefully shutting down resources.
|
// Returns a Reaper for gracefully shutting down resources.
|
||||||
Reaper func(mapping *meta.RESTMapping) (kubectl.Reaper, error)
|
Reaper func(mapping *meta.RESTMapping) (kubectl.Reaper, error)
|
||||||
|
// PodSelectorForResource returns the pod selector associated with the provided resource name
|
||||||
|
// or an error.
|
||||||
|
PodSelectorForResource func(mapping *meta.RESTMapping, namespace, name string) (string, error)
|
||||||
// Returns a schema that can validate objects stored on disk.
|
// Returns a schema that can validate objects stored on disk.
|
||||||
Validator func() (validation.Schema, error)
|
Validator func() (validation.Schema, error)
|
||||||
// Returns the default namespace to use in cases where no other namespace is specified
|
// Returns the default namespace to use in cases where no other namespace is specified
|
||||||
@@ -128,6 +133,41 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
|
|||||||
Printer: func(mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) {
|
Printer: func(mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) {
|
||||||
return kubectl.NewHumanReadablePrinter(noHeaders), nil
|
return kubectl.NewHumanReadablePrinter(noHeaders), nil
|
||||||
},
|
},
|
||||||
|
PodSelectorForResource: func(mapping *meta.RESTMapping, namespace, name string) (string, error) {
|
||||||
|
// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
|
||||||
|
client, err := clients.ClientForVersion("")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch mapping.Kind {
|
||||||
|
case "ReplicationController":
|
||||||
|
rc, err := client.ReplicationControllers(namespace).Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return kubectl.MakeLabels(rc.Spec.Selector), nil
|
||||||
|
case "Pod":
|
||||||
|
rc, err := client.Pods(namespace).Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(rc.Labels) == 0 {
|
||||||
|
return "", fmt.Errorf("the pod has no labels and cannot be exposed")
|
||||||
|
}
|
||||||
|
return kubectl.MakeLabels(rc.Labels), nil
|
||||||
|
case "Service":
|
||||||
|
rc, err := client.ReplicationControllers(namespace).Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if rc.Spec.Selector == nil {
|
||||||
|
return "", fmt.Errorf("the service has no pod selector set")
|
||||||
|
}
|
||||||
|
return kubectl.MakeLabels(rc.Spec.Selector), nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("it is not possible to get a pod selector from %s", mapping.Kind)
|
||||||
|
}
|
||||||
|
},
|
||||||
Resizer: func(mapping *meta.RESTMapping) (kubectl.Resizer, error) {
|
Resizer: func(mapping *meta.RESTMapping) (kubectl.Resizer, error) {
|
||||||
client, err := clients.ClientForVersion(mapping.APIVersion)
|
client, err := clients.ClientForVersion(mapping.APIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -29,19 +29,22 @@ import (
|
|||||||
const (
|
const (
|
||||||
expose_long = `Take a replicated application and expose it as Kubernetes Service.
|
expose_long = `Take a replicated application and expose it as Kubernetes Service.
|
||||||
|
|
||||||
Looks up a ReplicationController by name, and uses the selector for that replication controller
|
Looks up a replication controller or service by name and uses the selector for that resource as the
|
||||||
as the selector for a new Service on the specified port.`
|
selector for a new Service on the specified port.`
|
||||||
|
|
||||||
expose_example = `// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
|
expose_example = `// Creates a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000.
|
||||||
$ kubectl expose nginx --port=80 --container-port=8000
|
$ kubectl expose nginx --port=80 --container-port=8000
|
||||||
|
|
||||||
|
// Creates a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https"
|
||||||
|
$ kubectl expose service nginx --port=443 --container-port=8443 --service-name=nginx-https
|
||||||
|
|
||||||
// Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.
|
// Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.
|
||||||
$ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream`
|
$ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream`
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command {
|
func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "expose NAME --port=port [--protocol=TCP|UDP] [--container-port=number-or-name] [--service-name=name] [--public-ip=ip] [--create-external-load-balancer=bool]",
|
Use: "expose RESOURCE NAME --port=port [--protocol=TCP|UDP] [--container-port=number-or-name] [--service-name=name] [--public-ip=ip] [--create-external-load-balancer=bool]",
|
||||||
Short: "Take a replicated application and expose it as Kubernetes Service",
|
Short: "Take a replicated application and expose it as Kubernetes Service",
|
||||||
Long: expose_long,
|
Long: expose_long,
|
||||||
Example: expose_example,
|
Example: expose_example,
|
||||||
@@ -66,8 +69,12 @@ func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
|
func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) error {
|
||||||
if len(args) != 1 {
|
var name, resource string
|
||||||
return util.UsageError(cmd, "<name> is required for expose")
|
switch l := len(args); {
|
||||||
|
case l == 2:
|
||||||
|
resource, name = args[0], args[1]
|
||||||
|
default:
|
||||||
|
return util.UsageError(cmd, "the type and name of a resource to expose are required arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace, err := f.DefaultNamespace()
|
namespace, err := f.DefaultNamespace()
|
||||||
@@ -83,7 +90,7 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err
|
|||||||
|
|
||||||
generator, found := kubectl.Generators[generatorName]
|
generator, found := kubectl.Generators[generatorName]
|
||||||
if !found {
|
if !found {
|
||||||
return util.UsageError(cmd, fmt.Sprintf("Generator: %s not found.", generator))
|
return util.UsageError(cmd, fmt.Sprintf("generator %q not found.", generator))
|
||||||
}
|
}
|
||||||
if util.GetFlagInt(cmd, "port") < 1 {
|
if util.GetFlagInt(cmd, "port") < 1 {
|
||||||
return util.UsageError(cmd, "--port is required and must be a positive integer.")
|
return util.UsageError(cmd, "--port is required and must be a positive integer.")
|
||||||
@@ -91,16 +98,25 @@ func RunExpose(f *Factory, out io.Writer, cmd *cobra.Command, args []string) err
|
|||||||
names := generator.ParamNames()
|
names := generator.ParamNames()
|
||||||
params := kubectl.MakeParams(cmd, names)
|
params := kubectl.MakeParams(cmd, names)
|
||||||
if len(util.GetFlagString(cmd, "service-name")) == 0 {
|
if len(util.GetFlagString(cmd, "service-name")) == 0 {
|
||||||
params["name"] = args[0]
|
params["name"] = name
|
||||||
} else {
|
} else {
|
||||||
params["name"] = util.GetFlagString(cmd, "service-name")
|
params["name"] = util.GetFlagString(cmd, "service-name")
|
||||||
}
|
}
|
||||||
if _, found := params["selector"]; !found {
|
if s, found := params["selector"]; !found || len(s) == 0 {
|
||||||
rc, err := client.ReplicationControllers(namespace).Get(args[0])
|
mapper, _ := f.Object()
|
||||||
|
v, k, err := mapper.VersionAndKindForResource(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
params["selector"] = kubectl.MakeLabels(rc.Spec.Selector)
|
mapping, err := mapper.RESTMapping(k, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s, err := f.PodSelectorForResource(mapping, namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
params["selector"] = s
|
||||||
}
|
}
|
||||||
if util.GetFlagBool(cmd, "create-external-load-balancer") {
|
if util.GetFlagBool(cmd, "create-external-load-balancer") {
|
||||||
params["create-external-load-balancer"] = "true"
|
params["create-external-load-balancer"] = "true"
|
||||||
|
Reference in New Issue
Block a user