|
// Copyright 2015 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. |
|
|
|
// podlet is a tiny CLI tool & daemon to launch kubernetes pods on a bare docker host. |
|
// Usage: |
|
// podlet [PODS...] [-daemon] |
|
// Examples: |
|
// launch a pod from stdin |
|
// $ cat pod.yaml | podlet |
|
// 962009ab45ee0baa5f5dfbd4a900241151f47fc7d4f2efa9e1c6ee3a84bfeae8 |
|
// 5e70817c999ed5e7ff6ebb4c8e5d238f1aa48b958b4fdf27aa40fd3194dcbc7a |
|
// launch multiple pods from files |
|
// $ podlet podi.yaml poda.yaml |
|
// podi.yaml: 6d01dfd8edc91d02c6497224e31bd2988a05526234f08afe1cb0d0ad29187578 |
|
// podi.yaml: 3507edb1ae9cf69f607ce398752fedcdd06866d4553f1a63abc7e9424c3928b4 |
|
// poda.yaml: 8c118d1a4d8daec3cdf5047c04adf1c9cf4f68d00b97ed658d475140bc89349b |
|
// poda.yaml: 0b9a6f7a57c89c6689f08384044ae2b35d7f033dd2dd983d24773f4d093c82ad |
|
// launch pods from HTTP |
|
// $ podlet -daemon |
|
// $ curl -X POST --data-binary @pod.yaml localhost:8000/pods |
|
// [{"Id":"6a173b0d475d8fce2880ce95ede6b8461c6a11d708b53859699d524fe607c1e6","Created":"0001-01-01T00:00:00Z","State":{"StartedAt":"0001-01-01T00:00:00Z","FinishedAt":"0001-01-01T00:00:00Z"}},{"Id":"d1355c04c5533dd1e1648d541191ad17440da2324d76fedb5d7803897a291e05","Created":"0001-01-01T00:00:00Z","State":{"StartedAt":"0001-01-01T00:00:00Z","FinishedAt":"0001-01-01T00:00:00Z"}}] |
|
package main |
|
|
|
import ( |
|
"encoding/json" |
|
"flag" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"log" |
|
"net/http" |
|
"os" |
|
|
|
docker "github.com/fsouza/go-dockerclient" |
|
"github.com/ghodss/yaml" |
|
) |
|
|
|
var addr = flag.String("addr", ":8000", "address to listen on") |
|
var dockerEndpoint = flag.String("dockerhost", "unix://var/run/docker.sock", "docker host endpoint") |
|
var daemon = flag.Bool("daemon", false, "daemon mode listen over HTTP for pod definitions") |
|
|
|
type Pod struct { |
|
Kind string |
|
ApiVersion string `json:"apiVersion"` |
|
Metadata struct { |
|
Name string |
|
} |
|
Spec struct { |
|
Containers []struct { |
|
Name string |
|
Image string |
|
Env []struct { |
|
Name string |
|
Value string |
|
} |
|
Ports []struct { |
|
HostIP string `json:"hostIP"` |
|
HostPort string `json:"hostPort"` |
|
ContainerPort string `json:"containerPort"` |
|
} |
|
VolumeMounts []struct { |
|
Name string |
|
MountPath string `json:"mountPath"` |
|
} `json:"volumeMounts"` |
|
} |
|
Volumes []struct { |
|
Name string |
|
HostPath struct { |
|
Path string |
|
} `json:"hostPath"` |
|
} |
|
} |
|
} |
|
|
|
func runPod(reader io.Reader) ([]*docker.Container, error) { |
|
// decode pod |
|
content, err := ioutil.ReadAll(reader) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to read pod: %v", err) |
|
} |
|
var pod Pod |
|
if err := yaml.Unmarshal(content, &pod); err != nil { |
|
return nil, fmt.Errorf("failed to decode pod: %v", err) |
|
} |
|
|
|
// compute port bindings |
|
exposedPorts := make(map[docker.Port]struct{}) |
|
portBindings := make(map[docker.Port][]docker.PortBinding) |
|
for _, c := range pod.Spec.Containers { |
|
for _, p := range c.Ports { |
|
exposedPorts[docker.Port(p.ContainerPort)] = struct{}{} |
|
portBindings[docker.Port(p.ContainerPort)] = []docker.PortBinding{ |
|
{ |
|
HostIP: p.HostIP, |
|
HostPort: p.HostPort, |
|
}, |
|
} |
|
} |
|
} |
|
|
|
// create net container |
|
netContainer, err := dockerClient.CreateContainer(docker.CreateContainerOptions{ |
|
Config: &docker.Config{ |
|
Image: "kubernetes/pause", |
|
ExposedPorts: exposedPorts, |
|
}, |
|
HostConfig: &docker.HostConfig{ |
|
PortBindings: portBindings, |
|
}, |
|
}) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to create net container: %v", err) |
|
} |
|
|
|
// create host volume mapp |
|
volumeMap := make(map[string]string) |
|
for _, v := range pod.Spec.Volumes { |
|
volumeMap[v.Name] = v.HostPath.Path |
|
} |
|
|
|
containers := []*docker.Container{ |
|
netContainer, |
|
} |
|
// create pods container |
|
for _, c := range pod.Spec.Containers { |
|
var env []string |
|
for _, e := range c.Env { |
|
env = append(env, fmt.Sprintf("%s=%s", e.Name, e.Value)) |
|
} |
|
var binds []string |
|
for _, v := range c.VolumeMounts { |
|
hostPath, ok := volumeMap[v.Name] |
|
if !ok { |
|
return nil, fmt.Errorf("hostPath not found for %q:", v.Name) |
|
} |
|
binds = append(binds, fmt.Sprintf("%s:%s", hostPath, v.MountPath)) |
|
} |
|
container, err := dockerClient.CreateContainer(docker.CreateContainerOptions{ |
|
Config: &docker.Config{ |
|
Image: c.Image, |
|
Env: env, |
|
}, |
|
HostConfig: &docker.HostConfig{ |
|
Binds: binds, |
|
NetworkMode: fmt.Sprintf("container:%s", netContainer.ID), |
|
}, |
|
}) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to create container %q: %v", c.Name, err) |
|
} |
|
containers = append(containers, container) |
|
} |
|
|
|
// start containers |
|
for _, c := range containers { |
|
if err := dockerClient.StartContainer(c.ID, nil); err != nil { |
|
return nil, fmt.Errorf("failed to start container %q: %v", c.ID, err) |
|
} |
|
} |
|
return containers, nil |
|
} |
|
|
|
func handlePods(w http.ResponseWriter, r *http.Request) error { |
|
containers, err := runPod(r.Body) |
|
if err != nil { |
|
return fmt.Errorf("api error: %v", err) |
|
} |
|
if err := json.NewEncoder(w).Encode(containers); err != nil { |
|
return fmt.Errorf("failed to marshall containers list in json: %v", err) |
|
} |
|
return nil |
|
} |
|
|
|
var dockerClient *docker.Client |
|
|
|
func init() { |
|
flag.Parse() |
|
var err error |
|
dockerClient, err = docker.NewClient(*dockerEndpoint) |
|
if err != nil { |
|
log.Fatal("failed to connect to docker endpoint %q: %v", *dockerEndpoint, err) |
|
} |
|
http.Handle("/pods", Handler(handlePods)) |
|
} |
|
|
|
type Handler func(w http.ResponseWriter, r *http.Request) error |
|
|
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
|
err := h(w, r) |
|
if err != nil { |
|
log.Println(err) |
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
|
return |
|
} |
|
} |
|
|
|
func main() { |
|
if *daemon { |
|
log.Fatal(http.ListenAndServe(*addr, nil)) |
|
return |
|
} |
|
if flag.NArg() == 0 { |
|
containers, err := runPod(os.Stdin) |
|
if err != nil { |
|
log.Printf("failed to run pod from stdin: %v", err) |
|
} |
|
for _, c := range containers { |
|
fmt.Println(c.ID) |
|
} |
|
} |
|
for _, arg := range flag.Args() { |
|
f, err := os.Open(arg) |
|
if err != nil { |
|
log.Fatalf("failed to open file %q: %v", arg, err) |
|
} |
|
defer f.Close() |
|
containers, err := runPod(f) |
|
if err != nil { |
|
log.Printf("failed to run pod %q: %v", arg, err) |
|
continue |
|
} |
|
for _, c := range containers { |
|
fmt.Printf("%s: %s\n", arg, c.ID) |
|
} |
|
} |
|
} |