The version of Kubernetes source code is
V1.6.6
.
Kubernetes是Google主导的一款目前最成功的开源软件,完全使用Google自己发明的开发语言Go编写的。 现在Kubernetes的代码量已经非常庞大,刚开始看源码的时候,可能的确需要鼓起很大的勇气。不过,在阅读源码过程中,你会慢慢地发现,大到系统架构,小到代码风格,Google对代码犹如艺术品般的追求。
下面以Kubernetes各模块的入口为例,展示Kubernetes代码风格的高度一致性。Kubernetes是通过微服务的方式实现的,因此模块比较多,主要模块包括kube-apiserver、kube-controller-manager、kube-scheduler、kubelet等。
Overview
下面以Kubelet为例,介绍kubernetes的command entrypoint的代码风格。
Folder Structure of Code
# tree cmd/
cmd/
├── cloud-controller-manager
│ ├── app
│ │ ├── BUILD
│ │ ├── controllermanager.go
│ │ └── options
│ │ ├── BUILD
│ │ └── options.go
│ ├── BUILD
│ └── controller-manager.go
├── kube-apiserver
│ ├── apiserver.go
│ ├── app
│ │ ├── aggregator.go
│ │ ├── apiextensions.go
│ │ ├── BUILD
│ │ ├── options
│ │ │ ├── BUILD
│ │ │ ├── options.go
│ │ │ ├── options_test.go
│ │ │ └── validation.go
│ │ ├── plugins.go
│ │ ├── preflight
│ │ │ ├── BUILD
│ │ │ ├── checks.go
│ │ │ └── checks_test.go
│ │ └── server.go
│ ├── BUILD
│ └── OWNERS
├── kube-controller-manager
│ ├── app
│ │ ├── apps.go
│ │ ├── autoscaling.go
│ │ ├── batch.go
│ │ ├── bootstrap.go
│ │ ├── BUILD
│ │ ├── certificates.go
│ │ ├── controllermanager.go
│ │ ├── controller_manager_test.go
│ │ ├── core.go
│ │ ├── extensions.go
│ │ ├── options
│ │ │ ├── BUILD
│ │ │ └── options.go
│ │ ├── plugins.go
│ │ └── policy.go
│ ├── BUILD
│ ├── controller-manager.go
│ └── OWNERS
├── kubectl
│ ├── app
│ │ ├── BUILD
│ │ └── kubectl.go
│ ├── BUILD
│ ├── kubectl.go
│ └── OWNERS
├── kubelet
│ ├── app
│ │ ├── auth.go
│ │ ├── bootstrap.go
│ │ ├── bootstrap_test.go
│ │ ├── BUILD
│ │ ├── options
│ │ │ ├── BUILD
│ │ │ ├── container_runtime.go
│ │ │ └── options.go
│ │ ├── OWNERS
│ │ ├── plugins.go
│ │ ├── server.go
│ │ ├── server_linux.go
│ │ ├── server_test.go
│ │ └── server_unsupported.go
│ ├── BUILD
│ ├── kubelet.go
│ └── OWNERS
├── kube-proxy
│ ├── app
│ │ ├── BUILD
│ │ ├── conntrack.go
│ │ ├── server.go
│ │ └── server_test.go
│ ├── BUILD
│ └── proxy.go
无论从目录结构,还是从文件命名上,各模块的command entrypoint都保持着高度的一致。如果需要新增一个模块,甚至可以copy已有模块,只需要简单修改一下具体实现内容,就可以run起来。看完文件框架后,我们再来看一下代码框架,了解需要简单修改的具体实现内容在哪里。
Command Entrypoint Template
下面是基于Kubelet的源码,屏蔽具体的业务逻辑,提取出来的一个template。从这个template中,可以清晰地看出被所有模块commend entrypoint共同使用的整体架构。
import (
"fmt"
"os"
"k8s.io/apiserver/pkg/util/flag"
"k8s.io/apiserver/pkg/util/logs"
"k8s.io/kubernetes/cmd/kubelet/app"
"k8s.io/kubernetes/cmd/kubelet/app/options"
"k8s.io/kubernetes/pkg/version/verflag"
"github.com/spf13/pflag"
)
func main() {
s := options.NewKubeletServer()
s.AddFlags(pflag.CommandLine)
flag.InitFlags()
logs.InitLogs()
defer logs.FlushLogs()
verflag.PrintAndExitIfRequested()
if err := app.Run(s, nil); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
cmd/kubelet/app/options/options.go
// NewKubeletServer will create a new KubeletServer with default values.
func NewKubeletServer() *KubeletServer {
versioned := &v1alpha1.KubeletConfiguration{}
api.Scheme.Default(versioned)
config := componentconfig.KubeletConfiguration{}
api.Scheme.Convert(versioned, &config, nil)
return &KubeletServer{
KubeConfig: flag.NewStringFlag("/var/lib/kubelet/kubeconfig"),
RequireKubeConfig: false, // in 1.5, default to true
KubeletConfiguration: config,
}
}
// AddFlags adds flags for a specific KubeletServer to the specified FlagSet
func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
// Add the flags
......
fs.DurationVar(&s.SyncFrequency.Duration, "sync-frequency", s.SyncFrequency.Duration, "Max period between synchronizing running containers and config")
fs.DurationVar(&s.FileCheckFrequency.Duration, "file-check-frequency", s.FileCheckFrequency.Duration, "Duration between checking config files for new data")
fs.DurationVar(&s.HTTPCheckFrequency.Duration, "http-check-frequency", s.HTTPCheckFrequency.Duration, "Duration between checking http for new data")
fs.StringVar(&s.ManifestURL, "manifest-url", s.ManifestURL, "URL for accessing the container manifest")
......
}
func Run(s *options.KubeletServer, kubeDeps *kubelet.KubeletDeps) error {
if err := run(s, kubeDeps); err != nil {
return fmt.Errorf("failed to run Kubelet: %v", err)
}
return nil
}
func run(s *options.KubeletServer, kubeDeps *kubelet.KubeletDeps) error {
// Start the kubelet with options
......
return nil
}
Flag Initialization
staging/src/k8s.io/apiserver/pkg/util/flag/flags.go
// InitFlags normalizes and parses the command line flags
func InitFlags() {
pflag.CommandLine.SetNormalizeFunc(WordSepNormalizeFunc)
pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
pflag.Parse()
}
InitFlags()首先注册一个normalize的function,在--help
输出参数提示的时候,将所有命令行参数的格式标准化一下。
然后将标准flag库的CommandLine添加到pflag库的FlagSet中,使添加到flag库的命令行参数也可以生效,例如:glog会默认从标准flag库中读取一些log配置相关的命令行参数。但是这里需要提醒一下,只有那些自定义的参数类型才能生效,例如:verbosity
、stderrThreshold
和vmodule
,而那些普通类型的参数是无法生效的,例如toStderr
和alsoToStderr
。最后调用pflag.Parse()
来解析所有命令行参数。
Log Initialization
staging/src/k8s.io/apiserver/pkg/util/logs/logs.go
// InitLogs initializes logs the way we want for kubernetes.
func InitLogs() {
log.SetOutput(GlogWriter{})
log.SetFlags(0)
// The default glog flush interval is 30 seconds, which is frighteningly long.
go wait.Until(glog.Flush, *logFlushFreq, wait.NeverStop)
}
// FlushLogs flushes logs immediately.
func FlushLogs() {
glog.Flush()
}
InitLogs()通过GlogWriter
在标准log库与Glog库间搭建了一个bridge,使来自于标准log库的log也能输出到Glog中。
由于Glog采用的是定时Flush缓冲区的机制将log输出到标准输出或者日志文件,默认的Flush间隔时间是30秒。
这个间隔时间实在是太长了,会导致log输出严重延时,不清楚Glog为什么采用如此长的默认时间间隔。
Kubernetes也无法忍受如此长的Flush间隔时间,因此单独起了一个goroutine,每隔5秒主动调用glog.Flush(),强制将日志输出。