2016-11-16 21:40:23 +00:00
/ *
Copyright 2016 The Kubernetes Authors .
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 services
import (
"flag"
"fmt"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/golang/glog"
2017-11-15 18:17:06 +00:00
"github.com/spf13/pflag"
2016-11-16 21:40:23 +00:00
2017-11-15 18:17:06 +00:00
"k8s.io/apimachinery/pkg/runtime"
2017-04-28 22:08:57 +00:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
2017-11-15 18:17:06 +00:00
"k8s.io/kubernetes/cmd/kubelet/app/options"
2017-04-28 22:08:57 +00:00
"k8s.io/kubernetes/pkg/features"
2017-11-15 18:17:06 +00:00
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1"
2016-11-16 21:40:23 +00:00
"k8s.io/kubernetes/test/e2e/framework"
"k8s.io/kubernetes/test/e2e_node/builder"
)
// TODO(random-liu): Replace this with standard kubelet launcher.
// args is the type used to accumulate args from the flags with the same name.
type args [ ] string
// String function of flag.Value
func ( a * args ) String ( ) string {
return fmt . Sprint ( * a )
}
// Set function of flag.Value
func ( a * args ) Set ( value string ) error {
// Someone else is calling flag.Parse after the flags are parsed in the
// test framework. Use this to avoid the flag being parsed twice.
// TODO(random-liu): Figure out who is parsing the flags.
if flag . Parsed ( ) {
return nil
}
// Note that we assume all white space in flag string is separating fields
na := strings . Fields ( value )
* a = append ( * a , na ... )
return nil
}
// kubeletArgs is the override kubelet args specified by the test runner.
var kubeletArgs args
func init ( ) {
flag . Var ( & kubeletArgs , "kubelet-flags" , "Kubelet flags passed to kubelet, this will override default kubelet flags in the test. Flags specified in multiple kubelet-flags will be concatenate." )
}
2016-12-02 08:32:38 +00:00
// RunKubelet starts kubelet and waits for termination signal. Once receives the
// termination signal, it will stop the kubelet gracefully.
func RunKubelet ( ) {
var err error
// Enable monitorParent to make sure kubelet will receive termination signal
// when test process exits.
e := NewE2EServices ( true /* monitorParent */ )
defer e . Stop ( )
e . kubelet , err = e . startKubelet ( )
if err != nil {
glog . Fatalf ( "Failed to start kubelet: %v" , err )
}
// Wait until receiving a termination signal.
waitForTerminationSignal ( )
}
2016-11-16 21:40:23 +00:00
const (
// Ports of different e2e services.
2017-07-05 06:08:47 +00:00
kubeletPort = "10250"
kubeletReadOnlyPort = "10255"
2017-10-26 12:55:13 +00:00
KubeletRootDirectory = "/var/lib/kubelet"
2016-11-16 21:40:23 +00:00
// Health check url of kubelet
kubeletHealthCheckURL = "http://127.0.0.1:" + kubeletReadOnlyPort + "/healthz"
)
// startKubelet starts the Kubelet in a separate process or returns an error
// if the Kubelet fails to start.
func ( e * E2EServices ) startKubelet ( ) ( * server , error ) {
glog . Info ( "Starting kubelet" )
2017-01-18 00:08:24 +00:00
2017-04-28 22:08:57 +00:00
// set feature gates so we can check which features are enabled and pass the appropriate flags
utilfeature . DefaultFeatureGate . Set ( framework . TestContext . FeatureGates )
2017-01-18 00:08:24 +00:00
// Build kubeconfig
kubeconfigPath , err := createKubeconfigCWD ( )
if err != nil {
return nil , err
}
2016-11-16 21:40:23 +00:00
// Create pod manifest path
manifestPath , err := createPodManifestDirectory ( )
if err != nil {
return nil , err
}
e . rmDirs = append ( e . rmDirs , manifestPath )
2017-10-26 12:55:13 +00:00
err = createRootDirectory ( KubeletRootDirectory )
2017-07-05 06:08:47 +00:00
if err != nil {
return nil , err
}
2016-11-16 21:40:23 +00:00
var killCommand , restartCommand * exec . Cmd
var isSystemd bool
// Apply default kubelet flags.
cmdArgs := [ ] string { }
2017-11-15 18:17:06 +00:00
kubeArgs := [ ] string { }
2016-11-16 21:40:23 +00:00
if systemdRun , err := exec . LookPath ( "systemd-run" ) ; err == nil {
// On systemd services, detection of a service / unit works reliably while
// detection of a process started from an ssh session does not work.
// Since kubelet will typically be run as a service it also makes more
// sense to test it that way
isSystemd = true
unitName := fmt . Sprintf ( "kubelet-%d.service" , rand . Int31 ( ) )
2017-03-20 18:29:27 +00:00
cmdArgs = append ( cmdArgs , systemdRun , "--unit=" + unitName , "--slice=runtime.slice" , "--remain-after-exit" , builder . GetKubeletServerBin ( ) )
2016-11-16 21:40:23 +00:00
killCommand = exec . Command ( "systemctl" , "kill" , unitName )
restartCommand = exec . Command ( "systemctl" , "restart" , unitName )
2017-10-10 22:16:32 +00:00
e . logs [ "kubelet.log" ] = LogFileData {
Name : "kubelet.log" ,
JournalctlCommand : [ ] string { "-u" , unitName } ,
2016-11-16 21:40:23 +00:00
}
2017-11-15 18:17:06 +00:00
kubeArgs = append ( kubeArgs ,
2017-03-20 18:29:27 +00:00
"--kubelet-cgroups=/kubelet.slice" ,
"--cgroup-root=/" ,
)
2016-11-16 21:40:23 +00:00
} else {
cmdArgs = append ( cmdArgs , builder . GetKubeletServerBin ( ) )
2017-11-15 18:17:06 +00:00
kubeArgs = append ( kubeArgs ,
2017-10-10 22:16:32 +00:00
// TODO(random-liu): Get rid of this docker specific thing.
2016-11-16 21:40:23 +00:00
"--runtime-cgroups=/docker-daemon" ,
"--kubelet-cgroups=/kubelet" ,
"--cgroup-root=/" ,
"--system-cgroups=/system" ,
)
}
2017-11-15 18:17:06 +00:00
kubeArgs = append ( kubeArgs ,
2017-01-18 00:08:24 +00:00
"--kubeconfig" , kubeconfigPath ,
2016-11-16 21:40:23 +00:00
"--address" , "0.0.0.0" ,
"--port" , kubeletPort ,
"--read-only-port" , kubeletReadOnlyPort ,
2017-10-26 12:55:13 +00:00
"--root-dir" , KubeletRootDirectory ,
2016-11-16 21:40:23 +00:00
"--volume-stats-agg-period" , "10s" , // Aggregate volumes frequently so tests don't need to wait as long
"--allow-privileged" , "true" ,
"--serialize-image-pulls" , "false" ,
2017-01-19 04:13:22 +00:00
"--pod-manifest-path" , manifestPath ,
2016-11-16 21:40:23 +00:00
"--file-check-frequency" , "10s" , // Check file frequently so tests won't wait too long
2017-09-01 21:43:13 +00:00
"--docker-disable-shared-pid=false" ,
2017-06-16 05:15:08 +00:00
// Assign a fixed CIDR to the node because there is no node controller.
//
// Note: this MUST be in sync with with the IP in
// - cluster/gce/config-test.sh and
// - test/e2e_node/conformance/run_test.sh.
"--pod-cidr" , "10.100.0.0/24" ,
2016-11-16 21:40:23 +00:00
"--eviction-pressure-transition-period" , "30s" ,
// Apply test framework feature gates by default. This could also be overridden
// by kubelet-flags.
"--feature-gates" , framework . TestContext . FeatureGates ,
"--eviction-hard" , "memory.available<250Mi,nodefs.available<10%,nodefs.inodesFree<5%" , // The hard eviction thresholds.
2016-11-18 16:48:40 +00:00
"--eviction-minimum-reclaim" , "nodefs.available=5%,nodefs.inodesFree=5%" , // The minimum reclaimed resources after eviction.
2016-11-16 21:40:23 +00:00
"--v" , LOG_VERBOSITY_LEVEL , "--logtostderr" ,
)
2017-04-28 22:08:57 +00:00
if utilfeature . DefaultFeatureGate . Enabled ( features . DynamicKubeletConfig ) {
// Enable dynamic config if the feature gate is enabled
2017-11-15 18:17:06 +00:00
dir , err := getDynamicConfigDirectory ( )
2017-04-28 22:08:57 +00:00
if err != nil {
return nil , err
}
2017-11-15 18:17:06 +00:00
kubeArgs = append ( kubeArgs , "--dynamic-config-dir" , dir )
2017-04-28 22:08:57 +00:00
}
2016-11-16 21:40:23 +00:00
// Enable kubenet by default.
2017-04-11 23:23:54 +00:00
cniBinDir , err := getCNIBinDirectory ( )
2016-11-16 21:40:23 +00:00
if err != nil {
return nil , err
}
2017-04-11 23:23:54 +00:00
cniConfDir , err := getCNIConfDirectory ( )
if err != nil {
return nil , err
}
2017-11-15 18:17:06 +00:00
kubeArgs = append ( kubeArgs ,
2016-11-16 21:40:23 +00:00
"--network-plugin=kubenet" ,
2017-04-11 23:23:54 +00:00
"--cni-bin-dir" , cniBinDir ,
"--cni-conf-dir" , cniConfDir )
2016-11-16 21:40:23 +00:00
// Keep hostname override for convenience.
if framework . TestContext . NodeName != "" { // If node name is specified, set hostname override.
2017-11-15 18:17:06 +00:00
kubeArgs = append ( kubeArgs , "--hostname-override" , framework . TestContext . NodeName )
2016-11-16 21:40:23 +00:00
}
// Override the default kubelet flags.
2017-11-15 18:17:06 +00:00
kubeArgs = append ( kubeArgs , kubeletArgs ... )
// If the config file feature gate is enabled, generate the file and remove the flags it applies to
if utilfeature . DefaultFeatureGate . Enabled ( features . KubeletConfigFile ) {
kc , other , err := splitKubeletConfigArgs ( kubeArgs )
if err != nil {
return nil , err
}
// replace kubeArgs with the new command line, which has had the KubeletConfiguration flags removed
kubeArgs = other
path , err := writeKubeletConfigFile ( kc )
if err != nil {
return nil , err
}
// ensure the test context feature gates (typically DynamicKubeletConfig and KubeletConfigFile)
// are set on the command line
kubeArgs = append ( kubeArgs , "--feature-gates" , framework . TestContext . FeatureGates )
// add the flag to load config from a file
kubeArgs = append ( kubeArgs , "--init-config-dir" , filepath . Dir ( path ) )
}
// combine the kubelet parameters with the command
cmdArgs = append ( cmdArgs , kubeArgs ... )
2016-11-16 21:40:23 +00:00
// Adjust the args if we are running kubelet with systemd.
if isSystemd {
adjustArgsForSystemd ( cmdArgs )
}
cmd := exec . Command ( cmdArgs [ 0 ] , cmdArgs [ 1 : ] ... )
server := newServer (
"kubelet" ,
cmd ,
killCommand ,
restartCommand ,
[ ] string { kubeletHealthCheckURL } ,
"kubelet.log" ,
e . monitorParent ,
true /* restartOnExit */ )
return server , server . start ( )
}
2017-11-15 18:17:06 +00:00
// splitKubeletConfigArgs parses args onto a KubeletConfiguration object and also returns the unknown args
func splitKubeletConfigArgs ( args [ ] string ) ( * kubeletconfig . KubeletConfiguration , [ ] string , error ) {
kc , err := options . NewKubeletConfiguration ( )
if err != nil {
return nil , nil , err
}
fs := pflag . NewFlagSet ( "kubeletconfig" , pflag . ContinueOnError )
options . AddKubeletConfigFlags ( fs , kc )
known , other := splitKnownArgs ( fs , args )
if err := fs . Parse ( known ) ; err != nil {
return nil , nil , err
}
return kc , other , nil
}
// splitKnownArgs splits argument list into those known by the flagset, and those not known
// only tests for longhand args, e.g. prefixed with `--`
// TODO(mtaufen): I don't think the kubelet has any shorthand args, but if it does we will need to modify this.
func splitKnownArgs ( fs * pflag . FlagSet , args [ ] string ) ( [ ] string , [ ] string ) {
known := [ ] string { }
other := [ ] string { }
lastFlag := len ( args )
for i := len ( args ) - 1 ; i >= 0 ; i -- {
if strings . HasPrefix ( args [ i ] , "--" ) {
if fs . Lookup ( strings . TrimPrefix ( args [ i ] , "--" ) ) == nil {
// flag is unknown, add flag and params to other
// prepend to maintain order
other = append ( append ( [ ] string ( nil ) , args [ i : lastFlag ] ... ) , other ... )
// cut from known
} else {
// flag is known, add flag and params to known
// prepend to maintain order
known = append ( append ( [ ] string ( nil ) , args [ i : lastFlag ] ... ) , known ... )
}
// mark the last location where we saw a flag
lastFlag = i
}
}
return known , other
}
// writeKubeletConfigFile writes the kubelet config file based on the args and returns the filename
func writeKubeletConfigFile ( internal * kubeletconfig . KubeletConfiguration ) ( string , error ) {
// extract the KubeletConfiguration and convert to versioned
versioned := & v1alpha1 . KubeletConfiguration { }
scheme , _ , err := scheme . NewSchemeAndCodecs ( )
if err != nil {
return "" , err
}
if err := scheme . Convert ( internal , versioned , nil ) ; err != nil {
return "" , err
}
// encode
encoder , err := newKubeletConfigJSONEncoder ( )
if err != nil {
return "" , err
}
data , err := runtime . Encode ( encoder , versioned )
if err != nil {
return "" , err
}
// create the init conifg directory
dir , err := createKubeletInitConfigDirectory ( )
if err != nil {
return "" , err
}
// write init config file
path := filepath . Join ( dir , "kubelet" )
if err := ioutil . WriteFile ( path , data , 0755 ) ; err != nil {
return "" , err
}
return path , nil
}
func newKubeletConfigJSONEncoder ( ) ( runtime . Encoder , error ) {
_ , kubeletCodecs , err := scheme . NewSchemeAndCodecs ( )
if err != nil {
return nil , err
}
mediaType := "application/json"
info , ok := runtime . SerializerInfoForMediaType ( kubeletCodecs . SupportedMediaTypes ( ) , mediaType )
if ! ok {
return nil , fmt . Errorf ( "unsupported media type %q" , mediaType )
}
return kubeletCodecs . EncoderForVersion ( info . Serializer , v1alpha1 . SchemeGroupVersion ) , nil
}
2016-11-16 21:40:23 +00:00
// createPodManifestDirectory creates pod manifest directory.
func createPodManifestDirectory ( ) ( string , error ) {
cwd , err := os . Getwd ( )
if err != nil {
return "" , fmt . Errorf ( "failed to get current working directory: %v" , err )
}
path , err := ioutil . TempDir ( cwd , "pod-manifest" )
if err != nil {
return "" , fmt . Errorf ( "failed to create static pod manifest directory: %v" , err )
}
return path , nil
}
2017-01-18 00:08:24 +00:00
// createKubeconfig creates a kubeconfig file at the fully qualified `path`. The parent dirs must exist.
func createKubeconfig ( path string ) error {
kubeconfig := [ ] byte ( ` apiVersion : v1
kind : Config
users :
- name : kubelet
clusters :
- cluster :
server : ` + getAPIServerClientURL() + `
insecure - skip - tls - verify : true
name : local
contexts :
- context :
cluster : local
user : kubelet
name : local - context
current - context : local - context ` )
if err := ioutil . WriteFile ( path , kubeconfig , 0666 ) ; err != nil {
return err
}
return nil
}
2017-07-05 06:08:47 +00:00
func createRootDirectory ( path string ) error {
if _ , err := os . Stat ( path ) ; err != nil {
if os . IsNotExist ( err ) {
return os . MkdirAll ( path , os . FileMode ( 0755 ) )
} else {
return err
}
}
return nil
}
2017-01-18 00:08:24 +00:00
func kubeconfigCWDPath ( ) ( string , error ) {
cwd , err := os . Getwd ( )
if err != nil {
return "" , fmt . Errorf ( "failed to get current working directory: %v" , err )
}
return filepath . Join ( cwd , "kubeconfig" ) , nil
}
// like createKubeconfig, but creates kubeconfig at current-working-directory/kubeconfig
// returns a fully-qualified path to the kubeconfig file
func createKubeconfigCWD ( ) ( string , error ) {
kubeconfigPath , err := kubeconfigCWDPath ( )
if err != nil {
return "" , err
}
if err = createKubeconfig ( kubeconfigPath ) ; err != nil {
return "" , err
}
return kubeconfigPath , nil
}
2017-04-11 23:23:54 +00:00
// getCNIBinDirectory returns CNI directory.
func getCNIBinDirectory ( ) ( string , error ) {
2016-11-16 21:40:23 +00:00
cwd , err := os . Getwd ( )
if err != nil {
return "" , err
}
return filepath . Join ( cwd , "cni" , "bin" ) , nil
}
2017-04-11 23:23:54 +00:00
// getCNIConfDirectory returns CNI Configuration directory.
func getCNIConfDirectory ( ) ( string , error ) {
cwd , err := os . Getwd ( )
if err != nil {
return "" , err
}
return filepath . Join ( cwd , "cni" , "net.d" ) , nil
}
2017-04-28 22:08:57 +00:00
// getDynamicConfigDir returns the directory for dynamic Kubelet configuration
2017-11-15 18:17:06 +00:00
func getDynamicConfigDirectory ( ) ( string , error ) {
2017-04-28 22:08:57 +00:00
cwd , err := os . Getwd ( )
if err != nil {
return "" , err
}
return filepath . Join ( cwd , "dynamic-kubelet-config" ) , nil
}
2017-11-15 18:17:06 +00:00
// createKubeletInitConfigDirectory creates and returns the name of the directory for dynamic Kubelet configuration
func createKubeletInitConfigDirectory ( ) ( string , error ) {
cwd , err := os . Getwd ( )
if err != nil {
return "" , err
}
path := filepath . Join ( cwd , "init-kubelet-config" )
if err := os . MkdirAll ( path , 0755 ) ; err != nil {
return "" , err
}
return path , nil
}
2016-11-16 21:40:23 +00:00
// adjustArgsForSystemd escape special characters in kubelet arguments for systemd. Systemd
// may try to do auto expansion without escaping.
func adjustArgsForSystemd ( args [ ] string ) {
for i := range args {
args [ i ] = strings . Replace ( args [ i ] , "%" , "%%" , - 1 )
args [ i ] = strings . Replace ( args [ i ] , "$" , "$$" , - 1 )
}
}