转载请注明出处:https://hts0000.github.io/
欢迎与我联系:hts_0000@sina.com
Kubernetes架构
K8S使用CS架构,也就是Server-Client架构。客户端通过WebUI、CLI等工具与Kubernetes Master连接下达命令,Master再将这些命令下发给对应Node进行执行。

K8S Master包含四个核心组件:
API Server:处理API请求,Kubernetes中所有组件都会与API Server进行连接
Controller:进行集群状态管理,比如容器故障恢复、自动水平扩容等
Scheduler:进行调度管理,比如观察和计算节点与容器负载,决定将容器调度到那个节点
ETCD:分布式存储系统,存储Kubernetes集群所需的元信息

K8S Node是真正运行业务负载的地方,需要运行的容器会经过Scheduler计算决定运行在哪个Node上,计算完之后通知API Server,API Server通知对应Node上的Kubelet组件运行对应的容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
|
Kubernetes元信息
Labels
标签是Kubernetes中最重要的元数据,它使用Key:Value Pair的方式来标识资源对象,以便后续Selector能够筛选和组合资源。Selector支持与或非、集合的的逻辑组合来筛选资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 给资源打上标签,<resource>表示资源的类型,<resource-name>表示具体资源名称
kubectl label <resource> <resource-name> key=value
# 重写已有标签
kubectl label <resource> <resource-name> key=value --overwrite
# 查看资源标签
kubectl get <resource> --show-labels
# 给资源删除标签,key后面加 '-' 表示删除改标签
kubectl label <resource> <resource-name> key-
# 筛选资源时指定标签
kubectl get <resource> --show-labels -l 'key=value'
kubectl get <resource> --show-labels -l 'key in (value1, value2)'
|
Annotation
注解用于记录资源的非标示性信息,扩展资源的描述。
1
2
3
4
5
6
7
8
|
# 给资源加上注解,<resource>表示资源的类型,<resource-name>表示具体资源名称
kubectl annotate <resource> <resource-name> describe='some describe for resource.'
# 查看资源注解信息,在annotation中可以看到加上的注解
kubectl get <resource> <resource-name> -o yaml
# kubectl工具apply文件创建资源时,会创建一个独特的annotation叫:kubectl.kubernetes.io/last-configuration,里面记录了apply是文件的内容,是一个json串
kubectl get deployments nginx -o yaml | grep -A 5 annotations
|
OwnerReference
所有者用于标识资源的所有者/创建者。比如有些资源是一个集合,比如Pod集合会由replicaset或statefulset创建,而想要删除整个集合资源时,就可以用到OwnerReference来找到属于同一集合的资源进行统一删除。
1
2
|
# Deployment类型的资源是Replicaset更高级的封装,因此查看Replicaset类型的资源也可以看到Deployment的资源,因此也可以看到Deployment资源的OwnerReference
kubectl get replicasets nginx -o yaml
|
控制器模式
控制器模式指的是在一个控制循环中,通过控制器、被控系统及观测传感器这三个逻辑组件,驱使被控系统达到期望状态。
Kubernetes中的声明式API就是声明资源期望达到的状态(spec),通过控制模式不断地将当前状态(status)向期望状态逼近。这在资源扩容或故障恢复时,有明显的逻辑优势。在扩容时,声明式API理解的是当前状态与期望状态不一致,为了达到状态一致而触发扩容,而命令式API理解的是收到一条命令,该命令要求扩容机器。
这两种逻辑在出错后重试时,会产生巨大的差异。试想一下扩容成功了,但是扩容程序认为自己失败了,会怎么样?声明式API重试时,会观测到当前状态与期望状态其实已经一致了,从而停止扩容,就算这个时间错开,扩容了两次,在下个观测周期到来,传感器依然会发现状态不一致而触发缩容,这种设计下当前状态总是同期望状态一起变化的。
命令式API如果发生两次扩容,系统很难理解和避免这种错误,特别是期望状态不断变化时,程序必须非常小心的处理每一个可能发生错误的地方,加上大量的判断和回滚,这严重影响性能和故障恢复时间,而且也无法完全保证多次故障发生后,状态仍然与期望的一致。究其根源在于命令式API只有一次任务的过程,如果该过程发生错误,必须通过强一致的事务来保证回滚,然后再继续尝试。

