Posted on 

kubernetes I - learn & install

0x00 background

因新项目与 kubernetes 本身相关,而我对 kubernetes 的复杂一无所知,所以需要探索它。
这个 post 主要记录学习 k8s 梳理出来的 XCx 基本概念以及如何参与开发,部分图片来源于 [^slideshare], 大量资料来自 [^officialdoc], 有点补充”官网的文档不如 repo 里面来的全面”!

0x01 what’s kubernetes ?

Kubernetes(缩写 k8s) 是 Google 开源的容器集群管理系统,主要是 go 实现,其目的是提供应用部署,维护,扩展机制等功能,利用 Kubernetes 能方便地管理跨机器运行容器化的应用。
其主要功能如下:

  • 使用 Docker 对应用程序包装 (package), 实例化 (instantiate), 运行 (run).
  • 以集群的方式运行,管理跨机器的容器。
  • 解决 Docker 跨机器容器之间的通讯问题。
  • Kubernetes 的自我修复机制使得容器集群总是运行在用户期望的状态。

0x02 why kubernetes?

kubernetes 是容器编排工具,目前这是最具竞争的领域之一,少数几个容器管理简单,但是调度管理以及监控大规模容器很具有挑战性,编排工具处理多种多样任务,如查找最优的位置或者服务器来运行容器,处理失败的任务,分享储存卷或者创建负载均衡与容器间通讯的覆盖网络等。
常见的容器编排项目如下:

  • Kubernetes: 是 Googlet 团队发起并维护的,目前在功能特性方面是最先进的。
  • Docker Swarm: 允许在 Docker 集群中调度容器,与 Docker 环境紧密集成。
  • Mesosphere: 通用数据中心管理系统,也能管理容器,还可以与其它编排系统 (如 Kubernetes) 集成。
  • CoreOS fleet: CoreOS 操作系统的一部分,管理在 CoreOS 集群中任何调度命令。

0x10 k8s architecture[^design]

k8s 的架构设计:

架构图
架构图

由图可见 Kubernetes 首先是一套分布式系统,节点分为两类:一类是属于管理平面的主节点 / 控制节点 (Master Node); 一类是属于运行平面的工作节点 (Worker Node); 复杂的工作交给控制节点去做了,工作节点负责提供稳定的操作接口和能力抽象。
没有能发现 Kubernetes 中对于控制平面的分布式实现,但是由于数据后端自身就是一套分布式的数据库 (etcd), 因此可以很容易扩展到分布式实现。

0x11 The Kubernetes Node

  • kubelet: 是工作节点执行操作的 agent, 负责具体的容器生命周期管理,根据从数据库中获取的信息来管理容器,并上报 pod 运行状态等;
  • kube-proxy: 是一个简单的网络访问代理,同时也是一个 Load Balancer. 它负责将访问到某个服务的请求具体分配给工作节点上的 Pod(同一类标签).

0x12 The Kubernetes Control Plane

services of master node

  • apiserver 是整个系统的对外接口,提供一套 RESTful 的 Kubernetes API, 供客户端和其它组件调用;
  • scheduler 负责对资源进行调度,分配某个 pod 到某个节点上是 pluggable 的,意味着很容易选择其它实现方式;
  • controller-manager 负责管理控制器,包括 endpoint-controller(刷新服务和 pod 的关联信息) 和 replication-controller(维护某个 pod 的复制为配置的数值).

worker

  • kubelet 是工作节点执行操作的 agent, 负责具体的容器生命周期管理,根据从数据库中获取的信息来管理容器,并上报 pod 运行状态等;
  • kube-proxy 是一个简单的网络访问代理,同时也是一个 Load Balancer. 它负责将访问到某个服务的请求具体分配给工作节点上的 Pod(同一类标签).

etcd

  • etcd 作为数据后端,又作为消息中间件,通过 etcd 来存储所有的主节点上的状态信息,很容易实现主节点的分布式扩展,组件可以自动的去侦测 etcd 中的数值变化来获得通知,并且获得更新后的数据来执行相应的操作。

0x20 quick practice

