Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add support for Config{} to kubecfg #987

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion cmd/kubecfg/kubecfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import (
"text/template"
"time"

goyaml "gopkg.in/v1/yaml"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to name this nothing else is called yaml, afaik.


"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
kube_client "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
Expand Down Expand Up @@ -179,7 +181,7 @@ func main() {
}
method := flag.Arg(0)

matchFound := executeAPIRequest(method, client) || executeControllerRequest(method, client)
matchFound := executeAPIRequest(method, client) || executeControllerRequest(method, client) || executeConfigRequest(method, client)
if matchFound == false {
glog.Fatalf("Unknown command %s", method)
}
Expand Down Expand Up @@ -370,6 +372,63 @@ func executeControllerRequest(method string, c *kube_client.Client) bool {
return true
}

func executeConfigRequest(method string, c *kube_client.Client) bool {
if method != "apply" {
return false
}

data, err := ioutil.ReadFile(*config)
if err != nil {
glog.Fatalf("Unable to read %v: %v\n", *config, err)
}

obj := api.Config{}
if err := goyaml.Unmarshal(data, &obj); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use yaml directly. This must be plummed through api.Encode/Decode for versioning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to version the Config object itself? I thought it will be just a wrapper for kubecfg and it will not be exposed by the kubernetes API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The objects inside the config object absolutely have to be versioned on disk. This means that either you have to version the config object, or you have to turn it into something like an array of APIObjects, which I recommend anyway.

glog.Fatalf("Unable to parse config: %v", err)
}

for _, service := range obj.Services {
createObject(c, service)
}
for _, pod := range obj.Pods {
createObject(c, pod)
}
for _, replicationController := range obj.ReplicationControllers {
createObject(c, replicationController)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really need to think about ordering for something like this. Some pods may depend on some services. Can the pod handle it if it starts before its dependencies? Clearly the right answer is "yes it should", but only the human setting up the system knows this.

I suppose it's probably fine to say that this only works for pods where start order doesn't matter. The human can always sort their services/pods into batches and start each batch with this method. But we should explicitly say this somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also related to health-checking. How do you know that the service is 'ready'? For example the official mysql Docker image, when started it will first start the mysql to create users/init database and then it will stop and start to run the mysql daemon.

Other option is to assume that containers/images are resilient to service failure and so it will be the container responsibility to poll the services.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for now it is probably sufficient to start the services first (as Brian suggests below). Starting the service will cause the appropriate environment variables to get set, even if the service isn't live yet; users of the service would have to retry if it's not up yet.

}

return true
}

func createObject(c *kube_client.Client, obj interface{}) {
var path string

data, err := api.Encode(obj)
if err != nil {
glog.Fatalf("Error: %v", err)
}

switch obj.(type) {
case api.Service:
path = "/services"
case api.Pod:
path = "/pods"
case api.ReplicationController:
path = "/replicationControllers"
default:
glog.Fatalf("Error: Unknown object: %T", obj)
}

request := c.Verb("POST").
Path(path).
ParseSelectorParam("labels", *selector).
Body(data)

if _, err := request.Do().Get(); err != nil {
glog.Fatalf("Error: %v", err)
}
}

func humanReadablePrinter() *kubecfg.HumanReadablePrinter {
printer := kubecfg.NewHumanReadablePrinter()
// Add Handler calls here to support additional types
Expand Down
105 changes: 105 additions & 0 deletions examples/config/guestbook.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"services": [
{
"id": "frontend",
"kind": "Service",
"apiVersion": "v1beta1",
"port": 5432,
"selector": {
"name": "frontend"
}
},
{
"id": "redismaster",
"kind": "Service",
"apiVersion": "v1beta1",
"port": 10000,
"selector": {
"name": "redis-master"
}
},
{
"id": "redisslave",
"kind": "Service",
"apiVersion": "v1beta1",
"port": 10001,
"labels": {
"name": "redisslave"
},
"selector": {
"name": "redisslave"
}
}
],
"pods": [
{
"id": "redis-master-2",
"kind": "Pod",
"apiVersion": "v1beta1",
"desiredState": {
"manifest": {
"version": "v1beta1",
"id": "redis-master-2",
"containers": [{
"name": "master",
"image": "dockerfile/redis",
"ports": [{
"containerPort": 6379
}]
}]
}
},
"labels": {
"name": "redis-master"
}
}
],
"replicationControllers": [
{
"id": "frontendController",
"kind": "ReplicationController",
"apiVersion": "v1beta1",
"desiredState": {
"replicas": 3,
"replicaSelector": {"name": "frontend"},
"podTemplate": {
"desiredState": {
"manifest": {
"version": "v1beta1",
"id": "frontendController",
"containers": [{
"name": "php-redis",
"image": "brendanburns/php-redis",
"ports": [{"containerPort": 80, "hostPort": 8000}]
}]
}
},
"labels": {"name": "frontend"}
}},
"labels": {"name": "frontend"}
},
{
"id": "redisSlaveController",
"kind": "ReplicationController",
"apiVersion": "v1beta1",
"desiredState": {
"replicas": 2,
"replicaSelector": {"name": "redisslave"},
"podTemplate": {
"desiredState": {
"manifest": {
"version": "v1beta1",
"id": "redisSlaveController",
"containers": [{
"name": "slave",
"image": "brendanburns/redis-slave",
"ports": [{"containerPort": 6379, "hostPort": 6380}]
}]
}
},
"labels": {"name": "redisslave"}
}},
"labels": {"name": "redisslave"}
}
]
}
6 changes: 6 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,3 +478,9 @@ type WatchEvent struct {
type APIObject struct {
Object interface{}
}

type Config struct {
Services []Service `yaml:"services,omitempty" json:"services,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an api.XList object already, for all of these. Might make sense to use it.

Pods []Pod `yaml:"pods,omitempty" json:"pods,omitempty"`
ReplicationControllers []ReplicationController `yaml:"replicationControllers,omitempty" json:"replicationControllers,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider using this sort of struct for this purpose. This allows each object in the file to be independently versioned and typed, which means it's not confined to the three here.

type APIObjectList struct {
  JSONBase `yaml:",inline" json:",inline"`
  Items    []APIObject `yaml:"items,omitempty" json:"items,omitempty"`
}

(APIObject needs some fixes to ensure that it continues to use the specific Codec, but that won't matter until we have multiple versions.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or could we just do a flat list of API objects with no unifying metadata.

}