Kubernetes Schedule
优先级
priorityclass和priority
污点
NoSchedule和NoExecute区别:
- NoSchedule:尽量不将Pod调度到该Node上,已在该Node上的Pod不受影响
- NoExecute:除非Pod容忍该污点,否则不会调度到该Node上,已在该Node上没有容忍污点的Pod会被驱逐
1
2
3
4
5
6
7
8
9
10
11
|
spec:
...
template:
...
spec:
tolerations:
- effect: NoSchedule
key: app-name
operator: Equal
value: aaa-job
...
|
亲和性
分为node亲和性、pod亲和性和pod反亲和性。
node亲和性——nodeAffinity:类似nodeSelector,限制pod尽量或必须调度到匹配的node上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
spec:
...
template:
metadata:
labels:
app: nginx-hts0000
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- cn-shenzhen.10.77.89.38
weight: 100
...
|
pod亲和性——podAffinity:
pod反亲和性——podAntiAffinity:
Kubernetes中的资源
Deployment
Deployment是对Replicaset的高级封装,Replicaset控制Pod的副本数量,Deployment负责控制使用哪个Replicaset。创建或修改Deployment就会新创建一个Replicaset,每个Replicaset都记录了管理的Pod的副本、容器等等信息。Deployment回滚就是回滚使用历史版本的Replicaset信息来创建Pod。
Deployment资源文件模板
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
|
apiVersion: app/v1
kind: Deployment
# 声明资源元信息:名称、标签、注解、命名空间等
metadata:
name: nginx-deployment
labels:
app: nginx
# 声明资源期望的状态
spec:
# 具体配置需要根据资源类型进行配置
# 期望Pod数量
replicas: 3
# Pod的选择器
selector:
# 配置标签匹配
matchLabels:
app: nginx
# 每个Pod的模板
template:
# Pod元信息
metadata:
labels:
app: nginx
# Pod期望状态
spec:
# Pod中的containers
containers:
- name: nginx
image: nginx:1.18
ports:
- containerPort: 80
|
查看Deployment资源的字段解释
1
2
3
4
5
6
7
8
9
|
# 查看Deployment资源
# READY:就绪Pod个数
# UP-TO-DATE:达到最新版本Pod个数
# AVAILABLE:可以Pod个数
kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-depoloyment 4/4 4 4 23h
nginx-depoloyment-2 2/2 2 2 22h
web 3/3 3 3 12d
|
更新Deployment资源Pod的镜像
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 使用kubectl工具更新镜像
# deployment.v1.apps:指定资源类型和资源组,可以忽略
# nginx-deployment:资源名称
# nginx=nginx:1.19.1:nginx指的是Pod中具体哪一个container,以及修改成什么镜像
kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.19.1
# 也可以修改资源的yaml文件,重新apply来修改
# 具体操作为打印资源的yaml格式配置到文件中,修改指定容器的镜像版本,重新apply
kubectl get deployments nginx-deployment -o yaml > deployment-update.yaml
kubectl apply -f deployment-update.yaml
# 其实每一次变更,Kubernetes都会保留历史的信息,方便回滚,默认会保留10次的变更历史
kubectl get deployments nginx-deployment -o yaml | grep -A revisionHistoryLimit
# 因为Deployment是对Replicaset的高级封装,多次修改Deployment会保留多份Replicaset
# 因为保存了历史信息,所以可以很快回退
# 回退时会设置当前版本的Replicaset副本数为0,增加回退版本Replicaset副本数,当然会控制两边达到一个平滑过渡。
|
回滚操作
1
2
3
4
5
6
7
8
|
# 查看历史版本
kubectl rollout history deployments.apps/nginx-deployment
# 回退上一版本
kubectl rollout undo deployments.apps/nginx-deployment
# 回退指定版本
kubectl rollout undo deployments.apps/nginx-deployment --to-revision=2
|
Deployment状态流转图
每一个资源都有其对应的当前状态(status)。