前面简要说明架构设计,这里介绍一些 k8s 的几个概念与主要组件作用图示,在快速实践中会对其有个初步印象,更多概念性的东西参考 [^term].

  • Cluster: 集群是指由 Kubernetes 使用一系列的物理机,虚拟机和其他基础资源来运行你的应用程序。
  • Node: 一个节点是一个运行 Kubernetes 中的主机。
  • Pod: 一个 Pod 对应于由若干容器组成的一个容器组,同个组内的容器共享一个存储卷 (volume).
  • Pos-states: 包含所有容器状态集合,包括容器组状态类型,容器组生命周期,事件,重启策略,以及 replication controllers.
  • Replication-Controllers: 主要负责指定数量的 pod 在同一时间一起运行。
  • Services: 一个 Kubernetes 服务是容器组逻辑的高级抽象,同时也对外提供访问容器组的策略。
  • Volumes: 一个卷就是一个目录,容器对其有访问权限。
  • Labels: 标签是用来连接一组对象的,比如容器组。标签可以被用来组织和选择子对象。
  • Namespace : Namespace 好比一个资源名字的前缀。它帮助不同的项目,团队或是客户可以共享 cluster, 防止命名冲突。
  • selector: 是一个通过匹配 labels 来定义资源之间关系得表达式,例如为一个负载均衡的 service 指定所目标 Pod.

单节点从 docker 快速部署 k8s 逻辑示意图:

0x21 by docker

利用 docker 可以快速上手 k8s(仅限于单机), 不过在 1.3 的分支里面这个文档已经被弄丢了。
在使用 docker 之前依赖先决条件 (kernel 支持 memory and swap accounting, 启用 cgroup 的 momory 控制器), 不过你默认安装较新的 linux 发行版一般都能满足。
手工拉取一个镜像gcr.io/google_containers/hyperkube:v1.3.7, 利用这个镜像创建容器时候会自己拉取其他镜像。

1
2
3
4
5
6
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
$ sudo docker pull gcr.io/google_containers/hyperkube:v1.3.7
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gcr.io/google_containers/hyperkube v1.3.7 24db5b90d9c0 8 days ago 404.7 MB

master

启动主节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# docker run \
--volume=/:/rootfs:ro \
--volume=/sys:/sys:ro \
--volume=/dev:/dev \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--volume=/var/lib/kubelet/:/var/lib/kubelet:rw \
--volume=/var/run:/var/run:rw \
--net=host \
--pid=host \
--privileged=true \
-d \
gcr.io/google_containers/hyperkube:v1.3.7 \
/hyperkube kubelet --containerized \
--hostname-override="127.0.0.1" \
--address="0.0.0.0" \
--api-servers=http://localhost:8080 \
--config=/etc/kubernetes/manifests

service proxy

在 tag 1.3.7 里面不需要手工去 etcd 了,在启动服务代理时候会自动帮启动。

1
2
3
4
5
# docker run -d --net=host \
--privileged gcr.io/google_containers/hyperkube:v1.3.7 \
/hyperkube proxy \
--master=http://127.0.0.1:8080 \
--v=2

启动完成主节点与服务代理过后,经过一段时间等待 (猜测时间长短取决于网速), 可以发现镜像依赖自己解决了。

1
2
3
4
5
6
7
8
9
10
11
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gcr.io/google_containers/hyperkube-amd64 v1.3.7 24db5b90d9c0 8 days ago 404.7 MB
gcr.io/google_containers/hyperkube v1.3.7 24db5b90d9c0 8 days ago 404.7 MB
gcr.io/google_containers/kubernetes-dashboard-amd64 v1.1.1 f739d2414b14 6 weeks ago 55.83 MB
gcr.io/google_containers/exechealthz-amd64 1.1 c3a89c92ef5b 7 weeks ago 8.332 MB
gcr.io/google_containers/kubedns-amd64 1.5 3afb7dbce540 12 weeks ago 50.82 MB
gcr.io/google-containers/kube-addon-manager-amd64 v4 fb28c478466a 3 months ago 240.4 MB
gcr.io/google_containers/kube-dnsmasq-amd64 1.3 9a15e39d0db8 3 months ago 5.126 MB
gcr.io/google_containers/pause-amd64 3.0 99e59f495ffa 4 months ago 746.9 kB
gcr.io/google_containers/etcd-amd64 2.2.5 72bd8a257d7a 5 months ago 30.45 MB

testing

在其中开始一个 kube-ui 部署。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ sudo dnf install kubernetes-client -y # 安装命令行工具

