kubernetes 设计理念及主要概念之Pod(二)

Kubernetes使用共享网络将多个物理机或虚拟机汇集到一个集群中,在各服务器之间进行通信,该集群时配置Kubernetes的所有组件、功能和工作负载的物理平台。集群中一台服务器(或高可用部署中的一组服务器)作为Master,负责管理整个集群,其余作为Worker Node,集群中的主机可以是物理服务器,也可以是虚拟机(包括云VPS)。

集群概念

  1. Master节点

Master是集群的网关和中枢,负责为用户和客户端暴露API、状态信息、管理和调度负责,以及编排其它组件之间的通信等任务。单个Master节点可以完成所有功能,实际生产环境由于负载均衡等目的,需要协同部署多个Master节点。

  1. Node节点

Node节点属于Worker节点,由多个主机构建。Worker节点不暴露任何信息,不对外开发接口。

主要概念

Kubernetes由很多技术概念,同时对应很多API对象,API对象是k8s集群中的管理操作单元。

每个API对象都有3大类属性:元数据metadat、规范spec和状态status。元数据是用来表示API对象的,每个对象至少有3个元数据:namespace、name和uid;除此之外还有各种各样的标签label是用来标识和匹配不同的对象。规范spec描述了用户期望k8s集群中的分布式系统达到的理想状态(desired State),状态status描述了系统实际当前达到的状态(Status)。

k8s中的API对象设计理念之一,是所有操作都是声明式的(Declarative)。所以你会发现会有很多yaml配置。声明式操作在分布式系统中的好处是稳定,不怕丢失操作或运行多次。

下面简述一些常见的API对象。

Pod

Pod是k8s的最小调度单元,同一个Pod中的容器共享网络名称空间和存储资源,这些容器可经本地回环节口lo直接通信,但彼此又在Mount、User及PID等名称空间上保持隔离。

特性:

  • 包含多个共享IPC、Network和UTC namespace的容器,可直接本地通信
  • 所有Pod内容器都可以访问共享的Volume,可以访问共享数据
  • Pod一旦调度后就跟Node绑定,即使Node挂掉也不会重新调度,推荐使用Deployments、Daemonsets等控制器来容错
  • 优雅终止:Pod删除的时候先给其内的进程发送SIGTERM,等待一段时间(grace period)后才强制停止运行的进程
  • 特权容器(通过SecurityContext配置)具有改变系统配置的权限

Pod

通过yaml或接送描述Pod和其内Container的运行环境以及期望状态,比如一个最简单的nginx pod可以定义为,

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
sepc:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80

volume可以为容器提供持久化存储,比如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: redis
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}

重启机制,RestartPolicy

目前支持三种RestartPolicy

  • Always: 只要退出就重启
  • OnFailure: 失败退出时重启
  • Never: 只要退出就不再重启

这里的重启指在Pod所在Node本地重启,它不会调度到其它Node上去。

环境变量为容器提供了一些重要的资源,包括容器和Pod的基本信息以及集群中服务的信息等,

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
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- name: test-container
image: gcr.io/google_containers/busybox
command: [ "sh", "-c"]
args:
- env
resource:
requests:
memory: "32Mi"
cpu: "125m"
limits:
memory: "64Mi"
cpu: "250m"
env:
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: sepc.nodeName
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: MY_CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: test-container
resource: requests.cpu
- name: MY_CPU_LIMIT
valueFrom:
resourceFieldRef:
containerName: test-container
resource: limits.cpu
- name: MY_MEM_REQUEST
valueFrom:
resourceFieldRef:
containerName: test-container
resource: requests.memory
- name: MY_MEM_LIMIT
valueFrom:
resourceFieldRef:
containerName: test-container
resource: limits.memory
restartPolicy: Never

ImagePullPolicy策略,

  • Always: 不管镜像是否存在都会进行一次拉取。
  • Never:不管镜像是否存在都不会进行拉取。
  • IfNotPresent: 镜像不存在时,才进行拉取。

注意,

  • 默认为IfNotPresent,但:latest标签的镜像默认为Always
  • 拉取镜像时docker会进行MD5校验,如果镜像中的MD5没变,不会拉取镜像数据。
  • 生产环境中应该尽量避免使用:latest标签,而开发环境可以借助:latest标签自动拉取最新的镜像。