spec字段解释
- MinReadySeconds:Deployment 会根据 Pod ready 来看 Pod 是否可用,但是如果我们设置了 MinReadySeconds 之后,比如设置为 30 秒,那 Deployment 就一定会等到 Pod ready 超过 30 秒之后才认为 Pod 是 available 的。Pod available 的前提条件是 Pod ready,但是 ready 的 Pod 不一定是 available 的,它一定要超过 MinReadySeconds 之后,才会判断为 available
- revisionHistoryLimit:保留历史 revision,即保留历史 ReplicaSet 的数量,默认值为 10 个。这里可以设置为一个或两个,如果回滚可能性比较大的话,可以设置数量超过 10
- paused:paused 是标识,Deployment 只做数量维持,不做新的发布,这里在 Debug 场景可能会用到
- progressDeadlineSeconds:前面提到当 Deployment 处于扩容或者发布状态时,它的 condition 会处于一个 processing 的状态,processing 可以设置一个超时时间。如果超过超时时间还处于 processing,那么 controller 将认为这个 Pod 会进入 failed 的状态
升级策略字段解析
- MaxUnavailable:滚动过程中最多百分之几 Pod 不可用
- MaxSurge:滚动过程中最多存在百分之几 Pod 超过预期 replicas 数量
Deployment发布策略
- 灰度发布:按百分比逐步升级,阶段性
- 滚动发布:持续完成升级,每次升级部分
- 蓝绿发布:分组升级
Service
Service用于为一组相同的资源提供统一的访问入口,比如一个Depolyment中有十个Pod副本,每个Pod提供的应用是一样的,希望对用户暴露一个统一的入口,Service就是用来干这个的。
Service配置文件中,使用selector.matchLabels来匹配一组Pod,为这组Pod提供一个统一入口。
Service通过type来配置类型,支持的类型及其功能:
- ClusterIP:默认值,将
Service暴露在Service网段上,只能集群内部访问
- NodePort:将
Service暴露在Node网段上,可以通过Node IP来访问
- LoadBalancer:外接云厂商产品来做负载均衡
配置文件模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
apiVersion: v1
kind: Service
metadata:
name: hello-world-service
labels:
app: hello-world
spec:
selector:
matchLabels: # 根据标签选中所有匹配到的pod
app: hello-world
# clusterIp: 10.110.211.92 # 指定service网段的ip,不知道就从service网段中获取一个可用的
type: ClusterIP # Service的类型
ports:
- protocol: TCP
port: 8080 # 暴露到集群内部的端口
targetPort: 80 # 对应pod暴露的端口,从集群内部或外部访问的请求最终会请求这个端口
nodePort: 80 # 暴露到集群外部的端口
|
查看Service资源字段解释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
kubectl describe services
Name: demo-deployment
Namespace: demo
Labels: app=demo
Annotations: <none>
Selector: app=demo
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.110.211.92
IPs: 10.110.211.92
Port: <unset> 18080/TCP
TargetPort: 18080/TCP
NodePort: <unset> 32581/TCP
# Endpoints:通过标签匹配到的所有Pod
Endpoints: 10.244.169.162:18080,10.244.169.164:18080,10.244.36.77:18080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
|
值得一提的是,Service的type为NodePort时,无论访问哪台Node的这个端口,都能访问,但是查看开放的端口,又没有看到监听。其实这是Kubernetes的网络插件来管理的,通常情况下时直接修改iptables,在内核层面完成转发。
Job
Job就是一个任务,对于资源文件指定的任务启动一个对应的Pod执行,如果该任务要求执行10次,而且并发执行,那么将会创建10个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
|
apiVersion: batch/v1
kind: Jon
metadata:
name: hello-world
labels:
type: bash
# 描述Job
spec:
# 该Job需要执行多少次
completions: 10
# 并发执行的Pod数
parallelism: 2
# 重试次数
backoffLimit: 4
# 描述执行Job的Pod
template:
spec:
# 有三种策略
# Never:
# OnFailure:
# Always:
restartPolicy: Never
conatiners:
- name: hello-world
image: busybox
command: ["sh", "-c", "echo hello world && sleep 60"]
|
查看Job资源字段解释
1
2
3
4
5
6
7
|
# COMPLETIONS:一共几次任务,完成了几次
# DURATION:Pod中业务运行具体时长
# AGE:
kubectl get jobs
NAME COMPLETIONS DURATION AGE
hello-world 1/1 80s 33m
hello-world-parallelism 8/8 5m20s 6m44s
|
查看Job执行日志
CronJob
CronJob与Job类似,增加了定时执行功能,使用习惯和语法与Linux的crontab类似。
配置文件模板
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
|
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-world-cron
labels:
type: bash
spec:
schedule: "*/1 * * * *"
# 超过指定时间Job未启动,就停止这个Job
startingDeadlineSecond: 10
# 是否允许并行运行
concurrencyPolicy: Allow
# 允许留存历史Job个数
successfulJobsHistoryLimit: 10
jobTemplate:
spec:
template:
spec:
# 有三种策略
# Never:
# OnFailure:
# Always:
restartPolicy: OnFailure
containers:
- name: hello-world-cron
image: busybox
command:
- /bin/sh
- -c
- echo hello world from CronJob; sleep 60
|
DaemonSet
DaemonSet叫做守护进程控制器,主要作用如下:
- 保证每一个
Node上面运行同一个Pod
- 新增或移除
Node时能动态感知到并自动添加或删除
- 跟踪每一个
Pod的状态,在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
|
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
# 这些容忍度设置是为了让该守护进程集在控制平面节点上运行
# 如果你不希望自己的控制平面节点运行 Pod,可以删除它们
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
|
Ingress
ingress需要额外安装,通常安装ingress-nginx
配置文件模板
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
|
# 快速输出一个模板
kubectl create ingress test-ingress -n default --class=nginx --rule=test.domain.com/path1*=test1-svc:80 --rule=test.domain.com/path2*=test2-svc:80 --dry-run=client -o yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
creationTimestamp: null
name: test-ingress
namespace: default
spec:
ingressClassName: nginx
rules:
- host: test.domain.com
http:
paths:
- backend:
service:
name: test1-svc
port:
number: 80
path: /path1
pathType: Prefix
- backend:
service:
name: test2-svc
port:
number: 80
path: /path2
pathType: Prefix
status:
loadBalancer: {}
|
通过注解的方式,可以动态修改nginx ingress的配置,比如,加上注解:nginx.ingress.kubernetes.io/proxy-read-timeout: "300s",可以动态修改连接读超时的时间。
Ingress的访问控制
nginx ingress可以通过注解的方式来控制nginx的配置,因此我们可以使用xxx注解,来实现配置allow和deny。
Nginx Ingress配置跨域
需要加上如下注解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true" # 启用CORS。
nginx.ingress.kubernetes.io/cors-allow-origin: "*" # 允许所有域访问。
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS" # 允许的HTTP方法。
# 允许的自定义请求头。
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range"
nginx.ingress.kubernetes.io/cors-expose-headers: "Content-Length,Content-Range" # 暴露的响应头。
nginx.ingress.kubernetes.io/cors-max-age: "86400" # 预检请求缓存时间。
...
|
坑
nginx.ingress.kubernetes.io/cors-allow-headers: *不意味着允许所有header,而是预设了一组默认的header,参考文档。
不在预设之外的header,仍然需要单独手动加上,不然依然会有跨域问题。
Network Policy
网络策略用于限制同集群内同namespace、同集群内跨namespace、跨集群的Pod之间的通信。
可视化的网络策略编辑器:https://editor.networkpolicy.io/?id=8izZAPuYL6eFiitL
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
|
# networkpolicy1
# restricts all Pods in Namespace space1 to only have outgoing traffic to Pods in Namespace space2.
# incoming traffic not affected.
# and also allow outgoing dns traffic on port 53 TCP and UDP.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: np
namespace: space1
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: space2
# networkpolicy2
# restricts all Pods in Namespace space2 to only have incoming traffic from Pods in Namespace space1.
# outgoing traffic not affected.
# and also allow outgoing dns traffic on port 53 TCP and UDP.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: np
namespace: space2
spec:
podSelector: {}
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: space1
egress:
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
# 获取networkpolicy
kubectl get networkpolicy --all-namespaces
|
RBAC
ClusterRole/Role定义了一组权限,以及这组权限在哪些地方可用(整个集群或单个namespace)。
ClusterRoleBinding/RoleBinding将一组权限与Account联系起来,并定义它的应用位置(整个集群或单个namespace)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 创建sa
kubectl create serviceaccount my-sa -n ns-1
kubectl create serviceaccount my-sa -n ns-2
# 创建clusterrolebinding
kubectl create clusterrolebinding my-sa-view --clusterrole view --serviceaccount ns1:my-sa --serviceaccount ns2:my-sa
# 查看clusterrolebinding
kubectl get clusterrolebinding my-sa-view -o wide
# 创建clusterrole
kubectl create clusterrole my-clusterrole --verb=create,delete --resource deployments
k -n ns1 create rolebinding pipeline-deployment-manager --clusterrole pipeline-deployment-manager --serviceaccount ns1:pipeline
k -n ns2 create rolebinding pipeline-deployment-manager --clusterrole pipeline-deployment-manager --serviceaccount ns2:pipeline
# 检查当前账号权限
kubectl auth can-i delete deployments --as system:serviceaccount:ns1:pipeline -n ns1
|
PriorityClass
PriorityClass是Kubernetes中的一种资源,用于定义pod的优先级。PriorityClass可以被分配给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
|
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: level4
preemptionPolicy: PreemptLowerPriority
value: 400000000
---
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: important
name: important
namespace: lion
spec:
priorityClassName: level4
containers:
- image: nginx:1.21.6-alpine
name: important
resources:
requests:
memory: 1Gi
dnsPolicy: ClusterFirst
restartPolicy: Always
|
Kubernetes应用配置管理
首先看一下需求来源。大家应该都有过这样的经验,就是用一个容器镜像来启动一个container。要启动这个容器,其实有很多需要配套的问题待解决:
- 第一,比如说一些可变的配置。因为我们不可能把一些可变的配置写到镜像里面,当这个配置需要变化的时候,可能需要我们重新编译一次镜像,这个肯定是不能接受的;
- 第二就是一些敏感信息的存储和使用。比如说应用需要使用一些密码,或者用一些 token;
- 第三就是我们容器要访问集群自身。比如我要访问 kube-apiserver,那么本身就有一个身份认证的问题;
- 第四就是容器在节点上运行之后,它的资源需求;
- 第五个就是容器在节点上,它们是共享内核的,那么它的一个安全管控怎么办?
- 最后一点我们说一下容器启动之前的一个前置条件检验。比如说,一个容器启动之前,我可能要确认一下 DNS 服务是不是好用?又或者确认一下网络是不是联通的?那么这些其实就是一些前置的校验。
Kubernetes中对这些配置进行管理,有配套的工具和资源:
- 可变配置就用 ConfigMap;
- 敏感信息是用 Secret;
- 身份认证是用 ServiceAccount 这几个独立的资源来实现的;
- 资源配置是用 Resources;
- 安全管控是用 SecurityContext;
- 前置校验是用 InitContainers 这几个在 spec 里面加的字段,来实现的这些配置管理。

