diff --git a/docs/kubectl.md b/docs/kubectl.md index 3423bd87464..61d82e1649b 100644 --- a/docs/kubectl.md +++ b/docs/kubectl.md @@ -394,7 +394,11 @@ Additional help topics: kubectl rollingupdate Perform a rolling update of the given ReplicationController kubectl resize Set a new size for a resizable resource (currently only Replication Controllers) kubectl run-container Run a particular image on the cluster. +<<<<<<< HEAD kubectl stop Gracefully shutdown a resource +======= + kubectl expose Take a replicated application and expose it as Kubernetes Service +>>>>>>> Add a service generator and a command to easily expose services. Use "kubectl help [command]" for more information about that command. ``` @@ -899,7 +903,7 @@ Examples: Usage: ``` - kubectl run-container --image= [--replicas=replicas] [--dry-run=] [--overrides=] [flags] + kubectl run-container --image= [--port=] [--replicas=replicas] [--dry-run=] [--overrides=] [flags] Available Flags: --alsologtostderr=false: log to standard error as well as files @@ -911,12 +915,11 @@ Usage: --cluster="": The name of the kubeconfig cluster to use --context="": The name of the kubeconfig context to use --dry-run=false: If true, only print the object that would be sent, don't actually do anything - --generator="run-container/v1": The name of the api generator that you want to use. Default 'run-container-controller-v1' + --generator="run-container/v1": The name of the api generator that you want to use. Default 'run-container-controller/v1' -h, --help=false: help for run-container --image="": The image for the container you wish to run. --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. --kubeconfig="": Path to the kubeconfig file to use for CLI requests. - -l, --labels="": Labels to apply to the pod(s) created by this call to run. --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes @@ -928,6 +931,8 @@ Usage: -o, --output="": Output format: json|yaml|template|templatefile --output-version="": Output the formatted object with the given version (default api-version) --overrides="": An inline JSON override for the generated object. If this is non-empty, it is parsed used to override the generated object. Requires that the object supply a valid apiVersion field. + --port=-1: The port that this container exposes. + --public-ip="": A public IP address to use for this service. -r, --replicas=1: Number of replicas to create for this container. Default 1 -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr @@ -940,6 +945,7 @@ Usage: ``` +<<<<<<< HEAD #### stop Gracefully shutdown a resource @@ -949,11 +955,29 @@ If the resource is resizable it will be resized to 0 before deletion. Examples: $ kubectl stop replicationcontroller foo foo stopped +======= +#### expose +Take a replicated application and expose it as Kubernetes Service. + +Looks up a ReplicationController named , and uses the selector for that replication controller +as the selector for a new Service which services on + +Examples: +$ kubectl expose nginx --port=80 --container-port=8000 + + +$ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream + +>>>>>>> Add a service generator and a command to easily expose services. Usage: ``` +<<<<<<< HEAD kubectl stop [flags] +======= + kubectl expose --port= [--protocol=TCP|UDP] [--container-port=] [--service-name=] [--public-ip=] [--create-external-load-balancer] [flags] +>>>>>>> Add a service generator and a command to easily expose services. Available Flags: --alsologtostderr=false: log to standard error as well as files @@ -963,18 +987,45 @@ Usage: --client-certificate="": Path to a client key file for TLS. --client-key="": Path to a client key file for TLS. --cluster="": The name of the kubeconfig cluster to use +<<<<<<< HEAD --context="": The name of the kubeconfig context to use --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. --kubeconfig="": Path to the kubeconfig file to use for CLI requests. +======= + --container-port="": Name or number for the port on the container that the service should direct traffic to. Optional. + --context="": The name of the kubeconfig context to use + --create-external-load-balancer=false: If true, create an external load balancer for this service. Implementation is cloud provider dependent. Default false + --dry-run=false: If true, only print the object that would be sent, don't actually do anything + --generator="service/v1": The name of the api generator that you want to use. Default 'service/v1' + -h, --help=false: help for expose + --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + --kubeconfig="": Path to the kubeconfig file to use for CLI requests. + -l, --labels="": Labels to apply to the pod(s) created by this call to run. +>>>>>>> Add a service generator and a command to easily expose services. --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace --log_dir=: If non-empty, write log files in this directory --log_flush_frequency=5s: Maximum number of seconds between log flushes --logtostderr=true: log to standard error instead of files --match-server-version=false: Require server version to match client version +<<<<<<< HEAD --namespace="": If present, the namespace scope for this CLI request. --ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests. -s, --server="": The address of the Kubernetes API server --stderrthreshold=2: logs at or above this threshold go to stderr +======= + -n, --namespace="": If present, the namespace scope for this CLI request. + --no-headers=false: When using the default output, don't print headers + --ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests. + -o, --output="": Output format: json|yaml|template|templatefile + --output-version="": Output the formatted object with the given version (default api-version) + --overrides="": An inline JSON override for the generated object. If this is non-empty, it is parsed used to override the generated object. Requires that the object supply a valid apiVersion field. + --port=-1: The port that the service should serve on. Required. + --protocol="TCP": The network protocol for the service you want to be created. Default 'tcp' + -s, --server="": The address of the Kubernetes API server + --service-name="": The name for the newly create service. + --stderrthreshold=2: logs at or above this threshold go to stderr + -t, --template="": Template string or path to template file to use when -o=template or -o=templatefile. +>>>>>>> Add a service generator and a command to easily expose services. --token="": Bearer token for authentication to the API server. --user="": The name of the kubeconfig user to use --v=0: log level for V logs diff --git a/pkg/api/types.go b/pkg/api/types.go index a800631a8cd..8202b1e68e1 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -654,7 +654,6 @@ type ServiceSpec struct { PublicIPs []string `json:"publicIPs,omitempty"` // ContainerPort is the name of the port on the container to direct traffic to. - // Optional, if unspecified use the first port on the container. ContainerPort util.IntOrString `json:"containerPort,omitempty"` // Optional: Supports "ClientIP" and "None". Used to maintain session affinity. diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 2a94c6efc35..1d18063e872 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -212,6 +212,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, cmds.AddCommand(f.NewCmdRunContainer(out)) cmds.AddCommand(f.NewCmdStop(out)) + cmds.AddCommand(f.NewCmdExposeService(out)) return cmds } diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go new file mode 100644 index 00000000000..10ef362e539 --- /dev/null +++ b/pkg/kubectl/cmd/expose.go @@ -0,0 +1,108 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "io" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" + "github.com/spf13/cobra" +) + +func (f *Factory) NewCmdExposeService(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "expose --port= [--protocol=TCP|UDP] [--container-port=] [--service-name=] [--public-ip=] [--create-external-load-balancer]", + Short: "Take a replicated application and expose it as Kubernetes Service", + Long: `Take a replicated application and expose it as Kubernetes Service. + +Looks up a ReplicationController named , and uses the selector for that replication controller +as the selector for a new Service which services on + +Examples: +$ kubectl expose nginx --port=80 --container-port=8000 + + +$ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream + +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + usageError(cmd, " is required for expose") + } + + namespace := GetKubeNamespace(cmd) + client, err := f.Client(cmd) + checkErr(err) + + rc, err := client.ReplicationControllers(namespace).Get(args[0]) + checkErr(err) + + generatorName := GetFlagString(cmd, "generator") + generator, found := kubectl.Generators[generatorName] + if !found { + usageError(cmd, fmt.Sprintf("Generator: %s not found.", generator)) + } + if GetFlagInt(cmd, "port") < 1 { + usageError(cmd, "--port is required and must be a positive integer.") + } + names := generator.ParamNames() + params := kubectl.MakeParams(cmd, names) + if len(GetFlagString(cmd, "service-name")) == 0 { + params["name"] = args[0] + } else { + params["name"] = GetFlagString(cmd, "service-name") + } + params["labels"] = kubectl.MakeLabels(rc.Spec.Selector) + if GetFlagBool(cmd, "create-external-load-balancer") { + params["create-external-load-balancer"] = "true" + } + + err = kubectl.ValidateParams(names, params) + checkErr(err) + + service, err := generator.Generate(params) + checkErr(err) + + inline := GetFlagString(cmd, "overrides") + if len(inline) > 0 { + Merge(service, inline, "Service") + } + + // TODO: extract this flag to a central location, when such a location exists. + if !GetFlagBool(cmd, "dry-run") { + service, err = client.Services(namespace).Create(service.(*api.Service)) + checkErr(err) + } + + err = PrintObject(cmd, service, f, out) + checkErr(err) + }, + } + AddPrinterFlags(cmd) + cmd.Flags().String("generator", "service/v1", "The name of the api generator that you want to use. Default 'service/v1'") + cmd.Flags().String("protocol", "TCP", "The network protocol for the service you want to be created. Default 'tcp'") + cmd.Flags().Int("port", -1, "The port that the service should serve on. Required.") + cmd.Flags().Bool("create-external-load-balancer", false, "If true, create an external load balancer for this service. Implementation is cloud provider dependent. Default false") + cmd.Flags().StringP("labels", "l", "", "Labels to apply to the pod(s) created by this call to run.") + cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, don't actually do anything") + cmd.Flags().String("container-port", "", "Name or number for the port on the container that the service should direct traffic to. Optional.") + cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is parsed used to override the generated object. Requires that the object supply a valid apiVersion field.") + cmd.Flags().String("service-name", "", "The name for the newly create service.") + return cmd +} diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 05a2d9056ce..5a5b66dc885 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -27,7 +27,7 @@ import ( func (f *Factory) NewCmdRunContainer(out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "run-container --image= [--replicas=replicas] [--dry-run=] [--overrides=]", + Use: "run-container --image= [--port=] [--replicas=replicas] [--dry-run=] [--overrides=]", Short: "Run a particular image on the cluster.", Long: `Create and run a particular image, possibly replicated. Creates a replication controller to manage the created container(s) @@ -46,7 +46,7 @@ Examples: is required for run") + usageError(cmd, " is required for run-container") } namespace, err := f.DefaultNamespace(cmd) @@ -86,11 +86,12 @@ Examples: }, } AddPrinterFlags(cmd) - cmd.Flags().String("generator", "run-container/v1", "The name of the api generator that you want to use. Default 'run-container-controller-v1'") + cmd.Flags().String("generator", "run-container/v1", "The name of the api generator that you want to use. Default 'run-container-controller/v1'") cmd.Flags().String("image", "", "The image for the container you wish to run.") cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default 1") cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, don't actually do anything") - cmd.Flags().StringP("labels", "l", "", "Labels to apply to the pod(s) created by this call to run.") + cmd.Flags().String("public-ip", "", "A public IP address to use for this service.") cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is parsed used to override the generated object. Requires that the object supply a valid apiVersion field.") + cmd.Flags().Int("port", -1, "The port that this container exposes.") return cmd } diff --git a/pkg/kubectl/generate.go b/pkg/kubectl/generate.go index 1344c5efd01..f4fdc14046b 100644 --- a/pkg/kubectl/generate.go +++ b/pkg/kubectl/generate.go @@ -18,8 +18,10 @@ package kubectl import ( "fmt" + "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/golang/glog" "github.com/spf13/cobra" ) @@ -42,6 +44,7 @@ type Generator interface { // TODO: Dynamically create this from a list of template files? var Generators map[string]Generator = map[string]Generator{ "run-container/v1": BasicReplicationController{}, + "service/v1": ServiceGenerator{}, } // ValidateParams ensures that all required params are present in the params map @@ -68,3 +71,29 @@ func MakeParams(cmd *cobra.Command, params []GeneratorParam) map[string]string { } return result } + +func MakeLabels(labels map[string]string) string { + out := []string{} + for key, value := range labels { + out = append(out, fmt.Sprintf("%s=%s", key, value)) + } + return strings.Join(out, ",") +} + +// ParseLabels turns a string representation of a label set into a map[string]string +func ParseLabels(labelString string) map[string]string { + if len(labelString) == 0 { + return nil + } + labels := map[string]string{} + labelSpecs := strings.Split(labelString, ",") + for ix := range labelSpecs { + labelSpec := strings.Split(labelSpecs[ix], "=") + if len(labelSpec) != 2 { + glog.Errorf("unexpected label spec: %s", labelSpecs[ix]) + continue + } + labels[labelSpec[0]] = labelSpec[1] + } + return labels +} diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go index 46546cfe904..9bde1cf9713 100644 --- a/pkg/kubectl/run.go +++ b/pkg/kubectl/run.go @@ -18,11 +18,9 @@ package kubectl import ( "strconv" - "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" - "github.com/golang/glog" ) type BasicReplicationController struct{} @@ -33,6 +31,7 @@ func (BasicReplicationController) ParamNames() []GeneratorParam { {"name", true}, {"replicas", true}, {"image", true}, + {"port", false}, } } @@ -74,23 +73,13 @@ func (BasicReplicationController) Generate(params map[string]string) (runtime.Ob }, }, } + port, err := strconv.Atoi(params["port"]) + if err != nil { + controller.Spec.Template.Spec.Containers[0].Ports = []api.Port{ + { + ContainerPort: port, + }, + } + } return &controller, nil } - -// TODO: extract this to a common location. -func ParseLabels(labelString string) map[string]string { - if len(labelString) == 0 { - return nil - } - labels := map[string]string{} - labelSpecs := strings.Split(labelString, ",") - for ix := range labelSpecs { - labelSpec := strings.Split(labelSpecs[ix], "=") - if len(labelSpec) != 2 { - glog.Errorf("unexpected label spec: %s", labelSpecs[ix]) - continue - } - labels[labelSpec[0]] = labelSpec[1] - } - return labels -} diff --git a/pkg/kubectl/service.go b/pkg/kubectl/service.go new file mode 100644 index 00000000000..29e3886d4d8 --- /dev/null +++ b/pkg/kubectl/service.go @@ -0,0 +1,85 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubectl + +import ( + "fmt" + "strconv" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +type ServiceGenerator struct{} + +func (ServiceGenerator) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"labels", true}, + {"port", true}, + {"public-ip", false}, + {"create-external-load-balancer", false}, + {"protocol", false}, + {"container-port", false}, + } +} + +func (ServiceGenerator) Generate(params map[string]string) (runtime.Object, error) { + labelString, found := params["labels"] + if !found || len(labelString) == 0 { + return nil, fmt.Errorf("'labels' is a required parameter.") + } + labels := ParseLabels(labelString) + name, found := params["name"] + if !found { + return nil, fmt.Errorf("'name' is a required parameter.") + } + port, err := strconv.Atoi(params["port"]) + if err != nil { + return nil, err + } + service := api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Labels: labels, + }, + Spec: api.ServiceSpec{ + Port: port, + Protocol: api.Protocol(params["protocol"]), + Selector: labels, + }, + } + containerPort, found := params["container-port"] + if found && len(containerPort) > 0 { + cPort, err := strconv.Atoi(containerPort) + if err != nil { + service.Spec.ContainerPort = util.NewIntOrStringFromInt(cPort) + } else { + service.Spec.ContainerPort = util.NewIntOrStringFromString(containerPort) + } + } else { + service.Spec.ContainerPort = util.NewIntOrStringFromInt(port) + } + if params["create-external-load-balancer"] == "true" { + service.Spec.CreateExternalLoadBalancer = true + } + if len(params["public-ip"]) == 0 { + service.Spec.PublicIPs = []string{params["public-ip"]} + } + return &service, nil +}