$ kubectl cluster-info
Kubernetes master is running at http://localhost:8080
KubeDNS is running at http://localhost:8080/api/v1/proxy/namespaces/kube-system/services/kube-dns
kubernetes-dashboard is running at http://localhost:8080/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard

$ kubectl get node
NAME STATUS AGE
127.0.0.1 Ready 2m

$ sudo docker pull gcr.io/google_containers/kube-ui:v5 # 把镜像拉回来, 避免下一条指令的漫长等待
Trying to pull repository gcr.io/google_containers/kube-ui ...
v5: Pulling from gcr.io/google_containers/kube-ui
a3ed95caeb02: Pull complete
71c3140c0e84: Pull complete
Digest: sha256:2165d142900fefb3605533e649de5f0276e82697dc548b5a60e1a455b1cf20ff
Status: Downloaded newer image for gcr.io/google_containers/kube-ui:v5

$ kubectl -s http://localhost:8080 run kube-ui --image=gcr.io/google_containers/kube-ui:v5 --port=80
deployment "kube-ui" created

访问 http://master_node_ip:8080/ui.

这时候你初步接触完成了你准备把环境还原 (保留镜像).

1
2
$ for ((i=0; i<2; i++)); do docker rm -f `docker ps -qa`; done
$ for i in $(mount | grep kube | cut -d' ' -f 3-3 | tr '\n' ' '); do umount $i; done && rm -rf /var/lib/kubelet/

0x22 by vagrant[^vagrant]

这个方法 k8s 开发者常用的部署方法之一 – 在本地创建基于虚拟机的集群。
源码的安装个人喜欢go get github.com/kubernetes/kubernetes方式拉代码回来。
关于二进制部署安利 NFS 方法,nfs 对比默认的 rsync, 使用 nfs 建立集群速度快,节约磁盘空间 (我遇到磁盘 100%).

如果你 (比如我手贱) 操作失误误删/var/lib/libvirt, 需要下面的指令来重新创建 default-pool.

1
2
3
4
5
6
7
8
9
10
11
# virsh pool-define /dev/stdin <<EOF
<pool type='dir'>
<name>default</name>
<target>
<path>/var/lib/libvirt/images</path>
</target>
</pool>
EOF

# virsh pool-start default
# virsh pool-autostart default

通过项目自己带的脚本部署的脚本大体轮廓是下面这样,至于为什么调用那么多rm -rf是因为我是处女座喜欢每一次的 master 节点都是 1,node 是 2 依次排列下去。
可以通过dnf install vagrant-libvirt redhat-lsb-cxx redhat-rpm-config-41-2 -y && vagrant plugin install vagrant-proxyconf安装 vagrant 和一个代理插件。

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
#!/bin/bash

export KUBERNETES_PROVIDER=vagrant
export KUBERNETES_VAGRANT_USE_NFS=true
export VAGRANT_HTTP_PROXY=http:/username:password@10.0.58.88:8080
export VAGRANT_HTTPS_PROXY=http://username:password@10.0.58.88:8080
iptables -X
iptables -F
iptables -Z

install() {
rpm -qa | grep vagrant-libvirt
if [ $? != 0 ]; then
dnf install vagrant-libvirt -y
fi
vagrant plugin install vagrant-proxyconf # 让 vagrant 支持的代理的插件
pushd $GOPATH/src/github.com/kubernetes/kubernetes
./cluster/kube-up.sh
popd
}

remove() {
sudo virsh destroy kubernetes_master
sudo virsh destroy kubernetes_node-1
sudo virsh undefine kubernetes_master
sudo virsh undefine kubernetes_node-1
sudo rm -rf /var/lib/libvirt/images/kubernetes_*
sudo rm -rf /var/lib/libvirt/qemu/
sudo rm -rf /var/lib/libvirt/dnsmasq/
sudo systemctl restart libvirtd.service
}

$1

在使用项目在自带脚本去建立集群时想在虚拟机节点里面预配置东西 (如:dnf 代理,环境变量) 可以切到/var/lib/libvirt/images, 到这里看见镜像模板,节点都是基于这个镜像做差异生成的,可以qemu-system-x86_64 kube-fedora23_vagrant_box_image_0.img启动它,手工修改配置后关机。
(如果在 nfs 挂载时候 hang 住了,多数可能是防火墙问题记得清理一下).