ConfigMap
存储Pod的配置,用于实现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
|
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-world-config
labels:
hello: world
# 具体的配置以key:value的方式存储
data:
# 简单的标量类型key:value形式
MAX_CONNECTION: "10"
FILE_NAME: hello_world
player: hts0000
# 文件名和内容形式的key:value
hello-world-config.json: |
{
"name": "hts0000",
"age": 10,
"sex": boy
}
# 类文件键
game.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
|
通过命令行创建
1
|
kubectl create configmap hello-config --from-literal=hello=world --from-literal=MaxConnection=10
|
使用ConfigMap
可以在Deployment等资源中使用ConfigMap,下面以Deployment为例,打印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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-deployment
labels:
hello: world
spec:
replicas: 3
selector:
matchLabels:
hello: world
template:
metadata:
labels:
hello: world
spec:
volumes: # 将ConfigMap作为卷,Pod内就可以将这个卷挂载到容器内部
- name: hello-world-config-volumes
configMap:
name: hello-world-config # ConfigMap的名字
items: # 来自ConfigMap的一组键,将被创建为文件
- key: hello-world-config.json
path: hello-world-config.json
- key: game.properties
path: game.properties
- key: user-interface.properties
path: user-interface.properties
containers:
- name: say-hello
image: busybox
env: # 从hello-world-config中获取配置,写入容器的环境变量
- name: MAX_CONNECTION # 这个名字可以和ConfigMap中不同
valueFrom:
configMapKeyRef: # value的来源为ConfigMap
name: hello-world-config
key: MAX_CONNECTION
- name: FILE_NAME
valueFrom:
configMapKeyRef:
name: hello-world-config
key: FILE_NAME
- name: MY_NAME
valueFrom:
configMapKeyRef:
name: hello-world-config
key: player
volumeMounts: # 将hello-world-config-volumes挂载到容器内部
- name: hello-world-config-volumes
mountPath: "/config"
readOnly: true
command: # 打印上面加载进来的环境变量和文件
- /bin/sh
- -c
- echo hello world; echo MAX_CONNECTION $(MAX_CONNECTION); \
echo FILE_NAME $(FILE_NAME); echo player $(MY_NAME); \
echo '########################################'; \
cat /config/hello-world-config.json; \
echo '########################################'; \
cat /config/game.properties; \
echo '########################################'; \
cat /config/user-interface.properties; \
echo '########################################'; \
sleep 10000000000
|
Secret
Secret是一个主要用来存储密码token等一些敏感信息的资源对象,里面的敏感信息是采用 base-64编码保存起来的。
配置文件模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
apiVersion: v1
kind: Secret
metadata:
name: mysecret
namespace: default
# 指定Secret的一个类型
# Opaque:用户定义的任意数据
# kubernetes.io/service-account-token:服务账号令牌
# kubernetes.io/dockercfg:~/.dockercfg文件的序列化形式
# kubernetes.io/dockerconfigjson:~/.docker/config.json文件的序列化形式
# kubernetes.io/basic-auth:用于基本身份认证的凭据
# kubernetes.io/ssh-auth:用于 SSH 身份认证的凭据
# kubernetes.io/tls:用于 TLS 客户端或者服务器端的数据
# bootstrap.kubernetes.io/token:启动引导令牌数据
type: Opaque
data:
aHRzMDAwMAo=: MTIzNDU2Cg== # 敏感信息经过base64加密后存储在Secret中
|
ServiceAccount
ServiceAccount用于解决Pod在集群里面的身份认证问题,ServiceAccount会去关联一个底层的Secret,也就是说身份认证信息实际存储于Secret里面。
Resources
Resources用于配置容器使用的资源,比如配置使用的CPU时间、内存大小、硬盘大小或GPU算力等,也支持自定义实现资源限制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
requests:
cpu: "250m"
memory: "100Mi"
limits:
cpu: "300m"
memory: "200Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
|
SecurityContext
SecurityContext主要是用于限制容器的一个行为,它能保证系统和其他容器的安全。
InitContainers
InitContainer会在普通的Container之前启动,如果定义了多个InitContainer,那么他们会按定义顺序依次启动。InitContainer常用与为普通的Container进行初始化。
实践
本实践通过helm在Kubernetes上部署Prometheus,采集自定义的Demo应用的指标数据,并通过Grafana展示出来。
实践使用到了Kubernetes的Deployment、ConfigMap、服务发现、Service等一系列能力,结合Prometheus和Grafana实现监控系统的搭建。
自定义Exporter
根据Prometheus提供的SDK,实现一个可以向外暴露Metrics指标的应用。
官方教程:https://prometheus.io/docs/guides/go-application/
实现demo:https://github.com/hts0000/exporter-demo
打包成Docker镜像
使用以下Dockerfile文件将Demo打包成镜像。打包是使用多阶段编译的方式减小镜像的体积。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
FROM golang:1.20.3-alpine AS builder
RUN go env -w GO111MODULE=on
RUN go env -w GOPROXY=https://goproxy.cn,direct
COPY . /go/src/exporter-demo
WORKDIR /go/src/exporter-demo
RUN go build -o exporter-demo .
FROM alpine
COPY --from=builder /go/src/exporter-demo/exporter-demo /bin/exporter-demo
EXPOSE 18080
ENTRYPOINT [ "/bin/exporter-demo" ]
|
执行docker build -t demo:v1 .命令进行打包。
将Demo在K8S上运行起来
以Deployment的方式,运行三个Demo副本,同时使用ConfigMap定义副本将会使用到的配置。
demo-deployment.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
33
34
35
36
37
38
39
40
41
42
|
# 副本使用到的配置存储在demo-configmap这个ConfigMap里
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-configmap
namespace: demo
data:
ADDR: ':18080'
---
# demo副本信息
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
labels:
app: demo
namespace: demo
spec:
replicas: 3
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
annotation:
desc: 'simple app, expose metric from :18080/metrics url'
spec:
containers:
- name: demo
image: demo:v1
ports:
- containerPort: 18080 # 定义程序向外暴露的端口,需要跟ConfigMap中的配置对应
env:
- name: ADDR # 程序会读取ADDR环境变量获取程序启动监听地址
valueFrom:
configMapKeyRef:
name: demo-configmap # 此处为ConfigMap的名称
key: ADDR
|
镜像仓库认证
如果镜像仓库配置了需要认证,可以配置一个Secret资源,记录认证的用户名和密码。
1
2
3
4
5
6
7
8
9
|
apiVersion: v1
kind: Secret
metadata:
name: mysecret
namespace: default
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: >-
eyJhdXRocyI6eyJtaW5pc28tcmVnaXN0cnktdnBjLmNuLXNoZW56aGVuLmNyLmFsaXl1bmNzLmNvbSI6eyJ1c2VybmFtZSI6ImRvY2tlci1yb0BtaW5pc28xIiwicGFzc3dvcmQiOiJWM2liZEZ3a2xNMnlQMzF6IiwiYXV0aCI6IlpHOWphMlZ5TFhKdlFHMXBibWx6YnpFNlZqTnBZbVJHZDJ0c1RUSjVVRE14ZWc9PSJ9fX0=
|
然后在Deployment中加上imagePullSecrets配置项,引用Secret的配置,即可完成镜像仓库的认证。
1
2
3
4
5
6
7
8
9
10
|
apiVersion: apps/v1
kind: Deployment
...
spec:
...
spec:
# 配置拉取使用的Secret
imagePullSecrets:
- name: miniso-repo-acr
...
|
执行kubectl apply -f demo-deployment.yaml命令,创建Deployment。
执行kubectl get deployments -n demo查看Deployments信息。-n选项指定命名空间,因为我们将demo-deployment创建在demo这个命名空间里的。