访问DNS策略,

通过设置dnsPolicy参数,设置Pod中容器访问DNS的策略

  • ClusterFirst: 优先基于cluster domain后缀,通过kube-dns查询
  • Default:首先从kubelet中配置的DNS查询

默认是ClusterFirst

通过hostIPC设置参数为Ture,使用主机的IPC明明空间,默认为False。

通过hostNetwork设置参数为True,使用主机的命名空间,默认为False。

通过hostPID设置参数为True,使用主机的PID命名空间,默认为False。

通过subdomain参数设置Pod的子域名,默认为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: busybox2
labels:
name: busybox
spec:
hostname: busybox-2
subdomain: default-subdomain
containers:
- image: busybox
command:
- sleep
- "3600"
name: busybox

kubernetes通过cgroups限制容器的CPU和内存等计算资源,包括requests和limits等:

  • spec.containers[].resources.limits.cpu:CPU上限,可以短暂超过,容器也不会被停止
  • spec.containers[].resources.limits.memory:内存上限,不可以超过;如果超过,容器可能会被停止或调度到其它资源充足的机器上
  • spec.containers[].resources.requests.cpu:CPU请求,可以超过
  • spec.containers[].resources.requests.memory:内存请求,可以超过;但如果超过,容器可能会在Node内存不足时清理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiServersion: v1
kind: Pod
metadata:
labels:
app: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
resources:
requests:
cpu: "300m"
memory: "56Mi"
limits:
cpu: "500m"
memory: "128Mi"

为了保证容器在部署后确实处在正常运行状态,Kubernetes提供了两种探针(probe,支持exec、tcp和httpGet方式)来探测容器的状态:

  • LivenessProbe:探测应用是否处于健康状态,如果不健康则删除重建容器
  • ReadinessProbe:探测应用是否启动完成并且处于正常服务状态,如果不正常则更新容器状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: v1
kind: Pod
metadata:
labels:
app: nginx
name: nginx
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: http
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 15
timeoutSeconds: 1
readinessProbe:
httpGet:
path: /ping
port: 80
initialDelaySeconds: 5
timeoutSeconds: 1

initContainers在容器执行前运行,常用来初始化容器操作,

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
apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
initContainers:
- name: install
image: busygox
command:
- wget
- "-O"
- "/work-dir/index.html"
- http://kubernetes.io
volumeMounts:
- name: workdir
mountPath: "/work-dir"
dnsPolicy: Default
volumes:
- name: workdir
emptyDir: {}

容器生命周期钩子(Container Lifecycle Hooks)监听容器生命周期的特定事件,并在事件发生时执行已注册的回调函数。支持两种钩子:

  • postStart:容器启动后执行,注意由于一步执行,它无法保证一定在ENTRYPOINT之后运行。如果失败,容器会被杀死,并根据RestartPolicy决定是否重启
  • preStop:容器停止前执行,常用于资源清理。如果失败,容器同样也会被杀死

钩子的回调函数支持两种方式:

  • exec: 在容器内执行命令
  • httpGet:向指定URL发起GET请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh" "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/usr/sbin/nginx", "-s", "quit"]

默认情况下,容器都是以非特权容器方式运行。比如,不能再容器中创建虚拟网卡、配置虚拟网络。

Kubernetes提供了修改Capabilities的机制,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: hello-world
spec:
containers:
- name: friendly-container
image: "alpine:3.4"
command: ["/bin/echo", "hello", "world"]
securityContext:
capabilities:
add:
- NET_ADMIN
drop:
- KILL

可以通过给Pod增加 kubernetes.io/ingressbandwidthkubernetes.io/egress-bandwidth 这两个annotation来限制Pod的网络带宽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: qos
annotations:
kubernetes.io/ingress-bandwidth: 3M
kubernetes.io/egress-bandwidth: 4M
spec:
containers:
- name: iperf3
image: networkstatic/iperf3
command:
- iperf3
- -s

可以通过nodeSelector、nodeAffinity、podAffinity以及Taints和tolerations等来讲Pod调度到需要的Node上。

也可以通过设置nodeName参数,将Pod调度到制定node节点上。

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd

