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"
2017-12-26 03:55:26 +00:00
"time"
2016-11-16 21:40:23 +00:00
"github.com/golang/glog"
2017-12-26 03:55:26 +00:00
"github.com/spf13/pflag"
2016-11-16 21:40:23 +00:00
2017-12-26 03:55:26 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
2017-04-28 22:08:57 +00:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
2017-12-26 03:55:26 +00:00
utilflag "k8s.io/apiserver/pkg/util/flag"
"k8s.io/kubernetes/cmd/kubelet/app/options"
2017-04-28 22:08:57 +00:00
"k8s.io/kubernetes/pkg/features"
2017-12-26 03:55:26 +00:00
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
2017-10-12 18:15:09 +00:00
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
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
2017-11-22 15:21:37 +00:00
var kubeletContainerized bool
var hyperkubeImage string
2017-12-26 03:55:26 +00:00
var genKubeletConfigFile bool
2016-11-16 21:40:23 +00:00
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." )
2017-11-22 15:21:37 +00:00
flag . BoolVar ( & kubeletContainerized , "kubelet-containerized" , false , "Run kubelet in a docker container" )
flag . StringVar ( & hyperkubeImage , "hyperkube-image" , "" , "Docker image with containerized kubelet" )
2018-01-30 00:20:35 +00:00
flag . BoolVar ( & genKubeletConfigFile , "generate-kubelet-config-file" , true , "The test runner will generate a Kubelet config file containing test defaults instead of passing default flags to the Kubelet." )
2016-11-16 21:40:23 +00:00
}
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 ) {
2017-11-22 15:21:37 +00:00
if kubeletContainerized && hyperkubeImage == "" {
return nil , fmt . Errorf ( "the --hyperkube-image option must be set" )
}
2016-11-16 21:40:23 +00:00
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
}
2017-12-26 03:55:26 +00:00
// KubeletConfiguration file path
kubeletConfigPath , err := kubeletConfigCWDPath ( )
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
}
2017-12-26 03:55:26 +00:00
// PLEASE NOTE: If you set new KubeletConfiguration values or stop setting values here,
// you must also update the flag names in kubeletConfigFlags!
kubeletConfigFlags := [ ] string { }
// set up the default kubeletconfiguration
kc , err := options . NewKubeletConfiguration ( )
if err != nil {
return nil , err
}
kc . CgroupRoot = "/"
kubeletConfigFlags = append ( kubeletConfigFlags , "cgroup-root" )
kc . VolumeStatsAggPeriod = metav1 . Duration { Duration : 10 * time . Second } // Aggregate volumes frequently so tests don't need to wait as long
kubeletConfigFlags = append ( kubeletConfigFlags , "volume-stats-agg-period" )
kc . SerializeImagePulls = false
kubeletConfigFlags = append ( kubeletConfigFlags , "serialize-image-pulls" )
kc . PodManifestPath = manifestPath
kubeletConfigFlags = append ( kubeletConfigFlags , "pod-manifest-path" )
kc . FileCheckFrequency = metav1 . Duration { Duration : 10 * time . Second } // Check file frequently so tests won't wait too long
kubeletConfigFlags = append ( kubeletConfigFlags , "file-check-frequency" )
// 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.
kc . PodCIDR = "10.100.0.0/24"
kubeletConfigFlags = append ( kubeletConfigFlags , "pod-cidr" )
kc . EvictionPressureTransitionPeriod = metav1 . Duration { Duration : 30 * time . Second }
kubeletConfigFlags = append ( kubeletConfigFlags , "eviction-pressure-transition-period" )
kc . EvictionHard = map [ string ] string {
"memory.available" : "250Mi" ,
"nodefs.available" : "10%" ,
"nodefs.inodesFree" : "5%" ,
}
kubeletConfigFlags = append ( kubeletConfigFlags , "eviction-hard" )
kc . EvictionMinimumReclaim = map [ string ] string {
"nodefs.available" : "5%" ,
"nodefs.inodesFree" : "5%" ,
}
kubeletConfigFlags = append ( kubeletConfigFlags , "eviction-minimum-reclaim" )
2016-11-16 21:40:23 +00:00
var killCommand , restartCommand * exec . Cmd
var isSystemd bool
// Apply default kubelet flags.
cmdArgs := [ ] string { }
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-11-22 15:21:37 +00:00
if kubeletContainerized {
cmdArgs = append ( cmdArgs , systemdRun , "--unit=" + unitName , "--slice=runtime.slice" , "--remain-after-exit" ,
"/usr/bin/docker" , "run" , "--name=kubelet" ,
"--rm" , "--privileged" , "--net=host" , "--pid=host" ,
"-e HOST=/rootfs" , "-e HOST_ETC=/host-etc" ,
"-v" , "/etc/localtime:/etc/localtime:ro" ,
"-v" , "/etc/machine-id:/etc/machine-id:ro" ,
"-v" , filepath . Dir ( kubeconfigPath ) + ":/etc/kubernetes" ,
"-v" , "/:/rootfs:ro,rslave" ,
"-v" , "/run:/run" ,
"-v" , "/sys/fs/cgroup:/sys/fs/cgroup:rw" ,
"-v" , "/sys:/sys:rw" ,
"-v" , "/usr/bin/docker:/usr/bin/docker:ro" ,
"-v" , "/var/lib/cni:/var/lib/cni" ,
"-v" , "/var/lib/docker:/var/lib/docker" ,
"-v" , "/var/lib/kubelet:/var/lib/kubelet:rw,rslave" ,
"-v" , "/var/log:/var/log" ,
"-v" , manifestPath + ":" + manifestPath + ":rw" ,
)
2017-12-26 03:55:26 +00:00
// if we will generate a kubelet config file, we need to mount that path into the container too
if genKubeletConfigFile {
cmdArgs = append ( cmdArgs , "-v" , filepath . Dir ( kubeletConfigPath ) + ":" + filepath . Dir ( kubeletConfigPath ) + ":ro" )
}
cmdArgs = append ( cmdArgs , hyperkubeImage , "/hyperkube" , "kubelet" , "--containerized" )
2017-11-22 15:21:37 +00:00
kubeconfigPath = "/etc/kubernetes/kubeconfig"
} else {
2017-12-26 03:55:26 +00:00
cmdArgs = append ( cmdArgs ,
systemdRun ,
"--unit=" + unitName ,
"--slice=runtime.slice" ,
"--remain-after-exit" ,
builder . GetKubeletServerBin ( ) )
2017-11-22 15:21:37 +00:00
}
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-12-26 03:55:26 +00:00
kc . KubeletCgroups = "/kubelet.slice"
kubeletConfigFlags = append ( kubeletConfigFlags , "kubelet-cgroups" )
2016-11-16 21:40:23 +00:00
} else {
cmdArgs = append ( cmdArgs , builder . GetKubeletServerBin ( ) )
2017-12-26 03:55:26 +00:00
// TODO(random-liu): Get rid of this docker specific thing.
cmdArgs = append ( cmdArgs , "--runtime-cgroups=/docker-daemon" )
kc . KubeletCgroups = "/kubelet"
kubeletConfigFlags = append ( kubeletConfigFlags , "kubelet-cgroups" )
kc . SystemCgroups = "/system"
kubeletConfigFlags = append ( kubeletConfigFlags , "system-cgroups" )
2016-11-16 21:40:23 +00:00
}
2017-11-22 04:55:43 +00:00
cmdArgs = append ( cmdArgs ,
2017-01-18 00:08:24 +00:00
"--kubeconfig" , kubeconfigPath ,
2017-10-26 12:55:13 +00:00
"--root-dir" , KubeletRootDirectory ,
2017-09-01 21:43:13 +00:00
"--docker-disable-shared-pid=false" ,
2016-11-16 21:40:23 +00:00
"--v" , LOG_VERBOSITY_LEVEL , "--logtostderr" ,
2018-01-04 23:17:52 +00:00
"--allow-privileged" , "true" ,
2016-11-16 21:40:23 +00:00
)
2017-04-28 22:08:57 +00:00
2017-10-26 11:04:18 +00:00
// Apply test framework feature gates by default. This could also be overridden
// by kubelet-flags.
if framework . TestContext . FeatureGates != "" {
cmdArgs = append ( cmdArgs , "--feature-gates" , framework . TestContext . FeatureGates )
2017-12-26 03:55:26 +00:00
utilflag . NewMapStringBool ( & kc . FeatureGates ) . Set ( framework . TestContext . FeatureGates )
2017-10-26 11:04:18 +00:00
}
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-22 04:55:43 +00:00
dynamicConfigDir , err := getDynamicConfigDir ( )
2017-04-28 22:08:57 +00:00
if err != nil {
return nil , err
}
2017-11-22 04:55:43 +00:00
cmdArgs = append ( cmdArgs , "--dynamic-config-dir" , dynamicConfigDir )
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-22 04:55:43 +00:00
cmdArgs = append ( cmdArgs ,
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-22 04:55:43 +00:00
cmdArgs = append ( cmdArgs , "--hostname-override" , framework . TestContext . NodeName )
2016-11-16 21:40:23 +00:00
}
2018-01-08 18:46:51 +00:00
if framework . TestContext . ContainerRuntime != "" {
cmdArgs = append ( cmdArgs , "--container-runtime" , framework . TestContext . ContainerRuntime )
}
if framework . TestContext . ContainerRuntimeEndpoint != "" {
cmdArgs = append ( cmdArgs , "--container-runtime-endpoint" , framework . TestContext . ContainerRuntimeEndpoint )
}
if framework . TestContext . ImageServiceEndpoint != "" {
cmdArgs = append ( cmdArgs , "--image-service-endpoint" , framework . TestContext . ImageServiceEndpoint )
}
2017-12-26 03:55:26 +00:00
// Write config file or flags, depending on whether --generate-kubelet-config-file was provided
if genKubeletConfigFile {
if err := writeKubeletConfigFile ( kc , kubeletConfigPath ) ; err != nil {
return nil , err
}
// add the flag to load config from a file
cmdArgs = append ( cmdArgs , "--config" , kubeletConfigPath )
} else {
// generate command line flags from the default config, since --generate-kubelet-config-file was not provided
addKubeletConfigFlags ( & cmdArgs , kc , kubeletConfigFlags )
}
2016-11-16 21:40:23 +00:00
// Override the default kubelet flags.
2017-11-22 04:55:43 +00:00
cmdArgs = append ( cmdArgs , kubeletArgs ... )
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-12-26 03:55:26 +00:00
// addKubeletConfigFlags adds the flags we care about from the provided kubelet configuration object
func addKubeletConfigFlags ( cmdArgs * [ ] string , kc * kubeletconfig . KubeletConfiguration , flags [ ] string ) {
fs := pflag . NewFlagSet ( "kubelet" , pflag . ExitOnError )
options . AddKubeletConfigFlags ( fs , kc )
for _ , name := range flags {
* cmdArgs = append ( * cmdArgs , "--" + name , fs . Lookup ( name ) . Value . String ( ) )
}
}
// writeKubeletConfigFile writes the kubelet config file based on the args and returns the filename
func writeKubeletConfigFile ( internal * kubeletconfig . KubeletConfiguration , path string ) error {
// extract the KubeletConfiguration and convert to versioned
2017-10-12 18:15:09 +00:00
versioned := & v1beta1 . KubeletConfiguration { }
2017-12-26 03:55:26 +00:00
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 directory, if it does not exist
dir := filepath . Dir ( path )
if err := os . MkdirAll ( dir , 0755 ) ; err != nil {
return err
}
// write the file
if err := ioutil . WriteFile ( path , data , 0755 ) ; err != nil {
return err
}
return 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 )
}
2017-10-12 18:15:09 +00:00
return kubeletCodecs . EncoderForVersion ( info . Serializer , v1beta1 . SchemeGroupVersion ) , nil
2017-12-26 03:55:26 +00:00
}
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
}
2017-12-26 03:55:26 +00:00
func kubeletConfigCWDPath ( ) ( string , error ) {
cwd , err := os . Getwd ( )
if err != nil {
return "" , fmt . Errorf ( "failed to get current working directory: %v" , err )
}
// DO NOT name this file "kubelet" - you will overwrite the the kubelet binary and be very confused :)
return filepath . Join ( cwd , "kubelet-config" ) , nil
}
2017-01-18 00:08:24 +00:00
// 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-22 04:55:43 +00:00
func getDynamicConfigDir ( ) ( 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
}
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 )
}
}