执行kubectl get pods -n demo查看Pods信息。

确保Pods已经正常运行。
使用Helm安装Prometheus、Altermanager和Grafana
Helm是Kubernetes的包管理工具,可以方便的在Kubernetes上部署软件。
如果我们要手动部署Prometheus到Kubernetes上可能需要如下步骤:
- 使用
Prometheus官方镜像包甚至自行给Prometheus打包
- 根据产品特性,选择部署资源。比如单实例的部署为
Pod,集群的部署为Deployment或DaemonSet等等
- 抽离其中可定制化的配置,比如监听地址、采集间隔、监控服务器信息等等,根据
Kubernetes的最佳实践,这些配置最好写到ConfigMap中去
- 还需要考虑访问权限和安全性的问题,如果不希望其他用户访问
Prometheus这个资源,还要单独配置ServiceAccount等
- …
需要做的事情很多,而且要按照最佳实践来部署,有很高的学习成本。因此Helm就是来解放这一过程的,用户只需要一行命令,就可以安装官方提供的Chart(Helm里一个包称之为Chart),官方提供的无疑是最贴合最佳实践的,用户只需要简单的配置,即可在Kubernetes上启动一个实例,甚至一个集群。
Helm官方文档:https://helm.sh/zh/docs/
需要注意的是,Helm与Kubernetes有版本支持的要求,不同版本的Helm支持的Kubernetes版本参考文档:https://helm.sh/zh/docs/topics/version_skew/。