虽然初始化两个节点起来成功了,但是 k8s 集群并没有起来,看上面图片显示需要自己 make 一个 release 出来,在开发机器上进入 repo 的目录sudo make quick-release, 过后重新跑以上脚本,然后大体上看到下面这个就算开发环境部署起来了,这时候 repo 下面有个 vagrant 生成的目录里面记录这节点一些信息,只有在当前目录vagrant ssh master才能有效。

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
This can take some time based on your network, disk, and cpu speed.
It is possible for an error to occur during Salt provision of cluster and this could loop forever.
Validating master
Validating node-1
..............................................
Waiting for each node to be registered with cloud provider
Flag --api-version has been deprecated, flag is no longer respected and will be deleted in the next release
Validating we can run kubectl commands.
Connection to 192.168.121.223 closed.

Kubernetes cluster is running.

The master is running at:

https://10.245.1.2

Administer and visualize its resources using Cockpit:

https://10.245.1.2:9090

For more information on Cockpit, visit http://cockpit-project.org

The user name and password to use is located in /root/.kube/config

... calling validate-cluster
Found 1 node(s).
NAME STATUS AGE
kubernetes-node-1 Ready 6m
Cluster not working yet.
Validate output:
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-1 Healthy {"health": "true"}
etcd-0 Healthy {"health": "true"}
Cluster validation succeeded
Done, listing cluster services:

Kubernetes master is running at https://10.245.1.2
Heapster is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/heapster
KubeDNS is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/kube-dns
kubernetes-dashboard is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard
Grafana is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana
InfluxDB is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

kube-push脚本的工作实在是太慢了,因为用kube-up自动创建集群前已经声明使用 NFS 做共享了,在节点的/vagrant可以看见开发机上面的 repo 目录。

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
[root@localhost kubernetes]# free -m
total used free shared buff/cache available
Mem: 31966 3763 6175 4 22027 27757
Swap: 15999 0 15999

[root@localhost kubernetes]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 58
Model name: Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
Stepping: 9
CPU MHz: 1603.046
CPU max MHz: 3900.0000
CPU min MHz: 1600.0000
BogoMIPS: 6783.90
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 8192K
NUMA node0 CPU(s): 0-7
...

[root@localhost kubernetes]# time ./cluster/kube-push.sh
...
real 52m8.300s
user 0m6.753s
sys 0m0.856s

0x30 the network of k8s

k8s 采用扁平化的网络模型,每个 pod 都有一个全局唯一的 ip,pod 之间可以跨主机通信,相比于 Docker 原生的 NAT 方式来说,这样使得容器在网络层面更像虚拟机或者物理机,复杂度整体降低,更加容易实现服务发现,迁移,负载均衡等功能。为了实现这个目标 k8s 网络完成的工作如下:

  • 紧耦合的容器之间通信,通过 pod 和 localhost 访问解决。
  • pod 之间通信,建立通信子网,比如隧道,路由,Flannel,Open vSwitch, Weave.
  • pod 和 service, 以及外部系统和 Service 的通信,引入 Service 解决。

Kubernetes 的网络会给每个 Pod 分配一个 IP 地址,不需要在 Pod 之间建立链接,也基本不需要去处理容器和主机之间的端口映射。
注意:pod 重建后,IP 会被重新分配,所以内网通信不要依赖 Pod IP; 通过 Service 环境变量或者 DNS 解决。

0x40 testing in k8s

在发 pr 前你需要确认你的修改至少通过了单元测试与集成测试,如果要想被合并还要通过端到端的测试也是必须的。
测试的代码组织是利用 golang 的 testing.

0x41 unit testing

你可以在单元测试期间使用KUBE_GOFLAGS变量来设置 go flags.
运行全部的单元测试。

1
2
cd kubernetes
make test # Run all unit tests.

运行单个包或者多个的单元测试。

1
2
make test WHAT=pkg/api                # run tests for pkg/api
make test WHAT="pkg/api pkg/kubelet" # run tests for pkg/api and pkg/kubelet

对包里面具体参数的单元测试。

1
2
3
4
5
# Runs TestValidatePod in pkg/api/validation with the verbose flag set
make test WHAT=pkg/api/validation KUBE_GOFLAGS="-v" KUBE_TEST_ARGS='-run ^TestValidatePod$'