默认情况下,容器的/etc/hosts时kubelet自动生成的,并且仅包含localhost和podName等。不建议直接修改/etc/hosts文件,因为在Pod启动或重启时会被覆盖。

默认的/etc/hosts文件格式如下,其中nginx-4217019353-fb2c5是podName:

1
kubectl exec nginx-app-5dd4f9fd4d-nm4sx -- cat /etc/hosts

executor

从v1.7开始,可以通过pod.Spec.HostAliases来增加hosts内容,如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
name: hostaliases-pod
spec:
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "foo.local"
- "bar.local"
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
containers:
- name: cat-hosts
image: busybox
command:
- cat
args:
- "/etc/hosts"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl logs hostaliases-pod

# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.244.1.35 hostaliases-pod

# Entries added by HostAliases.
127.0.0.1 foo.local bar.local
10.1.2.3 foo.remote bar.remote

Namespace

Namespace是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或用户组。常见的pods,services,replication controller和deployments等都是属于某一个namespace的(默认是default),而node,persistentVolumes等则不属于任何namespace。

Namespace常用来隔离不同的用户,比如Kubernetes自带的服务一般运行在kube-system namespace中。

  1. 查询

名称空间的选项可以用--namespace-n来指定,不指定默认就是default。也可以通过--all-namespace=true来查看所有namespace下的资源。

1
2
3
4
5
kubectl get namespaces
NAME STATUS AGE
default Active 32h
kube-public Active 32h
kube-system Active 32h

注意:kubectl get namespace等效;namespace包含两种状态“Active”和“Terminating”。在namespace删除过程中,namespace状态被设置成“Terminating”。

  1. 创建

命令行直接创建,

1
kubectl create namespace new-namespace

通过文件创建,

1
2
3
4
apiVersion: v1
kind: Namespace
metadata:
name: new-namespace
1
kubectl create -f ./my-namespace.yaml

注意:namespace的name满足正则表达式[a-z0-9]([-a-z0-9]*[a-z0-9])?,最大长度为63位,

  1. 删除
  • 删除一个namespace会自动删除所有属于该namespace的资源
  • defaultkube-system名称空间不可删除
  • PersistentVolumes是不属于任何namespace的,但PersistentVolumeClaim是属于某个特定namespace的
  • Events是否属于namespace取决于产生events的对象

Node

Node是Pod真正运行的主机,可以是物理机,也可以是虚拟机。为了管理Pod,每个Node节点上至少运行container runtime(比如docker或rkt)、kubeletkube-proxy服务。

nodes

  1. Node管理

不像其它的资源(如Pod和Namespace),Node本质上不是Kubernetes来创建的,Kubernetes只是管理Node上的资源。虽然可以通过Manifest创建一个Node对象(如下json),但Kubernetes也只是去检查是否真的是有这么一个Node,如果检查失败,也不会往上调度Pod。

1
2
3
4
5
6
7
8
9
10
{
"kind": "Node",
"apiVersion": "v1",
"metadata": {
"name": "10.240.79.157",
"labels": {
"name": "my-first-k8s-node"
}
}
}

这个检查是由Node Controller来完成的。Node Controller负责

  • 维护Node状态
  • 与Cloud Provider同步Node
  • 给Node分配容器CIDR
  • 删除带有NoExecute taint的Node上的Pods

默认情况下,kubelet在启动时会向master注册自己,并创建Node资源。

  1. Node的状态

每个Node都包括以下状态信息

  • 地址:包括hostname、外网IP和内网IP
  • 条件(Condition):包括OutOfDisk、Ready、MemoryPressure和DiskPressure
  • 容量(Capacity):Node上的可用资源,包括CPU、内存和Pod总数
  • 基本信息(Info):包括内核版本、容器引擎版本、OS类型等
  1. Taints和tolerations

Taints和tolerations用于保证Pod不被调度到不合适的Node上,Taint应用于Node上,而toleration则应用于Pod上(Toleration是可选的)。

比如,可以使用taint命令给node1添加taints:

1
2
kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node2 key1=value2:NoExecutte
  1. Node维护模式

标识Node为不可调度,但不影响其上正在运行的Pod,这种维护Node时时非常有用的

1
kubectl cordon $NODENAME