首先安装Helm很简单,下载下来就是个二进制包,放到/usr/bin目录即可。下载地址:https://github.com/helm/helm/releases
前往Helm Charts Hub查找Prometheus Chart。
Charts Hub地址:https://artifacthub.io/。

一般来说里面的教程会提示增加一个仓库,但是这些仓库下载拉取镜像时,经常会失败,所以我们需要配置Helm的下载仓库,Helm支持配置多个仓库,下载的时候需要加上指定仓库的前缀。
1
2
3
4
5
|
# 添加仓库地址
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add stable http://mirror.azure.cn/kubernetes/charts
helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
helm repo add incubator https://charts.helm.sh/incubator
|
执行helm repo list查看所有仓库。

我们在bitnami仓库和官方提供的prometheus-community仓库分别查找promethues,看看他们的版本差异。

可以看到两个仓库的版本是同步的。为了下载安装顺利,我们选择使用bitnami仓库。
Helm在下载安装Chart时,需要指定仓库作为前缀。
1
2
3
4
5
|
# 在bitnami/prometheus仓库中下载prometheus
helm install prometheus bitnami/prometheus
# 如法炮制下载grafana
helm install grafana bitnami/grafana
|
下载完之后查看,altermanager也安装上了。

暴露Grafana和Prometheus访问地址
默认情况下Grafana和Prometheus的Service Type为ClusterIP,只能通过Service网段访问,也就是只有集群内可访问。我们希望集群外也能访问,就需要将Service Type修改为NodePort,并给资源分配一个nodePort,即Node上的Port。
首先查看所有的Service。