# Runs tests that match the regex ValidatePod|ValidateConfigMap in pkg/api/validation
make test WHAT=pkg/api/validation KUBE_GOFLAGS="-v" KUBE_TEST_ARGS="-run ValidatePod\|ValidateConfigMap$"

反复运行单元测试

1
2
# Have 2 workers run all tests 5 times each (10 total iterations).
make test PARALLEL=2 ITERATION=5

Unit test coverage

获取测试覆盖率,以下命令会生成一个 html 文档,当然也可以对一个 package 做这样的操作。

1
2
make test KUBE_COVER=y
make test WHAT=pkg/kubectl KUBE_COVER=y

Benchmark unit tests

典型的 benchmark 指令如下:

1
go test ./pkg/apiserver -benchmem -run=XXX -bench=BenchmarkWatch

其中:

  • -run=XXX 是一个表达式过滤出 test case 来运行。
  • -bench=BenchmarkWatch 将会运行 BenchmarkWatch 中的测试,参考grep -nr BenchmarkWatch .
  • -benchmem 启用内存分配状态

0x42 Integration tests

  • 集成测试仅改访问本地的资源

    • 最普遍的 etcd 或者服务都应该在本地。
  • 所有重大的特性都依赖集成测试

    • 包括 kubectl 这个命令行工具。
  • 首选的测试多场景的或输入的方法是 TableDrivenTests

  • 每一个测试都应创建自己的 master,http server 和 config.

安装 etcd

k8s 的集成测试需要你的PATH里面有 etcd.

1
2
3
4
5
6
7
8
9
10
# Install etcd and add to PATH

# Option a) install inside kubernetes root
hack/install-etcd.sh # Installs in ./third_party/etcd
echo export PATH="$PATH:$(pwd)/third_party/etcd" >> ~/.profile # Add to PATH

# Option b) install manually
grep -E "image.*etcd" cluster/saltbase/etcd/etcd.manifest # Find version
# Install that version using yum/apt-get/etc
echo export PATH="$PATH:<LOCATION>" >> ~/.profile # Add to PATH

许多测试会在内部开始一个 etcd 服务,存储测试数据在操作系统的临时文件目录,如果你看见是因为没有足够磁盘或者一个 volume 不可预知的写延迟导致的test failures, 可以用TEST_ETCD_DIR环境变量来覆盖掉默认的写位置。

运行集成测试

1
make test-integration  # Run all integration tests.

这个脚本会运行test/integration中的测试脚本。

1
make test-integration KUBE_GOFLAGS="-v" KUBE_TEST_ARGS="-run ^TestPodUpdateActiveDeadlineSeconds$"

可以利用KUBE_TEST_ARGS变量与 hack /test-integration.sh 脚本来运行具体的集成测试,上面的是例子。
不过KUBE_TEST_ARGS变量被设置过后,那么 test case 将只会在 v1 版本的 api 上跑且watch cachecase 将会被跳过。

0x43 end-to-end tests

k8s 的端到端测试提供了一个测试系统中端到端行为的机制,也是确认用户操作是否符合开发者的规范的最后一个信号,尽管单元测试与集成测试提供了不错的信号。现实是像 Kubernetes 分布式系统的情况并不少见问题是 – 微小的变化可能会通过所有单元测试和集成测试,但在系统层面导致了不可预见的变化,端到端的测试弥补了单元测试与集成测试在这方面的不足,它的目标是确认基于 k8s 代码的一致与可靠的行为,并在用户接触前捕获到难以测试的 bug.

在 k8s 的端到端的测试构建基于 GinkgoGomega, 它们是 (Behavior-Driven Development (BDD) )[^BDD] 测试框架,在沉浸进测试代码前建议先阅读文档。

典型用法

先把环境准备一下:

1
2
3
export KUBERNETES_PROVIDER=vagrant
export VAGRANT_DEFAULT_PROVIDER=libvirt
go run hack/e2e.go -v --build --up --test --down

运行下面任一指令之一:

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
# Build binaries for testing
go run hack/e2e.go -v --build

# Create a fresh cluster. Deletes a cluster first, if it exists
go run hack/e2e.go -v --up

# Run all tests
go run hack/e2e.go -v --test

# Run tests matching the regex "\[Feature:Performance\]"
go run hack/e2e.go -v --test --test_args="--ginkgo.focus=\[Feature:Performance\]"

