Kubernetes 入门指南

Pod 的介绍

  在 Kubernetes 中,Pod 是最小的调度单元,Pod 其实就是一个容器组,它可以包含一个或多个容器(Pod 翻译为豆荚,一个豆荚可以有多个豆子)。对于一个 Pod 来说,它会运行在某个宿主机上,当需要调度的时候,Kubernetes 会将 Pod 作为一个整体进行调度。
  由于整个 Pod 是运行在一个宿主机上的,所以 Pod 中的所有容器共享这些资源:

  • 所有容器共享同一个 IP。
  • 所有容器共享同一个 hostname。
  • 当某个 Volume 被挂载到 Pod 的文件系统时,这个 Volume 可以被多个容器共享。
  • 容器之间可以通过 System V IPC 或 POSIX 消息队列进行通信。

  思考一下,什么样的容器才适合放到同一个 Pod 中呢?

Pod 的配置

  可以使用 yaml 配置文件去描述 Pod,并写清楚我们希望 Pod 达到的状态:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80

  使用下面的命令可以创建 Pod,用于运行 Nginx:

1
$ kubectl apply -f pod-nginx.yaml

  也可以获取 Pod 的运行状态:

1
2
3
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 8m

  如果想要获取 Pod 中所有容器的运行状态,可以使用:

1
$ kubectl get pods nginx -o json

  如果想获得 Pod 的更多详细信息(包括所有容器的信息,以及和 Pod 相关的事件),可以使用:

1
$ kubectl describe pods nginx

  删除 Pod 时,可以根据 Pod 的名称删除指定的 Pod:

1
$ kubectl delete pods/nginx

  也可以根据 yaml 配置文件删除 Pod:

1
$ kubectl delete -f pod-nginx.yaml

Pod 的调试

  在运行上面的 Nginx Pod 之后,如何判断 Nginx 正常工作呢?Kubernetes API 提供了端口转发的功能,可以在另一个终端执行下面的命令:

1
$ kubectl port-forward nginx 8000:80

  上面的命令会将localhost:8000的请求转发到 Nginx Pod 的80端口。可以验证一下:

1
$ curl localhost:8000

  如果没有差错,这个命令会输出 Nginx 首页的内容。


  另一种有效的调试手段,就是进入到容器内部去实地考察:

1
$ kubectl exec -it nginx bash

  如果 Pod 中有多个容器,可以使用-c参数指定具体的容器:

1
$ kubectl exec -it nginx bash -c nginx

  当然,更加经常使用的调试手段,就是观察容器的输出日志:

1
$ kubectl logs nginx

Pod 的健康检查

  默认情况下,Kubernetes 会开启进程的健康检查:如果 Kubernetes 检测到容器里面的进程退出了,那么它就会重启这个容器。
  只有进程级别的健康检查是远远不够的,设想一下,假设程序发生死锁了,此时进程仍然在运行,但是它已经不能正确输出响应了。为解决这个问题,还需要在应用层实现健康检查。
  Kubernetes 提供了三种类型的健康检查:

  • HTTP 健康检查:向容器的 HTTP 接口发起 HTTP 请求,如果返回的状态码在 [200, 400) 之间,就认为请求是成功的。
  • TCP 健康检查:与容器的某个 port 建立 TCP 连接,如果连接建立成功,则说明容器是健康的。
  • Exec 健康检查:在容器里面执行一个命令,如果返回 0 则认为是成功的,否则失败。

  需要注意的是,健康检查是容器级别的,而不是 Pod 级别的。这意味着一个 Pod 里面的每个容器,都需要配置不同的健康检查规则。
  下面的例子使用的是 HTTP 健康检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
livenessProbe:
httpGet:
path: /index.html
port: 80
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 3
ports:
- containerPort: 80

  下面解释一下各项配置的意义:

  • initialDelaySeconds: 5:使用 5 秒用来等待 Pod 的初始化。
  • timeoutSeconds: 1:为 HTTP 请求设置超时时间为 1 秒。
  • periodSeconds: 10:每 10 秒执行一次健康检查。
  • failureThreshold: 3:设置失败的阈值,如果超过 3 次健康检查都失败了,容器就会被重启。