执行kubectl edit services prometheus-server实时修改配置,将type修改为NodePort,增加nodePort配置。grafana也是一样的操作。


然后就可以通过Node IP来访问Grafana和Prometheus了。


Prometheus访问K8S动态发现Pod
现在Prometheus默认只有Prometheus自己和Altermanager这两个采集源。我们增加上demo-deployment中的所有pod。但是pod ip是不固定的,如果pod重启或滚动升级,ip地址会变化。这时就需要将Prometheus配置为主动与K8S读取Pod配置。
Prometheus的配置存储在ConfigMap中,我们先查看所有ConfigMap。

执行kubectl edit configmap prometheus-server命令进行编辑。增加针对demo这个namespace的指标采集。

Prometheus启动后配置就固定了,需要重启,我们可以把prometheus这个pod删掉,让Kubernetes Deployment自动帮我们重启。重启之后就能看到三个demo副本的监控信息了。

配置Grafana
在Grafana中配置Prometheus数据源,并展示指标的数据。
配置数据源时,地址就是Prometheus所在Node的地址,以及nodePort配置的端口。

添加一个Panel。

在Options和侧边栏里还可以配置指标和Panel的名称。

访问测试
使用ab工具访问这些Pod,就可以在Grafana中看到指标的变化了。需要注意执行ab命令的机器,必须是Kubernetes集群内的机器,ab访问的地址为Pod在集群内的地址,如果希望集群外访问,可以创建一个Services,通过向外暴露demo-deployment的统一入口。