# Conversely, exclude tests that match the regex "Pods.*env"
go run hack/e2e.go -v --test --test_args="--ginkgo.skip=Pods.*env"

# Run tests in parallel, skip any that must be run serially
GINKGO_PARALLEL=y go run hack/e2e.go --v --test --test_args="--ginkgo.skip=\[Serial\]"

# Flags can be combined, and their actions will take place in this order:
# --build, --up, --test, --down
#
# You can also specify an alternative provider, such as 'aws'
#
# e.g.:
KUBERNETES_PROVIDER=aws go run hack/e2e.go -v --build --up --test --down

# -ctl can be used to quickly call kubectl against your e2e cluster. Useful for
# cleaning up after a failed test or viewing logs. Use -v to avoid suppressing
# kubectl output.
go run hack/e2e.go -v -ctl='get events'
go run hack/e2e.go -v -ctl='delete pod foobar'

强力清理:在运行期间可以C+c可以正确的关闭,但是如果出现什么错误时候可以还有虚拟机在运行要被强制清除就使用下面命令:

1
go run hack/e2e.go -v --down

0x50 debug with development

根据社区提供的文档 debug 的方式应该是log与我之前经验是一致的,gdb 对 go 支持太弱了尤其是大型项目中。

debug with gdb

gdb 支持 python 写的插件,够使用这个特点让 gdb 支持其 runtime 的 trace.

1
2
3
define goruntime
source /usr/lib/golang/src/runtime/runtime-gdb.py
end

但是实践用起来有点糙,单点全是汇编指令,一把线程,手工调试简直了,不过操作还是还是 gdb 的习惯。

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
[root@localhost kubernetes]# make WHAT=cmd/kubectl/
can't load package: package github.com/kubernetes/kubernetes: no buildable Go source files in /root/workspace/go/src/github.com/kubernetes/kubernetes
+++ [0929 04:28:33] Generating bindata:
/root/workspace/go/src/github.com/kubernetes/kubernetes/test/e2e/framework/gobindata_util.go
+++ [0929 04:28:33] Building the toolchain targets:
k8s.io/kubernetes/hack/cmd/teststale
+++ [0929 04:28:34] Building go targets for linux/amd64:
cmd/kubectl/
[root@localhost kubernetes]# ./_output/bin/kubectl version
Client Version: version.Info{Major:"1", Minor:"5+", GitVersion:"v1.5.0-alpha.0.1361+aa9880fe246a33-dirty", GitCommit:"aa9880fe246a33356c89567d5ed5b11f22661540", GitTreeState:"dirty", BuildDate:"2016-09-29T06:56:41Z", GoVersion:"go1.6.3", Compiler:"gc", Platform:"linux/amd64"}
Unable to connect to the server: Service Unavailable
[root@localhost kubernetes]# gdb ./_output/bin/kubectl -q
(gdb) b kubectl.go:35
Breakpoint 1 at 0x46a2a6: file /root/workspace/go/src/github.com/kubernetes/kubernetes/_output/local/go/src/k8s.io/kubernetes/cmd/kubectl/app/kubectl.go, line 35.
(gdb) r
Starting program: /root/workspace/go/src/github.com/kubernetes/kubernetes/_output/local/bin/linux/amd64/kubectl
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.23.1-10.fc24.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff77fa700 (LWP 4062)]
[New Thread 0x7ffff6ff9700 (LWP 4063)]
[New Thread 0x7ffff67f8700 (LWP 4064)]
[New Thread 0x7ffff5ff7700 (LWP 4065)]
[New Thread 0x7ffff57f6700 (LWP 4066)]
[New Thread 0x7ffff4c83700 (LWP 4067)]
[New Thread 0x7fffe7fff700 (LWP 4068)]
[New Thread 0x7fffe77fe700 (LWP 4069)]
[New Thread 0x7fffe6ffd700 (LWP 4071)]
[New Thread 0x7fffe67fc700 (LWP 4074)]
[New Thread 0x7fffe5ffb700 (LWP 4075)]
[New Thread 0x7fffe57fa700 (LWP 4076)]