Pod 的资源限制

  在创建 Pod 的时候,Kubernetes 会将 Pod 调度到某台宿主机上。然而有的宿主机比较繁忙,有的则比较空闲。为了提高 Kubernetes 对资源的利用率(例如 CPU 或内存),我们可以告诉 Kubernetes,这个 Pod 需要使用多少资源,从而协助 Kubernetes 优化调度策略。
  由于每个容器对资源的需求是不同的,所以资源限制需要具体到某个容器。譬如说,一个 Pod 包括容器 A 和 B,容器 A 至少需要 1GB 内存,容器 B 至少需要 2GB 内存,那么整个 Pod 就至少需要 3 GB 内存。
  有两种类型的资源限制:

  • requests:用来指定容器至少需要多少资源。
  • limits:用来限制容器最多可以使用多少资源。

  下面的例子中,Nginx 容器至少需要 0.5 个 CPU 核心以及 128 MB 内存,最多消耗 1 个 CPU 核心以及 256 MB 内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
resources:
requests:
cpu: "500m"
memory: "128Mi"
limits:
cpu: "1000m"
memory: "256Mi"
ports:
- containerPort: 80

Pod 的 Volume

  默认情况下,容器内的文件是暂时性存在的,当容器重启的时候,文件也会消失。在这种情况下,容器只适合用于运行无状态的程序。但在一些场景下,需要使用到数据持久化:

  • 需要在容器中运行有状态的程序,例如数据库。
  • 需要在一个 Pod 里面的多个容器之间共享文件。

  我们可以通过将 Volume 挂载到 Pod 的文件系统,来解决这些问题。这里先介绍几种常用的 Volume 类型:emptyDirhostPathawsElasticBlockStore
  先介绍emptyDir类型的 Volume。当 Pod 被调度到某台宿主机时,emptyDir类型的 Volume 就会被创建,并且这个 Volume 可以被 Pod 里面的多个容器共享。一旦 Pod 离开了这个宿主机,emptyDir中的数据会被永久删除。需要注意的是,如果 Pod 里面的容器 crash 并重启了,这并不会导致 Pod 离开了这个宿主机,所以不会导致emptyDir的数据被删除。
  hostPath类型的 Volume,用来将宿主机的目录挂载到 Pod 的文件系统,这样在容器内就能访问宿主机上的某个目录。一旦 Pod 离开了这个宿主机,hostPath中的数据还会继续存在于宿主机,但数据不会跟随 Pod 迁移到另一个宿主机。
  awsElasticBlockStore类型的 Volume,用来将 AWS EBS 磁盘挂载到 Pod 的文件系统。由于 EBS 是云磁盘,当 Pod 被调度到另一个宿主机的之后,可以保证数据卷仍然可用。

