2015-04-28 23:11:37 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
2015-04-28 23:11:37 +00:00
|
|
|
|
|
|
|
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 rkt
|
|
|
|
|
|
|
|
import (
|
2015-08-26 01:50:42 +00:00
|
|
|
"bytes"
|
2015-04-30 01:11:30 +00:00
|
|
|
"encoding/json"
|
2015-04-28 23:11:37 +00:00
|
|
|
"fmt"
|
2015-04-30 20:34:46 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2015-04-28 23:11:37 +00:00
|
|
|
"os/exec"
|
2015-04-30 20:34:46 +00:00
|
|
|
"path"
|
2015-05-04 23:51:31 +00:00
|
|
|
"strconv"
|
2015-04-28 23:11:37 +00:00
|
|
|
"strings"
|
2015-10-08 01:38:01 +00:00
|
|
|
"syscall"
|
2015-04-30 20:34:46 +00:00
|
|
|
"time"
|
2015-04-28 23:11:37 +00:00
|
|
|
|
2015-08-05 22:05:17 +00:00
|
|
|
appcschema "github.com/appc/spec/schema"
|
|
|
|
appctypes "github.com/appc/spec/schema/types"
|
|
|
|
"github.com/coreos/go-systemd/dbus"
|
|
|
|
"github.com/coreos/go-systemd/unit"
|
|
|
|
"github.com/docker/docker/pkg/parsers"
|
|
|
|
docker "github.com/fsouza/go-dockerclient"
|
|
|
|
"github.com/golang/glog"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
2015-09-03 21:40:58 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/record"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/credentialprovider"
|
|
|
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
2015-10-19 22:15:59 +00:00
|
|
|
proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results"
|
2015-10-14 03:46:32 +00:00
|
|
|
kubeletutil "k8s.io/kubernetes/pkg/kubelet/util"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/securitycontext"
|
|
|
|
"k8s.io/kubernetes/pkg/types"
|
2015-08-13 12:59:15 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util"
|
2015-10-08 01:38:01 +00:00
|
|
|
utilexec "k8s.io/kubernetes/pkg/util/exec"
|
2015-10-07 21:04:41 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/sets"
|
2015-04-28 23:11:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2015-10-21 20:04:10 +00:00
|
|
|
RktType = "rkt"
|
|
|
|
|
2015-10-08 21:48:45 +00:00
|
|
|
acVersion = "0.7.1"
|
|
|
|
minimumRktVersion = "0.9.0"
|
|
|
|
recommendRktVersion = "0.9.0"
|
2015-08-13 23:39:17 +00:00
|
|
|
systemdMinimumVersion = "219"
|
2015-04-30 06:33:07 +00:00
|
|
|
|
2015-08-13 23:39:17 +00:00
|
|
|
systemdServiceDir = "/run/systemd/system"
|
|
|
|
rktDataDir = "/var/lib/rkt"
|
|
|
|
rktLocalConfigDir = "/etc/rkt"
|
2015-04-28 23:11:37 +00:00
|
|
|
|
|
|
|
kubernetesUnitPrefix = "k8s"
|
|
|
|
unitKubernetesSection = "X-Kubernetes"
|
|
|
|
unitPodName = "POD"
|
|
|
|
unitRktID = "RktID"
|
2015-08-25 20:03:33 +00:00
|
|
|
unitRestartCount = "RestartCount"
|
2015-04-28 23:11:37 +00:00
|
|
|
|
|
|
|
dockerPrefix = "docker://"
|
2015-05-04 23:51:31 +00:00
|
|
|
|
|
|
|
authDir = "auth.d"
|
|
|
|
dockerAuthTemplate = `{"rktKind":"dockerAuth","rktVersion":"v1","registries":[%q],"credentials":{"user":%q,"password":%q}}`
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
defaultImageTag = "latest"
|
2015-04-28 23:11:37 +00:00
|
|
|
)
|
|
|
|
|
2015-09-28 22:46:29 +00:00
|
|
|
// Runtime implements the Containerruntime for rkt. The implementation
|
2015-04-28 23:11:37 +00:00
|
|
|
// uses systemd, so in order to run this runtime, systemd must be installed
|
|
|
|
// on the machine.
|
2015-09-28 22:46:29 +00:00
|
|
|
type Runtime struct {
|
2015-05-06 18:02:08 +00:00
|
|
|
systemd *dbus.Conn
|
2015-05-01 01:34:15 +00:00
|
|
|
// The absolute path to rkt binary.
|
|
|
|
rktBinAbsPath string
|
|
|
|
config *Config
|
2015-04-28 23:11:37 +00:00
|
|
|
// TODO(yifan): Refactor this to be generic keyring.
|
|
|
|
dockerKeyring credentialprovider.DockerKeyring
|
2015-05-06 18:02:08 +00:00
|
|
|
|
|
|
|
containerRefManager *kubecontainer.RefManager
|
|
|
|
generator kubecontainer.RunContainerOptionsGenerator
|
|
|
|
recorder record.EventRecorder
|
2015-10-19 22:15:59 +00:00
|
|
|
livenessManager proberesults.Manager
|
2015-05-06 23:51:37 +00:00
|
|
|
volumeGetter volumeGetter
|
2015-08-17 23:19:25 +00:00
|
|
|
imagePuller kubecontainer.ImagePuller
|
2015-04-28 23:11:37 +00:00
|
|
|
}
|
|
|
|
|
2015-09-28 22:46:29 +00:00
|
|
|
var _ kubecontainer.Runtime = &Runtime{}
|
2015-05-01 23:12:14 +00:00
|
|
|
|
2015-05-06 23:51:37 +00:00
|
|
|
// TODO(yifan): Remove this when volumeManager is moved to separate package.
|
|
|
|
type volumeGetter interface {
|
|
|
|
GetVolumes(podUID types.UID) (kubecontainer.VolumeMap, bool)
|
|
|
|
}
|
|
|
|
|
2015-04-28 23:11:37 +00:00
|
|
|
// New creates the rkt container runtime which implements the container runtime interface.
|
|
|
|
// It will test if the rkt binary is in the $PATH, and whether we can get the
|
|
|
|
// version of it. If so, creates the rkt container runtime, otherwise returns an error.
|
2015-05-06 18:02:08 +00:00
|
|
|
func New(config *Config,
|
|
|
|
generator kubecontainer.RunContainerOptionsGenerator,
|
|
|
|
recorder record.EventRecorder,
|
|
|
|
containerRefManager *kubecontainer.RefManager,
|
2015-10-19 22:15:59 +00:00
|
|
|
livenessManager proberesults.Manager,
|
|
|
|
volumeGetter volumeGetter,
|
2015-10-20 21:49:44 +00:00
|
|
|
imageBackOff *util.Backoff,
|
|
|
|
serializeImagePulls bool,
|
|
|
|
) (*Runtime, error) {
|
2015-04-28 23:11:37 +00:00
|
|
|
systemdVersion, err := getSystemdVersion()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result, err := systemdVersion.Compare(systemdMinimumVersion)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if result < 0 {
|
|
|
|
return nil, fmt.Errorf("rkt: systemd version is too old, requires at least %v", systemdMinimumVersion)
|
|
|
|
}
|
|
|
|
|
|
|
|
systemd, err := dbus.New()
|
|
|
|
if err != nil {
|
2015-04-30 01:11:30 +00:00
|
|
|
return nil, fmt.Errorf("cannot connect to dbus: %v", err)
|
2015-04-28 23:11:37 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 17:03:45 +00:00
|
|
|
rktBinAbsPath := config.Path
|
|
|
|
if rktBinAbsPath == "" {
|
|
|
|
// No default rkt path was set, so try to find one in $PATH.
|
|
|
|
var err error
|
|
|
|
rktBinAbsPath, err = exec.LookPath("rkt")
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot find rkt binary: %v", err)
|
|
|
|
}
|
2015-04-28 23:11:37 +00:00
|
|
|
}
|
|
|
|
|
2015-09-28 22:46:29 +00:00
|
|
|
rkt := &Runtime{
|
2015-05-06 18:02:08 +00:00
|
|
|
systemd: systemd,
|
|
|
|
rktBinAbsPath: rktBinAbsPath,
|
|
|
|
config: config,
|
|
|
|
dockerKeyring: credentialprovider.NewDockerKeyring(),
|
|
|
|
containerRefManager: containerRefManager,
|
|
|
|
generator: generator,
|
|
|
|
recorder: recorder,
|
2015-10-19 22:15:59 +00:00
|
|
|
livenessManager: livenessManager,
|
2015-05-14 00:57:54 +00:00
|
|
|
volumeGetter: volumeGetter,
|
2015-04-28 23:11:37 +00:00
|
|
|
}
|
2015-10-20 21:49:44 +00:00
|
|
|
if serializeImagePulls {
|
|
|
|
rkt.imagePuller = kubecontainer.NewSerializedImagePuller(recorder, rkt, imageBackOff)
|
|
|
|
} else {
|
|
|
|
rkt.imagePuller = kubecontainer.NewImagePuller(recorder, rkt, imageBackOff)
|
|
|
|
}
|
2015-04-28 23:11:37 +00:00
|
|
|
|
|
|
|
// Test the rkt version.
|
|
|
|
version, err := rkt.Version()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-08 21:48:45 +00:00
|
|
|
result, err = version.Compare(minimumRktVersion)
|
2015-04-28 23:11:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if result < 0 {
|
2015-10-08 21:48:45 +00:00
|
|
|
return nil, fmt.Errorf("rkt: version is too old, requires at least %v", minimumRktVersion)
|
2015-04-28 23:11:37 +00:00
|
|
|
}
|
2015-10-08 21:48:45 +00:00
|
|
|
|
|
|
|
result, err = version.Compare(recommendRktVersion)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if result != 0 {
|
|
|
|
// TODO(yifan): Record an event to expose the information.
|
|
|
|
glog.Warningf("rkt: current version %q is not recommended (recommended version %q)", version, recommendRktVersion)
|
|
|
|
}
|
|
|
|
|
2015-04-28 23:11:37 +00:00
|
|
|
return rkt, nil
|
|
|
|
}
|
|
|
|
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) buildCommand(args ...string) *exec.Cmd {
|
2015-08-13 23:39:17 +00:00
|
|
|
cmd := exec.Command(r.rktBinAbsPath)
|
2015-04-28 23:11:37 +00:00
|
|
|
cmd.Args = append(cmd.Args, r.config.buildGlobalOptions()...)
|
|
|
|
cmd.Args = append(cmd.Args, args...)
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
// runCommand invokes rkt binary with arguments and returns the result
|
2015-05-14 00:57:54 +00:00
|
|
|
// from stdout in a list of strings. Each string in the list is a line.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) runCommand(args ...string) ([]string, error) {
|
2015-04-28 23:11:37 +00:00
|
|
|
glog.V(4).Info("rkt: Run command:", args)
|
|
|
|
|
2015-08-26 01:50:42 +00:00
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
cmd := r.buildCommand(args...)
|
|
|
|
cmd.Stdout, cmd.Stderr = &stdout, &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to run %v: %v\nstdout: %v\nstderr: %v", args, err, stdout.String(), stderr.String())
|
2015-04-28 23:11:37 +00:00
|
|
|
}
|
2015-08-26 01:50:42 +00:00
|
|
|
return strings.Split(strings.TrimSpace(stdout.String()), "\n"), nil
|
2015-04-28 23:11:37 +00:00
|
|
|
}
|
2015-04-30 03:04:29 +00:00
|
|
|
|
|
|
|
// makePodServiceFileName constructs the unit file name for a pod using its UID.
|
|
|
|
func makePodServiceFileName(uid types.UID) string {
|
2015-08-13 23:39:17 +00:00
|
|
|
// TODO(yifan): Add name for readability? We need to consider the
|
|
|
|
// limit of the length.
|
2015-04-30 03:04:29 +00:00
|
|
|
return fmt.Sprintf("%s_%s.service", kubernetesUnitPrefix, uid)
|
|
|
|
}
|
2015-04-30 01:11:30 +00:00
|
|
|
|
|
|
|
type resource struct {
|
|
|
|
limit string
|
|
|
|
request string
|
|
|
|
}
|
|
|
|
|
|
|
|
// rawValue converts a string to *json.RawMessage
|
|
|
|
func rawValue(value string) *json.RawMessage {
|
|
|
|
msg := json.RawMessage(value)
|
|
|
|
return &msg
|
|
|
|
}
|
|
|
|
|
2015-05-14 20:19:39 +00:00
|
|
|
// rawValue converts the request, limit to *json.RawMessage
|
|
|
|
func rawRequestLimit(request, limit string) *json.RawMessage {
|
|
|
|
if request == "" {
|
2015-09-18 13:51:32 +00:00
|
|
|
request = limit
|
2015-05-14 20:19:39 +00:00
|
|
|
}
|
|
|
|
if limit == "" {
|
2015-09-18 13:51:32 +00:00
|
|
|
limit = request
|
2015-05-14 20:19:39 +00:00
|
|
|
}
|
|
|
|
return rawValue(fmt.Sprintf(`{"request":%q,"limit":%q}`, request, limit))
|
|
|
|
}
|
|
|
|
|
2015-04-30 01:11:30 +00:00
|
|
|
// setIsolators overrides the isolators of the pod manifest if necessary.
|
2015-05-05 23:02:13 +00:00
|
|
|
// TODO need an apply config in security context for rkt
|
2015-04-30 01:11:30 +00:00
|
|
|
func setIsolators(app *appctypes.App, c *api.Container) error {
|
2015-05-05 23:02:13 +00:00
|
|
|
hasCapRequests := securitycontext.HasCapabilitiesRequest(c)
|
|
|
|
if hasCapRequests || len(c.Resources.Limits) > 0 || len(c.Resources.Requests) > 0 {
|
2015-04-30 01:11:30 +00:00
|
|
|
app.Isolators = []appctypes.Isolator{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retained capabilities/privileged.
|
|
|
|
privileged := false
|
2015-05-09 21:17:36 +00:00
|
|
|
if c.SecurityContext != nil && c.SecurityContext.Privileged != nil {
|
|
|
|
privileged = *c.SecurityContext.Privileged
|
2015-04-30 01:11:30 +00:00
|
|
|
}
|
2015-05-09 21:17:36 +00:00
|
|
|
|
2015-04-30 01:11:30 +00:00
|
|
|
var addCaps string
|
|
|
|
if privileged {
|
|
|
|
addCaps = getAllCapabilities()
|
|
|
|
} else {
|
2015-05-05 23:02:13 +00:00
|
|
|
if hasCapRequests {
|
|
|
|
addCaps = getCapabilities(c.SecurityContext.Capabilities.Add)
|
|
|
|
}
|
2015-04-30 01:11:30 +00:00
|
|
|
}
|
|
|
|
if len(addCaps) > 0 {
|
|
|
|
// TODO(yifan): Replace with constructor, see:
|
|
|
|
// https://github.com/appc/spec/issues/268
|
|
|
|
isolator := appctypes.Isolator{
|
|
|
|
Name: "os/linux/capabilities-retain-set",
|
|
|
|
ValueRaw: rawValue(fmt.Sprintf(`{"set":[%s]}`, addCaps)),
|
|
|
|
}
|
|
|
|
app.Isolators = append(app.Isolators, isolator)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Removed capabilities.
|
2015-05-05 23:02:13 +00:00
|
|
|
var dropCaps string
|
|
|
|
if hasCapRequests {
|
|
|
|
dropCaps = getCapabilities(c.SecurityContext.Capabilities.Drop)
|
|
|
|
}
|
2015-04-30 01:11:30 +00:00
|
|
|
if len(dropCaps) > 0 {
|
|
|
|
// TODO(yifan): Replace with constructor, see:
|
|
|
|
// https://github.com/appc/spec/issues/268
|
|
|
|
isolator := appctypes.Isolator{
|
|
|
|
Name: "os/linux/capabilities-remove-set",
|
|
|
|
ValueRaw: rawValue(fmt.Sprintf(`{"set":[%s]}`, dropCaps)),
|
|
|
|
}
|
|
|
|
app.Isolators = append(app.Isolators, isolator)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resources.
|
|
|
|
resources := make(map[api.ResourceName]resource)
|
|
|
|
for name, quantity := range c.Resources.Limits {
|
|
|
|
resources[name] = resource{limit: quantity.String()}
|
|
|
|
}
|
|
|
|
for name, quantity := range c.Resources.Requests {
|
|
|
|
r, ok := resources[name]
|
|
|
|
if !ok {
|
|
|
|
r = resource{}
|
|
|
|
}
|
|
|
|
r.request = quantity.String()
|
|
|
|
resources[name] = r
|
|
|
|
}
|
2015-08-10 18:15:13 +00:00
|
|
|
var acName appctypes.ACIdentifier
|
2015-04-30 01:11:30 +00:00
|
|
|
for name, res := range resources {
|
|
|
|
switch name {
|
|
|
|
case api.ResourceCPU:
|
|
|
|
acName = "resource/cpu"
|
|
|
|
case api.ResourceMemory:
|
|
|
|
acName = "resource/memory"
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("resource type not supported: %v", name)
|
|
|
|
}
|
|
|
|
// TODO(yifan): Replace with constructor, see:
|
|
|
|
// https://github.com/appc/spec/issues/268
|
|
|
|
isolator := appctypes.Isolator{
|
|
|
|
Name: acName,
|
2015-05-14 20:19:39 +00:00
|
|
|
ValueRaw: rawRequestLimit(res.request, res.limit),
|
2015-04-30 01:11:30 +00:00
|
|
|
}
|
|
|
|
app.Isolators = append(app.Isolators, isolator)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-21 18:47:05 +00:00
|
|
|
// findEnvInList returns the index of environment variable in the environment whose Name equals env.Name.
|
|
|
|
func findEnvInList(envs appctypes.Environment, env kubecontainer.EnvVar) int {
|
|
|
|
for i, e := range envs {
|
|
|
|
if e.Name == env.Name {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
2015-04-30 01:11:30 +00:00
|
|
|
// setApp overrides the app's fields if any of them are specified in the
|
|
|
|
// container's spec.
|
2015-05-14 00:57:54 +00:00
|
|
|
func setApp(app *appctypes.App, c *api.Container, opts *kubecontainer.RunContainerOptions) error {
|
2015-04-30 01:11:30 +00:00
|
|
|
// Override the exec.
|
2015-08-21 18:47:05 +00:00
|
|
|
|
|
|
|
if len(c.Command) > 0 {
|
|
|
|
app.Exec = c.Command
|
|
|
|
}
|
|
|
|
if len(c.Args) > 0 {
|
|
|
|
app.Exec = append(app.Exec, c.Args...)
|
2015-04-30 01:11:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(yifan): Use non-root user in the future, see:
|
|
|
|
// https://github.com/coreos/rkt/issues/820
|
|
|
|
app.User, app.Group = "0", "0"
|
|
|
|
|
|
|
|
// Override the working directory.
|
|
|
|
if len(c.WorkingDir) > 0 {
|
|
|
|
app.WorkingDirectory = c.WorkingDir
|
|
|
|
}
|
|
|
|
|
2015-08-21 18:47:05 +00:00
|
|
|
// Merge the environment. Override the image with the ones defined in the spec if necessary.
|
|
|
|
for _, env := range opts.Envs {
|
|
|
|
if ix := findEnvInList(app.Environment, env); ix >= 0 {
|
|
|
|
app.Environment[ix].Value = env.Value
|
|
|
|
continue
|
|
|
|
}
|
2015-04-30 01:11:30 +00:00
|
|
|
app.Environment = append(app.Environment, appctypes.EnvironmentVariable{
|
|
|
|
Name: env.Name,
|
|
|
|
Value: env.Value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Override the mount points.
|
2015-05-14 00:57:54 +00:00
|
|
|
if len(opts.Mounts) > 0 {
|
2015-04-30 01:11:30 +00:00
|
|
|
app.MountPoints = []appctypes.MountPoint{}
|
|
|
|
}
|
2015-05-14 00:57:54 +00:00
|
|
|
for _, m := range opts.Mounts {
|
2015-04-30 01:11:30 +00:00
|
|
|
mountPointName, err := appctypes.NewACName(m.Name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
app.MountPoints = append(app.MountPoints, appctypes.MountPoint{
|
|
|
|
Name: *mountPointName,
|
2015-05-14 00:57:54 +00:00
|
|
|
Path: m.ContainerPath,
|
2015-04-30 01:11:30 +00:00
|
|
|
ReadOnly: m.ReadOnly,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Override the ports.
|
2015-05-14 00:57:54 +00:00
|
|
|
if len(opts.PortMappings) > 0 {
|
2015-04-30 01:11:30 +00:00
|
|
|
app.Ports = []appctypes.Port{}
|
|
|
|
}
|
2015-05-14 00:57:54 +00:00
|
|
|
for _, p := range opts.PortMappings {
|
|
|
|
name, err := appctypes.SanitizeACName(p.Name)
|
2015-04-30 01:11:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-05-14 00:57:54 +00:00
|
|
|
portName := appctypes.MustACName(name)
|
2015-04-30 01:11:30 +00:00
|
|
|
app.Ports = append(app.Ports, appctypes.Port{
|
|
|
|
Name: *portName,
|
|
|
|
Protocol: string(p.Protocol),
|
|
|
|
Port: uint(p.ContainerPort),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Override isolators.
|
|
|
|
return setIsolators(app, c)
|
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
// parseImageName parses a docker image string into two parts: repo and tag.
|
|
|
|
// If tag is empty, return the defaultImageTag.
|
|
|
|
func parseImageName(image string) (string, string) {
|
|
|
|
repoToPull, tag := parsers.ParseRepositoryTag(image)
|
|
|
|
// If no tag was specified, use the default "latest".
|
|
|
|
if len(tag) == 0 {
|
|
|
|
tag = defaultImageTag
|
|
|
|
}
|
|
|
|
return repoToPull, tag
|
|
|
|
}
|
|
|
|
|
2015-05-13 20:00:37 +00:00
|
|
|
// getImageManifest invokes 'rkt image cat-manifest' to retrive the image manifest
|
|
|
|
// for the image.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) getImageManifest(image string) (*appcschema.ImageManifest, error) {
|
2015-05-13 20:00:37 +00:00
|
|
|
var manifest appcschema.ImageManifest
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
repoToPull, tag := parseImageName(image)
|
|
|
|
imgName, err := appctypes.SanitizeACIdentifier(repoToPull)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
output, err := r.runCommand("image", "cat-manifest", fmt.Sprintf("%s:%s", imgName, tag))
|
2015-05-13 20:00:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(output) != 1 {
|
2015-05-14 00:57:54 +00:00
|
|
|
return nil, fmt.Errorf("invalid output: %v", output)
|
2015-05-13 20:00:37 +00:00
|
|
|
}
|
|
|
|
return &manifest, json.Unmarshal([]byte(output[0]), &manifest)
|
|
|
|
}
|
|
|
|
|
2015-04-30 01:11:30 +00:00
|
|
|
// makePodManifest transforms a kubelet pod spec to the rkt pod manifest.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) makePodManifest(pod *api.Pod, pullSecrets []api.Secret) (*appcschema.PodManifest, error) {
|
2015-05-14 00:57:54 +00:00
|
|
|
var globalPortMappings []kubecontainer.PortMapping
|
2015-04-30 01:11:30 +00:00
|
|
|
manifest := appcschema.BlankPodManifest()
|
|
|
|
|
|
|
|
for _, c := range pod.Spec.Containers {
|
2015-10-02 13:45:46 +00:00
|
|
|
if err, _ := r.imagePuller.PullImage(pod, &c, pullSecrets); err != nil {
|
2015-08-17 23:19:25 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-05-13 20:00:37 +00:00
|
|
|
imgManifest, err := r.getImageManifest(c.Image)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if imgManifest.App == nil {
|
2015-08-21 18:47:05 +00:00
|
|
|
imgManifest.App = new(appctypes.App)
|
2015-05-13 20:00:37 +00:00
|
|
|
}
|
|
|
|
|
2015-05-06 21:20:39 +00:00
|
|
|
img, err := r.getImageByName(c.Image)
|
2015-04-30 01:11:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-08-17 23:19:25 +00:00
|
|
|
hash, err := appctypes.NewHash(img.ID)
|
2015-04-30 01:11:30 +00:00
|
|
|
if err != nil {
|
2015-05-06 21:20:39 +00:00
|
|
|
return nil, err
|
2015-04-30 01:11:30 +00:00
|
|
|
}
|
|
|
|
|
2015-05-14 00:57:54 +00:00
|
|
|
opts, err := r.generator.GenerateRunContainerOptions(pod, &c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
globalPortMappings = append(globalPortMappings, opts.PortMappings...)
|
|
|
|
|
|
|
|
if err := setApp(imgManifest.App, &c, opts); err != nil {
|
2015-05-06 21:20:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-05-14 00:57:54 +00:00
|
|
|
|
2015-08-10 18:15:13 +00:00
|
|
|
name, err := appctypes.SanitizeACName(c.Name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
appName := appctypes.MustACName(name)
|
|
|
|
|
2015-04-30 01:11:30 +00:00
|
|
|
manifest.Apps = append(manifest.Apps, appcschema.RuntimeApp{
|
2015-08-10 18:15:13 +00:00
|
|
|
Name: *appName,
|
2015-04-30 01:11:30 +00:00
|
|
|
Image: appcschema.RuntimeImage{ID: *hash},
|
2015-05-13 20:00:37 +00:00
|
|
|
App: imgManifest.App,
|
2015-04-30 01:11:30 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-05-06 23:51:37 +00:00
|
|
|
volumeMap, ok := r.volumeGetter.GetVolumes(pod.UID)
|
|
|
|
if !ok {
|
2015-10-14 03:46:32 +00:00
|
|
|
return nil, fmt.Errorf("cannot get the volumes for pod %q", kubeletutil.FormatPodName(pod))
|
2015-05-06 23:51:37 +00:00
|
|
|
}
|
|
|
|
|
2015-04-30 01:11:30 +00:00
|
|
|
// Set global volumes.
|
|
|
|
for name, volume := range volumeMap {
|
|
|
|
volName, err := appctypes.NewACName(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot use the volume's name %q as ACName: %v", name, err)
|
|
|
|
}
|
|
|
|
manifest.Volumes = append(manifest.Volumes, appctypes.Volume{
|
|
|
|
Name: *volName,
|
|
|
|
Kind: "host",
|
2015-10-07 19:19:06 +00:00
|
|
|
Source: volume.Builder.GetPath(),
|
2015-04-30 01:11:30 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set global ports.
|
2015-05-14 00:57:54 +00:00
|
|
|
for _, port := range globalPortMappings {
|
|
|
|
name, err := appctypes.SanitizeACName(port.Name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot use the port's name %q as ACName: %v", port.Name, err)
|
2015-04-30 01:11:30 +00:00
|
|
|
}
|
2015-05-14 00:57:54 +00:00
|
|
|
portName := appctypes.MustACName(name)
|
|
|
|
manifest.Ports = append(manifest.Ports, appctypes.ExposedPort{
|
|
|
|
Name: *portName,
|
|
|
|
HostPort: uint(port.HostPort),
|
|
|
|
})
|
2015-04-30 01:11:30 +00:00
|
|
|
}
|
|
|
|
// TODO(yifan): Set pod-level isolators once it's supported in kubernetes.
|
|
|
|
return manifest, nil
|
|
|
|
}
|
|
|
|
|
2015-04-30 20:34:46 +00:00
|
|
|
func newUnitOption(section, name, value string) *unit.UnitOption {
|
|
|
|
return &unit.UnitOption{Section: section, Name: name, Value: value}
|
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
// apiPodToruntimePod converts an api.Pod to kubelet/container.Pod.
|
|
|
|
// we save the this for later reconstruction of the kubelet/container.Pod
|
|
|
|
// such as in GetPods().
|
2015-08-13 23:39:17 +00:00
|
|
|
func apiPodToruntimePod(uuid string, pod *api.Pod) *kubecontainer.Pod {
|
2015-04-30 20:34:46 +00:00
|
|
|
p := &kubecontainer.Pod{
|
|
|
|
ID: pod.UID,
|
|
|
|
Name: pod.Name,
|
|
|
|
Namespace: pod.Namespace,
|
|
|
|
}
|
|
|
|
for i := range pod.Spec.Containers {
|
|
|
|
c := &pod.Spec.Containers[i]
|
|
|
|
p.Containers = append(p.Containers, &kubecontainer.Container{
|
2015-10-07 17:58:05 +00:00
|
|
|
ID: buildContainerID(&containerID{uuid, c.Name}),
|
2015-04-30 20:34:46 +00:00
|
|
|
Name: c.Name,
|
|
|
|
Image: c.Image,
|
2015-05-15 23:14:08 +00:00
|
|
|
Hash: kubecontainer.HashContainer(c),
|
2015-04-30 20:34:46 +00:00
|
|
|
Created: time.Now().Unix(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
// serviceFilePath returns the absolute path of the service file.
|
|
|
|
func serviceFilePath(serviceName string) string {
|
|
|
|
return path.Join(systemdServiceDir, serviceName)
|
|
|
|
}
|
|
|
|
|
2015-04-30 20:34:46 +00:00
|
|
|
// preparePod will:
|
|
|
|
//
|
|
|
|
// 1. Invoke 'rkt prepare' to prepare the pod, and get the rkt pod uuid.
|
2015-09-01 02:25:26 +00:00
|
|
|
// 2. Create the unit file and save it under systemdUnitDir.
|
2015-04-30 20:34:46 +00:00
|
|
|
//
|
2015-08-26 01:50:42 +00:00
|
|
|
// On success, it will return a string that represents name of the unit file
|
|
|
|
// and the runtime pod.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *kubecontainer.Pod, error) {
|
2015-04-30 20:34:46 +00:00
|
|
|
// Generate the pod manifest from the pod spec.
|
2015-08-17 23:19:25 +00:00
|
|
|
manifest, err := r.makePodManifest(pod, pullSecrets)
|
2015-04-30 20:34:46 +00:00
|
|
|
if err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
2015-08-13 23:39:17 +00:00
|
|
|
manifestFile, err := ioutil.TempFile("", fmt.Sprintf("manifest-%s-", pod.Name))
|
2015-04-30 20:34:46 +00:00
|
|
|
if err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
manifestFile.Close()
|
|
|
|
if err := os.Remove(manifestFile.Name()); err != nil {
|
|
|
|
glog.Warningf("rkt: Cannot remove temp manifest file %q: %v", manifestFile.Name(), err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
data, err := json.Marshal(manifest)
|
|
|
|
if err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
// Since File.Write returns error if the written length is less than len(data),
|
|
|
|
// so check error is enough for us.
|
|
|
|
if _, err := manifestFile.Write(data); err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
// Run 'rkt prepare' to get the rkt UUID.
|
|
|
|
cmds := []string{"prepare", "--quiet", "--pod-manifest", manifestFile.Name()}
|
2015-09-01 02:25:26 +00:00
|
|
|
if r.config.Stage1Image != "" {
|
|
|
|
cmds = append(cmds, "--stage1-image", r.config.Stage1Image)
|
|
|
|
}
|
2015-04-30 20:34:46 +00:00
|
|
|
output, err := r.runCommand(cmds...)
|
|
|
|
if err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
if len(output) != 1 {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, fmt.Errorf("invalid output from 'rkt prepare': %v", output)
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
uuid := output[0]
|
2015-08-13 23:39:17 +00:00
|
|
|
glog.V(4).Infof("'rkt prepare' returns %q", uuid)
|
2015-04-30 20:34:46 +00:00
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
// Create systemd service file for the rkt pod.
|
2015-08-26 01:50:42 +00:00
|
|
|
runtimePod := apiPodToruntimePod(uuid, pod)
|
|
|
|
b, err := json.Marshal(runtimePod)
|
2015-04-30 20:34:46 +00:00
|
|
|
if err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
|
2015-08-13 23:39:17 +00:00
|
|
|
var runPrepared string
|
2015-09-14 21:56:51 +00:00
|
|
|
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork {
|
2015-10-08 21:48:45 +00:00
|
|
|
runPrepared = fmt.Sprintf("%s run-prepared --mds-register=false --net=host %s", r.rktBinAbsPath, uuid)
|
2015-08-13 23:39:17 +00:00
|
|
|
} else {
|
2015-10-08 21:48:45 +00:00
|
|
|
runPrepared = fmt.Sprintf("%s run-prepared --mds-register=false %s", r.rktBinAbsPath, uuid)
|
2015-08-13 23:39:17 +00:00
|
|
|
}
|
|
|
|
|
2015-09-15 16:43:59 +00:00
|
|
|
// TODO handle pod.Spec.HostPID
|
2015-08-10 08:14:01 +00:00
|
|
|
// TODO handle pod.Spec.HostIPC
|
2015-09-15 16:43:59 +00:00
|
|
|
|
2015-04-30 20:34:46 +00:00
|
|
|
units := []*unit.UnitOption{
|
|
|
|
newUnitOption(unitKubernetesSection, unitRktID, uuid),
|
|
|
|
newUnitOption(unitKubernetesSection, unitPodName, string(b)),
|
2015-08-13 23:39:17 +00:00
|
|
|
// This makes the service show up for 'systemctl list-units' even if it exits successfully.
|
|
|
|
newUnitOption("Service", "RemainAfterExit", "true"),
|
2015-04-30 20:34:46 +00:00
|
|
|
newUnitOption("Service", "ExecStart", runPrepared),
|
2015-09-01 02:25:26 +00:00
|
|
|
// This enables graceful stop.
|
|
|
|
newUnitOption("Service", "KillMode", "mixed"),
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
// Check if there's old rkt pod corresponding to the same pod, if so, update the restart count.
|
|
|
|
var restartCount int
|
|
|
|
var needReload bool
|
|
|
|
serviceName := makePodServiceFileName(pod.UID)
|
|
|
|
if _, err := os.Stat(serviceFilePath(serviceName)); err == nil {
|
|
|
|
// Service file already exists, that means the pod is being restarted.
|
2015-04-30 20:34:46 +00:00
|
|
|
needReload = true
|
2015-08-25 20:03:33 +00:00
|
|
|
_, info, err := r.readServiceFile(serviceName)
|
|
|
|
if err != nil {
|
|
|
|
glog.Warningf("rkt: Cannot get old pod's info from service file %q: (%v), will ignore it", serviceName, err)
|
|
|
|
restartCount = 0
|
|
|
|
} else {
|
|
|
|
restartCount = info.restartCount + 1
|
|
|
|
}
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
2015-08-25 20:03:33 +00:00
|
|
|
units = append(units, newUnitOption(unitKubernetesSection, unitRestartCount, strconv.Itoa(restartCount)))
|
|
|
|
|
2015-10-14 03:46:32 +00:00
|
|
|
glog.V(4).Infof("rkt: Creating service file %q for pod %q", serviceName, kubeletutil.FormatPodName(pod))
|
2015-08-25 20:03:33 +00:00
|
|
|
serviceFile, err := os.Create(serviceFilePath(serviceName))
|
2015-04-30 20:34:46 +00:00
|
|
|
if err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
2015-08-25 20:03:33 +00:00
|
|
|
defer serviceFile.Close()
|
2015-04-30 20:34:46 +00:00
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
_, err = io.Copy(serviceFile, unit.Serialize(units))
|
2015-04-30 20:34:46 +00:00
|
|
|
if err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
2015-08-25 20:03:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if needReload {
|
|
|
|
if err := r.systemd.Reload(); err != nil {
|
2015-08-26 01:50:42 +00:00
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return serviceName, runtimePod, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// generateEvents is a helper function that generates some container
|
|
|
|
// life cycle events for containers in a pod.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) generateEvents(runtimePod *kubecontainer.Pod, reason string, failure error) {
|
2015-08-26 01:50:42 +00:00
|
|
|
// Set up container references.
|
|
|
|
for _, c := range runtimePod.Containers {
|
2015-10-07 17:58:05 +00:00
|
|
|
containerID := c.ID
|
2015-08-26 01:50:42 +00:00
|
|
|
id, err := parseContainerID(containerID)
|
|
|
|
if err != nil {
|
|
|
|
glog.Warningf("Invalid container ID %q", containerID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ref, ok := r.containerRefManager.GetRef(containerID)
|
|
|
|
if !ok {
|
|
|
|
glog.Warningf("No ref for container %q", containerID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note that 'rkt id' is the pod id.
|
|
|
|
uuid := util.ShortenString(id.uuid, 8)
|
|
|
|
switch reason {
|
|
|
|
case "Created":
|
2015-10-27 08:50:18 +00:00
|
|
|
r.recorder.Eventf(ref, kubecontainer.CreatedContainer, "Created with rkt id %v", uuid)
|
2015-08-26 01:50:42 +00:00
|
|
|
case "Started":
|
2015-10-27 08:50:18 +00:00
|
|
|
r.recorder.Eventf(ref, kubecontainer.StartedContainer, "Started with rkt id %v", uuid)
|
2015-08-26 01:50:42 +00:00
|
|
|
case "Failed":
|
2015-10-27 08:50:18 +00:00
|
|
|
r.recorder.Eventf(ref, kubecontainer.FailedToStartContainer, "Failed to start with rkt id %v with error %v", uuid, failure)
|
2015-08-26 01:50:42 +00:00
|
|
|
case "Killing":
|
2015-10-27 08:50:18 +00:00
|
|
|
r.recorder.Eventf(ref, kubecontainer.KillingContainer, "Killing with rkt id %v", uuid)
|
2015-08-26 01:50:42 +00:00
|
|
|
default:
|
|
|
|
glog.Errorf("rkt: Unexpected event %q", reason)
|
2015-08-25 20:03:33 +00:00
|
|
|
}
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
2015-08-26 01:50:42 +00:00
|
|
|
return
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
// RunPod first creates the unit file for a pod, and then
|
|
|
|
// starts the unit over d-bus.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) RunPod(pod *api.Pod, pullSecrets []api.Secret) error {
|
2015-10-14 03:46:32 +00:00
|
|
|
glog.V(4).Infof("Rkt starts to run pod: name %q.", kubeletutil.FormatPodName(pod))
|
2015-04-30 20:34:46 +00:00
|
|
|
|
2015-08-26 01:50:42 +00:00
|
|
|
name, runtimePod, prepareErr := r.preparePod(pod, pullSecrets)
|
|
|
|
|
|
|
|
// Set container references and generate events.
|
|
|
|
// If preparedPod fails, then send out 'failed' events for each container.
|
|
|
|
// Otherwise, store the container references so we can use them later to send events.
|
|
|
|
for i, c := range pod.Spec.Containers {
|
|
|
|
ref, err := kubecontainer.GenerateContainerRef(pod, &c)
|
|
|
|
if err != nil {
|
2015-10-14 03:46:32 +00:00
|
|
|
glog.Errorf("Couldn't make a ref to pod %q, container %v: '%v'", kubeletutil.FormatPodName(pod), c.Name, err)
|
2015-08-26 01:50:42 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if prepareErr != nil {
|
2015-10-27 08:50:18 +00:00
|
|
|
r.recorder.Eventf(ref, kubecontainer.FailedToCreateContainer, "Failed to create rkt container with error: %v", prepareErr)
|
2015-08-26 01:50:42 +00:00
|
|
|
continue
|
|
|
|
}
|
2015-10-07 17:58:05 +00:00
|
|
|
containerID := runtimePod.Containers[i].ID
|
2015-08-26 01:50:42 +00:00
|
|
|
r.containerRefManager.SetRef(containerID, ref)
|
|
|
|
}
|
|
|
|
|
|
|
|
if prepareErr != nil {
|
|
|
|
return prepareErr
|
2015-04-30 20:34:46 +00:00
|
|
|
}
|
|
|
|
|
2015-08-26 01:50:42 +00:00
|
|
|
r.generateEvents(runtimePod, "Created", nil)
|
|
|
|
|
2015-04-30 20:34:46 +00:00
|
|
|
// TODO(yifan): This is the old version of go-systemd. Should update when libcontainer updates
|
|
|
|
// its version of go-systemd.
|
2015-08-25 20:03:33 +00:00
|
|
|
// RestartUnit has the same effect as StartUnit if the unit is not running, besides it can restart
|
|
|
|
// a unit if the unit file is changed and reloaded.
|
2015-08-26 01:50:42 +00:00
|
|
|
if _, err := r.systemd.RestartUnit(name, "replace"); err != nil {
|
|
|
|
r.generateEvents(runtimePod, "Failed", err)
|
2015-04-30 20:34:46 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-08-26 01:50:42 +00:00
|
|
|
|
|
|
|
r.generateEvents(runtimePod, "Started", nil)
|
|
|
|
|
2015-04-30 20:34:46 +00:00
|
|
|
return nil
|
|
|
|
}
|
2015-04-30 22:11:07 +00:00
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
// readServiceFile reads the service file and constructs the runtime pod and the rkt info.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) readServiceFile(serviceName string) (*kubecontainer.Pod, *rktInfo, error) {
|
2015-08-25 20:03:33 +00:00
|
|
|
f, err := os.Open(serviceFilePath(serviceName))
|
2015-04-30 22:11:07 +00:00
|
|
|
if err != nil {
|
2015-08-25 20:03:33 +00:00
|
|
|
return nil, nil, err
|
2015-04-30 22:11:07 +00:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
var pod kubecontainer.Pod
|
|
|
|
opts, err := unit.Deserialize(f)
|
|
|
|
if err != nil {
|
2015-08-25 20:03:33 +00:00
|
|
|
return nil, nil, err
|
2015-04-30 22:11:07 +00:00
|
|
|
}
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
info := emptyRktInfo()
|
2015-04-30 22:11:07 +00:00
|
|
|
for _, opt := range opts {
|
|
|
|
if opt.Section != unitKubernetesSection {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch opt.Name {
|
|
|
|
case unitPodName:
|
|
|
|
err = json.Unmarshal([]byte(opt.Value), &pod)
|
|
|
|
if err != nil {
|
2015-08-25 20:03:33 +00:00
|
|
|
return nil, nil, err
|
2015-04-30 22:11:07 +00:00
|
|
|
}
|
|
|
|
case unitRktID:
|
2015-08-25 20:03:33 +00:00
|
|
|
info.uuid = opt.Value
|
|
|
|
case unitRestartCount:
|
|
|
|
cnt, err := strconv.Atoi(opt.Value)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
info.restartCount = cnt
|
2015-04-30 22:11:07 +00:00
|
|
|
default:
|
2015-08-25 20:03:33 +00:00
|
|
|
return nil, nil, fmt.Errorf("rkt: unexpected key: %q", opt.Name)
|
2015-04-30 22:11:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
if info.isEmpty() {
|
|
|
|
return nil, nil, fmt.Errorf("rkt: cannot find rkt info of pod %v, unit file is broken", pod)
|
2015-04-30 22:11:07 +00:00
|
|
|
}
|
2015-08-25 20:03:33 +00:00
|
|
|
return &pod, info, nil
|
2015-04-30 22:11:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetPods runs 'systemctl list-unit' and 'rkt list' to get the list of rkt pods.
|
2015-08-08 21:29:57 +00:00
|
|
|
// Then it will use the result to construct a list of container runtime pods.
|
2015-04-30 22:11:07 +00:00
|
|
|
// If all is false, then only running pods will be returned, otherwise all pods will be
|
|
|
|
// returned.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) GetPods(all bool) ([]*kubecontainer.Pod, error) {
|
2015-04-30 22:11:07 +00:00
|
|
|
glog.V(4).Infof("Rkt getting pods")
|
|
|
|
|
|
|
|
units, err := r.systemd.ListUnits()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var pods []*kubecontainer.Pod
|
|
|
|
for _, u := range units {
|
|
|
|
if strings.HasPrefix(u.Name, kubernetesUnitPrefix) {
|
2015-11-06 02:19:45 +00:00
|
|
|
var status kubecontainer.ContainerStatus
|
|
|
|
switch {
|
|
|
|
case u.SubState == "running":
|
|
|
|
status = kubecontainer.ContainerStatusRunning
|
|
|
|
default:
|
|
|
|
status = kubecontainer.ContainerStatusExited
|
|
|
|
}
|
|
|
|
if !all && status != kubecontainer.ContainerStatusRunning {
|
2015-04-30 22:11:07 +00:00
|
|
|
continue
|
|
|
|
}
|
2015-08-25 20:03:33 +00:00
|
|
|
pod, _, err := r.readServiceFile(u.Name)
|
2015-04-30 22:11:07 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Warningf("rkt: Cannot construct pod from unit file: %v.", err)
|
|
|
|
continue
|
|
|
|
}
|
2015-11-19 02:18:53 +00:00
|
|
|
for _, c := range pod.Containers {
|
|
|
|
c.Status = status
|
|
|
|
}
|
2015-04-30 22:11:07 +00:00
|
|
|
pods = append(pods, pod)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pods, nil
|
|
|
|
}
|
2015-04-30 23:58:12 +00:00
|
|
|
|
|
|
|
// KillPod invokes 'systemctl kill' to kill the unit that runs the pod.
|
2015-08-21 22:08:12 +00:00
|
|
|
// TODO(yifan): Handle network plugin.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) KillPod(pod *api.Pod, runningPod kubecontainer.Pod) error {
|
2015-08-20 01:57:58 +00:00
|
|
|
glog.V(4).Infof("Rkt is killing pod: name %q.", runningPod.Name)
|
2015-08-26 01:50:42 +00:00
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
serviceName := makePodServiceFileName(runningPod.ID)
|
2015-08-26 01:50:42 +00:00
|
|
|
r.generateEvents(&runningPod, "Killing", nil)
|
|
|
|
for _, c := range runningPod.Containers {
|
2015-10-07 17:58:05 +00:00
|
|
|
r.containerRefManager.ClearRef(c.ID)
|
2015-08-26 01:50:42 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 21:04:41 +00:00
|
|
|
// Touch the systemd service file to update the mod time so it will
|
|
|
|
// not be garbage collected too soon.
|
|
|
|
if err := os.Chtimes(serviceFilePath(serviceName), time.Now(), time.Now()); err != nil {
|
|
|
|
glog.Errorf("rkt: Failed to change the modification time of the service file %q: %v", serviceName, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-01 02:25:26 +00:00
|
|
|
// Since all service file have 'KillMode=mixed', the processes in
|
|
|
|
// the unit's cgroup will receive a SIGKILL if the normal stop timeouts.
|
|
|
|
if _, err := r.systemd.StopUnit(serviceName, "replace"); err != nil {
|
2015-10-07 21:04:41 +00:00
|
|
|
glog.Errorf("rkt: Failed to stop unit %q: %v", serviceName, err)
|
2015-09-01 02:25:26 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-10-07 21:04:41 +00:00
|
|
|
return nil
|
2015-08-18 00:58:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// getPodStatus reads the service file and invokes 'rkt status $UUID' to get the
|
|
|
|
// pod's status.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) getPodStatus(serviceName string) (*api.PodStatus, error) {
|
2015-09-29 02:14:18 +00:00
|
|
|
var status api.PodStatus
|
|
|
|
|
2015-08-18 00:58:09 +00:00
|
|
|
// TODO(yifan): Get rkt uuid from the service file name.
|
2015-08-25 20:03:33 +00:00
|
|
|
pod, rktInfo, err := r.readServiceFile(serviceName)
|
2015-09-29 02:14:18 +00:00
|
|
|
if err != nil && !os.IsNotExist(err) {
|
2015-08-18 00:58:09 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-09-29 02:14:18 +00:00
|
|
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
// Pod does not exit, means it's not been created yet,
|
|
|
|
// return empty status for now.
|
|
|
|
// TODO(yifan): Maybe inspect the image and return waiting status.
|
|
|
|
return &status, nil
|
|
|
|
}
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
podInfo, err := r.getPodInfo(rktInfo.uuid)
|
2015-08-18 00:58:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-09-29 02:14:18 +00:00
|
|
|
status = makePodStatus(pod, podInfo, rktInfo)
|
2015-08-18 00:58:09 +00:00
|
|
|
return &status, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPodStatus returns the status of the given pod.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) GetPodStatus(pod *api.Pod) (*api.PodStatus, error) {
|
2015-08-25 20:03:33 +00:00
|
|
|
serviceName := makePodServiceFileName(pod.UID)
|
|
|
|
return r.getPodStatus(serviceName)
|
2015-04-30 23:58:12 +00:00
|
|
|
}
|
2015-05-04 23:51:31 +00:00
|
|
|
|
2015-10-21 20:04:10 +00:00
|
|
|
func (r *Runtime) Type() string {
|
|
|
|
return RktType
|
|
|
|
}
|
|
|
|
|
2015-05-04 23:51:31 +00:00
|
|
|
// Version invokes 'rkt version' to get the version information of the rkt
|
|
|
|
// runtime on the machine.
|
|
|
|
// The return values are an int array containers the version number.
|
|
|
|
//
|
|
|
|
// Example:
|
|
|
|
// rkt:0.3.2+git --> []int{0, 3, 2}.
|
|
|
|
//
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) Version() (kubecontainer.Version, error) {
|
2015-05-04 23:51:31 +00:00
|
|
|
output, err := r.runCommand("version")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Example output for 'rkt version':
|
|
|
|
// rkt version 0.3.2+git
|
|
|
|
// appc version 0.3.0+git
|
|
|
|
for _, line := range output {
|
|
|
|
tuples := strings.Split(strings.TrimSpace(line), " ")
|
|
|
|
if len(tuples) != 3 {
|
|
|
|
glog.Warningf("rkt: cannot parse the output: %q.", line)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if tuples[0] == "rkt" {
|
|
|
|
return parseVersion(tuples[2])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("rkt: cannot determine the version")
|
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
// TODO(yifan): This is very racy, unefficient, and unsafe, we need to provide
|
|
|
|
// different namespaces. See: https://github.com/coreos/rkt/issues/836.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) writeDockerAuthConfig(image string, credsSlice []docker.AuthConfiguration) error {
|
2015-08-17 23:19:25 +00:00
|
|
|
if len(credsSlice) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-05-08 17:30:59 +00:00
|
|
|
creds := docker.AuthConfiguration{}
|
|
|
|
// TODO handle multiple creds
|
|
|
|
if len(credsSlice) >= 1 {
|
|
|
|
creds = credsSlice[0]
|
|
|
|
}
|
|
|
|
|
2015-05-04 23:51:31 +00:00
|
|
|
registry := "index.docker.io"
|
|
|
|
// Image spec: [<registry>/]<repository>/<image>[:<version]
|
|
|
|
explicitRegistry := (strings.Count(image, "/") == 2)
|
|
|
|
if explicitRegistry {
|
|
|
|
registry = strings.Split(image, "/")[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
localConfigDir := rktLocalConfigDir
|
|
|
|
if r.config.LocalConfigDir != "" {
|
|
|
|
localConfigDir = r.config.LocalConfigDir
|
|
|
|
}
|
|
|
|
authDir := path.Join(localConfigDir, "auth.d")
|
|
|
|
if _, err := os.Stat(authDir); os.IsNotExist(err) {
|
|
|
|
if err := os.Mkdir(authDir, 0600); err != nil {
|
|
|
|
glog.Errorf("rkt: Cannot create auth dir: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2015-08-17 23:19:25 +00:00
|
|
|
|
2015-05-04 23:51:31 +00:00
|
|
|
config := fmt.Sprintf(dockerAuthTemplate, registry, creds.Username, creds.Password)
|
2015-08-17 23:19:25 +00:00
|
|
|
if err := ioutil.WriteFile(path.Join(authDir, registry+".json"), []byte(config), 0600); err != nil {
|
2015-05-04 23:51:31 +00:00
|
|
|
glog.Errorf("rkt: Cannot write docker auth config file: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PullImage invokes 'rkt fetch' to download an aci.
|
2015-05-05 21:25:55 +00:00
|
|
|
// TODO(yifan): Now we only support docker images, this should be changed
|
|
|
|
// once the format of image is landed, see:
|
|
|
|
//
|
2015-08-06 01:08:26 +00:00
|
|
|
// http://issue.k8s.io/7203
|
2015-05-05 21:25:55 +00:00
|
|
|
//
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) PullImage(image kubecontainer.ImageSpec, pullSecrets []api.Secret) error {
|
2015-05-06 21:42:03 +00:00
|
|
|
img := image.Image
|
2015-05-05 21:25:55 +00:00
|
|
|
// TODO(yifan): The credential operation is a copy from dockertools package,
|
|
|
|
// Need to resolve the code duplication.
|
2015-08-17 23:19:25 +00:00
|
|
|
repoToPull, _ := parseImageName(img)
|
2015-05-08 17:53:00 +00:00
|
|
|
keyring, err := credentialprovider.MakeDockerKeyring(pullSecrets, r.dockerKeyring)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
creds, ok := keyring.Lookup(repoToPull)
|
2015-05-05 21:25:55 +00:00
|
|
|
if !ok {
|
|
|
|
glog.V(1).Infof("Pulling image %s without credentials", img)
|
|
|
|
}
|
2015-05-04 23:51:31 +00:00
|
|
|
|
2015-05-05 21:25:55 +00:00
|
|
|
// Let's update a json.
|
|
|
|
// TODO(yifan): Find a way to feed this to rkt.
|
|
|
|
if err := r.writeDockerAuthConfig(img, creds); err != nil {
|
|
|
|
return err
|
2015-05-04 23:51:31 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
if _, err := r.runCommand("fetch", dockerPrefix+img); err != nil {
|
|
|
|
glog.Errorf("Failed to fetch: %v", err)
|
|
|
|
return err
|
2015-05-04 23:51:31 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
// TODO(yifan): Searching the image via 'rkt images' might not be the most efficient way.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) IsImagePresent(image kubecontainer.ImageSpec) (bool, error) {
|
2015-08-17 23:19:25 +00:00
|
|
|
repoToPull, tag := parseImageName(image.Image)
|
2015-09-16 18:05:19 +00:00
|
|
|
// Example output of 'rkt image list --fields=name':
|
|
|
|
//
|
|
|
|
// NAME
|
|
|
|
// nginx:latest
|
|
|
|
// coreos.com/rkt/stage1:0.8.1
|
|
|
|
//
|
|
|
|
// With '--no-legend=true' the fist line (NAME) will be omitted.
|
|
|
|
output, err := r.runCommand("image", "list", "--no-legend=true", "--fields=name")
|
2015-08-17 23:19:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
2015-05-04 23:51:31 +00:00
|
|
|
}
|
2015-08-17 23:19:25 +00:00
|
|
|
for _, line := range output {
|
|
|
|
parts := strings.Split(strings.TrimSpace(line), ":")
|
|
|
|
|
|
|
|
var imgName, imgTag string
|
|
|
|
switch len(parts) {
|
|
|
|
case 1:
|
|
|
|
imgName, imgTag = parts[0], defaultImageTag
|
|
|
|
case 2:
|
|
|
|
imgName, imgTag = parts[0], parts[1]
|
|
|
|
default:
|
|
|
|
continue
|
|
|
|
}
|
2015-05-04 23:51:31 +00:00
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
if imgName == repoToPull && imgTag == tag {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
2015-05-04 23:51:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SyncPod syncs the running pod to match the specified desired pod.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) SyncPod(pod *api.Pod, runningPod kubecontainer.Pod, podStatus api.PodStatus, pullSecrets []api.Secret, backOff *util.Backoff) error {
|
2015-10-14 03:46:32 +00:00
|
|
|
podFullName := kubeletutil.FormatPodName(pod)
|
2015-05-04 23:51:31 +00:00
|
|
|
|
|
|
|
// Add references to all containers.
|
2015-10-07 17:58:05 +00:00
|
|
|
unidentifiedContainers := make(map[kubecontainer.ContainerID]*kubecontainer.Container)
|
2015-05-04 23:51:31 +00:00
|
|
|
for _, c := range runningPod.Containers {
|
|
|
|
unidentifiedContainers[c.ID] = c
|
|
|
|
}
|
|
|
|
|
|
|
|
restartPod := false
|
|
|
|
for _, container := range pod.Spec.Containers {
|
2015-05-15 23:14:08 +00:00
|
|
|
expectedHash := kubecontainer.HashContainer(&container)
|
2015-05-04 23:51:31 +00:00
|
|
|
|
|
|
|
c := runningPod.FindContainerByName(container.Name)
|
|
|
|
if c == nil {
|
2015-08-25 17:39:41 +00:00
|
|
|
if kubecontainer.ShouldContainerBeRestarted(&container, pod, &podStatus) {
|
2015-05-04 23:51:31 +00:00
|
|
|
glog.V(3).Infof("Container %+v is dead, but RestartPolicy says that we should restart it.", container)
|
|
|
|
// TODO(yifan): Containers in one pod are fate-sharing at this moment, see:
|
|
|
|
// https://github.com/appc/spec/issues/276.
|
|
|
|
restartPod = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-08-10 17:30:34 +00:00
|
|
|
// TODO: check for non-root image directives. See ../docker/manager.go#SyncPod
|
|
|
|
|
2015-05-04 23:51:31 +00:00
|
|
|
// TODO(yifan): Take care of host network change.
|
|
|
|
containerChanged := c.Hash != 0 && c.Hash != expectedHash
|
|
|
|
if containerChanged {
|
|
|
|
glog.Infof("Pod %q container %q hash changed (%d vs %d), it will be killed and re-created.", podFullName, container.Name, c.Hash, expectedHash)
|
|
|
|
restartPod = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2015-10-19 22:15:59 +00:00
|
|
|
liveness, found := r.livenessManager.Get(c.ID)
|
|
|
|
if found && liveness != proberesults.Success && pod.Spec.RestartPolicy != api.RestartPolicyNever {
|
|
|
|
glog.Infof("Pod %q container %q is unhealthy, it will be killed and re-created.", podFullName, container.Name)
|
2015-05-04 23:51:31 +00:00
|
|
|
restartPod = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(unidentifiedContainers, c.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is any unidentified containers, restart the pod.
|
|
|
|
if len(unidentifiedContainers) > 0 {
|
|
|
|
restartPod = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if restartPod {
|
2015-09-29 02:14:18 +00:00
|
|
|
// Kill the pod only if the pod is actually running.
|
|
|
|
if len(runningPod.Containers) > 0 {
|
|
|
|
if err := r.KillPod(pod, runningPod); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-05-04 23:51:31 +00:00
|
|
|
}
|
2015-08-17 23:19:25 +00:00
|
|
|
if err := r.RunPod(pod, pullSecrets); err != nil {
|
2015-05-04 23:51:31 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-28 22:46:29 +00:00
|
|
|
// GarbageCollect collects the pods/containers.
|
|
|
|
// TODO(yifan): Enforce the gc policy, also, it would be better if we can
|
|
|
|
// just GC kubernetes pods.
|
2015-10-07 07:39:59 +00:00
|
|
|
func (r *Runtime) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy) error {
|
2015-05-04 23:51:31 +00:00
|
|
|
if err := exec.Command("systemctl", "reset-failed").Run(); err != nil {
|
2015-10-07 21:04:41 +00:00
|
|
|
glog.Errorf("rkt: Failed to reset failed systemd services: %v, continue to gc anyway...", err)
|
2015-05-04 23:51:31 +00:00
|
|
|
}
|
2015-10-07 21:04:41 +00:00
|
|
|
|
2015-10-07 07:39:59 +00:00
|
|
|
if _, err := r.runCommand("gc", "--grace-period="+gcPolicy.MinAge.String(), "--expire-prepared="+gcPolicy.MinAge.String()); err != nil {
|
2015-05-04 23:51:31 +00:00
|
|
|
glog.Errorf("rkt: Failed to gc: %v", err)
|
2015-10-07 21:04:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GC all inactive systemd service files.
|
|
|
|
units, err := r.systemd.ListUnits()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("rkt: Failed to list units: %v", err)
|
2015-05-04 23:51:31 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-10-07 21:04:41 +00:00
|
|
|
runningKubernetesUnits := sets.NewString()
|
|
|
|
for _, u := range units {
|
|
|
|
if strings.HasPrefix(u.Name, kubernetesUnitPrefix) && u.SubState == "running" {
|
|
|
|
runningKubernetesUnits.Insert(u.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
files, err := ioutil.ReadDir(systemdServiceDir)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("rkt: Failed to read the systemd service directory: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, f := range files {
|
2015-10-19 18:23:54 +00:00
|
|
|
if strings.HasPrefix(f.Name(), kubernetesUnitPrefix) && !runningKubernetesUnits.Has(f.Name()) && f.ModTime().Before(time.Now().Add(-gcPolicy.MinAge)) {
|
2015-10-07 21:04:41 +00:00
|
|
|
glog.V(4).Infof("rkt: Removing inactive systemd service file: %v", f.Name())
|
|
|
|
if err := os.Remove(serviceFilePath(f.Name())); err != nil {
|
|
|
|
glog.Warningf("rkt: Failed to remove inactive systemd service file %v: %v", f.Name(), err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-04 23:51:31 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-13 23:39:17 +00:00
|
|
|
// Note: In rkt, the container ID is in the form of "UUID:appName", where
|
2015-05-04 23:51:31 +00:00
|
|
|
// appName is the container name.
|
2015-09-01 02:25:26 +00:00
|
|
|
// TODO(yifan): If the rkt is using lkvm as the stage1 image, then this function will fail.
|
2015-10-07 17:58:05 +00:00
|
|
|
func (r *Runtime) RunInContainer(containerID kubecontainer.ContainerID, cmd []string) ([]byte, error) {
|
2015-05-04 23:51:31 +00:00
|
|
|
glog.V(4).Infof("Rkt running in container.")
|
|
|
|
|
|
|
|
id, err := parseContainerID(containerID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-08-13 23:39:17 +00:00
|
|
|
args := append([]string{}, "enter", fmt.Sprintf("--app=%s", id.appName), id.uuid)
|
2015-05-04 23:51:31 +00:00
|
|
|
args = append(args, cmd...)
|
|
|
|
|
2015-10-08 01:38:01 +00:00
|
|
|
result, err := r.buildCommand(args...).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
|
|
err = &rktExitError{exitErr}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// rktExitError implemets /pkg/util/exec.ExitError interface.
|
|
|
|
type rktExitError struct{ *exec.ExitError }
|
|
|
|
|
|
|
|
var _ utilexec.ExitError = &rktExitError{}
|
|
|
|
|
|
|
|
func (r *rktExitError) ExitStatus() int {
|
|
|
|
if status, ok := r.Sys().(syscall.WaitStatus); ok {
|
|
|
|
return status.ExitStatus()
|
|
|
|
}
|
|
|
|
return 0
|
2015-05-04 23:51:31 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 17:58:05 +00:00
|
|
|
func (r *Runtime) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error {
|
2015-08-18 00:58:09 +00:00
|
|
|
return fmt.Errorf("unimplemented")
|
2015-07-28 04:48:55 +00:00
|
|
|
}
|
|
|
|
|
2015-08-13 23:39:17 +00:00
|
|
|
// Note: In rkt, the container ID is in the form of "UUID:appName", where UUID is
|
|
|
|
// the rkt UUID, and appName is the container name.
|
2015-09-01 02:25:26 +00:00
|
|
|
// TODO(yifan): If the rkt is using lkvm as the stage1 image, then this function will fail.
|
2015-10-07 17:58:05 +00:00
|
|
|
func (r *Runtime) ExecInContainer(containerID kubecontainer.ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error {
|
2015-05-04 23:51:31 +00:00
|
|
|
glog.V(4).Infof("Rkt execing in container.")
|
|
|
|
|
|
|
|
id, err := parseContainerID(containerID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-08-13 23:39:17 +00:00
|
|
|
args := append([]string{}, "enter", fmt.Sprintf("--app=%s", id.appName), id.uuid)
|
2015-05-04 23:51:31 +00:00
|
|
|
args = append(args, cmd...)
|
|
|
|
command := r.buildCommand(args...)
|
|
|
|
|
|
|
|
if tty {
|
2015-05-08 06:36:47 +00:00
|
|
|
p, err := kubecontainer.StartPty(command)
|
2015-05-04 23:51:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer p.Close()
|
|
|
|
|
|
|
|
// make sure to close the stdout stream
|
|
|
|
defer stdout.Close()
|
|
|
|
|
|
|
|
if stdin != nil {
|
|
|
|
go io.Copy(p, stdin)
|
|
|
|
}
|
|
|
|
if stdout != nil {
|
|
|
|
go io.Copy(stdout, p)
|
|
|
|
}
|
|
|
|
return command.Wait()
|
|
|
|
}
|
|
|
|
if stdin != nil {
|
|
|
|
// Use an os.Pipe here as it returns true *os.File objects.
|
2015-05-20 23:00:19 +00:00
|
|
|
// This way, if you run 'kubectl exec <pod> -i bash' (no tty) and type 'exit',
|
2015-05-04 23:51:31 +00:00
|
|
|
// the call below to command.Run() can unblock because its Stdin is the read half
|
|
|
|
// of the pipe.
|
|
|
|
r, w, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
go io.Copy(w, stdin)
|
|
|
|
|
|
|
|
command.Stdin = r
|
|
|
|
}
|
|
|
|
if stdout != nil {
|
|
|
|
command.Stdout = stdout
|
|
|
|
}
|
|
|
|
if stderr != nil {
|
|
|
|
command.Stderr = stderr
|
|
|
|
}
|
|
|
|
return command.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// findRktID returns the rkt uuid for the pod.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) findRktID(pod *kubecontainer.Pod) (string, error) {
|
2015-08-25 20:03:33 +00:00
|
|
|
serviceName := makePodServiceFileName(pod.ID)
|
|
|
|
|
|
|
|
f, err := os.Open(serviceFilePath(serviceName))
|
2015-05-04 23:51:31 +00:00
|
|
|
if err != nil {
|
2015-08-25 20:03:33 +00:00
|
|
|
if os.IsNotExist(err) {
|
2015-09-01 02:25:26 +00:00
|
|
|
return "", fmt.Errorf("no service file %v for runtime pod %q, ID %q", serviceName, pod.Name, pod.ID)
|
2015-08-25 20:03:33 +00:00
|
|
|
}
|
2015-05-04 23:51:31 +00:00
|
|
|
return "", err
|
|
|
|
}
|
2015-08-25 20:03:33 +00:00
|
|
|
defer f.Close()
|
2015-05-04 23:51:31 +00:00
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
opts, err := unit.Deserialize(f)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2015-05-04 23:51:31 +00:00
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
for _, opt := range opts {
|
|
|
|
if opt.Section == unitKubernetesSection && opt.Name == unitRktID {
|
|
|
|
return opt.Value, nil
|
2015-05-04 23:51:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", fmt.Errorf("rkt uuid not found for pod %v", pod)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PortForward executes socat in the pod's network namespace and copies
|
|
|
|
// data between stream (representing the user's local connection on their
|
|
|
|
// computer) and the specified port in the container.
|
|
|
|
//
|
|
|
|
// TODO:
|
|
|
|
// - match cgroups of container
|
|
|
|
// - should we support nsenter + socat on the host? (current impl)
|
|
|
|
// - should we support nsenter + socat in a container, running with elevated privs and --pid=host?
|
|
|
|
//
|
|
|
|
// TODO(yifan): Merge with the same function in dockertools.
|
2015-09-01 02:25:26 +00:00
|
|
|
// TODO(yifan): If the rkt is using lkvm as the stage1 image, then this function will fail.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) PortForward(pod *kubecontainer.Pod, port uint16, stream io.ReadWriteCloser) error {
|
2015-05-04 23:51:31 +00:00
|
|
|
glog.V(4).Infof("Rkt port forwarding in container.")
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
rktID, err := r.findRktID(pod)
|
2015-05-04 23:51:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-08-25 20:03:33 +00:00
|
|
|
info, err := r.getPodInfo(rktID)
|
2015-05-04 23:51:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-22 20:29:51 +00:00
|
|
|
socatPath, lookupErr := exec.LookPath("socat")
|
2015-05-04 23:51:31 +00:00
|
|
|
if lookupErr != nil {
|
|
|
|
return fmt.Errorf("unable to do port forwarding: socat not found.")
|
|
|
|
}
|
|
|
|
|
2015-09-22 20:29:51 +00:00
|
|
|
args := []string{"-t", fmt.Sprintf("%d", info.pid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)}
|
|
|
|
|
|
|
|
nsenterPath, lookupErr := exec.LookPath("nsenter")
|
2015-05-04 23:51:31 +00:00
|
|
|
if lookupErr != nil {
|
|
|
|
return fmt.Errorf("unable to do port forwarding: nsenter not found.")
|
|
|
|
}
|
2015-09-22 20:29:51 +00:00
|
|
|
|
|
|
|
command := exec.Command(nsenterPath, args...)
|
2015-05-04 23:51:31 +00:00
|
|
|
command.Stdout = stream
|
2015-09-22 20:29:51 +00:00
|
|
|
|
|
|
|
// If we use Stdin, command.Run() won't return until the goroutine that's copying
|
|
|
|
// from stream finishes. Unfortunately, if you have a client like telnet connected
|
|
|
|
// via port forwarding, as long as the user's telnet client is connected to the user's
|
|
|
|
// local listener that port forwarding sets up, the telnet session never exits. This
|
|
|
|
// means that even if socat has finished running, command.Run() won't ever return
|
|
|
|
// (because the client still has the connection and stream open).
|
|
|
|
//
|
|
|
|
// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
|
|
|
|
// when the command (socat) exits.
|
|
|
|
inPipe, err := command.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err)
|
|
|
|
}
|
|
|
|
go func() {
|
|
|
|
io.Copy(inPipe, stream)
|
|
|
|
inPipe.Close()
|
|
|
|
}()
|
|
|
|
|
2015-05-04 23:51:31 +00:00
|
|
|
return command.Run()
|
|
|
|
}
|
|
|
|
|
2015-05-14 20:19:39 +00:00
|
|
|
// isUUID returns true if the input is a valid rkt UUID,
|
|
|
|
// e.g. "2372bc17-47cb-43fb-8d78-20b31729feda".
|
|
|
|
func isUUID(input string) bool {
|
|
|
|
if _, err := appctypes.NewUUID(input); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2015-08-18 00:58:09 +00:00
|
|
|
// getPodInfo returns the pod info of a single pod according
|
|
|
|
// to the uuid.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) getPodInfo(uuid string) (*podInfo, error) {
|
2015-08-18 00:58:09 +00:00
|
|
|
status, err := r.runCommand("status", uuid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
info, err := parsePodInfo(status)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
// getImageByName tries to find the image info with the given image name.
|
|
|
|
// TODO(yifan): Replace with 'rkt image cat-manifest'.
|
|
|
|
// imageName should be in the form of 'example.com/app:latest', which should matches
|
|
|
|
// the result of 'rkt image list'. If the version is empty, then 'latest' is assumed.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) getImageByName(imageName string) (*kubecontainer.Image, error) {
|
2015-08-17 23:19:25 +00:00
|
|
|
// TODO(yifan): Print hash in 'rkt image cat-manifest'?
|
|
|
|
images, err := r.ListImages()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
nameVersion := strings.Split(imageName, ":")
|
|
|
|
switch len(nameVersion) {
|
|
|
|
case 1:
|
|
|
|
imageName += ":" + defaultImageTag
|
|
|
|
case 2:
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("invalid image name: %q, requires 'name[:version]'")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, img := range images {
|
|
|
|
for _, t := range img.Tags {
|
|
|
|
if t == imageName {
|
|
|
|
return &img, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("cannot find the image %q", imageName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListImages lists all the available appc images on the machine by invoking 'rkt image list'.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) ListImages() ([]kubecontainer.Image, error) {
|
2015-10-08 21:48:45 +00:00
|
|
|
// Example output of 'rkt image list --fields=id,name --full':
|
2015-09-16 18:05:19 +00:00
|
|
|
//
|
2015-10-08 21:48:45 +00:00
|
|
|
// ID NAME
|
2015-09-16 18:05:19 +00:00
|
|
|
// sha512-374770396f23dd153937cd66694fe705cf375bcec7da00cf87e1d9f72c192da7 nginx:latest
|
|
|
|
// sha512-bead9e0df8b1b4904d0c57ade2230e6d236e8473f62614a8bc6dcf11fc924123 coreos.com/rkt/stage1:0.8.1
|
|
|
|
//
|
|
|
|
// With '--no-legend=true' the fist line (KEY NAME) will be omitted.
|
2015-10-08 21:48:45 +00:00
|
|
|
output, err := r.runCommand("image", "list", "--no-legend=true", "--fields=id,name", "--full")
|
2015-05-06 21:20:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(output) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
var images []kubecontainer.Image
|
2015-05-06 21:20:39 +00:00
|
|
|
for _, line := range output {
|
2015-08-17 23:19:25 +00:00
|
|
|
img, err := parseImageInfo(line)
|
|
|
|
if err != nil {
|
2015-05-06 21:20:39 +00:00
|
|
|
glog.Warningf("rkt: Cannot parse image info from %q: %v", line, err)
|
|
|
|
continue
|
|
|
|
}
|
2015-08-17 23:19:25 +00:00
|
|
|
images = append(images, *img)
|
2015-05-06 21:20:39 +00:00
|
|
|
}
|
|
|
|
return images, nil
|
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
// parseImageInfo creates the kubecontainer.Image struct by parsing the string in the result of 'rkt image list',
|
|
|
|
// the input looks like:
|
|
|
|
//
|
|
|
|
// sha512-91e98d7f1679a097c878203c9659f2a26ae394656b3147963324c61fa3832f15 coreos.com/etcd:v2.0.9
|
|
|
|
//
|
|
|
|
func parseImageInfo(input string) (*kubecontainer.Image, error) {
|
|
|
|
idName := strings.Split(strings.TrimSpace(input), "\t")
|
|
|
|
if len(idName) != 2 {
|
|
|
|
return nil, fmt.Errorf("invalid image information from 'rkt image list': %q", input)
|
|
|
|
}
|
|
|
|
return &kubecontainer.Image{
|
|
|
|
ID: idName[0],
|
|
|
|
Tags: []string{idName[1]},
|
|
|
|
}, nil
|
|
|
|
}
|
2015-05-13 20:00:37 +00:00
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
// RemoveImage removes an on-disk image using 'rkt image rm'.
|
|
|
|
// TODO(yifan): Use image ID to reference image.
|
2015-09-28 22:46:29 +00:00
|
|
|
func (r *Runtime) RemoveImage(image kubecontainer.ImageSpec) error {
|
2015-08-17 23:19:25 +00:00
|
|
|
img, err := r.getImageByName(image.Image)
|
2015-05-13 20:00:37 +00:00
|
|
|
if err != nil {
|
2015-08-17 23:19:25 +00:00
|
|
|
return err
|
2015-05-13 20:00:37 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 23:19:25 +00:00
|
|
|
if _, err := r.runCommand("image", "rm", img.ID); err != nil {
|
|
|
|
return err
|
2015-05-06 21:20:39 +00:00
|
|
|
}
|
2015-08-17 23:19:25 +00:00
|
|
|
return nil
|
2015-05-06 21:20:39 +00:00
|
|
|
}
|
2015-11-10 22:18:47 +00:00
|
|
|
|
|
|
|
func (r *Runtime) GetRawPodStatus(uid types.UID, name, namespace string) (*kubecontainer.RawPodStatus, error) {
|
|
|
|
return nil, fmt.Errorf("Not implemented yet")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) ConvertRawToPodStatus(_ *api.Pod, _ *kubecontainer.RawPodStatus) (*api.PodStatus, error) {
|
|
|
|
return nil, fmt.Errorf("Not implemented yet")
|
|
|
|
}
|