Thread 1 "kubectl" hit Breakpoint 1, k8s.io/kubernetes/cmd/kubectl/app.Run (~r0=...)
at /root/workspace/go/src/github.com/kubernetes/kubernetes/_output/local/go/src/k8s.io/kubernetes/cmd/kubectl/app/kubectl.go:35
35 defer logs.FlushLogs()
(gdb) list
30 WARNING: this logic is duplicated, with minor changes, in cmd/hyperkube/kubectl.go
31 Any salient changes here will need to be manually reflected in that file.
32 */
33 func Run() error {
34 logs.InitLogs()
35 defer logs.FlushLogs()
36
37 cmd := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr)
38 return cmd.Execute()
39 }
(gdb) bt
#0 k8s.io/kubernetes/cmd/kubectl/app.Run (~r0=...)
at /root/workspace/go/src/github.com/kubernetes/kubernetes/_output/local/go/src/k8s.io/kubernetes/cmd/kubectl/app/kubectl.go:35
#1 0x00000000004019d8 in main.main ()
at /root/workspace/go/src/github.com/kubernetes/kubernetes/_output/local/go/src/k8s.io/kubernetes/cmd/kubectl/kubectl.go:26
(gdb) info goroutines
* 1 running runtime.systemstack_switch
* 17 syscall runtime.goexit
2 waiting runtime.gopark
18 waiting runtime.gopark
19 waiting runtime.gopark
24 waiting runtime.gopark
* 34 syscall runtime.notetsleepg
25 waiting runtime.gopark
26 waiting runtime.gopark
27 waiting runtime.gopark
28 waiting runtime.gopark
29 waiting runtime.gopark
* 30 running runtime.systemstack_switch
31 waiting runtime.gopark
32 waiting runtime.gopark
* 4 syscall runtime.notetsleepg
13 waiting runtime.gopark
(gdb) goroutine 2 bt
#0 runtime.gopark (unlockf={void (runtime.g *, void *, bool *)} 0xc820028758, lock=0x39fb5e0 <runtime.forcegc>, reason="force gc (idle)",
traceEv=20 '\024', traceskip=1) at /usr/lib/golang/src/runtime/proc.go:263
#1 0x000000000042eca4 in runtime.goparkunlock (lock=0x39fb5e0 <runtime.forcegc>, reason="force gc (idle)", traceEv=20 '\024', traceskip=1)
at /usr/lib/golang/src/runtime/proc.go:268
#2 0x000000000042e9e8 in runtime.forcegchelper () at /usr/lib/golang/src/runtime/proc.go:229
#3 0x000000000045ea51 in runtime.goexit () at /usr/lib/golang/src/runtime/asm_amd64.s:1998
#4 0x0000000000000000 in ?? ()

debug with log

社区 debug 是使用glog包靠打印 log 来 debug, 其实这样是 go 编程排错非常好的实践.go 是面向并发编程的,用 gdb 这样的工具去调试大量的线程是不人道的。

重点是 -v:

As per the comments, the practical default level is V(2). Developers and QE environments may wish to run at V(3) or V(4). If you wish to change the loglevel, you can pass in -v=X where X is the desired maximum level to log.

这是开发对 glog 的 conventions.

  • glog.Errorf() - Always an error
  • glog.Warningf() - Something unexpected, but probably not an error
  • glog.Infof() has multiple levels:
    • glog.V(0) - Generally useful for this to ALWAYS be visible to an operator
      • Programmer errors
      • Logging extra info about a panic
      • CLI argument handling
    • glog.V(1) - A reasonable default log level if you don’t want verbosity.
      • Information about config (listening on X, watching Y)
      • Errors that repeat frequently that relate to conditions that can be corrected (pod detected as unhealthy)
    • glog.V(2) - Useful steady state information about the service and important log messages that may correlate to significant changes in the system. This is the recommended default log level for most systems.
      • Logging HTTP requests and their exit code
      • System state changing (killing pod)
      • Controller state change events (starting pods)
      • Scheduler log messages
    • glog.V(3) - Extended information about changes
      • More info about system state changes
    • glog.V(4) - Debug level verbosity (for now)
      • Logging in particularly thorny parts of code where you may want to come back later and check it

[^officialdoc]: official doc
[^slideshare]: slideshare
[^design]: k8s design
[^term]: k8s term
[^vagrant]: docs/devel/local-cluster/vagrant
[^networking]: docs/design/networking
[^debug]: docs/devel/logging
[^hangon]: network hangon
[^devel_guide]: docs/devel
[^BDD]: 行为驱动开发