ConfigMaps

  在实践中,应用程序的配置文件通常不会放在镜像中,这有助于让镜像变得可复用。Kubernetes 提供了 ConfigMaps,用来为容器中的程序提供配置信息,包括:文件配置、环境变量以及命令行参数。
  假设有这样一个需求:Nginx 运行在容器中,我们需要修改 Nginx 的配置文件/etc/nginx/nginx.conf,并且在/etc/nginx/sites-enabled目录下增加一个域名的配置mydomain.conf
  首先,在本地目录config下创建配置文件,nginx.confmydomain.conf,内容如下:

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
$ cat config/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 300000;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
include mime.types;
default_type application/octet-stream;
include /etc/nginx/sites-enabled/*;
}
$ cat config/mydomain.conf
server {
listen 80;
server_name mydomain.com;
location / {
default_type text/plain;
return 200 'Hello, World!';
}
}

  接着,使用kubectl创建一个名为nginx-config的 ConfigMap:

1
2
$ kubectl create configmap nginx-config --from-file=config
configmap "nginx-config" created

  创建 ConfigMap 之后,可以查看它的内容:

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
$ kubectl get configmap nginx-config -o yaml
apiVersion: v1
data:
mydomain.conf: |
server {
listen 80;
server_name mydomain.com;
location / {
default_type text/plain;
return 200 'Hello, World!';
}
}
nginx.conf: |
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 300000;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
include mime.types;
default_type application/octet-stream;
include /etc/nginx/sites-enabled/*;
}
kind: ConfigMap
metadata:
creationTimestamp: 2017-11-30T07:32:25Z
name: nginx-config
namespace: default
resourceVersion: "723275"
selfLink: /api/v1/namespaces/default/configmaps/nginx-config
uid: 9c748e39-d5a0-11e7-afd1-027b8dfb02f0

  从上面的内容可以发现,ConfigMap 的data字段包含了多个键值对(key/value),其中 key 是文件名,而 value 则是文件的内容。


  最后要做的就是修改 Pod 的配置:

  1. 在 Pod 里面创建一个名为config-volume的 Volume(实际上这个 Volume 包含了nginx.confmydomain.conf)。
  2. 分别将这两个文件挂载到容器文件系统的不同路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat pod-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf # 这里说明只挂载 nginx.conf
- name: config-volume
mountPath: /etc/nginx/sites-enabled/mydomain.conf
subPath: mydomain.conf # 这里说明只挂载 mydomain.conf
volumes:
- name: config-volume # 这个 Volume 包含 nginx.conf 和 mydomain.conf
configMap:
name: nginx-config

  运行这个 Pod 并进入容器,可以发现配置文件挂载进去了:

1
2
3
4
$ kubectl apply -f pod-nginx.yaml
$ kubectl exec -it nginx bash
root@nginx:/# ls /etc/nginx/sites-enabled/mydomain.conf
/etc/nginx/sites-enabled/mydomain.conf

ReplicaSets

  对于无状态的程序来说,有时候我们想要运行程序的多个副本,以达到这些目的:

  • 提供冗余,在一定程序上提升程序的可用性。
  • 提升程序处理请求的能力。
  • 在必要时候,根据系统负载自动缩容或扩容。

  Kubernetes 提供了 ReplicaSets(副本集)用来解决这些问题。下面的例子是一个 ReplicaSet 的配置,其中包含两个 Nginx Pod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: nginx-set
spec:
replicas: 2 # 创建两个 Pod 的副本
selector:
matchLabels:
app: web-server
template: # Pod 的模板
metadata:
labels:
app: web-server # 为 Pod 打上标签,标签的 Key 是 app,value 是 web-server
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80

  接下来创建这个 ReplicaSet:

1
$ kubectl apply -f rs-nginx.yaml

  可以查看 ReplicaSet 的状态,可以看到有两个 Pod 在运行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl describe rs nginx-set
Name: nginx-set
Namespace: default
Image(s): nginx:1.7.9
Selector: app=web-server
Labels: app=web-server
Replicas: 2 current / 2 desired
Pods Status: 2 Running / 0 Waiting / 0 Succeeded / 0 Failed
No volumes.
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
11m 11m 1 {replicaset-controller } Normal SuccessfulCreate Created pod: nginx-set-25lr7
11m 11m 1 {replicaset-controller } Normal SuccessfulCreate Created pod: nginx-set-3tcqn

  如果想要改变 ReplicaSet 中 Pod 的数量要怎么做呢?可以修改 yaml 文件中 replicas 的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: nginx-set
spec:
replicas: 4 # 原来是 2,现在改成 4,会新增 2 个副本
selector:
matchLabels:
app: web-server
template:
metadata:
labels:
app: web-server
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80

  更新 ReplicaSet,让配置生效:

1
$ kubectl apply -f rs-nginx.yaml

  可以使用kubectl delete删除一个 ReplicaSet,删除之后,由这个 ReplicaSet 管理的 Pod 也会被一并删除:

1
$ kubectl delete rs nginx-set

参考资料