什么是Printer Printer可以对Kubernetes中的资源按一定格式进行打印输出,Printer主要供Kubectl get命令使用。目前Kubectl主要实现了JsonPrinter, YAMLPrinter, NamePrinter, TemplatePrinter, JSONPathPrinter, CustomColumnsPrinter, VersionedPrinter, HumanReadablePrinter。
在对这些Printer进行分析之前,先来看下/pkg/kubectl/resource_printer.go的GetPrinter()函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
func GetPrinter (format, formatArgument string , noHeaders bool ) (ResourcePrinter, bool , error) {
var printer ResourcePrinter
switch format {
case "json" :
printer = &JSONPrinter{}
case "yaml" :
printer = &YAMLPrinter{}
case "name" :
printer = &NamePrinter{
Typer: api.Scheme,
Decoder: api.Codecs.UniversalDecoder(),
}
case "template" , "go-template" :
if len (formatArgument) == 0 {
return nil , false , fmt.Errorf("template format specified but no template given" )
}
var err error
printer, err = NewTemplatePrinter([]byte (formatArgument))
if err != nil {
return nil , false , fmt.Errorf("error parsing template %s, %v\n" , formatArgument, err)
}
case "templatefile" , "go-template-file" :
if len (formatArgument) == 0 {
return nil , false , fmt.Errorf("templatefile format specified but no template file given" )
}
data, err := ioutil.ReadFile(formatArgument)
if err != nil {
return nil , false , fmt.Errorf("error reading template %s, %v\n" , formatArgument, err)
}
printer, err = NewTemplatePrinter(data)
if err != nil {
return nil , false , fmt.Errorf("error parsing template %s, %v\n" , string (data), err)
}
case "jsonpath" :
if len (formatArgument) == 0 {
return nil , false , fmt.Errorf("jsonpath template format specified but no template given" )
}
var err error
printer, err = NewJSONPathPrinter(formatArgument)
if err != nil {
return nil , false , fmt.Errorf("error parsing jsonpath %s, %v\n" , formatArgument, err)
}
case "jsonpath-file" :
if len (formatArgument) == 0 {
return nil , false , fmt.Errorf("jsonpath file format specified but no template file file given" )
}
data, err := ioutil.ReadFile(formatArgument)
if err != nil {
return nil , false , fmt.Errorf("error reading template %s, %v\n" , formatArgument, err)
}
printer, err = NewJSONPathPrinter(string (data))
if err != nil {
return nil , false , fmt.Errorf("error parsing template %s, %v\n" , string (data), err)
}
case "custom-columns" :
var err error
if printer, err = NewCustomColumnsPrinterFromSpec(formatArgument, api.Codecs.UniversalDecoder(), noHeaders); err != nil {
return nil , false , err
}
case "custom-columns-file" :
file, err := os.Open(formatArgument)
if err != nil {
return nil , false , fmt.Errorf("error reading template %s, %v\n" , formatArgument, err)
}
defer file.Close()
if printer, err = NewCustomColumnsPrinterFromTemplate(file, api.Codecs.UniversalDecoder()); err != nil {
return nil , false , err
}
case "wide" :
fallthrough
case "" :
return nil , false , nil
default :
return nil , false , fmt.Errorf("output format %q not recognized" , format)
}
return printer, true , nil
}
在GetPrinter()函数中,根据传入的参数format,会生成不同类型的Printer。需要注意的是,除了”wide”, “”以外,其他的format类型都视为标准类型(generic)。非标准类型通常是和”人”相关,如”wide”和””最后都是由HumanReadablePrinter实现。
JSONPrinter JSONPrinter可以以JSON的格式打印Object。JSONPrinter调用了json.MarshalIndent()来对Obj进行JSON格式化打印。json.MarshalIndent()函数的功能和Marshal一致,只是把Object格式化成人性化阅读的JSON,有点类似”| python -mjson.tool”的功能。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
type JSONPrinter struct {
}
func (p *JSONPrinter) AfterPrint (w io.Writer, res string ) error {
return nil
}
func (p *JSONPrinter) PrintObj (obj runtime.Object, w io.Writer) error {
switch obj := obj.(type ) {
case *runtime.Unknown:
var buf bytes.Buffer
err := json.Indent(&buf, obj.Raw, "" , " " )
if err != nil {
return err
}
buf.WriteRune('\n' )
_, err = buf.WriteTo(w)
return err
}
data, err := json.MarshalIndent(obj, "" , " " )
if err != nil {
return err
}
data = append (data, '\n' )
_, err = w.Write(data)
return err
}
func (p *JSONPrinter) HandledResources () []string {
return []string {}
}
调用例子:1
kubectl get pods -o json
YAMLPrinter YAMLPrinter可以以YAML的格式打印Object。YAMLPrinter通过调用yaml.Marshal()把Object转换成YAML格式并进行打印。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type YAMLPrinter struct {
version string
converter runtime.ObjectConvertor
}
func (p *YAMLPrinter) AfterPrint (w io.Writer, res string ) error {
return nil
}
func (p *YAMLPrinter) PrintObj (obj runtime.Object, w io.Writer) error {
switch obj := obj.(type ) {
case *runtime.Unknown:
data, err := yaml.JSONToYAML(obj.Raw)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
output, err := yaml.Marshal(obj)
if err != nil {
return err
}
_, err = fmt.Fprint(w, string (output))
return err
}
调用例子:1
kubectl get pods -o yaml
NamePrinter NamePrinter可以打印资源类型和名称:如pod/ubuntu-ssh-589933015-2lhgb。NamePrinter先获判断Object是否为列表,如果是列表,则递归调用PrintObj;否则先获取Object的name,然后获取Object的Kind,并转换成单数形式的GVR,然后返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
type NamePrinter struct {
Decoder runtime.Decoder
Typer runtime.ObjectTyper
}
func (p *NamePrinter) AfterPrint (w io.Writer, res string ) error {
return nil
}
func (p *NamePrinter) PrintObj (obj runtime.Object, w io.Writer) error {
if meta.IsListType(obj) {
items, err := meta.ExtractList(obj)
if err != nil {
return err
}
if errs := runtime.DecodeList(items, p.Decoder, runtime.UnstructuredJSONScheme); len (errs) > 0 {
return utilerrors.NewAggregate(errs)
}
for _, obj := range items {
if err := p.PrintObj(obj, w); err != nil {
return err
}
}
return nil
}
name := "<unknown>"
if acc, err := meta.Accessor(obj); err == nil {
if n := acc.GetName(); len (n) > 0 {
name = n
}
}
if kind := obj.GetObjectKind().GroupVersionKind(); len (kind.Kind) == 0 {
if gvks, _, err := p.Typer.ObjectKinds(obj); err == nil {
_, resource := meta.KindToResource(gvks[0 ])
fmt.Fprintf(w, "%s/%s\n" , resource.Resource, name)
} else {
fmt.Fprintf(w, "<unknown>/%s\n" , name)
}
} else {
_, resource := meta.KindToResource(kind)
fmt.Fprintf(w, "%s/%s\n" , resource.Resource, name)
}
return nil
}
调用例子:1
2
3
# kubectl get pods -o name
pod/nginx-1487191267-0t8x3
pod/ubuntu-ssh-589933015-2lhgb
TemplatePrinter TemplatePrinter基于txt/template包实现。txt/template一般的用法为: 定义模板:1
tmpl, err := template.New("test" ).Parse("hello, {{.}}" )
执行:1
err = tmpl.Execute(os.Stdout, name)
TemplatePrinter定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
type TemplatePrinter struct {
rawTemplate string
template *template.Template
}
func NewTemplatePrinter (tmpl []byte ) (*TemplatePrinter, error) {
t, err := template.New("output" ).
Funcs(template.FuncMap{"exists" : exists}).
Parse(string (tmpl))
if err != nil {
return nil , err
}
return &TemplatePrinter{
rawTemplate: string (tmpl),
template: t,
}, nil
}
func (p *TemplatePrinter) AfterPrint (w io.Writer, res string ) error {
return nil
}
func (p *TemplatePrinter) PrintObj (obj runtime.Object, w io.Writer) error {
var data []byte
var err error
if unstructured, ok := obj.(*runtime.Unstructured); ok {
data, err = json.Marshal(unstructured.Object)
} else {
data, err = json.Marshal(obj)
}
if err != nil {
return err
}
out := map [string ]interface {}{}
if err := json.Unmarshal(data, &out); err != nil {
return err
}
if err = p.safeExecute(w, out); err != nil {
fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n" , err)
fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n" , p.rawTemplate)
fmt.Fprintf(w, "\traw data was:\n\t\t%v\n" , string (data))
fmt.Fprintf(w, "\tobject given to template engine was:\n\t\t%+v\n\n" , out)
return fmt.Errorf("error executing template %q: %v" , p.rawTemplate, err)
}
return nil
}
func (p *TemplatePrinter) HandledResources () []string {
return []string {}
}
调用例子:1
2
# kubectl get pods nginx-1487191267-0t8x3 -o go-template="The name is {{.metadata.name}}"
The name is nginx-1487191267-0t8x3
或者1
2
# kubectl get pods nginx-1487191267-0t8x3 -o go-template-file=template
The name is nginx-1487191267-0t8x3
其中go-template-file=template
的template为文件,内容为:1
The name is {{.metadata.name}}
JSONPathPrinter JSONPathPrinter实现了一套匹配规则,详见https://kubernetes.io/docs/user-guide/jsonpath/。 jsonpath具体在/pkg/util/jsonpath/jsonpath.go中实现,具体分析此处略。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
type JSONPathPrinter struct {
rawTemplate string
*jsonpath.JSONPath
}
func NewJSONPathPrinter (tmpl string ) (*JSONPathPrinter, error) {
j := jsonpath.New("out" )
if err := j.Parse(tmpl); err != nil {
return nil , err
}
return &JSONPathPrinter{tmpl, j}, nil
}
func (j *JSONPathPrinter) AfterPrint (w io.Writer, res string ) error {
return nil
}
func (j *JSONPathPrinter) PrintObj (obj runtime.Object, w io.Writer) error {
var queryObj interface {} = obj
if meta.IsListType(obj) {
data, err := json.Marshal(obj)
if err != nil {
return err
}
queryObj = map [string ]interface {}{}
if err := json.Unmarshal(data, &queryObj); err != nil {
return err
}
}
if unknown, ok := obj.(*runtime.Unknown); ok {
data, err := json.Marshal(unknown)
if err != nil {
return err
}
queryObj = map [string ]interface {}{}
if err := json.Unmarshal(data, &queryObj); err != nil {
return err
}
}
if unstructured, ok := obj.(*runtime.Unstructured); ok {
queryObj = unstructured.Object
}
if err := j.JSONPath.Execute(w, queryObj); err != nil {
fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n" , err)
fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n" , j.rawTemplate)
fmt.Fprintf(w, "\tobject given to jsonpath engine was:\n\t\t%#v\n\n" , queryObj)
return fmt.Errorf("error executing jsonpath %q: %v\n" , j.rawTemplate, err)
}
return nil
}
func (p *JSONPathPrinter) HandledResources () []string {
return []string {}
}
调用例子:1
2
# kubectl get pods -o jsonpath={.items[*].metadata.name}
nginx-1487191267-0t8x3 ubuntu-ssh-589933015-2lhgb
或者1
2
# kubectl get pods -o jsonpath-file=template
nginx-1487191267-0t8x3 ubuntu-ssh-589933015-2lhgb
其中jsonpath-file=template
的template为文件,内容为:1
{.items[*].metadata.name}
CustomColumnsPrinter CustomColumnsPrinter允许用户定义列名,是JSONPath的加强版。支持从命令行直接输入模板和从文件读取模板两种方式。
读取命令行模板的CustomColumnsPrinter生成函数如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func NewCustomColumnsPrinterFromSpec (spec string , decoder runtime.Decoder, noHeaders bool ) (*CustomColumnsPrinter, error) {
if len (spec) == 0 {
return nil , fmt.Errorf("custom-columns format specified but no custom columns given" )
}
parts := strings.Split(spec, "," )
columns := make ([]Column, len (parts))
for ix := range parts {
colSpec := strings.Split(parts[ix], ":" )
if len (colSpec) != 2 {
return nil , fmt.Errorf("unexpected custom-columns spec: %s, expected <header>:<json-path-expr>" , parts[ix])
}
spec, err := massageJSONPath(colSpec[1 ])
if err != nil {
return nil , err
}
columns[ix] = Column{Header: colSpec[0 ], FieldSpec: spec}
}
return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: noHeaders}, nil
}
读取文件模板的CustomColumnsPrinter生成函数如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//***kubectl get pods -o custom-columns-file=/home/fankang/template***//
func NewCustomColumnsPrinterFromTemplate(templateReader io.Reader, decoder runtime.Decoder) (*CustomColumnsPrinter, error) {
scanner := bufio.NewScanner(templateReader)
if !scanner.Scan() {
return nil, fmt.Errorf("invalid template, missing header line. Expected format is one line of space separated headers, one line of space separated column specs.")
}
//***headers: [Type Name]***//
headers := splitOnWhitespace(scanner.Text())
if !scanner.Scan() {
return nil, fmt.Errorf("invalid template, missing spec line. Expected format is one line of space separated headers, one line of space separated column specs.")
}
//***specs: [{.kind} {.metadata.name}]***//
specs := splitOnWhitespace(scanner.Text())
//***如果headers和specs长度不一致,则直接返回***//
if len(headers) != len(specs) {
return nil, fmt.Errorf("number of headers (%d) and field specifications (%d) don't match", len(headers), len(specs))
}
columns := make([]Column, len(headers))
for ix := range headers {
spec, err := massageJSONPath(specs[ix])
if err != nil {
return nil, err
}
//***组装成Column***//
columns[ix] = Column{
Header: headers[ix],
FieldSpec: spec,
}
}
//***返回CustomColumnsPrinter***//
return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: false}, nil
}
CustomColumnsPrinter的PrintObj()方法如下,可以看出,调用了jsonpath.New()来解析模板。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
w := tabwriter.NewWriter(out, columnwidth, tabwidth, padding, padding_character, flags)
if !s.NoHeaders {
headers := make([]string, len(s.Columns))
for ix := range s.Columns {
headers[ix] = s.Columns[ix].Header
}
fmt.Fprintln(w, strings.Join(headers, "\t"))
}
parsers := make([]*jsonpath.JSONPath, len(s.Columns))
for ix := range s.Columns {
parsers[ix] = jsonpath.New(fmt.Sprintf("column%d", ix))
if err := parsers[ix].Parse(s.Columns[ix].FieldSpec); err != nil {
return err
}
}
if meta.IsListType(obj) {
objs, err := meta.ExtractList(obj)
if err != nil {
return err
}
for ix := range objs {
if err := s.printOneObject(objs[ix], parsers, w); err != nil {
return err
}
}
} else {
if err := s.printOneObject(obj, parsers, w); err != nil {
return err
}
}
return w.Flush()
}
调用例子:1
2
3
4
# kubectl get pods -o custom-columns=Type:{.kind},Name:{.metadata.name}
Type Name
Pod nginx-1487191267-0t8x3
Pod ubuntu-ssh-589933015-2lhgb
或者1
2
3
4
# kubectl get pods -o custom-columns-file=template
Type Name
Pod nginx-1487191267-0t8x3
Pod ubuntu-ssh-589933015-2lhgb
其中custom-columns-file=template
的template为文件,内容为:1
2
Type Name
{.kind} {.metadata.name}
VersionedPrinter VersionedPrinter是对其他Printer的封装,先把Object转换成对应版本的Object,然后再封装的Printer进行打印。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// VersionedPrinter takes runtime objects and ensures they are converted to a given API version
// prior to being passed to a nested printer.
type VersionedPrinter struct {
printer ResourcePrinter
converter runtime.ObjectConvertor
versions []unversioned.GroupVersion
}
// NewVersionedPrinter wraps a printer to convert objects to a known API version prior to printing.
func NewVersionedPrinter(printer ResourcePrinter, converter runtime.ObjectConvertor, versions ...unversioned.GroupVersion) ResourcePrinter {
return &VersionedPrinter{
printer: printer,
converter: converter,
versions: versions,
}
}
func (p *VersionedPrinter) AfterPrint(w io.Writer, res string) error {
return nil
}
// PrintObj implements ResourcePrinter
func (p *VersionedPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
if len(p.versions) == 0 {
return fmt.Errorf("no version specified, object cannot be converted")
}
converted, err := p.converter.ConvertToVersion(obj, unversioned.GroupVersions(p.versions))
if err != nil {
return err
}
return p.printer.PrintObj(converted, w)
}
version由kubectl get pods --output-version='v1' -o yaml -w
的–output-version指定,但没什么作用,而且kubectl get代码中只有Watch会使用VersionedPrinter,所以还待更深入的研究。
HumanReadablePrinter HumanReadablePrinter可以”人性化”地输出内容。HumanReadablePrinter中有handlerMap,handlerMap记录了待打印的对象到处理函数的映射关系。系统中每种资源都有对应的打印函数。
其中kubectl -o wide
调用的也是HumanReadablePrinter。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
type HumanReadablePrinter struct {
handlerMap map[reflect.Type]*handlerEntry
options PrintOptions
lastType reflect.Type
hiddenObjNum int
}
// NewHumanReadablePrinter creates a HumanReadablePrinter.
//***创建HumanReadablePrinter***//
func NewHumanReadablePrinter(options PrintOptions) *HumanReadablePrinter {
printer := &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry),
options: options,
}
printer.addDefaultHandlers()
return printer
}
//***打印第一行标题***//
func (h *HumanReadablePrinter) printHeader(columnNames []string, w io.Writer) error {
if _, err := fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")); err != nil {
return err
}
return nil
}
//***打印Pod***//
func (h *HumanReadablePrinter) printPod(pod *api.Pod, w io.Writer, options PrintOptions) error {
if err := printPodBase(pod, w, options); err != nil {
return err
}
return nil
}
func printPodBase(pod *api.Pod, w io.Writer, options PrintOptions) error {
name := formatResourceName(options.Kind, pod.Name, options.WithKind)
namespace := pod.Namespace
restarts := 0
totalContainers := len(pod.Spec.Containers)
readyContainers := 0
reason := string(pod.Status.Phase)
if pod.Status.Reason != "" {
reason = pod.Status.Reason
}
initializing := false
for i := range pod.Status.InitContainerStatuses {
container := pod.Status.InitContainerStatuses[i]
restarts += int(container.RestartCount)
switch {
case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0:
continue
case container.State.Terminated != nil:
// initialization is failed
if len(container.State.Terminated.Reason) == 0 {
if container.State.Terminated.Signal != 0 {
reason = fmt.Sprintf("Init:Signal:%d", container.State.Terminated.Signal)
} else {
reason = fmt.Sprintf("Init:ExitCode:%d", container.State.Terminated.ExitCode)
}
} else {
reason = "Init:" + container.State.Terminated.Reason
}
initializing = true
case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
reason = "Init:" + container.State.Waiting.Reason
initializing = true
default:
reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers))
initializing = true
}
break
}
if !initializing {
restarts = 0
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
container := pod.Status.ContainerStatuses[i]
restarts += int(container.RestartCount)
if container.State.Waiting != nil && container.State.Waiting.Reason != "" {
reason = container.State.Waiting.Reason
} else if container.State.Terminated != nil && container.State.Terminated.Reason != "" {
reason = container.State.Terminated.Reason
} else if container.State.Terminated != nil && container.State.Terminated.Reason == "" {
if container.State.Terminated.Signal != 0 {
reason = fmt.Sprintf("Signal:%d", container.State.Terminated.Signal)
} else {
reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode)
}
} else if container.Ready && container.State.Running != nil {
readyContainers++
}
}
}
if pod.DeletionTimestamp != nil && pod.Status.Reason == node.NodeUnreachablePodReason {
reason = "Unknown"
} else if pod.DeletionTimestamp != nil {
reason = "Terminating"
}
if options.WithNamespace {
if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil {
return err
}
}
if _, err := fmt.Fprintf(w, "%s\t%d/%d\t%s\t%d\t%s",
name,
readyContainers,
totalContainers,
reason,
restarts,
translateTimestamp(pod.CreationTimestamp),
); err != nil {
return err
}
//***-o wide则写入IP和NODE***//
if options.Wide {
nodeName := pod.Spec.NodeName
podIP := pod.Status.PodIP
if podIP == "" {
podIP = "<none>"
}
if _, err := fmt.Fprintf(w, "\t%s\t%s",
podIP,
nodeName,
); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, AppendLabels(pod.Labels, options.ColumnLabels)); err != nil {
return err
}
if _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, pod.Labels)); err != nil {
return err
}
return nil
}
调用例子:1
2
3
4
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-1487191267-0t8x3 1/1 Running 2 1d
ubuntu-ssh-589933015-2lhgb 1/1 Running 21 19d
或者1
2
3
4
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-1487191267-0t8x3 1/1 Running 2 1d 172.17.0.4 fankang
ubuntu-ssh-589933015-2lhgb 1/1 Running 21 19d 172.17.0.5 fankang