Merge pull request #72 from lavalamp/localkube

Localkube
pull/6/head
brendandburns 2014-06-12 11:15:08 -07:00
commit d100006616
11 changed files with 281 additions and 37 deletions

View File

@ -93,6 +93,16 @@ cd kubernetes
cluster/kube-down.sh
```
## Running locally
In a separate tab of your terminal, run:
```
cd kubernetes
hack/local-up.sh
```
This will build and start a lightweight local cluster, consisting of a master and a single minion. Type Control-C to shut it down. While it's running, you can use `hack/localcfg.sh` in place of `cluster/cloudcfg.sh` to talk to it.
## Where to go next?
[Detailed example application](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/examples/guestbook/guestbook.md)

View File

@ -13,6 +13,7 @@ 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.
*/
// apiserver is the main api server and master for the cluster.
// it is responsible for serving the cluster management API.
package main
@ -53,7 +54,7 @@ func main() {
}
var (
podRegistry registry.PodRegistry
podRegistry registry.PodRegistry
controllerRegistry registry.ControllerRegistry
serviceRegistry registry.ServiceRegistry
)
@ -77,7 +78,7 @@ func main() {
random := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
storage := map[string]apiserver.RESTStorage{
"pods": registry.MakePodRegistryStorage(podRegistry, containerInfo, registry.MakeFirstFitScheduler(machineList, podRegistry, random)),
"pods": registry.MakePodRegistryStorage(podRegistry, containerInfo, registry.MakeFirstFitScheduler(machineList, podRegistry, random)),
"replicationControllers": registry.MakeControllerRegistryStorage(controllerRegistry),
"services": registry.MakeServiceRegistryStorage(serviceRegistry),
}

View File

@ -20,7 +20,9 @@ import (
"fmt"
"log"
"net/http"
"net/url"
"os"
"path"
"strconv"
"time"
@ -39,7 +41,7 @@ var (
updatePeriod = flag.Duration("u", 60*time.Second, "Update interarrival period")
portSpec = flag.String("p", "", "The port spec, comma-separated list of <external>:<internal>,...")
servicePort = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
authConfig = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user")
authConfig = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file. If missing, prompt the user. Only used if doing https.")
json = flag.Bool("json", false, "If true, print raw JSON for responses")
yaml = flag.Bool("yaml", false, "If true, print raw YAML for responses")
)
@ -61,9 +63,16 @@ func main() {
usage()
}
method := flag.Arg(0)
url := *httpServer + "/api/v1beta1" + flag.Arg(1)
secure := true
parsedUrl, err := url.Parse(*httpServer)
if err != nil {
log.Fatalf("Unable to parse %v as a URL\n", err)
}
if parsedUrl.Scheme != "" && parsedUrl.Scheme != "https" {
secure = false
}
url := *httpServer + path.Join("/api/v1beta1", flag.Arg(1))
var request *http.Request
var err error
var printer cloudcfg.ResourcePrinter
if *json {
@ -74,9 +83,12 @@ func main() {
printer = &cloudcfg.HumanReadablePrinter{}
}
auth, err := cloudcfg.LoadAuthInfo(*authConfig)
if err != nil {
log.Fatalf("Error loading auth: %#v", err)
var auth *kube_client.AuthInfo
if secure {
auth, err = cloudcfg.LoadAuthInfo(*authConfig)
if err != nil {
log.Fatalf("Error loading auth: %#v", err)
}
}
switch method {
@ -94,7 +106,7 @@ func main() {
case "rollingupdate":
client := &kube_client.Client{
Host: *httpServer,
Auth: &auth,
Auth: auth,
}
cloudcfg.Update(flag.Arg(1), client, *updatePeriod)
case "run":
@ -108,19 +120,19 @@ func main() {
if err != nil {
log.Fatalf("Error parsing replicas: %#v", err)
}
err = cloudcfg.RunController(image, name, replicas, kube_client.Client{Host: *httpServer, Auth: &auth}, *portSpec, *servicePort)
err = cloudcfg.RunController(image, name, replicas, kube_client.Client{Host: *httpServer, Auth: auth}, *portSpec, *servicePort)
if err != nil {
log.Fatalf("Error: %#v", err)
}
return
case "stop":
err = cloudcfg.StopController(flag.Arg(1), kube_client.Client{Host: *httpServer, Auth: &auth})
err = cloudcfg.StopController(flag.Arg(1), kube_client.Client{Host: *httpServer, Auth: auth})
if err != nil {
log.Fatalf("Error: %#v", err)
}
return
case "rm":
err = cloudcfg.DeleteController(flag.Arg(1), kube_client.Client{Host: *httpServer, Auth: &auth})
err = cloudcfg.DeleteController(flag.Arg(1), kube_client.Client{Host: *httpServer, Auth: auth})
if err != nil {
log.Fatalf("Error: %#v", err)
}
@ -131,8 +143,7 @@ func main() {
if err != nil {
log.Fatalf("Error: %#v", err)
}
var body string
body, err = cloudcfg.DoRequest(request, auth.User, auth.Password)
body, err := cloudcfg.DoRequest(request, auth)
if err != nil {
log.Fatalf("Error: %#v", err)
}

View File

@ -13,6 +13,7 @@ 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.
*/
// The controller manager is responsible for monitoring replication controllers, and creating corresponding
// pods to achieve the desired state. It listens for new controllers in etcd, and it sends requests to the
// master to create/delete pods.

View File

@ -24,6 +24,7 @@ import (
"log"
"math/rand"
"os"
"os/exec"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
@ -57,7 +58,13 @@ func main() {
log.Fatal("Couldn't connnect to docker.")
}
hostname, err := exec.Command("hostname", "-f").Output()
if err != nil {
log.Fatalf("Couldn't determine hostname: %v", err)
}
my_kubelet := kubelet.Kubelet{
Hostname: string(hostname),
DockerClient: dockerClient,
FileCheckFrequency: *fileCheckFrequency,
SyncFrequency: *syncFrequency,

138
cmd/localkube/localkube.go Normal file
View File

@ -0,0 +1,138 @@
/*
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.
*/
// An all-in-one binary for standing up a fake Kubernetes cluster on your
// local machine.
// Assumes that there is a pre-existing etcd server running on localhost.
package main
import (
"flag"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/coreos/go-etcd/etcd"
"github.com/fsouza/go-dockerclient"
)
// kubelet flags
var (
file = flag.String("config", "", "Path to the config file")
syncFrequency = flag.Duration("sync_frequency", 10*time.Second, "Max period between synchronizing running containers and config")
fileCheckFrequency = flag.Duration("file_check_frequency", 20*time.Second, "Duration between checking file for new data")
httpCheckFrequency = flag.Duration("http_check_frequency", 20*time.Second, "Duration between checking http for new data")
manifest_url = flag.String("manifest_url", "", "URL for accessing the container manifest")
kubelet_address = flag.String("kubelet_address", "127.0.0.1", "The address for the kubelet info server to serve on")
kubelet_port = flag.Uint("kubelet_port", 10250, "The port for the kubelete info server to serve on")
)
// master flags
var (
master_port = flag.Uint("master_port", 8080, "The port for the master to listen on. Default 8080.")
master_address = flag.String("master_address", "127.0.0.1", "The address for the master to listen to. Default 127.0.0.1")
apiPrefix = flag.String("api_prefix", "/api/v1beta1", "The prefix for API requests on the server. Default '/api/v1beta1'")
)
// flags that affect both
var (
etcd_server = flag.String("etcd_server", "http://localhost:4001", "Url of local etcd server")
)
// Starts kubelet services. Never returns.
func fake_kubelet() {
endpoint := "unix:///var/run/docker.sock"
dockerClient, err := docker.NewClient(endpoint)
if err != nil {
log.Fatal("Couldn't connnect to docker.")
}
my_kubelet := kubelet.Kubelet{
Hostname: *kubelet_address,
DockerClient: dockerClient,
FileCheckFrequency: *fileCheckFrequency,
SyncFrequency: *syncFrequency,
HTTPCheckFrequency: *httpCheckFrequency,
}
my_kubelet.RunKubelet(*file, *manifest_url, *etcd_server, *kubelet_address, *kubelet_port)
}
// Starts api services (the master). Never returns.
func api_server() {
machineList := util.StringList{*kubelet_address}
etcdClient := etcd.NewClient([]string{*etcd_server})
podRegistry := registry.MakeEtcdRegistry(etcdClient, machineList)
controllerRegistry := registry.MakeEtcdRegistry(etcdClient, machineList)
serviceRegistry := registry.MakeEtcdRegistry(etcdClient, machineList)
containerInfo := &client.HTTPContainerInfo{
Client: http.DefaultClient,
Port: *kubelet_port,
}
storage := map[string]apiserver.RESTStorage{
"pods": registry.MakePodRegistryStorage(podRegistry, containerInfo, registry.MakeFirstFitScheduler(machineList, podRegistry)),
"replicationControllers": registry.MakeControllerRegistryStorage(controllerRegistry),
"services": registry.MakeServiceRegistryStorage(serviceRegistry),
}
endpoints := registry.MakeEndpointController(serviceRegistry, podRegistry)
go util.Forever(func() { endpoints.SyncServiceEndpoints() }, time.Second*10)
s := &http.Server{
Addr: fmt.Sprintf("%s:%d", *master_address, *master_port),
Handler: apiserver.New(storage, *apiPrefix),
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
}
// Starts up a controller manager. Never returns.
func controller_manager() {
controllerManager := registry.MakeReplicationManager(etcd.NewClient([]string{*etcd_server}),
client.Client{
Host: fmt.Sprintf("http://%s:%d", *master_address, *master_port),
})
go util.Forever(func() { controllerManager.Synchronize() }, 20*time.Second)
go util.Forever(func() { controllerManager.WatchControllers() }, 20*time.Second)
select {}
}
func main() {
flag.Parse()
// Set up logger for etcd client
etcd.SetLogger(log.New(os.Stderr, "etcd ", log.LstdFlags))
go api_server()
go fake_kubelet()
go controller_manager()
log.Printf("All components started.\nMaster running at: http://%s:%d\nKubelet running at: http://%s:%d\n",
*master_address, *master_port,
*kubelet_address, *kubelet_port)
select {}
}

View File

@ -22,7 +22,7 @@ source $(dirname $0)/config-go.sh
cd "${KUBE_TARGET}"
BINARIES="proxy integration apiserver controller-manager kubelet cloudcfg"
BINARIES="proxy integration apiserver controller-manager kubelet cloudcfg localkube"
for b in $BINARIES; do
echo "+++ Building ${b}"

51
hack/local-up.sh Executable file
View File

@ -0,0 +1,51 @@
#!/bin/bash
# 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.
# This command builds and runs a local kubernetes cluster.
if [ "$(which etcd)" == "" ]; then
echo "etcd must be in your PATH"
exit 1
fi
# Stop right away if the build fails
set -e
# Only build what we need
(
source $(dirname $0)/config-go.sh
cd "${KUBE_TARGET}"
BINARIES="cloudcfg localkube"
for b in $BINARIES; do
echo "+++ Building ${b}"
go build "${KUBE_GO_PACKAGE}"/cmd/${b}
done
)
echo "Starting etcd"
ETCD_DIR=$(mktemp -d -t kube-integration.XXXXXX)
trap "rm -rf ${ETCD_DIR}" EXIT
etcd -name test -data-dir ${ETCD_DIR} > /tmp/etcd.log &
ETCD_PID=$!
sleep 5
echo "Running localkube as root (so it can talk to docker's unix socket)"
sudo $(dirname $0)/../output/go/localkube
kill $ETCD_PID

27
hack/localcfg.sh Executable file
View File

@ -0,0 +1,27 @@
#!/bin/bash
# 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.
# This file is exactly like cloudcfg.sh, but it talks to a local master
# (which you're assumed to be running with localkube.sh).
CLOUDCFG=$(dirname $0)/../output/go/cloudcfg
if [ ! -x $CLOUDCFG ]; then
echo "Could not find cloudcfg binary. Run hack/build-go.sh to build it."
exit 1
fi
# 8080 is the default port for the master
$CLOUDCFG -h http://localhost:8080 $@

View File

@ -42,25 +42,28 @@ func promptForString(field string) string {
return result
}
// Parse an AuthInfo object from a file path
func LoadAuthInfo(path string) (client.AuthInfo, error) {
// Parse an AuthInfo object from a file path. Prompt user and create file if it doesn't exist.
func LoadAuthInfo(path string) (*client.AuthInfo, error) {
var auth client.AuthInfo
if _, err := os.Stat(path); os.IsNotExist(err) {
auth.User = promptForString("Username")
auth.Password = promptForString("Password")
data, err := json.Marshal(auth)
if err != nil {
return auth, err
return &auth, err
}
err = ioutil.WriteFile(path, data, 0600)
return auth, err
return &auth, err
}
data, err := ioutil.ReadFile(path)
if err != nil {
return auth, err
return nil, err
}
err = json.Unmarshal(data, &auth)
return auth, err
if err != nil {
return nil, err
}
return &auth, err
}
// Perform a rolling update of a collection of pods.
@ -99,23 +102,22 @@ func RequestWithBody(configFile, url, method string) (*http.Request, error) {
if err != nil {
return nil, err
}
return RequestWithBodyData(data, url, method)
return requestWithBodyData(data, url, method)
}
// RequestWithBodyData is a helper method that creates an HTTP request with the specified url, method
// requestWithBodyData is a helper method that creates an HTTP request with the specified url, method
// and body data
// FIXME: need to be public API?
func RequestWithBodyData(data []byte, url, method string) (*http.Request, error) {
func requestWithBodyData(data []byte, url, method string) (*http.Request, error) {
request, err := http.NewRequest(method, url, bytes.NewBuffer(data))
request.ContentLength = int64(len(data))
return request, err
}
// Execute a request, adds authentication, and HTTPS cert ignoring.
// TODO: Make this stuff optional
// FIXME: need to be public API?
func DoRequest(request *http.Request, user, password string) (string, error) {
request.SetBasicAuth(user, password)
// Execute a request, adds authentication (if auth != nil), and HTTPS cert ignoring.
func DoRequest(request *http.Request, auth *client.AuthInfo) (string, error) {
if auth != nil {
request.SetBasicAuth(auth.User, auth.Password)
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}

View File

@ -60,6 +60,7 @@ type DockerInterface interface {
// The main kubelet implementation
type Kubelet struct {
Hostname string
Client registry.EtcdClient
DockerClient DockerInterface
FileCheckFrequency time.Duration
@ -427,15 +428,10 @@ func (kl *Kubelet) getKubeletStateFromEtcd(key string, changeChannel chan<- []ap
// The channel to send new configurations across
// This function loops forever and is intended to be run in a go routine.
func (kl *Kubelet) SyncAndSetupEtcdWatch(changeChannel chan<- []api.ContainerManifest) {
hostname, err := exec.Command("hostname", "-f").Output()
if err != nil {
log.Printf("Couldn't determine hostname : %v", err)
return
}
key := "/registry/hosts/" + strings.TrimSpace(string(hostname))
key := "/registry/hosts/" + strings.TrimSpace(kl.Hostname)
// First fetch the initial configuration (watch only gives changes...)
for {
err = kl.getKubeletStateFromEtcd(key, changeChannel)
err := kl.getKubeletStateFromEtcd(key, changeChannel)
if err == nil {
// We got a successful response, etcd is up, set up the watch.
break